ThinkChat2.0新版上线,更智能更精彩,支持会话、画图、阅读、搜索等,送10W Token,即刻开启你的AI之旅 广告
[TOC] # View的绘制流程 ## View的测量 1、不管是View还是ViewGroup,每个人都会有一个父布局给过来的MeasureSpec,用来设置自己的宽高 * 测量模式为AT\_MOST或EXACTLY时,测量宽高值为MeasureSpec中的specSize * 测量模式为UNSPECIFIED时,测量宽高值为默认值 2、View在onMeasure方法中,根据从父布局ViewGroup那里得到的MeasureSpec,来设置自身的宽高。 3、同样,ViewGroup也是在onMeasure方法中设置自己的宽高。 4、ViewGroup在onMeasure方法中设置自己宽高之前会多做一步,循环为所有子View设置MeasureSpec,并调用每个子View的measure方法,让子View测量自己的宽高。 5、ViewGroup根据自己的MeasureSpec、padding以及子View的LayoutParams,为子View设置MeasureSpec。 * 子View设置为固定宽高时,子View的specMode为:Exactly,specSize为:LayoutParams中设置的值,子View的测量宽高为LayoutParams中设置的值。 * 子View设置为match\_parent时,子View的specMode为:等同ViewGroup的specMode,specSize为:ViewGroup的剩余空间,子View的测量宽高为ViewGroup的剩余空间。 * 子View设置为wrap\_content时,子View的specMode为:AT\_MOST,specSize为:ViewGroup的剩余空间,子View的测量宽高为ViewGroup的剩余空间。 6、自定义ViewGroup通常会重写onMeasure方法,在onMeasure方法中为所有子View生成MeasureSpec并测量子View宽高。 ## View的布局 1、View的layout方法中,调用setFrame方法为View自己指定位置,调用onLayout方法为所有的childView指定位置 2、setFrame方法为mLeft、mTop、mRight、mBottom几个属性赋值,来确定View自己相对于父布局的位置 3、ViewGroup的onLayout方法是一个抽象方法,子类需要实现此方法,并在实现中根据ViewGroup的布局逻辑计算出每个childView的位置,然后调用childView的layout方法进行子View布局 ## View的绘制 1、View的draw方法按流程分别绘制背景、content、childView、前景、默认焦点等。其中onDraw方法绘制View的内容。 2、如果是ViewGroup,会调用dispatchDraw方法绘制childView。 3、ViewGroup的dispatchDraw方法,会调用所有的childView的draw方法进行绘制子View。 4、可通过重写View的onDraw方法,拿到Canvas来绘制想要绘制的内容 ## 附 ViewGroup为childView生成MeasureSpec的规则: * View配置固定宽高时,View的SpecMode总是EXCATLY模式,SpecSize总是LayoutParams中设置的值 * View配置match\_parent时,ViewGroup是什么模式,View就是什么模式,SpecSize是ViewGroup剩余的空间 * View配置wrap\_content时,View的SpecMode总是AT_MOST模式,SpecSize总是ViewGroup的剩余空间 注意: 当View配置wrap\_content时,View的SpecMode总是为AT_MOST,SpecSize总是为ViewGroup的剩余空间。View在根据MeasureSpec设置测量宽高时,就会设置成ViewGroup的剩余空间,与期望的wrap\_content不符。 解决方案为自定义View时重写onMeasure方法,在View的SpecMode为AT_MOST时,为View指定一个宽高。ImageView、TextView等都是如此操作,可参考其源码。 # 触摸事件分发机制 ## Activity的分发 1、事件先分发给PhoneWindow,PhoneWindow不消费则传给Activity的onTouchEvent方法。 2、PhoneWindow传给DecorView,DecorView传给根布局ViewGroup。 ## ViewGroup的分发 1、首先调用onInterCeptTouchEvent方法,判断ViewGroup自己是否需要,需要则拦截,拦截后就不会传递给childView。 2、拦截后,调用ViewGroup自己的onTouchEvent方法。 3、不拦截则传递给childView,按视图层次结构依次传递下去。 4、最终的那个childView也不需要时,事件会进行回传,依次调用前面每一层的onTouchEvent方法。 ## View的分发 1、View会依次调用onTouchListener、onTouchEvent、onLongClickListener、onClickListener。 2、在onTouchListener、onTouchEvent中可决定是否消费事件,不消费,事件则开始回传。 3、注册了onLongClickListener、onClickListener及设置了Clickable等,View就会消费事件。 ## 伪代码 1、ViewGroup分发的伪代码 ```java // 返回值代表是否将事件消费掉 public boolean dispatchTouchEvent(MotionEvent event) { // 默认状态为未消费过 boolean result = false; // 如果没有拦截(ViewGroup一般不会进行拦截,可点击的ViewGroup除外) if (!onInterceptTouchEvent(event)) { // 则交给childView result = child.dispatchTouchEvent(event); } // 如果事件没有被childView消费 if (!result) { // 则调用自身 onTouchEvent() result = onTouchEvent(event); } // 返回事件消费状态 return result; } ``` 2、View分发的伪代码 ```java // 返回值代表是否将事件消费掉 public boolean dispatchTouchEvent(MotionEvent event) { if(mOnTouchListener.onTouch(this, event)) { return true; } else if (onTouchEvent(event)) { return true; } return false; } ``` ## 注意事项 * 拦截事件指事件不再往下传递,使用事件指从事件中获取想要的信息,消费事件指把事件吃了 * 只要给 View 注册了 onClickListener、onLongClickListener、OnContextClickListener 其中的任何一个监听器或者设置了 android:clickable=”true” 就代表这个 View 是可点击的,可点击的 View 就会消费事件 * ViewGroup 和 ChildView 同时注册了点击事件监听器时,事件优先给 ChildView 消费 * 同一次点击事件只能被一个 View 消费,防止事件混乱 # 绘制原理 1、LayoutInflater将布局中的xml标签转化为对象,CPU经过measure、layout、draw将他们转化为多边形(Polygons)或纹理(Texture),GPU对多边形或纹理进行栅格化操作,栅格化后的数据写入帧缓冲区中等待显示器显示。 2、CPU和GPU通过图形驱动层连接。 3、GPU负责绘制帧,屏幕负责逐行扫描显示帧,两者速度不一定保持一致。 4、因此引入垂直同步机制,由系统每隔16ms发出一次VSync信号后,CPU进行计算,GPU进行绘制,以此来强制GPU的刷新率和屏幕刷新率同步。 5、理想状态下屏幕每秒展示60帧时人眼感受不到卡顿,动画会比较流畅,因此为保持应用流畅,我们需要在16ms的时间内完成绘制,尽量减少measure、layout、draw的时间。有以下解决方案: * 精简布局层级 * 使用\<include>标签重用布局,使用\<merge>标签合并布局 * 使用ViewStub仅在需要时加载 * 删除无用的控件和布局 * 使用性能较好的ViewGroup,如Constraintlayout等 * onDraw方法中不要创建局部对象,会占用过多内存导致频繁gc * onDraw方法中不要执行耗时操作及循环操作 # 过度绘制 1、过度绘制指:屏幕上的某个像素在同一帧的时间内被绘制了多次。会浪费CPU和GPU资源。 2、开发者工具可提高可视化过度绘制情况:真彩色-没有过度绘制;蓝色-过度绘制1次;绿色-过度绘制2次;粉色-过度绘制3次;红色-过度绘制4次或更多 3、过度绘制解决方案: * 去掉Window的默认背景(在onCreate方法中或者在theme中) * 去掉视图中不必要的背景 * 使用ViewStub仅在需要时加载 * 使用\<include>标签重用布局,使布局更清晰明了;使用\<merge>标签合并布局,减少层级 * 采用性能更好的布局来精简层级