Để 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+
bao gồm 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 |
|
Hãy thực hiện một trong hai thao tác sau:
|
Trang này mô tả tác động của thay đổi về hành vi và hướng dẫn di chuyển sang các API mới.
Thay đổi về hành vi
Các phiên bản thư viện sau đây có 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()
nữa mà sử dụng các API hiệu ứng gợn sóng mới. Do đó, chúng không truy vấn LocalRippleTheme
.
Do đó, nếu bạn đặt LocalRippleTheme
trong ứng dụng, các thành phần Material sẽ không sử dụng các giá trị này.
Các phần sau đây mô tả cách di chuyển sang các 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 một thư viện Material, hãy thay thế trực tiếp rememberRipple()
bằng một lệnh gọi đến ripple()
trong thư viện tương ứng. API này tạo hiệu ứng gợn sóng bằ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 thành:
@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 phải được ghi nhớ. Bạn cũng có thể sử dụng lại trên nhiều thành phần, tương tự như các đối tượng sửa đổi. Vì vậy, hãy cân nhắc việc trích xuất quá trình tạo hiệu ứng gợn sóng thành một giá trị cấp cao nhất để lưu các lượt 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 đây bạn đã sử dụng rememberRipple()
cùng với RippleTheme
tuỳ chỉnh để định cấu hình hiệu ứng gợn sóng, thì thay vào đó, bạn nên cung cấp API gợn sóng của riêng mình để uỷ quyền cho các API nút gợn sóng được hiển thị trong material-ripple
. Sau đó, các thành phần của bạn có thể sử dụng hiệu ứng gợn sóng của riêng bạn, hiệu ứng này sẽ 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 từ RippleTheme
.
Di chuyển từ RippleTheme
Tạm thời chọn không sử dụng thay đổi về hành vi
Thư viện Material có CompositionLocal
, LocalUseFallbackRippleImplementation
tạm thời mà bạn có thể dùng để định cấu hình tất cả cá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 tạo dựa trên Material, bạn có thể cung cấp thành phần cục bộ một cách an toàn như một phần của giao diện ứng dụng:
@OptIn(ExperimentalMaterial3Api::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 nhất định
Các 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 hiệu ứng gợn sóng trong một cây con. Xin lưu ý rằng RippleConfiguration
và LocalRippleConfiguration
là các thuộc tính thử nghiệm và chỉ dành cho việc tuỳ chỉnh theo từng thành phần. Các API này không hỗ trợ hoạt động tuỳ chỉnh trên toàn cầu/toàn bộ giao diện; hãy xem phần 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 thành:
CompositionLocalProvider(LocalRippleConfiguration provides null) { 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 nhất định
Như mô tả trong phần trước, RippleConfiguration
và LocalRippleConfiguration
là các API thử nghiệm và chỉ dành cho hoạt động tuỳ chỉnh theo 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 thành:
@OptIn(ExperimentalMaterialApi::class) private val MyRippleConfiguration = RippleConfiguration(color = Color.Red, rippleAlpha = MyRippleAlpha) // ... CompositionLocalProvider(LocalRippleConfiguration provides MyRippleConfiguration) { Button { // ... } }
Sử dụng RippleTheme
để thay đổi toàn bộ hiệu ứng gợn sóng trong một ứng dụng
Trước đây, bạn có thể dùng LocalRippleTheme
để xác định hành vi gợn sóng ở cấp toàn bộ 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ì hiển thị một thành phần tạo giao diện chung, material-ripple
hiện 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 chế độ triển khai wrapper
bậc cao hơn, truy vấn các giá trị giao diện của chúng 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ế trực tiếp truy vấn những gì chúng cần và hiển thị mọi lớp giao diện theo chủ đề mà người dùng có thể định cấu hình theo yêu cầu ở trên cùng mà không cần tuân theo những gì được cung cấp ở lớp material-ripple
. Thay đổi này cũng giúp xác định rõ hơn chủ đề/quy cách mà hiệu ứng gợn sóng tuân thủ, vì chính API gợn sóng xác định hợp đồng đó, thay vì được suy ra ngầm từ chủ đề.
Để được hướng dẫn, hãy xem việc triển khai API hiệu ứng gợn sóng trong các thư viện Material và thay thế các lệnh gọi đến thành phần cục bộ Material khi cần cho hệ thống thiết kế của riêng bạn.
Di chuyển từ Indication
sang IndicationNodeFactory
Đang truyền bóng cho Indication
Nếu chỉ tạo một Indication
để truyền đi, 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ạo chế độ triển khai Indication
của riêng mình, thì quá trình di chuyển sẽ diễn ra đơn giản trong hầu hết các trường hợp. Ví dụ: hãy xem xét một Indication
áp dụng hiệu ứng thu phóng 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 dữ liệu 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ị một 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
ngay 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 thành:
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 của 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, có trách nhiệm duy nhất 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 thành:
object ScaleIndicationNodeFactory : IndicationNodeFactory { override fun create(interactionSource: InteractionSource): DelegatableNode { return ScaleIndicationNode(interactionSource) } override fun hashCode(): Int = -1 override fun equals(other: Any?) = other === this }
Dùng Indication
để tạo IndicationInstance
Trong hầu hết các trường hợp, bạn nên 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 gặp là bạn đang tạo IndicationInstance
theo cách thủ công bằng rememberUpdatedInstance
, bạn cần cập nhật việc triển khai để kiểm tra xem Indication
có phải là IndicationNodeFactory
hay không để có thể sử dụng một cách triển khai nhẹ 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, nó sẽ dùng Modifier.composed
để gọi rememberUpdatedInstance
.