diff --git a/README.md b/README.md index aa89009..e11e355 100644 --- a/README.md +++ b/README.md @@ -6,4 +6,12 @@ Screen Capture : ![Duplication](https://github.com/AvatarQing/MiClockView/blob/master/art/duplication.gif) The original clock : -![Duplication](https://github.com/AvatarQing/MiClockView/blob/master/art/miui.gif) +![Duplication](https://github.com/AvatarQing/MiClockView/blob/master/art/miui.gif) + +##UPDATE +update detail +![update](./art/update.png) + +The original clock : +![update](./art/miui-1.png) + diff --git a/app/build.gradle b/app/build.gradle index ec83434..9e6d516 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -1,13 +1,13 @@ apply plugin: 'com.android.application' android { - compileSdkVersion 21 - buildToolsVersion "21.1.2" + compileSdkVersion 24 + buildToolsVersion "24.0.2" defaultConfig { applicationId "org.avatarqing.miclock" minSdkVersion 14 - targetSdkVersion 21 + targetSdkVersion 24 versionCode 1 versionName "1.0" } diff --git a/app/src/org/avatarqing/miclock/ClockView.java b/app/src/org/avatarqing/miclock/ClockView.java index 9c4bfbd..d7f94e2 100644 --- a/app/src/org/avatarqing/miclock/ClockView.java +++ b/app/src/org/avatarqing/miclock/ClockView.java @@ -28,683 +28,703 @@ import java.util.Calendar; public class ClockView extends View { - private static final String TAG = ClockView.class.getSimpleName(); - private static boolean DEBUG = true; + private static final String TAG = ClockView.class.getSimpleName(); + private static boolean DEBUG = true; - private int colorBg = 0xff237ead; - private int colorBgRing = 0x80ffffff; - private int colorMPOuterRing = Color.WHITE; - private int colorHPOuterRing = 0xccdddddd; - private int colorMinutePointer = Color.WHITE; - private int colorHourPointer = 0xccdddddd; - private int colorTriangle = 0xffdddddd; - private int colorScaleRing = 0xccdddddd; - - private Paint paintBgRing; - private Paint paintProgressRing; - private Paint paintTriangle; - private Paint paintMinutePointer; - private Paint paintHourPointer; - private Paint paintMPOuterRing; - private Paint paintMPInnerCircle; - private Paint paintHPOuterRing; - private Paint paintHPInnerCircle; - private Paint paintNumber; - private Paint paintScaleRing; - - private RectF boundScaleRing; - private RectF boundTimeRing; - - private int centerX; - private int centerY; - - private float radius; - private float radiusScaleRing; - private float radiusMPOuterRing = 20; - private float radiusMPInnerRing = 10; - private float radiusHPOuterRing = 18; - private float radiusHPInnerRing = 8; - - private float strokeWidthRing = 30; - private float strokeWidthScaleRing = 2; - - private float zDepthScaleRing = 180; - private float zDepthDashRing = 100; - private float zDepthHourPointer = 50; - private float zDepthMinutePointer = 0; - - private float hpTopEdgeLength = 6; - private float hpBottomEdgeLength = 10; - private float hpPointerLength = 10; - private float hpTopCYOffset = 6; - - private float mpTopEdgeLength = 5; - private float mpBottomEdgeLength = 8; - private float mpPointerLength = 20; - private float mpTopCYOffset = 6; - - private float trianglePointerOffset = 6; - private float triangleSideLength = 40; - - private float canvasRotateX = 0; - private float canvasRotateY = 0; - - private float rotateHourPointer = 0; - private float rotateMinutePointer = 0; - private float rotateSecondPointer = 0; - - private float scaleTextSize = 13; - private float scaleTextAngle = 5; - private float[][] scaleTextDrawCoordinates; - - private float progressRingInitRotateDegree = 270; - - private float canvasMaxRotateDegree = 20; - - private float[] ringDashIntervals = new float[]{3f, 6f}; - - private float[] sweepGradientColorPos = new float[]{0f, 300f / 360f, 330f / 360f, 1f}; - private int[] sweepGradientColors = new int[]{Color.TRANSPARENT, 0x80ffffff, 0xddffffff, Color.WHITE}; - - private ValueAnimator steadyAnim; - private ValueAnimator secondAnim; - - private Path pathTriangle; - private Path pathMinutePointer; - private Path pathHourPointer; - - private Matrix matrixCanvas = new Matrix(); - private Matrix matrixSweepGradient = new Matrix(); - - private Shader sweepGradient; - - private Camera camera = new Camera(); - - public ClockView(Context context) { - super(context); - init(); - } - - public ClockView(Context context, AttributeSet attrs) { - super(context, attrs); - init(); - } - - @Override - protected void onAttachedToWindow() { - super.onAttachedToWindow(); - if (DEBUG) { - Log.d(TAG, "onAttachedToWindow"); - } + private int colorBg = 0xff237ead; + private int colorBgRing = 0x80ffffff; + private int colorMPOuterRing = Color.WHITE; + private int colorHPOuterRing = 0xccdddddd; + private int colorMinutePointer = Color.WHITE; + private int colorHourPointer = 0xccdddddd; + private int colorTriangle = 0xffdddddd; + private int colorScaleRing = 0xccdddddd; + + private Paint paintBgRing; + private Paint paintProgressRing; + private Paint paintTriangle; + private Paint paintMinutePointer; + private Paint paintHourPointer; + private Paint paintMPOuterRing; + private Paint paintMPInnerCircle; + private Paint paintHPOuterRing; + private Paint paintHPInnerCircle; + private Paint paintNumber; + private Paint paintScaleRing; + + private RectF boundScaleRing; + private RectF boundTimeRing; + + private int centerX; + private int centerY; + + private float radius; + private float radiusScaleRing; + private float radiusMPOuterRing = 20; + private float radiusMPInnerRing = 10; + private float radiusHPOuterRing = 18; + private float radiusHPInnerRing = 8; + + private float strokeWidthRing = 30; + private float strokeWidthScaleRing = 2; + + private float zDepthScaleRing = 180; + private float zDepthDashRing = 100; + private float zDepthHourPointer = 50; + private float zDepthMinutePointer = 0; + + private float hpTopEdgeLength = 6; + private float hpBottomEdgeLength = 10; + private float hpPointerLength = 10; + private float hpTopCYOffset = 6; + + private float mpTopEdgeLength = 5; + private float mpBottomEdgeLength = 8; + private float mpPointerLength = 20; + private float mpTopCYOffset = 6; + + private float trianglePointerOffset = 6; + private float triangleSideLength = 40; + + private float canvasRotateX = 0; + private float canvasRotateY = 0; + + //平移距离 + private float canvasTranslateX = 0; + private float canvasTranslateY = 0; + + private float rotateHourPointer = 0; + private float rotateMinutePointer = 0; + private float rotateSecondPointer = 0; + + private float scaleTextSize = 13; + private float scaleTextAngle = 5; + private float[][] scaleTextDrawCoordinates; + + private float progressRingInitRotateDegree = 270; + + private float canvasMaxRotateDegree = 15; + //最大平移斜率和最大旋转角度有关, 偏移的感觉应该是使分针指针表盘 感觉到层次,而不是单纯旋转整个平面 + private double canvasMaxTranslateSlope = Math.tan(Math.toRadians(90.0 - canvasMaxRotateDegree)); + + private float[] ringDashIntervals = new float[]{3f, 6f}; + + private float[] sweepGradientColorPos = new float[]{0f, 300f / 360f, 330f / 360f, 1f}; + private int[] sweepGradientColors = new int[]{Color.TRANSPARENT, 0x80ffffff, 0xddffffff, Color.WHITE}; + + private ValueAnimator steadyAnim; + private ValueAnimator secondAnim; + + private Path pathTriangle; + private Path pathMinutePointer; + private Path pathHourPointer; + + private Matrix matrixCanvas = new Matrix(); + private Matrix matrixSweepGradient = new Matrix(); + + private Shader sweepGradient; + + private Camera camera = new Camera(); + + public ClockView(Context context) { + super(context); + init(); + } + + public ClockView(Context context, AttributeSet attrs) { + super(context, attrs); + init(); + } + + @Override + protected void onAttachedToWindow() { + super.onAttachedToWindow(); + if (DEBUG) { + Log.d(TAG, "onAttachedToWindow"); + } // startNewSecondAnim(); - // register broadcast receiver - IntentFilter intentFilter = new IntentFilter(); - intentFilter.addAction(Intent.ACTION_TIME_TICK); - intentFilter.addAction(Intent.ACTION_TIME_CHANGED); - intentFilter.addAction(Intent.ACTION_SCREEN_ON); - intentFilter.addAction(Intent.ACTION_SCREEN_OFF); - getContext().registerReceiver(receiver, intentFilter); - } - - @Override - protected void onDetachedFromWindow() { - super.onDetachedFromWindow(); - if (DEBUG) { - Log.d(TAG, "onDetachedFromWindow"); - } - getContext().unregisterReceiver(receiver); - cancelSecondAnimIfNeed(); - } - - @Override - protected void onSizeChanged(int w, int h, int oldw, int oldh) { - super.onSizeChanged(w, h, oldw, oldh); - reset(); - } - - @Override - protected void onDraw(Canvas canvas) { - canvas.drawColor(colorBg); - rotateCanvas(canvas); - drawContent(canvas); - } - - private void rotateCanvas(Canvas canvas) { - matrixCanvas.reset(); - - camera.save(); - camera.rotateX(canvasRotateX); - camera.rotateY(canvasRotateY); - camera.getMatrix(matrixCanvas); - camera.restore(); - - int matrixCenterX = centerX; - int matrixCenterY = centerY; - // This moves the center of the view into the upper left corner (0,0) - // which is necessary because Matrix always uses 0,0, as it's transform point - matrixCanvas.preTranslate(-matrixCenterX, -matrixCenterY); - // This happens after the camera rotations are applied, moving the view - // back to where it belongs, allowing us to rotate around the center or - // any point we choose - matrixCanvas.postTranslate(matrixCenterX, matrixCenterY); - - canvas.concat(matrixCanvas); - } - - private void translateCanvas(Canvas canvas, float x, float y, float z) { - matrixCanvas.reset(); - camera.save(); - camera.translate(x, y, z); - camera.getMatrix(matrixCanvas); - camera.restore(); - - int matrixCenterX = centerX; - int matrixCenterY = centerY; - matrixCanvas.preTranslate(-matrixCenterX, -matrixCenterY); - matrixCanvas.postTranslate(matrixCenterX, matrixCenterY); - - canvas.concat(matrixCanvas); - } - - private void drawContent(Canvas canvas) { - // Check rotate bound - if (rotateSecondPointer >= 360f) { - rotateSecondPointer %= 360f; - } - if (rotateMinutePointer >= 360f) { - rotateMinutePointer %= 360f; - } - if (rotateHourPointer >= 360f) { - rotateHourPointer %= 360f; - } - - // Rotate ring sweep gradient - matrixSweepGradient.setRotate(getProgressRingRotateDegree(), centerX, centerY); - sweepGradient.setLocalMatrix(matrixSweepGradient); - paintProgressRing.setShader(sweepGradient); - - // Draw scale ring - canvas.save(); - translateCanvas(canvas, 0f, 0f, zDepthScaleRing); - canvas.drawArc(boundScaleRing, scaleTextAngle, 90 - 2 * scaleTextAngle, false, paintScaleRing); - canvas.drawArc(boundScaleRing, 90 + scaleTextAngle, 90 - 2 * scaleTextAngle, false, paintScaleRing); - canvas.drawArc(boundScaleRing, 180 + scaleTextAngle, 90 - 2 * scaleTextAngle, false, paintScaleRing); - canvas.drawArc(boundScaleRing, 270 + scaleTextAngle, 90 - 2 * scaleTextAngle, false, paintScaleRing); - canvas.drawText("12", scaleTextDrawCoordinates[0][0], scaleTextDrawCoordinates[0][1], paintNumber); - canvas.drawText("6", scaleTextDrawCoordinates[1][0], scaleTextDrawCoordinates[1][1], paintNumber); - canvas.drawText("9", scaleTextDrawCoordinates[2][0], scaleTextDrawCoordinates[2][1], paintNumber); - canvas.drawText("3", scaleTextDrawCoordinates[3][0], scaleTextDrawCoordinates[3][1], paintNumber); - canvas.restore(); - - // Draw dash ring and second pointer - canvas.save(); - translateCanvas(canvas, 0f, 0f, zDepthDashRing); - canvas.drawArc(boundTimeRing, 0, 359.5f, false, paintBgRing); - canvas.drawArc(boundTimeRing, 0, 359.5f, false, paintProgressRing); - canvas.rotate(rotateSecondPointer, centerX, centerY); - canvas.drawPath(pathTriangle, paintTriangle); - canvas.restore(); - - // Draw hour pointer - canvas.save(); - translateCanvas(canvas, 0f, 0f, zDepthHourPointer); - canvas.rotate(rotateHourPointer, centerX, centerY); - canvas.drawPath(pathHourPointer, paintHourPointer); - canvas.drawCircle(centerX, centerY, radiusHPOuterRing, paintHPOuterRing); - canvas.drawCircle(centerX, centerY, radiusHPInnerRing, paintHPInnerCircle); - canvas.restore(); - - // Draw minute pointer - canvas.save(); - translateCanvas(canvas, 0f, 0f, zDepthMinutePointer); - canvas.rotate(rotateMinutePointer, centerX, centerY); - canvas.drawPath(pathMinutePointer, paintMinutePointer); - canvas.drawCircle(centerX, centerY, radiusMPOuterRing, paintMPOuterRing); - canvas.drawCircle(centerX, centerY, radiusMPInnerRing, paintMPInnerCircle); - canvas.restore(); - } - - @Override - public boolean onTouchEvent(MotionEvent event) { - float x = event.getX(); - float y = event.getY(); - - int action = event.getActionMasked(); - switch (action) { - case MotionEvent.ACTION_DOWN: { - cancelSteadyAnimIfNeed(); - rotateCanvasWhenMove(x, y); - return true; - } - case MotionEvent.ACTION_MOVE: { - rotateCanvasWhenMove(x, y); - return true; - } - case MotionEvent.ACTION_UP: { - cancelSteadyAnimIfNeed(); - startNewSteadyAnim(); - return true; - } - } - return super.onTouchEvent(event); - } - - private void rotateCanvasWhenMove(float x, float y) { - float dx = x - centerX; - float dy = y - centerY; - - float percentX = dx / (getWidth() / 2); - float percentY = dy / (getHeight() / 2); - - if (percentX > 1f) { - percentX = 1f; - } else if (percentX < -1f) { - percentX = -1f; - } - if (percentY > 1f) { - percentY = 1f; - } else if (percentY < -1f) { - percentY = -1f; - } - - canvasRotateY = canvasMaxRotateDegree * percentX; - canvasRotateX = -(canvasMaxRotateDegree * percentY); - } - - private void init() { - initValues(); - startNewSecondAnim(); - } - - private void initValues() { - // Colors - colorBg = 0xff237ead; - colorBgRing = 0x80ffffff; - colorMPOuterRing = Color.WHITE; - colorHPOuterRing = 0xccdddddd; - colorMinutePointer = Color.WHITE; - colorHourPointer = 0xccdddddd; - colorTriangle = 0xffdddddd; - colorScaleRing = 0xccdddddd; - - // Angle - progressRingInitRotateDegree = 270; - canvasMaxRotateDegree = 20; - scaleTextAngle = 5; - - // Radius - radiusMPOuterRing = dp2px(8); - radiusMPInnerRing = dp2px(4); - radiusHPOuterRing = dp2px(8); - radiusHPInnerRing = dp2px(4); - - // Stroke width - strokeWidthRing = dp2px(25); - strokeWidthScaleRing = dp2px(2); - - // zDepth - zDepthScaleRing = dp2px(130); - zDepthDashRing = dp2px(100); - zDepthHourPointer = dp2px(50); - zDepthMinutePointer = 0; - - // Text properties on scale ring - scaleTextSize = sp2px(20); - - // Hour pointer - hpTopEdgeLength = dp2px(3); - hpBottomEdgeLength = dp2px(5); - hpPointerLength = radius * 3 / 5; - hpTopCYOffset = dp2px(3); - - // Minute pointer - mpTopEdgeLength = dp2px(2); - mpBottomEdgeLength = dp2px(3); - mpPointerLength = radius * 4 / 5; - mpTopCYOffset = dp2px(3); - - // Second pointer - trianglePointerOffset = dp2px(6); - triangleSideLength = dp2px(20); - - // Ring dash intervals - ringDashIntervals = new float[]{dp2px(1), dp2px(3)}; - - // Sweep gradient - sweepGradientColorPos = new float[]{0f, 300f / 360f, 330f / 360f, 1f}; - sweepGradientColors = new int[]{Color.TRANSPARENT, 0x80ffffff, 0xddffffff, Color.WHITE}; - } - - private void reset() { - initBound(); - initBgRing(); - initProgressRing(); - initScaleRing(); - initTriangleSecondPointer(); - initMinutePointer(); - initHourPointer(); - updateTimePointer(); - } - - private void initBound() { - radius = getWidth() * 3 / 7; - radiusScaleRing = radius * 4 / 3; - - centerX = getWidth() / 2; - centerY = getHeight() / 2; - - boundTimeRing = new RectF(centerX - radius, centerY - radius, centerX + radius, centerY + radius); - boundScaleRing = new RectF(centerX - radiusScaleRing, centerY - radiusScaleRing, centerX + radiusScaleRing, centerY + radiusScaleRing); - - hpPointerLength = radius * 3 / 5; - mpPointerLength = radius * 3 / 5; - } - - private void initScaleRing() { - // Scale ring paint - paintScaleRing = new Paint(); - paintScaleRing.setAntiAlias(true); - paintScaleRing.setStyle(Paint.Style.STROKE); - paintScaleRing.setStrokeWidth(strokeWidthScaleRing); - paintScaleRing.setColor(colorScaleRing); - - // Number text paint - paintNumber = new Paint(); - paintNumber.setAntiAlias(true); - paintNumber.setStyle(Paint.Style.FILL); - paintNumber.setColor(colorScaleRing); - paintNumber.setTextSize(scaleTextSize); - - // Parse text baseline - float scaleTextWidthTwo = paintNumber.measureText("12"); - float scaleTextWidthOne = paintNumber.measureText("6"); - float scaleTextHeight = paintNumber.measureText("12"); - - RectF topTextBound = new RectF(); - topTextBound.left = centerX - scaleTextWidthTwo / 2; - topTextBound.top = centerY - radiusScaleRing - scaleTextHeight / 2; - topTextBound.right = centerX + scaleTextWidthTwo / 2; - topTextBound.bottom = centerY - radiusScaleRing + scaleTextHeight / 2; - - RectF bottomTextBound = new RectF(); - bottomTextBound.left = centerX - scaleTextWidthOne / 2; - bottomTextBound.top = centerY + radiusScaleRing - scaleTextHeight / 2; - bottomTextBound.right = centerX + scaleTextWidthOne / 2; - bottomTextBound.bottom = centerY + radiusScaleRing + scaleTextHeight / 2; - - RectF leftTextBound = new RectF(); - leftTextBound.left = centerX - radiusScaleRing - scaleTextWidthOne / 2; - leftTextBound.top = centerY - scaleTextHeight / 2; - leftTextBound.right = centerX - radiusScaleRing + scaleTextWidthOne / 2; - leftTextBound.bottom = centerY + scaleTextHeight / 2; - - RectF rightTextBound = new RectF(); - rightTextBound.left = centerX + radiusScaleRing - scaleTextWidthOne / 2; - rightTextBound.top = leftTextBound.top; - rightTextBound.right = centerX + radiusScaleRing + scaleTextWidthOne / 2; - rightTextBound.bottom = leftTextBound.bottom; - - Paint.FontMetrics fm = paintNumber.getFontMetrics(); - - scaleTextDrawCoordinates = new float[4][2]; - scaleTextDrawCoordinates[0][0] = topTextBound.left; - scaleTextDrawCoordinates[0][1] = topTextBound.top + (topTextBound.bottom - topTextBound.top) / 2 - (fm.bottom - fm.top) / 2 - fm.top; - scaleTextDrawCoordinates[1][0] = bottomTextBound.left; - scaleTextDrawCoordinates[1][1] = bottomTextBound.top + (bottomTextBound.bottom - bottomTextBound.top) / 2 - (fm.bottom - fm.top) / 2 - fm - .top; - scaleTextDrawCoordinates[2][0] = leftTextBound.left; - scaleTextDrawCoordinates[2][1] = leftTextBound.top + (leftTextBound.bottom - leftTextBound.top) / 2 - (fm.bottom - fm.top) / 2 - fm.top; - scaleTextDrawCoordinates[3][0] = rightTextBound.left; - scaleTextDrawCoordinates[3][1] = rightTextBound.top + (rightTextBound.bottom - rightTextBound.top) / 2 - (fm.bottom - fm.top) / 2 - fm.top; - } - - private void initHourPointer() { - // Center Ring - paintHPOuterRing = new Paint(); - paintHPOuterRing.setStyle(Paint.Style.FILL); - paintHPOuterRing.setAntiAlias(true); - paintHPOuterRing.setColor(colorHPOuterRing); - - paintHPInnerCircle = new Paint(paintHPOuterRing); - paintHPInnerCircle.setColor(colorBg); - - // Minute Pointer - paintHourPointer = new Paint(); - paintHourPointer.setAntiAlias(true); - paintHourPointer.setStyle(Paint.Style.FILL); - paintHourPointer.setColor(colorHourPointer); - - float topX1 = centerX - hpTopEdgeLength / 2; - float topY1 = centerY - hpPointerLength + strokeWidthRing / 2; - float topX2 = centerX + hpTopEdgeLength / 2; - float topY2 = topY1; - float topCX1 = centerX; - float topCY1 = topY1 - hpTopCYOffset; - - float bottomX1 = centerX - hpBottomEdgeLength / 2; - float bottomY1 = centerY; - float bottomX2 = centerX + hpBottomEdgeLength / 2; - float bottomY2 = bottomY1; - - pathHourPointer = new Path(); - pathHourPointer.moveTo(bottomX1, bottomY1); - pathHourPointer.lineTo(bottomX2, bottomY2); - pathHourPointer.lineTo(topX2, topY2); - pathHourPointer.quadTo(topCX1, topCY1, topX1, topY1); - pathHourPointer.close(); - } - - private void initMinutePointer() { - // Center Ring - paintMPOuterRing = new Paint(); - paintMPOuterRing.setStyle(Paint.Style.FILL); - paintMPOuterRing.setAntiAlias(true); - paintMPOuterRing.setColor(colorMPOuterRing); - - paintMPInnerCircle = new Paint(paintMPOuterRing); - paintMPInnerCircle.setColor(colorBg); - - // Minute Pointer - paintMinutePointer = new Paint(); - paintMinutePointer.setStyle(Paint.Style.FILL); - paintMinutePointer.setAntiAlias(true); - paintMinutePointer.setColor(colorMinutePointer); - - float topX1 = centerX - mpTopEdgeLength / 2; - float topY1 = centerY - mpPointerLength + strokeWidthRing / 2; - float topX2 = centerX + mpTopEdgeLength / 2; - float topY2 = topY1; - float topCX1 = centerX; - float topCY1 = topY1 - mpTopCYOffset; - - float bottomX1 = centerX - mpBottomEdgeLength / 2; - float bottomY1 = centerY; - float bottomX2 = centerX + mpBottomEdgeLength / 2; - float bottomY2 = bottomY1; - - pathMinutePointer = new Path(); - pathMinutePointer.moveTo(bottomX1, bottomY1); - pathMinutePointer.lineTo(bottomX2, bottomY2); - pathMinutePointer.lineTo(topX2, topY2); - pathMinutePointer.quadTo(topCX1, topCY1, topX1, topY1); - pathMinutePointer.close(); - } - - private void initTriangleSecondPointer() { - paintTriangle = new Paint(); - paintTriangle.setColor(colorTriangle); - paintTriangle.setStyle(Paint.Style.FILL); - paintTriangle.setAntiAlias(true); - - float height = (float) (1.0 * Math.sqrt(3f) / 2 * triangleSideLength); - - float point1x = centerX; - float point1y = centerY - radius + strokeWidthRing / 2 + trianglePointerOffset; - float point2x = point1x - triangleSideLength / 2; - float point2y = point1y + height; - float point3x = point1x + triangleSideLength / 2; - float point3y = point1y + height; - - pathTriangle = new Path(); - pathTriangle.moveTo(point1x, point1y); - pathTriangle.lineTo(point2x, point2y); - pathTriangle.lineTo(point3x, point3y); - pathTriangle.close(); - } - - private void initBgRing() { - paintBgRing = new Paint(); - paintBgRing.setStyle(Paint.Style.STROKE); - paintBgRing.setStrokeWidth(strokeWidthRing); - paintBgRing.setAntiAlias(true); - paintBgRing.setPathEffect(new DashPathEffect(ringDashIntervals, 0)); - paintBgRing.setColor(colorBgRing); - } - - private void initProgressRing() { - paintProgressRing = new Paint(paintBgRing); - paintProgressRing.setColor(Color.WHITE); - sweepGradient = new SweepGradient(centerX, centerY, sweepGradientColors, sweepGradientColorPos); - paintProgressRing.setShader(sweepGradient); - } - - private void startNewSecondAnim() { - if (DEBUG) { - Log.d(TAG, "startNewSecondAnim"); - } - cancelSecondAnimIfNeed(); - updateTimePointer(); - - final float startDegree = 0f; - final float endDegree = 360f; - secondAnim = ValueAnimator.ofFloat(startDegree, endDegree); - // FIXME 不知为何某些机型动画实际执行时间是duration的一半 - // 在构造函数里启动动画就正常,在其他方法(如onAttachedToWindow或onSizeChanged)里调用本方法就不正常 - // 出问题的机型:小米4、小米1S - secondAnim.setDuration(60 * 1000); - secondAnim.setInterpolator(new LinearInterpolator()); - secondAnim.setRepeatMode(ValueAnimator.RESTART); - secondAnim.setRepeatCount(ValueAnimator.INFINITE); - secondAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { - - private float lastDrawValue = 0; - private float drawInterval = 0.1f; - - private float lastUpdatePointerValue = 0; - private float updatePointerInterval = 360 / 60 * 5; - - @Override - public void onAnimationUpdate(ValueAnimator animation) { - float newValue = (float) animation.getAnimatedValue(); - - // Check if it is the time to update pointer position - float increasedPointerValue = newValue - lastUpdatePointerValue; - if (increasedPointerValue < 0) { - increasedPointerValue = endDegree + increasedPointerValue; - } - if (increasedPointerValue >= updatePointerInterval) { - lastUpdatePointerValue = newValue; - updateTimePointer(); - } - - // Check if it is the time to invalidate - float increasedDrawValue = newValue - lastDrawValue; - if (increasedDrawValue < 0) { - increasedDrawValue = endDegree + increasedDrawValue; - } - if (increasedDrawValue >= drawInterval) { - lastDrawValue = newValue; - rotateSecondPointer += increasedDrawValue; - invalidate(); + // register broadcast receiver + IntentFilter intentFilter = new IntentFilter(); + intentFilter.addAction(Intent.ACTION_TIME_TICK); + intentFilter.addAction(Intent.ACTION_TIME_CHANGED); + intentFilter.addAction(Intent.ACTION_SCREEN_ON); + intentFilter.addAction(Intent.ACTION_SCREEN_OFF); + getContext().registerReceiver(receiver, intentFilter); + } + + @Override + protected void onDetachedFromWindow() { + super.onDetachedFromWindow(); + if (DEBUG) { + Log.d(TAG, "onDetachedFromWindow"); + } + getContext().unregisterReceiver(receiver); + cancelSecondAnimIfNeed(); + } + + @Override + protected void onSizeChanged(int w, int h, int oldw, int oldh) { + super.onSizeChanged(w, h, oldw, oldh); + reset(); + } + + @Override + protected void onDraw(Canvas canvas) { + canvas.drawColor(colorBg); + rotateCanvas(canvas); + drawContent(canvas); + } + + private void rotateCanvas(Canvas canvas) { + matrixCanvas.reset(); + + camera.save(); + camera.rotateX(canvasRotateX); + camera.rotateY(canvasRotateY); + camera.getMatrix(matrixCanvas); + camera.restore(); + + int matrixCenterX = centerX; + int matrixCenterY = centerY; + // This moves the center of the view into the upper left corner (0,0) + // which is necessary because Matrix always uses 0,0, as it's transform point + matrixCanvas.preTranslate(-matrixCenterX, -matrixCenterY); + // This happens after the camera rotations are applied, moving the view + // back to where it belongs, allowing us to rotate around the center or + // any point we choose + matrixCanvas.postTranslate(matrixCenterX, matrixCenterY); + + canvas.concat(matrixCanvas); + } + + private void translateCanvas(Canvas canvas, float x, float y, float z) { + matrixCanvas.reset(); + camera.save(); + camera.translate(x, y, z); + camera.getMatrix(matrixCanvas); + camera.restore(); + + int matrixCenterX = centerX; + int matrixCenterY = centerY; + matrixCanvas.preTranslate(-matrixCenterX, -matrixCenterY); + matrixCanvas.postTranslate(matrixCenterX, matrixCenterY); + + canvas.concat(matrixCanvas); + } + + private void drawContent(Canvas canvas) { + // Check rotate bound + if (rotateSecondPointer >= 360f) { + rotateSecondPointer %= 360f; + } + if (rotateMinutePointer >= 360f) { + rotateMinutePointer %= 360f; + } + if (rotateHourPointer >= 360f) { + rotateHourPointer %= 360f; + } + + // Rotate ring sweep gradient + matrixSweepGradient.setRotate(getProgressRingRotateDegree(), centerX, centerY); + sweepGradient.setLocalMatrix(matrixSweepGradient); + paintProgressRing.setShader(sweepGradient); + + // Draw scale ring + canvas.save(); + canvas.translate(canvasTranslateX * 1, canvasRotateY * 1); + translateCanvas(canvas, 0f, 0f, zDepthScaleRing); + canvas.drawArc(boundScaleRing, scaleTextAngle, 90 - 2 * scaleTextAngle, false, paintScaleRing); + canvas.drawArc(boundScaleRing, 90 + scaleTextAngle, 90 - 2 * scaleTextAngle, false, paintScaleRing); + canvas.drawArc(boundScaleRing, 180 + scaleTextAngle, 90 - 2 * scaleTextAngle, false, paintScaleRing); + canvas.drawArc(boundScaleRing, 270 + scaleTextAngle, 90 - 2 * scaleTextAngle, false, paintScaleRing); + canvas.drawText("12", scaleTextDrawCoordinates[0][0], scaleTextDrawCoordinates[0][1], paintNumber); + canvas.drawText("6", scaleTextDrawCoordinates[1][0], scaleTextDrawCoordinates[1][1], paintNumber); + canvas.drawText("9", scaleTextDrawCoordinates[2][0], scaleTextDrawCoordinates[2][1], paintNumber); + canvas.drawText("3", scaleTextDrawCoordinates[3][0], scaleTextDrawCoordinates[3][1], paintNumber); + canvas.restore(); + + // Draw dash ring and second pointer + canvas.save(); + canvas.translate(canvasTranslateX * 3, canvasTranslateY * 3); + translateCanvas(canvas, 0f, 0f, zDepthDashRing); + canvas.drawArc(boundTimeRing, 0, 359.5f, false, paintBgRing); + canvas.drawArc(boundTimeRing, 0, 359.5f, false, paintProgressRing); + canvas.rotate(rotateSecondPointer, centerX, centerY); + canvas.drawPath(pathTriangle, paintTriangle); + canvas.restore(); + + // Draw hour pointer + canvas.save(); + canvas.translate(canvasTranslateX * 6, canvasTranslateY * 6); + translateCanvas(canvas, 0f, 0f, zDepthHourPointer); + canvas.rotate(rotateHourPointer, centerX, centerY); + canvas.drawPath(pathHourPointer, paintHourPointer); + canvas.drawCircle(centerX, centerY, radiusHPOuterRing, paintHPOuterRing); + canvas.drawCircle(centerX, centerY, radiusHPInnerRing, paintHPInnerCircle); + canvas.restore(); + + // Draw minute pointer + canvas.save(); + canvas.translate(canvasTranslateX * 9, canvasTranslateY * 9f); + translateCanvas(canvas, 0f, 0f, zDepthMinutePointer); + canvas.rotate(rotateMinutePointer, centerX, centerY); + canvas.drawPath(pathMinutePointer, paintMinutePointer); + canvas.drawCircle(centerX, centerY, radiusMPOuterRing, paintMPOuterRing); + canvas.drawCircle(centerX, centerY, radiusMPInnerRing, paintMPInnerCircle); + canvas.restore(); + } + + @Override + public boolean onTouchEvent(MotionEvent event) { + float x = event.getX(); + float y = event.getY(); + + int action = event.getActionMasked(); + switch (action) { + case MotionEvent.ACTION_DOWN: { + cancelSteadyAnimIfNeed(); + rotateCanvasWhenMove(x, y); + return true; + } + case MotionEvent.ACTION_MOVE: { + rotateCanvasWhenMove(x, y); + return true; + } + case MotionEvent.ACTION_UP: { + cancelSteadyAnimIfNeed(); + startNewSteadyAnim(); + return true; + } + } + return super.onTouchEvent(event); + } + + private void rotateCanvasWhenMove(float x, float y) { + float dx = x - centerX; + float dy = y - centerY; + + float percentX = dx / (getWidth() / 2); + float percentY = dy / (getHeight() / 2); + + if (percentX > 1f) { + percentX = 1f; + } else if (percentX < -1f) { + percentX = -1f; + } + if (percentY > 1f) { + percentY = 1f; + } else if (percentY < -1f) { + percentY = -1f; + } + + canvasRotateY = canvasMaxRotateDegree * percentX; + canvasRotateX = -(canvasMaxRotateDegree * percentY); + canvasTranslateX = (float) canvasMaxTranslateSlope * percentX; + canvasTranslateY = (float) canvasMaxTranslateSlope * percentY; + } + + private void init() { + initValues(); + startNewSecondAnim(); + } + + private void initValues() { + // Colors + colorBg = 0xff237ead; + colorBgRing = 0x80ffffff; + colorMPOuterRing = Color.WHITE; + colorHPOuterRing = 0xccdddddd; + colorMinutePointer = Color.WHITE; + colorHourPointer = 0xccdddddd; + colorTriangle = 0xffdddddd; + colorScaleRing = 0xccdddddd; + + // Angle + progressRingInitRotateDegree = 270; + canvasMaxRotateDegree = 15; + canvasMaxTranslateSlope = Math.tan(Math.toRadians(90.0 - canvasMaxRotateDegree)); + scaleTextAngle = 5; + + // Radius + radiusMPOuterRing = dp2px(8); + radiusMPInnerRing = dp2px(4); + radiusHPOuterRing = dp2px(8); + radiusHPInnerRing = dp2px(4); + + // Stroke width + strokeWidthRing = dp2px(25); + strokeWidthScaleRing = dp2px(2); + + // zDepth + zDepthScaleRing = dp2px(130); + zDepthDashRing = dp2px(100); + zDepthHourPointer = dp2px(50); + zDepthMinutePointer = 0; + + // Text properties on scale ring + scaleTextSize = sp2px(20); + + // Hour pointer + hpTopEdgeLength = dp2px(3); + hpBottomEdgeLength = dp2px(5); + hpPointerLength = radius * 3 / 5; + hpTopCYOffset = dp2px(3); + + // Minute pointer + mpTopEdgeLength = dp2px(2); + mpBottomEdgeLength = dp2px(3); + mpPointerLength = radius * 4 / 5; + mpTopCYOffset = dp2px(3); + + // Second pointer + trianglePointerOffset = dp2px(6); + triangleSideLength = dp2px(20); + + // Ring dash intervals + ringDashIntervals = new float[]{dp2px(1), dp2px(3)}; + + // Sweep gradient + sweepGradientColorPos = new float[]{0f, 300f / 360f, 330f / 360f, 1f}; + sweepGradientColors = new int[]{Color.TRANSPARENT, 0x80ffffff, 0xddffffff, Color.WHITE}; + } + + private void reset() { + initBound(); + initBgRing(); + initProgressRing(); + initScaleRing(); + initTriangleSecondPointer(); + initMinutePointer(); + initHourPointer(); + updateTimePointer(); + } + + private void initBound() { + radius = getWidth() * 3 / 7; + radiusScaleRing = radius * 4 / 3; + + centerX = getWidth() / 2; + centerY = getHeight() / 2; + + boundTimeRing = new RectF(centerX - radius, centerY - radius, centerX + radius, centerY + radius); + boundScaleRing = new RectF(centerX - radiusScaleRing, centerY - radiusScaleRing, centerX + radiusScaleRing, centerY + radiusScaleRing); + + hpPointerLength = radius * 3 / 5; + mpPointerLength = radius * 3 / 5; + } + + private void initScaleRing() { + // Scale ring paint + paintScaleRing = new Paint(); + paintScaleRing.setAntiAlias(true); + paintScaleRing.setStyle(Paint.Style.STROKE); + paintScaleRing.setStrokeWidth(strokeWidthScaleRing); + paintScaleRing.setColor(colorScaleRing); + + // Number text paint + paintNumber = new Paint(); + paintNumber.setAntiAlias(true); + paintNumber.setStyle(Paint.Style.FILL); + paintNumber.setColor(colorScaleRing); + paintNumber.setTextSize(scaleTextSize); + + // Parse text baseline + float scaleTextWidthTwo = paintNumber.measureText("12"); + float scaleTextWidthOne = paintNumber.measureText("6"); + float scaleTextHeight = paintNumber.measureText("12"); + + RectF topTextBound = new RectF(); + topTextBound.left = centerX - scaleTextWidthTwo / 2; + topTextBound.top = centerY - radiusScaleRing - scaleTextHeight / 2; + topTextBound.right = centerX + scaleTextWidthTwo / 2; + topTextBound.bottom = centerY - radiusScaleRing + scaleTextHeight / 2; + + RectF bottomTextBound = new RectF(); + bottomTextBound.left = centerX - scaleTextWidthOne / 2; + bottomTextBound.top = centerY + radiusScaleRing - scaleTextHeight / 2; + bottomTextBound.right = centerX + scaleTextWidthOne / 2; + bottomTextBound.bottom = centerY + radiusScaleRing + scaleTextHeight / 2; + + RectF leftTextBound = new RectF(); + leftTextBound.left = centerX - radiusScaleRing - scaleTextWidthOne / 2; + leftTextBound.top = centerY - scaleTextHeight / 2; + leftTextBound.right = centerX - radiusScaleRing + scaleTextWidthOne / 2; + leftTextBound.bottom = centerY + scaleTextHeight / 2; + + RectF rightTextBound = new RectF(); + rightTextBound.left = centerX + radiusScaleRing - scaleTextWidthOne / 2; + rightTextBound.top = leftTextBound.top; + rightTextBound.right = centerX + radiusScaleRing + scaleTextWidthOne / 2; + rightTextBound.bottom = leftTextBound.bottom; + + Paint.FontMetrics fm = paintNumber.getFontMetrics(); + + scaleTextDrawCoordinates = new float[4][2]; + scaleTextDrawCoordinates[0][0] = topTextBound.left; + scaleTextDrawCoordinates[0][1] = topTextBound.top + (topTextBound.bottom - topTextBound.top) / 2 - (fm.bottom - fm.top) / 2 - fm.top; + scaleTextDrawCoordinates[1][0] = bottomTextBound.left; + scaleTextDrawCoordinates[1][1] = bottomTextBound.top + (bottomTextBound.bottom - bottomTextBound.top) / 2 - (fm.bottom - fm.top) / 2 - fm + .top; + scaleTextDrawCoordinates[2][0] = leftTextBound.left; + scaleTextDrawCoordinates[2][1] = leftTextBound.top + (leftTextBound.bottom - leftTextBound.top) / 2 - (fm.bottom - fm.top) / 2 - fm.top; + scaleTextDrawCoordinates[3][0] = rightTextBound.left; + scaleTextDrawCoordinates[3][1] = rightTextBound.top + (rightTextBound.bottom - rightTextBound.top) / 2 - (fm.bottom - fm.top) / 2 - fm.top; + } + + private void initHourPointer() { + // Center Ring + paintHPOuterRing = new Paint(); + paintHPOuterRing.setStyle(Paint.Style.FILL); + paintHPOuterRing.setAntiAlias(true); + paintHPOuterRing.setColor(colorHPOuterRing); + + paintHPInnerCircle = new Paint(paintHPOuterRing); + paintHPInnerCircle.setColor(colorBg); + + // Minute Pointer + paintHourPointer = new Paint(); + paintHourPointer.setAntiAlias(true); + paintHourPointer.setStyle(Paint.Style.FILL); + paintHourPointer.setColor(colorHourPointer); + + float topX1 = centerX - hpTopEdgeLength / 2; + float topY1 = centerY - hpPointerLength + strokeWidthRing / 2; + float topX2 = centerX + hpTopEdgeLength / 2; + float topY2 = topY1; + float topCX1 = centerX; + float topCY1 = topY1 - hpTopCYOffset; + + float bottomX1 = centerX - hpBottomEdgeLength / 2; + float bottomY1 = centerY; + float bottomX2 = centerX + hpBottomEdgeLength / 2; + float bottomY2 = bottomY1; + + pathHourPointer = new Path(); + pathHourPointer.moveTo(bottomX1, bottomY1); + pathHourPointer.lineTo(bottomX2, bottomY2); + pathHourPointer.lineTo(topX2, topY2); + pathHourPointer.quadTo(topCX1, topCY1, topX1, topY1); + pathHourPointer.close(); + } + + private void initMinutePointer() { + // Center Ring + paintMPOuterRing = new Paint(); + paintMPOuterRing.setStyle(Paint.Style.FILL); + paintMPOuterRing.setAntiAlias(true); + paintMPOuterRing.setColor(colorMPOuterRing); + + paintMPInnerCircle = new Paint(paintMPOuterRing); + paintMPInnerCircle.setColor(colorBg); + + // Minute Pointer + paintMinutePointer = new Paint(); + paintMinutePointer.setStyle(Paint.Style.FILL); + paintMinutePointer.setAntiAlias(true); + paintMinutePointer.setColor(colorMinutePointer); + + float topX1 = centerX - mpTopEdgeLength / 2; + float topY1 = centerY - mpPointerLength + strokeWidthRing / 2; + float topX2 = centerX + mpTopEdgeLength / 2; + float topY2 = topY1; + float topCX1 = centerX; + float topCY1 = topY1 - mpTopCYOffset; + + float bottomX1 = centerX - mpBottomEdgeLength / 2; + float bottomY1 = centerY; + float bottomX2 = centerX + mpBottomEdgeLength / 2; + float bottomY2 = bottomY1; + + pathMinutePointer = new Path(); + pathMinutePointer.moveTo(bottomX1, bottomY1); + pathMinutePointer.lineTo(bottomX2, bottomY2); + pathMinutePointer.lineTo(topX2, topY2); + pathMinutePointer.quadTo(topCX1, topCY1, topX1, topY1); + pathMinutePointer.close(); + } + + private void initTriangleSecondPointer() { + paintTriangle = new Paint(); + paintTriangle.setColor(colorTriangle); + paintTriangle.setStyle(Paint.Style.FILL); + paintTriangle.setAntiAlias(true); + + float height = (float) (1.0 * Math.sqrt(3f) / 2 * triangleSideLength); + + float point1x = centerX; + float point1y = centerY - radius + strokeWidthRing / 2 + trianglePointerOffset; + float point2x = point1x - triangleSideLength / 2; + float point2y = point1y + height; + float point3x = point1x + triangleSideLength / 2; + float point3y = point1y + height; + + pathTriangle = new Path(); + pathTriangle.moveTo(point1x, point1y); + pathTriangle.lineTo(point2x, point2y); + pathTriangle.lineTo(point3x, point3y); + pathTriangle.close(); + } + + private void initBgRing() { + paintBgRing = new Paint(); + paintBgRing.setStyle(Paint.Style.STROKE); + paintBgRing.setStrokeWidth(strokeWidthRing); + paintBgRing.setAntiAlias(true); + paintBgRing.setPathEffect(new DashPathEffect(ringDashIntervals, 0)); + paintBgRing.setColor(colorBgRing); + } + + private void initProgressRing() { + paintProgressRing = new Paint(paintBgRing); + paintProgressRing.setColor(Color.WHITE); + sweepGradient = new SweepGradient(centerX, centerY, sweepGradientColors, sweepGradientColorPos); + paintProgressRing.setShader(sweepGradient); + } + + private void startNewSecondAnim() { + if (DEBUG) { + Log.d(TAG, "startNewSecondAnim"); + } + cancelSecondAnimIfNeed(); + updateTimePointer(); + + final float startDegree = 0f; + final float endDegree = 360f; + secondAnim = ValueAnimator.ofFloat(startDegree, endDegree); + // FIXME 不知为何某些机型动画实际执行时间是duration的一半 + // 在构造函数里启动动画就正常,在其他方法(如onAttachedToWindow或onSizeChanged)里调用本方法就不正常 + // 出问题的机型:小米4、小米1S + secondAnim.setDuration(60 * 1000); + secondAnim.setInterpolator(new LinearInterpolator()); + secondAnim.setRepeatMode(ValueAnimator.RESTART); + secondAnim.setRepeatCount(ValueAnimator.INFINITE); + secondAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { + + private float lastDrawValue = 0; + private float drawInterval = 0.1f; + + private float lastUpdatePointerValue = 0; + private float updatePointerInterval = 360 / 60 * 5; + + @Override + public void onAnimationUpdate(ValueAnimator animation) { + float newValue = (float) animation.getAnimatedValue(); + + // Check if it is the time to update pointer position + float increasedPointerValue = newValue - lastUpdatePointerValue; + if (increasedPointerValue < 0) { + increasedPointerValue = endDegree + increasedPointerValue; + } + if (increasedPointerValue >= updatePointerInterval) { + lastUpdatePointerValue = newValue; + updateTimePointer(); + } + + // Check if it is the time to invalidate + float increasedDrawValue = newValue - lastDrawValue; + if (increasedDrawValue < 0) { + increasedDrawValue = endDegree + increasedDrawValue; + } + if (increasedDrawValue >= drawInterval) { + lastDrawValue = newValue; + rotateSecondPointer += increasedDrawValue; + invalidate(); // if (DEBUG) { // Log.d(TAG, String.format("newValue:%s , currentPlayTime:%s", newValue, animation.getCurrentPlayTime())); // } - } - } - }); - secondAnim.start(); - } - - private void cancelSecondAnimIfNeed() { - if (secondAnim != null && (secondAnim.isStarted() || secondAnim.isRunning())) { - secondAnim.cancel(); - if (DEBUG) { - Log.d(TAG, "cancelSecondAnimIfNeed"); - } - } - } - - private void startNewSteadyAnim() { - final String propertyNameRotateX = "canvasRotateX"; - final String propertyNameRotateY = "canvasRotateY"; - - PropertyValuesHolder holderRotateX = PropertyValuesHolder.ofFloat(propertyNameRotateX, canvasRotateX, 0); - PropertyValuesHolder holderRotateY = PropertyValuesHolder.ofFloat(propertyNameRotateY, canvasRotateY, 0); - steadyAnim = ValueAnimator.ofPropertyValuesHolder(holderRotateX, holderRotateY); - steadyAnim.setDuration(1000); - steadyAnim.setInterpolator(new BounceInterpolator()); - steadyAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { - @Override - public void onAnimationUpdate(ValueAnimator animation) { - canvasRotateX = (float) animation.getAnimatedValue(propertyNameRotateX); - canvasRotateY = (float) animation.getAnimatedValue(propertyNameRotateY); - } - }); - steadyAnim.start(); - } - - private void cancelSteadyAnimIfNeed() { - if (steadyAnim != null && (steadyAnim.isStarted() || steadyAnim.isRunning())) { - steadyAnim.cancel(); - } - } - - private void updateTimePointer() { - int second = Calendar.getInstance().get(Calendar.SECOND); - int minute = Calendar.getInstance().get(Calendar.MINUTE); - int hour = Calendar.getInstance().get(Calendar.HOUR); - float percentSecond = (float) (1.0 * second / 60); - float percentMinute = (float) (1.0 * (minute * 60 + second) / (60 * 60)); - float percentHour = (float) (1.0 * (hour * 60 * 60 + minute * 60 + second) / (60 * 60 * 12)); - rotateSecondPointer = 360 * percentSecond; - rotateMinutePointer = 360 * percentMinute; - rotateHourPointer = 360 * percentHour; - } - - private float getProgressRingRotateDegree() { - float degree = (rotateSecondPointer + progressRingInitRotateDegree) % 360; - return degree; - } - - public float dp2px(float dpValue) { - return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dpValue, getResources().getDisplayMetrics()); - } - - public float sp2px(float spValue) { - return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, spValue, getResources().getDisplayMetrics()); - } - - private BroadcastReceiver receiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - if (intent == null) { - return; - } - String action = intent.getAction(); - if (TextUtils.isEmpty(action)) { - return; - } - if (DEBUG) { - Log.d(TAG, String.format("action -> %s", action)); - } - if (action.equals(Intent.ACTION_TIME_TICK)) { - updateTimePointer(); - } else if (action.equals(Intent.ACTION_SCREEN_ON)) { - startNewSecondAnim(); - } else if (action.equals(Intent.ACTION_SCREEN_OFF)) { - cancelSecondAnimIfNeed(); - } - } - }; + } + } + }); + secondAnim.start(); + } + + private void cancelSecondAnimIfNeed() { + if (secondAnim != null && (secondAnim.isStarted() || secondAnim.isRunning())) { + secondAnim.cancel(); + if (DEBUG) { + Log.d(TAG, "cancelSecondAnimIfNeed"); + } + } + } + + private void startNewSteadyAnim() { + final String propertyNameRotateX = "canvasRotateX"; + final String propertyNameRotateY = "canvasRotateY"; + final String propertyNameTranslateX = "canvasTranslateX"; + final String propertyNameTranslateY = "canvasTranslateY"; + + PropertyValuesHolder holderRotateX = PropertyValuesHolder.ofFloat(propertyNameRotateX, canvasRotateX, 0); + PropertyValuesHolder holderRotateY = PropertyValuesHolder.ofFloat(propertyNameRotateY, canvasRotateY, 0); + PropertyValuesHolder holderTranslateX = PropertyValuesHolder.ofFloat(propertyNameTranslateX, canvasTranslateX, 0); + PropertyValuesHolder holderTranslateY = PropertyValuesHolder.ofFloat(propertyNameTranslateY, canvasTranslateY, 0); + steadyAnim = ValueAnimator.ofPropertyValuesHolder(holderRotateX, holderRotateY, holderTranslateX, holderTranslateY); + steadyAnim.setDuration(1000); + steadyAnim.setInterpolator(new BounceInterpolator()); + steadyAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { + @Override + public void onAnimationUpdate(ValueAnimator animation) { + canvasRotateX = (float) animation.getAnimatedValue(propertyNameRotateX); + canvasRotateY = (float) animation.getAnimatedValue(propertyNameRotateY); + canvasTranslateX = (float) animation.getAnimatedValue(propertyNameTranslateX); + canvasTranslateY = (float) animation.getAnimatedValue(propertyNameTranslateY); + + } + }); + steadyAnim.start(); + } + + private void cancelSteadyAnimIfNeed() { + if (steadyAnim != null && (steadyAnim.isStarted() || steadyAnim.isRunning())) { + steadyAnim.cancel(); + } + } + + private void updateTimePointer() { + int second = Calendar.getInstance().get(Calendar.SECOND); + int minute = Calendar.getInstance().get(Calendar.MINUTE); + int hour = Calendar.getInstance().get(Calendar.HOUR); + float percentSecond = (float) (1.0 * second / 60); + float percentMinute = (float) (1.0 * (minute * 60 + second) / (60 * 60)); + float percentHour = (float) (1.0 * (hour * 60 * 60 + minute * 60 + second) / (60 * 60 * 12)); + rotateSecondPointer = 360 * percentSecond; + rotateMinutePointer = 360 * percentMinute; + rotateHourPointer = 360 * percentHour; + } + + private float getProgressRingRotateDegree() { + float degree = (rotateSecondPointer + progressRingInitRotateDegree) % 360; + return degree; + } + + public float dp2px(float dpValue) { + return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dpValue, getResources().getDisplayMetrics()); + } + + public float sp2px(float spValue) { + return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, spValue, getResources().getDisplayMetrics()); + } + + private BroadcastReceiver receiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + if (intent == null) { + return; + } + String action = intent.getAction(); + if (TextUtils.isEmpty(action)) { + return; + } + if (DEBUG) { + Log.d(TAG, String.format("action -> %s", action)); + } + if (action.equals(Intent.ACTION_TIME_TICK)) { + updateTimePointer(); + } else if (action.equals(Intent.ACTION_SCREEN_ON)) { + startNewSecondAnim(); + } else if (action.equals(Intent.ACTION_SCREEN_OFF)) { + cancelSecondAnimIfNeed(); + } + } + }; } \ No newline at end of file diff --git a/art/miui-1.png b/art/miui-1.png new file mode 100644 index 0000000..334115b Binary files /dev/null and b/art/miui-1.png differ diff --git a/art/update.png b/art/update.png new file mode 100644 index 0000000..5e1ba0a Binary files /dev/null and b/art/update.png differ diff --git a/build.gradle b/build.gradle index a7134c9..4ecebd9 100644 --- a/build.gradle +++ b/build.gradle @@ -5,7 +5,7 @@ buildscript { jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:1.0.1' + classpath 'com.android.tools.build:gradle:2.2.0' // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 0c71e76..5c35eec 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Wed Apr 10 15:27:10 PDT 2013 +#Wed Oct 05 08:21:12 CST 2016 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-2.2.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-all.zip