您的位置:首页 > 移动开发 > Android开发

Android自定义View(二)画一个表

2016-08-17 16:59 597 查看


        书接上回:Android自定义View(一)关于super、this和构造方法


        这篇自定义个表盘的CustomWatchView给大家瞅瞅,主要是会说到Canvas和Paint这两个东西。

        先上个图看看效果,大概写了不到150行代码:



        这里大概分成两步:1是获取属性值;2是按照属性值绘图;

        如果不需要在xml文件配置属性,那么在自定义类里面公开几个属性的setter就好了;Android嘛,我们当然是倾向xml配置了。

        在res/values文件夹下新建attrs.xml,我们这里只是用到了主题颜色和表的半径两个属性:

<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="CustomWatchView">
<attr name="bodyColor" format="color" />
<attr name="radius" format="float" />
</declare-styleable>
</resources>

        name在自定义类会当做标识来获取整个styleable,<attr/>标签定义了xml使用的名称name,和对应的值类型format。

        现在有了属性定义了,接着往下走。

 

        新建一个类命名为CustomWatchView,添加成员变量和构造方法:

public class CustomWatchView extends View{
//画笔工具
private Paint mPaint;
//图像颜色
private int mColor;
//表盘半径
private float radius;

private float mHour = 0;
private float mMinutes = 0;
private float mSecond = 0;

public CustomWatchView(Context context) {
this(context,null);
}
public CustomWatchView(Context context, AttributeSet attrs) {
this(context, attrs,0);
}
public CustomWatchView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mPaint = new Paint();//实例化画笔
//获取自定义属性列表
TypedArray typedArray = context.getTheme().obtainStyledAttributes(attrs, R.styleable.CustomWatchView,defStyleAttr,0);
mColor = typedArray.getColor(R.styleable.CustomWatchView_bodyColor, Color.BLACK);//获取xml配置颜色,默认是黑色
radius = typedArray.getFloat(R.styleable.CustomWatchView_radius, 150);//获取xml配置的半径,默认为150
mPaint.setColor(mColor);//设置画笔颜色
mPaint.setStrokeWidth(4);
}

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

}

 

        可能有人注意到我在前接构造器中用的都是this,跟有些人的博客写的不一样,看过上一篇博客就知道,这么写其实是一样的,没啥问题,最终还是要调用到View(Context context)的。如果不明白,可以看一下上一篇,地址在本文顶部。

        上面代码我们已经获取到了需要的变量,接下来就是绘图了:

        1.      画外圈的大圆;

        2.      加点自己的痕迹和文本时间(可选)。

        3.      画刻度;

        4.      画时针;

        5.      画分针;

        6.      画秒;

        接下来只贴onDraw(Canvascanvas)的代码,这里把1、2一起放上来:

@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//画表盘
mPaint.setAntiAlias(true); //设置平滑
mPaint.setStyle(Paint.Style.STROKE);
canvas.translate(getWidth()/2,getHeight()/2);//其实绘制点移动到控件的中心
canvas.drawCircle(0,0,radius,mPaint);//画圆做表盘最外围
canvas.save();//保存一下图层

canvas.translate(-70, -100);//左移70,上移100
Paint citePaint = new Paint(mPaint);
citePaint.setColor(Color.GRAY);
citePaint.setTextSize(28);//设置文字大小
citePaint.setStrokeWidth(2);
canvas.drawText("shazeys", 20, 10, citePaint);
canvas.translate(0,radius);
citePaint.setColor(Color.RED);
canvas.drawText(getTimeStr(),15, 10, citePaint);
canvas.restore();//回复刚刚保存的位置
}

        关于getTimeStr()的代码,一会儿再贴;这里出现了一对表面看起来和绘画关系不大的方法save()和restore(),关于它俩,后半部分再说,这里先露个脸,混脸熟。

        到这里的效果是这样的:



        继续往下画刻度,代码在上段代码的下面,onDraw的内部:

//画刻度
Paint tmpPaint = new Paint(mPaint);
tmpPaint.setStrokeWidth(2);
float y = radius;
int count = 60;//刻度总数
for (int i=0; i<count; i++){
if (i%5==0){
canvas.drawLine(0f,y,0,y-12f,mPaint);//每五个画一个大的刻度
}else{
canvas.drawLine(0f,y-7,0f,y,tmpPaint);//普通刻度
}
canvas.rotate(360/count,0f,0f);//旋转画布
}

        效果:



        有前面的铺垫剩下的一锅上了:

//画中心的小圆和稍大的灰色小圆盘
tmpPaint.setColor(Color.GRAY);
tmpPaint.setStrokeWidth(4);
canvas.drawCircle(0,0,7,tmpPaint);//半径7的圆
tmpPaint.setStyle(Paint.Style.FILL);
tmpPaint.setColor(mColor);
canvas.drawCircle(0,0,4,tmpPaint);//半径4的圆

//画时针
canvas.rotate((float) (mHour*30 + mMinutes*0.5));//根据时间计算时针的角度,旋转画布
canvas.drawLine(0,10,0,-(radius-100),mPaint);//画时针
canvas.rotate((float) -(mHour*30 + mMinutes*0.5));//将画布转回去
//画分针
Paint miPaint = new Paint(mPaint);//新建画笔
miPaint.setColor(Color.DKGRAY);//设置颜色
miPaint.setStyle(Paint.Style.FILL);
miPaint.setStrokeWidth(3);
canvas.rotate(mMinutes*6);//计算分针的角度并旋转画布
canvas.drawLine(0,15,0,-(radius-50),miPaint);//画分针
canvas.rotate(-mMinutes*6);//将画布转回去
//画秒针的点
miPaint.setColor(Color.RED);//改变画笔颜色
canvas.rotate(mSecond*6);//秒针旋转角度
canvas.drawCircle(0,-y,5,miPaint);
//canvas.rotate(-mSecond*6);//转回去,最后一步了,可以不恢复




        基本的内容是画完了,那么作为手表,必须动起来吧?

        写个定时器,启动定时器,这里使用handler来实现,下面贴一下定时器的代码:

//用Handler实现计时器
final Handler mUpdateTimeHandler = new Handler(){
@Override
public void handleMessage(Message msg) {
switch (msg.what){
case MSG_UPDATE_TIME:
invalidate();
long time = System.currentTimeMillis();
mHour = (time/1000/60/60 + 8) % 12;//中国时区+8
mMinutes = time/1000/60 % 60;
mSecond = time/1000 % 60;
this.sendEmptyMessageDelayed(MSG_UPDATE_TIME,1000);
Log.i("TIME",mHour+":"+mMinutes+":"+mSecond);
break;
}
}
};

        对了,上面还欠个格式化时间值的方法:

//获取时间字符串
private String getTimeStr(){
return timeFormat(mHour)+":"+timeFormat(mMinutes)+":"+timeFormat(mSecond);
}
//格式化时间值
private String timeFormat(float value){
return value>9 ? (int)value+"" : "0"+(int)value;
}
        公开启动和停止的两个方法:
public void start(){
mUpdateTimeHandler.sendEmptyMessageAtTime(MSG_UPDATE_TIME,0);
}

public void stop(){
mUpdateTimeHandler.removeMessages(MSG_UPDATE_TIME);
}

        现在这个自定义控件就整体搞完了,可以在activity中看效果了,建议吧开关写在生命周期里,节省资源。

        但是现在还不能结束这篇博客,还有个坑没填:save()和restore()。

        它俩是成对出现的,总是先save后restore,而且是一对一的,这个感觉就像写数据库开启事务和关闭事务一样成双成对。

        save会记下当前的canvas的状态,然后在restore的时候,保留这之间的操作,然后回到save时的状态。这个再比较复杂的绘画比较常用。我们前面画时分秒的时候,都是先做了对应的旋转,然后再转回去,我们也可以改改用save和restore来处理,下面贴出用save和restore完整代码的类:

public class CustomWatchView extends View{
static final int MSG_UPDATE_TIME = 0;
//画笔工具
private Paint mPaint;
//图像颜色
private int mColor;
//表盘半径
private float radius;

private float mHour = 0;
private float mMinutes = 0;
private float mSecond = 0;

public CustomWatchView(Context context) {
this(context,null);
}
public CustomWatchView(Context context, AttributeSet attrs) {
this(context, attrs,0);
}
public CustomWatchView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mPaint = new Paint();//实例化画笔
//获取自定义属性列表
TypedArray typedArray = context.getTheme().obtainStyledAttributes(attrs, R.styleable.CustomWatchView,defStyleAttr,0);
//获取xml配置颜色,默认是黑色
mColor = typedArray.getColor(R.styleable.CustomWatchView_bodyColor, Color.BLACK);
//获取xml配置的半径,默认为150
radius = typedArray.getFloat(R.styleable.CustomWatchView_radius, 150);
mPaint.setColor(mColor);
mPaint.setStrokeWidth(4);
}

