La parte más importante de una vista personalizada es su apariencia. Dibujo personalizado puede ser fácil o complejo según las necesidades de tu aplicación. Este documento que abarca algunas de las operaciones más comunes.
Para obtener más información, consulta Descripción general de los elementos de diseño.
Cómo anular onDraw()
El paso más importante al dibujar una vista personalizada es anular el
onDraw()
. El parámetro para onDraw()
es un
Canvas
que la vista puede usar para dibujarse por sí misma. La clase Canvas
define métodos para dibujar texto, líneas, mapas de bits y muchos otros gráficos
primitivas. Puedes usar estos métodos en onDraw()
para crear tu
una interfaz de usuario (IU) personalizada.
Para comenzar, crea un
Paint
.
En la siguiente sección, se analiza Paint
con más detalle.
Cómo crear objetos de dibujo
El
android.graphics
framework divide el dibujo en dos áreas:
- Qué diseñar, controlado por
Canvas
. - Cómo diseñar, manejado por
Paint
Por ejemplo, Canvas
proporciona un método para dibujar una línea.
Paint
proporciona métodos para definir el color de esa línea.
Canvas
tiene un método para dibujar un rectángulo y Paint
.
define si se debe rellenar ese rectángulo con un color o dejarlo vacío.
Canvas
define formas que puedes dibujar en la pantalla.
Paint
define el color, el estilo, la fuente y otros aspectos de cada forma.
dibujes.
Antes de dibujar algo, crea uno o más objetos Paint
. El
El siguiente ejemplo hace esto en un método llamado init
. Este método es
llamado desde el constructor desde Java, pero se puede inicializar en línea en
Kotlin
Kotlin
@ColorInt private var textColor // Obtained from style attributes. @Dimension private var textHeight // Obtained from style attributes. private val textPaint = Paint(ANTI_ALIAS_FLAG).apply { color = textColor if (textHeight == 0f) { textHeight = textSize } else { textSize = textHeight } } private val piePaint = Paint(Paint.ANTI_ALIAS_FLAG).apply { style = Paint.Style.FILL textSize = textHeight } private val shadowPaint = Paint(0).apply { color = 0x101010 maskFilter = BlurMaskFilter(8f, BlurMaskFilter.Blur.NORMAL) }
Java
private Paint textPaint; private Paint piePaint; private Paint shadowPaint; @ColorInt private int textColor; // Obtained from style attributes. @Dimension private float textHeight; // Obtained from style attributes. private void init() { textPaint = new Paint(Paint.ANTI_ALIAS_FLAG); textPaint.setColor(textColor); if (textHeight == 0) { textHeight = textPaint.getTextSize(); } else { textPaint.setTextSize(textHeight); } piePaint = new Paint(Paint.ANTI_ALIAS_FLAG); piePaint.setStyle(Paint.Style.FILL); piePaint.setTextSize(textHeight); shadowPaint = new Paint(0); shadowPaint.setColor(0xff101010); shadowPaint.setMaskFilter(new BlurMaskFilter(8, BlurMaskFilter.Blur.NORMAL)); ... }
La creación de objetos con anticipación es una optimización importante. Las vistas tienen las siguientes características:
se vuelve a dibujar con frecuencia, y muchos objetos
de dibujo requieren una costosa inicialización.
Crear objetos de dibujo dentro de tu método onDraw()
de manera significativa
reduce el rendimiento y puede ralentizar la IU.
Cómo controlar eventos de diseño
Para dibujar correctamente tu vista personalizada, averigua de qué tamaño es. Complejidad personalizada las vistas a menudo deben realizar varios cálculos de diseño según el tamaño y la forma de su área en la pantalla. Nunca hagas suposiciones sobre el tamaño de tu en la pantalla. Incluso si solo una app usa tu vista, esa app debe controlar diferentes tamaños de pantalla, varias densidades de pantalla y diversos aspectos relaciones de aspecto en modo vertical y horizontal.
Aunque View
tiene muchos métodos para manejar las mediciones, la mayoría de ellos no necesitan
anulada. Si tu vista no necesita un control especial sobre el tamaño, solo
anular un método:
onSizeChanged()
Se llama a onSizeChanged()
cuando a tu vista se le asigna por primera vez un
y, si el tamaño de la vista cambia por algún motivo. Calcular
posiciones, dimensiones y cualquier otro valor relacionado con el tamaño de la vista en
onSizeChanged()
, en lugar de volver a calcularlos cada vez que dibujes.
En el siguiente ejemplo, onSizeChanged()
es donde la vista
calcula el rectángulo delimitador del gráfico y la posición relativa de la
etiqueta de texto y otros elementos visuales.
Cuando se asigna un tamaño a tu vista, el administrador de diseño asume que el tamaño
incluye el relleno de la vista. Controla los valores de padding cuando calcules tu
tamaño de la vista. En este fragmento de onSizeChanged()
, se muestra cómo
para hacerlo:
Kotlin
private val showText // Obtained from styled attributes. private val textWidth // Obtained from styled attributes. override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) { super.onSizeChanged(w, h, oldw, oldh) // Account for padding. var xpad = (paddingLeft + paddingRight).toFloat() val ypad = (paddingTop + paddingBottom).toFloat() // Account for the label. if (showText) xpad += textWidth.toFloat() val ww = w.toFloat() - xpad val hh = h.toFloat() - ypad // Figure out how big you can make the pie. val diameter = Math.min(ww, hh) }
Java
private Boolean showText; // Obtained from styled attributes. private int textWidth; // Obtained from styled attributes. @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); // Account for padding. float xpad = (float)(getPaddingLeft() + getPaddingRight()); float ypad = (float)(getPaddingTop() + getPaddingBottom()); // Account for the label. if (showText) xpad += textWidth; float ww = (float)w - xpad; float hh = (float)h - ypad; // Figure out how big you can make the pie. float diameter = Math.min(ww, hh); }
Si necesitas un control más detallado de los parámetros de diseño de la vista, implementa
onMeasure()
Los parámetros de este método son
View.MeasureSpec
valores que indican el tamaño que quiere que tenga el publicador superior de la vista y
si ese tamaño es un máximo estricto o solo una sugerencia. Como optimización,
estos valores se almacenan como números enteros empaquetados y se usan los métodos estáticos
View.MeasureSpec
para descomprimir la información almacenada en cada número entero.
A continuación, se incluye un ejemplo de implementación de onMeasure()
. En este
en la implementación, intenta que el área sea lo suficientemente grande como para que el gráfico sea lo más grande
como su etiqueta:
Kotlin
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { // Try for a width based on your minimum. val minw: Int = paddingLeft + paddingRight + suggestedMinimumWidth val w: Int = View.resolveSizeAndState(minw, widthMeasureSpec, 1) // Whatever the width is, ask for a height that lets the pie get as big as // it can. val minh: Int = View.MeasureSpec.getSize(w) - textWidth.toInt() + paddingBottom + paddingTop val h: Int = View.resolveSizeAndState(minh, heightMeasureSpec, 0) setMeasuredDimension(w, h) }
Java
@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { // Try for a width based on your minimum. int minw = getPaddingLeft() + getPaddingRight() + getSuggestedMinimumWidth(); int w = resolveSizeAndState(minw, widthMeasureSpec, 1); // Whatever the width is, ask for a height that lets the pie get as big as it // can. int minh = MeasureSpec.getSize(w) - (int)textWidth + getPaddingBottom() + getPaddingTop(); int h = resolveSizeAndState(minh, heightMeasureSpec, 0); setMeasuredDimension(w, h); }
Hay tres aspectos importantes que debes tener en cuenta en este código:
- Los cálculos tienen en cuenta el relleno de la vista. Como se mencionó anteriormente, anteriormente, esto es responsabilidad de la vista.
- El método auxiliar
resolveSizeAndState()
se usa para crear los valores finales de ancho y alto. Este ayudante devuelve un valor deView.MeasureSpec
apropiado comparando el el tamaño necesario de la vista al valor que se pasa aonMeasure()
. onMeasure()
no tiene valor de retorno. En cambio, el método comunica sus resultados llamandosetMeasuredDimension()
La llamada a este método es obligatoria. Si omites esta llamada, el La claseView
arroja una excepción de tiempo de ejecución.
Generación
Después de definir el código de medición y creación del objeto, puedes implementar
onDraw()
Cada vista implementa onDraw()
de manera diferente,
pero hay algunas operaciones comunes que comparten la mayoría de las vistas:
- Dibuja el texto con
drawText()
Especifica el tipo de letra llamandosetTypeface()
y el color del texto llamandosetColor()
- Dibujar formas primitivas con
drawRect()
,drawOval()
, ydrawArc()
Cambia si las formas están rellenas, contorneadas o ambas llamando asetStyle()
- Dibuja formas más complejas con el
Path
. Para definir una forma, agrega líneas y curvas a un objetoPath
. objeto, luego dibuja la forma usandodrawPath()
Al igual que con las formas primitivas, las rutas se pueden delinear o rellenar, o ambas. segúnsetStyle()
. -
Define los rellenos de gradientes creando
LinearGradient
. objetos. LlamadasetShader()
para usar tuLinearGradient
en formas rellenas. - Dibuja mapas de bits con
drawBitmap()
El siguiente código dibuja una combinación de texto, líneas y formas:
Kotlin
private val data = mutableListOf<Item>() // A list of items that are displayed. private var shadowBounds = RectF() // Calculated in onSizeChanged. private var pointerRadius: Float = 2f // Obtained from styled attributes. private var pointerX: Float = 0f // Calculated in onSizeChanged. private var pointerY: Float = 0f // Calculated in onSizeChanged. private var textX: Float = 0f // Calculated in onSizeChanged. private var textY: Float = 0f // Calculated in onSizeChanged. private var bounds = RectF() // Calculated in onSizeChanged. private var currentItem: Int = 0 // The index of the currently selected item. override fun onDraw(canvas: Canvas) { super.onDraw(canvas) canvas.apply { // Draw the shadow. drawOval(shadowBounds, shadowPaint) // Draw the label text. drawText(data[currentItem].label, textX, textY, textPaint) // Draw the pie slices. data.forEach {item -> piePaint.shader = item.shader drawArc( bounds, 360 - item.endAngle, item.endAngle - item.startAngle, true, piePaint ) } // Draw the pointer. drawLine(textX, pointerY, pointerX, pointerY, textPaint) drawCircle(pointerX, pointerY, pointerRadius, textPaint) } } // Maintains the state for a data item. private data class Item( var label: String, var value: Float = 0f, @ColorInt var color: Int = 0, // Computed values. var startAngle: Float = 0f, var endAngle: Float = 0f, var shader: Shader )
Java
private List<Item> data = new ArrayList<Item>(); // A list of items that are displayed. private RectF shadowBounds; // Calculated in onSizeChanged. private float pointerRadius; // Obtained from styled attributes. private float pointerX; // Calculated in onSizeChanged. private float pointerY; // Calculated in onSizeChanged. private float textX; // Calculated in onSizeChanged. private float textY; // Calculated in onSizeChanged. private RectF bounds; // Calculated in onSizeChanged. private int currentItem = 0; // The index of the currently selected item. protected void onDraw(Canvas canvas) { super.onDraw(canvas); // Draw the shadow. canvas.drawOval( shadowBounds, shadowPaint ); // Draw the label text. canvas.drawText(data.get(currentItem).label, textX, textY, textPaint); // Draw the pie slices. for (int i = 0; i < data.size(); ++i) { Item it = data.get(i); piePaint.setShader(it.shader); canvas.drawArc( bounds, 360 - it.endAngle, it.endAngle - it.startAngle, true, piePaint ); } // Draw the pointer. canvas.drawLine(textX, pointerY, pointerX, pointerY, textPaint); canvas.drawCircle(pointerX, pointerY, pointerRadius, textPaint); } // Maintains the state for a data item. private class Item { public String label; public float value; @ColorInt public int color; // Computed values. public int startAngle; public int endAngle; public Shader shader; }
Aplicar efectos gráficos
Android 12 (nivel de API 31) incorpora la
RenderEffect
que aplica efectos de gráficos comunes, como desenfoques, filtros de color,
Los efectos de sombreador de Android y mucho más
View
objetos y
y las jerarquías correspondientes. Puedes combinar efectos como efectos de cadena, que consisten
de un efecto interno y externo, o efectos combinados. Compatibilidad con esta función
varía según la potencia de procesamiento del dispositivo.
También puedes aplicar efectos al modelo
RenderNode
para
un View
llamando
View.setRenderEffect(RenderEffect)
Para implementar un objeto RenderEffect
, haz lo siguiente:
view.setRenderEffect(RenderEffect.createBlurEffect(radiusX, radiusY, SHADER_TILE_MODE))
Puedes crear la vista de manera programática o ampliarla a partir de un diseño XML y
Recupérala con la Vinculación de vistas .
findViewById()