當(dāng)一個activity中含有輸入框時,我們點擊輸入框,會彈出輸入法界面,整個界面的變化效果與manifest中對應(yīng)設(shè)置的android:windowSoftInputMode屬性有關(guān),一般可以設(shè)置的值如下,
<activity android:windowSoftInputMode=["stateUnspecified","stateUnchanged”, "stateHidden","stateAlwaysHidden”, "stateVisible","stateAlwaysVisible”, "adjustUnspecified","adjustResize”, "adjustPan"] …… >
具體怎么設(shè)置可以查看官方文檔。今天主要解決當(dāng)輸入法彈出時會覆蓋輸入框的問題。
什么情況會覆蓋?
當(dāng)android的應(yīng)用中如果一個activity設(shè)置了全屏屬性Theme.Light.NotittleBar.Fullscreen或者設(shè)置了activity對應(yīng)的主題中android:windowTranslucentStatus屬性,設(shè)置方式為:<item name="android:windowTranslucentStatus">true</item>,這是如果對應(yīng)的頁面上含有輸入框,將會導(dǎo)致點擊輸入框時軟鍵盤彈出后鍵盤覆蓋輸入框,導(dǎo)致輸入框看不見。
為什么?
這其實是因為在全屏?xí)r,adjustResize屬性已經(jīng)失效了,該問題是系統(tǒng)的一個bug,參考鏈接。adjustResize不生效,那有沒有其他方法來解決吶? 這時我們可以設(shè)置adjust屬性為adjustPan屬性,該屬性不會失效,但是由于adjustPan會將頁面整體平移,以留出輸入法空間,會有一個抖動的效果,體驗很差,哪有沒有體驗效果更好的方法吶?
解決方案:
如果跟布局采用FrameLayout,則可以復(fù)寫一個自定義FrameLayout,同時設(shè)置FrameLayout的android:fitsSystemWindows屬性為true。xml設(shè)置如下
<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;    }  }}官方解決方案:
官方其實也發(fā)現(xiàn)了問題,因此在android.support.design.internal下也重寫了FrameLayout來解決該問題,但是該類被標(biāo)記了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);    }  }}采用如上其中的任何一種方法就可以解決輸入法彈出后覆蓋輸入框問題。
其他問題?
在我們使用的過程中發(fā)現(xiàn)有用戶反饋,說只要進入我們采用該布局的頁面就會崩潰,我們查看了崩潰日志,發(fā)現(xiàn)有部分手機都使用了相同的一個安卓系統(tǒng),并且版本都是19,android4.4.x,一個被重寫過的系統(tǒng),該系統(tǒng)的代碼加載方式被重寫了。
為什么會崩潰?
我們代碼使用到了WindowInsets,該類是api 20才提供的,因此19的系統(tǒng)中其實是沒有該代碼的,但是該系統(tǒng)在xml的inflate的時候就解析了該類,導(dǎo)致classNotFound。
新的解決方案!
新的解決方案還是采用了上述的方式,不過會針對不同的版本寫不一樣的布局,分別為api 20以上與20以下提供不同的布局,這是采用系統(tǒng)的限定符實現(xiàn)的,之后20以上的原樣采用上述的方式,20以下去掉onApplyWindowInsets復(fù)寫,這樣不同的版本加載不同的代碼就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;    }  }總結(jié)到此整個解決方案已經(jīng)完成了,如過有更新的解決方案望大家分享。
新聞熱點
疑難解答
圖片精選