Android软键盘弹起交互的几种方案,如何做效果最丝滑?
作者:newki
链接:
https://juejin.cn/post/7150453629021847566
本文由作者授权发布。
public final class Keyboard1Utils {public static int sDecorViewInvisibleHeightPre;
private static ViewTreeObserver.OnGlobalLayoutListener onGlobalLayoutListener;private Keyboard1Utils() {
}private static int sDecorViewDelta = 0;
private static int getDecorViewInvisibleHeight(final Activity activity) {
final View decorView = activity.getWindow().getDecorView();
if (decorView == null) return sDecorViewInvisibleHeightPre;
final Rect outRect = new Rect();
decorView.getWindowVisibleDisplayFrame(outRect);int delta = Math.abs(decorView.getBottom() - outRect.bottom);
if (delta <= getNavBarHeight()) {
sDecorViewDelta = delta;
return 0;
}
return delta - sDecorViewDelta;
}public static void registerKeyboardHeightListener(final Activity activity, final KeyboardHeightListener listener) {
final int flags = activity.getWindow().getAttributes().flags;if ((flags & WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS) != 0) {
activity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS);
}final FrameLayout contentView = activity.findViewById(android.R.id.content);
sDecorViewInvisibleHeightPre = getDecorViewInvisibleHeight(activity);ViewTreeObserver.OnGlobalLayoutListener onGlobalLayoutListener = new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
int height = getDecorViewInvisibleHeight(activity);
if (sDecorViewInvisibleHeightPre != height) {listener.onKeyboardHeightChanged(height);
sDecorViewInvisibleHeightPre = height;
}
}
};
contentView.getViewTreeObserver()
.addOnGlobalLayoutListener(onGlobalLayoutListener);
}public static void unregisterKeyboardHeightListener(Activity activity) {
onGlobalLayoutListener = null;
View contentView = activity.getWindow().getDecorView().findViewById(android.R.id.content);
if (contentView == null) return;
contentView.getViewTreeObserver().removeGlobalOnLayoutListener(onGlobalLayoutListener);
}private static int getNavBarHeight() {
Resources res = Resources.getSystem();
int resourceId = res.getIdentifier("navigation_bar_height", "dimen", "android");
if (resourceId != 0) {
return res.getDimensionPixelSize(resourceId);
} else {
return 0;
}
}public interface KeyboardHeightListener {
void onKeyboardHeightChanged(int height);
}
}
override fun init() {Keyboard1Utils.registerKeyboardHeightListener(this) {
YYLogUtils.w("当前的软键盘高度:$it")
}
}
public class KeyboardHeightUtils extends PopupWindow {private KeyboardHeightListener mListener;
private View popupView;
private View parentView;
private Activity activity;public KeyboardHeightUtils(Activity activity) {
super(activity);
this.activity = activity;LayoutInflater inflator = (LayoutInflater) activity.getSystemService(Activity.LAYOUT_INFLATER_SERVICE);
this.popupView = inflator.inflate(R.layout.keyboard_popup_window, null, false);
setContentView(popupView);setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE | WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE);
setInputMethodMode(PopupWindow.INPUT_METHOD_NEEDED);parentView = activity.findViewById(android.R.id.content);
setWidth(0);
setHeight(WindowManager.LayoutParams.MATCH_PARENT);popupView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
if (popupView != null) {
handleOnGlobalLayout();
}
}
});
}public void start() {
parentView.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() {
@Override
public void onViewAttachedToWindow(View view) {
if (!isShowing() && parentView.getWindowToken() != null) {
setBackgroundDrawable(new ColorDrawable(0));
showAtLocation(parentView, Gravity.NO_GRAVITY, 0, 0);
}
}@Override
public void onViewDetachedFromWindow(View view) {
}
});}
public void close() {
this.mListener = null;
dismiss();
}public void registerKeyboardHeightListener(KeyboardHeightListener listener) {
this.mListener = listener;
}private void handleOnGlobalLayout() {
Point screenSize = new Point();
activity.getWindowManager().getDefaultDisplay().getSize(screenSize);Rect rect = new Rect();
popupView.getWindowVisibleDisplayFrame(rect);int keyboardHeight = screenSize.y - rect.bottom;
notifyKeyboardHeightChanged(keyboardHeight);
}private void notifyKeyboardHeightChanged(int height) {
if (mListener != null) {
mListener.onKeyboardHeightChanged(height);
}
}public interface KeyboardHeightListener {
void onKeyboardHeightChanged(int height);
}}
override fun init() {keyboardHeightUtils = KeyboardHeightUtils(this)
keyboardHeightUtils.registerKeyboardHeightListener {
YYLogUtils.w("第二种方式:当前的软键盘高度:$it")
}
keyboardHeightUtils.start()}
override fun onDestroy() {
super.onDestroy()
Keyboard1Utils.unregisterKeyboardHeightListener(this)keyboardHeightUtils.close();
}
ViewCompat.setWindowInsetsAnimationCallback(window.decorView, object : WindowInsetsAnimationCompat.Callback(DISPATCH_MODE_STOP) {
override fun onProgress(insets: WindowInsetsCompat, runningAnimations: MutableList<WindowInsetsAnimationCompat>): WindowInsetsCompat { val isVisible = insets.isVisible(WindowInsetsCompat.Type.ime())
val keyboardHeight = insets.getInsets(WindowInsetsCompat.Type.ime()).bottom
//当前是否展示
YYLogUtils.w("isVisible = $isVisible")
//当前的高度进度回调
YYLogUtils.w("keyboardHeight = $keyboardHeight")
return insets
}
})
ViewCompat.getWindowInsetsController(findViewById(android.R.id.content))?.apply {
show(WindowInsetsCompat.Type.ime())
}
fun addKeyBordHeightChangeCallBack(view: View, onAction: (height: Int) -> Unit) {
var posBottom: Int
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
val cb = object : WindowInsetsAnimation.Callback(DISPATCH_MODE_STOP) {
override fun onProgress(
insets: WindowInsets,
animations: MutableList<WindowInsetsAnimation>
): WindowInsets {
posBottom = insets.getInsets(WindowInsets.Type.ime()).bottom +
insets.getInsets(WindowInsets.Type.systemBars()).bottom
onAction.invoke(posBottom)
return insets
}
}
view.setWindowInsetsAnimationCallback(cb)
} else {
ViewCompat.setOnApplyWindowInsetsListener(view) { _, insets ->
posBottom = insets.getInsets(WindowInsetsCompat.Type.ime()).bottom +
insets.getInsets(WindowInsetsCompat.Type.systemBars()).bottom
onAction.invoke(posBottom)
insets
}
}
}
public final class Keyboard4Utils {public static int sDecorViewInvisibleHeightPre;
private static ViewTreeObserver.OnGlobalLayoutListener onGlobalLayoutListener;private Keyboard4Utils() {
}private static int sDecorViewDelta = 0;
private static int getDecorViewInvisibleHeight(final Activity activity) {
final View decorView = activity.getWindow().getDecorView();
if (decorView == null) return sDecorViewInvisibleHeightPre;
final Rect outRect = new Rect();
decorView.getWindowVisibleDisplayFrame(outRect);int delta = Math.abs(decorView.getBottom() - outRect.bottom);
if (delta <= getNavBarHeight()) {
sDecorViewDelta = delta;
return 0;
}
return delta - sDecorViewDelta;
}public static void registerKeyboardHeightListener(final Activity activity, final KeyboardHeightListener listener) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
invokeAbove31(activity, listener);
} else {
invokeBelow31(activity, listener);
}}
@RequiresApi(api = Build.VERSION_CODES.R)
private static void invokeAbove31(Activity activity, KeyboardHeightListener listener) {activity.getWindow().getDecorView().setWindowInsetsAnimationCallback(new WindowInsetsAnimation.Callback(DISPATCH_MODE_STOP) {
@NonNull
@Override
public WindowInsets onProgress(@NonNull WindowInsets windowInsets, @NonNull List<WindowInsetsAnimation> list) {int height = windowInsets.getInsets(WindowInsetsCompat.Type.ime()).bottom;
listener.onKeyboardHeightChanged(height);
return windowInsets;
}
});
}private static void invokeBelow31(Activity activity, KeyboardHeightListener listener) {
final int flags = activity.getWindow().getAttributes().flags;
if ((flags & WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS) != 0) {
activity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS);
}final FrameLayout contentView = activity.findViewById(android.R.id.content);
sDecorViewInvisibleHeightPre = getDecorViewInvisibleHeight(activity);onGlobalLayoutListener = new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
int height = getDecorViewInvisibleHeight(activity);
if (sDecorViewInvisibleHeightPre != height) {listener.onKeyboardHeightChanged(height);
sDecorViewInvisibleHeightPre = height;
}
}
};contentView.getViewTreeObserver().addOnGlobalLayoutListener(onGlobalLayoutListener);
}public static void unregisterKeyboardHeightListener(Activity activity) {
onGlobalLayoutListener = null;
View contentView = activity.getWindow().getDecorView().findViewById(android.R.id.content);
if (contentView == null) return;
contentView.getViewTreeObserver().removeGlobalOnLayoutListener(onGlobalLayoutListener);
}private static int getNavBarHeight() {
Resources res = Resources.getSystem();
int resourceId = res.getIdentifier("navigation_bar_height", "dimen", "android");
if (resourceId != 0) {
return res.getDimensionPixelSize(resourceId);
} else {
return 0;
}
}public interface KeyboardHeightListener {
void onKeyboardHeightChanged(int height);
}
}
Keyboard1Utils.registerKeyboardHeightListener(this) { YYLogUtils.w("当前的软键盘高度:$it")
updateVoiceIcon(it)
}
//更新语音图标的位置
private fun updateVoiceIcon(height: Int) {
mIvVoice.updateLayoutParams<FrameLayout.LayoutParams> {
bottomMargin = height
}
}
Keyboard3Utils.registerKeyboardHeightListener(this) {
YYLogUtils.w("第三种方式:当前的软键盘高度:$it")
updateVoiceIcon(it)
}//更新语音图标的位置
private fun updateVoiceIcon(height: Int) {mIvVoice.updateLayoutParams<FrameLayout.LayoutParams> {
bottomMargin = height
}
}