View view = getWindow().getDecorView();
setContentView
就是设置在这里,也就是在FrameLayout
上。DecorView是Android视图树的根节点,那么ViewRoot又是什么呢?
ViewRoot的定义是 连接器
,对应于ViewRootImpl
类。
它的作用在于:
ViewRoot负责与WMS交互通信,调整窗口大小以及布局。同时向DecorView派发输入事件,完成三大绘制流程measure、layout、draw。
action_mode_bar(状态栏)、titlebar(标题栏)、content(内容)以及最底部的导航栏
View是UI界面的基本构建块,它占据了一块矩形区域,负责绘图和事件处理。View同时也是android上其它UI控件的基类,可以用来创建其它交互式的UI组件(比如Button, TextView, 等等)。View的子类 ViewGroup 则是各自layout布局的基类,ViewGroup是一个不可见的容器,用于容纳其它的View(或其它的ViewGroup),而且还定义了相关的布局属性。
在屏幕上渲染的View必须经历这些生命周期方法才能正确地在屏幕上绘制。
View的生命周期主要由三部分组成:
之所以把此阶段称为“遍历”,是因为View的视图层次就像是从父节点(ViewGroup)到子节点的树状结构。因此,每个方法都是从父节点开始,一直遍历执行到最后一个节点:
每个节点的绘制流程又可以分为3个阶段:measure、layout、draw。
在onMeasure方法中View会对其所有的子元素执行measure过程,此时measure过程就从父容器“传递”到了子元素中,接着子元素会递归的对其子元素进行measure过程,如此反复完成对整个View树的遍历
onLayout与onDraw过程的执行流程与此类似。
onSaveInstanceState():
当Activity调用了onSaveInstanceState()方法后,便会对它的View Tree进行保存,而进一步对每一个子View调用其onSaveInstanceState()方法来保存状态。onSaveInstanceState()方法返回Parcelable对象,也即是序列化对象,是Android提供的一种序列化方式。
如果是保存自定义view的状态:
@Override
protected Parcelable onSaveInstanceState() {
Parcelable parcelable = super.onSaveInstanceState();
SavedState ss = new SavedState(parcelable);
//把当前的位置保存进SavedState
ss.currentPosition = mCurrentPosition;
return ss;
}
static class SavedState extends BaseSavedState{
//当前的ViewPager的位置
int currentPosition;
public SavedState(Parcel source) {
super(source);
currentPosition = source.readInt();
}
public SavedState(Parcelable superState) {
super(superState);
}
@Override
public void writeToParcel(Parcel out, int flags) {
super.writeToParcel(out, flags);
out.writeInt(currentPosition);
}
public static final Parcelable.Creator<SavedState> CREATOR = new Parcelable.Creator<SavedState>(){
@Override
public SavedState createFromParcel(Parcel source) {
return new SavedState(source);
}
@Override
public SavedState[] newArray(int size) {
return new SavedState[size];
}
};
}
状态恢复:
onRestoreInstanceState(Parcelable)方法内,根据传递进来的Parcelable参数,我们可以拿到我们之前保存的数据,再根据需要进行赋值或者调用某些方法来恢复状态就行了。
@Override
protected void onRestoreInstanceState(Parcelable state) {
SavedState ss = (SavedState) state;
super.onRestoreInstanceState(ss.getSuperState());
//调用别的方法,把保存的数据重新赋值给当前的自定义View
mViewPager.setCurrentItem(ss.currentPosition);
}
如果view的内容发生变化,会调用invalidate()
,返回到生命周期中的dispatchToDraw(),重新绘制。
如果view的边界发生变化,则会调用requestLayout()
,返回到测量步骤,重新执行测量、布局和绘制。
自定义一个类继承View,放到activity的layout中,打印log观察各个函数调用情况:
Activity onCreate
View LifeView(Context context, @Nullable AttributeSet attrs)
View onFinishInflate
Activity onStart
Activity onResume
View onAttachedToWindow
View onMeasure
View onSizeChanged
View onLayout
View onDraw
View onWindowFocusChanged true
View onMeasure
View onLayout
View onDraw
Activity onPause
View onWindowFocusChanged false
Activity onStop
Activity onDestroy
可以看出,在activity可见(onResume)后,view与window关联起来。 先测量,决定自身大小,决定在父容器中的位置和大小,然后绘制到屏幕上。根据上面的log,我们也可以看出View的主要绘制流程是measure、layout、draw。
在Activity的onCreate()中会执行setContent()
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
setContentView()指定一个布局文件,表示要在Activity上展示这个布局。 该方法有两个主要作用:
也就是说当Activity 处在"Create"状态时,整个ViewTree已经被创建了。
但是需等到onResume()的时候,view才依附到window,onAttachedToWindow
,然后开始绘制流程:measure、layout、draw。
View可能会有一个与之关联的数字id。通常来说这些是在layout xml文件中分配的id。view的ID并不需要全局独一无二的,而是要在它所属的树里是唯一的。
Android坐标系的定义为:
一个view占据一个方形的位置。view的位置用左上角的点来表示。位置和尺寸的单位是像素(pixel)。
view的位置由四个顶点决定:
视图的位置相对于父控件而言,四个顶点的位置描述分别由四个与父控件相关的值决定:
调用getLeft()和getTop()可以获得view的坐标。getLeft()返回view的left值,或者说是x值。getTop()返回top值,或者说是y值。这些方法返回的坐标是view在它的父view中的位置。例如,假设getLeft()返回20,表示这个view在它父view左边缘往右20个像素的位置。
另外,getRight()方法能返回view的right值。getBottom()方法返回bottom值。 getRight() == getLeft() + getWidth()
//get() :触摸点相对于其所在组件坐标系的坐标
event.getX();
event.getY();
//getRaw() :触摸点相对于屏幕默认坐标系的坐标
event.getRawX();
event.getRawY();
自定义view中常常是将一些简单的形状通过计算,从而组合到一起形成的效果。
这会涉及到画布的相关操作(旋转)、正余弦函数计算等,即会涉及到角度(angle)与弧度(radian)的相关知识。
view的尺寸(size)有宽(width)和高(height)。一个view实际上有两对宽高值。
第一对宽高是测量宽高(measured width/height)。这个尺寸==表示一个view想要在父view里要多大。==通过getMeasuredWidth() 和 getMeasuredHeight()方法可以得到测量宽高。
第二对宽高可以理解为实际宽高。这个宽高有可能与测量宽高不同。通过getWidth()和getHeight()可拿到宽高值。
为了测量尺寸,view需要考虑padding值。padding值的单位是像素(px),分为左上右下(left,top,right,bottom)。 padding可用于将view的内容偏移特定数量的像素。 例如,左padding为2像素的时候,会把view的内容从左向右推2个像素。 可以用setPadding(int, int, int, int)或者setPaddingRelative(int, int, int, int)方法设置padding值。 用getPaddingLeft(), getPaddingTop(), getPaddingRight(), getPaddingBottom(), getPaddingStart(), getPaddingEnd()获取对应的padding值。
view可以设定padding值,但没有margin值。ViewGroup能支持margin值。
事件分发的本质是将点击事件(motionEvent)
传递到某个具体的View
处理的整个过程。
Android的UI界面由Activity、ViewGroup、View及其派生类组成,事件就是在Activity、ViewGroup以及View之间传递。
事件分发的顺序:Activity -> ViewGroup -> View。即:1个点击事件发生后,事件先传到Activity、再传到ViewGroup、最终再传到 View。
事件分发过程由哪些方法协作完成?
/**
* 源码分析:Activity.dispatchTouchEvent()
*/
public boolean dispatchTouchEvent(MotionEvent ev) {
// 仅贴出核心代码
// ->>分析1
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
// 若getWindow().superDispatchTouchEvent(ev)的返回true
// 则Activity.dispatchTouchEvent()就返回true,则方法结束。即 :该点击事件停止往下传递 & 事件传递过程结束
// 否则:继续往下调用Activity.onTouchEvent
}
// ->>分析3
return onTouchEvent(ev);
}
/**
* 分析1:getWindow().superDispatchTouchEvent(ev)
* 说明:
* a. getWindow() = 获取Window类的对象
* b. Window类是抽象类,其唯一实现类 = PhoneWindow类
* c. Window类的superDispatchTouchEvent() = 1个抽象方法,由子类PhoneWindow类实现
*/
@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
return mDecor.superDispatchTouchEvent(event);
// mDecor = 顶层View(DecorView)的实例对象
// ->> 分析2
}
/**
* 分析2:mDecor.superDispatchTouchEvent(event)
* 定义:属于顶层View(DecorView)
* 说明:
* a. DecorView类是PhoneWindow类的一个内部类
* b. DecorView继承自FrameLayout,是所有界面的父类
* c. FrameLayout是ViewGroup的子类,故DecorView的间接父类 = ViewGroup
*/
public boolean superDispatchTouchEvent(MotionEvent event) {
return super.dispatchTouchEvent(event);
// 调用父类的方法 = ViewGroup的dispatchTouchEvent()
// 即将事件传递到ViewGroup去处理,详细请看后续章节分析的ViewGroup的事件分发机制
}
// 回到最初的分析2入口处
/**
* 分析3:Activity.onTouchEvent()
* 调用场景:当一个点击事件未被Activity下任何一个View接收/处理时,就会调用该方法
*/
public boolean onTouchEvent(MotionEvent event) {
// ->> 分析5
if (mWindow.shouldCloseOnTouch(this, event)) {
finish();
return true;
}
return false;
// 即 只有在点击事件在Window边界外才会返回true,一般情况都返回false,分析完毕
}
/**
* 分析4:mWindow.shouldCloseOnTouch(this, event)
* 作用:主要是对于处理边界外点击事件的判断:是否是DOWN事件,event的坐标是否在边界内等
*/
public boolean shouldCloseOnTouch(Context context, MotionEvent event) {
if (mCloseOnTouchOutside && event.getAction() == MotionEvent.ACTION_DOWN
&& isOutOfBounds(context, event) && peekDecorView() != null) {
// 返回true:说明事件在边界外,即 消费事件
return true;
}
// 返回false:在边界内,即未消费(默认)
return false;
}
当一个点击事件发生时,从Activity的事件分发开始,流程如下:
phonewindow
,前面已经介绍过每个activity都对应一个phonewindow
,这个phonewindow
是activity
与view体系
交互的接口层对象。phonewindow
会将点击事件分发到view体系
的顶层对象DecorView
DecorView
会继续将事件传递给自己的父类分发事件。 DecorView
本质上是一个帧布局framelayout
,而所有的layout的基类都是ViewGroup
,至此实现了事件从Activity
分发到ViewGroup
。ViewGroup
的事件分发结果返回为true(点击事件被viewgroup消费了),则直接结束。否则会执行Activity.onTouchEvent()
。Activity.onTouchEvent()
,该函数内部会执行shouldCloseOnTouch
,判断点击事件是否在Window边界外,如果在边界外,事件算被Activity消费,返回true。否则返回false,不算被消费,但依旧结束事件分发。前已述及,Activity将事件分发给ViewGroup后,如果ViewGroup没有消费事件,才会轮到Activity执行onTouchEvent(),处理事件。那么ViewGroup是如何分发事件的呢?
/**
* 源码分析:ViewGroup.dispatchTouchEvent()
*/
public boolean dispatchTouchEvent(MotionEvent ev) {
// 仅贴出关键代码
...
if (disallowIntercept || !onInterceptTouchEvent(ev)) {
// 分析1:ViewGroup每次事件分发时,都需调用onInterceptTouchEvent()询问是否拦截事件
// 判断值1-disallowIntercept:是否禁用事件拦截的功能(默认是false),可通过调用requestDisallowInterceptTouchEvent()修改
// 判断值2-!onInterceptTouchEvent(ev) :对onInterceptTouchEvent()返回值取反
// a. 若在onInterceptTouchEvent()中返回false,即不拦截事件,从而进入到条件判断的内部
// b. 若在onInterceptTouchEvent()中返回true,即拦截事件,从而跳出了该条件判断
// c. 关于onInterceptTouchEvent() ->>分析1
// 分析2
// 1. 通过for循环,遍历当前ViewGroup下的所有子View
for (int i = count - 1; i >= 0; i--) {
final View child = children[i];
if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE
|| child.getAnimation() != null) {
child.getHitRect(frame);
// 2. 判断当前遍历的View是不是正在点击的View,从而找到当前被点击的View
if (frame.contains(scrolledXInt, scrolledYInt)) {
final float xc = scrolledXFloat - child.mLeft;
final float yc = scrolledYFloat - child.mTop;
ev.setLocation(xc, yc);
child.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
// 3. 条件判断的内部调用了该View的dispatchTouchEvent()
// 即 实现了点击事件从ViewGroup到子View的传递(具体请看下面章节介绍的View事件分发机制)
if (child.dispatchTouchEvent(ev)) {
// 调用子View的dispatchTouchEvent后是有返回值的
// 若该控件可点击,那么点击时dispatchTouchEvent的返回值必定是true,因此会导致条件判断成立
// 于是给ViewGroup的dispatchTouchEvent()直接返回了true,即直接跳出
// 即该子View把ViewGroup的点击事件消费掉了
mMotionTarget = child;
return true;
}
}
}
}
}
}
...
return super.dispatchTouchEvent(ev);
// 若无任何View接收事件(如点击空白处)/ViewGroup本身拦截了事件(复写了onInterceptTouchEvent()返回true)
// 会调用ViewGroup父类的dispatchTouchEvent(),即View.dispatchTouchEvent()
// 因此会执行ViewGroup的onTouch() -> onTouchEvent() -> performClick() -> onClick(),即自己处理该事件,事件不会往下传递
// 具体请参考View事件分发机制中的View.dispatchTouchEvent()
...
}
/**
* 分析1:ViewGroup.onInterceptTouchEvent()
* 作用:是否拦截事件
* 说明:
* a. 返回false:不拦截(默认)
* b. 返回true:拦截,即事件停止往下传递(需手动复写onInterceptTouchEvent()其返回true)
*/
public boolean onInterceptTouchEvent(MotionEvent ev) {
// 默认不拦截
return false;
}
// 回到调用原处
Activity
传递到ViewGroup
时,首先会决定是否拦截事件。执行onInterceptTouchEvent()
。(注:只有viewgroup有拦截事件的权利)View.dispatchTouchEvent()
,因为ViewGroup
的父类为View
。相当于ViewGroup
自己消费该事件,调用自身的onTouch() -> onTouchEvent() -> performClick() -> onClick
,这个过程与下边要讲的view的事件分发处理是一致的。dispatchTouchEvent()
。至此,事件由ViewGroup
传递到了View
。ViewGroup
父类的事件分发函数View.dispatchTouchEvent()
.ViewGroup
调用父类的dispatchTouchEvent()与找到ViewGroup
的被点击的子View
,调用该子view的dispatchTouchEvent(),都涉及到View
的事件分发处理,从dispatchTouchEvent()函数开始。
/**
* 源码分析:View.dispatchTouchEvent()
*/
public boolean dispatchTouchEvent(MotionEvent event) {
if ( (mViewFlags & ENABLED_MASK) == ENABLED &&
mOnTouchListener != null &&
mOnTouchListener.onTouch(this, event)) {
return true;
}
return onTouchEvent(event);
}
// 说明:只有以下3个条件都为真,dispatchTouchEvent()才返回true;否则执行onTouchEvent()
// 1. (mViewFlags & ENABLED_MASK) == ENABLED
// 2. mOnTouchListener != null
// 3. mOnTouchListener.onTouch(this, event)
// 下面对这3个条件逐个分析
/**
* 条件1:(mViewFlags & ENABLED_MASK) == ENABLED
* 说明:
* 1. 该条件是判断当前点击的控件是否enable
* 2. 由于很多View默认enable,故该条件恒定为true(除非手动设置为false)
*/
/**
* 条件2:mOnTouchListener != null
* 说明:
* 1. mOnTouchListener变量在View.setOnTouchListener()里赋值
* 2. 即只要给控件注册了Touch事件,mOnTouchListener就一定被赋值(即不为空)
*/
public void setOnTouchListener(OnTouchListener l) {
mOnTouchListener = l;
}
/**
* 条件3:mOnTouchListener.onTouch(this, event)
* 说明:
* 1. 即回调控件注册Touch事件时的onTouch();
* 2. 需手动复写设置,具体如下(以按钮Button为例)
*/
button.setOnTouchListener(new OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
return false;
// 若在onTouch()返回true,就会让上述三个条件全部成立,从而使得View.dispatchTouchEvent()直接返回true,事件分发结束
// 若在onTouch()返回false,就会使得上述三个条件不全部成立,从而使得View.dispatchTouchEvent()中跳出If,执行onTouchEvent(event)
// onTouchEvent()源码分析 -> 分析1
}
});
/**
* 分析1:onTouchEvent()
*/
public boolean onTouchEvent(MotionEvent event) {
... // 仅展示关键代码
// 若该控件可点击,则进入switch判断中
if (((viewFlags & CLICKABLE) == CLICKABLE || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {
// 根据当前事件类型进行判断处理
switch (event.getAction()) {
// a. 事件类型=抬起View(主要分析)
case MotionEvent.ACTION_UP:
performClick();
// ->>分析2
break;
// b. 事件类型=按下View
case MotionEvent.ACTION_DOWN:
postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
break;
// c. 事件类型=结束事件
case MotionEvent.ACTION_CANCEL:
refreshDrawableState();
removeTapCallback();
break;
// d. 事件类型=滑动View
case MotionEvent.ACTION_MOVE:
final int x = (int) event.getX();
final int y = (int) event.getY();
int slop = mTouchSlop;
if ((x < 0 - slop) || (x >= getWidth() + slop) ||
(y < 0 - slop) || (y >= getHeight() + slop)) {
removeTapCallback();
if ((mPrivateFlags & PRESSED) != 0) {
removeLongPressCallback();
mPrivateFlags &= ~PRESSED;
refreshDrawableState();
}
}
break;
}
// 若该控件可点击,就一定返回true
return true;
}
// 若该控件不可点击,就一定返回false
return false;
}
/**
* 分析2:performClick()
*/
public boolean performClick() {
if (mOnClickListener != null) {
// 只要通过setOnClickListener()为控件View注册1个点击事件
// 那么就会给mOnClickListener变量赋值(即不为空)
// 则会往下回调onClick() & performClick()返回true
playSoundEffect(SoundEffectConstants.CLICK);
mOnClickListener.onClick(this);
return true;
}
return false;
}
onTouch()
,onTouch()
函数处理后,如果返回true,表示事件被消费了,不再向下传递,结束。onTouchEvent()
处理事件,在点击抬起时,执行performClick()
。onClick()
,如果没有注册,直接结束。window
对象,执行它的事件分发,window会将事件传递给DecorWindow
(视图树根),DecorWindow执行它父类的事件分发,DecorView本质上是一个FrameLayout
,而所有的Layout
布局的基类都是ViewGroup
,至此事件分发由Activity
传递到ViewGroup
.onTouchEvent()
。ViewGroup
传递到了View
。如果没有找到被点击的子view,即点到空白处,那么与被拦截的结果一样,ViewGrop自己消费事件。onTouch()
,监听回调如果返回true,表示事件被消费,直接结束。如果返回false,则事件未被消费,与没有注册Touch监听的效果一致,由View自身的onTouchEvent()
消费事件。在抬起时, onTouchEvent()会执行performClick(),判断是否注册Click监听,如果有,执行onClick(),如果没有直接结束。