Material Design 之 Behavior 的使用和自定義 Behavior

寫在前面

Material 系列文章:
Material Design 之 Toolbar 開發實踐總結
Material Design之 AppbarLayout 開發實踐總結html

前面兩篇文章講了Toolbar 和 AppbarLayout 相關的東西,還沒看過的同窗能夠去看看。前面咱們說過,CoordinatorLayout很強大,它能夠協調子View的交互動做,那麼CoordinatorLayout它是怎麼協調子View的呢?其實核心就是Behavior。那麼今天講的就是這個很重要的東西-Behavior,在上面篇文章中,咱們其實已經看到過Behavior這個東西了,在AppbarLayout 與NestedScrollView 聯動的時候,咱們爲NestedScrollView設置了一個Behavior, 經過app:layout_behavior="@string/appbar_scrolling_view_behavior",它的值是一個類的全路徑,這個Behavior 是Google已經爲咱們提供的,AppbarLayout的內部類,專門用於處理可滾動View(如:ScrollView、RecyclerView) 與AppbarLayout 聯動的。那麼這篇文章咱們經過介紹Google提供的一些Behavior 的使用場景、使用方式和自定義Behavior 來熟悉和掌握 Behavior。java

本文目錄:android

  • Behavior 介紹
  • BottomSheetBehavior/BottomSheetDialog 的使用
  • SwipeDissmissBehavior 的使用
  • 自定義 Behavior

正文

1,Behavior 介紹

看一下官方的介紹:Interaction behavior plugin for child views of CoordinatorLayout. 做用於CoordinatorLayout的子View的交互行爲插件。一個Behavior 實現了用戶的一個或者多個交互行爲,它們可能包括拖拽、滑動、快滑或者其餘一些手勢。git

Behavior 是一個頂層抽象類,其餘的一些具體行爲的Behavior 都是繼承自這個類。它提供了幾個重要的方法:github

  • layoutDependsOn
  • onDependentViewChanged
  • onStartNestedScroll
  • onNestedPreScroll
  • onNestedScroll
  • onStopNestedScroll
  • onNestedScrollAccepted
  • onNestedPreFling
  • onStartNestedScroll
  • onLayoutChild

解釋一下上面幾個方法和它們的調用時機:數組

