自定义View-酷炫时钟

来源:互联网 发布:百度数据 编辑:程序博客网 时间:2024/05/18 02:13

最近闲来无事,又帮别人写自定义View了,这次写的是一个时钟的效果,根据别人给的截图最终的结果如下图

说一下别人给的需求,如上图,时钟带时针,分针(给的需求就是这样的,只要时针和分针,在这个基础上我另外赠送了一个秒针走秒的效果,不然不像个时钟)这个就是实现基本的时钟效果,外加一个会议时间段的显示就是橙色的部分从早上8点到晚上18点之间,时间段不定,如上图是3个时间段.效果就是这样,给的几个切图如下

1.表盘切图


2.时针切图


3.分针切图


4.表盘中心覆盖切图


接下来的一个步骤非常重要就是打开photoshop,我们调整一下第一个切图,把表盘的中心点调整为图片宽度的一半位置,首先在PS里面拉一条辅助线,把辅助线定位在表盘中心点的位置,这个要细心一点慢慢定位,都是为了后面好处理,如下图


上面的步骤要心细一点不要有偏差,然后利用裁剪工具,把裁剪工具的中心点放在辅助线上,如下图


是不是感觉很麻烦啊?哈哈,因为我拿到切图的时候就觉得很郁闷这个美工真的切的一手"好图",前面的两个步骤都是为了能很好的找到这个表盘的中心点坐标,因为这个中心点很重要,到目前为止我们找到了X轴中心点,现在我们要找到Y轴中线点,我们继续,上面我们拉的是一条纵向辅助线,现在我们拉一条横向辅助线,并且使用PS的标尺工具测量一下中心点到边的距离,如下图


如上图箭头部分我们就得到了中心点Y坐标的值,但是我们不是使用这个Y值而是算一个比值,就是Y值/图片高度,这里我不过多做解释,想想就能明白了 .记录一下这个比值,我们就开工了,新建Android Studio工程ViewTest,然后新建一个ClockView继承于View

我们把上面计算好的比值定义一下

private static final float CLOCK_CENTER_RATIO = 0.320f;
接下来申明需要的Bitmap
private Bitmap clockBitmap;//表盘背景 private Bitmap hourPointerBitmap;//小时指针 private Bitmap minPointerBitmap;//分钟指针 private Bitmap coverBitmap;//中间小圆点
并且在init方法里面拿到实例

clockBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.image_door5_);        hourPointerBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.image_door6);        minPointerBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.image_door7);        coverBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.image_door8);
我们开始要画图了,到了这里我们要考虑一下onmeasure方法了,你们觉得这个onmeasure方法应该怎么写呢?我这里投机取巧了,用的就是最大的那个图片也就是切图1的宽高就是控件的宽高,不过你们仔细想想这样还是比较合理的看一下onmeasure方法

@Override    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {        super.onMeasure(widthMeasureSpec, heightMeasureSpec);        setMeasuredDimension(width, height);    }

写好了onmeasure,我们开始画个表盘看看,新建drawClock

 private void drawClock(Canvas canvas) {        canvas.drawBitmap(clockBitmap, 0, 0, null);    }
跑一下看看???哈哈


这里我把activity设置了无title全屏模式,背景白色.控件放在activity 的中心点,这里不是重点就不多说什么了.怎样效果还不错吧?接下来,我们开始画时针和分针了,这里问题就来了,我们的时针和分针都是图片,图片要绘制到画布上,用的是drawBitmap方法,但是canvas绘制Bitmap的时候是不能旋转的,但是我们的指针是转动的啊!!!!!怎么办呢?这里我们就要通过旋转画布来解决问题 了,具体画布的旋转的API的原理我就不说了,就说怎么实现这个效果就行,我们拿时针来实现,我们新建drawHourPointer方法,代码如下

private void drawHourPointer(Canvas canvas) {        float d = 0;        canvas.save();        canvas.translate(getWidth() / 2, getHeight() * CLOCK_CENTER_RATIO);        canvas.rotate(d);        canvas.drawBitmap(hourPointerBitmap, 0, -hourPointerBitmap.getHeight() / 2, null);        canvas.restore();    }
我们这里把度数设置成了0度,看下效果


嗯嗯,感觉还不错哈,我们试试改成任意度数,来个170度


不错不错就是我们需要的效果对吧?接下来我们把度数计算抽成一个方法,小时的度数=(小时数+分钟数/60.0f)*每小时数度数,这里呢,数学不好的好好想想就行了,看下度数计算的方法

 private float hour2Degrees(int hour, int min) {        hour = hour % 12;        return DEGREEE_PER_HOUR * (hour + min / 60.0f);    }
到这里我们顺便把分针度数的计算一起完成

 private float min2Degrees(int min, int sec) {        return DEGREEE_PER_MIN * (min + sec / 60.0f);    }
还有秒针的

 private float sec2Degree(int sec) {        return DEGREEE_PER_MIN * sec;    }
接下来我们需要申明一个Calendar对象并且获取实例

private Calendar calendar;
  calendar = Calendar.getInstance();
我们改造一下drawHourPointer方法,为了看到效果证明和系统时间是一致的,我把activity不设置为全屏colorPrimaryDark设置为白色,看下效果


按照同样的实现方式,我们来把分针实现,新建drawMinPointer方法

private void drawMinPointer(Canvas canvas) {        float d = min2Degrees(calendar.get(Calendar.MINUTE), calendar.get(Calendar.SECOND));        canvas.save();        canvas.translate(getWidth() / 2, getHeight() * CLOCK_CENTER_RATIO);        canvas.rotate(d - 90);        canvas.drawBitmap(minPointerBitmap, 0, -minPointerBitmap.getHeight() / 2, null);        canvas.restore();    }
然后看下效果


