我目前正在开发一个键盘应用程序,我已经实现了一个弹出窗口,当按下按键时应该立即显示。我正在使用 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,
)
}
我正在寻找有关如何排查和解决弹出窗口延迟问题的建议或指导。有人遇到过类似的问题吗?我可以采取哪些步骤来优化应用程序和弹出窗口的性能?
任何见解或建议将不胜感激。
预先感谢您的协助。
我不确定
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
的 文档。