在开发项目的时候,有时候会遇到一些比较复杂的页面,须要多个不一样的列表或者滑动布局、甚至是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的总体滑动距离。