Android 应用程序 jetpack compose 中显示弹出窗口的延迟

问题描述 投票:0回答:1

我目前正在开发一个键盘应用程序,我已经实现了一个弹出窗口,当按下按键时应该立即显示。我正在使用 jetpack 编写自己的 PopUp。但是,我遇到了弹出窗口出现意外延迟的情况。我正在寻求有关如何解决此延迟问题的指导。 当按下按键时,它应该立即显示,并且我已经实现了一个处理程序,可以在 250 毫秒后隐藏它。但我在延迟了大约几百毫秒后显示。执行触发操作时,弹出窗口应立即出现,没有任何明显的延迟。

这是我的 PopUp.kt。

package com.soloftech.keyboard.presentation.layout


import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.colorResource
import androidx.compose.ui.res.dimensionResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.sp
import androidx.compose.ui.window.Popup
import androidx.compose.ui.window.PopupProperties
import com.soloftech.keyboard.R

@Composable
fun PopupScreen(
    label: String,
    isVisible: Boolean
) {
    val popupWidth = dimensionResource(id = R.dimen.key_width)
    val popupHeight = dimensionResource(id = R.dimen.key_height)
    val cornerSize = dimensionResource(id = R.dimen.key_borderRadius)

    if (isVisible) {
        Popup(
            properties = PopupProperties(
                clippingEnabled = true,
                dismissOnClickOutside = true
            ),
            alignment = Alignment.BottomCenter
        ) {
            Box(
                Modifier
                    .size(width = popupWidth, height = popupHeight * 2)
                    .background(
                        colorResource(id = R.color.PopUpColor),
                        RoundedCornerShape(cornerSize)
                    ),
                contentAlignment = Alignment.TopCenter
            ) {
                Text(
                    text = label,
                    color = colorResource(id = R.color.KeyTextColor),
                    fontSize = dimensionResource(id = R.dimen.key_popup_textSize).value.sp
                )
            }
        }
    }
}

@Preview
@Composable
fun PopUpPreview() {
    PopupScreen(label = "F", isVisible = true)
}

我在这里称呼它。


        if (key.shouldShowPopUp) {
            // Display a pop-up screen if Clicked
            PopupScreen(
                label = popUpLabel,
                isVisible = isPopupVisible.value
            )
        }

IsPopUpVisible 值和处理程序代码在这里。

