当前位置:   article > 正文

Android RecyclerView_calculateextralayoutspace

calculateextralayoutspace

一、功能

1.去掉尽头阴影

android:fadingEdge="none"
ScrollView.setHorizontalFadingEdgeEnabled(false);

2.3及以上
        android:overScrollMode="never"
  • 1
  • 2
  • 3
  • 4
  • 5

2.分割线

/**
 * https://www.jianshu.com/p/e372cec819db
 */
public class SpacesItemDecoration extends RecyclerView.ItemDecoration {

    private int space;

    public SpacesItemDecoration(int space) {
        this.space = space;
    }

    @Override
    public void getItemOffsets(Rect outRect, @NotNull View view,
                               RecyclerView parent, @NotNull RecyclerView.State state) {
        outRect.left = space;
        outRect.right = space;
        outRect.bottom = space;
        if (parent.getChildLayoutPosition(view) == 0) {
            outRect.top = space;
        }
    }
}

//调用
recyclerView.addItemDecoration(new SpacesItemDecoration(27));
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25

3.图片加载滑动卡顿

  1. 将图片格式改为RGB_565;
  2. 裁剪图片与压缩图片;
  3. 滑动完毕后进行代码加载;
        recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
            @Override
            public void onScrollStateChanged(@NotNull RecyclerView recyclerView, int newState) {
                super.onScrollStateChanged(recyclerView, newState);
                if (newState == RecyclerView.SCROLL_STATE_DRAGGING || newState == RecyclerView.SCROLL_STATE_SETTLING) {
                    isScrolling = true;
                    Glide.with(MainActivity.this).pauseRequests();
                } else if (newState == RecyclerView.SCROLL_STATE_IDLE) {
                    if (isScrolling) {
                        Glide.with(MainActivity.this).resumeRequests();
                    }
                    isScrolling = false;
                }
            }

            @Override
            public void onScrolled(@NotNull RecyclerView recyclerView, int dx, int dy) {
                super.onScrolled(recyclerView, dx, dy);
            }
        });
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  1. Glide加载动画也可以去掉
Glide.with(mContext)
    .load(url) 
    .dontAnimate()
    ....
  • 1
  • 2
  • 3
  • 4
  1. 本地图片预加载在内存
  2. 通用:运用 include,merge,ViewStub 等标签,使布局层次尽量少

4.删除动画

  1. 重写ItemAnimator并对RecyclerView进行设置

  2. 设置ItemAnimator的动画时间

        SimpleItemAnimator simpleItemAnimator = (SimpleItemAnimator) recyclerView.getItemAnimator();
        if (simpleItemAnimator != null) {

            // 默认动画时间
            //        private long mAddDuration = 120;
            //        private long mRemoveDuration = 120;
            //        private long mMoveDuration = 250;
            //        private long mChangeDuration = 250;

            simpleItemAnimator.setAddDuration(0);
            simpleItemAnimator.setChangeDuration(0);
            simpleItemAnimator.setMoveDuration(0);
            simpleItemAnimator.setRemoveDuration(0);
            simpleItemAnimator.setSupportsChangeAnimations(false);
        }
        recyclerView.setItemAnimator(simpleItemAnimator);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

5.添加边缘渐变效果

https://blog.csdn.net/lixinxiaos/article/details/108831390

6.获取第一条可见的 position

在 RecyclerView 中,获取第一条可见 item 的 position 有两种方法:

1. 使用 findFirstVisibleItemPosition() 方法

int firstVisiblePosition = recyclerView.findFirstVisibleItemPosition();
  • 1

该方法返回 RecyclerView 中第一个可见的 item 的 position。需要注意的是,如果第一个 item 完全不可见,则该方法返回 -1。

2. 使用 findViewHolderForAdapterPosition() 方法

ViewHolder viewHolder = recyclerView.findViewHolderForAdapterPosition(0);
int firstVisiblePosition = viewHolder.getAdapterPosition();
  • 1
  • 2

该方法根据 adapter position 获取对应的 ViewHolder。如果该 position 对应的 item 可见,则返回的 ViewHolder 非空,否则返回 null。然后,可以通过 ViewHolder 的 getAdapterPosition() 方法获取该 item 的 position。

示例

recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
    @Override
    public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
        if (newState == RecyclerView.SCROLL_STATE_IDLE) {
            int firstVisiblePosition = recyclerView.findFirstVisibleItemPosition();
            // 处理第一个可见 item
        }
    }
});
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

注意事项

  • findFirstVisibleItemPosition() 方法返回的是第一个完全可见的 item 的 position。如果第一个 item 只有部分可见,则该方法返回 -1。
  • findViewHolderForAdapterPosition() 方法返回的是第一个可见 item 的 adapter position。如果第一个 item 只有部分可见,则该方法返回的 ViewHolder 非空。

7.滚动事件

RecyclerView的滚动方法回调有三个:

  • onScrollStateChanged(RecyclerView recyclerView, int newState)
  • onScrolled(RecyclerView recyclerView, int dx, int dy)
  • fling(int velocityX, int velocityY)

**onScrollStateChanged**方法在滚动状态改变时回调,参数有:

  • recyclerView:当前滚动的RecyclerView

  • newState:新的滚动状态,取值如下:

    • RecyclerView.SCROLL_STATE_IDLE:空闲状态
    • RecyclerView.SCROLL_STATE_DRAGGING:拖拽状态
    • RecyclerView.SCROLL_STATE_SETTLING:惯性滚动状态

当用户停止滚动时,RecyclerView会进入SCROLL_STATE_IDLE状态。即使在惯性滚动过程中,RecyclerView也会最终进入SCROLL_STATE_IDLE状态。

**onScrolled**方法在滚动时回调,参数有:

  • recyclerView:当前滚动的RecyclerView
  • dx:水平滚动距离
  • dy:垂直滚动距离

**fling**方法在惯性滚动开始时回调,参数有:

  • velocityX:水平方向的初始速度
  • velocityY:垂直方向的初始速度

以下是三个滚动方法回调的具体说明:

**onScrollStateChanged**方法可以用来监听RecyclerView的滚动状态变化,例如在空闲状态时可以加载更多数据。

**onScrolled**方法可以用来监听RecyclerView的滚动距离,例如在滚动到顶部或底部时可以显示提示信息。

**fling**方法可以用来控制RecyclerView的惯性滚动,例如可以设置惯性滚动的最大速度或距离。

以下是一些使用滚动方法回调的示例:

  • 在空闲状态时加载更多数据:
@Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
    if (newState == RecyclerView.SCROLL_STATE_IDLE) {
        // 加载更多数据
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 在滚动到顶部时显示提示信息:
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
    if (dy > 0) {
        // 滚动向上
        if (recyclerView.canScrollVertically(-1)) {
            // 滚动到顶部
            // 显示提示信息
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 控制惯性滚动的最大速度:
@Override
public boolean fling(int velocityX, int velocityY) {
    // 设置惯性滚动的最大速度
    int maxVelocity = 1000;
    if (velocityX > maxVelocity) {
        velocityX = maxVelocity;
    }
    if (velocityY > maxVelocity) {
        velocityY = maxVelocity;
    }
    return super.fling(velocityX, velocityY);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

二、四级缓存

缓 存涉及对象作用重新创建视图View(onCreateViewHolder)重新绑定数据(onBindViewHolder)
一级缓存mAttachedScrap缓存屏幕中可见范围的ViewHolderfalsefalse
二级缓存mCachedViews缓存滑动时即将与RecyclerView分离的ViewHolder,按子View的position或id缓存,默认最多存放2个falsefalse
三级缓存mViewCacheExtension 开发者自行实现的缓存- -
四级缓存mRecyclerPoolViewHolder缓存池,本质上是一个SparseArray,其中key是ViewType(int类型),value存放的是 ArrayList< ViewHolder>,默认每个ArrayList中最多存放5个ViewHolderfalsetrue

三、优化

1.避免绘制中耗时

    @Override
    public void onBindViewHolder(@NonNull ViewHolder coreViewHolder, int position) {
        setTag(coreViewHolder, position);
        bindView(coreViewHolder, getItem(position), position);
    }
  • 1
  • 2
  • 3
  • 4
  • 5

避免绘制方法内部进行耗时操作,绘制超过16ms,那么用户就会感觉到卡顿,所以要缩短方法执行时间,避免耗时方法的调用。
比如:

  1. 避免 日期的比较和日期的格式化 formatDate.format(date);

  2. 避免 mTextView.setText(Html.fromHtml(data).toString());

所以呢,最好在方法内部只赋值和写逻辑,数据的转换在之前处理。

2.优化数据刷新

主要思想是通过

mAdapter.notifyItemRangeInserted(position, count);
mAdapter.notifyItemRangeRemoved(position, count);
mAdapter.notifyItemMoved(fromPosition, toPosition);
mAdapter.notifyItemRangeChanged(position, count, payload);
  • 1
  • 2
  • 3
  • 4

代替 mAdapter.notifyDataSetChanged() 实现局部item刷新

DiffUtils:https://codechina.csdn.net/mirrors/mcxtzhang/DiffUtils?utm_source=csdn_github_accelerator

如果需要整列表刷新,需要使用 mAdapter.notifyDataSetChanged() 的情况,可以用setHasStableIds(true)

//setAdapter前
mAdapter.setHasStableIds(true);

//Adapter内部实现
    @Override
    public long getItemId(int position) {
        return mDatas.get(position).getId();
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

这样也就可以通过 mAdapter.notifyDataSetChanged()setHasStableIds(true)一起使用, 实现局部刷新,避免闪烁的现象。

同时也可以解决 DiffUtils{@link SortedListCallback} 的整列表刷新闪烁的问题。

3.布局优化解决很多问题

4.开启Prefetch预取

Prefetch是Support Library版本 25.3.1时,RecyclerView添加的功能。滑动的时候触发。
实现:

  1. 使用的是官方的LayoutManager,那么直接可以获取Prefetch的功能,无需任何其他的定制
  2. 自定义的LayoutManager,需要重写LayoutManager.collectAdjacentPrefetchPositions()
  3. 如果嵌套RecyclerView使用需要复写setInitialPrefetchItemCount(num)

Prefetch默认是打开的,如果要关闭Prefetch:LayoutManager.setItemPrefetchEnabled(false);

5.加大RecyclerView二级缓存

调用方法:

recyclerView.setItemViewCacheSize();
  • 1

原理:
recyclerView.setItemViewCacheSize();修改的是mRequestedCacheMax的值。因为
mViewCacheMax = mRequestedCacheMax + extraCache;所以mViewCacheMax 被修改。
mViewCacheMax控制的就是mCachedViews的长度。(extraCache是由prefetch的时候计算出来的)

mCachedViews在RecyclerView的缓存机制中负责缓存的是超出屏幕外的ViewHolder。主要用于解决RecyclerView滑动抖动时的情况。

至此,RecyclerView能更加滑动流畅。但是同时因为缓存量变大,内存肯定是占用变大。
所以呢,setItemViewCacheSize这是空间换时间的手段。

原理:https://blog.csdn.net/tangedegushi/article/details/88790754

6.item设置高度固定

recyclerView.setHasFixedSize(true);
  • 1

item高度是固定的话,来避免requestLayout重复计算浪费资源。

7.对ViewHodler进行回收

重写Adapter的方法:
onViewRecycled()
当 ViewHolder 已经确认被回收,且要放进 RecyclerViewPool 中前,该方法会被回调。
onViewDetachedFromWindow():当Item移除的时候调用

根据缓存原理,我们知道onViewRecycled()方法在item被移除的时候不一定调用;但是onViewDetachedFromWindow()方法对应的是removeView(),肯定是调用的,所以回收ViewHodler,还是需要在onViewDetachedFromWindow()中做。

8.利用四级缓存RecycledViewPool优化

介绍:

  1. 缓存ViewHolder
    这里面保存的ViewHolder不仅仅是removed掉的视图,而且是恢复了出厂设置的视图,任何绑定过的痕迹都没有了,想用这里缓存的ViewHolder那是铁定要重新走Adapter的数据刷新方法了
  2. 缓存数
    默认缓存数是DEFAULT_MAX_SCRAP = 5,并且缓存是按照itemType来分开存储的。而且这个缓存数量不是指整个缓存池只能缓存这么多,而是每个不同itemType的ViewHolder的缓存数量。
  3. RecycledViewPool可以自定义
    如果不设置RecycledViewPool,系统会默认创建一个
//自定义
        RecyclerView.RecycledViewPool recycledViewPool = new RecyclerView.RecycledViewPool();
        recycledViewPool
                .setMaxRecycledViews(VIEW_TYPE, POOL_MAX);
        recyclerView.setRecycledViewPool(recycledViewPool);
  • 1
  • 2
  • 3
  • 4
  • 5

这样的话,可以优化的方面有两个:

  1. 通过设置缓存数,优化滑动加载速度
  2. 复用RecycledViewPool
    复用时机:同一套布局,或者同一个adapter。如RecyclerView嵌套,内容一致;多个Fragment公用一个Adapter

关于共有RecycledViewPool的写法:

转自共享 RecycledViewPool 复用 View 导致的内存泄露问题及其解决:https://blog.csdn.net/hegan2010/article/details/113770217

public abstract class ButtonItemAdapter extends RecyclerView.Adapter<BindingViewHolder<ButtonItemBinding>> {
    private static final RecyclerView.RecycledViewPool BUTTON_RECYCLER_POOL
            = new RecyclerView.RecycledViewPool();

    protected static final int BUTTON_ITEM_TYPE = 1;

    static {
        BUTTON_RECYCLER_POOL.setMaxRecycledViews(BUTTON_ITEM_TYPE, 60);
    }

    public static RecyclerView.RecycledViewPool getButtonRecyclerPool() {
        return BUTTON_RECYCLER_POOL;
    }

    @Override
    public int getItemViewType(int position) {
        return BUTTON_ITEM_TYPE;
    }

    @NonNull
    @CallSuper
    @Override
    public BindingViewHolder<ButtonItemBinding> onCreateViewHolder(@NonNull ViewGroup parent,
            int viewType) {
        ButtonItemBinding binding = DataBindingUtil.inflate(
                LayoutInflater.from(parent.getContext()), R.layout.button_item, parent, false);
        return new BindingViewHolder<>(binding);
    }

    @CallSuper
    @Override
    public void onAttachedToRecyclerView(@NonNull RecyclerView recyclerView) {
        recyclerView.setRecycledViewPool(getButtonRecyclerPool());
        RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager();
        if (layoutManager instanceof LinearLayoutManager) {
            ((LinearLayoutManager) layoutManager).setRecycleChildrenOnDetach(true);
        }
    }

    @CallSuper
    @Override
    public void onDetachedFromRecyclerView(@NonNull RecyclerView recyclerView) {
        recyclerView.setRecycledViewPool(null);
    }
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46

在合适的时机,RecycledViewPool会自我清除掉所持有的ViewHolder对象引用,当然你也可以在你认为合适的时机手动调用clear()。

9.预加载ViewHolder

        LinearLayoutManager linearLayoutManager = new LinearLayoutManager(this) {
            @Override
            protected int getExtraLayoutSpace(RecyclerView.State state) {
                return 300;
            }
        };
        
        recyclerView.setLayoutManager(linearLayoutManager);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

应用场景:屏幕第一次滑动卡顿
getExtraLayoutSpace()会通过calculateExtraLayoutSpace()影响可见区域,从而达到预加载ViewHolder的目的。
不清楚这个方法是不是可用,不推荐使用。原因是calculateExtraLayoutSpace()方法标注:使用会带来显著的性能成本。原理不了解。

10.减少ItemView监听器的创建

参考地址

chatgpt

RecyclerView加载图片滑动卡顿:https://blog.csdn.net/qq_33240767/article/details/80783077

掉RecyclerView的默认item动画:https://blog.csdn.net/CSDN_LQR/article/details/54766560

RecyclerView性能优化:https://www.cnblogs.com/zgz345/p/13436005.html

RecyclerView Prefetch功能探究:https://blog.csdn.net/crazy_everyday_xrp/article/details/70344638

Android深入理解RecyclerView的缓存机制:https://blog.csdn.net/u013700502/article/details/105058771/

真正带你搞懂 RecyclerView 的缓存机制,再也不怕面试被虐了:https://zhuanlan.zhihu.com/p/80475040

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/你好赵伟/article/detail/174293
推荐阅读
相关标签
  

闽ICP备14008679号