- CompoundButton 源码分析
- LinearLayout 源码分析
- SearchView 源码解析
- LruCache 源码解析
- ViewDragHelper 源码解析
- BottomSheets 源码解析
- Media Player 源码分析
- NavigationView 源码解析
- Service 源码解析
- Binder 源码分析
- Android 应用 Preference 相关及源码浅析 SharePreferences 篇
- ScrollView 源码解析
- Handler 源码解析
- NestedScrollView 源码解析
- SQLiteOpenHelper/SQLiteDatabase/Cursor 源码解析
- Bundle 源码解析
- LocalBroadcastManager 源码解析
- Toast 源码解析
- TextInputLayout
- LayoutInflater 和 LayoutInflaterCompat 源码解析
- TextView 源码解析
- NestedScrolling 事件机制源码解析
- ViewGroup 源码解析
- StaticLayout 源码分析
- AtomicFile 源码解析
- AtomicFile 源码解析
- Spannable 源码分析
- Notification 之 Android 5.0 实现原理
- CoordinatorLayout 源码分析
- Scroller 源码解析
- SwipeRefreshLayout 源码分析
- FloatingActionButton 源码解析
- AsyncTask 源码分析
- TabLayout 源码解析
1.3 Behavior
在 CoordinatorLayout 中定义了 Behavior 类,它是用来辅助 layout 的工具。如果一个 CoordinatorLayout 的直接子 View 设置了 Behavior (或者通过类注解 @DefaultBehavior 指定 Behavior ),则该 Behavior 会储存在该 View 的 LayoutParam 中。
注意:不是 CoordinatorLayout 的直接子 View,设置 Behavior 是无效的。你可以看到任何一处对于 Behavior 的处理都是直接 getChildCount() 遍历。
在 Behavior 中有几类功能,我们一一进行介绍:
1.3.1 触摸响应类
Behavior 中有两个函数: onInterceptTouchEvent 、 onTouchEvent 。在 CoordinatorLayout 每次触发对应事件的时候会选择一个最适合的子 View 的 Behavior 执行对应函数。我们来看一下 CoordinatorLayout 是怎么分发和处理 Touch 事件的:
intercept
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
MotionEvent cancelEvent = null;
final int action = MotionEventCompat.getActionMasked(ev);
// 重置响应的 Behavoir
if (action == MotionEvent.ACTION_DOWN) {
resetTouchBehaviors();
}
// 在这里选择一个最佳 Behavior 进行处理
final boolean intercepted = performIntercept(ev, TYPE_ON_INTERCEPT);
if (cancelEvent != null) {
cancelEvent.recycle();
}
// 重置响应的 Behavior
if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
resetTouchBehaviors();
}
return intercepted;
}在 performIntercept 去选择一个最适合的 Behavior 来进行处理,这个方法不仅用于 onInterceptTouchEvent ,并且也用于 onTouchEvent ,根据传入 type 不同来识别对应方法。我们来看看它的逻辑:
private boolean performIntercept(MotionEvent ev, final int type) {
boolean intercepted = false;
boolean newBlock = false;
MotionEvent cancelEvent = null;
final int action = MotionEventCompat.getActionMasked(ev);
final List<View> topmostChildList = mTempList1;
// API>=21 时,使用 elevation 由低到高排列 View;API<21 时,按 View 添加顺序排列
getTopSortedChildren(topmostChildList);
final int childCount = topmostChildList.size();
for (int i = 0; i < childCount; i) {
final View child = topmostChildList.get(i);
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
final Behavior b = lp.getBehavior();
// ...(省略代码) 如果此次判定 intercept,则对上次的 Behavior 发送 CANCEL 事件。
// 根据传入 type 不同调用不同的方法
if (!intercepted && b != null) {
switch (type) {
case TYPE_ON_INTERCEPT:
intercepted = b.onInterceptTouchEvent(this, child, ev);
break;
case TYPE_ON_TOUCH:
intercepted = b.onTouchEvent(this, child, ev);
break;
}
if (intercepted) {
mBehaviorTouchView = child;
}
}
//...(省略代码) 如果 Behavior.blocksInteractionBelow() 返回 true,则不处理后续的事件。
}
topmostChildList.clear();
return intercepted;
}1.3.2 依赖关系类
这部分比较简单,就俩函数: layoutDependsOn :返回 true 则表示对另一个 View 有依赖关系; onDependentViewChanged & onDependentViewRemoved :如果被依赖的 View 在正常 layout 之后仍有 size/position 上的变化,或者被 remove 掉,都会触发对应方法。
那么问题来了,CoordinatorLayout 是怎么监听这个被依赖的 View 改变的事件的呢?
原来它里面有一个 ViewTreeObserver.OnPreDrawListener ,它在 onMeasure 的时候被添加到了 ViewTreeObserver 中,这样每一帧被绘制出来之前都会调用这个回调。
class OnPreDrawListener implements ViewTreeObserver.OnPreDrawListener {
@Override
public boolean onPreDraw() {
dispatchOnDependentViewChanged(false);
return true;
}
}这个 dispatchOnDependentViewChanged 里面代码比较多,就不放上来了,总结下来就是这样:
根据依赖关系遍历子 View,对每一个 View 做如下操作
- 判断一下新的布局边界与 lastChildRect 是否相同,是则记录新的布局边界为 lastChildRect,并继续后续流程,否则跳过;
- 对于之后每一个 View,如果它依赖于本 View,则调用它的
Behavior.onDependentViewChanged(如果有 Behavior 的话)。
至于 onDependentViewRemoved ,是在初始化的时候就会调用 ViewGroup.setOnHierarchyChangeListener() 方法设置一个 OnHierarchyChangeListener ,这样每次 add 和 remove 子 View 的时候就会接收到回调,同时对相应依赖关系的 View 进行处理。
1.3.3 布局类
onMeasureChild & onLayoutChild :如果重写了该方法并返回 true ,则 CoordinatorLayout 会使用 Behavior 对这个子 View 进行 measure/layout。具体的可以见下面的 Measure&Layout
1.3.4 嵌套滑动类
CoordinatorLayout 实现了 NestedScrollingParent ,当 CoordinatorLayout 内有一个支持 NestedScroll 的子 View 时,它的嵌套滑动事件通过 NestedScrollingParent 的回调分发到各直接子 View 的 Behavior 处理。虽然 Behavior 类没有实现 NestedScrollingParent ,但是实际上它的方法都有。有兴趣的同学可以去看看这个类,我们这里重点讲 CoordinatorLayout 的分发过程。
各个事件的分发过程类似,此处就举一个例子:
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);
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,每个都调一下对应的回调方法,只要有任何一个子 View 的 behavior 消耗了这个事件,就算消耗了这个事件。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论