美文网首页
自定义View

自定义View

作者: 还是昵称啊 | 来源:发表于2020-06-26 16:41 被阅读0次

    自定义View

    一、 View的绘制流程

    • onMeasure() --测量View的大小
    • onLayout()-- 确定子View的布局
    • onDraw()--实际绘制内容

    自定义View主要实现onMeasure()、onDraw(),自定义ViewGroup主要实现onMeasure()、onLayout();

    二、实现自定义

    1. 流程图

    <img src="C:\Users\Administrator\Desktop\自定义View的流程.jpg" alt="自定义View的流程" style="zoom:50%;" />

    2. MeasureSpec

    MeasureSpec是view中的内部类,是一个32位的int值,高两位表示Mode低30位表示size,MODE_SHIFT=30表示移位。

    • UNSPPECIFIED:不对View的大小做限制,在系统中使用
    • EXACTLY:父容器已经得到自己View的确切大小
    • AT_MOST:父容器指定一个大小,子view不可以超过这个值,如matchParent

    3. onMeasure()

    这个方法用于测量View的宽高。android中的View是树形结构,父View包含子View,子View又包含自己的子View,因此要想测量自己的宽高就需要递归的测量出子View的宽高。

    3.1 测量子View的宽高

    //layoutParams就是xml中的布局参数:layout_width,layout_height
    LayoutParams childLP = childView.getLayoutParams();
    
    //获取子View的MeasureSpec
    int childWidthSpec = getChildMeasureSpec(widthMeasureSpec,  paddingRight + paddingLeft,
            childLP.width);
    int childHeightSpec = getChildMeasureSpec(heightMeasureSpec,paddingTop + paddingBottom,
            childLP.height);
    

    使用getChildMeasureSpec方法测量出子View的宽高,其在这个方法中会计算出子View的最终size以及测量的mode。首先先获取子View的父View也就是当前定义的这个View的宽高和测量模式。然后根据测量模式来判断怎么样给子View分配大小,因为当前View的大小也是由这个View的父View所分配的。

    • EXACTLY

      case MeasureSpec.EXACTLY:
          //子View大小是确定的,返回子view要的大小和Exactly
          if (childDimension >= 0) {
              resultSize = childDimension;
              resultMode = MeasureSpec.EXACTLY;
              //子view要和父Vew一样大,有多少要多少
          } else if (childDimension == LayoutParams.MATCH_PARENT) {
              // Child wants to be our size. So be it.
              resultSize = size;
              resultMode = MeasureSpec.EXACTLY;
          } else if (childDimension == LayoutParams.WRAP_CONTENT) {
              // Child wants to determine its own size. It can't be
              // bigger than us.
              //子View不确定要多少,返回父布局的有的,最大不能超过父布局的大小
              resultSize = size;
              resultMode = MeasureSpec.AT_MOST;
          }
          break;
      

    如果这个View的大小是确定的,则分配给子View的大小和模式分为三种。第一种,子View的大小也是确定的,这种情况下就分配给子View所要求的大小;第二种,子View的大小要求和父View一样大就把父View的大小 分配给子View;第三种,子View不确定要多少,就把父View的大小分配给子View并把mode设为AT_MOST,即最大不能超过这个。

    • AT_MOST

      // Parent has imposed a maximum size on us
      case MeasureSpec.AT_MOST:
          if (childDimension >= 0) {
              // Child wants a specific size... so be it
              resultSize = childDimension;
              resultMode = MeasureSpec.EXACTLY;
          } else if (childDimension == LayoutParams.MATCH_PARENT) {
              // Child wants to be our size, but our size is not fixed.
              // Constrain child to not be bigger than us.
              resultSize = size;
              resultMode = MeasureSpec.AT_MOST;
          } else if (childDimension == LayoutParams.WRAP_CONTENT) {
              // Child wants to determine its own size. It can't be
              // bigger than us.
              resultSize = size;
              resultMode = MeasureSpec.AT_MOST;
          }
          break;
      

    如果这个View的有一个最大的宽高,其分配给子view的宽高也分为三种情况。子View要求一个确切的值,就分配给子View这个大小;如果子View要求和父View一样的大小就把父View有的最大的宽高分配给子View;如果子View不确定要多少也把父View的最大值给子View。

    • UNSPECIFIED

    通过上面的方法就能测量出子ViewMeasureSpec,然后调用子View的measure方法就能根据MeasureSpec测量出宽高

    //获取度量之后的宽高
    int childMeasuredWidth = childView.getMeasuredWidth();
    int childMeasuredHeight = childView.getMeasuredHeight();
    

    获取到所有的子View组成的布局的最大宽度和高度之后就要和这个View的父View分配这个View的大小做比较,如果这个View的mode为EXACTLY,这个View所计算出来的View的最终宽度为 MeasureSpec中获得的宽高和所有子view组成的布局的最大宽高的最小值。

    //判断这个ViewGroup的父布局给它的模式是什么,如果是确切的那这个ViewGroup的宽度只能是父布局给的宽度
    //不需要判断AT_MOST的情况
    int realWidth = widthMode == MeasureSpec.EXACTLY ? selfWidth : parentNeedWidth;
    int realHeight = heightMode == MeasureSpec.EXACTLY ? selfHeight : parentNeedHeight;
    

    最后保存测量出来的宽高值

    setMeasuredDimension(realWidth, realHeight);
    

    4. onLayout

    //左边界 起点
    int curLeft = getPaddingLeft();
    //上边界起点
    int curTop = getPaddingTop();
    for (int i = 0; i < mAllLines.size(); i++) {
        int lineHeight = mLineHeights.get(i);
        List<View> lineViews = mAllLines.get(i);
        for (View view : lineViews) {
            int left = curLeft;
            int top = curTop;
            //注意:这里只能拿到测量的宽度和高度
            // 不能使用getWidth和getHeight,这两个方法只有在当前View.layout执行后才会生效
            int right = left + view.getMeasuredWidth();
            int bottom = top + view.getMeasuredHeight();
            view.layout(left, top, right, bottom);
            curLeft = right + mHorizontalSpacing;
        }
        curLeft = getPaddingLeft();
        curTop = curTop + lineHeight + mVerticalSpacing;
    }
    

    重写这个方法用于布局子View,注意view只有在执行了layout方法之后才能通过getWidth等方法拿到其宽高值没在此之前只能通过getMeasuredWidth来获取。

    总结

    自定义ViewGroup主要需要重写onMeasure和onLayout两个方法,

    onMeasure的流程为

    • 测量子View的MeasureSpec
    • 根据子View的MeasureSpec获取子View测量到的宽高值,并保存计算出所有子View所需要的最大宽度和高度
    • 判断当前View的Mode是不是EXACTLY的如果是则将宽高值设置为所有子View所需的宽高值和这个View的父View分配给这个View的宽高值(通过MeasureSpec拿到)的最小值

    相关文章

      网友评论

          本文标题:自定义View

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