.pointerInput(Unit) {
            detectTapGestures(
                onTap = {
                    // Handle tap gesture on the key
                    isPopupVisible.value = true
                    Handler(Looper.getMainLooper()).postDelayed({
                        isPopupVisible.value = false
                    }, 250)
                    if (context != null) {
                        playSound(context = context)
                    }
                    vibrate(vibrator)

                    when (key.labelMain) {
                        LABEL_ABC -> onLayoutSwitchClick()
                        LABEL_123 -> onSymbolsLayoutSwitchClick()
                        LABEL_EXTENDED_SYMBOLS -> onExtendedSymbolsSwitchClick()
                        LABEL_SYMBOLS -> onSymbolsLayoutSwitchClick()
                        LABEL_NUMBERS -> onNumbersSwitchClick()
                        LABEL_CAPS -> {
                            onCapsClick()
                        }

                        else -> onKeyPressed()
                    }
                },

这是 isPopupVisible 的声明。

val isPopupVisible = remember { mutableStateOf(false) }

这是完整的 CustomKey 代码。

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.detectHorizontalDragGestures
import androidx.compose.foundation.gestures.detectTapGestures
import androidx.compose.foundation.indication
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.interaction.PressInteraction
import androidx.compose.foundation.layout.defaultMinSize
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.ripple.rememberRipple
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.tooling.preview.Preview
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.constants.LABEL_123
import com.soloftech.keyboard.domain.constants.LABEL_ABC
import com.soloftech.keyboard.domain.constants.LABEL_CAPS
import com.soloftech.keyboard.domain.constants.LABEL_EXTENDED_SYMBOLS
import com.soloftech.keyboard.domain.constants.LABEL_NUMBERS
import com.soloftech.keyboard.domain.constants.LABEL_SPACE
import com.soloftech.keyboard.domain.constants.LABEL_SYMBOLS
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,
    isCapsEnabled: Boolean = false,
    isCapsLockEnabled: Boolean = false,
    onKeyPressed: () -> Unit,
    onDragGesture: (Float) -> Unit,
    onLongKeyPressed: () -> Unit,
    onLongKeyPressedEnd: () -> Unit,
    modifier: Modifier,
    onLayoutSwitchClick: () -> Unit,
    onExtendedSymbolsSwitchClick: () -> Unit,
    onNumbersSwitchClick: () -> Unit,
    onSymbolsLayoutSwitchClick: () -> Unit,
    onCapsClick: () -> Unit,
    onCapsClickToLock: () -> Unit,
    context: Context?,
    vibrator: Vibrator? = null,
    iconResourceId: Int = key.icon,
    textColor: Color = colorResource(id = R.color.KeyTextColor),
    imeAction: Int? = null
) {
    // State to track the visibility of the pop-up
    val isPopupVisible = remember { mutableStateOf(false) }

    // Determine the background color of the key based on whether it's a special character
    val keyColor = if (key.isSpecialCharacter) {
        colorResource(id = R.color.SpecialKeyBackground)
    } else {
        colorResource(id = R.color.KeyBackground)
    }

    // Define the shape of the key based on its label
    val keyShape =
        if (key.shouldBeRounded) {
            RoundedCornerShape(56.dp)
        } else {
            RoundedCornerShape(6.dp)
        }

    // Calculate the main label of the key, considering Caps and Caps Lock state
    val labelMain = remember(key.labelMain, isCapsEnabled, isCapsLockEnabled) {
        if (isCapsEnabled || isCapsLockEnabled) {
            key.labelMain.uppercase()
        } else {
            key.labelMain
        }
    }

    // Determine the icon to display on the key
    val icon = when (key.labelMain) {
        LABEL_CAPS -> {
            if (isCapsEnabled) {
                R.drawable.caps_lock_on
            } else if (isCapsLockEnabled) {
                R.drawable.caps_lock_on_locked
            } 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
    }

    // State to track long press
    var isLongPressed by remember { mutableStateOf(false) }

    // Determine the label for the pop-up
    val popUpLabel = if (isLongPressed) key.labelSecondary ?: "" else key.labelMain

    // Interaction source for detecting gestures
    val interactionSource = remember { MutableInteractionSource() }

    ConstraintLayout(modifier = modifier
        .clip(keyShape)
        .background(color = keyColor)
        .indication(
            interactionSource = interactionSource, indication = rememberRipple(
                color = colorResource(id = R.color.white), radius = 256.dp
            )
        )
        .pointerInput(Unit) {
            detectTapGestures(
                onTap = {
                    // Handle tap gesture on the key
                    onClick(
                        isPopupVisible = isPopupVisible,
                        context = context,
                        vibrator = vibrator
                    )

                    when (key.labelMain) {
                        LABEL_ABC -> onLayoutSwitchClick()
                        LABEL_123 -> onSymbolsLayoutSwitchClick()
                        LABEL_EXTENDED_SYMBOLS -> onExtendedSymbolsSwitchClick()
                        LABEL_SYMBOLS -> onSymbolsLayoutSwitchClick()
                        LABEL_NUMBERS -> onNumbersSwitchClick()
                        LABEL_CAPS -> {
                            onCapsClick()
                        }

                        else -> onKeyPressed()
                    }
                },
                onDoubleTap = {
                    // Handle double tap gesture on the key
                    if (key.labelMain == LABEL_CAPS) {
                        onCapsClickToLock()
                    } else {
                        onKeyPressed()
                        onKeyPressed()
                    }
                    onClick(
                        isPopupVisible = isPopupVisible,
                        context = context,
                        vibrator = vibrator
                    )
                },
                onLongPress = {
                    // Handle long press gesture on the key
                    isLongPressed = true
                    if (key.labelMain == LABEL_CAPS) {
                        onCapsClickToLock()
                    } else {
                        onLongKeyPressed()
                    }
                    isPopupVisible.value = true
                    if (context != null) {
                        playSound(context = context)
                    }
                    vibrate(vibrator)
                },
                onPress = { offset ->
                    // Handle press gesture on the key
                    val press = PressInteraction.Press(offset)
                    interactionSource.emit(press)
                    tryAwaitRelease()
                    if (isPopupVisible.value) {
                        isPopupVisible.value = false
                    }
                    onLongKeyPressedEnd()
                    isLongPressed = false
                    interactionSource.emit(PressInteraction.Release(press))
                })

            detectHorizontalDragGestures { _, dragAmount ->
                // Handle horizontal drag gesture on the key
                onDragGesture(dragAmount)
            }
        }
        .defaultMinSize(minHeight = dimensionResource(id = R.dimen.key_height).value.dp)
    ) {
        if (key.shouldShowIcon) {
            // Display an icon on the key
            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) {
                LABEL_123, LABEL_ABC, LABEL_SPACE, LABEL_SYMBOLS ->
                    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,
                    modifier = Modifier.constrainAs(createRef()) {
                        top.linkTo(parent.top)
                        bottom.linkTo(parent.bottom)
                        start.linkTo(parent.start)
                        end.linkTo(parent.end)
                    })
            }
        }

        key.labelSecondary?.let {
            // Display secondary label if available
            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)
                })
        }

        if (key.shouldShowPopUp) {
            // Display a pop-up screen if Clicked
            PopupScreen(
                label = popUpLabel,
                isVisible = isPopupVisible.value
            )
        }

    }
}

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