/** * 表示是否給應用了Behavior 的View 指定一個依賴的佈局,一般,當依賴的View 佈局發生變化時 * 無論被被依賴View 的順序怎樣,被依賴的View也會從新佈局 * @param parent * @param child 綁定behavior 的View * @param dependency 依賴的view * @return 若是child 是依賴的指定的View 返回true,不然返回false */
    @Override
    public boolean layoutDependsOn(CoordinatorLayout parent, View child, View dependency) {
        return super.layoutDependsOn(parent, child, dependency);
    }

    /** * 當被依賴的View 狀態(如:位置、大小)發生變化時,這個方法被調用 * @param parent * @param child * @param dependency * @return */
    @Override
    public boolean onDependentViewChanged(CoordinatorLayout parent, View child, View dependency) {
        return super.onDependentViewChanged(parent, child, dependency);
    }

    /** * 當coordinatorLayout 的子View試圖開始嵌套滑動的時候被調用。當返回值爲true的時候代表 * coordinatorLayout 充當nested scroll parent 處理此次滑動,須要注意的是隻有當返回值爲true * 的時候,Behavior 才能收到後面的一些nested scroll 事件回調(如:onNestedPreScroll、onNestedScroll等) * 這個方法有個重要的參數nestedScrollAxes,代表處理的滑動的方向。 * * @param coordinatorLayout 和Behavior 綁定的View的父CoordinatorLayout * @param child 和Behavior 綁定的View * @param directTargetChild * @param target * @param nestedScrollAxes 嵌套滑動 應用的滑動方向,看 {@link ViewCompat#SCROLL_AXIS_HORIZONTAL}, * {@link ViewCompat#SCROLL_AXIS_VERTICAL} * @return */
    @Override
    public boolean onStartNestedScroll(CoordinatorLayout coordinatorLayout, View child, View directTargetChild, View target, int nestedScrollAxes) {
        return super.onStartNestedScroll(coordinatorLayout, child, directTargetChild, target, nestedScrollAxes);
    }

    /** * 嵌套滾動發生以前被調用 * 在nested scroll child 消費掉本身的滾動距離以前,嵌套滾動每次被nested scroll child * 更新都會調用onNestedPreScroll。注意有個重要的參數consumed,能夠修改這個數組表示你消費 * 了多少距離。假設用戶滑動了100px,child 作了90px 的位移,你須要把 consumed[1]的值改爲90, * 這樣coordinatorLayout就能知道只處理剩下的10px的滾動。 * @param coordinatorLayout * @param child * @param target * @param dx 用戶水平方向的滾動距離 * @param dy 用戶豎直方向的滾動距離 * @param consumed */
    @Override
    public void onNestedPreScroll(CoordinatorLayout coordinatorLayout, View child, View target, int dx, int dy, int[] consumed) {
        super.onNestedPreScroll(coordinatorLayout, child, target, dx, dy, consumed);
    }

    /** * 進行嵌套滾動時被調用 * @param coordinatorLayout * @param child * @param target * @param dxConsumed target 已經消費的x方向的距離 * @param dyConsumed target 已經消費的y方向的距離 * @param dxUnconsumed x 方向剩下的滾動距離 * @param dyUnconsumed y 方向剩下的滾動距離 */
    @Override
    public void onNestedScroll(CoordinatorLayout coordinatorLayout, View child, View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed) {
        super.onNestedScroll(coordinatorLayout, child, target, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed);
    }

    /** * 嵌套滾動結束時被調用,這是一個清除滾動狀態等的好時機。 * @param coordinatorLayout * @param child * @param target */
    @Override
    public void onStopNestedScroll(CoordinatorLayout coordinatorLayout, View child, View target) {
        super.onStopNestedScroll(coordinatorLayout, child, target);
    }

    /** * onStartNestedScroll返回true纔會觸發這個方法,接受滾動處理後回調,能夠在這個 * 方法裏作一些準備工做,如一些狀態的重置等。 * @param coordinatorLayout * @param child * @param directTargetChild * @param target * @param nestedScrollAxes */
    @Override
    public void onNestedScrollAccepted(CoordinatorLayout coordinatorLayout, View child, View directTargetChild, View target, int nestedScrollAxes) {
        super.onNestedScrollAccepted(coordinatorLayout, child, directTargetChild, target, nestedScrollAxes);
    }

    /** * 用戶鬆開手指而且會發生慣性動做以前調用,參數提供了速度信息,能夠根據這些速度信息 * 決定最終狀態,好比滾動Header,是讓Header處於展開狀態仍是摺疊狀態。返回true 表 * 示消費了fling. * * @param coordinatorLayout * @param child * @param target * @param velocityX x 方向的速度 * @param velocityY y 方向的速度 * @return */
    @Override
    public boolean onNestedPreFling(CoordinatorLayout coordinatorLayout, View child, View target, float velocityX, float velocityY) {
        return super.onNestedPreFling(coordinatorLayout, child, target, velocityX, velocityY);
    }

    //能夠重寫這個方法對子View 進行從新佈局
    @Override
    public boolean onLayoutChild(CoordinatorLayout parent, View child, int layoutDirection) {
        return super.onLayoutChild(parent, child, layoutDirection);
    }複製代碼

以上就是Behavior的一些重要方法,當咱們要自定義一個Behavior的時候,就會去重寫上面的一些方法。自定義Behavior 會放在文章最後講。對Behavior 有了一些瞭解後,接下來咱們看一下Google給我提供了一些特殊場景的Behavior。瀏覽器

2,BottomSheetBehavior/BottomSheetDialog 的使用

BottomSheetBehavior 實現的效果在咱們的項目中用的比較多,它就是從底部彈出一個佈局,在不少的應用中,分享功能都有這樣一個交互。在之前咱們一般都是用PopupWindow來搞定,前面也寫了一篇文章了,關於PupupWindow的使用和封裝,通用PopupWindow,幾行代碼搞定PopupWindow彈窗,有了BottomSheetBehavior 實現起來就簡單一點了。請看效果圖:app

