美文网首页
RecyclerView的ItemAnimator

RecyclerView的ItemAnimator

作者: freelifes | 来源:发表于2022-04-25 17:35 被阅读0次

ItemAnimator

ItemAnimator有2个实现类,分别为SimpleItemAnimator和DefaultItemAnimator, 记录了holder.itemView的边界,决定着item是否可以移动,改变,移除的动画,提供了基础的添加,移除,移动动画。以animateChange为例,看下如何实现的,之前的RecyclerView分析已经讲了什么场景会执行到这里。

public boolean animateChange(@NonNull RecyclerView.ViewHolder oldHolder, @NonNull RecyclerView.ViewHolder newHolder,
            @NonNull ItemHolderInfo preInfo, @NonNull ItemHolderInfo postInfo) {
        final int fromLeft = preInfo.left;
        final int fromTop = preInfo.top;
        final int toLeft, toTop;
        if (newHolder.shouldIgnore()) {
            toLeft = preInfo.left;
            toTop = preInfo.top;
        } else {
            toLeft = postInfo.left;
            toTop = postInfo.top;
        }
        return animateChange(oldHolder, newHolder, fromLeft, fromTop, toLeft, toTop);
    }

preInfo 就是layout之前的holder的info ,postInfo 就是holder真正layout之后的info。animateChange最终实现在DefaultItemAnimator中。

public boolean animateChange(RecyclerView.ViewHolder oldHolder, RecyclerView.ViewHolder newHolder,
            int fromX, int fromY, int toX, int toY) {
//比如数据没有改变,调用notifydatasetChange,最终也会调用到这
//里,如果viewtype没有变化,导致newViewholder是从pool中取的,
//不是创建的,这样older和new是同一个holder。
        if (oldHolder == newHolder) {
            // Don't know how to run change animations when the same view holder is re-used.
            // run a move animation to handle position changes.
            return animateMove(oldHolder, fromX, fromY, toX, toY);
        }
        final float prevTranslationX = oldHolder.itemView.getTranslationX();
        final float prevTranslationY = oldHolder.itemView.getTranslationY();
        final float prevAlpha = oldHolder.itemView.getAlpha();
        resetAnimation(oldHolder);
        int deltaX = (int) (toX - fromX - prevTranslationX);
        int deltaY = (int) (toY - fromY - prevTranslationY);
        // recover prev translation state after ending animation
        oldHolder.itemView.setTranslationX(prevTranslationX);
        oldHolder.itemView.setTranslationY(prevTranslationY);
        oldHolder.itemView.setAlpha(prevAlpha);
        if (newHolder != null) {
            // carry over translation values
            resetAnimation(newHolder);
            newHolder.itemView.setTranslationX(-deltaX);
            newHolder.itemView.setTranslationY(-deltaY);
            newHolder.itemView.setAlpha(0);
        }
        mPendingChanges.add(new ChangeInfo(oldHolder, newHolder, fromX, fromY, toX, toY));
        return true;
    }

