androidkotlinuser-interfaceandroid-jetpack-composeandroid-jetpack-compose-gesture

How to add rippleeffect on click while using PointerInput modifier


I have a custom key, and i want to use ripple effect on click, ripple effect is by default added into .clickable, but i want PointerInput that have onClick onLongClick and onPress, so how can i implement ripple effect while keeping these. Here is my CustomKey composeable.

package com.soloftech.keyboard.presentation.layout


import android.content.Context
import android.os.Vibrator
import android.view.inputmethod.EditorInfo
import androidx.compose.foundation.background
import androidx.compose.foundation.gestures.detectTapGestures
import androidx.compose.foundation.layout.defaultMinSize
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Icon
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.res.colorResource
import androidx.compose.ui.res.dimensionResource
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.constraintlayout.compose.ConstraintLayout
import com.soloftech.keyboard.R
import com.soloftech.keyboard.domain.keyboard.Key
import com.soloftech.keyboard.domain.playSound
import com.soloftech.keyboard.domain.showPopUp
import com.soloftech.keyboard.domain.vibrate

@Composable
fun CustomKey(
    key: Key,
    isCapsLockEnabled: Boolean = false,
    onKeyPressed: (Key) -> Unit,
    onLongKeyPressed: (Key) -> Unit,
    onLongKeyPressedEnd: () -> Unit,
    modifier: Modifier,
    onLayoutSwitchClick: () -> Unit,
    onExtendedSymbolsSwitchClick: () -> Unit,
    onNumbersSwitchClick: () -> Unit,
    onSymbolsLayoutSwitchClick: () -> Unit,
    onCapsClick: () -> Unit,
    context: Context?,
    vibrator: Vibrator? = null,
    iconResourceId: Int = key.icon,
    textColor: Color = colorResource(id = R.color.KeyTextColor),
    imeAction: Int? = null
) {

    val isPopupVisible = remember { mutableStateOf(false) }

    val keyColor = if (key.isSpecialCharacter) {
        colorResource(id = R.color.SpecialKeyBackground)
    } else {
        colorResource(id = R.color.KeyBackground)
    }

    val keyShape = when (key.labelMain) {
        "?123", "Done", "ABC" -> RoundedCornerShape(36.dp)
        else -> RoundedCornerShape(6.dp)
    }

    val labelMain = remember(key.labelMain, isCapsLockEnabled) {
        if (isCapsLockEnabled) {
            key.labelMain.uppercase()
        } else {
            key.labelMain
        }
    }


    val icon = when (key.labelMain) {
        "CAPS" -> {
            if (isCapsLockEnabled) {
                R.drawable.caps_lock_on
            } else {
                R.drawable.caps_lock_off
            }
        }

        "Done" -> {
            when (imeAction) {
                EditorInfo.IME_ACTION_DONE -> R.drawable.done_icon
                EditorInfo.IME_ACTION_SEARCH -> R.drawable.search_icon
                EditorInfo.IME_ACTION_NEXT -> R.drawable.next_icon
                EditorInfo.IME_ACTION_SEND -> R.drawable.send_icon
                EditorInfo.IME_ACTION_GO -> R.drawable.go_icon
                EditorInfo.IME_ACTION_NONE -> R.drawable.keyboard_return
                EditorInfo.IME_ACTION_PREVIOUS -> R.drawable.previous_icon
                else -> key.icon
            }
        }

        else -> iconResourceId
    }

    var isLongPressed by remember {
        mutableStateOf(false)
    }


    val popUpLabel = if (isLongPressed) key.labelSecondary ?: "" else key.labelMain

    ConstraintLayout(
        modifier = modifier
            .clip(keyShape)
            .background(color = keyColor)
            .pointerInput(Unit) {
                detectTapGestures(
                    onTap = {
                        onClick(
                            isPopupVisible = isPopupVisible,
                            context = context,
                            vibrator = vibrator
                        )
                        when (key.labelMain) {
                            "ABC" -> onLayoutSwitchClick()
                            "?123" -> onSymbolsLayoutSwitchClick()
                            "=\\<" -> onExtendedSymbolsSwitchClick()
                            "!?#" -> onSymbolsLayoutSwitchClick()
                            "Numbers" -> onNumbersSwitchClick()
                            "CAPS" -> {
                                onCapsClick()
                            }

                            else -> onKeyPressed(key)
                        }
                    },
                    onLongPress = {
                        onClick(
                            isPopupVisible = isPopupVisible,
                            context = context,
                            vibrator = vibrator
                        )
                        isLongPressed = true
                        onLongKeyPressed(key)
                    },
                    onPress = {
                        awaitRelease()
                        onLongKeyPressedEnd()
                        isLongPressed = false
                    }
                )
            }
                
            .defaultMinSize(minHeight = dimensionResource(id = R.dimen.key_height).value.dp)
    ) {
        if (key.shouldShowIcon) {
            Icon(
                painter = painterResource(id = icon),
                contentDescription = key.contentDescription,
                tint = textColor,
                modifier = Modifier
                    .constrainAs(createRef()) {
                        top.linkTo(parent.top)
                        bottom.linkTo(parent.bottom)
                        start.linkTo(parent.start)
                        end.linkTo(parent.end)
                    }
            )
        } else {
            when (key.labelMain) {
                "?123", "ABC", "Space", "!?#" -> AutoResizedText(
                    text = key.labelMain,
                    color = textColor,
                    modifier = Modifier.constrainAs(createRef()) {
                        top.linkTo(parent.top)
                        bottom.linkTo(parent.bottom)
                        start.linkTo(parent.start)
                        end.linkTo(parent.end)
                    }
                )

                else -> Text(
                    text = labelMain,
                    color = textColor,
                    fontSize = dimensionResource(id = R.dimen.key_textSize).value.sp,
                    fontWeight = FontWeight.Medium,
                    modifier = Modifier.constrainAs(createRef()) {
                        top.linkTo(parent.top)
                        bottom.linkTo(parent.bottom)
                        start.linkTo(parent.start)
                        end.linkTo(parent.end)
                    }
                )
            }
        }
        if (key.shouldShowPopUp) {
            PopupScreen(
                label = popUpLabel,
                offset = IntOffset(x = 0, y = -50),
                isVisible = isPopupVisible.value
            )
        }
        key.labelSecondary?.let {
            Text(
                text = it,
                color = textColor,
                fontSize = dimensionResource(id = R.dimen.key_textHintSize).value.sp,
                modifier = Modifier
                    .constrainAs(createRef()) {
                        top.linkTo(parent.top, margin = 2.dp)
                        end.linkTo(parent.end, margin = 2.dp)
                    }
            )
        }
    }
}

