java.lang.Object
↳FrameLayout
↳androidx.leanback.widget.BaseCardView
Subclasses:
ImageCardView
Gradle dependencies
compile group: 'androidx.leanback', name: 'leanback', version: '1.2.0-alpha04'
- groupId: androidx.leanback
- artifactId: leanback
- version: 1.2.0-alpha04
Artifact androidx.leanback:leanback:1.2.0-alpha04 it located at Google repository (https://maven.google.com/)
Androidx artifact mapping:
androidx.leanback:leanback com.android.support:leanback-v17
Androidx class mapping:
androidx.leanback.widget.BaseCardView android.support.v17.leanback.widget.BaseCardView
Overview
A card style layout that responds to certain state changes. It arranges its
children in a vertical column, with different regions becoming visible at
different times.
A BaseCardView will draw its children based on its type, the region
visibilities of the child types, and the state of the widget. A child may be
marked as belonging to one of three regions: main, info, or extra. The main
region is always visible, while the info and extra regions can be set to
display based on the activated or selected state of the View. The card states
are set by calling setActivated and
setSelected.
See BaseCardView.LayoutParams for layout attributes.
Summary
Fields |
---|
public static final int | CARD_REGION_VISIBLE_ACTIVATED Indicates that a card region is visible when the card is activated. |
public static final int | CARD_REGION_VISIBLE_ALWAYS Indicates that a card region is always visible. |
public static final int | CARD_REGION_VISIBLE_SELECTED Indicates that a card region is visible when the card is selected. |
public static final int | CARD_TYPE_INFO_OVER A Card type with 2 layout areas: A main area which is always visible, and
an info area that fades in over the main area when it is visible. |
public static final int | CARD_TYPE_INFO_UNDER A Card type with 2 layout areas: A main area which is always visible, and
an info area that appears below the main area. |
public static final int | CARD_TYPE_INFO_UNDER_WITH_EXTRA A Card type with 3 layout areas: A main area which is always visible; an
info area which will appear below the main area, and an extra area that
only appears after a short delay. |
public static final int | CARD_TYPE_MAIN_ONLY A simple card type with a single layout area. |
Methods |
---|
protected boolean | checkLayoutParams(ViewGroup.LayoutParams p)
|
protected BaseCardView.LayoutParams | generateDefaultLayoutParams()
|
public BaseCardView.LayoutParams | generateLayoutParams(AttributeSet attrs)
|
protected BaseCardView.LayoutParams | generateLayoutParams(ViewGroup.LayoutParams lp)
|
public int | getCardType()
Returns the type of this Card. |
public int | getExtraVisibility()
Returns the visibility of the extra region of the card. |
public int | getInfoVisibility()
Returns the visibility of the info region of the card. |
public boolean | isSelectedAnimationDelayed()
Returns a boolean indicating if the selected animation will run
immediately or be delayed the next time the card is Selected. |
protected int[] | onCreateDrawableState(int extraSpace)
|
protected void | onDetachedFromWindow()
|
protected void | onLayout(boolean changed, int left, int top, int right, int bottom)
|
protected void | onMeasure(int widthMeasureSpec, int heightMeasureSpec)
|
public void | setActivated(boolean activated)
Sets the Activated state of this Card. |
public void | setCardType(int type)
Sets the type of this Card. |
public void | setExtraVisibility(int visibility)
Sets the visibility of the extra region of the card. |
public void | setInfoVisibility(int visibility)
Sets the visibility of the info region of the card. |
public void | setSelected(boolean selected)
Sets the Selected state of this Card. |
public void | setSelectedAnimationDelayed(boolean delay)
Sets a flag indicating if the Selected animation (if the selected card
type implements one) should run immediately after the card is selected,
or if it should be delayed. |
public boolean | shouldDelayChildPressedState()
|
public java.lang.String | toString()
|
from java.lang.Object | clone, equals, finalize, getClass, hashCode, notify, notifyAll, wait, wait, wait |
Fields
public static final int
CARD_TYPE_MAIN_ONLYA simple card type with a single layout area. This card type does not
change its layout or size as it transitions between
Activated/Not-Activated or Selected/Unselected states.
See also: BaseCardView.getCardType()
public static final int
CARD_TYPE_INFO_OVERA Card type with 2 layout areas: A main area which is always visible, and
an info area that fades in over the main area when it is visible.
The card height will not change.
See also: BaseCardView.getCardType()
public static final int
CARD_TYPE_INFO_UNDERA Card type with 2 layout areas: A main area which is always visible, and
an info area that appears below the main area. When the info area is visible
the total card height will change.
See also: BaseCardView.getCardType()
public static final int
CARD_TYPE_INFO_UNDER_WITH_EXTRAA Card type with 3 layout areas: A main area which is always visible; an
info area which will appear below the main area, and an extra area that
only appears after a short delay. The info area appears below the main
area, causing the total card height to change. The extra area animates in
at the bottom of the card, shifting up the info view without affecting
the card height.
See also: BaseCardView.getCardType()
public static final int
CARD_REGION_VISIBLE_ALWAYSIndicates that a card region is always visible.
public static final int
CARD_REGION_VISIBLE_ACTIVATEDIndicates that a card region is visible when the card is activated.
public static final int
CARD_REGION_VISIBLE_SELECTEDIndicates that a card region is visible when the card is selected.
Constructors
public
BaseCardView(Context context)
public
BaseCardView(Context context, AttributeSet attrs)
public
BaseCardView(Context context, AttributeSet attrs, int defStyleAttr)
Methods
public void
setSelectedAnimationDelayed(boolean delay)
Sets a flag indicating if the Selected animation (if the selected card
type implements one) should run immediately after the card is selected,
or if it should be delayed. The default behavior is to delay this
animation. This is a one-shot override. If set to false, after the card
is selected and the selected animation is triggered, this flag is
automatically reset to true. This is useful when you want to change the
default behavior, and have the selected animation run immediately. One
such case could be when focus moves from one row to the other, when
instead of delaying the selected animation until the user pauses on a
card, it may be desirable to trigger the animation for that card
immediately.
Parameters:
delay: True (default) if the selected animation should be delayed
after the card is selected, or false if the animation should
run immediately the next time the card is Selected.
public boolean
isSelectedAnimationDelayed()
Returns a boolean indicating if the selected animation will run
immediately or be delayed the next time the card is Selected.
Returns:
true if this card is set to delay the selected animation the next
time it is selected, or false if the selected animation will run
immediately the next time the card is selected.
public void
setCardType(int type)
Sets the type of this Card.
Parameters:
type: The desired card type.
Returns the type of this Card.
Returns:
The type of this card.
public void
setInfoVisibility(int visibility)
Sets the visibility of the info region of the card.
Parameters:
visibility: The region visibility to use for the info region. Must
be one of BaseCardView.CARD_REGION_VISIBLE_ALWAYS,
BaseCardView.CARD_REGION_VISIBLE_SELECTED, or
BaseCardView.CARD_REGION_VISIBLE_ACTIVATED.
public int
getInfoVisibility()
Returns the visibility of the info region of the card.
public void
setExtraVisibility(int visibility)
Deprecated: Extra view's visibility is controlled by BaseCardView.setInfoVisibility(int)
Sets the visibility of the extra region of the card.
Parameters:
visibility: The region visibility to use for the extra region. Must
be one of BaseCardView.CARD_REGION_VISIBLE_ALWAYS,
BaseCardView.CARD_REGION_VISIBLE_SELECTED, or
BaseCardView.CARD_REGION_VISIBLE_ACTIVATED.
public int
getExtraVisibility()
Deprecated: Extra view's visibility is controlled by BaseCardView.getInfoVisibility()
Returns the visibility of the extra region of the card.
public void
setActivated(boolean activated)
Sets the Activated state of this Card. This can trigger changes in the
card layout, resulting in views to become visible or hidden. A card is
normally set to Activated state when its parent container (like a Row)
receives focus, and then activates all of its children.
Parameters:
activated: True if the card is ACTIVE, or false if INACTIVE.
See also: BaseCardView
public void
setSelected(boolean selected)
Sets the Selected state of this Card. This can trigger changes in the
card layout, resulting in views to become visible or hidden. A card is
normally set to Selected state when it receives input focus.
Parameters:
selected: True if the card is Selected, or false otherwise.
See also: BaseCardView
public boolean
shouldDelayChildPressedState()
protected void
onMeasure(int widthMeasureSpec, int heightMeasureSpec)
protected void
onLayout(boolean changed, int left, int top, int right, int bottom)
protected void
onDetachedFromWindow()
protected int[]
onCreateDrawableState(int extraSpace)
protected boolean
checkLayoutParams(ViewGroup.LayoutParams p)
public java.lang.String
toString()
Source
/*
* Copyright (C) 2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package androidx.leanback.widget;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
import android.view.ViewDebug;
import android.view.ViewGroup;
import android.view.animation.AccelerateDecelerateInterpolator;
import android.view.animation.Animation;
import android.view.animation.DecelerateInterpolator;
import android.view.animation.Transformation;
import android.widget.FrameLayout;
import androidx.annotation.VisibleForTesting;
import androidx.leanback.R;
import java.util.ArrayList;
/**
* A card style layout that responds to certain state changes. It arranges its
* children in a vertical column, with different regions becoming visible at
* different times.
*
* <p>
* A BaseCardView will draw its children based on its type, the region
* visibilities of the child types, and the state of the widget. A child may be
* marked as belonging to one of three regions: main, info, or extra. The main
* region is always visible, while the info and extra regions can be set to
* display based on the activated or selected state of the View. The card states
* are set by calling {@link #setActivated(boolean) setActivated} and
* {@link #setSelected(boolean) setSelected}.
* <p>
* See {@link BaseCardView.LayoutParams} for layout attributes.
* </p>
*/
public class BaseCardView extends FrameLayout {
private static final String TAG = "BaseCardView";
private static final boolean DEBUG = false;
/**
* A simple card type with a single layout area. This card type does not
* change its layout or size as it transitions between
* Activated/Not-Activated or Selected/Unselected states.
*
* @see #getCardType()
*/
public static final int CARD_TYPE_MAIN_ONLY = 0;
/**
* A Card type with 2 layout areas: A main area which is always visible, and
* an info area that fades in over the main area when it is visible.
* The card height will not change.
*
* @see #getCardType()
*/
public static final int CARD_TYPE_INFO_OVER = 1;
/**
* A Card type with 2 layout areas: A main area which is always visible, and
* an info area that appears below the main area. When the info area is visible
* the total card height will change.
*
* @see #getCardType()
*/
public static final int CARD_TYPE_INFO_UNDER = 2;
/**
* A Card type with 3 layout areas: A main area which is always visible; an
* info area which will appear below the main area, and an extra area that
* only appears after a short delay. The info area appears below the main
* area, causing the total card height to change. The extra area animates in
* at the bottom of the card, shifting up the info view without affecting
* the card height.
*
* @see #getCardType()
*/
public static final int CARD_TYPE_INFO_UNDER_WITH_EXTRA = 3;
/**
* Indicates that a card region is always visible.
*/
public static final int CARD_REGION_VISIBLE_ALWAYS = 0;
/**
* Indicates that a card region is visible when the card is activated.
*/
public static final int CARD_REGION_VISIBLE_ACTIVATED = 1;
/**
* Indicates that a card region is visible when the card is selected.
*/
public static final int CARD_REGION_VISIBLE_SELECTED = 2;
private static final int CARD_TYPE_INVALID = 4;
private int mCardType;
private int mInfoVisibility;
private int mExtraVisibility;
private ArrayList<View> mMainViewList;
ArrayList<View> mInfoViewList;
ArrayList<View> mExtraViewList;
private int mMeasuredWidth;
private int mMeasuredHeight;
private boolean mDelaySelectedAnim;
private int mSelectedAnimationDelay;
private final int mActivatedAnimDuration;
private final int mSelectedAnimDuration;
/**
* Distance of top of info view to bottom of MainView, it will shift up when extra view appears.
*/
float mInfoOffset;
float mInfoVisFraction;
float mInfoAlpha;
private Animation mAnim;
private final static int[] LB_PRESSED_STATE_SET = new int[]{
android.R.attr.state_pressed};
private final Runnable mAnimationTrigger = new Runnable() {
@Override
public void run() {
animateInfoOffset(true);
}
};
public BaseCardView(Context context) {
this(context, null);
}
public BaseCardView(Context context, AttributeSet attrs) {
this(context, attrs, R.attr.baseCardViewStyle);
}
@SuppressLint("CustomViewStyleable")
public BaseCardView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.lbBaseCardView,
defStyleAttr, 0);
try {
mCardType = a.getInteger(R.styleable.lbBaseCardView_cardType, CARD_TYPE_MAIN_ONLY);
Drawable cardForeground = a.getDrawable(R.styleable.lbBaseCardView_cardForeground);
if (cardForeground != null) {
setForeground(cardForeground);
}
Drawable cardBackground = a.getDrawable(R.styleable.lbBaseCardView_cardBackground);
if (cardBackground != null) {
setBackground(cardBackground);
}
mInfoVisibility = a.getInteger(R.styleable.lbBaseCardView_infoVisibility,
CARD_REGION_VISIBLE_ACTIVATED);
mExtraVisibility = a.getInteger(R.styleable.lbBaseCardView_extraVisibility,
CARD_REGION_VISIBLE_SELECTED);
// Extra region should never show before info region.
if (mExtraVisibility < mInfoVisibility) {
mExtraVisibility = mInfoVisibility;
}
mSelectedAnimationDelay = a.getInteger(
R.styleable.lbBaseCardView_selectedAnimationDelay,
getResources().getInteger(R.integer.lb_card_selected_animation_delay));
mSelectedAnimDuration = a.getInteger(
R.styleable.lbBaseCardView_selectedAnimationDuration,
getResources().getInteger(R.integer.lb_card_selected_animation_duration));
mActivatedAnimDuration =
a.getInteger(R.styleable.lbBaseCardView_activatedAnimationDuration,
getResources().getInteger(R.integer.lb_card_activated_animation_duration));
} finally {
a.recycle();
}
mDelaySelectedAnim = true;
mMainViewList = new ArrayList<View>();
mInfoViewList = new ArrayList<View>();
mExtraViewList = new ArrayList<View>();
mInfoOffset = 0.0f;
mInfoVisFraction = getFinalInfoVisFraction();
mInfoAlpha = getFinalInfoAlpha();
}
/**
* Sets a flag indicating if the Selected animation (if the selected card
* type implements one) should run immediately after the card is selected,
* or if it should be delayed. The default behavior is to delay this
* animation. This is a one-shot override. If set to false, after the card
* is selected and the selected animation is triggered, this flag is
* automatically reset to true. This is useful when you want to change the
* default behavior, and have the selected animation run immediately. One
* such case could be when focus moves from one row to the other, when
* instead of delaying the selected animation until the user pauses on a
* card, it may be desirable to trigger the animation for that card
* immediately.
*
* @param delay True (default) if the selected animation should be delayed
* after the card is selected, or false if the animation should
* run immediately the next time the card is Selected.
*/
public void setSelectedAnimationDelayed(boolean delay) {
mDelaySelectedAnim = delay;
}
/**
* Returns a boolean indicating if the selected animation will run
* immediately or be delayed the next time the card is Selected.
*
* @return true if this card is set to delay the selected animation the next
* time it is selected, or false if the selected animation will run
* immediately the next time the card is selected.
*/
public boolean isSelectedAnimationDelayed() {
return mDelaySelectedAnim;
}
/**
* Sets the type of this Card.
*
* @param type The desired card type.
*/
public void setCardType(int type) {
if (mCardType != type) {
if (type >= CARD_TYPE_MAIN_ONLY && type < CARD_TYPE_INVALID) {
// Valid card type
mCardType = type;
} else {
Log.e(TAG, "Invalid card type specified: " + type
+ ". Defaulting to type CARD_TYPE_MAIN_ONLY.");
mCardType = CARD_TYPE_MAIN_ONLY;
}
requestLayout();
}
}
/**
* Returns the type of this Card.
*
* @return The type of this card.
*/
public int getCardType() {
return mCardType;
}
/**
* Sets the visibility of the info region of the card.
*
* @param visibility The region visibility to use for the info region. Must
* be one of {@link #CARD_REGION_VISIBLE_ALWAYS},
* {@link #CARD_REGION_VISIBLE_SELECTED}, or
* {@link #CARD_REGION_VISIBLE_ACTIVATED}.
*/
public void setInfoVisibility(int visibility) {
if (mInfoVisibility != visibility) {
cancelAnimations();
mInfoVisibility = visibility;
mInfoVisFraction = getFinalInfoVisFraction();
requestLayout();
float newInfoAlpha = getFinalInfoAlpha();
if (newInfoAlpha != mInfoAlpha) {
mInfoAlpha = newInfoAlpha;
for (int i = 0; i < mInfoViewList.size(); i++) {
mInfoViewList.get(i).setAlpha(mInfoAlpha);
}
}
}
}
final float getFinalInfoVisFraction() {
return mCardType == CARD_TYPE_INFO_UNDER && mInfoVisibility == CARD_REGION_VISIBLE_SELECTED
&& !isSelected() ? 0.0f : 1.0f;
}
final float getFinalInfoAlpha() {
return mCardType == CARD_TYPE_INFO_OVER && mInfoVisibility == CARD_REGION_VISIBLE_SELECTED
&& !isSelected() ? 0.0f : 1.0f;
}
/**
* Returns the visibility of the info region of the card.
*/
public int getInfoVisibility() {
return mInfoVisibility;
}
/**
* Sets the visibility of the extra region of the card.
*
* @param visibility The region visibility to use for the extra region. Must
* be one of {@link #CARD_REGION_VISIBLE_ALWAYS},
* {@link #CARD_REGION_VISIBLE_SELECTED}, or
* {@link #CARD_REGION_VISIBLE_ACTIVATED}.
* @deprecated Extra view's visibility is controlled by {@link #setInfoVisibility(int)}
*/
@Deprecated
public void setExtraVisibility(int visibility) {
if (mExtraVisibility != visibility) {
mExtraVisibility = visibility;
}
}
/**
* Returns the visibility of the extra region of the card.
* @deprecated Extra view's visibility is controlled by {@link #getInfoVisibility()}
*/
@Deprecated
public int getExtraVisibility() {
return mExtraVisibility;
}
/**
* Sets the Activated state of this Card. This can trigger changes in the
* card layout, resulting in views to become visible or hidden. A card is
* normally set to Activated state when its parent container (like a Row)
* receives focus, and then activates all of its children.
*
* @param activated True if the card is ACTIVE, or false if INACTIVE.
* @see #isActivated()
*/
@Override
public void setActivated(boolean activated) {
if (activated != isActivated()) {
super.setActivated(activated);
applyActiveState();
}
}
/**
* Sets the Selected state of this Card. This can trigger changes in the
* card layout, resulting in views to become visible or hidden. A card is
* normally set to Selected state when it receives input focus.
*
* @param selected True if the card is Selected, or false otherwise.
* @see #isSelected()
*/
@Override
public void setSelected(boolean selected) {
if (selected != isSelected()) {
super.setSelected(selected);
applySelectedState(isSelected());
}
}
@Override
public boolean shouldDelayChildPressedState() {
return false;
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
mMeasuredWidth = 0;
mMeasuredHeight = 0;
int state = 0;
int mainHeight = 0;
int infoHeight = 0;
int extraHeight = 0;
findChildrenViews();
final int unspecifiedSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
// MAIN is always present
for (int i = 0; i < mMainViewList.size(); i++) {
View mainView = mMainViewList.get(i);
if (mainView.getVisibility() != View.GONE) {
measureChild(mainView, unspecifiedSpec, unspecifiedSpec);
mMeasuredWidth = Math.max(mMeasuredWidth, mainView.getMeasuredWidth());
mainHeight += mainView.getMeasuredHeight();
state = View.combineMeasuredStates(state, mainView.getMeasuredState());
}
}
setPivotX(mMeasuredWidth / 2);
setPivotY(mainHeight / 2);
// The MAIN area determines the card width
int cardWidthMeasureSpec = MeasureSpec.makeMeasureSpec(mMeasuredWidth, MeasureSpec.EXACTLY);
if (hasInfoRegion()) {
for (int i = 0; i < mInfoViewList.size(); i++) {
View infoView = mInfoViewList.get(i);
if (infoView.getVisibility() != View.GONE) {
measureChild(infoView, cardWidthMeasureSpec, unspecifiedSpec);
if (mCardType != CARD_TYPE_INFO_OVER) {
infoHeight += infoView.getMeasuredHeight();
}
state = View.combineMeasuredStates(state, infoView.getMeasuredState());
}
}
if (hasExtraRegion()) {
for (int i = 0; i < mExtraViewList.size(); i++) {
View extraView = mExtraViewList.get(i);
if (extraView.getVisibility() != View.GONE) {
measureChild(extraView, cardWidthMeasureSpec, unspecifiedSpec);
extraHeight += extraView.getMeasuredHeight();
state = View.combineMeasuredStates(state, extraView.getMeasuredState());
}
}
}
}
boolean infoAnimating = hasInfoRegion() && mInfoVisibility == CARD_REGION_VISIBLE_SELECTED;
mMeasuredHeight = (int) (mainHeight
+ (infoAnimating ? (infoHeight * mInfoVisFraction) : infoHeight)
+ extraHeight - (infoAnimating ? 0 : mInfoOffset));
// Report our final dimensions.
setMeasuredDimension(View.resolveSizeAndState(mMeasuredWidth + getPaddingLeft()
+ getPaddingRight(), widthMeasureSpec, state),
View.resolveSizeAndState(mMeasuredHeight + getPaddingTop() + getPaddingBottom(),
heightMeasureSpec, state << View.MEASURED_HEIGHT_STATE_SHIFT));
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
float currBottom = getPaddingTop();
// MAIN is always present
for (int i = 0; i < mMainViewList.size(); i++) {
View mainView = mMainViewList.get(i);
if (mainView.getVisibility() != View.GONE) {
mainView.layout(getPaddingLeft(),
(int) currBottom,
mMeasuredWidth + getPaddingLeft(),
(int) (currBottom + mainView.getMeasuredHeight()));
currBottom += mainView.getMeasuredHeight();
}
}
if (hasInfoRegion()) {
float infoHeight = 0f;
for (int i = 0; i < mInfoViewList.size(); i++) {
infoHeight += mInfoViewList.get(i).getMeasuredHeight();
}
if (mCardType == CARD_TYPE_INFO_OVER) {
// retract currBottom to overlap the info views on top of main
currBottom -= infoHeight;
if (currBottom < 0) {
currBottom = 0;
}
} else if (mCardType == CARD_TYPE_INFO_UNDER) {
if (mInfoVisibility == CARD_REGION_VISIBLE_SELECTED) {
infoHeight = infoHeight * mInfoVisFraction;
}
} else {
currBottom -= mInfoOffset;
}
for (int i = 0; i < mInfoViewList.size(); i++) {
View infoView = mInfoViewList.get(i);
if (infoView.getVisibility() != View.GONE) {
int viewHeight = infoView.getMeasuredHeight();
if (viewHeight > infoHeight) {
viewHeight = (int) infoHeight;
}
infoView.layout(getPaddingLeft(),
(int) currBottom,
mMeasuredWidth + getPaddingLeft(),
(int) (currBottom + viewHeight));
currBottom += viewHeight;
infoHeight -= viewHeight;
if (infoHeight <= 0) {
break;
}
}
}
if (hasExtraRegion()) {
for (int i = 0; i < mExtraViewList.size(); i++) {
View extraView = mExtraViewList.get(i);
if (extraView.getVisibility() != View.GONE) {
extraView.layout(getPaddingLeft(),
(int) currBottom,
mMeasuredWidth + getPaddingLeft(),
(int) (currBottom + extraView.getMeasuredHeight()));
currBottom += extraView.getMeasuredHeight();
}
}
}
}
// Force update drawable bounds.
onSizeChanged(0, 0, right - left, bottom - top);
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
removeCallbacks(mAnimationTrigger);
cancelAnimations();
}
private boolean hasInfoRegion() {
return mCardType != CARD_TYPE_MAIN_ONLY;
}
private boolean hasExtraRegion() {
return mCardType == CARD_TYPE_INFO_UNDER_WITH_EXTRA;
}
/**
* Returns target visibility of info region.
*/
private boolean isRegionVisible(int regionVisibility) {
switch (regionVisibility) {
case CARD_REGION_VISIBLE_ALWAYS:
return true;
case CARD_REGION_VISIBLE_ACTIVATED:
return isActivated();
case CARD_REGION_VISIBLE_SELECTED:
return isSelected();
default:
if (DEBUG) Log.e(TAG, "invalid region visibility state: " + regionVisibility);
return false;
}
}
/**
* Unlike isRegionVisible(), this method returns true when it is fading out when unselected.
*/
private boolean isCurrentRegionVisible(int regionVisibility) {
switch (regionVisibility) {
case CARD_REGION_VISIBLE_ALWAYS:
return true;
case CARD_REGION_VISIBLE_ACTIVATED:
return isActivated();
case CARD_REGION_VISIBLE_SELECTED:
if (mCardType == CARD_TYPE_INFO_UNDER) {
return mInfoVisFraction > 0f;
} else {
return isSelected();
}
default:
if (DEBUG) Log.e(TAG, "invalid region visibility state: " + regionVisibility);
return false;
}
}
private void findChildrenViews() {
mMainViewList.clear();
mInfoViewList.clear();
mExtraViewList.clear();
final int count = getChildCount();
boolean infoVisible = hasInfoRegion() && isCurrentRegionVisible(mInfoVisibility);
boolean extraVisible = hasExtraRegion() && mInfoOffset > 0f;
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
if (child == null) {
continue;
}
BaseCardView.LayoutParams lp = (BaseCardView.LayoutParams) child
.getLayoutParams();
if (lp.viewType == LayoutParams.VIEW_TYPE_INFO) {
child.setAlpha(mInfoAlpha);
mInfoViewList.add(child);
child.setVisibility(infoVisible ? View.VISIBLE : View.GONE);
} else if (lp.viewType == LayoutParams.VIEW_TYPE_EXTRA) {
mExtraViewList.add(child);
child.setVisibility(extraVisible ? View.VISIBLE : View.GONE);
} else {
// Default to MAIN
mMainViewList.add(child);
child.setVisibility(View.VISIBLE);
}
}
}
@Override
protected int[] onCreateDrawableState(int extraSpace) {
// filter out focus states, since leanback does not fade foreground on focus.
final int[] s = super.onCreateDrawableState(extraSpace);
final int N = s.length;
boolean pressed = false;
boolean enabled = false;
for (int i = 0; i < N; i++) {
if (s[i] == android.R.attr.state_pressed) {
pressed = true;
}
if (s[i] == android.R.attr.state_enabled) {
enabled = true;
}
}
if (pressed && enabled) {
return View.PRESSED_ENABLED_STATE_SET;
} else if (pressed) {
return LB_PRESSED_STATE_SET;
} else if (enabled) {
return View.ENABLED_STATE_SET;
} else {
return View.EMPTY_STATE_SET;
}
}
private void applyActiveState() {
if (hasInfoRegion() && mInfoVisibility == CARD_REGION_VISIBLE_ACTIVATED) {
setInfoViewVisibility(isRegionVisible(mInfoVisibility));
}
}
private void setInfoViewVisibility(boolean visible) {
if (mCardType == CARD_TYPE_INFO_UNDER_WITH_EXTRA) {
// Active state changes for card type
// CARD_TYPE_INFO_UNDER_WITH_EXTRA
if (visible) {
for (int i = 0; i < mInfoViewList.size(); i++) {
mInfoViewList.get(i).setVisibility(View.VISIBLE);
}
} else {
for (int i = 0; i < mInfoViewList.size(); i++) {
mInfoViewList.get(i).setVisibility(View.GONE);
}
for (int i = 0; i < mExtraViewList.size(); i++) {
mExtraViewList.get(i).setVisibility(View.GONE);
}
mInfoOffset = 0.0f;
}
} else if (mCardType == CARD_TYPE_INFO_UNDER) {
// Active state changes for card type CARD_TYPE_INFO_UNDER
if (mInfoVisibility == CARD_REGION_VISIBLE_SELECTED) {
animateInfoHeight(visible);
} else {
for (int i = 0; i < mInfoViewList.size(); i++) {
mInfoViewList.get(i).setVisibility(visible ? View.VISIBLE : View.GONE);
}
}
} else if (mCardType == CARD_TYPE_INFO_OVER) {
// Active state changes for card type CARD_TYPE_INFO_OVER
animateInfoAlpha(visible);
}
}
private void applySelectedState(boolean focused) {
removeCallbacks(mAnimationTrigger);
if (mCardType == CARD_TYPE_INFO_UNDER_WITH_EXTRA) {
// Focus changes for card type CARD_TYPE_INFO_UNDER_WITH_EXTRA
if (focused) {
if (!mDelaySelectedAnim) {
post(mAnimationTrigger);
mDelaySelectedAnim = true;
} else {
postDelayed(mAnimationTrigger, mSelectedAnimationDelay);
}
} else {
animateInfoOffset(false);
}
} else if (mInfoVisibility == CARD_REGION_VISIBLE_SELECTED) {
setInfoViewVisibility(focused);
}
}
void cancelAnimations() {
if (mAnim != null) {
mAnim.cancel();
mAnim = null;
// force-clear the animation, as Animation#cancel() doesn't work prior to N,
// and will instead cause the animation to infinitely loop
clearAnimation();
}
}
// This animation changes the Y offset of the info and extra views,
// so that they animate UP to make the extra info area visible when a
// card is selected.
void animateInfoOffset(boolean shown) {
cancelAnimations();
int extraHeight = 0;
if (shown) {
int widthSpec = MeasureSpec.makeMeasureSpec(mMeasuredWidth, MeasureSpec.EXACTLY);
int heightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
for (int i = 0; i < mExtraViewList.size(); i++) {
View extraView = mExtraViewList.get(i);
extraView.setVisibility(View.VISIBLE);
extraView.measure(widthSpec, heightSpec);
extraHeight = Math.max(extraHeight, extraView.getMeasuredHeight());
}
}
mAnim = new InfoOffsetAnimation(mInfoOffset, shown ? extraHeight : 0);
mAnim.setDuration(mSelectedAnimDuration);
mAnim.setInterpolator(new AccelerateDecelerateInterpolator());
mAnim.setAnimationListener(new Animation.AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {
}
@Override
public void onAnimationEnd(Animation animation) {
if (mInfoOffset == 0f) {
for (int i = 0; i < mExtraViewList.size(); i++) {
mExtraViewList.get(i).setVisibility(View.GONE);
}
}
}
@Override
public void onAnimationRepeat(Animation animation) {
}
});
startAnimation(mAnim);
}
// This animation changes the visible height of the info views,
// so that they animate in and out of view.
private void animateInfoHeight(boolean shown) {
cancelAnimations();
if (shown) {
for (int i = 0; i < mInfoViewList.size(); i++) {
View extraView = mInfoViewList.get(i);
extraView.setVisibility(View.VISIBLE);
}
}
float targetFraction = shown ? 1.0f : 0f;
if (mInfoVisFraction == targetFraction) {
return;
}
mAnim = new InfoHeightAnimation(mInfoVisFraction, targetFraction);
mAnim.setDuration(mSelectedAnimDuration);
mAnim.setInterpolator(new AccelerateDecelerateInterpolator());
mAnim.setAnimationListener(new Animation.AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {
}
@Override
public void onAnimationEnd(Animation animation) {
if (mInfoVisFraction == 0f) {
for (int i = 0; i < mInfoViewList.size(); i++) {
mInfoViewList.get(i).setVisibility(View.GONE);
}
}
}
@Override
public void onAnimationRepeat(Animation animation) {
}
});
startAnimation(mAnim);
}
// This animation changes the alpha of the info views, so they animate in
// and out. It's meant to be used when the info views are overlaid on top of
// the main view area. It gets triggered by a change in the Active state of
// the card.
private void animateInfoAlpha(boolean shown) {
cancelAnimations();
if (shown) {
for (int i = 0; i < mInfoViewList.size(); i++) {
mInfoViewList.get(i).setVisibility(View.VISIBLE);
}
}
float targetAlpha = shown ? 1.0f : 0.0f;
if (targetAlpha == mInfoAlpha) {
return;
}
mAnim = new InfoAlphaAnimation(mInfoAlpha, shown ? 1.0f : 0.0f);
mAnim.setDuration(mActivatedAnimDuration);
mAnim.setInterpolator(new DecelerateInterpolator());
mAnim.setAnimationListener(new Animation.AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {
}
@Override
public void onAnimationEnd(Animation animation) {
if (mInfoAlpha == 0.0) {
for (int i = 0; i < mInfoViewList.size(); i++) {
mInfoViewList.get(i).setVisibility(View.GONE);
}
}
}
@Override
public void onAnimationRepeat(Animation animation) {
}
});
startAnimation(mAnim);
}
@Override
public LayoutParams generateLayoutParams(AttributeSet attrs) {
return new BaseCardView.LayoutParams(getContext(), attrs);
}
@Override
protected LayoutParams generateDefaultLayoutParams() {
return new BaseCardView.LayoutParams(
LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
}
@Override
protected LayoutParams generateLayoutParams(ViewGroup.LayoutParams lp) {
if (lp instanceof LayoutParams) {
return new LayoutParams((LayoutParams) lp);
} else {
return new LayoutParams(lp);
}
}
@Override
protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
return p instanceof BaseCardView.LayoutParams;
}
/**
* Per-child layout information associated with BaseCardView.
*/
public static class LayoutParams extends FrameLayout.LayoutParams {
public static final int VIEW_TYPE_MAIN = 0;
public static final int VIEW_TYPE_INFO = 1;
public static final int VIEW_TYPE_EXTRA = 2;
/**
* Card component type for the view associated with these LayoutParams.
*/
@ViewDebug.ExportedProperty(category = "layout", mapping = {
@ViewDebug.IntToString(from = VIEW_TYPE_MAIN, to = "MAIN"),
@ViewDebug.IntToString(from = VIEW_TYPE_INFO, to = "INFO"),
@ViewDebug.IntToString(from = VIEW_TYPE_EXTRA, to = "EXTRA")
})
public int viewType = VIEW_TYPE_MAIN;
/**
* {@inheritDoc}
*/
@SuppressLint("CustomViewStyleable")
public LayoutParams(Context c, AttributeSet attrs) {
super(c, attrs);
TypedArray a = c.obtainStyledAttributes(attrs, R.styleable.lbBaseCardView_Layout);
viewType = a.getInt(
R.styleable.lbBaseCardView_Layout_layout_viewType, VIEW_TYPE_MAIN);
a.recycle();
}
/**
* {@inheritDoc}
*/
public LayoutParams(int width, int height) {
super(width, height);
}
/**
* {@inheritDoc}
*/
public LayoutParams(ViewGroup.LayoutParams p) {
super(p);
}
/**
* Copy constructor. Clones the width, height, and View Type of the
* source.
*
* @param source The layout params to copy from.
*/
public LayoutParams(LayoutParams source) {
super((ViewGroup.MarginLayoutParams) source);
this.viewType = source.viewType;
}
}
class AnimationBase extends Animation {
@VisibleForTesting
final void mockStart() {
getTransformation(0, null);
}
@VisibleForTesting
final void mockEnd() {
applyTransformation(1f, null);
cancelAnimations();
}
}
// Helper animation class used in the animation of the info and extra
// fields vertically within the card
final class InfoOffsetAnimation extends AnimationBase {
private float mStartValue;
private float mDelta;
public InfoOffsetAnimation(float start, float end) {
mStartValue = start;
mDelta = end - start;
}
@Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
mInfoOffset = mStartValue + (interpolatedTime * mDelta);
requestLayout();
}
}
// Helper animation class used in the animation of the visible height
// for the info fields.
final class InfoHeightAnimation extends AnimationBase {
private float mStartValue;
private float mDelta;
public InfoHeightAnimation(float start, float end) {
mStartValue = start;
mDelta = end - start;
}
@Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
mInfoVisFraction = mStartValue + (interpolatedTime * mDelta);
requestLayout();
}
}
// Helper animation class used to animate the alpha for the info views
// when they are fading in or out of view.
final class InfoAlphaAnimation extends AnimationBase {
private float mStartValue;
private float mDelta;
public InfoAlphaAnimation(float start, float end) {
mStartValue = start;
mDelta = end - start;
}
@Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
mInfoAlpha = mStartValue + (interpolatedTime * mDelta);
for (int i = 0; i < mInfoViewList.size(); i++) {
mInfoViewList.get(i).setAlpha(mInfoAlpha);
}
}
}
@Override
public String toString() {
if (DEBUG) {
StringBuilder sb = new StringBuilder();
sb.append(this.getClass().getSimpleName()).append(" : ");
sb.append("cardType=");
switch(mCardType) {
case CARD_TYPE_MAIN_ONLY:
sb.append("MAIN_ONLY");
break;
case CARD_TYPE_INFO_OVER:
sb.append("INFO_OVER");
break;
case CARD_TYPE_INFO_UNDER:
sb.append("INFO_UNDER");
break;
case CARD_TYPE_INFO_UNDER_WITH_EXTRA:
sb.append("INFO_UNDER_WITH_EXTRA");
break;
default:
sb.append("INVALID");
break;
}
sb.append(" : ");
sb.append(mMainViewList.size()).append(" main views, ");
sb.append(mInfoViewList.size()).append(" info views, ");
sb.append(mExtraViewList.size()).append(" extra views : ");
sb.append("infoVisibility=").append(mInfoVisibility).append(" ");
sb.append("extraVisibility=").append(mExtraVisibility).append(" ");
sb.append("isActivated=").append(isActivated());
sb.append(" : ");
sb.append("isSelected=").append(isSelected());
return sb.toString();
} else {
return super.toString();
}
}
}