WindowInsets - 获取导航栏,状态栏,键盘的高度和状态

WindowInsets - 获取导航栏,状态栏,键盘的高度和状态

背景

最新的 Android R(11) 推出了许多功能,有一个比较重要的功能(需梯子):
Synchronized IME transitions

A new set of APIs let you synchronize your app’s content with the IME (input method editor, aka soft keyboard) and system bars as they animate on and offscreen, making it much easier to create natural, intuitive and jank-free IME transitions. For frame-perfect transitions, a new insets animation listener notifies apps of per-frame changes to insets while the system bars or the IME animate. Additionally, apps can take control of the IME and system bar transitions through the WindowInsetsAnimationController API. For example, app-driven IME experiences let apps control the IME in response to overscrolling the app UI. Give these new IME transitions a try and let us know what other transitions are important to you.

按照文章的意思,可以监听键盘的高度变化,光介绍就非常让人激动人心.

凡是搞过键盘的同学都知道,监听 Android 键盘的高度非常复杂,网上的一些黑科技也只对某些场景,有些场景就是无法处理。

而且有一个非常关键的点:键盘只有完全弹出来了才知道高度,当我们想根据键盘的上升做一个动画时,就很难做到-<b>无法知道键盘动画的时间和最终高度</b>

我们来看看官方给的效果图:

image2.gif

话不多说,我们来测试一下

测试

引入

  1. 前期准备

    先将 Android Studio 和 Gradle, Android SDK 更新到最新, 我的版本分别是:
    Android Studio 3.6.1, Gradle 5.6.4, Android SDK R


有 Pixel 手机的直接更新到最新版本 R ,没有的可以下载最新的 R 镜像

  1. 更新 gradle 配置
android {
    compileSdkVersion 'android-R'
    buildToolsVersion "29.0.2"

    defaultConfig {
        applicationId "com.example.myapplication"
        minSdkVersion 'R'
        targetSdkVersion 'R'
        versionCode 1
        versionName "1.0"

        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
    }

    //....

}

使用

  1. 先设置 FitSystemWindows 为 false:
 //非常重要,没有这句话监听无法生效
window.setDecorFitsSystemWindows(false)
  1. 再对 view 设置监听:
class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        //非常重要,没有这句话监听无法生效
        window.setDecorFitsSystemWindows(false)
        setContentView(R.layout.activity_main)
        val callback = object : WindowInsetsAnimation.Callback(DISPATCH_MODE_CONTINUE_ON_SUBTREE) {
            override fun onProgress(
                insets: WindowInsets,
                animations: MutableList<WindowInsetsAnimation>
            ): WindowInsets {
                Log.e("MainActivity", "ime:" + insets.getInsets(WindowInsets.Type.ime()).top +
                            " " + insets.getInsets(WindowInsets.Type.ime()).bottom)
                return insets
            }
        }
        content.setWindowInsetsAnimationCallback(callback)
    }
}

运行结果:

MainActivity: ime:0 0
MainActivity: ime:0 0
MainActivity: ime:0 9
MainActivity: ime:0 37
MainActivity: ime:0 98
MainActivity: ime:0 207
MainActivity: ime:0 351
MainActivity: ime:0 526
MainActivity: ime:0 684
MainActivity: ime:0 799
MainActivity: ime:0 895
MainActivity: ime:0 1020
MainActivity: ime:0 1062
MainActivity: ime:0 1095
MainActivity: ime:0 1117
MainActivity: ime:0 1134
MainActivity: ime:0 1146
MainActivity: ime:0 1152
MainActivity: ime:0 1155

可以看到很清晰的打印出了键盘的每一帧高度,这样我们就可以根据高度回调,实现文章开头的效果

对比微信键盘弹出和 Android R 的键盘弹出,可以看到微信的上升和下降,都不是完全吻合,但是 Android R 一直是稳稳的贴着:

WechatIMG9.jpeg

未命名.gif

详细代码

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        //非常重要,没有这句话监听无法生效
        window.setDecorFitsSystemWindows(false)
        setContentView(R.layout.activity_main)
        val callback = object : WindowInsetsAnimation.Callback(DISPATCH_MODE_CONTINUE_ON_SUBTREE) {
            override fun onProgress(
                insets: WindowInsets,
                animations: MutableList<WindowInsetsAnimation>
            ): WindowInsets {
             val navigationBars = insets.getInsets(WindowInsets.Type.navigationBars())
                             val ime = insets.getInsets(WindowInsets.Type.ime())
                             Log.e(
                                 TAG, "ime:" + ime.top +
                                         " " + ime.bottom
                             )
                             val parmas = (content.layoutParams as ViewGroup.MarginLayoutParams)
                             parmas.bottomMargin = ime.bottom - navigationBars.bottom
                             content.layoutParams = parmas
                             return insets
                return insets
            }
        }
        content.setWindowInsetsAnimationCallback(callback)
    }
}

分析

除了上面例子用到的 onProgress() 方法,WindowInsetsAnimation.Callback 还有其他的属性和方法值得关注:

  1. 分发方式

