多层tablayout+ViewPager,NestedScrollView+ViewPager+RecyclerView,嵌套吸顶滑动冲突

先看实现的UI效果
在这里插入图片描述

其实就是仿BOSS的页面效果,第二层tab下的viewpager滑到最右边再右滑,就操作第一层viewpager滑动。页面上滑时把第一层tab和vp里的banner都推出界面,让第二层tab吸顶。

滑上去第二个tab块卡在顶部,如图
![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/e6b4913ee2484b4c828cdbdd1aae1474.png
我混乱的思路:
之前没有外层tab的需求,直接用CoordinatorLayout的一套放在布局文件里做吸顶就行了。
现在是把CoordinatorLayout挪到第一层用(用scrollView嵌套也行,但是也要处理滑动冲突,以及第一层viewpager卡在界面上才行,感觉更麻烦)

    <androidx.coordinatorlayout.widget.CoordinatorLayout
        android:id="@+id/coordinator"
        style="forn"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <com.google.android.material.appbar.AppBarLayout
            android:id="@+id/appbar"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:background="@color/transparent"
            android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar">

            <com.google.android.material.appbar.CollapsingToolbarLayout
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                app:layout_scrollFlags="scroll|exitUntilCollapsed"
                app:titleEnabled="false">

                <com.google.android.material.tabs.TabLayout
                    android:id="@+id/tab_layout"
                    android:layout_width="match_parent"
                    android:layout_height="@dimen/dp_44"
                    app:tabIndicator="@drawable/indicator_tab" />
            </com.google.android.material.appbar.CollapsingToolbarLayout>

        </com.google.android.material.appbar.AppBarLayout>

        <androidx.appcompat.widget.LinearLayoutCompat
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:orientation="vertical"
            app:layout_behavior="@string/appbar_scrolling_view_behavior">

            <com.shirley.widget.ChildViewPager
                android:layout_width="match_parent"
                android:layout_height="match_parent" />
        </androidx.appcompat.widget.LinearLayoutCompat>
    </androidx.coordinatorlayout.widget.CoordinatorLayout>

这样外层页面上滑时只会推出第一层tab,第一层viewpager都会卡在屏幕里,至于banner,在第二层页面里处理。

第二层页面用NestedScrollView嵌套了viewpager,放了一个第二层的假tabLayout在第二层vp顶部,计算banner滑出页面时,也就是真tab滑到顶部时,显示假tab,做吸顶效果。
我这里的NestedScrollView外面还套了一层SmartRefreshLayout,因为这个页面还有整体刷新功能。
第二层fragment中代码如下:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@color/windowBackground"
    android:orientation="vertical">
    
    <com.scwang.smartrefresh.layout.SmartRefreshLayout
        android:id="@+id/smart_refresh_layout"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_above="@+id/rl_bottom">

        <com.scwang.smartrefresh.layout.header.ClassicsHeader
            android:layout_width="match_parent"
            android:layout_height="wrap_content" />

        <com.shirley.widget.CustomNestedScrollView
            android:id="@+id/scroll_view"
            android:layout_width="match_parent"
            android:layout_height="match_parent">

         <LinearLayout
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
             android:orientation="vertical">

                <!-- banner -->
              <com.youth.banner.Banner
                   android:id="@+id/banner"
                   android:layout_width="match_parent"
                   android:layout_height="@dimen/dp_114"
                   android:contentDescription="@null"/>

                 <!-- 第二层tab -->
               <com.google.android.material.tabs.TabLayout
                    android:id="@+id/tab_layout"
                    android:layout_width="match_parent"
                    android:layout_height="50dp" />
                    
               <com.shirley.widget.ChildViewPager
                    android:id="@+id/view_pager"
                    android:layout_width="match_parent"
                    android:layout_height="match_parent"/>

            </LinearLayout>
        </com.shirley.widget.CustomNestedScrollView>
        
        <com.scwang.smartrefresh.layout.footer.ClassicsFooter
            android:layout_width="match_parent"
            android:layout_height="wrap_content" />
    </com.scwang.smartrefresh.layout.SmartRefreshLayout>

    <!-- 第二层假tab吸顶占位 -->
     <com.google.android.material.tabs.TabLayout
        android:layout_width="match_parent"
        android:layout_height="50dp"
        android:visibility="gone" />
        
</RelativeLayout>

不行我要下班了 今天先写到这

继续
布局中用到的viewpager是自定义的ChildViewPager,可以判断在最后一个item滑动时,把事件让给父viewpager。

public class ChildViewPager extends ViewPager {

    int startX;
    int startY;

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

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

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        //页面当前位置
        int currentPosition;
        //总页数
        int count = Objects.requireNonNull(getAdapter()).getCount();

        switch (ev.getAction()) {
            case MotionEvent.ACTION_DOWN:
                startX = (int) ev.getRawX();
                //startY = (int) ev.getY();
                //申请让父View 不要拦截触摸事件
                getParent().requestDisallowInterceptTouchEvent(true);
                break;
            case MotionEvent.ACTION_MOVE:
                int endX = (int) ev.getRawX();
                int disX = endX - startX;

                currentPosition = this.getCurrentItem();

                //最后一页且往左滑,由父View拦截触摸事件
                if (currentPosition == count - 1 && disX < 0) {
                    //申请让父View拦截触摸事件
                    getParent().requestDisallowInterceptTouchEvent(false);
                }
                //第一页且右滑,由父View拦截触摸事件
                else if (currentPosition == 0 && disX > 0) {
                    //申请让父View拦截触摸事件
                    getParent().requestDisallowInterceptTouchEvent(false);
                }

                //其他情况,由自己拦截
                else {
                    //申请让父View拦截触摸事件
                    getParent().requestDisallowInterceptTouchEvent(true);
                }
                break;
            case MotionEvent.ACTION_UP:
            case MotionEvent.ACTION_CANCEL:
                getParent().requestDisallowInterceptTouchEvent(false);
                break;
        }

        return super.dispatchTouchEvent(ev);
    }
}

