自定义控件三部曲视图篇(八)——RecyclerView系列之五回收复用实现方式二

本文详细介绍了如何在Android中自定义LayoutManager实现RecyclerView的回收复用,包括初步实现和优化方案。内容涵盖滚动原理、布局改造、优化回收布局等,旨在实现动态旋转效果的同时提高性能。
摘要由CSDN通过智能技术生成

前言 只要有坚强的持久心,一个庸俗平凡的人也会有成功的一天,否则即使是一个才识卓越的人,也只能遭遇失败的命运。 -----比尔盖茨


系列文章: Android自定义控件三部曲文章索引: http://blog.csdn.net/harvic880925/article/details/50995268


在上篇中,我们先将摆好所有要显示的新增item以后,再使用offsetChildrenVertical(-travel)函数来移动屏幕中所有item。很明显,这种方法仅适用于每个item,在移动时,没有特殊效果的情况,当我们在移动item时,同时需要改变item的角度、透明度等情况时,单纯使用offsetChildrenVertical(-travel)来移是不行的。针对这种情况,我们就只有使用第二种方法来实现回收复用了。

在本节中,我们最终实现的效果如下图所示:
在这里插入图片描述

从效果图中可以看出,本例中的每个item,在移动时,同时会绕Y轴旋转。

因为大部分的原理与上节中的CustomLayoutManager的实现相同,所以本节中的代码将从4.4中的CustomLayoutManager中改造而成。

一、 初步实现

1.1 实现原理

在这里,我们主要替换掉在上节中移动item所用的offsetChildrenVertical(-travel);函数,既然要将它弃用,那我们就只能自己布局每个item了。很明显,在这里我们主要处理的是滚动的情况,对于onLayoutChildren中的代码是不用改动的。

试想,在滚动dy时,有两种item需要重新布局:

  1. 第一种:原来已经在屏幕上的item
  2. 第二种:新增的item

所以,这里就涉及到怎么处理已经在屏幕上的item和新增item的重绘问题,我们可以效仿在onLayoutChildren中的处理方式,先调用detachAndScrapAttachedViews(recycler)将屏幕上已经在显示的所有Item离屏,然后再将所有item重绘。

那第二个问题又来了,我们应该从哪个item开始重绘,到哪个item结束呢?

很明显,在向下滚动时,低部Item下移,顶部空出来空白区域。所以我们只需要从当前在显示的Item向前遍历,直到index=0即可。
当向上滚动时,顶部Item上移,底部空出来空白区域。所以我们也只需要从当前在显示的顶部Item向上遍历,直到Item结束为止。

1.2 改造CustomLayoutManager

首先,onLayoutChildren不用改造,只需要改造scrollVerticallyBy即可。原来的到顶、到底判断和回收越界item的代码都不变:

public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler, RecyclerView.State state) {
   
    if (getChildCount() <= 0) {
   
        return dy;
    }

    int travel = dy;
    //如果滑动到最顶部
    if (mSumDy + dy < 0) {
   
        travel = -mSumDy;
    } else if (mSumDy + dy > mTotalHeight - getVerticalSpace()) {
   
        //如果滑动到最底部
        travel = mTotalHeight - getVerticalSpace() - mSumDy;
    }

    //回收越界子View
    for (int i = getChildCount() - 1; i >= 0; i--) {
   
        View child = getChildAt(i);
        if (travel > 0) {
   //需要回收当前屏幕,上越界的View
            if (getDecoratedBottom(child) - travel < 0) {
   
                removeAndRecycleView(child, recycler);
                continue;
            }
        } else if (travel < 0) {
   //回收当前屏幕,下越界的View
            if (getDecoratedTop(child) - travel > getHeight() - getPaddingBottom()) {
   
                removeAndRecycleView(child, recycler);
                continue;
            }
        }
    }
	…………
}

在回收越界的holderView之后,我们需要在使用detachAndScrapAttachedViews(recycler);将现在显示的所有item离屏缓存之前,先得到当前在显示的第一个item和最后一个item的索引,因为如果在将所有item从屏幕上离屏缓存以后,利用getChildAt(int position)是拿不到任何值的,会返回null,因为现在屏幕上已经没有View存在了。

View lastView = getChildAt(getChildCount() - 1);
View firstView = getChildAt(0);
detachAndScrapAttachedViews(recycler);
mSumDy += travel;
Rect visibleRect = getVisibleArea();

这里需要注意的是,我们在所有的布局操作前,先将移动距离mSumDy进行了累加。因为后面我们在布局item时,会弃用offsetChildrenVertical(-travel)移动item,而是在布局item时,就直接把item布局在新位置。最后,因为我们已经累加了mSumDy,所以我们需要改造getVisibleArea(),将原来getVisibleArea(int dy)中累加dy的操作去掉:

private Rect getVisibleArea() {
   
    Rect result = new Rect(getPaddingLeft(), getPaddingTop() + mSumDy, getWidth() + getPaddingRight(), getVerticalSpace() + mSumDy);
    return result;
}

接下来,就是布局屏幕上的所有item,同样是分情况:

if (travel >= 0) {
   
    int minPos = getPosition(firstView);
    for (int i = minPos; i < getItemCount(); i++) {
   
        Rect rect = mItemRects.get(i);
        if (Rect.intersects(visibleRect, rect)) {
   
            View child = recycler.getViewForPosition(i);
            addView(child);
            measureChildWithMargins(child, 0, 0);
            layoutDecorated(child, rect.left, rect.top - mSumDy, rect.right, rect.bottom - mSumDy);
        }
    }
} 

这里需要注意的是,当dy>0时,表示向上滚动(手指由下向上滑),所以我们需要从之前第一个可见的item向下遍历,因为我们不知道在什么情况下遍历结束,所以我们使用最后一个item的索引(getItemCount())做为结束位置。当然大家在这里也可以优化,可以使用下面的语句:

int max = minPos +
评论 26
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值