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