Con la función componible Canvas podemos capturar cuando el usuario toca con el dedo y comienza el desplazamiento dentro de la pantalla.
Por el momento estas funcionalidades en Compose son experimentales y como tal pueden cambiar en futuras versiones del API, veremos que cada vez que se utilizan funciones experimentales debemos agregar una anotación a la función respectiva.
Disponer una barra de selección de color en la parte superior y en la parte inferior un Canvas que ocupe el resto de la pantalla. Permitir dibujar a mano alzada utilizando el color seleccionado.
Crearemos un proyecto llamado: 'Compose32'
La interfaz visual a implementar debe ser similar a:
El código a implementar en Kotlin para obtener dicha funcionalidad es:
package com.tutorialesprogramacionya.compose32 import android.os.Bundle import android.view.MotionEvent import android.view.WindowManager import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.compose.foundation.Canvas import androidx.compose.foundation.horizontalScroll import androidx.compose.foundation.layout.* import androidx.compose.foundation.rememberScrollState import androidx.compose.material.* import androidx.compose.runtime.* import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.Modifier import androidx.compose.ui.geometry.Offset import androidx.compose.ui.graphics.Color import androidx.compose.ui.input.pointer.pointerInteropFilter import androidx.compose.ui.unit.dp class MainActivity : ComponentActivity() { @ExperimentalComposeUiApi override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN); setContent { PantallaPrincipal() } } } data class Punto(val x: Float, val y: Float, val color: Color) @ExperimentalComposeUiApi @Composable fun PantallaPrincipal() { val puntos = remember { mutableStateListOf<Punto>() } var colorSeleccionado by remember { mutableStateOf(Color.Red) } Column( ) { SelectorColor(seleccion = { colorSeleccionado = it }) Canvas(modifier = Modifier .fillMaxSize() .pointerInteropFilter { when (it.actionMasked) { MotionEvent.ACTION_UP -> { puntos.add(Punto(-1f, -1f, colorSeleccionado)) true } MotionEvent.ACTION_MOVE -> { puntos.add(Punto(it.x, it.y, colorSeleccionado)) true } MotionEvent.ACTION_DOWN -> { puntos.add(Punto(it.x, it.y, colorSeleccionado)) true } else -> false } }) { var primera = true var iniciox = 0f var inicioy = 0f for (punto in puntos) { if (punto.x == -1f && punto.y == -1f) { primera = true } else if (primera) { iniciox = punto.x inicioy = punto.y primera = false } else { drawLine( color = punto.color, start = Offset(x = iniciox, y = inicioy), end = Offset(x = punto.x, y = punto.y), strokeWidth = 12f ) iniciox = punto.x inicioy = punto.y } } } } } @Composable fun SelectorColor(seleccion: (color: Color) -> Unit) { Row( modifier = Modifier .fillMaxWidth() .horizontalScroll(rememberScrollState()), horizontalArrangement = Arrangement.SpaceBetween ) { for (rojo in 0..255 step 30) for (verde in 0..255 step 30) for (azul in 0..255 step 30) Button( modifier=Modifier.height(60.dp), onClick = { seleccion(Color(rojo,verde,azul)) }, colors = ButtonDefaults.textButtonColors( backgroundColor = Color(rojo,verde,azul) ) ) { } } }
Para borrar la barra de status de la parte superior implementamos el siguiente código antes que se llame a la función componible setContent:
class MainActivity : ComponentActivity() { @ExperimentalComposeUiApi override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN); setContent { PantallaPrincipal() } } }
Para mostrar la barra de selección de color implementamos la función componible 'SelectorColor', en la misma mediante tres for anidados procedemos a crear una serie de botones con distintos colores:
@Composable fun SelectorColor(seleccion: (color: Color) -> Unit) { Row( modifier = Modifier .fillMaxWidth() .horizontalScroll(rememberScrollState()), horizontalArrangement = Arrangement.SpaceBetween ) { for (rojo in 0..255 step 30) for (verde in 0..255 step 30) for (azul in 0..255 step 30) Button( modifier=Modifier.height(60.dp), onClick = { seleccion(Color(rojo,verde,azul)) }, colors = ButtonDefaults.textButtonColors( backgroundColor = Color(rojo,verde,azul) ) ) { } } }
Cuando se presiona alguno de los botones se llama a la función lambda de la función principal para que almacene el color seleccionado.
Definimos un data class que representa un punto de la pantalla y su color, luego crearemos una lista de este tipo de data class para almacenar cada punto donde el usuario dibuja:
data class Punto(val x: Float, val y: Float, val color: Color)
Como la captura de eventos de touch (toque) todavía no está finlalizada, debemos agregar la anotación @ExperimentalComposeUiApi previa a la declaración de la función (esto puede haber cambiado para el momento que esté haciendo el curso)
La función canvar mediante el parámetro modifier procedemos a llamar a la función pointerInteropFilter y pasar una función lambda que recibe como parámetro la acción que ha efectuado el operador (presión con el dedo, movimiento, levantado del dedo):
@ExperimentalComposeUiApi @Composable fun PantallaPrincipal() { val puntos = remember { mutableStateListOf<Punto>() } var colorSeleccionado by remember { mutableStateOf(Color.Red) } Column( ) { SelectorColor(seleccion = { colorSeleccionado = it }) Canvas(modifier = Modifier .fillMaxSize() .pointerInteropFilter { when (it.actionMasked) { MotionEvent.ACTION_UP -> { puntos.add(Punto(-1f, -1f, colorSeleccionado)) true } MotionEvent.ACTION_MOVE -> { puntos.add(Punto(it.x, it.y, colorSeleccionado)) true } MotionEvent.ACTION_DOWN -> { puntos.add(Punto(it.x, it.y, colorSeleccionado)) true } else -> false }
Por ejemplo cuando sucede el evento ACTION_DOWN procedemos a guardar en la lista 'puntos' un objeto de la clase Punto donde acaba de presionar en pantalla. Luego sucede lo mismo cada vez que mueve el dedo estando en la superficie de la pantalla, finalmente cuando levanta el dedo de la pantalla guardamos los valores -1,-1 para tener en cuenta que ahí finaliza el trazo de la línea.
Para dibujar propiamente dicho utilizamos la función drawLine y trazamos líneas de un punto a otro, con la salvedad donde encontremos un valor (-1,-1) significa que ha finalizado el trazo de la línea:
}) { var primera = true var iniciox = 0f var inicioy = 0f for (punto in puntos) { if (punto.x == -1f && punto.y == -1f) { primera = true } else if (primera) { iniciox = punto.x inicioy = punto.y primera = false } else { drawLine( color = punto.color, start = Offset(x = iniciox, y = inicioy), end = Offset(x = punto.x, y = punto.y), strokeWidth = 12f ) iniciox = punto.x inicioy = punto.y } } } } }
Este proyecto lo puede descargar en un zip desde este enlace: Compose32.zip