目录表java
这是最终的实现效果(左上),主要包括刻度线的绘制和拖动效果:android
此效果由view绘制实现,用到了圆弧、bitmap和文字的绘制api。刻度线的绘制则是经过不断旋转canvas画布来循环画线实现的,都是比较常规的绘制方案。git
在绘制bitmap的时候,难点是肯定bitmap的坐标(图中圆环的中心坐标),即根据圆心坐标,半径,扇形角度来求扇形终射线与圆弧交叉点的xy坐标。若是咱们知道bitmap的左上角坐标,那么绘制工做就很简单了。所幸网上已经提供了解决方案及背后的数学模型。代码详情。github
拖动效果是在咱们容许的区域内,当手指按下,手指滑动,手指弹起时,不断绘制对应的进度p,给人一种圆环被拖着动画的错觉,其实这只是不断重绘的结果。这里须要咱们经过onTouchEvent方法来监听手势及获取当前坐标。难点在于这是一个弧形轨迹,咱们怎么经过当前坐标来获取角度,再根据角度获取相对应的进度。代码示例以下:web
@Override public synchronized boolean onTouchEvent(MotionEvent event) { int action = event.getAction(); int x = (int) event.getX(); int y = (int) event.getY(); switch (action) { case MotionEvent.ACTION_DOWN: // isOnRing 注释见下 if (isOnRing(x, y) && y <= radius + getPaddingTop() + specialScaleLineLength + scaleToRingSpace) { updateProgress(x, y); return true; } break; case MotionEvent.ACTION_MOVE: if(y <= radius + getPaddingTop() + specialScaleLineLength + scaleToRingSpace) { updateProgress(x, y); } return true; case MotionEvent.ACTION_UP: invalidate(); break; } return super.onTouchEvent(event); } /** * 根据当前点的位置求角度,再转换成当前进度 */ private void updateProgress(int eventX, int eventY) { double angle = Math.atan2(eventY - (radius + getPaddingLeft() + specialScaleLineLength + scaleToRingSpace) , eventX - (radius + getPaddingLeft() + specialScaleLineLength + scaleToRingSpace)) / Math.PI; angle = ((2 + angle) % 2 + (-beginLocation / 180f)) % 2; if((int)Math.round(angle * 100) >= 0){ progress = (int)Math.round(angle * 100); realShowProgress = getShowProgress(progress); } invalidate(); }
须要注意的是,在咱们拖动小图标时,咱们须要定一个特定的接收事件的区域范围,只有当用户按在了规定的可滑动区域内,才能让用户拖动进度条,并非在任意位置都能拖动小图标改变进度的。canvas
/** * 判断当前触摸屏幕的位置是否位于我们定的可滑动区域内 */ private boolean isOnRing(float eventX, float eventY) { boolean result = false; double distance = Math.sqrt(Math.pow(eventX - (radius + getPaddingLeft() + specialScaleLineLength + scaleToRingSpace), 2) + Math.pow(eventY - (radius+getPaddingLeft() + specialScaleLineLength + scaleToRingSpace), 2)); if (distance < (2 * radius+getPaddingLeft() + getPaddingRight() + 2 * (specialScaleLineLength + scaleToRingSpace)) && distance > radius - slideAbleLocation) { result = true; } return result; }
其他具体实现方法我就不细说了,源码我放在了gitHub上,有须要的能够下载或在线看下。api
此效果(左下)由view绘制实现,用到了圆形、过圆心弧及文字这几种基本图形的绘制api。刻度线的绘制则是经过不断旋转canvas画布来循环画线实现的,都是比较常规的绘制方案。ide
此view的难点是外围文字在环绕过程当中,坐标位置的确认,即依圆心坐标,半径,扇形角度,如何计算出扇形终射线与圆弧交叉点的xy坐标,所幸网上都能找到解决方案及背后的数学模型。代码以下:svg
// 绘制外围文字 private void paintOutWord(Canvas canvas, String state) { PointF progressPoint = CommentUtil.calcArcEndPointXY (radius + getPaddingLeft() + specialScaleLineLength + scaleToRingSpace + wordWith , radius + getPaddingTop() + specialScaleLineLength + scaleToRingSpace + wordHeigh , radius + specialScaleLineLength + scaleToRingSpace , progress * (360 / 100f), -90); int left = (int) progressPoint.x; int top = (int) progressPoint.y; wordPaint.getTextBounds(state, 0, state.length(), rect); if(left < radius + getPaddingLeft() + specialScaleLineLength + scaleToRingSpace + wordWith){ left -= rect.width(); } if(top > radius + getPaddingTop() + specialScaleLineLength + scaleToRingSpace + wordHeigh){ top += rect.height(); } canvas.drawText(state, left, top, wordPaint); }
此方法的做用即获取扇形终射线与圆弧交叉点的xy坐标,感兴趣的能够研究下:动画
/** * 依圆心坐标,半径,扇形角度,计算出扇形终射线与圆弧交叉点的xy坐标 * @param cirX 圆centerX * @param cirY 圆centerY * @param radius 圆半径 * @param cirAngle 当前弧角度 * @param orginAngle 起点弧角度 * @return 扇形终射线与圆弧交叉点的xy坐标 */ public static PointF calcArcEndPointXY(float cirX, float cirY, float radius, float cirAngle, float orginAngle) { cirAngle = (orginAngle + cirAngle) % 360; return calcArcEndPointXY(cirX, cirY, radius, cirAngle); } /* * @param cirAngle 当前弧角度 */ public static PointF calcArcEndPointXY(float cirX, float cirY, float radius, float cirAngle) { float posX = 0.0f; float posY = 0.0f; // 将角度转换为弧度 float arcAngle = (float) (Math.PI * cirAngle / 180.0); if (cirAngle < 90) { posX = cirX + (float) (Math.cos(arcAngle)) * radius; posY = cirY + (float) (Math.sin(arcAngle)) * radius; } else if (cirAngle == 90) { posX = cirX; posY = cirY + radius; } else if (cirAngle > 90 && cirAngle < 180) { arcAngle = (float) (Math.PI * (180 - cirAngle) / 180.0); posX = cirX - (float) (Math.cos(arcAngle)) * radius; posY = cirY + (float) (Math.sin(arcAngle)) * radius; } else if (cirAngle == 180) { posX = cirX - radius; posY = cirY; } else if (cirAngle > 180 && cirAngle < 270) { arcAngle = (float) (Math.PI * (cirAngle - 180) / 180.0); posX = cirX - (float) (Math.cos(arcAngle)) * radius; posY = cirY - (float) (Math.sin(arcAngle)) * radius; } else if (cirAngle == 270) { posX = cirX; posY = cirY - radius; } else { arcAngle = (float) (Math.PI * (360 - cirAngle) / 180.0); posX = cirX + (float) (Math.cos(arcAngle)) * radius; posY = cirY - (float) (Math.sin(arcAngle)) * radius; } return new PointF(posX, posY); }
颜色的渐变效果,即获取每一个刻度所对应的颜色段内等比例的16进制颜色值,参考代码以下:
/** * 经过刻度获取当前渐变颜色值 * @param p 当前刻度 * @param specialScaleCorlors 每一个范围的颜色值 * @return 当前须要的颜色值 */ public static int evaluateColor(int p, int[] specialScaleCorlors) { // 颜色范围 int startInt = 0xFFbebebe; int endInt = 0xFFbebebe; float fraction = 0.5f; if(p != 0 && p != 100){ startInt = specialScaleCorlors[p / 20]; endInt = specialScaleCorlors[p / 20 + 1]; fraction = (p - (p / 20) * 20) / 20f; } int startA = (startInt >> 24) & 0xff; int startR = (startInt >> 16) & 0xff; int startG = (startInt >> 8) & 0xff; int startB = startInt & 0xff; int endA = (endInt >> 24) & 0xff; int endR = (endInt >> 16) & 0xff; int endG = (endInt >> 8) & 0xff; int endB = endInt & 0xff; return (int) ((startA + (int) (fraction * (endA - startA))) << 24) | (int) ((startR + (int) (fraction * (endR - startR))) << 16) | (int) ((startG + (int) (fraction * (endG - startG))) << 8) | (int) ((startB + (int) (fraction * (endB - startB)))); }
其他具体实现方法我就不细说了,源码我放在了gitHub上,有须要的能够下载或在线看下。