Si hay un requisito de la siguiente manera:
Después de nuestra observación, podemos obtener los siguientes puntos de demanda:
- Una lista que puede desplazarse infinitamente
- Diferentes tipos de artículos
- El artículo tiene forma de hexágono.
- El artículo tiene partes superpuestas horizontal y verticalmente
1. Utilice el desarrollo de View tradicional
Si usamos el desarrollo de View tradicional, es natural pensar que podemos usarlo RecyclerView
para lograrlo, pero hay dos puntos difíciles:
- Los artículos superpuestos causarán
RecyclerView
problemas con el mecanismo de reciclaje y los artículos no se pueden reciclar correctamente - Para el elemento hexagonal, no hay un componente nativo correspondiente que se pueda usar directamente
En primer lugar, para el primer punto, necesitamos personalizar RecyclerView
el uso de LayoutManager
:
Pero esto es realmente un poco complicado y difícil (para la mayoría de los desarrolladores comunes, ignórelo), porque necesitamos entender completamente LayoutManager
cómo funciona la estrategia de reciclaje antes de que podamos comenzar a modificar el código; de lo contrario, es posible crearlo nosotros mismos. un error en la rueda del coche, entonces la pérdida supera la ganancia.
En segundo lugar, para el elemento hexagonal, debemos 自定义View+Canvas绘制
implementar:
Pero hacerlo tendrá ciertas restricciones en la escalabilidad, porque usamos la herencia de FrameLayout para lograr, si hay otras necesidades en el futuro, es posible que necesitemos modificar constantemente esta vista personalizada.
Después de resolver los dos puntos complicados anteriores, finalmente podemos escribir código para resolver este requisito, pero aún necesitamos crear muchos archivos de preparación relacionados, como adaptador, archivos de diseño xml...
En resumen, si usa el desarrollo de View tradicional, para usarlo RecyclerView
para lograr este requisito, necesitamos escribir muchas cosas desordenadas:
2. Desarrolla con Jetpack Compose
Si usamos Jetpack Compose para desarrollar este requisito, será muy simple, el trabajo más grande que debemos hacer puede ser crear un hexágono Shape
para Modifier.clip()
el método a usar:
Necesitamos personalizar un hexágono HexagonShape
(la implementación se dará más adelante en este artículo), y luego podemos escribir el código comercial directamente. Podemos usar para LazyColumn
implementar la lista de desplazamiento. Para la función de superposición e intercalado de Item, recuerde que LazyColumn
hay un verticalArrangement
atributo? Podemos hacer esto Arrangement.spacedBy()
estableciendo un dp
valor negativo para esta propiedad:
LazyList
Para elementos de varios tipos, de hecho, la sintaxis DSL de los componentes de la serie en Jetpack Compose item
puede admitir naturalmente la configuración de elementos de varios tipos, por ejemplo:
Por supuesto, también podemos optar por items
mostrar la especificación directamente en esta función DSL contentType
para distinguir la visualización de diferentes componentes Composable:
LazyColumn(...) {
items(list, contentType = {
it.type }) {
item ->
if (item.type == 1) {
// 一种类型的Composable组件
......
} else {
// 另一种类型的Composable组件
......
}
}
}
Para obtener más información sobre LazyList
el uso de componentes de serie, consulte mi otro artículo: Listas en Jetpack Compose .
En resumen, si usa Jetpack Compose para desarrollar este requisito, en comparación con el desarrollo de View, el trabajo realizado es muy poco:
Usar Jetpack Compose para desarrollar también tiene las siguientes ventajas:
- En comparación con el desarrollo de View, se reduce una gran cantidad de código (se puede reducir al menos el 50% del código comercial)
- Complejidad de código reducida en comparación con el desarrollo de View
- Legibilidad y mantenibilidad muy mejoradas
3. Código fuente de implementación de Jetpack Compose
El siguiente es el código de ejemplo completo para lograr los requisitos anteriores:
@Composable
fun HexagonItemList() {
LazyColumn(
verticalArrangement = Arrangement.spacedBy((-100).dp),
contentPadding = PaddingValues(20.dp)
) {
items(4) {
Hexagon(it) }
item {
RecommendedText() }
items(50) {
Hexagon(it) }
}
}
@Composable
fun Hexagon(index: Int) {
val arrangement = if (index % 2 == 0) Arrangement.Start else Arrangement.End
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = arrangement,
) {
Box(
modifier = Modifier
.fillMaxWidth(0.55f)
.height(200.dp)
.clip(HexagonShape)
.background(OrangeColor),
contentAlignment = Alignment.Center
) {
Text(text = "Item $index", fontSize = 15.sp)
}
}
}
@Composable
fun RecommendedText() {
Column(
modifier = Modifier.height(300.dp).fillMaxWidth(),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
Text(text = "Recommended items", fontSize = 20.sp, fontWeight = FontWeight.Bold)
Text(text = "Based on your interests")
}
}
val OrangeColor = Color(255,197,2)
val HexagonShape = Polygon(6, 0f)
/**
* 根据边数sides创建指定多边形的Shape, 可以用于Modifier.clip()方法中
*/
class Polygon(private val sides: Int, private val rotation: Float = 0f) : Shape {
override fun createOutline(
size: Size,
layoutDirection: LayoutDirection,
density: Density
): Outline {
return Outline.Generic(
Path().apply {
val radius = if (size.width > size.height) size.width / 2f else size.height / 2f
val angle = 2.0 * Math.PI / sides
val cx = size.width / 2f
val cy = size.height / 2f
val r = rotation * (Math.PI / 180)
moveTo(
cx + (radius * cos(0.0 + r).toFloat()),
cy + (radius * sin(0.0 + r).toFloat())
)
for (i in 1 until sides) {
lineTo(
cx + (radius * cos(angle * i + r).toFloat()),
cy + (radius * sin(angle * i + r).toFloat())
)
}
close()
}
)
}
}
El efecto de ejecución real es el siguiente:
Los estudiantes cuidadosos pueden haber descubierto que los hexágonos en el diagrama esquemático de los requisitos mencionados al principio tienen esquinas redondeadas. Desafortunadamente, la herencia directa anterior para Shape
crear métodos personalizados no puede especificar las esquinas redondeadas de los polígonos. Sin embargo, hay más de una forma de crear polígonos en Compose. Por ejemplo, podemos elegir usarlo Modifier.drawBehind{ }
para Canvas
dibujar un hexágono. En este momento, podemos especificar las propiedades relacionadas con las esquinas redondeadas:
/**
* 绘制带圆角的六边形,关键点是设置绘制的style的pathEffect,通过PathEffect.cornerPathEffect指定圆角半径
*/
fun DrawScope.drawHexagon(color: Color, cornerRadius: Float = 0f, fill: Boolean = true) {
val canvasWidth = size.width
val canvasHeight = size.height
val cx = canvasWidth / 2
val cy = canvasHeight / 2
val radius = (canvasHeight - 20.dp.toPx()) / 2
val path = createPolygonPath(cx, cy, 6, radius)
if (fill) {
// 这种方式绘制可以填充整个多边形的内容区域
drawIntoCanvas {
canvas ->
canvas.drawOutline(
outline = Outline.Generic(path),
paint = Paint().apply {
this.color = color
pathEffect = PathEffect.cornerPathEffect(cornerRadius)
}
)
}
} else {
// 但是也可以通过两遍绘制来实现填充的效果
// drawPath(
// color = color,
// path = path,
// style = Fill
// )
// 这种方式只会绘制边框
drawPath(
color = color,
path = path,
style = Stroke(
width = 4.dp.toPx(),
pathEffect = PathEffect.cornerPathEffect(cornerRadius)
)
)
}
}
/**
* 根据边数sides创建多边形
*/
fun createPolygonPath(cx: Float, cy: Float, sides: Int, radius: Float): Path {
val angle = 2.0 * Math.PI / sides
return Path().apply {
moveTo(
cx + (radius * cos(0.0)).toFloat(),
cy + (radius * sin(0.0)).toFloat()
)
for (i in 1 until sides) {
lineTo(
cx + (radius * cos(angle * i)).toFloat(),
cy + (radius * sin(angle * i)).toFloat()
)
}
close()
}
}
Esto se drawHexagon
definirá como DrawScope
una función de extensión de , de modo que Canvas
se pueda llamar en cualquier lugar donde se pueda usar la API.
Luego puede Hexagon
aplicar esta función en el componente para dibujar el hexágono:
@Composable
fun Hexagon(index: Int) {
val arrangement = if (index % 2 == 0) Arrangement.Start else Arrangement.End
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = arrangement,
) {
Box(
modifier = Modifier
.fillMaxWidth(0.55f)
.height(200.dp)
.drawBehind {
drawHexagon(OrangeColor, 30f) },
contentAlignment = Alignment.Center
) {
Text(text = "Item $index", fontSize = 15.sp)
}
}
}
Por supuesto, también puede drawBehind { drawHexagon(OrangeColor, 30f) }
definir esta oración como una Modifier
función de extensión aquí.
El método de llamada es el mismo que antes, y la indirección se puede ajustar ligeramente:
@Composable
fun HexagonItemList() {
LazyColumn(
verticalArrangement = Arrangement.spacedBy((-115).dp),
contentPadding = PaddingValues(30.dp)
) {
items(4) {
Hexagon(it) }
item {
RecommendedText() }
items(50) {
Hexagon(it) }
}
}
Efecto de funcionamiento real:
Puedes ver que el hexágono ahora tiene esquinas redondeadas, tal como en la imagen al principio. Y la forma definida de esta manera se puede aplicar en casi cualquier componente Composable Modifier
, lo que es mucho más cómodo que escribir una Vista personalizada en la Vista tradicional.
Para obtener más información sobre el dibujo de Canvas, consulte mi artículo anterior: Canvas en Jetpack Compose .
Resumir
En comparación con el desarrollo de View tradicional, Jetpack Compose es realmente delicioso Puede cumplir con los requisitos comerciales más complejos con menos código, más legible y mejor mantenible. Si eres un veterano en el desarrollo de Android, creo que tendrás una comprensión más profunda de la diferencia entre los dos expresados en este artículo. Entonces, ¡es hora de cambiar la escopeta por un cañón! Envíe esto a los encargados de tomar decisiones sobre el desarrollo de su equipo.
Si está interesado en obtener más contenido sobre Jetpack Compose, consulte mi otro artículo sobre este aspecto: Resumen de aprendizaje de Jetpack Compose .
参考:Diseño divertido con diseños Lazy: Consejo de la comunidad - MAD Skills