自定义View的基础学习 | 青训营笔记

106 阅读4分钟

这是我参与「第四届青训营 」笔记创作活动的第1天

引言:

在后续的UI制作中,我们会需要用到很多功能各异的控件,但是Android官方提供的控件由很少,这时候我们就需要提供自定义View来实现我们所需要的功能,由于之前没有系统的接触自定义View的学习,所以在学习完自定义View的课程后,我又在网上找了不少的文章进行参考学习,主要的参考文章在笔记的最底端,在学习完后,我自己写了一个Demo,这个Demo会贯穿我的整个自定义View的学习,下面的正文部分就是我在学习和写Demo时的过程

正文:

1.自己定义View的可更改属性

在Android官方的控件中,有许多可以在布局文件中更改的属性,如果我们也想让自定义控件的属性能在布局文件中更改的话我们就需要自己定义这个控件需要什么属性

·首先我们需要在“res/values”目录下新建一个“attrs.xml文件”,然后在这个文件中设置View的属性(代码如下)

<?xml version="1.0" encoding="utf-8"?>
<resources>

    <!--在顶层声明,这样的话如果不同的View有相同属性的话就不用重新写一遍-->
    <attr name="text" format="string" />
    <attr name="textColor" format="color" />


    <declare-styleable name="CustomView">
        <attr name="text" />
        <attr name="textColor" />
        <!--我们也可以在一个View中单独设置一个属性-->
        <attr name="textSize" format="dimension" />
    </declare-styleable>

</resources>

以上设置的三个属性分别是:

1.显示的文字(String类型)

2.显示的颜色(color类型)

3.字体的大小(domension类型)

其中:

declare-styleable name填写的时自定义View的类名

attr name填写的是自定义属性的名字

formate填写的是对于自定义属性的类型

 

2.通过View的构造方法获取属性

如果想实现自定义View首先我们要新建一个类,让这个类去继承View,然后去重写View的三个构造方法(这里其实有四个构造方法,后面会详细说)

public class CustomView extends View {


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

public CustomView(Context context, @Nullable AttributeSet attrs) {\
        super(context, attrs);\
    }

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

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

一般来说,我们需要写前三个构造方法,只重写其中一个或两个构造方法可能会导致一些属性无法获取,下面将详细说明各个构造方法的作用

第1个构造方法:这个构造方法用于创建一个新的控件,没有其它作用。

第2个构造方法:获取xml布局文件中的属性参数,如layout_width、background等

第3个构造方法:在第2个构造方法的基础上,指向一个默认的Style

第4个构造方法:在第3个构造方法的基础上,额外添加一个style。

注意:

如果defStyleAttr有效(defStyleAttr不为0或者有定义defStyleAttr),则defStyleRes无效;如果defStyleAttr无效,则defStyleRes有效,两者不会同时有效。

 

属性获取优先级上:布局xml申明的属性 > style设置的属性 > defStyleAttr > defStyleRes > theme设置的属性

 

接下来我们需要获取自定义View的属性:

public class CustomView extends View {

    //文本
    private String text;
    //颜色
    private int color;
    //文本大小
    private int textSize;

    //获取画板对象,用于绘制View
    Paint paint = new Paint();
    //获取Rect,矩形绘制工具
    Rect rect = new Rect();

    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);
        //获取自定义的属性
        TypedArray array = context.getTheme().obtainStyledAttributes(attrs, R.styleable.CustomView,defStyleAttr,0);

        int count = array.getIndexCount();
        for(int i = 0; i < count; i++){
            int attr = array.getIndex(i);
            switch(attr){
                //获取文本
                case R.styleable.CustomView_text:
                    text = array.getString(attr);
                    break;
                //获取颜色,默认设置为黑色
                case R.styleable.CustomView_textColor:
                    color = array.getColor(attr, Color.BLACK);
                    break;
                //获取字体大小,默认设置为16sp
                case R.styleable.CustomView_textSize:
                    textSize = array.getDimensionPixelSize(attr, (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP,16,getResources().getDisplayMetrics()));
                    break;
            }
        }
    }
}

可以看到,上面写了三个构造方法,但是前两个构造方法都是调用了下一个构造方法,只有第三个构造方法调用了父类的构造方法,根据上面对构造方法的分析,这个自定义View一共引入了三个参数,即:

