Подтвердить что ты не робот

Нанесение закругленного полого пальца по дуге

Я хочу создать округленный график, который отобразит диапазон значений из моего приложения. Значения можно отнести к 3 категориям: низкий, средний, высокий - представлены тремя цветами: синим, зеленым и красным (соответственно).

Над этим диапазоном я хочу показать фактически измеренные значения - в виде "большого пальца" над соответствующей частью диапазона:

См. прикрепленную фотографию

Расположение белого большого пальца по дуге диапазона может изменяться в соответствии с измеренными значениями.

В настоящее время я могу нарисовать трехцветный диапазон, рисуя 3 дуги над одним и тем же центром, внутри метода viewDraw:

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);

И это результат дуги:

текущая дуга

Мой вопрос: как я:

  • Создайте плавный градиент между этими тремя цветами (я попытался использовать SweepGradient, но это не дало мне правильного результата).
  • Создайте белый белый цвет, как показано на рисунке, чтобы я мог контролировать, где его отображать.

  • Анимировать этот белый большой палец над моей дугой диапазона.

Примечание: трехцветный диапазон статичен, поэтому другое решение может состоять в том, чтобы просто взять выталкиваемый и нарисовать белый большой палец над ним (и оживить его), поэтому я открыт, чтобы услышать такое решение:)

4b9b3361

Ответ 1

Я бы использовал маски для ваших первых двух проблем.

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

Поскольку ваша цель - иметь цветную дугу с закругленными крышками, нам нужно определить область оба прямоугольника, которые должны быть видны пользователю. Это означает, что большинство обоих прямоугольников будут скрыты и, следовательно, не видны. Вместо этого остается только область дуги.

Результат должен выглядеть так:

второй шаг

Для достижения необходимого поведения мы определяем маску, которая только показывает область дуги внутри прямоугольники. Для этого мы интенсивно используем метод 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. Создайте оверлейный белый палец

Это решает вашу первую проблему. Второе может быть достигнуто с помощью маски снова, хотя это мы хотим достичь чего-то другого. Раньше мы хотели показать только определенную область (дугу) фонового изображения (являющегося двумя прямоугольниками). На этот раз мы хотим сделать обратное: Мы определяем фоновое изображение (большой палец) и маскируем его внутреннее содержимое, так что только кажется, что инсульт остается. При нанесении на изображение дуги большой палец накладывает цветную дугу на прозрачная область содержимого.

Итак, первым шагом будет рисование большого пальца. Для этого мы используем дугу с тем же радиусом, что и фоновая дуга, но разные углы, что приводит к значительно меньшей дуге. Но поскольку большой палец должен "окружать" фоновую дугу, ширина его хода должна быть больше, чем фоновая дуга.

@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 являются плохой идеей и должны избегать. Для простоты я не смог следовать этому совету. Также код должен использоваться как руководство в правильном направлении, а не просто копировать и вставлять, поскольку оно делает тяжелым использование магических чисел и, как правило, не соответствует хорошим стандартам кодирования.

Ответ 2

  • Я бы немного изменил то, как вы рисуете свое мнение, глядя на оригинальный дизайн, вместо того, чтобы рисовать 3 шапки, я бы нарисовал всего одну строку, так что SweepGradient будет работать.

  • Эта мига будет немного сложной, у вас есть 2 варианта:

    • создать Path с 4 дугами
    • нарисовать 2 дуги - один большой белый (заполненный белым цветом, поэтому вы все еще хотите использовать Paint.Style.STROKE), а еще один поверх этого, чтобы он заполнил прозрачность, вы можете достичь этого с помощью PorterDuff xfermode, возможно, вам понадобится пара попыток, пока вы не получите это, не очистив зеленый круг.
  • Я предполагаю, что вы хотите анимировать положение большого пальца, поэтому просто используйте простой Animation, который делает недействительным представление и соответственно рисует позицию просмотра большого пальца.

Надеюсь, что это поможет