小松的技术博客

六和敬

若今生迷局深陷,射影含沙。便许你来世袖手天下,一幕繁华。 你可愿转身落座,掌间朱砂,共我温酒煮茶。

Dialog的高度自适应SoftKeyboard的升起与降落

Android处理有键盘显示隐藏的情况是比较麻烦的。如果网上搜的话,可以看到实现方案基本是基于getViewTreeObserver(). addOnGlobalLayoutListener的,经典代码如下:

//from:http://www.cnblogs.com/gejs/p/4363460.html
et.getViewTreeObserver().addOnGlobalLayoutListener(new OnGlobalLayoutListener(){

        //当键盘弹出隐藏的时候会 调用此方法。
        @Override
        public void onGlobalLayout() {
            Rect r = new Rect();
            //获取当前界面window可视部分
            MainActivity.this.getWindow().getDecorView().getWindowVisibleDisplayFrame(r);
            //获取屏幕的高度
            int screenHeight =  MainActivity.this.getWindow().getDecorView().getRootView().getHeight();
            //此处就是用来获取键盘的高度的, 在键盘没有弹出的时候 此高度为0 键盘弹出的时候为一个正数
            int heightDifference = screenHeight - r.bottom;
            Log.d("Keyboard Size", "Size: " + heightDifference);
        }

    });

看似好像键盘的升起和降落都得到了解决,但却只是看似,网上大多数教程都没有指出上述代码的适用场景,那就是:

  1. 在Activity中弹其键盘
  2. Activity在manifest中注册了android:windowSoftInputMode="adjustResize"

其原理可通过以下几步解释清楚:

  1. 就是在键盘弹器降落时,activity会调整窗口大小而不被键盘遮挡,这个时候会触发activity中view得layout事件。
  2. 通过view的getViewTreeObserver(). addOnGlobalLayoutListener方法添加的监听器会在layout结束后触发,我们在这个时机来获取activity中可视区域的高度和整体高度,然后通过差值计算键盘高度以及判断显示隐藏状态

这个方法有用但是有需要注意的地方:

  1. getViewTreeObserver(). addOnGlobalLayoutListener会在每次layout调用,可能产生性能问题
  2. 这种方法对Dialog弹键盘无效。

Dialog中有输入的情况是蛮少见的,但最近的设计频繁出现,因此也就必须解决上述的第二个问题了。经过多次尝试,发现还是发现了其规律,也就总结在此。

Dialog在键盘升起时会自动往上移动的,如果Dialog高度小,那么它会自动居于剩余空间的中间,我们不用去任何事情。但如果Dialog的高度大于剩余空间的高度呢,Dialog就会有部分被遮挡住,这个时候产品需求一般就是减小dialog的高度,使之完全显示在输入法的上方。那么需要怎么做呢?

我们依旧是通过getViewTreeObserver(). addOnGlobalLayoutListener来做,但是getWindowVisibleDisplayFrame(r)显示的却不是键盘上方空间的大小了,.getDecorView().getRootView().getHeight()显示的也不是屏幕高度了。

我们通过实验来获取两者的数据,然后看看能不能有所发现,测试代码如下:

mRootView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
    public void onGlobalLayout() {
        View mDecor = mDialog.getWindow().getDecorView();
        Rect r = new Rect();
        Log.d("mScrollHeight",""+mScrollHeight); //收集设置的scrollViewHeight
        mDecor.getWindowVisibleDisplayFrame(r);
        Log.d("r.bottom",""+r.bottom); //收集getWindowVisibleDisplayFrame得到的r.bottom的值
        int mDecorHeight = mDecor.getHeight();
        Log.d("mDecorHeight",""+mDecorHeight);//收集DecorView的高度
    }
});

我们通过改变Dialog的子元素scrollView的高度来改变Dialog的高度,然后收集信息,看看结果:

实验1.设置高度1200

键盘降落状体

11-22 22:25:27.580 32168-32168/org.cgspine.smdialog D/mScrollHeight: 1200
11-22 22:25:27.580 32168-32168/org.cgspine.smdialog D/r.bottom: 1920
11-22 22:25:27.580 32168-32168/org.cgspine.smdialog D/mDecorHeight: 1631

键盘升起状态

