Androidカスタムコントロール:クロック

13469 ワード

実現原理分析
  • 目盛線の描画:目盛線を描くのは簡単です.canvasです.drawLineですが、角度によって30度ごとに1つの目盛線を描くのはどうやって実現するのか、最初は角度に基づいて、三角関数などを利用して各目盛線の開始座標と終了座標を計算するのか考えていましたが、この方法は複雑すぎて、少し油断すると計算が間違ってしまいます.でもキャンバスの回転を利用してcanvas.rotateは非常に簡単で、目盛線は12時方向に描くだけで、1つの目盛線を描くたびにキャンバスを30度回転させ、12時方向に描くだけでいいです.
  • ポインタ描画:同じくcanvas.drawLineは3つのポインタを描き、paintに異なる属性を設定して時計回り、分針、秒針の表示スタイルを実現します.同じように、角度に基づいてポインタの座標を計算すると複雑です.ここでもキャンバスの回転によって、回転の角度はどのように決定されますか.現在の時間に基づいて決定されます(具体的なアルゴリズムの後のコードで具体的な分析).
  • ダイナミック:クロックのダイナミック回転を実現するには、onDrawで1秒ごとに現在の時間を取得し、3つのポインタの回転角度を計算して描画する必要があります.

  • このような分析では、カスタムクロックは簡単です.円を描き、キャンバスの回転で目盛とポインタを描きます.
    具体的な実装プロセス
  • //   
    canvas.drawCircle(centerX, centerY, radius, circlePaint);
    
    を描画します.centerXとcenterYは円心で、現在のコントロールの中心点でいいです.radiusは円の半径で、現在のコントロールの幅の高い最小値/2でいいです.または、自分で設定します.
  • 目盛線12個の目盛線を描き、12回ループし、3個の目盛線ごとに15分の目盛線となり、異なるスタイルの区分を設定することができます.次に12時方向に目盛線を描きます.開始x座標:円心x座標;開始y座標:円心y座標-半径+ギャップ;終了x座標:円心x座標;終了y座標:開始y座標+目盛線長;1つの目盛線を描くたびに、キャンバスは前に30度回転し、12時の目盛線を描き続けます.このように、目盛線は回転後のキャンバスに基づいて描き、つまり斜めに目盛線を描き、目盛線の描画を容易に実現します.ここでは主な描画コードを示し、すべてのコードの後ろに
    //     
    private final static int MARK_LENGTH = 20;
    
    //        
    private final static int MARK_GAP = 12;
    
    //     
    for (int i = 0; i < 12; i++) {
        if (i % 3 == 0) {//   
            markPaint.setColor(mQuarterMarkColor);
        } else {
            markPaint.setColor(mMinuteMarkColor);
        }
        canvas.drawLine(
                centerX,
                centerY - radius + MARK_GAP,
                centerX,
                centerY - radius + MARK_GAP + MARK_LENGTH,
                markPaint);
        canvas.rotate(30, centerX, centerY);
    }
    canvas.save();
    
  • が貼られている.
  • 描画ポインタ時計回り、分針、秒針をそれぞれ3つのcanvasで描画し、最後にこの3つのキャンバスのbitmapをコントロールのcanvasに描画します.各キャンバスの回転角度を個別に制御するためです.まず時計回りの針の角度を分析して、時計の1周は12時間で、360度で、それでは1時間ごとに30度で、現在の時間の時間がh(12時間制)だと仮定して、時計回りの回転角度はh*30で、目盛り線と同じように、私達もこの角度の針の各種の座標を計算しないで、直接時計回りのキャンバスをh*30度回転して、それから12時方向の時計回りを描けばいいです.次に分針角度、時計の1周は60分、360度で、それでは毎分6度で、現在の時間の分がmであると仮定して、分針の回転角度はm*6最後に秒針角度で、時計の1周は60秒、360度で、それでは毎秒は6度で、現在の時間の秒数がsであると仮定して、秒針の回転角度はs*6分析が終わった時計回りで、分針、秒針の角度は取得して、それではその後はとても簡単です.onDrawでは、1秒ごとに現在の時間の時間分秒を取得し、上記のアルゴリズムに従って角度を計算し、対応するキャンバスを回転させた後、対応するポインタ(もちろんキャンバスのクリアと復元に注意)を描くと、時間が経つにつれて回転する時計が出てきます.ここでは時計回りを描く主なコードを示します.他の2つのポインタは似ています.具体的なコードの後ろに
    @Override
    protected void onDraw(Canvas canvas) {
        Calendar calendar = Calendar.getInstance();
        int hour12 = calendar.get(Calendar.HOUR);
        int minute = calendar.get(Calendar.MINUTE);
        int second = calendar.get(Calendar.SECOND);
    
        //      
        hourCanvas.save();
        //    
        hourCanvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
        //    
        hourCanvas.rotate(hour12 * 30, centerX, centerY);
        //  12       
        hourCanvas.drawLine(centerX, centerY,
                centerX, centerY - hourLineLength, hourPaint);
        //      ,          ,          
        hourCanvas.restore();
    
        canvas.drawBitmap(hourBitmap, 0, 0, null); 
    
        //  1s    
        postInvalidateDelayed(1000);
    }
    
    が貼られていますが、秒針は1秒1秒回転します.しかし、時計回りと分針はいつも整数の位置にあります.60秒を過ぎると、分針は次の分にジャンプします.60分を過ぎると、時計回りは次の時間にジャンプします.私たちが普段見ている時計は秒針の回転に伴っています.分針も時計回りも一定のずれ量がありますが、もちろん私たちの時計もこんなにかっこいいですが、どうやって計算しますか?時計回り:前に述べたように、1時間あたり30度時計回りに回転し、現在の時間がh(12時間制)であると仮定すると、時計回りの回転角度はh*30である.では、毎分時計回りに何度回転するかというと、答えは30/60=0.5度(毎時60分、毎時30度)なので、時計回りのずれ量はm*0.5なので、現在の時間が1:30だと仮定すると、時計回りの回転角度は1*30+30*0.5で、45度で、変数式に変更するとh*30+m*0.5になります.では、分針の回転角度はm*6であり、毎秒分針は6/60(毎分60秒、毎分6度)回転するので、分針のオフセット量はs*0.1であり、分針キャンバスの回転コードは
    hourCanvas.rotate(hour12 * 30 + minute * 0.5f, centerX, centerY);
    
    秒針である:秒針は毎秒6度回転
    minuteCanvas.rotate(minute * 6 + second * 0.1f, centerX, centerY);
    
  • まとめ
    上の3つのステップを経て、ゆっくりと動く時計を描きました.
    完全なコードとプロジェクトはみんな私のgithubの中で見ることができて、中に関連する使用方法があって、同時にこのプロジェクトはmaven倉庫にアップロードして、gradleを通じて直接使用することができます
    secondCanvas.rotate(second * 6, centerX, centerY);
    

    githubアドレス:https://github.com/zhijieeeeee/ClockView
    完全なコード
    compile 'com.don:clockviewlibrary:1.0.1'
    

    カスタムプロパティー
    public class ClockView extends View {
    
        //  wrap_content      
        private final static int DEFAULT_SIZE = 400;
    
        //     
        private final static int MARK_WIDTH = 8;
    
        //     
        private final static int MARK_LENGTH = 20;
    
        //        
        private final static int MARK_GAP = 12;
    
        //    
        private final static int HOUR_LINE_WIDTH = 10;
    
        //    
        private final static int MINUTE_LINE_WIDTH = 6;
    
        //    
        private final static int SECOND_LINE_WIDTH = 4;
    
        //    
        private int centerX;
        private int centerY;
    
        //   
        private int radius;
    
        //    
        private Paint circlePaint;
    
        //     
        private Paint markPaint;
    
        //    
        private Paint hourPaint;
    
        //    
        private Paint minutePaint;
    
        //    
        private Paint secondPaint;
    
        //    
        private int hourLineLength;
    
        //    
        private int minuteLineLength;
    
        //    
        private int secondLineLength;
    
        private Bitmap hourBitmap;
        private Bitmap minuteBitmap;
        private Bitmap secondBitmap;
    
        private Canvas hourCanvas;
        private Canvas minuteCanvas;
        private Canvas secondCanvas;
    
        //    
        private int mCircleColor = Color.WHITE;
        //     
        private int mHourColor = Color.BLACK;
        //     
        private int mMinuteColor = Color.BLACK;
        //     
        private int mSecondColor = Color.RED;
        //         
        private int mQuarterMarkColor = Color.parseColor("#B5B5B5");
        //        
        private int mMinuteMarkColor = Color.parseColor("#EBEBEB");
        //    3      
        private boolean isDrawCenterCircle = false;
    
        //      
        private OnCurrentTimeListener onCurrentTimeListener;
    
        public void setOnCurrentTimeListener(OnCurrentTimeListener onCurrentTimeListener) {
            this.onCurrentTimeListener = onCurrentTimeListener;
        }
    
        public ClockView(Context context) {
            super(context);
            init();
        }
    
        public ClockView(Context context, AttributeSet attrs) {
            this(context, attrs, 0);
        }
    
        public ClockView(Context context, AttributeSet attrs, int defStyleAttr) {
            super(context, attrs, defStyleAttr);
            TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.ClockView);
            mCircleColor = a.getColor(R.styleable.ClockView_circle_color, Color.WHITE);
            mHourColor = a.getColor(R.styleable.ClockView_hour_color, Color.BLACK);
            mMinuteColor = a.getColor(R.styleable.ClockView_minute_color, Color.BLACK);
            mSecondColor = a.getColor(R.styleable.ClockView_second_color, Color.RED);
            mQuarterMarkColor = a.getColor(R.styleable.ClockView_quarter_mark_color, Color.parseColor("#B5B5B5"));
            mMinuteMarkColor = a.getColor(R.styleable.ClockView_minute_mark_color, Color.parseColor("#EBEBEB"));
            isDrawCenterCircle = a.getBoolean(R.styleable.ClockView_draw_center_circle, false);
            a.recycle();
            init();
        }
    
    
        @Override
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
            reMeasure(widthMeasureSpec, heightMeasureSpec);
    
            int width = getMeasuredWidth();
            int height = getMeasuredHeight();
            centerX = width / 2 ;
            centerY = height / 2;
            radius = Math.min(width, height) / 2;
    
            hourLineLength = radius / 2;
            minuteLineLength = radius * 3 / 4;
            secondLineLength = radius * 3 / 4;
    
            //  
            hourBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
            hourCanvas = new Canvas(hourBitmap);
    
            //  
            minuteBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
            minuteCanvas = new Canvas(minuteBitmap);
    
            //  
            secondBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
            secondCanvas = new Canvas(secondBitmap);
    
        }
    
        @Override
        protected void onDraw(Canvas canvas) {
            super.onDraw(canvas);
            //   
            canvas.drawCircle(centerX, centerY, radius, circlePaint);
            //     
            for (int i = 0; i < 12; i++) {
                if (i % 3 == 0) {//   
                    markPaint.setColor(mQuarterMarkColor);
                } else {
                    markPaint.setColor(mMinuteMarkColor);
                }
                canvas.drawLine(
                        centerX,
                        centerY - radius + MARK_GAP,
                        centerX,
                        centerY - radius + MARK_GAP + MARK_LENGTH,
                        markPaint);
                canvas.rotate(30, centerX, centerY);
            }
            canvas.save();
    
            Calendar calendar = Calendar.getInstance();
            int hour12 = calendar.get(Calendar.HOUR);
            int minute = calendar.get(Calendar.MINUTE);
            int second = calendar.get(Calendar.SECOND);
    
            //(   )     (3600 )    30 ,        (1/120) 
            //(   )     (60  )    30 ,         (1/2) 
            hourCanvas.save();
            //    
            hourCanvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
            hourCanvas.rotate(hour12 * 30 + minute * 0.5f, centerX, centerY);
            hourCanvas.drawLine(centerX, centerY,
                    centerX, centerY - hourLineLength, hourPaint);
            if (isDrawCenterCircle)//           
                hourCanvas.drawCircle(centerX, centerY, 2 * HOUR_LINE_WIDTH, hourPaint);
            hourCanvas.restore();
    
            //     (60 )    6 ,        (1/10) ; minute 1 ,  second 0
            minuteCanvas.save();
            //    
            minuteCanvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
            minuteCanvas.rotate(minute * 6 + second * 0.1f, centerX, centerY);
            minuteCanvas.drawLine(centerX, centerY,
                    centerX, centerY - minuteLineLength, minutePaint);
            if (isDrawCenterCircle)//           
                minuteCanvas.drawCircle(centerX, centerY, 2 * MINUTE_LINE_WIDTH, minutePaint);
            minuteCanvas.restore();
    
            //      6 
            secondCanvas.save();
            //    
            secondCanvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
            secondCanvas.rotate(second * 6, centerX, centerY);
            secondCanvas.drawLine(centerX, centerY,
                    centerX, centerY - secondLineLength, secondPaint);
            if (isDrawCenterCircle)//           
                secondCanvas.drawCircle(centerX, centerY, 2 * SECOND_LINE_WIDTH, secondPaint);
            secondCanvas.restore();
    
            canvas.drawBitmap(hourBitmap, 0, 0, null);
            canvas.drawBitmap(minuteBitmap, 0, 0, null);
            canvas.drawBitmap(secondBitmap, 0, 0, null);
    
            //  1s    
            postInvalidateDelayed(1000);
    
            if (onCurrentTimeListener != null) {
                //    24     
                int h = calendar.get(Calendar.HOUR_OF_DAY);
                String currentTime = intAdd0(h) + ":" + intAdd0(minute) + ":" + intAdd0(second);
                onCurrentTimeListener.currentTime(currentTime);
            }
        }
    
        /**
         *    
         */
        private void init() {
            circlePaint = new Paint();
            circlePaint.setAntiAlias(true);
            circlePaint.setStyle(Paint.Style.FILL);
            circlePaint.setColor(mCircleColor);
    
            markPaint = new Paint();
            circlePaint.setAntiAlias(true);
            markPaint.setStyle(Paint.Style.FILL);
            markPaint.setStrokeCap(Paint.Cap.ROUND);
            markPaint.setStrokeWidth(MARK_WIDTH);
    
            hourPaint = new Paint();
            hourPaint.setAntiAlias(true);
            hourPaint.setColor(mHourColor);
            hourPaint.setStyle(Paint.Style.FILL);
            hourPaint.setStrokeCap(Paint.Cap.ROUND);
            hourPaint.setStrokeWidth(HOUR_LINE_WIDTH);
    
            minutePaint = new Paint();
            minutePaint.setAntiAlias(true);
            minutePaint.setColor(mMinuteColor);
            minutePaint.setStyle(Paint.Style.FILL);
            minutePaint.setStrokeCap(Paint.Cap.ROUND);
            minutePaint.setStrokeWidth(MINUTE_LINE_WIDTH);
    
            secondPaint = new Paint();
            secondPaint.setAntiAlias(true);
            secondPaint.setColor(mSecondColor);
            secondPaint.setStyle(Paint.Style.FILL);
            secondPaint.setStrokeCap(Paint.Cap.ROUND);
            secondPaint.setStrokeWidth(SECOND_LINE_WIDTH);
    
        }
    
        /**
         *     view  
         */
        private void reMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            int measureWidth = MeasureSpec.getSize(widthMeasureSpec);
            int measureHeight = MeasureSpec.getSize(heightMeasureSpec);
            int measureWidthMode = MeasureSpec.getMode(widthMeasureSpec);
            int measureHeightMode = MeasureSpec.getMode(heightMeasureSpec);
            if (measureWidthMode == MeasureSpec.AT_MOST
                    && measureHeightMode == MeasureSpec.AT_MOST) {
                setMeasuredDimension(DEFAULT_SIZE, DEFAULT_SIZE);
            } else if (measureWidthMode == MeasureSpec.AT_MOST) {
                setMeasuredDimension(DEFAULT_SIZE, measureHeight);
            } else if (measureHeightMode == MeasureSpec.AT_MOST) {
                setMeasuredDimension(measureWidth, DEFAULT_SIZE);
            }
        }
    
        public interface OnCurrentTimeListener {
            void currentTime(String time);
        }
    
        /**
         * int  10   0
         *
         * @param i
         * @return
         */
        private String intAdd0(int i) {
            DecimalFormat df = new DecimalFormat("00");
            if (i < 10) {
                return df.format(i);
            } else {
                return i + "";
            }
        }
    }