Android:寻找具有内半径和外半径的drawArc()方法

36

我有以下自定义视图:

alt text

我使用Canvas的drawArc()方法实现了这个视图。然而,使用这个方法时,我无法限制弧形的内部半径。

我想要的是像这样的效果:

alt text

只剩下一个外环。

我需要一个可以设置弧形内部半径的drawArc()函数。 有人有什么想法吗?

(顺便说一下,覆盖内部区域是不起作用的,因为它需要是透明的。在绘制红色和蓝色圆锥之后,使用Color.TRANSPARENT绘制内部圆形并不能清除旧颜色。 它只是放置另一层在上面,这一层是透明的,通过它我仍然可以看到红色和蓝色)


8
请问您能否在最终需求确认后,发布您最新的代码?谢谢。 - anddev
有人知道如何制作平滑的相遇角边缘吗?在我的情况下,当我用4-5个等分和不同颜色制作环时,我可以看到边缘处的背景。 - Akhil Dad
5个回答

75
你可以这样做:
    Paint paint = new Paint();
    final RectF rect = new RectF();
    //Example values
    rect.set(mWidth/2- mRadius, mHeight/2 - mRadius, mWidth/2 + mRadius, mHeight/2 + mRadius); 
    paint.setColor(Color.GREEN);
    paint.setStrokeWidth(20);
    paint.setAntiAlias(true);
    paint.setStrokeCap(Paint.Cap.ROUND);
    paint.setStyle(Paint.Style.STROKE);
    canvas.drawArc(rect, -90, 360, false, paint);

关键在于paint.setStyle(Paint.Style.STROKE);,它使用在setStrokeWidth中定义的描边来裁剪弧形的中心(例如,在示例中绘制了一个半径为mRadius、厚度为20px的弧形)。

希望对您有所帮助!


非常好用!有时候我会忘记从容器中减去描边,导致圆形被裁剪。谢谢。 - Tom
4
如果您使用 paint.setStrokeCap(Paint.Cap.BUTT); 而不是 paint.setStrokeCap(Paint.Cap.ROUND);,你将得到与问题所要求的完全相同的结果。 - PhoneixS
6
我认为你可以安全地使用 canvas.drawArc(rect,0,360,false,paint); 替代 -90。 - Gerard
你能给这个设置一个渐变颜色(比如着色器)吗? - Snake
“Paint.Cap.BUTT” 是默认值,所以您可以直接删除 “paint.setStrokeCap();” 这一行。 - Vitor Hugo Schwaab

33

您可以使用名为“Clear”的PorterDuff xfermode在内部区域上绘制,这将擦除像素。


1
这是一个可能的解决方案。但是对于内部圆中已经绘制了某些内容,我既不想覆盖它,也不想擦除像素然后重新绘制,该怎么办呢? - znq
2
在离屏的ARGB8888位图上绘制圆形,进行清除操作,然后将位图绘制到画布上。这样你也不必每次都重新绘制圆形。 - Romain Guy
所以,在研究如何实际操作后,我有点迷茫:ARGB8888位图是“普通”位图吗http://goo.gl/ygTn - 我如何获得与Canvas相同的绘图功能? - znq
2
更新:我发现我可以用以下方式实现Bitmap bm = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888); Canvas c = new Canvas(bm);然后在画布上绘制,这将在位图上写入实际像素。 - znq

20
private static final float CIRCLE_LIMIT = 359.9999f;
/**
 * Draws a thick arc between the defined angles, see {@link Canvas#drawArc} for more.
 * This method is equivalent to
 * <pre><code>
 * float rMid = (rInn + rOut) / 2;
 * paint.setStyle(Style.STROKE); // there's nothing to fill
 * paint.setStrokeWidth(rOut - rInn); // thickness
 * canvas.drawArc(new RectF(cx - rMid, cy - rMid, cx + rMid, cy + rMid), startAngle, sweepAngle, false, paint);
 * </code></pre>
 * but supports different fill and stroke paints.
 * 
 * @param canvas
 * @param cx horizontal middle point of the oval
 * @param cy vertical middle point of the oval
 * @param rInn inner radius of the arc segment
 * @param rOut outer radius of the arc segment
 * @param startAngle see {@link Canvas#drawArc}
 * @param sweepAngle see {@link Canvas#drawArc}, capped at &plusmn;360
 * @param fill filling paint, can be <code>null</code>
 * @param stroke stroke paint, can be <code>null</code>
 * @see Canvas#drawArc
 */
public static void drawArcSegment(Canvas canvas, float cx, float cy, float rInn, float rOut, float startAngle,
        float sweepAngle, Paint fill, Paint stroke) {
    if (sweepAngle > CIRCLE_LIMIT) {
        sweepAngle = CIRCLE_LIMIT;
    }
    if (sweepAngle < -CIRCLE_LIMIT) {
        sweepAngle = -CIRCLE_LIMIT;
    }

    RectF outerRect = new RectF(cx - rOut, cy - rOut, cx + rOut, cy + rOut);
    RectF innerRect = new RectF(cx - rInn, cy - rInn, cx + rInn, cy + rInn);

    Path segmentPath = new Path();
    double start = toRadians(startAngle);
    segmentPath.moveTo((float)(cx + rInn * cos(start)), (float)(cy + rInn * sin(start)));
    segmentPath.lineTo((float)(cx + rOut * cos(start)), (float)(cy + rOut * sin(start)));
    segmentPath.arcTo(outerRect, startAngle, sweepAngle);
    double end = toRadians(startAngle + sweepAngle);
    segmentPath.lineTo((float)(cx + rInn * cos(end)), (float)(cy + rInn * sin(end)));
    segmentPath.arcTo(innerRect, startAngle + sweepAngle, -sweepAngle);
    if (fill != null) {
        canvas.drawPath(segmentPath, fill);
    }
    if (stroke != null) {
        canvas.drawPath(segmentPath, stroke);
    }
}

通过复制rInnrOut来扩展为椭圆弧,分别用于x和y方向。

还不是问题的一部分,但要在段落中央绘制文本:

textPaint.setTextAlign(Align.CENTER);
Path midway = new Path();
float r = (rIn + rOut) / 2;
RectF segment = new RectF(cx - r, cy - r, cx + r, cy + r);
midway.addArc(segment, startAngle, sweepAngle);
canvas.drawTextOnPath("label", midway, 0, 0, textPaint);

这段代码太棒了!不过我有一个问题,我能不能让圆之间的线条颜色与圆周颜色不同? - Snake
@Snake,你需要将segmentPath.*To()调用拆分成多个Path,并使用所选的描边颜色分别绘制每个Path。你可以使用2个路径来实现你所描述的效果:moveTo,lineTo,moveTo,lineTomoveTo,arcTo,moveTo,arcTo。你仍然需要保留当前的路径以便填充。 - TWiStErRob
我“想”我大致理解了它。由于我仍然需要您的帮助,我按照您的建议创建了另一个问题。期待您的帮助。这是链接http://stackoverflow.com/questions/27850634/change-the-following-code-to-include-different-colors-for-different-strokes - Snake
不必使用 lineTo。对于第二个 arcTo(),在最后一个参数中添加 false;这将从第一个弧的末端绘制一条线到第二个弧的开头。然后关闭路径(close())。 - John Perry

3
绘制圆形和弧线。下面的代码可能有点混乱,但它可能会有所帮助。
        int sweepAngle sweepAngle = (360/7)%360;
    int startAngle = -90;
    int x = getWidth()/2;
    int y = getHeight()/2;
    int radius;
    radius = getWidth()/2-50;
    paint.setStyle(Paint.Style.STROKE);
    paint.setStrokeWidth(50);
    paint.setColor(Color.WHITE);

    paint.setColor(Color.parseColor("#CD5C5C"));
    mBarPaintFill.setAntiAlias(true);

    canvas.drawCircle(x , y , radius, paint);
    paint.setColor(Color.BLUE);
    for (int i = 1 ; i<=5 ; i++){

        canvas.drawArc(x-radius,y-radius,x+radius,y+radius,startAngle,sweepAngle,false,paint);
        startAngle = (startAngle + sweepAngle+20)%360;
    }

enter image description here


2
您可以尝试使用 ShapeDrawable。
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android" >
<item>
    <shape android:shape="oval" >
        <size
            android:height="56dp"
            android:width="56dp" />

        <stroke
            android:width="10dp"
            android:color="#0000ff" />
    </shape>
</item>
<item>
    <shape android:shape="oval" >
        <size
            android:height="24dp"
            android:width="25dp" />

        <stroke
            android:dashGap="10dp"
            android:dashWidth="10dp"
            android:width="10dp"
            android:color="#FF0000" />
    </shape>
</item>


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接