Android 软键盘丝滑切换(一)
在开发app中,软键盘弹出会有卡顿,闪一下的现象,会影响体验效果。为了实现微信软键盘与表情面板流畅的切换效果,查看了好多例子,查阅的很多资料,换了几种实现方式,都达不到流畅切换的效果,最终结合资料,参考网上的很多例子实现了想要的效果。(由于Demo中代码部分为Java代码,所以文中代码也由Kotlin与Java组成)Demo地址:https://github.com/xiaoyu00/KeyboardDemo
效果图
实现思路
在manifest android:windowSoftInputMode属性提供了系统自带的键盘弹出界面变化的几种方式,但都达不到流畅切换效果,更别说更复杂的微信键盘切换了,所以我们不用系统的方式,自己实现。
首先要实时获取到软键盘弹出时的属性:是否弹出,动画执行过程,高度等;然后在界面加载完成后计算带表情面板布局的高度;最后围绕键盘弹出收回的过程中,在键盘属性变化时对整个布局做平移处理。
实现
一、键盘监听
键盘的变化监听是整个实现的核心,Android手机的键盘所有属性是直接获取不到的。监听键盘变化只能用其它方式,网上有几种方方法,我这选择的是根view Insets监听方式(实现OnApplyWindowInsetsListener接口,设置ViewCompat.setWindowInsetsAnimationCallback)。
1.自定义RootViewDeferringInsetsCallback继承WindowInsetsAnimationCompat.Callback实现OnApplyWindowInsetsListener接口
public class RootViewDeferringInsetsCallback extends WindowInsetsAnimationCompat.Callback implements OnApplyWindowInsetsListener {
private View view;
private WindowInsetsCompat lastWindowInsets;
private boolean deferredInsets = false;
private int persistentInsetTypes;
private int deferredInsetTypes;
private boolean isPadding = false;
...//构造方法
@Override
public WindowInsetsCompat onApplyWindowInsets(View v, WindowInsetsCompat windowInsets) {
view = v;
lastWindowInsets = windowInsets;
if (isPadding) {
int types;
if (deferredInsets) {
types = deferredInsetTypes;
} else {
types = persistentInsetTypes | deferredInsetTypes;
}
Insets typeInsets = windowInsets.getInsets(types);
v.setPadding(typeInsets.left, typeInsets.top, typeInsets.right, typeInsets.bottom);
}
return WindowInsetsCompat.CONSUMED;
}
@Override
public void onPrepare(@NonNull WindowInsetsAnimationCompat animation) {
if ((animation.getTypeMask() & deferredInsetTypes) != 0) {
deferredInsets = true;
}
}
@NonNull
@Override
public WindowInsetsCompat onProgress(@NonNull WindowInsetsCompat insets, @NonNull List<WindowInsetsAnimationCompat> runningAnimations) {
return insets;
}
@Override
public void onEnd(@NonNull WindowInsetsAnimationCompat animation) {
if (deferredInsets && (animation.getTypeMask() & deferredInsetTypes) != 0) {
deferredInsets = false;
if (lastWindowInsets != null && view != null) {
ViewCompat.dispatchApplyWindowInsets(view, lastWindowInsets);
}
}
}
}
2.定义KeyBoardListener接口
public interface KeyBoardListener { void onAnimStart(int moveDistance); void onAnimDoing(int offsetX,int offsetY); void onAnimEnd(); }
3.自定义KeyBoardInsetsCallBack继承RootViewDeferringInsetsCallback
public class KeyBoardInsetsCallBack extends RootViewDeferringInsetsCallback { public static final int KEYBOARD_TYPE = WindowInsetsCompat.Type.ime(); public static final int SYSTEM_BAR_TYPE = WindowInsetsCompat.Type.systemBars(); private KeyBoardListener keyboardListener; public KeyBoardInsetsCallBack(int dispatchMode, KeyBoardListener keyboardListener) { super(dispatchMode); this.keyboardListener = keyboardListener; } public KeyBoardInsetsCallBack(KeyBoardListener keyboardListener) { this(DISPATCH_MODE_STOP, keyboardListener); } @NonNull @Override public WindowInsetsAnimationCompat.BoundsCompat onStart(@NonNull WindowInsetsAnimationCompat animation, @NonNull WindowInsetsAnimationCompat.BoundsCompat bounds) { keyboardListener.onAnimStart(bounds.getUpperBound().bottom - bounds.getLowerBound().bottom);//计算键盘弹出高度 return super.onStart(animation, bounds); } @NonNull @Override public WindowInsetsCompat onProgress(@NonNull WindowInsetsCompat insets, @NonNull List<WindowInsetsAnimationCompat> runningAnimations) { Insets typesInset = insets.getInsets(KEYBOARD_TYPE); Insets otherInset = insets.getInsets(SYSTEM_BAR_TYPE); Insets subtract = Insets.subtract(typesInset, otherInset); Insets diff = Insets.max(subtract, Insets.NONE); keyboardListener.onAnimDoing(diff.left - diff.right, diff.top - diff.bottom); return insets; } @Override public void onEnd(@NonNull WindowInsetsAnimationCompat animation) { keyboardListener.onAnimEnd(); } }
4.最后在Activity中界面加载完成后添加键盘监听
val keyBoardInsetsCallBack =KeyBoardInsetsCallBack(object : KeyBoardListener { override fun onAnimStart(moveDistance: Int) { ... } override fun onAnimDoing(offsetX: Int, offsetY: Int) { ... } override fun onAnimEnd() { ... } }) ViewCompat.setWindowInsetsAnimationCallback(window.decorView, keyBoardInsetsCallBack)
这样就完成了键盘弹出收回过程的监听。
二、计算并设置View大小
在界面加载完成后计算View高度,contentLayout为根Layout
contentLayout.viewTreeObserver .addOnGlobalLayoutListener(object : ViewTreeObserver.OnGlobalLayoutListener { override fun onGlobalLayout() { contentLayout.viewTreeObserver.removeOnGlobalLayoutListener(this) calculationLayoutSize() initKeyBoardListener() } })
private fun calculationLayoutSize() { val layoutParams = contentLayout.layoutParams as FrameLayout.LayoutParams val layoutParams2 = listLayout.layoutParams as LinearLayout.LayoutParams val cHeight: Int = contentLayout.height PANEL_HEIGHT=(cHeight*0.45).toInt()//PANEL_HEIGHT为表情面板高度 layoutParams2.height = cHeight listLayout.layoutParams = layoutParams2//listlayout为表情面板上面内容layout layoutParams.height = cHeight + PANEL_HEIGHT contentLayout.layoutParams = layoutParams }
整个布局xml
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/layout_content" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" tools:context=".MainActivity"> <LinearLayout android:id="@+id/layout_list" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> //滑动列表 <ScrollView android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="1" android:text="Hello World!"> ... </ScrollView> // 输入框 <LinearLayout android:id="@+id/layout_edit" android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal" android:paddingVertical="6dp" android:paddingHorizontal="16dp"> <EditText android:id="@+id/et_input" android:layout_width="0dp" android:layout_height="36dp" android:layout_weight="1" android:background="@drawable/msg_editor_border" /> <ImageView android:id="@+id/face_btn" android:layout_width="26.88dp" android:layout_height="26.88dp" android:layout_margin="5dp" android:scaleType="fitXY" android:src="@drawable/action_face_selector" /> </LinearLayout> </LinearLayout> <LinearLayout android:id="@+id/view_panel" android:layout_width="match_parent" android:layout_height="match_parent" android:background="@color/grey" android:gravity="center" android:orientation="vertical"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:textSize="18sp" android:text="这是表情面板" /> </LinearLayout> </LinearLayout>
接下来在键盘弹出收起过程中对根布局做平移处理。
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· [翻译] 为什么 Tracebit 用 C# 开发
· Deepseek官网太卡,教你白嫖阿里云的Deepseek-R1满血版
· 2分钟学会 DeepSeek API,竟然比官方更好用!
· .NET 使用 DeepSeek R1 开发智能 AI 客户端
· 刚刚!百度搜索“换脑”引爆AI圈,正式接入DeepSeek R1满血版