fun onClick(
    isPopupVisible: MutableState<Boolean>, context: Context?, vibrator: Vibrator?
) {
    // Handle the click event on the key
    showPopUp(isPopupVisible)
    if (context != null) {
        playSound(context = context)
    }
    vibrate(vibrator)
}

这是键盘布局。

package com.soloftech.keyboard.presentation.layout


import android.content.Context
import android.os.Vibrator
import android.util.Log
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.key
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.colorResource
import androidx.compose.ui.res.dimensionResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.soloftech.keyboard.R
import com.soloftech.keyboard.data.layout.extendedSymbolsLayout
import com.soloftech.keyboard.data.layout.numbersLayout
import com.soloftech.keyboard.data.layout.qwertyLayout_small
import com.soloftech.keyboard.data.layout.symbolsLayout
import com.soloftech.keyboard.domain.KeyboardVisibilityProvider
import com.soloftech.keyboard.domain.keyboard.LayoutState
import com.soloftech.keyboard.presentation.MainActivity
import com.soloftech.keyboard.presentation.service.CustomImeService

@Composable
fun CustomKeyboard(
    imeService: CustomImeService? = null,
    vibrator: Vibrator? = null,
    context: Context?,
    showNumbersRow: Boolean = false,
    imeActionType: Int? = null
) {
    // Define the height of the keyboard based on whether the numbers row should be shown
    val keyboardHeight = if (showNumbersRow) 248.dp else 208.dp

    // Mutable state variables for Caps and Caps Lock
    var isCapsEnabled by remember { mutableStateOf(true) }
    var isCapsLockEnabled by remember { mutableStateOf(false) }

    // Mutable state variable to track the current layout
    var currentLayout by remember { mutableStateOf(LayoutState.Qwerty) }

    // Detect changes in keyboard visibility
    var isKeyboardVisible by remember { mutableStateOf(false) }
    KeyboardVisibilityProvider { isVisible ->
        isKeyboardVisible = isVisible
        if (!isKeyboardVisible) {
            // Keyboard is hidden, switch back to qwerty layout and disable Caps Lock
            currentLayout = LayoutState.Qwerty
            isCapsLockEnabled = false
        }
    }

    // Determine which keys to display based on the current layout
    var keys = when (currentLayout) {
        LayoutState.Qwerty -> qwertyLayout_small
        LayoutState.Symbols -> symbolsLayout
        LayoutState.ExtendedSymbols -> extendedSymbolsLayout
        LayoutState.Numbers -> numbersLayout
    }

    // Click handlers for layout switches
    val onLayoutSwitchClick = {
        currentLayout = LayoutState.Qwerty
    }

    val onNumbersSwitchClick = {
        currentLayout = LayoutState.Numbers
    }

    val onSymbolsLayoutSwitchClick = {
        currentLayout = LayoutState.Symbols
    }

    val onExtendedSymbolsSwitchClick = {
        currentLayout = LayoutState.ExtendedSymbols
    }

    // Click handler for Caps
    val onCapsClick = {
        if (isCapsLockEnabled) {
            isCapsLockEnabled = false
        } else {
            isCapsEnabled = !isCapsEnabled
        }
    }

    // Click handler to toggle Caps Lock
    val onCapsClickToLock = {
        isCapsLockEnabled = !isCapsLockEnabled
        isCapsEnabled = false
    }

    // Composable layout for the keyboard
    key(keys) {
        Column(
            modifier = Modifier
                .fillMaxWidth()
                .height(keyboardHeight)
                .background(colorResource(id = R.color.KeyboardBackground)),
        ) {
            // Adjust the keys based on whether the numbers row is shown
            keys = keys.drop(if (!showNumbersRow && currentLayout == LayoutState.Qwerty) 1 else 0)
                .toTypedArray()
            keys.forEachIndexed { rowIndex, row ->
                Row(
                    modifier = Modifier
                        .fillMaxWidth()
                        .padding(
                            horizontal = if (showNumbersRow) {
                                if (rowIndex == 2 && currentLayout == LayoutState.Qwerty) 16.dp
                                else dimensionResource(id = R.dimen.key_marginH).value.dp
                            } else {
                                if (rowIndex == 1 && currentLayout == LayoutState.Qwerty) 16.dp
                                else dimensionResource(id = R.dimen.key_marginH).value.dp
                            },
                            vertical = dimensionResource(id = R.dimen.key_marginV).value.dp

                        ),
                    horizontalArrangement = Arrangement.Center
                ) {
                    row.forEach { key ->
                        CustomKey(
                            key = key,
                            isCapsEnabled = isCapsEnabled,
                            isCapsLockEnabled = isCapsLockEnabled,
                            onKeyPressed = {
                                if (key.isCharacter) {
                                    imeService?.commitText(
                                        key = key,
                                        isCapsEnabled = isCapsEnabled,
                                        isCapsLockEnabled = isCapsLockEnabled
                                    )
                                    if (!isCapsLockEnabled && isCapsEnabled) {
                                        isCapsEnabled = false
                                    }
                                } else {
                                    imeService?.doSomethingWith(key, false)
                                }
                            },
                            onDragGesture = { dragAmount ->
                                if (key.labelMain == "Delete") {
                                    Log.d(MainActivity.TAG, "CustomKey: ${dragAmount.toString()}")
                                }
                            },
                            onLongKeyPressed = {
                                imeService?.doSomethingWith(key, true)
                                if (!isCapsLockEnabled && isCapsEnabled) {
                                    isCapsEnabled = false
                                }
                            },
                            onLongKeyPressedEnd = {
                                imeService?.longPressedStops()
                            },
                            modifier = Modifier
                                .padding(horizontal = 2.dp)
                                .weight(key.weight),
                            onLayoutSwitchClick = {
                                onLayoutSwitchClick()
                            },
                            onExtendedSymbolsSwitchClick = {
                                onExtendedSymbolsSwitchClick()
                            },
                            onNumbersSwitchClick = {
                                onNumbersSwitchClick()
                            },
                            onSymbolsLayoutSwitchClick = {
                                onSymbolsLayoutSwitchClick()
                            },
                            onCapsClick = {
                                onCapsClick()
                            },
                            onCapsClickToLock = {
                                onCapsClickToLock()
                            },
                            context = context,
                            vibrator = vibrator,
                            imeAction = imeActionType
                        )
                    }
                }
            }
        }
    }
}

