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