寫在前面
Material 系列文章:
Material Design 之 Toolbar 開發實踐總結
Material Design之 AppbarLayout 開發實踐總結
前面兩篇文章講了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。
本文目錄:
- Behavior 介紹
- BottomSheetBehavior/BottomSheetDialog 的使用
- SwipeDissmissBehavior 的使用
- 自定義 Behavior
正文
1,Behavior 介紹
看一下官方的介紹:Interaction behavior plugin for child views of CoordinatorLayout. 作用於CoordinatorLayout的子View的互動行為外掛。一個Behavior 實現了使用者的一個或者多個互動行為,它們可能包括拖拽、滑動、快滑或者其他一些手勢。
Behavior 是一個頂層抽象類,其他的一些具體行為的Behavior 都是繼承自這個類。它提供了幾個重要的方法:
- 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 實現起來就簡單一點了。請看效果圖:
看看怎麼用BottomSheetBehavior:
1,在xml佈局檔案中為需要從底部彈出的佈局繫結BottomSheetBehavior,程式碼如下:
<?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效果 如下:
來張gif圖效果更清楚:
本文通過BottomSheetDialog 實現的效果圖如下:
歌單程式碼如下:
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 消失,效果如下:
使用也非常簡單,在程式碼中只接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 和狀態改變,在這些回撥裡面可以做一些自己的邏輯。最後效果圖:
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個方法就可以了,分別是
layoutDependsOn
和onDependentViewChanged
, layoutDependsOn方法判斷是指定依賴的View時,返回true,然後在onDependentViewChanged 裡,被依賴的View做需要的行為動作。
- 第二種就是重寫
onStartNestedScroll
、onNestedPreScroll
、onNestedScroll
等一系列方法,前面第一步分已經講過。
上面兩種方法相比,第一種很簡單,第二種複雜一些,但是第二種實現的效果也要複雜。下面就以開眼首頁的滑動Header效果為例,來實現一個自定義的Behavior。開眼首頁滑動header效果如下:
效果如上:就是列表滑動的時候是覆蓋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>複製程式碼
最後實現的效果如下:
最後
以上就是關於Behavior 的全部內容,自定義Behavior 這一塊,特別是處理滑動巢狀對於剛接觸的同學來說還是挺難的,不過當掌握了之後,我們能做出很多炫酷的效果。所以,再困難也值得花時間去學習。本文到此結束,如有問題,歡迎交流。所有關於Material Design 的使用示例都在這裡:MaterialDesignSamples
參考資料:
1,自定義Behavior的藝術探索-仿UC瀏覽器主頁
2,使用 CoordinatorLayout 實現複雜聯動效果
3,Material之Behavior實現支付寶密碼彈窗 仿淘寶/天貓商品屬性選擇