SystemUI中NavigationBar分析
需求
SystemUI是一个与系统组件显示紧密相关的应用,包含快捷中心、消息通知、状态栏、导航栏、任务中心等诸多模块,本文介绍NavigationBar模块。SystemUI源码位于/frameworks/base/packages/SystemUI,Android13平台。NavigationBar显示如下:
关键类
- NavigationBarComponent.java:NavigationBar组件类,采用Dagger进行依赖注入
- NavigationBar.java:将导航栏view添加到window
- navigation_bar.xml:NavigationBar布局文件
- NavigationBarView.java:设置导航栏图标
- NavigationBarInflaterView:解析config中导航栏排布信息,创建对应的view
- home.xml/back.xml:导航栏按钮对应的布局
- KeyButtonView.java:导航栏图标的View,如果设置了keycode,则将点击事件touch以keycode方式交由系统处理
代码流程
1. NavigationBar模块启动
Android13平台的SystemUI代码较旧平台变化比较大,各个组件采用了Dagger进行依赖注入(DI)。在SystemUIApplication启动的时候进行了组件的初始化,NavigationBar组件如下:
// SystemUI\src\com\android\systemui\navigationbar\NavigationBarComponent.java
@Subcomponent(modules = { NavigationBarModule.class })
@NavigationBarComponent.NavigationBarScope
public interface NavigationBarComponent {@Subcomponent.Factoryinterface Factory {NavigationBarComponent create(@BindsInstance @DisplayId Context context,@BindsInstance @Nullable Bundle savedState);}NavigationBar getNavigationBar();
}// SystemUI\src\com\android\systemui\navigationbar\NavigationBarModule.java
@Module
public interface NavigationBarModule {@Provides@NavigationBarScopestatic NavigationBarFrame provideNavigationBarFrame(@DisplayId LayoutInflater layoutInflater) {return (NavigationBarFrame) layoutInflater.inflate(R.layout.navigation_bar_window, null);}@Provides@NavigationBarScopestatic NavigationBarView provideNavigationBarview(@DisplayId LayoutInflater layoutInflater, NavigationBarFrame frame) {View barView = layoutInflater.inflate(R.layout.navigation_bar, frame);return barView.findViewById(R.id.navigation_bar_view);}
}
从上面可以看到navigation_bar是布局文件,NavigationBarView是具体的view,NavigationBar中实现导航栏view添加到window。
2.布局文件navigation_bar.xml
NavigationBarView和NavigationBarInflaterView实际上都是Framelayout
// SystemUI\res\layout\navigation_bar.xml
<com.android.systemui.navigationbar.NavigationBarViewxmlns:android="http://schemas.android.com/apk/res/android"android:id="@+id/navigation_bar_view"android:layout_height="match_parent"android:layout_width="match_parent"android:clipChildren="false"android:clipToPadding="false"android:background="@drawable/system_bar_background"><com.android.systemui.navigationbar.NavigationBarInflaterViewandroid:id="@+id/navigation_inflater"android:layout_width="match_parent"android:layout_height="match_parent"android:clipChildren="false"android:clipToPadding="false" /></com.android.systemui.navigationbar.NavigationBarView>
3.NavigationBarView
我们接着看NavigationBarView,主要做了下面几件事情:
- 在构造方法中创建了返回、主页等ButtonDispatcher。
- 布局加载完成时,找到了子view(NavigationInflaterView),并将ButtonDispatcher设置给了NavigationInflaterView
- onAttachedToWindow()时,将对应的图标设置给返回、主页等view
我们发现NavigationBarView中并没有创建返回、主页等对应的view,将返回、主页等对应的view添加到ViewGroup的操作在NavigationInflaterView中
// SystemUI\src\com\android\systemui\navigationbar\NavigationBarView.java
// 创建ButtonDispatcher
public NavigationBarView(Context context, AttributeSet attrs) {mButtonDispatchers.put(R.id.back, new ButtonDispatcher(R.id.back));mButtonDispatchers.put(R.id.home, new ButtonDispatcher(R.id.home));mButtonDispatchers.put(R.id.recent_apps, new ButtonDispatcher(R.id.recent_apps));
}
// 布局加载完成
public void onFinishInflate() {super.onFinishInflate();mNavigationInflaterView = findViewById(R.id.navigation_inflater);mNavigationInflaterView.setButtonDispatchers(mButtonDispatchers);reloadNavIcons();// reloadNavIcons()中调用了updateIcons()
}
// 获取图标
private void updateIcons(Configuration oldConfig) {final boolean orientationChange = oldConfig.orientation != mConfiguration.orientation;final boolean densityChange = oldConfig.densityDpi != mConfiguration.densityDpi;final boolean dirChange = oldConfig.getLayoutDirection() != mConfiguration.getLayoutDirectin();// 获取返回按钮、主页、按钮图标drawableif (orientationChange || densityChange) {mDockedIcon = getDrawable(R.drawable.ic_sysbar_docked);mHomeDefaultIcon = getHomeDrawable();}if (densityChange || dirChange) {mRecentIcon = getDrawable(R.drawable.ic_sysbar_recent);mContextualButtonGroup.updateIcons(mLightIconColor, mDarkIconColor);}if (orientationChange || densityChange || dirChange) {mBackIcon = getBackDrawable();}
}// 返回按钮图标,KeyButtonDrawable实际上是一个Drawable
public KeyButtonDrawable getBackDrawable() {KeyButtonDrawable drawable = getDrawable(getBackDrawableRes());orientBackButton(drawable);return drawable;
}// 设置图标
protected void onAttachedToWindow() {super.onAttachedToWindow();requestApplyInsets();reorient();updateNavButtonIcons();
}
4.NavigationBarInflaterView
NavigationBarInflaterView是真正创建返回、主页按钮view的地方,先解析config中设置config_navBarLayout排列信息,然后通过对应layout创建KeyButtonView。部分代码如下:
// SystemUI\src\com\android\systemui\navigationbar\NavigationBarInflaterView.java
// 布局加载完成
protected void onFinishInflate() {super.onFinishInflate();inflateChildren(); // 加载布局clearViews();// 清空传递过来的ButtonDispatcher中保存的viewinflateLayout(getDefaultLayout()); // 关键点:加载布局,创建view
}// getDefaultLayout()是获取按钮排布信息,从config.xml中获取,如:<string name="config_navBarLayout" translatable="false">left[.5W],back[1WC];home;recent[1WC],right[.5W]</string>
// 解析newLayout创建view
protected void inflateLayout(String newLayout) {if (newLayout == null) {newLayout = getDefaultLayout();}String[] sets = newLayout.split(GRAVITY_SEPARATOR, 3);if (sets.length != 3) {Log.d(TAG, "Invalid layout.");newLayout = getDefaultLayout();sets = newLayout.split(GRAVITY_SEPARATOR, 3);}String[] start = sets[0].split(BUTTON_SEPARATOR);String[] center = sets[1].split(BUTTON_SEPARATOR);String[] end = sets[2].split(BUTTON_SEPARATOR);// Inflate these in start to end order or accessibility traversal will be messed up.inflateButtons(start, mHorizontal.findViewById(com.android.internal.R.id.input_method_nav_ends_group),false /* landscape */, true /* start */);inflateButtons(center, mHorizontal.findViewById(com.android.internal.R.id.input_method_nav_center_group),false /* landscape */, false /* start */);addGravitySpacer(mHorizontal.findViewById(com.android.internal.R.id.input_method_nav_ends_group));inflateButtons(end, mHorizontal.findViewById(com.android.internal.R.id.input_method_nav_ends_group),false /* landscape */, false /* start */);updateButtonDispatchersCurrentView();
}// 创建view并添加到viewgroup
protected View inflateButton(String buttonSpec, ViewGroup parent, boolean landscape,boolean start) {LayoutInflater inflater = landscape ? mLandscapeInflater : mLayoutInflater;View v = createView(buttonSpec, parent, inflater); // 关键点:创建viewif (v == null) return null;v = applySize(v, buttonSpec, landscape, start);parent.addView(v);addToDispatchers(v);View lastView = landscape ? mLastLandscape : mLastPortrait;View accessibilityView = v;if (v instanceof ReverseRelativeLayout) {accessibilityView = ((ReverseRelativeLayout) v).getChildAt(0);}if (lastView != null) {accessibilityView.setAccessibilityTraversalAfter(lastView.getId());}if (landscape) {mLastLandscape = accessibilityView;} else {mLastPortrait = accessibilityView;}return v;
}// 通过对应的布局创建view,实际上创建的是KeyButtonView
View createView(String buttonSpec, ViewGroup parent, LayoutInflater inflater) {View v = null;String button = extractButton(buttonSpec);if (LEFT.equals(button)) {button = extractButton(NAVSPACE);} else if (RIGHT.equals(button)) {button = extractButton(MENU_IME_ROTATE);}if (HOME.equals(button)) {v = inflater.inflate(R.layout.home, parent, false);} else if (BACK.equals(button)) {v = inflater.inflate(R.layout.back, parent, false);} else if (RECENT.equals(button)) {v = inflater.inflate(R.layout.recent_apps, parent, false);}return v;
}
5.KeyButtonView
如上一步back按钮的布局文件如下。
<com.android.systemui.navigationbar.buttons.KeyButtonViewxmlns:android="http://schemas.android.com/apk/res/android"xmlns:systemui="http://schemas.android.com/apk/res-auto"android:id="@+id/back"android:layout_width="@dimen/navigation_key_width"android:layout_height="match_parent"android:layout_weight="0"systemui:keyCode="4"android:scaleType="center"android:contentDescription="@string/accessibility_back"android:paddingStart="@dimen/navigation_key_padding"android:paddingEnd="@dimen/navigation_key_padding"/>
KeyButtonView是一个ImageView,重写了onTouchEvent,设置了keyCode,则点击后给系统发送对应的keyevent
// SystemUI\src\com\android\systemui\navigationbar\buttons\KeyButtonView.java
public boolean onTouchEvent(MotionEvent ev) {...switch (action) {case MotionEvent.ACTION_DOWN:if (mCode != KEYCODE_UNKNOWN) {sendEvent(KeyEvent.ACTION_DOWN, 0, mDownTime);} else {// Provide the same haptic feedback that the system offers for virtual keys.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY);}}
}
private void sendEvent(int action, int flags, long when) {final int repeatCount = (flags & KeyEvent.FLAG_LONG_PRESS) != 0 ? 1 : 0;final KeyEvent ev = new KeyEvent(mDownTime, when, action, mCode, repeatCount,0, KeyCharacterMap.VIRTUAL_KEYBOARD, 0,flags | KeyEvent.FLAG_FROM_SYSTEM | KeyEvent.FLAG_VIRTUAL_HARD_KEY,InputDevice.SOURCE_KEYBOARD);int displayId = INVALID_DISPLAY;if (getDisplay() != null) {displayId = getDisplay().getDisplayId();}if (displayId != INVALID_DISPLAY) {ev.setDisplayId(displayId);}mInputManager.injectInputEvent(ev, InputManager.INJECT_INPUT_EVENT_MODE_ASYNC);
}
总结
- 将导航栏View添加到Window进行显示
- 通过读取解析xml里config的图标排布信息,来创建对应的view
- 如果设置了keycode,则将点击事件touch以keycode方式交由系统处理
参考
- Dagger/Hilt依赖注入使用:https://developer.android.com/training/dependency-injection?hl=zh-cn
- 解析Android 8.1平台SystemUI 导航栏加载流程:https://www.jb51.net/article/174313.htm
相关文章:
SystemUI中NavigationBar分析
需求 SystemUI是一个与系统组件显示紧密相关的应用,包含快捷中心、消息通知、状态栏、导航栏、任务中心等诸多模块,本文介绍NavigationBar模块。SystemUI源码位于/frameworks/base/packages/SystemUI,Android13平台。NavigationBar显示如下&…...

MySQL的底层原理与架构
前言 了解MySQL的架构和原理对于很多的后续很多的操作会有很大的帮助与理解。并且很多知识都与底层架构相关联。 了解MySQL架构 通过上面的架构图可以得知,Server层中主要由 连接器、查询缓存、解析器/分析器、优化器、执行器 几部分组成的,下面将主要…...

三极管的截止、放大、饱和区
三极管的几个区,都有什么用: 截止区:晶体管不导通,用于开关电路的“关”状态。 放大区:晶体管用于信号放大,集电极电流与基极电流成正比。 饱和区:晶体管完全导通,用于开关电路的“…...
2025-2-7-算法学习(一) 动态规划-习题1 300.最长递增子序列
文章目录 算法学习(一) 动态规划-习题1 300.最长递增子序列(1)题目(2)举例:(3)提示(4)分析(5)动态规划代码:&a…...
学习日记-250207
一.论文 1.Prompt Learning for News Recommendation 任务不一致(LLM与实际任务)产生prompt提示。 Prompt Learning for News Recommendation 论文阅读 SIGIR2023-CSDN博客 2.GPT4Rec: A Generative Framework for Personalized Recommendation and…...

【Block总结】PSA,金字塔挤压注意力,解决传统注意力机制在捕获多尺度特征时的局限性
论文信息 标题: EPSANet: An Efficient Pyramid Squeeze Attention Block on Convolutional Neural Network论文链接: arXivGitHub链接: https://github.com/murufeng/EPSANet 创新点 EPSANet提出了一种新颖的金字塔挤压注意力(PSA)模块,旨…...
代码随想录算法训练营第三十一天| 回溯算法04
491. 递增子序列 题目: 代码随想录 视频讲解:回溯算法精讲,树层去重与树枝去重 | LeetCode:491.递增子序列_哔哩哔哩_bilibili 这题需要注意的点: 1. path长度在2以上才放入最终结果 2. 需要记录已经使用过的数字&am…...

pycharm集成通义灵码应用
在pycharm中安装通义灵码 1、打开files-settings 2、选中plugins-搜索”TONGYI Lingma“,点击安装 3.安装完成后在pycharm的右侧就有通义灵码的标签 4、登录账号 5、查看代码区域代码,每一个方法前面都多了通义灵码的标识,可以直接选择…...

赛博算命之 ”梅花易数“ 的 “JAVA“ 实现 ——从玄学到科学的探索
hello~朋友们!好久不见! 今天给大家带来赛博算命第三期——梅花易数的java实现 赛博算命系列文章: 周易六十四卦 掐指一算——小六壬 更多优质文章:个人主页 JAVA系列:JAVA 大佬们互三哦~互三必回!…...
【Leetcode刷题记录】54. 螺旋矩阵--模拟,以及循环条件处理的一些细节
54. 螺旋矩阵 给你一个 m 行 n 列的矩阵 matrix ,请按照 顺时针螺旋顺序 ,返回矩阵中的所有元素。 示例 1: 输入:matrix [[1,2,3],[4,5,6],[7,8,9]] 输出:[1,2,3,6,9,8,7,4,5] 解题思路 顺时针螺旋顺序也就是“从左向…...
c++计算机教程
目的 做出-*/%计算机 要求 做出可以计算-*/%的计算机 实现 完整代码 #include<bits/stdc.h> int main() {std::cout<<"加 减- 乘* 除/ 取余% \没有了|(因为可以算三位)"<<"\n"<<"提示:每打完一个符号或打完一个数,\…...
蓝桥杯Java之输入输出练习题
题目 1:多组AB(基础版) 题目描述: 输入多组数据,每组数据包含两个整数 A 和 B,计算它们的和。输入以 文件结尾(EOF) 结束。 输入格式: 每行包含两个整数 A 和 B&#x…...

【R语言】环境空间
一、环境空间的特点 环境空间是一种特殊类型的变量,它可以像其它变量一样被分配和操作,还可以以参数的形式传递给函数。 R语言中环境空间具有如下3个特点: 1、对象名称唯一性 此特点指的是在不同的环境空间中可以有同名的变量出现&#x…...
【系统架构设计师】分布式数据库透明性
目录 1. 说明2. 分片透明3. 复制透明4. 位置透明5. 逻辑透明(局部数据模型透明)6.例题6.1 例题1 1. 说明 1.在分布式数据库系统中,分片透明、复制透明、位置透明和逻辑透明是几个重要的基本概念。2.分片透明、复制透明、位置透明和逻辑透明是…...

openpnp2.2 - 环境搭建 - 编译 + 调试 + 打包
文章目录 openpnp2.2 - 环境搭建 - 编译 调试 打包概述笔记前置任务克隆代码库切到最新的tag清理干净编译工程关掉旧工程打开已经克隆好的openpnp2.2工程将IDEA的SDK配置为openjdk23 切换中英文UI设置JAVA编译器 构建工程跑测试用例单步调试下断点导出工程的JAR包安装install…...

OpenCV:图像修复
目录 简述 1. 原理说明 1.1 Navier-Stokes方法(INPAINT_NS) 1.2 快速行进方法(INPAINT_TELEA) 2. 实现步骤 2.1 输入图像和掩膜(Mask) 2.2 调用cv2.inpaint()函数 2.3 完整代码示例 2.4 运行结果 …...
QT全局所有QSS样式实时切换
方法如下: void loadQss(int qssType) {QString name;if (qssType 1)name ":/qss/day.qss";elsename ":/qss/night.qss";QFile file(name);file.open(QFile::ReadOnly);QString qss;qss file.readAll();qApp->setStyleSheet(qss);file.…...

MySQL三大版本的演进
三大版本的演进 文章目录 三大版本的演进一:5.6版本(大跃进时期)1:支持只读事务2:innodb存储引擎增强2.1:缓冲池刷盘策略优化2.2:BufferPool缓冲池预热 3:新增Performance_Schema库监…...

利用 IMU 估计人体关节轴向和位置 —— 论文推导
Title: 利用 IMU 估计人体关节轴向和位置 —— “Joint axis and position estimation from inertial measurement data by exploiting kinematic constraints” —— 论文推导 文章目录 I. 论文回顾II. 铰接关节的约束1. 铰接关节约束的原理2. 铰接关节约束的梯度3. 铰接关节约…...

脚本一键生成管理下游k8s集群的kubeconfig
一、场景 1.1 需要管理下游k8s集群的场景。 1.2 不希望使用默认的cluster-admin权限的config. 二、脚本 **重点参数: 2.1 配置变量。 1、有单独namespace的权限和集群只读权限。 2、自签名的CA证书位置要正确。 2.2 如果配置错误,需要重新…...
React 第五十五节 Router 中 useAsyncError的使用详解
前言 useAsyncError 是 React Router v6.4 引入的一个钩子,用于处理异步操作(如数据加载)中的错误。下面我将详细解释其用途并提供代码示例。 一、useAsyncError 用途 处理异步错误:捕获在 loader 或 action 中发生的异步错误替…...

微信小程序之bind和catch
这两个呢,都是绑定事件用的,具体使用有些小区别。 官方文档: 事件冒泡处理不同 bind:绑定的事件会向上冒泡,即触发当前组件的事件后,还会继续触发父组件的相同事件。例如,有一个子视图绑定了b…...

python/java环境配置
环境变量放一起 python: 1.首先下载Python Python下载地址:Download Python | Python.org downloads ---windows -- 64 2.安装Python 下面两个,然后自定义,全选 可以把前4个选上 3.环境配置 1)搜高级系统设置 2…...

《从零掌握MIPI CSI-2: 协议精解与FPGA摄像头开发实战》-- CSI-2 协议详细解析 (一)
CSI-2 协议详细解析 (一) 1. CSI-2层定义(CSI-2 Layer Definitions) 分层结构 :CSI-2协议分为6层: 物理层(PHY Layer) : 定义电气特性、时钟机制和传输介质(导线&#…...

Opencv中的addweighted函数
一.addweighted函数作用 addweighted()是OpenCV库中用于图像处理的函数,主要功能是将两个输入图像(尺寸和类型相同)按照指定的权重进行加权叠加(图像融合),并添加一个标量值&#x…...
如何为服务器生成TLS证书
TLS(Transport Layer Security)证书是确保网络通信安全的重要手段,它通过加密技术保护传输的数据不被窃听和篡改。在服务器上配置TLS证书,可以使用户通过HTTPS协议安全地访问您的网站。本文将详细介绍如何在服务器上生成一个TLS证…...

Psychopy音频的使用
Psychopy音频的使用 本文主要解决以下问题: 指定音频引擎与设备;播放音频文件 本文所使用的环境: Python3.10 numpy2.2.6 psychopy2025.1.1 psychtoolbox3.0.19.14 一、音频配置 Psychopy文档链接为Sound - for audio playback — Psy…...
GitHub 趋势日报 (2025年06月08日)
📊 由 TrendForge 系统生成 | 🌐 https://trendforge.devlive.org/ 🌐 本日报中的项目描述已自动翻译为中文 📈 今日获星趋势图 今日获星趋势图 884 cognee 566 dify 414 HumanSystemOptimization 414 omni-tools 321 note-gen …...

Android 之 kotlin 语言学习笔记三(Kotlin-Java 互操作)
参考官方文档:https://developer.android.google.cn/kotlin/interop?hlzh-cn 一、Java(供 Kotlin 使用) 1、不得使用硬关键字 不要使用 Kotlin 的任何硬关键字作为方法的名称 或字段。允许使用 Kotlin 的软关键字、修饰符关键字和特殊标识…...

零基础在实践中学习网络安全-皮卡丘靶场(第九期-Unsafe Fileupload模块)(yakit方式)
本期内容并不是很难,相信大家会学的很愉快,当然对于有后端基础的朋友来说,本期内容更加容易了解,当然没有基础的也别担心,本期内容会详细解释有关内容 本期用到的软件:yakit(因为经过之前好多期…...