Android 类似淘宝的吸顶特效 NestedScrollView+RecycleView

运行图

布局的设计

要实现上面的效果需要搞定NestedScrollView和RecycleView的滑动冲突。有人要问RecycleView为何要滑动自动撑大不就好了么?这个问题其实对于有限的资源加载来说是很好的解决方案,但是如果涉及到的是图文结合的并且有大批量的数据的时候就需要用到RecycleView的复用机制,这样就要求RecycleView固定高度。

这里面涉及到的几个参数:

1:整屏高度 screenHeight  (用此方法可以省掉Context的传入)

    /**
     * 获取屏幕高度 px
     * @return
     */
    public static int getScreenHeightPixels() {
        return Resources.getSystem().getDisplayMetrics().heightPixels;
    }

2:头部悬浮框高度 floatActionBarHeight

这里直接用View去getHeight()就行,这个方法在使用的时候调用就好,如果一开始就调用有可能拿不到数据,因为页面还没加载完成

3:底部导航高度 bottomBarHeight

这个可以是固定值,看自己的设计是多大的,我的设定是48dp,所以转化后的结果是。

  //将设置的db转为屏幕像素
    public static int dp2px(int dpVal) {
        return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
                dpVal, Resources.getSystem().getDisplayMetrics());
    }

4:悬浮tab的高度 floatTabBarHeight

跟导航的高度一样处理。

在这里RecycleView的高度值我们有了方案。

scrollRecycleHeight=screenHeight-floatActionBarHeight-bottomBarHeight-floatTabBarHeight

设置高度在数据返回回来的时候或者一开始的时候设置都行。

接下来要处理的就是滑动冲突的问题

NestedScrollingChildHelper 提供了一个禁止view滑动的方法,这个类封装在了RecycleView内部,所以RecycleView也可以调用。
 public void setNestedScrollingEnabled(boolean enabled) {
        if (this.mIsNestedScrollingEnabled) {
            ViewCompat.stopNestedScroll(this.mView);
        }

        this.mIsNestedScrollingEnabled = enabled;
    }

说到这里小伙伴可能会说那就简单了,只要NestedScrollView没有滑动到浮层tab的位置的时候都禁止RecyceView的滑动就好了,超出的时候允许其滑动是不是就可以解决了?

事实的情况并不是这样。当我们测试的时候,当我们慢慢滑动NestedScrollView,当屏幕出现RecycleView的时候,这时候NestedScroolView的滑动惯性还在继续,我们此时触摸滑动RecycleView 是可以滑动的。具体原因大家可以查看源码去分析下,这里不做分析。

解决方案

重写NestedScrollView 实现拦截。

public class NestedInsScrollView extends NestedScrollView {
    private int limitHeight = 0;
    private float startX, startY;

    public void setLimitHeight(int limitHeight) {
        this.limitHeight = limitHeight;
    }
    public NestedInsScrollView(@NonNull Context context) {
        super(context);
    }

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

    public NestedInsScrollView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }
    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        int scrollY = 0;
        switch (ev.getAction()) {
            case MotionEvent.ACTION_DOWN:
                startX = ev.getX();
                startY = ev.getY();
                break;
            case MotionEvent.ACTION_MOVE:
                scrollY = getScrollY();
                // 判断是否需要拦截子View的滑动事件
                float offsetX = Math.abs(ev.getX() - startX);
                float offsetY = Math.abs(ev.getY() - startY);
                if (offsetY > offsetX && ev.getY() - startY < 0) {//向上的垂直滑动
                    if (scrollY < limitHeight) {
                        return true;
                    }
                }
                break;
            case MotionEvent.ACTION_UP:
            case MotionEvent.ACTION_CANCEL:
                break;
        }
        return super.onInterceptTouchEvent(ev);
    }
}

这里面通过重写onInterceptTouchEvent 在ACTION_MOVE事件中判断是否可以滚动,如果不允许RecycleView滚动则事件不往下分发。

这里面需要注意的点:

1:要判断事件是垂直滚动 (因为内部的View还要处理左右滚动的事件)

2:要判断是向上滚动 (因为头部的RecycleView还要处理下拉刷新)

3:最后只有满足垂直向上并且滚动距离达到tab的浮动距离的时候才拦截

     homeScrollView.setOnScrollChangeListener(new 
            NestedScrollView.OnScrollChangeListener() {
            @Override
            public void onScrollChange(NestedScrollView v, int scrollX, int     
                                  scrollY, int oldScrollX, int oldScrollY) {
                int limitHeight = ((ViewGroup) 
                homeScrollView.getChildAt(0)).getChildAt(0).getHeight();
                int topHeight = homeHeadTopLayout.getHeight();
                homeScrollView.setLimitHeight(limitHeight - topHeight -             
                DisplayUtils.dp2px(5));
                // 例如:
                if (scrollY >= limitHeight - topHeight - DisplayUtils.dp2px(5)) {
                    main_home_services_tab_recycle.setNestedScrollingEnabled(true);
                } else {
                   main_home_services_tab_recycle.setNestedScrollingEnabled(false);
                }
            }
        });

这时候再设置  setNestedScrollingEnabled(true/false);

就可以解决问题了。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
好的,针对你的问题,我可以提供一些思路和参考代码。 首先,实现 Android 中的自定义吸顶,可以采用两种方式: 1. 使用固定顶部控件 + ScrollView + ViewPager 的方式实现。 这种方式比较常见,具体实现思路如下: (1)在布局文件中定义一个固定在顶部的控件,例如 LinearLayout,将其设置为可见性为 gone,即不可见。 (2)在 ScrollView 中添加 ViewPager,将其填充满整个布局,用于滑动切换不同的子页面。 (3)监听 ScrollView滑动事件,在滑动到一定位置时,将顶部控件设置为可见,实现吸顶效果。 具体实现代码可以参考以下链接: - https://www.jianshu.com/p/4f28a4d0c3b1 - https://www.cnblogs.com/xiaohuafice/p/11050662.html 2. 使用 CoordinatorLayout + AppBarLayout 实现。 这种方式相对来说比较简单,具体实现思路如下: (1)在布局文件中使用 CoordinatorLayout 作为根布局,并添加一个 AppBarLayout 作为子布局。 (2)在 AppBarLayout 中添加一个 Toolbar 控件作为顶部的固定控件,将其设置为可见性为 gone。 (3)在子页面中,使用 NestedScrollView 作为滑动的容器,并将其放在 AppBarLayout 的下面。 (4)监听 NestedScrollView滑动事件,在滑动到一定位置时,将 Toolbar 设置为可见,实现吸顶效果。 具体实现代码可以参考以下链接: - https://www.jianshu.com/p/5d0f7e7e7c97 - https://www.jianshu.com/p/5d0f7e7e7c97 希望以上内容能够帮助到你,有什么问题可以再和我交流哦!

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Rnwater

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值