关于GAStudio哥玄酷的华为时钟界面代码学习

package gastudio.clock.ui;

import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Point;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.Xfermode;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
import android.view.animation.Animation;
import android.view.animation.LinearInterpolator;

import java.util.Calendar;

import gastudio.clock.R;
import gastudio.clock.utils.Constants;
import gastudio.clock.utils.UiUtils;

public class ClockViewByPath extends View {

private static final int DEFAULT_CLOCK_ANIMATION_DURATION = Constants.MINUTE;
private static final int FULL_ANGLE = 360;
private static final int SECOND_PER_MINUTE = 60;

private static final int DEFAULT_TOTAL_CLOCK_SCALE_LINE_NUM = 120;
private static final int DEFAULT_CLOCK_VIEW_WIDTH = 260;
private static final int ANGLE_PER_SECOND = FULL_ANGLE / SECOND_PER_MINUTE;
private static final int ANGLE_PER_SCALE = FULL_ANGLE / DEFAULT_TOTAL_CLOCK_SCALE_LINE_NUM;

private static final int DEFAULT_CLOCK_SCALE_LINE_COLOR = Color.WHITE;
private static final int DEFAULT_CLOCK_SCALE_LINE_WIDTH = 2;
private static final int DEFAULT_CLOCK_SCALE_LINE_HEIGHT = 14;
private static final int ADJUST_CLOCK_SCALE_LINE_START_X = 1;

private static final int DEFAULT_DIGITAL_TIME_TEXT_COLOR = Color.WHITE;
private static final int DEFAULT_DIGITAL_TIME_TEXT_SIZE = 60;
private static final String DEFAULT_DEFAULT_DIGITAL_TIME_TEXT = "00:00";

private static final int DEFAULT_CLOCK_POINT_COLOR = Color.RED;
private static final int DEFAULT_CLOCK_POINT_RADIUS = 6;

private static final float[] CLOCK_SCALE_LINE_BASE_LEN_ARRAY = new float[]{
        1F, 1.1F, 1.21F, 1.32F, 1.452F,
        1.551F, 1.6827F, 1.75F, 1.6827F, 1.551F,
        1.452F, 1.32F, 1.21F, 1.1F, 1F};
// This is max in CLOCK_SCALE_LINE_BASE_LEN_ARRAY
private static final float RATIO_OF_MAX_HEIGTH_TO_NORMAL_HEIGHT = 1.75F;

private int mClockScaleLineWidth;
private int mClockScaleLineHeight;
private int mClockScaleLineMaxHeight;
private int mClockScaleLineColor;
private int mAdjustClockScaleLineStartX;

private int mClockViewCenterX;
private int mClockViewCenterY;
private int mClockMaskRadius;
private RectF mClockViewRectF;
private Path mClockMaskPath;
private float mClockMaskAdjustAngle;

private int mClockPointRadius;
private int mClockPointColor;
private int mClockPointCenterX;
private int mClockPointCenterY;

private int mDigitalTimeTextStartX;
private int mDigitalTimeTextStartY;
private int mDigitalTimeTextSize;
private int mDigitalTimeTextColor;
private Rect mDigitalTimeTextRect;
private String mLastDigitalTimeStr;

private Paint mPaint;
private Xfermode mXfermode;
private ValueAnimator mClockAnimator;

private float mNowClockAngle;
private float mInitClockAngle;
private long mLastTimeMillis;
private Calendar mCalendar;

public ClockViewByPath(Context context) {
    this(context, null);
}

public ClockViewByPath(Context context, AttributeSet attrs) {
    this(context, attrs, 0);
}

public ClockViewByPath(Context context, AttributeSet attrs, int defStyleAttr) {
    this(context, attrs, defStyleAttr, 0);
}

public ClockViewByPath(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
    super(context, attrs, defStyleAttr, defStyleRes);
    TypedArray a = context.obtainStyledAttributes(
            attrs, R.styleable.ClockView);
    mDigitalTimeTextSize = a.getDimensionPixelSize(R.styleable.ClockView_timeTextSize,
            UiUtils.dipToPx(context, DEFAULT_DIGITAL_TIME_TEXT_SIZE));
    mDigitalTimeTextColor = a.getColor(R.styleable.ClockView_timeTextSize,
            DEFAULT_DIGITAL_TIME_TEXT_COLOR);
    mClockPointRadius = a.getDimensionPixelSize(R.styleable.ClockView_pointRadius,
            UiUtils.dipToPx(context, DEFAULT_CLOCK_POINT_RADIUS));
    mClockPointColor = a.getColor(R.styleable.ClockView_pointColor,
            DEFAULT_CLOCK_POINT_COLOR);
    mClockScaleLineWidth = a.getDimensionPixelSize(R.styleable.ClockView_timeScaleWidth,
            UiUtils.dipToPx(context, DEFAULT_CLOCK_SCALE_LINE_WIDTH));
    mClockScaleLineHeight = a.getDimensionPixelSize(R.styleable.ClockView_timeScaleHeight,
            UiUtils.dipToPx(context, DEFAULT_CLOCK_SCALE_LINE_HEIGHT));
    mClockScaleLineMaxHeight = (int) (RATIO_OF_MAX_HEIGTH_TO_NORMAL_HEIGHT * mClockScaleLineHeight);
    mClockScaleLineColor = a.getDimensionPixelSize(R.styleable.ClockView_timeScaleColor,
            UiUtils.dipToPx(context, DEFAULT_CLOCK_SCALE_LINE_COLOR));
    a.recycle();
    init();
}

@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
    super.onSizeChanged(w, h, oldw, oldh);
    int len = w > h ? h : w;
    mClockViewRectF.set(0, 0, len, len);
    mClockViewRectF.offset((w - len) / 2, (h - len) / 2);
    mClockViewCenterX = (int) mClockViewRectF.centerX();
    mClockViewCenterY = (int) mClockViewRectF.centerY();
    mDigitalTimeTextStartX = mClockViewCenterX - mDigitalTimeTextRect.left - mDigitalTimeTextRect.width() / 2;
    mDigitalTimeTextStartY = mClockViewCenterY - mDigitalTimeTextRect.top - mDigitalTimeTextRect.height() / 2;
    mClockPointCenterX = mClockViewCenterX;
    mClockPointCenterY = (int) (mClockViewRectF.top + mAdjustClockScaleLineStartX
            + mClockScaleLineMaxHeight + mClockPointRadius * 2);
    mClockMaskRadius = (int) (mClockViewRectF.width() / 2 - mClockScaleLineMaxHeight);
    generateMaskPath();
    mClockMaskAdjustAngle = (float) (CLOCK_SCALE_LINE_BASE_LEN_ARRAY.length + 1) / 2 * ANGLE_PER_SCALE;
}

