1.事件分发
优先级:onTouch > onTouchEvent > OnLongClickListener>OnClickListener
public boolean dispatchTouchEvent(MotionEvent event) {
//先调用onTouch
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
result = true;
}
// 再调用onTouchEvent
if (!result && onTouchEvent(event)) {
result = true;
}
}
public boolean onTouchEvent(MotionEvent event) {
switch(action){
case MotionEvent.ACTION_UP:
// Only perform take click actions if we were in the pressed state
if (!focusTaken) {
// Use a Runnable and post this rather than calling
// performClick directly. This lets other visual state
// of the view update before click actions start.
if (mPerformClick == null) {
//处理点击事件
mPerformClick = new PerformClick();
}
if (!post(mPerformClick)) {
//调用onClick方法
performClickInternal();
}
}
case MotionEvent.ACTION_DOWN:
if (!clickable) {
//处理长按事件
checkForLongClick(0, x, y);
break;
}
case MotionEvent.ACTION_CANCEL:
if (clickable) {
setPressed(false);
}
removeTapCallback();
removeLongPressCallback();
mInContextButtonPress = false;
mHasPerformedLongPress = false;
mIgnoreNextUpEvent = false;
mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
break;
case MotionEvent.ACTION_MOVE:
if (clickable) {
drawableHotspotChanged(x, y);
}
// Be lenient about moving outside of buttons
if (!pointInView(x, y, mTouchSlop)) {
// Outside button
// Remove any future long press/tap checks
removeTapCallback();
removeLongPressCallback();
if ((mPrivateFlags & PFLAG_PRESSED) != 0) {
setPressed(false);
}
mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
}
break;
}
}
ACTION_CANCEL 触发时机:
子控件接收到down或move后,被父控件在 onInterceptTouchEvent 根据move位置进行拦截,将事件转换成cancel传给子控件 ,以至于子控件接受不到后续Up事件.
if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {
event.setAction(MotionEvent.ACTION_CANCEL);
if (child == null) {
handled = super.dispatchTouchEvent(event);
} else {
handled = child.dispatchTouchEvent(event);
}
event.setAction(oldAction);
return handled;
}
不过子View可以通过设置requestDisallowInterceptTouchEvent(true)来达到禁止父ViewGroup拦截事件的目的
- post方法为什么绘制后执行
public boolean post(Runnable action) {
final AttachInfo attachInfo = mAttachInfo;
if (attachInfo != null) {
return attachInfo.mHandler.post(action);
}
// Postpone the runnable until we know on which thread it needs to run.
// Assume that the runnable will be successfully placed after attach.
getRunQueue().post(action);
return true;
}
/**
* Returns the queue of runnable for this view.
*
* @return the queue of runnables for this view
*/
private HandlerActionQueue getRunQueue() {
if (mRunQueue == null) {
mRunQueue = new HandlerActionQueue();
}
return mRunQueue;
}
//dispatchAttachedToWindow中调用mRunQueue执行之前缓存的消息
void dispatchAttachedToWindow(AttachInfo info, int visibility) {
mAttachInfo = info;
....
// Transfer all pending runnables.
if (mRunQueue != null) {
mRunQueue.executeActions(info.mHandler);
mRunQueue = null;
}
}
//dispatchAttachedToWindow调用时机
//ViewGroup.java
// Android 26.0
public void addView(View child, int index, LayoutParams params) {
...
// addViewInner() will call child.requestLayout() when setting the new LayoutParams
// therefore, we call requestLayout() on ourselves before, so that the child's request
// will be blocked at our level
requestLayout();
invalidate(true);
addViewInner(child, index, params, false);
}
private void addViewInner(View child, int index, LayoutParams params,
boolean preventRequestLayout) {
....
child.dispatchAttachedToWindow(mAttachInfo, (mViewFlags&VISIBILITY_MASK));
}
3.View的三个构造函数调用时机:
public RelativeLayout(Context context) {
this(context, null);
}
public RelativeLayout(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public RelativeLayout(Context context, AttributeSet attrs, int defStyleAttr) {
this(context, attrs, defStyleAttr, 0);
}
代码动态创建对象调用函数1,xml中创建对象调用函数2;第三个函数系统是不调用的,要由View(我们自定义的或系统预定义的View)显式调用;
- MeasureSpec : MeasureSpec.getMode+MeasureSpec.getSize
如果不考虑父 view 的影响,测试结果如下:
- wrap_parent -> MeasureSpec.AT_MOST
- match_parent -> MeasureSpec.EXACTLY
- 具体值 -> MeasureSpec.EXACTLY
实际情况:
- childMeasureSpec = parentMeasureSpec + lp.height
MeasureSpec.UNSPECIFIED:ScrollView的子View ,不受父View限制。