底部导航栏新增功能按键

场景需求:

在底部导航栏添加power案件,单击息屏,长按 关机

如下实现图
在这里插入图片描述

借此需求,需要掌握技能:

  • 底部导航栏如何实现
  • 新增、修改、删除底部导航栏流程
  • 对底部导航栏部分样式如何修改。 比如放不下、顺序排列、坑点如何修改等

修改-新增文件:

新增

\vendor\mediatek\proprietary\packages\apps\SystemUI\res\layout\power.xml         [新增底部导航 power 功能按键布局文件]
\vendor\mediatek\proprietary\packages\apps\SystemUI\res\drawable\ic_lock_power_off.xml  [新增power按键icon图标]

修改

\vendor\mediatek\proprietary\packages\apps\SystemUI\src\com\android\systemui\navigationbar\NavigationBarView.java
\vendor\mediatek\proprietary\packages\apps\SystemUI\src\com\android\systemui\navigationbar\NavigationBar.java
\vendor\mediatek\proprietary\packages\apps\SystemUI\src\com\android\systemui\navigationbar\NavigationBarInflaterView.java需改:新增 power 功能菜单
\vendor\mediatek\proprietary\packages\apps\SystemUI\res\values\config.xml      
\vendor\mediatek\proprietary\packages\apps\SystemUI\res\values-sw900dp\config.xml
\vendor\mediatek\proprietary\packages\apps\SystemUI\res\values-sw600dp\config.xml//按键太多,设置按键宽度,让竖屏情况下多个功能虚拟按键能够正常显示出来:我们对应的dimens 文件为values-sw600dp  故,只修改这个文件即可
/vendor/mediatek/proprietary/packages/apps/SystemUI/res/values-sw600dp/dimens.xml

具体修改点

修改点 一)
功能按键添加:power

<string name="config_navBarLayout" translatable="false">left;volume_sub,back,home,recent,wb_sunny,volume_add,power,screenshot;right</string>

修改点 二)
虚拟按键宽度和padding 重写设置:

<dimen name="navigation_key_width">90dp</dimen>

修改点 三)

NavigationBarView.java            [设置按键图标、按键放到集合中,便于封装操作]
NavigationBar.java                 [点触事件 长按、短按事件]
NavigationBarInflaterView.java   [加载布局,加载功能按键view]
见修改文件  wang 相关 add 和 end 结束位置
下文 详细说明

参考资料

Android 8.1平台SystemUI 导航栏加载流程解析:
Android 9.0 SystemUI NavigationBar
Android13 关于SystemUI更新Nav Bar add volume button && other button
Android 导航栏功能项的显示与屏蔽
Android 12 自定义底部导航栏
Android11 底部导航栏添加虚拟按钮-问题合集

实现思路和具体方案

在实现方案之前,一脸蒙蔽,那是缺乏一定的知识储备,实现后发现思路很清晰的,理解流程,一步一步实现即可。可以参考现有的实现方案,比如:home/back/recent 部分Android版本
本身就已经有的功能,照葫芦画瓢。

主要思路如下:

  • 创建功能按键布局layout[对应 xml]:参考已有的功能按键
  • 配置文件中,新增功能按键菜单,只有有了默认的配置才会加载上面的布局View
  • NavigationBar为系统加载的组件,在组件中实现点击事件,在关联的加载布局View 类
    NavigationBarInflaterView.java 和 NavigationBarView.java 实现布局的功能按键View的添加和功能view 的获取/设置View背景等,最终实现功能按键的加载。

布局创建

比如我们的power.xml

<?xml version="1.0" encoding="utf-8"?>
<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/power"android:layout_width="@dimen/navigation_key_width"android:layout_height="match_parent"android:layout_weight="0"systemui:keyCode="26"android:scaleType="center"android:contentDescription="@string/accessibility_home"android:paddingStart="@dimen/navigation_key_padding"android:paddingEnd="@dimen/navigation_key_padding"/>

那为什么这么创建? 找一下 已有的 功能按键 比如Home 按键:

<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/home"android:layout_width="@dimen/navigation_key_width"android:layout_height="match_parent"android:layout_weight="0"systemui:keyCode="3"android:scaleType="center"android:contentDescription="@string/accessibility_home"android:paddingStart="@dimen/navigation_key_padding"android:paddingEnd="@dimen/navigation_key_padding"/>

