JKeyboardPanelSwitch键盘面板防抖方案

背景

咱app社交模块,键盘和自定义面板切换的时候,出现了闪烁的现象
我们使用了JKeyboardPanelSwitch解决了这个问题,此篇文章简要分析其原理。

聊天面板、键盘切换闪烁

键盘、面板交互方式为:
键盘.show();
面板.hide();

软键盘布局解释

项目中,常用的软键盘弹出模式(windowSoftInputMode)有两种

  • adjustResize
    布局高度压缩,腾出位置给键盘
  • adjustPan
    键盘空间不够的时候,布局整体向上推,腾出位置给键盘


    软键盘弹出模式,adjustPan整体平移标题栏消失

原因分析

键盘、面板的visible操作,触发了两次根布局的onMeasure、onLayout事件,

键盘->自定义面板
1. onMeasure,宽:1080 高:1127
2. onLayout,宽:1080 高:1127 
3. onMeasure,宽:1080 高:1127
4. onMeasure,宽:1080 高:1930
5. onLayout,宽:1080 高:1930 
第3步时候的UI,高度还未恢复的时候,面板已出现,位置明显偏上
自定义面板->键盘
1. onMeasure,宽:1080 高:1930
2. onLayout,宽:1080 高:1930 
3. onMeasure,宽:1080 高:1930
4. onMeasure,宽:1080 高:1127
5. onLayout,宽:1080 高:1127 
第3步时候的UI,此时面板已经消失,键盘从底部弹出的动画比较突兀

解决思路

简单点说,界面切换平滑的前提:

  1. panel的显示、隐藏操作,不能引起重新测量、重新布局。
  2. panel的高度和键盘一致,这样切换面板,输入框的Y轴位置保持固定。

下面,我们看JKeyboardPanelSwitch是如何解决闪动问题

1.避免重复onLayout方法

根布局两次Layout的触发的原因

  • 在adjustResize情况下,键盘弹开、收起
  • 位于面板容器visible状态改变,引起父容器重新测量
2.键盘、面板高度一致

在监听根布局的时候,收缩、展开高度的差,经过计算,就是键盘的高度。把该高度赋值给面板,我们就能保证键盘、面板切换时,输入框在Y轴上的位置不变了。

JKeyboardPanelSwitch核心类分析

JKeyboardPanelSwitch在布局上提供了两个容器

  1. 根节点容器:通过布局高度的变化,计算出偏差值,得出键盘高度,通知面板显示、隐藏。
  2. 面板容器:负责接收根节点的隐藏、显示通知,并延后至onMeasure里执行以防闪烁。


    根节点onGlobalLayout流程

根节点

根节点容器处理了两个重要的事件,我们逐个分析

  • onGlobalLayout
    计算键盘高度、并赋值给面板容器;
    计算键盘是否弹出状态

  • onMeasure
    根据布局高度变化的差值,通知面板显示、隐藏

为什么要在onGlobalLayout中处理真正的键盘变化并且进行键盘高度变化存储?

因为效率:由于onMeasure与onLayout可能被多次调用,而onGlobalLayout是布局变化后只会被一次调用,并且我们需要处理所有键盘高度的变化(如搜狗输入法的动态调整键盘高度)因此在onGlobalLayout中计算键盘高度变化以及有效高度进行存储更为恰当。

为什么需要在根节点的onMeasure中判断,而不是其他地方判断是否是真正键盘引起变化的?

对于我们可见的'最早'获知布局变化的,就是根节点的onMeasure,其次,必须要在面板容器的onMeasure之前获知,因此比较恰当的地方就是面板容器的父布局的onMeasure,还有就是,其实真正感知键盘变化我们可以非常确定的也只有对我们可见的最外层的布局。综合之,并且就封装而言就只能是根节点的onMeasure甚至更早。

面板节点

