參考文獻:(感謝作者的開源精神)
嚴振杰的博客
希小風的博客
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:
- 一般處理兩個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