【Android】Android自定义View和ViewGroup知识点汇总

这篇文章总结一下自定义View的实际操作

一、View的绘制流程

onMeasure()->onDraw()。

onMeasure的作用: 测量本控件的大小,测量之后的大小给到父控件(父控件一定是ViewGroup)中的onLayout来使用,对当前控件的onDraw没有直接作用。

onMeasure的过程: 首先获取函数参数widthMeasureSpec和heightMeasureSpec中的size和mode,这个size并不是实际你需要的size,需要拿到mode(EXACTLY、AT_MOST、UNSPECIFIED)重新计算出你需要的size。计算完成之后,必须调用setMeasuredDimension(width, height)设置你计算出的宽高。

默认情况下,wrap_content和match_parent的size是一样的,所以需要你重新定义wrap_content(对应AT_MOST)模式下的size。

如果你是自定义View控件,并且wrap_content和match_parent是一样的,即都是最大大小,那么就不需要重写onMeasure。

二、ViewGroup的绘制流程

onMeasure()->onLayout()->onDraw()(一般不重写)。

onLayout的作用: 用于摆放子控件,即决定子控件的位置和大小。通过调用view.layout(left,top,right,bottom)来摆放。

onLayout的过程: 如何摆放呢?首先要知道每个子控件的宽高吧?这个宽高就是通过上一步onMeasure函数计算出来的。通过view.getMeasuredWidth()或view.getMeasuredHight()获取onMeasure函数计算的宽高。然后调用view.layout(left,top,right,bottom)来摆放子控件,right一般等于left+view.getMeasuredWidth(),bottom一般等于top+view.getMeasuredHeight()。left就是view左边距离父控件左边的水平距离,top就是view顶部距离父控件顶部的垂直距离。

强调一下,onMeasure函数的作用,它用于测量子控件的大小,但并不是最终的大小,最终的大小由父控件的onLayout来决定。

举个例子,你爸妈问你中午想吃什么,你告诉你爸妈想吃海鲜,你爸妈参考了你的意见之后,最终吃什么决定权在你爸妈手上,并不是你想吃什么就吃什么。

三、需要重写的函数

View:必须重写onDraw来绘制图形,不一定要重写onMeasure,需要定义wrap_content时才需要重写onMeasure。

ViewGroup:必须重写onLayout函数,必须重写onMeasure函数,在onMeasure中必须调用measureChildren函数(否则子view布局中定义的宽高无效)。ViewGroup的onMeasure函数继承自View,ViewGroup本身没有调用过measureChildren,所以我们必须重写ViewGroup的onMeasure函数。

四、ViewGroup的onMeasure重写要点:

获取父布局给的宽高Mode和Size,最终调用setMeasureDimension(width,height)来设置它自己测量的宽高(并不是最终展示出的宽高),同时要调用measureChildren函数来测量子View的宽高。

五、ViewGroup的onLayout重写要点:

这个函数必须重写,用来定义子View的位置及宽高。需要遍历子View,依次调用子View的layout(l,t,r,b)来进行布局。其中l表示左边相对parent的最左边的距离,t表示顶部相对parent的顶部的距离,r表示最右边相对parent最左边的距离,b表示底部相对parent顶部的距离。即坐标系的原点是parent的左上角。一般r=l+getMeasureWidth(),b=t+getMeasuredHeight()。这个getMeasuredWidth()就是onMeasure函数测量的值,getMeasuredHeight同理。所以onMeasure函数测量的宽高并不是最终的宽高,View或ViewGroup位置及宽高是由父容器的onLayout函数决定的。

六、getWidth()与getMeasuredWidth()的区别

只有当前View或ViewGroup()的layout()函数被调用了之后,getWidth()(getHeigth())才能获取正确的值。

在onDraw函数中可以通过getWidth和getHeigth获取正确的值,因为当前view的onLayout函数已经调过了。

只有当前View或ViewGroup()的setMeasuredDimenstion()函数被调用了之后,getMeasuredWidth()(getMeasuredHeight())才能获取到正确的值。


一个简单的自定义文本控件的例子:

import android.content.Context;

import android.content.res.TypedArray;
import android.graphics.Canvas;

import android.graphics.Paint;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;


import androidx.annotation.Nullable;

public class MyView extends View {

    private int mStepTextSize;//字体大小
    private int mStepTextColor;//字体颜色
    private Paint mPaint;
    private String mText;//文本内容

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

    public MyView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.MyView);
        mStepTextColor = typedArray.getColor(R.styleable.MyView_textColor, mStepTextColor);
        mStepTextSize = typedArray.getDimensionPixelSize(R.styleable.MyView_textSize, mStepTextSize);
        mText = typedArray.getString(R.styleable.MyView_text);
        mPaint = new Paint();
        mPaint.setTextSize(mStepTextSize);
        mPaint.setColor(mStepTextColor);
        typedArray.recycle();
    }

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

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

	/**
	* 只需要定义AT_MOST即wrap_content属性下的size,其它用默认值。
	**/
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

        int width = MeasureSpec.getSize(widthMeasureSpec);
        if (MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.AT_MOST) {
            Rect rect = new Rect();
            mPaint.getTextBounds(mText, 0, mText.length(), rect);
            width = rect.width();
        }

        int height = MeasureSpec.getSize(heightMeasureSpec);
        if (MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.AT_MOST) {
            Rect rect = new Rect();
            mPaint.getTextBounds(mText, 0, mText.length(), rect);
            height = rect.height();
        }
		//设置计算之后的宽高
        setMeasuredDimension(width, height);

    }

    @Override
    protected void onDraw(Canvas canvas) {
        canvas.drawText(mText, 0, getHeight()-1, mPaint);
    }

}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值