(Создать пользовательский интерфейс) - клавиатура (IME) перекрывает содержимое приложения

Несколько дней назад я столкнулся с проблемой, когда часть моего обзора перекрывается клавиатурой.

Допустим, у нас есть 3 разных диалога (может быть любой контент), которые выглядят так:

Когда я хочу что-то написать, последний диалог закрывается клавиатурой:

И нет возможности увидеть, что написал пользователь. Вот мой код:

      @Composable
fun BuildWordsView(navController: NavController, sharedViewModel: SharedViewModel) {

        Column(
            modifier = Modifier
                .fillMaxWidth()
                .background(PrimaryLight)
                .fillMaxSize()

        ) {
            BuildWordsScreenContents()
        }

}

@Composable
fun BuildWordsScreenContents() {

    Column(
        Modifier
            .fillMaxSize()
            .padding(all = 16.dp)

    ) {

        val inputBoxModifier = Modifier
            .clip(RoundedCornerShape(10.dp))
            .background(Primary)
            .weight(12f)
            .wrapContentHeight()

        InputBlock("Dialog1", inputBoxModifier)
        Spacer(Modifier.weight(1f))
        InputBlock("Dialog2", inputBoxModifier)
        Spacer(Modifier.weight(1f))
        InputBlock("Dialog3", inputBoxModifier)
    }
}



@Composable
fun InputBlock(dialogText: String, inputBlockModifier: Modifier) {
    Column(modifier = inputBlockModifier) {
        Text(
            dialogText,
            fontSize = 30.sp,
            textAlign = TextAlign.Center,
            modifier = Modifier
                .fillMaxWidth()
                .wrapContentSize(Alignment.Center)
        )
        var text by remember { mutableStateOf("") }

        TextField(
            value = text,
            modifier = Modifier
                .fillMaxWidth()
                .wrapContentSize(Alignment.Center),
            onValueChange = { text = it },
            label = { Text("Label") }
        )
    }
}

Этот вопрос кажется похожим на мой, но ответы изменяют содержание представления, которого я хочу избежать:

Программная клавиатура перекрывает содержимое окна компоновки реактивного ранца

К настоящему времени я понял, как решить эту проблему, и делюсь своим подходом в качестве ответа.

3 ответа

Мой подход к решению этой проблемы заключается в использовании Insets для Jetpack Compose :

https://google.github.io/accompanist/insets/

Чтобы начать решать проблему, вам нужно добавить зависимость в gradle (текущая версия 0.22.0-rc).

      dependencies { 
    implementation "com.google.accompanist:accompanist-insets:0.22.0-rc"
}

Затем вам нужно обернуть свой контент в свою деятельность с помощью ProvideWindowInsets

      setContent {
    ProvideWindowInsets {
        YourTheme {
            //YOUR CONTENT HERE
        }
    }
}

Кроме того, вам нужно добавить следующую строку в вашу функцию onCreate():

WindowCompat.setDecorFitsSystemWindows(window, false)

Обновление: несмотря на то, что эта функция рекомендуется, по моему опыту, этот подход может не работать. Если вы столкнулись с какой-либо проблемой, вам может потребоваться удалить эту строку.

Теперь ваш проект настроен на использование вставок.

В следующих шагах я буду использовать код, который я предоставил в вопросе

Прежде всего, вам нужно обернуть основную колонку

ProvideWindowInsets(windowInsetsAnimationsEnabled = true)

Затем давайте немного изменим модификатор, добавив:

      .statusBarsPadding()
.navigationBarsWithImePadding()
.verticalScroll(rememberScrollState())

Как видите, хитрость в моем подходе заключается в использовании verticalScroll(). Окончательный код основного столбца должен выглядеть так:

      @Composable
fun BuildWordsView(navController: NavController, sharedViewModel: SharedViewModel) {

    ProvideWindowInsets(windowInsetsAnimationsEnabled = true) {

        Column(
            modifier = Modifier
                .fillMaxWidth()
                .background(PrimaryLight)
                .statusBarsPadding()
                .navigationBarsWithImePadding()
                .verticalScroll(rememberScrollState())
                .fillMaxSize()

        ) {
            BuildWordsScreenContents()
        }
    }
}

Теперь давайте изменим модификатор Column в fun BuildWordsScreenContents()

Основная модификация заключается в том, что мы предоставляем высоту нашего экрана:

.height(LocalConfiguration.current.screenHeightDp.dp)

Это означает, что высота нашего столбца идеально подходит для нашего экрана. Поэтому, когда клавиатура не открыта, столбец не будет прокручиваться

Есть полный код:

      @Composable
fun BuildWordsView(navController: NavController, sharedViewModel: SharedViewModel) {

    ProvideWindowInsets(windowInsetsAnimationsEnabled = true) {

        Column(
            modifier = Modifier
                .fillMaxWidth()
                .background(PrimaryLight)
                .statusBarsPadding()
                .navigationBarsWithImePadding()
                .verticalScroll(rememberScrollState())
                .fillMaxSize()

        ) {
            BuildWordsScreenContents()
        }
    }
}

@Composable
fun BuildWordsScreenContents() {

    Column(
        Modifier
            .height(LocalConfiguration.current.screenHeightDp.dp)
            .padding(all = 16.dp)

    ) {

        val inputBoxModifier = Modifier
            .clip(RoundedCornerShape(10.dp))
            .background(Primary)
            .weight(12f)
            .wrapContentHeight()

        InputBlock("Dialog1", inputBoxModifier)
        Spacer(Modifier.weight(1f))
        InputBlock("Dialog2", inputBoxModifier)
        Spacer(Modifier.weight(1f))
        InputBlock("Dialog3", inputBoxModifier)
    }
}





@Composable
fun InputBlock(dialogText: String, inputBlockModifier: Modifier) {
    Column(modifier = inputBlockModifier) {
        Text(
            dialogText,
            fontSize = 30.sp,
            textAlign = TextAlign.Center,
            modifier = Modifier
                .fillMaxWidth()
                .wrapContentSize(Alignment.Center)
        )
        var text by remember { mutableStateOf("") }

        TextField(
            value = text,
            modifier = Modifier
                .fillMaxWidth()
                .wrapContentSize(Alignment.Center),
            onValueChange = { text = it },
            label = { Text("Label") }
        )
    }
}

Окончательный код позволяет нам прокручивать представление вниз:

Важное примечание для API 30-

Для API ниже 30 вам необходимо изменить файл AndroidManifest.xml.

В <activity вам нужно добавить android:windowSoftInputMode="adjustResize"чтобы заставить его работать. Это не изменяет размер ваших компонентов, но обязательно, чтобы этот подход работал .

Манифест должен выглядеть так:

      <activity
    android:name=".MainActivity"
    android:windowSoftInputMode="adjustResize"

Не стесняйтесь давать мне любые советы, как я могу улучшить свой вопрос. Насколько я знаю, эта проблема так же стара, как Android, и я хотел создать краткое руководство, как с этим справиться. Удачного кодирования!

Вот мое решение с использованием экспериментальных функций в Compose 1.2.0.

В (:проект)

      ...
    ext {
        compose_version = '1.2.0-beta03'
    }
...

Вbuild.gradle(:приложение)

      ...
dependencies {
    implementation 'androidx.core:core-ktx:1.8.0'
    implementation "androidx.compose.ui:ui:$compose_version"
    implementation "androidx.compose.material:material:$compose_version"
    implementation "androidx.compose.ui:ui-tooling-preview:$compose_version"
    implementation "androidx.compose.foundation:foundation-layout:$compose_version"
...
}

ВAndroidManifest.xml

               <activity
            ...
            android:windowSoftInputMode="adjustResize" >

ВAuthScreen.kt

      @OptIn(ExperimentalFoundationApi::class, ExperimentalComposeUiApi::class)
@Composable
fun AuthScreen(

    val focusManager = LocalFocusManager.current
    val coroutineScope = rememberCoroutineScope()

    // Setup the handles to items to scroll to.
    val bringIntoViewRequesters = mutableListOf(remember { BringIntoViewRequester() })
    repeat(6) {
        bringIntoViewRequesters += remember { BringIntoViewRequester() }
    }
    val buttonViewRequester = remember { BringIntoViewRequester() }


    fun requestBringIntoView(focusState: FocusState, viewItem: Int) {
        if (focusState.isFocused) {
            coroutineScope.launch {
                delay(200) // needed to allow keyboard to come up first.
                if (viewItem >= 2) { // force to scroll to button for lower fields
                    buttonViewRequester.bringIntoView()
                } else {
                    bringIntoViewRequesters[viewItem].bringIntoView()
                }
            }
        }
    }

    Column(
        horizontalAlignment = Alignment.CenterHorizontally,
        verticalArrangement = Arrangement.Top,
        modifier = Modifier
            .fillMaxSize()
            .statusBarsPadding()
            .navigationBarsPadding()
            .imePadding()
            .padding(10.dp)
            .verticalScroll(rememberScrollState())

    ) {

        repeat(6) { viewItem ->
            Row(
                modifier = Modifier
                    .bringIntoViewRequester(bringIntoViewRequesters[viewItem]),
            ) {
                TextField(
                    value = "",
                    onValueChange = {},
                    keyboardOptions = KeyboardOptions(imeAction = ImeAction.Next),
                    keyboardActions = KeyboardActions(
                        onNext = { focusManager.moveFocus(FocusDirection.Down) }),
                    modifier = Modifier
                        .onFocusEvent { focusState ->
                            requestBringIntoView(focusState, viewItem)
                        },
                )
            }
        }


        Button(
            onClick = {},
            modifier = Modifier
                .bringIntoViewRequester(buttonViewRequester)
        ) {
            Text(text = "I'm Visible")
        }
    }
}

Попробуйте погуглить по таким ключевым словам: Modifier.statusBarsPadding(), systemBarsPadding(), navigationBarsPadding().

      class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        makeStatusBarTransparent()
        //WindowCompat.setDecorFitsSystemWindows(window, false)

        setContent {
            Box(
                Modifier
                    .background(Color.Blue)
                    .fillMaxSize()
                    .padding(top = 10.dp, bottom = 10.dp)
                    .statusBarsPadding() //systemBarsPadding
            ) {
                //Box(Modifier.background(Color.Green).navigationBarsPadding()) {
                Greeting("TopStart", Alignment.TopStart)
                Greeting("BottomStart", Alignment.BottomStart)
                Greeting("TopEnd", Alignment.TopEnd)
                Greeting("BottomEnd", Alignment.BottomEnd)
                //}
            }
        }

/*        setContent {
            MyComposeApp1Theme {
                // A surface container using the 'background' color from the theme
                Surface(modifier = Modifier.fillMaxSize(), color = Color.Red) {
                    Box(Modifier
                            .fillMaxSize()
                            .padding(top = 34.dp)
                    ) {
                        Greeting("Android")
                    }
                }
            }
        }*/
    }
}

@Composable
fun Greeting(name: String, contentAlignment: Alignment) {
    Box(
        modifier = Modifier.fillMaxSize(),
        contentAlignment = contentAlignment
    ) {
        Text(
            text = "Hello $name!",
            Modifier
                .background(color = Color.Cyan)
        )

    }
}

@Preview(showBackground = true)
@Composable
fun DefaultPreview() {
    MyComposeApp1Theme {
        Greeting("Android", Alignment.TopStart)
    }
}

@Suppress("DEPRECATION")
fun Activity.makeStatusBarTransparent() {
    window.apply {
        clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS)
        addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS)
        decorView.systemUiVisibility =
            View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR
        statusBarColor = android.graphics.Color.GREEN//android.graphics.Color.TRANSPARENT
    }
}

val Int.dp
    get() = TypedValue.applyDimension(
        TypedValue.COMPLEX_UNIT_DIP,
        toFloat(),
        Resources.getSystem().displayMetrics
    )
Другие вопросы по тегам