#android #android-jetpack-compose
Вопрос:
Я использую базовое текстовое поле.
Когда я начинаю редактирование, кнопка «Назад» становится кнопкой «Скрыть клавиатуру» (стрелка вниз).
Первое нажатие кнопки «Назад» скрывает клавиатуру, но фокус по-прежнему сосредоточен на текстовом поле. И 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}")
}
)
В третий раз бэкхендлер работает нормально. Просто использовал его для тестирования, он мне здесь не нужен, он ожидал, что фокус потеряется после первого нажатия кнопки «Назад».
Комментарии:
1. Согласно примечаниям к выпуску, это исправлено в версии 1.1.0 jetc.dev/issues/077.html
2. @mmm111mmm из разговора в выпуске «сочинение » я понимаю, что после исправления вторая обратная сторона закроет приложение, но отключение клавиатуры все равно не очистит фокус, что все еще не то, что я ожидаю в своем приложении
Ответ №1:
Существует проблема с составлением сфокусированного текстового поля, из-за которой кнопка «Назад» не позволяет закрыть приложение, когда клавиатура скрыта. Он помечен как исправленный, но будет включен в какой-нибудь будущий выпуск, а не в 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
}
}
}
}
p.s. Вам необходимо установить зависимость «Вставки аккомпаниатора» для LocalWindowInsets.current.ime
Использование:
BasicTextField(
value = valueState.value,
onValueChange = {
valueState.value = it
},
modifier = Modifier
.clearFocusOnKeyboardDismiss()
)
Комментарии:
1. На самом деле, я не могу заставить это работать
LocalWindowInsets.current.ime.isVisible
, всегда возвращает false, когда я открываю и закрываю программную клавиатуру. По крайней мере, в эмуляторе.2. @mmm111mmm вы следили
WindowCompat.setDecorFitsSystemWindows(window, false)
за примечаниемaccompanist insets
?3. Спасибо. И я добавил
ProvideWindowInsets
. Теперь моя проблема в том, чтобы расположить мой экран, теперь он не учитывает строку состояния и нижнюю панель навигации…4. @mmm111mmm если у вас нет плана взаимодействия с ними, просто добавьте
systemBarsPadding
для всего приложения что-то вроде этого . В другом случае вам нужно добавить это дополнение, когда это необходимо, к каждому конкретному экрану5. Да, это работает, но теперь, когда я открываю клавиатуру с текстовым полем, прикрепленным к нижней части экрана, текстовое поле теперь скрыто клавиатурой, как я использую
android:windowSoftInputMode="adjustResize
. Это единственное рабочее решение, которое я могу найти. Но я чувствую, что это проблема с составлением, и сейчас это слишком сложно: удалите вставки окон, чтобы вы могли обнаруживать открытие и закрытие клавиатуры, чтобы расфокусировать текстовое поле, а также вам нужно разработать приложение без вставок.
Ответ №2:
Я нашел, возможно, более простое решение, используя древовидный наблюдатель 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()
}
}
}
}
Ответ №3:
@ммм111ммм, только твой подход сработал для меня. Я хотел бы предложить чистый способ его инкапсуляции.
- Создайте этот составной :
@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()
}
}
}
- Используйте это составное устройство на сайте вызова один раз на уровне приложения
setContent {
AppKeyboardFocusManager()
YouAppMaterialTheme {
...
}
}
- Создайте менеджера с помощью подхода @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
}
}
Ответ №4:
Спасибо за все ответы здесь. Взяв ссылку на ответы здесь, вот решение без использования какой-либо библиотеки
1. Создайте расширение в режиме просмотра, чтобы определить, открыта клавиатура или нет
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())
Ответ №5:
В классе, который наследуется от приложения, добавьте следующий код, чтобы определить, когда создается основное действие, и включите код, который определяет, когда клавиатура отображается или скрыта:
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 и добавьте модификатор в текстовое поле:
@Composable
fun MyComposable() {
val focusManager = LocalFocusManager.current
OutlinedTextField(
modifier = Modifier.clearFocusOnKeyboardClose(focusManager = focusManager)
)
}
Текстовое поле будет очищаться от фокуса всякий раз, когда клавиатура закрыта.