View Layout

前言

Layout 的过程就是将我们的View根据ViewGroup(例如FramLayout)规则、VIew 的 LayoutParams相关参数以及View Measure之后的大小来确定View的位置的过程

LayoutParams

LayoutParams翻译过来就是布局参数,子View通过父容器对应的LayoutParams来告诉父容器应该如何放置自己。所以基本上每个ViewGroup都拥有一个对应的LayoutParams来支持他们的布局规则,有时候我们也需要自定义LayoutParams来定义自己的布局规则

LayoutParams与View如何建立联系

  • 在XML中定义View,这个系统会默认给View添加一个ViewGroup的LayoutParams
  • 在java代码中直接生成View对应的实例对象

当我们自定义ViewGroup的时候需要注意下面几点

/**
* 通过代码添加子View,可重载
*/
public void addView(View child) {
    addView(child, -1);
}

/**
* 通过代码添加子View,指定位置,可重载
*/
public void addView(View child, int index) {
    if (child == null) {
        throw new IllegalArgumentException("Cannot add a null child view to a ViewGroup");
    }
    LayoutParams params = child.getLayoutParams();
    if (params == null) {
      // 生产默认LayoutParams 需要用户重载下面的方法
        params = generateDefaultLayoutParams();
        if (params == null) {
            throw new IllegalArgumentException("generateDefaultLayoutParams() cannot return null");
        }
    }
    addView(child, index, params);
}

/**
* 通过代码添加子View,指定高宽,可重载
*/
public void addView(View child, int width, int height) {
    final LayoutParams params = generateDefaultLayoutParams();
    params.width = width;
    params.height = height;
    addView(child, -1, params);
}

/**
* 注意:通过addView 添加的View,如果不传LayoutParams 则必须通过下面的方法生产默认的LayoutParams
* 重载该方法
*/
protected LayoutParams generateDefaultLayoutParams() {
    return new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
}

/**
* 添加一个View 指定params
*/
@Override
public void addView(View child, LayoutParams params) {
    addView(child, -1, params);
}

/**
* 添加一个View 指定index和params
*/
public void addView(View child, int index, LayoutParams params) {
    if (DBG) {
        System.out.println(this + " addView");
    }

    if (child == null) {
        throw new IllegalArgumentException("Cannot add a null child view to a ViewGroup");
    }

    // addViewInner() will call child.requestLayout() when setting the new LayoutParams
    // therefore, we call requestLayout() on ourselves before, so that the child's request
    // will be blocked at our level
    requestLayout();
    invalidate(true);
    addViewInner(child, index, params, false);
}

private void addViewInner(View child, int index, LayoutParams params,
        boolean preventRequestLayout) {

    ....

    if (mTransition != null) {
        mTransition.addChild(this, child);
    }

  	//检查传入的参数是否合法
    if (!checkLayoutParams(params)) {
      	//不合法将会进行转化操作
        params = generateLayoutParams(params);
    }

  	//是否要阻止重新执行布局流程
    if (preventRequestLayout) {
        child.mLayoutParams = params;//这个不会引起子View的重新布局(onMeasure -> onLayout -> onDraw)
    } else {
        child.setLayoutParams(params);//这个会引起子View的重新布局(onMeasure -> onLayout -> onDraw)
    }

    if (index < 0) {
        index = mChildrenCount;
    }

    addInArray(child, index);

    // tell our children
    if (preventRequestLayout) {
        child.assignParent(this);
    } else {
        child.mParent = this;
    }
    .......
}

/**
*	注意:检查LayoutParams 是否合规,重载该方法来自定义检查
*/
protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
    return  p != null;
}

/**
* 注意:checkLayoutParams 失败之后会调用该方法,重载该方法 添加完整参数
*/
protected LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
  	return p;
}  

/**
* 注意:通过xml添加的子View,是通过这个方法来生产LayoutParams,记得重载该方法
*/
public LayoutParams generateLayoutParams(AttributeSet attrs) {
    return new LayoutParams(getContext(), attrs);
}


复制代码

自定义LayoutParams

1.创建自定义属性
<declare-styleable name="MyGroupView">
    <attr name="layout_simple_attr" format="integer"></attr>
    <attr name="android:layout_gravity" format="dimension"></attr>
</declare-styleable>
复制代码
2.继承MarginLayoutParams
public class LayoutParams extends ViewGroup.MarginLayoutParams {

    public int simpleAttr;
    public int gravity;


    public LayoutParams(Context c, AttributeSet attrs) {
        super(c, attrs);
        //解析布局
        TypedArray typedArray = c.obtainStyledAttributes(attrs, R.styleable.MyGroupView);
        simpleAttr = typedArray.getInteger(R.styleable.MyGroupView_layout_simple_attr, 0);
        gravity = typedArray.getInteger(R.styleable.MyGroupView_android_layout_gravity, -1);
        typedArray.recycle();//释放资源
    }

    public LayoutParams(int width, int height) {
        super(width, height);
    }

    public LayoutParams(ViewGroup.MarginLayoutParams source) {
        super(source);
    }

    public LayoutParams(ViewGroup.LayoutParams source) {
        super(source);
    }
}
复制代码
3.重写ViewGroup中几个与LayoutParams相关的方法
// 已FrameLayout 为例

// 检查LayoutParams 是否合法
@Override
protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
    return p instanceof LayoutParams;
}

//生成默认LayoutParams
@Override
protected LayoutParams generateDefaultLayoutParams() {
    return new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
}

//对传入的lp 进行转化
@Override
protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams lp) {
    if (sPreserveMarginParamsInLayoutParamConversion) {
        if (lp instanceof LayoutParams) {
            return new LayoutParams((LayoutParams) lp);
        } else if (lp instanceof MarginLayoutParams) {
            return new LayoutParams((MarginLayoutParams) lp);
        }
    }
    return new LayoutParams(lp);
}

// 通过XML加载的View 通过该方法构造LayoutParams
@Override
public LayoutParams generateLayoutParams(AttributeSet attrs) {
    return new FrameLayout.LayoutParams(getContext(), attrs);
}

复制代码

Layout

layout流程图

public void layout(int l, int t, int r, int b) {
  // 判断需不需要先执行onMesuare,这个通常在measure方法里设置,告诉layout 我还未调用onMeasure 方法,你需要执行一遍
    if ((mPrivateFlags3 & PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT) != 0) {
        onMeasure(mOldWidthMeasureSpec, mOldHeightMeasureSpec);
        mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
    }

    int oldL = mLeft;
    int oldT = mTop;
    int oldB = mBottom;
    int oldR = mRight;

  // 通常如果你只是移动下视图位置我们只需要调用layout方法。属性动画的位移动画实现方式就是直接调用layout减少measure的计算量,setFrame/setOpticalFrame可能会触发invalidate流程
    boolean changed = isLayoutModeOptical(mParent) ?
            setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);

    if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
        onLayout(changed, l, t, r, b);
       .....
    }
   ....
}
复制代码
分类:
Android
标签: