在圆弧上画一个圆形的空心拇指

Drawing a rounded hollow thumb over arc

我想创建一个圆角图来显示我的应用程序中的一系列值。这些值可以分为 3 类:低、中、高 - 由 3 种颜色表示:蓝色、绿色和红色(分别)。

超出此范围,我想显示实际测量值 - 在相关范围部分以 "thumb" 的形式:

根据测量值,白色拇指在范围弧上的位置可能会发生变化。

目前,我可以通过在视图的 onDraw 方法内在同一中心绘制 3 条弧来绘制 3 色范围:

width = (float) getWidth();
height = (float) getHeight();

float radius;

if (width > height) {
    radius = height / 3;
} else {
    radius = width / 3;
}

paint.setAntiAlias(true);
paint.setStrokeWidth(arcLineWidth);
paint.setStrokeCap(Paint.Cap.ROUND);
paint.setStyle(Paint.Style.STROKE);

center_x = width / 2;
center_y = height / 1.6f;

left = center_x - radius;
float top = center_y - radius;
right = center_x + radius;
float bottom = center_y + radius;

oval.set(left, top, right, bottom);

//blue arc
paint.setColor(colorLow);
canvas.drawArc(oval, 135, 55, false, paint);

//red arc
paint.setColor(colorHigh);
canvas.drawArc(oval, 350, 55, false, paint);

//green arc
paint.setColor(colorNormal);

canvas.drawArc(oval, 190, 160, false, paint);

这是结果弧:

我的问题是,我该如何:

  1. 在这 3 种颜色之间创建平滑的 渐变 (我尝试使用 SweepGradient 但它没有给我正确的结果)。
  2. 如图所示创建overlay white thumb,这样我就可以控制显示它的位置。

  3. 动画这个白色拇指在我的范围弧上。

注意:三色范围是静态的 - 所以另一种解决方案可以是只获取可绘制对象并在其上绘制白色拇指(并为其设置动画),所以我也愿意听到这样的解决方案:)

  1. 我会稍微改变一下您绘制视图的方式,通过查看原始设计,而不是绘制 3 个大写字母,我只绘制 1 条线,这样 SweepGradient将工作。

  2. 这可能有点棘手,您有 2 个选择:

    • 创建一个 Path 有 4 个圆弧
    • 绘制2条弧线-一个是大白(填充为白色所以你仍然想使用Paint.Style.STROKE)和另一个在上面使它填充透明,你可以用PorterDuff xfermode实现它,它可能需要你尝试几次,直到你在没有清除绿色圆圈的情况下得到它。
  3. 我想你想要设置拇指位置的动画,所以只需使用简单的 Animation 即可使视图无效并相应地绘制拇指视图位置。

希望对您有所帮助

我会为你的前两个问题使用面具。

1。创建平滑渐变

第一步是绘制两个具有线性渐变的矩形。首先 矩形包含蓝色和绿色,而第二个矩形包含绿色 和红色如下图所示。我标记了两个矩形相互接触的线 黑色以澄清它们实际上是两个不同的矩形。

这可以使用以下代码(摘录)实现:

// Both color gradients
private Shader shader1 = new LinearGradient(0, 400, 0, 500, Color.rgb(59, 242, 174), Color.rgb(101, 172, 242), Shader.TileMode.CLAMP);
private Shader shader2 = new LinearGradient(0, 400, 0, 500, Color.rgb(59, 242, 174), Color.rgb(255, 31, 101), Shader.TileMode.CLAMP);
private Paint paint = new Paint();

// ...

@Override
protected void onDraw(Canvas canvas) {
    float width = 800;
    float height = 800;
    float radius = width / 3;

    // Arc Image

    Bitmap.Config conf = Bitmap.Config.ARGB_8888; // See other config types
    Bitmap mImage = Bitmap.createBitmap(800, 800, conf); // This creates a mutable bitmap
    Canvas imageCanvas = new Canvas(mImage);

    // Draw both rectangles
    paint.setShader(shader1);
    imageCanvas.drawRect(0, 0, 400, 800, paint);
    paint.setShader(shader2);
    imageCanvas.drawRect(400, 0, 800, 800, paint);

    // /Arc Image

    // Draw the rectangle image
    canvas.save();
    canvas.drawBitmap(mImage, 0, 0, null);
    canvas.restore();
}

