Android View的绘制流程

ViewRoot

ViewRoot对应于ViewRootImpl类,它是连接WindowManager和DecorView的纽带,也可以说是Window和View的桥梁,他主要功能有:

  • 完成View的绘制过程,包括measure、layout、draw过程
  • 向DecorView分发Event事件,包括MotionEvent,KeyEvent等

当建立好了decorView与ViewRootImpl的关联后,ViewRootImpl类的requestLayout()方法会被调用,该方法为View绘制的起点方法。

ViewRoot与DectorView的关联过程

我们从Activity的setContentView方法开始分析。该方法会创建DecorView,并将xml布局文件添加到DecorView的content区域,最后调用handleResumeActivity将decorView加入到PhoneWindow中。

  • ActivityThread调用handleResumeActivity方法将顶层的DecorView添加到PhoneWindow窗口

    final void handleResumeActivity(IBinder token,
        boolean clearHide, boolean isForward, boolean reallyResume) {
        if (r.window == null && !a.mFinished && willBeVisible)  {
            //获得当前Activity的PhoneWindow对象
            r.window = r.activity.getWindow();
            //获得当前phoneWindow内部类DecorView对象
            View decor = r.window.getDecorView();
            //设置窗口顶层视图DecorView可见度
            decor.setVisibility(View.INVISIBLE);
            //当前Activity的WindowManagerImpl对象
            ViewManager wm = a.getWindowManager();
            WindowManager.LayoutParams l = r.window.getAttributes();
            a.mDecor = decor;
            l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
            l.softInputMode |= forwardBit;
            if (a.mVisibleFromClient) {
                //标记根布局DecorView已经添加到窗口
                a.mWindowAdded = true;
                //将根布局DecorView添加到当前Activity的窗口上面
                wm.addView(decor, l);
        }
    }
    
  • WindowManagerGloba.addView()方法将DectorView与ViewRootlmpl进行关联

    public void addView(View view, ViewGroup.LayoutParams params,
        Display display, Window parentWindow) {
        ............
        ViewRootImpl root;
        View panelParentView = null;
        ............
        //获得ViewRootImpl对象root
         root = new ViewRootImpl(view.getContext(), display);
        ...........
        // do this last because it fires off messages to start doing things
        try {
            //将传进来的参数DecorView设置到root中
            root.setView(view, wparams, panelParentView);
        } catch (RuntimeException e) {
        }
    }
    
  • 接下来调用ViewRootImpl类中的setView()方法

    public void setView(View view, WindowManager.LayoutParams attrs,
        View panelParentView) {
        synchronized (this) {
            if (mView == null) {
            //将顶层视图DecorView赋值给全局的mView
                mView = view;
            .............
            //标记已添加DecorView
             mAdded = true;
            .............
            //请求布局,View绘制的起点方法
            requestLayout();
        }
    

    }

可以看出经过以上代码调用,DectorView与ViewRootImpl建立了关联,接下来就通过requestLayout()方法正试开始View的绘制过程了。

View的绘制过程

requestLayout最终会调用performTraversals方法来完成View的绘制。
整个View的绘制流程是在ViewRootImpl类的performTraversals()方法开始的,performTraversals方法会经过measure、layout和draw三个过程。所以View的绘制是ViewRootImpl完成的,另外当手动调用invalidate(主线程),postInvalidate(子线程)也会最终调用performTraversals来重新绘制View。

private void performTraversals() {
    ......
    mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    ......
    mView.layout(0, 0, mView.getMeasuredWidth(), mView.getMeasuredHeight());
    ......
    mView.draw(canvas);
    ......
}
Measure

measure过程如果是ViewGroup,需要遍历测量其孩子后再测量自己。如果是View则只负责测量自己就行。

  • MeasureSpec
    MeasureSpec是一个32位整数,由SpecMode和SpecSize两部分组成,其中高2位为SpecMode,低30位为SpecSize。SpecMode为测量模式,SpecSize为相应测量模式下的测量尺寸。
  • SpecMode的取值可为以下三种
    EXACTLY: 父容器测量出子View的精确大小SpecSize,如match_parent,具体dp值
    AT_MOST: 父容器指定子View的大小不得超过SpecSize,如wrap_content
    UNSPECIFIED: 父容器不对子View尺寸作限制,通常用于系统内部

在onCreate()中获取View的高宽时不能保证View已测量完毕,因此需要调用view.post(runnable)将测量view的代码投递到消息队列尾部,这样能保证View初始化好之后才回调runnable

Layout

Layout的作用是ViewGroup用来确定子元素位置,当ViewGroup的位置被确定后,它在onLayout中会遍历所有子元素并调用其layout方法,在layout方法中onLayout方法又会被调用。layout方法中通过setFrame来确定View本身的位置,而onLayout方法则会确定所有子元素的位置。

Draw

它的作用是将View绘制到屏幕上,绘制过程有以下几步:
1.绘制背景background
2.绘制自己
2.绘制children(ViewGroup)
3.绘制装饰,滚动条等

onDraw(Canvas canvas)最终是调用canvas.translate() native层的方法进行绘制的

常见自定义View类型

1.继承View重写onDraw方法
2.继承ViewGroup派生自定义Layout
3.继承特定View (Button,TextView)
4.继承特定ViewGroup(LinearLayout等)

自定义View注意事项

1.需要在onMeasure中实现支持wrap_content(指定一个默认大小)

为什么你的自定义View wrap_content不起作用?
https://blog.csdn.net/carson_ho/article/details/62037760

2.需自己实现padding效果

  • 如果是View则在ondraw方法里处理padding
  • 如果是ViewGroup需要在onMeasure和onLayout中考虑padding和margin的影响

3.不要使用Handler,view本身提供了post方法
4.如果有线程或动画,需要在onDetachedFromWindow中停止
5.如果有滑动冲突,需要处理冲突

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值