Android嵌套滾動和協(xié)調(diào)滾動的多種實(shí)現(xiàn)方法
Android的嵌套滾動的幾種實(shí)現(xiàn)方式
很多 Android 開發(fā)者雖然做了幾年的開發(fā),但是可能還是對滾動的幾種方式不是很了解,本系列也不會涉及到底層滾動原理,只是探討一下 Android 布局滾動的幾種方式。
什么叫嵌套滾動?什么叫協(xié)調(diào)滾動?
只要是涉及到滾動那必然父容器和子容器,按照原理來說子容器先滾動,當(dāng)子容器滾不動了再讓父容器滾動,或者先讓父容器滾動,父容器滾不動了再讓子容器滾動,這種就叫嵌套滾動。代表為 NestedScrollView 。
如果只是子容器滾動,父容器中的其他控件在子容器滾動過程中做一些布局,透明度,動畫等操作,這種叫協(xié)調(diào)滾動。代表為 CoordinatorLayout 。
這里我們從嵌套滾動的實(shí)現(xiàn)方式開始講起。(不細(xì)講原理,本文只探討實(shí)現(xiàn)的方式與步驟?。?/p>
一、嵌套滾動 NestedScrollingParent/Child
最近看到一些文章又開始講 NestedScrollingParent/Child
的嵌套滾動了,這...屬實(shí)是懷舊了。
依稀記得大概是2017年左右吧,谷歌出了一個 NestedScrollingParent/Child
嵌套滾動,當(dāng)時應(yīng)該是很轟動的。Android 開發(fā)者真的苦于嵌套滾動久矣。
NestedScrolling
機(jī)制能夠讓父view和子view在滾動時進(jìn)行配合,其基本流程如下:
- 當(dāng)子view開始滾動之前,可以通知父view,讓其先于自己進(jìn)行滾動;
- 子view自己進(jìn)行滾動
- 子view滾動之后,還可以通知父view繼續(xù)滾動
要實(shí)現(xiàn)這樣的交互,父View需要實(shí)現(xiàn) NestedScrollingParent
接口,而子View需要實(shí)現(xiàn) NestedScrollingChild
接口。
作為一個可以嵌入 NestedScrollingChild
的父 View,需要實(shí)現(xiàn) NestedScrollingParent
,這個接口方法和 NestedScrollingChild
大致有一一對應(yīng)的關(guān)系。同樣,也有一個 NestedScrollingParentHelper 輔助類來默默的幫助你實(shí)現(xiàn)和 Child 交互的邏輯?;瑒觿幼魇?Child 主動發(fā)起,Parent 就收滑動回調(diào)并作出響應(yīng)。
從上面的 Child 分析可知,滑動開始的調(diào)用 startNestedScroll(),Parent 收到 onStartNestedScroll() 回調(diào),決定是否需要配合 Child 一起進(jìn)行處理滑動,如果需要配合,還會回調(diào) onNestedScrollAccepted()。
每次滑動前,Child 先詢問 Parent 是否需要滑動,即 dispatchNestedPreScroll(),這就回調(diào)到 Parent 的 onNestedPreScroll(),Parent 可以在這個回調(diào)中“劫持”掉 Child 的滑動,也就是先于 Child 滑動。
Child 滑動以后,會調(diào)用 onNestedScroll(),回調(diào)到 Parent 的 onNestedScroll(),這里就是 Child 滑動后,剩下的給 Parent 處理,也就是 后于 Child 滑動。
最后,滑動結(jié)束,調(diào)用 onStopNestedScroll() 表示本次處理結(jié)束。
更詳細(xì)的教程大家可以看看鴻洋的文章。
這里我做一個簡單的示例,后面的效果都是基于這個布局實(shí)現(xiàn)。
public class MyNestedScrollChild extends LinearLayout implements NestedScrollingChild { private NestedScrollingChildHelper mScrollingChildHelper; private final int[] offset = new int[2]; private final int[] consumed = new int[2]; private int lastY; private int mShowHeight; public MyNestedScrollChild(Context context) { super(context); } public MyNestedScrollChild(Context context, @Nullable AttributeSet attrs) { super(context, attrs); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { //第一次測量,因?yàn)椴季治募懈叨仁莣rap_content,因此測量模式為ATMOST,即高度不能超過父控件的剩余空間 super.onMeasure(widthMeasureSpec, heightMeasureSpec); mShowHeight = getMeasuredHeight(); //第二次測量,對高度沒有任何限制,那么測量出來的就是完全展示內(nèi)容所需要的高度 heightMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); super.onMeasure(widthMeasureSpec, heightMeasureSpec); } @Override public boolean onTouchEvent(MotionEvent e) { switch (e.getAction()) { case MotionEvent.ACTION_DOWN: lastY = (int) e.getRawY(); break; case MotionEvent.ACTION_MOVE: int y = (int) (e.getRawY()); int dy = y - lastY; lastY = y; if (startNestedScroll(ViewCompat.SCROLL_AXIS_VERTICAL) //如果找到了支持嵌套滾動的父類 && dispatchNestedPreScroll(0, dy, consumed, offset)) {//父類進(jìn)行了一部分滾動 int remain = dy - consumed[1];//獲取滾動的剩余距離 if (remain != 0) { scrollBy(0, -remain); } } else { scrollBy(0, -dy); } } return true; } //scrollBy內(nèi)部會調(diào)用scrollTo //限制滾動范圍 @Override public void scrollTo(int x, int y) { int MaxY = getMeasuredHeight() - mShowHeight; if (y > MaxY) { y = MaxY; } if (y < 0) { y = 0; } super.scrollTo(x, y); } private NestedScrollingChildHelper getScrollingChildHelper() { if (mScrollingChildHelper == null) { mScrollingChildHelper = new NestedScrollingChildHelper(this); mScrollingChildHelper.setNestedScrollingEnabled(true); } return mScrollingChildHelper; } @Override public void setNestedScrollingEnabled(boolean enabled) { getScrollingChildHelper().setNestedScrollingEnabled(enabled); } @Override public boolean isNestedScrollingEnabled() { return getScrollingChildHelper().isNestedScrollingEnabled(); } @Override public boolean startNestedScroll(int axes) { return getScrollingChildHelper().startNestedScroll(axes); } @Override public void stopNestedScroll() { getScrollingChildHelper().stopNestedScroll(); } @Override public boolean hasNestedScrollingParent() { return getScrollingChildHelper().hasNestedScrollingParent(); } @Override public boolean dispatchNestedScroll(int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed, int[] offsetInWindow) { return getScrollingChildHelper().dispatchNestedScroll(dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, offsetInWindow); } @Override public boolean dispatchNestedPreScroll(int dx, int dy, int[] consumed, int[] offsetInWindow) { return getScrollingChildHelper().dispatchNestedPreScroll(dx, dy, consumed, offsetInWindow); } @Override public boolean dispatchNestedFling(float velocityX, float velocityY, boolean consumed) { return getScrollingChildHelper().dispatchNestedFling(velocityX, velocityY, consumed); } @Override public boolean dispatchNestedPreFling(float velocityX, float velocityY) { return getScrollingChildHelper().dispatchNestedPreFling(velocityX, velocityY); } }
定義Parent實(shí)現(xiàn)文本布局置頂效果:
public class MyNestedScrollParent extends LinearLayout implements NestedScrollingParent { private ImageView img; private TextView tv; private MyNestedScrollChild nsc; private NestedScrollingParentHelper mParentHelper; private int imgHeight; private int tvHeight; public MyNestedScrollParent(Context context) { super(context); init(); } public MyNestedScrollParent(Context context, @Nullable AttributeSet attrs) { super(context, attrs); init(); } private void init() { mParentHelper = new NestedScrollingParentHelper(this); } //獲取子view @Override protected void onFinishInflate() { img = (ImageView) getChildAt(0); tv = (TextView) getChildAt(1); nsc = (MyNestedScrollChild) getChildAt(2); img.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { @Override public void onGlobalLayout() { if (imgHeight <= 0) { imgHeight = img.getMeasuredHeight(); } } }); tv.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { @Override public void onGlobalLayout() { if (tvHeight <= 0) { tvHeight = tv.getMeasuredHeight(); } } }); super.onFinishInflate(); } //在此可以判斷參數(shù)target是哪一個子view以及滾動的方向,然后決定是否要配合其進(jìn)行嵌套滾動 @Override public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes) { if (target instanceof MyNestedScrollChild) { return true; } return false; } @Override public void onNestedScrollAccepted(View child, View target, int nestedScrollAxes) { mParentHelper.onNestedScrollAccepted(child, target, nestedScrollAxes); } @Override public void onStopNestedScroll(View target) { mParentHelper.onStopNestedScroll(target); } //先于child滾動 //前3個為輸入?yún)?shù),最后一個是輸出參數(shù) @Override public void onNestedPreScroll(View target, int dx, int dy, int[] consumed) { if (showImg(dy) || hideImg(dy)) {//如果需要顯示或隱藏圖片,即需要自己(parent)滾動 scrollBy(0, -dy);//滾動 consumed[1] = dy;//告訴child我消費(fèi)了多少 } } //后于child滾動 @Override public void onNestedScroll(View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed) { } //返回值:是否消費(fèi)了fling @Override public boolean onNestedPreFling(View target, float velocityX, float velocityY) { return false; } //返回值:是否消費(fèi)了fling @Override public boolean onNestedFling(View target, float velocityX, float velocityY, boolean consumed) { return false; } @Override public int getNestedScrollAxes() { return mParentHelper.getNestedScrollAxes(); } //-------------------------------------------------- //下拉的時候是否要向下滾動以顯示圖片 public boolean showImg(int dy) { if (dy > 0) { if (getScrollY() > 0 && nsc.getScrollY() == 0) { return true; } } return false; } //上拉的時候,是否要向上滾動,隱藏圖片 public boolean hideImg(int dy) { if (dy < 0) { if (getScrollY() < imgHeight) { return true; } } return false; } //scrollBy內(nèi)部會調(diào)用scrollTo //限制滾動范圍 @Override public void scrollTo(int x, int y) { if (y < 0) { y = 0; } if (y > imgHeight) { y = imgHeight; } super.scrollTo(x, y); } }
頁面的布局如下:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout 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:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent" android:background="@color/white"> <com.guadou.lib_baselib.view.titlebar.EasyTitleBar android:layout_width="match_parent" android:layout_height="wrap_content" app:Easy_title="NestedParent/Child的滾動" /> <com.guadou.kt_demo.demo.demo8_recyclerview.scroll8.MyNestedScrollParent android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <ImageView android:layout_width="match_parent" android:layout_height="150dp" android:contentDescription="我是測試的圖片" android:src="@mipmap/ic_launcher" /> <TextView android:layout_width="match_parent" android:layout_height="50dp" android:gravity="center" android:background="#ccc" android:text="我是測試的分割線" /> <com.guadou.kt_demo.demo.demo8_recyclerview.scroll8.MyNestedScrollChild android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical"> <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@string/scroll_content" /> </com.guadou.kt_demo.demo.demo8_recyclerview.scroll8.MyNestedScrollChild> </com.guadou.kt_demo.demo.demo8_recyclerview.scroll8.MyNestedScrollParent> </LinearLayout>
看看效果:
二、嵌套滾動 NestedScrollView
NestedScrollingParent/Child
的定義也太過復(fù)雜了吧,如果只是一些簡單的效果如 ScrollView 嵌套 LinearLayout 這樣的簡單效果,我們直接可以使用 NestedScrollView
來實(shí)現(xiàn)
因此,我們可以簡單的把 NestedScrollView 類比為 ScrollView,其作用就是作為控件父布局,從而具備嵌套滑動功能。
<?xml version="1.0" encoding="utf-8"?> <LinearLayout 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/white" android:orientation="vertical"> <com.guadou.lib_baselib.view.titlebar.EasyTitleBar android:layout_width="match_parent" android:layout_height="wrap_content" app:Easy_title="NestedScrollView的滾動" /> <androidx.core.widget.NestedScrollView android:layout_width="match_parent" android:layout_height="wrap_content"> <LinearLayout android:orientation="vertical" android:layout_width="match_parent" android:layout_height="wrap_content"> <ImageView android:layout_width="match_parent" android:layout_height="150dp" android:contentDescription="我是測試的圖片" android:src="@mipmap/ic_launcher" /> <TextView android:layout_width="match_parent" android:layout_height="50dp" android:gravity="center" android:background="#ccc" android:text="我是測試的分割線" /> <ScrollView android:layout_width="match_parent" android:layout_height="wrap_content"> <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@string/scroll_content" /> </ScrollView> </LinearLayout> </androidx.core.widget.NestedScrollView> </LinearLayout>
效果:
三、嵌套滾動-自定義布局
除了使用官方提供的方式,我們還能使用自定義View的方式,自己處理事件與監(jiān)聽。
使用自定義ViewGroup的方式,添加全部的布局,并測量與排版,并且對事件做攔截處理。內(nèi)部是如LinearLayout的垂直布局,實(shí)現(xiàn)了 ScrollingView
支持滾動,并處理滾動。有源碼,大概2800行代碼,這里就不方便貼出來了。
如何使用:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout 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/white" android:orientation="vertical"> <com.guadou.lib_baselib.view.titlebar.EasyTitleBar android:layout_width="match_parent" android:layout_height="wrap_content" app:Easy_title="自定義View實(shí)現(xiàn)的滾動" /> <com.guadou.kt_demo.demo.demo8_recyclerview.scroll10.ConsecutiveScrollerLayout android:layout_width="match_parent" android:layout_height="match_parent" android:scrollbars="vertical"> <ImageView android:layout_width="match_parent" android:layout_height="150dp" android:contentDescription="我是測試的圖片" android:src="@mipmap/ic_launcher" /> <TextView app:layout_isSticky="true" //可以實(shí)現(xiàn)吸頂效果 android:layout_width="match_parent" android:layout_height="50dp" android:gravity="center" android:background="#ccc" android:text="我是測試的分割線" /> <ScrollView android:layout_width="match_parent" android:layout_height="wrap_content"> <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@string/scroll_content" /> </ScrollView> </com.guadou.kt_demo.demo.demo8_recyclerview.scroll10.ConsecutiveScrollerLayout> </LinearLayout>
效果:
總結(jié)
其實(shí)嵌套滾動要實(shí)現(xiàn)類似的效果,方式還有很多種,如自定義的ViewPager,自定義ListView,或者RecyclerView加上頭布局也能實(shí)現(xiàn)類似的效果。這里我只展示了基于 ScrollingView 自行滾動的方式。
嵌套的滾動主要方式就是這些,這些簡單的效果我們用協(xié)調(diào)滾動,如 CoordinatorLayout
也能實(shí)現(xiàn)同樣的效果。后面會講一些協(xié)調(diào)滾動的實(shí)現(xiàn)由幾種方式。
到此這篇關(guān)于Android嵌套滾動和協(xié)調(diào)滾動的多種實(shí)現(xiàn)方法的文章就介紹到這了,更多相關(guān)Android嵌套滾動與協(xié)調(diào)滾動內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
基于Android studio3.6的JNI教程之ncnn之語義分割ENet
這篇文章主要介紹了基于Android studio3.6的JNI教程之ncnn之語義分割ENet的相關(guān)知識,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值 ,需要的朋友可以參考下2020-03-03淺談Android獲取ImageView上的圖片,和一個有可能遇到的問題
下面小編就為大家?guī)硪黄獪\談Android獲取ImageView上的圖片,和一個有可能遇到的問題。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-04-04Android AutoCompleteTextView控件使用實(shí)例
AutoCompleteTextView這個控件用于輸入框的自動完成提示,非常適合搜索框等。它本質(zhì)上是個EditText,實(shí)際上它也是從EditText繼承的,使用起來也十分簡單2014-04-04Android小程序?qū)崿F(xiàn)個人信息管理系統(tǒng)
這篇文章主要為大家詳細(xì)介紹了Android小程序?qū)崿F(xiàn)個人信息管理系統(tǒng),文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下2020-05-05Android Broadcast原理分析之registerReceiver詳解
這篇文章主要介紹了Android Broadcast原理分析之registerReceiver詳解,本篇文章通過簡要的案例,講解了該項(xiàng)技術(shù)的了解與使用,以下就是詳細(xì)內(nèi)容,需要的朋友可以參考下2021-08-08Android創(chuàng)建外部lib庫及自定義View的圖文教程
這篇文章主要給大家介紹了關(guān)于Android創(chuàng)建外部lib庫及自定義View的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2018-11-11Kotlin類的繼承實(shí)現(xiàn)詳細(xì)介紹
這篇文章主要介紹了Kotlin類的繼承,在Java中類的繼承默認(rèn)是繼承父類的方法和參數(shù)的,但是在kotlin中默認(rèn)是不繼承的,那么我們接下來來驗(yàn)證2022-09-09Android Studio中引入Lambda表達(dá)式的方法
這篇文章主要給大家介紹了在Android Studio中引入Lambda表達(dá)式的方法,文中通過圖文介紹的非常詳細(xì),對大家具有一定的參考價值,需要的朋友們下面來一起看看吧。2017-03-03Android學(xué)習(xí)筆記(一)環(huán)境安裝及第一個hello world
最近在學(xué)習(xí)安卓開發(fā),記錄下環(huán)境安裝和第一個hello world的誕生過程,希望對大家有所幫助2014-07-07解決Android studio xml界面無法預(yù)覽問題
這篇文章主要介紹了解決Android studio xml界面無法預(yù)覽問題,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-03-03