Để 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 các thay đổi sau đây về API:
Không dùng nữa |
Thay thế |
|---|---|
|
|
|
Thay vào đó, các API Lưu ý: Trong bối 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 API mới.
Thay đổi về hành vi
Các phiên bản thư viện sau đây có thay đổi về hiệu ứng 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 này của thư viện Material không còn sử dụng rememberRipple() nữa; thay vào đó,
chúng sử dụng API hiệu ứng gợn sóng mới. Do đó, các ứng dụng này 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 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 Tuỳ chỉnh giao diện Material. Sau đó, hãy truyền đối tượng được trả về cho 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 composable và không cần phải ghi nhớ. Bạn cũng có thể sử dụng lại hiệu ứng 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 việc trích xuất hiệu ứng gợn sóng thành một giá trị cấp cao nhất để tiết kiệm 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ì bạn nên cung cấp API hiệu ứng gợn sóng của riêng mình để uỷ quyền cho các API nút hiệu ứng 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 riêng
sử dụng trực tiếp các giá trị giao diện của bạn. Để biết thêm thông tin, hãy xem bài viết Di chuyển từRippleTheme.
Di chuyển từ RippleTheme
Sử dụng RippleTheme để tắt hiệu ứng gợn sóng cho một thành phần nhất định
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 chỉ dùng để tuỳ chỉnh theo từng thành phần. Các API này không hỗ trợ tuỳ chỉnh chung/trên toàn giao diện; hãy xem phần Sử dụng RippleTheme để thay đổi chung tất cả 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 chỉ dành cho việc 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 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 tất cả hiệu ứng gợn sóng trong một ứng dụng trên toàn hệ thống
Trước đây, bạn có thể sử dụng LocalRippleTheme để xác định hành vi hiệu ứng 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 các thành phần cục bộ của hệ thống thiết kế tuỳ chỉnh và hiệu ứng gợn sóng. Thay vì hiển thị một dữ liệu gốc theo chủ đề 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 ra cách triển khai wrapper bậc cao hơn, truy vấn các giá trị giao diện của chúng, sau đó uỷ quyền triển khai hiệu ứng gợn sóng cho nút do hàm này tạo ra.
Điều này cho phép hệ thống thiết kế truy vấn trực tiếp những gì họ cần và hiển thị mọi lớp giao diện bắt buộc mà người dùng có thể định cấu hình ở trên cùng mà không cần phải tuân thủ những gì được cung cấp ở lớp material-ripple. Thay đổi này cũng giúp làm rõ hơn giao diện/quy cách mà hiệu ứng gợn sóng tuân thủ, vì chính API hiệu ứng gợn sóng xác định hợp đồng đó, thay vì được lấy ngầm từ giao diện.
Để được hướng dẫn, hãy xem phần triển khai API hiệu ứng gợn sóng trong thư viện Material và thay thế các lệnh gọi đến thành phần cục bộ 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 một 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,
nên 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 phương thức triển khai Indication của riêng mình, thì quá trình di chuyển sẽ đơ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 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 dữ liệu này theo hai bước:
Di chuyển
ScaleIndicationInstancethànhDrawModifierNode. Nền tảng API choDrawModifierNoderất giống vớiIndicationInstance: API 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 logiccollectLatestngay 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 thu thậ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 }
Sử dụng Indication để tạo IndicationInstance
Trong hầu hết các 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 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, hệ thống sẽ sử dụng Modifier.composed để gọi rememberUpdatedInstance.