1、动画执行之前,设置初始值,记录,mPendingChanges.add()。
oldViewHolder设置:它本身的TranslationX/Y和Alpha。
newViewHolder设置:newHolder设置为 toX - fromX - prevTranslatonX 和alpha为0,如果oldholder和newHolder的位置重合,则这个值为0。
假如希望有个move的效果,ToX的值是不可能变的,我们可以改变fromX的值,比如在调用notifyItemChange之前,设置fromX的值为holder.item的宽度,也就是设置holder.itemView的left值。

 public void runPendingAnimations() {
        boolean removalsPending = !mPendingRemovals.isEmpty();
        boolean movesPending = !mPendingMoves.isEmpty();
        boolean changesPending = !mPendingChanges.isEmpty();
        boolean additionsPending = !mPendingAdditions.isEmpty();
        if (!removalsPending && !movesPending && !additionsPending && !changesPending) {
            // nothing to animate
            return;
        }

        // Next, change stuff, to run in parallel with move animations
        if (changesPending) {
            final ArrayList<DefaultItemAnimator.ChangeInfo> changes = new ArrayList<>();
            changes.addAll(mPendingChanges);
            mChangesList.add(changes);
            mPendingChanges.clear();
            Runnable changer = new Runnable() {
                @Override
                public void run() {
                    for (DefaultItemAnimator.ChangeInfo change : changes) {
                        animateChangeImpl(change);
                    }
                    changes.clear();
                    mChangesList.remove(changes);
                }
            };
            if (removalsPending) {
                RecyclerView.ViewHolder holder = changes.get(0).oldHolder;
                ViewCompat.postOnAnimationDelayed(holder.itemView, changer, getRemoveDuration());
            } else {
                changer.run();
            }
        }
    }

    void animateChangeImpl(final DefaultItemAnimator.ChangeInfo changeInfo) {
        final RecyclerView.ViewHolder holder = changeInfo.oldHolder;
        final View view = holder == null ? null : holder.itemView;
        final RecyclerView.ViewHolder newHolder = changeInfo.newHolder;
        final View newView = newHolder != null ? newHolder.itemView : null;
        if (view != null) {
            final ViewPropertyAnimator oldViewAnim = view.animate().setDuration(
                    getChangeDuration());
            mChangeAnimations.add(changeInfo.oldHolder);
            oldViewAnim.translationX(changeInfo.toX - changeInfo.fromX);
            oldViewAnim.translationY(changeInfo.toY - changeInfo.fromY);
            oldViewAnim.alpha(0).setListener(new AnimatorListenerAdapter() {
                @Override
                public void onAnimationStart(Animator animator) {
                    dispatchChangeStarting(changeInfo.oldHolder, true);
                }

                @Override
                public void onAnimationEnd(Animator animator) {
                    oldViewAnim.setListener(null);
                    view.setAlpha(1);
                    view.setTranslationX(0);
                    view.setTranslationY(0);
                    dispatchChangeFinished(changeInfo.oldHolder, true);
                    mChangeAnimations.remove(changeInfo.oldHolder);
                    dispatchFinishedWhenDone();
                }
            }).start();
        }
        if (newView != null) {
            final ViewPropertyAnimator newViewAnimation = newView.animate();
            mChangeAnimations.add(changeInfo.newHolder);
            newViewAnimation.translationX(0).translationY(0).setDuration(getChangeDuration())
                    .alpha(1).setListener(new AnimatorListenerAdapter() {
                @Override
                public void onAnimationStart(Animator animator) {
                    dispatchChangeStarting(changeInfo.newHolder, false);
                }
                @Override
                public void onAnimationEnd(Animator animator) {
                    newViewAnimation.setListener(null);
                    newView.setAlpha(1);
                    newView.setTranslationX(0);
                    newView.setTranslationY(0);
                    dispatchChangeFinished(changeInfo.newHolder, false);
                    mChangeAnimations.remove(changeInfo.newHolder);
                    dispatchFinishedWhenDone();
                }
            }).start();
        }
    }

2、依次执行动画,设置结束值,runPendingAnimations()。
因为item改变,分为olderView离开和newView出现,所以对应2个动画。
oldViewHolder设置 : oldViewAnim.translationX(changeInfo.toX - changeInfo.fromX); 如果new和old的位置不变,则changeInfo.toX - changeInfo.fromX的值为0,不会有位移效果。只有alpha会从1变成0。
newViewHolder设置: 从初始位置到translationX(0)。alpha从0到1。
同样,如果需要位移效果,改FromX的值,调用notifyitemChange的时候,把holder的left改了,就有move效果了。
3、测试下改了FromX的效果。

   fun setVisiableAnimal(align: Boolean = true) {
        adapter.setVisiableFlag(true)
        var firstTop = 0
        var findFirstVisibleItemPosition = manager.findFirstVisibleItemPosition()
        var findLastVisibleItemPosition = manager.findLastVisibleItemPosition()
        for (index in findFirstVisibleItemPosition until findLastVisibleItemPosition + 1) {
            var viewHolder = recyclerView.findViewHolderForAdapterPosition(index)
            viewHolder?.let {
                viewHolder.itemView.left = recyclerView.width + if (!align) index * 100 else 0
                if (index == findFirstVisibleItemPosition) {
                    firstTop = viewHolder.itemView.top
                }
                viewHolder.itemView.top = firstTop
            }
        }
        adapter.notifyItemRangeChanged(findFirstVisibleItemPosition, (findLastVisibleItemPosition - findFirstVisibleItemPosition) + 1)
    }
recyclerView动画.gif

ItemDecoration

主要用于特殊View的绘制和偏移的view,还可以绘制分割线,高亮,可视化边界。
1、绘制特殊的View。如下2个方法。
onDraw(Canvas, RecyclerView, RecyclerView.State) 在item绘制之前。
onDrawOver(Canvas, RecyclerView, RecyclerView.State) 在item绘制之后。

   private void drawVertical(Canvas canvas, RecyclerView parent) {
        canvas.save();
        final int left;
        final int right;
        //noinspection AndroidLintNewApi - NewApi lint fails to handle overrides.
        if (parent.getClipToPadding()) {
            left = parent.getPaddingLeft();
            right = parent.getWidth() - parent.getPaddingRight();
            canvas.clipRect(left, parent.getPaddingTop(), right,
                    parent.getHeight() - parent.getPaddingBottom());
        } else {
            left = 0;
            right = parent.getWidth();
        }

        final int childCount = parent.getChildCount();
        for (int i = 0; i < childCount; i++) {
            final View child = parent.getChildAt(i);
            parent.getDecoratedBoundsWithMargins(child, mBounds);
            final int bottom = mBounds.bottom + Math.round(child.getTranslationY());
            final int top = bottom - mDivider.getIntrinsicHeight();
            mDivider.setBounds(left, top, right, bottom);
            mDivider.draw(canvas);
        }
        canvas.restore();
    }

以绘制vertical为例,left和right是确定的。绘制每一个child的botttom或者top即可。
parent.getDecoratedBoundsWithMargins(child, mBounds); mBounds包含了child的位置和大小(包含margin和decorated insert),所以mBouns.bottom - mDivider.getIntrinsicHeight() 就是top。也就是绘制分割线的top值。
2、偏移view。
getItemOffsets(Rect , int ,RecyclerView) measure的时候,会把Decoration的大小计算在view所需的大小中。
可以通过getChildAdapterPosition(View)拿到adapter position 然后给指定的position设置分割线等。

    public void getItemOffsets(Rect outRect, View view, RecyclerView parent,
            RecyclerView.State state) {
       //表示view的bottom包含一个getIntrinsicHeight的分割线
        if (mOrientation == VERTICAL) {
            outRect.set(0, 0, 0, mDivider.getIntrinsicHeight());
        } else {
       //表示View的right包含一个mDivider.getIntrinsicWidth()的分割线
            outRect.set(0, 0, mDivider.getIntrinsicWidth(), 0);
        }
    }

ItemTouchHelper 也是继承自ItemDecoration,并且重写了onDraw()方法,加入了拖拽Dx,Dy偏移量,方便在拖拽过程中,我们可以改变recyclerView中item的展示效果,依据dy执行缩放等。类似卡片,先在onlayoutchildren中布局完成,在childDraw中执行拖拽动画。

 public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
        // we don't know if RV changed something so we should invalidate this index.
        mOverdrawChildPosition = -1;
        float dx = 0, dy = 0;
        if (mSelected != null) {
            getSelectedDxDy(mTmpPosition);
            dx = mTmpPosition[0];
            dy = mTmpPosition[1];
        }
        mCallback.onDraw(c, parent, mSelected,
                mRecoverAnimations, mActionState, dx, dy);
    }

getAdapterPostion getLayoutPosition

1、如果调用了notifyDatasetChanged但是layout还没完成,此时getAdapterPostion为-1 。
2、当item被删除了,或者viewholder已经被回收,getAdapterPostion返回-1。
3、layoutmanager中,应该使用getLayoutPostion参与计算,比如用户需要第五个位置的item。在adapter中,应该使用getAdapterPosition,比如点击。

相关文章

网友评论

      本文标题:RecyclerView的ItemAnimator

      本文链接:https://www.haomeiwen.com/subject/knugdrtx.html