美文网首页
自定义View基础概述

自定义View基础概述

作者: w达不溜w | 来源:发表于2022-02-21 12:55 被阅读0次
    1.自定义View的基本方法
    • onMeasure():测量视图大小
    • onLayout():确定View位置,进行页面布局
    • onDraw():绘制视图
    2.View类的构造函数

    View类是Android中各组件的基类,有4个构造函数

    class CustomView : View {
        //用于代码构造View()
        constructor(context: Context?) : super(context)
    
        //用于xml中构造View,使用默认style(AttributeSet对应自定义属性值集合)
        constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs)
    
        //用于xml中构造View,使用特定style
        constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super(
            context,
            attrs,
            defStyleAttr
        )
        //用于xml中构造View,使用特定style,特定资源 (不常用)
        constructor(
            context: Context?,
            attrs: AttributeSet?,
            defStyleAttr: Int,
            defStyleRes: Int
        ) : super(context, attrs, defStyleAttr, defStyleRes)
    }
    
    3.View视图结构
    View视图结构.png
    • PhoneWindow:负责管理界面显示和事件响应,是Activity与View系统交互的接口
    • DecorView:Android视图树的根节点,继承FrameLayout,作为整个视图容器来使用
    • measure, layout和draw过程,都是从View树的根节点开始,自上而下遍历(即树形递归),最终确定整个View树的相关属性
    4.Android坐标系
    Android坐标系.png

    View的位置是相对于父控件而言的

    • getTop() 获取子View左上角到父View顶部的距离
    • getLeft() 获取子View左上角到父View左侧的距离
    • getBottom() 获取子View右下角到父View顶部的距离
    • getRight() 获取子View右下角到父View左侧的距离

    MotionEvent中 :

    event.getX()/event.getY():触摸点相对于其所在View坐标系的坐标
    event.getRawX()/event.getRawY():触摸点相对于屏幕坐标系的坐标

    5.MeasureSpect(重点)

    整个测量过程是从根节点开始的遍历过程,在这个过程中父View需要告诉子View具体的模式和宽高值(对子View是一种约束,子View需要在允许的范围内绘制),最终android用一个int类型来表示模式和值。int占4个字节,32位,高2位存测量模式(mode),低30位存测量大小(size)

    测量模式 说明
    EXACTLY 确切的size
    AT_MOST 父View为子View指定一个最大size,子View不能大于该size
    UNSPECIFIED 父View不约束子View(即子View可以取任意size),系统内部使用(如RecyclerView、ScrollView)

    MeasureSpec类就是⽤来处理这些事情的,子View的MeasureSpect值是根据子View的布局参数(LayoutParams)和父容器的MeasureSpect值计算得到的:

    /**
        * 将父控件的测量规格和子View的布局参数计算得到一个最符合子View的测量规格
      *
      * @param spec  父控件的测量规格
      * @param padding 父控件里已经占用的大小
      * @param childDimension 子View布局LayoutParams里的尺寸
      * @return 子View的测量规格
      */
    public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
      //父控件的测量模式
      int specMode = MeasureSpec.getMode(spec);
      //父控件的测量大小
      int specSize = MeasureSpec.getSize(spec);
        //获取父控件的size与padding的差值(也就是父控件剩余大小),若小于0则返回0
      int size = Math.max(0, specSize - padding);
        //定义返回值存储变量size
      int resultSize = 0;
      //定义返回值存储变量mode
      int resultMode = 0;
      //依据父控件的mode进行分支逻辑处理
      switch (specMode) {
          //父控件测量有精确尺寸
        case MeasureSpec.EXACTLY:
          if (childDimension >= 0) {
            //①如果child的布局参数有固定值(如android:layout_width="100dp"),那么child的size也可以确定下来(100dp),mode为EXACTLY,size为自己的大小
            resultSize = childDimension;
            resultMode = MeasureSpec.EXACTLY;
          } else if (childDimension == LayoutParams.MATCH_PARENT) {
            //②如果child的布局参数是math_parent(也就是child想要占满父控件),而此时父控件是精确模式,有精确尺寸,都给你,child也确定下来了,mode为EXACTLY,size为父控件size
            resultSize = size;
            resultMode = MeasureSpec.EXACTLY;
          } else if (childDimension == LayoutParams.WRAP_CONTENT) {
            //③如果child的布局参数是wrap_content(也就是child想要根据自己的逻辑决定自己的大小),那就自己决定,不过大小不能大于父控件,所以child的model为AT_MOST,size为父控件size
            resultSize = size;
            resultMode = MeasureSpec.AT_MOST;
          }
          break;
    
          //父控件测量模式是AT_MOST,也就是父控件和还不知道自己的大小,但是不能超过size
        case MeasureSpec.AT_MOST:
          if (childDimension >= 0) {
            //④child能确定自己大小,自己确定
            resultSize = childDimension;
            resultMode = MeasureSpec.EXACTLY;
          } else if (childDimension == LayoutParams.MATCH_PARENT) {
            //⑤child想要和父控件一样大,但是父控件自己不确定自己的大小,所以child的size的上限是父控件size
            resultSize = size;
            resultMode = MeasureSpec.AT_MOST;
          } else if (childDimension == LayoutParams.WRAP_CONTENT) {
            //⑥child想要根据自己逻辑确定大小,size上限为父控件的size
            resultSize = size;
            resultMode = MeasureSpec.AT_MOST;
          }
          break;
    
          //UNSPECIFIED一般用于系统内部多次measure的情况下,如RecyclerView,ScrollView测量子View给的就是UNSPECIFIED,我们暂时不关注
          // Parent asked to see how big we want to be
        case MeasureSpec.UNSPECIFIED:
          if (childDimension >= 0) {
            // Child wants a specific size... let him have it
            resultSize = childDimension;
            resultMode = MeasureSpec.EXACTLY;
          } else if (childDimension == LayoutParams.MATCH_PARENT) {
            // Child wants to be our size... find out how big it should
            // be
            resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
            resultMode = MeasureSpec.UNSPECIFIED;
          } else if (childDimension == LayoutParams.WRAP_CONTENT) {
            // Child wants to determine its own size.... find out how
            // big it should be
            resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
            resultMode = MeasureSpec.UNSPECIFIED;
          }
          break;
      }
      //noinspection ResourceType
      return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
    
    小结: MeasureSpect测量模式.png

    相关文章

      网友评论

          本文标题:自定义View基础概述

          本文链接:https://www.haomeiwen.com/subject/batilrtx.html