Highlighting Text Input with Jetpack Compose

Highlighting Text Input with Jetpack Compose

Recientemente lanzamos una nueva función en Buffer, llamada Ideas. Con Ideas, puede almacenar sus mejores ideas, editarlas hasta que estén listas y soltarlas directamente en la cola del búfer. Ahora que tenemos las ideas en nuestras aplicaciones web y móviles, tenemos algo de tiempo para compartir algunas de las lecciones aprendidas al desarrollar esta función. En esta publicación de blog, profundizaremos en cómo agregar compatibilidad con el resaltado de URL a Ideas Composer en Android, utilizando Jetpack Compose.


Comenzamos a adoptar Jetpack Compose en nuestra aplicación en 2021, usándolo como el estándar para crear todas nuestras funciones nuevas, mientras lo adoptamos gradualmente en las partes existentes de nuestra aplicación. Creamos la función Ideas completamente con Jetpack Compose, por lo que, combinado con un desarrollo de funciones más rápido y una mayor previsibilidad dentro de nuestro estado de interfaz de usuario, hemos tenido muchas oportunidades para explorar Compose más a fondo y obtener más información sobre cómo podemos lograr ciertos requisitos en nuestra aplicación.

Dentro de Idea Generator admitimos el resaltado de enlaces dinámicos. Esto significa que si escribe la URL en el área de texto, el enlace se resaltará; al hacer clic en ese enlace, aparecerá la ventana emergente “Abrir enlace”, que abrirá el enlace en el navegador cuando se haga clic en él.

En esta publicación de blog, nos centraremos en el enlace que destaca la implementación y cómo se puede lograr esto en Jetpack Compose usando TextField autor.


Para el autor de ideas, usamos una extensión TextField Configurable para admitir la entrada de texto. Este autor contiene un argumento, visualTransformationque se utiliza para aplicar cambios visuales al texto introducido.

TextField(
    ...
    visualTransformation = ...
)

Este argumento requiere una VisualTransformation Una implementación que se utiliza para aplicar una transformación visual al texto introducido. Si miramos el código fuente de esta interfaz, veremos una función de filtro que toma el contenido del TextField y lo devuelve. TransformedText Una referencia que contiene el texto modificado.

@Immutable
fun interface VisualTransformation {
    fun filter(text: AnnotatedString): TransformedText
}

Cuando se trata de este script modificado, debemos proporcionar una implementación que cree un archivo AnnotatedString Referencia con nuestros cambios aplicados. Este contenido modificado se compila en un archivo TransformedText Vuelve a escribir a TextField para configurar.

Así podemos definir y aplicar transiciones al contenido. TextFielddebemos comenzar creando una nueva aplicación para VisualTransformation la interfaz para la cual crearemos una nueva clase, UrlTransformation. Esta clase se implementará VisualTransformation argumento, además de tomar un único argumento en forma de Color. Definimos este argumento para que podamos pasar una referencia al color del tema que se aplicará dentro de nuestra lógica, donde estaremos fuera del alcance del componible y no podremos acceder a nuestro tema componible.

class UrlTransformation(
    val color: Color
) : VisualTransformation {

}

Con esta clase definida, ahora necesitamos implementar el filtro de la función VisualTransformation interfaz de usuario. Dentro de esta función, devolveremos una instancia de TransformedText clase Podemos saltar al código fuente de esta clase y ver que hay dos propiedades que se requieren al instanciar esta clase.

/**
 * The transformed text with offset offset mapping
 */
class TransformedText(
    /**
     * The transformed text
     */
    val text: AnnotatedString,

    /**
     * The map used for bidirectional offset mapping from original to transformed text.
     */
    val offsetMapping: OffsetMapping
)

Se requieren ambos argumentos, por lo que tendremos que proporcionar un valor para cada uno al instanciar TransformedText Clase.

  • texto – Esta será la versión modificada del texto proporcionado a la función de filtro.
  • asignación de desplazamiento – Según la documentación, este es el mapa utilizado para el mapeo bidireccional del texto original al texto convertido
class UrlTransformation(
    val color: Color
) : VisualTransformation {
    override fun filter(text: AnnotatedString): TransformedText {
        return TransformedText(
            ...,
            OffsetMapping.Identity
        )
    }
}

a mi offsetMapping argumento, simplemente pasamos OffsetMapping.Identity Valor: este es el valor predeterminado predefinido utilizado para un archivo OffsetMapping Una interfaz, utilizada cuando se puede utilizar para convertir texto que no cambia la cantidad de caracteres. Cuando se trata del argumento del script, necesitaremos escribir alguna lógica que tome el contenido existente, aplique el resaltado y lo devuelva como nuevo. AnnotatedString referencia a pasar a TransformedText Referencia. Para esta lógica, crearemos una nueva función, buildAnnotatedStringWithUrlHighlighting. Esto requerirá dos argumentos: el texto que se resaltará, junto con el color que se usará para resaltar.

fun buildAnnotatedStringWithUrlHighlighting(
    text: String, 
    color: Color
): AnnotatedString {
    
}

