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,参考链接。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)

相关推荐

  • Android 仿支付宝密码输入框效果

    模仿支付宝输入效果,实现很简单,就是画个矩形框和圆形,其他的通过组合view来实现所有功能,虽然简单但是封装起来,方便以后使用,也分享一下,希望对别人也有点帮助. 1.如何使用,可以设置自己的进入退出动画,不设置则没有动画效果,自己觉得封装之后还是非常用好的. private MyInputPwdUtil myInputPwdUtil; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(sa

  • Android UI设计系列之自定义EditText实现带清除功能的输入框(3)

    最近公司的产品在陆续做升级,上级领导给的任务是优化代码结构以及项目架构,力争把项目写的精巧简练,于是我们满工程找冗余... 我们都知道每一个项目基本上都是有登陆页的,在登陆页中肯定是少不了输入框了,当我们在输入框中输入数据后如果输入的内容不正确或者是错误的或者是想重新输入,如果嗯键盘上的删除键就得一个一个的去删除,这时候我们或许就想要是能有一个标记当点击了这个标记能把我们刚刚输入的内容清空就好了.这样可以极大的提升用户体验,就拿QQ的登陆来说吧,效果如下: 当点击密码框右侧的小×图标时输入的内容

  • Android实现动态显示或隐藏密码输入框的内容

    本文实例展示了Android实现动态显示或隐藏密码输入框内容的方法,分享给大家供大家参考之用.具体方法如下: 该功能可通过设置EditText的setTransformationMethod()方法来实现隐藏密码或者显示密码. 示例代码如下: private Button mBtnPassword; private EditText mEtPassword; private boolean mbDisplayFlg = false; /** Called when the activity is

  • android仿微信支付宝的支付密码输入框示例

    大家好,我是狸小华,萌汉子一枚.今天给大家带来的是仿微信/支付宝的密码输入框.这个效果也出来有一段时间了,所以搜索一下还是有不少的网友实现,但是,但是!经过一番查看后,我发现他们的实现分为两大类. 一,直接继承EditText,然后在ondraw里面做文章:二,EditText外面包一个viewGroup.我不喜欢这两种实现方式,觉着有些臃肿了,所以我详细介绍下我的实现方式:直接继承View,获取用户的输入,然后draw出来. 我们实现的是上面的密码输入框,这个键盘...系统自带的哦,调用用户输

  • Android文本输入框(EditText)输入密码时显示与隐藏

    代码很简单,这里就不多废话了. 复制代码 代码如下: package cc.c; import android.app.Activity; import android.os.Bundle; import android.text.Selection; import android.text.Spannable; import android.text.method.HideReturnsTransformationMethod; import android.text.method.Passw

  • Android软键盘挡住输入框的终极解决方案

    前言 开发做得久了,总免不了会遇到各种坑. 而在Android开发的路上,『软键盘挡住了输入框』这个坑,可谓是一个旷日持久的巨坑--来来来,我们慢慢看. 入门篇 最基本的情况,如图所示:在页面底部有一个EditText,如果不做任何处理,那么在软键盘弹出的时候,就有可能会挡住EditText. 对于这种情况的处理其实很简单,只需要在AndroidManifest文件中对activity设置:android:windowSoftInputMode的值adjustPan或者adjustResize即

  • Android输入框控件ClearEditText实现清除功能

    本文给大家带来一个很实用的小控件ClearEditText,就是在Android系统的输入框右边加入一个小图标,点击小图标可以清除输入框里面的内容,IOS上面直接设置某个属性就可以实现这一功能,但是Android原生EditText不具备此功能,所以要想实现这一功能我们需要重写EditText,接下来就带大家来实现这一小小的功能 我们知道,我们可以为我们的输入框在上下左右设置图片,所以我们可以利用属性android:drawableRight设置我们的删除小图标,如图 我这里设置了左边和右边的图

  • Android实现IP地址输入框的方法示例代码

    前言 本文主要给大家介绍了关于Android实现IP地址格式输入框的相关内容,分享出来供大家参考学习,下面话不多说了,来一起看看详细的介绍吧. 实现效果图: 解决方案: 1.添加4个EditText和三个TextView 2.设置TextView内容为点,且靠下方.设置EditText背景和边框为透明 3.为每个EditText添加监听事件 示例代码 Layout: <LinearLayout android:layout_width="match_parent" android

  • Android仿支付宝、京东的密码键盘和输入框

    首先看下效果图 一:布局代码 键盘由0~9的数字,删除键和完成键组成,也可以根据需求通过GridView适配器的getItemViewType方法来定义.点击键的时候背景有变色的效果. 密码输入框由六个EditText组成,每个输入框最对能输入一个数字,监听最后一个输入框来完成密码输入结束的监听. 二:键盘 键盘中的主要逻辑处理,键盘样式,item的点击事件 @Override public int getViewTypeCount() { return 2; } @Override publi

  • android输入框与文本框加滚动条scrollview示例

    我们都知道EditText与TextView是Android的文本输入框和文本显示框,但是基于手机屏幕的大小因素,如果在需要输入较多文字或者显示较多内容的时候,手机屏幕是远远不够的,因此让文本框具有滚动条的功能是手机上必备的,要加上滚动条,其实很简单,只需要在文本输入框或者文本显示框上面加上滚动条控件即可,该控件名字为ScrollView,以下我们对比下(以TextView举例). 复制代码 代码如下: //A.未加滚动效果 <TextView android:layout_width="

随机推荐