MaterialDesign系列文章(十七)Behavior的相關問題

不怕跌倒,所以飛翔

參考文獻:(感謝作者的開源精神)
嚴振杰的博客
希小風的博客

1.Behavior介紹

Behavior是Android新出的Design庫里新增的布局概念。Behavior只有是CoordinatorLayout的直接子View才有意義。可以為任何View添加一個Behavior。
Behavior是一系列回調。讓你有機會以非侵入的為View添加動態的依賴布局,和處理父布局(CoordinatorLayout)滑動手勢的機會。如果我們想實現控件之間任意的交互效果,完全可以通過自定義 Behavior 的方式達到。

Behavior官方提供的app:layout_behavior屬性

關于這個我仔細找了一下就找到了兩個

  • appbar_scrolling_view_behavior 這個是appBarLayout的一個子類android.support.design.widget.AppBarLayout$ScrollingViewBehavior中提供的
  • bottom_sheet_behavior 這個是單獨的一個類中實現的android.support.design.widget.BottomSheetBehavior

這里說明了兩個問題:

  • 第一這個字符串設置的值應該是類的全路徑名稱
  • 第二這個類可以自定義(但是自定義的時候應該也指定全路徑名稱)

2.Behavior的自定義

這里我準備按照希小風的博客風格去逐步實

其實Behavior就是一個應用于View的觀察者模式,一個View跟隨著另一個View的變化而變化,或者說一個View監聽另一個View,在Behavior中,被觀察View也就是事件源被稱為denpendcy,而觀察View被成為child

這里先貼出繼承繼承CoordinatorLayout.Behavior<V extends View>常用的方法

 /**
     * 表示是否給應用了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 狀態(如:位置、大?。┌l生變化時,這個方法被調用
     * @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);
    }

2.1Button與TextView聯動

這里主要是自定義了一個Behavior

/**
 * 作者 : 賀金龍
 * 創建時間 :  2017/11/8 11:33
 * 類描述 : 這個是第一個簡單的自定義EasyBehavior
 * 類說明 : 這里的泛型應給是被觀察者,也就是child
 */
public class EasyBehavior extends CoordinatorLayout.Behavior<TextView> {
   
    public EasyBehavior(Context context, AttributeSet attrs) {
        /*這里說明一下這個構造方法一定要些上,否則會報錯的*/
        super(context, attrs);
    }

    @Override
    public boolean layoutDependsOn(CoordinatorLayout parent, TextView child, View dependency) {
        /*這里主要是說明觀察者是什么類型的,如果返回true說明是觀察者觀察的View否則返回false也就不會產生聯動了*/
        return dependency instanceof Button;
    }


    @Override
    public boolean onDependentViewChanged(CoordinatorLayout parent, TextView child, View dependency) {
        /*這里主要是觀察者位置改變的時候,被觀察者的位置在觀察者位置的基礎上響應的增加了200*/
        child.setX(dependency.getX() + 200);
        child.setY(dependency.getY() + 200);
        child.setText(dependency.getX() + "," + dependency.getY());
        return true;
    }
}

注釋已經寫的很詳細了,所以這里就不在贅述了...

2.2仿UC首頁折疊的Behavior效果

這個效果可以下載一個UC去看一下,其實這個效果基本上就是頂上放一個TextView和AppBarLayout中底部ToolBar進行
聯動(其實我覺得就是通過這兩個都是通過計算位置進行處理的),這里我就粘一下代碼了,這里一看就能懂
清單文件:

<android.support.design.widget.CoordinatorLayout
    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"
    tools:ignore="RtlHardcoded">

    <android.support.design.widget.AppBarLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
        app:elevation="0dp">

        <android.support.design.widget.CollapsingToolbarLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            app:layout_scrollFlags="scroll|exitUntilCollapsed|snap">

            <ImageView
                android:layout_width="match_parent"
                android:layout_height="300dp"
                android:scaleType="centerCrop"
                android:src="@mipmap/ic_launcher"
                app:layout_collapseMode="parallax"
                app:layout_collapseParallaxMultiplier="0.9"/>

            <FrameLayout
                android:id="@+id/frameLayout"
                android:layout_width="match_parent"
                android:layout_height="100dp"
                android:layout_gravity="bottom|center_horizontal"
                android:background="@color/colorPrimaryDark"
                android:orientation="vertical"
                app:layout_collapseMode="parallax"
                app:layout_collapseParallaxMultiplier="0.3">

            </FrameLayout>
        </android.support.design.widget.CollapsingToolbarLayout>
    </android.support.design.widget.AppBarLayout>

    <android.support.v4.widget.NestedScrollView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:scrollbars="none"
        app:behavior_overlapTop="30dp"
        app:layout_behavior="@string/appbar_scrolling_view_behavior">

        <include layout="@layout/uc_behavior"/>
    </android.support.v4.widget.NestedScrollView>

    <android.support.v7.widget.Toolbar
        android:id="@+id/main.toolbar"
        android:layout_width="match_parent"
        android:layout_height="?attr/actionBarSize"
        android:background="@color/colorPrimaryDark"
        app:layout_anchor="@id/frameLayout"
        app:theme="@style/ThemeOverlay.AppCompat.Dark"
        app:title="這個是toolBar的標題">
    </android.support.v7.widget.Toolbar>

    <TextView
        android:id="@+id/tv_title"
        android:layout_width="match_parent"
        android:layout_height="50dp"
        android:background="@color/colorPrimaryDark"
        android:gravity="center"
        android:textColor="#fff"
        android:textSize="18sp"
        app:layout_behavior="com.hejin.materialdesign.behavior.ToolBarBehavior"/>

</android.support.design.widget.CoordinatorLayout>

這里注意一點啊,里面有一個屬性app:layout_anchor="@id/frameLayout"這個屬性代表有依附的意思,簡單的說就是通過依附達到共同享用滑動事件的意思,也就是說上面的FramLayout滑動的時候ToolBar就會一起跟著滑動的,這里依附的話,會在被依附的控件的最上邊
behavior文件

/**
 * 作者 : 賀金龍
 * 創建時間 :  2017/11/8 14:41
 * 類描述 : 實現ToolBar和TextView聯動的Behavior
 * 類說明 :
 */
public class ToolBarBehavior extends CoordinatorLayout.Behavior<TextView> {

    private int mStartY;

    public ToolBarBehavior(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    public boolean layoutDependsOn(CoordinatorLayout parent, TextView child, View dependency) {
        return dependency instanceof Toolbar;
    }

    @Override
    public boolean onDependentViewChanged(CoordinatorLayout parent, TextView child, View dependency) {
        if (mStartY == 0) {/*這里獲取的是點擊處對控件頂部的距離*/
            mStartY = (int) dependency.getY();
        }

        /*計算ToolBar從開始引動到最后的百分比,也就是ToolBar的當前高度比上總高度*/
        float percent = dependency.getY() / mStartY;

        /*改變child的坐標(從消失到可見)*/
        child.setY(child.getHeight() * (1 - percent) - child.getHeight());
        return true;
    }
}

2017年11月09日添加:

自定義簡書的Behavior

public class BottomBehavior extends CoordinatorLayout.Behavior<View> {

    public BottomBehavior(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    public boolean layoutDependsOn(CoordinatorLayout parent, View child, View dependency) {
        return dependency instanceof AppBarLayout;
    }

    public boolean onDependentViewChanged(CoordinatorLayout parent, View child, View dependency) {
        float translationY = Math.abs(dependency.getTop());//獲取更隨布局的頂部位置
        child.setTranslationY(translationY);
        return true;
    }
}

這里寫了好久都沒有成功,后來我發現了一個問題,toolBar在移動的時候是嵌套在AppBarLayout中的,所以你是監聽不到,因此這里不能使用ToolBar而是使用的AppBarLayout

感覺要是真的像自定義一些很難的還是不行,加強學習吧


2018年03月19日補充

其實關于自定義Behavior的內容,之前自己了解的不夠,今天補充一些內容

補充說明1:

首先你要理解那個是依賴的View,那個是被觀察的View(我這里是這么理解的)

上面所有的child代表的是被觀察的View(也就是綁定的View,說白了就是在xml布局中設置layout_behavior的那個View)

補充說明2:

這里面有一個API->ViewCompat.offsetTopAndBottom(view, offset);
這個方法的意思是,使view移動相應的位置,位置的大小取決于offset,向下為正,向上為負,這里面還有左右移動的,api和這個類似,自己找一下就可以了

補充說明3:

