Android輸入法彈出時覆蓋輸入框問題
阿新 • • 發佈:2018-09-25
express anti 參考 inpu contex 針對 screen .org apply
本文來自網易雲社區
作者:孫有軍
當一個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+款雲產品!
更多網易研發、產品、運營經驗分享請訪問網易雲社區。
相關文章:
【推薦】 SpringBoot入門(四)——自動配置
【推薦】 網易雲易盾朱星星:最容易被駁回的10大APP過檢項
Android輸入法彈出時覆蓋輸入框問題