BaseCardView.java revision b55f0880c367c0b7e4132e7d954b60725e01e0b2
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 */ 16 17package android.support.v17.leanback.widget; 18 19import android.content.Context; 20import android.content.res.TypedArray; 21import android.support.v17.leanback.R; 22import android.util.AttributeSet; 23import android.util.Log; 24import android.view.View; 25import android.view.ViewDebug; 26import android.view.ViewGroup; 27import android.view.animation.AccelerateDecelerateInterpolator; 28import android.view.animation.Animation; 29import android.view.animation.DecelerateInterpolator; 30import android.view.animation.Transformation; 31 32import java.util.ArrayList; 33 34/** 35 * A card style layout that arranges its children in a vertical column according 36 * to the card type set for the parent and the card view type property set for 37 * these children. A BaseCardView can have from 1 to 3 card areas, depending 38 * on the chosen card type. Children are assigned to these areas according to 39 * the view type indicated by their layout parameters. The card type defines 40 * when these card areas are visible or not, and how they animate inside the 41 * parent card. These transitions are triggered when the card changes state. A 42 * card has 2 sets of states: Activated(true/false), and Selected(true/false). 43 * These states, combined with the card type chosen, determine what areas of the 44 * card are visible depending on the current state. They card type also 45 * determines the animations that are triggered when transitioning between 46 * states. The card states are set by calling {@link #setActivated(boolean) 47 * setActivated()} and {@link #setSelected(boolean) setSelected()}. 48 * <p> 49 * See {@link BaseCardView.LayoutParams BaseCardView.LayoutParams} for 50 * layout attributes. 51 * </p> 52 * 53 * @attr ref com.google.android.recline.R.styleable#BaseCardView_cardType 54 */ 55public class BaseCardView extends ViewGroup { 56 private static final String TAG = "BaseCardView"; 57 private static final boolean DEBUG = false; 58 59 /** 60 * A simple card type with a single layout area. This card type does not 61 * change its layout or size as it transitions between 62 * Activated/Not-Activated or Selected/Unselected states. 63 * 64 * @see #getCardType() 65 */ 66 public static final int CARD_TYPE_MAIN_ONLY = 0; 67 68 /** 69 * A Card type with 2 layout areas: A main area, always visible, and an info 70 * area, which is only visible when the card is set to its Active state. The 71 * info area fades in over the main area, and does not cause the card height 72 * to change. 73 * 74 * @see #getCardType() 75 */ 76 public static final int CARD_TYPE_INFO_OVER = 1; 77 78 /** 79 * A Card type with 2 layout areas: A main area, always visible, and an info 80 * area, which is only visible when the card is set to its Active state. The 81 * info area appears below the main area, causing the total card height to 82 * change when the card switches between Active and Inactive states. 83 * 84 * @see #getCardType() 85 */ 86 public static final int CARD_TYPE_INFO_UNDER = 2; 87 88 /** 89 * A Card type with 3 layout areas: A main area, always visible; an info 90 * area, which is only visible when the card is set to its Active state; and 91 * an extra area, which only becomes visible when the card is set to 92 * Selected state. The info area appears below the main area, causing the 93 * total card height to change when the card switches between Active and 94 * Inactive states. The extra area only appears if the card stays in its 95 * Selected state for a certain (small) amount of time. It animates in at 96 * the bottom of the card, shifting up the info view. This does not affect 97 * the card height. 98 * 99 * @see #getCardType() 100 */ 101 public static final int CARD_TYPE_INFO_UNDER_WITH_EXTRA = 3; 102 103 /** 104 * Indicates that a card region is always visible. 105 */ 106 public static final int CARD_REGION_VISIBLE_ALWAYS = 0; 107 108 /** 109 * Indicates that a card region is visible when the card is activated. 110 */ 111 public static final int CARD_REGION_VISIBLE_ACTIVATED = 1; 112 113 /** 114 * Indicates that a card region is visible when the card is selected. 115 */ 116 public static final int CARD_REGION_VISIBLE_SELECTED = 2; 117 118 private static final int CARD_TYPE_INVALID = 4; 119 120 private int mCardType; 121 private int mInfoVisibility; 122 private int mExtraVisibility; 123 124 private ArrayList<View> mMainViewList; 125 private ArrayList<View> mInfoViewList; 126 private ArrayList<View> mExtraViewList; 127 128 private boolean mCardSelected; 129 130 private int mMeasuredWidth; 131 private int mMeasuredHeight; 132 private boolean mDelaySelectedAnim; 133 private int mSelectedAnimationDelay; 134 private final int mActivatedAnimDuration; 135 private final int mSelectedAnimDuration; 136 137 private float mInfoOffset; 138 private float mInfoVisFraction; 139 private float mInfoAlpha = 1.0f; 140 private Animation mAnim; 141 142 private final Runnable mAnimationTrigger = new Runnable() { 143 @Override 144 public void run() { 145 animateInfoOffset(true); 146 } 147 }; 148 149 public BaseCardView(Context context) { 150 this(context, null); 151 } 152 153 public BaseCardView(Context context, AttributeSet attrs) { 154 this(context, attrs, R.attr.baseCardViewStyle); 155 } 156 157 public BaseCardView(Context context, AttributeSet attrs, int defStyle) { 158 super(context, attrs, defStyle); 159 160 TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.lbBaseCardView, 0, 0); 161 162 try { 163 mCardType = a.getInteger(R.styleable.lbBaseCardView_cardType, CARD_TYPE_MAIN_ONLY); 164 mInfoVisibility = a.getInteger(R.styleable.lbBaseCardView_infoVisibility, 165 CARD_REGION_VISIBLE_ACTIVATED); 166 mExtraVisibility = a.getInteger(R.styleable.lbBaseCardView_extraVisibility, 167 CARD_REGION_VISIBLE_SELECTED); 168 // Extra region should never show before info region. 169 if (mExtraVisibility < mInfoVisibility) { 170 mExtraVisibility = mInfoVisibility; 171 } 172 173 mSelectedAnimationDelay = a.getInteger( 174 R.styleable.lbBaseCardView_selectedAnimationDelay, 175 getResources().getInteger(R.integer.lb_card_selected_animation_delay)); 176 177 mSelectedAnimDuration = a.getInteger( 178 R.styleable.lbBaseCardView_selectedAnimationDuration, 179 getResources().getInteger(R.integer.lb_card_selected_animation_duration)); 180 181 mActivatedAnimDuration = 182 a.getInteger(R.styleable.lbBaseCardView_activatedAnimationDuration, 183 getResources().getInteger(R.integer.lb_card_activated_animation_duration)); 184 } finally { 185 a.recycle(); 186 } 187 188 mCardSelected = false; 189 mDelaySelectedAnim = true; 190 191 mMainViewList = new ArrayList<View>(); 192 mInfoViewList = new ArrayList<View>(); 193 mExtraViewList = new ArrayList<View>(); 194 195 mInfoOffset = 0.0f; 196 mInfoVisFraction = 0.0f; 197 } 198 199 /** 200 * Sets a flag indicating if the Selected animation (if the selected card 201 * type implements one) should run immediately after the card is selected, 202 * or if it should be delayed. The default behavior is to delay this 203 * animation. This is a one-shot override. If set to false, after the card 204 * is selected and the selected animation is triggered, this flag is 205 * automatically reset to true. This is useful when you want to change the 206 * default behavior, and have the selected animation run immediately. One 207 * such case could be when focus moves from one row to the other, when 208 * instead of delaying the selected animation until the user pauses on a 209 * card, it may be desirable to trigger the animation for that card 210 * immediately. 211 * 212 * @param delay True (default) if the selected animation should be delayed 213 * after the card is selected, or false if the animation should 214 * run immediately the next time the card is Selected. 215 */ 216 public void setSelectedAnimationDelayed(boolean delay) { 217 mDelaySelectedAnim = delay; 218 } 219 220 /** 221 * Returns a boolean indicating if the selected animation will run 222 * immediately or be delayed the next time the card is Selected. 223 * 224 * @return true if this card is set to delay the selected animation the next 225 * time it is selected, or false if the selected animation will run 226 * immediately the next time the card is selected. 227 */ 228 public boolean isSelectedAnimationDelayed() { 229 return mDelaySelectedAnim; 230 } 231 232 /** 233 * Sets the type of this Card. 234 * 235 * @param type The desired card type. 236 */ 237 public void setCardType(int type) { 238 if (mCardType != type) { 239 if (type >= CARD_TYPE_MAIN_ONLY && type < CARD_TYPE_INVALID) { 240 // Valid card type 241 mCardType = type; 242 } else { 243 Log.e(TAG, "Invalid card type specified: " + type + 244 ". Defaulting to type CARD_TYPE_MAIN_ONLY."); 245 mCardType = CARD_TYPE_MAIN_ONLY; 246 } 247 requestLayout(); 248 } 249 } 250 251 /** 252 * Returns the type of this Card. 253 * 254 * @return The type of this card. 255 */ 256 public int getCardType() { 257 return mCardType; 258 } 259 260 public void setInfoVisibility(int visibility) { 261 mInfoVisibility = visibility; 262 } 263 264 public int getInfoVisibility() { 265 return mInfoVisibility; 266 } 267 268 public void setExtraVisibility(int visibility) { 269 mExtraVisibility = visibility; 270 } 271 272 public int getExtraVisibility() { 273 return mExtraVisibility; 274 } 275 276 /** 277 * Sets the Activated state of this Card. This can trigger changes in the 278 * card layout, resulting in views to become visible or hidden. A card is 279 * normally set to Activated state when its parent container (like a Row) 280 * receives focus, and then activates all of its children. 281 * 282 * @param activated True if the card is ACTIVE, or false if INACTIVE. 283 * @see #isActivated() 284 */ 285 @Override 286 public void setActivated(boolean activated) { 287 if (activated != isActivated()) { 288 super.setActivated(activated); 289 applyActiveState(isActivated()); 290 } 291 } 292 293 /** 294 * Sets the Selected state of this Card. This can trigger changes in the 295 * card layout, resulting in views to become visible or hidden. A card is 296 * normally set to Selected state when it receives input focus. 297 * 298 * @param selected True if the card is Selected, or false otherwise. 299 * @see #isSelected() 300 */ 301 @Override 302 public void setSelected(boolean selected) { 303 if (mCardSelected != selected) { 304 mCardSelected = selected; 305 applySelectedState(mCardSelected); 306 } 307 } 308 309 /** 310 * returns the Selected state of this Card. 311 * 312 * @return True if the card is Selected, or false otherwise. 313 * @see #setSelected(boolean) 314 */ 315 @Override 316 public boolean isSelected() { 317 return mCardSelected; 318 } 319 320 @Override 321 public boolean shouldDelayChildPressedState() { 322 return false; 323 } 324 325 @Override 326 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 327 mMeasuredWidth = 0; 328 mMeasuredHeight = 0; 329 int state = 0; 330 int mainHeight = 0; 331 int infoHeight = 0; 332 int extraHeight = 0; 333 334 findChildrenViews(); 335 336 final int unspecifiedSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); 337 // MAIN is always present 338 for (int i = 0; i < mMainViewList.size(); i++) { 339 View mainView = mMainViewList.get(i); 340 if (mainView.getVisibility() != View.GONE) { 341 measureChild(mainView, unspecifiedSpec, unspecifiedSpec); 342 mMeasuredWidth = Math.max(mMeasuredWidth, mainView.getMeasuredWidth()); 343 mainHeight += mainView.getMeasuredHeight(); 344 state = View.combineMeasuredStates(state, mainView.getMeasuredState()); 345 } 346 } 347 348 349 // The MAIN area determines the card width 350 int cardWidthMeasureSpec = MeasureSpec.makeMeasureSpec(mMeasuredWidth, MeasureSpec.EXACTLY); 351 352 if (hasInfoRegion()) { 353 for (int i = 0; i < mInfoViewList.size(); i++) { 354 View infoView = mInfoViewList.get(i); 355 if (infoView.getVisibility() != View.GONE) { 356 measureChild(infoView, cardWidthMeasureSpec, unspecifiedSpec); 357 if (mCardType != CARD_TYPE_INFO_OVER) { 358 infoHeight += infoView.getMeasuredHeight(); 359 } 360 state = View.combineMeasuredStates(state, infoView.getMeasuredState()); 361 } 362 } 363 364 if (hasExtraRegion()) { 365 for (int i = 0; i < mExtraViewList.size(); i++) { 366 View extraView = mExtraViewList.get(i); 367 if (extraView.getVisibility() != View.GONE) { 368 measureChild(extraView, cardWidthMeasureSpec, unspecifiedSpec); 369 extraHeight += extraView.getMeasuredHeight(); 370 state = View.combineMeasuredStates(state, extraView.getMeasuredState()); 371 } 372 } 373 } 374 } 375 376 boolean infoAnimating = mInfoVisibility == CARD_REGION_VISIBLE_SELECTED; 377 mMeasuredHeight = (int) (mainHeight + 378 (infoAnimating ? (infoHeight * mInfoVisFraction) : infoHeight) 379 + extraHeight - (infoAnimating ? 0 : mInfoOffset)); 380 381 // Report our final dimensions. 382 setMeasuredDimension(View.resolveSizeAndState(mMeasuredWidth + getPaddingLeft() + 383 getPaddingRight(), widthMeasureSpec, state), 384 View.resolveSizeAndState(mMeasuredHeight + getPaddingTop() + getPaddingBottom(), 385 heightMeasureSpec, state << View.MEASURED_HEIGHT_STATE_SHIFT)); 386 } 387 388 @Override 389 protected void onLayout(boolean changed, int left, int top, int right, int bottom) { 390 float currBottom = getPaddingTop(); 391 392 // MAIN is always present 393 for (int i = 0; i < mMainViewList.size(); i++) { 394 View mainView = mMainViewList.get(i); 395 if (mainView.getVisibility() != View.GONE) { 396 mainView.layout(getPaddingLeft(), 397 (int) currBottom, 398 mMeasuredWidth + getPaddingLeft(), 399 (int) (currBottom + mainView.getMeasuredHeight())); 400 currBottom += mainView.getMeasuredHeight(); 401 } 402 } 403 404 if (hasInfoRegion()) { 405 float infoHeight = 0f; 406 for (int i = 0; i < mInfoViewList.size(); i++) { 407 infoHeight += mInfoViewList.get(i).getMeasuredHeight(); 408 } 409 410 if (mCardType == CARD_TYPE_INFO_OVER) { 411 // retract currBottom to overlap the info views on top of main 412 currBottom -= infoHeight; 413 if (currBottom < 0) { 414 currBottom = 0; 415 } 416 } else if (mCardType == CARD_TYPE_INFO_UNDER) { 417 if (mInfoVisibility == CARD_REGION_VISIBLE_SELECTED) { 418 infoHeight = infoHeight * mInfoVisFraction; 419 } 420 } else { 421 currBottom -= mInfoOffset; 422 } 423 424 for (int i = 0; i < mInfoViewList.size(); i++) { 425 View infoView = mInfoViewList.get(i); 426 if (infoView.getVisibility() != View.GONE) { 427 int viewHeight = infoView.getMeasuredHeight(); 428 if (viewHeight > infoHeight) { 429 viewHeight = (int) infoHeight; 430 } 431 infoView.layout(getPaddingLeft(), 432 (int) currBottom, 433 mMeasuredWidth + getPaddingLeft(), 434 (int) (currBottom + viewHeight)); 435 currBottom += viewHeight; 436 infoHeight -= viewHeight; 437 if (infoHeight <= 0) { 438 break; 439 } 440 } 441 } 442 443 if (hasExtraRegion()) { 444 for (int i = 0; i < mExtraViewList.size(); i++) { 445 View extraView = mExtraViewList.get(i); 446 if (extraView.getVisibility() != View.GONE) { 447 extraView.layout(getPaddingLeft(), 448 (int) currBottom, 449 mMeasuredWidth + getPaddingLeft(), 450 (int) (currBottom + extraView.getMeasuredHeight())); 451 currBottom += extraView.getMeasuredHeight(); 452 } 453 } 454 } 455 } 456 } 457 458 @Override 459 protected void onDetachedFromWindow() { 460 super.onDetachedFromWindow(); 461 removeCallbacks(mAnimationTrigger); 462 cancelAnimations(); 463 mInfoOffset = 0.0f; 464 mInfoVisFraction = 0.0f; 465 } 466 467 private boolean hasInfoRegion() { 468 return mCardType != CARD_TYPE_MAIN_ONLY; 469 } 470 471 private boolean hasExtraRegion() { 472 return mCardType == CARD_TYPE_INFO_UNDER_WITH_EXTRA; 473 } 474 475 private boolean isRegionVisible(int regionVisibility) { 476 switch (regionVisibility) { 477 case CARD_REGION_VISIBLE_ALWAYS: 478 return true; 479 case CARD_REGION_VISIBLE_ACTIVATED: 480 return isActivated(); 481 case CARD_REGION_VISIBLE_SELECTED: 482 return isActivated() && isSelected(); 483 default: 484 if (DEBUG) Log.e(TAG, "invalid region visibility state: " + regionVisibility); 485 return false; 486 } 487 } 488 489 private void findChildrenViews() { 490 mMainViewList.clear(); 491 mInfoViewList.clear(); 492 mExtraViewList.clear(); 493 494 final int count = getChildCount(); 495 496 boolean infoVisible = isRegionVisible(mInfoVisibility); 497 boolean extraVisible = hasExtraRegion() && mInfoOffset > 0f; 498 499 if (mCardType == CARD_TYPE_INFO_UNDER && mInfoVisibility == CARD_REGION_VISIBLE_SELECTED) { 500 infoVisible = mInfoVisFraction > 0f; 501 } 502 503 for (int i = 0; i < count; i++) { 504 final View child = getChildAt(i); 505 506 if (child == null) { 507 continue; 508 } 509 510 BaseCardView.LayoutParams lp = (BaseCardView.LayoutParams) child 511 .getLayoutParams(); 512 if (lp.viewType == LayoutParams.VIEW_TYPE_INFO) { 513 mInfoViewList.add(child); 514 child.setVisibility(infoVisible ? View.VISIBLE : View.GONE); 515 } else if (lp.viewType == LayoutParams.VIEW_TYPE_EXTRA) { 516 mExtraViewList.add(child); 517 child.setVisibility(extraVisible ? View.VISIBLE : View.GONE); 518 } else { 519 // Default to MAIN 520 mMainViewList.add(child); 521 child.setVisibility(View.VISIBLE); 522 } 523 } 524 525 } 526 527 private void applyActiveState(boolean active) { 528 if (mInfoVisibility == CARD_REGION_VISIBLE_ACTIVATED) { 529 setInfoViewVisibility(active); 530 } 531 } 532 533 private void setInfoViewVisibility(boolean visible) { 534 if (mCardType == CARD_TYPE_INFO_UNDER_WITH_EXTRA) { 535 // Active state changes for card type 536 // CARD_TYPE_INFO_UNDER_WITH_EXTRA 537 if (visible) { 538 for (int i = 0; i < mInfoViewList.size(); i++) { 539 mInfoViewList.get(i).setVisibility(View.VISIBLE); 540 } 541 } else { 542 for (int i = 0; i < mInfoViewList.size(); i++) { 543 mInfoViewList.get(i).setVisibility(View.GONE); 544 } 545 for (int i = 0; i < mExtraViewList.size(); i++) { 546 mExtraViewList.get(i).setVisibility(View.GONE); 547 } 548 mInfoOffset = 0.0f; 549 } 550 } else if (mCardType == CARD_TYPE_INFO_UNDER) { 551 // Active state changes for card type CARD_TYPE_INFO_UNDER 552 if (mInfoVisibility == CARD_REGION_VISIBLE_SELECTED) { 553 animateInfoHeight(visible); 554 } else { 555 for (int i = 0; i < mInfoViewList.size(); i++) { 556 mInfoViewList.get(i).setVisibility(visible ? View.VISIBLE : View.GONE); 557 } 558 } 559 } else if (mCardType == CARD_TYPE_INFO_OVER) { 560 // Active state changes for card type CARD_TYPE_INFO_OVER 561 animateInfoAlpha(visible); 562 } 563 } 564 565 private void applySelectedState(boolean focused) { 566 removeCallbacks(mAnimationTrigger); 567 568 if (mCardType == CARD_TYPE_INFO_UNDER_WITH_EXTRA) { 569 // Focus changes for card type CARD_TYPE_INFO_UNDER_WITH_EXTRA 570 if (focused) { 571 if (!mDelaySelectedAnim) { 572 post(mAnimationTrigger); 573 mDelaySelectedAnim = true; 574 } else { 575 postDelayed(mAnimationTrigger, mSelectedAnimationDelay); 576 } 577 } else { 578 animateInfoOffset(false); 579 } 580 } else if (mInfoVisibility == CARD_REGION_VISIBLE_SELECTED) { 581 setInfoViewVisibility(focused); 582 } 583 } 584 585 private void cancelAnimations() { 586 if (mAnim != null) { 587 mAnim.cancel(); 588 mAnim = null; 589 } 590 } 591 592 // This animation changes the Y offset of the info and extra views, 593 // so that they animate UP to make the extra info area visible when a 594 // card is selected. 595 private void animateInfoOffset(boolean shown) { 596 cancelAnimations(); 597 598 int extraHeight = 0; 599 if (shown) { 600 int widthSpec = MeasureSpec.makeMeasureSpec(mMeasuredWidth, MeasureSpec.EXACTLY); 601 int heightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); 602 603 for (int i = 0; i < mExtraViewList.size(); i++) { 604 View extraView = mExtraViewList.get(i); 605 extraView.setVisibility(View.VISIBLE); 606 extraView.measure(widthSpec, heightSpec); 607 extraHeight = Math.max(extraHeight, extraView.getMeasuredHeight()); 608 } 609 } 610 611 mAnim = new InfoOffsetAnimation(mInfoOffset, shown ? extraHeight : 0); 612 mAnim.setDuration(mSelectedAnimDuration); 613 mAnim.setInterpolator(new AccelerateDecelerateInterpolator()); 614 mAnim.setAnimationListener(new Animation.AnimationListener() { 615 @Override 616 public void onAnimationStart(Animation animation) { 617 } 618 619 @Override 620 public void onAnimationEnd(Animation animation) { 621 if (mInfoOffset == 0f) { 622 for (int i = 0; i < mExtraViewList.size(); i++) { 623 mExtraViewList.get(i).setVisibility(View.GONE); 624 } 625 } 626 } 627 628 @Override 629 public void onAnimationRepeat(Animation animation) { 630 } 631 632 }); 633 startAnimation(mAnim); 634 } 635 636 // This animation changes the visible height of the info views, 637 // so that they animate in and out of view. 638 private void animateInfoHeight(boolean shown) { 639 cancelAnimations(); 640 641 int extraHeight = 0; 642 if (shown) { 643 int widthSpec = MeasureSpec.makeMeasureSpec(mMeasuredWidth, MeasureSpec.EXACTLY); 644 int heightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); 645 646 for (int i = 0; i < mExtraViewList.size(); i++) { 647 View extraView = mExtraViewList.get(i); 648 extraView.setVisibility(View.VISIBLE); 649 extraView.measure(widthSpec, heightSpec); 650 extraHeight = Math.max(extraHeight, extraView.getMeasuredHeight()); 651 } 652 } 653 654 mAnim = new InfoHeightAnimation(mInfoVisFraction, shown ? 1.0f : 0f); 655 mAnim.setDuration(mSelectedAnimDuration); 656 mAnim.setInterpolator(new AccelerateDecelerateInterpolator()); 657 mAnim.setAnimationListener(new Animation.AnimationListener() { 658 @Override 659 public void onAnimationStart(Animation animation) { 660 } 661 662 @Override 663 public void onAnimationEnd(Animation animation) { 664 if (mInfoOffset == 0f) { 665 for (int i = 0; i < mExtraViewList.size(); i++) { 666 mExtraViewList.get(i).setVisibility(View.GONE); 667 } 668 } 669 } 670 671 @Override 672 public void onAnimationRepeat(Animation animation) { 673 } 674 675 }); 676 startAnimation(mAnim); 677 } 678 679 // This animation changes the alpha of the info views, so they animate in 680 // and out. It's meant to be used when the info views are overlaid on top of 681 // the main view area. It gets triggered by a change in the Active state of 682 // the card. 683 private void animateInfoAlpha(boolean shown) { 684 cancelAnimations(); 685 686 if (shown) { 687 for (int i = 0; i < mInfoViewList.size(); i++) { 688 mInfoViewList.get(i).setVisibility(View.VISIBLE); 689 } 690 } 691 692 mAnim = new InfoAlphaAnimation(mInfoAlpha, shown ? 1.0f : 0.0f); 693 mAnim.setDuration(mActivatedAnimDuration); 694 mAnim.setInterpolator(new DecelerateInterpolator()); 695 mAnim.setAnimationListener(new Animation.AnimationListener() { 696 @Override 697 public void onAnimationStart(Animation animation) { 698 } 699 700 @Override 701 public void onAnimationEnd(Animation animation) { 702 if (mInfoAlpha == 0.0) { 703 for (int i = 0; i < mInfoViewList.size(); i++) { 704 mInfoViewList.get(i).setVisibility(View.GONE); 705 } 706 } 707 } 708 709 @Override 710 public void onAnimationRepeat(Animation animation) { 711 } 712 713 }); 714 startAnimation(mAnim); 715 } 716 717 @Override 718 public LayoutParams generateLayoutParams(AttributeSet attrs) { 719 return new BaseCardView.LayoutParams(getContext(), attrs); 720 } 721 722 @Override 723 protected LayoutParams generateDefaultLayoutParams() { 724 return new BaseCardView.LayoutParams( 725 LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT); 726 } 727 728 @Override 729 protected LayoutParams generateLayoutParams(ViewGroup.LayoutParams lp) { 730 if (lp instanceof LayoutParams) { 731 return new LayoutParams((LayoutParams) lp); 732 } else { 733 return new LayoutParams(lp); 734 } 735 } 736 737 @Override 738 protected boolean checkLayoutParams(ViewGroup.LayoutParams p) { 739 return p instanceof BaseCardView.LayoutParams; 740 } 741 742 /** 743 * Per-child layout information associated with BaseCardView. 744 */ 745 public static class LayoutParams extends ViewGroup.LayoutParams { 746 public static final int VIEW_TYPE_MAIN = 0; 747 public static final int VIEW_TYPE_INFO = 1; 748 public static final int VIEW_TYPE_EXTRA = 2; 749 750 /** 751 * Card component type for the view associated with these LayoutParams. 752 */ 753 @ViewDebug.ExportedProperty(category = "layout", mapping = { 754 @ViewDebug.IntToString(from = VIEW_TYPE_MAIN, to = "MAIN"), 755 @ViewDebug.IntToString(from = VIEW_TYPE_INFO, to = "INFO"), 756 @ViewDebug.IntToString(from = VIEW_TYPE_EXTRA, to = "EXTRA") 757 }) 758 public int viewType = VIEW_TYPE_MAIN; 759 760 /** 761 * {@inheritDoc} 762 */ 763 public LayoutParams(Context c, AttributeSet attrs) { 764 super(c, attrs); 765 TypedArray a = c.obtainStyledAttributes(attrs, R.styleable.lbBaseCardView_Layout); 766 767 viewType = a.getInt( 768 R.styleable.lbBaseCardView_Layout_layout_viewType, VIEW_TYPE_MAIN); 769 770 a.recycle(); 771 } 772 773 /** 774 * {@inheritDoc} 775 */ 776 public LayoutParams(int width, int height) { 777 super(width, height); 778 } 779 780 /** 781 * {@inheritDoc} 782 */ 783 public LayoutParams(ViewGroup.LayoutParams p) { 784 super(p); 785 } 786 787 /** 788 * Copy constructor. Clones the width, height, and View Type of the 789 * source. 790 * 791 * @param source The layout params to copy from. 792 */ 793 public LayoutParams(LayoutParams source) { 794 super(source); 795 796 this.viewType = source.viewType; 797 } 798 } 799 800 // Helper animation class used in the animation of the info and extra 801 // fields vertically within the card 802 private class InfoOffsetAnimation extends Animation { 803 private float mStartValue; 804 private float mDelta; 805 806 public InfoOffsetAnimation(float start, float end) { 807 mStartValue = start; 808 mDelta = end - start; 809 } 810 811 @Override 812 protected void applyTransformation(float interpolatedTime, Transformation t) { 813 mInfoOffset = mStartValue + (interpolatedTime * mDelta); 814 requestLayout(); 815 } 816 } 817 818 // Helper animation class used in the animation of the visible height 819 // for the info fields. 820 private class InfoHeightAnimation extends Animation { 821 private float mStartValue; 822 private float mDelta; 823 824 public InfoHeightAnimation(float start, float end) { 825 mStartValue = start; 826 mDelta = end - start; 827 } 828 829 @Override 830 protected void applyTransformation(float interpolatedTime, Transformation t) { 831 mInfoVisFraction = mStartValue + (interpolatedTime * mDelta); 832 requestLayout(); 833 } 834 } 835 836 // Helper animation class used to animate the alpha for the info views 837 // when they are fading in or out of view. 838 private class InfoAlphaAnimation extends Animation { 839 private float mStartValue; 840 private float mDelta; 841 842 public InfoAlphaAnimation(float start, float end) { 843 mStartValue = start; 844 mDelta = end - start; 845 } 846 847 @Override 848 protected void applyTransformation(float interpolatedTime, Transformation t) { 849 mInfoAlpha = mStartValue + (interpolatedTime * mDelta); 850 for (int i = 0; i < mInfoViewList.size(); i++) { 851 mInfoViewList.get(i).setAlpha(mInfoAlpha); 852 } 853 } 854 } 855} 856