怎么样效果还可以吧?时间显示也是正确的,最后我们实现一个比较难的秒针效果,不过呢,我们按照上面的实现方法,把秒针画出来,秒针用的是画线段的方法实现的,看下代码

private void drawSecPointer(Canvas canvas) {        canvas.save();        canvas.translate(getWidth() / 2, getHeight() * CLOCK_CENTER_RATIO);        canvas.rotate(secDegree - 90);        canvas.drawLine(0, -2, minPointerBitmap.getWidth(), 2, secPaint);        canvas.restore();    }
然后看下效果吧


还不错吧?接下来实现秒针的走秒效果,是滴答滴答的很有节奏感的,到这里我不知道你们会有什么实现方法,但是应该和我想的差不多就是用动画来做,我这里用了一个ValueAnimator实现的,直接看代码吧,一看代码就知道怎么回事了 

valueAnimator = ValueAnimator.ofFloat(0f, 6f);        valueAnimator.setDuration(1000);        valueAnimator.setRepeatCount(-1);        valueAnimator.setInterpolator(new BounceInterpolator());        valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {            @Override            public void onAnimationUpdate(ValueAnimator animation) {                float avalue = (float) animation.getAnimatedValue();                secDegree = sec2Degree(calendar.get(Calendar.SECOND)) + avalue;                postInvalidate();            }        });        valueAnimator.addListener(new Animator.AnimatorListener() {            @Override            public void onAnimationStart(Animator animation) {            }            @Override            public void onAnimationEnd(Animator animation) {            }            @Override            public void onAnimationCancel(Animator animation) {            }            @Override            public void onAnimationRepeat(Animator animation) {                calendar.setTime(new Date());            }        });
一秒钟对应的度数是6度,所以Value的取值在0-6之间,时间是一秒,循环次数-1的意思就是永久循环,,秒针的度数需要申明为全局变量,所以这里申明一个全局变量

 private float secDegree;
我们给动画添加了值变化监听,并且在onAnimationUpdate里面不断的设置secDegree 的值,不断的postinvalidate进行绘制.信息的人会发现我们这里添加了一个弹性插值器,为什么要添加这个呢?因为弹性插值器的效果特别进阶现实生活中时钟的走秒效果.最后在每次动画重复的时候重新设置calendar 的时间,我们看下效果


怎么样是不是很不错呢?我们最后把中心圆圈的切图画上就行了,添加一个方法

private void drawCover(Canvas canvas) {        canvas.drawBitmap(coverBitmap, getWidth() / 2 - coverBitmap.getWidth() / 2, getHeight() * CLOCK_CENTER_RATIO_ - coverBitmap.getHeight() / 2, null);    }
看看加上后的效果图


到这里其实是种的效果已经全部实现好了,最后我们要实现的就是时间段显示的问题,就像刚开始的完成图那样时钟的外圈有橙色的圆环部分,首先 两个bean类

public class TimeDuration {    private int start;    private int end;    public TimeDuration() {    }    public TimeDuration(int start, int end) {        this.start = start;        this.end = end;    }    public int getStart() {        return start;    }    public void setStart(int start) {        this.start = start;    }    public int getEnd() {        return end;    }    public void setEnd(int end) {        this.end = end;    }}
public class TimeDurationDegree {    private float startDegree;    private float sweepDegree;    public float getStartDegree() {        return startDegree;    }    public void setStartDegree(float startDegree) {        this.startDegree = startDegree;    }    public float getSweepDegree() {        return sweepDegree;    }    public void setSweepDegree(float sweepDegree) {        this.sweepDegree = sweepDegree;    }    public TimeDurationDegree() {    }    public TimeDurationDegree(float startDegree, float sweepDegree) {        this.startDegree = startDegree;        this.sweepDegree = sweepDegree;    }}
bean类很简单,TimeDuration就是每一个小时间段的起点和终点时间,TimeDurationDegree是转换后的起始度数和扫过的度数

然后申明一个列表

private ArrayList<TimeDurationDegree> degreeArrayList;
再申明一个设置时间段的方法

 public void setDurationList(List<TimeDuration> durationList) {        valueAnimator.pause();        degreeArrayList.clear();        for (TimeDuration timeDuration : durationList) {            TimeDurationDegree degree = new TimeDurationDegree();            degree.setStartDegree(timeDuration.getStart() * DEGREEE_PER_HOUR - 90);            degree.setSweepDegree((timeDuration.getEnd() - timeDuration.getStart()) * DEGREEE_PER_HOUR);            degreeArrayList.add(degree);        }        valueAnimator.start();    }

接着就是实现的圆环效果的方法了,我用的是drawarc方法实现的,具体代码如下

private void drawBgYellowCircle(Canvas canvas) {    if (degreeArrayList.size() > 0) {        canvas.save();        for (TimeDurationDegree degree : degreeArrayList) {            RectF rectF=new RectF(getWidth() / 2 - bgCircleRadius, 0, getWidth() / 2 + bgCircleRadius, bgCircleRadius * 2);            canvas.drawArc(rectF, degree.getStartDegree(), degree.getSweepDegree(), true, bgCirclePaint);        }        canvas.restore();    }}


随便设置几组数据看看效果

 ArrayList<TimeDuration> timeDurations = new ArrayList<>();        timeDurations.add(new TimeDuration(8, 10));        timeDurations.add(new TimeDuration(11, 12));        timeDurations.add(new TimeDuration(13, 16));        clockView.setDurationList(timeDurations);



到这里整个自定义时钟就已经全部完成了

下面贴上github代码地址

点击打开链接


 
原创粉丝点击