@Preview
@Composable
fun CustomKeyboardPreview() {
    CustomKeyboard(
        vibrator = null,
        context = null,
    )
}


我正在寻找有关如何排查和解决弹出窗口延迟问题的建议或指导。有人遇到过类似的问题吗?我可以采取哪些步骤来优化应用程序和弹出窗口的性能?

任何见解或建议将不胜感激。

预先感谢您的协助。

android kotlin popup android-jetpack-compose android-softkeyboard
1个回答
0
投票

我不确定

Hander.postDelayed()
在 Jetpack Compose 的上下文中如何表现。您可能想尝试使用
CoroutineScope
LaunchedEffect
代替,例如这样:

val coroutineScope = rememberCoroutineScope()  // in top of Composable
// ...
onTap = {
    // Handle tap gesture on the key
    isPopupVisible.value = true
    coroutineScope.launch{
        delay(250)
        isPopupVisible.value = false
    }
    // ...
}

或者您也可以将处理延迟的责任转移到 PopupScreen 上,与此类似:

Composable
fun PopupScreen(
    label: String,
    isVisible: Boolean,
    onHide: () -> Unit
) {
    val popupWidth = dimensionResource(id = R.dimen.key_width)
    val popupHeight = dimensionResource(id = R.dimen.key_height)
    val cornerSize = dimensionResource(id = R.dimen.key_borderRadius)

    LaunchedEffect(Unit){
        delay(250)
        onHide()
    }

    // ...
}

在调用 Composable 中,对

onHide
回调做出相应的反应:

if (key.shouldShowPopUp) {
    // Display a pop-up screen if Clicked
    PopupScreen(
        label = popUpLabel,
        isVisible = isPopupVisible.value,
        onHide = { isPopupVisible.value = false }
    )
}

查看有关 LaunchedEffect

rememberCoroutineScope
文档

© www.soinside.com 2019 - 2024. All rights reserved.