@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//画表盘
mPaint.setAntiAlias(true); //设置平滑
mPaint.setStyle(Paint.Style.STROKE);
canvas.translate(getWidth()/2,getHeight()/2);//其实绘制点移动到控件的中心
canvas.drawCircle(0,0,radius,mPaint);//画圆做表盘最外围
canvas.save();//保存一下图层

canvas.translate(-70, -100);//左移70,上移100
Paint citePaint = new Paint(mPaint);
citePaint.setColor(Color.GRAY);
citePaint.setTextSize(28);
citePaint.setStrokeWidth(2);
canvas.drawText("shazeys", 20, 10, citePaint);
canvas.translate(0,radius);
citePaint.setColor(Color.RED);
canvas.drawText(getTimeStr(),15, 10, citePaint);
canvas.restore();//回复刚刚保存的位置

//画刻度
Paint tmpPaint = new Paint(mPaint);
tmpPaint.setStrokeWidth(2);
float y = radius;
int count = 60;//刻度总数
for (int i=0; i<count; i++){
if (i%5==0){
canvas.drawLine(0f,y,0,y-12f,mPaint);//每五个画一个大的刻度
}else{
canvas.drawLine(0f,y-7,0f,y,tmpPaint);//普通刻度
}
canvas.rotate(360/count,0f,0f);//旋转画布
}

//画中心的小圆和稍大的灰色小圆盘
tmpPaint.setColor(Color.GRAY);
tmpPaint.setStrokeWidth(4);
canvas.drawCircle(0,0,7,tmpPaint);
tmpPaint.setStyle(Paint.Style.FILL);
tmpPaint.setColor(mColor);
canvas.drawCircle(0,0,4,tmpPaint);

//画时针
canvas.save();
canvas.rotate((float) (mHour*30 + mMinutes*0.5));//根据时间计算时针的角度,旋转画布
canvas.drawLine(0,10,0,-(radius-100),mPaint);//画时针
//        canvas.rotate((float) -(mHour*30 + mMinutes*0.5));//将画布转回去
canvas.restore();
//画分针
canvas.save();
Paint miPaint = new Paint(mPaint);//新建画笔
miPaint.setColor(Color.DKGRAY);//设置颜色
miPaint.setStyle(Paint.Style.FILL);
miPaint.setStrokeWidth(3);
canvas.rotate(mMinutes*6);//计算分针的角度并旋转画布
canvas.drawLine(0,15,0,-(radius-50),miPaint);//画分针
//        canvas.rotate(-mMinutes*6);//将画布转回去
canvas.restore();
//画秒针的点
canvas.save();
miPaint.setColor(Color.RED);//改变画笔颜色
canvas.rotate(mSecond*6);//秒针旋转角度
canvas.drawCircle(0,-y,5,miPaint);
//canvas.rotate(-mSecond*6);//转回去,最后一步了,可以不恢复
canvas.restore();
}

//用Handler实现计时器
final Handler mUpdateTimeHandler = new Handler(){
@Override
public void handleMessage(Message msg) {
switch (msg.what){
case MSG_UPDATE_TIME:
invalidate();
long time = System.currentTimeMillis();
mHour = (time/1000/60/60 + 8) % 12;
mMinutes = time/1000/60 % 60;
mSecond = time/1000 % 60;
this.sendEmptyMessageDelayed(MSG_UPDATE_TIME,1000);
Log.i("TIME",mHour+":"+mMinutes+":"+mSecond);
break;
}
}
};
//获取时间字符串
private String getTimeStr(){
return timeFormat(mHour)+":"+timeFormat(mMinutes)+":"+timeFormat(mSecond);
}
//格式化时间值
private String timeFormat(float value){
return value>9 ? (int)value+"" : "0"+(int)value;
}
public void start(){
mUpdateTimeHandler.sendEmptyMessageAtTime(MSG_UPDATE_TIME,0);
}

public void stop(){
mUpdateTimeHandler.removeMessages(MSG_UPDATE_TIME);
}
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: