В настоящее время у нас есть приложение со следующими требованиями
android:windowSoftInputMode = "adjustPan"
ViewCompat.setWindowInsetsAnimationCallback
и ViewCompat.setOnApplyWindowInsetsListener
для взаимодействия с видимостью мягкой клавиатуры с плавной анимацией.Вот наш код при взаимодействии с видимостью мягкой клавиатуры. Это работает очень хорошо в случае, когда наш EditText
не прокручивается.
Анимация прошла довольно хорошо, когда клавиатура показывается и скрывается.
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);
}
}
<?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
можно прокручивать.
Кто-нибудь знает, какова основная причина этой проблемы и как мы можем ее решить?
Демо для демонстрации такой проблемы можно скачать с https://github.com/yccheok/programming-issue/tree/main/jumpy
Использование как adjustPan
, так и WindowInsetsAnimation
кажется чрезмерно анимирующим контент.
AdjustPan - Размер главного окна активности не изменяется, чтобы освободить место для мягкой клавиатуры. Вместо этого содержимое окна автоматически панорамируется, так что текущий фокус никогда не перекрывается клавиатурой, и пользователи всегда могут видеть, что они печатают. Обычно это менее желательно, чем изменение размера, потому что пользователю может потребоваться закрыть виртуальную клавиатуру, чтобы получить доступ к скрытым частям окна и взаимодействовать с ними.
Это означает, что действие выдвигает свою верхнюю часть до тех пор, пока не станет видимым место для виджета EditText
(или его редактируемой части), чтобы пользователь мог видеть, что он печатает.
И я верю, что разработчики Google предоставили API вставок, чтобы рано или поздно отказаться от устаревших громоздких вещей; например, недавняя настройка adjustResize
программно теперь устарела с уровня API 30 и заменена встроенным API.
Для дополнительной проверки неудобства между adjustPan
и WindowInsetsAnimation
, попробуйте следующие несколько сценариев в своем образце репо:
android:windowSoftInputMode = "adjustPan"
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
помогает. Спасибо.