1.获取context,构建新的控件

2.获取attrs,取得控件的属性

3.获取style(这里设置了0,使defStyleAttr无效)

对属性值的操作方面就不再赘述了,可以看一下代码中的注释

 

3.重写onMeasure方法

在我们使用官方控件设置宽高时,都会或多或少的用到warp_content(自适应),但如果自定义控件的话,我们就需要自己写代码来计算自定义控件的大小。

写代码之前我们需要先了解一下onMeasure的一些规则:

1.onMeasure( ) 方法中的宽高默认使用match_parent

 

2.mode的三种情况:MeasureSpec.UNSPECIFIED,MeasureSpec.EXACTLY,MeasureSpec.AT_MOST

 

3.MeasureSpec.UNSPECIFIED是未指定尺寸,这种情况不多,一般都是父控件是AdapterView 通过measure方法传入的模式

 

4.MeasureSpec.EXACTLY是精确尺寸,比如andorid:layout_width="50dp",或者为match_parent

 

5.MeasureSpec.AT_MOST是最大尺寸, 表示子布局限制在一个最大值内,一般为WARP_CONTENT

了解了这些规则,我们就可以开始重写onMeasure( ) 方法了

//当布局文件中的宽高设置为了warp_content时,我们需要重写onMeasure方法来实现自适应功能
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    //获取属性
    int widthMode = MeasureSpec.getMode(widthMeasureSpec);
    int widthSize = MeasureSpec.getSize(widthMeasureSpec);
    int heightMode = MeasureSpec.getMode(heightMeasureSpec);
    int heightSize = MeasureSpec.getSize(heightMeasureSpec);
    int width;
    int height;

    //width设置了match_parent或精确尺寸(10dp等)
    if (widthMode == MeasureSpec.EXACTLY)
    {
        width = widthSize;
    } else {
        //计算布局的width应该为多大
        paint.setTextSize(textSize);
        paint.getTextBounds(text, 0, text.length(), rect);
        float textWidth = rect.width();
        width = (int) (getPaddingLeft() + textWidth + getPaddingRight());
    }

    //height设置了match_parent或精确尺寸(10dp等)
    if (heightMode == MeasureSpec.EXACTLY)
    {
        height = heightSize;
    } else {
        //计算布局的width应该为多大
        paint.setTextSize(textSize);
        paint.getTextBounds(text, 0, text.length(), rect);
        float textHeight = rect.height();
        height = (int) (getPaddingTop() + textHeight + getPaddingBottom());
    }
    //设置布局的宽高
    setMeasuredDimension(width, height);
}

 

4.重写onDraw方法

//通过重写onDraw方法来绘制UI
@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    //绘制矩形文本背景
    paint.setColor(Color.BLUE);
    canvas.drawRect(0,0,getMeasuredWidth(),getMeasuredHeight(),paint);
    //绘制文本
    paint.setColor(color);
    canvas.drawText(text,getWidth()/2 - rect.width()/2,getHeight()/2 + rect.height()/2,paint);
}

这里看注释就应该懂了

5.控件测试

完成了一个简单的自定义控件后,我们就可以开始测试一下了,在布局文件中写上我们刚刚自定义的控件

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <com.qxy.study.CustomView.CustomView
        android:id="@+id/customButton"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="325dp"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"

        android:padding="50dp"

        app:text="我是文字"
        app:textColor="@color/black"
        app:textSize="32sp" />
</androidx.constraintlayout.widget.ConstraintLayout>

这里需要注意的是使用自定义View时需要设置命名空间“xmlns:app="schemas.android.com/apk/res-aut… 这里现在新建布局文件一般都会自动写上,这一段代码的作用是自动获取app项目下的自定义属性(包括一些安卓新版本的属性),所以可以看到上面的代码在使用自定义属性的时候在自定义属性前加上“app:”就可以了

 

接下来看看效果:

可以看到一个简单的自定义控件就完成了

小结:

通过学习自定义View的基础知识,我更清晰的了解了自定义控件和官方控件的使用,写Demo的期间也遇到过很多问题,又很多的想法,这些我都在网上查资料后得到了答案并记录在这篇笔记中

引用文章:

blog.csdn.net/lmj62356579…

blog.csdn.net/GracefulGui…