IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 移动开发 -> RecyclerView懒加载失效问题(三) -> 正文阅读

[移动开发]RecyclerView懒加载失效问题(三)

前言:通过NestedScrollView嵌套RecyclerView可以轻松实现嵌套滑动,但我们会发现RecyclerView懒加载失效了。

<?xml version="1.0" encoding="utf-8"?>
<androidx.core.widget.NestedScrollView xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    xmlns:app="http://schemas.android.com/apk/res-auto">
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        android:descendantFocusability="blocksDescendants">
        <TextView
            android:id="@+id/tv_title"
            android:layout_width="match_parent"
            android:layout_height="100dp"
            android:background="@color/colorAccent"
            android:gravity="center"
            android:text="这是头部" />
        <androidx.recyclerview.widget.RecyclerView
            android:id="@+id/rv_content"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
            android:orientation="vertical"/>
    </LinearLayout>
</androidx.core.widget.NestedScrollView>

原因:NestedScrollView高度虽然为屏幕高度,但其对子布局会进行wrap_content方式测量,这里LinearLayout即使是match_parent也不是屏幕高度,因此传递给其子RecyclerView也wrap_content方式得到的高度,导致RecyclerView一次加载出全部数据。(NestedScrollView嵌套滑动原理是还是平面式的滑动,RecyclerView由于加载了全部数据,本身不再滑动而是随着将LinearLayout移动实现的)

方案:要使RecyclerView懒加载则不能使其高度包裹所有item,需要指定最大高度(本例中最大高度为屏幕高度,先是NestedScrollView响应滑动当LinearLayout至最底部时,此时RecyclerView正好显示全,接下来再滑动就由RecyclerView来完成,这才是真正的嵌套滑动)

1、自定义RecyclerView使其支持设置最大高度

public class MaxHeightRecyclerView extends RecyclerView {
    private int mMaxHeight;

    public MaxHeightRecyclerView(@NonNull Context context) {
        super(context);
    }

    public MaxHeightRecyclerView(@NonNull Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    protected void onMeasure(int widthSpec, int heightSpec) {
        if (mMaxHeight != 0){
            super.onMeasure(widthSpec, View.MeasureSpec.makeMeasureSpec(mMaxHeight, View.MeasureSpec.AT_MOST));
        }else {
            super.onMeasure(widthSpec, heightSpec);
        }
    }

    public void setMaxHeight(int maxHeight) {
        this.mMaxHeight = maxHeight;
    }
}

2、自定义NestedScrollView,使其给RecyclerView设置最大高度

public class LazyNestedScrollView extends androidx.core.widget.NestedScrollView{
    private int mRecyclerViewId;
    private MaxHeightRecyclerView mRecyclerView;

    public LazyNestedScrollView(@NonNull Context context) {
        super(context);
    }

    public LazyNestedScrollView(@NonNull Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.LazyNestedScrollView);
        //获取嵌套RecyclerView控件的id
        mRecyclerViewId = typedArray.getResourceId(R.styleable.LazyNestedScrollView_recyclerview_id, 0);
        typedArray.recycle();
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        //在super前给RecyclerView设置最大值,然后通过super进行测量即可
        int height = MeasureSpec.getSize(heightMeasureSpec);
        if (mRecyclerView != null){
            mRecyclerView.setMaxHeight(height);
        }else {
            findViewById(this);
            if (mRecyclerView != null){
                mRecyclerView.setMaxHeight(height);
            }
        }
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }

    //根据id递归查询RecyclerView
    private void findViewById(View view){
        if (view instanceof ViewGroup && !(view instanceof RecyclerView)){
            ViewGroup viewGroup = (ViewGroup) view;
            int childCount = viewGroup.getChildCount();
            for (int i = 0; i < childCount; i++){
                View childView = viewGroup.getChildAt(i);
                findViewById(childView);
            }
        }else {
            if (view.getId() == mRecyclerViewId && view instanceof MaxHeightRecyclerView){
                mRecyclerView = (MaxHeightRecyclerView) view;
            }
        }
    }

