我正在创建一个问答,每一个问题都是一张卡片。答案开始显示第一行,但当点击它时,它应该展开以显示完整的答案。
当一个答案被展开/折叠时,RecyclerView的其余部分应该是动画的,以便为展开或折叠腾出空间,以避免显示空白。
我在RecyclerView动画上观看了演讲,并且相信我想要一个定制的ItemAnimator,在那里我重写了animateChange。此时,我应该创建一个ObjectAnimator来动画视图的LayoutParams的高度。不幸的是,我很难把这一切绑在一起。在重写canReuseUpdatedViewHolder时,我还返回true,因此我们重用相同的viewholder。
@Override
public boolean canReuseUpdatedViewHolder(RecyclerView.ViewHolder viewHolder) {
return true;
}
@Override
public boolean animateChange(@NonNull RecyclerView.ViewHolder oldHolder,
@NonNull final RecyclerView.ViewHolder newHolder,
@NonNull ItemHolderInfo preInfo,
@NonNull ItemHolderInfo postInfo) {
Log.d("test", "Run custom animation.");
final ColorsAdapter.ColorViewHolder holder = (ColorsAdapter.ColorViewHolder) newHolder;
FrameLayout.LayoutParams params = (FrameLayout.LayoutParams) holder.tvColor.getLayoutParams();
ObjectAnimator halfSize = ObjectAnimator.ofInt(holder.tvColor.getLayoutParams(), "height", params.height, 0);
halfSize.start();
return super.animateChange(oldHolder, newHolder, preInfo, postInfo);
}
现在我只是想弄点动画,但什么都没发生.有什么想法吗?
发布于 2016-02-11 06:05:24
我认为您的动画没有工作,因为您不能这样动画LayoutParams
,虽然它将是整洁的,如果你可以。我试过了你的代码,它所做的只是让我的观点跳到新的高度。我发现让它工作的唯一方法是使用ValueAnimator
,如您在下面的示例中所看到的。
在使用DefaultItemAnimator
通过更新视图的可见性来显示/隐藏视图时,我注意到了一些缺点。虽然它确实为新视图腾出了空间,并根据可扩展视图的可见性将其余的项目上下动画化,但我注意到它并没有显示可扩展视图的高度。它只是简单地消失在适当的位置和不合适的地方,只使用alpha值。
下面是一个定制的ItemAnimator
,它具有基于隐藏/显示ViewHolder
布局中的LinearLayout
的大小和alpha动画。它还允许重用相同的ViewHolder
,并在用户快速点击头部时尝试正确处理部分动画:
public static class MyAnimator extends DefaultItemAnimator {
@Override
public boolean canReuseUpdatedViewHolder(RecyclerView.ViewHolder viewHolder) {
return true;
}
private HashMap<RecyclerView.ViewHolder, AnimatorState> animatorMap = new HashMap<>();
@Override
public boolean animateChange(@NonNull RecyclerView.ViewHolder oldHolder, @NonNull final RecyclerView.ViewHolder newHolder, @NonNull ItemHolderInfo preInfo, @NonNull ItemHolderInfo postInfo) {
final ValueAnimator heightAnim;
final ObjectAnimator alphaAnim;
final CustomAdapter.ViewHolder vh = (CustomAdapter.ViewHolder) newHolder;
final View expandableView = vh.getExpandableView();
final int toHeight; // save height for later in case reversing animation
if(vh.isExpanded()) {
expandableView.setVisibility(View.VISIBLE);
// measure expandable view to get correct height
expandableView.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED);
toHeight = expandableView.getMeasuredHeight();
alphaAnim = ObjectAnimator.ofFloat(expandableView, "alpha", 1f);
} else {
toHeight = 0;
alphaAnim = ObjectAnimator.ofFloat(expandableView, "alpha", 0f);
}
heightAnim = ValueAnimator.ofInt(expandableView.getHeight(), toHeight);
heightAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
expandableView.getLayoutParams().height = (Integer) heightAnim.getAnimatedValue();
expandableView.requestLayout();
}
});
AnimatorSet animSet = new AnimatorSet()
.setDuration(getChangeDuration());
animSet.playTogether(heightAnim, alphaAnim);
animSet.addListener(new Animator.AnimatorListener() {
private boolean isCanceled;
@Override
public void onAnimationStart(Animator animation) { }
@Override
public void onAnimationEnd(Animator animation) {
if(!vh.isExpanded() && !isCanceled) {
expandableView.setVisibility(View.GONE);
}
dispatchChangeFinished(vh, false);
animatorMap.remove(newHolder);
}
@Override
public void onAnimationCancel(Animator animation) {
isCanceled = true;
}
@Override
public void onAnimationRepeat(Animator animation) { }
});
AnimatorState animatorState = animatorMap.get(newHolder);
if(animatorState != null) {
animatorState.animSet.cancel();
// animation already running. Set start current play time of
// new animations to keep them smooth for reverse animation
alphaAnim.setCurrentPlayTime(animatorState.alphaAnim.getCurrentPlayTime());
heightAnim.setCurrentPlayTime(animatorState.heightAnim.getCurrentPlayTime());
animatorMap.remove(newHolder);
}
animatorMap.put(newHolder, new AnimatorState(alphaAnim, heightAnim, animSet));
dispatchChangeStarting(newHolder, false);
animSet.start();
return false;
}
public static class AnimatorState {
final ValueAnimator alphaAnim, heightAnim;
final AnimatorSet animSet;
public AnimatorState(ValueAnimator alphaAnim, ValueAnimator heightAnim, AnimatorSet animSet) {
this.alphaAnim = alphaAnim;
this.heightAnim = heightAnim;
this.animSet = animSet;
}
}
}
这是使用稍微修改过的RecyclerView
演示的结果。
更新:
刚刚注意到,在重新阅读这个问题之后,您的用例实际上有点不同。您有一个文本视图,只想显示其中的一行,然后展开它以显示所有行。幸运的是,这简化了自定义动画器:
public static class MyAnimator extends DefaultItemAnimator {
@Override
public boolean canReuseUpdatedViewHolder(RecyclerView.ViewHolder viewHolder) {
return true;
}
private HashMap<RecyclerView.ViewHolder, ValueAnimator> animatorMap = new HashMap<>();
@Override
public boolean animateChange(@NonNull RecyclerView.ViewHolder oldHolder, @NonNull final RecyclerView.ViewHolder newHolder, @NonNull ItemHolderInfo preInfo, @NonNull ItemHolderInfo postInfo) {
ValueAnimator prevAnim = animatorMap.get(newHolder);
if(prevAnim != null) {
prevAnim.reverse();
return false;
}
final ValueAnimator heightAnim;
final CustomAdapter.ViewHolder vh = (CustomAdapter.ViewHolder) newHolder;
final TextView tv = vh.getExpandableTextView();
if(vh.isExpanded()) {
tv.measure(View.MeasureSpec.makeMeasureSpec(((View) tv.getParent()).getWidth(), View.MeasureSpec.AT_MOST), View.MeasureSpec.UNSPECIFIED);
heightAnim = ValueAnimator.ofInt(tv.getHeight(), tv.getMeasuredHeight());
} else {
Paint.FontMetrics fm = tv.getPaint().getFontMetrics();
heightAnim = ValueAnimator.ofInt(tv.getHeight(), (int)(Math.abs(fm.top) + Math.abs(fm.bottom)));
}
heightAnim.setDuration(getChangeDuration());
heightAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
tv.getLayoutParams().height = (Integer) heightAnim.getAnimatedValue();
tv.requestLayout();
}
});
heightAnim.addListener(new Animator.AnimatorListener() {
@Override
public void onAnimationEnd(Animator animation) {
dispatchChangeFinished(vh, false);
animatorMap.remove(newHolder);
}
@Override
public void onAnimationCancel(Animator animation) { }
@Override
public void onAnimationStart(Animator animation) { }
@Override
public void onAnimationRepeat(Animator animation) { }
});
animatorMap.put(newHolder, heightAnim);
dispatchChangeStarting(newHolder, false);
heightAnim.start();
return false;
}
}
新的演示:
发布于 2016-02-08 20:24:06
您不必实现自定义ItemAnimator
,默认的DefaultItemAnimator
已经支持所需的内容。但是,您需要告诉这个动画家,哪些视图发生了变化。我想您是在适配器中调用notifyDataSetChanged()
。这将防止RecyclerView中单个已更改项的动画(在您的示例中是项的展开/折叠)。
对于已更改的项,应使用notifyItemChanged(int position)
。下面是一个简短的itemClicked(int position)
方法,它扩展/折叠RecyclerView中的视图。字段expandedPosition
跟踪当前展开的项:
private void itemClicked(int position) {
if (expandedPosition == -1) {
// selected first item
expandedPosition = position;
notifyItemChanged(position);
} else if (expandedPosition == position) {
// collapse currently expanded item
expandedPosition = -1;
notifyItemChanged(position);
} else {
// collapse previously expanded item and expand new item
int oldExpanded = expandedPosition;
expandedPosition = position;
notifyItemChanged(oldExpanded);
notifyItemChanged(position);
}
}
其结果是:
发布于 2016-02-04 17:45:04
根据文档,您需要在animateChange中返回false或稍后调用runPendingAnimations。试试看还假。
http://developer.android.com/reference/android/support/v7/widget/RecyclerView.ItemAnimator.html
https://stackoverflow.com/questions/35207407
复制相似问题