我目前正在尝试创建一个可以按填充百分比调整的仪表。我遇到的问题是我根本不擅长数学。我想开始在'北'(第一张图像)中绘制一个圆弧,而不是在'东'中有一个0度点的普通圆弧(如第二张图所示)。
我希望能够通过沿着屏幕拖动/触摸来增加图像1中蓝色区域的大小(角度)。现在这些是我现在能以某种方式做的事情。我面临的真正问题是:
我使用以下代码绘制蓝色区域:
mStart = -90;
int degree = (int)((theta + Math.PI) * 180 / Math.PI);
mSweep = degree;
RectF mOvals = new RectF(c.x - outerRadius + circleThickness, c.y - outerRadius + circleThickness, c.x + outerRadius - circleThickness, c.y + outerRadius - circleThickness );
mArcSetLevel = new Path();
if(mArcSetLevel != null ) {
canvas.drawArc(mOvals, mStart, mSweep, true, arcPaint);
}
将起点设置为-90使其提前90度开始。为了跟踪触摸的角度,我使用这个公式,但这是出错的地方:
int py = (int)event.getY() - c.y;
int px = (int)event.getX() - c.x;
theta = (float) ((float) Math.atan2(py, px) - (Math.PI / 2)); // - Math.PI / 2 to correct -90 start
当我进一步超过270度时,蓝色区域被重置,并以更小的角度从北向西绘制自己(因为'-90'的'假'开始,如第三张图所示)。我的数学技能根本不足以让我能够解决这个问题,虽然我可以想到为什么会发生这种情况我似乎无法找到解决方案。
我制作的整个视图的(非常混乱的)代码如下:
private Canvas canvas;
//Canvas width and height
private int h = -1;
private int w = -1;
//circle properties
private Paint paint;
private Paint arcPaint;
private Path circle;
private Point c;
private int outerRadius;
private int circleThickness = 20;
//point click in wheel
private float theta = 0;
private float mStart;
private float mSweep;
private Paint mBgPaints = new Paint();
private Path mArcSetLevel;
int padding = 10;
OnMeterWheelChangeListener onMeterWheelChangeListener = null;
public MeterWheel(Context context){
super(context);
initCircleSeekBar();
}
public MeterWheel(Context context, AttributeSet attrs) {
super(context, attrs);
initCircleSeekBar();
}
private void initCircleSeekBar() {
canvas = new Canvas();
circle = new Path();
paint = new Paint();
arcPaint = new Paint();
c = new Point();
mBgPaints.setAntiAlias(true);
mBgPaints.setStyle(Paint.Style.FILL);
mBgPaints.setColor(0x88FF0000);
mBgPaints.setStrokeWidth(0.5f);
mArcSetLevel = new Path();
this.draw(canvas);
}
@Override
protected void onSizeChanged(int width, int height, int oldw, int oldh) {
// TODO Auto-generated method stub
super.onSizeChanged(width, height, oldw, oldh);
w = width;
h = height;
Log.i("POWERWHEEL", String.valueOf(w) + " " + String.valueOf(h));
c.set(w/2, h/2);
drawCircle();
}
private void drawCircle() {
outerRadius = Math.min(h,w)/2;
circleThickness = (int) (outerRadius*0.15);
circle.addArc(new RectF(c.x - outerRadius + circleThickness/2, c.y - outerRadius + circleThickness/2, c.x + outerRadius - circleThickness/2, c.y + outerRadius - circleThickness/2 ), 0, 360);
circle.moveTo(c.x, c.y);
//paint.setShader(new SweepGradient(w/2,h/2, colourarry, null));
paint.setColor(Color.GRAY);
paint.setStyle(Style.STROKE);
paint.setStrokeWidth(circleThickness);
paint.setAntiAlias(true);
arcPaint.setColor(Color.BLUE);
arcPaint.setStyle(Style.FILL);
arcPaint.setStrokeWidth(circleThickness);
arcPaint.setAntiAlias(true);
}
@SuppressLint("DrawAllocation")
@Override
protected void onDraw(Canvas canvas) {
// TODO Auto-generated method stub
super.onDraw(canvas);
if(circle != null){
//draw circle
canvas.drawPath(circle, paint);
mStart = -90;
int degree = (int)((theta + Math.PI) * 180 / Math.PI);
Log.d("POWERWHEEL", "" + degree);
mSweep = degree;
RectF mOvals = new RectF(c.x - outerRadius + circleThickness, c.y - outerRadius + circleThickness, c.x + outerRadius - circleThickness, c.y + outerRadius - circleThickness );
mArcSetLevel = new Path();
if(mArcSetLevel != null ) {
canvas.drawArc(mOvals, mStart, mSweep, true, arcPaint);
}
}
}
@Override
public boolean onTouchEvent(MotionEvent event) {
if (!isEnabled()) {
return false;
}
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
setPressed(true);
onStartTrackingTouch(event);
trackTouchEvent(event);
break;
case MotionEvent.ACTION_MOVE:
trackTouchEvent(event);
break;
case MotionEvent.ACTION_UP:
trackTouchEvent(event);
onStopTrackingTouch();
setPressed(false);
invalidate();
break;
case MotionEvent.ACTION_CANCEL:
onStopTrackingTouch();
setPressed(false);
invalidate();
break;
}
return true;
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int width = MeasureSpec.getSize(widthMeasureSpec);
int height = MeasureSpec.getSize(heightMeasureSpec);
setMeasuredDimension(width,height);
}
private void onStartTrackingTouch(MotionEvent event) {
}
private void onStopTrackingTouch() {
}
private void trackTouchEvent(MotionEvent event) {
int py = (int)event.getY() - c.y;
int px = (int)event.getX() - c.x;
theta = (float) ((float) Math.atan2(py, px) - (Math.PI / 2));
Log.d("POWERWHEEL", "theta: " + theta);
this.invalidate();
}
public void setSize(int x, int y){
h = y;
w = x;
}
public void setCirleThickness(int t){
circleThickness = t;
}
public void setOnMeterWheelChangeListener (OnMeterWheelChangeListener listener) {
onMeterWheelChangeListener = listener;
}
public interface OnMeterWheelChangeListener{
public void onStartTrackingTouch (MeterWheel colourWheel);
public void onStopTrackingTouch (MeterWheel colourWheel);
}
提前感谢一百万人!
答案 0 :(得分:1)
计算theta时,使用atan2,它以+/- pi返回角度。因此,当处于左上象限时,它将返回-pi / 2到-pi范围内的值(假设y是向下正向,x是向右正向)。直接减去pi / 2,给出-pi到-3pi / 2的范围。在onDraw中,然后再次添加pi(令人困惑),为此象限提供0到-pi / 2的扫描范围。这意味着它将从顶部的起始位置逆时针绘制弧0至pi / 2(或0至90度)。您必须确保扫描始终保持在0到pi的范围内。最好的解决方案是将坐标移动-pi / 2,这样代替Math.atan2(py,px),你可以执行Math.atan2(px,-py),然后如果θ为负,则添加2 * pi。像(我不写android)
theta = (float) Math.atan2(px, -py);
if (theta < 0) theta += 2 * Math.PI;
然后在onDraw
int degree = (int)(theta * 180 / Math.PI);
Log.d("POWERWHEEL", "" + degree);
mSweep = degree;
如果您仍然遇到问题,请检查mSweep是否始终在0到360度的范围内。