    @Override
    public void onNestedPreScroll(@NonNull View target, int dx, int dy, @NonNull int[] consumed, int type) {
        if (mRecyclerView != null){
            View child = getChildAt(0);
            MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
            //获取自己能够滑动的距离
            int topHeight = child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin - getMeasuredHeight();
            int scrollY = getScrollY();
            boolean topIsShow = scrollY  >=0 && scrollY  < topHeight;
            if(topIsShow) {
                //由自己响应滑动
                int remainScrollY= topHeight - scrollY;
                int selfScrollY = remainScrollY > dy ? dy : remainScrollY;
                scrollBy(0, selfScrollY);
                //告诉RecyclerView,自己滑动了多少距离
                consumed[1] = selfScrollY;
            } else {
                super.onNestedPreScroll(target, dx, dy, consumed, type);
            }
        }else {
            super.onNestedPreScroll(target, dx, dy, consumed, type);
        }
    }
}

注:

(1)RecyclerView的setNestedScrollingEnabled()应为true

(2)嵌套滑动是通过NestedScrollingParent3和NestedScrollingChild3来实现的,这里RecyclerView已经继承NestedScrollingChild3而NestedScrollView也已继承NestedScrollingParent3,RecyclerView先接收滑动事件然后先询问NestedScrollView来滑动(即onNestedPreScroll方法)然后将其滑动距离告诉RecyclerView,RecyclerView再对剩下的距离进行滑动

3、惯性滑动(NestedScrollView滑动到底部,将其滑动速度转化成惯性距离,计算子控件应滑距离=父惯性距离-父已滑距离,将子控件应滑距离转化成速度交给子控件进行惯性滑动)

(1)记录NestedScrollView惯性速度

@Override
public void fling(int velocityY) {
    super.fling(velocityY);
    mVelocityY = velocityY;
}

(2)将剩余的惯性速度传递给RecyclerView

    @Override
    protected void onScrollChanged(int scrollX, int scrollY, int oldScrollX, int oldScrollY) {
        super.onScrollChanged(scrollX, scrollY, oldScrollX, oldScrollY);
        if (mRecyclerView != null){
            View child = getChildAt(0);
            MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
            //判断是否滑动到底部
            if (scrollY == child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin - getMeasuredHeight()){
                if (mVelocityY > 0){
                    //将惯性速度转化为滑动距离
                    double distance = FlingHelper.getInstance(getContext()).getSplineFlingDistance(mVelocityY);
                    if (distance > scrollY){
                        //将剩余滑动距离转化为惯性速度
                        int velocityY = FlingHelper.getInstance(getContext()).getVelocityByDistance(distance - scrollY);
                        //将剩余惯性速度传递给RecyclerView
                        mRecyclerView.fling(0, velocityY);
                        //重置惯性速度
                        mVelocityY = 0;
                    }
                }
            }
        }
    }

(3)惯性速度和滑动距离转化工具类

public class FlingHelper {
    private static FlingHelper mFlingHelper;
    private static final double DECELERATION_RATE = Math.log(0.78) / Math.log(0.9);
    private float mFlingFriction = ViewConfiguration.getScrollFriction();
    private float mPhysicalCoeff;
    
    private FlingHelper(Context context){
        mPhysicalCoeff = context.getResources().getDisplayMetrics().density * 160.0f * 386.0878f * 0.84f;
    }

    public static FlingHelper getInstance(Context context){
        if (mFlingHelper == null){
            mFlingHelper = new FlingHelper(context);
        }
        return mFlingHelper;
    }

    public double getSplineFlingDistance(int i){
        return Math.exp(getSplineDeceleration(i) * (DECELERATION_RATE / (DECELERATION_RATE - 1.0))) * (mFlingFriction * mPhysicalCoeff);
    }

    private double getSplineDeceleration(int i){
        return Math.log(0.35f * Math.abs(i) / (mFlingFriction * mPhysicalCoeff));
    }

    public int getVelocityByDistance(double d){
        return (int) Math.abs((Math.exp(getSplineDecelerationByDistance(d)) * mFlingFriction * mPhysicalCoeff / 0.3499999940395355));
    }

    private double getSplineDecelerationByDistance(double d){
        return (DECELERATION_RATE - 1.0) * Math.log(d / (mFlingFriction * mPhysicalCoeff)) / DECELERATION_RATE;
    }
}

                
        
        
  移动开发 最新文章
Vue3装载axios和element-ui
android adb cmd
【xcode】Xcode常用快捷键与技巧
Android开发中的线程池使用
Java 和 Android 的 Base64
Android 测试文字编码格式
微信小程序支付
安卓权限记录
知乎之自动养号
【Android Jetpack】DataStore
上一篇文章      下一篇文章      查看所有文章
加:2022-09-21 00:40:22  更:2022-09-21 00:43:33 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/25 5:29:57-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码