private void generateMaskPath() {
    Log.i("zyl log 0223","generateMaskPath");
    Log.i("zyl log 0223","mClockViewCenterX = " + mClockViewCenterX);//260
    Log.i("zyl log 0223","mClockViewCenterY = " + mClockViewCenterY);//260
    Log.i("zyl log 0223","mClockViewCenterY - mClockMaskRadius - mClockScaleLineHeight = " + (mClockViewCenterY - mClockMaskRadius - mClockScaleLineHeight));
    Point point = new Point(mClockViewCenterX, mClockViewCenterY - mClockMaskRadius - mClockScaleLineHeight);//21
    mClockMaskPath.moveTo(point.x, point.y);

    // Generate contour of the special clock scale lines
    int arrayLen = CLOCK_SCALE_LINE_BASE_LEN_ARRAY.length;//15
    for (int index = 0; index < arrayLen; index++) {
        calculateNextPoint(point, CLOCK_SCALE_LINE_BASE_LEN_ARRAY[index],
                (float) Math.toRadians(ANGLE_PER_SCALE * (index + 1)));
         mClockMaskPath.lineTo(point.x, point.y);
    }

    // Generate contour of the normal clock scale lines
    int insertLen = mClockScaleLineMaxHeight - mClockScaleLineHeight;
    RectF cycleRectF = new RectF(mClockViewRectF);
    cycleRectF.inset(insertLen, insertLen);//03.10  ???
    //If dx is positive, then the sides are moved inwards, making the rectangle narrower
    mClockMaskPath.arcTo(cycleRectF, arrayLen * ANGLE_PER_SCALE - 90,//-45(对于圆心找-45度方位,与矩形交点即为划线起始点)
            (DEFAULT_TOTAL_CLOCK_SCALE_LINE_NUM - arrayLen) * ANGLE_PER_SCALE);//315,弧线划过的角度(相对于圆心)
}