自定义的CustomNestedScrollView,判断NestedScrollView是否拦截滑动事件

public class CustomNestedScrollView extends NestedScrollView {
    private boolean isNeedScroll = true;

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

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

    public CustomNestedScrollView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        float y = ev.getY();
        switch (ev.getAction()) {
            case MotionEvent.ACTION_MOVE:
                return isNeedScroll;
        }
        return super.onInterceptTouchEvent(ev);
    }

    /*
     * 处理NestedScrollView是否拦截滑动事件
     */
    public void setNeedScroll(boolean isNeedScroll) {
        this.isNeedScroll = isNeedScroll;
    }
}

第二层vp的子fragment中处理scrollView的事件分发和假tab吸顶,判断已经划过banner,显示假tab。
有个问题是scrollVIew一直拿到触摸事件,会抢走页面上的点击事件,加了个判断

        val measureW = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)
        val measureH = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)
        binding.rlBanner.measure(measureW, measureH)
        binding.scrollView.setOnScrollChangeListener { v, scrollX, scrollY, oldScrollX, oldScrollY ->
            if (Math.abs(scrollY - oldScrollY) < 2 || scrollY == 0) {
                //滑动不超过2 认为是点击事件(否则isNeedScroll=true会拦截界面点击事件)
                binding.scrollView.setNeedScroll(false)
            } else {
                if (scrollY > binding.rlBanner.measuredHeight) {
                    binding.llTabFake.visibility = View.VISIBLE
                    binding.scrollView.setNeedScroll(false)
                } else {
                    binding.llTabFake.visibility = View.GONE
                    binding.scrollView.setNeedScroll(true)
                }
            }
        }
        binding.scrollView.setNeedScroll(false)

还要处理scrollVIew嵌套viewpager时,vp的高度为0,这里动态给它一个高度,它的最大高度也就是吸顶时tab下方的屏幕高度

        binding.llTabFake.measure(measureW, measureH)
        val params: ViewGroup.LayoutParams = binding.viewPager.layoutParams
        params.height = ScreenUtil.getScreenH(requireContext()) - binding.llTabFake.measuredHeight + 1
        binding.viewPager.layoutParams = params

注意这里的高度有一个+1,因为不+1就刚好是TabLayout要出现的临界点,也就是ViewPager恰好的高度,但是这个时候又刚好是我们NestedScrollView拦截没有取消的临界点,所以在上滑时,TabLayout刚好悬浮顶部的时候,RecyclerView没有获取事件,就会无法进行滑动。
这里是参考的这个https://www.cnblogs.com/shen-hua/p/8052459.html
大概就是这样了


再次更新
发现嵌套的组件向上滑动时,经常会卡在banner上,只滑动最里层viewpager里的list,滑到最后才会把整个banner顶上去,露出隐藏的假tab
(如果你的页面没用中间那块banner就不用管,之前那样是刚好的)

在这里插入图片描述
手指在列表处向上滑动时,如下图,第一层tab已经推上去,banner和第二层tab卡在顶部不动,list可以往上滑,但是不会把上面那块顶出去
在这里插入图片描述

这里也找到了一个解决办法,参照https://www.jianshu.com/p/b9ddac13b135
在第二层自定义的viewPager中重写onMeasure()方法,把它测量中的MeasureSpec.EXACTLY换成MeasureSpec.UNSPECIFIED

public class ChildViewPager extends ViewPager {

    int startX;
    int startY;

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

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

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        //页面当前位置
        int currentPosition;

        //pager无数据
        if (getAdapter() == null)
            return super.dispatchTouchEvent(ev);

