美文网首页
Android持续滑动布局ConsecutiveScroller

Android持续滑动布局ConsecutiveScroller

作者: 因为我的心 | 来源:发表于2020-06-21 16:28 被阅读0次

    一、前言:

    在开发项目的时候,有时候会遇到一些比较复杂的页面,需要多个不同的列表或者滑动布局、甚至是WebView,组成一个完整的页面。要实现这样一个复杂的页面,在以前我们可能会通过布局嵌套的方式,在一个大的ScrollView下嵌套多个RecyclerView、WebView、ScrollView来实现。但是这种嵌套的方式不仅会严重影响布局的性能,而且处理滑动事件的冲突也是一件头疼的事,处理不好会严重影响用户操作的体验。ConsecutiveScrollerLayout正是为了解决这个难题而设计的滑动布局,它可以同时嵌套多个滑动布局(RecyclerView、WebView、ScrollView等)和普通控件(TextView、ImageView、LinearLayou、自定义View等),它把所有的子View看作是一个整体,由ConsecutiveScrollerLayout来统一处理布局的滑动,使得多个滑动布局就像一个整体一样连续滑动,它的效果就像是一个ScrollView一样。而且它支持嵌套所有的View,具有很好的通用性。使用者不需要关心它是如何滑动的,也不需要考虑滑动的冲突问题,并且不用担心它会影响子View的性能。

    下面是对ConsecutiveScrollerLayout的使用介绍和一写注意事项。

    项目地址: ConsecutiveScroller
    ConsecutiveScroller的设计思路和源码分析:Android可持续滑动布局:ConsecutiveScrollerLayout

    1、效果图

    image image

    二、使用:

    1、引入依赖

    在Project的build.gradle在添加以下代码

    allprojects {
          repositories {
             ...
             maven { url 'https://jitpack.io' }
          }
       }
    

    2、在Module的build.gradle在添加以下代码

    // 使用了Androidx
    implementation 'com.github.donkingliang:ConsecutiveScroller:2.6.2'
    
    // 或者
    // 使用Android support包
    implementation 'com.github.donkingliang:ConsecutiveScroller:3.6.2'
    

    由于Androidx和Android support包不兼容,所以ConsecutiveScroller使用两个版本分别支持使用Androidx和使用Android support包的项目。

    3、基本使用

    ConsecutiveScrollerLayout的使用非常简单,把需要滑动的布局作为ConsecutiveScrollerLayout的直接子View即可。

    <?xml version="1.0" encoding="utf-8"?>
    <com.donkingliang.consecutivescroller.ConsecutiveScrollerLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:id="@+id/scrollerLayout"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:scrollbars="vertical">
    
        <WebView
            android:id="@+id/webView"
            android:layout_width="match_parent"
            android:layout_height="match_parent" />
    
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="200dp"
            android:background="@android:color/holo_red_dark"
            android:gravity="center"
            android:orientation="vertical">
    
        </LinearLayout>
    
        <androidx.core.widget.NestedScrollView
            android:layout_width="match_parent"
            android:layout_height="match_parent">
    
        </androidx.core.widget.NestedScrollView>
    
        <androidx.recyclerview.widget.RecyclerView
            android:id="@+id/recyclerView1"
            android:layout_width="match_parent"
            android:layout_height="wrap_content" />
    
        <ImageView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:scaleType="fitXY"
            android:src="@drawable/temp" />
    
        <ScrollView
            android:layout_width="match_parent"
            android:layout_height="match_parent">
    
        </ScrollView>
    
      <!--  可以嵌套ConsecutiveScrollerLayout  -->
      <com.donkingliang.consecutivescroller.ConsecutiveScrollerLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:background="@color/design_default_color_primary">
    
            <TextView
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:padding="10dp"
                android:text=""
                android:textColor="@android:color/black"
                android:textSize="18sp" />
    
            <androidx.recyclerview.widget.RecyclerView
                android:id="@+id/recyclerView3"
                android:layout_width="match_parent"
                android:layout_height="match_parent" />
    
        </com.donkingliang.consecutivescroller.ConsecutiveScrollerLayout>
    </com.donkingliang.consecutivescroller.ConsecutiveScrollerLayout>
    

    4、关于margin

    ConsecutiveScrollerLayout支持左右margin,但是不支持上下margin,子View间的间距可以通过Space设置。

    <?xml version="1.0" encoding="utf-8"?>
    <com.donkingliang.consecutivescroller.ConsecutiveScrollerLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:id="@+id/scrollerLayout"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:scrollbars="vertical">
    
        <!--  使用Space设置上下边距   -->
        <Space
            android:layout_width="0dp"
            android:layout_height="20dp" />
    
        <!--  ConsecutiveScrollerLayout支持左右margin,但是不支持上下margin   -->
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="200dp"
            android:layout_marginLeft="20dp"
            android:layout_marginRight="20dp"
            android:background="@android:color/holo_red_dark"
            android:gravity="center"
            android:orientation="vertical">
    
            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="LinearLayout"
                android:textColor="@android:color/black"
                android:textSize="18sp" />
    
        </LinearLayout>
    
        <!--  使用Space设置上下边距   -->
        <Space
            android:layout_width="0dp"
            android:layout_height="20dp" />
    
    </com.donkingliang.consecutivescroller.ConsecutiveScrollerLayout>
    

    5、布局对齐方式

    ConsecutiveScrollerLayout的布局方式类似于垂直的LinearLayout,但是它没有gravity和子view layout_gravity属性。ConsecutiveScrollerLayout为它的子view提供了layout_align属性,用于设置子view和父布局的对齐方式。layout_align有三个值:左对齐(LEFT) 、右对齐(RIGHT) 和中间对齐(CENTER)。

    <?xml version="1.0" encoding="utf-8"?>
    <com.donkingliang.consecutivescroller.ConsecutiveScrollerLayout xmlns:android="http://schemas.android.com/apk/res/android"                                                                    xmlns:app="http://schemas.android.com/apk/res-auto"
        android:id="@+id/scrollerLayout"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:scrollbars="vertical">
      
        <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:background="@android:color/white"
            android:padding="10dp"
            android:text="吸顶View - 1"
            android:textColor="@android:color/black"
            android:textSize="18sp"
            app:layout_isSticky="true"
            app:layout_align="LEFT"/> // 对齐方式
      
    </com.donkingliang.consecutivescroller.ConsecutiveScrollerLayout>
    

    6、嵌套Fragment

    要想把一个Fragment嵌套在ConsecutiveScrollerLayout里。通常我们需要一个布局容器来承载我们的Fragment,或者直接把Fragment写在activity的布局里。如果Fragment是垂直滑动的,那么承载Fragment的容器需要是ConsecutiveScrollerLayout,以及Fragment的根布局也需要是垂直滑动的。我们推荐使用ConsecutiveScrollerLayout或者其他可垂直滑动的控件(比如:RecyclerView、NestedScrollView)作为Fragment的根布局。如果你的Fragment不是垂直滑动的,则可以忽略这个限制。

    <?xml version="1.0" encoding="utf-8"?>
    <com.donkingliang.consecutivescroller.ConsecutiveScrollerLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:id="@+id/scrollerLayout"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:scrollbars="vertical">
    
        <!--  承载Fragment的容器  -->
        <com.donkingliang.consecutivescroller.ConsecutiveScrollerLayout
            android:id="@+id/fragment_container"
            android:layout_width="match_parent"
            android:layout_height="match_parent"/>
      
    <!--  MyFragment的根布局是垂直滑动控件  -->
       <fragment
           android:layout_width="match_parent"
           android:layout_height="match_parent"
           android:name="com.donkingliang.consecutivescrollerdemo.MyFragment"/>
    
    </com.donkingliang.consecutivescroller.ConsecutiveScrollerLayout>
    

    7、布局吸顶

    要实现布局的吸顶效果,在以前,我们可能会写两个一摸一样的布局,一个隐藏在顶部,一个嵌套在ScrollView下,通过监听ScrollView的滑动来显示和隐藏顶部的布局。这种方式实现起来既麻烦也不优雅。ConsecutiveScrollerLayout内部实现了子View吸附顶部的功能,只要设置一个属性,就可以实现吸顶功能。而且支持设置多个子View吸顶,后面的View要吸顶时会把前面的吸顶View推出屏幕。

    <?xml version="1.0" encoding="utf-8"?>
    <com.donkingliang.consecutivescroller.ConsecutiveScrollerLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:id="@+id/scrollerLayout"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:scrollbars="vertical">
    
      <!-- 设置app:layout_isSticky="true"就可以使View吸顶 -->
        <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:background="@android:color/white"
            android:padding="10dp"
            android:text="吸顶View - 1"
            android:textColor="@android:color/black"
            android:textSize="18sp"
            app:layout_isSticky="true" />
    
        <WebView
            android:id="@+id/webView"
            android:layout_width="match_parent"
            android:layout_height="match_parent" />
    
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:background="@android:color/white"
            android:orientation="vertical"
            app:layout_isSticky="true">
    
            <TextView
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:padding="10dp"
                android:text="吸顶View - 2 我是个LinearLayout"
                android:textColor="@android:color/black"
                android:textSize="18sp" />
    
        </LinearLayout>
    
        <androidx.recyclerview.widget.RecyclerView
            android:id="@+id/recyclerView1"
            android:layout_width="match_parent"
            android:layout_height="wrap_content" />
    
        <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:background="@android:color/white"
            android:padding="10dp"
            android:text="吸顶View - 3"
            android:textColor="@android:color/black"
            android:textSize="18sp"
            app:layout_isSticky="true" />
    
        <androidx.core.widget.NestedScrollView
            android:layout_width="match_parent"
            android:layout_height="match_parent">
    
        </androidx.core.widget.NestedScrollView>
    
        <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:background="@android:color/white"
            android:padding="10dp"
            android:text="吸顶View - 4"
            android:textColor="@android:color/black"
            android:textSize="18sp"
            app:layout_isSticky="true" />
    
        <androidx.recyclerview.widget.RecyclerView
            android:id="@+id/recyclerView2"
            android:layout_width="match_parent"
            android:layout_height="wrap_content" />
    
    </com.donkingliang.consecutivescroller.ConsecutiveScrollerLayout>
    

    8、常驻吸顶

    如果你不希望吸顶的view被后面的吸顶view顶出屏幕,而且多个吸顶view排列吸附在顶部,你可以设置常驻吸顶模式:app:isPermanent="true"。

    <?xml version="1.0" encoding="utf-8"?>
    <com.donkingliang.consecutivescroller.ConsecutiveScrollerLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:id="@+id/scrollerLayout"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:isPermanent="true"  // 常驻吸顶
        android:scrollbars="vertical">
    
    </com.donkingliang.consecutivescroller.ConsecutiveScrollerLayout>
    

    9、关于吸顶功能的其他方法

    // 设置吸顶到顶部的距离,在距离顶部一定距离时开始悬停吸顶
    scrollerLayout.setStickyOffset(50);
    
    // 监听吸顶变化(普通模式)
    scrollerLayout.setOnStickyChangeListener(OnStickyChangeListener);
    // 监听吸顶变化(常驻模式)
    scrollerLayout.setOnPermanentStickyChangeListener(OnPermanentStickyChangeListener);
    // 获取当前吸顶view(普通模式)
    scrollerLayout.getCurrentStickyView(); 
    // 获取当前吸顶view(常驻模式)
    scrollerLayout.getCurrentStickyViews();
    

    10、局部滑动

    ConsecutiveScrollerLayout将所有的子View视作一个整体,由它统一处理页面的滑动事件,所以它默认会拦截可滑动的子View的滑动事件,由自己来分发处理。并且会追踪用户的手指滑动事件,计算调整ConsecutiveScrollerLayout滑动偏移。如果你希望某个子View自己处理自己的滑动事件,可以通过设置layout_isConsecutive属性来告诉父View不要拦截它的滑动事件,这样就可以实现在这个View自己的高度内滑动自己的内容,而不会作为ConsecutiveScrollerLayout的一部分来处理。

    <?xml version="1.0" encoding="utf-8"?>
    <com.donkingliang.consecutivescroller.ConsecutiveScrollerLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:id="@+id/scrollerLayout"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:scrollbars="vertical">
      
    <!--设置app:layout_isConsecutive="false"使父布局不拦截滑动事件-->
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="vertical"
            app:layout_isConsecutive="false">
    
            <TextView
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:padding="10dp"
                android:text="下面的红色区域是个RecyclerView,它在自己的范围内单独滑动。"
                android:textColor="@android:color/black"
                android:textSize="18sp"
                app:layout_isSticky="true" />
    
            <androidx.recyclerview.widget.RecyclerView
                android:id="@+id/recyclerView1"
                android:layout_width="match_parent"
                android:layout_height="300dp"
                android:layout_marginLeft="50dp"
                android:layout_marginRight="50dp"
                android:layout_marginBottom="30dp"
                android:background="@android:color/holo_red_dark"/>
    
        </LinearLayout>
    
        <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:padding="10dp"
            android:text="下面的是个NestedScrollView,它在自己的范围内单独滑动。"
            android:textColor="@android:color/black"
            android:textSize="18sp" />
    
        <androidx.core.widget.NestedScrollView
            android:layout_width="match_parent"
            android:layout_height="250dp"
            app:layout_isConsecutive="false">
    
        </androidx.core.widget.NestedScrollView>
    
    </com.donkingliang.consecutivescroller.ConsecutiveScrollerLayout>
    

    在这个例子中NestedScrollView希望在自己的高度里滑动自己的内容,而不是跟随ConsecutiveScrollerLayout滑动,只要给它设置layout_isConsecutive="false"就可以了。而LinearLayout虽然不是滑动布局,但是在下面嵌套了个滑动布局RecyclerView,所以它也需要设置layout_isConsecutive="false"。

    ConsecutiveScrollerLayout支持NestedScrolling机制,如果你的局部滑动的view实现了NestedScrollingChild接口(如:RecyclerView、NestedScrollView等),它滑动完成后会把滑动事件交给父布局。如果你不想你的子view或它的下级view与父布局嵌套滑动,可以给子view设置app:layout_isNestedScroll="false"。它可以禁止子view与ConsecutiveScrollerLayout的嵌套滑动

    11、滑动子view的下级view

    ConsecutiveScrollerLayout默认情况下只会处理它的直接子view的滑动,但有时候需要滑动的布局可能不是ConsecutiveScrollerLayout的直接子view,而是子view所嵌套的下级view。比如ConsecutiveScrollerLayout嵌套FrameLayout,FrameLayout嵌套ScrollView,我们希望ConsecutiveScrollerLayout也能正常处理ScrollView的滑动。为了支持这种需求,ConsecutiveScroller提供了一个接口:IConsecutiveScroller。子view实现IConsecutiveScroller接口,并通过实现接口方法告诉ConsecutiveScrollerLayout需要滑动的下级view,ConsecutiveScrollerLayout就能正确地处理它的滑动事件。IConsecutiveScroller需要实现两个方法:

        /**
         * 返回当前需要滑动的下级view。在一个时间点里只能有一个view可以滑动。
         */
        View getCurrentScrollerView();
    
        /**
         * 返回所有可以滑动的子view。由于ConsecutiveScrollerLayout允许它的子view包含多个可滑动的子view,所以返回一个view列表。
         */
        List<View> getScrolledViews();
    

    在前面提到的例子中,我们可以这样实现:

    public class MyFrameLayout extends FrameLayout implements IConsecutiveScroller {
    
        @Override
        public View getCurrentScrollerView() {
            // 返回需要滑动的ScrollView
            return getChildAt(0);
        }
    
        @Override
        public List<View> getScrolledViews() {
            // 返回需要滑动的ScrollView
            List<View> views = new ArrayList<>();
            views.add(getChildAt(0));
            return views;
        }
    }
    
    <?xml version="1.0" encoding="utf-8"?>
    <com.donkingliang.consecutivescroller.ConsecutiveScrollerLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:id="@+id/scrollerLayout"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:scrollbars="vertical">
    
        <com.donkingliang.consecutivescrollerdemo.widget.MyFrameLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent">
            <ScrollView
                android:layout_width="match_parent"
                android:layout_height="match_parent">
                <LinearLayout
                    android:layout_width="match_parent"
                    android:layout_height="match_parent">
    
                </LinearLayout>
            </ScrollView>
        </com.donkingliang.consecutivescrollerdemo.widget.MyFrameLayout>
    </com.donkingliang.consecutivescroller.ConsecutiveScrollerLayout>
    

    这样ConsecutiveScrollerLayout就能正确地处理ScrollView的滑动。这是一个简单的例子,在实际的需求中,我们一般不需要这样做。

    注意:

    1、getCurrentScrollerView()和getScrolledViews()必须正确地返回需要滑动的view,这些view可以是经过多层嵌套的,不一定是直接子view。所以使用者应该按照自己的实际场景去实现者两个方法。

    2、滑动的控件应该跟嵌套它的子view的高度保持一致,也就是说滑动的控件高度应该是match_parent,并且包裹它的子view和它本身都不要设置上下边距(margin和ppadding)。宽度没有这个限制。

    12、对ViewPager的支持

    如果你的ViewPager承载的子布局(或Fragment)不是可以垂直滑动的,那么使用普通的ViewPager即可。如果是可以垂直滑动的,那么你的ViewPager需要实现IConsecutiveScroller接口,并返回需要滑动的view对象。框架里提供了一个实现了IConsecutiveScroller接口自定义控件:ConsecutiveViewPager。使用这个控件,然后你的ConsecutiveViewPager的子view(或Fragment的根布局)是可垂直滑动的view,如:RecyclerView、NestedScrollView、ConsecutiveScrollerLayout即可。这样你的ViewPager就能正确地跟ConsecutiveScrollerLayout一起滑动了。

    <?xml version="1.0" encoding="utf-8"?>
    <com.donkingliang.consecutivescroller.ConsecutiveScrollerLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:id="@+id/scrollerLayout"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:scrollbars="vertical">
    
        <com.google.android.material.tabs.TabLayout
            android:id="@+id/tabLayout"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:background="@android:color/white"
            app:tabGravity="fill"
            app:tabIndicatorColor="@color/colorPrimary"
            app:tabIndicatorHeight="3dp"
            app:tabMode="scrollable"
            app:tabSelectedTextColor="@color/colorPrimary" />
    
        <com.donkingliang.consecutivescroller.ConsecutiveViewPager
            android:id="@+id/viewPager"
            android:layout_width="match_parent"
            android:layout_height="match_parent" />
    
    </com.donkingliang.consecutivescroller.ConsecutiveScrollerLayout>
    

    布局吸顶时会覆盖在下面的布局的上面,有时候我们希望TabLayout吸顶悬浮在顶部,但是不希望它覆盖遮挡ViewPager的内容。ConsecutiveViewPager提供了setAdjustHeight调整自己的布局高度,让自己不被TabLayout覆盖。注意:只有ConsecutiveScrollerLayout是ConsecutiveScrollerLayout的最低部时才能这样做。

    // 保证能获取到tabLayout的高度
    tabLayout.post(new Runnable() {
        @Override
        public void run() {
            viewPager.setAdjustHeight(tabLayout.getHeight());
        }
    });
    

    13、其他注意事项

    1、WebView在加载的过程中如果滑动的布局,可能会导致WebView与其他View在显示上断层,使用下面的方法一定程度上可以避免这个问题。

    webView.setWebChromeClient(new WebChromeClient() {
                @Override
                public void onProgressChanged(WebView view, int newProgress) {
                    super.onProgressChanged(view, newProgress);
                    scrollerLayout.checkLayoutChange();
                }
            });
    

    2、SmartRefreshLayout和SwipeRefreshLayout等刷新控件可以嵌套ConsecutiveScrollerLayout实现下拉刷新功能,但是ConsecutiveScrollerLayout内部嵌套它们来刷新子view,因为子view时ConsecutiveScrollerLayout滑动内容等一部分。除非你给SmartRefreshLayout或者SwipeRefreshLayout设置app:layout_isConsecutive="false"。

    3、继承AbsListView的布局(ListView、GridView等),在滑动上可能会与用户的手指滑动不同步,推荐使用RecyclerView代替。

    4、ConsecutiveScroller的minSdkVersion是19,如果你的项目支持19以下,可以设置:

    <uses-sdk tools:overrideLibrary="com.donkingliang.consecutivescroller"/>
    

    但是不要在minSdkVersion小于19的项目使用AbsListView的子类,因为ConsecutiveScrollerLayout使用了只有19以上才有的AbsListView API。

    5、使用ConsecutiveScrollerLayout提供的setOnVerticalScrollChangeListener()方法监听布局的滑动事件。View所提供的setOnScrollChangeListener()方法已无效。

    6、通过getOwnScrollY()方法获取ConsecutiveScrollerLayout的垂直滑动距离,View的getScrollY()方法获取的不是ConsecutiveScrollerLayout的整体滑动距离。

    参考链接:https://www.jianshu.com/p/0783b0a37fa1

    相关文章

      网友评论

          本文标题:Android持续滑动布局ConsecutiveScroller

          本文链接:https://www.haomeiwen.com/subject/vsraxktx.html