本文來自網易雲社區android
做者:孫有軍web
當一個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+款雲產品!express
更多網易研發、產品、運營經驗分享請訪問網易雲社區。apache
相關文章:
【推薦】 Kafka實踐、升級和新版本(0.10)特性預研
【推薦】 分佈式存儲系統可靠性系列五:副本放置算法&CopySetReplication
canvas