bottomSheetBehavior.gif

看看怎麼用BottomSheetBehavior:
1,在xml佈局文件中爲須要從底部彈出的佈局綁定BottomSheetBehavior,代碼以下:ide

<?xml version="1.0" encoding="utf-8"?>
<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">
   <TextView
       android:id="@+id/btn_show_bottom_sheet"
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"
       android:text="顯示/隱藏 BottomSheet"
       android:background="@android:color/darker_gray"
       android:textColor="@color/black"
       android:padding="10dp"
       />
 <FrameLayout
     android:id="@+id/share_view"
     app:layout_behavior="@string/bottom_sheet_behavior"
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
     android:background="@android:color/white"
     android:orientation="vertical"
     app:behavior_peekHeight="0dp"
     >
     <include layout="@layout/bottom_sheet_share_dialog"/>
 </FrameLayout>

</android.support.design.widget.CoordinatorLayout>複製代碼

注意上面這行代碼: app:behavior_peekHeight="0dp",peekHeight 屬性是設置bottomSheet 摺疊時的高度,咱們設置爲0表示摺疊的時候徹底隱藏,默認狀況時顯示佈局的高度,佈局會顯示在界面,因此,若是要一開始佈局不顯示在界面上的話,須要將peekHeight 設置爲0。也能夠在代碼中設置, 經過sheetBehavior.setPeekHeight(0)。佈局

2,在代碼中獲取到與佈局相關聯的BottomSheetBehavior,設置展開與摺疊的狀態就能夠了,BottomSheetBehavior有5種狀態:

1, STATE_EXPANDED 展開狀態,顯示完整佈局。
2,STATE_COLLAPSED 摺疊狀態,顯示peekHeigth 的高度,若是peekHeight爲0,則所有隱藏,與STATE_HIDDEN效果同樣。
3,STATE_DRAGGING 拖拽時的狀態
4,STATE_HIDDEN 隱藏時的狀態
5,STATE_SETTLING 釋放時的狀態

看代碼:

View shareView = findViewById(R.id.share_view);
        //獲取BottomSheetBehavior
        final BottomSheetBehavior sheetBehavior = BottomSheetBehavior.from(shareView);

        //設置摺疊時的高度
        //sheetBehavior.setPeekHeight(BottomSheetBehavior.PEEK_HEIGHT_AUTO);

        //監聽BottomSheetBehavior 狀態的變化
        sheetBehavior.setBottomSheetCallback(new BottomSheetBehavior.BottomSheetCallback() {
            @Override
            public void onStateChanged(@NonNull View bottomSheet, int newState) {

            }

            @Override
            public void onSlide(@NonNull View bottomSheet, float slideOffset) {

            }
        });
        //下滑的時候是否能夠隱藏
        sheetBehavior.setHideable(true);
        findViewById(R.id.btn_show_bottom_sheet).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if(sheetBehavior.getState() != BottomSheetBehavior.STATE_EXPANDED){
                    sheetBehavior.setState(BottomSheetBehavior.STATE_EXPANDED);
                }else {
                    sheetBehavior.setState(BottomSheetBehavior.STATE_HIDDEN);
                }

            }
        });複製代碼

代碼很簡單,重要的就是經過方法 sheetBehavior.setState()來改變狀態,是顯示仍是隱藏。其餘的幾個方法都添加了註釋,不用多講。

2.1, BottomSheetDialog

上面說了BottomSheetBehavior, 接下來看一下BottomSheetDialog, 一看名字就知道,它就是一個Dialog,使用方法和Dialog 同樣,它是對BootomSheetBehavior 進行了包裝,從底部彈出一個Dialog。BottomSheetDialog 使用起來比BottomSheetBahvior更方便,效果更佳。看一下它的源碼也很是簡單,就是Dialog 顯示的佈局綁定了BottomSheeBehavior,源碼以下:

