自定义View之模仿AppleWatch手表

转载请注明地址:https://blog.csdn.net/lynnchurch/article/details/99351807

最近想自定义View写个手表,然后在网上看到AppleWatch的一个手表界面很简洁漂亮,那就用自定义View来模仿实现它吧。

文章目录

    • 一、自定义属性
    • 二、构造方法
    • 三、解析自定义属性
    • 四、初始化画笔
    • 五、测量大小
    • 六、获取最终的尺寸
    • 七、绘制
    • 八、绘制表盘
    • 九、绘制刻度
    • 十、绘制外圈数字
    • 十一、绘制内圈数字
    • 十二、绘制指针
    • 十三、绘制时针
    • 十四、绘制分针
    • 十五、绘制秒针
    • 十六、源码地址

先来看看我们要实现的AppleWatch手表界面:
自定义View之模仿AppleWatch手表_第1张图片

以及我用自定义View实现的效果图:
自定义View之模仿AppleWatch手表_第2张图片
感觉如何,还是有几分神似吧。“No bb, show me your code.”,下面就用代码来说一下它的实现过程。

一、自定义属性

自定义属性如下:

    <declare-styleable name="WatchView">
        <attr name="hour_pointer_color" format="color" />
        <attr name="minute_pointer_color" format="color" />
        <attr name="second_pointer_color" format="color" />
        <attr name="twelve_color" format="color" />
        <attr name="sixty_color" format="color" />
        <attr name="scale_color" format="color" />
        <attr name="android:padding" />
        <attr name="android:background" />
    declare-styleable>

其他自定义属性也可以根据自己的需要去添加,android:paddingandroid:background用到了系统属性,其中android:background方便我们在没有设置背景的时候设置一个默认值。

二、构造方法

    public WatchView(Context context) {
        this(context, null, 0);
    }

    public WatchView(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public WatchView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        int widthPixels = context.getResources().getDisplayMetrics().widthPixels;
        int heightPixels = context.getResources().getDisplayMetrics().heightPixels;
        mScreenWidth = Math.min(widthPixels, heightPixels);
        parseAttrs(context, attrs, defStyleAttr);
        initPaint();
    }

三、解析自定义属性

    private void parseAttrs(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.WatchView, defStyleAttr, 0);
        mHourPointerColor = a.getColor(R.styleable.WatchView_hour_pointer_color, Color.WHITE);
        mMinutePointerColor = a.getColor(R.styleable.WatchView_minute_pointer_color, Color.WHITE);
        mSecondPointerColor = a.getColor(R.styleable.WatchView_second_pointer_color, Color.argb(222, 181, 140, 78));
        mTwelveColor = a.getColor(R.styleable.WatchView_twelve_color, Color.WHITE);
        mSixtyColor = a.getColor(R.styleable.WatchView_sixty_color, Color.WHITE);
        mScaleColor = a.getColor(R.styleable.WatchView_scale_color, Color.argb(122, 255, 255, 255));
        mPadding = a.getDimension(R.styleable.WatchView_android_padding, Utils.dip2px(context, 16));
        mBackground = a.getColor(R.styleable.WatchView_android_background, Color.BLACK);
        setBackgroundColor(mBackground);
        a.recycle();
    }

四、初始化画笔

    private void initPaint() {
        mPaint = new Paint();
        mPaint.setAntiAlias(true);
        mPaint.setDither(true);
        mPaintFlagsDrawFilter = new PaintFlagsDrawFilter(0, Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG);
    }

五、测量大小

在这里重写onMeasure方法,如果有设置宽度就采用设置的宽度,否则就采用屏幕的宽度,并且强制宽高相等。

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int size = MeasureSpec.getSize(widthMeasureSpec);
        if (size > 0) {
            size = Math.min(size, mScreenWidth);
        } else {
            size = mScreenWidth;
        }
        setMeasuredDimension(size, size);
    }

六、获取最终的尺寸

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        mSize = w;
        mTwelveTextSize = (w - 2 * mPadding) / 8f;
        mSixtyTextSize = (w - 2 * mPadding) / 20f;
        mScaleStrokeWidth = w / 156f;
        mScaleLength = w / 26f;
        mHourMinutePointerStrokeWidth = w / 30f;
        mCentrePointX = w / 2f;
        mCentrePointY = w / 2f;
    }

七、绘制

    @Override
    protected void onDraw(Canvas canvas) {
        canvas.setDrawFilter(mPaintFlagsDrawFilter);
        // 绘制表盘
        drawDial(canvas);
        // 绘制指针
        drawPointer(canvas);
    }

