Android RecyclerView Item动画(Checkbox全选、单选)

对RecyclerView Item做动画,刚刚开始研究的时候一些坑,在这里把一些设计思路分享出去: 添加动态位移,静态位移,缩放等动画,保证了动画状态的平滑衔接。

效果图:

这里写图片描述

RecyclerView,ListView这些具有Item复用性的View,想要对其Item做动画

需要注意以下几点:

  1. 如果要一点击,让所有Item做动画的效果。例如,上图的编辑和取消,这样的动态动画。可以对所有ViewHolder中的View直接做动画。但是需要在onBindViewHolder方法中对复用的item做静态动画,保证动画状态的平滑衔接。

  2. 每一个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<SlideViewHolder> 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<ItemBean> mItemBeans;
    private List<SlideViewHolder> 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<ItemBean> 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做动画/

  • 0
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值