29

アプリからの値の範囲を表示する丸みを帯びたグラフを作成したいと考えています。値は、低、中、高の 3 つのカテゴリに分類できます。これらは、青、緑、赤 (それぞれ) の 3 つの色で表されます。

この範囲を超えると、実際に測定された値を、関連する範囲部分に「親指」の形で表示したいと思います。

添付写真をご覧ください

測定値に応じて、範囲アーク上の白い親指の位置が変わる場合があります。

現在、ビューの 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. 写真に示すように、オーバーレイの白いつまみを作成して、表示する場所を制御できるようにします。

  3. この白い親指を範囲の弧の上でアニメーション化します

注: 3 色の範囲は静的です。つまり、別の解決策として、drawable を取得してその上に白い親指をペイントする (そしてアニメーション化する) ことができるので、そのような解決策も聞くことができます :)

4

3 に答える 3

31

最初の 2 つの問題にはマスクを使用します。

1.滑らかなグラデーションを作成する

最初のステップは、線形グラデーションで 2 つの四角形を描画することです。次の図に示すように、最初の四角形には青と緑の色が含まれ、2 つ目の四角形には緑と赤が含まれます。両方の長方形が互いに接する線を黒くマークして、実際には 2 つの異なる長方形であることを明確にしました。

最初の一歩

これは、次のコード (抜粋) を使用して実現できます。

// 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();
}

目標は丸みを帯びたキャップを持つ色付きの円弧にすることなので、次に、ユーザーに表示される両方の長方形の領域を定義する必要があります。これは、両方の長方形のほとんどがマスクされて見えなくなることを意味します。代わりに残るのはアーク領域だけです。

結果は次のようになります。

第二段階

必要な動作を実現するために、長方形内の円弧領域のみを表示するマスクを定義します。setXfermodeこのために、 のメソッドを多用しますPaint。引数として、 のさまざまなインスタンスを使用します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. オーバーレイの白い親指を作成します

これで最初の問題は解決します。2 つ目はマスクを使用して実現できますが、今回は別のことを実現したいと考えています。以前は、背景画像 (2 つの長方形) の特定の領域 (円弧) のみを表示したいと考えていました。今回は反対のことを行います。背景イメージ (親指) を定義し、その内部コンテンツをマスクして、ストロークだけが残るようにします。弧の画像に適用された親指は、透明なコンテンツ領域で色付きの弧を覆います。

したがって、最初のステップは親指を描くことです。これには、背景の円弧と同じ半径で異なる角度の円弧を使用するため、はるかに小さな円弧になります。ただし、親指は背景の弧を「取り囲む」必要があるため、そのストローク幅は背景の弧よりも大きくする必要があります。

@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 に表示されます。

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は悪い考えであり、避けるべきであることに注意してください。簡単にするために、私はこのアドバイスに従わなかった. また、このコードはマジック ナンバーを多用し、一般的に適切なコーディング基準に従っていないため、単純にコピー アンド ペーストするのではなく、正しい方向へのガイドとして使用する必要があります。

于 2016-02-15T20:16:20.040 に答える
1
  1. 元のデザインを見て、ビューの描画方法を少し変更します。3 つのキャップを描画する代わりに、1 本の線だけを描画します。そのようSweepGradientに機能します。

  2. これは少し難しいかもしれません。2 つのオプションがあります。

    • Path4 つの円弧で を作成する
    • 2 つの弧を描きます - 1 つは大きな白 (白で塗りつぶされているので、まだ使用したいPaint.Style.STROKE) で、もう 1 つはその上に塗りつぶして透明にします。PorterDuff xfermode で実現できます。緑色の円もクリアせずに。
  3. 親指の位置をアニメーション化したいのでAnimation、ビューを無効にする単純なものを使用し、それに応じて親指のビュー位置を描画します。

これが役立つことを願っています

于 2016-02-14T10:54:14.357 に答える