您好,登錄后才能下訂單哦!
本文小編為大家詳細(xì)介紹“Android嵌套滾動(dòng)與協(xié)調(diào)滾動(dòng)如何實(shí)現(xiàn)”,內(nèi)容詳細(xì),步驟清晰,細(xì)節(jié)處理妥當(dāng),希望這篇“Android嵌套滾動(dòng)與協(xié)調(diào)滾動(dòng)如何實(shí)現(xiàn)”文章能幫助大家解決疑惑,下面跟著小編的思路慢慢深入,一起來學(xué)習(xí)新知識(shí)吧。
CoordinatorLayout 顧名思義是協(xié)調(diào)布局,其原理很簡單,在onMeasure()的時(shí)候保存childView,通過 PreDrawListener監(jiān)聽childView的變化,最終通過雙層for循環(huán)找到對(duì)應(yīng)的Behavior,分發(fā)任務(wù)即可。CoordinatorLayout實(shí)現(xiàn)了NestedScrollingParent2,那么在childView實(shí)現(xiàn)了NestedScrollingChild方法時(shí)候也能解決滑動(dòng)沖突問題。
而Behavior就是一個(gè)應(yīng)用于View的觀察者模式,一個(gè)View跟隨者另一個(gè)View的變化而變化,或者說一個(gè)View監(jiān)聽另一個(gè)View。
在Behavior中,被觀察View 也就是事件源被稱為denpendcy,而觀察View,則被稱為child。
一般自定義Behavior來說分兩種情況:
監(jiān)聽另一個(gè)view的狀態(tài)變化,例如大小、位置、顯示狀態(tài)等
監(jiān)聽CoordinatorLayout里的滑動(dòng)狀態(tài)
這里我們以之前的效果為主來實(shí)現(xiàn)自定義的Behavior,先設(shè)置NestedScrollView在ImageView下面:
public class MyScrollBehavior extends ViewOffsetBehavior<NestedScrollView> { private int topImgHeight; private int topTextHeight; public MyScrollBehavior(Context context, AttributeSet attrs) { super(context, attrs); } @Override public boolean layoutDependsOn(@NonNull CoordinatorLayout parent, @NonNull NestedScrollView child, @NonNull View dependency) { return dependency instanceof ImageView ; } @Override protected void layoutChild(CoordinatorLayout parent, NestedScrollView child, int layoutDirection) { super.layoutChild(parent, child, layoutDirection); if (topImgHeight == 0) { final List<View> dependencies = parent.getDependencies(child); for (int i = 0, z = dependencies.size(); i < z; i++) { View view = dependencies.get(i); if (view instanceof ImageView) { topImgHeight = view.getMeasuredHeight(); } } } child.setTop(topImgHeight); child.setBottom(child.getBottom() + topImgHeight); } }
然后設(shè)置監(jiān)聽CoordinatorLayout里的滑動(dòng)狀態(tài),ImageView做同樣的滾動(dòng)
public class MyImageBehavior extends CoordinatorLayout.Behavior<View> { private int topBarHeight = 0; //負(fù)圖片高度 private int downEndY = 0; //默認(rèn)為0 public MyImageBehavior(Context context, AttributeSet attrs) { super(context, attrs); } @Override public boolean onStartNestedScroll(@NonNull CoordinatorLayout coordinatorLayout, @NonNull View child, @NonNull View directTargetChild, @NonNull View target, int axes, int type) { //監(jiān)聽垂直滾動(dòng) return (axes & ViewCompat.SCROLL_AXIS_VERTICAL) != 0; } @Override public void onNestedPreScroll(@NonNull CoordinatorLayout coordinatorLayout, @NonNull View child, @NonNull View target, int dx, int dy, @NonNull int[] consumed, int type) { if (topBarHeight == 0) { topBarHeight = -child.getMeasuredHeight(); } float transY = child.getTranslationY() - dy; //處理上滑 if (dy > 0) { if (transY >= topBarHeight) { translationByConsume(child, transY, consumed, dy); translationByConsume(target, transY, consumed, dy); } else { translationByConsume(child, topBarHeight, consumed, (child.getTranslationY() - topBarHeight)); translationByConsume(target, topBarHeight, consumed, (child.getTranslationY() - topBarHeight)); } } if (dy < 0 && !target.canScrollVertically(-1)) { //處理下滑 if (transY >= topBarHeight && transY <= downEndY) { translationByConsume(child, transY, consumed, dy); translationByConsume(target, transY, consumed, dy); } else { translationByConsume(child, downEndY, consumed, (downEndY - child.getTranslationY())); translationByConsume(target, downEndY, consumed, (downEndY - child.getTranslationY())); } } } @Override public boolean onNestedFling(@NonNull CoordinatorLayout coordinatorLayout, @NonNull View child, @NonNull View target, float velocityX, float velocityY, boolean consumed) { return super.onNestedFling(coordinatorLayout, child, target, velocityX, velocityY, consumed); } private void translationByConsume(View view, float translationY, int[] consumed, float consumedDy) { consumed[1] = (int) consumedDy; view.setTranslationY(translationY); } }
分別為ImageView和NestedScrollView設(shè)置對(duì)應(yīng)的 Behavior。
<?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="CoordinatorLayout+Behavior" /> <androidx.coordinatorlayout.widget.CoordinatorLayout android:layout_width="match_parent" android:layout_height="match_parent"> <ImageView android:layout_width="150dp" android:layout_height="150dp" app:layout_behavior="com.google.android.material.appbar.MyImageBehavior" android:layout_gravity="center_horizontal" android:contentDescription="我是測試的圖片" android:src="@mipmap/ic_launcher" /> <TextView android:layout_width="match_parent" android:layout_height="50dp" android:background="#ccc" android:gravity="center" android:text="我是測試的分割線" android:visibility="gone" /> <androidx.core.widget.NestedScrollView android:id="@+id/nestedScroll" android:layout_width="match_parent" android:layout_height="wrap_content" app:layout_behavior="com.google.android.material.appbar.MyScrollBehavior"> <TextView android:id="@+id/nestedScrollLayout" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@string/scroll_content" /> </androidx.core.widget.NestedScrollView> </androidx.coordinatorlayout.widget.CoordinatorLayout> </LinearLayout>
我們先把TextView隱藏先不處理TextView。
這樣我們就實(shí)現(xiàn)了自定義 Behavior 監(jiān)聽滾動(dòng)的實(shí)現(xiàn)。那么我們加上TextView 的 Behavior 監(jiān)聽ImageView的滾動(dòng),做對(duì)應(yīng)的滾動(dòng)。
先修改 MyScrollBehavior 讓他在ImageView和TextView下面
public class MyScrollBehavior extends ViewOffsetBehavior<NestedScrollView> { private int topImgHeight; private int topTextHeight; public MyScrollBehavior(Context context, AttributeSet attrs) { super(context, attrs); } @Override public boolean layoutDependsOn(@NonNull CoordinatorLayout parent, @NonNull NestedScrollView child, @NonNull View dependency) { return dependency instanceof ImageView || dependency instanceof TextView ; } @Override protected void layoutChild(CoordinatorLayout parent, NestedScrollView child, int layoutDirection) { super.layoutChild(parent, child, layoutDirection); if (topImgHeight == 0) { final List<View> dependencies = parent.getDependencies(child); for (int i = 0, z = dependencies.size(); i < z; i++) { View view = dependencies.get(i); if (view instanceof ImageView) { topImgHeight = view.getMeasuredHeight(); } else if (view instanceof TextView) { topTextHeight = view.getMeasuredHeight(); view.setTop(topImgHeight); view.setBottom(view.getBottom() + topImgHeight); } } } child.setTop(topImgHeight + topTextHeight); child.setBottom(child.getBottom() + topImgHeight + topTextHeight); } }
然后設(shè)置監(jiān)聽ImageView的滾動(dòng):
public class MyTextBehavior extends CoordinatorLayout.Behavior<View> { private int imgHeight; public MyTextBehavior(Context context, AttributeSet attrs) { super(context, attrs); } @Override public boolean layoutDependsOn(@NonNull CoordinatorLayout parent, @NonNull View child, @NonNull View dependency) { return dependency instanceof ImageView; } @Override public boolean onDependentViewChanged(@NonNull CoordinatorLayout parent, @NonNull View child, @NonNull View dependency) { //跟隨ImageView滾動(dòng),ImageView滾動(dòng)多少我滾動(dòng)多少 float translationY = dependency.getTranslationY(); if (imgHeight == 0) { imgHeight = dependency.getHeight(); } float offsetTranslationY = imgHeight + translationY; child.setTranslationY(offsetTranslationY); return true; } }
xml修改如下:
<?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="CoordinatorLayout+Behavior" /> <androidx.coordinatorlayout.widget.CoordinatorLayout android:layout_width="match_parent" android:layout_height="match_parent"> <ImageView android:layout_width="150dp" android:layout_height="150dp" app:layout_behavior="com.google.android.material.appbar.MyImageBehavior" android:layout_gravity="center_horizontal" android:contentDescription="我是測試的圖片" android:src="@mipmap/ic_launcher" /> <TextView android:layout_width="match_parent" android:layout_height="50dp" android:background="#ccc" app:layout_behavior="com.google.android.material.appbar.MyTextBehavior" android:gravity="center" android:text="我是測試的分割線" android:visibility="visible" /> <androidx.core.widget.NestedScrollView android:id="@+id/nestedScroll" android:layout_width="match_parent" android:layout_height="wrap_content" app:layout_behavior="com.google.android.material.appbar.MyScrollBehavior"> <TextView android:id="@+id/nestedScrollLayout" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@string/scroll_content" /> </androidx.core.widget.NestedScrollView> </androidx.coordinatorlayout.widget.CoordinatorLayout> </LinearLayout>
看到上面的示例,我們把常用的幾種 Behavior 都使用了一遍,系統(tǒng)的ViewOffsetBehavior 和監(jiān)聽滾動(dòng)的 Behavior 監(jiān)聽View的 Behavior。
為了實(shí)現(xiàn)這么一個(gè)簡單的效果就用了這么多類,這么復(fù)雜。我分分鐘就能實(shí)現(xiàn)!
行行,我知道你厲害,這不是為了演示同樣的效果,使用不同的方式實(shí)現(xiàn)嘛。通過 Behavior 可以實(shí)現(xiàn)一些嵌套滾動(dòng)不能完成的效果,比如鼎鼎大名的支付寶首頁效果,美團(tuán)詳情效果等。Behavior 更加的靈活,控制的粒度也更加的細(xì)。
但是如果只是簡單實(shí)現(xiàn)上面的效果,我們可以用 AppBarLayout + 內(nèi)部自帶的 Behavior 也能實(shí)現(xiàn)類似的效果,AppBarLayout內(nèi)部已經(jīng)封裝并使用了 Behavior 。我們看看如何實(shí)現(xiàn)。
其實(shí)內(nèi)部也是基于 Behavior 實(shí)現(xiàn)的,內(nèi)部實(shí)現(xiàn)為 HeaderBehavior 和 HeaderScrollingViewBehavior 。
對(duì)一些場景使用進(jìn)行了封裝,滾動(dòng)效果,吸頂效果,折疊效果等。我們看看同樣的效果,使用 AppBarLayout 如何實(shí)現(xiàn)吧:
<?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="CoordinatorLayout+AppBarLayout" /> <androidx.coordinatorlayout.widget.CoordinatorLayout android:layout_width="match_parent" android:layout_height="match_parent"> <com.google.android.material.appbar.AppBarLayout android:layout_width="match_parent" android:layout_height="wrap_content" app:elevation="0dp" android:background="@color/white" android:orientation="vertical"> <ImageView android:layout_width="match_parent" android:layout_height="150dp" android:contentDescription="我是測試的圖片" android:src="@mipmap/ic_launcher" app:layout_scrollFlags="scroll" /> <TextView android:layout_width="match_parent" android:layout_height="50dp" android:background="#ccc" android:gravity="center" android:text="我是測試的分割線" app:layout_scrollFlags="noScroll" /> </com.google.android.material.appbar.AppBarLayout> <androidx.core.widget.NestedScrollView android:layout_width="match_parent" android:layout_height="wrap_content" app:layout_behavior="@string/appbar_scrolling_view_behavior"> <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@string/scroll_content" /> </androidx.core.widget.NestedScrollView> </androidx.coordinatorlayout.widget.CoordinatorLayout> </LinearLayout>
So Easy ! 真的是太方便了,類似的效果我們都能使用 AppbarLayout 來實(shí)現(xiàn),比如一些詳情頁面頂部圖片,下面列表或ViewPager的都可以使用這種方式,更加的便捷。
不管怎么說,AppbarLayout 只能實(shí)現(xiàn)一些簡單的效果,如果想要一些粒度比較細(xì)的效果,我們還得使用自定義 Behavior 來實(shí)現(xiàn),但是它的實(shí)現(xiàn)確實(shí)是有點(diǎn)復(fù)雜,2019年谷歌推出了 MotionLayout 。
淘寶的出現(xiàn)可以說讓世上沒有難做的生意,那么 MotionLayout 的出現(xiàn)可以說讓 Android 沒有難實(shí)現(xiàn)的動(dòng)畫了。不管是動(dòng)畫效果,滾動(dòng)效果,MotionLayout 絕殺!能用 Behavior 實(shí)現(xiàn)的 MotionLayout 幾乎是都能做。
使用 MotionLayout 我們只需要定義起始點(diǎn)和結(jié)束點(diǎn)就行了,我們這里不需要根據(jù)百分比Fram進(jìn)行別的操作,所以只定義最簡單的使用。
我們看看如何用 MotionLayout 實(shí)現(xiàn)同樣的效果:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" xmlns:app="http://schemas.android.com/apk/res-auto" 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="MotionLayout的動(dòng)作" /> <androidx.constraintlayout.motion.widget.MotionLayout android:layout_width="match_parent" android:layout_weight="1" app:layoutDescription="@xml/scene_scroll_13" android:layout_height="0dp"> <ImageView android:id="@+id/iv_img" android:layout_width="150dp" android:layout_height="150dp" android:scaleType="centerCrop" android:contentDescription="我是測試的圖片" android:src="@mipmap/ic_launcher" /> <TextView android:id="@+id/tv_message" android:layout_width="match_parent" android:layout_height="50dp" android:background="#ccc" android:gravity="center" android:text="我是測試的分割線" tools:layout_editor_absoluteY="150dp" /> <androidx.core.widget.NestedScrollView android:id="@+id/nestedScroll" android:layout_width="match_parent" android:layout_height="wrap_content"> <TextView android:id="@+id/nestedScrollLayout" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@string/scroll_content" /> </androidx.core.widget.NestedScrollView> </androidx.constraintlayout.motion.widget.MotionLayout> </LinearLayout>
定義的scene_scroll_13.xml
<?xml version="1.0" encoding="utf-8"?> <MotionScene xmlns:android="http://schemas.android.com/apk/res/android" xmlns:motion="http://schemas.android.com/apk/res-auto"> <Transition motion:constraintSetEnd="@+id/end" motion:constraintSetStart="@+id/start"> <OnSwipe motion:dragDirection="dragUp" motion:touchAnchorId="@id/nestedScroll" /> </Transition> <ConstraintSet android:id="@+id/start"> <Constraint android:id="@id/iv_img" android:layout_width="150dp" android:layout_height="150dp" android:translationY="0dp" motion:layout_constraintLeft_toLeftOf="parent" motion:layout_constraintRight_toRightOf="parent" motion:layout_constraintTop_toTopOf="parent" /> <Constraint android:id="@id/tv_message" android:layout_width="match_parent" android:layout_height="50dp" motion:layout_constraintTop_toBottomOf="@id/iv_img" /> <Constraint android:id="@id/nestedScroll" android:layout_width="match_parent" android:layout_height="0dp" motion:layout_constraintBottom_toBottomOf="parent" motion:layout_constraintTop_toBottomOf="@id/tv_message" /> </ConstraintSet> <ConstraintSet android:id="@+id/end"> <Constraint android:id="@id/iv_img" android:layout_width="150dp" android:layout_height="150dp" android:translationY="-150dp" motion:layout_constraintLeft_toLeftOf="parent" motion:layout_constraintRight_toRightOf="parent" motion:layout_constraintTop_toTopOf="parent" /> <Constraint android:id="@id/tv_message" android:layout_width="match_parent" android:layout_height="50dp" motion:layout_constraintLeft_toLeftOf="parent" motion:layout_constraintRight_toRightOf="parent" motion:layout_constraintTop_toTopOf="parent" /> <Constraint android:id="@id/nestedScroll" android:layout_width="match_parent" android:layout_height="0dp" motion:layout_constraintBottom_toBottomOf="parent" motion:layout_constraintTop_toBottomOf="@id/tv_message" /> </ConstraintSet> </MotionScene>
讀到這里,這篇“Android嵌套滾動(dòng)與協(xié)調(diào)滾動(dòng)如何實(shí)現(xiàn)”文章已經(jīng)介紹完畢,想要掌握這篇文章的知識(shí)點(diǎn)還需要大家自己動(dòng)手實(shí)踐使用過才能領(lǐng)會(huì),如果想了解更多相關(guān)內(nèi)容的文章,歡迎關(guān)注億速云行業(yè)資訊頻道。
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。