对RecyclerView Item做动画,刚刚开始研究的时候一些坑,在这里把一些设计思路分享出去: 添加动态位移,静态位移,缩放等动画,保证了动画状态的平滑衔接。
效果图:
RecyclerView,ListView这些具有Item复用性的View,想要对其Item做动画
需要注意以下几点:
如果要一点击,让所有Item做动画的效果。例如,上图的编辑和取消,这样的动态动画。可以对所有ViewHolder中的View直接做动画。但是需要在onBindViewHolder方法中对复用的item做静态动画,保证动画状态的平滑衔接。
每一个Item的特有属性,例如,上图checkbox的选中状态,都需要把状态字段放到对应的Java bean中, 并在onBindViewHolder方法从java bean取出状态值,设置到view里。
首先,对一些细节进行分析。 如何设计一个自定义View,来让他可以自己移动,做动画起来?
1、首先,创建一个View,他是RecyclerView Item的根布局:
public class SlideRelativeLayout extends RelativeLayout {
public static final String TAG = SlideRelativeLayout.class.getSimpleName();
private CheckBox mCheckBox;
private RelativeLayout mContentSlide;
private int mOffset;
public SlideRelativeLayout(Context context) {
super(context);
}
public SlideRelativeLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
public SlideRelativeLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
mCheckBox = (CheckBox) findViewById(R.id.item_checkbox);
mCOntentSlide= (RelativeLayout) findViewById(R.id.item_content_rl);
setOffset(35);
}
public void setOffset(int offset) {
mOffset = (int) (getContext().getResources().getDisplayMetrics().density * offset + 0.5f);
}
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
public void openAnimation() {
ValueAnimator valueAnimator = new ValueAnimator();
valueAnimator.setIntValues(0, 1);
valueAnimator.setDuration(300);
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@TargetApi(Build.VERSION_CODES.HONEYCOMB_MR1)
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
float fraction = valueAnimator.getAnimatedFraction();
int endX = (int) (-mOffset * fraction);
doAnimationSet(endX, fraction);
}
});
valueAnimator.start();
}
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
public void closeAnimation() {
ValueAnimator valueAnimator = new ValueAnimator();
valueAnimator.setIntValues(0, 1);
valueAnimator.setDuration(150);
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@TargetApi(Build.VERSION_CODES.HONEYCOMB_MR1)
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
float fraction = valueAnimator.getAnimatedFraction();
int endX = (int) (-mOffset * (1 - fraction));
doAnimationSet(endX, (1 - fraction));
}
});
valueAnimator.start();
}
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
private void doAnimationSet(int dx, float fraction) {
mContentSlide.scrollTo(dx, 0);
mCheckBox.setScaleX(fraction);
mCheckBox.setScaleY(fraction);
mCheckBox.setAlpha(fraction * 255);
}
public void open() {
mContentSlide.scrollTo(-mOffset, 0);
}
public void close() {
mContentSlide.scrollTo(0, 0);
}
}
这里,在View树创建完毕之后找到我们需要做动画的子View:
@Override
protected void onFinishInflate() {
super.onFinishInflate();
mCheckBox = (CheckBox) findViewById(R.id.item_checkbox);
mCOntentSlide= (RelativeLayout) findViewById(R.id.item_content_rl);
setOffset(35);
}
然后,设计4个方法,分别为:动态的打开动画,动态的关闭动画,静态的打开动画,静态的关闭动画。
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
public void openAnimation() {
ValueAnimator valueAnimator = new ValueAnimator();
valueAnimator.setIntValues(0, 1);
valueAnimator.setDuration(300);
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@TargetApi(Build.VERSION_CODES.HONEYCOMB_MR1)
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
float fraction = valueAnimator.getAnimatedFraction();
int endX = (int) (-mOffset * fraction);
doAnimationSet(endX, fraction);
}
});
valueAnimator.start();
}
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
public void closeAnimation() {
ValueAnimator valueAnimator = new ValueAnimator();
valueAnimator.setIntValues(0, 1);
valueAnimator.setDuration(150);
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@TargetApi(Build.VERSION_CODES.HONEYCOMB_MR1)
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
float fraction = valueAnimator.getAnimatedFraction();
int endX = (int) (-mOffset * (1 - fraction));
doAnimationSet(endX, (1 - fraction));
}
});
valueAnimator.start();
}
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
private void doAnimationSet(int dx, float fraction) {
mContentSlide.scrollTo(dx, 0);
mCheckBox.setScaleX(fraction);
mCheckBox.setScaleY(fraction);
mCheckBox.setAlpha(fraction * 255);
}
public void open() {
mContentSlide.scrollTo(-mOffset, 0);
}
public void close() {
mContentSlide.scrollTo(0, 0);
}
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
private void doAnimationSet(int dx, float fraction) {
mContentSlide.scrollTo(dx, 0);
mCheckBox.setScaleX(fraction);
mCheckBox.setScaleY(fraction);
mCheckBox.setAlpha(fraction * 255);
}
对子View做动画我采取的策略是:使用属性动画,在每一贞动画里获取到对应的值,对子View做相应的动画,例如:动态的打开动画。
onAnimationUpdate方法,显示每一贞动画都会回调一次。
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
public void openAnimation() {
ValueAnimator valueAnimator = new ValueAnimator();
valueAnimator.setIntValues(0, 1);
valueAnimator.setDuration(300);
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@TargetApi(Build.VERSION_CODES.HONEYCOMB_MR1)
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
float fraction = valueAnimator.getAnimatedFraction();
int endX = (int) (-mOffset * fraction);
doAnimationSet(endX, fraction);
}
});
valueAnimator.start();
}
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
private void doAnimationSet(int dx, float fraction) {
mContentSlide.scrollTo(dx, 0);
mCheckBox.setScaleX(fraction);
mCheckBox.setScaleY(fraction);
mCheckBox.setAlpha(fraction * 255);
}
这样RecylerView 带有动态动画和静态动画的View就设计好了。
2、在bind方法中使用静态动画,动态动画对外提供方法调用:
private class SlideViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
private SlideRelativeLayout mSlideRelativeLayout;
private CheckBox mCheckBox;
private ItemBean mItemBean;
public SlideViewHolder(View itemView) {
super(itemView);
mSlideRelativeLayout = (SlideRelativeLayout) itemView.findViewById(R.id.item_root);
mCheckBox = (CheckBox) itemView.findViewById(R.id.item_checkbox);
itemView.setOnClickListener(this);
}
public void bind(ItemBean itemBean) {
mItemBean = itemBean;
mCheckBox.setChecked(itemBean.isChecked());
switch (mState) {
case NORMAL:
mSlideRelativeLayout.close();
break;
case SLIDE:
mSlideRelativeLayout.open();
break;
}
}
public void openItemAnimation() {
mSlideRelativeLayout.openAnimation();
}
public void closeItemAnimation() {
mSlideRelativeLayout.closeAnimation();
}
可以看到静态动画在bind里调用,打开或者关闭是由mState变量决定的。而动态的滑动需要手动调用,那怎么来使用这些动画呢?
动态动画的使用方法:存储所有创建出来的ViewHolder,统一调用动态动画方法。并设置mState变量值,防止滑动时动画不能平滑衔接。
private List mSlideViewHolders = new ArrayList<>();
public void openItemAnimation() {
mState = SLIDE;//
for (SlideViewHolder holder : mSlideViewHolders) {
holder.openItemAnimation();
}
}
public void closeItemAnimation() {
mState = NORMAL;
for (SlideViewHolder holder : mSlideViewHolders) {
holder.closeItemAnimation();
}
}
调用:
private void editItems() {
if ("编辑".equals(mRightTV.getText().toString())) {
mRightTV.setText("取消");
mSlideAdapter.openItemAnimation();
} else if ("取消".equals(mRightTV.getText().toString())) {
mRightTV.setText("编辑");
mSlideAdapter.closeItemAnimation();
}
}
总体就是:点击按钮 – 变量ViewHolder集合做动态动画,并设置mState变量 – 手机滑动屏幕走bind方法又是根据mState做静态动画。
动态动画起先,设置状态值,引导处理正确的静态动画,RecyclerView item的动画处理是不是变简单了。
还有,对item的特殊数据需要在对应的java bean里设置值,在bind方法取值设置到item中去。
最后贴出SlideAdapter的完整代码:
public class SlideAdapter extends RecyclerView.Adapter {
public static final int NORMAL = 1000;
public static final int SLIDE = 2000;
private int mState = NORMAL;
private List mItemBeans;
private List mSlideViewHolders = new ArrayList<>();
public void openItemAnimation() {
mState = SLIDE;
for (SlideViewHolder holder : mSlideViewHolders) {
holder.openItemAnimation();
}
}
public void closeItemAnimation() {
mState = NORMAL;
for (SlideViewHolder holder : mSlideViewHolders) {
holder.closeItemAnimation();
}
}
public void setItemBeans(List beans) {
mItemBeans = beans;
notifyDataSetChanged();
}
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
SlideViewHolder slideViewHolder = new SlideViewHolder(
LayoutInflater.from(parent.getContext()).inflate(R.layout.item, parent, false));
mSlideViewHolders.add(slideViewHolder);
return slideViewHolder;
}
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
((SlideViewHolder) holder).bind(mItemBeans.get(position));
}
@Override
public int getItemCount() {
return mItemBeans == null ? 0 : mItemBeans.size();
}
private class SlideViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
private SlideRelativeLayout mSlideRelativeLayout;
private CheckBox mCheckBox;
private ItemBean mItemBean;
public SlideViewHolder(View itemView) {
super(itemView);
mSlideRelativeLayout = (SlideRelativeLayout) itemView.findViewById(R.id.item_root);
mCheckBox = (CheckBox) itemView.findViewById(R.id.item_checkbox);
itemView.setOnClickListener(this);
}
public void bind(ItemBean itemBean) {
mItemBean = itemBean;
mCheckBox.setChecked(itemBean.isChecked());
switch (mState) {
case NORMAL:
mSlideRelativeLayout.close();
break;
case SLIDE:
mSlideRelativeLayout.open();
break;
}
}
public void openItemAnimation() {
mSlideRelativeLayout.openAnimation();
}
public void closeItemAnimation() {
mSlideRelativeLayout.closeAnimation();
}
public void setCheckBox() {
mCheckBox.setChecked(!mCheckBox.isChecked());
mItemBean.setChecked(mCheckBox.isChecked());
}
@Override
public void onClick(View v) {
setCheckBox();
}
}
}
代码下载:https://github.com/ruzhan123/RecyclerViewItemAnimation
本文转自:
https://ruzhan123.github.io/2016/07/01/2016-07-01-01-对RecyclerViewItem做动画/