博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
RecyclerView零点突破(动画+边线篇)
阅读量:6588 次
发布时间:2019-06-24

本文共 16624 字,大约阅读时间需要 55 分钟。

0、前言:

动画和边线估计有点冷门,很多人都将就凑合,今天我就来深入讲解一下吧

边线的方案是网上流传的一种,个人感觉也是最好的,并稍稍改进了一点
本篇使用的测试布局见上篇:

留图镇楼
镇楼1 镇楼2
本系列分为3篇:
  • RecyclerView零点突破(详细分析篇)


1、动画--解析内置DefaultItemAnimator与自定义

一共就不到700行代码,应该能hold住吧

为了方便研究,将DefaultItemAnimator拷贝一份到工程中

整体了解一下:

DefaultItemAnimator-->SimpleItemAnimator-->RecyclerView.ItemAnimator

几个核心的回调函数如下:


1.1.添加的时候:

默认效果是下面的条目整体下移,之后插入的条目淡入(透明度0~1)


1.1.1:查看添加时函数的执行情况

animateMove、endAnimationy一对调用了10次  animateAdd、endAnimation一对调用了1次  最后调用了runPendingAnimationsanimateMove的最大的条目position是:11,也就是当前页面的最大Position   经多次测试:插入位置之后的所有当前页的条目都会响应animateMove方法,且执行的先后顺序是随机的  插入目标的条目响应animateAdd方法复制代码