构造 WindowInsetsAnimation.Callback(int) 传入一个int 值表示分发方式,目前有两个值:

  • DISPATCH_MODE_CONTINUE_ON_SUBTREE :继续分发动画事件
  • DISPATCH_MODE_STOP :不再分发

这两个值和 view 的事件分发很类似,这里就不多解释了。

  1. 键盘弹出的开始和结束

假设需求仅仅是想获取键盘的高度,不需要实时获取高度变化,可以重写 start() 方法

val callback = object : WindowInsetsAnimation.Callback(DISPATCH_MODE_CONTINUE_ON_SUBTREE) {
            override fun onStart(
                animation: WindowInsetsAnimation,
                bounds: WindowInsetsAnimation.Bounds
            ): WindowInsetsAnimation.Bounds {
                Log.e(TAG,"start lowerBound:" + bounds.lowerBound.top + " " + bounds.lowerBound.bottom)
                Log.e( TAG,"start upperBound:" + bounds.upperBound.top + " " + bounds.upperBound.bottom)
                Log.e(TAG, "start time:" + animation.durationMillis)
                return super.onStart(animation, bounds)
            }
        }

其中 bounds 表示目标对象,从里面可以拿到动画结束后键盘有多高。

animation 中可以获取动画的执行时间,透明度等等。

导航栏,状态栏高度和状态

获取高度之前先来了解一下 WindowInsets.Type 有什么类型,上面我们用到了 ime() 是键盘,除了键盘,还有其他的类型,包括:

android.view.WindowInsets.Type.STATUS_BARS, //状态栏
android.view.WindowInsets.Type.NAVIGATION_BARS, //导航栏
android.view.WindowInsets.Type.CAPTION_BAR,
android.view.WindowInsets.Type.IME, //键盘
android.view.WindowInsets.Type.WINDOW_DECOR,
android.view.WindowInsets.Type.SYSTEM_GESTURES,
android.view.WindowInsets.Type.MANDATORY_SYSTEM_GESTURES,
and android.view.WindowInsets.Type.TAPPABLE_ELEMENT

类型很多,我们通常关心键盘,状态栏和导航栏

获取高度和状态

在 Android R 之前,获取状态栏高度通常是通过反射获取。但是有了 WindowInsets 就不用这么麻烦了:

content.setOnApplyWindowInsetsListener { view, windowInsets ->
    //状态栏
    val statusBars = windowInsets.getInsets(WindowInsets.Type.statusBars())
    //导航栏
    val navigationBars = windowInsets.getInsets(WindowInsets.Type.navigationBars())
    //键盘
    val ime = windowInsets.getInsets(WindowInsets.Type.ime())
    windowInsets
}

上面代码可以获取导航栏和状态栏的高度,假设要获取隐藏和显示,可以通过:

//注意:setOnApplyWindowInsetsListener 一设置监听就会回调,此时获取的 navigationBars 是否可见是 false
//等绘制完成再去获取就是 true,这个稍微比较坑一点
windowInsets.isVisible(WindowInsets.Type.navigationBars())

控制各种状态栏的显示和隐藏

在 R 之前,控制导航栏和状态,需要用上各种谜之属性:

view.systemUiVisibility = View.SYSTEM_UI_FLAG_LAYOUT_STABLE or
                          View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or
                          View.SYSTEM_UI_FLAG_HIDE_NAVIGATION

新版是这样的:

//对状态栏和键盘也可以同样控制
content.windowInsetsController?.show(WindowInsets.Type.navigationBars())
content.windowInsetsController?.hide(WindowInsets.Type.navigationBars())

其他

一番测试下来,新版的 API 对于之前来说,可以说是非常好用了。

目前存在以下几个问题:

  1. 不支持旧版

    如果仅是在 Android R 上使用,那这个工具就没这么香了,希望能通过 androidx 或 support 方式支持

  2. FitSystemWindows

    在有虚拟导航栏的手机上,FitSystemWindows 设置为 false,会强制改变 Activity 与导航栏的关系。

    默认情况下, Activity 在导航栏的上面,它们处于同一层,但是设置为 false 之后,导航栏会直接覆盖在 Activity 的上面。
    不过这可以通过给 Activity 的 parent 设置一个 padding 来解决。

  3. 等 R 发布后,还需要测试国产 ROM,以及第三方键盘的兼容性

总结

总的来说这个工具的出现,使获取,管理键盘等 APP 以外的装饰都变得非常友好。

由于 Android R 现在还是预览版,还没有公布源码,暂时不知道其内部实现原理,等后期公布了源码再分析一下原理

Demo

https://github.com/siyehua/WindowInsetsAnimation

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 160,108评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,699评论 1 296
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,812评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,236评论 0 213
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,583评论 3 288
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,739评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,957评论 2 315
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,704评论 0 204
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,447评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,643评论 2 249
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,133评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,486评论 3 256
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,151评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,108评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,889评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,782评论 2 277
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,681评论 2 272

推荐阅读更多精彩内容