private View wrapInBottomSheet(int layoutResId, View view, ViewGroup.LayoutParams params) {
        final CoordinatorLayout coordinator = (CoordinatorLayout) View.inflate(getContext(),
                R.layout.design_bottom_sheet_dialog, null);
        if (layoutResId != 0 && view == null) {
            view = getLayoutInflater().inflate(layoutResId, coordinator, false);
        }
        FrameLayout bottomSheet = (FrameLayout) coordinator.findViewById(R.id.design_bottom_sheet);
        mBehavior = BottomSheetBehavior.from(bottomSheet);
        mBehavior.setBottomSheetCallback(mBottomSheetCallback);
        mBehavior.setHideable(mCancelable);
        if (params == null) {
            bottomSheet.addView(view);
        } else {
            bottomSheet.addView(view, params);
        }
        // We treat the CoordinatorLayout as outside the dialog though it is technically inside
        coordinator.findViewById(R.id.touch_outside).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                if (mCancelable && isShowing() && shouldWindowCloseOnTouchOutside()) {
                    cancel();
                }
            }
        });
        return coordinator;
    }複製代碼

就這樣一個方法,獲取到Behavior,設置了一個監聽狀態的回調,設置了下滑能夠隱藏。而後將Dialog 顯示的佈局添加到了綁定了BottomSheetBehavior 的ViewGroup 裏。這個方法在setContent()方法被調用:

@Override
    public void setContentView(View view) {
        super.setContentView(wrapInBottomSheet(0, view, null));
    }

    @Override
    public void setContentView(View view, ViewGroup.LayoutParams params) {
        super.setContentView(wrapInBottomSheet(0, view, params));
    }複製代碼

接下來看一下使用方法,很是簡單,以網易雲音樂的歌單和分享UI爲例:
網易雲音樂歌單UI效果 以下:

網易雲音樂歌單.png

來張gif圖效果更清楚:

網易雲音樂效果圖.gif

本文經過BottomSheetDialog 實現的效果圖以下:

bottomSheetDialog.gif

歌單代碼以下:

private void showBottomSheetDialog(){
        BottomSheetDialog dialog = new BottomSheetDialog(this);
        View view = LayoutInflater.from(this).inflate(R.layout.bottom_sheet_dialog,null);

        handleList(view);

        dialog.setContentView(view);
        dialog.setCancelable(true);
        dialog.setCanceledOnTouchOutside(true);
        dialog.show();
    }

    private void handleList(View contentView){
        RecyclerView recyclerView = (RecyclerView) contentView.findViewById(R.id.recyclerView);
        LinearLayoutManager manager = new LinearLayoutManager(this);
        manager.setOrientation(LinearLayoutManager.VERTICAL);
        recyclerView.setLayoutManager(manager);
        MusicAdapter adapter = new MusicAdapter();
        recyclerView.setAdapter(adapter);
        adapter.setData(mockData());
        adapter.notifyDataSetChanged();
    }複製代碼

分享代碼以下:

/** * share Dialog */
    private void showShareDialog(){
        if(mBottomSheetDialog == null){
            mBottomSheetDialog = new BottomSheetDialog(this);
            View view = LayoutInflater.from(this).inflate(R.layout.bottom_sheet_share_dialog,null);
            mBottomSheetDialog.setContentView(view);
            mBottomSheetDialog.setCancelable(true);
            mBottomSheetDialog.setCanceledOnTouchOutside(true);
            // 解決下滑隱藏dialog 後,再次調用show 方法顯示時,不能彈出Dialog
            View view1 = mBottomSheetDialog.getDelegate().findViewById(android.support.design.R.id.design_bottom_sheet);
            final BottomSheetBehavior bottomSheetBehavior = BottomSheetBehavior.from(view1);
            bottomSheetBehavior.setBottomSheetCallback(new BottomSheetBehavior.BottomSheetCallback() {
                @Override
                public void onStateChanged(@NonNull View bottomSheet, int newState) {
                    if (newState == BottomSheetBehavior.STATE_HIDDEN) {
                        Log.i("BottomSheet","onStateChanged");
                        mBottomSheetDialog.dismiss();
                        bottomSheetBehavior.setState(BottomSheetBehavior.STATE_COLLAPSED);
                    }
                }

                @Override
                public void onSlide(@NonNull View bottomSheet, float slideOffset) {

                }
            });
        }else{
            mBottomSheetDialog.show();
        }

    }複製代碼