这里有三个点需要注意:

  • layout_width 值的设置,底部导航栏功能按键过多,会出现不会显示全的问题,需要适配。
  • keyCode,有keyCode设置,会根据KeyButtonView 监听到keyCode
    按键值,这样可以监听并执行对应逻辑。【此需求暂无用到】
  • id: 这个id 在NavigationBarView,中的 mButtonDispatchers.put(R.id.power, new
    ButtonDispatcher(R.id.power)); 对应 匹配,不然找不到id。

布局加载

添加到NavigationBarInflaterView中去,NavigationBarInflaterView 见名知意。本身代码量不多的,就是一个View,加载功能按键子View

NavigationBarInflaterView extends FrameLayout ,正常的一个FragmentLayout ,如下举例几个简单方法

加载配置

 protected String getDefaultLayout() {//wangfangchen add /*final int defaultResource = QuickStepContract.isGesturalMode(mNavBarMode)? R.string.config_navBarLayoutHandle: mOverviewProxyService.shouldShowSwipeUpUI()? R.string.config_navBarLayoutQuickstep: R.string.config_navBarLayout;*/final int defaultResource =		 R.string.config_navBarLayout;Log.d(TAG,"getDefaultLayout  "+getContext().getString(defaultResource));//wangfangchen end  return getContext().getString(defaultResource);}

所以我们需要新增功能按键就需要在配置 文件中添加功能按键:power

<string name="config_navBarLayout" translatable="false">left;volume_sub,back,home,recent,wb_sunny,volume_add,power,screenshot;right</string>

加载子View

@Overrideprotected void onFinishInflate() {super.onFinishInflate();inflateChildren();clearViews();inflateLayout(getDefaultLayout());}protected void inflateLayout(String newLayout) {mCurrentLayout = newLayout;if (newLayout == null) {newLayout = getDefaultLayout();}String[] sets = newLayout.split(GRAVITY_SEPARATOR, 3);Log.d(TAG, "inflateLayout  newLayout:"+newLayout);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(R.id.ends_group),false /* landscape */, true /* start */);inflateButtons(start, mVertical.findViewById(R.id.ends_group),true /* landscape */, true /* start */);inflateButtons(center, mHorizontal.findViewById(R.id.center_group),false /* landscape */, false /* start */);inflateButtons(center, mVertical.findViewById(R.id.center_group),true /* landscape */, false /* start */);addGravitySpacer(mHorizontal.findViewById(R.id.ends_group));addGravitySpacer(mVertical.findViewById(R.id.ends_group));inflateButtons(end, mHorizontal.findViewById(R.id.ends_group),false /* landscape */, false /* start */);inflateButtons(end, mVertical.findViewById(R.id.ends_group),true /* landscape */, false /* start */);updateButtonDispatchersCurrentView();}private void inflateButtons(String[] buttons, ViewGroup parent, boolean landscape,boolean start) {for (int i = 0; i < buttons.length; i++) {Log.d(TAG,"inflateButtons buttons[i]:"+buttons[i]+"  landscape:"+landscape+"    start:"+start);inflateButton(buttons[i], parent, landscape, start);}}

创建 子view:createView

