Để cải thiện hiệu suất kết hợp của các thành phần tương tác sử dụng Modifier.clickable
, chúng tôi đã ra mắt các API mới. Các API này cho phép triển khai Indication
hiệu quả hơn, chẳng hạn như hiệu ứng gợn sóng.
androidx.compose.foundation:foundation:1.7.0+
và androidx.compose.material:material-ripple:1.7.0+
có những thay đổi sau đây về API:
Không dùng nữa |
Thay thế |
---|---|
|
|
|
Thay vào đó, các API Lưu ý: Trong ngữ cảnh này, "Thư viện Material" đề cập đến |
|
Either:
|
Trang này mô tả tác động của việc thay đổi hành vi và hướng dẫn di chuyển sang API mới.
Thay đổi về hành vi
Các phiên bản thư viện sau đây có sự thay đổi về hành vi gợn sóng:
androidx.compose.material:material:1.7.0+
androidx.compose.material3:material3:1.3.0+
androidx.wear.compose:compose-material:1.4.0+
Các phiên bản thư viện Material này không còn sử dụng rememberRipple()
; thay vào đó, chúng sử dụng các API gợn sóng mới. Do đó, chúng không truy vấn LocalRippleTheme
.
Do đó, nếu bạn thiết lập LocalRippleTheme
trong ứng dụng, các thành phần Material sẽ không sử dụng các giá trị này.
Phần sau đây mô tả cách tạm thời quay lại hành vi cũ mà không di chuyển; tuy nhiên, bạn nên chuyển sang các API mới. Để biết hướng dẫn di chuyển, hãy xem phần Di chuyển từ rememberRipple
sang ripple
và các phần tiếp theo.
Nâng cấp phiên bản thư viện Material mà không cần di chuyển
Để bỏ chặn việc nâng cấp các phiên bản thư viện, bạn có thể sử dụng API LocalUseFallbackRippleImplementation CompositionLocal
tạm thời để định cấu hình các thành phần Material nhằm quay lại hành vi cũ:
CompositionLocalProvider(LocalUseFallbackRippleImplementation provides true) { MaterialTheme { App() } }
Hãy nhớ cung cấp phương thức này bên ngoài MaterialTheme
để có thể cung cấp các hiệu ứng gợn sóng cũ thông qua LocalIndication
.
Các phần sau đây mô tả cách chuyển sang API mới.
Di chuyển từ rememberRipple
sang ripple
Sử dụng thư viện Material
Nếu bạn đang sử dụng thư viện Material, hãy trực tiếp thay thế rememberRipple()
bằng lệnh gọi đến ripple()
từ thư viện tương ứng. API này tạo hiệu ứng gợn sóng bằng cách sử dụng các giá trị bắt nguồn từ API giao diện Material. Sau đó, hãy truyền đối tượng được trả về đến Modifier.clickable
và/hoặc các thành phần khác.
Ví dụ: đoạn mã sau đây sử dụng các API không dùng nữa:
Box( Modifier.clickable( onClick = {}, interactionSource = remember { MutableInteractionSource() }, indication = rememberRipple() ) ) { // ... }
Bạn nên sửa đổi đoạn mã trên để:
@Composable private fun RippleExample() { Box( Modifier.clickable( onClick = {}, interactionSource = remember { MutableInteractionSource() }, indication = ripple() ) ) { // ... } }
Xin lưu ý rằng ripple()
không còn là một hàm có khả năng kết hợp và không cần được ghi nhớ. Bạn cũng có thể sử dụng lại phương thức này trên nhiều thành phần, tương tự như đối tượng sửa đổi. Vì vậy, hãy cân nhắc trích xuất hoạt động tạo hiệu ứng gợn sóng cho một giá trị cấp cao nhất để lưu mức phân bổ.
Triển khai hệ thống thiết kế tuỳ chỉnh
Nếu đang triển khai hệ thống thiết kế của riêng mình và trước đó đã dùng rememberRipple()
cùng với RippleTheme
tuỳ chỉnh để định cấu hình hiệu ứng gợn sóng, thì bạn nên cung cấp API gợn sóng của riêng mình để uỷ quyền các API nút gợn sóng hiển thị trong material-ripple
. Sau đó, các thành phần có thể sử dụng hiệu ứng gợn sóng của riêng bạn để sử dụng trực tiếp các giá trị giao diện. Để biết thêm thông tin, hãy xem phần Di chuyển khỏiRippleTheme
.
Di chuyển từ RippleTheme
Tạm thời chọn không áp dụng thay đổi về hành vi
Thư viện Material có một CompositionLocal
tạm thời là LocalUseFallbackRippleImplementation
mà bạn có thể sử dụng để định cấu hình tất cả thành phần Material nhằm quay lại sử dụng rememberRipple
. Bằng cách này, rememberRipple
sẽ tiếp tục truy vấn LocalRippleTheme
.
Đoạn mã sau đây minh hoạ cách sử dụng API LocalUseFallbackRippleImplementation CompositionLocal
:
CompositionLocalProvider(LocalUseFallbackRippleImplementation provides true) { MaterialTheme { App() } }
Nếu đang sử dụng một giao diện ứng dụng tuỳ chỉnh được xây dựng dựa trên Material, thì bạn có thể cung cấp cấu trúc cục bộ một cách an toàn dưới dạng một phần giao diện của ứng dụng:
@OptIn(ExperimentalMaterialApi::class) @Composable fun MyAppTheme(content: @Composable () -> Unit) { CompositionLocalProvider(LocalUseFallbackRippleImplementation provides true) { MaterialTheme(content = content) } }
Để biết thêm thông tin, hãy xem phần Nâng cấp phiên bản thư viện Material mà không cần di chuyển.
Sử dụng RippleTheme
để tắt hiệu ứng gợn sóng cho một thành phần cụ thể
Thư viện material
và material3
hiển thị RippleConfiguration
và LocalRippleConfiguration
, cho phép bạn định cấu hình giao diện của các hiệu ứng gợn sóng trong cây con. Xin lưu ý rằng RippleConfiguration
và LocalRippleConfiguration
đang trong giai đoạn thử nghiệm và chỉ dành cho mục đích tuỳ chỉnh theo từng thành phần. Các API này không hỗ trợ tuỳ chỉnh toàn cục/giao diện; hãy xem bài viết Sử dụng RippleTheme
để thay đổi toàn bộ hiệu ứng gợn sóng trong một ứng dụng để biết thêm thông tin về trường hợp sử dụng đó.
Ví dụ: đoạn mã sau đây sử dụng các API không dùng nữa:
private object DisabledRippleTheme : RippleTheme { @Composable override fun defaultColor(): Color = Color.Transparent @Composable override fun rippleAlpha(): RippleAlpha = RippleAlpha(0f, 0f, 0f, 0f) } // ... CompositionLocalProvider(LocalRippleTheme provides DisabledRippleTheme) { Button { // ... } }
Bạn nên sửa đổi đoạn mã trên để:
@OptIn(ExperimentalMaterialApi::class) private val DisabledRippleConfiguration = RippleConfiguration(isEnabled = false) // ... CompositionLocalProvider(LocalRippleConfiguration provides DisabledRippleConfiguration) { Button { // ... } }
Sử dụng RippleTheme
để thay đổi màu/alpha của hiệu ứng gợn sóng cho một thành phần cụ thể
Như mô tả trong phần trước, RippleConfiguration
và LocalRippleConfiguration
là các API thử nghiệm và chỉ dành để tuỳ chỉnh theo từng thành phần.
Ví dụ: đoạn mã sau đây sử dụng các API không dùng nữa:
private object DisabledRippleThemeColorAndAlpha : RippleTheme { @Composable override fun defaultColor(): Color = Color.Red @Composable override fun rippleAlpha(): RippleAlpha = MyRippleAlpha } // ... CompositionLocalProvider(LocalRippleTheme provides DisabledRippleThemeColorAndAlpha) { Button { // ... } }
Bạn nên sửa đổi đoạn mã trên để:
@OptIn(ExperimentalMaterialApi::class) private val MyRippleConfiguration = RippleConfiguration(color = Color.Red, rippleAlpha = MyRippleAlpha) // ... CompositionLocalProvider(LocalRippleConfiguration provides MyRippleConfiguration) { Button { // ... } }
Dùng RippleTheme
để thay đổi toàn bộ các hiệu ứng gợn sóng trong một ứng dụng
Trước đây, bạn có thể sử dụng LocalRippleTheme
để xác định hành vi gợn sóng ở cấp độ toàn giao diện. Về cơ bản, đây là điểm tích hợp giữa thành phần hệ thống thiết kế tuỳ chỉnh cục bộ và hiệu ứng gợn sóng. Thay vì để lộ giao diện nguyên gốc chung, material-ripple
giờ đây hiển thị một hàm createRippleModifierNode()
. Hàm này cho phép các thư viện hệ thống thiết kế tạo cách triển khai wrapper
có thứ tự cao hơn, truy vấn các giá trị giao diện của các thư viện đó rồi uỷ quyền triển khai hiệu ứng gợn sóng cho nút do hàm này tạo.
Điều này cho phép các hệ thống thiết kế truy vấn trực tiếp những gì chúng cần và hiển thị mọi lớp giao diện cần thiết mà người dùng có thể định cấu hình ở trên cùng mà không cần tuân theo nội dung được cung cấp ở lớp material-ripple
. Thay đổi này cũng làm rõ hơn giao diện/thông số kỹ thuật mà hiệu ứng gợn sóng đang tuân theo, vì chính API gợn sóng xác định hợp đồng đó, thay vì được ngầm nguồn gốc từ giao diện.
Để được hướng dẫn, hãy xem cách triển khai API gợn sóng trong thư viện Material và thay thế các lệnh gọi đến cục bộ thành phần Material nếu cần cho hệ thống thiết kế của riêng bạn.
Di chuyển từ Indication
sang IndicationNodeFactory
Đi qua Indication
Nếu chỉ tạo Indication
để truyền, chẳng hạn như tạo một hiệu ứng gợn sóng để truyền đến Modifier.clickable
hoặc Modifier.indication
, thì bạn không cần thực hiện bất kỳ thay đổi nào. IndicationNodeFactory
kế thừa từ Indication
,
vì vậy, mọi thứ sẽ tiếp tục biên dịch và hoạt động.
Đang tạo Indication
Nếu bạn đang tự triển khai Indication
, thì trong hầu hết trường hợp, quá trình di chuyển sẽ đơn giản. Ví dụ: hãy xem xét một Indication
áp dụng hiệu ứng tỷ lệ khi nhấn:
object ScaleIndication : Indication { @Composable override fun rememberUpdatedInstance(interactionSource: InteractionSource): IndicationInstance { // key the remember against interactionSource, so if it changes we create a new instance val instance = remember(interactionSource) { ScaleIndicationInstance() } LaunchedEffect(interactionSource) { interactionSource.interactions.collectLatest { interaction -> when (interaction) { is PressInteraction.Press -> instance.animateToPressed(interaction.pressPosition) is PressInteraction.Release -> instance.animateToResting() is PressInteraction.Cancel -> instance.animateToResting() } } } return instance } } private class ScaleIndicationInstance : IndicationInstance { var currentPressPosition: Offset = Offset.Zero val animatedScalePercent = Animatable(1f) suspend fun animateToPressed(pressPosition: Offset) { currentPressPosition = pressPosition animatedScalePercent.animateTo(0.9f, spring()) } suspend fun animateToResting() { animatedScalePercent.animateTo(1f, spring()) } override fun ContentDrawScope.drawIndication() { scale( scale = animatedScalePercent.value, pivot = currentPressPosition ) { this@drawIndication.drawContent() } } }
Bạn có thể di chuyển thẻ này theo 2 bước:
Di chuyển
ScaleIndicationInstance
thànhDrawModifierNode
. Nền tảng API choDrawModifierNode
rất giống vớiIndicationInstance
: nền tảng này hiển thị hàmContentDrawScope#draw()
có chức năng tương đương vớiIndicationInstance#drawContent()
. Bạn cần thay đổi hàm đó, sau đó triển khai logiccollectLatest
trực tiếp bên trong nút, thay vìIndication
.Ví dụ: đoạn mã sau đây sử dụng các API không dùng nữa:
private class ScaleIndicationInstance : IndicationInstance { var currentPressPosition: Offset = Offset.Zero val animatedScalePercent = Animatable(1f) suspend fun animateToPressed(pressPosition: Offset) { currentPressPosition = pressPosition animatedScalePercent.animateTo(0.9f, spring()) } suspend fun animateToResting() { animatedScalePercent.animateTo(1f, spring()) } override fun ContentDrawScope.drawIndication() { scale( scale = animatedScalePercent.value, pivot = currentPressPosition ) { this@drawIndication.drawContent() } } }
Bạn nên sửa đổi đoạn mã trên để:
private class ScaleIndicationNode( private val interactionSource: InteractionSource ) : Modifier.Node(), DrawModifierNode { var currentPressPosition: Offset = Offset.Zero val animatedScalePercent = Animatable(1f) private suspend fun animateToPressed(pressPosition: Offset) { currentPressPosition = pressPosition animatedScalePercent.animateTo(0.9f, spring()) } private suspend fun animateToResting() { animatedScalePercent.animateTo(1f, spring()) } override fun onAttach() { coroutineScope.launch { interactionSource.interactions.collectLatest { interaction -> when (interaction) { is PressInteraction.Press -> animateToPressed(interaction.pressPosition) is PressInteraction.Release -> animateToResting() is PressInteraction.Cancel -> animateToResting() } } } } override fun ContentDrawScope.draw() { scale( scale = animatedScalePercent.value, pivot = currentPressPosition ) { this@draw.drawContent() } } }
Di chuyển
ScaleIndication
để triển khaiIndicationNodeFactory
. Vì logic bộ sưu tập hiện đã được chuyển vào nút, nên đây là một đối tượng nhà máy rất đơn giản và trách nhiệm duy nhất của nó là tạo một thực thể nút.Ví dụ: đoạn mã sau đây sử dụng các API không dùng nữa:
object ScaleIndication : Indication { @Composable override fun rememberUpdatedInstance(interactionSource: InteractionSource): IndicationInstance { // key the remember against interactionSource, so if it changes we create a new instance val instance = remember(interactionSource) { ScaleIndicationInstance() } LaunchedEffect(interactionSource) { interactionSource.interactions.collectLatest { interaction -> when (interaction) { is PressInteraction.Press -> instance.animateToPressed(interaction.pressPosition) is PressInteraction.Release -> instance.animateToResting() is PressInteraction.Cancel -> instance.animateToResting() } } } return instance } }
Bạn nên sửa đổi đoạn mã trên để:
object ScaleIndicationNodeFactory : IndicationNodeFactory { override fun create(interactionSource: InteractionSource): DelegatableNode { return ScaleIndicationNode(interactionSource) } override fun hashCode(): Int = -1 override fun equals(other: Any?) = other === this }
Sử dụng Indication
để tạo IndicationInstance
Trong hầu hết trường hợp, bạn nên sử dụng Modifier.indication
để hiển thị Indication
cho một thành phần. Tuy nhiên, trong trường hợp hiếm hoi là bạn đang tạo IndicationInstance
theo cách thủ công bằng rememberUpdatedInstance
, bạn cần cập nhật phương thức triển khai để kiểm tra xem Indication
có phải là IndicationNodeFactory
hay không để có thể sử dụng phương thức triển khai đơn giản hơn. Ví dụ: Modifier.indication
sẽ uỷ quyền nội bộ cho nút đã tạo nếu đó là IndicationNodeFactory
. Nếu không, lệnh này sẽ sử dụng Modifier.composed
để gọi rememberUpdatedInstance
.