最近看到很多app實現動畫很好看,支持下拉刷新放大,上滑折疊,而且下半部分還是一個Tab+Viewpager布局, 網上很多的輪子都是自定義 Behavior 從而實現這些效果。
自定義Behavior的基礎知識
廢話少說,只講關鍵
Behavior是Android新出的Design庫里新增的布局概念。Behavior只有是CoordinatorLayout的直接子View才有意義,我們可以為任何View添加一個Behavior。說白了Behavior就是一系列回調,讓你有機會以非侵入的方式為View添加動態的依賴布局,和處理父布局(CoordinatorLayout)滑動手勢的機會。
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.
1.創建一個類繼承Behavior,同時指定其泛型,如果你需要設置其為某個特定的View類,可以在繼承的時候做的工作,類似這樣
public class CircleImageInUsercBehavior extends CoordinatorLayout.Behavior<CircleImageView> {
public CircleImageInUsercBehavior(Context context, AttributeSet attrs) {
super(context, attrs);
}
}
如何設置上去呢?兩種方式
① 可以在xml中直接指定你的Behavior,就像這樣
app:layout_behavior=“你的Behavior的全路徑”
②在代碼里動態注冊一個,就像AppBarLayout那樣
@DefaultBehavior(AppBarLayout.Behavior.class)
public class AppBarLayout extends LinearLayout {}
2.有哪些有用的方法可以重寫呢?
@Override
//當依賴的View發生變化時,主要用于跟隨其做運動,如果只是簡單的動畫,就可以在這里實現了
public boolean onDependentViewChanged(CoordinatorLayout parent, View child, View dependency) {
//這里實現自己的運動的方法,
//child代表的是當前使用這個Behavior的View啦
//dependency就是我們所依賴的對象
return true;
}
@Override
//確定你是否依賴于這個View
public boolean layoutDependsOn(CoordinatorLayout parent, View child, View dependency) {
return dependency.getId() == targetId;
}
這里需要說明的一點是,所謂的嵌套滾動是包含了滾動(scroll)和快速的劃動(fling:vt.(尤指生氣地)猛扔;急派;放肆地投入;扔在一邊,拋棄vt.& vi.猛撲;猛沖;急伸)
@Override
//開始嵌套滾動
public boolean onStartNestedScroll(CoordinatorLayout coordinatorLayout, View child, View directTargetChild, View target, int nestedScrollAxes) {
return true;//這里返回true,才會接受到后續滑動事件。
}
@Override
//嵌套滾動的過程中
public void onNestedScroll(CoordinatorLayout coordinatorLayout, View child, View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed) {
//進行滑動事件處理
}
@Override
//快速的劃動中
public boolean onNestedFling(CoordinatorLayout coordinatorLayout, View child, View target, float velocityX, float velocityY, boolean consumed) {
return super.onNestedFling(coordinatorLayout, child, target, velocityX, velocityY, consumed);
}
@Override
//即將開始嵌套滾動,每次滑動前,Child 先詢問 Parent 是否需要滑動,即dispatchNestedPreScroll(),這就回調到 Parent 的onNestedPreScroll(),Parent 可以在這個回調中“劫持”掉 Child 的滑動,也就是先于 Child 滑動。
public void onNestedPreScroll(CoordinatorLayout coordinatorLayout, AppBarLayout child, View target, int dx, int dy, int[] consumed) {
super.onNestedPreScroll(coordinatorLayout, child, target, dx, dy, consumed);
}
@Override
//即將開始快速劃動,這里可以做一些對動畫的緩沖處理,也就是我們如何去應對用戶快速的操作
public boolean onNestedPreFling(CoordinatorLayout coordinatorLayout, AppBarLayout child, View target, float velocityX, float velocityY) {
return super.onNestedPreFling(coordinatorLayout, child, target, velocityX, velocityY);
}
例子的實現
例子中呢,用到了兩個Behavior:
- 用戶頭像的放大以及縮小的Behavior(CircleImageInUsercBehavior),按照上面的方法,我們可以很明白的知道實現步驟了
1.繼承
public class CircleImageInUsercBehavior extends CoordinatorLayout.Behavior<CircleImageView>
1.重寫onDependentViewChanged
//當dependency變化的時候調用
@Override
public boolean onDependentViewChanged(CoordinatorLayout parent, CircleImageView child, View dependency) {
//初始化一些基礎參數
init(parent, child, dependency);
//計算比例
...
//設置頭像的大小
ViewCompat.setScaleX(child, percent);
ViewCompat.setScaleY(child, percent);
return false;
}
那我有一個問題了,是不是說每一個view想要做跟隨動畫,都得創建一個相應的Behavior呢?答案很明顯是NO~!
看完下一個例子你就會明白了:
2.另外一個Behavior(AppBarLayoutOverScrollViewBehavior)用途主要有以下3點:
*控制背景圖的放大以及回彈
*中間middle部分跟隨背景圖的放大縮小做相應的移動
*Toolbar的背景Alpha的改變
第一步:初始化參數,通過tag查找每一個View,這里需要注意,我們需要在布局文件中,每個相應的View都需要聲明相同的tag 如 android:tag="你的tag",當然,也可以用原始的findViewById,這里只是希望id改動時,我們的Behavior可以不受到影響
@Override
public boolean onLayoutChild(CoordinatorLayout parent, AppBarLayout abl, int layoutDirection) {
...
if (mToolBar == null) {
mToolBar = (Toolbar) parent.findViewWithTag(TAG_TOOLBAR);
}
...
abl.addOnOffsetChangedListener(new AppBarLayout.OnOffsetChangedListener() {
...//實現Toolbar的背景變化
});
...
}
第二步:開始scale動畫(下拉上劃滑動過程中)
@Override
public void onNestedPreScroll(CoordinatorLayout coordinatorLayout, AppBarLayout child, View target, int dx, int dy, int[] consumed) {
if (!isRecovering) {//未在回彈動畫中,開始我們的變化動畫
if (...) {
scale(child, target, dy);
return;
}
}
super.onNestedPreScroll(coordinatorLayout, child, target, dx, dy, consumed);
@Override
public boolean onNestedPreFling(CoordinatorLayout coordinatorLayout, AppBarLayout child, View target, float velocityX, float velocityY) {
if (velocityY > 100) {//當y速度>100,就秒彈回
isAnimate = false;
}
return super.onNestedPreFling(coordinatorLayout, child, target, velocityX, velocityY);
}
}
第三步:松手的回彈
@Override
public void onStopNestedScroll(CoordinatorLayout coordinatorLayout, AppBarLayout abl, View target) {
recovery(abl);//回彈,這個方法詳細請看源碼
super.onStopNestedScroll(coordinatorLayout, abl, target);
}
附:AppBarLayout的跟隨動畫,不僅僅是上面的一種方式
我們也可以在邏輯代碼中通過原生的Listener來實現
mAppBarLayout.addOnOffsetChangedListener(new AppBarLayout.OnOffsetChangedListener() {
@Override
public void onOffsetChanged(AppBarLayout appBarLayout, int verticalOffset) {
//計算進度百分比
float percent = Float.valueOf(Math.abs(verticalOffset)) / Float.valueOf(appBarLayout.getTotalScrollRange());
...//根據百分比做你想做的
}
});