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 如果配置错误,需要重新…...
OpenClaw多语言支持:百川2-13B量化模型国际化任务实践
OpenClaw多语言支持:百川2-13B量化模型国际化任务实践 1. 为什么需要多语言自动化助手 去年接手一个跨国协作项目时,我每天要处理来自五个国家的邮件、文档和会议记录。最头疼的不是时差问题,而是不同语言的文档混在一起——英文技术规范、…...
时间管理大师:OpenClaw+nanobot自动规划每日日程
时间管理大师:OpenClawnanobot自动规划每日日程 1. 为什么需要AI日程规划助手 作为一个长期被多线程任务困扰的技术从业者,我一直在寻找能够真正理解我工作习惯的智能日程管理方案。市面上的日历应用大多只能机械地记录事件,而无法根据任务…...
表格拖拽排序实战:从业务需求到代码落地的全链路指南
表格拖拽排序实战:从业务需求到代码落地的全链路指南 【免费下载链接】ngx-datatable ✨ A feature-rich yet lightweight data-table crafted for Angular 项目地址: https://gitcode.com/gh_mirrors/ng/ngx-datatable 在现代Web应用中,数据表格…...
视频内容自动打标:基于Emotion2Vec+ Large的语音情绪分析方案
视频内容自动打标:基于Emotion2Vec Large的语音情绪分析方案 1. 引言:语音情绪分析在视频内容管理中的价值 在视频内容爆炸式增长的今天,如何高效管理和检索海量视频素材成为内容平台面临的重大挑战。传统的人工打标方式不仅效率低下&#…...
M2LOrder模型Mathtype公式编辑器的趣味扩展:为数学证明添加情感注释
M2LOrder模型Mathtype公式编辑器的趣味扩展:为数学证明添加情感注释 你有没有过这样的经历?面对一篇复杂的数学论文或教材,读到某个证明步骤时,心里忍不住嘀咕:“这一步也太巧妙了,怎么想到的?…...
Python+PySpark+Hadoop房价预测系统 房价预测 房源推荐系统 二手房推荐系统 随机森林回归预测模型、链家二手房 可视化大屏
1、项目 介绍 技术栈: Python房价预测分析系统 毕业设计 大屏 爬虫 机器学习 Flask框架、Echarts可视化、requests 爬虫、随机森林回归预测模型、链家二手房2、项目界面 (1)数据可视化大屏(2)房价预测(3&am…...
VibeVoice与Vue3前端整合:浏览器端语音合成方案
VibeVoice与Vue3前端整合:浏览器端语音合成方案 1. 为什么要在浏览器里直接合成语音 你有没有遇到过这样的场景:在做一个在线教育应用时,想让系统自动朗读课文,但每次都要把文字发到后端服务器,等几秒钟再把音频文件…...
MIKE21桥墩模拟避坑指南:从‘默认糙率倒置’到‘软启动设置’的完整配置流程
MIKE21桥墩模拟避坑指南:从糙率倒置到软启动的实战精要 当第一次打开MIKE21的桥墩模拟模块时,大多数工程师都会面临三个灵魂拷问:为什么输入的糙率值比教科书大几十倍?软启动参数究竟该设多长?桥墩断面分段数对结果影响…...
墨语灵犀处理时序数据实战:基于LSTM的预测模型辅助开发
墨语灵犀处理时序数据实战:基于LSTM的预测模型辅助开发 最近在做一个销量预测的项目,团队里新来的同事看着一长串的时序数据和复杂的LSTM模型代码,有点无从下手。他问我:“这些参数到底该怎么调?预测出来的曲线波动这…...
通义千问1.5-1.8B-Chat-GPTQ-Int4 Java开发集成:SpringBoot项目实战指南
通义千问1.5-1.8B-Chat-GPTQ-Int4 Java开发集成:SpringBoot项目实战指南 最近在帮一个朋友做项目,他们想在自己的Java应用里加个智能对话功能,看中了通义千问1.5-1.8B-Chat-GPTQ-Int4这个模型。这模型挺有意思的,体积小但能力不弱…...
