Как избежать «дерганой» проблемы при взаимодействии с видимостью мягкой клавиатуры

В настоящее время у нас есть приложение со следующими требованиями

  1. Обязательно используйте android:windowSoftInputMode = "adjustPan"
  2. Используйте ViewCompat.setWindowInsetsAnimationCallback и ViewCompat.setOnApplyWindowInsetsListener для взаимодействия с видимостью мягкой клавиатуры с плавной анимацией.

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

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


MainActivity.java

public class MainActivity extends AppCompatActivity {
    EditText editText;
    LinearLayout toolbar;
    FrameLayout keyboardView;

    private int systemBarsHeight = 0;
    private int keyboardHeightWhenVisible = 0;
    private boolean keyboardVisible = false;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        editText = findViewById(R.id.edit_text);
        toolbar = findViewById(R.id.toolbar);
        keyboardView = findViewById(R.id.keyboard_view);

        final View rootView = getWindow().getDecorView().getRootView();

        ViewCompat.setOnApplyWindowInsetsListener(rootView, (v, insets) -> {
            boolean imeVisible = insets.isVisible(WindowInsetsCompat.Type.ime());

            systemBarsHeight = insets.getInsets(WindowInsetsCompat.Type.systemBars()).bottom;

            keyboardVisible = imeVisible;

            if (keyboardVisible) {
                keyboardHeightWhenVisible = insets.getInsets(WindowInsetsCompat.Type.ime()).bottom;
            }

            // https://stackoverflow.com/questions/75325095/how-to-use-windowinsetscompat-correctly-to-listen-to-keyboard-height-change-in-a
            return ViewCompat.onApplyWindowInsets(v, insets);
        });

        WindowInsetsAnimationCompat.Callback callback = new WindowInsetsAnimationCompat.Callback(
                WindowInsetsAnimationCompat.Callback.DISPATCH_MODE_STOP
        ) {
            @NonNull
            @Override
            public WindowInsetsCompat onProgress(@NonNull WindowInsetsCompat insets, @NonNull List<WindowInsetsAnimationCompat> runningAnimations) {
                // Find an IME animation.
                WindowInsetsAnimationCompat imeAnimation = null;
                for (WindowInsetsAnimationCompat animation : runningAnimations) {
                    if ((animation.getTypeMask() & WindowInsetsCompat.Type.ime()) != 0) {
                        imeAnimation = animation;
                        break;
                    }
                }
                if (imeAnimation != null) {
                    int keyboardViewHeight;
                    if (keyboardVisible) {
                        keyboardViewHeight = (int) (keyboardHeightWhenVisible * imeAnimation.getInterpolatedFraction()) - systemBarsHeight;
                    } else {
                        keyboardViewHeight = (int) (keyboardHeightWhenVisible * (1.0-imeAnimation.getInterpolatedFraction())) - systemBarsHeight;
                    }

                    keyboardViewHeight = Math.max(0, keyboardViewHeight);

                    ViewGroup.LayoutParams params = keyboardView.getLayoutParams();
                    params.height = keyboardViewHeight;
                    keyboardView.setLayoutParams(params);

                    Log.i("CHEOK", "keyboardVisible = " + keyboardVisible + ", keyboardViewHeight = " + keyboardViewHeight);
                }
                return insets;

            }
        };

        ViewCompat.setWindowInsetsAnimationCallback(rootView, callback);
    }
}

Activity_main.xml

<?xml version = "1.0" encoding = "utf-8"?>
<LinearLayout xmlns:android = "http://schemas.android.com/apk/res/android"
    xmlns:tools = "http://schemas.android.com/tools"
    android:layout_width = "match_parent"
    android:layout_height = "match_parent"
    android:orientation = "vertical"
    tools:context = ".MainActivity">

    <EditText
        android:id = "@+id/edit_text"

        android:padding = "16dp"
        android:scrollbars = "vertical"
        android:layout_width = "match_parent"
        android:layout_height = "0dp"
        android:layout_weight = "1"
        android:gravity = "top" />

    <LinearLayout
        android:id = "@+id/toolbar"

        android:layout_width = "match_parent"
        android:layout_height = "48dp"
        android:orientation = "horizontal"
        android:background = "#ffff00" />

    <FrameLayout
        android:id = "@+id/keyboard_view"

        android:background = "#ff0000"
        android:layout_width = "match_parent"
        android:layout_height = "0dp" />
