Android自定义View的基本步骤和使用自定义属性

本文详细介绍了Android中自定义View的基本步骤,包括如何从头开始创建组件,以及如何从已有的组件扩展。重点讲解了构造方法、onMeasure()和onDraw()的重写,以及事件处理。此外,还阐述了如何在自定义控件中添加并使用自定义属性,包括在attrs.xml中声明属性、在XML布局中应用以及在组件类中读取属性值的过程。

在前面几篇博客了解了Android中View的绘制流程和自定义View的几个常用类,在这一篇博客中主要介绍一下Android中自定义View的基本步骤以及简单的使用自定义属性。

通常来说,自定义组件有两种定义方式:
从0开始定义自定义组件,组件类继承View;
从Android已有的组件进行扩展,定义出更加个性化或者更适合需求的控件。


Android自定义View的基本步骤:

① 对于直接继承自View的组件:

创建继承自View的自定义组件,第一步就是重写构造方法,接着就是重写onMeasure()方法进行测量和onDraw()方法绘制结果,第三步就是处理相关的事件,在这里主要说第一、二步,事件的处理可以浏览《Android中的事件分发机制》这篇博客。

以下代码我创建了一个继承自View的自定义组件,并重写了onMeasure()方法和onDraw()方法:

public class TestView extends View {
    public TestView(Context context) {
        super(context);
    }

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

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

    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
    public TestView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        // 测量组件大小
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        // 绘制组件
        super.onDraw(canvas);
    }
}
四个构造方法
public TestView(Context context)
public TestView(Context context, AttributeSet attrs)
public TestView(Context context, AttributeSet attrs, int defStyleAttr)
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public TestView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes)
这四个构造方法的调用场景并不一样:

第一个只有一个参数,是在代码中创建组件时调用,比如创建一个按钮:Button btn = new Button(this),this是Context的子类;

第二个构造方法在layout布局文件中使用组件时调用,参数attrs表示当前配组件在xml文件中的属性集合,例如在要layout.xml中定义一个按钮:<Button android:layout_width = "match_parent" android:layout_height = "wrap_content" android:text = "确定"/>,Android会调用第二个构造方法创建出Button对象;

第三个构造方法是不会自动调用的,当我们在Theme中定义了Style属性时通常在第二个构造方法中手动调用;

第四个构造方法是在Android5.0以上才加上的。

测量大小
protected void onMeasure(int widthMeasureSpec,int heightMeasureSpec)
该方法主要用于子类的重写和扩展,如果不重写该方法,父类View有自己的默认实现。在Android中,自定义组件的大小都由自身通过onMeasure()进行测量,不管界面布局有多么复杂,每个组件都负责计算自己的大小。

绘图
protected void onDraw(Canvas canvas)
该方法用于显示组件的外观,界面最终的显示结果就是通过canvas绘制出来的结果。在View类中,该方法并没有任何的默认实现。

需要了解更多关于onMeasure()方法和onDraw()方法的可以查看《Android自定义View之View的绘制流程》这篇博客。
事件处理

如果需要事件处理,就重写dispatchTouchEvent(MotionEvent event)和onTouchEvent(MotionEvent event)方法。

需要了解更多关于事件处理相关的内容,可以查看《Android中的事件分发机制》这篇博客。

例:下面的代码继承自View,并且重写了onMeasure()和onDraw()方法,在界面上绘制了一段文字:

public class TestView extends View {
    private String text = "Android自定义组件练习";
    private Paint paint;

    public TestView(Context context) {
        this(context, null); // 调用两个参数的构造方法
    }

