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