1.1.2:animateAdd分析
-->[DefaultItemAnimator#animateAdd]-----------------------------------------------------@Overridepublic boolean animateAdd(final ViewHolder holder) {    resetAnimation(holder);//重置动画    holder.itemView.setAlpha(0);//将该条目透明度设为0,也就是点击时的空白区域    mPendingAdditions.add(holder);//将这个透明的条目加入mPendingAdditions列表    return true;}-->[DefaultItemAnimator#animateAdd]-----------------------------------------------------private void resetAnimation(ViewHolder holder) {    if (sDefaultInterpolator == null) {        sDefaultInterpolator = new ValueAnimator().getInterpolator();    }    holder.itemView.animate().setInterpolator(sDefaultInterpolator);    endAnimation(holder);}-->[待添加的ViewHolder列表]-----------------------------------------------------private ArrayList
mPendingAdditions = new ArrayList<>();复制代码

1.1.3:mPendingAdditions的endAnimation分析
@Override    public void endAnimation(ViewHolder item) {        Log.e(TAG, "endAnimation: ");        final View view = item.itemView;//条目视图        view.animate().cancel();//先取消条目视图的动画        //略n行....        //添加的条目布局列表:mPendingAdditions          if (mPendingAdditions.remove(item)) {//移除该条目            view.setAlpha(1);//将该条目透明度设为1            dispatchAddFinished(item);        }        //略n行....        dispatchFinishedWhenDone();    }复制代码

1.1.4:mPendingAdditions在runPendingAnimations中
-->[ArrayList
列表]-----------------------------------------------------ArrayList
> mAdditionsList = new ArrayList<>();-->[DefaultItemAnimator#runPendingAnimations]-----------------------------------------------------@Overridepublic void runPendingAnimations() { //mPendingAdditions不为空,可以添加 boolean additionsPending = !mPendingAdditions.isEmpty(); //additionsPending为false可导致直接返回,不执行动画 if (!removalsPending && !movesPending && !additionsPending && !changesPending) { return; } //略n行.... if (additionsPending) { final ArrayList
additions = new ArrayList<>(); additions.addAll(mPendingAdditions);//将mPendingAdditions的视图装到additions mAdditionsList.add(additions);//mAdditionsList的盒子装additions mPendingAdditions.clear();//mPendingAdditions光荣下岗 Runnable adder = new Runnable() {//居然是Runnable...记住这小子的名字[adder] @Override public void run() { for (ViewHolder holder : additions) {//遍历:additions animateAddImpl(holder);//----动画的核心---- } additions.clear();//清空additions mAdditionsList.remove(additions);//移除additions } }; if (removalsPending || movesPending || changesPending) {//如果有其他的动画待执行 long removeDuration = removalsPending ? getRemoveDuration() : 0; long moveDuration = movesPending ? getMoveDuration() : 0; long changeDuration = changesPending ? getChangeDuration() : 0; long totalDelay = removeDuration + Math.max(moveDuration, changeDuration); View view = additions.get(0).itemView; ViewCompat.postOnAnimationDelayed(view, adder, totalDelay); } else { adder.run();//[adder]走起 } }}-->[要进行添加动画的ViewHolder]-----------------------------------------------------ArrayList
mAddAnimations = new ArrayList<>();-->[DefaultItemAnimator#animateAddImpl]-----------------------------------------------------void animateAddImpl(final ViewHolder holder) { final View view = holder.itemView;//获取布局视图 final ViewPropertyAnimator animation = view.animate();//获取视图的animate mAddAnimations.add(holder);//mAddAnimations篮子装一下 animation.alpha(1).setDuration(getAddDuration())//tag1:默认时长120ms---执行透明度动画 .setListener(new AnimatorListenerAdapter() { @Override public void onAnimationStart(Animator animator) { dispatchAddStarting(holder); } @Override//取消动画时将Alpha设为1 public void onAnimationCancel(Animator animator) { view.setAlpha(1); } @Override//办完事,清场,该走的走,该清的清 public void onAnimationEnd(Animator animator) { animation.setListener(null); dispatchAddFinished(holder); mAddAnimations.remove(holder); dispatchFinishedWhenDone(); } }).start();}-->[android.support.v7.widget.RecyclerView.ItemAnimator#getAddDuration]------------------------tag1-----------------------------private long mAddDuration = 120;public long getAddDuration() { return mAddDuration;}复制代码

1.2:自定义添加动画
1.2.1:定点旋转

既然分析到它是怎么动起来的,当然可以改一下,比如:

注意:animateAddImpl里的动画是在移动结束后调用的

-->[RItemAnimator#animateAddImpl]-----------------------------------------------------animation.rotation(360).setDuration(1000)        .setListener(new AnimatorListenerAdapter() {            @Override            public void onAnimationStart(Animator animator) {                view.setAlpha(1);复制代码

1.2.2抖动
缩放抖动 移动抖动

感觉ViewPropertyAnimator用得不怎么爽,还是用AnimatorSet+ObjectAnimator吧

用AnimatorSet装一下效果,可以实现更复杂的多动画叠加,然后添加监听,和源码保持一致
一直想做条目抖动效果,总是实现了,

void animateAddImpl(final ViewHolder holder) {    final View view = holder.itemView;    mAddAnimations.add(holder);    ObjectAnimator translationX = ObjectAnimator//创建实例            //(View,属性名,初始化值,结束值)            .ofFloat(view, "translationX", 0, 20, -20, 0, 20, -20, 0, 20, -20, 0)            .setDuration(300);//设置时长    ObjectAnimator scaleX = ObjectAnimator//创建实例            //(View,属性名,初始化值,结束值)            .ofFloat(view, "scaleX", 1, 0.95f, 1.05f, 1, 0.95f, 1.05f, 1, 0.95f, 1.05f,1)            .setDuration(300);//设置时长    AnimatorSet set = new AnimatorSet();    set.playTogether(scaleX,translationX);//两个效果一起    //set.playSequentially(translationX);//添加动画    //set.playSequentially(scaleX);//添加动画    set.addListener(new AnimatorListenerAdapter() {        @Override        public void onAnimationCancel(Animator animation) {            view.setAlpha(1);        }        @Override        public void onAnimationEnd(Animator animation) {            dispatchAddFinished(holder);            mAddAnimations.remove(holder);            dispatchFinishedWhenDone();        }        @Override        public void onAnimationStart(Animator animation) {            view.setAlpha(1);            dispatchAddStarting(holder);        }    });    set.start();}复制代码

1.2.3:定轴旋转
rotationX rotationY
//定轴旋转ObjectAnimator rotationY = ObjectAnimator//创建实例        //(View,属性名,初始化值,结束值)        .ofFloat(view, "rotationY", 0,360)        .setDuration(1000);//设置时长ObjectAnimator rotationX = ObjectAnimator//创建实例        //(View,属性名,初始化值,结束值)        .ofFloat(view, "rotationX", 0,360)        .setDuration(1000);//设置时长复制代码

1.3:插入下item的动画:
效果1 效果2
1.3.1:简析:
分析同添加:运动核心在DefaultItemAnimator#animateMoveImpl方法里,相关集合:  private ArrayList
mPendingMoves = new ArrayList<>(); ArrayList
> mMovesList = new ArrayList<>(); ArrayList
mRemoveAnimations = new ArrayList<>();复制代码
-->[下面的条目执行:animateMove()]-----------------------------------------------------@Overridepublic boolean animateMove(final ViewHolder holder, int fromX, int fromY,                           int toX, int toY) {    final View view = holder.itemView;//获取item视图View    fromX += (int) holder.itemView.getTranslationX();    fromY += (int) holder.itemView.getTranslationY();    resetAnimation(holder);    int deltaX = toX - fromX;    int deltaY = toY - fromY;    if (deltaX == 0 && deltaY == 0) {        dispatchMoveFinished(holder);        return false;    }//尺寸计算    if (deltaX != 0) {//对item视图进行平移        view.setTranslationX(-deltaX);    }    if (deltaY != 0) {        view.setTranslationY(-deltaY);    }    //mPendingMoves添加MoveInfo---移动的相关信息封装在MoveInfo中,相当于封装属性的空壳类    mPendingMoves.add(new MoveInfo(holder, fromX, fromY, toX, toY));    return true;}-->[mPendingMoves在runPendingAnimations()中的表现]-----------------------------------------------------boolean movesPending = !mPendingMoves.isEmpty();if (!removalsPending && !movesPending && !additionsPending && !changesPending) {    // nothing to animate    return;}//和添加是一个套路---核心运动方法在:animateMoveImplif (movesPending) {    final ArrayList
moves = new ArrayList<>(); moves.addAll(mPendingMoves); mMovesList.add(moves); mPendingMoves.clear(); Runnable mover = new Runnable() { @Override public void run() { for (MoveInfo moveInfo : moves) { animateMoveImpl(moveInfo.holder, moveInfo.fromX, moveInfo.fromY, moveInfo.toX, moveInfo.toY); } moves.clear(); mMovesList.remove(moves); } };-->[mPendingMoves在runPendingAnimations()中的表现]-----------------------------------------------------void animateMoveImpl(final ViewHolder holder, int fromX, int fromY, int toX, int toY) { final View view = holder.itemView; final int deltaX = toX - fromX; final int deltaY = toY - fromY; if (deltaX != 0) { view.animate().translationX(0); } if (deltaY != 0) { view.animate().translationY(0); } final ViewPropertyAnimator animation = view.animate(); mMoveAnimations.add(holder); //运动的逻辑(此处无特效): animation.setDuration(getMoveDuration()).setListener(new AnimatorListenerAdapter() { @Override public void onAnimationStart(Animator animator) { dispatchMoveStarting(holder); } @Override public void onAnimationCancel(Animator animator) { if (deltaX != 0) { view.setTranslationX(0); } if (deltaY != 0) { view.setTranslationY(0); } } @Override public void onAnimationEnd(Animator animator) { animation.setListener(null); dispatchMoveFinished(holder); mMoveAnimations.remove(holder); dispatchFinishedWhenDone(); } }).start();}复制代码

1.3.2:效果1:
-->[animateMoveImpl()中]-----------------------------------------------------//定轴旋转ObjectAnimator//创建实例        .ofFloat(view, "rotationX", 0, 360)        .setDuration(1000).start();//设置时长复制代码

1.3.3:效果2:
-->[animateMoveImpl()中]-----------------------------------------------------ObjectAnimator//创建实例        .ofFloat(view, "ScaleX", 1, 0.5f, 1.2f,0.8f,1)        .setDuration(1000).start();//设置时长ObjectAnimator//创建实例        .ofFloat(view, "ScaleY", 1, 0.5f, 1.2f,0.8f,1)        .setDuration(1000).start();//设置时长复制代码

1.4:小结

移除貌似没有对当前item的特效,对item下面的特效还是在animateMoveImpl

更新数据的item的特效在:animateChangeImpl()都是一个套路,这里就不赘述了 将上篇的视图改改就能实现镇楼图了,这里也不赘述了

其实看懂了DefaultItemAnimator,item的动画也不是很难  貌似有个动画库,个人感觉没有必要,拿DefaultItemAnimator稍微改几句就行了  毕竟需求是不断变动的,一个库不可能涵盖所以需求,而且很多用不到的特效还占空间    微妙的修整还是要懂才行,能应对变化的只有变化本身,记住修改效果的地方:更新数据:animateChangeImpl()  添加数据:animateAddImpl()  移动:animateMoveImpl()复制代码

2.边线的绘制:

缺陷:对于网格和瀑布流结尾处处理欠佳(不过这两种布局一般都不用边线)

2.1:效果一览
2.1.1:三个构造函数:

2.1.2:三种样式:


2.2:代码实现
2.2.1:使用
//mIdRvGoods.addItemDecoration(new RVItemDivider(this, RVItemDivider.Type.HORIZONTAL,10,Color.BLACK));//mIdRvGoods.addItemDecoration(new RVItemDivider(this, RVItemDivider.Type.VERTICAL));//水平加竖直mIdRvGoods.addItemDecoration(new RVItemDivider(this, RVItemDivider.Type.VERTICAL,10,Color.BLACK));mIdRvGoods.addItemDecoration(new RVItemDivider(this, RVItemDivider.Type.HORIZONTAL, 10, Color.BLACK));复制代码
2.2.2:代码实现
/** * 作者:张风捷特烈
* 时间:2018/12/3 0003:10:36
* 邮箱:1981462002@qq.com
* 说明:RecyclerView的分割线 */public class RVItemDivider extends RecyclerView.ItemDecoration { public enum Type { VERTICAL,//竖直线 HORIZONTAL,//水平线 } private Paint mPaint;//画笔 private Drawable mDivider;//Drawable分割线 private int mDividerHeight = 1;//分割线高度,默认为1px private Type mOrientation;//线的方向 private static final int[] ATTRS = new int[]{android.R.attr.listDivider}; public RVItemDivider(Context context, Type orientation) { mOrientation = orientation; final TypedArray a = context.obtainStyledAttributes(ATTRS); mDivider = a.getDrawable(0); a.recycle(); } public RVItemDivider(Context context, Type orientation, int drawableId) { this(context, orientation); mDivider = ContextCompat.getDrawable(context, drawableId); mDividerHeight = mDivider.getIntrinsicHeight(); } /** * 自定义分割线 * * @param context 上下文 * @param orientation 列表方向 * @param dividerHeight 分割线高度 * @param dividerColor 分割线颜色 */ public RVItemDivider(Context context, Type orientation, int dividerHeight, int dividerColor) { this(context, orientation); mDividerHeight = dividerHeight; mPaint = new Paint(Paint.ANTI_ALIAS_FLAG); mPaint.setColor(dividerColor); mPaint.setStyle(Paint.Style.FILL); } /** * 获取分割线尺寸 * * @param outRect 线的矩框 * @param view 线 * @param parent RecyclerView * @param state 状态 */ @Override public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) { super.getItemOffsets(outRect, view, parent, state); switch (mOrientation) { case HORIZONTAL: outRect.set(0, 0, 0, mDividerHeight);//横线矩框 break; case VERTICAL: outRect.set(0, 0, mDividerHeight, 0);//横线矩框 } } /** * 绘制分割线 * * @param canvas 画布 * @param parent RecyclerView * @param state 状态 */ @Override public void onDraw(Canvas canvas, RecyclerView parent, RecyclerView.State state) { super.onDraw(canvas, parent, state); switch (mOrientation) { case VERTICAL: drawVertical(canvas, parent);//竖线矩框 break; case HORIZONTAL: drawHorizontal(canvas, parent);//横线矩框 break; } } /** * 绘制水平线 * * @param canvas 画布 * @param parent RecyclerView */ private void drawHorizontal(Canvas canvas, RecyclerView parent) { final int left = parent.getPaddingLeft(); final int right = parent.getMeasuredWidth() - parent.getPaddingRight(); final int childNum = parent.getChildCount(); for (int i = 0; i < childNum; i++) {//遍历所有的孩子 final View child = parent.getChildAt(i); RecyclerView.LayoutParams layoutParams = (RecyclerView.LayoutParams) child.getLayoutParams(); //线的左上角坐标(itemView底部+边距,itemView底部+边距+线高) final int top = child.getBottom() + layoutParams.bottomMargin; final int bottom = top + mDividerHeight; if (mDivider != null) {//有mDivider时---绘制mDivider mDivider.setBounds(left, top, right, bottom); mDivider.draw(canvas); } if (mPaint != null) {//有mPaint时---绘制矩形 canvas.drawRect(left, top, right, bottom, mPaint); } } } /** * 绘制竖直线--------同理 * * @param canvas 画布 * @param parent RecyclerView */ private void drawVertical(Canvas canvas, RecyclerView parent) { final int top = parent.getPaddingTop(); final int bottom = parent.getMeasuredHeight() - parent.getPaddingBottom(); final int childSize = parent.getChildCount(); for (int i = 0; i < childSize; i++) { final View child = parent.getChildAt(i); RecyclerView.LayoutParams layoutParams = (RecyclerView.LayoutParams) child.getLayoutParams(); final int left = child.getRight() + layoutParams.rightMargin; final int right = left + mDividerHeight; if (mDivider != null) { mDivider.setBounds(left, top, right, bottom); mDivider.draw(canvas); } if (mPaint != null) { canvas.drawRect(left, top, right, bottom, mPaint); } } }}复制代码

后记:捷文规范

1.本文成长记录及勘误表
日期 备注
2018-12-5
2.更多关于我
笔名 QQ 微信 爱好
张风捷特烈 1981462002 zdl1994328 语言
3.声明

1----本文由张风捷特烈原创,转载请注明

2----欢迎广大编程爱好者共同交流
3----个人能力有限,如有不正之处欢迎大家批评指证,必定虚心改正
4----看到这里,我在此感谢你的喜欢与支持


你可能感兴趣的文章
星级 评分
查看>>
hdu 1728 逃离迷宫(dFS+优先队列)
查看>>
通信协议之广播---recvfrom 放回客户端的ip地址第一次全为0.0.0.0
查看>>
subversion SVN
查看>>
php 常用函数
查看>>
oracle-3-子查询和常用函数
查看>>
Hibernate原生SQL查询
查看>>
item2
查看>>
使用jQuery实现一个类似GridView的编辑,更新,取消和删除的功能
查看>>
幸运的背后,总是靠自身的努力在支撑
查看>>
云计算面临安全挑战
查看>>
C# 线程手册 第三章 使用线程 Monitor.TryEnter()
查看>>
分享11个超棒的移动应用(mobile apps)开发解决方案
查看>>
mysql5.5.17源代码安装
查看>>
关于【cocos2dx-3.0beta-制作flappybird】教程在3.2project中出现找不到CCMenuItem.h的解决方法...
查看>>
7z格式、LZMA压缩算法和7-Zip详细介绍
查看>>
imx6 uboot splash image
查看>>
转:全栈工程师的知识栈列表
查看>>
C/C++获取文件大小
查看>>
深入理解Java内存模型(五)——锁
查看>>