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:
- Cree un degradado suave entre esos 3 colores (probé con
SweepGradient
pero no me dio el resultado correcto). Cree el pulgar blanco superpuesto como se muestra en la imagen, de modo que pueda controlar dónde mostrarlo.
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 :)
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
.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.
- crear un
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.