Compose cung cấp nhiều đối tượng sửa đổi cho các hành vi phổ biến ngay khi bạn bắt đầu sử dụng, nhưng bạn cũng có thể tạo đối tượng sửa đổi tuỳ chỉnh của riêng mình.
Đối tượng sửa đổi có nhiều phần:
- Một nhà máy sửa đổi
- Đây là một hàm mở rộng trên
Modifier
, cung cấp một API thành ngữ cho đối tượng sửa đổi của bạn và cho phép các đối tượng sửa đổi dễ dàng được liên kết với nhau. Nhà máy đối tượng sửa đổi tạo ra các phần tử đối tượng sửa đổi mà Compose dùng để sửa đổi giao diện người dùng.
- Đây là một hàm mở rộng trên
- Một phần tử đối tượng sửa đổi
- Đây là nơi bạn có thể triển khai hành vi của đối tượng sửa đổi.
Có nhiều cách để triển khai một đối tượng sửa đổi tuỳ chỉnh, tuỳ thuộc vào chức năng cần thiết. Thông thường, cách dễ nhất để triển khai một đối tượng sửa đổi tuỳ chỉnh là chỉ cần triển khai một nhà máy đối tượng sửa đổi tuỳ chỉnh kết hợp các nhà máy đối tượng sửa đổi khác đã được xác định với nhau. Nếu cần có hành vi tuỳ chỉnh hơn, hãy triển khai phần tử sửa đổi bằng cách sử dụng các API Modifier.Node
. Các API này ở cấp thấp hơn nhưng mang lại nhiều sự linh hoạt hơn.
Nối các đối tượng sửa đổi hiện có với nhau
Bạn thường có thể tạo các đối tượng sửa đổi tuỳ chỉnh chỉ bằng cách sử dụng các đối tượng sửa đổi hiện có. Ví dụ: Modifier.clip()
được triển khai bằng đối tượng sửa đổi graphicsLayer
. Chiến lược này sử dụng các phần tử đối tượng sửa đổi hiện có và bạn cung cấp nhà máy đối tượng sửa đổi tuỳ chỉnh của riêng mình.
Trước khi triển khai đối tượng sửa đổi tuỳ chỉnh của riêng bạn, hãy xem liệu bạn có thể sử dụng cùng một chiến lược hay không.
fun Modifier.clip(shape: Shape) = graphicsLayer(shape = shape, clip = true)
Hoặc nếu thấy mình thường xuyên lặp lại cùng một nhóm đối tượng sửa đổi, bạn có thể bao bọc các đối tượng đó vào đối tượng sửa đổi của riêng mình:
fun Modifier.myBackground(color: Color) = padding(16.dp) .clip(RoundedCornerShape(8.dp)) .background(color)
Tạo đối tượng sửa đổi tuỳ chỉnh bằng một nhà máy đối tượng sửa đổi có thể kết hợp
Bạn cũng có thể tạo đối tượng sửa đổi tuỳ chỉnh bằng cách sử dụng một hàm có khả năng kết hợp để truyền các giá trị đến một đối tượng sửa đổi hiện có. Đây được gọi là đối tượng sửa đổi có thể kết hợp.
Việc sử dụng một nhà máy sửa đổi thành phần kết hợp để tạo một đối tượng sửa đổi cũng cho phép sử dụng các API Compose cấp cao hơn, chẳng hạn như animate*AsState
và các API ảnh động dựa trên trạng thái Compose khác. Ví dụ: đoạn mã sau đây cho thấy một đối tượng sửa đổi tạo ảnh động cho thay đổi về độ trong suốt khi được bật/tắt:
@Composable fun Modifier.fade(enable: Boolean): Modifier { val alpha by animateFloatAsState(if (enable) 0.5f else 1.0f) return this then Modifier.graphicsLayer { this.alpha = alpha } }
Nếu đối tượng sửa đổi tuỳ chỉnh của bạn là một phương thức tiện lợi để cung cấp các giá trị mặc định từ một CompositionLocal
, thì cách dễ nhất để triển khai phương thức này là sử dụng một nhà máy đối tượng sửa đổi có thể kết hợp:
@Composable fun Modifier.fadedBackground(): Modifier { val color = LocalContentColor.current return this then Modifier.background(color.copy(alpha = 0.5f)) }
Phương pháp này có một số điểm cần lưu ý như được nêu chi tiết bên dưới.
Các giá trị CompositionLocal
được phân giải tại vị trí gọi của nhà máy đối tượng sửa đổi
Khi bạn tạo một đối tượng sửa đổi tuỳ chỉnh bằng cách sử dụng một nhà máy đối tượng sửa đổi có thể kết hợp, các thành phần cục bộ sẽ lấy giá trị từ cây thành phần nơi chúng được tạo, chứ không phải nơi chúng được sử dụng. Điều này có thể dẫn đến kết quả không mong muốn. Ví dụ: hãy lấy ví dụ về đối tượng sửa đổi cục bộ thành phần ở trên, được triển khai hơi khác một chút bằng cách sử dụng một hàm có khả năng kết hợp:
@Composable fun Modifier.myBackground(): Modifier { val color = LocalContentColor.current return this then Modifier.background(color.copy(alpha = 0.5f)) } @Composable fun MyScreen() { CompositionLocalProvider(LocalContentColor provides Color.Green) { // Background modifier created with green background val backgroundModifier = Modifier.myBackground() // LocalContentColor updated to red CompositionLocalProvider(LocalContentColor provides Color.Red) { // Box will have green background, not red as expected. Box(modifier = backgroundModifier) } } }
Nếu bạn không muốn công cụ sửa đổi hoạt động theo cách này, hãy sử dụng Modifier.Node
tuỳ chỉnh thay thế, vì các thành phần kết hợp cục bộ sẽ được phân giải chính xác tại vị trí sử dụng và có thể được nâng lên một cách an toàn.
Các đối tượng sửa đổi hàm có khả năng kết hợp không bao giờ bị bỏ qua
Các đối tượng sửa đổi của nhà máy có khả năng kết hợp không bao giờ bị bỏ qua vì các hàm có khả năng kết hợp có giá trị trả về không thể bị bỏ qua. Điều này có nghĩa là hàm đối tượng sửa đổi của bạn sẽ được gọi trong mọi lần kết hợp lại, điều này có thể tốn kém nếu hàm này kết hợp lại thường xuyên.
Bạn phải gọi đối tượng sửa đổi hàm có khả năng kết hợp trong một hàm có khả năng kết hợp
Giống như mọi hàm có khả năng kết hợp, đối tượng sửa đổi của nhà máy có khả năng kết hợp phải được gọi từ trong thành phần. Điều này giới hạn vị trí mà một đối tượng sửa đổi có thể được chuyển đến, vì đối tượng sửa đổi đó không bao giờ được chuyển ra khỏi thành phần. Ngược lại, các nhà máy đối tượng sửa đổi không có khả năng kết hợp có thể được chuyển ra ngoài các hàm có khả năng kết hợp để cho phép tái sử dụng dễ dàng hơn và cải thiện hiệu suất:
val extractedModifier = Modifier.background(Color.Red) // Hoisted to save allocations @Composable fun Modifier.composableModifier(): Modifier { val color = LocalContentColor.current.copy(alpha = 0.5f) return this then Modifier.background(color) } @Composable fun MyComposable() { val composedModifier = Modifier.composableModifier() // Cannot be extracted any higher }
Triển khai hành vi của đối tượng sửa đổi tuỳ chỉnh bằng cách sử dụng Modifier.Node
Modifier.Node
là một API cấp thấp để tạo các đối tượng sửa đổi trong Compose. Đây là API mà Compose triển khai các đối tượng sửa đổi của riêng mình và là cách hiệu quả nhất để tạo đối tượng sửa đổi tuỳ chỉnh.
Triển khai đối tượng sửa đổi tuỳ chỉnh bằng cách sử dụng Modifier.Node
Có 3 phần để triển khai đối tượng sửa đổi tuỳ chỉnh bằng Modifier.Node:
- Một quy trình triển khai
Modifier.Node
chứa logic và trạng thái của đối tượng sửa đổi. - Một
ModifierNodeElement
tạo và cập nhật các thực thể nút sửa đổi. - Một factory công cụ sửa đổi không bắt buộc như được trình bày chi tiết ở trên.
Các lớp ModifierNodeElement
không có trạng thái và các thực thể mới được phân bổ cho mỗi lần kết hợp lại, trong khi các lớp Modifier.Node
có thể có trạng thái và sẽ tồn tại qua nhiều lần kết hợp lại, thậm chí có thể được dùng lại.
Phần sau đây mô tả từng phần và cho thấy ví dụ về cách tạo một đối tượng sửa đổi tuỳ chỉnh để vẽ một hình tròn.
Modifier.Node
Việc triển khai Modifier.Node
(trong ví dụ này là CircleNode
) sẽ triển khai chức năng của đối tượng sửa đổi tuỳ chỉnh.
// Modifier.Node private class CircleNode(var color: Color) : DrawModifierNode, Modifier.Node() { override fun ContentDrawScope.draw() { drawCircle(color) } }
Trong ví dụ này, hàm sửa đổi sẽ vẽ vòng tròn bằng màu được truyền vào.
Một nút triển khai Modifier.Node
cũng như không có hoặc có nhiều loại nút. Có nhiều loại nút dựa trên chức năng mà đối tượng sửa đổi của bạn yêu cầu. Ví dụ trên cần có khả năng vẽ, vì vậy, ví dụ này triển khai DrawModifierNode
, cho phép ghi đè phương thức vẽ.
Sau đây là các loại có sẵn:
Node |
Tác dụng |
Đường liên kết đến mẫu |
Một |
||
|
||
Việc triển khai giao diện này cho phép |
||
Một |
||
Một |
||
Một |
||
Một |
||
Một |
||
|
||
Một Điều này có thể hữu ích khi kết hợp nhiều cách triển khai nút thành một. |
||
Cho phép các lớp |
Các nút sẽ tự động bị vô hiệu hoá khi bạn gọi phương thức cập nhật trên phần tử tương ứng của chúng. Vì ví dụ của chúng ta là một DrawModifierNode
, nên bất cứ khi nào lệnh cập nhật được gọi trên phần tử, nút sẽ kích hoạt một lần vẽ lại và màu sắc của nút sẽ cập nhật chính xác. Bạn có thể chọn không sử dụng tính năng tự động vô hiệu hoá như mô tả chi tiết bên dưới.
ModifierNodeElement
ModifierNodeElement
là một lớp bất biến lưu giữ dữ liệu để tạo hoặc cập nhật đối tượng sửa đổi tuỳ chỉnh:
// ModifierNodeElement private data class CircleElement(val color: Color) : ModifierNodeElement<CircleNode>() { override fun create() = CircleNode(color) override fun update(node: CircleNode) { node.color = color } }
Các hoạt động triển khai ModifierNodeElement
cần ghi đè các phương thức sau:
create
: Đây là hàm khởi tạo nút đối tượng sửa đổi của bạn. Phương thức này được gọi để tạo nút khi công cụ sửa đổi của bạn được áp dụng lần đầu tiên. Thông thường, thao tác này tương đương với việc tạo nút và định cấu hình nút đó bằng các tham số đã được truyền vào nhà máy đối tượng sửa đổi.update
: Hàm này được gọi bất cứ khi nào đối tượng sửa đổi này được cung cấp ở cùng một vị trí mà nút này đã tồn tại, nhưng một thuộc tính đã thay đổi. Điều này được xác định bằng phương thứcequals
của lớp. Nút đối tượng sửa đổi đã được tạo trước đó sẽ được gửi dưới dạng một tham số đến lệnh gọiupdate
. Tại thời điểm này, bạn nên cập nhật các thuộc tính của nút để tương ứng với các tham số đã cập nhật. Khả năng các nút được dùng lại theo cách này là yếu tố then chốt đểModifier.Node
mang lại hiệu suất cao hơn; do đó, bạn phải cập nhật nút hiện có thay vì tạo một nút mới trong phương thứcupdate
. Trong ví dụ về vòng tròn, màu của nút sẽ được cập nhật.
Ngoài ra, các phương thức triển khai ModifierNodeElement
cũng cần triển khai equals
và hashCode
. update
sẽ chỉ được gọi nếu phép so sánh bằng với phần tử trước đó trả về giá trị false.
Ví dụ trên sử dụng một lớp dữ liệu để đạt được điều này. Các phương thức này được dùng để kiểm tra xem một nút có cần cập nhật hay không. Nếu phần tử của bạn có các thuộc tính không góp phần vào việc có cần cập nhật một nút hay không, hoặc bạn muốn tránh các lớp dữ liệu vì lý do tương thích nhị phân, thì bạn có thể triển khai equals
và hashCode
theo cách thủ công, ví dụ: phần tử sửa đổi khoảng đệm.
Nhà máy sửa đổi
Đây là nền tảng API công khai của đối tượng sửa đổi. Hầu hết các hoạt động triển khai chỉ cần tạo phần tử đối tượng sửa đổi và thêm phần tử đó vào chuỗi đối tượng sửa đổi:
// Modifier factory fun Modifier.circle(color: Color) = this then CircleElement(color)
Ví dụ đầy đủ
Ba phần này kết hợp với nhau để tạo đối tượng sửa đổi tuỳ chỉnh nhằm vẽ một hình tròn bằng cách sử dụng các API Modifier.Node
:
// Modifier factory fun Modifier.circle(color: Color) = this then CircleElement(color) // ModifierNodeElement private data class CircleElement(val color: Color) : ModifierNodeElement<CircleNode>() { override fun create() = CircleNode(color) override fun update(node: CircleNode) { node.color = color } } // Modifier.Node private class CircleNode(var color: Color) : DrawModifierNode, Modifier.Node() { override fun ContentDrawScope.draw() { drawCircle(color) } }
Các trường hợp phổ biến khi sử dụng Modifier.Node
Khi tạo đối tượng sửa đổi tuỳ chỉnh bằng Modifier.Node
, bạn có thể gặp phải một số trường hợp phổ biến sau.
Không có tham số
Nếu đối tượng sửa đổi của bạn không có tham số, thì đối tượng đó không bao giờ cần cập nhật và hơn nữa, không cần phải là một lớp dữ liệu. Dưới đây là ví dụ về cách triển khai một đối tượng sửa đổi áp dụng một lượng khoảng đệm cố định cho một thành phần kết hợp:
fun Modifier.fixedPadding() = this then FixedPaddingElement data object FixedPaddingElement : ModifierNodeElement<FixedPaddingNode>() { override fun create() = FixedPaddingNode() override fun update(node: FixedPaddingNode) {} } class FixedPaddingNode : LayoutModifierNode, Modifier.Node() { private val PADDING = 16.dp override fun MeasureScope.measure( measurable: Measurable, constraints: Constraints ): MeasureResult { val paddingPx = PADDING.roundToPx() val horizontal = paddingPx * 2 val vertical = paddingPx * 2 val placeable = measurable.measure(constraints.offset(-horizontal, -vertical)) val width = constraints.constrainWidth(placeable.width + horizontal) val height = constraints.constrainHeight(placeable.height + vertical) return layout(width, height) { placeable.place(paddingPx, paddingPx) } } }
Tham chiếu thành phần kết hợp cục bộ
Các đối tượng sửa đổi Modifier.Node
không tự động quan sát các thay đổi đối với các đối tượng trạng thái Compose, chẳng hạn như CompositionLocal
. Lợi thế của đối tượng sửa đổi Modifier.Node
so với các đối tượng sửa đổi chỉ được tạo bằng một nhà máy thành phần kết hợp là chúng có thể đọc giá trị của thành phần cục bộ từ nơi đối tượng sửa đổi được dùng trong cây giao diện người dùng, chứ không phải nơi đối tượng sửa đổi được phân bổ, bằng cách sử dụng currentValueOf
.
Tuy nhiên, các thực thể nút sửa đổi không tự động theo dõi các thay đổi về trạng thái. Để tự động phản ứng với một thành phần cục bộ đang thay đổi, bạn có thể đọc giá trị hiện tại của thành phần đó trong một phạm vi:
DrawModifierNode
:ContentDrawScope
LayoutModifierNode
:MeasureScope
&IntrinsicMeasureScope
SemanticsModifierNode
:SemanticsPropertyReceiver
Ví dụ này quan sát giá trị của LocalContentColor
để vẽ một nền dựa trên màu sắc của giá trị đó. Vì ContentDrawScope
quan sát các thay đổi về ảnh chụp nhanh, nên thao tác này sẽ tự động vẽ lại khi giá trị của LocalContentColor
thay đổi:
class BackgroundColorConsumerNode : Modifier.Node(), DrawModifierNode, CompositionLocalConsumerModifierNode { override fun ContentDrawScope.draw() { val currentColor = currentValueOf(LocalContentColor) drawRect(color = currentColor) drawContent() } }
Để phản ứng với các thay đổi về trạng thái bên ngoài một phạm vi và tự động cập nhật đối tượng sửa đổi, hãy sử dụng ObserverModifierNode
.
Ví dụ: Modifier.scrollable
sử dụng kỹ thuật này để theo dõi các thay đổi trong LocalDensity
. Dưới đây là một ví dụ đơn giản:
class ScrollableNode : Modifier.Node(), ObserverModifierNode, CompositionLocalConsumerModifierNode { // Place holder fling behavior, we'll initialize it when the density is available. val defaultFlingBehavior = DefaultFlingBehavior(splineBasedDecay(UnityDensity)) override fun onAttach() { updateDefaultFlingBehavior() observeReads { currentValueOf(LocalDensity) } // monitor change in Density } override fun onObservedReadsChanged() { // if density changes, update the default fling behavior. updateDefaultFlingBehavior() } private fun updateDefaultFlingBehavior() { val density = currentValueOf(LocalDensity) defaultFlingBehavior.flingDecay = splineBasedDecay(density) } }
Đối tượng sửa đổi ảnh động
Các phương thức triển khai Modifier.Node
có quyền truy cập vào một coroutineScope
. Điều này cho phép sử dụng API có thể tạo ảnh động trong Compose. Ví dụ: đoạn mã này sửa đổi CircleNode
ở trên để liên tục mờ dần và hiện dần:
class CircleNode(var color: Color) : Modifier.Node(), DrawModifierNode { private lateinit var alpha: Animatable<Float, AnimationVector1D> override fun ContentDrawScope.draw() { drawCircle(color = color, alpha = alpha.value) drawContent() } override fun onAttach() { alpha = Animatable(1f) coroutineScope.launch { alpha.animateTo( 0f, infiniteRepeatable(tween(1000), RepeatMode.Reverse) ) { } } } }
Chia sẻ trạng thái giữa các đối tượng sửa đổi bằng cách sử dụng uỷ quyền
Các đối tượng sửa đổi Modifier.Node
có thể uỷ quyền cho các nút khác. Có nhiều trường hợp sử dụng cho việc này, chẳng hạn như trích xuất các phương thức triển khai phổ biến trên nhiều đối tượng sửa đổi, nhưng bạn cũng có thể dùng để chia sẻ trạng thái chung trên các đối tượng sửa đổi.
Ví dụ: một cách triển khai cơ bản của nút công cụ sửa đổi có thể nhấp chia sẻ dữ liệu tương tác:
class ClickableNode : DelegatingNode() { val interactionData = InteractionData() val focusableNode = delegate( FocusableNode(interactionData) ) val indicationNode = delegate( IndicationNode(interactionData) ) }
Chọn không sử dụng tính năng tự động vô hiệu hoá nút
Các nút Modifier.Node
sẽ tự động không hợp lệ khi các lệnh gọi ModifierNodeElement
tương ứng cập nhật. Đôi khi, trong một đối tượng sửa đổi phức tạp hơn, bạn có thể muốn chọn không sử dụng hành vi này để kiểm soát chi tiết hơn thời điểm đối tượng sửa đổi của bạn làm mất hiệu lực các giai đoạn.
Điều này có thể đặc biệt hữu ích nếu đối tượng sửa đổi tuỳ chỉnh của bạn sửa đổi cả bố cục và bản vẽ. Khi chọn không tự động làm mới, bạn chỉ cần làm mới khi chỉ các thuộc tính liên quan đến thao tác vẽ (chẳng hạn như color
) thay đổi và không làm mới bố cục.
Điều này có thể cải thiện hiệu suất của hệ số điều chỉnh.
Dưới đây là một ví dụ giả định về trường hợp này với một đối tượng sửa đổi có hàm lambda color
, size
và onClick
làm thuộc tính. Đối tượng sửa đổi này chỉ vô hiệu hoá những gì cần thiết và bỏ qua mọi hoạt động vô hiệu hoá không cần thiết:
class SampleInvalidatingNode( var color: Color, var size: IntSize, var onClick: () -> Unit ) : DelegatingNode(), LayoutModifierNode, DrawModifierNode { override val shouldAutoInvalidate: Boolean get() = false private val clickableNode = delegate( ClickablePointerInputNode(onClick) ) fun update(color: Color, size: IntSize, onClick: () -> Unit) { if (this.color != color) { this.color = color // Only invalidate draw when color changes invalidateDraw() } if (this.size != size) { this.size = size // Only invalidate layout when size changes invalidateMeasurement() } // If only onClick changes, we don't need to invalidate anything clickableNode.update(onClick) } override fun ContentDrawScope.draw() { drawRect(color) } override fun MeasureScope.measure( measurable: Measurable, constraints: Constraints ): MeasureResult { val size = constraints.constrain(size) val placeable = measurable.measure(constraints) return layout(size.width, size.height) { placeable.place(0, 0) } } }