测量与布局

ViewGroup的绘制流程

绘制流程分为三步:测量、布局、绘制,分别对应onMeasure(), onLayout(),onDraw()
  • onMeasure():测量当前控件的大小,为正式布局提供建议(只是建议,至于是否使用,要看onLayout()函数
  • onLayout():使用layout()函数对所有子控件进行布局
  • onDraw():根据布局的位置绘图

onMeasure()函数与MeasureSpec

  • 布局绘制设计两个过程:测量过程和布局过程,测量过程通过measure()函数来实现,是View树自顶向下遍历,每个View在循环过程中将尺寸细节往下传递,当测量过程完成之后,所有的View都存储了自己的尺寸,布局过程则通过layout()函数来实现,这个函数也是自顶向下的,在这个过程中,每个父View扶额通过计算好的尺寸放置它的子View
onMeasure()函数
//widthMeasureSpec: 当前父类传递过来的建议值的宽
//heightMeasureSpec:当前父类传递过来的建议值的高
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
MeasureSpec的组成
  • 它是由mode+size两部分组成的,widthMeasureSpec和heightMeasure转换为二进制数字都是32位,前2位表示模式,后30位表示size
  • **模式分类:**UNSPECIFIED(未指定)EXACTLY(完全)AT_MOST(至多)
    • UNSPECIFIED:父元素不对子元素施加任何束缚,子元素可以得到任意想要的大小,对应的二进制数值:00000000000000000000000000000000
    • EXACTLY:;父元素决定子元素的确切大小,子元素被限定在给定的边界里而忽略它本身的大小,对应的二进制数值:01000000000000000000000000000000
    • AT_MOST:子元素至多达到指定大小的值,对应的二进制数值:10000000000000000000000000000000
  • **模式提取:**widthMeasureSpec和heightMeasureSpec是由模式和数值组成的,而且二进制的前两位代表模式
    eg: 模拟模式提取:
int MODE_MASK = 0xc0000000;
public static int getMode(iint measureSpec){
    return (measureSpec & MODE_MASK);
}

public static int getSize(int measureSpec){
    return (measureSpec & ~MODE_MASK);
}

//Android中提供的提取模式的方法
//获取模式
MeasureSpec.getMode(int spec)
//获取数值
MeasureSpec.getSize(int spec)

//具体使用
int measureWidth = MeasureSpec.getSize(widthMeasureSpec);
int measureHeight = MeasureSpec.getSize(heightMeasureSpec);
int measureWidthMode = MeasureSpec.getMode(widthMesureSpec);
int measureHeightMode = MeasureSpec.getMode(heightMeasureSpec);
  • 模式的用处:
  • XML布局和模式的关系:
    • wrap_content->MeasureSpec.AT_MOST
    • match_parent->MeasureSpec.EXACTLY
    • 具体值->MeasureSpec.EXACTLY
onLayout()函数
  • 在ViewGroup中的onLayout()函数的默认行为是
//派生自ViewGroup的类都必须去实现这个函数然后在内部按照规则在子布局中进行布局
protected abstract void onLayout(boolean changed, int l, int t, int r, int b);

eg:

public class MyLinLayout extends ViewGroup {
    public MyLinLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
    }


    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        //获取父类传过来的建议的宽度和高度值
        int measureWidth = MeasureSpec.getSize(widthMeasureSpec);
        int measureHeight = MeasureSpec.getSize(heightMeasureSpec);
        int measureWidthMode = MeasureSpec.getMode(widthMeasureSpec);
        int measureHeightMode = MeasureSpec.getMode(heightMeasureSpec);


        int height = 0;
        int width = 0;
        int count = getChildCount();
        //通过测量所有的子控件来决定他所占位置的大小
        for(int i = 0;i<count;i++){
            View child = getChildAt(i);
            measureChild(child, widthMeasureSpec, heightMeasureSpec);
            int childWidth = child.getMeasuredWidth();
            int childHeight = child.getMeasuredHeight();
            height += childHeight;
            width = Math.max(childWidth, width);
        }
        setMeasuredDimension((measureWidthMode == MeasureSpec.EXACTLY)?measureWidth:width, (measureHeightMode== MeasureSpec.EXACTLY?measureHeight:height));


    }


    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        int top = 0;
        int count = getChildCount();
        for(int i = 0;i<count;i++){
            View child = getChildAt(i);
            int childHeight = child.getMeasuredHeight();
            int childWidth = child.getMeasuredWidth();
            child.layout(0, top, childWidth,top+childHeight);
            top+=childHeight;
        }
    }
}

