使用onMeasure和onLayout的小Demo

先看效果图:


上图的小方块是自定义的View

包裹小方块的是一个自定义的ViewGroup


为了简便,我将自定义View写成了自定义ViewGroup的内部类形式:

public class ViewGroupDemo extends ViewGroup {
    Random random = new Random();
    Paint paint = new Paint();
    private Context context;
    int lie = 3; // 一行三个(列数)
    int padding = 10; //  padding

    public ViewGroupDemo(Context context) {
        super(context);
        this.context = context;
        paint.setStyle(Paint.Style.FILL);
    }

    public ViewGroupDemo(Context context, AttributeSet attrs) {
        super(context, attrs);
        this.context = context;
        paint.setStyle(Paint.Style.FILL);

    }

    public ViewGroupDemo(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        this.context = context;
        paint.setStyle(Paint.Style.FILL);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int height;
        // xml中的宽度是match_parent。属于精确布局
        int width = measureDimension(MeasureSpec.getSize(widthMeasureSpec),widthMeasureSpec); 
        // 算出子View的宽度
        int childWidth = (width - (lie + 1) * padding) / lie;
        if (MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.EXACTLY) {
            height = MeasureSpec.getSize(heightMeasureSpec);
        } else {
            int defHeight = ((int) Math.ceil(getChildCount() / (double)lie)) * (childWidth + padding) + padding;
            height = measureDimension(defHeight,heightMeasureSpec);
        }

        for (int i = 0; i < getChildCount(); i++) { // 分别计算每个子视图的宽高
            // 因为是正方形小块,子视图宽高一样。这里设置的值,会被传到子view的onMeasure()方法中
            getChildAt(i).measure(childWidth, childWidth); 
        }
        setMeasuredDimension(width, height); // 设置Group的宽高

//      默认情况下,match_parent 或者 wrap_content 没有任何区别,其显示大小由父控件决定,它会填充满整个父控件的空间。
//      固定的值,则其显示大小为该设定的值。
//      如果你的自定义控件的大小计算就是跟系统默认的行为一致的话,那么你就不需要重写onMeasure函数了。
//      super.onMeasure(widthMeasureSpec,heightMeasureSpec);
    }


    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        int height = padding;
        // 遍历子视图,为每个视图设置布局位置
        for (int i = 0; i < getChildCount(); i++) {
            int childWidth = getChildAt(i).getMeasuredWidth();
            int childHeight = getChildAt(i).getMeasuredHeight();
            if (i % lie == 0 && i != 0)
                height += (childHeight + padding);
            int left = (i % lie) * padding + padding + (i % lie) * childWidth;
            int top = height;
            int right = (i % lie) * padding + padding + (i % lie) * childWidth + childWidth;
            int bottom = height + childHeight;
            // 四个参数分别为这个子视图的左、上、右、下的位置坐标。这样就能确定这个子视图的位置信息
            getChildAt(i).layout(left, top, right, bottom);
        }
    }

    /**
     * 改变视图中的方块个数和每行个数
     * @param count
     * @param lie
     */
    public void change(int count, int lie) {
        this.removeAllViews();
        this.lie = lie;
        for (int i = 0; i < count; i++) {
            this.addView(new MyView(context));
        }
    }

    protected int measureDimension(int defaultSize,int measureSpec ) {
        int result;
        int specMode = MeasureSpec.getMode(measureSpec);
        int specSize = MeasureSpec.getSize(measureSpec);
        //1. layout给出了确定的值,比如:100dp
        //2. layout使用的是match_parent,但父控件的size已经可以确定了,比如设置的是具体的值或者match_parent
        if (specMode == MeasureSpec.EXACTLY) {
            result = specSize; //建议:result直接使用确定值
        }
        //1. layout使用的是wrap_content
        //2. layout使用的是match_parent,但父控件使用的是确定的值或者wrap_content
        else if (specMode == MeasureSpec.AT_MOST) {
            result = Math.min(defaultSize, specSize); //建议:result不能大于specSize
        }
        //UNSPECIFIED,没有任何限制,所以可以设置任何大小
        //多半出现在自定义的父控件的情况下,期望由自控件自行决定大小
        else {
            result = defaultSize;
        }
        return result;
    }

    public class MyView extends View {

        public MyView(Context context) {
            super(context);
        }

        public MyView(Context context, AttributeSet attrs) {
            super(context, attrs);
        }

        public MyView(Context context, AttributeSet attrs, int defStyleAttr) {
            super(context, attrs, defStyleAttr);
        }

        @Override
        protected void onDraw(Canvas canvas) {
            Paint paint = new Paint();
            // 随机设置paint的颜色,每次绘制界面时都会设置随机颜色
            paint.setARGB(random.nextInt(100) + 126, random.nextInt(256), random.nextInt(256), random.nextInt(256));
            // 画一个和View大小相等的矩形
            canvas.drawRect(0, 0, getWidth(), getHeight(), paint);
        }

        @Override
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            // 测量View的宽高
            setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec), MeasureSpec.getSize(heightMeasureSpec));
        }
    }
}



Activity的代码如下:

public class MainActivity extends Activity {

    private EditText et_count;
    private ViewGroupDemo groupDemo;
    private EditText et_lie;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        et_count = (EditText) findViewById(R.id.et_count);
        et_lie = (EditText) findViewById(R.id.et_lie);
        groupDemo = (ViewGroupDemo) findViewById(R.id.mygroup);
        groupDemo.change(5,3); // 默认个数为5个,三列
    }

    public void click(View v) {
        String value = et_count.getText().toString();
        String lie = et_lie.getText().toString();
        try {
            int count = Integer.parseInt(value);
            int l = Integer.parseInt(lie);
            if (count > 0 && count <= 100) {
                if (l<=20&&l>0){
                    groupDemo.change(count,l);
                }else {
                    Toast.makeText(MainActivity.this,"输入错误",Toast.LENGTH_LONG).show();
                }
            }
        } catch (NumberFormatException e) {
            e.printStackTrace();
        }
    }
}


布局代码:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".MainActivity">
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">
        <EditText
            android:id="@+id/et_count"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="2"
            android:hint="个数最多100"
            android:inputType="number" />
        <EditText
            android:id="@+id/et_lie"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="2"
            android:hint="列数,最多20"
            android:inputType="number" />
        <Button
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:onClick="click"
            android:text="变换" />
    </LinearLayout>
    <ScrollView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content">

        <cn.pan.viewgroupdemo.ViewGroupDemo
            android:id="@+id/mygroup"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:focusable="true"
            android:focusableInTouchMode="true"></cn.pan.viewgroupdemo.ViewGroupDemo>
    </ScrollView>
</LinearLayout>














评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值