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

        书接上回: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);
    }
}







评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

公贵买其鹿

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值