getMeasuredWidth()与getWidth()函数的区别

  • getMeasuredWidth()函数在measure()过程结束后就可以获取到宽度值,而getWidth()函数要在layout()过程结束后才能获取到值
  • getMeasuredWidth()函数中的值是通过setMeasuredDimension()函数来进行设置的;而getWidth()函数中的值则是通过layout(left, top, right, bottom)函数来进行设置的

container什么时候被布局

  • 它在onLayout()函数中布局他所有的子控件,它自然也有自己的父控件,一层一层向上由各自的父控件完成自己的布局,在所有控件的顶部有一个ViewRoot,它是所有控件的祖先节点

获取子控件的margin值的方法

  • 获取方法以及示例:
    • 如果要自定以ViewGroup支持子空间的layout_margin参数,则自定义的ViewGroup类必须重写generateLayoutParams()函数,并且在该函数中返回一个ViewGroup.MarginLayoutParams派生类的对象
<?xml version="1.0" encoding="utf-8"?>
<com.example.adminstator.myviewdesign.fengzhuangkongjian.ViewHuiZhi.MyLinLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical"
    tools:context=".fengzhuangkongjian.ViewHuiZhi.ViewDrawActivity">


    <TextView
        android:text="第一个View"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_margin="10dp"
        android:background="#ff0000"/>
    <TextView
        android:text="第二个View"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_margin="20dp"
        android:background="#00ff00"/>
    <TextView
        android:text="第三个View"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_margin="30dp"
        android:background="#0000ff"/>
</com.example.adminstator.myviewdesign.fengzhuangkongjian.ViewHuiZhi.MyLinLayout>
  • 但是由于并没有在onLayout()中添加根据margin来布局的计算,所以我们需要重写如下三个函数:
//在container()初始化子空间时,会调用generateLayoutParams(LayoutParams p)来为子控件生成对应的布局属性,但是只是默认生成layout_width, layout_height所对应的布局参数,即在正常情况下调用generateLayoutParams()函数生成的,所以说正常情况下调用该函数生成的LayoutParams实例是不可以获取到margin的值的
@Override
protected LayoutParams generateLayoutParams(LayoutParams p) {
    return new MarginLayoutParams(p);
}


//从指定的xml中获取对应的layout_width和layout_height值
@Override
public LayoutParams generateLayoutParams(AttributeSet attrs) {
    return new MarginLayoutParams(getContext(), attrs);
}



//如果使用默认的构造函数,就生成layout_width=”wrap_content"和layout_height="wrap_content"对应的参数
@Override
protected LayoutParams generateDefaultLayoutParams() {
    return new MarginLayoutParams(LayoutParams.MATCH_PARENT,  LayoutParams.MATCH_PARENT);
}
  • 之后在onMeasure()和onLayout()函数中分别对子控件的高度和宽度增加margin的大小:
MarginLayoutParams marginLayoutParams = (MarginLayoutParams)child.getLayoutParams();
int childHeight = child.getMeasuredHeight()+marginLayoutParams.topMargin+marginLayoutParams.bottomMargin;
int childWidth = child.getMeasuredWidth()+marginLayoutParams.leftMargin+marginLayoutParams.rightMargin;
generateLayoutParams()与MarginLayoutParams()函数的实现
  1. generateLayoutParams()函数如何获取布局值
//首先调用LayoutParams()函数产生布局信息
public LayoutParams generateLayoutParams(AttributeSet attrs) {
    return new LayoutParams(getContext(), attrs);
}


//其次LayoutParams()函数产生布局信息
public LayoutParams(Context c, AttributeSet attrs) {
    TypedArray a = c.obtainStyledAttributes(attrs, R.styleable.ViewGroup_Layout);
    setBaseAttributes(a,
            R.styleable.ViewGroup_Layout_layout_width,
            R.styleable.ViewGroup_Layout_layout_height);
    a.recycle();
}


//最后调用setbaseAttributes()函数来获得对应的宽和高,通过TypeArray对自定义的xml进行解析的过程
protected void setBaseAttributes(TypedArray a, int widthAttr, int heightAttr) {
    width = a.getLayoutDimension(widthAttr, "layout_width");
    height = a.getLayoutDimension(heightAttr, "layout_height");
}

MarginLayoutParams()函数的实现:
public MarginLayoutParams(Context c, AttributeSet attrs) {
    super();


    TypedArray a = c.obtainStyledAttributes(attrs, R.styleable.ViewGroup_MarginLayout);
    setBaseAttributes(a,
            R.styleable.ViewGroup_MarginLayout_layout_width,
            R.styleable.ViewGroup_MarginLayout_layout_height);


    int margin = a.getDimensionPixelSize(
            com.android.internal.R.styleable.ViewGroup_MarginLayout_layout_margin, -1);
     
     //提取layout_margin的值并进行设置
    if (margin >= 0) {
        leftMargin = margin;
        topMargin = margin;
        rightMargin= margin;
        bottomMargin = margin;
    } 
    //如果用户没有设置layout_margin,而是单个设置的,就一个个提取
    else {
        int horizontalMargin = a.getDimensionPixelSize(
                R.styleable.ViewGroup_MarginLayout_layout_marginHorizontal, -1);
        int verticalMargin = a.getDimensionPixelSize(
                R.styleable.ViewGroup_MarginLayout_layout_marginVertical, -1);


        if (horizontalMargin >= 0) {
            leftMargin = horizontalMargin;
            rightMargin = horizontalMargin;
        } else {
            leftMargin = a.getDimensionPixelSize(
                    R.styleable.ViewGroup_MarginLayout_layout_marginLeft,
                    UNDEFINED_MARGIN);
            if (leftMargin == UNDEFINED_MARGIN) {
                mMarginFlags |= LEFT_MARGIN_UNDEFINED_MASK;
                leftMargin = DEFAULT_MARGIN_RESOLVED;
            }
            rightMargin = a.getDimensionPixelSize(
                    R.styleable.ViewGroup_MarginLayout_layout_marginRight,
                    UNDEFINED_MARGIN);
            if (rightMargin == UNDEFINED_MARGIN) {
                mMarginFlags |= RIGHT_MARGIN_UNDEFINED_MASK;
                rightMargin = DEFAULT_MARGIN_RESOLVED;
            }
        }


        startMargin = a.getDimensionPixelSize(
                R.styleable.ViewGroup_MarginLayout_layout_marginStart,
                DEFAULT_MARGIN_RELATIVE);
        endMargin = a.getDimensionPixelSize(
                R.styleable.ViewGroup_MarginLayout_layout_marginEnd,
                DEFAULT_MARGIN_RELATIVE);


        if (verticalMargin >= 0) {
            topMargin = verticalMargin;
            bottomMargin = verticalMargin;
        } else {
            topMargin = a.getDimensionPixelSize(
                    R.styleable.ViewGroup_MarginLayout_layout_marginTop,
                    DEFAULT_MARGIN_RESOLVED);
            bottomMargin = a.getDimensionPixelSize(
                    R.styleable.ViewGroup_MarginLayout_layout_marginBottom,
                    DEFAULT_MARGIN_RESOLVED);
        }


        if (isMarginRelative()) {
           mMarginFlags |= NEED_RESOLUTION_MASK;
        }
    }


    final boolean hasRtlSupport = c.getApplicationInfo().hasRtlSupport();
    final int targetSdkVersion = c.getApplicationInfo().targetSdkVersion;
    if (targetSdkVersion < JELLY_BEAN_MR1 || !hasRtlSupport) {
        mMarginFlags |= RTL_COMPATIBILITY_MODE_MASK;
    }


    // Layout direction is LTR by default
    mMarginFlags |= LAYOUT_DIRECTION_LTR;


    a.recycle();
}

  • 所以重写函数的原因就是默认的函数只可以提取layout_width和layout_height参数,只有MarginLayoutParams()才能提取margin的功能

猜你喜欢

转载自blog.csdn.net/qq_39424143/article/details/95079837