隐藏软件键盘在撰写中违反Kotlin流/频道事件收集

发布于 2025-01-18 07:52:56 字数 1614 浏览 2 评论 0原文

我正在尝试使用 Compose UI 隐藏 Android 应用程序中的软键盘。 ViewModel 通过 kotlin 协程通道发出一些事件:

private val _screenEvents = Channel<ScreenEvent>(capacity = Channel.UNLIMITED)
val screenEvents: Flow<ScreenEvent> = _screenEvents.receiveAsFlow()

事件的发送方式如下:

_screenEvents.trySend(event)

在 Compose 屏幕中,事件在 LaunchedEffect 中收集,任何隐藏键盘的方法都只能工作一次,不会收集连续的事件。

val keyboard = LocalSoftwareKeyboardController.current
val inputService = LocalTextInputService.current
val focusManager = LocalFocusManager.current
LaunchedEffect(Unit) {
    viewModel.screenEvents
        .collect { event ->
            when (event) {
                is ScreenEvent.CollapseSearchResults -> {
                    // keyboard?.hide()
                    // inputService?.hideSoftwareKeyboard()
                    focusManager.clearFocus()
                    bottomSheetState.collapse()
                }
                ...
            }
        }
}
TextField(value = "") {}

但如果我像这样交换线路:

bottomSheetState.collapse()
// keyboard?.hide()
// inputService?.hideSoftwareKeyboard()
focusManager.clearFocus()

一切都可以根据需要多次正常工作。但是折叠底板和隐藏键盘的动画是连续的,它不适合我。

有人可以向我解释一下问题是什么以及如何解决它吗?

编辑

如果 UI 中的 TextField 具有焦点并且显示软键盘,则会产生此问题。如果用户在动画播放时按住 BottomSheet,则效果相同。事实证明,BottomSheet 动画是可以取消的,并且在这种情况下会抛出 CancellationException。

最小、完整、可重现的示例: https://gist.github.com/Alektas/e86e75a596cb20797f5c9acac238e24f

I'm trying to hide the soft keyboard in an Android app with Compose UI.
There are events emitted by ViewModel through the kotlin coroutines channel:

private val _screenEvents = Channel<ScreenEvent>(capacity = Channel.UNLIMITED)
val screenEvents: Flow<ScreenEvent> = _screenEvents.receiveAsFlow()

Events are send like this:

_screenEvents.trySend(event)

In Compose screen, events are collected in LaunchedEffect and any way to hide keyboard only works once, consecutive events are not collected.

val keyboard = LocalSoftwareKeyboardController.current
val inputService = LocalTextInputService.current
val focusManager = LocalFocusManager.current
LaunchedEffect(Unit) {
    viewModel.screenEvents
        .collect { event ->
            when (event) {
                is ScreenEvent.CollapseSearchResults -> {
                    // keyboard?.hide()
                    // inputService?.hideSoftwareKeyboard()
                    focusManager.clearFocus()
                    bottomSheetState.collapse()
                }
                ...
            }
        }
}
TextField(value = "") {}

But if I swap the lines like this:

bottomSheetState.collapse()
// keyboard?.hide()
// inputService?.hideSoftwareKeyboard()
focusManager.clearFocus()

Everything works fine as many times as necessary. But the animations of collapsing bottom sheet and hiding keyboard are sequential and it doesn't suit me.

Can someone explain to me what is the problem and how can I solve it?

Edit

This issue is produced if TextField in UI has focus and soft keyboard is shown. The same if user holds BottomSheet while it's animation. It's turned out that BottomSheet animation is cancellable and it throws CancellationException in this cases.

Minimal, complete, reproducible example: https://gist.github.com/Alektas/e86e75a596cb20797f5c9acac238e24f

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

掌心的温暖 2025-01-25 07:52:56

当前,FlowCollector - BottomSheetState.Collapse是在启动>启动范围中启动的。因此,当崩溃流收集器中发生异常时,启动范围被停用。

尝试构图CoroutinesCope如下。

val scope = rememberCoroutineScope()

LaunchedEffect(Unit) {
    viewModel.screenEvents
        .collect { event ->
            when (event) {
                is ScreenEvent.CollapseSearchResults -> {
                    // keyboard?.hide()
                    // inputService?.hideSoftwareKeyboard()
                    focusManager.clearFocus()
                    scope.launch { bottomSheetState.collapse() }
                }
                ...
            }
        }
}

Currently, FlowCollector - bottomSheetState.collapse is launch in LaunchedEffect scope. thus, when an exception occurs in collapse flow collector, LaunchedEffect scope is deactivated.

Try Composition CoroutineScope as below.

val scope = rememberCoroutineScope()

LaunchedEffect(Unit) {
    viewModel.screenEvents
        .collect { event ->
            when (event) {
                is ScreenEvent.CollapseSearchResults -> {
                    // keyboard?.hide()
                    // inputService?.hideSoftwareKeyboard()
                    focusManager.clearFocus()
                    scope.launch { bottomSheetState.collapse() }
                }
                ...
            }
        }
}
东风软 2025-01-25 07:52:56

事实证明,当 FocusManagerTextField 清除焦点时,BottomSheet 动画是可以取消的,并抛出 CancellationException

在此处输入图像描述

作为一种解决方法,我现在用 try/catch 封装了折叠。如果有人提出更好的想法如何解决这个问题,我会很高兴。

focusManager.clearFocus()
try {
    bottomSheetState.collapse()
} catch (e: Exception) {
    e.printStackTrace()
}

It's turned out that BottomSheet animations are cancellable and throw CancellationException when FocusManager clears focus from TextField

enter image description here

As a workaround, I've wrapped collapsing with try/catch for now. I'd be glad If someone showed a better idea how to solve this problem.

focusManager.clearFocus()
try {
    bottomSheetState.collapse()
} catch (e: Exception) {
    e.printStackTrace()
}
只有影子陪我不离不弃 2025-01-25 07:52:56

这更多是关于重新组件的更多信息,每当您的状态更改构成函数开始重新组件时,启动效果 coroutinecontext将被取消,并且您的底部表show/hide将被中断。
要解决此问题,您可以尝试不可变的工作,例如以下

           try {
                    sheetState.show()
                }finally {
                    withContext(NonCancellable){
                        sheetState.show()
                    }
                }

是参考链接运行不可融合的块

it's more about re-composition it seems, whenever your state change compose function starts recomposing, LaunchEffect coroutineContext will be canceled and your bottom sheet show/hide will get interrupted.
to fix this you can try NonCancelable Job like following

           try {
                    sheetState.show()
                }finally {
                    withContext(NonCancellable){
                        sheetState.show()
                    }
                }

here is the reference link Run non-cancellable block

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文