Để 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 bạn
cách triển khai Indication
hiệu quả, chẳng hạn như Ripples.
androidx.compose.foundation:foundation:1.7.0+
và
androidx.compose.material:material-ripple:1.7.0+
bao gồm API sau
các thay đổi:
Không dùng nữa |
Thay thế |
---|---|
|
|
|
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 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()
; 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 đặt LocalRippleTheme
trong ứng dụng, Material
thành phần 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 hoạt động cũ
không cần di chuyển; tuy nhiên, bạn nên chuyển sang các API mới. Cho
hướng dẫn di chuyển, hãy xem 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 hàm
LocalUseFallbackRippleImplementation CompositionLocal
API để định cấu hình
Các thành phần Material sẽ quay lại hành vi cũ:
CompositionLocalProvider(LocalUseFallbackRippleImplementation provides true) { MaterialTheme { App() } }
Đảm bảo cung cấp phần này bên ngoài MaterialTheme
để các Ripples cũ có thể
được cung cấp 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 một
gọi đến ripple()
từ thư viện tương ứng. API này tạo ra một 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 đó, chuyển giá trị được trả về
đối tượng với 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() ) ) { // ... } }
Lưu ý rằng ripple()
không còn là hàm có khả năng kết hợp và không cần phải là
đã nhớ. Kết quả này cũng có thể được sử dụng lại 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 hoạt động tạo hiệu ứng gợn sóng thành giá trị cấp cao nhất để
lưu phân bổ.
Triển khai hệ thống thiết kế tuỳ chỉnh
Nếu bạn đang triển khai hệ thống thiết kế của riêng mình và trước đó bạn đã sử dụng
rememberRipple()
cùng với một RippleTheme
tuỳ chỉnh để định cấu hình hiệu ứng gợn sóng,
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 nút gợn sóng
API được 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 của bạ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 tham gia thay đổi về hành vi
Thư viện Material có CompositionLocal
tạm thời,
LocalUseFallbackRippleImplementation
mà bạn có thể dùng để định cấu hình tất cả
Thành phần Material nên 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 dùng giao diện tuỳ chỉnh của ứng dụng được xây dựng dựa trên Material, bạn có thể cung cấp thành phần kết hợp cục bộ trong giao diện của ứng dụng một cách an toàn:
@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 bài viết Nâng cấp phiên bản thư viện Material mà không có di chuyển.
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
gợn sóng trong cây con. Lưu ý rằng RippleConfiguration
và
LocalRippleConfiguration
đang trong giai đoạn thử nghiệm và chỉ dành cho mỗi thành phần
phần tuỳ chỉnh. Chế độ tuỳ chỉnh trên toàn giao diện/toàn cầu không được hỗ trợ cho những mẫu này
API; xem Sử dụng RippleTheme
để thay đổi tổng thể tất cả các 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 để:
CompositionLocalProvider(LocalRippleConfiguration provides null) { Button { // ... } }
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
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ộ tất cả Ripples 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 tại
toàn chủ đề. Về cơ bản, đây là một điểm tích hợp giữa
thiết kế cấu trúc cục bộ và Ripples. Thay vì đưa ra thông điệp chung chung
gốc chủ đề, material-ripple
hiện sẽ hiển thị createRippleModifierNode()
. Hàm này cho phép các thư viện hệ thống thiết kế tạo ra
yêu cầu triển khai wrapper
, truy vấn các giá trị giao diện của chúng rồi uỷ quyền
phương thức 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ị bất kỳ
các 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 phải tuân theo
dữ liệu được cung cấp ở lớp material-ripple
. Thay đổi này cũng giúp
nêu rõ chủ đề/thông số kỹ thuật mà hiệu ứng gợn sóng tuân thủ, vì đây là
chính Ripples API xác định hợp đồng đó thay vì được ngầm ẩn
bắt nguồn từ giao diện.
Để biết hướng dẫn, hãy xem bài viết triển khai API gợn sóng trong Material các thư viện và thay thế các lệnh gọi đến thành phần kết hợp 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 xung quanh Indication
Nếu bạn chỉ tạo Indication
để truyền, chẳng hạn như tạo một
gợn sóng để chuyển đến Modifier.clickable
hoặc Modifier.indication
, bạn không không
cần thực hiện thay đổi nào. IndicationNodeFactory
kế thừa từ Indication
,
để 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 Indication
áp dụng một
mức độ tác động đến báo chí:
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 qua 2 bước:
Di chuyển
ScaleIndicationInstance
thànhDrawModifierNode
. Nền tảng API choDrawModifierNode
rất giống vớiIndicationInstance
: nó 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 chức năng đó, sau đó triển khai trực tiếp logiccollectLatest
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 thu thập hiện được chuyển vào nút, đây là một nhà máy rất đơn giản mà chỉ có trách nhiệm 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 một 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 thủ công
IndicationInstance
đang sử dụng rememberUpdatedInstance
, bạn cần cập nhật
để kiểm tra xem Indication
có phải là IndicationNodeFactory
hay không.
có thể sử dụng 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ẽ sử dụng Modifier.composed
để gọi rememberUpdatedInstance
.