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