代碼很簡單,和其餘普通Dialog的用法同樣。值的主意的一點是這裏有個bug ,那就是當你下滑隱藏了Dialog 以後,下次直接調用show方法來顯示Dialog時(沒有從新new 的狀況下),Dialog不能顯示,緣由是由於BottomSheetDialog 源碼中,關閉的Dialog 是依賴BottomSheetBehavior 的,當下滑隱藏的時候,BottomSheet的狀態也爲STATE_HIDDEN,而且同時dismiss Dialog,下次show 的時候,是沒有辦法顯示一個狀態爲STATE_HIDDEN 的佈局的。 網上搜了一下,有不少人都碰到過,解決方法來自這篇文章Material之Behavior實現支付寶密碼彈窗 仿淘寶/天貓商品屬性選擇, 解決思路:獲取到BottomSheetDialog 的佈局,而後拿到綁定的BottomSheetBehavior,從新設置監聽,在調用dismiss 方法時,咱們從新設置一些Behavior 的狀態。代碼以下:

// 解決下滑隱藏dialog 後,再次調用show 方法顯示時,不能彈出Dialog
            View view1 = mBottomSheetDialog.getDelegate().findViewById(android.support.design.R.id.design_bottom_sheet);
            final BottomSheetBehavior bottomSheetBehavior = BottomSheetBehavior.from(view1);
            bottomSheetBehavior.setBottomSheetCallback(new BottomSheetBehavior.BottomSheetCallback() {
                @Override
                public void onStateChanged(@NonNull View bottomSheet, int newState) {
                    if (newState == BottomSheetBehavior.STATE_HIDDEN) {
                        Log.i("BottomSheet","onStateChanged");
                        mBottomSheetDialog.dismiss();
                        bottomSheetBehavior.setState(BottomSheetBehavior.STATE_COLLAPSED);
                    }
                }

                @Override
                public void onSlide(@NonNull View bottomSheet, float slideOffset) {

                }
            });複製代碼

以上就是BottomSheetBehavior 和BottomSheetDialog 的用法。

3,SwipeDissmissBehavior 的使用

上面講了BottomSheetBehavior 和BottomSheetDialog 的用法,接下來看另外一種場景的Behavior-SwipeDissmissBehavior,叫滑動消失或者滑動關閉,這個Behavior 在咱們項目中用得可能就不是不少了。有個場景就是Snackbar的使用了,Android 5.0 以上 ,增長了Snackbar提示消息,Snackbar 的Behavior 的就是 SwipeDissmissBehavior 的應用,當滑動Snackbar 的時候,Snackbar 消失,效果以下:

snackbar的behavir.gif

使用也很是簡單,在代碼中只接new 一個SwipeDismissBehavior,設置一些屬性後,添加到CoordinatorLayout.LayoutParams,代碼以下:

mSwipeLayout = findViewById(R.id.swipe_layout);
        SwipeDismissBehavior swipe = new SwipeDismissBehavior();

        /** * //設置滑動的方向,有3個值 * * 1,SWIPE_DIRECTION_ANY 表示向左像右滑動均可以, * 2,SWIPE_DIRECTION_START_TO_END,只能從左向右滑 * 3,SWIPE_DIRECTION_END_TO_START,只能從右向左滑 */
        swipe.setSwipeDirection(SwipeDismissBehavior.SWIPE_DIRECTION_START_TO_END);

        swipe.setStartAlphaSwipeDistance(0f);

        swipe.setSensitivity(0.2f);

        swipe.setListener(new SwipeDismissBehavior.OnDismissListener() {
            @Override
            public void onDismiss(View view) {
                Log.e(TAG,"------>onDissmiss");
            }

            @Override
            public void onDragStateChanged(int state) {
                Log.e(TAG,"------>onDragStateChanged");
            }
        });

        CoordinatorLayout.LayoutParams layoutParams = (CoordinatorLayout.LayoutParams) mSwipeLayout.getLayoutParams();
        if(layoutParams!=null){
            layoutParams.setBehavior(swipe);
        }複製代碼

