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;
    }
}

                
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 懒加载的原理是当图片进入浏览器可视区域时才进行加载,如果你是通过图片链接发起get请求获取加密数据,那么就会导致图片一开始就被加载,懒加载的效果自然就失效了。 解决方案: 1. 将加密数据返回为 base64 编码的字符串格式,然后将其赋值给 img 标签的 src 属性,这样就可以直接渲染图片,并且可以懒加载。 2. 在服务器端对图片进行加密处理,然后返回加密后的图片数据和解密所需的密钥,在客户端进行解密后再渲染图片,这样也可以实现懒加载的效果。 需要注意的是,第二种方案需要在客户端进行解密操作,可能会增加一些计算量,需要考虑到性能问题。同时,为了保证图片的安全性,需要使用可靠的加密算法进行处理。 ### 回答2: 在Vue中,要用图片链接发起GET请求获取加密数据并解密后渲染,可能会遇到懒加载失效问题。解决这个问题可以尝试以下几种方法: 1. 使用Vue的延迟加载插件,例如vue-lazyload,来处理图片懒加载。这个插件可以在图片进入可视区域后才加载图片,从而避免懒加载失效问题。 2. 通过监听图片DOM元素的可见性来进行懒加载。可以使用Intersection Observer API来检测图片是否进入可视区域,如果进入可视区域则开始加载图片。具体实现可以参考Vue官方文档中关于Intersection Observer的使用。 3. 可以将获取的加密数据先解密后再绑定到Vue的data属性中,并在页面上渲染解密后的数据。这样可以避免在图片加载时才解密的延迟,从而解决懒加载失效问题。 4. 如果以上方法都无法解决问题,可以考虑使用Vue的computed属性,将获取图片链接和解密逻辑封装在computed属性中,每当获取的图片链接发生变化时,自动进行解密并返回解密后的数据。这样可以有效地解决懒加载失效问题,并且保持代码的可读性和维护性。 综上所述,针对Vue中使用图片链接发起GET请求获取加密数据,解密后渲染时懒加载失效问题,可以尝试使用延迟加载插件、Intersection Observer API、解密后绑定数据或computed属性等方法来解决。 ### 回答3: 在Vue中,要通过图片链接发起GET请求获取加密数据并解密后渲染,有时会出现懒加载失效问题。这个问题可以通过以下几种方式解决: 1. 引入第方库:可以使用一些第方库,例如axios或vue-resource来发送图片链接的GET请求。这些库提供了更多的配置选项,如自定义请求头、请求参数等。同时,它们也提供了响应拦截器的功能,可以在获取到加密数据后进行解密,并进行相应的渲染。 2. 自定义指令:可以通过自定义指令来实现图片的懒加载功能。可以监听滚动事件,并判断图片是否出现在可视区域内,如果是,则发送GET请求获取加密数据,并进行解密后的渲染。这种方式可以更加灵活地控制懒加载的条件。 3. 使用Vue的生命周期方法:可以在created或mounted生命周期方法中发送GET请求获取加密数据,并进行解密后的渲染。这样可以确保组件加载完成后再进行图片的加载和渲染,避免懒加载失效问题。 4. 使用异步组件:可以将图片的加载和渲染放在异步组件中进行处理。异步组件可以延迟加载和渲染,当异步组件被激活时才进行图片的加载和渲染。这样可以确保在需要时进行图片的加载,避免懒加载失效问题。 以上是几种解决Vue中图片链接懒加载失效问题的方法,可以根据具体情况选择其中一种或多种方式来解决。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值