Android自定义View时钟效果

来源:互联网 发布:php get 参数隐藏 编辑:程序博客网 时间:2024/05/16 08:20

今天继续聊自定义View,当然今天的这个比较麻烦一些,如果没有自定义View的经历,建议先看看自定义文字View与水印图片View

自定义文字View

自定义水印图片View

前面的自定义文字View,图片View都属于比较简单的自定义View,今天玩点有难度的,当然目的也是为了更加熟悉自定义View的各个步骤与坐标的计算、画笔的各种属性等。话不多聊  ,我们今天实现下如下的效果:

首先进行简单的分析

钟表盘构成属性如下:

1、外部圆形边框(宽度、颜色)

2、内部一周的小黑点(宽度、颜色)

3、内部的1-12数字(字号、颜色)

4、时针分针和秒针(规格、颜色)

简单分析之后就能确定我们需要哪些的属性值,当然此处全部自定义属性,有些事没有必要的。

分析之后进行属性的自定义:

属性的自定义如下:

    <declare-styleable name="DemoClockView01">        <attr name="borderwidth" format="dimension"/>        <attr name="bordercolor" format="color"/>        <attr name="pointcolor" format="color"/>        <attr name="hourcolor" format="color"/>        <attr name="minutecolor" format="color"/>        <attr name="secondcolor" format="color"/>        <attr name="numsize" format="dimension"/>        <attr name="numcolor" format="color"/>    </declare-styleable>
其中的borderwidth和bordercolor为边框的宽度和颜色,pointcolor为一圈的小黑点的颜色,hourcolor、minutecolor和secondcolor为时针、分针和秒针的颜色,numsize和numcolor为一圈数字的字号大小和颜色。

属性自定义完成之后再Java代码中拿到相关的属性值:

代码如下:

   TypedArray array = context.getTheme().obtainStyledAttributes(attrs, R.styleable.DemoClockView01, defStyleAttr, 0);        for (int i = 0; i < array.getIndexCount(); i++) {//用getIndexCount   减少循环次数,提高性能   用.length也不能执行所有的case情况            int attr = array.getIndex(i);            switch (attr) {                case R.styleable.DemoClockView01_borderwidth://边框宽度                    mBorderWidth = array.getDimensionPixelSize(attr, (int) TypedValue.applyDimension(                            TypedValue.COMPLEX_UNIT_DIP, 8, getResources().getDisplayMetrics()));                    break;                case R.styleable.DemoClockView01_bordercolor://边框颜色                    mBorderColor = array.getColor(attr, Color.BLACK);                    break;                case R.styleable.DemoClockView01_numcolor://数字颜色                    mNumColor = array.getColor(attr, Color.BLACK);                    break;                case R.styleable.DemoClockView01_numsize://数字字号                    mNumSize = array.getDimensionPixelSize(attr, (int) TypedValue.applyDimension(                            TypedValue.COMPLEX_UNIT_SP, 16, getResources().getDisplayMetrics()));                    break;                case R.styleable.DemoClockView01_pointcolor://周围小点颜色                    mPointColor = array.getColor(attr, Color.BLACK);                    break;                case R.styleable.DemoClockView01_hourcolor://时针颜色                    mHourColor = array.getColor(attr, Color.BLACK);                    break;                case R.styleable.DemoClockView01_minutecolor://分针颜色                    mMinuteColor = array.getColor(attr, Color.BLACK);                    break;                case R.styleable.DemoClockView01_secondcolor://秒针颜色                    mSecondColor = array.getColor(attr, Color.BLACK);                    break;            }        }        array.recycle();
拿到相关的属性之后进行控件宽和高的测量:

代码如下:

   @Override    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {        int widthMode = MeasureSpec.getMode(widthMeasureSpec);        int widthSize = MeasureSpec.getSize(widthMeasureSpec);        int heightMode = MeasureSpec.getMode(heightMeasureSpec);        int heightSize = MeasureSpec.getSize(heightMeasureSpec);        if (widthMode == MeasureSpec.EXACTLY) {            mWidth = widthSize;        } else {            int desire = getPaddingLeft() + getPaddingRight() + (int) TypedValue.applyDimension(                    TypedValue.COMPLEX_UNIT_DIP, 200, getResources().getDisplayMetrics());            mWidth = Math.min(desire, widthSize);        }        if (heightMode == MeasureSpec.EXACTLY) {            mHeight = heightSize;        } else {            int desire = getPaddingTop() + getPaddingBottom() + (int) TypedValue.applyDimension(                    TypedValue.COMPLEX_UNIT_DIP, 200, getResources().getDisplayMetrics());            mHeight = Math.min(desire, heightSize);        }        mWidth = Math.min(mWidth, mHeight);//取最小值  防止绘制内容出错   以最小的边来为基准进行相关的绘制        setMeasuredDimension(mWidth, mWidth);    }
测量完成之后进行最后的绘制,从外到内一个一个来绘制:

1、首先是外部边框,圆形,计算圆心及半径,绘制如下:

 /**         * 圆心的xy和圆环的宽度         */        final int cx, cy, width;        cx = getPaddingLeft() + (getMeasuredWidth() - getPaddingLeft() - getPaddingRight()) / 2;        cy = getPaddingTop() + (getMeasuredHeight() - getPaddingTop() - getPaddingBottom()) / 2;        width = Math.min(getWidth() / 2, getHeight() / 2);//半径        mPaint.setAntiAlias(true);//去除边缘锯齿,优化绘制效果        mPaint.setColor(mBorderColor);        if (mBorderColor == 0){            mPaint.setColor(Color.BLACK);        }        canvas.drawCircle(cx, cy, width, mPaint);//外圆  红色        mPaint.setColor(Color.WHITE);        canvas.drawCircle(cx, cy, width - mBorderWidth, mPaint);//内圆 白色
此步骤完成之后在布局文件饮用控件即可看到外部的圆环效果,如下图:

2、周围小黑点的绘制:

 mPaint.setColor(mPointColor);        if (mPointColor == 0) {            mPaint.setColor(Color.BLACK);        }        canvas.save();//保存当前的状态        for (int i = 0; i < 60; i++) {//总共60个点  所以绘制60次  //绘制一圈的小黑点            if (i % 5 == 0) {                canvas.drawRect(cx - TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 2, getResources().getDisplayMetrics()),                        getPaddingTop() + mBorderWidth + TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 3, getResources().getDisplayMetrics()),                        cx + TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 2, getResources().getDisplayMetrics()),                        getPaddingTop() + mBorderWidth + TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 15, getResources().getDisplayMetrics()), mPaint);            } else {                canvas.drawRect(cx - TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 1, getResources().getDisplayMetrics()),                        getPaddingTop() + mBorderWidth + TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 3, getResources().getDisplayMetrics()),                        cx + TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 1, getResources().getDisplayMetrics()),                        getPaddingTop() + mBorderWidth + TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 10, getResources().getDisplayMetrics()), mPaint);            }            canvas.rotate(6, cx, cy);//360度  绘制60次   每次旋转6度        }        canvas.restore();//将canvas转回来
此步骤绘制完成之后即可看到圆环加上小圆点的效果,效果如下图:

3、数字的绘制(此处绘制的数字可能旋转了,因为绘制的时候是旋转画布绘制的,当然也可以计算每个数字点的坐标进行相关的绘制)

mPaint.setColor(mNumColor);        if (mNumColor == 0) {            mPaint.setColor(Color.BLACK);        }        mPaint.setTextSize(mNumSize);        if (mNumSize == 0) {            mPaint.setTextSize((int) TypedValue.applyDimension(                    TypedValue.COMPLEX_UNIT_SP, 16, getResources().getDisplayMetrics()));        }        mPaint.setTextSize(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 14, getResources().getDisplayMetrics()));        String[] strs = new String[]{"12", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11",};//绘制数字1-12  (数字角度不对  可以进行相关的处理)        Rect rect = new Rect();        canvas.save();        for (int i = 0; i < 12; i++) {//绘制12次  每次旋转30度            mPaint.getTextBounds(strs[i], 0, strs[i].length(), rect);            canvas.drawText(strs[i], cx - rect.width() / 2,                    getPaddingTop() + mBorderWidth + TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 18, getResources().getDisplayMetrics()) + rect.height(), mPaint);            canvas.rotate(30, cx, cy);        }        canvas.restore();
此处绘制完成即可看到圆环、黑点和数字效果如下图:

4、时针、分针、秒针和圆心的绘制:

  mPaint.setColor(mHourColor);        if (mHourColor == 0) {            mPaint.setColor(Color.BLACK);        }        canvas.save();//绘制时针        canvas.drawRect(cx - TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 3, getResources().getDisplayMetrics()),                getPaddingTop() + mBorderWidth + TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 20, getResources().getDisplayMetrics()) + rect.width(),                cx + TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 3, getResources().getDisplayMetrics()),                cy, mPaint);        canvas.restore();        mPaint.setColor(mMinuteColor);        if (mMinuteColor == 0) {            mPaint.setColor(Color.BLACK);        }        canvas.save();//保存后面的状态        canvas.rotate(60, cx, cy);        canvas.drawRect(cx - TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 2, getResources().getDisplayMetrics()),                getPaddingTop() + mBorderWidth + TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 25, getResources().getDisplayMetrics()),                cx + TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 2, getResources().getDisplayMetrics()),                cy, mPaint);        canvas.restore();//撤销保存的状态        mPaint.setColor(mSecondColor);        if (mSecondColor == 0) {            mPaint.setColor(Color.BLACK);        }        canvas.save();        mPaint.setColor(Color.RED);        canvas.rotate(120, cx, cy);        canvas.drawRect(cx - TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 1, getResources().getDisplayMetrics()),                getPaddingTop() + mBorderWidth + TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 25, getResources().getDisplayMetrics()),                cx + TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 1, getResources().getDisplayMetrics()),                cy, mPaint);        canvas.restore();        mPaint.setColor(Color.RED);        canvas.drawCircle(cx, cy, TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 6, getResources().getDisplayMetrics()), mPaint);//圆心,红色
此时完成即可看到完整的钟表盘效果。

如下图:

静态时钟功能到此实现。

下面呢的任务就是让其动起来:

           1)时间任务,每隔1s绘制界面。

在构造方法中启动时间任务,如下:

  Timer timer = new Timer("绘制线程");        timer.schedule(new TimerTask() {            @Override            public void run() {                         }        }, 0, 1000);

2)计算每个时刻的对应的时针、分针、及秒针的角度。

