当前位置: 首页 > news >正文

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是一个与系统组件显示紧密相关的应用&#xff0c;包含快捷中心、消息通知、状态栏、导航栏、任务中心等诸多模块&#xff0c;本文介绍NavigationBar模块。SystemUI源码位于/frameworks/base/packages/SystemUI&#xff0c;Android13平台。NavigationBar显示如下&…...

MySQL的底层原理与架构

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

三极管的截止、放大、饱和区

三极管的几个区&#xff0c;都有什么用&#xff1a; 截止区&#xff1a;晶体管不导通&#xff0c;用于开关电路的“关”状态。 放大区&#xff1a;晶体管用于信号放大&#xff0c;集电极电流与基极电流成正比。 饱和区&#xff1a;晶体管完全导通&#xff0c;用于开关电路的“…...

2025-2-7-算法学习(一) 动态规划-习题1 300.最长递增子序列

文章目录 算法学习&#xff08;一&#xff09; 动态规划-习题1 300.最长递增子序列&#xff08;1&#xff09;题目&#xff08;2&#xff09;举例&#xff1a;&#xff08;3&#xff09;提示&#xff08;4&#xff09;分析&#xff08;5&#xff09;动态规划代码&#xff1a;&a…...

学习日记-250207

一.论文 1.Prompt Learning for News Recommendation 任务不一致&#xff08;LLM与实际任务&#xff09;产生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提出了一种新颖的金字塔挤压注意力&#xff08;PSA&#xff09;模块&#xff0c;旨…...

代码随想录算法训练营第三十一天| 回溯算法04

491. 递增子序列 题目&#xff1a; 代码随想录 视频讲解&#xff1a;回溯算法精讲&#xff0c;树层去重与树枝去重 | LeetCode&#xff1a;491.递增子序列_哔哩哔哩_bilibili 这题需要注意的点&#xff1a; 1. path长度在2以上才放入最终结果 2. 需要记录已经使用过的数字&am…...

pycharm集成通义灵码应用

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

赛博算命之 ”梅花易数“ 的 “JAVA“ 实现 ——从玄学到科学的探索

hello~朋友们&#xff01;好久不见&#xff01; 今天给大家带来赛博算命第三期——梅花易数的java实现 赛博算命系列文章&#xff1a; 周易六十四卦 掐指一算——小六壬 更多优质文章&#xff1a;个人主页 JAVA系列&#xff1a;JAVA 大佬们互三哦~互三必回&#xff01;&#xf…...

【Leetcode刷题记录】54. 螺旋矩阵--模拟,以及循环条件处理的一些细节

54. 螺旋矩阵 给你一个 m 行 n 列的矩阵 matrix &#xff0c;请按照 顺时针螺旋顺序 &#xff0c;返回矩阵中的所有元素。 示例 1&#xff1a; 输入&#xff1a;matrix [[1,2,3],[4,5,6],[7,8,9]] 输出&#xff1a;[1,2,3,6,9,8,7,4,5] 解题思路 顺时针螺旋顺序也就是“从左向…...

c++计算机教程

目的 做出-*/%计算机 要求 做出可以计算-*/%的计算机 实现 完整代码 #include<bits/stdc.h> int main() {std::cout<<"加 减- 乘* 除/ 取余% \没有了|(因为可以算三位)"<<"\n"<<"提示:每打完一个符号或打完一个数,\…...

蓝桥杯Java之输入输出练习题

题目 1&#xff1a;多组AB&#xff08;基础版&#xff09; 题目描述&#xff1a; 输入多组数据&#xff0c;每组数据包含两个整数 A 和 B&#xff0c;计算它们的和。输入以 文件结尾&#xff08;EOF&#xff09; 结束。 输入格式&#xff1a; 每行包含两个整数 A 和 B&#x…...

【R语言】环境空间

一、环境空间的特点 环境空间是一种特殊类型的变量&#xff0c;它可以像其它变量一样被分配和操作&#xff0c;还可以以参数的形式传递给函数。 R语言中环境空间具有如下3个特点&#xff1a; 1、对象名称唯一性 此特点指的是在不同的环境空间中可以有同名的变量出现&#x…...

【系统架构设计师】分布式数据库透明性

目录 1. 说明2. 分片透明3. 复制透明4. 位置透明5. 逻辑透明&#xff08;局部数据模型透明&#xff09;6.例题6.1 例题1 1. 说明 1.在分布式数据库系统中&#xff0c;分片透明、复制透明、位置透明和逻辑透明是几个重要的基本概念。2.分片透明、复制透明、位置透明和逻辑透明是…...

openpnp2.2 - 环境搭建 - 编译 + 调试 + 打包

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

OpenCV:图像修复

目录 简述 1. 原理说明 1.1 Navier-Stokes方法&#xff08;INPAINT_NS&#xff09; 1.2 快速行进方法&#xff08;INPAINT_TELEA&#xff09; 2. 实现步骤 2.1 输入图像和掩膜&#xff08;Mask&#xff09; 2.2 调用cv2.inpaint()函数 2.3 完整代码示例 2.4 运行结果 …...

QT全局所有QSS样式实时切换

方法如下&#xff1a; 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三大版本的演进

三大版本的演进 文章目录 三大版本的演进一&#xff1a;5.6版本&#xff08;大跃进时期&#xff09;1&#xff1a;支持只读事务2&#xff1a;innodb存储引擎增强2.1&#xff1a;缓冲池刷盘策略优化2.2&#xff1a;BufferPool缓冲池预热 3&#xff1a;新增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. 二、脚本 **重点参数&#xff1a; 2.1 配置变量。 1、有单独namespace的权限和集群只读权限。 2、自签名的CA证书位置要正确。 2.2 如果配置错误&#xff0c;需要重新…...

XML Group端口详解

在XML数据映射过程中&#xff0c;经常需要对数据进行分组聚合操作。例如&#xff0c;当处理包含多个物料明细的XML文件时&#xff0c;可能需要将相同物料号的明细归为一组&#xff0c;或对相同物料号的数量进行求和计算。传统实现方式通常需要编写脚本代码&#xff0c;增加了开…...

Debian系统简介

目录 Debian系统介绍 Debian版本介绍 Debian软件源介绍 软件包管理工具dpkg dpkg核心指令详解 安装软件包 卸载软件包 查询软件包状态 验证软件包完整性 手动处理依赖关系 dpkg vs apt Debian系统介绍 Debian 和 Ubuntu 都是基于 Debian内核 的 Linux 发行版&#xff…...

【论文笔记】若干矿井粉尘检测算法概述

总的来说&#xff0c;传统机器学习、传统机器学习与深度学习的结合、LSTM等算法所需要的数据集来源于矿井传感器测量的粉尘浓度&#xff0c;通过建立回归模型来预测未来矿井的粉尘浓度。传统机器学习算法性能易受数据中极端值的影响。YOLO等计算机视觉算法所需要的数据集来源于…...

Neo4j 集群管理:原理、技术与最佳实践深度解析

Neo4j 的集群技术是其企业级高可用性、可扩展性和容错能力的核心。通过深入分析官方文档,本文将系统阐述其集群管理的核心原理、关键技术、实用技巧和行业最佳实践。 Neo4j 的 Causal Clustering 架构提供了一个强大而灵活的基石,用于构建高可用、可扩展且一致的图数据库服务…...

Python爬虫(一):爬虫伪装

一、网站防爬机制概述 在当今互联网环境中&#xff0c;具有一定规模或盈利性质的网站几乎都实施了各种防爬措施。这些措施主要分为两大类&#xff1a; 身份验证机制&#xff1a;直接将未经授权的爬虫阻挡在外反爬技术体系&#xff1a;通过各种技术手段增加爬虫获取数据的难度…...

【HTTP三个基础问题】

面试官您好&#xff01;HTTP是超文本传输协议&#xff0c;是互联网上客户端和服务器之间传输超文本数据&#xff08;比如文字、图片、音频、视频等&#xff09;的核心协议&#xff0c;当前互联网应用最广泛的版本是HTTP1.1&#xff0c;它基于经典的C/S模型&#xff0c;也就是客…...

IP如何挑?2025年海外专线IP如何购买?

你花了时间和预算买了IP&#xff0c;结果IP质量不佳&#xff0c;项目效率低下不说&#xff0c;还可能带来莫名的网络问题&#xff0c;是不是太闹心了&#xff1f;尤其是在面对海外专线IP时&#xff0c;到底怎么才能买到适合自己的呢&#xff1f;所以&#xff0c;挑IP绝对是个技…...

MacOS下Homebrew国内镜像加速指南(2025最新国内镜像加速)

macos brew国内镜像加速方法 brew install 加速formula.jws.json下载慢加速 &#x1f37a; 最新版brew安装慢到怀疑人生&#xff1f;别怕&#xff0c;教你轻松起飞&#xff01; 最近Homebrew更新至最新版&#xff0c;每次执行 brew 命令时都会自动从官方地址 https://formulae.…...

实战三:开发网页端界面完成黑白视频转为彩色视频

​一、需求描述 设计一个简单的视频上色应用&#xff0c;用户可以通过网页界面上传黑白视频&#xff0c;系统会自动将其转换为彩色视频。整个过程对用户来说非常简单直观&#xff0c;不需要了解技术细节。 效果图 ​二、实现思路 总体思路&#xff1a; 用户通过Gradio界面上…...

认识CMake并使用CMake构建自己的第一个项目

1.CMake的作用和优势 跨平台支持&#xff1a;CMake支持多种操作系统和编译器&#xff0c;使用同一份构建配置可以在不同的环境中使用 简化配置&#xff1a;通过CMakeLists.txt文件&#xff0c;用户可以定义项目结构、依赖项、编译选项等&#xff0c;无需手动编写复杂的构建脚本…...