Loại Hiệu Ứng Gợn Nước Khi Nhấp Chuột Trong Jetpack Compose

Vấn đề: Khi sử dụng các thành phần điều khiển từ gói Material trong Jetpack Compose, việc nhấp chuột mặc định sẽ tạo ra hiệu ứng gợn nước. Làm thế nào để loại bỏ hiệu ứng gợn nước này?

Hãy xem xét chữ ký của Modifier.clickable:

fun Modifier.clickable(
    interactionSource: MutableInteractionSource,
    indication: Indication?,
    enabled: Boolean = true,
    onClickLabel: String? = null,
    role: Role? = null,
    onClick: () -> Unit
)

Thực ra, tham số indication chính là yếu tố quyết định hiệu ứng này.

Để loại bỏ hiệu ứng gợn nước cho một thành phần cụ thể, chúng ta chỉ cần đặt tham số này thành null.

Để thiết lập toàn hệ thống thì sao? Giá trị mặc định của tham số indicationindication = LocalIndication.current. Rõ ràng đây là cách được truyền xuống thông qua CompositionLocal. Vậy chúng ta chỉ cần định nghĩa một Indication riêng và thay thế giá trị này ở cấp trên. Ví dụ, nếu muốn áp dụng cho toàn bộ Activity, hãy thay thế nó trong thành phần gốc của Activity đó. Nếu muốn áp dụng cho toàn ứng dụng, có thể thực hiện trong theme.

Đầu tiên, hãy xem xét Indication mặc định được thực hiện như thế nào:

val LocalIndication = staticCompositionLocalOf<Indication> {
    DefaultDebugIndication
}

private object DefaultDebugIndication : Indication {

    private class DefaultDebugIndicationInstance(
        private val isPressed: State<Boolean>,
        private val isHovered: State<Boolean>,
        private val isFocused: State<Boolean>,
    ) : IndicationInstance {
        override fun ContentDrawScope.drawIndication() {
            drawContent()
            if (isPressed.value) {
                drawRect(color = Color.Black.copy(alpha = 0.3f), size = size)
            } else if (isHovered.value || isFocused.value) {
                drawRect(color = Color.Black.copy(alpha = 0.1f), size = size)
            }
        }
    }

    @Composable
    override fun rememberUpdatedInstance(interactionSource: InteractionSource): IndicationInstance {
        val isPressed = interactionSource.collectIsPressedAsState()
        val isHovered = interactionSource.collectIsHoveredAsState()
        val isFocused = interactionSource.collectIsFocusedAsState()
        return remember(interactionSource) {
            DefaultDebugIndicationInstance(isPressed, isHovered, isFocused)
        }
    }
}

Thực tế, trong tệp này đã có một triển khai NoIndication, nhưng nó ở trạng thái private:

private object NoIndication : Indication {
    private object NoIndicationInstance : IndicationInstance {
        override fun ContentDrawScope.drawIndication() {
            drawContent()
        }
    }

    @Composable
    override fun rememberUpdatedInstance(interactionSource: InteractionSource): IndicationInstance {
        return NoIndicationInstance
    }
}

Ở đây có hai giao diện liên quan:

@Stable
interface Indication {

    @Composable
    fun rememberUpdatedInstance(interactionSource: InteractionSource): IndicationInstance
}

interface IndicationInstance {

    fun ContentDrawScope.drawIndication()
}

Logic khá rõ ràng, để tùy chỉnh một Indication, chúng ta cần hai bước:

  1. Triển khai giao diện IndicationInstance, thực hiện phương thức ContentDrawScope.drawIndication(). Trả về đối tượng này. Từ tên phương pháp, có vẻ đây là nơi vẽ hiệu ứng nhấp chuột. Trong triển khai mặc định, sử dụng interactionSource, tùy vào trạng thái nhấp chuột khác nhau để vẽ các hiệu ứng khác nhau.
  2. Triển khai giao diện Indication, thực hiện phương thức rememberUpdatedInstance, trả về đối tượng kiểu IndicationInstance ở trên.

Với nền tảng lý thuyết này, chúng ta hãy thực hành loại bỏ hiệu ứng gợn nước khi nhấp chuột, cũng gồm hai bước:

  1. Định nghĩa một Indication không có hiệu ứng nhấp chuột. Chỉ cần sao chép cái private trong mã nguồn để ứng dụng của chúng ta có thể sử dụng:
object NoRippleEffect: Indication {
    private object NoRippleEffectInstance: IndicationInstance {
        override fun ContentDrawScope.drawIndication() {
            drawContent()
        }
    }

    @Composable
    override fun rememberUpdatedInstance(interactionSource: InteractionSource): IndicationInstance {
        return NoRippleEffectInstance
    }
}
  1. Áp dụng Indication này:
@Composable
fun AppTheme(
    darkTheme: Boolean = isSystemInDarkTheme(),
    // Màu động có sẵn trên Android 12+
    dynamicColor: Boolean = true,
    content: @Composable () -> Unit
) {
    ...
    MaterialTheme(
        colorScheme = colorScheme,
        typography = Typography,
        content = {
            CompositionLocalProvider(LocalIndication provides NoRippleEffect) {
                content()
            }
        }
    )
}

Xong!

Tin rằng với nền tảng lý thuyết này, chúng ta hoàn toàn có thể tùy chỉnh hiệu ứng nhấp chuột theo ý muốn của riêng mình.

Thẻ: Jetpack Compose Android UI Material Design Interaction Source CompositionLocal

Đăng vào ngày 19 tháng 6 lúc 04:03