- 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 消耗了这个事件,就算消耗了这个事件。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论