Android 手勢移動 圓形進度條
首先明确:在Android中坐标系方向為 向右X軸正方向,向下Y軸正方向。
确定自定義view大小(圓弧所在矩形的大小)
mCRadius: 圓弧寬度半徑
mOval = new RectF(getPaddingLeft() + mCRadius, getPaddingTop() + mCRadius,
getWidth() - getPaddingRight() - mCRadius,
getHeight() - getPaddingBottom() - mCRadius);
圓形進度條,主要畫圓弧,先畫軌迹弧形,然後是進度弧形。
畫軌迹:360°圓弧,270°(有缺口)的圓弧。
isFilled: 填充模式
if (isFilled) {
trackPaint.setStyle(Paint.Style.FILL);
} else {
trackPaint.setStyle(Paint.Style.STROKE);
}
if (circleBroken) {
canvas.drawArc(mOval, 135, 270, isFilled, trackPaint);
} else {
canvas.drawArc(mOval, 90, 360, isFilled, trackPaint);
}
畫進度:偏移進度 * (270°或者360°)/ 100 = 偏移角度
if (isFilled) {
progressPaint.setStyle(Paint.Style.FILL);
} else {
progressPaint.setStyle(Paint.Style.STROKE);
}
if (circleBroken) {
canvas.drawArc(mOval, 135 + mStartProgress * 2.7f, (moveProgress - mStartProgress) * 2.7f, isFilled, progressPaint);
} else {
canvas.drawArc(mOval, 270 + mStartProgress * 3.6f, (moveProgress - mStartProgress) * 3.6f, isFilled, progressPaint);
}
中間部分繪制百分比進度值:
/**
* draw the progress text
*
* @param canvas mCanvas
*/
private void drawProgressText(Canvas canvas) {
if (textVisibility) {
mTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mTextPaint.setStyle(Paint.Style.FILL);
mTextPaint.setTextSize(mProgressTextSize);
mTextPaint.setColor(mProgressTextColor);
mTextPaint.setTextAlign(Paint.Align.CENTER);
// Typeface font = ResourcesCompat.getFont(getContext(),R.font.bahnschrift);
// mTextPaint.setTypeface(font);
String progressText = ((int) moveProgress) + "%";
float x = (getWidth() + getPaddingLeft() - getPaddingRight()) / 2;
float y = (getHeight() + getPaddingTop() - getPaddingBottom() - (mTextPaint.descent() + mTextPaint.ascent())) / 2;
canvas.drawText(progressText, x, y, mTextPaint);
}
}
手勢觸摸移動,根據手指觸摸的坐标,計算與圓心之間的角度,根據
起始角度再計算其偏移角度,最後計算偏移進度。
private void moved(float x,float y){
int startAngle = circleBroken ? 135 : 270;
int endAngle = circleBroken ? 270 : 360;
if (!isEffectiveArea(x,y)){
return;
}
int cx = getWidth() / 2;
int cy = getHeight() / 2;
// y 對邊 x 鄰邊
double radian = Math.atan2(y - cy,x - cx);
if (radian < 0) {
radian = radian + 2 * Math.PI; // 小于0的弧度值 轉成 正值
}
float angle = Math.round(Math.toDegrees(radian)); // 相對于坐标系的實際角度
// 90度缺口部分 不進行計算
if (circleBroken && angle <= 135 && angle >= 45) return;
float mSweepAngle = getLastAngle(angle,startAngle);
// 加上起始角度的偏移角度
//通過角度得到xy坐标
// angleToXY(mSweepAngle);
//通過角度擷取進度值
float mProgress = (100 * mSweepAngle / endAngle);
if (mProgress > 99) mProgress = 100;
setProgress(mProgress);
}
private float getLastAngle(float angle,int startAngel) {
//設定起始角度的關鍵代碼,正常情況下Math.round(Math.toDegrees(radian))就可以了。
float lastAngle;
if (angle < startAngel) {
lastAngle = 360 - startAngel + Math.abs(angle);
} else {
lastAngle = angle - startAngel;
}
return lastAngle;
}
/**
* 計算其觸摸點是否在圓弧可拖動範圍内
*/
private boolean isEffectiveArea(float x,float y){
boolean result = false;
int cx = getWidth() / 2;
int cy = getHeight() / 2;
double distance = Math.sqrt(Math.pow(x - cx, 2) + Math.pow(y - cy, 2));
//弧線外面
boolean isCircleOuter = distance <= getWidth();
//弧線裡面,如果想設定裡面範圍都可以拖動,可以 0
boolean isCircleInner = distance >= (cx - getPaddingLeft() - mCRadius - 40);
if (isCircleOuter && isCircleInner) {
result = true;
}
return result;
}
根據進度計算坐标值,主要用于托快放置位置。
/**
* 通過角度得到x,y軸,用于托快上使用 // 相對于起始角度的偏移角度
* @param angle
*/
private void angleToXY(float angle) {
// 向右為0,向下為90
float startAngle = circleBroken ? 135 : 270;
// 計算實際角度
float total = 360 - startAngle;
float caculateAngle; // 實際角度值
if (angle < total) {
caculateAngle = angle + startAngle;
} else {
caculateAngle = angle - total;
}
double radian = Math.toRadians(caculateAngle); // 計算實際角度的弧度值
double radius = getWidth() / 2 - getPaddingLeft()- mCRadius; // 減去預留的部分值 mOval.left 得到半徑斜邊
double x = getWidth() / 2 + radius * Math.cos(radian); // 鄰邊 / 斜邊
double y = getHeight() / 2 + radius * Math.sin(radian); // 對應邊 / 斜邊
mThumbLeft = (float) (x - mCRadius);
mThumbTop = (float) (y - mCRadius);
}
private void progressToXY() {
// 通過進度值擷取角度
int endAngle = circleBroken ? 270 : 360;
float angle = moveProgress / 100 * endAngle;
angleToXY(angle);
}