自定义View核心基础

【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.


image.png
注意:一个屏幕对应一个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.

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,607评论 6 507
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,239评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,960评论 0 355
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,750评论 1 294
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,764评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,604评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,347评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,253评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,702评论 1 315
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,893评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,015评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,734评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,352评论 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,934评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,052评论 1 270
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,216评论 3 371
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,969评论 2 355

推荐阅读更多精彩内容