【Android】View的事件分发机制_view的分发事件-CSDN博客
自定义View核心基础
1.View的绘制原理
===>ActivityThread.handleResumeActivity()===>WindowManagerImpl.addView()
===>WindowManagerGlobal.addView()===>ViewRootImpl.setView()
===>ViewRootImpl.requstLayout()===>>>ViewRootImpl.schduleTraversal()
===>>>Choreographer.postCallBack()【post一个定时回调任务】===>>>TraversalRunnable.run()===>>>doTraversal()
===>performTraversal()
===>1.performMeasure=>View.measure===>View.onMeasure(或者ViewGroup.onMeasure)
===>ViewGroup.onMeasure()===>遍历ViewGroup===>View.measure 【递归测量】
===>2.perfromLayout() ===>View.layout===>View.setFrame
===>ViewGroup.onLayout=>遍历ViewGroup===>View.layout 【递归定位】
===>3.performDraw=>drawSoftWare===>View.draw===>View.onDraw
===>ViewGroup.dispatchDraw===>遍历ViewGroup===>View.draw() 【递归绘制】
2.自定义View的注意点
(1).自定义View中的4种构造函数
//主要是在Java代码中new一个View
public ChildView(Context context){
this(context,null)
}
//布局文件中使用,会调用
public ChildView(Context context,AttributeSet attrs){
this(context,attrs,0)
}
//使用自定义属性或者自定义样式时调用
public ChildView(Context context,AttributeSet attrs,int defStyleAttr){
this(context,attrs,defStyleAttr,0)
}
public ChildView(Context context,AttribueSet attrs,int defStyleAttr,int defStyleRes){
this(context,attrs,defSytleAttr,defStyleRes)
}
(2).自定义View的类型
- 继承View重写onDraw()
-继承ViewGroup派生Layout
-继承特定的View,比如TextView,然后进行扩展
-继承特定的ViewGroup,比如LinearLayout
(3).View需要支持padding
直接继承View的控件需要在onDraw方法中处理padding,否则用户设置padding属性就不会起作用。
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
// 获取当前View的padding
int leftPadding = getPaddingLeft();
int topPadding = getPaddingTop();
int rightPadding = getPaddingRight();
int bottomPadding = getPaddingBottom();
// 计算内容区域的宽和高
int contentWidth = getWidth() - leftPadding - rightPadding;
int contentHeight = getHeight() - topPadding - bottomPadding;
// 接下来,你可以在这个内容区域内绘制你的内容
// 例如,绘制一个矩形:
Paint paint = new Paint();
paint.setColor(Color.RED);
canvas.drawRect(leftPadding, topPadding, leftPadding + contentWidth, topPadding + contentHeight,
paint);
}
(4).如果在View中要处理异步消息,尽量不要直接使用handler,可以使用View.post,这个方法的处理逻辑是将Runnable的action保存到一个HandlerActionQueue队列(数组实现),在ViewRootImpl中执行绘制流程后,才会遍历HandlerActionQueue队列,然后逐一通过handler.postDelayed(action,delayTime)添加到MessageQueue中,
(5).在自定义View的onMeasuer,onDraw,onLayout方法中尽量少用局部变量,存在频繁调用的可能,尤其是动画里面,容器产生内存抖动,导致手机App的卡顿。
3.ViewGroup和View的区别.
(1).事件分发方面的区别:
ViewGroup. dispatchTouchEvent>>onInterceptTouchEvent>>onTouchEvent()
View.dispatchTouchEvent>>onTouchEvent()
(2).UI绘制方面的区别
View. onMeasure().onLayout.onDraw()
ViewGroup.onMeasure(),onLayout(),onDraw(),dispatchDraw(),drawChild()
4.View的绘制是从Activity的哪个生命周期方法开始执行的?
在调用了 onResume 生命周期方法后,开始执行绘制流程,所以在onResume中无法获取宽高.
requestLayout>>schduleTraversal>>Chrographer.postCallback(Runnable)>>
doTraversal>>performTraversal>>performMeasure>>View.measure>>View.onMeasure |ViewGroup.onMeasure>>View.measure>>performLayout>>View.layout>>View.setFrame>>
ViewGroup.onLayout>>遍历ViewGroup,View.layout >>performDraw>>View.draw>>
View.onDraw>>ViewGroup.dispatchDraw>>遍历ViewGroup|View.draw
public void handleResumeActivity){
//performResumeActivity>>>performResume>>>mInstrumentation.callActivityOnResume
final ActitivityClientRecord r=performResumeActivity();
//WindowManagerImpl.addView>>>WindowManagerGlobal.addView>>>ViewRootImpl.setView()
//>>>requestLayout
wm.addView(); //2.执行View的流程
}
5.Activity,Window,View之间的关系
Activity持有一个PhoneWindow对象,用来展示用户界面,并且可以和用户进行交互。
Window是一个容器,是View的载体,用来存放和管理View,是窗体的具体展示。
6.你工作这么些年,有注意到子线程不能更新UI?
子线程也并非不一定完全不能更新UI,下面的代码就是可以更新UI.
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
tv = findViewById(R.id.tv);
new Thread(new Runnable() {
@Override
public void run() {
tv.setText("Test");
}
}).start();
}
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread();
mLayoutRequested = true;
scheduleTraversals();
}
}
void checkThread() {
if (mThread != Thread.currentThread()) {
throw new CalledFromWrongThreadException(
"Only the original thread that created a view hierarchy can touch its views.");
}
}
ViewRootImpl对象是在onResume方法回调之后才创建,那么就说明在生命周期方法onCreate里面,甚至是在onResume里面,其实可以在子线程中更新UI,因为此时还没有创建ViewRootImpl对象,并不会对检测当前线程是否是主线程,访问UI是没有加对象锁的,在子线程环境下更新UI,会造成各种未知的风险。
7.在onResume中是否可以测量宽高.
不一定可以测量,如果使用View.post(runnable)可以测量,如果使用handler.post(runnable)无法测量
8.DecorView和ViewRootImpl,WindowManagerService,SurfaceFlinger ,Serface都有什么关系呢?
- DecorView是整个ViewTree的最顶层View ,他是一个FramenLayout,包含系统状态栏和用户布局View.
- ViewRootImpl 管理DecorView,连接DecorView和WMS,负责ViewTree的测量,布局,绘制和输入事件分发.
- WindowManagerService 管理窗口的创建,销毁,分发输入事件,与SurfaceFlinger协作,将合成图像显示到屏幕上
- SerfaceFlinger 为窗口分配图像缓冲区【GraphicBuffer】,创建Surface对象.
协作流程:
(1).窗口创建
Activity或者Dialog在创建时,会生成一个DecorView,在onResume之后,会把DecorView添加到WindowManager,并生成一个ViewRootImpl,然后将DecorView设置为根试图,binder通信,向WindowManagerService注册一个窗口,并申请Surface。
(2).窗口显示
ViewRootImpl负责管理DecorView,对它进行onMeasure,onLayout,onDraw,并将绘制的结果Surface,通过binder通信方式,传递给WMS,然后WMS通过SerfaceControl,进行Binder通信,传递给SurfaceFlinger,有SurfaceFlinger和成最终的图像,显示到屏幕上。
(3).输入事件处理.
触摸事件有InputManagerService捕获,并传递给WMS,然后WMS根据窗口的焦点状态和层级,将事件发送给正确的窗口,ViewRootImpl接收事件,分发给DecorView及其子视图处理.
(4).窗口更新
当DecorView的内容发生变化的时候,ViewRootImpl会重新onMeasure,onLayout,onDraw,绘制结果Surface,通过binder通信的方式 ,给到WMS,最终由SurfaceFlinger合成并显示。
(5).窗口销毁
当Activity或者Dialog销毁时,ViewRootImpl通过binder通信,通知WMS移除窗口,释放资源,WMS通知SurfaceFlinger销毁Surface.
注意:一个屏幕对应一个BufferQueue队列,可能有多个窗口,一个窗口对应一个Window对象,对应一个DecorView,对应一个ViewRootImpl,一个Surface,对应一个GraphicBuffer. ViewRootImpl是一个虚拟的父View,它的成员mView就是DecorView.
9.自定义View调用invalidate()方法后,为啥有时候onDraw方法不会调用.
(1).View不可见
(2).View未添加到布局当中,invalidate不会生效。
(3).View的尺寸为0.
(4).主线程阻塞,onDraw无法及时执行.
(5).重绘区域为空,导致onDraw不会被调用。
(6).启动硬件加速,某些绘制操作可能不经过onDraw().
10.invalidate()和postInvalidate()的区别?
invalidate是在UI线程中刷新UI,postInvalidate是在非UI线程中更新UI,内部是通过handler.sendMessageDelayed最后执行invalidate.