Android自定义圆弧进度条,计算坐标到圆心得连线与x轴(心中要有轴)夹角
效果图片已经看到了,圆弧可以根据手的滑动旋转。
下面直接贴代码,有兴趣自己研读一下.不在过多赘述,自己能理解贯通最好。方法可以灵活运用,与多个场景。
下面这个方法是关键,以后可以都理解利用:(计算坐标到圆心得连线与x轴(心中要有轴)夹角)
/**
* 以按钮圆心为坐标圆点,建立坐标系,求出(targetX, targetY)坐标与x轴的夹角
*
* @param targetX x坐标
* @param targetY y坐标
* @return (targetX, targetY)坐标与x轴的夹角
*/
private float calcAngle(float targetX, float targetY) {
float x = targetX - weith / 2;//len/2 圆点x坐标
float y = targetY - height / 2;//len/2 圆点y坐标
double radian;
if (x != 0) {
float tan = Math.abs(y / x);
if (x > 0) {
if (y >= 0) {
radian = Math.atan(tan);
} else {
radian = 2 * Math.PI - Math.atan(tan);
}
} else {
if (y >= 0) {
radian = Math.PI - Math.atan(tan);
} else {
radian = Math.PI + Math.atan(tan);
}
}
} else {
if (y > 0) {
radian = Math.PI / 2;
} else {
radian = -Math.PI / 2;
}
}
return (float) ((radian * 180) / Math.PI);
}
下面的就是完整代码:”
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.RectF;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.MotionEvent;
import android.view.View;
import com.iszcc.x5audio.R;
import com.iszcc.x5audio.uitl.LogUtil;
public class RotateView extends View {
private int weith, height;
private Bitmap mBitmap;
private Paint mTextPaint;
private String text = "100";
private Paint basePaint;
private int maxAcgle = 270;//圆弧最大角度
private float recentAcgle;//当前角度
private int minRang = 0;//范围
private int maxRang = 1;//范围
private int newScale = 0;//保留小数位数
private int mTextSize = 40;
private int mTextStrokeWidth = 40;
private int arcStrokeWidth = 3;
//内弧粗度
private int arcWidth = 50;
private float chazhi = 1;
private int recentInt;//当前值
public RotateView(Context context) {
super(context);
init(context);
}
public RotateView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
init(context);
}
public RotateView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int heightSize = MeasureSpec.getSize(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(widthMeasureSpec);
/*1、UNSPECIFIED
父控件没有对子控件施加任何约束,子控件可以是任意大小(也就是未指定)
UNSPECIFIED在源码里处理和EXACTLY一样,当View的宽高值为0的时候或者没有设置
宽高值的时候,模式为UNSPECIFIED
*2、EXACTLY
父控件决定子控件确切的大小,子控件被限定在给定的边界里面,忽略本身的大小
当设置width为match_parent时,模式为EXACTLY,因为子控件View会占据剩余的父空间,
所以大小是确定的。
*3、AT_MOST
* 子控件最大能够达到的指定大小
* 当设置wrap_content时,模式为AT_MOST,表示子控件View的大小最多是多少,
* 这样这个子控件View会根据这个上限来设置自己的尺寸
*/
if (widthMode == MeasureSpec.EXACTLY) {
weith = widthSize;
} else {
weith = 700;
}
if (heightMode == MeasureSpec.EXACTLY) {
height = heightSize;
} else {
height = 700;
}
//应用测量值
setMeasuredDimension(weith, height);
}
private void init(Context context) {
mBitmap = BitmapFactory.decodeResource(context.getResources(), R.mipmap.mainvol_bg);
mTextPaint = new Paint();
mTextPaint.setColor(Color.WHITE);
mTextPaint.setStrokeWidth(dp2px(mTextStrokeWidth));
mTextPaint.setTextSize(dp2px(mTextSize));
basePaint = new Paint();
basePaint.setAntiAlias(true);
basePaint.setColor(Color.WHITE);
basePaint.setStyle(Paint.Style.STROKE);
basePaint.setStrokeWidth(arcStrokeWidth);
}
float downX = 0;
float downY = 0;
int resetInt = 0;
@Override
public boolean onTouchEvent(MotionEvent event) {
super.onTouchEvent(event);
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
downX = event.getX();
downY = event.getY();
break;
case MotionEvent.ACTION_MOVE:
float angleCha = calcAngle(event.getX(), event.getY()) - calcAngle(downX, downY);
//Log.e("RotateView:", angleCha + " " + calcAngle(event.getX(), event.getY()) + " __________ " + calcAngle(downX, downY));
downY = event.getY();
downX = event.getX();
// 防止越界
if (angleCha < -300) {
angleCha = angleCha + 360;
} else if (angleCha > 300) {
angleCha = angleCha - 360;
}
recentAcgle += angleCha;
if (recentAcgle < 0) {
recentAcgle = 0;
} else if (recentAcgle >= maxAcgle) {
recentAcgle = maxAcgle;
}
if (recentAcgle == maxAcgle) {
resetInt = maxRang;
} else {
resetInt = (int) (recentAcgle / chazhi);
}
if (resetInt != recentInt) {
recentInt = resetInt;
if (listener != null) {
listener.onRotateChange(recentInt);
}
}
/*//保留一位小数的返回值
BigDecimal b = new BigDecimal(recentAcgle / chazhi);
recentInt = b.setScale(newScale, BigDecimal.ROUND_HALF_UP).floatValue() + minRang;*/
invalidate();
break;
case MotionEvent.ACTION_UP:
LogUtil.e("MotionEvent.ACTION_UP 00 : "+recentInt);
if (listener != null) {
LogUtil.e("MotionEvent.ACTION_UP : "+recentInt);
listener.onStop(recentInt);
}
/* float angleChaUp = calcAngle(event.getX(), event.getY()) - calcAngle(downX, downY);
downY = event.getY();
downX = event.getX();
// 防止越界
if (angleChaUp < -300) {
angleChaUp = angleChaUp + 360;
} else if (angleChaUp > 300) {
angleChaUp = angleChaUp - 360;
}
recentAcgle += angleChaUp;
if (recentAcgle < 0) {
recentAcgle = 0;
} else if (recentAcgle > maxAcgle) {
recentAcgle = maxAcgle;
}
if (recentAcgle == maxAcgle) {
resetInt = maxRang;
} else {
resetInt = (int) (recentAcgle / chazhi);
}
if (resetInt != recentInt) {
recentInt = resetInt;
if (listener != null) {
LogUtil.e("MotionEvent.ACTION_UP : "+recentInt);
listener.onStop(recentInt);
}
}*/
break;
case MotionEvent.ACTION_CANCEL:
break;
}
return true;
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.save();
Rect dst = new Rect(0, 0, weith, height);
Rect src = new Rect(0, 0, mBitmap.getWidth(), mBitmap.getHeight());
canvas.drawBitmap(mBitmap, src, dst, null);
//画内层弧
basePaint.setColor(Color.WHITE);
basePaint.setStrokeWidth(arcWidth);
RectF innerArc = new RectF(arcStrokeWidth + 80, arcStrokeWidth + 80, weith - arcStrokeWidth - 80, height - arcStrokeWidth - 80);
canvas.drawArc(innerArc, 135, recentAcgle, false, basePaint);
mTextPaint.setTextSize(dp2px(mTextSize));
mTextPaint.setStrokeWidth(dp2px(mTextStrokeWidth));
float textWieth = mTextPaint.measureText(text);
canvas.drawText(text, weith / 2 - textWieth / 2, height / 2 + dp2px(mTextSize) / 2, mTextPaint);
}
private int dp2px(float dp) {
return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
dp, getResources().getDisplayMetrics());
}
private int sp2px(float sp) {
return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP,
sp, getResources().getDisplayMetrics());
}
/**
* 以按钮圆心为坐标圆点,建立坐标系,求出(targetX, targetY)坐标与x轴的夹角
*
* @param targetX x坐标
* @param targetY y坐标
* @return (targetX, targetY)坐标与x轴的夹角
*/
private float calcAngle(float targetX, float targetY) {
float x = targetX - weith / 2;//len/2 圆点x坐标
float y = targetY - height / 2;//len/2 圆点y坐标
double radian;
if (x != 0) {
float tan = Math.abs(y / x);
if (x > 0) {
if (y >= 0) {
radian = Math.atan(tan);
} else {
radian = 2 * Math.PI - Math.atan(tan);
}
} else {
if (y >= 0) {
radian = Math.PI - Math.atan(tan);
} else {
radian = Math.PI + Math.atan(tan);
}
}
} else {
if (y > 0) {
radian = Math.PI / 2;
} else {
radian = -Math.PI / 2;
}
}
return (float) ((radian * 180) / Math.PI);
}
/**
* 设置范围最大值和最小值
* 保留几位小数
*
* @param min 最小值
* @param max 最大值
* newScale 保留小数位数
*/
public void setRotate(int min, int max) {
this.maxRang = max;
this.minRang = min;
// this.newScale = newScale;
this.newScale = 0;
chazhi = maxAcgle / (float) (max - min);
}
public void setText(String text) {
this.text = text;
invalidate();
}
public void setRecentInt(int recent) {
recentAcgle = (recent / (float) (maxRang - minRang)) * maxAcgle;
recentInt = recent;
}
public void setTextSizeWidth(int textSize, int textStrokeWidth) {
this.mTextSize = textSize;
this.mTextStrokeWidth = textStrokeWidth;
}
public interface OnRotateViewListener {
void onStart(int angle);
void onRotateChange(int angle);
void onStop(int angle);
}
private OnRotateViewStopListener listener;
public void setOnRotateViewStopListener(OnRotateViewStopListener listener) {
this.listener = listener;
}
public interface OnRotateViewStopListener {
void onRotateChange(int angle);
void onStop(int angle);
}
}