自定义 SimpleVG

来源:互联网 发布:淘宝网店图标尺寸 编辑:程序博客网 时间:2024/06/17 17:18

一、实现一个简单的 ViewGroup

该自定义 ViewGroup 类似 LinearLayout 垂直布局的形式


这里写图片描述

  1. 布局

    <?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"    android:layout_width="match_parent"    android:layout_height="match_parent">    <cc.catface.helloworld.view.SimpleVG        android:layout_width="match_parent"        android:layout_height="wrap_content"        android:background="#20f0">        <Button            android:layout_width="match_parent"            android:layout_height="wrap_content"            android:text="first button" />        <TextView            android:layout_width="match_parent"            android:layout_height="wrap_content"            android:text="你的信仰是什么" />        <Button            android:layout_width="200dp"            android:layout_height="wrap_content"            android:text="second button" />        <Button            android:layout_width="300dp"            android:layout_height="60dp"            android:text="third button" />    </cc.catface.helloworld.view.SimpleVG></LinearLayout>
  2. onMeasure方法中遍历测得所有子控件的宽高

    @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {    super.onMeasure(widthMeasureSpec, heightMeasureSpec);    int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);    int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);    int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);    int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);    // 保存实际布局的宽高    int width = 0;    int height = 0;     // 轮询遍历所有子控件的宽高    for (int i = 0; i < getChildCount(); i++) {        View child = getChildAt(i);        measureChild(child, widthMeasureSpec, heightMeasureSpec);        // 得到子控件的宽高        int childWidth = child.getMeasuredWidth();        int childHeight = child.getMeasuredHeight();        // 整个布局的高为所有子控件高值的总和        height += childHeight;        // 整个布局的宽为所有子空间中最宽的那个控件的宽值        width = Math.max(width, childWidth);    }    setMeasuredDimension((widthSpecMode == MeasureSpec.AT_MOST) ? width : widthSpecSize, (heightSpecMode == MeasureSpec.AT_MOST) ? height : heightSpecSize);}
    • 简述一下上面代码中的最后一行setMeasuredDimension(……),实际为以下代码的简写

      if (widthSpecMode == MeasureSpec.AT_MOST && heightSpecMode == MeasureSpec.AT_MOST) {    setMeasuredDimension(width, height);} else if (widthSpecMode == MeasureSpec.AT_MOST) {    setMeasuredDimension(width, heightSpecSize);} else if (heightSpecMode == MeasureSpec.AT_MOST) {    setMeasuredDimension(widthSpecSize, height);} else {    setMeasuredDimension(widthSpecSize, heightSpecSize);}

      也就是说,宽或高谁是 wrap_content(AT_MOST),那么就将自己测量处理的到的结果交给系统。反之,如果是 match_parent(EXACTLY)或者具体 dp/px值(EXACTLY),那么就将父容器的建议值交给系统,就是 MeasureSpec.getSize方法得到的宽或高值交给系统

  3. onLayout方法中确定所有子控件的位置

    @Override protected void onLayout(boolean changed, int l, int t, int r, int b) {    int top = 0;    for (int i = 0; i < getChildCount(); i++) {        View child = getChildAt(i);        int childWidth = child.getMeasuredWidth();        int childHeight = child.getMeasuredHeight();        // 该行即确定各个子控件的left,top,right,button位置坐标点,具体分析如下图        child.layout(0, top, childWidth, top + childHeight);        top += childHeight;    }}

    上面代码 child.layout(0, top, childWidth, top + childHeight);的分析图


    这里写图片描述

而、为自定义布局中的子控件添加 margin

  1. 布局

    <!--  为增加展示效果,为各子控件添加了背景色,并添加同样效果的LinearLayout布局作为对比 --><?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="match_parent"android:orientation="vertical">    <cc.catface.helloworld.view.SimpleVG        android:layout_width="match_parent"        android:layout_height="wrap_content"        android:background="#20f0">        <Button            android:layout_width="match_parent"            android:layout_height="wrap_content"            android:layout_marginTop="30px"            android:background="#100f"            android:text="first button" />        <TextView            android:layout_width="match_parent"            android:layout_height="wrap_content"            android:layout_marginTop="60px"            android:background="#200f"            android:text="你的信仰是什么" />        <Button            android:layout_width="200dp"            android:layout_height="wrap_content"            android:layout_marginBottom="15px"            android:layout_marginLeft="20px"            android:layout_marginRight="30px"            android:layout_marginTop="90px"            android:background="#300f"            android:text="second button" />        <Button            android:layout_width="300dp"            android:layout_height="60dp"            android:layout_marginTop="120px"            android:background="#400f"            android:text="third button" />    </cc.catface.helloworld.view.SimpleVG>    <View        android:layout_width="match_parent"        android:layout_height="10px"        android:background="#000" />    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"        android:layout_width="match_parent"        android:layout_height="wrap_content"        android:background="#20f0"        android:orientation="vertical">        <Button            android:layout_width="match_parent"            android:layout_height="wrap_content"            android:layout_marginTop="30px"            android:background="#100f"            android:text="first button" />        <TextView            android:layout_width="match_parent"            android:layout_height="wrap_content"            android:layout_marginTop="60px"            android:background="#200f"            android:text="你的信仰是什么" />        <Button            android:layout_width="200dp"            android:layout_height="wrap_content"            android:layout_marginBottom="15px"            android:layout_marginLeft="20px"            android:layout_marginRight="30px"            android:layout_marginTop="90px"            android:background="#300f"            android:text="second button" />        <Button            android:layout_width="300dp"            android:layout_height="60dp"            android:layout_marginTop="120px"            android:background="#400f"            android:text="third button" />    </LinearLayout></LinearLayout>

    结果如下,margin作用并没有显示出来


    这里写图片描述

    原因很容易得出,因为布局中添加了margin,肯定要在父容器的测量和规划布局中得到 margin值并做对应处理啊。也就是在 onMeasure方法和 onLayout方法中获取 margin值并做对应处理,具体处理情况如下

  2. 针对 margin 重新 measure 和 layout

    具体就是通过 MarginLayoutParams 获取到 margin值,然后做相应处理

    1. onMeasure:重点是两条*号中间的处理

      for (int i = 0; i < getChildCount(); i++) {    View child = getChildAt(i);    measureChild(child, widthMeasureSpec, heightMeasureSpec);    // ******************************************************************************************//    MarginLayoutParams childMarginarginLayoutParams = (MarginLayoutParams) child.getLayoutParams();    // 分别为当前子控件的宽/高    int childWidth = child.getMeasuredWidth();    int childHeight = child.getMeasuredHeight();    // 分别为当前布局的中宽/高(添加对应各子控件的margin值)    width = Math.max(width, childWidth + childMarginarginLayoutParams.leftMargin + childMarginarginLayoutParams.rightMargin);    height += childHeight + childMarginarginLayoutParams.topMargin + childMarginarginLayoutParams.bottomMargin;    // ******************************************************************************************************\\}
    2. onLayout:重点是两条*号中间的处理

      for (int i = 0; i < getChildCount(); i++) {    View child = getChildAt(i);    // *******************************************************************************************/    MarginLayoutParams childMarginarginLayoutParams = (MarginLayoutParams) child.getLayoutParams();    // 子控件的宽/高    int childWidth = child.getMeasuredWidth();    int childHeight = child.getMeasuredHeight();    // 子控件的各margin值    int leftMargin = childMarginarginLayoutParams.leftMargin;    int topMargin = childMarginarginLayoutParams.topMargin;    int rightMargin = childMarginarginLayoutParams.rightMargin;    int bottomMargin = childMarginarginLayoutParams.bottomMargin;    // 先记录各个子控件的topMargin值    top += topMargin;    // 左上位置坐标(leftMargin, top)很好理解    // 子控件宽/高分别就是左上坐标加上自己的测量宽高值    child.layout(leftMargin, top, leftMargin + childWidth, top + childHeight);    // 当前子控件位置确定后,记录下一个子控件的top点为当前子控件高度+当前子控件的bottomMargin值    // 当记录下一个子控件的top点时,配合上面的 top += topMargin 即可得到结果    top += (childHeight + bottomMargin);    // *******************************\\}
  3. 效果

    结果报错:类转换异常


    这里写图片描述

    针对 margin 在 onMeasure方法和 onLayout方法中做处理,需要复写如下三个方法

    // 为当前自定义ViewGroup获取MarginLayoutParams以对margin做相关处理@Override protected LayoutParams generateLayoutParams(LayoutParams p) {    return new MarginLayoutParams(p);}@Override public LayoutParams generateLayoutParams(AttributeSet attrs) {    return new MarginLayoutParams(getContext(), attrs);}@Override protected LayoutParams generateDefaultLayoutParams() {    return new MarginLayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);}

    上述过程处理完成后的最终效果如下


    这里写图片描述

原创粉丝点击