6

我正在使用 BasicTextField。

当我开始编辑时,后退按钮变为隐藏键盘按钮(向下箭头)。

首先按下后退按钮隐藏键盘,但焦点仍在文本字段上。onFocusChanged和处理程序都BackPressHandler没有被调用。

第二次按下后退按钮清除焦点:onFocusChanged被调用和BackPressHandler未被调用。

BackHandler {
    println("BackPressHandler")
}
val valueState = remember { mutableStateOf(TextFieldValue(text = "")) }
BasicTextField(
    value = valueState.value,
    onValueChange = {
        valueState.value = it
    },
    modifier = Modifier
        .fillMaxWidth()
        .onFocusChanged {
            println("isFocused ${it.isFocused}")
        }
)

第三次 BackHandler 工作正常。只是用于测试,我不应该在这里需要它,它预计在第一次后退按钮点击后焦点会丢失

4

5 回答 5

8

当键盘隐藏时,聚焦文本字段存在撰写问题,阻止后退按钮关闭应用程序。它被标记为已修复,但将包含在未来的某个版本中,而不是1.0

但是,据我了解,在键盘被关闭后文本字段没有失去焦点的事实是 Android 上的预期行为(因为可能连接了键盘?我没有得到原因)。这也是它在旧的 android 布局中的工作方式

这对我来说似乎很奇怪,所以我使用了以下修饰符,当键盘消失时它会放弃焦点:

fun Modifier.clearFocusOnKeyboardDismiss(): Modifier = composed {
    var isFocused by remember { mutableStateOf(false) }
    var keyboardAppearedSinceLastFocused by remember { mutableStateOf(false) }
    if (isFocused) {
        val imeIsVisible = LocalWindowInsets.current.ime.isVisible
        val focusManager = LocalFocusManager.current
        LaunchedEffect(imeIsVisible) {
            if (imeIsVisible) {
                keyboardAppearedSinceLastFocused = true
            } else if (keyboardAppearedSinceLastFocused) {
                focusManager.clearFocus()
            }
        }
    }
    onFocusEvent {
        if (isFocused != it.isFocused) {
            isFocused = it.isFocused
            if (isFocused) {
                keyboardAppearedSinceLastFocused = false
            }
        }
    }
}

ps 你需要安装accompanist insets依赖LocalWindowInsets.current.ime

用法:

BasicTextField(
    value = valueState.value,
    onValueChange = {
        valueState.value = it
    },
    modifier = Modifier
        .clearFocusOnKeyboardDismiss()
)
于 2021-07-17T13:19:41.807 回答
3

我使用 Android 的树观察器找到了一个可以说更简单的解决方案。

您无需使用其他库或从布局中删除插图。

每当键盘隐藏时,它都会清除撰写中的焦点。

希望发布时不需要

class MainActivity : ComponentActivity() {

  var kbClosed: () -> Unit = {}
  var kbClosed: Boolean = false

  override fun onCreate(state: Bundle?) {
    super.onCreate(state)
    setContent {
      val focusManager = LocalFocusManager.current
      kbClosed = {
        focusManager.clearFocus()
      }
      MyComponent()
    }
    setupKeyboardDetection(findViewById<View>(android.R.id.content))
  }

  fun setupKeyboardDetection(contentView: View) {
    contentView.viewTreeObserver.addOnGlobalLayoutListener {
      val r = Rect()
      contentView.getWindowVisibleDisplayFrame(r)
      val screenHeight = contentView.rootView.height
      val keypadHeight = screenHeight - r.bottom
      if (keypadHeight > screenHeight * 0.15) { // 0.15 ratio is perhaps enough to determine keypad height.
        kbClosed = false
        // kb opened
      } else if(!kbClosed) {
        kbClosed = true
        kbClosed()
      }
    }
  }
}
于 2021-08-19T13:54:25.540 回答
3

@ mmm111mmm,只有你的方法对我有用。我想建议一种干净的方式来封装它。

  1. 创建这个可组合:
@Composable
fun AppKeyboardFocusManager() {
    val context = LocalContext.current
    val focusManager = LocalFocusManager.current
    DisposableEffect(key1 = context) {
        val keyboardManager = KeyBoardManager(context)
        keyboardManager.attachKeyboardDismissListener {
            focusManager.clearFocus()
        }
        onDispose {
            keyboardManager.release()
        }
    }
}
  1. 在应用程序级别的呼叫站点使用此可组合
setContent {
        AppKeyboardFocusManager()
        YouAppMaterialTheme {
          ...
        }
    }
  1. 使用@mmm111mmm 方法创建管理器
/***
 * Compose issue to be fixed in alpha 1.03
 * track from here : https://issuetracker.google.com/issues/192433071?pli=1
 * current work around
 */
class KeyBoardManager(context: Context) {

    private val activity = context as Activity
    private var keyboardDismissListener: KeyboardDismissListener? = null

    private abstract class KeyboardDismissListener(
        private val rootView: View,
        private val onKeyboardDismiss: () -> Unit
    ) : ViewTreeObserver.OnGlobalLayoutListener {
        private var isKeyboardClosed: Boolean = false
        override fun onGlobalLayout() {
            val r = Rect()
            rootView.getWindowVisibleDisplayFrame(r)
            val screenHeight = rootView.rootView.height
            val keypadHeight = screenHeight - r.bottom
            if (keypadHeight > screenHeight * 0.15) {
                // 0.15 ratio is right enough to determine keypad height.
                isKeyboardClosed = false
            } else if (!isKeyboardClosed) {
                isKeyboardClosed = true
                onKeyboardDismiss.invoke()
            }
        }
    }

