Material Design系列之Behavior實現Android知乎首頁_Android

來源:互聯網
上載者:User

本部落格目的:仿知乎首頁向上滑動時動畫隱藏Toolbar、FlocationActionButton、Tab導航,下滑時顯示,如果和你的期望不同,那麼你可以不需要看了,免的浪費你的寶貴時間噢。

效果預覽

知乎效果:

本部落格實現效果:


今天效果的原始碼下載連結在文章末尾。

實現分析

這個效果其實並不難實現,但是它的用處很大,當使用者手指上滑,螢幕上顯示下方內容的時候,隱藏Toolbar、Tab導航、FAB來騰出更大的空間顯示內容,讓使用者爽。簡單粗暴,但這就是我們的目的。

首先就是頭部的Toolbar,這個就不用說了吧,基本會,不會的人隨便看我一篇部落格的demo都有這個效果,簡直小學層級。

其次來看看FAB(FlocationActionButton)的顯示和隱藏,知乎是用的平移,我們這裡做個最佳化改動,當然平移也是可以的,如果你看過我的Material Design系列,自訂Behavior之上滑顯示返回頂部按鈕這篇部落格的話。那麼我們的FAB的動畫隱藏和顯示也是用上一篇部落格的原理,沒有看上一篇部落格的同學需要回過頭看看噢,這裡不在贅述。

最後來看下面的Tab導航的隱藏和顯示,這個確確實實用平移更好是吧,然而相信你如果看過我Material Design系列,Behavior之BottomSheetBehavior與BottomSheetDialog這篇部落格的話,這個效果實現起來也不難。強烈建議看下文之前讀這篇文章,不然真的沒法繼續看下去了。

其實代碼量還是很少的,主要是Behavior原理、Behavior和CoordinatorLayout如何結合使用。so,強烈建議去上讀下上面兩篇部落格噢。

……

好的,五分鐘過去了,我相信你大概已經速讀了上面提到的兩篇部落格了。那麼在第一篇FAB的那篇部落格中實現的效果是手指向上滑時(螢幕顯示下方的內容時)顯示FAB用來回到頂部,但是這裡剛好是相反的:向上滑時隱藏FAB。如果你認真讀了原理解釋的那一段或者運行過demo,這個效果so easy吧。第二篇部落格中也講到了如何隱藏和顯示這個Tab導航,那麼有的同學就覺得今天的部落格就結束了吧?答案當然是No了,不然我也不會再開一篇部落格來講這個了。

為什麼呢?還是有痛點的,痛點在哪裡?就是上面講到的兩個Behavior如何和CoordinatorLayout結合使用,同時實現兩個效果。而且BottomSheetBehavior隱藏和顯示Tab導航這個裡面之前我們使用Button來控制的,如何做到`CoordinatorLayout中的ContentView滑動時動態顯示和隱藏Tab導航呢?

接下來來點真材實料,帶領大家一起代碼擼起來。

頁面配置

上面的引文和介紹,我們已經知道了FAB的顯示和隱藏用自訂Behavior實現,Tab導航用BottomSheetBehavior來實現,那麼我們布局檔案也該問世了:

<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>

還是稍微解釋下,內容地區是一個RecyclerView,使用的Behavior是design的ScrollingViewBehavior:

app:layout_behavior="@string/appbar_scrolling_view_behavior"

然後一個LinearLayout,使用的Behavior是design的BottomSheetBehavior:

app:layout_behavior="@string/bottom_sheet_behavior"

最後一個FloatingActionButton,使用我們的自訂ScaleDownShowBehavior:

app:layout_behavior="@string/scale_down_show_behavior"

其他兩個都是design內建的,唯有FloatingActionButton的ScaleDownShowBehavior需要我們自訂,那麼下面我們就來實現下ScaleDownShowBehavior。

自訂Behavior實現FAB的動畫控制

這裡又談到了自訂Behavior了,首先就來實現:使用者手指在螢幕上滑,隱藏FAB,留出更多位置給使用者。

這裡還是繼承FloatingActionButton.Behavior:

public class ScaleDownShowBehavior extends FloatingActionButton.Behavior { public ScaleDownShowBehavior(Context context, AttributeSet attrs) {  super(); }}

這裡我們的滑動方向還是不變,監聽豎著方向的滑動:

@Overridepublic boolean onStartNestedScroll(CoordinatorLayout coordinatorLayout, ...) { return nestedScrollAxes == ViewCompat.SCROLL_AXIS_VERTICAL;}

那麼我們就要稍微改一下我們的開始滑動時回調這個方法了:onNestedScroll():

@Override// 隱藏動畫是否正在執行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:

複製代碼 代碼如下:
<string name="scale_down_show_behavior">com.yanzhenjie.definebehavior.behavior.ScaleDownShowBehavior</string>

啊呀,好激動呀,我趕緊運行一下。但是但是。。。運行後發現見鬼啊,只有FAB會跟著顯示和隱藏,完全看不到Tab導航呀,嚴振傑你是在忽悠人麽?哈哈哈哈,且聽我細細道來。

通過監聽ScaleDownShowBehavior中的view顯示/隱藏來控制Tab導覽列

其實只要看過Material Design系列,Behavior之BottomSheetBehavior與BottomSheetDialog這篇文章的同學會發現,用BottomSheetBehavior的控制項預設都是隱藏起來的,需要我們去調用它的方法來控制它的View的顯示。所以我們這裡需要在CoordinatorLayout中的ContentView滾動的時候來調用BottomSheetBehavior的方法使它依附的View顯示與隱藏。

那麼我們發現ScaleDownShowBehavior被系統自動調用了,也觸發了View的隱藏和顯示,CoordinatorLayout這貨沒有給我們自動調用BottomSheetBehavior,我們怎麼辦?如果你沒有忘記的話,我們自訂ScaleDownShowBehavior的時候,在onNestedScroll()方法中有個地方是去調用了FAB的顯示和隱藏,所以我們在這裡加一個回調監聽,讓外部可以監聽到它的動作,是不是同時可以控制BottomSheetBehavior了?如果還沒有向明的話看代碼。

先在ScaleDownShowBehavior中定一個Listener:

// 外部監聽顯示和隱藏。public interface OnStateChangedListener { void onChanged(boolean isShow);}

然後在ScaleDownShowBehavior的onNestedScroll()方法中回調:

private OnStateChangedListener mOnStateChangedListener;public void setOnStateChangedListener(OnStateChangedListener mOnStateChangedListener) { this.mOnStateChangedListener = mOnStateChangedListener;}@Overridepublic 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);  } }}

好完美啊。來來來,設定一個監聽。。。我勒個去,突然發現怎麼從FAB拿到這個Behavior啊?且看我下面的分析,保證讓你柳暗花明又一村啊。

拿到FAB的Behavior對象,通過監聽控制BottomSheetBehavior的View的顯示/隱藏

我們這知道,給一個View設定Behavior對象的時候是在xml中設定,所以Behavior是一個View的LayoutParams屬性吧?哈哈哈明白了吧,然後Behavior又必須和CoordinatorLayout結合使用,不然也是扯淡,so,這個View也必須是CoordinatorLayout的子View,所以在ScaleDownShowBehavior中產生了如下的一個靜態方法(為了方便閱讀,提示寫了中文):

public static <V extends View> ScaleDownShowBehavior from(V view) { ViewGroup.LayoutParams params = view.getLayoutParams(); if (!(params instanceof CoordinatorLayout.LayoutParams)) {  throw new IllegalArgumentException("這個View不是CoordinatorLayout的子View"); } CoordinatorLayout.Behavior behavior = ((CoordinatorLayout.LayoutParams) params).getBehavior(); if (!(behavior instanceof ScaleDownShowBehavior)) {  throw new IllegalArgumentException("這個View的Behaviro不是ScaleDownShowBehavior"); } return (ScaleDownShowBehavior) behavior;}

所以我們在Activity中:

private BottomSheetBehavior mBottomSheetBehavior;@Overrideprotected 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); }};

哎喲喂,不知不覺中已經把我們的效果實現了,這裡最重要的就是onStateChangedListener了,這裡實現了Tab導航的隱藏和顯示,它的狀態是從ScaleDownShowBehavior中回調出來的。

頁面初始化好後顯示Tab導航

我們上文中說道,添加了BottomSheetBehavior屬性的View,預設是隱藏的,所以我們在頁面初始化時要把我們的Tab導航顯示出來:

private boolean initialize = false;@Overridepublic 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

以上就是本文的全部內容,希望對大家的學習有所協助,也希望大家多多支援雲棲社區。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.