Android 软键盘适配:adjustPan vs adjustResize 到底差在哪

Android 软键盘适配:adjustPan vs adjustResize 到底差在哪

写 Android 界面的人几乎都踩过这个坑:输入框在页面底部,一点击,软键盘弹出来,要么把输入框挡住了,要么把整个布局挤得变形。解决这两个问题的核心,就是 android:windowSoftInputMode

但它有好几个值,adjustPanadjustResizeadjustNothingstateVisiblestateAlwaysHidden……名字相近,效果却天差地别。这篇文章用图示帮你一次搞懂。

一、windowSoftInputMode 是什么

它是 Activity(或 <application>)级别的一个属性,告诉系统当软键盘弹出/收起时,窗口该如何处理。它有两组、可以组合使用的值。

第一组:键盘是否显示(state 系列) —— 控制键盘”什么时候出现”

含义
stateUnspecified 默认,由系统决定
stateUnchanged 保持上一个界面的键盘状态
stateHidden 键盘默认隐藏
stateAlwaysHidden 键盘始终隐藏,除非用户手动拉出
stateVisible 键盘默认显示
stateAlwaysVisible 键盘始终显示

第二组:布局如何调整(adjust 系列) —— 控制键盘”出现后,界面怎么变”。这才是本篇文章的主角。

含义
adjustNothing 什么都不做
adjustPan 界面整体上移,让焦点输入框露出来
adjustResize 界面重新调整大小(压缩),给键盘腾出空间

在 AndroidManifest 里这样写(两组值用 | 拼接):

<activity
    android:name=".MainActivity"
    android:windowSoftInputMode="adjustResize|stateHidden"
    ... />

下面重点讲 adjustPanadjustResize

二、初始状态

先看没有键盘弹出时,一个典型页面的样子:顶部标题栏,中间是可滚动内容区,底部是输入框。

Android 软键盘未弹出时的初始状态

窗口高度等于完整屏幕高度,一切正常。

三、adjustResize:压缩布局,给键盘让路

核心思路:窗口的高度直接缩小,缩掉的部分就是键盘占据的高度。

整个 Activity 的可用区域变矮,布局系统重新测量,于是:

  • match_parent 的内容会被压缩
  • 底部的内容(比如输入框)会被”顶”到键盘上方
  • 如果你的布局是 flex 式、可滚动的(ScrollViewRecyclerView),会自动重排

adjustResize 模式:窗口高度被压缩

关键点adjustResize 会把窗口高度真的缩小,相当于你屏幕变矮了。

适用场景
– 底部有输入框,且希望输入框始终紧贴键盘上方
– 布局是可滚动、可伸缩的(聊天界面的典型做法)

代价/坑
– 如果布局里有固定高度、不可伸缩的元素,可能被挤压错位
– 图片、背景会被拉伸或裁剪
– 需要 android:fitsSystemWindows 配合,处理沉浸式/状态栏的场景

四、adjustPan:整体平移,把焦点框”抬”上来

核心思路:窗口大小不变,整个界面向上平移一段距离。

系统计算出当前聚焦的输入框距离键盘顶部有多远,然后把整个界面整体上移,刚好让输入框出现在键盘上方可见的位置。

adjustPan 模式:整个窗口上移

关键点:窗口高度不变,只是”画面”往上顶,顶部的内容会被顶出屏幕看不见。

适用场景
– 界面顶部内容不重要、被顶掉没关系的场景
– 布局不允许压缩(比如固定尺寸的全屏游戏、全屏视频)
– 某些把内容写死、不希望触发重新测量的复杂布局

代价/坑
– 顶部内容(标题栏、工具栏)可能被顶出屏幕
– 无法做到”输入框贴键盘”这种精细效果
– 滚动位置可能受影响

五、一张图看懂区别

adjustResize vs adjustPan 对比图

维度 adjustResize adjustPan
窗口高度 改变(缩小) 不变
界面怎么动 布局重新测量、压缩 整个窗口平移
顶部内容 保留(被压缩) 可能被顶出屏幕
底部输入框 贴住键盘上方 被”抬”到可见位置
是否触发重排
典型用途 聊天/表单 全屏/固定布局

六、最常见的坑(很多人栽在这里)

1. 设置了 adjustResize,但界面还是没反应?

八成是你的 Activity 用了全屏/沉浸式模式,或者 ThemewindowFullscreen 盖过了 windowSoftInputMode

2. 用了 adjustResize,背景图被压扁了

因为窗口真的变小了。解决:把背景放在不会因 resize 扭曲的层级,或用 .9.png / 独立的 Background view。

3. 想”输入框贴键盘”,两个都能实现吗?

只有 adjustResize 能保证”贴住”。adjustPan 只是”露出”输入框,间距不可控。

4. 组合使用

android:windowSoftInputMode="adjustResize|stateAlwaysHidden"

stateAlwaysHidden 表示:刚进页面键盘不弹,等用户点输入框才弹。这是表单页很常用的组合。

七、代码示例(一个可跑的最小 Demo)

一个底部输入框 + 顶部标题的页面,切换两种模式对比。

AndroidManifest.xml

<application ...>
    <activity
        android:name=".ResizeActivity"
        android:windowSoftInputMode="adjustResize" />
    <activity
        android:name=".PanActivity"
        android:windowSoftInputMode="adjustPan" />
</application>

activity_resize.xml(底部输入框,中间可滚动)

<LinearLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="标题栏(adjustResize 下会被压缩但不消失)" />

    <ScrollView
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1">
        <!-- 中间内容 -->
    </ScrollView>

    <EditText
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="输入框" />
</LinearLayout>

把两个 Activity 都跑一遍,你就能直观看到:ResizeActivity 里标题栏还在、输入框贴键盘;PanActivity 里标题栏被顶没了。

八、总结

  • 要输入框始终贴键盘、要界面优雅收缩 → 用 adjustResize
  • 要简单粗暴、不管顶部死活、布局不能压缩 → 用 adjustPan
  • 两者可以搭配 state... 系列一起用
  • 实际开发中,聊天/表单页面绝大多数用 adjustResize