android MD 进阶[五] CoordinatorLayout 从源码到实战..
Posted android超级兵
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了android MD 进阶[五] CoordinatorLayout 从源码到实战..相关的知识,希望对你有一定的参考价值。
android MD进阶(五) coordinatorLayout从源码到实战
前言: 上一篇:android View生命周期 介绍了view的生命周期,上上一篇:android MD进阶[四] NestedScrollView 从源码到实战…因为和本篇息息相关,没有了解过的同学可以先看看这两篇哦 ~
CoordinatorLayout源码版本: 1.1.0
废话不多说,先来看看今天要完成的效果:
效果一 | 效果二 |
---|---|
什么是coordinatorLayout?
coordinatorLayout
意为协调者布局, 每个 ViewGroup 都有相应的特征例如:
- LinearLayout 线性布局 常用来水平/垂直摆放childView
- RelativeLayout 相对布局 常用来 AView 在 BView 的某个位置,来摆放 childView
- ConstraintLayout 约束布局 常用来 CView 在 DView 的某个约束位置,很好的解决了布局嵌套层级过深以及布局困难的问题
所以coordinatorLayout
的特征是什么呢?
它的特征为: 可以配合每个 childView
来协调使用,比如,在移动 AView 的过程中我想让 BView 和 CView 发生改变(例如效果一
),那么就可以用它,它的缺点也非常明显,布局起来稍稍优点麻烦…
难道说我用coordinatorLayout
就可以让他协调起来? 用意念吗? 那当然不行.
单指coordinatorLayout
没有太大的作用,重要的是coordinatorLayout
配合 behavior
来使用!
那什么是 behavior
呢 ?
behavior
为行为,假设 AView 移动过程中 BView 想要跟随者 AView 移动,那么 BView 直接在 xml 中添加一个 app:layout_behavior="XXX"
属性,然后自定义CoordinatorLayout.Behavior
即可
最后再来看一张代码图,先有个初步的了解!
这个效果是如果 MoveView
发生移动,那么ImageView就发生颜色的变化
所以本篇的重点就是CoordinatorLayout.Behavior
源码分析,以及使用到实战!
CoordinatorLayout.Behavior初步认识
# CoordinatorLayout.java
public static abstract class Behavior<V extends View>
/*
* TODO 当解析layout完成时候调用 View#onAttachedToWindow() 然后紧接着调用该方法
*/
public void onAttachedToLayoutParams(@NonNull CoordinatorLayout.LayoutParams params)
/*
* TODO 当 view销毁的时候调用
*/
public void onDetachedFromLayoutParams()
/**
* TODO: 当 CoordinatorLayout#onInterceptTouchEvent() 事件的时候调用
*/
public boolean onInterceptTouchEvent(@NonNull CoordinatorLayout parent, @NonNull V child,
@NonNull MotionEvent ev)
return false;
/**
* TODO: 当 CoordinatorLayout#onTouchEvent() 事件的时候调用
*/
public boolean onTouchEvent(@NonNull CoordinatorLayout parent, @NonNull V child,
@NonNull MotionEvent ev)
return false;
/*
* TODO 设置背景色
*
* 需要配合 getScrimOpacity() 使用 因为 getScrimOpacity() 默认 = 0f
*/
@ColorInt
public int getScrimColor(@NonNull CoordinatorLayout parent, @NonNull V child)
return Color.BLACK;
/*
* TODO 设置不透明度
*/
@FloatRange(from = 0, to = 1)
public float getScrimOpacity(@NonNull CoordinatorLayout parent, @NonNull V child)
return 0.f;
/*
* TODO 需要依赖的view
*
* @param child: 当前view
* @param dependency: 需要依赖的View
*/
public boolean layoutDependsOn(@NonNull CoordinatorLayout parent, @NonNull V child,
@NonNull View dependency)
return false;
/*
* TODO 需要依赖的view发生变化的时候调用
*/
public boolean onDependentViewChanged(@NonNull CoordinatorLayout parent, @NonNull V child,
@NonNull View dependency)
return false;
/*
* TODO 当被依赖的view移除view的时候调用
*/
public void onDependentViewRemoved(@NonNull CoordinatorLayout parent, @NonNull V child,
@NonNull View dependency)
/*
* TODO 调用 CoordinatorLayout#onMeasureChild() 的时候调用
*/
public boolean onMeasureChild(@NonNull CoordinatorLayout parent, @NonNull V child,
int parentWidthMeasureSpec, int widthUsed,
int parentHeightMeasureSpec, int heightUsed)
return false;
/*
* TODO 调用CoordinatorLayout$onLayout() 的时候调用
*/
public boolean onLayoutChild(@NonNull CoordinatorLayout parent, @NonNull V child,
int layoutDirection)
return false;
/*
* TODO 当 NestedScrollingChild#startNestedScroll() 的时候调用
*/
@Deprecated
public boolean onStartNestedScroll(@NonNull CoordinatorLayout coordinatorLayout,
@NonNull V child, @NonNull View directTargetChild, @NonNull View target,
@ScrollAxis int axes)
return false;
/*
* TODO 当 NestedScrollingChild2#startNestedScroll() 的时候调用
*/
public boolean onStartNestedScroll(@NonNull CoordinatorLayout coordinatorLayout,
@NonNull V child, @NonNull View directTargetChild, @NonNull View target,
@ScrollAxis int axes, @NestedScrollType int type)
if (type == ViewCompat.TYPE_TOUCH)
return onStartNestedScroll(coordinatorLayout, child, directTargetChild,
target, axes);
return false;
/*
* TODO 当 NestedScrollingChild#startNestedScroll() = true的时候调用
*/
@Deprecated
public void onNestedScrollAccepted(@NonNull CoordinatorLayout coordinatorLayout,
@NonNull V child, @NonNull View directTargetChild, @NonNull View target,
@ScrollAxis int axes)
/*
* TODO 当 NestedScrollingChild2#startNestedScroll() = true的时候调用
*/
public void onNestedScrollAccepted(@NonNull CoordinatorLayout coordinatorLayout,
@NonNull V child, @NonNull View directTargetChild, @NonNull View target,
@ScrollAxis int axes, @NestedScrollType int type)
if (type == ViewCompat.TYPE_TOUCH)
onNestedScrollAccepted(coordinatorLayout, child, directTargetChild,
target, axes);
/*
* TODO 当 NestedScrollingChild#stopNestedScroll() 调用的时候执行
*/
@Deprecated
public void onStopNestedScroll(@NonNull CoordinatorLayout coordinatorLayout,
@NonNull V child, @NonNull View target)
// Do nothing
/*
* TODO 当 NestedScrollingChild2#stopNestedScroll() 调用的时候执行
*/
public void onStopNestedScroll(@NonNull CoordinatorLayout coordinatorLayout,
@NonNull V child, @NonNull View target, @NestedScrollType int type)
...
/*
* TODO 当 NestedScrollingChild#dispatchNestedScroll() 调用的时候执行
*/
@Deprecated
public void onNestedScroll(@NonNull CoordinatorLayout coordinatorLayout, @NonNull V child,
@NonNull View target, int dxConsumed, int dyConsumed, int dxUnconsumed,
int dyUnconsumed)
// Do nothing
/*
* TODO 当 NestedScrollingChild2#dispatchNestedScroll() 调用的时候执行
*/
@Deprecated
public void onNestedScroll(@NonNull CoordinatorLayout coordinatorLayout, @NonNull V child,
@NonNull View target, int dxConsumed, int dyConsumed, int dxUnconsumed,
int dyUnconsumed, @NestedScrollType int type)
...
/*
* TODO 当 NestedScrollingChild3#dispatchNestedScroll() 调用的时候执行
*/
public void onNestedScroll(@NonNull CoordinatorLayout coordinatorLayout, @NonNull V child,
@NonNull View target, int dxConsumed, int dyConsumed, int dxUnconsumed,
int dyUnconsumed, @NestedScrollType int type, @NonNull int[] consumed)
...
/*
* TODO 当 NestedScrollingChild#dispatchNestedPreScroll() 调用的时候执行
*/
@Deprecated
public void onNestedPreScroll(@NonNull CoordinatorLayout coordinatorLayout,
@NonNull V child, @NonNull View target, int dx, int dy, @NonNull int[] consumed)
// Do nothing
/*
* TODO 当 NestedScrollingChild2#dispatchNestedPreScroll() 调用的时候执行
*/
public void onNestedPreScroll(@NonNull CoordinatorLayout coordinatorLayout,
@NonNull V child, @NonNull View target, int dx, int dy, @NonNull int[] consumed,
@NestedScrollType int type)
if (type == ViewCompat.TYPE_TOUCH)
onNestedPreScroll(coordinatorLayout, child, target, dx, dy, consumed);
/*
* TODO 当 NestedScrollingChild#dispatchNestedFling() 调用的时候执行
*/
public boolean onNestedFling(@NonNull CoordinatorLayout coordinatorLayout,
@NonNull V child, @NonNull View target, float velocityX, float velocityY,
boolean consumed)
return false;
/*
* TODO 当 NestedScrollingChild2#dispatchNestedFling() 调用的时候执行
*/
public boolean onNestedPreFling(@NonNull CoordinatorLayout coordinatorLayout,
@NonNull V child, @NonNull View target, float velocityX, float velocityY)
return false;
/*
* TODO 恢复状态
*/
public void onRestoreInstanceState(@NonNull CoordinatorLayout parent, @NonNull V child,
@NonNull Parcelable state)
/*
* TODO 保存状态 和V iew / Activity 保存状态一样
*
* tips: 1. 必须保证View在xml中设置了id (android:id="@+id/XXX")
* 2. 必须保证view参数中有behavior属性 (app:behavior="www.com.XXX")
*/
@Nullable
public Parcelable onSaveInstanceState(@NonNull CoordinatorLayout parent, @NonNull V child)
return BaseSavedState.EMPTY_STATE;
搞这么多,谁能看懂啊… 别着急,本篇换个思路,先来1 个简单的 demo,然后再步入源码分析,由俭入奢.
我的第一个自定义 Behavior
先来自定义一个跟随手指滑动的 View
MoveView.kt
class MoveView @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr)
private var lastOffset = OffSet(0f, 0f)
override fun onTouchEvent(event: MotionEvent): Boolean
when (event.action)
MotionEvent.ACTION_DOWN ->
lastOffset = OffSet(event.x, event.y)
MotionEvent.ACTION_MOVE ->
val dx = event.x - lastOffset.x
val dy = event.y - lastOffset.y
ViewCompat.offsetLeftAndRight(this, dx.toInt())
ViewCompat.offsetTopAndBottom(this, dy.toInt())
return true
这段代码有手就行,不用多讲
现在要做的就是,当 View 移动的时候,另一个 View 颜色跟随变化
来看一眼布局:
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.example.meterialproject.view.behavior.demo1.MoveView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:background="@color/purple_700" />
<androidx.appcompat.widget.AppCompatImageView
android:layout_width="100dp"
android:layout_height="100dp"
android:background="@color/green"
app:layout_behavior=".view.behavior.demo1.ColorBehavior" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>
自定义 behavior
# ColorBehavior.kt
class ColorBehavior(val context: Context, attrs: AttributeSet?) :
CoordinatorLayout.Behavior<AppCompatImageView>(context, attrs)
companion object
const val TAG = "szjColorBehavior"
/*
* TODO 判断跟随变化的 View
* @param parent: CoordinatorLayout
* @param child: 当前的 view
* @param dependency: 需要依赖的 View
*/
override fun layoutDependsOn(
parent: CoordinatorLayout,
child: AppCompatImageView,
dependency: View
): Boolean
return dependency is MoveView
// 改变当前的状态
override fun onDependentViewChanged(
parent: CoordinatorLayout,
child: AppCompatImageView,
dependency: View
): Boolean
// 随机颜色
child.setBackgroundColor(context.randomColor())
return super.onDependentViewChanged(parent, child, dependency)
参数介绍:
参数 | 作用 | 默认 |
---|---|---|
layoutDependsOn() | 用来判断以来的 View | false |
onDependentViewChanged() | 当 layoutDependsOn() = ture,并且当前 view 可见状态下执行 | false |
这里有 2 个重要的角色
- 依赖方: AppCompatImageView
- 被依赖方: MoveView
那么现在依赖方(AppCompatImageView)
就可以监听到被依赖方(MoveView)
的变化
初步原理图:
初步分析:
CoordinatorLayout
保存记录所有的ChildView
, 通过 addOnPreDrawListener()
监听所有View的变化
然后通过遍历所有保存的childView
, 判断childView
中是否有behavior
如果有behavior
那么在判断 Behavior#layoutDependsOn()
是否依赖MoveView
如果也依赖于MoveView
,那么就将事件传递给对应的 Behavior#onDependentViewChanged
所以到底是什么意思呢?
一句话总结就是 如果Behavior#layoutDependsOn()
返回true 就会执行到 Behavior#onDependentViewChanged()
的方法
addOnPreDrawListener和 setOnHierarchyChangeListener
-
addOnPreDrawListener: 是ViewGroup用来监听所有childView变化的, 只要childView有变化,例如 DOWN / MOVE 事件等
-
setOnHierarchyChangeListener 是ViewGroup监听自身childView发生变化来响应的 一共有2个方法:
-
onChildViewAdded 添加响应
-
onChildViewRemoved 删除响应
效果图:
我认为这是CoordinatorLayout的核心!
CoordinatorLayout 源码分析
看源码的小技巧:
首先必须知道你要看的源码是做什么的,必须会使用,比如说本篇 CoordinatorLayout
的源码,就是用来协调childView的
-
构造函数 (必看)
-
如果是
View
根据View
的生命周期看每个方法的实现, 如果是ViewGroup
别忘记看generateLayoutParams()
方法 -
紧盯着主线流程
-
先看某个方法的作用,在看细节
-
熟能生巧,多看,多分析,多画流程图
-
加注释!
-
可以尝试打断点
CoordinatorLayout采取的策略就是跟随 view 的生命周期开始看,首先从构造聊起 !
构造方法
# CoordinatorLayout.java
public CoordinatorLayout(@NonNull Context context)
this(context, null);
public CoordinatorLayout(@NonNull Context context, @Nullable AttributeSet attrs)
this(context, attrs, R.attr.coordinatorLayoutStyle);
public CoordinatorLayout(@NonNull Context context, @Nullable AttributeSet attrs,
@AttrRes int defStyleAttr)
// 监听 ViewGroup 的变化
super.setOnHierarchyChangeListener(new HierarchyChangeListener());
...
# CoordinatorLayout.java
private class HierarchyChangeListener implements OnHierarchyChangeListener
@Override
public void onChildViewAdded(View parent, View child)
...
@Override
public void onChildViewRemoved(View parent, View child)
// 重点 当 childView 被删除的时候调用
onChildViewsChanged(EVENT_VIEW_REMOVED);
..
核心方法,下面源码也会用到 !!
# CoordinatorLayout.java
final void onChildViewsChanged(@DispatchChangeEvent final int type)
// 当前的 childView
final int childCount = mDependencySortedChildren.size();
// 循环所有 childView
// 为了找到需要依赖的 view
for (int i = 0; i < childCount; i++)
final View child = mDependencySortedChildren.get(i);
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
// 如果 当前的状态 = EVENT_PRE_DRAW 并且 childView 不可见
// 那么退出本次循环
if (type == EVENT_PRE_DRAW && child.getVisibility() == View.GONE)
continue;
...
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();
// 如果 childView 有依赖的Behavior 并且 Behavior#layoutDependsOn() = true 就继续执行
if (b != null && b.layoutDependsOn(this, checkChild, child)Android自助餐之Jni进阶
我的渲染技术进阶之旅如何在Windows系统编译Filament的android版本程序?
我的渲染技术进阶之旅如何在Windows系统编译Filament的android版本程序?