【具体表现为:表情面板与输入法面板高度不一致,从而导致弹出输入法(layout被挤压)时,同时又需要隐藏表情面板(layout被拉升),最终让界面产生了高度差抖动,所以在切换时明显会有不大好的抖动体验)】
使用了解决抖动的解决方案后,效果如下:
【这样的方案明显比微博的切换更平滑】
老样子,先说思路。主要我们要用到两个输入法弹出模式,分别是:adjustResize(调整模式) 、adjustNothing(不做任何调整) 。(更多介绍请参看我的上一篇文章:输入法弹出参数分析)
1.初始情况时(键盘和表情面板都未展开):我们为表情面板设置一个默认高度(因为我们还不知道键盘有多高)并将输入发弹出模式设置为adjustResize模式。
2.当我们点击了EditText时,系统将会弹出输入法,由于之前我们设置的模式为adjustResize,因此,输入法会挤压Layout,并且挤压的高度最终会固定到一个值(键盘的高度),当我们检测到挤压后,将这个挤压差值(也就是键盘高度)记录下来,作为表情面板的新高度值。于此同时,我们将表情面板隐藏。
3.当我们点击了表情按钮时,我们需要先判断输入法是否已展开。
1)如果已经展开,那么我们的任务是将键盘平滑隐藏并显示表情面板。具体做法为:先将Activity的输入法弹出模式设置为adjustNothing,然后将上一步记录下来的键盘高度作为表情面板的高度,再将表情面板显示,此时由于键盘弹出模式为adjustNothing,所以键盘不会有任何抖动,并且由于表情面板与键盘等高,因此EditText也不会下移,最后将输入法隐藏。
2)如果输入法未展开,我们再判断表情面板是否展开,如果展开了就隐藏并将输入法弹出模式归位为adjustResize,如果未展开就直接显示并将输入法弹出模式设置为adjustNothing。
大致的实现思路就是上面说到的,但是,既然都准备动手做帮助类了,就顺便将点击空白处折叠键盘和表情面板一起做了。具体实现思路为:在Activity的DecorView上面遮罩一层FrameLayout,用于监听触摸的Aciton_Down事件,如果在输入范围之外,则折叠表情面板和键盘。示意图如下:
该说的说完了,开动。
1、创建InputMethodUtils类,构造方法需要传递Activity参数,并申明所需要的成员变量,并实现View.OnClickListener接口(因为我们要监听表情按钮的点击事件)。代码如下:
public class InputMethodUtils implements View.OnClickListener {// 键盘是否展开的标志位private boolean sIsKeyboardShowing;// 键盘高度变量private int sKeyBoardHeight = 0;// 绑定的Activityprivate Activity activity;/** * 构造函数 ** @param activity *需要处理输入法的当前的Activity */public InputMethodUtils(Activity activity) {this.activity = activity;//DisplayUtils为屏幕尺寸工具类DisplayUtils.init(activity);// 默认键盘高度为267dpsetKeyBoardHeight(DisplayUtils.dp2px(267));}@Overridepublic void onClick(View v) {}}//DisplayUtils的实现代码为:/** * 屏幕参数的辅助工具类。例如:获取屏幕高度,宽度,statusBar的高度,px和dp互相转换等 * 【注意,使用之前一定要初始化!一次初始化就OK(建议APP启动时进行初始化)。 初始化代码 DisplayUtils.init(context)】 * @author 蓝亭书序 */private static class DisplayUtils {// 四舍五入的偏移值private static final float ROUND_CEIL = 0.5f;// 屏幕矩阵对象private static DisplayMetrics sDisplayMetrics;// 资源对象(用于获取屏幕矩阵)private static Resources sResources;// statusBar的高度(由于这里获取statusBar的高度使用的反射,比较耗时,所以用变量记录)private static int statusBarHeight = -1;/** * 初始化操作 ** @param context *context上下文对象 */public static void init(Context context) {sDisplayMetrics = context.getResources().getDisplayMetrics();sResources = context.getResources();}/** * 获取屏幕高度 单位:像素 ** @return 屏幕高度 */public static int getScreenHeight() {return sDisplayMetrics.heightPixels;}/** * 获取屏幕宽度 单位:像素 ** @return 屏幕宽度 */public static float getDensity() {return sDisplayMetrics.density;}/** * dp 转 px ** @param dp *dp值 * @return 转换后的像素值 */public static int dp2px(int dp) {return (int) (dp * getDensity() + ROUND_CEIL);}/** * 获取状态栏高度 ** @return 状态栏高度 */public static int getStatusBarHeight() {// 如果之前计算过,直接使用上次的计算结果if (statusBarHeight == -1) {final int defaultHeightInDp = 19;// statusBar默认19dp的高度statusBarHeight = DisplayUtils.dp2px(defaultHeightInDp);try {Class<?> c = Class.forName("com.android.internal.R$dimen");Object obj = c.newInstance();Field field = c.getField("status_bar_height");statusBarHeight = sResources.getDimensionPixelSize(Integer.parseInt(field.get(obj).toString()));} catch (Exception e) {e.printStackTrace();}}return statusBarHeight;}}【搬砖去了,等会继续写… … 】好了,继续写… …
/** * 用于控制点击某个按钮显示或者隐藏“表情面板”的绑定bean对象。<br/> * 例如:我想点击“表情”按钮显示“表情面板”,我就可以这样做:<br/> * ViewBinder viewBinder = new ViewBinder(btn_emotion,emotionPanel);<br/> * 这样就创建出了一个ViewBinder对象<br/> * <font color="red">【注意事项,使用此类时,千万不要使用trigger的setOnClickListener来监听事件( * 使用OnTriggerClickListener来代替),也不要使用setTag来设置Tag,否则会导致使用异常】</font> * @author 蓝亭书序 */public static class ViewBinder {private View trigger;//表情按钮对象private View panel;//表情面板对象//替代的监听器private OnTriggerClickListener listener;/** * 创建ViewBinder对象<br/> * 例如:我想点击“表情”按钮显示“表情面板”,我就可以这样做:<br/> * ViewBinder viewBinder = new * ViewBinder(btn_emotion,emotionPanel,listener);<br/> * 这样就创建出了一个ViewBinder对象 ** @param trigger *触发对象 * @param panel *点击触发对象需要显示/隐藏的面板对象 * @param listener *Trigger点击的监听器(千万不要使用setOnClickListener,否则会覆盖本工具类的监听器) */public ViewBinder(View trigger, View panel,OnTriggerClickListener listener) {this.trigger = trigger;this.panel = panel;this.listener = listener;trigger.setClickable(true);}@Overridepublic boolean equals(Object obj) {if (this == obj)return true;if (obj == null)return false;if (getClass() != obj.getClass())return false;ViewBinder other = (ViewBinder) obj;if (panel == null) {if (other.panel != null)return false;} else if (!panel.equals(other.panel))return false;if (trigger == null) {if (other.trigger != null)return false;} else if (!trigger.equals(other.trigger))return false;return true;}public OnTriggerClickListener getListener() {return listener;}public void setListener(OnTriggerClickListener listener) {this.listener = listener;}public View getTrigger() {return trigger;}public void setTrigger(View trigger) {this.trigger = trigger;}public View getPanel() {return panel;}public void setPanel(View panel) {this.panel = panel;}}其中OnTriggerClickListener是为了解决trigger占用监听器的问题(我们内部逻辑需要占用监听器,如果外部想实现额外的点击逻辑不能再为trigger添加监听器,所以使用OnTriggerClickListener来代替原原声的OnClickListener)。OnTriggerClickListener为一个接口,实现代码如下:
/** * ViewBinder的触发按钮点击的监听器 * @author 蓝亭书序 */public static interface OnTriggerClickListener {/** * 点击事件的回调函数* @param v 被点击的按钮对象 */public void onClick(View v);}3、实现了ViewBinder后,我们还需要实现一个遮罩View,用于监听ACTION_DOWN事件。代码如下:
/** * 点击软键盘区域以外自动关闭软键盘的遮罩View * @author 蓝亭书序 */private class CloseKeyboardOnOutsideContainer extends FrameLayout {public CloseKeyboardOnOutsideContainer(Context context) {this(context, null);}public CloseKeyboardOnOutsideContainer(Context context,AttributeSet attrs) {this(context, attrs, 0);}public CloseKeyboardOnOutsideContainer(Context context,AttributeSet attrs, int defStyle) {super(context, attrs, defStyle);}/*如果不知道这个方法的作用的话,需要了解下Android的事件分发机制哈,如果有时间我也可以写个文章介绍下。dispatchTouchEvent方法主要是ViewGroup在事件分发之前进行事件进行判断,如果返回true表示此ViewGroup拦截此事件,这个事件将不会传递给他的子View,如果返回false,反之。*/@Overridepublic boolean dispatchTouchEvent(MotionEvent event) {//这段逻辑不复杂,看一遍应该就懂boolean isKeyboardShowing = isKeyboardShowing();boolean isEmotionPanelShowing = hasPanelShowing();if ((isKeyboardShowing || isEmotionPanelShowing)&& event.getAction() == MotionEvent.ACTION_DOWN) {int touchY = (int) (event.getY());int touchX = (int) (event.getX());if (isTouchKeyboardOutside(touchY)) {if (isKeyboardShowing) {hideKeyBordAndSetFlag(activity.getCurrentFocus());}if (isEmotionPanelShowing) {closeAllPanels();}}if (isTouchedFoucusView(touchX, touchY)) {// 如果点击的是输入框(会弹出输入框),那么延时折叠表情面板postDelayed(new Runnable() {@Overridepublic void run() {setKeyboardShowing(true);}}, 500);}}return super.onTouchEvent(event);}}/** * 是否点击软键盘和输入法外面区域 * @param activity *当前activity * @param touchY *点击y坐标(不包括statusBar的高度) */private boolean isTouchKeyboardOutside(int touchY) {View foucusView = activity.getCurrentFocus();if (foucusView == null) {return false;}int[] location = new int[2];foucusView.getLocationOnScreen(location);int editY = location[1] - DisplayUtils.getStatusBarHeight();int offset = touchY - editY;if (offset > 0 && offset < foucusView.getMeasuredHeight()) {return false;}return true;}/** * 是否点击的是当前焦点View的范围 * @param x *x方向坐标 * @param y *y方向坐标(不包括statusBar的高度) * @return true表示点击的焦点View,false反之 */private boolean isTouchedFoucusView(int x, int y) {View foucusView = activity.getCurrentFocus();if (foucusView == null) {return false;}int[] location = new int[2];foucusView.getLocationOnScreen(location);int foucusViewTop = location[1] - DisplayUtils.getStatusBarHeight();int offsetY = y - foucusViewTop;if (offsetY > 0 && offsetY < foucusView.getMeasuredHeight()) {int foucusViewLeft = location[0];int foucusViewLength = foucusView.getWidth();int offsetX = x - foucusViewLeft;if (offsetX >= 0 && offsetX <= foucusViewLength) {return true;}}return false;}4、准备工作做完,我们可以继续完善InputMethodUtils类了,由于我们需要存储ViewBinder对象(主要用于控制按钮和面板之间的关联关系),所以,我们还需要在InputMethodUtils中申明一个集合。代码如下:
// 触发与面板对象集合(使用set可以自动过滤相同的ViewBinder)private Set<ViewBinder> viewBinders = new HashSet<ViewBinder>();5、与viewBinders 随之而来的一些常用方法有必要写一下(例如折叠所有表情面板、获取当前哪个表情面板展开着等),代码如下:
/** * 添加ViewBinder * @param viewBinder *变长参数 */public void setViewBinders(ViewBinder... viewBinder) {for (ViewBinder vBinder : viewBinder) {if (vBinder != null) {viewBinders.add(vBinder);vBinder.trigger.setTag(vBinder);vBinder.trigger.setOnClickListener(this);}}updateAllPanelHeight(sKeyBoardHeight);}/** * 重置所有面板 * @param dstPanel *重置操作例外的对象 */private void resetOtherPanels(View dstPanel) {for (ViewBinder vBinder : viewBinders) {if (dstPanel != vBinder.panel) {vBinder.panel.setVisibility(View.GONE);}}}/** * 关闭所有的面板 */public void closeAllPanels() {resetOtherPanels(null);//重置面板后,需要将输入法弹出模式一并重置updateSoftInputMethod(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE);}/** * 判断是否存在正在显示的面板 * @return true表示存在,false表示不存在 */public boolean hasPanelShowing() {for (ViewBinder viewBinder : viewBinders) {if (viewBinder.panel.isShown()) {return true;}}return false;}/** * 更新所有面板的高度 * @param height *具体高度(单位px) */private void updateAllPanelHeight(int height) {for (ViewBinder vBinder : viewBinders) {ViewGroup.LayoutParams params = vBinder.panel.getLayoutParams();params.height = height;vBinder.panel.setLayoutParams(params);}}6、通过监听Layout的变化来判断输入法是否已经展开。代码如下:
/** * 设置View树监听,以便判断键盘是否弹出。<br/> * 【只有当Activity的windowSoftInputMode设置为adjustResize时才有效!所以我们要处理adjustNoting(不会引起Layout的形变)的情况键盘监听(后文会提到)】 */private void detectKeyboard() {final View activityRootView = ((ViewGroup) activity.findViewById(android.R.id.content)).getChildAt(0);if (activityRootView != null) {ViewTreeObserver observer = activityRootView.getViewTreeObserver();if (observer == null) {return;}observer.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {@Overridepublic void onGlobalLayout() {final Rect r = new Rect();activityRootView.getWindowVisibleDisplayFrame(r);int heightDiff = DisplayUtils.getScreenHeight()- (r.bottom - r.top);//Layout形变超过键盘的一半表示键盘已经展开了boolean show = heightDiff >= sKeyBoardHeight / 2;setKeyboardShowing(show);// 设置键盘是否展开状态if (show) {int keyboardHeight = heightDiff- DisplayUtils.getStatusBarHeight();// 设置新的键盘高度setKeyBoardHeight(keyboardHeight);}}});}}7、完成键盘的显示/隐藏和动态控制输入法弹出模式的常用方法。代码如下:
/** * 隐藏输入法 * @param currentFocusView *当前焦点view */public static void hideKeyboard(View currentFocusView) {if (currentFocusView != null) {IBinder token = currentFocusView.getWindowToken();if (token != null) {InputMethodManager im = (InputMethodManager) currentFocusView.getContext().getSystemService(Context.INPUT_METHOD_SERVICE);im.hideSoftInputFromWindow(token, 0);}}}/** * 更新输入法的弹出模式(注意这是静态方法,可以直接当做工具方法使用) * @param activity 对应的Activity * @param softInputMode * <br/> *键盘弹出模式:WindowManager.LayoutParams的参数有:<br/> * 可见状态: SOFT_INPUT_STATE_UNSPECIFIED, *SOFT_INPUT_STATE_UNCHANGED, SOFT_INPUT_STATE_HIDDEN, *SOFT_INPUT_STATE_ALWAYS_VISIBLE, or SOFT_INPUT_STATE_VISIBLE.<br/> * 适配选项有: SOFT_INPUT_ADJUST_UNSPECIFIED, *SOFT_INPUT_ADJUST_RESIZE, or SOFT_INPUT_ADJUST_PAN. */public static void updateSoftInputMethod(Activity activity,int softInputMode) {if (!activity.isFinishing()) {WindowManager.LayoutParams params = activity.getWindow().getAttributes();if (params.softInputMode != softInputMode) {params.softInputMode = softInputMode;activity.getWindow().setAttributes(params);}}}/** * 更新输入法的弹出模式(遇上面的静态方法的区别是直接使用的是绑定的activity对象) ** @param softInputMode * <br/> *键盘弹出模式:WindowManager.LayoutParams的参数有:<br/> * 可见状态: SOFT_INPUT_STATE_UNSPECIFIED, *SOFT_INPUT_STATE_UNCHANGED, SOFT_INPUT_STATE_HIDDEN, *SOFT_INPUT_STATE_ALWAYS_VISIBLE, or SOFT_INPUT_STATE_VISIBLE.<br/> * 适配选项有: SOFT_INPUT_ADJUST_UNSPECIFIED, *SOFT_INPUT_ADJUST_RESIZE, or SOFT_INPUT_ADJUST_PAN. */public void updateSoftInputMethod(int softInputMode) {updateSoftInputMethod(activity, softInputMode);}8、在构造方法中将这些组件都初始化,并做相关设置,代码如下:
/** * 构造函数 ** @param activity *需要处理输入法的当前的Activity */public InputMethodUtils(Activity activity) {this.activity = activity;DisplayUtils.init(activity);// 默认键盘高度为267dpsetKeyBoardHeight(DisplayUtils.dp2px(267));updateSoftInputMethod(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE);detectKeyboard();// 监听View树变化,以便监听键盘是否弹出enableCloseKeyboardOnTouchOutside(activity);}/** 1. 设置键盘的高度 2.3. @param keyBoardHeight 4.键盘的高度(px单位) */private void setKeyBoardHeight(int keyBoardHeight) {sKeyBoardHeight = keyBoardHeight;updateAllPanelHeight(keyBoardHeight);}/** 5. 开启点击外部关闭键盘的功能(其实就是将遮罩View添加到Decorview) 6.7. @param activity */private void enableCloseKeyboardOnTouchOutside(Activity activity) {CloseKeyboardOnOutsideContainer frameLayout = new CloseKeyboardOnOutsideContainer(activity);activity.addContentView(frameLayout, new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,ViewGroup.LayoutParams.MATCH_PARENT));}【突然有事,先写到这,等会来完善…】回来了,接着写。
/** * 解决输入法与表情面板之间切换时抖动冲突的控制辅助工具类(能做到将面板与输入法之间平滑切换).另外,具备点击空白处自动收起面板和输入法的功能.<br/> * 使用方法介绍如下: * <hr/> * <font color= "red">申明:【此类中,我们将表情面板选项、显示表情面板的按钮、表情面板按钮的点击事件 * 作为一个整体,包装在ViewBinder类中(点击表情面板按钮时,将会展开表情面 板 ) 】</font> <br/> * 因此,第一步,我们将需要操作的表情面板、按钮、事件绑定在一起,创建ViewBinder类(可以是很多个)代码示例如下:<br/> * //如果不想监听按钮点击事件,之间将listener参数替换成null即可<br/> * ViewBinder viewBinder1 = new ViewBinder(btn_1,panel1,listener1);<br/> * ViewBinder viewBinder2 = new ViewBinder(btn_2,panel2,listener2);<br/> * ...<br/> * 第二步:创建InputMethodUtils类<br/> * InputMethodUtils inputMethodUtils = new InputMethodUtils(this);<br/> * 第三部:将ViewBinder传递给InputMethodUtils。<br/> * inputMethodUtils.setViewBinders(viewBinder1,viewBinder2);//这个参数为动态参数, * 支持多个参数传递进来 * <hr/> * 本类还提供两个常用的工具方法:<br/> * InputMethodUtils.hideKeyboard();//用于隐藏输入法<br/> * InputMethodUtils.updateSoftInputMethod();//用于将当前Activity的输入法模式切换成指定的输入法模式 * <br/> ** @author 李长军 2016.11.26 */public class InputMethodUtils implements View.OnClickListener {// 键盘是否展开的标志位private boolean sIsKeyboardShowing;// 键盘高度private int sKeyBoardHeight = 0;// 绑定的Activityprivate Activity activity;// 触发与面板对象集合private Set<ViewBinder> viewBinders = new HashSet<ViewBinder>();/** * 构造函数 ** @param activity *需要处理输入法的当前的Activity */public InputMethodUtils(Activity activity) {this.activity = activity;DisplayUtils.init(activity);// 默认键盘高度为267dpsetKeyBoardHeight(DisplayUtils.dp2px(267));updateSoftInputMethod(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE);detectKeyboard();// 监听View树变化,以便监听键盘是否弹出enableCloseKeyboardOnTouchOutside(activity);}/** * 添加ViewBinder ** @param viewBinder *变长参数 */public void setViewBinders(ViewBinder... viewBinder) {for (ViewBinder vBinder : viewBinder) {if (vBinder != null) {viewBinders.add(vBinder);vBinder.trigger.setTag(vBinder);vBinder.trigger.setOnClickListener(this);}}updateAllPanelHeight(sKeyBoardHeight);}@Overridepublic void onClick(View v) {ViewBinder viewBinder = (ViewBinder) v.getTag();View panel = viewBinder.panel;resetOtherPanels(panel);// 重置所有面板if (isKeyboardShowing()) {updateSoftInputMethod(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING);panel.setVisibility(View.VISIBLE);hideKeyBordAndSetFlag(activity.getCurrentFocus());} else if (panel.isShown()) {updateSoftInputMethod(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE);panel.setVisibility(View.GONE);} else {updateSoftInputMethod(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE);panel.setVisibility(View.VISIBLE);}if (viewBinder.listener != null) {viewBinder.listener.onClick(v);}}/** * 获取键盘是否弹出 ** @return true表示弹出 */public boolean isKeyboardShowing() {return sIsKeyboardShowing;}/** * 获取键盘的高度 ** @return 键盘的高度(px单位) */public int getKeyBoardHeight() {return sKeyBoardHeight;}/** * 关闭所有的面板 */public void closeAllPanels() {resetOtherPanels(null);updateSoftInputMethod(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE);}/** * 判断是否存在正在显示的面板 ** @return true表示存在,false表示不存在 */public boolean hasPanelShowing() {for (ViewBinder viewBinder : viewBinders) {if (viewBinder.panel.isShown()) {return true;}}return false;}/** * 更新输入法的弹出模式 ** @param softInputMode * <br/> *键盘弹出模式:WindowManager.LayoutParams的参数有:<br/> * 可见状态: SOFT_INPUT_STATE_UNSPECIFIED, *SOFT_INPUT_STATE_UNCHANGED, SOFT_INPUT_STATE_HIDDEN, *SOFT_INPUT_STATE_ALWAYS_VISIBLE, or SOFT_INPUT_STATE_VISIBLE.<br/> * 适配选项有: SOFT_INPUT_ADJUST_UNSPECIFIED, *SOFT_INPUT_ADJUST_RESIZE, or SOFT_INPUT_ADJUST_PAN. */public void updateSoftInputMethod(int softInputMode) {updateSoftInputMethod(activity, softInputMode);}/** * 隐藏输入法 ** @param currentFocusView *当前焦点view */public static void hideKeyboard(View currentFocusView) {if (currentFocusView != null) {IBinder token = currentFocusView.getWindowToken();if (token != null) {InputMethodManager im = (InputMethodManager) currentFocusView.getContext().getSystemService(Context.INPUT_METHOD_SERVICE);im.hideSoftInputFromWindow(token, 0);}}}/** * 更新输入法的弹出模式 ** @param activity * @param softInputMode * <br/> *键盘弹出模式:WindowManager.LayoutParams的参数有:<br/> * 可见状态: SOFT_INPUT_STATE_UNSPECIFIED, *SOFT_INPUT_STATE_UNCHANGED, SOFT_INPUT_STATE_HIDDEN, *SOFT_INPUT_STATE_ALWAYS_VISIBLE, or SOFT_INPUT_STATE_VISIBLE.<br/> * 适配选项有: SOFT_INPUT_ADJUST_UNSPECIFIED, *SOFT_INPUT_ADJUST_RESIZE, or SOFT_INPUT_ADJUST_PAN. */public static void updateSoftInputMethod(Activity activity,int softInputMode) {if (!activity.isFinishing()) {WindowManager.LayoutParams params = activity.getWindow().getAttributes();if (params.softInputMode != softInputMode) {params.softInputMode = softInputMode;activity.getWindow().setAttributes(params);}}}/** * 隐藏键盘,并维护显示或不显示的逻辑 ** @param currentFocusView *当前的焦点View */private void hideKeyBordAndSetFlag(View currentFocusView) {sIsKeyboardShowing = false;hideKeyboard(currentFocusView);}/** * 重置所有面板 */private void resetOtherPanels(View dstPanel) {for (ViewBinder vBinder : viewBinders) {if (dstPanel != vBinder.panel) {vBinder.panel.setVisibility(View.GONE);}}}/** * 更新所有面板的高度 ** @param height *具体高度 */private void updateAllPanelHeight(int height) {for (ViewBinder vBinder : viewBinders) {ViewGroup.LayoutParams params = vBinder.panel.getLayoutParams();params.height = height;vBinder.panel.setLayoutParams(params);}}/** * 设置键盘弹出与否状态 ** @param show *true表示弹出,false表示未弹出 */private void setKeyboardShowing(boolean show) {sIsKeyboardShowing = show;if (show) {resetOtherPanels(null);updateSoftInputMethod(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE);}}/** * 设置键盘的高度 ** @param keyBoardHeight *键盘的高度(px单位) */private void setKeyBoardHeight(int keyBoardHeight) {sKeyBoardHeight = keyBoardHeight;updateAllPanelHeight(keyBoardHeight);}/** * 是否点击软键盘和输入法外面区域 ** @param activity *当前activity * @param touchY *点击y坐标(不包括statusBar的高度) */private boolean isTouchKeyboardOutside(int touchY) {View foucusView = activity.getCurrentFocus();if (foucusView == null) {return false;}int[] location = new int[2];foucusView.getLocationOnScreen(location);int editY = location[1] - DisplayUtils.getStatusBarHeight();int offset = touchY - editY;if (offset > 0 && offset < foucusView.getMeasuredHeight()) {return false;}return true;}/** * 是否点击的是当前焦点View的范围 ** @param x *x方向坐标 * @param y *y方向坐标(不包括statusBar的高度) * @return true表示点击的焦点View,false反之 */private boolean isTouchedFoucusView(int x, int y) {View foucusView = activity.getCurrentFocus();if (foucusView == null) {return false;}int[] location = new int[2];foucusView.getLocationOnScreen(location);int foucusViewTop = location[1] - DisplayUtils.getStatusBarHeight();int offsetY = y - foucusViewTop;if (offsetY > 0 && offsetY < foucusView.getMeasuredHeight()) {int foucusViewLeft = location[0];int foucusViewLength = foucusView.getWidth();int offsetX = x - foucusViewLeft;if (offsetX >= 0 && offsetX <= foucusViewLength) {return true;}}return false;}/** * 开启点击外部关闭键盘的功能 ** @param activity */private void enableCloseKeyboardOnTouchOutside(Activity activity) {CloseKeyboardOnOutsideContainer frameLayout = new CloseKeyboardOnOutsideContainer(activity);activity.addContentView(frameLayout, new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,ViewGroup.LayoutParams.MATCH_PARENT));}/** * 设置View树监听,以便判断键盘是否弹出。<br/> * 【只有当Activity的windowSoftInputMode设置为adjustResize时才有效】 */private void detectKeyboard() {final View activityRootView = ((ViewGroup) activity.findViewById(android.R.id.content)).getChildAt(0);if (activityRootView != null) {ViewTreeObserver observer = activityRootView.getViewTreeObserver();if (observer == null) {return;}observer.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {@Overridepublic void onGlobalLayout() {final Rect r = new Rect();activityRootView.getWindowVisibleDisplayFrame(r);int heightDiff = DisplayUtils.getScreenHeight()- (r.bottom - r.top);boolean show = heightDiff >= sKeyBoardHeight / 2;setKeyboardShowing(show);// 设置键盘是否展开状态if (show) {int keyboardHeight = heightDiff- DisplayUtils.getStatusBarHeight();// 设置新的键盘高度setKeyBoardHeight(keyboardHeight);}}});}}/** * ViewBinder的触发按钮点击的监听器 ** @author 李长军 **/public static interface OnTriggerClickListener {/** ** @param v */public void onClick(View v);}/** * 用于控制点击某个按钮显示或者隐藏“表情面板”的绑定bean对象。<br/> * 例如:我想点击“表情”按钮显示“表情面板”,我就可以这样做:<br/> * ViewBinder viewBinder = new ViewBinder(btn_emotion,emotionPanel);<br/> * 这样就创建出了一个ViewBinder对象<br/> * <font color="red">【注意事项,使用此类时,千万不要使用trigger的setOnClickListener来监听事件( * 使用OnTriggerClickListener来代替),也不要使用setTag来设置Tag,否则会导致使用异常】</font> ** @author 李长军 **/public static class ViewBinder {private View trigger;private View panel;private OnTriggerClickListener listener;/** * 创建ViewBinder对象<br/> * 例如:我想点击“表情”按钮显示“表情面板”,我就可以这样做:<br/> * ViewBinder viewBinder = new * ViewBinder(btn_emotion,emotionPanel,listener);<br/> * 这样就创建出了一个ViewBinder对象 ** @param trigger *触发对象 * @param panel *点击触发对象需要显示/隐藏的面板对象 * @param listener *Trigger点击的监听器(千万不要使用setOnClickListener,否则会覆盖本工具类的监听器) */public ViewBinder(View trigger, View panel,OnTriggerClickListener listener) {this.trigger = trigger;this.panel = panel;this.listener = listener;trigger.setClickable(true);}@Overridepublic boolean equals(Object obj) {if (this == obj)return true;if (obj == null)return false;if (getClass() != obj.getClass())return false;ViewBinder other = (ViewBinder) obj;if (panel == null) {if (other.panel != null)return false;} else if (!panel.equals(other.panel))return false;if (trigger == null) {if (other.trigger != null)return false;} else if (!trigger.equals(other.trigger))return false;return true;}public OnTriggerClickListener getListener() {return listener;}public void setListener(OnTriggerClickListener listener) {this.listener = listener;}public View getTrigger() {return trigger;}public void setTrigger(View trigger) {this.trigger = trigger;}public View getPanel() {return panel;}public void setPanel(View panel) {this.panel = panel;}}/** * 点击软键盘区域以外自动关闭软键盘的遮罩View ** @author 李长军 */private class CloseKeyboardOnOutsideContainer extends FrameLayout {public CloseKeyboardOnOutsideContainer(Context context) {this(context, null);}public CloseKeyboardOnOutsideContainer(Context context,AttributeSet attrs) {this(context, attrs, 0);}public CloseKeyboardOnOutsideContainer(Context context,AttributeSet attrs, int defStyle) {super(context, attrs, defStyle);}@Overridepublic boolean dispatchTouchEvent(MotionEvent event) {boolean isKeyboardShowing = isKeyboardShowing();boolean isEmotionPanelShowing = hasPanelShowing();if ((isKeyboardShowing || isEmotionPanelShowing)&& event.getAction() == MotionEvent.ACTION_DOWN) {int touchY = (int) (event.getY());int touchX = (int) (event.getX());if (isTouchKeyboardOutside(touchY)) {if (isKeyboardShowing) {hideKeyBordAndSetFlag(activity.getCurrentFocus());}if (isEmotionPanelShowing) {closeAllPanels();}}if (isTouchedFoucusView(touchX, touchY)) {// 如果点击的是输入框,那么延时折叠表情面板postDelayed(new Runnable() {@Overridepublic void run() {setKeyboardShowing(true);}}, 500);}}return super.onTouchEvent(event);}}/** * 屏幕参数的辅助工具类。例如:获取屏幕高度,宽度,statusBar的高度,px和dp互相转换等 * 【注意,使用之前一定要初始化!一次初始化就OK(建议APP启动时进行初始化)。 初始化代码 DisplayUtils.init(context)】 ** @author 李长军 2016.11.25 */private static class DisplayUtils {// 四舍五入的偏移值private static final float ROUND_CEIL = 0.5f;// 屏幕矩阵对象private static DisplayMetrics sDisplayMetrics;// 资源对象(用于获取屏幕矩阵)private static Resources sResources;// statusBar的高度(由于这里获取statusBar的高度使用的反射,比较耗时,所以用变量记录)private static int statusBarHeight = -1;/** * 初始化操作 ** @param context *context上下文对象 */public static void init(Context context) {sDisplayMetrics = context.getResources().getDisplayMetrics();sResources = context.getResources();}/** * 获取屏幕高度 单位:像素 ** @return 屏幕高度 */public static int getScreenHeight() {return sDisplayMetrics.heightPixels;}/** * 获取屏幕宽度 单位:像素 ** @return 屏幕宽度 */public static float getDensity() {return sDisplayMetrics.density;}/** * dp 转 px ** @param dp *dp值 * @return 转换后的像素值 */public static int dp2px(int dp) {return (int) (dp * getDensity() + ROUND_CEIL);}/** * 获取状态栏高度 ** @return 状态栏高度 */public static int getStatusBarHeight() {// 如果之前计算过,直接使用上次的计算结果if (statusBarHeight == -1) {final int defaultHeightInDp = 19;// statusBar默认19dp的高度statusBarHeight = DisplayUtils.dp2px(defaultHeightInDp);try {Class<?> c = Class.forName("com.android.internal.R$dimen");Object obj = c.newInstance();Field field = c.getField("status_bar_height");statusBarHeight = sResources.getDimensionPixelSize(Integer.parseInt(field.get(obj).toString()));} catch (Exception e) {e.printStackTrace();}}return statusBarHeight;}}}以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持脚本之家。