您的位置:首页 > 其它

自己动手,丰衣足食 —— 学习自定义View(一)

2016-04-25 18:13 295 查看

自己动手,丰衣足食 —— 学习自定义View(一)

前言

我们经常在做项目的时候遇到这样的情况,客户提出需求,UI把设计稿拿出来,你发现直接用现成的开源库好像不行哎,多多少少有些不同。这时候你就会想:要是能自己画一个出来就好了。

所以说:自己写出来的View最靠谱,最灵活,你想让它什么样它就是什么样,前提是你要能画出来…

这也是为什么自定义View的知识如此重要的原因了,所以,打算系统地总结下这方面的知识。

首先,我们先由一个最简单的同心圆开始,例子简单,主要是归纳下自定义View的步骤,为以后更复杂的View打下基础。

好了,废话少说,先看一下这次View的效果,见下图:



(图1)

这次我们要完成的就是上图中的带进度条的同心圆部分。

自定义View的大致步骤:

自定义属性;

创建自定义View的类,继承自View或其它继承自View的控件;

添加类的构造函数(在构造函数中,我们要获取之前自定义的属性);

重写父类的一些方法,例如onDraw,OnMeasure等.

下面结合代码具体来看一下:

1.在res/values下面新建一个attrs.xml,在里面我们可以定义一些View要用到属性。

有人就问了,这些属性有什么用呢?举个例子,我们平时用到的控件有许多属性,这样我们可以根据实际需要控制它的长宽,颜色等等,这样可以提高View的重用性,避免频繁修改代码。

首先确认一下本次需要要到的属性:

/**
* 圆环的颜色
*/
private int roundColor;

/**
* 圆环进度的颜色
*/
private int roundProgressColor;

/**
* 内圆的颜色
*/
private int roundInsideColor;

/**
* 进度百分比的字符串的颜色
*/
private int textProgressColor;

/**
* 标题文字的字符串的颜色
*/
private int textTitleColor;

/**
* 标题文字的字符串
*/
private String textTitle;

/**
* 进度百分比的字符串的字体
*/
private float textProgressSize;

/**
* 标题文字的字符串的字体
*/

private float textTitleSize;

/**
* 圆环的宽度
*/
private float roundWidth;

/**
* 最大进度
*/
private int max;

/**
* 当前进度
*/
private int progress;
/**
* 是否显示中间的进度
*/
private boolean textIsDisplayable;

/**
* 进度的风格,实心或者空心
*/
private int style;

public static final int STROKE = 0;
public static final int FILL = 1;


然后根据上面的内容写出attrs.xml:

<?xml version="1.0" encoding="UTF-8"?>
<resources>
<declare-styleable name="RoundProgressBar">
<attr name="roundColor" format="color"/>
<attr name="roundProgressColor" format="color"/>
<attr name="roundInsideColor" format="color"/>
<attr name="roundWidth" format="dimension"></attr>
<attr name="textProgressSize" format="dimension" />
<attr name="textProgressColor" format="color" />
<attr name="textTitle" format="string" />
<attr name="textTitleSize" format="dimension" />
<attr name="textTitleColor" format="color" />
<attr name="max" format="integer"></attr>
<attr name="style">
<enum name="STROKE" value="0"></enum>
<enum name="FILL" value="1"></enum>
</attr>
</declare-styleable>
...
</resources>


declare-styleable 标签后的name是此styleable的名字,attr 标签后的name是属性的名字,format类似于值的类型,具体可查看这个链接

2.创建一个自定义View的类,它必须继承自View或者其它继承自View的控件。

...

public class RoundProgressBar extends View {

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

public RoundProgressBar(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}

public RoundProgressBar(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);

...
}

@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);

...
}
...

}


可以看到,我们创建好以后,重写了它的三个构造方法,并且重写了onDraw这个方法,至于干什么我们下一步再说。

3.在构造方法中获取自定义的属性。

我们知道,构造方法是在这个类对象刚被创建的时候被调用的,那么这3个构造方法我们需要用哪个呢?

第一个是在正常创建类对象时被调用的:

RoundProgressBar roundProgressBar = new RoundProgressBar(this);


第二个是在xml里添加一个View:

<com.customview.RoundProgressBar
android:layout_width="120dp"
android:layout_height="120dp"  />


里面添加的属性会被存放在AttributeSet参数里。

前两个方法都会在某些情况下被系统自动调用,而第三个方法则需要我们实现调用。而且由代码中可以看出,第三个方法是被第二个方法调用,第二个方法又是被第一个方法调用。所以,我们只要在第三个方法中获取自定义属性,就能确保自定义属性的获取。关于第三个构造方法的其它高级技巧,这里就不再深入了。

public RoundProgressBar(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
...
TypedArray mTypedArray = context.obtainStyledAttributes(attrs, R.styleable.RoundProgressBar);

//获取自定义属性和默认值
roundColor = mTypedArray.getColor(R.styleable.RoundProgressBar_roundColor, Color.RED);
roundProgressColor = mTypedArray.getColor(R.styleable.RoundProgressBar_roundProgressColor, Color.GREEN);
roundInsideColor = mTypedArray.getColor(R.styleable.RoundProgressBar_roundInsideColor, Color.TRANSPARENT);
textTitle = mTypedArray.getString(R.styleable.RoundProgressBar_textTitle);
textProgressColor = mTypedArray.getColor(R.styleable.RoundProgressBar_textProgressColor, Color.GREEN);
textTitleColor = mTypedArray.getColor(R.styleable.RoundProgressBar_textTitleColor, Color.GREEN);
textProgressSize = mTypedArray.getDimension(R.styleable.RoundProgressBar_textProgressSize, 15);
textTitleSize = mTypedArray.getDimension(R.styleable.RoundProgressBar_textTitleSize, 15);
roundWidth = mTypedArray.getDimension(R.styleable.RoundProgressBar_roundWidth, 5);
max = mTypedArray.getInteger(R.styleable.RoundProgressBar_max, 100);
textIsDisplayable = mTypedArray.getBoolean(R.styleable.RoundProgressBar_textIsDisplayable, true);
style = mTypedArray.getInt(R.styleable.RoundProgressBar_style, 0);

mTypedArray.recycle();
}