    fun attachKeyboardDismissListener(onKeyboardDismiss: () -> Unit) {
        val rootView = activity.findViewById<View>(android.R.id.content)
        keyboardDismissListener = object : KeyboardDismissListener(rootView, onKeyboardDismiss) {}
        keyboardDismissListener?.let {
            rootView.viewTreeObserver.addOnGlobalLayoutListener(it)
        }
    }

    fun release() {
        val rootView = activity.findViewById<View>(android.R.id.content)
        keyboardDismissListener?.let {
            rootView.viewTreeObserver.removeOnGlobalLayoutListener(it)
        }
        keyboardDismissListener = null
    }
}
于 2021-09-26T07:27:16.113 回答
3

感谢这里的所有答案。从这里的答案中参考后,这是一个不使用任何库的解决方案

1.在View上创建一个扩展来判断键盘是否打开

fun View.isKeyboardOpen(): Boolean {
    val rect = Rect()
    getWindowVisibleDisplayFrame(rect);
    val screenHeight = rootView.height
    val keypadHeight = screenHeight - rect.bottom;
    return keypadHeight > screenHeight * 0.15
}

2.创建一个可观察的状态来确定键盘是否打开

这将监听 LocalView 上的全局布局更新,在每个事件中,我们检查键盘打开/关闭状态。

@Composable
fun rememberIsKeyboardOpen(): State<Boolean> {
    val view = LocalView.current

    return produceState(initialValue = view.isKeyboardOpen()) {
        val viewTreeObserver = view.viewTreeObserver
        val listener = OnGlobalLayoutListener { value = view.isKeyboardOpen() }
        viewTreeObserver.addOnGlobalLayoutListener(listener)

        awaitDispose { viewTreeObserver.removeOnGlobalLayoutListener(listener)  }
    }
}

3.创建修饰符

此修饰符将负责清除键盘可见/不可见事件的焦点。

fun Modifier.clearFocusOnKeyboardDismiss(): Modifier = composed {

    var isFocused by remember { mutableStateOf(false) }
    var keyboardAppearedSinceLastFocused by remember { mutableStateOf(false) }

    if (isFocused) {
        val isKeyboardOpen by rememberIsKeyboardOpen()

        val focusManager = LocalFocusManager.current
        LaunchedEffect(isKeyboardOpen) {
            if (isKeyboardOpen) {
                keyboardAppearedSinceLastFocused = true
            } else if (keyboardAppearedSinceLastFocused) {
                focusManager.clearFocus()
            }
        }
    }
    onFocusEvent {
        if (isFocused != it.isFocused) {
            isFocused = it.isFocused
            if (isFocused) {
                keyboardAppearedSinceLastFocused = false
            }
        }
    }
}

4.使用它

最后,将它与TextField可组合使用

BasicTextField(Modifier.clearFocusOnKeyboardDismiss())
于 2021-12-28T11:20:10.913 回答
0

在继承自 Application 的类中,添加以下代码以检测主 Activity 何时创建,并包含检测键盘何时显示或隐藏的代码:

import android.app.Activity
import android.app.Application
import android.content.res.Resources
import android.graphics.Rect
import android.os.Bundle
import android.util.DisplayMetrics
import androidx.compose.runtime.mutableStateOf

class App : Application() {

    private val activityLifecycleTracker: AppLifecycleTracker = AppLifecycleTracker()

    override fun onCreate() {
        super.onCreate()
        registerActivityLifecycleCallbacks(activityLifecycleTracker)
    }

    companion object {
        val onKeyboardClosed = mutableStateOf(false)
    }

    /**
     * Callbacks for handling the lifecycle of activities.
     */
    class AppLifecycleTracker : ActivityLifecycleCallbacks {

        override fun onActivityCreated(activity: Activity, p1: Bundle?) {
            val displayMetrics: DisplayMetrics by lazy { Resources.getSystem().displayMetrics }
            val screenRectPx = displayMetrics.run { Rect(0, 0, widthPixels, heightPixels) }

            // Detect when the keyboard closes.
            activity.window.decorView.viewTreeObserver.addOnGlobalLayoutListener {
                val r = Rect()
                activity.window.decorView.getWindowVisibleDisplayFrame(r)
                val heightDiff: Int = screenRectPx.height() - (r.bottom - r.top)

                onKeyboardClosed.value = (heightDiff <= 100)
            }
        }

        override fun onActivityStarted(activity: Activity) {
        }

        override fun onActivityResumed(activity: Activity) {
        }

        override fun onActivityPaused(p0: Activity) {
        }

        override fun onActivityStopped(activity: Activity) {
        }

        override fun onActivitySaveInstanceState(p0: Activity, p1: Bundle) {
        }

        override fun onActivityDestroyed(p0: Activity) {
        }
    }
}

添加以下修饰符扩展:

@Stable
fun Modifier.clearFocusOnKeyboardClose(focusManager: FocusManager): Modifier {
    if (App.onKeyboardClosed.value) {
        focusManager.clearFocus()
    }

    return this
}

在您的可组合文件中,添加对 FocusManager 的引用并将修饰符添加到您的 TextField:

@Composable
fun MyComposable() {
   val focusManager = LocalFocusManager.current
   
    OutlinedTextField(
                     modifier = Modifier.clearFocusOnKeyboardClose(focusManager = focusManager)
    )
}

每当键盘关闭时,TextField 都会清除它的焦点。

于 2021-11-21T09:18:14.127 回答