    public TestView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);// 调用三个参数的构造方法
    }

    public TestView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();//初始化
    }

    /**初始化方法*/
    private void init() {
        paint = new Paint(Paint.ANTI_ALIAS_FLAG);
        paint.setColor(Color.parseColor("#f80000"));
        paint.setTextSize(50);
    }

    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
    public TestView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
        init();//初始化
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        // 测量组件大小
        int width = getViewWidth(widthMeasureSpec);//获取控件的宽
        int height = getViewHeight(heightMeasureSpec);//获取控件的高
        setMeasuredDimension(width,height);//设置控件的宽和高为测量得到的宽和高
    }

    /**获取控件的高*/
    private int getViewHeight(int heightMeasureSpec) {
        int mode = MeasureSpec.getMode(heightMeasureSpec); // 获取高度模式
        int height = MeasureSpec.getSize(heightMeasureSpec); // 获取高度值
        if (MeasureSpec.EXACTLY == mode) {
            // 如果模式是精确的,直接返回高度值
            return height;
        }else{
            // 如果模式不是精确的,就进行测量
            Rect bounds = new Rect();
            paint.getTextBounds(text, 0, text.length(), bounds); // 测量文字的高度
            return bounds.height() + 20; // 文字的高度加上20像素作为边距作为控件的高度
        }
    }

    /**获取控件的宽*/
    private int getViewWidth(int widthMeasureSpec) {
        int mode = MeasureSpec.getMode(widthMeasureSpec); // 获取宽度模式
        int height = MeasureSpec.getSize(widthMeasureSpec); // 获取宽度值
        if (MeasureSpec.EXACTLY == mode) {
            // 如果模式是精确的,直接返回高度值
            return height;
        }else{
            // 如果模式不是精确的,就进行测量
            Rect bounds = new Rect();
            paint.getTextBounds(text,0,text.length(),bounds); // 测量文字的宽度
            return bounds.width() + 20; // 文字的宽度加上20像素作为边距作为控件的宽度
        }
    }

    @Override
    protected void onDraw(Canvas canvas) {
        // 绘制组件
        canvas.drawText(text,10,getMeasuredHeight() - 10,paint);// 直接将文字绘制到界面上
    }
}
在layout.xml中直接使用运行得到结果如图:


② 对于直接继承自ViewGroup的组件:

创建继承自ViewGroup的自定义组件,主要步骤和继承自View的相差不大。第一步就是重写构造方法,接着就是重写onMeasure()方法进行测量,只是在这里需要递归测量所有子组件的大小;不需要重写onDraw()方法,可以重写dispatchDraw()方法调用子组件的绘制方法让子组件绘制自身另外不同的就是继承至ViewGroup的自定义组件必须重写onLayout()方法给每一个孩子控件确定位置(当我们继承FrameLayout和LinearLayout类似的容器组件时,没有重写onLayout()方法是因为这些容器组件内已经实现onLayout()方法),第三步就是处理相关的事件,在这里主要说第一、二步,事件的处理可以查看《Android中的事件分发机制》这篇博客

了解ViewGroup中的dispatchDraw()方法调用View中的onDraw()方法过程和ViewGroup的onMeasure()方法简单用例,可以查看Android自定义View之View的绘制流程》这篇博客。


在自定义控件中使用自定义属性:
在TestView类中,我们并没有做特别定义就能使用一些属性,那是因为组件从View继承后,View已经具备了一些属性,比如layout_width、layout_height,所以不需要定义就能使用。在Android\sdk\platforms\android-23\data\res\values\attrs.xml文件中,找到<declare-styleable name="View">这一行(在sdk23是从2152行开始),接下来的600多行都是与View的默认属性有关的,常用的属性比如layout_widthlayout_height、background、alpha等属性都是默认的属性。更多的可以直接查看以上文件。

但是现在我想在xml文件中直接可以指定文字的大小、颜色以及绘制的文字内容,在默认的View中就没有这样的属性,也就是需要我们自定义,具体的定义过程如下:

Android中自定义属性主要有以下几个步骤:
◆ 在 res/values/attrs.xml 文件中为指定组件定义 declare-styleable 标记, 并将所有的属性都定义在该标记中;
◆ 在 layout 文件中使用自定义属性;
◆ 在组件类的构造方法中读取属性值。