八、绘制表盘

    /**
     * 绘制表盘
     *
     * @param canvas
     */
    private void drawDial(Canvas canvas) {
        // 绘制刻度
        drawScale(canvas);

        // 绘制外圈数字
        drawOuterNumber(canvas);

        // 绘制内圈数字
        drawInterNumber(canvas);
    }

九、绘制刻度

    /**
     * 绘制刻度
     *
     * @param canvas
     */
    private void drawScale(Canvas canvas) {
        mPaint.setStrokeWidth(mScaleStrokeWidth);
        mPaint.setStrokeCap(Paint.Cap.ROUND);
        mPaint.setColor(mScaleColor);
        for (int i = 60; i >= 1; i--) {
            if (i % 5 != 0) {
                canvas.drawLine(mSize / 2, mPadding, mSize / 2, mPadding + mScaleLength, mPaint);
            }
            canvas.rotate(-6, mSize / 2, mSize / 2);
        }
    }

十、绘制外圈数字

    /**
     * 绘制外圈数字
     *
     * @param canvas
     */
    private void drawOuterNumber(Canvas canvas) {
        mPaint.setColor(mSixtyColor);
        mPaint.setTextSize(mSixtyTextSize);
        String numberText;
        Rect textBound = new Rect();
        // 外圈半径
        float outerRadius = mSize / 2f - mPadding - mScaleLength / 2f;
        // 外圈绘制坐标
        float outerX;
        float outerY;
        for (int i = 5; i <= 60; i++) {
            if (i % 5 != 0) {
                continue;
            }
            // 将角度转换成弧度值
            double radians = i * 6f / 360 * 2 * Math.PI;
            // 测量文本区域
            numberText = mSixtyFormat.format(i);
            mPaint.getTextBounds(numberText, 0, numberText.length(), textBound);
            // 文字是以左下角为起点进行绘制的,为了保证文字的中心点位于圆圈上所以得进行位移
            outerX = mCentrePointX + outerRadius * (float) Math.sin(radians) - textBound.width() / 2f;
            outerY = mCentrePointY - outerRadius * (float) Math.cos(radians) + textBound.height() / 2f;
            canvas.drawText(numberText, outerX, outerY, mPaint);
        }
    }

十一、绘制内圈数字

    /**
     * 绘制内圈数字
     *
     * @param canvas
     */
    private void drawInterNumber(Canvas canvas) {
        mPaint.setColor(mTwelveColor);
        mPaint.setTextSize(mTwelveTextSize);
        String numberText;
        Rect textBound = new Rect();
        // 内圈半径
        float interRadius = mSize / 2f - mPadding - 1.6f * mScaleLength - mTwelveTextSize / 2f;
        // 内圈绘制坐标
        float interX;
        float interY;
        for (int i = 1; i <= 12; i++) {
            // 将角度转换成弧度值
            double radians = i * 30f / 360 * 2 * Math.PI;
            // 测量文本区域
            numberText = String.valueOf(i);
            mPaint.getTextBounds(numberText, 0, numberText.length(), textBound);
            // 文字是以左下角为起点进行绘制的,为了保证文字的中心点位于圆圈上所以得进行位移
            interX = mCentrePointX + interRadius * (float) Math.sin(radians) - textBound.width() / 2f;
            interY = mCentrePointY - interRadius * (float) Math.cos(radians) + textBound.height() / 2f;
            canvas.drawText(numberText, interX, interY, mPaint);
        }
    }

十二、绘制指针

    /**
     * 绘制指针
     *
     * @param canvas
     */
    private void drawPointer(Canvas canvas) {
        mCalendar.setTimeInMillis(System.currentTimeMillis());
        // 绘制时针
        drawHourPointer(canvas);
        // 绘制分针
        drawMinutePointer(canvas);
        // 绘制秒针
        drawSecondPointer(canvas);

        // 每隔1秒刷新一次
        postInvalidateDelayed(1000);
    }

