KeyboardUtil【软键盘弹出后输入框上移一定的高度】
版权声明:本文为HaiyuKing原创文章,转载请注明出处!
前言
演示获取软键盘高度并保存,然后根据输入框的原有位置是否被软键盘挡住了,如果被挡住了则将整体页面上移一定的高度,当软键盘隐藏的时候再下移回来的功能。
效果图
代码分析
KeyboardUtil:显示、隐藏软键盘,以及保存软键盘的高度值;
KeyboardSharedPreferences:SharedPreferences存储工具类;
ViewAnimationUtil:上移、下移的动画效果;
首先,获取软键盘的高度值并保存【当点击输入框,弹出软键盘的时候会进行保存,具体逻辑见代码】
//计算整体view需要移动的高度值(总高度 - 可见区域高度 + top(标题栏高度) = 隐藏区域高度(软键盘高度值)) ((RelativeLayout)rootLayout).getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { public void onGlobalLayout() { //当保存的高度值小于300的时候执行 // 【当APP第一次打开的时候,这里的代码也会执行,那么此时keyboardHeight==0,那么会继续执行下面代码,但是keyboardHeight计算后的值一般会小于300,所以此时不能保存keyboardHeight!!!】 // 【当触摸输入框的时候,不管输入框在软键盘上方还是下方,此时keyboardHeight计算后的值>300】【也就是弹出系统软键盘后整体view向上移动的距离(rect.bottom值变小了),也就可以理解为系统软键盘的高度】 if(keyboardHeight < 300) { Rect rect = new Rect(); rootLayout.getWindowVisibleDisplayFrame(rect);//rect指可见区域 Log.e(TAG, "{onGlobalLayout}rootLayout.getRootView().getHeight()=" + rootLayout.getRootView().getHeight());//【移动前的rootLayout的bottom】 Log.e(TAG, "{onGlobalLayout}rect.bottom=" + rect.bottom);//【移动后的rootLayout的bottom】 Log.e(TAG, "{onGlobalLayout}rect.top=" + rect.top);//【标题栏的高度值】 keyboardHeight = rootLayout.getRootView().getHeight() - rect.bottom + rect.top; Log.e(TAG, "{onGlobalLayout}keyboardHeight=" + keyboardHeight);// if (keyboardHeight > 300) { KeyboardUtil.saveKeyboardHeight(MainActivity.this, keyboardHeight); } }else {//方案一 Rect rect = new Rect(); rootLayout.getWindowVisibleDisplayFrame(rect);//rect指可见区域 Log.e(TAG, "{onGlobalLayout}rect.bottom=" + rect.bottom);//【移动后的rootLayout的bottom】 Log.e(TAG, "{onGlobalLayout}keyboardHeight=" + keyboardHeight);// if(rect.bottom != keyboardHeight){//代表软键盘隐藏了,当软键盘显示的时候,rect.bottom == keyboardHeight downEditRect(); } } } });
然后,输入框添加触摸事件监听,用于上移的动画
//输入框触摸的监听事件 edt_user.setOnTouchListener(new View.OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { //【注意:edt_user.getBottom()是指系统软键盘弹出来后的输入框的bottom值,,缺少顶部标题栏的区域高度,而且连续点击后值不变】 Log.i(TAG, "{initViews}edt_user.getBottom()=" + edt_user.getBottom()); //计算相对于Windows的坐标 int[] locationW = new int[2]; edt_user.getLocationInWindow(locationW); Log.i(TAG, "{onTouch}locationW=" + locationW[0] +";" + locationW[1]); //计算相对于Screen的坐标 int[] locationS = new int[2]; edt_user.getLocationOnScreen(locationS); Log.i(TAG, "{onTouch}locationS=" + locationS[0] +";" + locationS[1]); Log.i(TAG, "{onTouch}edt_user.getMeasuredHeight()=" + edt_user.getMeasuredHeight());//输入框的高度 int edtBottom = locationW[1] + edt_user.getMeasuredHeight();//输入框的底部的Y坐标值== topY + Height; showEditRect(edt_user,edtBottom); return false; } });
最后,监听软键盘隐藏、整体页面添加点击事件,实现下移动画【监听软键盘隐藏是在上面的addOnGlobalLayoutListener方法中实现的】
//整个界面区域的触摸事件 rootLayout.setOnTouchListener(new View.OnTouchListener() { public boolean onTouch(View v, MotionEvent event) { Log.v(TAG, "{initialize}rootLayout=onTouch"); //隐藏自定义的软键盘区域 hideEditRect(); return true; } });
使用步骤
一、项目组织结构图
注意事项:
1、 导入类文件后需要change包名以及重新import R文件路径
2、 Values目录下的文件(strings.xml、dimens.xml、colors.xml等),如果项目中存在,则复制里面的内容,不要整个覆盖
二、导入步骤
将keyboard包复制到项目中
package com.why.project.keyboardutildemo.keyboard; import android.app.Activity; import android.content.Context; import android.content.res.Resources; import android.util.Log; import android.view.View; import android.view.inputmethod.InputMethodManager; import com.why.project.keyboardutildemo.R; /** * Used 软键盘的操作类(显示、隐藏软件盘、保存软键盘的高度值——用于控制输入框的上升) */ public class KeyboardUtil { /**最后一次保存的键盘高度*/ private static int LAST_SAVE_KEYBOARD_HEIGHT = 0; /**输入法软键盘区域的最大高度*/ private static int MAX_PANEL_HEIGHT = 0; /**输入法软键盘区域的最小高度*/ private static int MIN_PANEL_HEIGHT = 0; /**显示软键盘*/ public static void showKeyboard(View view) { if(view != null){ view.requestFocus(); InputMethodManager inputManager = (InputMethodManager)view.getContext().getSystemService(Context.INPUT_METHOD_SERVICE); if (inputManager != null) { inputManager.showSoftInput(view, 0); } } } /**隐藏软键盘 * 第一个参数中的windowToken应当是之前请求显示软键盘的View的windowToken,也就是执行showSoftInput()时第一个参数中的View的windowToken。 * 但是实际情况是,用任意一个当前布局中的已经加载的View的windowToken都可以隐藏软键盘,哪怕这个View被设置为INVISIBLE或GONE。 * 因此,如果不知道之前是谁请求显示的软键盘,可以随便传入一个当前布局中存在的View的windowToken。 * 特别的,可以传入一个Activity的顶层View的windowToken,即getWindow().getDecorView().getWindowToken(),来隐藏当前Activity中显示的软键盘, * 而不用管之前调用showSoftInput()的究竟是哪个View。*/ public static void hideKeyboard(Activity mActivity) { InputMethodManager imm = (InputMethodManager) mActivity.getSystemService(Context.INPUT_METHOD_SERVICE); if (imm != null) { imm.hideSoftInputFromWindow(mActivity.getWindow().getDecorView().getWindowToken(), 0); } } /**隐藏软键盘*/ public static void hideKeyboard(View view) { InputMethodManager imm = (InputMethodManager)view.getContext().getSystemService(Context.INPUT_METHOD_SERVICE); view.clearFocus(); imm.hideSoftInputFromWindow(view.getWindowToken(), 0); } /**是否正在显示软键盘*/ public static boolean isShowKeyboard(Context context, View view) { boolean bool = false; InputMethodManager imm = (InputMethodManager)context.getSystemService(Context.INPUT_METHOD_SERVICE); if (imm.hideSoftInputFromWindow(view.getWindowToken(), 0)) { imm.showSoftInput(view, 0); bool = true; } return bool; } /**保存软键盘的高度值*/ public static boolean saveKeyboardHeight(Context context, int keyboardHeight) { Log.d("KeyboardUtil", "keyboardHeight="+keyboardHeight); if(keyboardHeight <= 300 || LAST_SAVE_KEYBOARD_HEIGHT == keyboardHeight) { return false; } LAST_SAVE_KEYBOARD_HEIGHT = keyboardHeight; return KeyboardSharedPreferences.save(context, keyboardHeight); } /**获取软键盘的高度值*/ public static int getKeyboardHeight(Context context) { if(LAST_SAVE_KEYBOARD_HEIGHT == 0) { LAST_SAVE_KEYBOARD_HEIGHT = KeyboardSharedPreferences.get(context, getMinPanelHeight(context.getResources())); } return LAST_SAVE_KEYBOARD_HEIGHT; } /**获取面板的最大高度值-自定义的*/ private static int getMaxPanelHeight(Resources res) { if(MAX_PANEL_HEIGHT == 0) { MAX_PANEL_HEIGHT = res.getDimensionPixelSize(R.dimen.keyboard_content_panel_max_height); } return MAX_PANEL_HEIGHT; } /**获取面板的最小高度值-自定义的*/ private static int getMinPanelHeight(Resources res) { if(MIN_PANEL_HEIGHT == 0) { MIN_PANEL_HEIGHT = res.getDimensionPixelSize(R.dimen.keyboard_content_panel_min_height); } return MIN_PANEL_HEIGHT; } }
package com.why.project.keyboardutildemo.keyboard; import android.content.Context; import android.content.SharedPreferences; /** * Used SharedPreferences存储软键盘的高度值 */ public class KeyboardSharedPreferences { /**存储文件名*/ private static final String FILE_NAME = "KeyboardSharedPreferences"; /**存储Key值*/ private static final String KEY_KEYBORD_HEIGHT = "keyboardHeight"; private static volatile SharedPreferences SP; /**实例化SharedPreferences*/ private static SharedPreferences with(Context context) { if(SP == null) { synchronized(KeyboardSharedPreferences.class) { if(SP == null) { SP = context.getSharedPreferences(FILE_NAME, 0); } } } return SP; } /**存储软键盘的高度*/ public static boolean save(Context context, int keyboardHeight) { return with(context).edit().putInt(KEY_KEYBORD_HEIGHT, keyboardHeight).commit(); } /**读取存储软键盘的高度(带默认值)*/ public static int get(Context context, int defaultHeight) { return with(context).getInt(KEY_KEYBORD_HEIGHT, defaultHeight); } }
package com.why.project.keyboardutildemo.keyboard; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.AnimatorSet; import android.animation.ObjectAnimator; import android.view.View; /** * Used 界面上升下降动画效果工具类(全) */ public class ViewAnimationUtil { private static ViewAnimationListener mViewAnimationListener = null; public ViewAnimationUtil() { } /**区域的上升动画效果*/ public static void editAreaAnimator(View view, float translationFromY, float translationToY, float scalingFromRatio, float scalingToRatio, final boolean doEnd) { ObjectAnimator animTY = ObjectAnimator.ofFloat(view, "translationY", new float[] {translationFromY, translationToY}); ObjectAnimator animTSX = ObjectAnimator.ofFloat(view, "scaleX", new float[] {scalingFromRatio, scalingToRatio}); ObjectAnimator animTSY = ObjectAnimator.ofFloat(view, "scaleY", new float[] {scalingFromRatio, scalingToRatio}); AnimatorSet editAreaSet = new AnimatorSet(); int EDIT_DURATION = 500; editAreaSet.setDuration((long)EDIT_DURATION); editAreaSet.playTogether(new Animator[] {animTY, animTSX, animTSY}); editAreaSet.addListener(new AnimatorListenerAdapter() { public void onAnimationEnd(Animator animation) { super.onAnimationEnd(animation); if((doEnd) && mViewAnimationListener != null) { mViewAnimationListener.endAnimation(); } } }); editAreaSet.start(); } public static void setViewAnimationListener(ViewAnimationListener mmViewAnimationListener) { mViewAnimationListener = mmViewAnimationListener; } public static abstract interface ViewAnimationListener { public abstract void endAnimation(); } }
在dimens.xml文件中添加以下代码
<resources> <!-- Default screen margins, per the Android Design guidelines. --> <dimen name="activity_horizontal_margin">16dp</dimen> <dimen name="activity_vertical_margin">16dp</dimen> <!-- *********KeyboardUtil相关********* --> <dimen name="keyboard_content_panel_max_height">120dp</dimen> <dimen name="keyboard_content_panel_min_height">0dp</dimen> </resources>
三、使用方法
输入框的背景
input_box_send.9.png
布局文件
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/layoutroot" android:layout_width="match_parent" android:layout_height="match_parent" > <!-- 当之有一个EditText或者AutoCompleteTextView的时候,进入画面时是默认得到焦点的。 要想去除焦点,可以在auto之前加一个0像素的layout,并设置他先得到焦点。 --> <LinearLayout android:layout_width="0px" android:layout_height="0px" android:focusable="true" android:focusableInTouchMode="true"/> <LinearLayout android:id="@+id/centerLayout" android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical" android:layout_alignParentBottom="true" android:layout_marginBottom="200dp"> <EditText android:id="@+id/edt_user" android:layout_width="match_parent" android:layout_height="48dp" android:inputType="text" android:hint="请输入用户名" android:lines="1" android:background="@drawable/input_box_send" android:layout_margin="5dp" /> <EditText android:id="@+id/edt_send" android:layout_width="match_parent" android:layout_height="48dp" android:hint="请输入密码" android:lines="1" android:imeOptions="actionGo" android:inputType="textPassword" android:background="@drawable/input_box_send" android:layout_margin="5dp" /> </LinearLayout> </RelativeLayout>
其中第一个输入框的原有位置在弹起的软键盘上方,第二个输入框的原有位置在弹起的软键盘下方。
activity中使用如下
package com.why.project.keyboardutildemo; import android.graphics.Rect; import android.os.Bundle; import android.support.v7.app.AppCompatActivity; import android.util.Log; import android.view.MotionEvent; import android.view.View; import android.view.ViewTreeObserver; import android.widget.EditText; import android.widget.RelativeLayout; import com.why.project.keyboardutildemo.keyboard.KeyboardUtil; import com.why.project.keyboardutildemo.keyboard.ViewAnimationUtil; public class MainActivity extends AppCompatActivity { private static final String TAG = MainActivity.class.getSimpleName(); private RelativeLayout rootLayout;//整体View private EditText edt_send;//输入框view private EditText edt_user;//输入框view /**区域是否上升了*/ private boolean editAreaIsUp = false; /**软键盘的高度值*/ private int keyboardHeight = 0; /**需要上升的高度值*/ private int textBottom = 0; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); initViews(); } @Override public void onWindowFocusChanged(boolean hasFocus) { super.onWindowFocusChanged(hasFocus); } private void initViews() { rootLayout = (RelativeLayout) findViewById(R.id.layoutroot); edt_send = (EditText) findViewById(R.id.edt_send); edt_user = (EditText) findViewById(R.id.edt_user); //计算整体view需要移动的高度值(总高度 - 可见区域高度 + top(标题栏高度) = 隐藏区域高度(软键盘高度值)) ((RelativeLayout)rootLayout).getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { public void onGlobalLayout() { //当保存的高度值小于300的时候执行 // 【当APP第一次打开的时候,这里的代码也会执行,那么此时keyboardHeight==0,那么会继续执行下面代码,但是keyboardHeight计算后的值一般会小于300,所以此时不能保存keyboardHeight!!!】 // 【当触摸输入框的时候,不管输入框在软键盘上方还是下方,此时keyboardHeight计算后的值>300】【也就是弹出系统软键盘后整体view向上移动的距离(rect.bottom值变小了),也就可以理解为系统软键盘的高度】 if(keyboardHeight < 300) { Rect rect = new Rect(); rootLayout.getWindowVisibleDisplayFrame(rect);//rect指可见区域 Log.e(TAG, "{onGlobalLayout}rootLayout.getRootView().getHeight()=" + rootLayout.getRootView().getHeight());//【移动前的rootLayout的bottom】 Log.e(TAG, "{onGlobalLayout}rect.bottom=" + rect.bottom);//【移动后的rootLayout的bottom】 Log.e(TAG, "{onGlobalLayout}rect.top=" + rect.top);//【标题栏的高度值】 keyboardHeight = rootLayout.getRootView().getHeight() - rect.bottom + rect.top; Log.e(TAG, "{onGlobalLayout}keyboardHeight=" + keyboardHeight);// if (keyboardHeight > 300) { KeyboardUtil.saveKeyboardHeight(MainActivity.this, keyboardHeight); } }else {//方案一 Rect rect = new Rect(); rootLayout.getWindowVisibleDisplayFrame(rect);//rect指可见区域 Log.e(TAG, "{onGlobalLayout}rect.bottom=" + rect.bottom);//【移动后的rootLayout的bottom】 Log.e(TAG, "{onGlobalLayout}keyboardHeight=" + keyboardHeight);// if(rect.bottom != keyboardHeight){//代表软键盘隐藏了,当软键盘显示的时候,rect.bottom == keyboardHeight downEditRect(); } } } }); //整个界面区域的触摸事件 rootLayout.setOnTouchListener(new View.OnTouchListener() { public boolean onTouch(View v, MotionEvent event) { Log.v(TAG, "{initialize}rootLayout=onTouch"); //隐藏自定义的软键盘区域 hideEditRect(); return true; } }); //输入框触摸的监听事件 edt_user.setOnTouchListener(new View.OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { //【注意:edt_user.getBottom()是指系统软键盘弹出来后的输入框的bottom值,,缺少顶部标题栏的区域高度,而且连续点击后值不变】 Log.i(TAG, "{initViews}edt_user.getBottom()=" + edt_user.getBottom()); //计算相对于Windows的坐标 int[] locationW = new int[2]; edt_user.getLocationInWindow(locationW); Log.i(TAG, "{onTouch}locationW=" + locationW[0] +";" + locationW[1]); //计算相对于Screen的坐标 int[] locationS = new int[2]; edt_user.getLocationOnScreen(locationS); Log.i(TAG, "{onTouch}locationS=" + locationS[0] +";" + locationS[1]); Log.i(TAG, "{onTouch}edt_user.getMeasuredHeight()=" + edt_user.getMeasuredHeight());//输入框的高度 int edtBottom = locationW[1] + edt_user.getMeasuredHeight();//输入框的底部的Y坐标值== topY + Height; showEditRect(edt_user,edtBottom); return false; } }); //输入框触摸的监听事件 edt_send.setOnTouchListener(new View.OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { //【注意:edt_send.getBottom()是指系统软键盘弹出来后的输入框的bottom值,缺少顶部标题栏的区域高度,而且连续点击后值不变】 Log.i(TAG, "{initViews}edt_send.getBottom()=" + edt_send.getBottom()); //计算相对于Windows的坐标 int[] locationW = new int[2]; edt_send.getLocationInWindow(locationW); Log.i(TAG, "{initViews}locationW1=" + locationW[0] +";" + locationW[1]); //计算相对于Screen的坐标 int[] locationS = new int[2]; edt_send.getLocationOnScreen(locationS); Log.i(TAG, "{initViews}locationS1=" + locationS[0] +";" + locationS[1]); int edtBottom = locationW[1] + edt_send.getMeasuredHeight();//输入框的底部的Y坐标值== topY + Height; showEditRect(edt_send,edtBottom); return false; } }); } /** * 显示自定义的软键盘区域 * @param view - 输入框view * @param bottom 输入框的bottom【弹出系统软键盘后的值】*/ public void showEditRect(final View view, final int bottom) { //实现编辑区域的上升动画效果 view.postDelayed(new Runnable() { public void run() { Log.w(TAG, "(showEditRect)bottom="+bottom); Log.w(TAG, "(showEditRect)keyboardHeight="+keyboardHeight); if(keyboardHeight != 0 && bottom - keyboardHeight > 0){//为什么需要判断bottom - keyboardHeight > 0??因为当已经弹出软键盘后继续点击输入框的时候,就不需要在上移了,而可以通过bottom值变小了来解决继续上移的问题。 textBottom = view.getMeasuredHeight(); Log.w(TAG, "(showEditRect)textBottom="+textBottom); makeEditAreaUpAndSmall(((float)textBottom)); } } }, 300); } /**隐藏自定义软键盘区域*/ private void hideEditRect(){ KeyboardUtil.hideKeyboard(this); downEditRect(); } /**下移*/ private void downEditRect(){ if(textBottom > 0) { makeEditAreaOriginal((float)textBottom); textBottom = 0; } } /**编辑区域上升的动画效果:指定高度*/ public void makeEditAreaUpAndSmall(float to) { if (!this.editAreaIsUp) { ViewAnimationUtil.editAreaAnimator(rootLayout, 0.0F, -to, 1.0F, 1.0F,false); this.editAreaIsUp = true; } } /**编辑区域回到原始的动画效果*/ public void makeEditAreaOriginal(float from) { if(this.editAreaIsUp) { ViewAnimationUtil.editAreaAnimator(rootLayout, -from, 0.0F, 1.0F, 1.0F,false); this.editAreaIsUp = false; } } }
混淆配置
无
参考资料
暂时空缺