안드로이드 사용자 정의 컨트롤:클록

14381 단어 Android
원리 분석을 실현하다
  • 눈금선 그리기: 눈금선을 그리는 것은 매우 간단합니다. 바로canvas입니다.drawLine, 그러나 각도에 따라 30도마다 각도선을 그리는 것은 어떻게 이루어질까. 우리가 처음에 생각한 것은 각도에 따라 삼각함수 등을 이용하여 각도선의 시작 좌표와 끝 좌표를 계산하는 것일 수도 있다. 그러나 이런 방식은 너무 복잡해서 자칫 잘못 계산할 수도 있다.하지만 화포를 이용한 회전 canvas.rotate는 매우 간단합니다. 눈금선은 12시 방향에 따라 그리면 됩니다. 매번 눈금선을 그려서 천을 30도 회전시키고 12시 방향에 따라 그리면 됩니다.
  • 포인터 그리기: 마찬가지로 canvas를 통과합니다.drawLine은 세 개의 바늘을 그려서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 좌표 + 눈금선 길이 시작하기;한 개의 눈금선을 그린 후에 화포는 이전의 기초 위에서 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개의 캔버스로 그립니다. 마지막으로 이 3개의 캔버스의bitmap을 컨트롤의 캔버스에 그립니다. 각 캔버스의 회전 각도를 단독으로 제어하기 위해서입니다.먼저 시계 바늘의 각도를 분석하면 시계 한 바퀴가 12시간, 360도이다. 그러면 시간당 30도이다. 현재 시간의 시간이 h(12시간제)라고 가정하면 시계 바늘의 회전 각도는 h*30이다. 눈금선과 같이 우리는 이 각도의 바늘의 각종 좌표를 계산하지 않고 시계 바늘의 화포를 h*30도로 회전한다.그리고 12시 방향의 시계 바늘을 그리면 됩니다.이어서 분침 각도, 시계 한 바퀴는 60분, 360도, 그러면 분당 6도이다. 현재 시간의 분이 m라고 가정하면 분침의 회전 각도는 m*6이고 마지막은 초침 각도이다. 시계 한 바퀴는 60초, 360도이다. 그러면 초당 6도이다. 현재 시간의 초수가 s라고 가정하면 초침의 회전 각도는 s*6이다. 시계 바늘, 분침을 분석했다.초침의 각도를 얻으면 그 다음은 매우 간단하다. 온드라우에서 우리는 1초에 한 번씩 현재 시간의 시분초를 얻고 위의 알고리즘에 따라 각도를 계산한 다음에 해당하는 화포를 회전시킨 다음에 해당하는 지침을 그린다(화포의 청소와 복원에 주의해야 한다). 그러면 시간의 흐름에 따라 회전하는 시계가 나온다.여기에 시계 바늘을 그리는 주요 코드를 제시한다. 다른 두 개의 바늘은 유사하다. 구체적인 코드 뒤에
    @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분이 지나야 시계 바늘이 다음 시간으로 넘어간다.우리가 평소에 보는 시계는 모두 초침의 회전에 따라 분침과 시침은 일정한 편이량을 가진다. 물론 우리의 시계도 이렇게 멋있어야 한다. 그러면 어떻게 계산합니까?시침: 앞에서 말했듯이 시침마다 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입니다.그러면 위의 코드
    hourCanvas.rotate(hour12 * 30 + minute * 0.5f, centerX, centerY);
    
    를 수정합니다. 분침: 현재 시간의 분이 m라고 가정하면 분침의 회전 각도는 m*6이고 초당 분침은 6/60(분당 60초, 분당 6도)이기 때문에 분침의 편이량은 s*0.1입니다. 그러면 분침화포가 회전하는 코드는
    minuteCanvas.rotate(minute * 6 + second * 0.1f, centerX, centerY);
    
    초침입니다. 초침은 초당 6도
    secondCanvas.rotate(second * 6, centerX, centerY);
    
  • 입니다.
    총결산
    위의 세 단계를 거쳐 우리는 천천히 움직이는 시계를 그렸다.
    온전한 코드와 프로젝트는 저의github에서 보실 수 있습니다. 그 안에 관련 사용 방법이 있습니다. 또한 이 프로젝트는 마븐 창고에 업로드되어gradle을 통해 직접 사용할 수 있습니다.
    compile 'com.don:clockviewlibrary:1.0.1'
    

    github 주소:https://github.com/zhijieeeeee/ClockView
    전체 코드
    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 + "";
            }
        }
    }
    

    사용자 정의 속성
    
        
        
        
        
        
        
        
    
    

    좋은 웹페이지 즐겨찾기