对于Android自定义控件,这是Android开发进阶的一个重要技能,从此踏入自定义的门槛。今天就先继承自View,实现两个比较基本的自定义控件。先看效果图:
自定义控件一般需要如下几步:
1、自定义属性。因为在自定义控件的过程中往往需要在控件上添加我们自己希望的属性。
2、在自定义控件中获取我们自定义的属性。
3、自定义OnMeasure()方法。(可选,因为系统默认了一个实现方法,许多时候我们并不需要处理这个方法)
4、自定义onDraw()方法。(在屏幕上绘制出控件的模样)
5、在布局文件中引用类型
由于上面两个控件自定义的操作步骤是相同的,所以两个控件就一起说了。
第一步自定义属性:
每个自定义的属性都需要给定一个属性值得类型,几种类型已经在下面代码中标识出来
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!--
这个是第一个的自定义属性,name表示属性的名称,format表示属性值得类型
color:颜色 比如android:textColor="#ffffff"
dimension:尺寸 比如 android:textSize="16sp"
enum:枚举类型 比如 android:layout_width="wrap_content"
boolean: true 或者 false
flag:位或类型 用 | 连接起来, 比如android:layout_gravity="center|vertical"
float:浮点类型
fraction:百分数
reference:引用类型 比如 android:textColor="@color/mycolor"
string:字符串类型
-->
<declare-styleable name="ProgressView">
<attr name="colorWidth" format="dimension"/>
<attr name="noColorWidth" format="dimension"/>
<attr name="color" format="color"/>
<attr name="width" format="dimension"/>
<attr name="speed" format="enum">
<enum name="fast" value="5"/>
<enum name="middle" value="3"/>
<enum name="slow" value="1"/>
</attr>
</declare-styleable>
<declare-styleable name="ClockView">
<attr name="hourColor" format="color"/>
<attr name="minuteColor" format="color"/>
<attr name="secondColor" format="color"/>
<attr name="tableColor" format="color"/>
</declare-styleable>
</resources>
下面是第二三四步的代码,首先来看第一个自定义控件的代码。说明已经在代码中标注出来,主要实现的是onDraw()方法用来绘制控件,这部分有时候需要考虑的是数学问题,而在这个控件中实现不断向右滑动就是利用科count的不断连续性的改变实现的。
public class ProgressView extends View {
//下面的几个变量用来存储各个属性值。
int colorWidth;
int noColorWidth;
int color;
Paint paint;
int count = 0;
int screenWidth;
int speed;
int width;
public ProgressView(Context context) {
super(context);
}
public ProgressView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public ProgressView(Context context, AttributeSet attrs) {
super(context, attrs);
//通过typedArray来获取各个属性值
TypedArray array = context.obtainStyledAttributes(attrs,R.styleable.ProgressView);
colorWidth = array.getDimensionPixelSize(R.styleable.ProgressView_colorWidth,20);
noColorWidth = array.getDimensionPixelSize(R.styleable.ProgressView_noColorWidth,20);
color = array.getColor(R.styleable.ProgressView_color, Color.BLUE);
speed = array.getInt(R.styleable.ProgressView_speed,3);
width = array.getDimensionPixelSize(R.styleable.ProgressView_width,10);
//初始化画笔
paint = new Paint();
paint.setColor(color);
paint.setStrokeWidth(width);
}
@Override
protected void onDraw(Canvas canvas) {
screenWidth = getWidth();
int start = count % (colorWidth + noColorWidth);
start = start - (colorWidth + noColorWidth);
while (start < screenWidth) {
canvas.drawLine(start,getHeight()/2,start+colorWidth,getHeight()/2,paint);
start += colorWidth+noColorWidth;
}
count += speed;
count %= (colorWidth+noColorWidth);
/*
每当绘制一次count增加,然后调用invalidate(),会重新执行onDraw方法,而每次绘制会根据count的值绘制
到不同的位置,从而就达到了动态的效果。
这里有两个方法postInvalidate()和invalidate()方法.其中前者是在工作线程中被调用,
而后者在UI线程中被调用
*/
invalidate();
}
}
下面的代码是实现那个时钟控件。
package com.zwbin.custom;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.view.View;
import java.util.Calendar;
import java.util.Timer;
import java.util.TimerTask;
/**
* Created by wenbin on 16/9/14.
*/
public class ClockView extends View {
int hourColor;
int minuteColor;
int secondColor;
int tableColor;
Paint paint;
public ClockView(Context context) {
super(context);
}
public ClockView(Context context, AttributeSet attrs) {
super(context, attrs);
TypedArray typedArray = context.obtainStyledAttributes(attrs,R.styleable.ClockView);
hourColor = typedArray.getColor(R.styleable.ClockView_hourColor, Color.RED);
minuteColor = typedArray.getColor(R.styleable.ClockView_minuteColor, Color.BLUE);
secondColor = typedArray.getColor(R.styleable.ClockView_secondColor, Color.GREEN);
tableColor = typedArray.getColor(R.styleable.ClockView_tableColor, Color.GRAY);
paint = new Paint();
}
public ClockView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
protected void onDraw(Canvas canvas) {
//开始画表盘
paint.setColor(tableColor);
paint.setStrokeWidth(3);
paint.setStyle(Paint.Style.STROKE);
int width = getWidth();
int height = getHeight();
int radius = Math.min(width/2,height/2);
canvas.drawCircle(width/2,height/2,radius,paint);
paint.setStrokeWidth(8);
//表盘最长的四条线
for (int i = 0;i < 4;i++) {
double degree = Math.PI/2 * i;
double sin = Math.sin(degree);
double cos = Math.cos(degree);
canvas.drawLine((int)(radius*sin + width/2),
(int)(height/2-radius*cos),
(int)((radius-60)*sin + width/2),
(int)(height/2 - (radius-60)*cos),
paint);
}
//表盘第二长的12条线
for (int i = 0;i < 12;i++) {
double degree = Math.PI/6 * i;
double sin = Math.sin(degree);
double cos = Math.cos(degree);
canvas.drawLine((int)(radius*sin + width/2),
(int)(height/2-radius*cos),
(int)((radius-40)*sin + width/2),
(int)(height/2 - (radius-40)*cos),
paint);
}
//表盘最短的60条线
for (int i = 0;i < 60;i++) {
double degree = Math.PI/30 * i;
double sin = Math.sin(degree);
double cos = Math.cos(degree);
canvas.drawLine((int)(radius*sin + width/2),
(int)(height/2-radius*cos),
(int)((radius-20)*sin + width/2),
(int)(height/2 - (radius-20)*cos),
paint);
}
//开始画中心点
paint.setStrokeWidth(30);
canvas.drawPoint(width/2,height/2,paint);
Calendar calendar = Calendar.getInstance();
int hour = calendar.get(Calendar.HOUR);
int mimute = calendar.get(Calendar.MINUTE);
int second = calendar.get(Calendar.SECOND);
//开始画时针
int hourRadius = radius/3;
paint.setStrokeWidth(30);
paint.setColor(hourColor);
canvas.drawLine(width/2,
height/2,
(int)(width/2+hourRadius*Math.sin(hour*Math.PI/6)),
(int)(height/2-hourRadius*Math.cos(hour*Math.PI/6)),
paint);
//开始画分针
int minuteRadius = radius/2;
paint.setStrokeWidth(20);
paint.setColor(minuteColor);
canvas.drawLine(width/2,
height/2,
(int)(width/2+minuteRadius*Math.sin(mimute*Math.PI/30)),
(int)(height/2-minuteRadius*Math.cos(mimute*Math.PI/30)),
paint);
//开始画秒针
int secondRadius = radius*2/3;
paint.setStrokeWidth(10);
paint.setColor(secondColor);
canvas.drawLine(width/2,
height/2,
(int)(width/2+secondRadius*Math.sin(second*Math.PI/30)),
(int)(height/2-secondRadius*Math.cos(second*Math.PI/30)),
paint);
invalidate();
}
}
可以很明显得看到,代码的相似度很高,最主要的区别就是在onDraw中绘制表的部分,这部分是根据相关坐标来绘制圆圈、直线、点组成的。也和上一个自定义控件一样,每次绘制完成会调用invalidate()来进行重新绘制,表盘位置不变,时针、分针、秒针会根据时间绘制到不同的位置,连续进行绘制就会出现动态移动的效果。
下面进行最后一步,在布局文件中使用。
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res/com.zwbin.custom"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context="com.zwbin.custom.MainActivity">
<com.zwbin.custom.ProgressView
android:layout_width="match_parent"
android:layout_height="50dp"
app:colorWidth="50dp"
app:noColorWidth="50dp"
app:speed="middle"
app:color="#FF0000"
app:width="10dp"/>
<com.zwbin.custom.ClockView
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>
至此这两个基础的控件绘制完成了,下一篇会对这一篇的代码进行详解。