十三、绘制时针

    /**
     * 绘制时针
     *
     * @param canvas
     */
    private void drawHourPointer(Canvas canvas) {
        int hour = mCalendar.get(Calendar.HOUR);
        int minute = mCalendar.get(Calendar.MINUTE);
        int second = mCalendar.get(Calendar.SECOND);
        mPaint.setColor(mHourPointerColor);
        mPaint.setStrokeWidth(mHourMinutePointerStrokeWidth / 2.6f);
        mPaint.setStrokeCap(Paint.Cap.ROUND);
        // 时针转过的角度
        float hourAngle = (hour + minute / 60f + second / 3600f) * 360 / 12f;
        double hourRadians = hourAngle / 360 * 2 * Math.PI;
        // 绘制指针细的一端
        float thinPointerLength = mSize / 15f;
        float thinPointerX = mCentrePointX + thinPointerLength * (float) Math.sin(hourRadians);
        float thinPointerY = mCentrePointY - thinPointerLength * (float) Math.cos(hourRadians);
        canvas.drawLine(mCentrePointX, mCentrePointY, thinPointerX, thinPointerY, mPaint);
        // 绘制指针粗的一端
        float thickPointerLength = mSize / 5f;
        float thickPointerX = mCentrePointX + thickPointerLength * (float) Math.sin(hourRadians);
        float thickPointerY = mCentrePointY - thickPointerLength * (float) Math.cos(hourRadians);
        mPaint.setStrokeWidth(mHourMinutePointerStrokeWidth);
        canvas.drawLine(thinPointerX, thinPointerY, thickPointerX, thickPointerY, mPaint);
        // 绘制圆心点
        canvas.drawCircle(mCentrePointX, mCentrePointY, mHourMinutePointerStrokeWidth / 2, mPaint);
    }

十四、绘制分针

    /**
     * 绘制分针
     *
     * @param canvas
     */
    private void drawMinutePointer(Canvas canvas) {
        int minute = mCalendar.get(Calendar.MINUTE);
        int second = mCalendar.get(Calendar.SECOND);
        mPaint.setColor(mMinutePointerColor);
        mPaint.setStrokeWidth(mHourMinutePointerStrokeWidth / 2.6f);
        mPaint.setStrokeCap(Paint.Cap.ROUND);
        // 分针转过的角度
        float minuteAngle = (minute + second / 60f) * 360 / 60f;
        double minuteRadians = minuteAngle / 360 * 2 * Math.PI;
        // 绘制指针细的一端
        float thinPointerLength = mSize / 15f;
        float thinPointerX = mCentrePointX + thinPointerLength * (float) Math.sin(minuteRadians);
        float thinPointerY = mCentrePointY - thinPointerLength * (float) Math.cos(minuteRadians);
        canvas.drawLine(mCentrePointX, mCentrePointY, thinPointerX, thinPointerY, mPaint);
        // 绘制指针粗的一端
        float thickPointerLength = mSize / 2.85f;
        float thickPointerX = mCentrePointX + thickPointerLength * (float) Math.sin(minuteRadians);
        float thickPointerY = mCentrePointY - thickPointerLength * (float) Math.cos(minuteRadians);
        mPaint.setStrokeWidth(mHourMinutePointerStrokeWidth);
        canvas.drawLine(thinPointerX, thinPointerY, thickPointerX, thickPointerY, mPaint);
        // 绘制圆心点
        canvas.drawCircle(mCentrePointX, mCentrePointY, mHourMinutePointerStrokeWidth / 2, mPaint);
    }

十五、绘制秒针

    /**
     * 绘制秒针
     *
     * @param canvas
     */
    private void drawSecondPointer(Canvas canvas) {
        int second = mCalendar.get(Calendar.SECOND);
        mPaint.setColor(mSecondPointerColor);
        mPaint.setStrokeWidth(mHourMinutePointerStrokeWidth / 4f);
        mPaint.setStrokeCap(Paint.Cap.ROUND);
        // 秒针转过的角度
        float secondAngle = second * 360 / 60f;
        double secondRadians = secondAngle / 360 * 2 * Math.PI;
        // 绘制长指针的一端
        float longPointerLength = mSize / 2.5f;
        float longPointerX = mCentrePointX + longPointerLength * (float) Math.sin(secondRadians);
        float longPointerY = mCentrePointY - longPointerLength * (float) Math.cos(secondRadians);
        canvas.drawLine(mCentrePointX, mCentrePointY, longPointerX, longPointerY, mPaint);
        // 绘制短指针的一端
        float shortPointerLength = mSize / 14.5f;
        float thickPointerX = mCentrePointX + shortPointerLength * (float) Math.sin(secondRadians + Math.PI);
        float thickPointerY = mCentrePointY - shortPointerLength * (float) Math.cos(secondRadians + Math.PI);
        canvas.drawLine(mCentrePointX, mCentrePointY, thickPointerX, thickPointerY, mPaint);
        // 绘制圆心点
        mPaint.setColor(Color.BLACK);
        canvas.drawCircle(mCentrePointX, mCentrePointY, mHourMinutePointerStrokeWidth / 5, mPaint);
        mPaint.setColor(mSecondPointerColor);
        canvas.drawCircle(mCentrePointX, mCentrePointY, mHourMinutePointerStrokeWidth / 3, mPaint);
    }

十六、源码地址

源码地址:https://github.com/lynnchurch/AndroidSample
该源码是很多示例的一个集合,AppleWatch手表的示例在View->WatchView中。

你可能感兴趣的:(android)