问题:你是否遇到过这种情况
好不简单精心画好了一个页面,却发现半路出现了一个键盘。今天要说的问题就是在某些情况下,键盘会挡住一些重要的控件,影响用户体验。
解决思路
解决思路很简单:监听键盘弹出事件,然后做相应的处理。那么问题又来了,android并没有直接的键盘弹出事件的监听。(针对登录界面,处理方式有很多种。比如我最开始用的好像是ScrollView,不过现在我试了下没成功,可能是某些地方没设置好。或者改变布局等等,反正一大堆的解决方式。本文主要介绍一种类似监听键盘弹出的方法,然后移动控件。当然,你不仅可以移动输入框,按钮等,还可以缩放logo等动画。)
既然不能直接获得软键盘弹出的监听,那我们可以变向,软键盘弹出后,activity的可视化区域会变小,我们只要判断可视化区域的底部高度与原始的可视化区域底部高度的大小,就能知道键盘是否弹出了。(百度上很多在onLayout中进行判断,但是我没有成功,后来发现可以在onResume中进行判断)
直接撸代码(重写activity的onResume方法)
@Override
protected void onResume() {
super.onResume();
getWindow().getDecorView().addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
@Override
public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) {
//获取窗口可见区域,虚拟键盘会影响getWindowVisibleDisplayFrame()执行结果outRect中的bottom属性的值。如果窗口高度是MATCH_PARENT的,则rect中的bottom属性的值将等于屏幕高度减去虚拟键盘的高度。
Rect rect = new Rect();
getWindow().getDecorView().getWindowVisibleDisplayFrame(rect);
//记录初始的登录框view的y值(这里试一下屏蔽前几次onresume,加载activity的时候会onresume,具体自己去控制)
if(isFirstEnter<1){
isFirstEnter++;
int[] logoXy = new int[2];
view.getLocationOnScreen(logoXy);
startY = logoXy[1];
return;
}
//记录当前控件的位置
int[] location = new int[2];
view.getLocationOnScreen(location);
//rect.bottom:窗口可见区域的bottom
if(bottom!=0 && oldBottom!=0 && bottom - rect.bottom <= 0){
//输入法隐藏了
Toast.makeText(LoginActivity.this, "隐藏", Toast.LENGTH_SHORT).show();
//判断y是不是小于初始y,是则需要执行动画(修改输入框的时候会有bug)
if(location[1]<startY){
//输入法隐藏,view下移动
ObjectAnimator anim = ObjectAnimator.ofFloat(view, "translationY", -400f,0.0f);
anim.setDuration(300);
anim.start();
}
}else {
Toast.makeText(LoginActivity.this, "弹出", Toast.LENGTH_SHORT).show();
//判断y是不是y是不是小于初始y,是则不需要执行动画
if(location[1]==startY){
//输入法弹出了,view上移动
//上移动view(参数:动画控件,执行什么动画,起始位子,0.0标识当前位置,后面都是移动距离,可以跟多个,正标识下移动,负标表示上移动)
ObjectAnimator anim = ObjectAnimator.ofFloat(view, "translationY", 0.0f, -400f);
anim.setDuration(300);
anim.start();
}
}
}
});
}
布局代码
<?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="com.example.admin.myapplication.LoginActivity">
<View
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1"/>
<LinearLayout
android:id="@+id/ll_all"
android:orientation="vertical"
android:gravity="center"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<EditText
android:id="@+id/et_no"
android:background="@drawable/bg_linearlayout_mormal"
android:layout_width="match_parent"
android:layout_marginLeft="10dp"
android:layout_marginRight="10dp"
android:layout_height="40dp"
android:hint="第一个输入框" />
<EditText
android:id="@+id/et_re"
android:background="@drawable/bg_linearlayout_mormal"
android:layout_width="match_parent"
android:layout_height="40dp"
android:layout_marginLeft="10dp"
android:layout_marginRight="10dp"
android:layout_marginTop="5dp"
android:hint="第二个输入框" />
<Button
android:text="登录"
android:background="@drawable/btn_submit_bg"
android:layout_width="100dp"
android:layout_height="50dp" />
</LinearLayout>
</LinearLayout>
效果:
第一次录制gif,效果可能不是很好。最后附上完整的activity代码
public class LoginActivity extends AppCompatActivity {
int isFirstEnter=0;
int startY;
private View view;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_login);
view = findViewById(R.id.ll_all);
getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE);
}
/**
* 用于判断输入法是否弹出,防止输入法挡住登录按钮
* 此方法需设置getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE);可以试试不加是什么效果(两个输入框都点的试试)
*/
@Override
protected void onResume() {
super.onResume();
getWindow().getDecorView().addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
@Override
public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) {
//获取窗口可见区域,虚拟键盘会影响getWindowVisibleDisplayFrame()执行结果outRect中的bottom属性的值。如果窗口高度是MATCH_PARENT的,则rect中的bottom属性的值将等于屏幕高度减去虚拟键盘的高度。
Rect rect = new Rect();
getWindow().getDecorView().getWindowVisibleDisplayFrame(rect);
//记录初始的登录框view的y值(这里试一下屏蔽前几次onresume,加载activity的时候会onresume,具体自己去控制)
if(isFirstEnter<1){
isFirstEnter++;
int[] logoXy = new int[2];
view.getLocationOnScreen(logoXy);
startY = logoXy[1];
return;
}
//记录当前控件的位置
int[] location = new int[2];
view.getLocationOnScreen(location);
//rect.bottom:窗口可见区域的bottom
if(bottom!=0 && oldBottom!=0 && bottom - rect.bottom <= 0){
//输入法隐藏了
Toast.makeText(LoginActivity.this, "隐藏", Toast.LENGTH_SHORT).show();
//判断y是不是小于初始y,是则需要执行动画(修改输入框的时候会有bug)
if(location[1]<startY){
//输入法隐藏,view下移动
ObjectAnimator anim = ObjectAnimator.ofFloat(view, "translationY", -400f,0.0f);
anim.setDuration(300);
anim.start();
}
}else {
Toast.makeText(LoginActivity.this, "弹出", Toast.LENGTH_SHORT).show();
//判断y是不是y是不是小于初始y,是则不需要执行动画
if(location[1]==startY){
//输入法弹出了,view上移动
//缩放view(参数:动画控件,执行什么动画,起始位子,0.0标识当前位置,后面都是移动距离,可以跟多个,正标识下移动,负标表示上移动)
ObjectAnimator anim = ObjectAnimator.ofFloat(view, "translationY", 0.0f, -400f);
anim.setDuration(100);
anim.start();
}
}
}
});
}}
扩展:关于软键盘的烦人的事
1.进入页面不弹出软件盘,可以获得焦点
在oncreate中添加
<pre><code>getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN);</pre></code>
或者在清单文件中的activity中直接设置对应的属性。
2点击输入框不弹出软件盘
在对应点击监听事件中加入
InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); imm.hideSoftInputFromWindow(et_number.getWindowToken(), 0);
若是要主动弹出键盘,则可以
((InputMethodManager)getSystemService(INPUT_METHOD_SERVICE)).toggleSoftInput(0,InputMethodManager.HIDE_NOT_ALWAYS);
此处可能会遇到一个bug,如果在点击输入框之前,输入框没有获得焦点,则不会触发点击事件,所以当页面上只有一个输入框,且没有其他控件和输入框抢夺焦点时,可以使用上面的方法。
另一种方法(不推荐使用,适合变态需求)
在edittext中添加
android:clickable="true" android:focusableInTouchMode="false"
屏蔽掉输入法的获得焦点事件,然后露出点击事件。在点击事件触发后,动态给输入框设置获得焦点权限,当焦点离开时,再去屏蔽掉输入框的获得焦点事件。
在onclick中
etWeight.setFocusable(true); etWeight.setFocusableInTouchMode(true); etWeight.requestFocus(); etWeight.requestFocusFromTouch();
在onFocusChange中加入
if(!hasFocus){ etWeight.setFocusable(false); etWeight.setFocusableInTouchMode(false); }
后记
要想解决软键盘的问题,还是要去啃下源码,上述的方法只是一种解决办法却不是最好的方法,如果有大神看到,还请赐教更好的解决方法,或者给个传送门关于android软键盘篇的,不胜感激!一篇关于软键盘的源码简单分析传送门
补充
1看到一种获取软键盘弹出状态的方法
/**
* 获取键盘的高度
*/
private int getSoftKeyboardHeight() {
Rect rect = new Rect();
activity.getWindow().getDecorView().getWindowVisibleDisplayFrame(rect);
//屏幕当前可见高度,不包括状态栏
int displayHeight = rect.bottom - rect.top;
//屏幕可用高度
int availableHeight = ScreenUtils.getAvailableScreenHeight(activity);
//用于计算键盘高度
int softInputHeight = availableHeight - displayHeight - ScreenUtils.getStatusBarHeight(activity);
Log.e("TAG-di", displayHeight + "");
Log.e("TAG-av", availableHeight + "");
Log.e("TAG-so", softInputHeight + "");
if (softInputHeight != 0) {
// 因为考虑到用户可能会主动调整键盘高度,所以只能是每次获取到键盘高度时都将其存储起来
sharedPreferences.edit().putInt(KEY_SOFT_KEYBOARD_HEIGHT, softInputHeight).apply();
}
return softInputHeight;
}
/**
* 判断是否显示了键盘
*/
private boolean isSoftKeyboardShown() {
return getSoftKeyboardHeight() != 0;
}
2。一篇大神的关于软键盘的博客,传送门,里面详细介绍了五种方法,非常值得一看。
扩展(qq聊天界面的实现)
问题,看上去只需要设置一个getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN)或者getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE)就可以。
但是头部会被挤出屏幕,仔细观察会发现qq下面的功能框是一个可变高度的视图,当输入法弹出的时候,视图的高度和输入法高度一样,输入法消失时,视图也会跟着消失。然后我们就可以根据上面博客中的第4种去实现就可以,这里只提供思路,demo没有抽出来。