private void calculateNextPoint(Point point, float scale, float angle) {
    Log.i("zyl log 0228","mClockMaskRadius = " + mClockMaskRadius);//211
    Log.i("zyl log 0228","mClockScaleLineHeight = " + mClockScaleLineHeight);//28
    float originLen = mClockMaskRadius + mClockScaleLineHeight;
    float adjustLen = mClockMaskRadius + mClockScaleLineHeight * scale;
    Log.i("zyl log 0228","mClockViewCenterX + (int) (adjustLen * Math.sin(angle) = " + (mClockViewCenterX + (int) (adjustLen * Math.sin(angle) ) ) ) ;
    Log.i("zyl log 0228","mClockViewCenterY - mClockMaskRadius - mClockScaleLineHeight\n" +
            "                        + (int) (-adjustLen * Math.cos(angle) + originLen) = " + (mClockViewCenterY - mClockMaskRadius - mClockScaleLineHeight
            + (int) (-adjustLen * Math.cos(angle) + originLen)));
    point.set(mClockViewCenterX + (int) (adjustLen * Math.sin(angle)),
            mClockViewCenterY - mClockMaskRadius - mClockScaleLineHeight
                    + (int) (-adjustLen * Math.cos(angle) + originLen));
}

private void init() {
    mClockViewRectF = new RectF();
    mDigitalTimeTextRect = new Rect();
    mPaint = new Paint();
    mPaint.setAntiAlias(true);
    mPaint.setTextSize(mDigitalTimeTextSize);
    mPaint.getTextBounds(DEFAULT_DEFAULT_DIGITAL_TIME_TEXT, 0,
            DEFAULT_DEFAULT_DIGITAL_TIME_TEXT.length(), mDigitalTimeTextRect);
    mPaint.setStrokeWidth(mClockScaleLineWidth);
    mPaint.setStrokeCap(Paint.Cap.ROUND);
    mXfermode = new PorterDuffXfermode(PorterDuff.Mode.DST_OUT);//取下层绘制非交集部分(大小正方形中套圆)
    mAdjustClockScaleLineStartX = UiUtils.dipToPx(getContext(), ADJUST_CLOCK_SCALE_LINE_START_X);
    mClockMaskPath = new Path();
    mCalendar = Calendar.getInstance();
    mLastDigitalTimeStr = String.format("%02d:%02d", mCalendar.get(Calendar.HOUR),
            mCalendar.get(Calendar.MINUTE));
}

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
    int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
    if (widthSpecMode != MeasureSpec.EXACTLY) {
        widthMeasureSpec = MeasureSpec.makeMeasureSpec(
                UiUtils.dipToPx(getContext(), DEFAULT_CLOCK_VIEW_WIDTH), MeasureSpec.EXACTLY);
    }
    if (heightSpecMode != MeasureSpec.EXACTLY) {
        heightMeasureSpec = MeasureSpec.makeMeasureSpec(
                UiUtils.dipToPx(getContext(), DEFAULT_CLOCK_VIEW_WIDTH), MeasureSpec.EXACTLY);
    }
    setMeasuredDimension(widthMeasureSpec, heightMeasureSpec);
}

