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