可以看到,我们先是调用context.obtainStyledAttributes(attrs,R.styleable.RoundProgressBar)来获取TypedArray,然后从TypedArray获取我们定义的属性。

另外,获取属性的方法,第一个参数就是我们自定义的属性,第二个参数是一个默认值。

4.接下来,得到了属性值,我们就可以在onDraw里把自定义View描画出来了。

@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);

/**
* 画外层的圆环
*/
int centre = getWidth() / 2; //获取圆心的x坐标
int radius = (int) (centre - roundWidth / 2); //圆环的半径
int radiusInside = (int) (centre - roundWidth); //内圆的半径
paint.setColor(roundColor); //设置圆环的颜色
paint.setStyle(Paint.Style.STROKE); //设置空心
paint.setStrokeWidth(roundWidth); //设置圆环的宽度
paint.setAntiAlias(true);  //消除锯齿
canvas.drawCircle(centre, centre, radius, paint); //画出圆环

/**
* 画内层的圆
*/
paint.setColor(roundInsideColor);
paint.setStyle(Paint.Style.FILL);
paint.setStrokeWidth(radiusInside);
paint.setAntiAlias(true);
canvas.drawCircle(centre, centre, radiusInside, paint);

/**
* 画进度百分比
*/
paint.setStrokeWidth(0);
paint.setColor(textProgressColor);
paint.setTextSize(textProgressSize);
paint.setTypeface(Typeface.DEFAULT_BOLD); //设置字体
int percent = (int) (((float) progress / (float) max) * 100);  //中间的进度百分比,先转换成float在进行除法运算,不然都为0
float textWidth = paint.measureText(percent + "%");   //测量字体宽度,我们需要根据字体的宽度设置在圆环中间

if (textIsDisplayable && percent != 0 && style == STROKE) {
canvas.drawText(percent + "%", centre - textWidth / 2, centre - radiusInside/3 + textProgressSize / 2, paint); //画出进度百分比
}

/**
* 画标题
*/
paint.setStrokeWidth(0);
paint.setColor(textTitleColor);
paint.setTextSize(textTitleSize);
paint.setTypeface(Typeface.DEFAULT_BOLD); //设置字体
textWidth = paint.measureText(textTitle);   //测量字体宽度,我们需要根据字体的宽度设置在圆环中间

if (textIsDisplayable  && style == STROKE) {
canvas.drawText(textTitle, centre - textWidth / 2, centre + radiusInside/3 + textProgressSize / 2, paint); //画出进度百分比
}

/**
* 画圆弧 ,画圆环的进度
*/

//设置进度是实心还是空心
paint.setStrokeWidth(roundWidth); //设置圆环的宽度
paint.setColor(roundProgressColor);  //设置进度的颜色
RectF oval = new RectF(centre - radius, centre - radius, centre
+ radius, centre + radius);  //矩形用于定义的圆弧的形状和大小的界限

switch (style) {
case STROKE: {
paint.setStyle(Paint.Style.STROKE);
canvas.drawArc(oval, 0, 360 * progress / max, false, paint);  //根据进度画圆弧
break;
}
case FILL: {
paint.setStyle(Paint.Style.FILL_AND_STROKE);
if (progress != 0)
canvas.drawArc(oval, 0, 360 * progress / max, true, paint);  //根据进度画扇形
break;
}
}

}


首先要说明一下关于View的坐标系知识,View相对于整个屏幕左上角的位置坐标叫做绝对坐标,而相对于父控件的位置坐标叫做相对坐标,以下如不说明,使用的都是相对坐标。

关于绘图的具体细节就不深入了,感兴趣的同学可以自己画图算下坐标和长度以及相关的API。

这样,这个自定义的RoundProgressBar 就算是写好了,那么我们怎么在自己写的代码里使用它呢?

跟平常一样的xml布局文件,添加一个RoundProgressBar :

...
xmlns:custom="http://schemas.android.com/apk/res-auto"
...
<com.customview.RoundProgressBar
android:id="@+id/rpb"
android:layout_width="120dp"
android:layout_height="120dp"
android:layout_gravity="center_horizontal"
android:layout_marginTop="31dp"
custom:roundColor="@color/light_green"
custom:roundProgressColor="@color/yellow"
custom:roundInsideColor="@color/blue"
custom:roundWidth="17dp"
custom:textProgressSize="20sp"
custom:textProgressColor="@color/white"
custom:textTitle="当前全市拥堵率"
custom:textTitleSize="8sp"
custom:textTitleColor="@color/white"
custom:textIsDisplayable="true"
/>


可以看到,我们在布局文件顶部添加了一个命名空间,这样我们就可以使用自定义属性的前缀“custom”了,后面跟那些属性,就像系统默认的“android”一样。

好了,关于自定义View的基础知识就介绍到这里,下一次我们深入研究一下它的其它特性。

参考文章:

http://blog.csdn.net/xiaanming/article/details/10298163

http://shaohui.xyz/2016/07/08/Android%E8%87%AA%E5%AE%9A%E4%B9%89view%E8%AF%A6%E8%A7%A3/

http://www.jianshu.com/p/e76374706c3e?hmsr=toutiao.io&utm_medium=toutiao.io&utm_source=toutiao.io

http://blog.csdn.net/lmj623565791/article/details/24252901
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: