弹出键盘布局闪动原理和解决

  在开发中,遇到一个问题:做一个微信一样,表情输入和软键盘在切换的时候,聊天界面不闪动的问题。为了解决这个问题,需要知道一下Android的软键盘弹出的时候发生的几个变化。

  当AndroidMainfest.xml 中配置android:windowSoftInputMode="adjustResize|stateHidden" 属性后,如果弹出软键盘,那么会重绘界面。基本流程如下(API 10):

    1. Android 收到打开软键盘命令

    2. Android 打开软键盘后,调用App 注册在AWM 中的接口,告知它,界面需要进行变化.

      2.1 调用ViewRoot.java#W#resized(w,h)

      2.2 调用viewRoot.dispatchResized()

      2.3构造Message msg = obtainMessage(reportDraw ? RESIZED_REPORT :RESIZED),然后post过去

    3. 在RootView的handleMessage的caseRESIZED_REPORT: 收到具体的大小,配置App Window的大小,特别是bottom 的大小, 最后调用requestLayout进行重绘

  所以只要在父布局onMeasure之前,隐藏/显示 合适高度的VIEW,既可以使得其他子VIEW高度不变化,从而避免界面闪动。引用自http://blog.dreamtobe.cn/2015/09/01/keyboard-panel-switch/。

  代码如下:

  Layout:

<?xml version="1.0" encoding="utf-8"?><com.test.MyActivity.MyLineLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:id="@+id/mll_main" android:layout_width="match_parent" android:layout_height="match_parent">    <FrameLayout            android:background="#f3f3f3" android:id="@+id/fl_list" android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="1">        <TextView                android:gravity="center" android:layout_width="match_parent" android:layout_height="match_parent" android:text="DARKGEM"/>    </FrameLayout>    <LinearLayout            android:id="@+id/ll_edit" android:layout_width="match_parent" android:orientation="horizontal" android:layout_height="50dp">        <EditText                android:id="@+id/et_input" android:layout_width="0dp" android:layout_weight="1" android:layout_height="match_parent"/>        <Button                android:id="@+id/btn_trigger" android:text="trigger" android:layout_width="100dp" android:layout_height="match_parent"/>    </LinearLayout>    <FrameLayout            android:id="@+id/fl_panel" android:background="#CCCCCC" android:layout_width="match_parent" android:layout_height="0dp"/></com.test.MyActivity.MyLineLayout>

  Activity:


package com.test;import android.app.Activity;import android.content.Context;import android.graphics.Rect;import android.os.Bundle;import android.util.AttributeSet;import android.util.Log;import android.view.MotionEvent;import android.view.View;import android.view.WindowManager;import android.view.inputmethod.InputMethodManager;import android.widget.Button;import android.widget.EditText;import android.widget.FrameLayout;import android.widget.LinearLayout; /** * <pre> * 聊天界面布局闪动处理, 基本原理如下: *          1. 弹出键盘的时候,会导致 RootView 的bottom 变小,直到容纳 键盘+虚拟按键 *          2. 收回键盘的时候,会导致 RootView的bottom 变大,直到容纳 虚拟键盘 *          3. 因为RootView bottom的变化,会导致整个布局高度(bottom - top)的变化,所以就会发生布局闪动的情况. 而为了 *          避免这种情况,只需要在发生变动的父布局调用 onMeasure() 之前,将子View的高度和配置为最终高度,既可以实现弹 *          出/收回键盘 不闪动<strong>特定部分布局</strong>的效果(如微信聊天界面)。 * </pre> */ public class MyActivity extends Activity {    MyLineLayout mll_main;    FrameLayout fl_list;    LinearLayout ll_edit;    EditText et_input;    Button btn_trigger;    FrameLayout fl_panel;    Rect rect = new Rect(); enum State { //空状态  NONE, //打开输入法状态  KEYBOARD, //打开面板状态  PANEL,    }    State state = State.NONE;    @Override protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.main);        mll_main = (MyLineLayout) findViewById(R.id.mll_main);        fl_list = (FrameLayout) findViewById(R.id.fl_list);        ll_edit = (LinearLayout) findViewById(R.id.ll_edit);        et_input = (EditText) findViewById(R.id.et_input);        btn_trigger = (Button) findViewById(R.id.btn_trigger);        fl_panel = (FrameLayout) findViewById(R.id.fl_panel);        mll_main.onMeasureListener = new MyLineLayout.OnMeasureListener() { /**             * 可能会发生多次 调用的情况,因为存在 layout_weight 属性,需要2次测试,给定最终大小             * */ @Override public void onMeasure(int maxHeight, int oldHeight, int nowHeight) { switch (state) { case NONE: {                    } break; case PANEL: { //state 处于 panel 状态只有一种可能,就是主动点击切换到panel, //1.如果之前是keyboard状态,则在本次onMeasure的时候,一定要把panel显示出来 //避免 mll 刷动 //2. 如果之前处于 none状态,那么本次触发来自于 postDelay,可以忽略  fl_panel.setVisibility(View.VISIBLE);                    } break; case KEYBOARD: { //state = KEYBOARD 状态,只有一种可能,就是主动点击了 EditText //1. 如果之前是panel状态,则一般已经有了固有高度,这个高度刚刚好满足键盘的高度,那么只用隐藏掉 //panel 既可以实现页面不进行刷新 //2. 如果之前为none状态,则可以忽略  fl_panel.setVisibility(View.GONE); //处于键盘状态,需要更新键盘高度为面板的高度 if (oldHeight >= nowHeight) { //记录当前的缩放大小为键盘大小 int h = maxHeight - nowHeight; //避免 输入法 悬浮状态, 保留一个最低高度 if (h < 500) {                                h = 500;                            }                            fl_panel.getLayoutParams().height = h;                        }                    } break;                }                Log.d("SC_SIZE", String.format("onMeasure %d %d %d", maxHeight, nowHeight, oldHeight));            }        };        fl_list.setOnTouchListener(new View.OnTouchListener() {            @Override public boolean onTouch(View v, MotionEvent event) {                hideSoftInputView();                fl_panel.setVisibility(View.GONE);                state = State.NONE; return false;            }        });        et_input.setOnClickListener(new View.OnClickListener() {            @Override public void onClick(View v) {                state = State.KEYBOARD;            }        });        btn_trigger.setOnClickListener(new View.OnClickListener() {            @Override public void onClick(View v) { switch (state) { case NONE: case KEYBOARD: {                        hideSoftInputView();                        state = State.PANEL; //无论App 处于什么状态,都追加一个 显示 panel 的方法,避免处于非正常状态无法打开panel getWindow().getDecorView().postDelayed(new Runnable() {                            @Override public void run() {                                fl_panel.setVisibility(View.VISIBLE);                            }                        }, 100);                    } break; case PANEL: {                        state = State.NONE;                        fl_panel.setVisibility(View.GONE);                    } break;                }            }        }); //设置基本panel 高度,以使得第一次能正常打开panel  getWindow().getDecorView().getWindowVisibleDisplayFrame(rect);        fl_panel.getLayoutParams().height = rect.height() / 2;        fl_panel.setVisibility(View.GONE);    } /**     * 隐藏软键盘输入 */ public void hideSoftInputView() {        InputMethodManager manager = ((InputMethodManager) this.getSystemService(Activity.INPUT_METHOD_SERVICE)); if (getWindow().getAttributes().softInputMode != WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN) { if (getCurrentFocus() != null && manager != null)                manager.hideSoftInputFromWindow(getCurrentFocus().getWindowToken(), InputMethodManager.HIDE_NOT_ALWAYS);        }    } /**     * Created by Administrator on 2015/11/20. */ public static class MyLineLayout extends LinearLayout {        OnMeasureListener onMeasureListener; int maxHeight = 0; int oldHeight; public MyLineLayout(Context context) {            super(context);        } public MyLineLayout(Context context, AttributeSet attrs) {            super(context, attrs);        }        @Override protected void onLayout(boolean changed, int l, int t, int r, int b) {            super.onLayout(changed, l, t, r, b);        }        @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int height = MeasureSpec.getSize(heightMeasureSpec); if (onMeasureListener != null) {                onMeasureListener.onMeasure(maxHeight, oldHeight, height);            }            oldHeight = height;            super.onMeasure(widthMeasureSpec, heightMeasureSpec);        }        @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) {            super.onSizeChanged(w, h, oldw, oldh); //之所以,在这里记录 maxHeight的大小,是因为 onMeasure 中可能多次调用,中间可能会逐步出现 ActionBar,BottomVirtualKeyboard, //所以 onMeasure中获取的maxHeight存在误差 if (h > maxHeight) {                maxHeight = h;            }            Log.d("SC_SIZE", String.format("Size Change %d %d", h, oldh));        } interface OnMeasureListener { void onMeasure(int maxHeight, int oldHeight, int nowHeight);        }    }}

测试效果图:

  Android弹出键盘布局闪动原理和解决_第1张图片 Android弹出键盘布局闪动原理和解决_第2张图片

 项目源码:

    https://git.oschina.net/darkgem/KeyboardSwitch.git

 APK:

    https://git.oschina.net/darkgem/KeyboardSwitch/raw/master/out/production/myapp/myapp.apk

 参考:

    https://github.com/angeldevil/KeyboardAwareDemo

    https://github.com/Jacksgong/JKeyboardPanelSwitch

更多相关文章

  1. Android 显示系统键盘搜索按键,实现搜索功能
  2. 关于Android弹出软键盘“顶起”View的问题
  3. android:layout_weight让layout自动调整到剩余高度
  4. Android:使用VideoView(TV显示屏待机状态,循环播放演示视频)
  5. Android中系统状态栏的隐藏和显示
  6. Android:获取屏幕完整宽高,包含状态栏
  7. Android 软键盘小知识点
  8. Android GPS状态改变与监听

随机推荐

  1. Android源码下载小结
  2. Android悬浮通知无效的问题
  3. Android(安卓)判断手机是否安装某个应用
  4. 无法在Android(安卓)Studio中设置断点
  5. Android(安卓)通过ViewPager实现广告轮播
  6. Android(安卓)DatePicker和TimePicker案
  7. 安卓新技术学习资料整理 常更新哦~
  8. android自定义属性attrs
  9. Android初学笔记之
  10. 快速开发android,离不开这10个优秀的开源