Android自定義View時鐘效果

今天繼續聊自定義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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章