因为你的目标是有一个带圆帽的彩色圆弧,我们接下来需要定义面积 用户应该可见的两个矩形。这意味着两个矩形中的大部分 将被掩盖,因此不可见。取而代之的只有圆弧区。

结果应如下所示:

为了实现所需的行为,我们定义了一个仅显示内部弧形区域的掩码 矩形。为此,我们大量使用 PaintsetXfermode 方法。作为参数 我们使用 PorterDuffXfermode.

的不同实例
private Paint maskPaint;
private Paint imagePaint;

// ...

// To be called within all constructors
private void init() {
    // I encourage you to research what this does in detail for a better understanding

    maskPaint = new Paint();
    maskPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));

    imagePaint = new Paint();
    imagePaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OVER));
}

@Override
protected void onDraw(Canvas canvas) {
    // @step1

    // Mask

    Bitmap mMask = Bitmap.createBitmap(800, 800, conf);
    Canvas maskCanvas = new Canvas(mMask);

    paint.setColor(Color.WHITE);
    paint.setShader(null);
    paint.setStrokeWidth(70);
    paint.setStyle(Paint.Style.STROKE);
    paint.setStrokeCap(Paint.Cap.ROUND);
    paint.setAntiAlias(true);
    final RectF oval = new RectF();
    center_x = 400;
    center_y = 400;
    oval.set(center_x - radius,
            center_y - radius,
            center_x + radius,
            center_y + radius);

    maskCanvas.drawArc(oval, 135, 270, false, paint);

    // /Mask

    canvas.save();
    // This is new compared to step 1
    canvas.drawBitmap(mMask, 0, 0, maskPaint);
    canvas.drawBitmap(mImage, 0, 0, imagePaint); // Notice the imagePaint instead of null
    canvas.restore();
}

2。创建覆盖白拇指

这解决了你的第一个问题。第二个可以再次使用蒙版来实现,尽管这个 是时候我们想要实现不同的东西了。之前,我们只想显示特定区域(圆弧) 背景图像(即两个矩形)。这次我们要反其道而行之: 我们定义一个背景图像(拇指)并屏蔽掉它的内部内容,这样只有 中风似乎还在。应用于弧形图像,拇指覆盖彩色弧形 一个透明的内容区域。

所以第一步是画拇指。我们为此使用与以下半径相同的圆弧 背景弧度不同,但角度不同,导致弧度小得多。但因为 thumb 应该 "surround" 背景弧,它的笔画宽度必须大于 背景弧度。

@Override
protected void onDraw(Canvas canvas) {
    // @step1

    // @step2

    // Thumb Image

    mImage = Bitmap.createBitmap(800, 800, conf);
    imageCanvas = new Canvas(mImage);

    paint.setColor(Color.WHITE);
    paint.setStrokeWidth(120);
    final RectF oval2 = new RectF();
    center_x = 400;
    center_y = 400;
    oval2.set(center_x - radius,
            center_y - radius,
            center_x + radius,
            center_y + radius);

    imageCanvas.drawArc(oval2, 270, 45, false, paint);

    // /Thumb Image

    canvas.save();
    canvas.drawBitmap(RotateBitmap(mImage, 90f), 0, 0, null);
    canvas.restore();
}

public static Bitmap RotateBitmap(Bitmap source, float angle)
{
    Matrix matrix = new Matrix();
    matrix.postRotate(angle);
    return Bitmap.createBitmap(source, 0, 0, source.getWidth(), source.getHeight(), matrix, true);
}

代码的结果如下所示。

现在我们有了一个覆盖背景弧的拇指,我们需要定义遮罩 删除拇指的内部,使背景弧再次可见。

