Android:关于View的一些笔记

291 阅读3分钟

"三天不学习,赶不上阿凡提"  --巴依老爷

1. 先从setContentView(R.layout.main)说起吧

Android 常规的布局是用xml来配置的,需要通过反射来初始化

   void setContentView(int resId){
       // 实际将xml初始化View
       LayoutInflater.from(this).inflate(resId,viewGroup)
   }
   -> LayoutInflate
   View inflate(resId,viewGroupRoot,isAttachRoot){
       Resource res = context.resource
       //根据Id获取到此layout的解析器
       XmlResourceParser parser = res.getLayout(resId)
       return inflate(parser,viewGroupRoot,isAttachRoot)
   }
   ->// 由于XML文件的解析很费时,所以在构建时用aapt工具,将XMl预处理成Parser等压缩的二进制格式
   View inflate(parser,rootView,isAttach){
      rInflate(parse,rootView) -> rInflateChildren()...
      ->
      String name = parser.getName()
      final View temp = createViewFromTag(rootView,name)   
      // 这里生成只有宽高的LayoutParams
      final ViewGroup.LayoutParams params = rootView.generateLayoutParams(attrs)
      //递归到子View
      rInflateChildren(...)
      //最后组成完整View树
      rootView.addView(view,params)
      -> rootView.mChildren[].add(view.apply{ setLayoutParams(params))
   }

  至此,从一个的Xml文件转换到一个View树,每层View都设置了LayoutParams,只不过,还没有画出到屏幕上。在这个过程中,LayoutParams作为一个包含了在Xml中配置的各种参数的容器对象,成为View的参数。

2. View树的开始绘制

这中间忽略掉Window-ViewRoot的创建,绑定等过程,来到ViewRootImpl。View树的绘制从这里开始。

2.1 Measure - 测量过程

    // 执行遍历
   void performTraversals(){
       // 先进行测量的分析
       performMeasure(childWidthMeasureSpec,childWidthMeasureSpec)
       
       measureHierarchy()
       performLayout()
       performDraw()...
       
   }
   // 遍历执行Measure,调用View的measure()
   void measure(int widthMeasureSpec,int heightMeasureSpec){
   
     //此时主要是对width/heightMeasureSpec的再调整,以及对比新旧参数的变化
     //之后调用onMeasure()进入执行测量的实际工作
     onMeasure(widthMeasureSpec,heightMeasureSpec)   
   
   }
   
   // 此时传入的是当前View自身的初始化时设置的LayoutParams中的wrap match xxdp...
   void onMesure(int widthMeasureSpec,int heightMeasureSpec){
   
     //1.从这两个int参数中获取MeasureMode,MeasureSize等信息
     int mWidth = MeasureSpec.getSize(widthMeasureSpec) 
     int mWidthMode MeasureSpec.getMode(widthMeasureSpec)
     int mHeight = ..
     int mHeightMode ...
     ----
     
     //2.如果是ViewGroup有子View,在这一步会递归到子View的measure操作中去
     int childMeasureSpec = child.getLayoutParams()...
     child.measure(childWidthMeasureSpec,childHeightMeasureSpec)
     
     //3.在这一步中,会综合协商子View和自身ViewGroup的MeasureSpec
     //比如 AT_MOST下需要参考将子View的宽高作为自身的宽高,EXACTLY就不需要参考
     //加上padding等附加的参数,最后得出本ViewGroup自身应该设置的实际宽高
     // 当然,这个步骤是高度自定义的,不同的模式有不同的测量方式,比如可以将AT_MOST就设置宽高成定值等等
     
     .... //省略掉这个过程
     
     //设置自身宽高
     setMeasureDimension(mMeasureWidth,mMeasureHeight)
   }    

Measure 过程综述:整体而言就是,将View树的每个节点View,根据其自身设置的LayoutParams,同时考虑到其自身作为ViewGroup和子View之间的布局关系,来测量出每个节点View的宽高 measureWidth,measureHeight。

2.2 Layout - 布局过程

    void performLayout(){
         host.layout(0,0,host.getMeasureWidth(),host.getMeasureHeight())
    }
    ->View的layout()方法
    //参数就是上层ViewGroup计算好的上下左右边界
    void layout(int l,int t,int r,int b){
        onLayout(changed,l,t,r,b)
        ->layoutChildren() ...
        //计算好子View的上下左右边界,递归到子View的layout过程
    }
    // 这部分没有什么需要分析得细节,因为在之前的measure过程,都已经确定了每一层的宽高
    

Layout 过程概述:将测量好的宽高,结合每层ViewGroup的布局关系,进而确定成整个屏幕坐标系下的绝对坐标

测量好了坐标才可以绘制

3.Draw - 绘制

此部分涉及到的较为复杂 分析待续...