Android持續滑動佈局ConsecutiveScrollerLayout的使用

在開發項目的時候,有時候會遇到一些比較複雜的頁面,須要多個不一樣的列表或者滑動佈局、甚至是WebView,組成一個完整的頁面。要實現這樣一個複雜的頁面,在之前咱們可能會經過佈局嵌套的方式,在一個大的ScrollView下嵌套多個RecyclerView、WebView、ScrollView來實現。可是這種嵌套的方式不只會嚴重影響佈局的性能,並且處理滑動事件的衝突也是一件頭疼的事,處理很差會嚴重影響用戶操做的體驗。ConsecutiveScrollerLayout正是爲了解決這個難題而設計的滑動佈局,它能夠同時嵌套多個滑動佈局(RecyclerView、WebView、ScrollView等)和普通控件(TextView、ImageView、LinearLayou、自定義View等),它把全部的子View看做是一個總體,由ConsecutiveScrollerLayout來統一處理佈局的滑動,使得多個滑動佈局就像一個總體同樣連續滑動,它的效果就像是一個ScrollView同樣。並且它支持嵌套全部的View,具備很好的通用性。使用者不須要關心它是如何滑動的,也不須要考慮滑動的衝突問題,而且不用擔憂它會影響子View的性能。php

下面是對ConsecutiveScrollerLayout的使用介紹和一寫注意事項。java

項目地址: ConsecutiveScrollerandroid

ConsecutiveScroller的設計思路和源碼分析:Android可持續滑動佈局:ConsecutiveScrollerLayoutgit

效果圖

sample.gif

sticky.gif

引入依賴

在Project的build.gradle在添加如下代碼github

allprojects {
      repositories {
         ...
         maven { url 'https://jitpack.io' }
      }
   }
複製代碼

在Module的build.gradle在添加如下代碼web

implementation 'com.github.donkingliang:ConsecutiveScroller:2.6.2'
複製代碼

注意: 若是你準備使用這個庫,請務必認真閱讀下面的文檔。它能讓你瞭解ConsecutiveScrollerLayout能夠實現的功能,以及避免沒必要要的錯誤。app

基本使用

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>
複製代碼

關於margin

ConsecutiveScrollerLayout支持左右margin,可是不支持上下margin,子View間的間距能夠經過Space設置。maven

<?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>

複製代碼

佈局對齊方式

ConsecutiveScrollerLayout的佈局方式相似於垂直的LinearLayout,可是它沒有gravity和子view layout_gravity屬性。ConsecutiveScrollerLayout爲它的子view提供了layout_align屬性,用於設置子view和父佈局的對齊方式。layout_align有三個值:左對齊(LEFT)右對齊(RIGHT)中間對齊(CENTER)ide

<?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>
複製代碼

嵌套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>
複製代碼

佈局吸頂

要實現佈局的吸頂效果,在之前,咱們可能會寫兩個一摸同樣的佈局,一個隱藏在頂部,一個嵌套在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>
複製代碼

常駐吸頂

若是你不但願吸頂的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>
複製代碼

關於吸頂功能的其餘方法

// 設置吸頂到頂部的距離,在距離頂部必定距離時開始懸停吸頂
scrollerLayout.setStickyOffset(50);

// 監聽吸頂變化(普通模式)
scrollerLayout.setOnStickyChangeListener(OnStickyChangeListener);
// 監聽吸頂變化(常駐模式)
scrollerLayout.setOnPermanentStickyChangeListener(OnPermanentStickyChangeListener);
// 獲取當前吸頂view(普通模式)
scrollerLayout.getCurrentStickyView(); 
// 獲取當前吸頂view(常駐模式)
scrollerLayout.getCurrentStickyViews();
複製代碼

局部滑動

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的嵌套滑動

滑動子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的滑動。這是一個簡單的例子,在實際的需求中,咱們通常不須要這樣作。

注意:

一、getCurrentScrollerView()和getScrolledViews()必須正確地返回須要滑動的view,這些view能夠是通過多層嵌套的,不必定是直接子view。因此使用者應該按照本身的實際場景去實現者兩個方法。

二、滑動的控件應該跟嵌套它的子view的高度保持一致,也就是說滑動的控件高度應該是match_parent,而且包裹它的子view和它自己都不要設置上下邊距(margin和ppadding)。寬度沒有這個限制。

對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());
    }
});
複製代碼

其餘注意事項

一、WebView在加載的過程當中若是滑動的佈局,可能會致使WebView與其餘View在顯示上斷層,使用下面的方法必定程度上能夠避免這個問題。

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

二、SmartRefreshLayout和SwipeRefreshLayout等刷新控件能夠嵌套ConsecutiveScrollerLayout實現下拉刷新功能,可是ConsecutiveScrollerLayout內部嵌套它們來刷新子view,由於子view時ConsecutiveScrollerLayout滑動內容等一部分。除非你給SmartRefreshLayout或者SwipeRefreshLayout設置app:layout_isConsecutive="false"。

三、繼承AbsListView的佈局(ListView、GridView等),在滑動上可能會與用戶的手指滑動不一樣步,推薦使用RecyclerView代替。

四、ConsecutiveScroller的minSdkVersion是19,若是你的項目支持19如下,能夠設置:

<uses-sdk tools:overrideLibrary="com.donkingliang.consecutivescroller"/>
複製代碼

可是不要在minSdkVersion小於19的項目使用AbsListView的子類,由於ConsecutiveScrollerLayout使用了只有19以上纔有的AbsListView API。

五、使用ConsecutiveScrollerLayout提供的setOnVerticalScrollChangeListener()方法監聽佈局的滑動事件。View所提供的setOnScrollChangeListener()方法已無效。

六、經過getOwnScrollY()方法獲取ConsecutiveScrollerLayout的垂直滑動距離,View的getScrollY()方法獲取的不是ConsecutiveScrollerLayout的總體滑動距離。

相關文章
相關標籤/搜索