攻克Android软键盘的疑难杂症

vdog5u0y 9年前
   <p>在Activity中含有EditText时,我们常常在AndroidManifest.xml中为该Activity设置 <a href="/misc/goto?guid=4959671792203787710" rel="nofollow,noindex">Android</a> :windowSoftInputMode属性,其中最常用的值就是adjustResize和adjustPan。在此请思考几个问题:</p>    <ol>     <li>adjustResize和adjustPan有什么区别?</li>     <li>adjustResize和adjustPan的应用场景有何差异?</li>     <li>当设置android:windowSoftInputMode后如何监听软键盘的弹起与隐藏?</li>    </ol>    <p>在看到第三个问题的时候,有人会想:</p>    <p>干嘛要去监听软键盘的弹起呢?有什么用呢?</p>    <p style="text-align: center;"><img src="https://simg.open-open.com/show/50da5b33aef3691aded5ab24b37e51fc.jpg"></p>    <p>嗯哼,看到了吧:当键盘弹起来的时候在紧靠键盘上方的地方出现了一个自定义布局,点击笑脸就可以发送专属emoji表情,点击礼盒就可以发送福利。</p>    <p>当然,在键盘收起的时候这个布局也就不可见了。</p>    <p>除此以外,在其他不少场景也会有类似的UI设计。在这些情况下,我们都要监听键盘软键盘的弹起与隐藏。善良的童鞋会想:这个没难度呀,调用一下官方的API就行。很久以前,我也是这么想的。可是,事与愿违,官方文档中根本就没有提供检测软键盘状态的接口。</p>    <p>既然官方没有把这个API洗干净整整齐齐的摆在眼前,那我们就自己实现它。</p>    <p>adjustResize</p>    <p>在AndroidManifest.xml中为该Activity设置</p>    <p>android:windowSoftInputMode=”adjustResize”</p>    <p>该模式下系统会调整屏幕的大小以保证软键盘的显示空间。</p>    <p>举个例子:</p>    <p>屏幕的高为1920px,那么整个Activity的布局高度也为1920px。当设置该属性后点击界面中的EditText,此时弹出软键盘其高度为800px。为了完整地显示此软键盘,系统会调整Activity布局的高度为1920px-800px=1120px。</p>    <p>所以,此时的布局与原本的布局就发生了一些变化,比如:整体布局显示不完整,控件外观的变形,控件显示位置的错乱等等。这些现象都是因为原布局的高度变小所导致。</p>    <p>以下,再结合代码详细分析该情况。</p>    <pre>  <code class="language-java">import android.content.Context;  import android.util.AttributeSet;  import android.widget.RelativeLayout;  /**   * 原创作者:   * 谷哥的小弟   *   * 博客地址:   * http://blog.csdn.net/lfdfhl   *   */  public class RelativeLayoutSubClass extends RelativeLayout{      private OnSoftKeyboardListener mSoftKeyboardListener;      public RelativeLayoutSubClass(Context context, AttributeSet attrs) {          super(context, attrs);      }        @Override      protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {          super.onMeasure(widthMeasureSpec, heightMeasureSpec);          System.out.println("----> onMeasure");      }        @Override      protected void onLayout(boolean changed, int l, int t, int r, int b) {          super.onLayout(changed, l, t, r, b);          mSoftKeyboardListener.onSoftKeyboardChange();          System.out.println("----> onLayout");      }        @Override      protected void onSizeChanged(int w, int h, int oldw, int oldh) {          super.onSizeChanged(w, h, oldw, oldh);          System.out.println("----> onSizeChanged");      }        public void setSoftKeyboardListener(OnSoftKeyboardListener listener){          mSoftKeyboardListener=listener;      }        public interface OnSoftKeyboardListener{          public void onSoftKeyboardChange();      }    }</code></pre>    <p>我们将该自定义RelativeLayout作为Activity布局的根</p>    <pre>  <code class="language-java"><?xml version="1.0" encoding="utf-8"?>  <cc.testsoftinputmode.RelativeLayoutSubClass      xmlns:android="http://schemas.android.com/apk/res/android"      xmlns:tools="http://schemas.android.com/tools"      android:id="@+id/rootLayout"      android:layout_width="match_parent"      android:layout_height="match_parent"      android:orientation="vertical"      tools:context="cc.testsoftinputmode.MainActivity">        <RelativeLayout          android:layout_width="match_parent"          android:layout_height="200dp"          android:layout_alignParentTop="true"          android:background="#7fb80e">          <TextView              android:layout_width="wrap_content"              android:layout_height="wrap_content"              android:text="浅绿色部分在屏幕顶部"              android:textSize="25sp"              android:layout_centerInParent="true"              android:textColor="#843900"/>      </RelativeLayout>        <EditText          android:layout_width="wrap_content"          android:layout_height="wrap_content"          android:hint="这里是一个输入框"          android:textSize="25sp"          android:layout_centerInParent="true"          android:textColor="#843900"/>        <RelativeLayout          android:layout_width="match_parent"          android:layout_height="200dp"          android:layout_alignParentBottom="true"          android:background="#ffc20e">          <TextView              android:layout_width="wrap_content"              android:layout_height="wrap_content"              android:text="浅黄色部分在屏幕底部"              android:textSize="25sp"              android:layout_centerInParent="true"              android:textColor="#f05b72"/>      </RelativeLayout>    </cc.testsoftinputmode.RelativeLayoutSubClass></code></pre>    <p>效果如下:</p>    <p style="text-align: center;"><img src="https://simg.open-open.com/show/dc7323586c964ea432b5abfb291e80ba.jpg"></p>    <p>点击EditText,弹出软键盘:</p>    <p style="text-align: center;"><img src="https://simg.open-open.com/show/a3cb7b55871714f63322351b3f6236d7.jpg"></p>    <p>现象呢,我们已经看到了。我们再来瞅瞅当软键盘状态变化的时候RelativeLayoutSubClass中有哪些行为发生:</p>    <ol>     <li>软键盘状态变化时会调用其onMeasure(),onLayout(),onSizeChanged()</li>     <li>在onSizeChanged()中可以确知软键盘状态变化前后布局宽高的数值</li>    </ol>    <p>至此,发现一个关键点:onSizeChanged()</p>    <p>我们可以以此为切入点检测软键盘的状态变化,所以定义一个接口OnSoftKeyboardListener提供给Activity使用,具体代码如下:</p>    <pre>  <code class="language-java">import android.os.Bundle;  import android.support.v7.app.AppCompatActivity;    /**   * 原创作者:   * 谷哥的小弟   *   * 博客地址:   * http://blog.csdn.net/lfdfhl   *   */  public class MainActivity extends AppCompatActivity {      private RelativeLayoutSubClass mRootLayout;      private int screenHeight;      private int screenWidth;      private int threshold;      @Override      protected void onCreate(Bundle savedInstanceState) {          super.onCreate(savedInstanceState);          setContentView(R.layout.activity_main);          init();      }      private void init(){          screenHeight=getResources().getDisplayMetrics().heightPixels;          screenWidth=getResources().getDisplayMetrics().widthPixels;          threshold=screenHeight/3;          mRootLayout= (RelativeLayoutSubClass) findViewById(R.id.rootLayout);          mRootLayout.setSoftKeyboardListener(new RelativeLayoutSubClass.OnSoftKeyboardListener() {              @Override              public void onSoftKeyboardChange(int w, int h, int oldw, int oldh) {                  if (oldh-h>threshold){                      System.out.println("----> 软键盘弹起");                  }else if(h-oldh>threshold){                      System.out.println("----> 软键盘收起");                  }              }          });      }  }</code></pre>    <p>请注意onSoftKeyboardChange()的回调:</p>    <ul>     <li>假若oldh-h大于屏幕高度的三分之一,则软键盘弹起</li>     <li>假若h-oldh大于屏幕高度的三分之一,则软键盘收起</li>    </ul>    <p>小结:</p>    <p>当软键盘状态发生改变时,通过对Activity布局文件根Layout的onSizeChanged()判断软键盘的弹起或隐藏</p>    <p>adjustPan</p>    <p>在AndroidManifest.xml中为该Activity设置</p>    <pre>  <code class="language-java">android:windowSoftInputMode=”adjustPan”</code></pre>    <p>该模式下系统会将界面中的内容自动移动从而使得焦点不被键盘覆盖,即用户能总是看到输入内容的部分</p>    <p>比如,还是刚才的那个布局,现在将其windowSoftInputMode设置为adjustPan再点击EditText,效果如下:</p>    <p style="text-align: center;"><img src="https://simg.open-open.com/show/ef33e216f1a2a92d50e72a116a8e9590.jpg"></p>    <p>嗯哼,看到了吧:</p>    <p>为了避免软键盘弹起后遮挡EditText,系统将整个布局上移了,也就是我们常说的将布局顶上去了。</p>    <p>此时再来看看当软键盘状态变化的时候RelativeLayoutSubClass中有哪些行为发生:</p>    <ol>     <li>软键盘状态变化时会调用其onMeasure(),onLayout()</li>     <li>onSizeChanged()并没有被调用</li>     <li>整个布局的高度也没有变化</li>    </ol>    <p>哦噢,这时并没有执行onSizeChanged()方法,这也就说原本检测软键盘状态的方法在这就行不通了,得另辟蹊径了。</p>    <p>当软键盘弹起时,原布局中是不是有一部分被键盘完全遮挡了呢?</p>    <p>对吧,也就是说原布局的可视范围(更精确地说是可视高度)发生了变化:变得比以前小了。所以,我们可以以此为突破口,具体代码如下:</p>    <pre>  <code class="language-java">public boolean isSoftKeyboardShow(View rootView) {          screenHeight=getResources().getDisplayMetrics().heightPixels;          screenWidth=getResources().getDisplayMetrics().widthPixels;          threshold=screenHeight/3;          int rootViewBottom=rootView.getBottom();          Rect rect = new Rect();          rootView.getWindowVisibleDisplayFrame(rect);          int visibleBottom=rect.bottom;          int heightDiff = rootViewBottom - visibleBottom;          System.out.println("----> rootViewBottom="+rootViewBottom+",visibleBottom="+visibleBottom);          System.out.println("----> heightDiff="+heightDiff+",threshold="+threshold);          return heightDiff > threshold;      }</code></pre>    <ol>     <li>获取根布局(RelativeLayoutSubClass)原本的高度 <pre>  <code class="language-java">int rootViewBottom=rootView.getBottom();</code></pre> <p> </p> </li>     <li>获取当前根布局的可视高度 <pre>  <code class="language-java">Rect rect = new Rect();    rootView.getWindowVisibleDisplayFrame(rect);    int visibleBottom=rect.bottom;</code></pre> <p> </p> </li>     <li>计算两者的差值 <pre>  <code class="language-java">int heightDiff = rootViewBottom – visibleBottom;</code></pre> <p> </p> </li>     <li>判断软键盘是否弹起 <pre>  <code class="language-java">return heightDiff > threshold;</code></pre> <p> </p> </li>    </ol>    <p>具体的方法是有了,那么该在哪里调用该方法呢?</p>    <p>其实,和之前的类似,只需在RelativeLayoutSubClass的onLayout()中执行即可。具体代码如下:</p>    <pre>  <code class="language-java">import android.content.Context;  import android.util.AttributeSet;  import android.widget.RelativeLayout;  /**   * 原创作者:   * 谷哥的小弟   *   * 博客地址:   * http://blog.csdn.net/lfdfhl   *   */  public class RelativeLayoutSubClass extends RelativeLayout{      private OnSoftKeyboardListener mSoftKeyboardListener;      public RelativeLayoutSubClass(Context context, AttributeSet attrs) {          super(context, attrs);      }        @Override      protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {          super.onMeasure(widthMeasureSpec, heightMeasureSpec);          System.out.println("----> onMeasure");      }        @Override      protected void onLayout(boolean changed, int l, int t, int r, int b) {          super.onLayout(changed, l, t, r, b);          mSoftKeyboardListener.onSoftKeyboardChange();          System.out.println("----> onLayout");      }        @Override      protected void onSizeChanged(int w, int h, int oldw, int oldh) {          super.onSizeChanged(w, h, oldw, oldh);          System.out.println("----> onSizeChanged");      }        public void setSoftKeyboardListener(OnSoftKeyboardListener listener){          mSoftKeyboardListener=listener;      }        public interface OnSoftKeyboardListener{          public void onSoftKeyboardChange();      }    }</code></pre>    <p>在对应的Activity中实现该Listener,具体代码如下:</p>    <pre>  <code class="language-java">import android.graphics.Rect;  import android.os.Bundle;  import android.support.v7.app.AppCompatActivity;  import android.view.View;  /**   * 原创作者:   * 谷哥的小弟   *   * 博客地址:   * http://blog.csdn.net/lfdfhl   *   */  public class MainActivity extends AppCompatActivity{      private RelativeLayoutSubClass mRootLayout;      private int screenHeight;      private int screenWidth;      private int threshold;      @Override      protected void onCreate(Bundle savedInstanceState) {          super.onCreate(savedInstanceState);          setContentView(R.layout.activity_main);          init();      }        private void init(){          mRootLayout= (RelativeLayoutSubClass) findViewById(R.id.rootLayout);          mRootLayout.setSoftKeyboardListener(new RelativeLayoutSubClass.OnSoftKeyboardListener() {              @Override              public void onSoftKeyboardChange() {                  boolean isShow=isSoftKeyboardShow(mRootLayout);                  System.out.println("----> isShow="+isShow);              }          });      }        public boolean isSoftKeyboardShow(View rootView) {          screenHeight=getResources().getDisplayMetrics().heightPixels;          screenWidth=getResources().getDisplayMetrics().widthPixels;          threshold=screenHeight/3;          int rootViewBottom=rootView.getBottom();          Rect rect = new Rect();          rootView.getWindowVisibleDisplayFrame(rect);          int visibleBottom=rect.bottom;          int heightDiff = rootViewBottom - visibleBottom;          System.out.println("----> rootViewBottom="+rootViewBottom+",visibleBottom="+visibleBottom);          System.out.println("----> heightDiff="+heightDiff+",threshold="+threshold);          return heightDiff > threshold;      }    }</code></pre>    <p>嗯哼,至此当windowSoftInputMode设置为adjustPan时软键盘的状态监听也得到了实现</p>    <p> </p>    <p>来自:http://www.androidchina.net/5530.html</p>    <p> </p>