Android输入法弹出时覆盖输入框问题

天下之事常成于困约,而败于奢靡。这篇文章主要讲述Android输入法弹出时覆盖输入框问题相关的知识,希望能为你提供帮助。
本文来自网易云社区
作者:孙有军
 
  当一个activity中含有输入框时,我们点击输入框,会弹出输入法界面,整个界面的变化效果与manifest中对应设置的android:windowSoftInputMode属性有关,一般可以设置的值如下,

< activity  android:windowSoftInputMode=["stateUnspecified","stateUnchanged”,  "stateHidden", "stateAlwaysHidden”,  "stateVisible","stateAlwaysVisible”,  "adjustUnspecified", "adjustResize”,  "adjustPan"]  ……  >

      具体怎么设置可以查看官方文档。今天主要解决当输入法弹出时会覆盖输入框的问题。

什么情况会覆盖?
      当android的应用中如果一个activity设置了全屏属性Theme.Light.NotittleBar.Fullscreen或者设置了activity对应的主题中android:windowTranslucentStatus属性,设置方式为:`< item  name="android:windowTranslucentStatus"> true< /item> `,这是如果对应的页面上含有输入框,将会导致点击输入框时软键盘弹出后键盘覆盖输入框,导致输入框看不见。

为什么?
      这其实是因为在全屏时,adjustResize属性已经失效了,该问题是系统的一个bug,[参考链接](http://code.google.com/p/android/issues/detail?id=5497)。adjustResize不生效,那有没有其他方法来解决呐?  这时我们可以设置adjust属性为adjustPan属性,该属性不会失效,但是由于adjustPan会将页面整体平移,以留出输入法空间,会有一个抖动的效果,体验很差,哪有没有体验效果更好的方法呐?

解决方案:
      如果跟布局采用FrameLayout,则可以复写一个自定义FrameLayout,同时设置FrameLayout的android:fitsSystemWindows属性为true。xml设置如下

< com.sample.ui.widget.InsetFrameLayout  xmlns:android="http://schemas.android.com/apk/res/android"                                       android:layout_width="match_parent"                                       android:layout_height="match_parent"                                       android:fitsSystemWindows="true”>

      我们自定义该FrameLayout为InsetFrameLayout,InsetFrameLayout  代码如下:

public  final  class  InsetFrameLayout  extends  FrameLayout  {        private  int[]  mInsets  =  new  int[4];         public  InsetFrameLayout(Context  context)  {                super(context);         }        public  InsetFrameLayout(Context  context,  AttributeSet  attrs)  {                super(context,  attrs);         }        public  InsetFrameLayout(Context  context,  AttributeSet  attrs,  int  defStyle)  {                super(context,  attrs,  defStyle);         }        public  final  int[]  getInsets()  {                return  mInsets;         }        @Override         protected  final  boolean  fitSystemWindows(Rect  insets)  {                if  (Build.VERSION.SDK_INT  > =  Build.VERSION_CODES.KITKAT)  {                        //  Intentionally  do  not  modify  the  bottom  inset.  For  some  reason,                         //  if  the  bottom  inset  is  modified,  window  resizing  stops  working.                        mInsets[0]  =  insets.left;                         mInsets[1]  =  insets.top;                         mInsets[2]  =  insets.right;                         insets.left  =  0;                         insets.top  =  0;                         insets.right  =  0;                 }                return  super.fitSystemWindows(insets);         }        @Override         public  final  WindowInsets  onApplyWindowInsets(WindowInsets  insets)  {                if  (Build.VERSION.SDK_INT  > =  Build.VERSION_CODES.KITKAT_WATCH)  {                         mInsets[0]  =  insets.getSystemWindowInsetLeft();                         mInsets[1]  =  insets.getSystemWindowInsetTop();                         mInsets[2]  =  insets.getSystemWindowInsetRight();                         return  super.onApplyWindowInsets(insets.replaceSystemWindowInsets(0,  0,  0,                                         insets.getSystemWindowInsetBottom()));                 }  else  {                        return  insets;                 }         }}

官方解决方案:
      官方其实也发现了问题,因此在android.support.design.internal下也重写了FrameLayout来解决该问题,但是该类被标记了hide。

/*   *  Copyright  (C)  2015  The  Android  Open  Source  Project   *   *  Licensed  under  the  Apache  License,  Version  2.0  (the  "License");   *  you  may  not  use  this  file  except  in  compliance  with  the  License.   *  You  may  obtain  a  copy  of  the  License  at   *   *            http://www.apache.org/licenses/LICENSE-2.0   *   *  Unless  required  by  applicable  law  or  agreed  to  in  writing,  software   *  distributed  under  the  License  is  distributed  on  an  "AS  IS"  BASIS,   *  WITHOUT  WARRANTIES  OR  CONDITIONS  OF  ANY  KIND,  either  express  or  implied.   *  See  the  License  for  the  specific  language  governing  permissions  and   *  limitations  under  the  License.   */package  android.support.design.internal; import  android.content.Context; import  android.content.res.TypedArray; import  android.graphics.Canvas; import  android.graphics.Rect; import  android.graphics.drawable.Drawable; import  android.support.annotation.NonNull; import  android.support.design.R; import  android.support.v4.view.ViewCompat; import  android.support.v4.view.WindowInsetsCompat; import  android.util.AttributeSet; import  android.view.View; import  android.widget.FrameLayout; /**   *  @hide   */public  class  ScrimInsetsFrameLayout  extends  FrameLayout  {        private  Drawable  mInsetForeground;         private  Rect  mInsets;         private  Rect  mTempRect  =  new  Rect();         public  ScrimInsetsFrameLayout(Context  context)  {                this(context,  null);         }        public  ScrimInsetsFrameLayout(Context  context,  AttributeSet  attrs)  {                this(context,  attrs,  0);         }        public  ScrimInsetsFrameLayout(Context  context,  AttributeSet  attrs,  int  defStyleAttr)  {                super(context,  attrs,  defStyleAttr);                 final  TypedArray  a  =  context.obtainStyledAttributes(attrs,                                 R.styleable.ScrimInsetsFrameLayout,  defStyleAttr,                                 R.style.Widget_Design_ScrimInsetsFrameLayout);                 mInsetForeground  =  a.getDrawable(R.styleable.ScrimInsetsFrameLayout_insetForeground);                 a.recycle();                 setWillNotDraw(true);   //  No  need  to  draw  until  the  insets  are  adjusted                ViewCompat.setOnApplyWindowInsetsListener(this,                                new  android.support.v4.view.OnApplyWindowInsetsListener()  {                                        @Override                                         public  WindowInsetsCompat  onApplyWindowInsets(View  v,                                                         WindowInsetsCompat  insets)  {                                                if  (null  ==  mInsets)  {                                                         mInsets  =  new  Rect();                                                 }                                                 mInsets.set(insets.getSystemWindowInsetLeft(),                                                                 insets.getSystemWindowInsetTop(),                                                                 insets.getSystemWindowInsetRight(),                                                                 insets.getSystemWindowInsetBottom());                                                 setWillNotDraw(mInsets.isEmpty()  ||  mInsetForeground  ==  null);                                                 ViewCompat.postInvalidateOnAnimation(ScrimInsetsFrameLayout.this);                                                 return  insets.consumeSystemWindowInsets();                                         }                                 });         }        @Override         public  void  draw(@NonNull  Canvas  canvas)  {                super.draw(canvas);                 int  width  =  getWidth();                 int  height  =  getHeight();                 if  (mInsets  !=  null  & &   mInsetForeground  !=  null)  {                        int  sc  =  canvas.save();                         canvas.translate(getScrollX(),  getScrollY());                         //  Top                         mTempRect.set(0,  0,  width,  mInsets.top);                         mInsetForeground.setBounds(mTempRect);                         mInsetForeground.draw(canvas);                         //  Bottom                         mTempRect.set(0,  height  -  mInsets.bottom,  width,  height);                         mInsetForeground.setBounds(mTempRect);                         mInsetForeground.draw(canvas);                         //  Left                         mTempRect.set(0,  mInsets.top,  mInsets.left,  height  -  mInsets.bottom);                         mInsetForeground.setBounds(mTempRect);                         mInsetForeground.draw(canvas);                         //  Right                         mTempRect.set(width  -  mInsets.right,  mInsets.top,  width,  height  -  mInsets.bottom);                         mInsetForeground.setBounds(mTempRect);                         mInsetForeground.draw(canvas);                         canvas.restoreToCount(sc);                 }         }        @Override         protected  void  onAttachedToWindow()  {                super.onAttachedToWindow();                 if  (mInsetForeground  !=  null)  {                         mInsetForeground.setCallback(this);                 }         }        @Override         protected  void  onDetachedFromWindow()  {                super.onDetachedFromWindow();                 if  (mInsetForeground  !=  null)  {                         mInsetForeground.setCallback(null);                 }         }}

      采用如上其中的任何一种方法就可以解决输入法弹出后覆盖输入框问题。

其他问题?
      在我们使用的过程中发现有用户反馈,说只要进入我们采用该布局的页面就会崩溃,我们查看了崩溃日志,发现有部分手机都使用了相同的一个安卓系统,并且版本都是19,android4.4.x,一个被重写过的系统,该系统的代码加载方式被重写了。

为什么会崩溃?
      我们代码使用到了WindowInsets,该类是api  20才提供的,因此19的系统中其实是没有该代码的,但是该系统在xml的inflate的时候就解析了该类,导致classNotFound。

新的解决方案!
      新的解决方案还是采用了上述的方式,不过会针对不同的版本写不一样的布局,分别为api  20以上与20以下提供不同的布局,这是采用系统的限定符实现的,之后20以上的原样采用上述的方式,20以下去掉onApplyWindowInsets复写,这样不同的版本加载不同的代码就OK了。

  @Override         public  final  WindowInsets  onApplyWindowInsets(WindowInsets  insets)  {                if  (Build.VERSION.SDK_INT  > =  Build.VERSION_CODES.KITKAT_WATCH)  {                         mInsets[0]  =  insets.getSystemWindowInsetLeft();                         mInsets[1]  =  insets.getSystemWindowInsetTop();                         mInsets[2]  =  insets.getSystemWindowInsetRight();                         return  super.onApplyWindowInsets(insets.replaceSystemWindowInsets(0,  0,  0,                                         insets.getSystemWindowInsetBottom()));                 }  else  {                        return  insets;                 }         }

总结
      到此整个解决方案已经完成了,如过有更新的解决方式望大家分享。

 
 
网易云免费体验馆,0成本体验20+款云产品!
更多网易研发、产品、运营经验分享请访问网易云社区。
【Android输入法弹出时覆盖输入框问题】 
相关文章:
【推荐】  SpringBoot入门(四)——自动配置
【推荐】  网易云易盾朱星星:最容易被驳回的10大APP过检项

    推荐阅读