有兩個重要的方法,wipe.setSwipeDirection設置滑動方向,有三個取值,上面已經註釋,不過多解釋,還有就是swipe.setListener能夠監聽dissmiss 和狀態改變,在這些回調裏面能夠作一些本身的邏輯。最後效果圖:

swipeDissmissBehavir.gif

4,自定義Behavior

上面講了Google 爲咱們提供的一些場景使用的Behavior,固然還有一些Google 提供的一些組件使用的Behavior,AppbarLayout內部的Behavior,如專門協調 AppbarLayout 與可滾動View(NestedScrollView,RecyclerView )的, FloatActionButton內部的Behavior ,協調和Snackbar 的關係,保證Snackbar 彈出的時候不被FAB 遮擋。還有就是上面說的Snackbar內部的Behavior 等等。可是有時候,要實現多個View之間的的交互時,咱們能夠自定義Behavior ,下面就說說怎麼自定義一個Behavior。

自定義Behavior 最關鍵的就是文章第一部分介紹的Behavior 提供的那一些方法,忘了的請到回去看一下第一部分的方法註釋。自定義Behavior 分爲兩種:

  • 第一種是經過監聽一個View的狀態,如位置、大小的變化,來改變其餘View的行爲,這種只須要重寫2個方法就能夠了,分別是layoutDependsOnonDependentViewChanged, layoutDependsOn方法判斷是指定依賴的View時,返回true,而後在onDependentViewChanged 裏,被依賴的View作須要的行爲動做。
  • 第二種就是重寫onStartNestedScrollonNestedPreScrollonNestedScroll等一系列方法,前面第一步分已經講過。

上面兩種方法相比,第一種很簡單,第二種複雜一些,可是第二種實現的效果也要複雜。下面就以開眼首頁的滑動Header效果爲例,來實現一個自定義的Behavior。開眼首頁滑動header效果以下:

開眼首頁效果.gif

效果如上:就是列表滑動的時候是覆蓋Header(不是Header縮小,Header沒動),而後就是Header有一個alpha 的變化。

1,首先是整個佈局,Header 固定在頂部,列表在Header 的下方,CoordinatorLayout 是一個FrameLayout,不能提供這樣的佈局,咱們須要重寫onLayoutChild 來讓列表位於Header下面:

@Override
    public boolean onLayoutChild(CoordinatorLayout parent, View child, int layoutDirection) {
        Log.i(TAG,"onLayoutChild.....");
        CoordinatorLayout.LayoutParams params = (CoordinatorLayout.LayoutParams) child.getLayoutParams();
        if(params!=null && params.height == CoordinatorLayout.LayoutParams.MATCH_PARENT){
            child.layout(0,0,parent.getWidth(),parent.getHeight());
            child.setTranslationY(getHeaderHeight());
            return true;
        }

        return super.onLayoutChild(parent, child, layoutDirection);
    }複製代碼

咱們須要知道Header的高度,將Header的高度寫在dimens文件中,getHeaderHeight()方法以下:

/** * 獲取Header 高度 * @return */
    public int getHeaderHeight(){
        return MaterialDesignSimpleApplication.getAppContext().getResources().getDimensionPixelOffset(R.dimen.header_height);
    }複製代碼

2,當開始滑動的時候,利用setTranslationY 來移動列表,知道徹底蓋住header ,這是時候,列表就不移動了,只是列表的滑動了。當下滑到頂端的時候,又將列表向下滑動,直到header 徹底顯示,思路就是這樣。開眼的首頁向上滑洞的時候,Header 有一個alpha的變化,本例子沒有實現,其實也很簡單,只要重寫onDependentViewChanged方法,在裏面根據滑動距離算出alpha 變化的值就能夠了。自定義Behavior 完整代碼以下:

/** * * 自定義Behavior :實現RecyclerView(或者其餘可滑動View,如:NestedScrollView) 滑動覆蓋header 的效果 * Created by zhouwei on 16/12/19. */

