/*
* 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 android.support.v17.leanback.widget;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.drawable.Drawable;
import android.support.v17.leanback.R;
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 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.
*
*
* 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}.
*
* See {@link BaseCardView.LayoutParams} for layout attributes.
*
*/
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 mMainViewList;
private ArrayList mInfoViewList;
private ArrayList mExtraViewList;
private int mMeasuredWidth;
private int mMeasuredHeight;
private boolean mDelaySelectedAnim;
private int mSelectedAnimationDelay;
private final int mActivatedAnimDuration;
private final int mSelectedAnimDuration;
private float mInfoOffset;
private float mInfoVisFraction;
private float mInfoAlpha = 1.0f;
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);
}
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();
mInfoViewList = new ArrayList();
mExtraViewList = new ArrayList();
mInfoOffset = 0.0f;
mInfoVisFraction = 0.0f;
}
/**
* 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) {
mInfoVisibility = visibility;
if (mInfoVisibility == CARD_REGION_VISIBLE_SELECTED && isSelected()) {
mInfoVisFraction = 1.0f;
} else {
mInfoVisFraction = 0.0f;
}
requestLayout();
}
}
/**
* 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}.
*/
public void setExtraVisibility(int visibility) {
if (mExtraVisibility != visibility) {
mExtraVisibility = visibility;
requestLayout();
}
}
/**
* Returns the visibility of the extra region of the card.
*/
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(isActivated());
}
}
/**
* 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();
mInfoOffset = 0.0f;
mInfoVisFraction = 0.0f;
}
private boolean hasInfoRegion() {
return mCardType != CARD_TYPE_MAIN_ONLY;
}
private boolean hasExtraRegion() {
return mCardType == CARD_TYPE_INFO_UNDER_WITH_EXTRA;
}
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 isActivated() && 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 = isRegionVisible(mInfoVisibility);
boolean extraVisible = hasExtraRegion() && mInfoOffset > 0f;
if (mCardType == CARD_TYPE_INFO_UNDER && mInfoVisibility == CARD_REGION_VISIBLE_SELECTED) {
infoVisible = infoVisible && mInfoVisFraction > 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) {
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(boolean active) {
if (hasInfoRegion() && mInfoVisibility <= CARD_REGION_VISIBLE_ACTIVATED) {
setInfoViewVisibility(active);
}
if (hasExtraRegion() && mExtraVisibility <= CARD_REGION_VISIBLE_ACTIVATED) {
//setExtraVisibility(active);
}
}
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);
}
}
private void cancelAnimations() {
if (mAnim != null) {
mAnim.cancel();
mAnim = null;
}
}
// 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.
private 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();
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 InfoHeightAnimation(mInfoVisFraction, shown ? 1.0f : 0f);
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 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);
}
}
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}
*/
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(source);
this.viewType = source.viewType;
}
}
// Helper animation class used in the animation of the info and extra
// fields vertically within the card
private class InfoOffsetAnimation extends Animation {
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.
private class InfoHeightAnimation extends Animation {
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.
private class InfoAlphaAnimation extends Animation {
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();
}
}
}