为了实现这一点,我们基本上使用与之前相同的参数来创建另一个圆弧,但是 这次笔划宽度必须与用于背景弧的宽度相同 这标记了我们要在拇指内部移除的区域。

使用以下代码,生成的图像如图4所示。

@Override
protected void onDraw(Canvas canvas) {
    // @step1

    // @step2

    // Thumb Image
    // ...
    // /Thumb Image

    // Thumb Mask

    mMask = Bitmap.createBitmap(800, 800, conf);
    maskCanvas = new Canvas(mMask);

    paint.setColor(Color.WHITE);
    paint.setStrokeWidth(70);
    paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
    final RectF oval3 = new RectF();
    center_x = 400;
    center_y = 400;
    oval3.set(center_x - radius,
            center_y - radius,
            center_x + radius,
            center_y + radius);

    maskCanvas.drawBitmap(mImage, 0, 0, null);
    maskCanvas.drawArc(oval3, 270, 45, false, paint);

    // /Thumb Mask

    canvas.save();
    canvas.drawBitmap(RotateBitmap(mMask, 90f), 0, 0, null); // Notice mImage changed to mMask
    canvas.restore();
}

3。动画白色拇指

你问题的最后一部分是制作弧线运动的动画。我没有固体 解决方案,但也许可以指导您朝着有用的方向发展。我会尝试以下操作:

首先将缩略图定义为 ImageView,它是整个弧形图的一部分。改变时 图形的选定值,围绕背景中心旋转拇指图像 弧。因为我们想为运动设置动画,只需设置拇指图像的旋转即可 不够。相反,我们使用像这样的 RotateAnimation

final RotateAnimation animRotate = new RotateAnimation(0.0f, -90.0f, // You have to replace these values with your calculated angles
        RotateAnimation.RELATIVE_TO_SELF, // This may be a tricky part. You probably have to change this to RELATIVE_TO_PARENT
        0.5f, // x pivot
        RotateAnimation.RELATIVE_TO_SELF,
        0.5f); // y pivot

animRotate.setDuration(1500);
animRotate.setFillAfter(true);
animSet.addAnimation(animRotate);

thumbView.startAnimation(animSet);

我想这离最终结果还很远,但它很可能会帮助您搜索所需的 解决方案。重要的是你的枢轴值必须参考你的中心 背景弧,因为这是您的拇指图像应该旋转的点。

我已经用 API 级别 16 和 22、23 测试了我的(完整)代码,所以我希望这个答案至少 为您提供解决问题的新思路。

请注意,onDraw 方法中的分配操作不是一个好主意,应该 避免。为简单起见,我没有遵循这个建议。该代码也将用作 一个正确方向的指南,而不是简单地复制和粘贴,因为它会让人感到沉重 使用幻数,通常不遵循良好的编码标准。

创建渐变比跟随路径不是那么简单。 因此,我可以建议您使用一些已经使用过的库。

包括图书馆:

dependencies {
    ...
    compile 'com.github.paroca72:sc-gauges:3.0.7'
}

在 XML 中创建仪表:

<com.sccomponents.gauges.library.ScArcGauge
            android:id="@+id/gauge"
            android:layout_width="300dp"
            android:layout_height="wrap_content"
            android:layout_gravity="center_horizontal" />

您的代码:

ScArcGauge gauge = this.findViewById(R.id.gauge);
gauge.setAngleSweep(270);
gauge.setAngleStart(135);
gauge.setHighValue(90);

int lineWidth = 50;
ScCopier baseLine = gauge.getBase();
baseLine.setWidths(lineWidth);
baseLine.setColors(Color.parseColor("#dddddd"));
baseLine.getPainter().setStrokeCap(Paint.Cap.ROUND);

ScCopier progressLine = gauge.getProgress();
progressLine.setWidths(lineWidth);
progressLine.setColors(
     Color.parseColor("#65AAF2"),
     Color.parseColor("#3EF2AD"),
     Color.parseColor("#FF2465")
);
progressLine.getPainter().setStrokeCap(Paint.Cap.ROUND);

你的结果:

您可以在此站点上找到更复杂的内容: ScComponents