QMUI实战(一)—为何我们要使用 LauncherActivity?

简介: QMUI 2 发布了,但是里面换肤等相关的很多东西,如果不讲,那么很多人估计就只能复制粘贴下 QMUIDemo 的代码,而并不能用好 QMUI, 或者是通过 QMUI 来提升自己的 UI 开发能力,毕竟现在很多 Android 开发都是轻 UI 而重数据流,遇到需要复杂 UI 的地方,在 Github 上随便找个组件套上去就行了,如果找得到刚好符合需求的还好,如果找到的不是那么切合需求,那一天的状态很可能就是“一壶茶、一包烟、几个间距颜色调一天”了。

QMUI 2 发布了,但是里面换肤等相关的很多东西,如果不讲,那么很多人估计就只能复制粘贴下 QMUIDemo 的代码,而并不能用好 QMUI, 或者是通过 QMUI 来提升自己的 UI 开发能力,毕竟现在很多 Android 开发都是轻 UI 而重数据流,遇到需要复杂 UI 的地方,在 Github 上随便找个组件套上去就行了,如果找得到刚好符合需求的还好,如果找到的不是那么切合需求,那一天的状态很可能就是“一壶茶、一包烟、几个间距颜色调一天”了。


我开这个系列(挖这个坑),不仅仅是写怎么用 QMUI 了,而是想通过 QMUI 来讲一些我个人在 QMUI 和项目开发过程中收获和心得。我开了一个新的 Github 仓库,名叫 GankWithQmui,打算从零开始用 kotlin 去写一个 Demo 型 App ,以此来指出 Android UI 开发可能涉及到各种知识。当然更新时间或者更新主题都是不定的,个人比较随意,有了状态,才能操作拉满;没有状态,可能很快就被终结了。


每篇博文会以一个关键问题来作为标题,问题的背后可能就是我们进行最佳实践的原因了。也是希望大家看完博文能彻底掌握的知识点。不断累积,才能在今后的任何场景下得心应手。好了,我们正式开始今天的征途。

项目初始化


我们直接用 Android Studio(AS)生成无 Activity 的项目,语言选择 kotlin。我使用的 package 为 org.cgsdream.gankwithqmui,的环境是:


  • Android Studio 4
  • kotlin 1.3.60-eap-25
  • gradle 4.0.0-alpha04

如果你 clone 代码,在你的 AS 上打不开,修改这些版本号应该就可以了。

引入QMUI依赖


我们引入 qmui 库,以及 arch 库, arch 库因为有一些使用注解进行代码生成的功能,因此还需要加入 arch-compliler 库,后面的博文会慢慢来解释它们的用途。


我们使用 kapt 来代替 annotationProcessor, 因此需要在 app/build.gradle 里添加下面这行代码:

apply plugin: 'kotlin-kapt'

然后在 app/build.gradle 的 dependencies 里加入 qmui 相关依赖:

def qmui_version = '2.0.0-alpha02'
implementation "com.qmuiteam:qmui:$qmui_version"
implementation "com.qmuiteam:arch:$qmui_version"
kapt "com.qmuiteam:arch-compiler:$qmui_version" // use annotationProcessor if java

Application 与 Theme


新建 Application,我取名为 GankApplication, QMUI 的手势返回需要注册 ActivityLifecycleCallbacks, 因此我们需要关于它的初始化代码:

class GankApplication: Application() {
    override fun onCreate() {
        super.onCreate()
        QMUISwipeBackActivityManager.init(this)
    }
}

这个时候,我们去到 AndroidManifest.xml 文件, 将 application 标签的 name 指向 GankApplication

<application
    android:allowBackup="true"
    android:icon="@mipmap/ic_launcher"
    android:label="@string/app_name"
    android:roundIcon="@mipmap/ic_launcher_round"
    android:supportsRtl="true"
    android:theme="@style/AppTheme"
    android:name=".GankApplication">
        <meta-data
            android:name="android.notch_support"
            android:value="true"/>
        <meta-data
            android:name="android.max_aspect"
            android:value="2.34"/>
        <meta-data
            android:name="notch.config"
            android:value="portrait|landscape"/>
