package com.bootstrap.widget; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.ValueAnimator; import android.content.Context; import android.content.res.TypedArray; import android.support.v7.widget.CardView; import android.support.v7.widget.RecyclerView; import android.util.AttributeSet; import android.view.View; import android.view.ViewGroup; import android.view.ViewParent; import android.view.animation.DecelerateInterpolator; import com.bootstrap.R; public class ExpandableCardView extends CardView { private boolean expanded, animating; private int targetId; private View target; private RecyclerView recyclerView; private StateChangedListener stateChangedListener; public ExpandableCardView(final Context context) { super(context); init(context, null); } public ExpandableCardView(final Context context, final AttributeSet attrs) { super(context, attrs); init(context, attrs); } public ExpandableCardView(final Context context, final AttributeSet attrs, final int defStyleAttr) { super(context, attrs, defStyleAttr); init(context, attrs); } public void expand(final boolean withAnimation) { if (animating || expanded) { return; } expanded = true; if (withAnimation) { animateExpanding(); } else { final ViewGroup.LayoutParams layoutParams = target.getLayoutParams(); layoutParams.height = ViewGroup.LayoutParams.WRAP_CONTENT; target.setLayoutParams(layoutParams); target.setVisibility(View.VISIBLE); } if (stateChangedListener != null) { stateChangedListener.onStateChanged(this, true); } } public void collapse(final boolean withAnimation) { if (animating || !expanded) { return; } expanded = false; if (withAnimation) { animateCollapsing(); } else { target.setVisibility(View.GONE); } if (stateChangedListener != null) { stateChangedListener.onStateChanged(this, false); } } public void setTargetId(final int targetId) { this.targetId = targetId; } public void setStateChangedListener(final StateChangedListener stateChangedListener) { this.stateChangedListener = stateChangedListener; } private void init(final Context context, final AttributeSet attrs) { if (attrs != null) { final TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.ExpandableCardView); targetId = array.getResourceId(R.styleable.ExpandableCardView_cardTarget, 0); expanded = array.getBoolean(R.styleable.ExpandableCardView_cardExpanded, false); array.recycle(); } setOnClickListener(new OnClickListener() { @Override public void onClick(final View view) { if (expanded) { collapse(true); } else { expand(true); } } }); } private static ValueAnimator createHeightAnimator(final View view, final int start, final int end) { final ValueAnimator animator = ValueAnimator.ofInt(start, end); animator.setInterpolator(new DecelerateInterpolator()); animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(final ValueAnimator valueAnimator) { final int value = (Integer) valueAnimator.getAnimatedValue(); final ViewGroup.LayoutParams layoutParams = view.getLayoutParams(); layoutParams.height = value; view.setLayoutParams(layoutParams); } }); return animator; } private void animateExpanding() { target.setVisibility(View.VISIBLE); final View parent = (View) target.getParent(); final int widthSpec = View.MeasureSpec.makeMeasureSpec( parent.getMeasuredWidth() - parent.getPaddingLeft() - parent.getPaddingRight(), View.MeasureSpec.AT_MOST); final int heightSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED); target.measure(widthSpec, heightSpec); final ValueAnimator animator = createHeightAnimator(target, 0, target.getMeasuredHeight()); animator.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationStart(final Animator animation) { animating = true; } @Override public void onAnimationEnd(final Animator animation) { animating = false; } }); if (recyclerView != null) { final int recyclerViewHeight = recyclerView.getHeight(); final int recyclerViewBottomPadding = recyclerView.getPaddingBottom(); animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(final ValueAnimator animation) { if (recyclerView.getLayoutManager().canScrollVertically()) { final int bottom = getBottom(); if (bottom > recyclerViewHeight) { final int top = getTop(); if (top > 0) { recyclerView.smoothScrollBy(0, Math.min(bottom - recyclerViewHeight + recyclerViewBottomPadding + 4, top)); } } } } }); } animator.start(); } private void animateCollapsing() { final int originalHeight = target.getHeight(); final ValueAnimator animator = createHeightAnimator(target, originalHeight, 0); animator.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationStart(final Animator animation) { animating = true; } @Override public void onAnimationEnd(final Animator animator) { target.setVisibility(View.GONE); animating = false; } }); animator.start(); } @Override protected void onFinishInflate() { super.onFinishInflate(); target = findViewById(targetId); target.setVisibility(expanded ? View.VISIBLE : View.GONE); post(new Runnable() { @Override public void run() { ViewParent parent; do { parent = getParent(); if (parent instanceof RecyclerView) { recyclerView = (RecyclerView) parent; break; } } while (parent == null); } }); } public interface StateChangedListener { void onStateChanged(final View view, final boolean expanded); } }