自定义View

目录

引言

1 自定义属性

2 onDraw 绘制View

3 onMeasure 测量 View 的大小并确定它们的宽度和高度

4 完整代码

5 绘制流程

6 总结


引言

    自定义View核心考虑两件事情:View的大小( onMeasure )、View显示什么( onDraw )。
  • 自定义属性
  • onDraw 绘制View
  • onMeasure  测量 View 的大小并确定它们的宽度和高度

1 自定义属性

    在attr.xml中定义View的属性,内容如下:
<declare-styleable name="CustomView">   //View对应的属性
    <attr name="custom_text" format="string" />     //属性名及类型
    <attr name="custom_color" format="color|reference" /> //属性及类型
    <attr name="custom_size" format="dimension" />  //属性及类型
</declare-styleable>
    在java中如何使用自定义的属性值。
//获取declare-styleable标签
TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.CustomView);
//获取declare-styleable标签下的attr
text = ta.getString(R.styleable.CustomView_custom_text);
textSize = ta.getDimension(R.styleable.CustomView_custom_size,12);
textColor = ta.getColor(R.styleable.CustomView_custom_color,0x000000);
//属性值recycle后方可使用
ta.recycle();
Q:自定义属性为何要使用属性配置?能不能在自定义View文件内使用set函数来设置?
A:当然可以使用set函数来做配置,只是不够灵活,无法在xml中配置;自定义View要呈现什么样的状态才是最佳的?自定义View的最高境界是犹如原生,所以使用属性配置会更好一些。
Q: 读取配置之后,为什么要recycle();
A:recycle的作用是释放内存
    TypeArray是用来读取XML中定义的属性值的一个类,它用于在自定义视图或ViewGroup中读取自定义属性。
在使用完TypeArray之后,需要调用recycle()方法进行回收。原因是,TypeArray的生命周期不由垃圾回收机制掌控,而是由资源回收机制管理。因此,在使用完TypeArray时,需要显式地调用recycle()方法来释放资源,防止资源的浪费和内存泄漏。
同时,还需要注意,在回收TypeArray之后,不能再访问它的内容,否则会引发运行时异常。

2 onDraw 绘制View

    在onDraw函数中绘制View,比如:绘制文本。
@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    //绘制文本
    canvas.drawText(text,getPaddingLeft()+0,getPaddingTop()+mTextBound.height(),mPaint);
}
此方法实现在画布上绘制文本。

3 onMeasure 测量 View 的大小并确定它们的宽度和高度

    onMeasure() 是重要的生命周期回调方法之一,主要作用是测量 View 的大小并确定它们的宽度和高度。
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);

    
    //获取 View 宽度的测量模式和大小
    int specWidth = MeasureSpec.getSize(widthMeasureSpec);
    int specMode = MeasureSpec.getMode(widthMeasureSpec);

    int mWidth = 0;
    int mHeight = 0;

    if(specMode == MeasureSpec.EXACTLY){
        //确定的大小或match_parent
        mWidth = specWidth;
    } else if(specMode == MeasureSpec.AT_MOST){
        //wrap_content
        mWidth = getPaddingLeft()+getPaddingRight()+mTextBound.width();
    }


    //获取 View 宽度的测量模式和大小
    specMode =  MeasureSpec.getMode(heightMeasureSpec);
    int specHeight = MeasureSpec.getSize(heightMeasureSpec);


    if(specMode == MeasureSpec.EXACTLY){
        //确定的大小或match_parent
        mHeight = specHeight;
    } else if(specMode == MeasureSpec.AT_MOST){
        //wrap_content
        mHeight = getPaddingTop()+getPaddingBottom()+mTextBound.height();
    }
    //设置View的测量宽度和测量高度
    setMeasuredDimension(mWidth,mHeight);
}
    自定义View的宽度和高度模式分为2种,EXACTLY为固定的大小和match_parent,AT_MOST为wrap_content,需根据模式来计算View的宽度和高度。
    注意:在计算宽度和高度时要加入padding值。
    计算出View宽度和高度后,需要使用setMeasuredDimension设置测量的宽度和高度,方可生效。
    如果不实现onMeasure方法,自定义View会使用父控件的宽度和高度。

4 完整代码

     完整代码如下:
public class CustomView extends View {


    private final String text;  //文字内容
    private final Paint mPaint;//画笔
    private final Rect mTextBound; //文本边界
    private final float textSize; //字体大小
    private final int textColor; //字体颜色


    public CustomView(Context context) {
        this(context,null);
    }


    public CustomView(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs,0);
    }


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


        //获取declare-styleable标签
        TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.CustomView);
        //获取declare-styleable标签下的attr
        text = ta.getString(R.styleable.CustomView_custom_text);
        textSize = ta.getDimension(R.styleable.CustomView_custom_size,12);
        textColor = ta.getColor(R.styleable.CustomView_custom_color,0x000000);
        //属性值recycle后方可使用
        ta.recycle();


        //画笔 设置颜色、样式、大小
        mPaint = new Paint();
        //文本边界,绘制文本需要的绘制的位置 left right top bottom
        mTextBound = new Rect();
        //设置大小
        mPaint.setTextSize(textSize);
        mPaint.setColor(textColor);
        //获取绘制文本的位置
        mPaint.getTextBounds(text,0,text.length(),mTextBound);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        //获取 View 宽度的测量模式和大小
        int specWidth = MeasureSpec.getSize(widthMeasureSpec);
        int specMode = MeasureSpec.getMode(widthMeasureSpec);


        int mWidth = 0;
        int mHeight = 0;


        if(specMode == MeasureSpec.EXACTLY){
            //确定的大小或match_parent
            mWidth = specWidth;
        } else if(specMode == MeasureSpec.AT_MOST){
            //wrap_content
            mWidth = getPaddingLeft()+getPaddingRight()+mTextBound.width();
        }


        //获取 View 宽度的测量模式和大小
        specMode =  MeasureSpec.getMode(heightMeasureSpec);
        int specHeight = MeasureSpec.getSize(heightMeasureSpec);


        if(specMode == MeasureSpec.EXACTLY){
            //确定的大小或match_parent
            mHeight = specHeight;
        } else if(specMode == MeasureSpec.AT_MOST){
            //wrap_content
            mHeight = getPaddingTop()+getPaddingBottom()+mTextBound.height();
        }
        //设置View的测量宽度和测量高度
        setMeasuredDimension(mWidth,mHeight);
    }


    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        //绘制文本
        canvas.drawText(text,getPaddingLeft()+0,getPaddingTop()+mTextBound.height(),mPaint);
    }
}
    在绘制文本时,首先需要文本的内容,然后需要获取文本的边界,即使用Rect获取View的left、right、top、bootom当可确定View的大小,Android 的界面使用坐标系来布局,所以对于界面内的控件在绘制时要使用坐标点来定位和确定大小。

5 绘制流程

    视图是先测量onMeasure后绘制onDraw。
    

6 总结

    此文的目的是为了记录自定义View的核心内容,至于自定义View绘制的具体实现由实际需求决定,绘制api如何用等可参考官方控件和源码中对控件的介绍,如文本类的控件可参考TextView。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值