在构造方法初始化时间如下(这里的时间类使用Calendar类):

 mCalendar = Calendar.getInstance();

在onDraw方法中进行时间及角度的计算:

   //关于当前时间的计算,默认为当前时间  当然是可以设置的        int hour = mCalendar.get(Calendar.HOUR);//HOUR    进制为12小时   HOUR_OF_DAY  为24小时        int minute = mCalendar.get(Calendar.MINUTE);//分钟        int second = mCalendar.get(Calendar.SECOND) + 1;//秒数        if (second == 60) {            minute += 1;            second = 0;        }        if (minute == 60){            hour += 1;            minute = 0;        }        if (hour == 12){            hour = 0;        }        mCalendar.set(Calendar.SECOND, second);        mCalendar.set(Calendar.MINUTE, minute);        mCalendar.set(Calendar.HOUR, hour);        float hourDegree = 360 * hour / 12 + 360 / 12 * minute / 60;//时针转动的角度   小时对应角度  加上  分钟对应角度   秒针忽略        float minuteDegree = 360 * minute / 60 + 360 / 60 * second / 60;//分针转动的角度   分针对应角度  加上  秒数对应角度        float secondDegree = 360 * second / 60;// 秒数对应角度

3)子线程、UI线程的切换绘制。

Handler进行子线程到子线程的转换。

最终的实现效果如下,也可以自己进行时间的设置:

  

到此效果实现。

Demo下载

git地址:https://github.com/SnowJun/OwnView

2 0
原创粉丝点击