CoordinatorLayout自定義Behavior和原始碼分析
前沿
Behavior是Android新出的Design庫裡新增的佈局概念。Behavior只有是CoordinatorLayout的直接子View才有意義。可以為任何View新增一個Behavior。Behavior是一系列回撥。讓你有機會以非侵入的為View新增動態的依賴佈局,和處理父佈局(CoordinatorLayout)滑動手勢的機會。
一、 某個View需要監聽另一個View的狀態(比如:位置、大小、顯示狀體)
一個View監聽另一個View,只需要在自定義Behavior重寫:layoutDependsOn/onDependentViewChanged方法
自定義Behavior
public class CustomBehavior extends CoordinatorLayout.Behavior {
public CustomBehavior(Context context, AttributeSet attrs) {
super(context, attrs);
}
/**
* 用來決定需要監聽哪些控制元件或者容器的狀態(1.知道監聽誰;2.什麼狀態改變)
* CoordinatorLayout parent ,父容器
* View child, 子控制元件---需要監聽dependency這個view的檢視們---觀察者
* View dependency,你要監聽的那個View
*/
@Override
public boolean layoutDependsOn(CoordinatorLayout parent, View child, View dependency) {
//還可以根據ID或者TAG來判斷是哪一個TextView
return dependency instanceof TextView || super.layoutDependsOn(parent, child, dependency);
}
/**
* 當被監聽的view發生改變的時候回撥
* 可以在此方法裡面做一些響應的聯動動畫等效果。
*/
@Override
public boolean onDependentViewChanged(CoordinatorLayout parent, View child, View dependency) {
//獲取被監聽的view的狀態---垂直方向位置
int offset = dependency.getTop() - child.getTop();
//讓child進行平移
ViewCompat.offsetTopAndBottom(child, offset);
child.animate().rotation(child.getTop()*90);
return true;
}
}
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="match_parent">
<TextView
android:id="@+id/tv1"
android:tag="tv1"
android:layout_width="80dp"
android:layout_height="80dp"
android:background="#ff0"
android:layout_gravity="left|top"
android:text="被觀察--dependent" />
<ImageView
app:layout_behavior="com.example.mycustombehavior.CustomBehavior"
android:layout_width="80dp"
android:layout_height="80dp"
android:layout_gravity="right|top"
android:background="#f00"
android:src="@mipmap/ic_launcher"
android:text="觀察者" />
</android.support.design.widget.CoordinatorLayout>
MainActivity中設定一個控制元件的點選事件
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
findViewById(R.id.tv1).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
ViewCompat.offsetTopAndBottom(v, 10);
}
});
}
效果圖:
tips:
1、自定義Behavior一定要重寫構造方法不然就會報錯(因為在CoordinatorLayout裡利用反射去獲取這個Behavior的時候就是拿的這個構造。)
2、layoutDependsOn(parent,child,dependency) : 用來決定需要監聽哪些控制元件或者容器的狀態,parent父容器;child子控制元件也是觀察者;dependency監聽的View也是被觀察者
3、onDependentViewChanged(parent,child,dependecy) : 當被監聽的View發生改變的時候回撥,可以在此方法裡面做一些相應的聯動效果
二、某個View需要監聽CoordinatorLayout裡面所有控制元件的滑動狀態。
某個View需要監聽CoordinatorLayout裡面所有控制元件的滑動效果需要重寫:onStartNestedScroll
/onNestedScroll
,或則onNestedPreScroll
方法,
==注意==:能被CoordinatorLayout捕獲到的滑動狀態的控制元件只有:recyclerView
/NestScrollView
/ViewPager
自定義Behavior
public class SyncScrollBehavior extends CoordinatorLayout.Behavior<View>{
public SyncScrollBehavior(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
public boolean onStartNestedScroll(@NonNull CoordinatorLayout coordinatorLayout, @NonNull View child, @NonNull View directTargetChild, @NonNull View target, int axes) {
return (axes == ViewCompat.SCROLL_AXIS_VERTICAL) || super.onStartNestedScroll(coordinatorLayout, child, directTargetChild, target, axes);
}
@Override
public void onNestedPreScroll(@NonNull CoordinatorLayout coordinatorLayout, @NonNull View child, @NonNull View target, int dx, int dy, @NonNull int[] consumed) {
int scrollY = target.getScrollY();
child.setScrollY(scrollY);
super.onNestedPreScroll(coordinatorLayout, child, target, dx, dy, consumed);
}
/*@Override
public boolean onNestedFling(@NonNull CoordinatorLayout coordinatorLayout, @NonNull View child, @NonNull View target, float velocityX, float velocityY, boolean consumed) {
// 快速滑動的慣性移動(鬆開手指後還會有滑動效果)
((NestedScrollView)child).fling((int) velocityY);
return super.onNestedFling(coordinatorLayout, child, target, velocityX, velocityY, consumed);
}*/
}
xml檔案
<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" >
<android.support.v4.widget.NestedScrollView
android:layout_width="80dp"
android:layout_height="match_parent"
android:layout_gravity="left" >
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:orientation="vertical" >
<TextView
android:layout_width="80dp"
android:layout_height="180dp"
android:layout_gravity="left|top"
android:background="#ff0"
android:text="被觀察--dependent" />
<TextView
android:layout_width="80dp"
android:layout_height="180dp"
android:layout_gravity="left|top"
android:background="#ff0"
android:text="被觀察--dependent" />
...
</LinearLayout>
</android.support.v4.widget.NestedScrollView>
<android.support.v4.widget.NestedScrollView
android:layout_width="80dp"
android:layout_height="match_parent"
app:layout_behavior="com.example.mycustombehavior2.SyncScrollBehavior"
android:layout_gravity="right" >
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:orientation="vertical" >
<TextView
android:layout_width="80dp"
android:layout_height="180dp"
android:layout_gravity="left|top"
android:background="#ff0"
android:text="被觀察--dependent" />
<TextView
android:layout_width="80dp"
android:layout_height="180dp"
android:layout_gravity="left|top"
android:background="#ff0"
android:text="被觀察--dependent" />
...
</LinearLayout>
</android.support.v4.widget.NestedScrollView>
</android.support.design.widget.CoordinatorLayout>
以上程式碼有點問題,就是當快速滑動的時候會出現錯位,原因是慣性引起。
如下圖:
所以解決問題有兩種:
① 、需要在onNestedFling方法呼叫RecyclerView的fling方法(上面自定義Behavior裡的註釋的程式碼)
②、上面程式碼自定義Behavio的兩個方法都是過時的,因此使用不是過時的就可以了
public class SyncScrollBehavior extends CoordinatorLayout.Behavior{
public SyncScrollBehavior(Context context, AttributeSet attrs) {
super(context, attrs);
}
@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) {
int scrollY = target.getScrollY();
child.setScrollY(scrollY);
super.onNestedPreScroll(coordinatorLayout, child, target, dx, dy, consumed, type);
}
}
最終效果:
三、Behavior原始碼分析
在CoordinatorLayout原始碼parseBehavior函式中,此函式是在初始化CoordinatorLayout.LayoutParams的時候呼叫
public static class LayoutParams extends ViewGroup.MarginLayoutParams {
Behavior mBehavior;
...
LayoutParams(Context context, AttributeSet attrs) {
super(context, attrs);
final TypedArray a = context.obtainStyledAttributes(attrs,
R.styleable.CoordinatorLayout_LayoutParams);
...
mBehaviorResolved = a.hasValue(
R.styleable.CoordinatorLayout_LayoutParams_layout_behavior);
if (mBehaviorResolved) {
mBehavior = parseBehavior(context, attrs, a.getString(
R.styleable.CoordinatorLayout_LayoutParams_layout_behavior));
}
a.recycle();
}
}
static Behavior parseBehavior(Context context, AttributeSet attrs, String name) {
...
if (c == null) {
final Class<Behavior> clazz = (Class<Behavior>) Class.forName(fullName, true,
context.getClassLoader());
c = clazz.getConstructor(CONSTRUCTOR_PARAMS);
c.setAccessible(true);
constructors.put(fullName, c);
}
return c.newInstance(context, attrs);
...
}
其中CONSTRUCTOR_PARAMS
static final Class<?>[] CONSTRUCTOR_PARAMS = new Class<?>[] {
Context.class,
AttributeSet.class
};
由此可見必須要在子類中重寫構造方法
根據上面文中的一和二來檢視原始碼.
首先一中的layoutDependsOn和onDependentViewChanged是在CoordinatorLayout類onChildViewsChanged方法中進行呼叫
final void onChildViewsChanged(@DispatchChangeEvent final int type) {
......
for (int i = 0; i < childCount; i++) {
final View child = mDependencySortedChildren.get(i);
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
if (type == EVENT_PRE_DRAW && child.getVisibility() == View.GONE) {
// Do not try to update GONE child views in pre draw updates.
continue;
}
......
// Update any behavior-dependent views for the change
for (int j = i + 1; j < childCount; j++) {
final View checkChild = mDependencySortedChildren.get(j);
final LayoutParams checkLp = (LayoutParams) checkChild.getLayoutParams();
final Behavior b = checkLp.getBehavior();
if (b != null && b.layoutDependsOn(this, checkChild, child)) {
if (type == EVENT_PRE_DRAW && checkLp.getChangedAfterNestedScroll()) {
// If this is from a pre-draw and we have already been changed
// from a nested scroll, skip the dispatch and reset the flag
checkLp.resetChangedAfterNestedScroll();
continue;
}
final boolean handled;
switch (type) {
case EVENT_VIEW_REMOVED:
// EVENT_VIEW_REMOVED means that we need to dispatch
// onDependentViewRemoved() instead
b.onDependentViewRemoved(this, checkChild, child);
handled = true;
break;
default:
// Otherwise we dispatch onDependentViewChanged()
handled = b.onDependentViewChanged(this, checkChild, child);
break;
}
if (type == EVENT_NESTED_SCROLL) {
// If this is from a nested scroll, set the flag so that we may skip
// any resulting onPreDraw dispatch (if needed)
checkLp.setChangedAfterNestedScroll(handled);
}
}
}
}
...
}
從原始碼中看出當Child為GONE時將不會執行後面的onDependenViewChanged等方法
通過檢視onChildViewsChanged方法的呼叫的源頭可以看出最終也是由onNestedPreScroll和onNestedScroll呼叫
@Override
public void onNestedScroll(View target, int dxConsumed, int dyConsumed,
int dxUnconsumed, int dyUnconsumed) {
final int childCount = getChildCount();
boolean accepted = false;
for (int i = 0; i < childCount; i++) {
final View view = getChildAt(i);
if (view.getVisibility() == GONE) {//如果為GONE則跳過
// If the child is GONE, skip...
continue;
}
final LayoutParams lp = (LayoutParams) view.getLayoutParams();
if (!lp.isNestedScrollAccepted()) {
continue;
}
final Behavior viewBehavior = lp.getBehavior();
if (viewBehavior != null) {
viewBehavior.onNestedScroll(this, view, target, dxConsumed, dyConsumed,
dxUnconsumed, dyUnconsumed);
accepted = true;//當呼叫了Behavior的onNestedScroll方法也將會呼叫onChildViewChanged方法
}
}
if (accepted) {
onChildViewsChanged(EVENT_NESTED_SCROLL);//呼叫了上面提到的onChildViewChanged方法進而呼叫layoutDependsOn和onDependentViewChanged
}
}
當時onStartNestedScroll方法並沒有呼叫,當然這也和在onStartNestedScroll方法進行判斷滑動View是豎直還是水平有關
@Override
public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes) {
boolean handled = false;
final int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
final View view = getChildAt(i);
if (view.getVisibility() == View.GONE) {
// If it's GONE, don't dispatch
continue;
}
final LayoutParams lp = (LayoutParams) view.getLayoutParams();
final Behavior viewBehavior = lp.getBehavior();
if (viewBehavior != null) {
final boolean accepted = viewBehavior.onStartNestedScroll(this, view, child, target,
nestedScrollAxes);
handled |= accepted;
lp.acceptNestedScroll(accepted);
} else {
lp.acceptNestedScroll(false);
}
}
return handled;
}
至此簡易的原始碼介紹結束
相關文章
- 自定義View事件篇進階篇(三)-CoordinatorLayout與BehaviorView事件
- JAVA Comparator 自定義排序 原始碼分析Java排序原始碼
- mybaits原始碼分析--自定義外掛(七)AI原始碼
- Netty原始碼分析之自定義編解碼器Netty原始碼
- 自定義View事件之進階篇(四)-自定義Behavior實戰View事件
- OverScroll彈性滾動和慣性滾動效果的實現原理——CoordinatorLayout+Behavior
- 自定義behavior-仿華為應用市場
- drf-jwt原始碼分析以及自定義token簽發認證、alc和rbacJWT原始碼
- Mybatis原始碼分析(七)自定義快取、分頁的實現MyBatis原始碼快取
- 影片直播系統原始碼,自定義背景和狀態管理原始碼
- 重學c#系列——盛派自定義異常原始碼分析(八)C#原始碼
- Django(63)drf許可權原始碼分析與自定義許可權Django原始碼
- app直播原始碼,java自定義註解APP原始碼Java
- 自定義來電秀怎麼實現?Android 來電秀原始碼分析Android原始碼
- Django(64)頻率認證原始碼分析與自定義頻率認證Django原始碼
- element-ui - 原始碼學習 - 自定義事件UI原始碼事件
- 自定義 loader 讀取 *.vue 檔案原始碼Vue原始碼
- 學習JUC原始碼(2)——自定義同步元件原始碼元件
- React-原始碼解析-生命週期(自定義元件)React原始碼元件
- [iOS] [OC] NSNotificationCenter 進階及自定義(附原始碼)iOS原始碼
- 短影片app原始碼,自定義快速捲軸FastScrollBarAPP原始碼AST
- Kong Gateway 修改原始碼完成自定義錯誤返回Gateway原始碼
- 直播系統app原始碼,TabLayout:自定義字型大小APP原始碼TabLayout自定義字型
- 線上直播系統原始碼,自定義底部 BottomNavigationBar原始碼Navigation
- 線上直播原始碼,自定義氣泡效果(BubbleView)原始碼View
- 自定義 behavior 完美仿 QQ 瀏覽器首頁,美團商家詳情頁瀏覽器
- scheduleWithFixedDelay和scheduleAtFixedRate原始碼分析原始碼
- ThreadLocal和ThreadLocalMap原始碼分析thread原始碼
- CountDownLatch 概述和原始碼分析CountDownLatch原始碼
- WMRouter使用和原始碼分析原始碼
- app直播原始碼,uniapp之自定義頂部樣式APP原始碼
- 直播平臺原始碼,Flutter 自定義 虛線 分割線原始碼Flutter
- 手機直播原始碼,Flutter 自定義 虛線 分割線原始碼Flutter
- 直播軟體原始碼,自定義RecyclerView支援快速滾動原始碼View
- spring原始碼深度解析— IOC 之 自定義標籤解析Spring原始碼
- spring security 授權方式(自定義)及原始碼跟蹤Spring原始碼
- 直播商城原始碼,vue 自定義指令過濾特殊字元原始碼Vue字元
- app直播原始碼,vue 自定義指令過濾特殊字元APP原始碼Vue字元
- 以太坊原始碼分析(30)eth-bloombits和filter原始碼分析原始碼OOMFilter