面板节点重写了setVisibility()、onMeasure()函数,修改了显示、隐藏操作触发的时机;

  • 显示:键盘->面板
    在键盘收起、面板展开的时候,拦截面板的setVisibility操作;
    延迟到在根节点因键盘收起完毕导致onMeasure的事件里,此时再显示面板。
  @Override
    public void setVisibility(int visibility) {
拦截用户显示操作,用于键盘收起->面板展开的流程
visible的操作,在根布局的onMeasure里会通知
 if (panelLayoutHandler.filterSetVisibility(visibility)) {
            return;
        }
        super.setVisibility(visibility);
    }
  • 隐藏:面板->键盘
    根节点键盘弹出完毕后,会触发一次onMeasure,此时标记面板要隐藏
    根节点分发onMeasure事件到面板节点,面板节点检测到隐藏标记,在此时Gone掉自己,同时把宽高设置为0,从而在当前帧不会被键盘顶起
 public int[] processOnMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        if (mIsHide) {
            panelLayout.setVisibility(View.GONE);
            /*
             * The current frame will be visible nil.
             */
            widthMeasureSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.EXACTLY);
            heightMeasureSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.EXACTLY);
        }

        final int[] processedMeasureWHSpec = new int[2];
        processedMeasureWHSpec[0] = widthMeasureSpec;
        processedMeasureWHSpec[1] = heightMeasureSpec;

        return processedMeasureWHSpec;
    }

小结:我们基于 adjustResize在根布局可以监听到布局改变,从而控制面板达到防抖动效果。
但是在以下两种状态下,onMeasure检测不到高度的改变,我们需要另行处理

  • 全屏模式
  • 状态栏透明,但是根布局没有设置fitSystemWindow=true

onMeasure失效情况下的做法

键盘模式采用adjustPan。这种模式下,我们仅需要引入面板节点即可。
adjustPan我们知道,当输入框底部的空隙可以容纳键盘高度时,界面是不会滚动的。
因此,JKeyboardPanelSwitch的做法是:

  • 键盘弹出
    在输入框触摸时,把面板设置为invisible,把输入框顶高。此时键盘弹出的空间足够,界面就不会滚动
  focusView.setOnTouchListener(new View.OnTouchListener() {
                @Override
                public boolean onTouch(View v, MotionEvent event) {
                    if (event.getAction() == MotionEvent.ACTION_UP) {
                        /*
                         * Show the fake empty keyboard-same-height panel to fix the conflict when
                         * keyboard going to show.
                         * @see KPSwitchConflictUtil#showKeyboard(View, View)
                         */
                        panelLayout.setVisibility(View.INVISIBLE);
                    }
                    return false;
                }
            });
  • 键盘隐藏
    根布局的onGlobalLayout检测到键盘高度发生了变化,如果是收起,则通知面板去隐藏
    KPSwitchFSPanelLayoutHandler#onKeyboardShowing

  • 面板弹出
    面板从InVisible 切换到Visible,隐藏键盘

额外注意
   @Override
    protected void onPause() {
        super.onPause();
        panelRoot.recordKeyboardStatus(getWindow());
    }

我们可以看到接入的Activity的onPause函数里多了一段代码。注释掉后,发现Activity的切到后台,回来之后,界面又被顶上去了。我们回顾下原因

  1. activity切到后台,键盘隐藏,触发onGlobalLayout,隐藏panel
  2. 切回activity,因为键盘拥有焦点,activity如果没有声明stateAlwaysHidden,键盘会自动弹出。
  3. 此时因为panel是gone的,键盘容纳空间不够,因此会把界面顶上去。

因此,我们在onPause的时候,需要标记键盘是否弹出

 private void saveFocusView(final View focusView) {
        recordedFocusView = focusView;
        focusView.clearFocus();
        panelLayout.setVisibility(View.GONE);
    }

在切回activity的时候触发onGlobalLayout,判断是否有保存的标记,如果有,手动执行一次面板invisble、键盘弹出操作

private void restoreFocusView() {
        panelLayout.setVisibility(View.INVISIBLE);
        KeyboardUtil.showKeyboard(recordedFocusView);

    }
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,744评论 6 502
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,505评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,105评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,242评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,269评论 6 389
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,215评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,096评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,939评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,354评论 1 311
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,573评论 2 333
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,745评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,448评论 5 344
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,048评论 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,683评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,838评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,776评论 2 369
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,652评论 2 354

推荐阅读更多精彩内容