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

自定义view————Android上的劳力士

2015-12-02 17:08 176 查看
来来来,先看看效果。




记得在学校学习的时候也写过一个C语言的手表,不过指针好像一直转不准,想想还是当年太年轻~囧~~

最近在研究canvas,看到某人写的例子里面也有只手表,今天便花了点时间自己重新写了一个,当是练手了。

例子本身没什么难的,初学者应该都看得懂。

首先拆分这个view:

手表的刻度和整点时刻是不变的,可以通过view的宽高直接画在中心

指针单独拿出来画,因为要计算角度

所以onDraw里面的内容是这样:

[code]@Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        this.mTotalWidth = getMeasuredWidth() - getPaddingLeft() - getPaddingRight();
        this.mTotalHeight = getMeasuredHeight() - getPaddingBottom() - getPaddingTop();

        int smallerSize = this.mTotalWidth < this.mTotalHeight ? this.mTotalWidth : mTotalHeight;
        mStandard = smallerSize >> 1;

        //将画布移至中心
        canvas.translate(getPaddingLeft() + this.mTotalWidth >> 1, getPaddingTop() + this.mTotalHeight >> 1);

        drawAppearance(canvas);

        drawTime(canvas);

        if (!mIsStop) {

            postInvalidateDelayed(INTERVAL_REFRESH);
        }
    }


第5、6行是为了计算padding;因为手表是圆形,所以表应该是画在一个长宽均为表的直径的正方形内,所以第8行取了view长度、宽度中的一个较小值。

同时为了保证view的扩展性,手表的半径、指针的长度等都没有写固定值,而是根据view 的大小按比例来计算的(mStandard即作为整个view其他部分的长度参考),所以你就能看到上面那个gif中有三种不同尺寸的大小,除了我改了下整点时刻的字体大小外,没有改过其他任何地方的一句代码。

如果你愿意,当然也可以把字体做成适配view大小的,或者干脆写一个属性,在布局文件中赋值然后再代码中获取。

第14行中,有个drawAppearance(canvas);方法,主要是画表的刻度等。

[code]    /**
     * 画外观,包括刻度和整点时刻的文字
     */
    private void drawAppearance(Canvas canvas) {

        //表的半径为标准长度值的0.8,留点空隙
        float radius = mStandard * 0.8f;

        //只描边,不填充
        mPaint.setStyle(Paint.Style.STROKE);
        //画表的大圆
        canvas.drawCircle(0, 0, radius, mPaint);
        //画表的圆心
        canvas.drawCircle(0, 0, 5, mPaint);

        canvas.save();
        drawCalibration(canvas, radius);
        canvas.restore();
    }


可以看到,表的半径是上面那个mStandard值的0.8,第7行画表最外面的圆,第9行画圆心,12行开始画刻度。因为 drawCalibration(canvas, radius);方法中涉及到了canvas的旋转等操作,为了避免对后续的操作造成影响,所以在该方法的调用过后还原了那些操作,因此你可以看到有save、restore的调用。

画刻度的方法drawCalibration(canvas, radius);代码如下:

[code]/**
     * 画刻度
     */
    private void drawCalibration(Canvas canvas, float radius) {

        //整点时刻文字的大小
        mPaint.setTextSize(DensityUtil.sp2px(mContext, TIME_NUMBER_SIZE));
        //使文字居中
        mPaint.setTextAlign(Paint.Align.CENTER);

        int calibrationHour = DensityUtil.dp2px(mContext, CALIBRATION_LENGTH_HOUR);
        int calibrationMinute = DensityUtil.dp2px(mContext, CALIBRATION_LENGTH_MINUTE);
        int diver = DensityUtil.dp2px(mContext, CALIBRATION_TEXT_DIVER);

        //使表的12点在正上方
        canvas.rotate(210);

        for (int i = 0; i < 60; i++) {

            int length = calibrationMinute;

            if (i % 5 == 0) {

                length = calibrationHour;
                canvas.drawText(i / 5 + 1 + "", 0, radius - CALIBRATION_LENGTH_HOUR - diver, mPaint);
            }
            canvas.drawLine(0, radius, 0, radius - length, mPaint);
            canvas.rotate(6, 0, 0);
        }
    }


可以看到第7行使用了工具类,将sp转成px。做过自定义控件的朋友应该都知道,直接写数值是不科学的,这样在不同屏幕上面的显示效果很差,无法适配,应该使用sp转换成px的方式来设置字体的大小。dp同理,其实和布局文件中使用dp、sp是一个道理。

下面画刻度的过程就是每次旋转画布6°,重复60次的过程。

类似于旋转等的这些操作就像是虽然你的笔还是原来的地方画,但是画纸旋转或者移动了,所以你画的东西的方位就产生变化。

对于后面的过程,聪明的你想必已经无需我多做解释了。

画完刻度画指针,来看onDraw方法中的drawTime:

[code]    private void drawTime(final Canvas canvas) {

        Calendar calendar = Calendar.getInstance();

        canvas.save();

        drawPointer(canvas, calendar);
        drawDigitalTime(canvas, calendar);

        canvas.restore();
    }


这里有一个画指针时间和一个画数字时间的,先看drawPointer(canvas, calendar);

[code]/**
     * 画指针
     */
    private void drawPointer(Canvas canvas, Calendar calendar) {

        canvas.save();
        canvas.rotate(270);

        int hour = calendar.get(Calendar.HOUR);
        int minute = calendar.get(Calendar.MINUTE);
        int second = calendar.get(Calendar.SECOND);

        //每小时所占的角度为30度
        float degree = (hour + minute / 60.0f) * 30;
        canvas.save();
        canvas.rotate(degree);
        canvas.drawLine(0, 0, mStandard * POINTER_LENGTH_HOUR, 0, mPaint);
        canvas.restore();

        //每分钟所占的角度为6度
        degree = (minute + second / 60.0f) * 6;
        canvas.save();
        canvas.rotate(degree);
        canvas.drawLine(0, 0, mStandard * POINTER_LENGTH_MINUTE, 0, mPaint);
        canvas.restore();

        //每秒钟所占的角度为6度
        degree = second * 6;
        canvas.save();
        canvas.rotate(degree);
        canvas.drawLine(0, 0, mStandard * POINTER_LENGTH_SECOND, 0, mPaint);
        canvas.restore();

        canvas.restore();
    }


第7行旋转了270°,为什么是270这个是试出来的,没仔细研究,有时间再看。

其他注释都写得很多了,没什么好说,就是每次旋转画完一个指针之后记得还原旋转的操作了,免得你转了几次之后自己都晕了。

下面是drawDigitalTime方法:

[code]/**
     * 画当前时间的数字显示
     */
    private void drawDigitalTime(Canvas canvas, Calendar calendar) {

        int hour = calendar.get(Calendar.HOUR_OF_DAY);
        int minute = calendar.get(Calendar.MINUTE);

        String time = String.format("北京时间:%s:%s", hour, minute);
        Rect bound = new Rect();
        mPaint.getTextBounds(time, 0, time.length(), bound);
        if (mStandard > bound.width()) {

            canvas.drawText(time, 0, mTotalHeight >> 3, mPaint);
        }
    }


画数字时间之前先计算下字符串的宽度,如果宽度值比上面那个mStandard还大,那就不画那个数字时间了。

最后是view界面的刷新问题,一开始考虑在画指针的时候写个while循环然后每个多少秒刷新,但是会造成ui阻塞以及可能重叠等问题,后来想想其实直接调用invalidate就可以了,同时为了避免过度刷新,加了一个延迟刷新的操作,所以onDraw方法中18~21行中可以看到:

[code]        if (!mIsStop) {

            postInvalidateDelayed(INTERVAL_REFRESH);
        }


代码在这里

end.
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: