Material Design系列之Behavior實(shí)現(xiàn)Android知乎首頁(yè)
本博客目的:仿知乎首頁(yè)向上滑動(dòng)時(shí)動(dòng)畫隱藏Toolbar、FlocationActionButton、Tab導(dǎo)航,下滑時(shí)顯示,如果和你的期望不同,那么你可以不需要看了,免的浪費(fèi)你的寶貴時(shí)間噢。
效果預(yù)覽
知乎效果:
本博客實(shí)現(xiàn)效果:
今天效果的源代碼下載鏈接在文章末尾。
實(shí)現(xiàn)分析
這個(gè)效果其實(shí)并不難實(shí)現(xiàn),但是它的用處很大,當(dāng)用戶手指上滑,屏幕上顯示下方內(nèi)容的時(shí)候,隱藏Toolbar、Tab導(dǎo)航、FAB來(lái)騰出更大的空間顯示內(nèi)容,讓用戶爽。簡(jiǎn)單粗暴,但這就是我們的目的。
首先就是頭部的Toolbar,這個(gè)就不用說(shuō)了吧,基本會(huì),不會(huì)的人隨便看我一篇博客的demo都有這個(gè)效果,簡(jiǎn)直小學(xué)級(jí)別。
其次來(lái)看看FAB(FlocationActionButton)的顯示和隱藏,知乎是用的平移,我們這里做個(gè)優(yōu)化改動(dòng),當(dāng)然平移也是可以的,如果你看過(guò)我的Material Design系列,自定義Behavior之上滑顯示返回頂部按鈕這篇博客的話。那么我們的FAB的動(dòng)畫隱藏和顯示也是用上一篇博客的原理,沒(méi)有看上一篇博客的同學(xué)需要回過(guò)頭看看噢,這里不在贅述。
最后來(lái)看下面的Tab導(dǎo)航的隱藏和顯示,這個(gè)確確實(shí)實(shí)用平移更好是吧,然而相信你如果看過(guò)我Material Design系列,Behavior之BottomSheetBehavior與BottomSheetDialog這篇博客的話,這個(gè)效果實(shí)現(xiàn)起來(lái)也不難。強(qiáng)烈建議看下文之前讀這篇文章,不然真的沒(méi)法繼續(xù)看下去了。
其實(shí)代碼量還是很少的,主要是Behavior原理、Behavior和CoordinatorLayout如何結(jié)合使用。so,強(qiáng)烈建議去上讀下上面兩篇博客噢。
……
好的,五分鐘過(guò)去了,我相信你大概已經(jīng)速讀了上面提到的兩篇博客了。那么在第一篇FAB的那篇博客中實(shí)現(xiàn)的效果是手指向上滑時(shí)(屏幕顯示下方的內(nèi)容時(shí))顯示FAB用來(lái)回到頂部,但是這里剛好是相反的:向上滑時(shí)隱藏FAB。如果你認(rèn)真讀了原理解釋的那一段或者運(yùn)行過(guò)demo,這個(gè)效果so easy吧。第二篇博客中也講到了如何隱藏和顯示這個(gè)Tab導(dǎo)航,那么有的同學(xué)就覺(jué)得今天的博客就結(jié)束了吧?答案當(dāng)然是No了,不然我也不會(huì)再開(kāi)一篇博客來(lái)講這個(gè)了。
為什么呢?還是有難點(diǎn)的,難點(diǎn)在哪里?就是上面講到的兩個(gè)Behavior如何和CoordinatorLayout結(jié)合使用,同時(shí)實(shí)現(xiàn)兩個(gè)效果。而且BottomSheetBehavior隱藏和顯示Tab導(dǎo)航這個(gè)里面之前我們使用Button來(lái)控制的,如何做到`CoordinatorLayout中的ContentView滑動(dòng)時(shí)動(dòng)態(tài)的顯示和隱藏Tab導(dǎo)航呢?
接下來(lái)來(lái)點(diǎn)真材實(shí)料,帶領(lǐng)大家一起代碼擼起來(lái)。
頁(yè)面布局
上面的引文和介紹,我們已經(jīng)知道了FAB的顯示和隱藏用自定義Behavior實(shí)現(xiàn),Tab導(dǎo)航用BottomSheetBehavior來(lái)實(shí)現(xiàn),那么我們布局文件也該問(wèn)世了:
<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="match_parent"> <android.support.design.widget.AppBarLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:theme="@style/AppTheme.AppBarOverlay"> <android.support.v7.widget.Toolbar android:id="@+id/toolbar" android:layout_width="match_parent" android:layout_height="?attr/actionBarSize" app:layout_scrollFlags="scroll|enterAlways|snap" app:popupTheme="@style/AppTheme.PopupOverlay" /> </android.support.design.widget.AppBarLayout> <android.support.v7.widget.RecyclerView android:id="@+id/recyclerView" android:layout_width="match_parent" android:layout_height="match_parent" app:layout_behavior="@string/appbar_scrolling_view_behavior" /> <LinearLayout android:id="@+id/tab_layout" android:layout_width="match_parent" android:layout_height="?actionBarSize" android:layout_alignParentBottom="true" android:background="@android:color/white" app:layout_behavior="@string/bottom_sheet_behavior"> <Button android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1" android:text="第一" /> <Button android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1" android:text="第二" /> <Button android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1" android:text="第三" /> <Button android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1" android:text="第四" /> </LinearLayout> <android.support.design.widget.FloatingActionButton android:id="@+id/fab" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="bottom|end" android:layout_marginBottom="70dp" android:layout_marginEnd="16dp" android:layout_marginRight="16dp" android:src="@mipmap/ic_action_new" app:layout_behavior="@string/scale_down_show_behavior" app:layout_scrollFlags="scroll|enterAlways|snap" /> </android.support.design.widget.CoordinatorLayout>
還是稍微解釋下,內(nèi)容區(qū)域是一個(gè)RecyclerView,使用的Behavior是design的ScrollingViewBehavior:
app:layout_behavior="@string/appbar_scrolling_view_behavior"
然后一個(gè)LinearLayout,使用的Behavior是design的BottomSheetBehavior:
app:layout_behavior="@string/bottom_sheet_behavior"
最后一個(gè)FloatingActionButton,使用我們的自定義ScaleDownShowBehavior:
app:layout_behavior="@string/scale_down_show_behavior"
其他兩個(gè)都是design自帶的,唯有FloatingActionButton的ScaleDownShowBehavior需要我們自定義,那么下面我們就來(lái)實(shí)現(xiàn)下ScaleDownShowBehavior。
自定義Behavior實(shí)現(xiàn)FAB的動(dòng)畫控制
這里又談到了自定義Behavior了,首先就來(lái)實(shí)現(xiàn):用戶手指在屏幕上滑,隱藏FAB,留出更多位置給用戶。
這里還是繼承FloatingActionButton.Behavior:
public class ScaleDownShowBehavior extends FloatingActionButton.Behavior { public ScaleDownShowBehavior(Context context, AttributeSet attrs) { super(); } }
這里我們的滑動(dòng)方向還是不變,監(jiān)聽(tīng)豎著方向的滑動(dòng):
@Override public boolean onStartNestedScroll(CoordinatorLayout coordinatorLayout, ...) { return nestedScrollAxes == ViewCompat.SCROLL_AXIS_VERTICAL; }
那么我們就要稍微改一下我們的開(kāi)始滑動(dòng)時(shí)回調(diào)這個(gè)方法了:onNestedScroll():
@Override // 隱藏動(dòng)畫是否正在執(zhí)行 private boolean isAnimatingOut = false; public void onNestedScroll(CoordinatorLayout coordinatorLayout, FloatingActionButton child, View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed) { if ((dyConsumed > 0 || dyUnconsumed > 0) && !isAnimatingOut && child.getVisibility() == View.VISIBLE) {// 手指上滑,隱藏FAB AnimatorUtil.scaleHide(child, listener); } else if ((dyConsumed < 0 || dyUnconsumed < 0) && child.getVisibility() != View.VISIBLE) { AnimatorUtil.scaleShow(child, null);// 手指下滑,顯示FAB } } private ViewPropertyAnimatorListener listener = new ViewPropertyAnimatorListener() { @Override public void onAnimationStart(View view) { isAnimatingOut = true; } @Override public void onAnimationEnd(View view) { isAnimatingOut = false; view.setVisibility(View.GONE); } @Override public void onAnimationCancel(View arg0) { isAnimatingOut = false; } };
好吧,代碼非常少,完成了。那么我們就在string.xml中定義好,剛才我們引用的變量@string/scale_down_show_behavior:
啊呀,好激動(dòng)呀,我趕緊運(yùn)行一下。但是但是。。。運(yùn)行后發(fā)現(xiàn)見(jiàn)鬼啊,只有FAB會(huì)跟著顯示和隱藏,完全看不到Tab導(dǎo)航呀,嚴(yán)振杰你是在忽悠人麼?哈哈哈哈,且聽(tīng)我細(xì)細(xì)道來(lái)。
通過(guò)監(jiān)聽(tīng)ScaleDownShowBehavior中的view顯示/隱藏來(lái)控制Tab導(dǎo)航欄
其實(shí)只要看過(guò)Material Design系列,Behavior之BottomSheetBehavior與BottomSheetDialog這篇文章的同學(xué)會(huì)發(fā)現(xiàn),用BottomSheetBehavior的控件默認(rèn)都是隱藏起來(lái)的,需要我們?nèi)フ{(diào)用它的方法來(lái)控制它的View的顯示。所以我們這里需要在CoordinatorLayout中的ContentView滾動(dòng)的時(shí)候來(lái)調(diào)用BottomSheetBehavior的方法使它依附的View顯示與隱藏。
那么我們發(fā)現(xiàn)ScaleDownShowBehavior被系統(tǒng)自動(dòng)調(diào)用了,也觸發(fā)了View的隱藏和顯示,CoordinatorLayout這貨沒(méi)有給我們自動(dòng)調(diào)用BottomSheetBehavior,我們?cè)趺崔k?如果你沒(méi)有忘記的話,我們自定義ScaleDownShowBehavior的時(shí)候,在onNestedScroll()方法中有個(gè)地方是去調(diào)用了FAB的顯示和隱藏,所以我們?cè)谶@里加一個(gè)回調(diào)監(jiān)聽(tīng),讓外部可以監(jiān)聽(tīng)到它的動(dòng)作,是不是同時(shí)可以控制BottomSheetBehavior了?如果還沒(méi)有向明的話看代碼。
先在ScaleDownShowBehavior中定一個(gè)Listener:
// 外部監(jiān)聽(tīng)顯示和隱藏。 public interface OnStateChangedListener { void onChanged(boolean isShow); }
然后在ScaleDownShowBehavior的onNestedScroll()方法中回調(diào):
private OnStateChangedListener mOnStateChangedListener; public void setOnStateChangedListener(OnStateChangedListener mOnStateChangedListener) { this.mOnStateChangedListener = mOnStateChangedListener; } @Override public void onNestedScroll(CoordinatorLayout coordinatorLayout, FloatingActionButton child, View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed) { if ((dyConsumed > 0 || dyUnconsumed > 0) && !isAnimatingOut && child.getVisibility() == View.VISIBLE) {//往下滑 AnimatorUtil.scaleHide(child, viewPropertyAnimatorListener); if (mOnStateChangedListener != null) { mOnStateChangedListener.onChanged(false); } } else if ((dyConsumed < 0 || dyUnconsumed < 0) && child.getVisibility() != View.VISIBLE) { AnimatorUtil.scaleShow(child, null); if (mOnStateChangedListener != null) { mOnStateChangedListener.onChanged(true); } } }
好完美啊。來(lái)來(lái)來(lái),設(shè)置一個(gè)監(jiān)聽(tīng)。。。我勒個(gè)去,突然發(fā)現(xiàn)怎么從FAB拿到這個(gè)Behavior???且看我下面的分析,保證讓你柳暗花明又一村啊。
拿到FAB的Behavior對(duì)象,通過(guò)監(jiān)聽(tīng)控制BottomSheetBehavior的View的顯示/隱藏
我們這知道,給一個(gè)View設(shè)置Behavior對(duì)象的時(shí)候是在xml中設(shè)置,所以Behavior是一個(gè)View的LayoutParams屬性吧?哈哈哈明白了吧,然后Behavior又必須和CoordinatorLayout結(jié)合使用,不然也是扯淡,so,這個(gè)View也必須是CoordinatorLayout的子View,所以在ScaleDownShowBehavior中產(chǎn)生了如下的一個(gè)靜態(tài)方法(為了方便閱讀,提示寫了中文):
public static <V extends View> ScaleDownShowBehavior from(V view) { ViewGroup.LayoutParams params = view.getLayoutParams(); if (!(params instanceof CoordinatorLayout.LayoutParams)) { throw new IllegalArgumentException("這個(gè)View不是CoordinatorLayout的子View"); } CoordinatorLayout.Behavior behavior = ((CoordinatorLayout.LayoutParams) params).getBehavior(); if (!(behavior instanceof ScaleDownShowBehavior)) { throw new IllegalArgumentException("這個(gè)View的Behaviro不是ScaleDownShowBehavior"); } return (ScaleDownShowBehavior) behavior; }
所以我們?cè)贏ctivity中:
private BottomSheetBehavior mBottomSheetBehavior; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.zhihu_main); ScaleDownShowBehavior scaleDownShowFab = ScaleDownShowBehavior.from(FAB); scaleDownShowFab.setOnStateChangedListener(onStateChangedListener); mBottomSheetBehavior = BottomSheetBehavior.from(findViewById(R.id.tab_layout)); } private OnStateChangedListener onStateChangedListener = new OnStateChangedListener() { @Override public void onChanged(boolean isShow) { mBottomSheetBehavior.setState( isShow ? BottomSheetBehavior.STATE_EXPANDED : BottomSheetBehavior.STATE_COLLAPSED); } };
哎喲喂,不知不覺(jué)中已經(jīng)把我們的效果實(shí)現(xiàn)了,這里最重要的就是onStateChangedListener了,這里實(shí)現(xiàn)了Tab導(dǎo)航的隱藏和顯示,它的狀態(tài)是從ScaleDownShowBehavior中回調(diào)出來(lái)的。
頁(yè)面初始化好后顯示Tab導(dǎo)航
我們上文中說(shuō)道,添加了BottomSheetBehavior屬性的View,默認(rèn)是隱藏的,所以我們?cè)陧?yè)面初始化時(shí)要把我們的Tab導(dǎo)航顯示出來(lái):
private boolean initialize = false; @Override public void onWindowFocusChanged(boolean hasFocus) { super.onWindowFocusChanged(hasFocus); if (!initialize) { initialize = true; mBottomSheetBehavior.setState(BottomSheetBehavior.STATE_EXPANDED); } }
源碼下載:http://xiazai.jb51.net/201609/yuanma/AndroidBehavior(jb51.net).rar
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
優(yōu)化和瘦身Android APK的六個(gè)小技巧
Android應(yīng)用的大小對(duì)用戶體驗(yàn)和應(yīng)用性能至關(guān)重要,大型APK文件會(huì)增加應(yīng)用的安裝時(shí)間,啟動(dòng)時(shí)間和頁(yè)面加載時(shí)間,降低了用戶體驗(yàn),因此,APK瘦身是Android開(kāi)發(fā)中的重要任務(wù),在本文中,我們將分享6個(gè)小技巧,幫助你優(yōu)化和瘦身Android應(yīng)用,需要的朋友可以參考下2023-11-11Android使用TextInputLayout創(chuàng)建登陸頁(yè)面
這篇文章主要為大家詳細(xì)介紹了Android使用TextInputLayout創(chuàng)建登陸頁(yè)面,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-10-10Android 監(jiān)聽(tīng)網(wǎng)絡(luò)狀態(tài)方法詳解
這篇文章主要介紹了Android 監(jiān)聽(tīng)網(wǎng)絡(luò)狀態(tài)方法詳解的相關(guān)資料,需要的朋友可以參考下2017-07-07Android開(kāi)發(fā)性能優(yōu)化總結(jié)
這篇文章主要介紹了Android開(kāi)發(fā)性能優(yōu)化總結(jié)的相關(guān)資料,非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友可以參考下2016-09-09Android開(kāi)發(fā)筆記之:對(duì)實(shí)踐TDD的一些建議說(shuō)明
本篇文章是對(duì)Android中實(shí)踐TDD的一些建議進(jìn)行了詳細(xì)的分析介紹,需要的朋友參考下2013-05-05Android仿淘寶頭條基于TextView實(shí)現(xiàn)上下滾動(dòng)通知效果
這篇文章主要介紹了Android TextView實(shí)現(xiàn)上下滾動(dòng)通知效果,需要的朋友可以參考下2017-03-03Android Studio修改Log信息顏色的實(shí)現(xiàn)
這篇文章主要介紹了Android Studio修改Log信息顏色的實(shí)現(xiàn),具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-04-04Android實(shí)現(xiàn)在列表List中顯示半透明小窗體效果的控件用法詳解
這篇文章主要介紹了Android實(shí)現(xiàn)在列表List中顯示半透明小窗體效果的控件用法,結(jié)合實(shí)例形式分析了Android半透明提示框的實(shí)現(xiàn)與設(shè)置技巧,需要的朋友可以參考下2016-06-06