在開發項目的時候,有時候會遇到一些比較複雜的頁面,須要多個不一樣的列表或者滑動佈局、甚至是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
在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>
複製代碼
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嵌套在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的嵌套滑動
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承載的子佈局(或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的總體滑動距離。