11-22 22:29:42.720 32168-32168/org.cgspine.smdialog D/mScrollHeight: 1200
11-22 22:29:42.720 32168-32168/org.cgspine.smdialog D/r.bottom: 1322
11-22 22:29:42.720 32168-32168/org.cgspine.smdialog D/mDecorHeight: 1631

将键盘高度调高

11-22 22:33:24.790 32168-32168/org.cgspine.smdialog D/mScrollHeight: 1200
11-22 22:33:24.790 32168-32168/org.cgspine.smdialog D/r.bottom: 1102
11-22 22:33:24.790 32168-32168/org.cgspine.smdialog D/mDecorHeight: 1631

实验2.设置高度900

键盘降落状体

11-22 22:30:47.910 32168-32168/org.cgspine.smdialog D/mScrollHeight: 900
11-22 22:30:47.910 32168-32168/org.cgspine.smdialog D/r.bottom: 1920
11-22 22:30:47.910 32168-32168/org.cgspine.smdialog D/mDecorHeight: 1331

键盘升起状态

11-22 22:31:11.790 32168-32168/org.cgspine.smdialog D/mScrollHeight: 900
11-22 22:31:11.790 32168-32168/org.cgspine.smdialog D/r.bottom: 1622
11-22 22:31:11.790 32168-32168/org.cgspine.smdialog D/mDecorHeight: 1331

实验3.设置高度400(升起后也不会被遮挡)

键盘降落状体

11-22 22:31:40.150 32168-32168/org.cgspine.smdialog D/mScrollHeight: 400
11-22 22:31:40.150 32168-32168/org.cgspine.smdialog D/r.bottom: 1920
11-22 22:31:40.150 32168-32168/org.cgspine.smdialog D/mDecorHeight: 831

键盘升起状态

11-22 22:32:22.290 32168-32168/org.cgspine.smdialog D/mScrollHeight: 400
11-22 22:32:22.290 32168-32168/org.cgspine.smdialog D/r.bottom: 1920
11-22 22:32:22.290 32168-32168/org.cgspine.smdialog D/mDecorHeight: 831

以上测试基本涵盖了键盘高度、Dialog内容高度、键盘状态对这些参数的影响,并可以推断出一下结论:

  1. DecorView的高度不受键盘影响,这其实就是dialog的高度,读者可以自己验证;
  2. 如果dialog不会被键盘遮住,那么r.bottom始终为屏幕高度,并不会改变
  3. 如果dialog会被键盘遮挡,嘛呢r.bottom的值会减小,且键盘越高,减小的值越大
  4. 可以进一步计算一下(键盘升起状态用mDecorHeight + r.bottom - 1920):

    • 实验2:1322 + 1631 - 1920 = 1033
    • 实验1:1622 + 1331 - 1920 = 1033

    我们可以发现得到的值竟然相等

如此我便有了一下猜想:

r.bottom = 屏幕高度 - dialog被键盘遮挡住的高度

那么该如何验证呢?

我们可以用猜想去推测结论4的值代表的是什么?

因为dialog部分被遮挡,因此必然dailog被推到屏幕最上方,然后我们把猜想代入结论四的公式中:我们以实验2为例:

1622 + 1331 - 1920 
= 屏幕高度 - dialog被键盘遮挡住的高度 + 1331 -1920
= 1920 - dialog被键盘遮挡住的高度 + 1331 - 1920
= 1331 - dialog被键盘遮挡住的高度
= dialog高度 - dialog被键盘遮挡住的高度
= dialog可视区域的高度
= 状态栏到键盘头部的距离
= 1033

这样我们就去用测量工具进行测量,我通过测量,刚好符合,说明自己的推断还是蛮准确的,然后个人在已知的设备上实践,基本上都OK。

通过上面的换算,我们还能够获取键盘的高度:

键盘高度 = 屏幕高度 - (r.bottom+mDecorHeight - 屏幕高度) - r.top(为状态栏高度)
= 2*屏幕高度 - (r.bottom + r.top + mDecorHeight)

基于这个结论,自己写了一个提供高度随键盘高度升起降落变化的例子,也提交到了gitup上,可以下载参考(提示:dialog最好保持它的window大小恒定,否则在移动动画中会有跳动,后期有空会解释它的原因),链接如下:

https://github.com/cgspine/SMDialog

←支付宝← →微信 →
comments powered by Disqus