prefacio
Me pregunto si ya has empezado a entender Jetpack Compose.
Si ya has empezado a entender y lo has escrito a mano. Bueno, no sé si te has dado cuenta de que hay muchas aplicaciones de Scopes en Compose. Por ejemplo, weight
los modificadores solo se pueden usar en RowScope
o ColumnScope
ámbitos. Como otro ejemplo, item
los componentes solo se pueden usar en LazyListScope
el alcance.
Si no ha aprendido acerca de Compose, también debe saber que hay 5 funciones de alcance en la biblioteca estándar de Kotlin: let()
apply()
also()
with()
run()
estas 5 funciones retendrán y devolverán el objeto de contexto de diferentes maneras, es decir, cuando se llame a estas funciones, en El código escrito en sus parámetros lambda estará en un ámbito específico.
No sé si has pensado en cómo se implementan estas restricciones de alcance. Si queremos personalizar una función Composable que solo admite el uso en un ámbito específico, ¿cómo deberíamos escribirla?
Este artículo te resolverá esta duda.
alcance
Pero antes de comenzar oficialmente, agreguemos algunos conocimientos básicos sobre el alcance en Kotlin.
que es alcance
De hecho, para nosotros los programadores, sin importar el idioma que aprendamos, debemos tener una comprensión del alcance.
Para dar un ejemplo simple:
val valueFile = "file"
fun a() {
val valueA = "a"
println(valueFile)
println(valueA)
println(valueB)
}
fun b() {
val valueB = "b"
println(valueFile)
println(valueA)
println(valueB)
}
No es necesario ejecutar este código para saber que definitivamente informará un error, porque no se puede acceder en la función a valueB
, no se puede acceder en la función b valueA
. Pero se puede acceder a ambas funciones con éxito valueFile
.
Esto se debe a que valueFile
el alcance de es todo el archivo .kt, es decir, siempre que esté en el código de este archivo, se puede acceder a él.
valueA
Los ámbitos de y están valueB
respectivamente en función a y b, obviamente solo se pueden usar en sus respectivos ámbitos.
De manera similar, si queremos llamar a un método o función de una clase, también debemos considerar el alcance:
class Test {
val valueTest = "test"
fun a(): String {
val valueA = "a"
println(valueTest)
println(valueA)
return "returnA"
}
fun b() {
println(valueA)
println(valueTest)
println(a())
}
}
fun main() {
println(valueTest)
println(valueA)
println(a())
}
Los ejemplos dados aquí pueden no ser apropiados, pero aquí es para ilustrar esta situación, no se enrede demasiado ~
Obviamente, el código anterior main
no puede acceder a las variables valueTest
y en la función valueA
, y no puede llamar a la función a()
; mientras que Test
la función en la clase a()
obviamente puede acceder valueTest
a y valueA
, y la función b()
también puede llamar a la función a()
, que puede acceder a la variable valueTest
pero no puede acceder a la variable valueA
.
Esto se debe a que las funciones a()
y b()
la variable valueTest
están en el mismo ámbito, Test
el ámbito de la clase.
La variable valueA
se encuentra a()
en el ámbito de la función, y dado que a()
se encuentra Test
en el ámbito de , de hecho valueA
el ámbito de aquí se denomina ámbito anidado, es decir, se encuentra en el ámbito de a()
y al mismo tiempo.Test
Debido a que esta sección es solo para presentar lo que vamos a presentar hoy, se presenta brevemente mucho conocimiento sobre el alcance. Para obtener más conocimiento sobre el alcance, lea la Referencia 1.
Funciones con alcance en la biblioteca estándar de Kotlin
En el prefacio dijimos que hay cinco cosas llamadas funciones de alcance en la biblioteca estándar de Kotlin: with
, run
, let
, also
, apply
.
¿Qué hacen?
Primero veamos un formulario de código que encontramos a menudo:
val person = Person()
person.fullName = "equationl"
person.lastName = "l"
person.firstName = "equation"
person.age = 24
person.gender = "man"
En algunos casos, es posible que necesitemos escribir un montón de repeticiones muchas veces person
, lo cual es muy legible y engorroso de escribir.
En este punto podemos usar la función scope, por ejemplo usando with
para reescribir:
with(person) {
fullName = "equationl"
lastName = "l"
firstName = "equation"
age = 24
gender = "man"
}
En este punto, podemos omitirlo person
y acceder directamente o modificar su valor de atributo, esto se debe a que with
el primer parámetro recibe el objeto de contexto lambda que debe usarse como segundo parámetro, es decir, el segundo parámetro lambda es anónimo El alcance de la función es el objeto pasado por el primer parámetro. En este momento, el indicador del IDE también señaló que el alcance de la función anónima de with es Person
:
Entonces, en esta función anónima, puede acceder directamente o modificar los atributos de Persona.
De manera similar, también podemos usar run
la función para reescribir:
person.run {
fullName = "equationl"
lastName = "l"
firstName = "equation"
age = 24
gender = "man"
}
Se puede ver que run
es with
muy similar a , excepto que run
recibe el objeto de contexto en forma de función de extensión , y su parámetro es solo una función anónima lambda.
También hay let
:
person.let {
it.fullName = "equationl"
it.lastName = "l"
it.firstName = "equation"
it.age = 24
it.gender = "man"
}
La diferencia entre it y run
es que el objeto de contexto en la función anónima ya no es un receptor implícito (this), sino que existe como un parámetro (it).
Usa also()
entonces:
person.also {
it.fullName = "equationl"
it.lastName = "l"
it.firstName = "equation"
it.age = 24
it.gender = "man"
}
Al igual que let
, también es una función de extensión, y el contexto también se pasa a la función anónima como parámetro, pero de manera diferente, let
devolverá el objeto de contexto, lo que puede facilitar llamadas en cadena, como:
val personString = person
.also {
it.age = 25
}
.toString()
Y finalmente apply
:
person.apply {
fullName = "equationl"
lastName = "l"
firstName = "equation"
age = 24
gender = "man"
}
Al igual que also
, es una función de extensión y también devuelve un objeto de contexto, pero su contexto será el receptor implícito en lugar de un parámetro de la función anónima.
El siguiente es un cuadro comparativo y una tabla de sus 5 funciones:
función | forma contextual | valor devuelto | ¿Es una función de extensión? |
---|---|---|---|
con | receptor implícito (este) | función lambda (Unidad) | No |
correr | receptor implícito (este) | función lambda (Unidad) | Sí |
dejar | El parámetro de la función anónima (it) | función lambda (Unidad) | Sí |
también | El parámetro de la función anónima (it) | objeto de contexto | Sí |
aplicar | receptor implícito (este) | objeto de contexto | Sí |
Restricciones de alcance en Compose
Como dijimos en el prefacio, hay muchas aplicaciones de restricciones de alcance en Compose.
Por ejemplo, el modificador Modifier, de esta lista de modificadores Compose , también podemos ver que el alcance de muchos modificadores es limitado:
La razón para restringir los modificadores aquí es muy simple:
En el sistema Android View, no hay ningún tipo de seguridad. Los desarrolladores generalmente se encuentran probando diferentes parámetros de diseño para descubrir cuáles se consideran y su significado en el contexto de un padre en particular.
En el sistema de vista xml tradicional, no hay restricciones en los parámetros del diseño, lo que lleva al hecho de que todos los parámetros se pueden usar en cualquier diseño, lo que causará algunos problemas. Ligeramente, los parámetros no son válidos y se escriben un montón de parámetros inútiles; en serio, puede interferir con el uso normal del diseño.
Por supuesto, la restricción del modificador Modifier es solo una de las aplicaciones en Compose, y hay muchos ejemplos de restricciones de alcance en Compose, por ejemplo:
En la figura anterior item
solo se puede usar en LazyListScope
el alcance, drawRect
solo se puede usar en DrawScope
el alcance.
Por supuesto, como dijimos antes, no solo hay funciones y métodos en el alcance, sino también las propiedades de la clase, por ejemplo, una propiedad DrawScope
llamada se proporciona en el alcance size
, y el tamaño actual del lienzo se puede obtener a través de él:
Entonces, ¿cómo se logran estos?
Personaliza nuestra función de límite de alcance
principio
Antes de comenzar a implementar nuestra propia función de alcance, primero debemos comprender el principio.
Canvas
Aquí tomamos el de Compose como ejemplo.
Primero Canvas
la definición de:
Se puede ver que aquí Canvas
se reciben dos parámetros : el modificador y la lambda de onDraw, y el Receiver (receptor) de esta lambda es DrawScope
, es decir, el alcance de la función anónima onDraw es limitado DrawScope
, lo que también significa que se puede usar en funciones anónimas usar DrawScope
propiedades, métodos, etc. internamente dentro del alcance.
Veamos DrawScope
qué divino es esto:
Puede ver que esta es una interfaz, que define algunas variables de atributos (como dijimos anteriormente size
) y algunos métodos (como dijimos anteriormente drawRect
).
Luego implemente esta interfaz y escriba el código de implementación específico:
lograr
Entonces, en resumen, si queremos implementar nuestras propias restricciones de alcance, se puede dividir aproximadamente en tres pasos:
- Escritura de interfaces como ámbitos
- implementar esta interfaz
- En el método expuesto, el receptor de parámetros lambda utiliza la interfaz definida anteriormente
Tomemos un ejemplo.
Supongamos que queremos implementar una capa de guía de máscara en Compose para guiar a los nuevos usuarios a operar, algo como esto:
Fuente de la imagen Intro-showcase-view
Pero esperamos que las indicaciones en la capa de guía se puedan diversificar, por ejemplo, puede admitir indicaciones de texto, indicaciones de imagen e incluso reproducir indicaciones de video o animación, pero no queremos que estos elementos de indicación se llamen fuera de la capa de máscara. , debido a que dependen de algunos parámetros de la capa de máscara, provocarán errores si se llaman externamente.
En este momento, el uso de restricciones de alcance es muy apropiado.
Primero, escribimos una interfaz:
interface ShowcaseScreenScope {
val isShowOnce: Boolean
@Composable
fun ShowcaseTextItem()
}
En esta interfaz, definimos una variable de atributo isShowOnce
para indicar si la capa de guía solo se muestra una vez y definimos un método ShowcaseTextItem
para mostrar una cadena de texto en la capa de guía. De manera similar, también podemos definir para ShowcaseImageItem
indicar imágenes de visualización.
Luego implemente esta interfaz:
private class ShowcaseScopeImpl: ShowcaseScreenScope {
override val isShowOnce: Boolean
get() = TODO("在这里编写是否只显示一次的逻辑")
@Composable
override fun ShowcaseTextItem() {
// 在这里写你的实现代码
Text(text = "我是说明文字")
}
}
En la implementación de la interfaz, escriba el código lógico de implementación correspondiente según nuestras necesidades.
Finalmente, escriba un Composable que esté disponible para llamadas externas:
@Composable
fun ShowcaseScreen(content: @Composable ShowcaseScreenScope.() -> Unit) {
// 在这里实现其他逻辑(例如显示遮罩)后调用 content
// ……
ShowcaseScopeImpl().content()
}
En este componible, podemos procesar otra lógica primero, como mostrar la interfaz de usuario de la capa de máscara o mostrar animaciones, y luego llamar para ShowcaseScopeImpl().content()
combinar los subelementos que pasamos.
Finalmente, cuando use solo llame:
ShowcaseScreen {
if (!isShowOnce) {
ShowcaseTextItem()
}
}
Por supuesto, esto ShowcaseTextItem()
y isShowOnce
están en ShowcaseScreenScope
el alcance y no se pueden llamar fuera:
Resumir
Este artículo presenta brevemente el concepto de alcance en Kotlin y la función de alcance en la biblioteca estándar, y se extiende a la aplicación de alcance en Compose, finalmente analiza el principio de implementación y explica cómo personalizar una función de alcance de Compose propia.
La redacción de este artículo puede ser relativamente simple, y muchos puntos de conocimiento son precisos, sin demasiada explicación. Se recomienda que los lectores lean los artículos escritos por otros peces gordos en el enlace de referencia al final del artículo.