I wrote a keyboard animation to make a control (any view) follow the slide-in and slide-out animation of the Softkeyboard. In Android 10 and below everything works fine.
I took the idea from this (meanwhile) outdated article at proandroiddev (written before Android 11 and unfortunately written in Kotlin and it also doesn't care much about deprecated methods), but translation to Java worked fine - the only Problem left is with Api30+
In Android 11, when the animation ends, the WindowInsetListener
(or the delayedTransition
, I don't know what causes the issue) seems to double the offset (i.e. adds the full height of the Softkeyboard a second time to the views' bounds).
See those two gif's showing the differences (Sorry for the quality - I am not too deep into video recording).
Same code running on both devices.
Android 10 on the left, Android 11 on the right
Here is the Listener:
class WindowInsetsListener implements View.OnApplyWindowInsetsListener {
private final KeyboardAnimationListener animationListener;
private int previousOffset = 0;
WindowInsetsListener(KeyboardAnimationListener animationListener) {
this.animationListener = animationListener;
}
@Override
public WindowInsets onApplyWindowInsets(View decorView, WindowInsets insets) {
WindowInsets result = insets;
int offset = insets.getSystemWindowInsetBottom() < insets.getStableInsetBottom() ?
insets.getSystemWindowInsetBottom() :
insets.getSystemWindowInsetBottom() - insets.getStableInsetBottom();
if (offset != previousOffset && animationListener.animate(decorView, offset)) {
previousOffset = offset;
result = consumeBottomInset(insets);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
return WindowInsets.CONSUMED;
}
}
return decorView.onApplyWindowInsets(result);
}
private WindowInsets consumeBottomInset(WindowInsets insets) {
Rect insetRect = new Rect(
insets.getSystemWindowInsetLeft(),
insets.getSystemWindowInsetTop(),
insets.getSystemWindowInsetRight(),
Math.min(insets.getSystemWindowInsetBottom(), insets.getStableInsetBottom()));
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
Insets newInsets = Insets.of(insetRect);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
return new WindowInsets.Builder().
setInsets(WindowInsets.Type.systemBars(), newInsets).build();
} else {
return new WindowInsets.Builder().
setSystemWindowInsets(newInsets).build();
}
} else {
return insets.replaceSystemWindowInsets(insetRect);
}
}
Here is my animate Method called from the Listener:
@Override
public boolean animate(@NonNull View view, int offset) {
ViewGroup group = getGroup(view); // searches the hierarchy up until it finds the group
if (group != null) {
TransitionManager.beginDelayedTransition(group, bounds);
setBottomPadding(view, offset);
return true;
}
return false;
}
protected void setBottomPadding(View view, int offset) {
view.setPadding(
view.getPaddingLeft(),
view.getPaddingTop(),
view.getPaddingRight(),
offset);
}
I tried to respect all deprecations through the versions. Especially this part of Android changed in every version. But even if I take out the "new" implementations for Api 29 and 30 and when I use the deprecated .replaceSystemWindowInsets
method, the effect stays the same.
Anyone has an idea about additional flags or something else that needs to be supplied in Api 30+?