@Nullableprotected View inflateButton(String buttonSpec, ViewGroup parent, boolean landscape,boolean start) {LayoutInflater inflater = landscape ? mLandscapeInflater : mLayoutInflater;View v = createView(buttonSpec, parent, inflater);if (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 到 NavigationBarView

View createView(String buttonSpec, ViewGroup parent, LayoutInflater inflater) {Log.d(TAG,"createView   buttonSpec:"+buttonSpec);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);} else if (MENU_IME_ROTATE.equals(button)) {v = inflater.inflate(R.layout.menu_ime, parent, false);} else if (NAVSPACE.equals(button)) {v = inflater.inflate(R.layout.nav_key_space, parent, false);} else if (CLIPBOARD.equals(button)) {v = inflater.inflate(R.layout.clipboard, parent, false);} else if (CONTEXTUAL.equals(button)) {v = inflater.inflate(R.layout.contextual, parent, false);} else if (HOME_HANDLE.equals(button)) {v = inflater.inflate(R.layout.home_handle, parent, false);} else if (IME_SWITCHER.equals(button)) {v = inflater.inflate(R.layout.ime_switcher, parent, false);//huanghb add} else if (VOLUME_ADD.equals(button)) {v = inflater.inflate(R.layout.volume_add, parent, false);} else if (VOLUME_SUB.equals(button)) {v = inflater.inflate(R.layout.volume_sub, parent, false);} else if (SCREENSHOT.equals(button)) {v = inflater.inflate(R.layout.screenshot, parent, false);} //wangfangchen add else if (POWER.equals(button)) {v = inflater.inflate(R.layout.power, parent, false);} //wangfangchen end else if (WB_SUNNY.equals(button)) {v = inflater.inflate(R.layout.wb_sunny, parent, false);} else if (button.startsWith(KEY)) {Log.d(TAG,"createView   buttonSpec:"+buttonSpec+"   button:"+button+"   KEY:"+KEY);String uri = extractImage(button);int code = extractKeycode(button);v = inflater.inflate(R.layout.custom_key, parent, false);((KeyButtonView) v).setCode(code);if (uri != null) {if (uri.contains(":")) {((KeyButtonView) v).loadAsync(Icon.createWithContentUri(uri));} else if (uri.contains("/")) {int index = uri.indexOf('/');String pkg = uri.substring(0, index);int id = Integer.parseInt(uri.substring(index + 1));((KeyButtonView) v).loadAsync(Icon.createWithResource(pkg, id));}}}return v;}

加载布局View NavigationBarInflaterView

上面已经分析了NavigationBarInflaterView 如何加载子功能按键,那么这个View 是在哪里已经并初始化呢?
看布局:navigation_bar.xm


<?xml version="1.0" encoding="utf-8"?>
<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>

找navigation_bar.xml 加载地方
1)NavigationBar.java oreateView 方法
在这里插入图片描述

备注:NavigationBar 就暂时不分析了,可以参考其它博客或自己的System UI加载流程。

NavigationBarView View的封装

上面分析 NavigationBarView 嵌套了 NavigationBarInflaterView,也就是对NavigationBarInflaterView 的一层封装,

private final SparseArray<ButtonDispatcher> mButtonDispatchers = new SparseArray<>();
放置:view 到数组//huanghb addmButtonDispatchers.put(R.id.volume_add, new ButtonDispatcher(R.id.volume_add));mButtonDispatchers.put(R.id.volume_sub, new ButtonDispatcher(R.id.volume_sub));mButtonDispatchers.put(R.id.screenshot, new ButtonDispatcher(R.id.screenshot));mButtonDispatchers.put(R.id.wb_sunny, new ButtonDispatcher(R.id.wb_sunny));//huanghb end//wangfangchen add		mButtonDispatchers.put(R.id.power, new ButtonDispatcher(R.id.power));//wangfangchen end		

从通过view 数组,获取子view

      return mButtonDispatchers.get(R.id.recent_apps);}public ButtonDispatcher getBackButton() {return mButtonDispatchers.get(R.id.back);}public ButtonDispatcher getHomeButton() {return mButtonDispatchers.get(R.id.home);}public ButtonDispatcher getImeSwitchButton() {return mButtonDispatchers.get(R.id.ime_switcher);}public ButtonDispatcher getAccessibilityButton() {return mButtonDispatchers.get(R.id.accessibility_button);}这不就是对子菜单功能按键的一层封装吗?	

子布局显示并控制NavigationBar

在上面已经通过分析了布局,反向关联了NavigationBarInflaterView 加载->NavigationBar 加载。就从这个角度来分析布局显示出来后如何控制。

看部分相关方法并分析

准备View prepareNavigationBarView()

  private void prepareNavigationBarView() {Log.d(TAG,"prepareNavigationBarView");mNavigationBarView.reorient();ButtonDispatcher recentsButton = mNavigationBarView.getRecentsButton();recentsButton.setOnClickListener(this::onRecentsClick);recentsButton.setOnTouchListener(this::onRecentsTouch);ButtonDispatcher homeButton = mNavigationBarView.getHomeButton();homeButton.setOnTouchListener(this::onHomeTouch);reconfigureHomeLongClick();ButtonDispatcher accessibilityButton = mNavigationBarView.getAccessibilityButton();accessibilityButton.setOnClickListener(this::onAccessibilityClick);accessibilityButton.setOnLongClickListener(this::onAccessibilityLongClick);updateAccessibilityServicesState(mAccessibilityManager);ButtonDispatcher imeSwitcherButton = mNavigationBarView.getImeSwitchButton();imeSwitcherButton.setOnClickListener(this::onImeSwitcherClick);updateScreenPinningGestures();//huanghb add//Log.d("huanghb","prepareNavigationBarView");ShowVolumeButton();ShowScreenShotButton();ShowWbSunnyButton();//huanghb end//wangfangchen add ShowPowerButton();//wangfangchen end }

设置点击事件 长按事件

//wangfangchen add private void ShowPowerButton(){Log.d(TAG,"ShowPowerButton");ButtonDispatcher powerButton = mNavigationBarView.getPowerButton();// String customScreenshot = SystemProperties.get("persist.fise.screenshot.icon","0");powerButton.setOnClickListener(this::onPowerClick);//powerButton.setOnTouchListener(this::onScreenShotTouch);powerButton.setOnLongClickListener(this::onPowerLongClick);powerButton.setVisibility(View.VISIBLE); }//wangfangchen end 	//wangfangchen add private void onPowerClick(View v) {Log.d(TAG,"onPowerClick");mHandler.removeCallbacks(mPower);mHandler.postDelayed(mPower , 200);}private boolean onPowerLongClick(View v) {Log.d(TAG,"onPowerLongClick");setActionNavigationbar();mHandler.removeCallbacks(mPowerReboot);mHandler.postDelayed(mPowerReboot, 200);return  true;}//wangfangchen end 

在对应的线程里面执行逻辑即可

总结

上面分析就很明朗了,需要搞清楚
1)功能按键 布局,如何加载
2)配置 功能菜单 ,如何配置
2)三个类:NavigationBarView NavigationBar NavigationBarInflaterView 联系

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/483542.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

