先看下效果:
说明:可以根据需要自行修改,黄色球为开始,红色球为结束,对应圆环里面的 :开始<--->结束;最大进度可调,默认100;开始和结束球都可手动拖动。(不喜勿碰)
不废话,直接上代码:
第一步,使用到的工具类
1. 先上工具类:
import android.graphics.Point; import android.graphics.PointF; import static android.R.attr.id; public class ChartUtils { /** * 依圆心坐标,半径,扇形角度,计算出扇形终射线与圆弧交叉点的xy坐标 * * @param cirX 圆centerX * @param cirY 圆centerY * @return 扇形终射线与圆弧交叉点的xy坐标 */ 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); } /** * 依圆心坐标,半径,扇形角度,计算出扇形终射线与圆弧交叉点的xy坐标 * * @param cirX 圆centerX * @param cirY 圆centerY * @param radius 圆半径 * @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); } public static double calSweep(float x, float y, float radius) { double sweep = 0; if (x > radius) { if (y <= radius) { // 一 double t = (x - radius) / (radius - y); sweep = (Math.toDegrees(Math.atan(t))) + 270; } else { // 四 double t = (y - radius) / (x - radius); sweep = (Math.toDegrees(Math.atan(t))); } } else { if (y <= radius) { // 二 double t = (radius - y) / (radius - x); sweep = (Math.toDegrees(Math.atan(t))) + 180; } else { // 三 double t = (radius - x) / (y - radius); sweep = (Math.toDegrees(Math.atan(t))) + 90; } } return sweep%360; } public static PointF calPointByAngle(int x, int y, int r, float angle) { PointF p = new PointF(); if (angle >= 0 && angle < 90) { // 第四象限 p.x = (float) (r * (Math.cos(Math.PI * angle / 180.0)) + 1); p.y = (float) (r * (1 +Math.sin(Math.PI * angle / 180.0))); } else if (angle >= 90 && angle < 180) { // 第三象限 p.x = (float) (r * (-Math.sin(Math.PI * (angle - 90) / 180.0)) + 1); p.y = (float) (r * (1 + Math.cos(Math.PI * (angle - 90) / 180.0))); } else if (angle >= 180 && angle < 270) { // 第二象限 p.x = (float) (r * (-Math.cos(Math.PI * (angle - 180) / 180.0)) + 1); p.y = (float) (r * (1 - Math.sin(Math.PI * (angle - 180) / 180.0))); } else if (angle >= 270 && angle <= 360) { // 第一象限 p.x = (float) (r * (Math.sin(Math.PI * (angle - 270) / 180.0)) + 1); p.y = (float) (r * (1 - Math.cos(Math.PI * (angle - 270) / 180.0))); } return p; } }
第二步,自定义view:
2:circle:
import android.content.Context; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.Point; import android.graphics.PointF; import android.graphics.Rect; import android.graphics.RectF; import android.os.Build; import android.support.annotation.Nullable; import android.support.annotation.RequiresApi; import android.util.AttributeSet; import android.util.Log; import android.view.MotionEvent; import android.view.View; import java.util.Map; import static android.R.attr.angle; import static android.R.attr.centerX; import static android.R.attr.centerY; import static android.R.attr.max; import static android.R.attr.radius; import static android.R.attr.textColor; import static android.R.attr.textSize; import static android.R.attr.x; import static android.R.attr.y; public class Circle extends View { private Paint mPaint; private int circleColor; private int progressColor; private int progressWidth; private float startAngle; private float sweepAngle; private int radius; private int centreX; private int centreY; private int maxError = 70; private int maxError0 = 100; private boolean downOnArc; private boolean isSecond = true; private int maxProgress = 100; /** * 中间进度百分比的字符串的颜色 */ private int textColor; /** * 中间进度百分比的字符串的字体 */ private float textSize; public Circle(Context context) { this(context, null); } public Circle(Context context, @Nullable AttributeSet attrs) { this(context, attrs, 0); } public Circle(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); mPaint = new Paint(); circleColor = Color.GRAY; progressColor = Color.GREEN; progressWidth = 30; startAngle = 270; sweepAngle = 60; textColor = Color.BLACK; textSize = 40; } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); //获取测量模式 int widthMode = MeasureSpec.getMode(widthMeasureSpec); int heightMode = MeasureSpec.getMode(heightMeasureSpec); //获取测量大小 int widthSize = MeasureSpec.getSize(widthMeasureSpec); int heightSize = MeasureSpec.getSize(heightMeasureSpec); int mWidth = 0; int mHeight = 0; if (widthMode == MeasureSpec.EXACTLY && heightMode == MeasureSpec.EXACTLY) { radius = widthSize / 2; centreX = widthSize / 2; centreY = heightSize / 2; mWidth = widthSize; mHeight = heightSize; } if (widthMode == MeasureSpec.AT_MOST && heightMode == MeasureSpec.AT_MOST) { mWidth = (int) (radius * 2); mHeight = (int) (radius * 2); centreX = radius; centreY = radius; } setMeasuredDimension(mWidth, mHeight); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); mPaint.setColor(circleColor); mPaint.setStrokeWidth(progressWidth); mPaint.setAntiAlias(true); mPaint.setStyle(Paint.Style.STROKE); // RectF rect = new RectF(progressWidth, progressWidth, getWidth() - progressWidth, getHeight() - progressWidth); RectF rect = new RectF(progressWidth * 2, progressWidth * 2, getWidth() - progressWidth * 2, getHeight() - progressWidth * 2); canvas.drawArc(rect, 0, 360, false, mPaint); mPaint.setColor(progressColor); canvas.drawArc(rect, startAngle, sweepAngle, false, mPaint); // 画小圆 Paint p = new Paint(); p.setColor(Color.RED); p.setStrokeWidth(4); p.setStyle(Paint.Style.FILL); PointF point = ChartUtils.calcArcEndPointXY(centreX, centreY, radius - progressWidth * 2, sweepAngle, startAngle); canvas.drawCircle(point.x, point.y, 40, p); // 画小圆2 p.setColor(Color.YELLOW); PointF point2 = ChartUtils.calcArcEndPointXY(centreX, centreY, radius - progressWidth * 2, 0, startAngle); canvas.drawCircle(point2.x, point2.y, 40, p); /** * 画文字 */ mPaint.setStrokeWidth(0); mPaint.setColor(textColor); mPaint.setTextSize(textSize); String textTime = getTimeText(startAngle, sweepAngle); float textWidth = mPaint.measureText(textTime); canvas.drawText(textTime, centreX - textWidth / 2, centreY + textSize / 2, mPaint); } private String getTimeText(float startAngle, float sweepAngle) { float startProgress = (startAngle + 90) % 360 / 360 * maxProgress; float endProgress = sweepAngle / 360 * maxProgress + startProgress; String result = startProgress + "<--->" + endProgress%maxProgress; return result; } @Override public boolean onTouchEvent(MotionEvent event) { int action = event.getAction(); int x = (int) event.getX(); int y = (int) event.getY(); switch (action) { case MotionEvent.ACTION_DOWN: if (isTouchArc(x, y)) { downOnArc = true; changePosition(x, y, radius); return true; } break; case MotionEvent.ACTION_MOVE: if (downOnArc) { changePosition(x, y, radius); return true; } break; case MotionEvent.ACTION_UP: downOnArc = false; changePosition(x, y, radius); break; } return super.onTouchEvent(event); } // 判断是否按在圆边上 private boolean isTouchArc(int x, int y) { // double d = getTouchRadius(x, y); PointF p = ChartUtils.calcArcEndPointXY(centreX, centreY, radius, sweepAngle, startAngle); PointF p2 = ChartUtils.calcArcEndPointXY(centreX, centreY, radius, 0, startAngle); int absx = (int) Math.abs(x - p.x); int absy = (int) Math.abs(y - p.y); int absx2 = (int) Math.abs(x - p2.x); int absy2 = (int) Math.abs(y - p2.y); if (absx <= maxError && absy <= maxError) { isSecond = true; return true; } if (absx2 <= maxError0 && absy2 <= maxError0) { isSecond = false; return true; } return false; } private void changePosition(int x, int y, int r) { double v = ChartUtils.calSweep(x, y, r); if (sweepAngle >= 360) { sweepAngle = sweepAngle % 360; } if (isSecond) { changeSecond(x, y, r, v); } else { changeFirst(v); } if (changeListener != null) { changeListener.onProgressChange(startAngle, sweepAngle); } invalidate(); } // 改变第二个点的位置 private void changeSecond(int x, int y, int r, double v) { if (x > r) { if (y <= r) { if (v >= startAngle) { sweepAngle = (float) (v - startAngle); } else { sweepAngle = (float) (360 - (startAngle - v)); } } else { sweepAngle = (float) (360 - (startAngle - v)); } } else { sweepAngle = (float) (360 + v - startAngle); } sweepAngle = sweepAngle % 360; } // 改变第一个点的位置 private void changeFirst(double v) { // float secondAngle = (startAngle + sweepAngle) % 360; if (sweepAngle < 0) { sweepAngle = sweepAngle + 360; } float cSweep = (float) (v - startAngle); startAngle = (float) v; sweepAngle = sweepAngle - cSweep; } // 判断第一个原点是不是跟在第二个后面 private boolean isAfterFllow(float start, float sweep) { float startProgress = (startAngle + 90) % 360 / 360 * maxProgress; float endProgress = sweepAngle / 360 * maxProgress + startProgress; return endProgress <= maxProgress && (Math.ceil(sweep) >= 30); } // 计算某点到圆点的距离 private double getTouchRadius(int x, int y) { int cx = x - getWidth() / 2; int cy = y - getHeight() / 2; return Math.hypot(cx, cy); } private OnProgressChangeListener changeListener; public interface OnProgressChangeListener { void onProgressChange(float start, float sweep); } public void setListener(OnProgressChangeListener changeListener) { this.changeListener = changeListener; } }
第三步,布局:
<com.example.dyy.circleandaddress.Circle android:id="@+id/circle" android:layout_gravity="center_horizontal" android:layout_width="300dp" android:layout_height="300dp" android:padding="100dp" />
end!