viral trata que moto hoy android draw shapes android-graphics

android - trata - video viral de la moto



Dibujando un pulgar hueco redondeado sobre arco (3)

Quiero crear un gráfico redondeado que muestre un rango de valores desde mi aplicación. Los valores se pueden clasificar en 3 categorías: bajo, medio, alto, que están representados por 3 colores: azul, verde y rojo (respectivamente).

Por encima de este rango, quiero mostrar los valores realmente medidos, en forma de "pulgar" sobre la parte relevante del rango:

La ubicación del pulgar blanco sobre el arco de rango puede cambiar, de acuerdo con los valores medidos.

Actualmente, puedo dibujar el rango de 3 colores dibujando 3 arcos sobre el mismo centro, dentro del método onDraw de la vista:

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

Y este es el arco de resultados.

Mi pregunta es, ¿cómo puedo:

  1. Cree un degradado suave entre esos 3 colores (probé con SweepGradient pero no me dio el resultado correcto).
  2. Cree el pulgar blanco superpuesto como se muestra en la imagen, de modo que pueda controlar dónde mostrarlo.

  3. Animar este pulgar blanco sobre mi arco de alcance.

Nota: el rango de 3 colores es estático, por lo que otra solución puede ser simplemente tomar el dibujo y pintar el pulgar blanco sobre él (y animarlo), así que estoy abierto a escuchar esa solución también :)


  1. Cambiaría un poco la forma en que dibuja su vista, al buscar en el diseño original, en lugar de dibujar 3 mayúsculas, solo dibujaría 1 línea, de esa forma funcionará SweepGradient .

  2. Esto puede ser un poco complicado, tienes 2 opciones:

    • crear un Path con 4 arcos
    • dibuja 2 arcos, uno es el blanco grande (lleno de blanco, así que aún quieres usar Paint.Style.STROKE ) y otro encima de eso lo hace transparente, puedes lograrlo con PorterDuff xfermode, probablemente te lleve un par de intenta hasta que lo consigas sin borrar el círculo verde también.
  3. Me imagino que quieres animar la posición del pulgar, así que solo usa Animation simple que invalida la vista y dibuja la posición del pulgar en consecuencia.

Espera que esto ayude


Crear un gradiente que siga un camino no es tan simple. Así que puedo sugerirte que uses algunas bibliotecas de las que ya lo hiciste.

Incluye la biblioteca:

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

Crear el indicador en XML:

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

Tu codigo:

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

Tu resultado:

Puedes encontrar algo más complejo en este sitio: ScComponents


Yo usaría máscaras para tus dos primeros problemas.

1. Crea un degradado suave.

El primer paso sería dibujar dos rectángulos con un degradado lineal. El primer rectángulo contiene los colores azul y verde, mientras que el segundo rectángulo contiene verde y rojo como se ve en la siguiente imagen. Marqué la línea donde ambos rectángulos se tocan en negro para aclarar que son de hecho dos rectángulos diferentes.

Esto se puede lograr usando el siguiente código (extracto):

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

Como su objetivo es tener un arco de color con tapas redondeadas, a continuación debemos definir el área de ambos rectángulos que debe ser visible para el usuario. Esto significa que la mayoría de los dos rectángulos se enmascararán y, por lo tanto, no serán visibles. En cambio, lo único que queda es el área del arco.

El resultado debería verse así:

Para lograr el comportamiento necesario, definimos una máscara que solo revela el área de arco dentro de los rectángulos. Para esto hacemos un uso setXfermode método setXfermode de Paint . Como argumento usamos diferentes instancias de un 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. Crear el pulgar blanco superposición.

Esto resuelve tu primer problema. El segundo se puede lograr usando máscaras nuevamente, aunque esta vez queremos lograr algo diferente. Antes, queríamos mostrar solo un área específica (el arco) de la imagen de fondo (que son los dos rectángulos). Esta vez queremos hacer lo contrario: definimos una imagen de fondo (el pulgar) y ocultamos su contenido interno, de modo que solo quede el trazo. Aplicado a la imagen de arco, el pulgar superpone el arco de color con un área de contenido transparente.

Así que el primer paso sería dibujar el pulgar. Usamos un arco para esto con el mismo radio que el arco de fondo pero con diferentes ángulos, lo que resulta en un arco mucho más pequeño. Pero como el pulgar debe "rodear" el arco de fondo, su ancho de trazo debe ser más grande que el arco de fondo.

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

El resultado del código se muestra a continuación.

Así que ahora que tenemos un pulgar que está sobreponiendo el arco de fondo, necesitamos definir la máscara que quita la parte interna del pulgar, para que el arco de fondo vuelva a ser visible.

Para lograr esto, básicamente utilizamos los mismos parámetros que antes para crear otro arco, pero esta vez el ancho del trazo debe ser idéntico al ancho utilizado para el arco de fondo, ya que marca el área que queremos eliminar dentro del pulgar.

Usando el siguiente código, la imagen resultante se muestra en la imagen 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. Animar el pulgar blanco.

La última parte de tu pregunta sería animar el movimiento del arco. No tengo una solución sólida para esto, pero tal vez pueda guiarlo en una dirección útil. Intentaría lo siguiente:

Primero, defina el pulgar como un ImageView que forma parte de todo su gráfico de arco. Al cambiar los valores seleccionados de su gráfico, gire la imagen del pulgar alrededor del centro del arco de fondo. Debido a que queremos animar el movimiento, solo ajustar la rotación de la imagen del pulgar no sería adecuado. En su lugar usamos un tipo de RotateAnimation así:

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

Supongo que esto está lejos de ser definitivo, pero puede ayudarlo en su búsqueda de la solución necesaria. Es muy importante que los valores de pivote tengan que referirse al centro del arco de fondo, ya que este es el punto en el que debe girar la imagen del pulgar.

He probado mi código (completo) con niveles de API 16 y 22, 23, por lo que espero que esta respuesta le brinde nuevas ideas sobre cómo resolver sus problemas.

Tenga en cuenta que las operaciones de asignación dentro del método onDraw son una mala idea y deben evitarse. Por simplicidad no pude seguir este consejo. Además, el código se debe usar como una guía en la dirección correcta y no para ser simplemente copiado y pegado, ya que hace un uso intensivo de los números mágicos y generalmente no cumple con los buenos estándares de codificación.