Dibujo de un pulgar hueco redondeado sobre arco
Quiero crear un gráfico redondeado que muestre un rango de valores de mi aplicación. Los valores se pueden clasificar en 3 categorías: baja, media, alta – que están representadas 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 del rango relevante:
- Android: ¿Cómo cambiar el enfoque en el formulario con la acción del usuario?
- Los archivos XAML.g.cs generados automáticamente no se pueden compilar en el proyecto PCL Xamarin Forms
- Xamarin Forms Android Problemas, el paquete android.support.design. * No existe
- ¿Cómo puedo evitar el desplazamiento salvaje cuando un campo de formulario de entrada de texto de posición fija obtiene el foco?
- Forma de desplazamiento dentro de un ShapeDrawable
La posición del pulgar blanco sobre el arco de rango puede cambiar, según los valores medidos.
Actualmente, soy capaz de dibujar la gama 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 resultado del arco:
Mi pregunta es, ¿cómo puedo:
- Crear un gradiente suave entre esos 3 colores (intenté usar
SweepGradient
pero no me dio el resultado correcto). -
Cree el pulgar blanco de la superposición como se muestra en la imagen, para que pueda controlar dónde mostrarlo.
-
Animar este pulgar blanco sobre mi arco de rango.
Nota: el rango de 3 colores es estático – por lo que otra solución puede ser simplemente tomar el dibujable y pintar el pulgar blanco sobre él (y animarlo), así que estoy abierto a escuchar una solución así 🙂
- ¿Puedes manejar eventos en formas irregulares en Android?
- Esquina redondeada para texto en android
- Okhttp loggin interceptor en varias partes
- Dibujo de múltiples formas con ShapeDrawable en xml con Android
- Teclado rápidamente se oculta y reaparece en Android usando Xamarin Forms
- Cómo iterar a través de elementos de una vista
- Input type = "file" accept = "image / *" captura = "cámara" no funciona para móviles
- Cómo cambiar el color por defecto cuando el interruptor está "on" (azul a verde) en formas xamarin. En defecto del ios es GReen pero en androide es azul
Yo usaría máscaras para sus dos primeros problemas.
1. Crear un gradiente suave
El primer paso sería dibujar dos rectángulos con un gradiente 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. Marcé la línea donde ambos rectángulos se tocan negro para aclarar que son de hecho dos rectángulos diferentes.
Esto se puede lograr utilizando 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 casquillos redondeados, a continuación necesitamos definir el área de los dos rectángulos que deben ser visibles para el usuario. Esto significa que la mayoría de los dos rectángulos serán enmascarados lejos y por lo tanto no visibles. En cambio, lo único que queda es el área de arco.
El resultado debería tener este aspecto:
Con el fin de lograr el comportamiento necesario se define una máscara que sólo revela el área de arco dentro de los rectángulos. Para ello 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. Cree el pulgar blanco de la superposición
Esto soluciona su primer problema. El segundo se puede lograr usando máscaras de nuevo, aunque esta vez queremos lograr algo diferente. Antes, queríamos mostrar sólo un área específica (el arco) de la imagen de fondo (siendo los dos rectángulos). Esta vez queremos hacer lo contrario: Definimos una imagen de fondo (el pulgar) y enmascaramos su contenido interno, de modo que sólo el golpe parece permanecer. 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. Utilizamos un arco para esto con el mismo radio que el arco de fondo, pero con ángulos diferentes, resultando en un arco mucho más pequeño. Pero debido a que el pulgar debe "rodear" el arco de fondo, su anchura de trazo tiene que ser mayor 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 superpone el arco de fondo, necesitamos definir la máscara que elimina la parte interna del pulgar, de modo que el arco de fondo vuelva a ser visible nuevamente.
Para lograr esto usamos básicamente los mismos parámetros que antes para crear otro arco, pero esta vez el ancho del trazo tiene que ser idéntico al ancho utilizado para el arco de fondo ya que esto marca el área que queremos eliminar dentro del pulgar.
Utilizando 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 su pregunta sería animar el movimiento del arco. No tengo una solución sólida para esto, pero tal vez puede guiarlo en una dirección útil. Yo intentaría lo siguiente:
Primero defina el pulgar como un ImageView
que forma parte de su gráfico de arco completo. 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, simplemente establecer la rotación de la imagen del pulgar no sería adecuada. En su lugar, utilizamos un tipo de RotateAnimation
como 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);
Esto está lejos de ser definitiva, supongo, pero muy bien puede ayudarle en su búsqueda de la solución necesaria. Es muy importante que sus valores de pivote tengan que referirse al centro de su arco de fondo, ya que éste es el punto en el que su imagen de pulgar debe girar alrededor.
He probado mi código (completo) con el API de nivel 16 y 22, 23, por lo que espero que esta respuesta al menos le da 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 seguí este consejo. También el código debe ser utilizado como una guía en la dirección correcta y no ser simplemente copia y pegado, ya que hace un uso intensivo de números mágicos y generalmente no sigue buenas normas de codificación.
-
Me gustaría cambiar un poco de la forma en que dibuja su punto de vista, mirando el diseño original, en lugar de dibujar 3 tapas que dibujaría sólo una línea, de esa manera el
SweepGradient
funcionará. -
Este migth sea un pedacito complicado, usted tiene 2 opciones:
- Crear un
Path
con 4 arcos - Dibujar 2 arcos-uno es el blanco grande (lleno de blanco por lo que todavía quiere utilizar
Paint.Style.STROKE
) y otro en la parte superior de que lo hacen relleno transparente, se puede lograr con PorterDuff xfermode, probablemente le llevará par de Intenta hasta que usted consigue que sin limpiar el círculo verde también.
- Crear un
-
Me imagino que desea animar la posición del pulgar, por lo que sólo tiene que utilizar la
Animation
simple que invalidan la vista y dibujar la posición vista del pulgar en consecuencia.
Espera que esto ayude
- Android: ¿Cómo puedo detener Runnable?
- Iniciar IntentService de Actividad y actualizar Actividad cuando IntentService finaliza