如何在 Firefox 中清除特定网站的浏览历史记录

以下&#xff0c;我将介绍如何清除特定网站的浏览历史记录。清除历史记录可以保护隐私&#xff0c;特别是在公共或共享设备上使用时&#xff0c;还能节省设备存储空间&#xff0c;避免浏览历史占用过多内存。 如何清除特定网站的浏览历史记录 在 Firefox 中&#xff0c;清除特…

SpringMVC(二)

Model 以Map方式进行存储&#xff0c;用于向作用域中存值。 注意&#xff1a;在Model中增加模型数据&#xff0c;若不指定key&#xff0c;则默认使用对象的类型作为key Controller //控制器类 public class IndexController {RequestMapping("/index3")public Strin…

ABE 中的隐藏属性:DIPPE(去中心化内积谓词加密)

1. 引言 相关论文有&#xff1a; Yan Michalevsky 和 Marc Joye 2018年论文 Decentralized policy-hiding ABE with receiver privacy&#xff0c;发表于23rd European Symposium on Research in Computer Security, ESORICS 2018。Amit Sahai 和 Brent Waters 2005年论文 Fu…

计算机网络——不同版本的 HTTP 协议

介绍 HTTP&#xff0c;即超文本传输协议&#xff08;HyperText Transfer Protocol&#xff09;&#xff0c;是应用层的一个简单的请求-响应协议&#xff0c;它指定了客户端可能发送给服务器什么样的消息以及得到什么样的响应。本文将介绍 HTTP 协议各个版本。 HTTP/1.0 HTTP/1…

Linux——基础命令(2) 文件内容操作

目录 ​编辑 文件内容操作 1.Vim &#xff08;1&#xff09;移动光标 &#xff08;2&#xff09;复制 &#xff08;3&#xff09;剪切 &#xff08;4&#xff09;删除 &#xff08;5&#xff09;粘贴 &#xff08;6&#xff09;替换,撤销,查找 &#xff08;7&#xff…

嵌入式硬件实战提升篇(三)商用量产电源设计方案 三路电源输入设计 电源管理 多输入供电自动管理 DCDC降压

引言&#xff1a;本文你能实际的了解到实战量产产品中电源架构设计的要求和过程&#xff0c;并且从实际实践出发搞懂电源架构系统&#xff0c;你也可以模仿此架构抄板到你自己的项目&#xff0c;并结合硬件篇之前的项目以及理论形成正真的三路电源输入设计与开发板电源架构块供…

30分钟学会正则表达式

正则表达式是对字符串操作的一种逻辑公式&#xff0c;就是用事先定义好的一些特定字符、及这些特定字符的组合&#xff0c;组成一个“规则字符串”&#xff0c;这个“规则字符串”用来表达对字符串的一种过滤逻辑。 作用 匹配 查看一个字符串是否符合正则表达式的语法 搜索 正…

如何手搓一个智能激光逗猫棒

背景 最近家里的猫胖了&#xff0c;所以我就想做个逗猫棒。找了一圈市场上的智能逗猫棒&#xff0c;运行轨迹比较单一&#xff0c;互动性不足。 轨迹单一&#xff0c;活动范围有限 而我希望后续可以结合人工智能物联网&#xff0c;通过摄像头来捕捉猫的位置&#xff0c;让小…

【C语言】递归的内存占用过程