        //总页数
        int count = Objects.requireNonNull(getAdapter()).getCount();

        switch (ev.getAction()) {
            case MotionEvent.ACTION_DOWN:
                startX = (int) ev.getRawX();
                startY = (int) ev.getY();
                //申请让父View 不要拦截触摸事件
                getParent().requestDisallowInterceptTouchEvent(true);
                break;
            case MotionEvent.ACTION_MOVE:
                int endX = (int) ev.getRawX();
                int disX = endX - startX;
                int moveY = (int) ev.getRawY();

                currentPosition = this.getCurrentItem();

                //最后一页且往左滑,由父View拦截触摸事件
                if (currentPosition == count - 1 && disX < 0) {
                    //申请让父View拦截触摸事件
                    getParent().requestDisallowInterceptTouchEvent(false);
                }
                //第一页且右滑,由父View拦截触摸事件
                else if (currentPosition == 0 && disX > 0) {
                    //申请让父View拦截触摸事件
                    getParent().requestDisallowInterceptTouchEvent(false);
                }

                //其他情况,由自己拦截
                else {
                    //申请让父View拦截触摸事件
                    getParent().requestDisallowInterceptTouchEvent(true);
                }
                break;
            case MotionEvent.ACTION_UP:
            case MotionEvent.ACTION_CANCEL:
                getParent().requestDisallowInterceptTouchEvent(false);
                break;
        }

        return super.dispatchTouchEvent(ev);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int height = 0;
        for (int i = 0; i < getChildCount(); i++) {
            View child = getChildAt(i);
            if(child.getVisibility() != GONE){
                child.measure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
                int h = child.getMeasuredHeight();
                if(h > height){
                    height = h;
                }
            }
        }

        heightMeasureSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY);
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }
}
好的,下面是一个使用TabLayout+ViewPager+Fragmet+RecyclerView结合的小demo的示例代码。 1. 首先,在xml文件中添加TabLayout和ViewPager组件。 ```xml <android.support.design.widget.TabLayout android:id="@+id/tab_layout" android:layout_width="match_parent" android:layout_height="wrap_content" /> <android.support.v4.view.ViewPager android:id="@+id/view_pager" android:layout_width="match_parent" android:layout_height="match_parent" /> ``` 2. 创建Fragment类,用于展示RecyclerView数据。 ```java public class SimpleFragment extends Fragment { private RecyclerView recyclerView; private SimpleAdapter adapter; @Nullable @Override public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { View view = inflater.inflate(R.layout.fragment_simple, container, false); recyclerView = view.findViewById(R.id.recycler_view); recyclerView.setLayoutManager(new LinearLayoutManager(getContext())); adapter = new SimpleAdapter(); recyclerView.setAdapter(adapter); return view; } } ``` 3. 创建FragmentPagerAdapter类,用于管理Fragment。 ```java public class SimplePagerAdapter extends FragmentPagerAdapter { private String[] titles; public SimplePagerAdapter(FragmentManager fm, String[] titles) { super(fm); this.titles = titles; } @Override public Fragment getItem(int position) { return new SimpleFragment(); } @Override public int getCount() { return titles.length; } @Nullable @Override public CharSequence getPageTitle(int position) { return titles[position]; } } ``` 4. 创建RecyclerView Adapter类,用于展示数据。 ```java public class SimpleAdapter extends RecyclerView.Adapter<SimpleViewHolder> { private List<String> data; public SimpleAdapter() { data = new ArrayList<>(); for (int i = 0; i < 20; i++) { data.add("Item " + i); } } @NonNull @Override public SimpleViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_simple, parent, false); return new SimpleViewHolder(view); } @Override public void onBindViewHolder(@NonNull SimpleViewHolder holder, int position) { holder.textView.setText(data.get(position)); } @Override public int getItemCount() { return data.size(); } } ``` 5. 创建RecyclerView ViewHolder类,用于展示每个item。 ```java public class SimpleViewHolder extends RecyclerView.ViewHolder { public TextView textView; public SimpleViewHolder(View itemView) { super(itemView); textView = itemView.findViewById(R.id.text_view); } } ``` 6. 最后,在Activity中进行初始化和设置。 ```java public class MainActivity extends AppCompatActivity { private TabLayout tabLayout; private ViewPager viewPager; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); tabLayout = findViewById(R.id.tab_layout); viewPager = findViewById(R.id.view_pager); String[] titles = {"Tab 1", "Tab 2", "Tab 3"}; SimplePagerAdapter adapter = new SimplePagerAdapter(getSupportFragmentManager(), titles); viewPager.setAdapter(adapter); tabLayout.setupWithViewPager(viewPager); } } ``` 这样就完成了TabLayout+ViewPager+Fragmet+RecyclerView结合的小demo。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值