Android输入法弹出时覆盖输入框问题的解决方法
当一个activity中含有输入框时,我们点击输入框,会弹出输入法界面,整个界面的变化效果与manifest中对应设置的android:windowSoftInputMode属性有关,一般可以设置的值如下,
<activityandroid:windowSoftInputMode=[ "stateUnspecified", "stateUnchanged”, "stateHidden", "stateAlwaysHidden”, "stateVisible", "stateAlwaysVisible”, "adjustUnspecified", "adjustResize”, "adjustPan"]……>
具体怎么设置可以查看官方文档。今天主要解决当输入法弹出时会覆盖输入框的问题。
什么情况会覆盖?
当android的应用中如果一个activity设置了全屏属性Theme.Light.NotittleBar.Fullscreen或者设置了activity对应的主题中android:windowTranslucentStatus属性,设置方式为:<itemname="android:windowTranslucentStatus">true</item>,这是如果对应的页面上含有输入框,将会导致点击输入框时软键盘弹出后键盘覆盖输入框,导致输入框看不见。
为什么?
这其实是因为在全屏时,adjustResize属性已经失效了,该问题是系统的一个bug,参考链接。adjustResize不生效,那有没有其他方法来解决呐?这时我们可以设置adjust属性为adjustPan属性,该属性不会失效,但是由于adjustPan会将页面整体平移,以留出输入法空间,会有一个抖动的效果,体验很差,哪有没有体验效果更好的方法呐?
解决方案:
如果跟布局采用FrameLayout,则可以复写一个自定义FrameLayout,同时设置FrameLayout的android:fitsSystemWindows属性为true。xml设置如下
<com.sample.ui.widget.InsetFrameLayoutxmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:fitsSystemWindows="true”>
我们自定义该FrameLayout为InsetFrameLayout,InsetFrameLayout代码如下:
publicfinalclassInsetFrameLayoutextendsFrameLayout{ privateint[]mInsets=newint[4]; publicInsetFrameLayout(Contextcontext){ super(context); } publicInsetFrameLayout(Contextcontext,AttributeSetattrs){ super(context,attrs); } publicInsetFrameLayout(Contextcontext,AttributeSetattrs,intdefStyle){ super(context,attrs,defStyle); } publicfinalint[]getInsets(){ returnmInsets; } @Override protectedfinalbooleanfitSystemWindows(Rectinsets){ if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.KITKAT){ //Intentionallydonotmodifythebottominset.Forsomereason, //ifthebottominsetismodified,windowresizingstopsworking. mInsets[0]=insets.left; mInsets[1]=insets.top; mInsets[2]=insets.right; insets.left=0; insets.top=0; insets.right=0; } returnsuper.fitSystemWindows(insets); } @Override publicfinalWindowInsetsonApplyWindowInsets(WindowInsetsinsets){ if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.KITKAT_WATCH){ mInsets[0]=insets.getSystemWindowInsetLeft(); mInsets[1]=insets.getSystemWindowInsetTop(); mInsets[2]=insets.getSystemWindowInsetRight(); returnsuper.onApplyWindowInsets(insets.replaceSystemWindowInsets(0,0,0, insets.getSystemWindowInsetBottom())); }else{ returninsets; } } }
官方解决方案:
官方其实也发现了问题,因此在android.support.design.internal下也重写了FrameLayout来解决该问题,但是该类被标记了hide。
/* *Copyright(C)2015TheAndroidOpenSourceProject * *LicensedundertheApacheLicense,Version2.0(the"License"); *youmaynotusethisfileexceptincompliancewiththeLicense. *YoumayobtainacopyoftheLicenseat * *http://www.apache.org/licenses/LICENSE-2.0 * *Unlessrequiredbyapplicablelaworagreedtoinwriting,software *distributedundertheLicenseisdistributedonan"ASIS"BASIS, *WITHOUTWARRANTIESORCONDITIONSOFANYKIND,eitherexpressorimplied. *SeetheLicenseforthespecificlanguagegoverningpermissionsand *limitationsundertheLicense. */ packageandroid.support.design.internal; importandroid.content.Context; importandroid.content.res.TypedArray; importandroid.graphics.Canvas; importandroid.graphics.Rect; importandroid.graphics.drawable.Drawable; importandroid.support.annotation.NonNull; importandroid.support.design.R; importandroid.support.v4.view.ViewCompat; importandroid.support.v4.view.WindowInsetsCompat; importandroid.util.AttributeSet; importandroid.view.View; importandroid.widget.FrameLayout; /** *@hide */ publicclassScrimInsetsFrameLayoutextendsFrameLayout{ privateDrawablemInsetForeground; privateRectmInsets; privateRectmTempRect=newRect(); publicScrimInsetsFrameLayout(Contextcontext){ this(context,null); } publicScrimInsetsFrameLayout(Contextcontext,AttributeSetattrs){ this(context,attrs,0); } publicScrimInsetsFrameLayout(Contextcontext,AttributeSetattrs,intdefStyleAttr){ super(context,attrs,defStyleAttr); finalTypedArraya=context.obtainStyledAttributes(attrs, R.styleable.ScrimInsetsFrameLayout,defStyleAttr, R.style.Widget_Design_ScrimInsetsFrameLayout); mInsetForeground=a.getDrawable(R.styleable.ScrimInsetsFrameLayout_insetForeground); a.recycle(); setWillNotDraw(true);//Noneedtodrawuntiltheinsetsareadjusted ViewCompat.setOnApplyWindowInsetsListener(this, newandroid.support.v4.view.OnApplyWindowInsetsListener(){ @Override publicWindowInsetsCompatonApplyWindowInsets(Viewv, WindowInsetsCompatinsets){ if(null==mInsets){ mInsets=newRect(); } mInsets.set(insets.getSystemWindowInsetLeft(), insets.getSystemWindowInsetTop(), insets.getSystemWindowInsetRight(), insets.getSystemWindowInsetBottom()); setWillNotDraw(mInsets.isEmpty()||mInsetForeground==null); ViewCompat.postInvalidateOnAnimation(ScrimInsetsFrameLayout.this); returninsets.consumeSystemWindowInsets(); } }); } @Override publicvoiddraw(@NonNullCanvascanvas){ super.draw(canvas); intwidth=getWidth(); intheight=getHeight(); if(mInsets!=null&&mInsetForeground!=null){ intsc=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 protectedvoidonAttachedToWindow(){ super.onAttachedToWindow(); if(mInsetForeground!=null){ mInsetForeground.setCallback(this); } } @Override protectedvoidonDetachedFromWindow(){ super.onDetachedFromWindow(); if(mInsetForeground!=null){ mInsetForeground.setCallback(null); } } }
采用如上其中的任何一种方法就可以解决输入法弹出后覆盖输入框问题。
其他问题?
在我们使用的过程中发现有用户反馈,说只要进入我们采用该布局的页面就会崩溃,我们查看了崩溃日志,发现有部分手机都使用了相同的一个安卓系统,并且版本都是19,android4.4.x,一个被重写过的系统,该系统的代码加载方式被重写了。
为什么会崩溃?
我们代码使用到了WindowInsets,该类是api20才提供的,因此19的系统中其实是没有该代码的,但是该系统在xml的inflate的时候就解析了该类,导致classNotFound。
新的解决方案!
新的解决方案还是采用了上述的方式,不过会针对不同的版本写不一样的布局,分别为api20以上与20以下提供不同的布局,这是采用系统的限定符实现的,之后20以上的原样采用上述的方式,20以下去掉onApplyWindowInsets复写,这样不同的版本加载不同的代码就OK了。
@Override publicfinalWindowInsetsonApplyWindowInsets(WindowInsetsinsets){ if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.KITKAT_WATCH){ mInsets[0]=insets.getSystemWindowInsetLeft(); mInsets[1]=insets.getSystemWindowInsetTop(); mInsets[2]=insets.getSystemWindowInsetRight(); returnsuper.onApplyWindowInsets(insets.replaceSystemWindowInsets(0,0,0, insets.getSystemWindowInsetBottom())); }else{ returninsets; } }
总结到此整个解决方案已经完成了,如过有更新的解决方案望大家分享。