支持 NestedScrollView 与 RecyclerView 嵌套双滑动的布局管理器

NestedScrollView 与 RecyclerView 的嵌套使用问题,在网上看到的都是说给 RecyclerView 设置 setNestedScrollingEnabled(false)。但是这个方法会有一个问题,
那就是要求 RecyclerView 的高度是 wrap_content 的 (要不然高度太小了,底部的 Item View 滑动不出来),即有多少 Item View,就得要有相应的多大高度。但这不是我想要的,一是这会导致 ViewHolder 不能被回收!!!RecyclerView 就失去了 Recycle 的意义!!! 二是在向顶部快速 fling 滑动的时候,整体滑动效果不连贯。 有人还称这个方法很优雅,其实一点也不优雅。。。

上视频看看使用前,使用后分别是什么样的效果:

before

after

(原创作品,转载请声明出处:https://blog.csdn.net/hegan2010/article/details/113103751)

为了解决这个问题,使得 NestedScrollView 与 RecyclerView 可以双滑动,并且不产生滑动冲突,写了一个布局管理器。
原理是,在RecyclerView的可见区域顶部低于 NestedScrollView 可见区域顶部时,减少 RecyclerView 在Y轴上消费掉滑动距离,或者设为0,或者为两者高度距离差,让NestedScrollView 消费掉更多的滑动距离。

实现代码如下(继承自GridLayoutManager,可以根据实际情况修改为继承自哪个LayoutManager,只要重载了 scrollVerticallyBy 方法就行):

import android.content.Context;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewParent;

import androidx.annotation.CallSuper;
import androidx.core.view.ViewCompat;
import androidx.core.view.ViewParentCompat;
import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.RecyclerView;

public class AutoScrollGridLayoutManager extends GridLayoutManager implements
        View.OnLayoutChangeListener {
    protected RecyclerView mRecyclerView;
    private boolean mForceScrolling;

    public AutoScrollGridLayoutManager(Context context, AttributeSet attrs, int defStyleAttr,
            int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
    }

    public AutoScrollGridLayoutManager(Context context, int spanCount) {
        super(context, spanCount);
    }

    public AutoScrollGridLayoutManager(Context context, int spanCount, int orientation,
            boolean reverseLayout) {
        super(context, spanCount, orientation, reverseLayout);
    }

    @CallSuper
    @Override
    public void onAttachedToWindow(RecyclerView view) {
        super.onAttachedToWindow(view);
        mRecyclerView = view;
        mRecyclerView.addOnLayoutChangeListener(this);
    }

    @CallSuper
    @Override
    public void onDetachedFromWindow(RecyclerView view, RecyclerView.Recycler recycler) {
        super.onDetachedFromWindow(view, recycler);
        mRecyclerView.removeOnLayoutChangeListener(this);
        mRecyclerView = null;
    }

    @CallSuper
    @Override
    public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler,
            RecyclerView.State state) {
        if (dy == 0) {
            return super.scrollVerticallyBy(dy, recycler, state);
        } else if (mForceScrolling) {
            super.scrollVerticallyBy(-dy, recycler, state);
            mForceScrolling = false;
            return 0;
        }
        ViewGroup parentView = getParentNestedScrollView(mRecyclerView);
        if (parentView == null) {
            return super.scrollVerticallyBy(dy, recycler, state);
        }
        Rect parentVisibleRect = new Rect();
        parentView.getGlobalVisibleRect(parentVisibleRect);
        Rect recyclerVisibleRect = new Rect();
        mRecyclerView.getGlobalVisibleRect(recyclerVisibleRect);
        if (dy > 0) {
            int verticalOffset = recyclerVisibleRect.top - parentVisibleRect.top;
            if (verticalOffset <= 0) {
                return super.scrollVerticallyBy(dy, recycler, state);
            } else { // verticalOffset > 0
                if (dy <= verticalOffset) {
                    return 0;
                } else { // dy > verticalOffset
                    return super.scrollVerticallyBy(dy - verticalOffset, recycler, state);
                }
            }
        } else {
            int verticalOffset = recyclerVisibleRect.bottom - parentVisibleRect.bottom;
            if (verticalOffset >= 0) {
                return super.scrollVerticallyBy(dy, recycler, state);
            } else { // verticalOffset < 0
                if (dy >= verticalOffset) {
                    return 0;
                } else { // dy < verticalOffset
                    return super.scrollVerticallyBy(dy - verticalOffset, recycler, state);
                }
            }
        }
    }

    @CallSuper
    @Override
    public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft,
            int oldTop, int oldRight, int oldBottom) {
        mForceScrolling = false;
        ViewGroup parentView = getParentNestedScrollView(mRecyclerView);
        if (parentView == null) {
            return;
        }
        int offset = mRecyclerView.computeVerticalScrollOffset();
        int extent = mRecyclerView.computeVerticalScrollExtent();
        int range = mRecyclerView.computeVerticalScrollRange();
        int bottomRemain = range - offset - extent;
        if (bottomRemain <= 0) {
            return;
        }
        Rect parentVisibleRect = new Rect();
        parentView.getGlobalVisibleRect(parentVisibleRect);
        Rect recyclerVisibleRect = new Rect();
        mRecyclerView.getGlobalVisibleRect(recyclerVisibleRect);
        int bottomCovered = parentVisibleRect.bottom - recyclerVisibleRect.bottom;
        if (bottomCovered > 0) {
            mForceScrolling = true;
            mRecyclerView.startNestedScroll(ViewCompat.SCROLL_AXIS_VERTICAL);
            mRecyclerView.scrollBy(0, -Math.min(bottomRemain, bottomCovered));
        }
    }

    public static ViewGroup getParentNestedScrollView(RecyclerView recyclerView) {
        if (!recyclerView.isNestedScrollingEnabled()) {
            return null;
        }
        int axes = ViewCompat.SCROLL_AXIS_VERTICAL;
        ViewParent p = recyclerView.getParent();
        View child = recyclerView;
        while (p != null) {
            if (ViewParentCompat.onStartNestedScroll(p, child, recyclerView, axes)) {
                return (p instanceof ViewGroup) ? (ViewGroup) p : null;
            }
            if (p instanceof View) {
                child = (View) p;
            }
            p = p.getParent();
        }
        return null;
    }
}
已标记关键词 清除标记
相关推荐
课程简介: 历经半个多月的时间,Debug亲自撸的 “企业员工角色权限管理平台” 终于完成了。正如字面意思,本课程讲解的是一个真正意义上的、企业级的项目实战,主要介绍了企业级应用系统中后端应用权限的管理,其中主要涵盖了六大核心业务模块、十几张数据库表。 其中的核心业务模块主要包括用户模块、部门模块、岗位模块、角色模块、菜单模块和系统日志模块;与此同时,Debug还亲自撸了额外的附属模块,包括字典管理模块、商品分类模块以及考勤管理模块等等,主要是为了更好地巩固相应的技术栈以及企业应用系统业务模块的开发流程! 核心技术栈列表: 值得介绍的是,本课程在技术栈层面涵盖了前端和后端的大部分常用技术,包括Spring Boot、Spring MVC、Mybatis、Mybatis-Plus、Shiro(身份认证与资源授权跟会话等等)、Spring AOP、防止XSS攻击、防止SQL注入攻击、过滤器Filter、验证码Kaptcha、热部署插件Devtools、POI、Vue、LayUI、ElementUI、JQuery、HTML、Bootstrap、Freemarker、一键打包部署运行工具Wagon等等,如下图所示: 课程内容与收益: 总的来说,本课程是一门具有很强实践性质的“项目实战”课程,即“企业应用员工角色权限管理平台”,主要介绍了当前企业级应用系统中员工、部门、岗位、角色、权限、菜单以及其他实体模块的管理;其中,还重点讲解了如何基于Shiro的资源授权实现员工-角色-操作权限、员工-角色-数据权限的管理;在课程的最后,还介绍了如何实现一键打包上传部署运行项目等等。如下图所示为本权限管理平台的数据库设计图: 以下为项目整体的运行效果截图: 值得一提的是,在本课程中,Debug也向各位小伙伴介绍了如何在企业级应用系统业务模块的开发中,前端到后端再到数据库,最后再到服务器的上线部署运行等流程,如下图所示:
©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页