递归 递归是函数调用自身的一种编程技术。在C语言中&#xff0c;递归的实现会占用内存栈&#xff08;Call Stack&#xff09;&#xff0c;每次递归调用都会在栈上分配一个新的 “栈帧&#xff08;Stack Frame&#xff09;”&#xff0c;用于存储本次调用的函数局部变量、返回地…

Bert+CRF的NER实战

CRF&#xff08;条件随机场-Conditional Random Field&#xff09; 原始本文&#xff1a;我在北京吃炸酱面 标注示例&#xff08;采用BIO标注方式&#xff09;&#xff1a; 我O在O北B-PLA京I-PLA吃O炸B-FOOD酱I-FOOD面I-FOOD CRF&#xff1a; 目的&#xff1a;提出一些不可能…

pycharm链接neo4j数据库(简单)

1.安装pycharm 2.安装库 pip install py2neo -i https://pypi.tuna.tsinghua.edu.cn/simple 3.代码试运行 from py2neo import Graph, Node, Relationship# 连接到Neo4j数据库&#xff0c;使用Bolt协议 graph Graph("bolt://localhost:7687", auth("neo…

故障诊断 | Transformer-LSTM组合模型的故障诊断(Matlab)

效果一览 文章概述 故障诊断 | Transformer-LSTM组合模型的故障诊断(Matlab) 源码设计 %% 初始化 clear close all clc disp(此程序务必用2023b及其以上版本的MATLAB!否则会报错!) warning off %

flask的第一个应用

本文编写一个简单的实例来记录下flask的使用 文章目录 简单实例flask中的路由无参形式有参形式 参数类型不同的http方法本文小结 简单实例 flask的依赖包都安装好之后&#xff0c;我们就可以写一个最简单的web应用程序了&#xff0c;我们把这个应用程序命名为first.py: from fl…

jmeter 压测常用静默参数解释应用

简介&#xff1a; JMeter静默压测&#xff08;即无界面压测&#xff09;是一种常用的性能测试方法&#xff0c;用于模拟多个用户同时访问系统并测量系统的响应时间和吞吐量等关键性能指标。在JMeter静默压测中&#xff0c;常用的压测参数及其解释如下&#xff1a; 一、基本…

《Python基础》之Pandas库

目录 一、简介 二、Pandas的核心数据结构 1、Series 2、DataFrame 三、数据读取与写入 1、数据读取 2、数据写入 四、数据清洗与处理 1、处理缺失值 2、处理重复值 3、数据转换 五、数据分析与可视化 1、统计描述 2、分组聚合 3、数据可视化 六、高级技巧 1、时…

【C语言】结构体(四)

本篇重点是typedef关键字 一&#xff0c;是什么&#xff1f; typedef用来定义新的数据类型&#xff0c;通常typedef与结构体的定义配合使用。 简单来说就是取别名 ▶ struct 是用来定义新的数据类型——结构体 ▶ typedef是给数据类型取别名。 二&#xff0c;为什么&#xf…

12月2日星期一今日早报简报微语报早读

12月2日星期一&#xff0c;农历十一月初二&#xff0c;早报#微语早读。 1、公安部&#xff1a;全国机动车所有人12月2日起均可申领电子行驶证&#xff1b; 2、2025年国考笔试开考&#xff1a;参考率约为86.7%&#xff0c;约65人录1人&#xff1b; 3、今日头条、拼多多等9款A…

Navicat连接SQL Server及SpringBoot连接SQL Server(jtds)

Navicat连接SQL Server 安装自带的SQL Server客户端 去到Navicat安装目录&#xff0c;找到安装程序&#xff0c;安装即可。 安装对应版本的Microsoft ODBC Driver for SQL Server 打开Navicat输入对应的SQL Server相关信息 然后点测试连接&#xff0c;提示连接成功。 Spr…

【机器学习】CatBoost 模型实践:回归与分类的全流程解析

一. 引言 本篇博客首发于掘金 https://juejin.cn/post/7441027173430018067。 PS&#xff1a;转载自己的文章也算原创吧。 在机器学习领域&#xff0c;CatBoost 是一款强大的梯度提升框架&#xff0c;特别适合处理带有类别特征的数据。本篇博客以脱敏后的保险数据集为例&#x…

用三维模型的顶点法向量计算法线贴图

法线贴图的核心概念是在不增加额外多边形数目的情况下&#xff0c;通过模拟细节来改善光照效果。具体流程包括&#xff1a; 法线的计算与存储&#xff1a;通过法线映射将三维法线向量转化为法线贴图的 RGB 值。渲染中的使用&#xff1a;在片段着色器中使用法线贴图来替代原有的…