原本今天早上起來就想寫這篇文章的,可是看着心愛的騎士隊正在打比賽,想着詹皇的2:0。。。想着猛龍的東部第一,今天真的是給猛龍打成「朦朧」了。。。猛龍的球迷不會打我吧!!!哈哈android
昨天有小夥伴說要我說一下Behavior的使用,對於這個東西我也不是很瞭解,可是不瞭解能夠學嗎!!!其實做爲程序員,咱們在平常開發中會接觸到不少新東西,不是咱們每個都要去了解的,咱們也不會有那麼多的精力。可是爲何有的人能夠會的那麼多呢?其實有的時候我總在想這個問題,後來我發現一件頗有意思的事情,他們也不見得什麼都懂,可是以往的經歷讓他們知道怎麼去接觸一個新的東西,怎麼去快速上手這個東西。其實咱們應該培養的是解決問題的能力,而不是什麼都會。。。好了,閒話就扯到這裏吧!下面開始今天的內容。其實我挺喜歡詹皇的!哈哈。。。git
關於Behavior的描述是這樣的程序員
Interaction behavior plugin for child views of CoordinatorLayout. A Behavior implements one or more interactions that a user can take on a child view. These interactions may include drags, swipes, flings, or any other gestures.github
簡單的翻譯一下:CoordinatorLayout中子View的交互行爲,能夠在CoordinatorLayout的子類中實現一個或多個交互,這些交互多是拖動,滑動,閃動或任何其餘手勢。其實就是實現CoordinatorLayout內部控件的交互行爲,能夠在非侵入的方式實現相應的交互!他能作什麼呢?看了後面就知道了!!!哈哈。。。算法
關於這個問題,我去google找了找,下面這張圖說明一切:bash
但其實,開發中經常使用的就BottomSheetBehavior、SwipeDismissBehavior剩下的就是自定義了。app
BottomSheetBehavior主要是實現從底部彈出內容的Behavior。其實這個裏面包含不少內容,像BottomSheet、BottomSheetDialog、BottomSheetDialogFragment咱們這裏一個一個說明一下:ide
這個通常是使用在相應的佈局中的!爲何這麼說呢,由於它能夠直接在佈局中使用,就醬紫啊!!!先介紹一下里面比較重要的概念,不然我怕你嚇啥麼不知道啥麼啥(原諒個人一口東北話)!佈局
根佈局是CoordinatorLayout,這個是重點啊!!!ui
這裏注意幾點問題:
app:layout_behavior="@string/bottom_sheet_behavior"
的佈局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"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.jinlong.newmaterialdesign.behavior.BehaviorActivity">
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="bottomSheet"
android:text="展現bottomSheet" />
<LinearLayout
android:id="@+id/ll_bottom"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
app:behavior_peekHeight="50dp"
app:layout_behavior="@string/bottom_sheet_behavior">
<TextView
android:layout_width="match_parent"
android:layout_height="50dp"
android:background="#f25b41"
android:gravity="center"
android:text="底部還有內容啊!!!" />
<TextView
android:layout_width="match_parent"
android:layout_height="50dp"
android:background="#009988"
android:gravity="center"
android:text="標籤1" />
<TextView
android:layout_width="match_parent"
android:layout_height="50dp"
android:background="#002288"
android:gravity="center"
android:text="標籤2" />
<TextView
android:layout_width="match_parent"
android:layout_height="50dp"
android:background="#009922"
android:gravity="center"
android:text="標籤3" />
<TextView
android:layout_width="match_parent"
android:layout_height="50dp"
android:background="#00aa88"
android:gravity="center"
android:text="標籤4" />
<TextView
android:layout_width="match_parent"
android:layout_height="50dp"
android:background="#999988"
android:gravity="center"
android:text="標籤5" />
</LinearLayout>
</android.support.design.widget.CoordinatorLayout>
複製代碼
主頁面的邏輯
BottomSheetBehavior<LinearLayout> bottomSheetBehavior = BottomSheetBehavior.from(mLlBottomSheet);
if (bottomSheetBehavior.getState() == BottomSheetBehavior.STATE_EXPANDED) {
//展開狀態,隱藏
bottomSheetBehavior.setState(BottomSheetBehavior.STATE_COLLAPSED);
} else {
//其餘的狀態展開
bottomSheetBehavior.setState(BottomSheetBehavior.STATE_EXPANDED);
}
複製代碼
其實這個東西的使用和對話框的使用基本上是同樣的。你setContentView()進去一個佈局,而後調用show()方法展現一下就能夠了,可是這裏有一個特別須要注意的地方,若是你在對話框中設置的佈局超過整個屏幕的話(這裏不是說你設置了match就是全屏了,是有效內容。這裏建議你試試就知道了),整個內容不會鋪滿全屏,頂部會留出一段空間,和peek的效果相似,這裏注意一下就能夠了!其餘的使用和對話框的使用同樣,這裏直接貼一下主要代碼!!!
BottomSheetDialog sheetDialog = new BottomSheetDialog(this); sheetDialog.setContentView(R.layout.sheet_dialog);
sheetDialog.show();
複製代碼
其實這個和寫一個Fragment是同樣的,可是也存在和上面彈出對話框的那種問題,就是當你佈局過大的狀況下會留出一段空間。
這裏主要說明兩點問題:
BottomSheetBehavior.from((View) view.getParent());
show(getSupportFragmentManager(), "dialog");
顯示。這個是滑動消失和滑動關閉,不少狀況下都是和5.0新出的Snackbar。一個和Toast相似的東西,由於不是本文重點,因此關於Snackbar就不展開說了!其實除了Snackbar使用到這個,基本上沒有那個APP想把本身的頁面劃沒了吧!!!其實用法仍是很簡單的,主要建立一個對象,設置一些像一個的參數就能夠了!直接上代碼:
TextView tvTitle = findViewById(R.id.tv_title);
SwipeDismissBehavior<View> mSwipe = new SwipeDismissBehavior();
/*
* SWIPE_DIRECTION_START_TO_END 只能從左向右滑動
* SWIPE_DIRECTION_END_TO_START 只能從右向左滑動
* SWIPE_DIRECTION_ANY 左右滑動均可以
*/
mSwipe.setSwipeDirection(SwipeDismissBehavior.SWIPE_DIRECTION_ANY);
mSwipe.setListener(new SwipeDismissBehavior.OnDismissListener() {
@Override
public void onDismiss(View view) {
//View消失的回調
}
@Override
public void onDragStateChanged(int state) {
/*
* STATE_IDLE 空閒狀態
* STATE_DRAGGING 滑動中
* STATE_SETTLING 消失
*/
}
});
複製代碼
註釋已經很詳細了,這裏注意一點啊,若是設置了滑動刪除功能,這個頁面就存在滑動刪除的功能了,是頁面存在這個功能,里門的大多數控件都能存在滑動刪除功能,可是我嘗試了,AppBarLayout等一些相應的控件不能,估計是設置了behavior的控件不能滑動刪除,其餘的均可以,可是這個只是個人猜想,沒有驗證!!!
這裏先明確一個概念,behavior的嵌套滾動都是依照一個相應的參考物,因此在自定義的時候必定要區分哪一個是依照的View哪一個是被觀察的View,只有區分了這些才能更好的理解下面的內容,下面出現的全部child都是被觀察的View,也就是xml中定義behavior的View。
layoutDependsOn(CoordinatorLayout parent, View child, View dependency) 表示是否給應用了Behavior 的View 指定一個依賴的佈局
onDependentViewChanged(CoordinatorLayout parent, View child, View dependency) 當依賴的View發生變化的時候hi掉的方法
onStartNestedScroll(@NonNull CoordinatorLayout coordinatorLayout, @NonNull View child, @NonNull View directTargetChild, @NonNull View target, int axes, int type) 當用戶手指按下的時候,你是否要處理此次操做。當你肯定要處理此次操做的時候,返回true;若是返回false的時候,就不會去響應後面的回調事件了。你想怎麼滑就怎麼話,我都不作處理。這裏的(axes)滾動方向很重要,能夠經過此參數判斷滾動方向!
onNestedScrollAccepted(@NonNull CoordinatorLayout coordinatorLayout, @NonNull V child, @NonNull View directTargetChild, @NonNull View target, @ScrollAxis int axes, @NestedScrollType int type) 當onStartNestedScroll準備處理此次滑動的時候(返回true的時候),回調這個方法。能夠在這個方法中作一些響應的準備工做!
onNestedPreScroll(@NonNull CoordinatorLayout coordinatorLayout, @NonNull View child, @NonNull View target, int dx, int dy, @NonNull int[] consumed, @NestedScrollType int type) 當滾動開始執行的時候回調這個方法。
onNestedScroll(@NonNull CoordinatorLayout coordinatorLayout, @NonNull View child, @NonNull View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed, int type) 上面這個方法結束的時候,coordinatorLayout處理剩下的距離,好比還剩10px。可是coordinatorLayout發現滾動2px的時候就已經到頭了。那麼結束其滾動,調用該方法,並將coordinatorLayout處理剩下的像素數做爲參(dxUnconsumed、dyUnconsumed)
傳過來,這裏傳過來的就是 8px。參數中還會有coordinatorLayout處理過的像素數(dxConsumed、dyConsumed)。老大開始處理剩下的距離了!這個方法主要處理一些越界後的滾動。仍是不懂對吧!還拿大家老大作比喻:好比上面還剩 10%的工做,這時老大處理了2%後發現已經能夠上線了,因而老大結束了工做,並將處理剩下的內容(dxUnconsumed、dyUnconsumed)紀錄下來,告訴你。老大處理了的內容(dxConsumed、dyConsumed)也告訴了你。
if (dyConsumed > 0 && dyUnconsumed == 0) {
System.out.println("上滑中。。。");
}
if (dyConsumed == 0 && dyUnconsumed > 0) {
System.out.println("到邊界了還在上滑。。。");
}
if (dyConsumed < 0 && dyUnconsumed == 0) {
System.out.println("下滑中。。。");
}
if (dyConsumed == 0 && dyUnconsumed < 0) {
System.out.println("到邊界了,還在下滑。。。");
}
複製代碼
onNestedPreFling(@NonNull CoordinatorLayout coordinatorLayout, @NonNull View child, @NonNull View target, float velocityX, float velocityY) 當手指鬆開發生慣性動做以前調用,這裏提供了響應的速度,你能夠根據速度判斷是否須要進行摺疊等一系列的操做,你要肯定響應這個方法的話,返回true。
onStopNestedScroll(@NonNull CoordinatorLayout coordinatorLayout, @NonNull View child, @NonNull View target, int type) 中止滾動的時候回調的方法。當你不去響應Fling的時候會直接回調這個方法。在這裏能夠作一些清理工做。或者其餘的內容。。。
onLayoutChild(CoordinatorLayout parent, View child, int layoutDirection) 肯定子View位置的方法,這個方法能夠從新定義子View的位置(這裏明確是設置behavior的那個View哦),例以下面這樣
基本上能用到的API就這麼多,可是這裏面的內容不少,先好好理解一下,我其實都不怎麼理解,沒事不理解沒事,後面幾個例子就ok了!
這裏說明一下自定義Behavior分爲兩種類型,一種是依賴相應的View變化而變化、一種是依賴滾動變化。
這個最經典的案例就是底欄跟隨AppBarLayout移動給移動,其實代碼很簡單,只要算出AppBarLayout的移動距離,動態的設置給相應的依賴控件就能夠了。一波代碼走起!!!先上一張效果圖。
public class TwoBehavior extends CoordinatorLayout.Behavior<View> {
private String TAG = TwoBehavior.class.getSimpleName();
//這個千萬要寫,不然會出異常
public TwoBehavior(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
public boolean layoutDependsOn(CoordinatorLayout parent, View child, View dependency) {
//依賴於AppBarLayout的
return dependency instanceof AppBarLayout;
}
@Override
public boolean onDependentViewChanged(CoordinatorLayout parent, View child, View dependency) {
//計算出AppBarLayout移動的距離
float top = Math.abs(dependency.getTop());
Log.e(TAG, "AppBarLayout移動的距離" + top);
child.setTranslationY(top);
return true;
}
}
複製代碼
而後代碼裏使用:app:layout_behavior="全路徑"
就能夠實現了,由於CoordinatorLayout裏面的內容怕你不知道,因此這裏我仍是貼一下相應的清單文件,要不你又該把實現不了的鍋讓我背了,這個鍋我不背。。。
<?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"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.jinlong.newmaterialdesign.behavior.TwoBehaviorActivity">
<android.support.design.widget.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<android.support.v7.widget.Toolbar
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="@color/colorPrimary"
app:layout_scrollFlags="scroll|enterAlways"
app:title="底部聯動的Behavior"
app:titleTextColor="@android:color/white" />
</android.support.design.widget.AppBarLayout>
<android.support.v4.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="200dp"
android:background="#009988"
android:gravity="center"
android:text="標籤1" />
<TextView
android:layout_width="match_parent"
android:layout_height="200dp"
android:background="#002288"
android:gravity="center"
android:text="標籤2" />
<TextView
android:layout_width="match_parent"
android:layout_height="200dp"
android:background="#009922"
android:gravity="center"
android:text="標籤3" />
<TextView
android:layout_width="match_parent"
android:layout_height="200dp"
android:background="#00aa88"
android:gravity="center"
android:text="標籤4" />
<TextView
android:layout_width="match_parent"
android:layout_height="200dp"
android:background="#999988"
android:gravity="center"
android:text="標籤5" />
</LinearLayout>
</android.support.v4.widget.NestedScrollView>
<TextView
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:layout_gravity="bottom"
android:background="@color/colorPrimary"
android:gravity="center"
android:text="這個一個底欄"
android:textColor="@android:color/white"
app:layout_behavior="com.jinlong.newmaterialdesign.behavior.TwoBehavior" />
</android.support.design.widget.CoordinatorLayout>
複製代碼
這裏面處理View的變化纔是難點,反正我是這麼認爲的,不懂的同窗能夠補充一下相應的知識,什麼View變化,獲取相應位置的方法等些許內容。網上仍是挺多的,依賴的View基本上都是這麼實現的。都是實現這兩個方法的,玩轉了就行了,多寫寫天然就熟了。
這個就比較難了,由於涉及到相應的滾動計算什麼的,只有多寫多看才能熟,先來一個簡單的例子吧。剩下的就靠你們多多練習了!!!
@Override
public boolean onLayoutChild(CoordinatorLayout parent, View child, int layoutDirection) {
//設置了behavior的佈局
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的高度,能夠設置成任何你想的高度
*/
public int getHeaderHeight(){
// 當你設置到相應的清單文件的時候,你就這麼弄
// return Context.getResources().getDimensionPixelOffset(R.dimen.header_height);
return 500;
}
複製代碼
這裏的位置肯定主要用到了一個View.setTranslationY()的方法,這個方法我查了查,和View.getTop()的方法有些相似,是相對於父控件左上角的偏移量。那麼就很好理解了,設置Behavior的View便宜到指定的位置下面,由於這裏設置了一個相應的圖片高度,因此這裏就是在圖片的下面。
@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 || super.onStartNestedScroll(coordinatorLayout, child, directTargetChild, target, axes, type);
}
複製代碼
首先點你手指開始滑動的時候,會執行下面這個方法。
@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);
// 在這個方法裏面只處理向上滑動
if(dy < 0){
return;
}
//計算每次移動的距離
float transY = child.getTranslationY() - dy;
if(transY > 0){
child.setTranslationY(transY);
consumed[1]= dy;
}
}
複製代碼
說明一下:dy<0 表明的是向下滑動。child.getTranslationY()獲取的是設置behavior的View距離CoordinatorLayout頂部的偏移量。dy表明的是每一次移動的距離。因此transY計算的就是每一次移動後應該距離頂部的距離設置給相應的View。這裏consumed[1]= dy表示的是你處理的距離(後面會用到的)。因此就不難理解了,當你每次向上滑動的時候,會計算相應的數值,設置給child,使它逐漸的向上移動,當到達頂部以後就不進行改變相應的豎直了。就醬紫了...
每次上面的方法執行完成的時候會調用下面這個方法
@Override
public void onNestedScroll(@NonNull CoordinatorLayout coordinatorLayout, @NonNull View child, @NonNull View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed, int type) {
super.onNestedScroll(coordinatorLayout, child, target, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, type);
// 在這個方法裏只處理向下滑動
if(dyUnconsumed >0){
return;
}
float transY = child.getTranslationY() - dyUnconsumed;
Log.i(TAG,"------>transY:"+transY+"****** child.getTranslationY():"+child.getTranslationY()+"--->dyUnconsumed"+dyUnconsumed);
if(transY > 0 && transY < getHeaderHeight()){
child.setTranslationY(transY);
}
}
複製代碼
還記的我上面說的那個老大的問題吧?當你consumed[1]= dy的時候,就會傳遞過來相應的參數dxUnconsumed/dyUnconsumed表明處理剩下的參數,可是這裏要注意一點。針對於上面這個例子當你上一個方法return的時候,那麼這個dxUnconsumed/dyUnconsumed就會有值的!那就不難理解了,當你向下滑動的時候會之間改變child的位置,直到child所有顯示出來爲止!
public class OneBehavior extends CoordinatorLayout.Behavior {
private String TAG = OneBehavior.class.getSimpleName();
public OneBehavior(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
public void onStopNestedScroll(@NonNull CoordinatorLayout coordinatorLayout, @NonNull View child, @NonNull View target, int type) {
super.onStopNestedScroll(coordinatorLayout, child, target, 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 || super.onStartNestedScroll(coordinatorLayout, child, directTargetChild, target, axes, type);
}
@Override
public void onNestedScroll(@NonNull CoordinatorLayout coordinatorLayout, @NonNull View child, @NonNull View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed, int type) {
super.onNestedScroll(coordinatorLayout, child, target, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, type);
// 在這個方法裏只處理向下滑動
if(dyUnconsumed >0){
return;
}
float transY = child.getTranslationY() - dyUnconsumed;
Log.i(TAG,"------>transY:"+transY+"****** child.getTranslationY():"+child.getTranslationY()+"--->dyUnconsumed"+dyUnconsumed);
if(transY > 0 && transY < getHeaderHeight()){
child.setTranslationY(transY);
}
}
@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);
// 在這個方法裏面只處理向上滑動
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 boolean onLayoutChild(CoordinatorLayout parent, View child, int layoutDirection) {
//設置了behavior的佈局
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的高度,能夠設置成任何你想的高度
*/
public int getHeaderHeight(){
// 當你設置到相應的清單文件的時候,你就這麼弄
// return Context.getResources().getDimensionPixelOffset(R.dimen.header_height);
return 500;
}
}
複製代碼
佈局的內容就簡單多了。直接引入一個behavior就能夠了。有一個問題注意下,由於上面面我是寫死的高度,因此佈局裏面就是一個寫死的高度了。若是你想作好適配的話,就直接從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:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<ImageView
android:layout_width="match_parent"
android:layout_height="500px"
android:scaleType="centerCrop"
android:src="@mipmap/heard_1" />
<!--這是一張圖片-->
<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="com.jinlong.newmaterialdesign.behavior.OneBehavior">
<!--這是是全路徑-->
<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>
複製代碼
基本上就這麼多了,其實我在算法上面真的很渣、很渣一個很渣已經不能形容我了。我已經很努力的給大家講明白了。不懂的能夠給我留言,其實自定義Behavior真的是看見一個你練一個,慢慢的你就能開車了!其實我也是看了別人分享的東西以後總結的。有什麼很差的地方還請多多指教!關於Behavior原理感興趣的同窗能夠看看HelloCsld的這篇文章講的也是挺透徹的!但願今天講的這些能幫到你!今天就到這裏吧!拜拜。。。
代碼在gitHub上的地址感興趣的同窗能夠去看看!!!