ExpandingEntryCardView.java revision 97ed5019d75bd6afdfd5f3a8150161d9d9441275
1/*
2 * Copyright (C) 2014 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16package com.android.contacts.quickcontact;
17
18import android.content.Context;
19import android.content.Intent;
20import android.content.res.Resources;
21import android.graphics.ColorFilter;
22import android.graphics.Rect;
23import android.graphics.drawable.Drawable;
24import android.text.TextUtils;
25import android.transition.ChangeBounds;
26import android.transition.ChangeScroll;
27import android.transition.Fade;
28import android.transition.Transition;
29import android.transition.Transition.TransitionListener;
30import android.transition.TransitionManager;
31import android.transition.TransitionSet;
32import android.util.AttributeSet;
33import android.util.Log;
34import android.view.LayoutInflater;
35import android.view.TouchDelegate;
36import android.view.View;
37import android.view.ViewGroup;
38import android.widget.ImageView;
39import android.widget.LinearLayout;
40import android.widget.RelativeLayout;
41import android.widget.TextView;
42
43import com.android.contacts.R;
44
45import java.util.ArrayList;
46import java.util.List;
47
48/**
49 * Display entries in a LinearLayout that can be expanded to show all entries.
50 */
51public class ExpandingEntryCardView extends LinearLayout {
52
53    private static final String TAG = "ExpandingEntryCardView";
54    private static final int DURATION_EXPAND_ANIMATION_FADE_IN = 200;
55    private static final int DELAY_EXPAND_ANIMATION_FADE_IN = 100;
56
57    public static final int DURATION_EXPAND_ANIMATION_CHANGE_BOUNDS = 300;
58    public static final int DURATION_COLLAPSE_ANIMATION_CHANGE_BOUNDS = 300;
59
60    /**
61     * Entry data.
62     */
63    public static final class Entry {
64
65        private final int mViewId;
66        private final Drawable mIcon;
67        private final String mHeader;
68        private final String mSubHeader;
69        private final Drawable mSubHeaderIcon;
70        private final String mText;
71        private final Drawable mTextIcon;
72        private final Intent mIntent;
73        private final Drawable mAlternateIcon;
74        private final Intent mAlternateIntent;
75        private final String mAlternateContentDescription;
76        private final boolean mShouldApplyColor;
77        private final boolean mIsEditable;
78
79        public Entry(int viewId, Drawable icon, String header, String subHeader, String text,
80                Intent intent, Drawable alternateIcon, Intent alternateIntent,
81                String alternateContentDescription, boolean shouldApplyColor,
82                boolean isEditable) {
83            this(viewId, icon, header, subHeader, null, text, null, intent, alternateIcon,
84                    alternateIntent, alternateContentDescription, shouldApplyColor, isEditable);
85        }
86
87        public Entry(int viewId, Drawable mainIcon, String header, String subHeader,
88                Drawable subHeaderIcon, String text, Drawable textIcon, Intent intent,
89                Drawable alternateIcon, Intent alternateIntent, String alternateContentDescription,
90                boolean shouldApplyColor, boolean isEditable) {
91            mViewId = viewId;
92            mIcon = mainIcon;
93            mHeader = header;
94            mSubHeader = subHeader;
95            mSubHeaderIcon = subHeaderIcon;
96            mText = text;
97            mTextIcon = textIcon;
98            mIntent = intent;
99            mAlternateIcon = alternateIcon;
100            mAlternateIntent = alternateIntent;
101            mAlternateContentDescription = alternateContentDescription;
102            mShouldApplyColor = shouldApplyColor;
103            mIsEditable = isEditable;
104        }
105
106        Drawable getIcon() {
107            return mIcon;
108        }
109
110        String getHeader() {
111            return mHeader;
112        }
113
114        String getSubHeader() {
115            return mSubHeader;
116        }
117
118        Drawable getSubHeaderIcon() {
119            return mSubHeaderIcon;
120        }
121
122        public String getText() {
123            return mText;
124        }
125
126        Drawable getTextIcon() {
127            return mTextIcon;
128        }
129
130        Intent getIntent() {
131            return mIntent;
132        }
133
134        Drawable getAlternateIcon() {
135            return mAlternateIcon;
136        }
137
138        Intent getAlternateIntent() {
139            return mAlternateIntent;
140        }
141
142        String getAlternateContentDescription() {
143            return mAlternateContentDescription;
144        }
145
146        boolean shouldApplyColor() {
147            return mShouldApplyColor;
148        }
149
150        boolean isEditable() {
151            return mIsEditable;
152        }
153
154        int getViewId() {
155            return mViewId;
156        }
157    }
158
159    public interface ExpandingEntryCardViewListener {
160        void onCollapse(int heightDelta);
161        void onExpand(int heightDelta);
162    }
163
164    private View mExpandCollapseButton;
165    private TextView mExpandCollapseTextView;
166    private TextView mTitleTextView;
167    private CharSequence mExpandButtonText;
168    private CharSequence mCollapseButtonText;
169    private OnClickListener mOnClickListener;
170    private boolean mIsExpanded = false;
171    private int mCollapsedEntriesCount;
172    private ExpandingEntryCardViewListener mListener;
173    private List<List<Entry>> mEntries;
174    private int mNumEntries = 0;
175    private boolean mAllEntriesInflated = false;
176    private List<List<View>> mEntryViews;
177    private LinearLayout mEntriesViewGroup;
178    private final Drawable mCollapseArrowDrawable;
179    private final Drawable mExpandArrowDrawable;
180    private int mThemeColor;
181    private ColorFilter mThemeColorFilter;
182    private boolean mIsAlwaysExpanded;
183    /** The ViewGroup to run the expand/collapse animation on */
184    private ViewGroup mAnimationViewGroup;
185    private LinearLayout mBadgeContainer;
186    private final List<ImageView> mBadges;
187
188    private final OnClickListener mExpandCollapseButtonListener = new OnClickListener() {
189        @Override
190        public void onClick(View v) {
191            if (mIsExpanded) {
192                collapse();
193            } else {
194                expand();
195            }
196        }
197    };
198
199    public ExpandingEntryCardView(Context context) {
200        this(context, null);
201    }
202
203    public ExpandingEntryCardView(Context context, AttributeSet attrs) {
204        super(context, attrs);
205        LayoutInflater inflater = LayoutInflater.from(context);
206        View expandingEntryCardView = inflater.inflate(R.layout.expanding_entry_card_view, this);
207        mEntriesViewGroup = (LinearLayout)
208                expandingEntryCardView.findViewById(R.id.content_area_linear_layout);
209        mTitleTextView = (TextView) expandingEntryCardView.findViewById(R.id.title);
210        mCollapseArrowDrawable =
211                getResources().getDrawable(R.drawable.expanding_entry_card_collapse_white_24);
212        mExpandArrowDrawable =
213                getResources().getDrawable(R.drawable.expanding_entry_card_expand_white_24);
214
215        mExpandCollapseButton = inflater.inflate(
216                R.layout.quickcontact_expanding_entry_card_button, this, false);
217        mExpandCollapseTextView = (TextView) mExpandCollapseButton.findViewById(R.id.text);
218        mExpandCollapseButton.setOnClickListener(mExpandCollapseButtonListener);
219        mBadgeContainer = (LinearLayout) mExpandCollapseButton.findViewById(R.id.badge_container);
220
221        mBadges = new ArrayList<ImageView>();
222    }
223
224    /**
225     * Sets the Entry list to display.
226     *
227     * @param entries The Entry list to display.
228     */
229    public void initialize(List<List<Entry>> entries, int numInitialVisibleEntries,
230            boolean isExpanded, boolean isAlwaysExpanded,
231            ExpandingEntryCardViewListener listener, ViewGroup animationViewGroup) {
232        LayoutInflater layoutInflater = LayoutInflater.from(getContext());
233        mIsExpanded = isExpanded;
234        mIsAlwaysExpanded = isAlwaysExpanded;
235        // If isAlwaysExpanded is true, mIsExpanded should be true
236        mIsExpanded |= mIsAlwaysExpanded;
237        mEntryViews = new ArrayList<List<View>>(entries.size());
238        mEntries = entries;
239        mNumEntries = 0;
240        mAllEntriesInflated = false;
241        for (List<Entry> entryList : mEntries) {
242            mNumEntries += entryList.size();
243            mEntryViews.add(new ArrayList<View>());
244        }
245        mCollapsedEntriesCount = Math.min(numInitialVisibleEntries, mNumEntries);
246        // Only show the head of each entry list if the initial visible number falls between the
247        // number of lists and the total number of entries
248        if (mCollapsedEntriesCount > mEntries.size()) {
249            mCollapsedEntriesCount = mEntries.size();
250        }
251        mListener = listener;
252        mAnimationViewGroup = animationViewGroup;
253
254        if (mIsExpanded) {
255            updateExpandCollapseButton(getCollapseButtonText());
256            inflateAllEntries(layoutInflater);
257        } else {
258            updateExpandCollapseButton(getExpandButtonText());
259            inflateInitialEntries(layoutInflater);
260        }
261        insertEntriesIntoViewGroup();
262        applyColor();
263    }
264
265    /**
266     * Sets the text for the expand button.
267     *
268     * @param expandButtonText The expand button text.
269     */
270    public void setExpandButtonText(CharSequence expandButtonText) {
271        mExpandButtonText = expandButtonText;
272        if (mExpandCollapseTextView != null && !mIsExpanded) {
273            mExpandCollapseTextView.setText(expandButtonText);
274        }
275    }
276
277    /**
278     * Sets the text for the expand button.
279     *
280     * @param expandButtonText The expand button text.
281     */
282    public void setCollapseButtonText(CharSequence expandButtonText) {
283        mCollapseButtonText = expandButtonText;
284        if (mExpandCollapseTextView != null && mIsExpanded) {
285            mExpandCollapseTextView.setText(mCollapseButtonText);
286        }
287    }
288
289    @Override
290    public void setOnClickListener(OnClickListener listener) {
291        mOnClickListener = listener;
292    }
293
294    private void insertEntriesIntoViewGroup() {
295        mEntriesViewGroup.removeAllViews();
296
297        if (mIsExpanded) {
298            for (List<View> viewList : mEntryViews) {
299                if (viewList != mEntryViews.get(0)) {
300                    addSeparator(viewList.get(0));
301                }
302                for (View view : viewList) {
303                    addEntry(view);
304                }
305            }
306        } else {
307            for (int i = 0; i < mCollapsedEntriesCount; i++) {
308                if (i > 0) {
309                    addSeparator(mEntryViews.get(i).get(0));
310                }
311                addEntry(mEntryViews.get(i).get(0));
312            }
313        }
314
315        removeView(mExpandCollapseButton);
316        if (mCollapsedEntriesCount < mNumEntries
317                && mExpandCollapseButton.getParent() == null && !mIsAlwaysExpanded) {
318            addView(mExpandCollapseButton, -1);
319        }
320    }
321
322    private void addEntry(View entry) {
323        mEntriesViewGroup.addView(entry);
324    }
325
326    private void addSeparator(View entry) {
327        View separator = new View(getContext());
328        separator.setBackgroundColor(getResources().getColor(
329                R.color.expanding_entry_card_item_separator_color));
330        LayoutParams layoutParams = generateDefaultLayoutParams();
331        Resources resources = getResources();
332        layoutParams.height = resources.getDimensionPixelSize(
333                R.dimen.expanding_entry_card_item_separator_height);
334        // The separator is aligned with the text in the entry. This is offset by a default
335        // margin. If there is an icon present, the icon's width and margin are added
336        int marginStart = resources.getDimensionPixelSize(
337                R.dimen.expanding_entry_card_item_padding_start);
338        ImageView entryIcon = (ImageView) entry.findViewById(R.id.icon);
339        if (entryIcon.getVisibility() == View.VISIBLE) {
340            int imageWidthAndMargin =
341                    resources.getDimensionPixelSize(
342                            R.dimen.expanding_entry_card_item_icon_width) +
343                    resources.getDimensionPixelSize(
344                            R.dimen.expanding_entry_card_item_image_spacing);
345            marginStart += imageWidthAndMargin;
346        }
347        layoutParams.setMarginStart(marginStart);
348        separator.setLayoutParams(layoutParams);
349        mEntriesViewGroup.addView(separator);
350    }
351
352    private CharSequence getExpandButtonText() {
353        if (!TextUtils.isEmpty(mExpandButtonText)) {
354            return mExpandButtonText;
355        } else {
356            // Default to "See more".
357            return getResources().getText(R.string.expanding_entry_card_view_see_more);
358        }
359    }
360
361    private CharSequence getCollapseButtonText() {
362        if (!TextUtils.isEmpty(mCollapseButtonText)) {
363            return mCollapseButtonText;
364        } else {
365            // Default to "See less".
366            return getResources().getText(R.string.expanding_entry_card_view_see_less);
367        }
368    }
369
370    /**
371     * Inflates the initial entries to be shown.
372     */
373    private void inflateInitialEntries(LayoutInflater layoutInflater) {
374        // If the number of collapsed entries equals total entries, inflate all
375        if (mCollapsedEntriesCount == mNumEntries) {
376            inflateAllEntries(layoutInflater);
377        } else {
378            // Otherwise inflate the top entry from each list
379            for (int i = 0; i < mCollapsedEntriesCount; i++) {
380                mEntryViews.get(i).add(createEntryView(layoutInflater, mEntries.get(i).get(0),
381                        /* showIcon = */ View.VISIBLE));
382            }
383        }
384    }
385
386    /**
387     * Inflates all entries.
388     */
389    private void inflateAllEntries(LayoutInflater layoutInflater) {
390        if (mAllEntriesInflated) {
391            return;
392        }
393        for (int i = 0; i < mEntries.size(); i++) {
394            List<Entry> entryList = mEntries.get(i);
395            List<View> viewList = mEntryViews.get(i);
396            for (int j = viewList.size(); j < entryList.size(); j++) {
397                final int iconVisibility;
398                final Entry entry = entryList.get(j);
399                // If the entry does not have an icon, mark gone. Else if it has an icon, show
400                // for the first Entry in the list only
401                if (entry.getIcon() == null) {
402                    iconVisibility = View.GONE;
403                } else if (j == 0) {
404                    iconVisibility = View.VISIBLE;
405                } else {
406                    iconVisibility = View.INVISIBLE;
407                }
408                viewList.add(createEntryView(layoutInflater, entry, iconVisibility));
409            }
410        }
411        mAllEntriesInflated = true;
412    }
413
414    public void setColorAndFilter(int color, ColorFilter colorFilter) {
415        mThemeColor = color;
416        mThemeColorFilter = colorFilter;
417        applyColor();
418    }
419
420    public void setEntryHeaderColor(int color) {
421        if (mEntries != null) {
422            for (List<View> entryList : mEntryViews) {
423                for (View entryView : entryList) {
424                    TextView header = (TextView) entryView.findViewById(R.id.header);
425                    if (header != null) {
426                        header.setTextColor(color);
427                    }
428                }
429            }
430        }
431    }
432
433    /**
434     * The ColorFilter is passed in along with the color so that a new one only needs to be created
435     * once for the entire activity.
436     * 1. Title
437     * 2. Entry icons
438     * 3. Expand/Collapse Text
439     * 4. Expand/Collapse Button
440     */
441    public void applyColor() {
442        if (mThemeColor != 0 && mThemeColorFilter != null) {
443            // Title
444            if (mTitleTextView != null) {
445                mTitleTextView.setTextColor(mThemeColor);
446            }
447
448            // Entry icons
449            if (mEntries != null) {
450                for (List<Entry> entryList : mEntries) {
451                    for (Entry entry : entryList) {
452                        if (entry.shouldApplyColor()) {
453                            Drawable icon = entry.getIcon();
454                            if (icon != null) {
455                                icon.setColorFilter(mThemeColorFilter);
456                            }
457                        }
458                        Drawable alternateIcon = entry.getAlternateIcon();
459                        if (alternateIcon != null) {
460                            alternateIcon.setColorFilter(mThemeColorFilter);
461                        }
462                    }
463                }
464            }
465
466            // Expand/Collapse
467            mExpandCollapseTextView.setTextColor(mThemeColor);
468            mCollapseArrowDrawable.setColorFilter(mThemeColorFilter);
469            mExpandArrowDrawable.setColorFilter(mThemeColorFilter);
470        }
471    }
472
473    private View createEntryView(LayoutInflater layoutInflater, Entry entry, int iconVisibility) {
474        final View view = layoutInflater.inflate(
475                R.layout.expanding_entry_card_item, this, false);
476
477        view.setId(entry.getViewId());
478
479        final ImageView icon = (ImageView) view.findViewById(R.id.icon);
480        icon.setVisibility(iconVisibility);
481        if (entry.getIcon() != null) {
482            icon.setImageDrawable(entry.getIcon());
483        }
484        final TextView header = (TextView) view.findViewById(R.id.header);
485        if (!TextUtils.isEmpty(entry.getHeader())) {
486            header.setText(entry.getHeader());
487        } else {
488            header.setVisibility(View.GONE);
489        }
490
491        final TextView subHeader = (TextView) view.findViewById(R.id.sub_header);
492        if (!TextUtils.isEmpty(entry.getSubHeader())) {
493            subHeader.setText(entry.getSubHeader());
494        } else {
495            subHeader.setVisibility(View.GONE);
496        }
497
498        final ImageView subHeaderIcon = (ImageView) view.findViewById(R.id.icon_sub_header);
499        if (entry.getSubHeaderIcon() != null) {
500            subHeaderIcon.setImageDrawable(entry.getSubHeaderIcon());
501        } else {
502            subHeaderIcon.setVisibility(View.GONE);
503        }
504
505        final TextView text = (TextView) view.findViewById(R.id.text);
506        if (!TextUtils.isEmpty(entry.getText())) {
507            text.setText(entry.getText());
508        } else {
509            text.setVisibility(View.GONE);
510        }
511
512        final ImageView textIcon = (ImageView) view.findViewById(R.id.icon_text);
513        if (entry.getTextIcon() != null) {
514            textIcon.setImageDrawable(entry.getTextIcon());
515        } else {
516            textIcon.setVisibility(View.GONE);
517        }
518
519        if (entry.getIntent() != null) {
520            view.setOnClickListener(mOnClickListener);
521            view.setTag(entry.getIntent());
522        }
523
524        // If only the header is visible, add a top margin to match icon's top margin
525        if (header.getVisibility() == View.VISIBLE && subHeader.getVisibility() == View.GONE &&
526                text.getVisibility() == View.GONE) {
527            RelativeLayout.LayoutParams headerLayoutParams =
528                    (RelativeLayout.LayoutParams) header.getLayoutParams();
529            headerLayoutParams.topMargin = (int) (getResources().getDimension(
530                    R.dimen.expanding_entry_card_item_icon_margin_top));
531            header.setLayoutParams(headerLayoutParams);
532        }
533
534        final ImageView alternateIcon = (ImageView) view.findViewById(R.id.icon_alternate);
535        if (entry.getAlternateIcon() != null && entry.getAlternateIntent() != null) {
536            alternateIcon.setImageDrawable(entry.getAlternateIcon());
537            alternateIcon.setOnClickListener(mOnClickListener);
538            alternateIcon.setTag(entry.getAlternateIntent());
539            alternateIcon.setId(entry.getViewId());
540            alternateIcon.setVisibility(View.VISIBLE);
541            alternateIcon.setContentDescription(entry.getAlternateContentDescription());
542
543            // Expand the clickable area for alternate icon to be top to bottom and to end edge
544            // of the entry view
545            view.post(new Runnable() {
546                @Override
547                public void run() {
548                    final Rect alternateIconRect = new Rect();
549                    alternateIcon.getHitRect(alternateIconRect);
550
551                    alternateIconRect.bottom = view.getHeight();
552                    alternateIconRect.top = 0;
553                    if (getLayoutDirection() == View.LAYOUT_DIRECTION_RTL) {
554                        alternateIconRect.left = 0;
555                    } else {
556                        alternateIconRect.right = view.getWidth();
557                    }
558                    final TouchDelegate touchDelegate =
559                            new TouchDelegate(alternateIconRect, alternateIcon);
560                    view.setTouchDelegate(touchDelegate);
561                }
562            });
563        }
564
565        return view;
566    }
567
568    private void updateExpandCollapseButton(CharSequence buttonText) {
569        final Drawable arrow = mIsExpanded ? mCollapseArrowDrawable : mExpandArrowDrawable;
570        updateBadges();
571        if (getLayoutDirection() == View.LAYOUT_DIRECTION_RTL) {
572            mExpandCollapseTextView.setCompoundDrawablesWithIntrinsicBounds(null, null, arrow,
573                    null);
574        } else {
575            mExpandCollapseTextView.setCompoundDrawablesWithIntrinsicBounds(arrow, null, null,
576                    null);
577        }
578        mExpandCollapseTextView.setText(buttonText);
579    }
580
581    private void updateBadges() {
582        if (mIsExpanded) {
583            mBadgeContainer.removeAllViews();
584        } else {
585            // Inflate badges if not yet created
586            if (mBadges.size() < mEntries.size() - mCollapsedEntriesCount) {
587                for (int i = mCollapsedEntriesCount; i < mEntries.size(); i++) {
588                    Drawable badgeDrawable = mEntries.get(i).get(0).getIcon();
589                    if (badgeDrawable != null) {
590                        ImageView badgeView = new ImageView(getContext());
591                        LinearLayout.LayoutParams badgeViewParams = new LinearLayout.LayoutParams(
592                                (int) getResources().getDimension(
593                                        R.dimen.expanding_entry_card_item_icon_width),
594                                (int) getResources().getDimension(
595                                        R.dimen.expanding_entry_card_item_icon_height));
596                        badgeViewParams.setMarginEnd((int) getResources().getDimension(
597                                R.dimen.expanding_entry_card_badge_separator_margin));
598                        badgeView.setLayoutParams(badgeViewParams);
599                        badgeView.setImageDrawable(badgeDrawable);
600                        mBadges.add(badgeView);
601                    }
602                }
603            }
604            mBadgeContainer.removeAllViews();
605            for (ImageView badge : mBadges) {
606                mBadgeContainer.addView(badge);
607            }
608        }
609    }
610
611    private void expand() {
612        ChangeBounds boundsTransition = new ChangeBounds();
613        boundsTransition.setDuration(DURATION_EXPAND_ANIMATION_CHANGE_BOUNDS);
614
615        Fade fadeIn = new Fade(Fade.IN);
616        fadeIn.setDuration(DURATION_EXPAND_ANIMATION_FADE_IN);
617        fadeIn.setStartDelay(DELAY_EXPAND_ANIMATION_FADE_IN);
618
619        TransitionSet transitionSet = new TransitionSet();
620        transitionSet.addTransition(boundsTransition);
621        transitionSet.addTransition(fadeIn);
622
623        final ViewGroup transitionViewContainer = mAnimationViewGroup == null ?
624                this : mAnimationViewGroup;
625
626        transitionSet.addListener(new TransitionListener() {
627            @Override
628            public void onTransitionStart(Transition transition) {
629                // The listener is used to turn off suppressing, the proper delta is not necessary
630                mListener.onExpand(0);
631            }
632
633            @Override
634            public void onTransitionEnd(Transition transition) {
635            }
636
637            @Override
638            public void onTransitionCancel(Transition transition) {
639            }
640
641            @Override
642            public void onTransitionPause(Transition transition) {
643            }
644
645            @Override
646            public void onTransitionResume(Transition transition) {
647            }
648        });
649
650        TransitionManager.beginDelayedTransition(transitionViewContainer, transitionSet);
651
652        mIsExpanded = true;
653        // In order to insert new entries, we may need to inflate them for the first time
654        inflateAllEntries(LayoutInflater.from(getContext()));
655        insertEntriesIntoViewGroup();
656        updateExpandCollapseButton(getCollapseButtonText());
657    }
658
659    private void collapse() {
660        final int startingHeight = mEntriesViewGroup.getMeasuredHeight();
661        mIsExpanded = false;
662        updateExpandCollapseButton(getExpandButtonText());
663
664        final ChangeBounds boundsTransition = new ChangeBounds();
665        boundsTransition.setDuration(DURATION_COLLAPSE_ANIMATION_CHANGE_BOUNDS);
666
667        final ChangeScroll scrollTransition = new ChangeScroll();
668        scrollTransition.setDuration(DURATION_COLLAPSE_ANIMATION_CHANGE_BOUNDS);
669
670        TransitionSet transitionSet = new TransitionSet();
671        transitionSet.addTransition(boundsTransition);
672        transitionSet.addTransition(scrollTransition);
673
674        final ViewGroup transitionViewContainer = mAnimationViewGroup == null ?
675                this : mAnimationViewGroup;
676
677        boundsTransition.addListener(new TransitionListener() {
678            @Override
679            public void onTransitionStart(Transition transition) {
680                /*
681                 * onTransitionStart is called after the view hierarchy has been changed but before
682                 * the animation begins.
683                 */
684                int finishingHeight = mEntriesViewGroup.getMeasuredHeight();
685                mListener.onCollapse(startingHeight - finishingHeight);
686            }
687
688            @Override
689            public void onTransitionEnd(Transition transition) {
690            }
691
692            @Override
693            public void onTransitionCancel(Transition transition) {
694            }
695
696            @Override
697            public void onTransitionPause(Transition transition) {
698            }
699
700            @Override
701            public void onTransitionResume(Transition transition) {
702            }
703        });
704
705        TransitionManager.beginDelayedTransition(transitionViewContainer, transitionSet);
706
707        insertEntriesIntoViewGroup();
708    }
709
710    /**
711     * Returns whether the view is currently in its expanded state.
712     */
713    public boolean isExpanded() {
714        return mIsExpanded;
715    }
716
717    /**
718     * Sets the title text of this ExpandingEntryCardView.
719     * @param title The title to set. A null title will result in the title being removed.
720     */
721    public void setTitle(String title) {
722        if (mTitleTextView == null) {
723            Log.e(TAG, "mTitleTextView is null");
724        }
725        if (title == null) {
726            mTitleTextView.setVisibility(View.GONE);
727            findViewById(R.id.title_separator).setVisibility(View.GONE);
728        }
729        mTitleTextView.setText(title);
730        mTitleTextView.setVisibility(View.VISIBLE);
731        findViewById(R.id.title_separator).setVisibility(View.VISIBLE);
732    }
733
734    public boolean shouldShow() {
735        return mEntries != null && mEntries.size() > 0;
736    }
737}
738