Android键盘面板冲突 布局闪动处理方案

JanMcAllist 9年前
   <blockquote>     <p>起源,之前在微信工作的时候,为了给用户带来更好的基础体验,做了很多尝试,踩了很多输入法的坑,特别是动态调整键盘高度,二级页面是透明背景,魅族早期的Smart bar等, 后来逐一完善了,考虑到拥抱开源,看业界还是有很多应用存在类似问题。就有了这个repo</p>    </blockquote>    <blockquote>     <p>之前有写过一篇核心思想: <a href="/misc/goto?guid=4959671279781767167">Switching between the panel and the keyboard in Wechat</a>。</p>    </blockquote>    <p><a href="https://simg.open-open.com/show/a58bcab24595a06c5e00999cbf913c2b.gif"><img alt="Android键盘面板冲突 布局闪动处理方案" src="https://simg.open-open.com/show/a58bcab24595a06c5e00999cbf913c2b.gif" width="320" height="569"></a><a href="https://simg.open-open.com/show/a2ea35385c57c52afce8a9d8f1d9ad76.gif"><img alt="Android键盘面板冲突 布局闪动处理方案" src="https://simg.open-open.com/show/a2ea35385c57c52afce8a9d8f1d9ad76.gif" width="320" height="569"></a> <a href="https://simg.open-open.com/show/f0d9c6c05f4c4f117fa263a1d02f21ac.gif"><img alt="Android键盘面板冲突 布局闪动处理方案" src="https://simg.open-open.com/show/f0d9c6c05f4c4f117fa263a1d02f21ac.gif" width="320" height="569"></a><a href="https://simg.open-open.com/show/0102f48202a2bb9e4dcdcfd8790ecd57.gif"><img alt="Android键盘面板冲突 布局闪动处理方案" src="https://simg.open-open.com/show/0102f48202a2bb9e4dcdcfd8790ecd57.gif" width="320" height="569"></a></p>    <h2>欢迎提交 Pull requests</h2>    <ul>     <li>尽量多的英文注解。</li>     <li>每个提交尽量的细而精准。</li>     <li>Commit message 遵循: <a href="/misc/goto?guid=4959655417394256205">AngularJS's commit message convention</a>。</li>     <li>尽可能的遵循IDE的代码检查建议(如 Android Studio 的 'Inspect Code')。</li>    </ul>    <h2>如何使用</h2>    <p>在<code>build.gradle</code>中引入:</p>    <pre>  <code class="language-java">compile 'cn.dreamtobe.kpswitch:library:1.4.4'  </code></pre>    <h2>使用引导</h2>    <h3>非全屏主题情况下使用引导</h3>    <blockquote>     <p>所谓非全屏主题,就是 <code>(activity.getWindow().getAttributes().flags & WindowManager.LayoutParams.FLAG_FULLSCREEN) == 0</code></p>    </blockquote>    <p><a href="https://simg.open-open.com/show/a58bcab24595a06c5e00999cbf913c2b.gif"><img alt="Android键盘面板冲突 布局闪动处理方案" src="https://simg.open-open.com/show/a58bcab24595a06c5e00999cbf913c2b.gif" width="320" height="569"></a></p>    <h3>I. <code>AndroidManifest</code></h3>    <blockquote>     <p>可直接参照: <a href="/misc/goto?guid=4959671279956830019">AndroidManifest.xml</a></p>     <p>对应的Activity,在<strong><code>AndroidManifest</code>中配置</strong><code>android:windowSoftInputMode=adjustResize</code></p>    </blockquote>    <pre>  <code class="language-java"><manifest    ...>    <application      ...>        <activity        android:name=".activity.ChattingActivity"        android:windowSoftInputMode=adjustResize"/>        ...    </application>    ...  </manifest>  </code></pre>    <h3>II. 需要处理页面的layout xml</h3>    <blockquote>     <p>可直接参照: <a href="/misc/goto?guid=4959671280061830455">activity_chatting_resolved.xml</a></p>    </blockquote>    <ol>     <li>需要用到 <strong>最上层布局</strong> (<a href="/misc/goto?guid=4959671280153176690">KPSwitchRootFrameLayout</a>/<a href="/misc/goto?guid=4959671280228449004">KPSwitchRootLinearLayout</a>/<a href="/misc/goto?guid=4959671280309050782">KPSwitchRootRelativeLayout</a>)</li>     <li>需要用到 <strong>面板布局</strong>(<a href="/misc/goto?guid=4959671280397454306">KPSwitchPanelFrameLayout</a>/<a href="/misc/goto?guid=4959671280479110200">KPSwitchPanelLinearLayout</a>/<a href="/misc/goto?guid=4959671280566935113">KPSwitchPanelRelativeLayout</a>)。</li>    </ol>    <p>简单案例:</p>    <pre>  <code class="language-java"><?xml version="1.0" encoding="utf-8"?>  <!-- 可选用 KPSwitchRootLinearLayout、KPSwitchRootRelativeLayout、KPSwitchRootFrameLayout -->  <cn.dreamtobe.kpswitch.widget.KPSwitchRootLinearLayout xmlns:android="http://schemas.android.com/apk/res/android"      android:layout_width="match_parent"      android:layout_height="match_parent"      android:orientation="vertical">        <!-- 布局内容 -->      ...        <!-- 可选用 KPSwitchPanelLinearLayout、KPSwitchPanelRelativeLayout、KPSwitchPanelFrameLayout -->      <cn.dreamtobe.kpswitch.widget.KPSwitchPanelLinearLayout          android:id="@+id/panel_root"          android:layout_width="fill_parent"          android:layout_height="@dimen/panel_height"          android:visibility="gone">          <!-- 面板内容 -->          ...      </cn.dreamtobe.kpswitch.widget.KPSwitchPanelLinearLayout>    </cn.dreamtobe.kpswitch.widget.KPSwitchRootLinearLayout>  </code></pre>    <h3>III. 需要处理页面的Activity:</h3>    <blockquote>     <p>可直接参照: <a href="/misc/goto?guid=4959671280636962441">ChattingResolvedActivity.java</a></p>    </blockquote>    <ol>     <li>处理一些事件(<a href="/misc/goto?guid=4959671280719693834">KPSwitchConflictUtil</a>)</li>     <li>键盘状态(高度与显示与否)监听(<a href="/misc/goto?guid=4959671280798553311">KeyboardUtil#attach()</a>)</li>    </ol>    <p>简单案例:</p>    <pre>  <code class="language-java">...    // 面板View  private KPSwitchPanelLinearLayout mPanelLayout;  // 键盘焦点View,用于输入内容  private EditText mSendEdt;  // 用于切换键盘与面板的按钮View  private ImageView mPlusIv;    @Override  public void onCreate(Bundle saveInstanceState){      ...        mPanelLayout = (KPSwitchPanelLinearLayout)findViewById(R.id.panel_root);      mSendEdt = (EditText) findViewById(R.id.send_edt);      mPlusIv = (ImageView) findViewById(R.id.plus_iv);        /**       * 这个Util主要是监控键盘的状态: 显示与否 以及 键盘的高度       * 这里也有提供给外界监听 键盘显示/隐藏 的监听器,具体参看       * 这个接口 {@Link KeyboardUtil#attach(Activity, IPanelHeightTarget, OnKeyboardShowingListener)}       */      KeyboardUtil.attach(this, mPanelLayout);        /**       * 这个Util主要是协助处理一些面板与键盘相关的事件。       * 这个方法主要是对一些相关事件进行注册,如切换面板与键盘等,具体参看源码,比较简单。       * 里面还提供了一些已经处理了冲突的工具方法: 显示面板;显示键盘;键盘面板切换;隐藏键盘与面板;       *       * @param panelRoot 面板的布局。       * @param switchPanelKeyboardBtn 用于触发切换面板与键盘的按钮。       * @param focusView 键盘弹起时会给这个View focus,收回时这个View会失去focus,通常是发送的EditText。       */      KPSwitchConflictUtil.attach(mPanelLayout, mPlusIv, mSendEdt);    }    ...    ...    // 如果需要处理返回收起面板的话  @Override  public boolean dispatchKeyEvent(KeyEvent event){      if (event.getAction() == KeyEvent.ACTION_UP &&              event.getKeyCode() == KeyEvent.KEYCODE_BACK) {          if (mPanelLayout.getVisibility() == View.VISIBLE) {              KPSwitchConflictUtil.hidePanelAndKeyboard(mPanelLayout);              return true;          }      }      return super.dispatchKeyEvent(event);  }</code></pre>    <h3>全屏主题情况下使用引导</h3>    <blockquote>     <p>所谓全屏主题,就是 <code>(activity.getWindow().getAttributes().flags & WindowManager.LayoutParams.FLAG_FULLSCREEN) != 0</code></p>    </blockquote>    <p><a href="https://simg.open-open.com/show/a2ea35385c57c52afce8a9d8f1d9ad76.gif"><img alt="Android键盘面板冲突 布局闪动处理方案" src="https://simg.open-open.com/show/a2ea35385c57c52afce8a9d8f1d9ad76.gif" width="320" height="569"></a></p>    <h3>I. <code>AndroidManifest</code></h3>    <blockquote>     <p>可直接参照: <a href="/misc/goto?guid=4959671279956830019">AndroidManifest.xml</a></p>     <p>对应的Activity,在 <strong><code>AndroidManifest</code>中配置</strong> <code>android:windowSoftInputMode=adjustUnspecified</code>,或者不配置,默认就是这个模式。</p>    </blockquote>    <h3>II. 需要处理页面的layout xml</h3>    <blockquote>     <p>可直接参照: <a href="/misc/goto?guid=4959671280895811038">activity_chatting_fullscreen_resolved.xml</a></p>     <p>这边只需要用到一个 <strong>面板布局</strong>(<a href="/misc/goto?guid=4959671280984058325">KPSwitchFSPanelFrameLayout</a>/<a href="/misc/goto?guid=4959671281067127125">KPSwitchFSPanelLinearLayout</a>/<a href="/misc/goto?guid=4959671281149009317">KPSwitchFSPanelRelativeLayout</a>)</p>    </blockquote>    <pre>  <code class="language-java"><?xml version="1.0" encoding="utf-8"?>  ...      ...        <!-- 可选用 KPSwitchFSPanelFrameLayout、KPSwitchFSPanelLinearLayout、KPSwitchFSPanelRelativeLayout -->      <cn.dreamtobe.kpswitch.widget.KPSwitchFSPanelFrameLayout          android:id="@+id/panel_root"          style="@style/Panel"          android:visibility="gone">            ...      </cn.dreamtobe.kpswitch.widget.KPSwitchFSPanelFrameLayout>    ...  </code></pre>    <h3>III. 需要处理页面的Activity:</h3>    <blockquote>     <p>可直接参照: <a href="/misc/goto?guid=4959671281236043683">ChattingResolvedFullScreenActivity.java</a></p>    </blockquote>    <ol>     <li>主要是处理一些事件(<a href="/misc/goto?guid=4959671280719693834">KPSwitchConflictUtil</a>)</li>     <li>键盘状态(高度与显示与否)监听(<a href="/misc/goto?guid=4959671280798553311">KeyboardUtil#attach()</a>)</li>     <li>在<code>onPause</code>时,记录键盘状态用于从后台回到当前布局,恢复键盘状态不至于冲突(<a href="/misc/goto?guid=4959671281335014670">IFSPanelConflictLayout#recordKeyboardStatus()</a>)</li>    </ol>    <p>如下使用案例:</p>    <pre>  <code class="language-java">...    // 面板View  private KPSwitchFSPanelLinearLayout mPanelLayout;  // 键盘焦点View,用于输入内容  private EditText mSendEdt;  // 用于切换键盘与面板的按钮View  private ImageView mPlusIv;    @Override  public void onCreate(Bundle saveInstanceState){      ...          mPanelLayout = (KPSwitchFSPanelLinearLayout)findViewById(R.id.panel_root);      mSendEdt = (EditText) findViewById(R.id.send_edt);      mPlusIv = (ImageView) findViewById(R.id.plus_iv);        /**       * 这个Util主要是监控键盘的状态: 显示与否 以及 键盘的高度       * 这里也有提供给外界监听 键盘显示/隐藏 的监听器,具体参看       * 这个接口 {@Link KeyboardUtil#attach(Activity, IPanelHeightTarget, OnKeyboardShowingListener)}       */      KeyboardUtil.attach(this, mPanelLayout);        /**       * 这个Util主要是协助处理一些面板与键盘相关的事件。       * 这个方法主要是对一些相关事件进行注册,如切换面板与键盘等,具体参看源码,比较简单。       * 里面还提供了一些已经处理了冲突的工具方法: 显示面板;显示键盘;键盘面板切换;隐藏键盘与面板;       *       * @param panelRoot 面板的布局。       * @param switchPanelKeyboardBtn 用于触发切换面板与键盘的按钮。       * @param focusView 键盘弹起时会给这个View focus,收回时这个View会失去focus,通常是发送的EditText。       */      KPSwitchConflictUtil.attach(mPanelLayout, mPlusIv, mSendEdt);    }    @Override  protected void onPause() {    super.onPause();    // 用于记录当前的键盘状态,在从后台回到当前页面的时候,键盘状态能够正确的恢复并且不会导致布局冲突。    mPanelLayout.recordKeyboardStatus(getWindow());  }    ...    // 如果需要处理返回收起面板的话  @Override  public boolean dispatchKeyEvent(KeyEvent event){      if (event.getAction() == KeyEvent.ACTION_UP &&              event.getKeyCode() == KeyEvent.KEYCODE_BACK) {          if (mPanelLayout.getVisibility() == View.VISIBLE) {              KPSwitchConflictUtil.hidePanelAndKeyboard(mPanelLayout);              return true;          }      }      return super.dispatchKeyEvent(event);  }</code></pre>    <h2>基本原理</h2>    <ul>     <li>键盘高度计算,以及键盘是否显示的计算,参看:<a href="/misc/goto?guid=4959671281414400681">KeyboardUtil.KeyboardStatusListener#calculateKeyboardHeight</a>、<a href="/misc/goto?guid=4959671281495827888">KeyboardUtil.KeyboardStatusListener#calculateKeyboardShowing</a>。</li>     <li>处理闪动问题,参看: <a href="/misc/goto?guid=4959671281577979210">KPSwitchRootLayoutHandler</a>,以及如果是非全屏主题用到的面板布局:<a href="/misc/goto?guid=4959671281661839497">KPSwitchPanelLayoutHandler</a>;如果是全屏主题用到的面板布局: <a href="/misc/goto?guid=4959671281743351486">KPSwitchFSPanelLayoutHandler</a>。</li>    </ul>