  1. 一般處理兩個View之間的移動的時候,都會用到
    boolean layoutDependsOn(CoordinatorLayout parent, TextView child, View dependency)
    這個方法是處理相應的依賴關系的,也就是上面說的依賴和被觀察的關系
    onDependentViewChanged(CoordinatorLayout parent, TextView child, View dependency)
    這個方法是處理相應位置的改變的一些內容的,說簡單點就是當你被觀察的View位置什么的發生改變就會回調這個方法.

2.當處理滑動的時候會用到幾個相應的方法

  • boolean onStartNestedScroll(@NonNull CoordinatorLayout coordinatorLayout, @NonNull View child, @NonNull View directTargetChild, @NonNull View target, int axes, int type)
    這個是在你手指觸碰到控件的時候調用的方法,這里面參數有必要說明一下:
    參數1:coordinatorLayout對象,這個沒有什么好說的
    參數2:child這個是相應的被觀察者,也就是要被移動的那個view
    參數3:directTargetChild我理解這個參數是滑動的直接子View(這個我不太確定)
    參數4:target這個是被觀察的View
    參數5:代表是水平滑動還是豎直滑動的一個類型值取值包含ViewCompat#SCROLL_AXIS_HORIZONTAL\ViewCompat#SCROLL_AXIS_VERTICAL分別對應著豎直和水平滑動,
    參數6:代表一個type的值,沒用到可能是下面的那個方法做區分用的吧;
    這里引用一個判斷豎直滾動的例子
    @Override
    public boolean onStartNestedScroll(@NonNull CoordinatorLayout coordinatorLayout, @NonNull View child, @NonNull View directTargetChild, @NonNull View target, int axes, int type) {
        return (axes & ViewCompat.SCROLL_AXIS_VERTICAL) != 0;
    }
  • void onNestedPreScroll(@NonNull CoordinatorLayout coordinatorLayout, @NonNull View child, @NonNull View target, int dx, int dy, @NonNull int[] consumed, int type)
    這個方法是滾動的時候調用的,當滾動發生的時候,這里計算出了相應的數值,還是簡單的說明一下不一樣的參數吧!
    參數4參數5:dx代表x/y軸的速度值
    參數6我也沒弄明白是什么,我打印的時候一直是0
    這里引用一個聯動的例子
    @Override
    public void onNestedPreScroll(@NonNull CoordinatorLayout coordinatorLayout, @NonNull View child, @NonNull View target, int dx, int dy, @NonNull int[] consumed, int type) {
        super.onNestedPreScroll(coordinatorLayout, child, target, dx, dy, consumed, type);
        int leftScrolled = target.getScrollY();//獲取滾動的Y軸的距離
        child.setScrollY(leftScrolled);
    }

很好理解,就是目標滾的距離是多少就讓依賴的View滾多遠

boolean onNestedPreFling(@NonNull CoordinatorLayout coordinatorLayout, @NonNull View child, @NonNull View target, float velocityX, float velocityY)
這個方法就是滾動的慣性,就是當你使用了很大力氣的時候推一下,當你松手的時候他還會滾一段時間的.也是簡單說一下參數
參數4和參數5代表的是松手時刻的瞬時速度
上個例子

    @Override
    public boolean onNestedPreFling(@NonNull CoordinatorLayout coordinatorLayout, @NonNull View child, @NonNull View target, float velocityX, float velocityY) {
        ((NestedScrollView) child).fling((int) velocityY);
        return true;
    }

這里就直接把相應的速度進行傳遞就可以了!


就先些這些吧!今天有點累了,眼睛有點疼,其實我覺得這個后期多些兩個就會了~~~
下面這些文章準備不累的時候好好實現一下...
http://blog.csdn.net/king1425/article/details/61923877
http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2016/0224/3991.html
http://www.lxweimin.com/p/488283f74e69
http://www.lxweimin.com/p/82d18b0d18f4


這一系列文章的地址,希望對大家有幫助

項目地址

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容