A partir de esta función, tenemos que volver AnnotatedString La referencia que crearemos con buildAnnotatedString. Dentro de esta función, comenzaremos usando la operación de agregar para mapear el contenido textual de un archivo. AnnotatedString.

fun buildAnnotatedStringWithUrlHighlighting(
    text: String, 
    color: Color
): AnnotatedString {
    return buildAnnotatedString {
        append(text)
    }
}

A continuación, necesitaremos tomar el contenido de nuestra cadena y aplicar un resaltado a cualquier URL existente. Antes de que podamos hacer eso, necesitamos definir las URL en la cadena. La detección de URL puede variar según el caso de uso, así que para simplificar las cosas, escribamos un código de ejemplo que encuentre las URL en un texto específico. Este código tomará la cadena especificada y filtrará las URL, proporcionando como resultado una lista de cadenas de URL.

text?.split("\\s+".toRegex())?.filter { word ->
    Patterns.WEB_URL.matcher(word).matches()
}

Ahora que sabemos qué URL hay en la cadena, debemos resaltarlas. Esto tendría la forma de un estilo de cadena anotado, aplicado mediante la operación addStyle.

fun addStyle(style: SpanStyle, start: Int, end: Int)

Al llamar a esta función, necesitamos un pase SpanStyle que deseamos aplicar, junto con el índice inicial y final al que se debe aplicar este patrón. Comenzaremos calculando este índice inicial y final; para simplificar las cosas, supondremos que solo hay URL únicas en nuestra cadena.

text?.split("\\s+".toRegex())?.filter { word ->
    Patterns.WEB_URL.matcher(word).matches()
}.forEach {
    val startIndex = text.indexOf(it)
    val endIndex = startIndex + it.length
}

Aquí ubicamos el índice de inicio usando un archivo indexOf función, que nos dará el índice de inicio de la URL dada. Luego usaremos el índice inicial y la longitud de la URL para calcular el índice final. Luego podemos pasar estos valores a los argumentos correspondientes para addStyle Función.

text?.split("\\s+".toRegex())?.filter { word ->
    Patterns.WEB_URL.matcher(word).matches()
}.forEach {
    val startIndex = text.indexOf(it)
    val endIndex = startIndex + it.length
    addStyle(
        start = startIndex, 
        end = endIndex
    )
}

A continuación, tenemos que guardar SpanStyle que queremos aplicar al rango de índice especificado. Aquí simplemente queremos resaltar el texto usando el color proporcionado, por lo que pasaremos el valor del color de los argumentos de nuestra función como argumento de color para SpanStyle Función.

text?.split("\\s+".toRegex())?.filter { word ->
    Patterns.WEB_URL.matcher(word).matches()
}.forEach {
    val startIndex = text.indexOf(it)
    val endIndex = startIndex + it.length
    addStyle(
        style = SpanStyle(
            color = color
        ),
        start = startIndex, 
        end = endIndex
    )
}

Con esto en su lugar, ahora tenemos una funcionalidad completa que tomará el texto proporcionado y resaltará cualquier URL que se use Color Referencia.

fun buildAnnotatedStringWithUrlHighlighting(
    text: String, 
    color: Color
): AnnotatedString {
    return buildAnnotatedString {
        append(text)
        text?.split("\\s+".toRegex())?.filter { word ->
            Patterns.WEB_URL.matcher(word).matches()
        }.forEach {
            val startIndex = text.indexOf(it)
            val endIndex = startIndex + it.length
            addStyle(
                style = SpanStyle(
                    color = color,
                    textDecoration = TextDecoration.None
                ),
                start = startIndex, end = endIndex
            )
        }
    }
}

A continuación, tendremos que volver al archivo. UrlTransformation clase y pasar el resultado buildAnnotatedStringWithUrlHighlighting llamada de función a TransformedText Controversia.

class UrlTransformation(
    val color: Color
) : VisualTransformation {
    override fun filter(text: AnnotatedString): TransformedText {
        return TransformedText(
            buildAnnotatedStringWithUrlHighlighting(text, color),
            OffsetMapping.Identity
        )
    }
}

Ahora que lo tenemos UrlTransformation La ejecución está completa, podemos instanciarla y pasar la referencia a visualTransformation argumento TextField autor. Aquí usamos el color deseado de MaterialTheme Una referencia, que se utilizará al etiquetar URL en nuestro sitio TextField Contenido.

TextField(
    ...
    visualTransformation = UrlTransformation(
        MaterialTheme.colors.secondary)
)

Con lo anterior en su lugar, ahora tenemos soporte para el resaltado dinámico de URL dentro TextField autor. Esto significa que cuando un usuario ahora inserta una URL en el autor de una idea, la identificamos como la URL resaltándola con un color secundario de nuestro tema.

En esta publicación, hemos aprendido cómo podemos aplicar el etiquetado dinámico de URL al contenido de un archivo. TextField autor. En la próxima publicación, exploraremos cómo agregamos la ventana emergente Abrir enlace al hacer clic en una URL dentro del área de entrada del autor.

Leave a Reply

Your email address will not be published. Required fields are marked *