@Preview
@Composable
fun CustomKeyPreview() {
    CustomKey(
        key = Key("f", 1F, labelSecondary = "@"),
        onKeyPressed = {},
        onLongKeyPressed = {},
        onLongKeyPressedEnd = {},
        modifier = Modifier,
        onLayoutSwitchClick = {},
        onExtendedSymbolsSwitchClick = {},
        onSymbolsLayoutSwitchClick = { },
        onCapsClick = {},
        onNumbersSwitchClick = {},
        context = null
    )
}

fun onClick(
    isPopupVisible: MutableState<Boolean>,
    context: Context?,
    vibrator: Vibrator?
) {
    showPopUp(isPopupVisible)
    if (context != null) {
        playSound(context = context)
    }
    vibrate(vibrator)
}

How to add ripple effect. Thanks


Solution

  • You can do it as

    // This is for emitting press or release event
    val interactionSource = remember { MutableInteractionSource() }
    
    val pointerModifier = Modifier
        // other modifiers
        .indication(interactionSource, rememberRipple())
        .pointerInput(Unit) {
            detectTapGestures(
                onPress = { offset: Offset ->
                    val press = PressInteraction.Press(offset)
                    interactionSource.emit(press)
                    // Waits for the press to be released before returning.
                    // If the press was released, true is returned, or if the gesture
                    // was canceled by motion being consumed by another gesture, false is returned.
                    tryAwaitRelease()
                    // We emit a release press interaction here
                    interactionSource.emit(PressInteraction.Release(press))
    
                },
            )
        }