Compose cung cấp nhiều đối tượng sửa đổi cho các hành vi phổ biến ngay từ đầu, 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:
- Nhà máy sửa đổi
- Đây là một hàm mở rộng trên
Modifier
, cung cấp một API tương thích cho đối tượng sửa đổi và cho phép các đối tượng sửa đổi dễ dàng liên kết với nhau. Chiến lược phát hành đĩa đơn nhà máy của đối tượng sửa đổi tạo ra các phần tử đối tượng sửa đổi mà Compose sử 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 đố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ỉ để triển khai một nhà máy của đối tượng sửa đổi tuỳ chỉnh,
kết hợp các tuỳ chọn khác đã được xác định
nhà máy sửa đổi với nhau. Nếu bạn cần thêm hành vi tuỳ chỉnh, hãy triển khai
phần tử đối tượng sửa đổi sử dụng API Modifier.Node
, ở cấp thấp hơn nhưng
giúp bạn linh hoạt hơn.
Liên kết các đối tượng sửa đổi hiện có với nhau
Thông thường, bạn có thể tạo đối tượng sửa đổi tuỳ chỉnh chỉ bằng cách sử dụng
đối tượng sửa đổi. Ví dụ: Modifier.clip()
được triển khai bằng cách sử dụ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 của đối tượng sửa đổi tuỳ chỉnh của riêng bạn.
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 chiến lược.
fun Modifier.clip(shape: Shape) = graphicsLayer(shape = shape, clip = true)
Hoặc nếu thấy mình đang lặp lại cùng một nhóm đối tượng sửa đổi thường xuyên, bạn có thể gói chúng vào đối tượng sửa đổi của riêng bạn:
fun Modifier.myBackground(color: Color) = padding(16.dp) .clip(RoundedCornerShape(8.dp)) .background(color)
Tạo một đối tượng sửa đổi tuỳ chỉnh bằng cách sử dụng factory đối tượng sửa đổi có thể kết hợp
Bạn cũng có thể tạo một đối tượng sửa đổi tuỳ chỉnh bằng cách sử dụng hàm có khả năng kết hợp để truyền các giá trị cho một đối tượng sửa đổi hiện có. Đây được gọi là nhà máy của đối tượng sửa đổi có thể kết hợp.
Việc sử dụng nhà máy của đối tượng sửa đổi có thể 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 Compose khác
có trạng thái hỗ trợ API ảnh động. Ví dụ: đoạn mã sau đây cho thấy
đối tượng sửa đổi tạo hiệu ứng thay đổi alpha khi 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 là một phương thức thuận tiện để cung cấp các giá trị mặc định từ
CompositionLocal
, cách dễ nhất để triển khai việc này là sử dụng thành phần kết hợp
nhà máy của đối tượng sửa đổi:
@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 ý đượ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 factory đối tượng sửa đổi
Khi tạo một đối tượng sửa đổi tuỳ chỉnh bằng cách sử dụng nhà máy của đối tượng sửa đổi có thể kết hợp, cấu trúc cục bộ sẽ lấy giá trị từ cây tổng hợp nơi chúng được tạo, chứ không phải đã 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 bố cục ví dụ về đối tượng sửa đổi cục bộ ở trên, được triển khai hơi khác bằng cách sử dụng 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 đây không phải là cách bạn mong đợi công cụ sửa đổi hoạt động, hãy sử dụng một
Modifier.Node
thay vào đó, vì thành phần kết hợp cục bộ sẽ
được giải quyết chính xác tại trang web sử dụng và có thể được di chuyển lên trên một cách an toàn.
Đối tượng sửa đổi hàm có khả năng kết hợp không bao giờ bị bỏ qua
Đối tượng sửa đổi nhà máy có thể 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ó trả về giá trị không thể bỏ qua. Tức là hàm sửa đổi của bạn sẽ được gọi trong mọi quá trình tái cấu trúc, điều này có thể gây tốn kém nếu kết hợp lại thường xuyên.
Đối tượng sửa đổi hàm có khả năng kết hợp phải được gọi trong một hàm có khả năng kết hợp
Giống như tất cả hàm có khả năng kết hợp, đối tượng sửa đổi factory có thể kết hợp phải được gọi từ trong cấu trúc. Điều này giới hạn vị trí mà một đối tượng sửa đổi có thể được di chuyển lên trên, vì nó có thể không bao giờ bị di chuyển ra khỏi cấu trúc. Khi so sánh, đối tượng sửa đổi không phải thành phần kết hợp nhà máy có thể được di chuyển ra khỏi các hàm có khả năng kết hợp để cho phép sử dụng lại và sử dụng lại dễ dàng hơn 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à API cấp thấp hơn để tạo đối tượng sửa đổi trong Compose. Nó
cũng là API mà Compose triển khai các đối tượng sửa đổi riêng và cũng là
hiệu quả nhất để tạo đối tượng sửa đổi tuỳ chỉnh.
Triển khai một đối tượng sửa đổi tuỳ chỉnh bằng Modifier.Node
Có 3 phần để triển khai đối tượng sửa đổi tuỳ chỉnh bằng Protected.Node:
- Phương thức triển khai
Modifier.Node
chứa logic và trạng thái của đối tượng sửa đổi. ModifierNodeElement
tạo và cập nhật đối tượng sửa đổi thực thể nút.- Nhà máy đối tượng sửa đổi không bắt buộc như đã nêu 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ớp
kết hợp lại, trong khi lớp Modifier.Node
có thể có trạng thái và sẽ tồn tại
trên nhiều lần tái cấu trúc và thậm chí có thể được sử dụng lại.
Phần sau đây mô tả từng phần và đưa ra ví dụ về cách tạo đối tượng sửa đổi tuỳ chỉnh để vẽ một vòng tròn.
Modifier.Node
Hoạt động triển khai Modifier.Node
(trong ví dụ này là CircleNode
) sẽ triển khai
thời gian
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, nó vẽ vòng tròn có màu được truyền vào đối tượng sửa đổi .
Một nút triển khai Modifier.Node
cũng như không hoặc nhiều loại nút khác. Có
các loại nút khác nhau dựa trên chức năng mà đối tượng sửa đổi yêu cầu. Chiến lược phát hành đĩa đơn
ví dụ ở trên cần vẽ được nên sẽ triển khai DrawModifierNode
,
cho phép thao tác ghi đè phương thức vẽ.
Có các loại sau:
Nút |
Tác dụng |
Đường liên kết mẫu |
|
||
|
||
Việc triển khai giao diện này cho phép |
||
|
||
Một |
||
|
||
|
||
Một |
||
Các |
||
Điều này có thể hữu ích khi kết hợp nhiều phương thức 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 cập nhật được gọi trên các nút tương ứng
. Vì ví dụ của chúng ta là DrawModifierNode
, nên bất kỳ cập nhật nào được gọi
phần tử, nút này kích hoạt vẽ lại và màu của nó cập nhật chính xác. Đó là
bạn có thể chọn không tự động vô hiệu hoá như trình bày chi tiết bên dưới.
ModifierNodeElement
ModifierNodeElement
là một lớp bất biến, chứa 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 } }
Quá trình triển khai ModifierNodeElement
cần ghi đè các phương thức sau:
create
: Đây là hàm tạo thực thể cho nút đối tượng sửa đổi. Điều này giúp được gọi để tạo nút khi đối tượng sửa đổi được áp dụng lần đầu tiên. Thông thường, số tiền để xây dựng nút và định cấu hình nút bằng các tham số được truyền vào factory 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 trong cùng một điểm mà nút này đã tồn tại nhưng thuộc tính đã thay đổi. Đây là được xác định bằng phương thứcequals
của lớp. Nút đối tượng sửa đổi tạo trước đó sẽ được gửi dưới dạng 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 nút để tương ứng với thuộc tính đã cập nhật tham số. Khả năng sử dụng lại các nút theo cách này là chìa khoá để mức tăng hiệu suất màModifier.Node
mang lại; 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 được cập nhật.
Ngoài ra, việ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ề 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 đóng góp vào việc liệu nút có cần được cập nhật hay không hoặc bạn muốn tránh dữ liệu
vì lý do tương thích với tệp nhị phân, thì bạn có thể triển khai equals
theo cách thủ công
và hashCode
, ví dụ: phần tử đối tượng sửa đổi khoảng đệm.
Nhà máy đối tượng 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 triển khai đều đơn giản tạo phần tử đối tượng sửa đổi rồi 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 ra đối tượng sửa đổi tuỳ chỉnh để vẽ một vòng tròn
bằ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 thường gặp khi sử dụng Modifier.Node
Khi tạo đối tượng sửa đổi tuỳ chỉnh bằng Modifier.Node
, sau đây là một số trường hợp phổ biến mà bạn có thể
gặp phải.
Không có tham số
Nếu đối tượng sửa đổi không có tham số, thì đối tượng sửa đổi không bao giờ cần cập nhật và hơn nữa, không cần phải là lớp dữ liệu. Sau đây là một ví dụ về cách triển khai của một đối tượng sửa đổi áp dụng 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ộ
Đối tượng sửa đổi Modifier.Node
không tự động quan sát các thay đổi đối với trạng thái Compose
chẳng hạn như CompositionLocal
. Ưu điểm của đối tượng sửa đổi Modifier.Node
có hơn
đối tượng sửa đổi vừa được tạo bằng nhà máy thành phần kết hợp là chúng có thể đọc
giá trị của cấu trúc cục bộ từ nơi đối tượng sửa đổi được dùng trong giao diện người dùng
cây mà 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 của đối tượng sửa đổi không tự động quan sát các thay đổi về trạng thái. Người nhận tự động phản ứng với thay đổi cục bộ của thành phần, bạn có thể đọc giá trị bên trong một phạm vi:
DrawModifierNode
:ContentDrawScope
LayoutModifierNode
:MeasureScope
vàIntrinsicMeasureScope
SemanticsModifierNode
:SemanticsPropertyReceiver
Ví dụ này ghi nhận giá trị của LocalContentColor
để vẽ nền dựa trên
trên màu của nó. Khi ContentDrawScope
quan sát các thay đổi của bản tổng quan nhanh,
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 những thay đổi về trạng thái nằm ngoài 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 để
quan sát 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
Quá trình triển khai Modifier.Node
có quyền truy cập vào coroutineScope
. Điều này cho phép
việc sử dụng API Animatable của Compose. Ví dụ: đoạn mã này sửa đổi
CircleNode
từ phía trên để hiện dần và rõ dần:
class CircleNode(var color: Color) : Modifier.Node(), DrawModifierNode { private val alpha = Animatable(1f) override fun ContentDrawScope.draw() { drawCircle(color = color, alpha = alpha.value) drawContent() } override fun onAttach() { coroutineScope.launch { alpha.animateTo( 0f, infiniteRepeatable(tween(1000), RepeatMode.Reverse) ) { } } } }
Trạng thái chia sẻ giữa các đối tượng sửa đổi bằng tính năng uỷ quyền
Đố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 cách triển khai phổ biến trên các đối tượng sửa đổi khác nhau,
nhưng cũng có thể dùng để chia sẻ trạng thái chung giữa các đối tượng sửa đổi.
Ví dụ: cách triển khai cơ bản một nút đối tượng sửa đổi có thể nhấp để chia sẻ dữ liệu về lượt tương tác:
class ClickableNode : DelegatingNode() { val interactionData = InteractionData() val focusableNode = delegate( FocusableNode(interactionData) ) val indicationNode = delegate( IndicationNode(interactionData) ) }
Chọn không tự động vô hiệu hoá nút
Các nút Modifier.Node
tự động vô hiệu hoá khi các nút tương ứng
Cập nhật về cuộc gọi ModifierNodeElement
. Đôi khi, trong một công cụ sửa đổi phức tạp hơn, bạn có thể
muốn chọn không tham gia hành vi này để kiểm soát chi tiết hơn thời điểm
hệ số sửa đổi của bạn sẽ 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 sửa đổi cả bố cục và
vẽ. Việc chọn không tự động vô hiệu hoá cho phép bạn chỉ vô hiệu hoá bản vẽ khi
chỉ các thuộc tính liên quan đến bản vẽ như color
, thay đổi chứ không vô hiệu hoá bố cục.
Điều này có thể cải thiện hiệu suất của đối tượng sửa đổi.
Một ví dụ giả định về điều này được minh hoạ bên dưới với một đối tượng sửa đổi có color
,
Hàm lambda size
và onClick
làm thuộc tính. Đối tượng sửa đổi này chỉ làm mất hiệu lực
là bắt buộc và bỏ qua mọi trường hợp vô hiệu hoá không:
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) } } }