</LinearLayout>

Вот результат.

Когда EditText не прокручивается


Однако наше приложение становится «дерганым», когда содержимое EditText можно прокручивать.

Когда EditText можно прокручивать, наше приложение становится «дерганым».


Кто-нибудь знает, какова основная причина этой проблемы и как мы можем ее решить?

Демо для демонстрации такой проблемы можно скачать с https://github.com/yccheok/programming-issue/tree/main/jumpy

Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
3
0
129
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Использование как adjustPan, так и WindowInsetsAnimation кажется чрезмерно анимирующим контент.

AdjustPan - Размер главного окна активности не изменяется, чтобы освободить место для мягкой клавиатуры. Вместо этого содержимое окна автоматически панорамируется, так что текущий фокус никогда не перекрывается клавиатурой, и пользователи всегда могут видеть, что они печатают. Обычно это менее желательно, чем изменение размера, потому что пользователю может потребоваться закрыть виртуальную клавиатуру, чтобы получить доступ к скрытым частям окна и взаимодействовать с ними.

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

И я верю, что разработчики Google предоставили API вставок, чтобы рано или поздно отказаться от устаревших громоздких вещей; например, недавняя настройка adjustResize программно теперь устарела с уровня API 30 и заменена встроенным API.

Для дополнительной проверки неудобства между adjustPan и WindowInsetsAnimation, попробуйте следующие несколько сценариев в своем образце репо:

  1. Удалить android:windowSoftInputMode = "adjustPan"
  2. Удалить ViewCompat.setWindowInsetsAnimationCallback(rootView, callback);

Любой сценарий будет работать исключительно без необходимости беспокоиться о прыгающем/прыгучем макете. Но в случае сценария нет. 1 отображается красный вид, потому что область активности не занимает весь экран окна (для этого требуется полноэкранное приложение ) с WindowCompat.setDecorFitsSystemWindows(getWindow(), false);. В этом руководстве подробно рассматриваются различные аспекты API вставок.

Я думаю, что причиной такого скачкообразного поведения является то, что adjustPan пытается выполнять свою работу, когда отображается клавиатура; но в конечном итоге жесткое кодирование полей в WindowInsetsAnimation выигрывает раунд и заставляет макет вернуться в норму в конце анимации.

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

Или, по крайней мере, оставьте его в файле манифеста, но отключите его непосредственно перед запуском анимации и снова включите его в конце анимации (т. е. отключите его поведение панорамирования во время анимации) с помощью обратных вызовов onPrepare() и onEnd():

WindowInsetsAnimationCompat
        .Callback callback = new WindowInsetsAnimationCompat.Callback(
        WindowInsetsAnimationCompat.Callback.DISPATCH_MODE_STOP
) {

    @Override
    public void onPrepare(@NonNull WindowInsetsAnimationCompat animation) {
        super.onPrepare(animation);
        // disable adjustPan
        getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING);
    }

    @Override
    public void onEnd(@NonNull WindowInsetsAnimationCompat animation) {
        super.onEnd(animation);
        getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN);
    }

    @NonNull
    @Override
    public WindowInsetsCompat onProgress(@NonNull WindowInsetsCompat insets, @NonNull List<WindowInsetsAnimationCompat> runningAnimations) {
        // ... code is omitted for simplicity 
    }
};

Но убедитесь, что у вас также есть полноэкранное приложение:

WindowCompat.setDecorFitsSystemWindows(getWindow(), false);

Переключение в полноэкранное приложение не помогает. Однако использование setSoftInputMode в onPrepare и onEnd помогает. Спасибо.

Cheok Yan Cheng 20.02.2023 08:06

Другие вопросы по теме