參考文獻:(感謝作者的開源精神)
嚴振杰的博客
希小風(fēng)的博客
1.Behavior介紹
Behavior是Android新出的Design庫里新增的布局概念。Behavior只有是CoordinatorLayout的直接子View才有意義??梢詾槿魏蜼iew添加一個Behavior。
Behavior是一系列回調(diào)。讓你有機會以非侵入的為View添加動態(tài)的依賴布局,和處理父布局(CoordinatorLayout)滑動手勢的機會。如果我們想實現(xiàn)控件之間任意的交互效果,完全可以通過自定義 Behavior 的方式達到。
Behavior官方提供的app:layout_behavior屬性
關(guān)于這個我仔細找了一下就找到了兩個
- appbar_scrolling_view_behavior 這個是appBarLayout的一個子類android.support.design.widget.AppBarLayout$ScrollingViewBehavior中提供的
- bottom_sheet_behavior 這個是單獨的一個類中實現(xiàn)的android.support.design.widget.BottomSheetBehavior
這里說明了兩個問題:
- 第一這個字符串設(shè)置的值應(yīng)該是類的全路徑名稱
- 第二這個類可以自定義(但是自定義的時候應(yīng)該也指定全路徑名稱)
2.Behavior的自定義
這里我準(zhǔn)備按照希小風(fēng)的博客風(fēng)格去逐步實
其實Behavior就是一個應(yīng)用于View的觀察者模式,一個View跟隨著另一個View的變化而變化,或者說一個View監(jiān)聽另一個View,在Behavior中,被觀察View也就是事件源被稱為denpendcy,而觀察View被成為child
這里先貼出繼承繼承CoordinatorLayout.Behavior<V extends View>常用的方法
/**
* 表示是否給應(yīng)用了Behavior 的View 指定一個依賴的布局,通常,當(dāng)依賴的View 布局發(fā)生變化時
* 不管被被依賴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);
}
/**
* 當(dāng)被依賴的View 狀態(tài)(如:位置、大?。┌l(fā)生變化時,這個方法被調(diào)用
* @param parent
* @param child
* @param dependency
* @return
*/
@Override
public boolean onDependentViewChanged(CoordinatorLayout parent, View child, View dependency) {
return super.onDependentViewChanged(parent, child, dependency);
}
/**
* 當(dāng)coordinatorLayout 的子View試圖開始嵌套滑動的時候被調(diào)用。當(dāng)返回值為true的時候表明
* coordinatorLayout 充當(dāng)nested scroll parent 處理這次滑動,需要注意的是只有當(dāng)返回值為true
* 的時候,Behavior 才能收到后面的一些nested scroll 事件回調(diào)(如:onNestedPreScroll、onNestedScroll等)
* 這個方法有個重要的參數(shù)nestedScrollAxes,表明處理的滑動的方向。
*
* @param coordinatorLayout 和Behavior 綁定的View的父CoordinatorLayout
* @param child 和Behavior 綁定的View
* @param directTargetChild
* @param target
* @param nestedScrollAxes 嵌套滑動 應(yīng)用的滑動方向,看 {@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);
}
/**
* 嵌套滾動發(fā)生之前被調(diào)用
* 在nested scroll child 消費掉自己的滾動距離之前,嵌套滾動每次被nested scroll child
* 更新都會調(diào)用onNestedPreScroll。注意有個重要的參數(shù)consumed,可以修改這個數(shù)組表示你消費
* 了多少距離。假設(shè)用戶滑動了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);
}
/**
* 進行嵌套滾動時被調(diào)用
* @param coordinatorLayout
* @param child
* @param target
* @param dxConsumed target 已經(jīng)消費的x方向的距離
* @param dyConsumed target 已經(jīng)消費的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);
}
/**
* 嵌套滾動結(jié)束時被調(diào)用,這是一個清除滾動狀態(tài)等的好時機。
* @param coordinatorLayout
* @param child
* @param target
*/
@Override
public void onStopNestedScroll(CoordinatorLayout coordinatorLayout, View child, View target) {
super.onStopNestedScroll(coordinatorLayout, child, target);
}
/**
* onStartNestedScroll返回true才會觸發(fā)這個方法,接受滾動處理后回調(diào),可以在這個
* 方法里做一些準(zhǔn)備工作,如一些狀態(tài)的重置等。
* @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);
}
/**
* 用戶松開手指并且會發(fā)生慣性動作之前調(diào)用,參數(shù)提供了速度信息,可以根據(jù)這些速度信息
* 決定最終狀態(tài),比如滾動Header,是讓Header處于展開狀態(tài)還是折疊狀態(tài)。返回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聯(lián)動
這里主要是自定義了一個Behavior
/**
* 作者 : 賀金龍
* 創(chuàng)建時間 : 2017/11/8 11:33
* 類描述 : 這個是第一個簡單的自定義EasyBehavior
* 類說明 : 這里的泛型應(yīng)給是被觀察者,也就是child
*/
public class EasyBehavior extends CoordinatorLayout.Behavior<TextView> {
public EasyBehavior(Context context, AttributeSet attrs) {
/*這里說明一下這個構(gòu)造方法一定要些上,否則會報錯的*/
super(context, attrs);
}
@Override
public boolean layoutDependsOn(CoordinatorLayout parent, TextView child, View dependency) {
/*這里主要是說明觀察者是什么類型的,如果返回true說明是觀察者觀察的View否則返回false也就不會產(chǎn)生聯(lián)動了*/
return dependency instanceof Button;
}
@Override
public boolean onDependentViewChanged(CoordinatorLayout parent, TextView child, View dependency) {
/*這里主要是觀察者位置改變的時候,被觀察者的位置在觀察者位置的基礎(chǔ)上響應(yīng)的增加了200*/
child.setX(dependency.getX() + 200);
child.setY(dependency.getY() + 200);
child.setText(dependency.getX() + "," + dependency.getY());
return true;
}
}
注釋已經(jīng)寫的很詳細了,所以這里就不在贅述了...
2.2仿UC首頁折疊的Behavior效果
這個效果可以下載一個UC去看一下,其實這個效果基本上就是頂上放一個TextView和AppBarLayout中底部ToolBar進行
聯(lián)動(其實我覺得就是通過這兩個都是通過計算位置進行處理的),這里我就粘一下代碼了,這里一看就能懂
清單文件:
<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的標(biāo)題">
</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文件
/**
* 作者 : 賀金龍
* 創(chuàng)建時間 : 2017/11/8 14:41
* 類描述 : 實現(xiàn)ToolBar和TextView聯(lián)動的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的當(dāng)前高度比上總高度*/
float percent = dependency.getY() / mStartY;
/*改變child的坐標(biāo)(從消失到可見)*/
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;
}
}
這里寫了好久都沒有成功,后來我發(fā)現(xiàn)了一個問題,toolBar在移動的時候是嵌套在AppBarLayout中的,所以你是監(jiān)聽不到,因此這里不能使用ToolBar而是使用的AppBarLayout
感覺要是真的像自定義一些很難的還是不行,加強學(xué)習(xí)吧
2018年03月19日補充
其實關(guān)于自定義Behavior的內(nèi)容,之前自己了解的不夠,今天補充一些內(nèi)容
補充說明1:
首先你要理解那個是依賴的View,那個是被觀察的View(我這里是這么理解的)
上面所有的child代表的是被觀察的View(也就是綁定的View,說白了就是在xml布局中設(shè)置layout_behavior的那個View)
補充說明2:
這里面有一個API->ViewCompat.offsetTopAndBottom(view, offset);
這個方法的意思是,使view移動相應(yīng)的位置,位置的大小取決于offset,向下為正,向上為負,這里面還有左右移動的,api和這個類似,自己找一下就可以了
補充說明3:
- 一般處理兩個View之間的移動的時候,都會用到
boolean layoutDependsOn(CoordinatorLayout parent, TextView child, View dependency)
這個方法是處理相應(yīng)的依賴關(guān)系的,也就是上面說的依賴和被觀察的關(guān)系
onDependentViewChanged(CoordinatorLayout parent, TextView child, View dependency)
這個方法是處理相應(yīng)位置的改變的一些內(nèi)容的,說簡單點就是當(dāng)你被觀察的View位置什么的發(fā)生改變就會回調(diào)這個方法.
2.當(dāng)處理滑動的時候會用到幾個相應(yīng)的方法
- boolean onStartNestedScroll(@NonNull CoordinatorLayout coordinatorLayout, @NonNull View child, @NonNull View directTargetChild, @NonNull View target, int axes, int type)
這個是在你手指觸碰到控件的時候調(diào)用的方法,這里面參數(shù)有必要說明一下:
參數(shù)1:coordinatorLayout對象,這個沒有什么好說的
參數(shù)2:child這個是相應(yīng)的被觀察者,也就是要被移動的那個view
參數(shù)3:directTargetChild我理解這個參數(shù)是滑動的直接子View(這個我不太確定)
參數(shù)4:target這個是被觀察的View
參數(shù)5:代表是水平滑動還是豎直滑動的一個類型值取值包含ViewCompat#SCROLL_AXIS_HORIZONTAL\ViewCompat#SCROLL_AXIS_VERTICAL分別對應(yīng)著豎直和水平滑動,
參數(shù)6:代表一個type的值,沒用到可能是下面的那個方法做區(qū)分用的吧;
這里引用一個判斷豎直滾動的例子
@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)
這個方法是滾動的時候調(diào)用的,當(dāng)滾動發(fā)生的時候,這里計算出了相應(yīng)的數(shù)值,還是簡單的說明一下不一樣的參數(shù)吧!
參數(shù)4參數(shù)5:dx代表x/y軸的速度值
參數(shù)6我也沒弄明白是什么,我打印的時候一直是0
這里引用一個聯(lián)動的例子
@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);
}
很好理解,就是目標(biāo)滾的距離是多少就讓依賴的View滾多遠
boolean onNestedPreFling(@NonNull CoordinatorLayout coordinatorLayout, @NonNull View child, @NonNull View target, float velocityX, float velocityY)
這個方法就是滾動的慣性,就是當(dāng)你使用了很大力氣的時候推一下,當(dāng)你松手的時候他還會滾一段時間的.也是簡單說一下參數(shù)
參數(shù)4和參數(shù)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;
}
這里就直接把相應(yīng)的速度進行傳遞就可以了!
就先些這些吧!今天有點累了,眼睛有點疼,其實我覺得這個后期多些兩個就會了~~~
下面這些文章準(zhǔn)備不累的時候好好實現(xiàn)一下...
http://blog.csdn.net/king1425/article/details/61923877
http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2016/0224/3991.html
http://www.itdecent.cn/p/488283f74e69
http://www.itdecent.cn/p/82d18b0d18f4