以前一直习惯把自己的项目和学习的知识本地储存,最近发现这个做法大错特错,也迫于找工作的关系,打算把以前自己的笔记逐步放到网上,今天是第一篇,是现在在做的一个项目的clock界面。
两个构造函数,很简单,自定义View必写,其中的init()包含了数据初始化和开始时钟的针走动动画。
public ClockView(Context context) { super(context); init();}public ClockView(Context context, AttributeSet attrs) { super(context, attrs); init();}//初始化数据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; // touch对图像产生的最大倾斜角度 scaleTextAngle = 5; // Radius radiusMPOuterRing = dp2px(8); // 分针中间小白圈外圆直径 radiusMPInnerRing = dp2px(4); // 分针中间小白圈内圆直径 radiusHPOuterRing = dp2px(8); // 时钟中间圈(透明)外圆直径 radiusHPInnerRing = dp2px(4); // 时钟中间圈(透明)内圆直径 // Stroke width strokeWidthRing = dp2px(25); // 环形刻度线圆环宽度 strokeWidthScaleRing = dp2px(2); // 外圈圆环宽度 // zDepth 难道是各个图形z轴(即垂直屏幕)的高度? zDepthScaleRing = dp2px(110); // 外圆环 zDepthDashRing = dp2px(90); // 刻度线圆环 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};}重写onDraw函数
@Overrideprotected void onDraw(Canvas canvas) { canvas.drawColor(colorBg); // 设置点击的3D效果 rotateCanvas(canvas); drawContent(canvas);}// 设置点击的3D效果private void rotateCanvas(Canvas canvas) { // 利用Camera和Matrix可以绘制3D图像 matrixCanvas.reset(); camera.save(); camera.rotateX(canvasRotateX); camera.rotateY(canvasRotateY); camera.getMatrix(matrixCanvas); camera.restore(); int matrixCenterX = centerX; int matrixCenterY = centerY; matrixCanvas.preTranslate(-matrixCenterX, -matrixCenterY); matrixCanvas.postTranslate(matrixCenterX, matrixCenterY); // 对matrix的变换应用到canvas上的所有对象 canvas.concat(matrixCanvas);}camera位于坐标点(0,0),也就是视图的左上角; camera.translate(10, 20, 30)的意思是把观察物体右移10,上移20,向前移30(即让物体远离camera, 这样物体将会变小); camera.rotateX(45)的意思是绕x轴顺时针旋转45度。举例来说,如果物体中间线和x轴重合的话, 绕x轴顺时针旋转45度就是指物体上半部分向里翻转,下半部分向外翻转; camera.rotateY(45)的意思是绕y轴顺时针旋转45度。举例来说,如果物体中间线和y轴重合的话,绕y轴顺时针旋转45度就是指物体右半部分向里翻转,左半部分向外翻转; camera.rotateZ(45)的意思是绕z轴顺时针旋转45度。举例来说,如果物体中间线和z轴重合的
话,绕z轴顺时针旋转45度就是指物体上半部分向左翻转,下半部分向右翻转;
preTranslate是指在setScale前,平移,postTranslate是指在setScale后平移 注意他们参数是平移的距离,而不是平移目的地的坐标! 由于缩放是以(0,0)为中心的,所以为了把界面的中心与(0,0)对齐,就要preTranslate(-centerX, -centerY), setScale完成后,调用postTranslate(centerX, centerY), 再把图片移回来,这样看到的动画效果就是activity的界面图片从中心不停的缩放了 注:centerX和centerY是界面中心的坐标
矩阵类:matrix float matrix = {MSCALE_X, MSKEW_X, MTRANS_X, MSKEW_Y, MSCALE_Y, MTRANS_Y, MPERSP_0, MPERSP_1, MPERSP_2 };
该操作主要是将图片旋转中心设置为屏幕中心,使X轴为图片的中心线 preTranslate方法的作用是在旋转之间先把图片向上移动图片高度的一半的距离, 这样图片就关于x轴对称了, 然后再进行旋转的变换, postTranslate方法是在变换之后再将图片向下移动图片高度的一半的距离也即是回到了原来的位置, 这样图片显示出来的结果就是对称的了。原理也很简单,旋转中心还是(0,0),只不过我们移动图片, 这样进行旋转变换的时候就会得到对称的结果了。
设置好点击效果后绘制界面
private void drawContent(Canvas canvas) { // 保证时分秒针的转动角度在0~360之内,即超过360重新从0开始计算 if (rotateSecondPointer >= 360f) { rotateSecondPointer %= 360f; } if (rotateMinutePointer >= 360f) { rotateMinutePointer %= 360f; } if (rotateHourPointer >= 360f) { rotateHourPointer %= 360f; } // 设置旋转 使用getProgressRingRotateDegree()为转过的角度 matrixSweepGradient.setRotate(getProgressRingRotateDegree(), centerX, centerY); sweepGradient.setLocalMatrix(matrixSweepGradient); // 设置渐变,类似于雷达效果 paintProgressRing.setShader(sweepGradient); // 画最外圈 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(); canvas.save(); translateCanvas(canvas, 0f, 0f, zDepthDashRing); //设置旋转 canvas.drawArc(boundTimeRing, 0, 360f, false, paintBgRing); // 画刻度圆盘(基本颜色) canvas.drawArc(boundTimeRing, 0, 360f, false, paintProgressRing); // 画秒针扫描环 //设置旋转(需要放在扫描环后面,避免扫描环产生两倍扫描速度) canvas.rotate(rotateSecondPointer, centerX, centerY); canvas.drawPath(pathTriangle, paintTriangle); //绘制三角形秒针 canvas.restore(); // 绘制时针 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(); // 绘制分针 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();}作用代码里写了,就不解释了
重写onTouch
// 设置touch事件@Overridepublic 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) { // 计算touch事件位置相对于屏幕中心的偏移量 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; } // 当拖动到屏幕边缘时会产生最大倾斜角度:canvasMaxRotateDegree // 计算拖动产生的倾斜角度 canvasRotateY = canvasMaxRotateDegree * percentX; canvasRotateX = -(canvasMaxRotateDegree * percentY);}刷新的时候需要重置的数据
private void reset() { initBound(); initBgRing(); initProgressRing(); initScaleRing(); initTriangleSecondPointer(); initMinutePointer(); initHourPointer(); updateTimePointer();}private void initBound() { radius = getWidth() * 3 / 7; // 半径为占屏幕的3/7 radiusScaleRing = radius * 4 / 3; // 半径为占屏幕的4/7 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 // 最外圈的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"); // 绘制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; // 绘制6的矩形框 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; // 绘制9的矩形框 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; // 绘制3的矩形框 RectF rightTextBound = new RectF(); rightTextBound.left = centerX + radiusScaleRing - scaleTextWidthOne / 2; rightTextBound.top = leftTextBound.top; rightTextBound.right = centerX + radiusScaleRing + scaleTextWidthOne / 2; rightTextBound.bottom = leftTextBound.bottom; /** * Top指的是指的是最高字符到baseline的值,即ascent的最大值, * bottom指的是最下字符到baseline的值, 即descent的最大值 */ // Top + 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 指针的paint paintHourPointer = new Paint(); paintHourPointer.setAntiAlias(true); paintHourPointer.setStyle(Paint.Style.FILL); paintHourPointer.setColor(colorHourPointer); // 时针的path 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); // 贝塞尔曲线(平滑曲线) topCX1, topCY1控制点;topX1, topY1为终点 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 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;}paint.setPathEffect():给画笔Paint对象设置绘制路径时的特效DashPathEffect 构造方法的参数决定了绘制的路径效果:public DashPathEffect(float[] intervals, float phase ) intervals是一个float数组,且其长度必须是偶数且>=2,指定了多少长度的实线之后再画多少长度的空白。 例如new DashPathEffect(new float[] { 8, 10, 8, 10}, 0); 指定了绘制8px的实线,再绘制10px的透明,再绘制8px的实线,再绘制10px的透明, 依次重复来绘制达到path对象的长度。 phase参数指定了绘制的虚线相对了起始地址(Path起点)的取余偏移(对路径总长度)。
秒针动画的设置
// 开始秒针动画private void 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) { // 通过getAnimatedValue()的方法来获取当前帧的值。 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(); }}Property Animation提供了AnimatorListener和AnimatorUpdateListener 两个监听器用于动画在播放过程中的重要动画事件 Animator.AnimatorListener: onAnimationStart() —— 动画开始时调用; onAnimationEnd() —— 动画结束时调用; onAnimationRepeat() —— 动画循环播放时调用; onAnimationCancel() —— 动画被取消时调用。不管终止的方式如何,被取消的动画仍然会调onAnimationEnd(); Animator.AnimatorUpdateListener: onAnimationUpdate() —— 动画每播放一帧时调用。 在动画过程中,可侦听此事件来获取并使用 ValueAnimator 计算出来的属性值。利用传入事件的 ValueAnimator 对象, 调用其 getAnimatedValue() 方法即可获取当前的属性值。如果使用 ValueAnimator来实现动画的话 , 则必需实现此侦听器。
倾斜动画 + 小工具 + 注册广播
// 倾斜返回动画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(500); steadyAnim.setInterpolator(new OvershootInterpolator()); 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 float getProgressRingRotateDegree() { // 应为角度是从90度即指向x轴正方向为初始,所以计算前要加上270度 float degree = (rotateSecondPointer + progressRingInitRotateDegree) % 360; return degree;}// 将dp转换为pxpublic float dp2px(float dpValue) { return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_Dip, dpValue, getResources().getDisplayMetrics());}// 将sp转换为pxpublic float sp2px(float spValue) { return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, spValue, getResources().getDisplayMetrics());}作用看代码
源码:https://github.com/DoyleCzh/Android-xiaomi-clock 结束
新闻热点
疑难解答