//03.08:
// 1.Canvas是一个很虚幻的概念,相当于一个透明图层(用过PS的同学应该都知道),每次Canvas画图时(即调用Draw系列函数),
// 都会产生一个透明图层,然后在这个图层上画图,画完之后覆盖在屏幕上显示
// 2.在画源图像时,会把之前画布上所有的内容都做为目标图像
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
// Save layer
//mPaint.setColor(Color.RED);
//canvas.drawRect(mClockViewRectF.left+10,mClockViewRectF.top+10,mClockViewRectF.right+10,mClockViewRectF.bottom+10,mPaint);
int layerOne = canvas.saveLayer(mClockViewRectF, mPaint, Canvas.ALL_SAVE_FLAG);
//03.08saveLayer会创建一个全新透明的bitmap,大小与指定保存的区域一致,其后的绘图操作都放在这个bitmap上进行。
// 在绘制结束后,会直接盖在上一层的Bitmap上显示
// Draw clock scale lines
mPaint.setColor(mClockScaleLineColor);
float clockScaleLineStartY = mAdjustClockScaleLineStartX + mClockViewRectF.top;
float clockScaleLineEndY = clockScaleLineStartY + mClockScaleLineMaxHeight;
for (int i = 0; i < 120; i++) {//120
Log.i(“zyl log 0309”,”clockScaleLineStartY = ” + clockScaleLineStartY);
Log.i(“zyl log 0309”,”clockScaleLineEndY = ” + clockScaleLineEndY);
canvas.drawLine(mClockViewCenterX, clockScaleLineStartY,
mClockViewCenterX, clockScaleLineEndY, mPaint);
canvas.rotate(ANGLE_PER_SCALE, mClockViewCenterX, mClockViewCenterY);//3度;其实是画布所在的坐标系的旋转
}
//2017.03.03 canvas.rotate方法会将坐标轴旋转!!!!
mPaint.setXfermode(mXfermode);
canvas.rotate(mNowClockAngle - mClockMaskAdjustAngle, mClockViewCenterX, mClockViewCenterY);
// Generate a mask by path
int layerTwo = canvas.saveLayer(mClockViewRectF, mPaint, Canvas.ALL_SAVE_FLAG);
Log.i(“zyl log 02281”,”mClockViewRectF = ” + mClockViewRectF);//(0,0,520,520)
Xfermode zXfermode = new PorterDuffXfermode(PorterDuff.Mode.SRC); //add by zyl 2017.03.09
//mPaint.setXfermode(null);
mPaint.setXfermode(zXfermode);
//为什么需要重置一下呢?
//1.因为在调用saveLayer时,会生成了一个全新的bitmap,这个bitmap的大小就是我们指定的保存区域的大小,
// 新生成的bitmap是全透明的,注意是透明的在调用saveLayer后所有的绘图操作都是在这个bitmap上进行的
//2.在layerOne时,Paint的Xfermode为取下层绘制非交集部分,那么在drawOval和drawPath之后效果跟新建一张透明图层效果是一样的
canvas.drawOval(mClockViewRectF, mPaint);//外层大圆
//mPaint.setXfermode(mXfermode);//取下层绘制非交集部分
zXfermode = new PorterDuffXfermode(PorterDuff.Mode.SRC_OUT);//03.09 ??? 为什么缺省一块?
mPaint.setXfermode(zXfermode);
//mPaint.setColor(mClockPointColor);
canvas.drawPath(mClockMaskPath, mPaint);//mClockMaskPath==眼球
//03.09:关于mClockMaskPath路径的计算???
canvas.restoreToCount(layerTwo);

    mPaint.setXfermode(null);
    // Draw clock point
    mPaint.setColor(mClockPointColor);
    canvas.rotate(mClockMaskAdjustAngle, mClockViewCenterX, mClockViewCenterY);
    canvas.drawCircle(mClockPointCenterX, mClockPointCenterY, mClockPointRadius, mPaint);

    canvas.restoreToCount(layerOne);
    updateTimeText(canvas);
}

private void updateTimeText(Canvas canvas) {
    long currentTimeMillis = System.currentTimeMillis();
    if (currentTimeMillis - mLastTimeMillis >= Constants.MINUTE) {
        mLastTimeMillis = currentTimeMillis;
        mCalendar.setTimeInMillis(currentTimeMillis);
        mLastDigitalTimeStr = String.format("%02d:%02d",
                mCalendar.get(Calendar.HOUR), mCalendar.get(Calendar.MINUTE));
    }
    mPaint.setColor(mDigitalTimeTextColor);
    canvas.drawText(mLastDigitalTimeStr, mDigitalTimeTextStartX, mDigitalTimeTextStartY, mPaint);
}

public void performAnimation() {
    cancelAnimation();
    mClockAnimator = ValueAnimator.ofFloat(0, FULL_ANGLE);
    mClockAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
        @Override
        public void onAnimationUpdate(ValueAnimator animation) {
            mNowClockAngle = (float) animation.getAnimatedValue();
            mNowClockAngle += mInitClockAngle;
            invalidate();
        }
    });
    mClockAnimator.setDuration(DEFAULT_CLOCK_ANIMATION_DURATION);
    mClockAnimator.setInterpolator(new LinearInterpolator());
    mClockAnimator.setRepeatCount(Animation.INFINITE);
    mClockAnimator.addListener(new AnimatorListenerAdapter() {
        @Override
        public void onAnimationStart(Animator animation) {
            super.onAnimationStart(animation);
            long currentTimeMillis = System.currentTimeMillis();
            mCalendar.setTimeInMillis(currentTimeMillis);
            mLastDigitalTimeStr = String.format("%02d:%02d", mCalendar.get(Calendar.HOUR),
                    mCalendar.get(Calendar.MINUTE));
            mInitClockAngle = (mCalendar.get(Calendar.SECOND)
                    + (float) mCalendar.get(Calendar.MILLISECOND) / Constants.SECOND) * ANGLE_PER_SECOND;
            mLastTimeMillis = currentTimeMillis - mCalendar.get(Calendar.SECOND) * Constants.SECOND
                    - mCalendar.get(Calendar.MILLISECOND);
        }
    });
    mClockAnimator.start();
}

public void cancelAnimation() {
    if (mClockAnimator != null) {
        mClockAnimator.removeAllUpdateListeners();
        mClockAnimator.removeAllListeners();
        mClockAnimator.cancel();
        mClockAnimator = null;
    }
}

}

你可能感兴趣的:(关于GAStudio哥玄酷的华为时钟界面代码学习)