public class CoverHeaderScrollBehavior extends CoordinatorLayout.Behavior<View> {
    public static final String TAG = "CoverHeaderScroll";

    public CoverHeaderScrollBehavior(Context context, AttributeSet attributeSet){
        super(context,attributeSet);
    }


    @Override
    public boolean onLayoutChild(CoordinatorLayout parent, View child, int layoutDirection) {
        Log.i(TAG,"onLayoutChild.....");
        CoordinatorLayout.LayoutParams params = (CoordinatorLayout.LayoutParams) child.getLayoutParams();
        if(params!=null && params.height == CoordinatorLayout.LayoutParams.MATCH_PARENT){
            child.layout(0,0,parent.getWidth(),parent.getHeight());
            child.setTranslationY(getHeaderHeight());
            return true;
        }

        return super.onLayoutChild(parent, child, layoutDirection);
    }

    @Override
    public boolean onStartNestedScroll(CoordinatorLayout coordinatorLayout, View child, View directTargetChild, View target, int nestedScrollAxes) {
        return (nestedScrollAxes & ViewCompat.SCROLL_AXIS_VERTICAL) != 0;
    }

    @Override
    public void onNestedPreScroll(CoordinatorLayout coordinatorLayout, View child, View target, int dx, int dy, int[] consumed) {
        super.onNestedPreScroll(coordinatorLayout, child, target, dx, dy, consumed);
        // 在這個方法裏面只處理向上滑動
        if(dy < 0){
            return;
        }

        float transY =  child.getTranslationY() - dy;
        Log.i(TAG,"transY:"+transY+"++++child.getTranslationY():"+child.getTranslationY()+"---->dy:"+dy);
        if(transY > 0){
            child.setTranslationY(transY);
            consumed[1]= dy;
        }
    }

    @Override
    public void onNestedScroll(CoordinatorLayout coordinatorLayout, View child, View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed) {
        super.onNestedScroll(coordinatorLayout, child, target, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed);
        // 在這個方法裏只處理向下滑動
        if(dyUnconsumed >0){
            return;
        }

        float transY = child.getTranslationY() - dyUnconsumed;
        Log.i(TAG,"------>transY:"+transY+"****** child.getTranslationY():"+child.getTranslationY()+"--->dyUnconsumed"+dxUnconsumed);
        if(transY > 0 && transY < getHeaderHeight()){
            child.setTranslationY(transY);
        }
    }

    /** * 獲取Header 高度 * @return */
    public int getHeaderHeight(){
        return MaterialDesignSimpleApplication.getAppContext().getResources().getDimensionPixelOffset(R.dimen.header_height);
    }

}複製代碼

xml 的代碼以下:

<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
              xmlns:app="http://schemas.android.com/apk/res-auto"
              android:orientation="vertical"
              android:layout_width="match_parent"
              android:layout_height="match_parent">
   <ImageView
       android:layout_width="match_parent"
       android:layout_height="@dimen/header_height"
       android:scaleType="centerCrop"
       android:src="@drawable/meizhi"
       />
    <android.support.v4.widget.NestedScrollView
        android:id="@+id/nested_scroll_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@android:color/white"
        app:layout_behavior="@string/cover_header_behavior"
        >
        <TextView
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:text="@string/large_text"
            />
    </android.support.v4.widget.NestedScrollView>
</android.support.design.widget.CoordinatorLayout>複製代碼

最後實現的效果以下:

仿開眼首頁效果.gif

最後

以上就是關於Behavior 的所有內容,自定義Behavior 這一塊,特別是處理滑動嵌套對於剛接觸的同窗來講仍是挺難的,不過當掌握了以後,咱們能作出不少炫酷的效果。因此,再困難也值得花時間去學習。本文到此結束,若有問題,歡迎交流。全部關於Material Design 的使用示例都在這裏:MaterialDesignSamples

參考資料:
1,自定義Behavior的藝術探索-仿UC瀏覽器主頁
2,使用 CoordinatorLayout 實現複雜聯動效果
3,Material之Behavior實現支付寶密碼彈窗 仿淘寶/天貓商品屬性選擇

相關文章
相關標籤/搜索