</application>

为了适配全面屏和挖孔屏,我们需要添加一些 meta-data,这个没什么好说的,官方文档要求是这样,照着来就行了。


现在让我们把目光聚焦到 application 的 theme。QMUI 是支持各种配置的,而配置手段就是 theme。 因此必须要掌握它。


我们点击 AppTheme 跳转过去,修改它的 parent 为 QMUI.Compat.NoActionBar。QMUI 的很多组件就可以在里面设置了,例如需要更改 QMUITopBar 的相关设置:

<style name="AppTheme" parent="QMUI.Compat.NoActionBar">
    <item name="qmui_skin_support_topbar_bg">@color/app_topbar_bg_color</item>
    <item name="qmui_skin_support_topbar_title_color">@color/qmui_config_color_white</item>
    <item name="qmui_skin_support_topbar_subtitle_color">@color/qmui_config_color_75_white</item>
    <item name="qmui_skin_support_topbar_text_btn_color_state_list">@color/qmui_config_color_white</item>
    <item name="qmui_skin_support_topbar_image_tint_color">@color/qmui_config_color_white</item>
</style>

ame="qmui_skin_support_topbar_image_tint_color">@color/qmui_config_color_white</item></style>

Theme 或者说 style 是具有继承性的, 我们声明了 AppTheme 的 parent 为 QMUI.Compat.NoActionBar, 也就是说我们 AppTheme 默认拥有了 QMUI.Compat.NoActionBar 全部配置,而在 AppTheme 里添加新的 item,则是覆盖 parent 的配置,Android 系统自带的组件也有很多是可配置的,如果有特殊需求,也是可以更改的,例如更改默认 TextView 的颜色。


那么问题来了, theme 的继承规则是怎样的呢?


theme 的继承规则有两种:

1.parent 继承,就是像我们上面使用的那样,用 parent 属性指定其父 theme。

2.点继承,例如 QMUI.Compat 这个 theme 是继承自 QMUI 的。


如果同时存在点继承和 parent 继承,那么采用 parent 继承。


所以 app 的 theme 继承链为:AppTheme 继承自 QMUI.Compat.NoActionBarQMUI.Compat.NoActionBar 继承自 QMUI.CompatQMUI.Compat 继承自 QMUI

LauncherActivity


App 启动后我们的第一个界面是什么呢? 很多 App 会有一个闪屏广告页,另外一些会存在一个 Launcher 页,而不会直接去主界面。这是为什么呢?


App 启动分为冷启动和热启动, 热启动比较简单,其实就是后台进程切换到前台。 但冷启动不一样,它需要创建进程、走 Application 初始化等步骤,然后才轮到 Activity 的启动。这个流程是很复杂的,也比较耗时,如果业务上又在 Application.onCreate 里做一堆初始化逻辑,那么这个过程就更慢了。


我们做优化时有一种方式是:让用户感知不到慢,一般是展示一些东西给用户,例如进度条、loading动画等。 Android 官方也采取了这种方式,就是立刻让用户看到界面,而这个界面展示什么呢?这个时候 Activity 在 theme 里提供背景图就派上用途了。


Activity 默认的背景就是白色,如果你没加任何处理,你会看到你的 App 冷启动时会出现闪白的情况。 因此我们需要提供一个自定义的背景图片,让用户看到更多东西,而不只是一片白色, 这就是 LauncherActivity 的一个功能。


其次,在这段时间,Activity.onCreate 都没被触发的,因此我们没办法在这段时间做沉浸式状态栏,我们会看到状态栏那里是黑色,进入主界面时,还是有不协调的感觉,特别是挖孔屏盛行后,有的手机那里特别高。 我们没有特别好的方式解决它,一般的做法是让 LauncherActivity 配置成全屏,界面切换时,就不会那么的突兀了,而独立的 LauncherActivity 也不需要我们去处理跳转到主界面后非全屏的切换问题,在 theme 里配置就行了,这就是 LauncherActivity 的第二个功能。(更新:在一些挖孔屏上,仅仅全屏,依旧会存在挖孔屏区域黑色的情况,这个时候我们需要在 theme 里加上 <item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>, 不过它需要放在 value-v27 目录下)