现在就以TestView为例,实现在xml文件中可以改变字体大小、颜色和文字内容:

第1步,在res/values/attrs.xml文件的resources节点下定义如下内容:

<declare-styleable name="TestView">
	<!--指定文字内容-->
	<attr name="text" format="string"/>
	<!--指定文字颜色-->
	<attr name="textColor" format="color"/>
	<!--指定文字大小-->
	<attr name="textSize" format="dimension"/>
</declare-styleable>

第2步,在xml文件中使用刚刚定义的属性:

<com.customerview.widget.TestView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        renj:text="Android自定义组View用自定义属性"
        renj:textColor="#0000FF"
        renj:textSize="30sp" />
注意,xml文件在使用自定义的属性时,前面并不是使用的android:开头,表示这不是Android中的属性,所以我们添加自定义的名称空间:

xmlns:renj="http://schemas.android.com/apk/res-auto"

完整的layout.xml文件:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:renj="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context="renj.customerview.MainActivity">

    <com.customerview.widget.TestView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        renj:text="Android自定义组View用自定义属性"
        renj:textColor="#0000FF"
        renj:textSize="20sp" />
</LinearLayout>

第3步,在组件类中读取自定义属性:

使用上面的代码,修改的部分如下,就可以读取到自定的属性了

public TestView(Context context, AttributeSet attrs, int defStyleAttr) {
	super(context, attrs, defStyleAttr);
	init(context,attrs);//初始化
}

@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public TestView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
	super(context, attrs, defStyleAttr, defStyleRes);
	init(context,attrs);//初始化
}
/**初始化方法*/
private void init(Context context,AttributeSet attrs) {
	TypedArray typedArray = context.obtainStyledAttributes(attrs,R.styleable.TestView);// 获取属性数组
	text = typedArray.getString(R.styleable.TestView_text);// 获取绘制内容,注意使用的方法getString()
	color = typedArray.getColor(R.styleable.TestView_textColor, DEFAULT_COLOR); //获取颜色,没有定义使用默认值
	textSize = typedArray.getDimension(R.styleable.TestView_textSize, DEFAULT_SIZE); //获取文字大小,没有定义使用默认值
	typedArray.recycle(); // 释放资源

	paint = new Paint(Paint.ANTI_ALIAS_FLAG);
	paint.setColor(color);
	paint.setTextSize(textSize);
}

注意:获取属性时,使用typedArray调用获取属性的方法时,如果是string类型的就调用getString(),如果获取颜色就调用getColor(),以此类推。

运行结果如图:



组件的属性都应该定义在declare-styleable标记中,该标记的name属性值一般来说都是组件类的名称(此处为TestView),虽然也可以取别的名称,但和组件名相同可以ᨀ高代码的可读性。组件的属性都定义在declare-styleable标记内,成为declare-styleable标记的子标记,每个属性由两部分组成:属性名和属性类型。属性通过attr来标识,属性名为name,属性类型为format,可选的属性类型如图:

说明:

string:字符串
boolean:布尔
color:颜色
float:浮点数
fraction类型的属性
integer:整数
fraction:百分数,在动画资源<scale>、<rotate>等标记中,fromX、fromY等属性就是
dimension:尺寸,可以带单位,比如长度通常为dp,字体大小通常为sp
enum:枚举,需要在attr标记中使用<enum>标记定义枚举值,定义如下:

<attr name="age" format="enum">
	<enum name="man" value="0"/>
	<enum name="woman" value="1"/>
</attr>
flag类型的属性也有一个子标记<flag>,定义如下:

<attr name="flagtest" format="flag">
	<flag name="flag1" value="value1"/>
	<flag name="flag2" value="value2"/>
	<flag name="flag3" value="value3"/>
</attr>
组件中常见的gravity属性就是属性flag类型,如图:



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值