再者,如果我们想做主界面的 A/B Test,或者某些场景产品希望调其它的界面,针对这种场景,我们可以在 LauncherActivity 里做跳转分发逻辑,大型 App 肯定会用到的,这应该是它的第三个功能。


说了这么多,让我们来构建 LauncherActivity 吧。


先创建 LauncherActivity,暂时先不做到主界面的跳转逻辑 :

class LauncherActivity: QMUIActivity(){
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
    }
}

然后在 AndroidManifest.xml 注册它:

<activity
    android:name=".LauncherActivity"
    android:theme="@style/AppTheme.Launcher">
    <intent-filter>
        <category android:name="android.intent.category.LAUNCHER"/>
        <action android:name="android.intent.action.MAIN"/>
    </intent-filter>
</activity>

我们加上 intent-filter,使得它作为默认启动的 Activity。 然后通过 theme 去指定全屏和背景:

<style name="AppTheme.Launcher">
    <item name="android:windowFullscreen">true</item>
    <item name="android:windowBackground">@drawable/launcher_bg</item>
</style>

然后添加让我们的背景 drawable, 这里我写的 launcher_bg 只是把 logo 放出来了,一般会定制一下,并且可以同 layer-list 叠加多个图层:

<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:drawable="@color/qmui_config_color_white"/>
    <item android:drawable="@mipmap/ic_launcher" android:gravity="center"/>
</layer-list>

以前的话,LauncherActivity 配置就完成了,但是现在不行,因为我们还需要适配 Dark Mode。 并且背景展示的时机太早了, QMUI 的 skin 机制还来不及执行,因此这个时候也只能走原生的机制:


Android Dark Mode 的适配和我们其它资源在不同屏幕宽度下适配的原理是一样的,都是根据不同的状态去不同的文件夹下取资源。而 Dark Mode 的特定文件夹就是 drawable-night。 这个目录默认是不存在,需要我们创建,然后在里面放置一个同名的 launcher_bg.xml。 这样 Dark Mode 下就会默认显示这个背景了。里面你可以自定义背景展示,我这里只是改了下背景颜色。

<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:drawable="@color/qmui_config_color_pure_black"/>
    <item android:drawable="@mipmap/ic_launcher" android:gravity="center"/>
</layer-list>

到这里,整个 LauncherActivity 就编码完成了,App 也可以编译运行了。 (commit id 为 86771225, 可以 checkout 查看)

总结


今天我们从头新建了一个项目,然后写了个开端,需要掌握的是:

1.theme 以及 theme 的继承规则。

2.冷启动与热启动,以及如何设置启动界面的背景。

3.Android 系统提供的 Dark Mode 适配机制。

目录
相关文章
|
Kubernetes 安全 应用服务中间件
案例实战| 学习笔记
快速学习案例实战。
118 0
案例实战| 学习笔记
|
机器学习/深度学习 Kubernetes 安全
案例实战二| 学习笔记
快速学习案例实战二。
101 0
案例实战二| 学习笔记
|
SQL 存储 分布式计算
HBSAE实战总结
HBSAE实践:(先启动zookeeper)
181 0
HBSAE实战总结
|
监控 JavaScript 机器人
Hubot 实战 | 学习笔记
快速学习 Hubot 实战
604 0
html+css实战118-特点
html+css实战118-特点
106 0
html+css实战118-特点
html+css实战91-pxcook使用
html+css实战91-pxcook使用
147 0
html+css实战91-pxcook使用
html+css实战2-精讲和实战
html+css实战2-精讲和实战
82 0
html+css实战2-精讲和实战