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

Android9底部导航栏出现空白按钮问题分析

Android9底部导航栏出现空白按钮问题分析

  • 底部导航栏的初始化
    • 进入NavigationBarView初始化:
      • 进入NavigationBarView的onFinishInflater
    • 进入NavigationBarInflaterView
      • NavigationBarInflaterView加载单个的button
    • 回到NavigationFragment的创建流程
      • 多次调用NavigationBarView的刷新
    • 初始化流程的错误追踪。
  • 空格按键的问题定位。
  • 临时解决办法

底部导航栏的初始化

NavigationBarFragment.onCreateView()初始化时渲染创建了navigation_bar

@Overridepublic View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,Bundle savedInstanceState) {return inflater.inflate(R.layout.navigation_bar, container, false);}

这个navigation_bar布局:
很直接的是一个NavigationBarView内部嵌套NavigationBarInflaterView

<com.android.systemui.statusbar.phone.NavigationBarViewxmlns:android="http://schemas.android.com/apk/res/android"xmlns:systemui="http://schemas.android.com/apk/res-auto"android:layout_height="match_parent"android:layout_width="match_parent"android:background="@drawable/system_bar_background"><com.android.systemui.statusbar.phone.NavigationBarInflaterViewandroid:id="@+id/navigation_inflater"android:layout_width="match_parent"android:layout_height="match_parent" />
</com.android.systemui.statusbar.phone.NavigationBarView>

进入NavigationBarView初始化:

创建了大量按键的按键矢量图对象,和按键调度对象。

public class NavigationBarView extends FrameLayout implements PluginListener<NavGesture> {private final SparseArray<ButtonDispatcher> mButtonDispatchers = new SparseArray<>();// 大量的keybuttondrawable成员变量。即按键的图片资源private KeyButtonDrawable mBackIcon;...private KeyButtonDrawable mHomeDefaultIcon, mHomeCarModeIcon;private KeyButtonDrawable mRecentIcon;....public NavigationBarView(Context context, AttributeSet attrs) {super(context, attrs);// 为所有的keybuttondrawable加载drawable矢量图资源reloadNavIcons();// 以按键id- buttonDispatcher对象为键值对,全部存入SparseArray数组中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));mButtonDispatchers.put(R.id.menu, new ButtonDispatcher(R.id.menu));mButtonDispatchers.put(R.id.ime_switcher, new ButtonDispatcher(R.id.ime_switcher));}

进入NavigationBarView的onFinishInflater

显示器尺寸和横竖屏布局的确认

@Overridepublic void onFinishInflate() {// 绑定NavigationInflaterView对象,并将初始化好的buttondispatcher数组传值mNavigationInflaterView = findViewById(R.id.navigation_inflater);mNavigationInflaterView.setButtonDispatchers(mButtonDispatchers);....// 更新横竖屏的布局加载updateRotatedViews();}
private void updateCurrentView() {final int rot = mDisplay.getRotation();// NavigationBarView共有0,90,180,270四个角度的背景,横竖两种layout布局。// 根据显示display的旋转角度来指定其中一个显示。for (int i = 0; i < 4; i++) {mRotatedViews[i].setVisibility(View.GONE);}.........}

进入NavigationBarInflaterView

所有button的显示是在这个view中确定的

@Overrideprotected void onFinishInflate() {super.onFinishInflate();// 贴上四个布局文件,并绑定子viewinflateChildren(); clearViews();// 解析配置文件渲染// getDefaultLayout获取到R.string.config_navBarLayoutQuickstep或R.string.config_navBarLayout// 是字符串left;volume_sub,back,home,recent,volume_add,screenshot;right[.1W]inflateLayout(getDefaultLayout()); }
protected void inflateLayout(String newLayout) {.......// 用;号取出三组按钮组String[] sets = newLayout.split(GRAVITY_SEPARATOR, 3);.....// 这三组分别是left, center, right,主要按键都在center组中。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.// 渲染三组,left是空格,center是音量,right是比较复杂的menu,ime,rotate等但均不显示inflateButtons(start, mRot0.findViewById(R.id.ends_group), isRot0Landscape, true); // 宽屏inflateButtons(start, mRot90.findViewById(R.id.ends_group), !isRot0Landscape, true);inflateButtons(center, mRot0.findViewById(R.id.center_group), isRot0Landscape, false);inflateButtons(center, mRot90.findViewById(R.id.center_group), !isRot0Landscape, false);addGravitySpacer(mRot0.findViewById(R.id.ends_group));// 插入空格符addGravitySpacer(mRot90.findViewById(R.id.ends_group));inflateButtons(end, mRot0.findViewById(R.id.ends_group), isRot0Landscape, false);inflateButtons(end, mRot90.findViewById(R.id.ends_group), !isRot0Landscape, false);updateButtonDispatchersCurrentView();}

NavigationBarInflaterView加载单个的button

到这里逐个加载配置中的button后,导航栏的显示已差不多完成。
点击事件不需要额外添加监听,每个按钮都是keyButtonview对象,自带了虚拟键值和onTouch事件。

protected View inflateButton(String buttonSpec, ViewGroup parent, boolean landscape,boolean start) {..// createView非常关键,根据条件获取按钮的资源。// 加载了按键的显示布局xml,并且带有systemui:keycode虚拟键值。// 每个按键布局都是一个keyButtonView,其中自带了点击touch事件处理,发送xml中的虚拟键值给系统。View v = createView(buttonSpec, parent, inflater);if (v == null) return null;v = applySize(v, buttonSpec, landscape, start);// 含有[]如,right[.1W]则重新分配尺寸parent.addView(v);  // 将按键填入父控件中进行显示addToDispatchers(v); //加入mButtonDispatchers集合中。.......return v;}

回到NavigationFragment的创建流程

在onViewCreated()流程中
prepareNavigationBarView()方法注册了一些点击事件,最重要的是更新横竖布局和按钮显示,通过sysprop确定了音量加减的显示。
最后notifyNavigationBarScreenOn()方法再次更新了按钮的显示。

private void prepareNavigationBarView() {// 再次刷新横竖屏布局的显示和隐藏,并且更新导航按钮的显示隐藏。mNavigationBarView.reorient();.................// 音量加减键ButtonDispatcher volumeAddButton=mNavigationBarView.getVolumeAddButton();ButtonDispatcher volumeSubButton=mNavigationBarView.getVolumeSubButton();// prop中为trueboolean isShowVolumeButton="true".equals(SystemProperties.get("ro.rk.systembar.voiceicon","true"));if(isShowVolumeButton){volumeAddButton.setVisibility(View.VISIBLE);volumeSubButton.setVisibility(View.VISIBLE);}else{volumeAddButton.setVisibility(View.GONE);volumeSubButton.setVisibility(View.GONE);}if (getContext().getResources().getConfiguration().smallestScreenWidthDp < 400) {volumeAddButton.setVisibility(View.GONE);volumeSubButton.setVisibility(View.GONE);}}
// 再次更新NavButtonIcons;
private void notifyNavigationBarScreenOn() {mNavigationBarView.updateNavButtonIcons();}

多次调用NavigationBarView的刷新

把构造方法中实例化的keybuttondrawable图像逐个设置给buttondispatcer,按钮有了具体的矢量图象。
最终结果就是显示back , home, recents三个键,外加上述提到的音量加减,其他全部隐藏

public void updateNavButtonIcons() {......getHomeButton().setImageDrawable(homeIcon);getBackButton().setImageDrawable(backIcon);getVolumeAddButton().setImageDrawable(mVolumeAddIcon);getVolumeSubButton().setImageDrawable(mVolumeSubIcon);getScreenshotButton().setImageDrawable(mScreenshotIcon);......getBackButton().setVisibility(disableBack      ? View.INVISIBLE : View.VISIBLE);getHomeButton().setVisibility(disableHome      ? View.INVISIBLE : View.VISIBLE);getRecentsButton().setVisibility(disableRecent ? View.INVISIBLE : View.VISIBLE);}

初始化流程的错误追踪。

在底部导航栏的初始化中,刚开始加载的配置是back;home ;contextual导致一直在追查音量加减在哪里加入的,实际上整个初始化流程多次刷新,最后一次加载的配置是left;volume_sub,back,home,recent,volume_add,screenshot;right[.1W]。

空格按键的问题定位。

不断的尝试中发现,安卓9盒子HDMI输出给18.5寸一体机时,调用的默认布局配置是layout-sw600dp,源码中特意增加的layout布局,其中仅有一个文件navigation_layout_rot90.xml,即横屏的NavigationInflaterView布局。
且这个布局内容很奇怪。ends_group组和center_group组都是linearlayout和默认layout中的资源文件不一样,但是改动或者同步都会导致导航栏只剩一个home键。

<FrameLayoutxmlns:android="http://schemas.android.com/apk/res/android"xmlns:systemui="http://schemas.android.com/apk/res-auto"android:layout_width="match_parent"android:layout_height="match_parent"><FrameLayoutandroid:id="@+id/nav_buttons"android:layout_width="match_parent"android:layout_height="match_parent"><LinearLayoutandroid:id="@+id/ends_group"android:layout_width="match_parent"android:layout_height="match_parent"android:orientation="horizontal"android:clipChildren="false" /><LinearLayoutandroid:id="@+id/center_group"android:layout_width="match_parent"android:layout_height="match_parent"android:gravity="center"android:orientation="horizontal"android:clipChildren="false" /></FrameLayout>
</FrameLayout>

临时解决办法

三组按钮,left(空格), center(关键功能键), right(复杂功能全都隐藏)。right组中的所有按钮都是隐藏的,而且基本没见过,直接取消right组按钮的加载后,空格按钮就消除了,在NavigationBarInflaterView中

protected void inflateLayout(String newLayout) {....// 取消掉right这组的按钮加载// inflateButtons(end, mRot0.findViewById(R.id.ends_group), isRot0Landscape, false);// inflateButtons(end, mRot90.findViewById(R.id.ends_group), !isRot0Landscape, false);updateButtonDispatchersCurrentView();}

相关文章:

Android9底部导航栏出现空白按钮问题分析

Android9底部导航栏出现空白按钮问题分析 底部导航栏的初始化 进入NavigationBarView初始化: 进入NavigationBarView的onFinishInflater进入NavigationBarInflaterView NavigationBarInflaterView加载单个的button回到NavigationFragment的创建流程 多次调用NavigationBarView的…...

秦时明月沧海手游阵容推荐,秦时明月沧海角色强度

秦时明月沧海角色强度如何&#xff1f;在秦时明月沧海手游中&#xff0c;您可以从大量的角色卡牌中选择并发展&#xff0c;为了顺利通过各种副本&#xff0c;玩家们需要精心搭配阵容。那么&#xff0c;具体该如何配置最强的角色呢&#xff1f; 下面&#xff0c;小编将带各位玩家…...

基于微信小程序的大学生科技竞赛竞技报名系统设计与实现(源码+lw+部署文档+讲解等)

文章目录 前言系统主要功能&#xff1a;具体实现截图论文参考详细视频演示为什么选择我自己的网站自己的小程序&#xff08;小蔡coding&#xff09;有保障的售后福利 代码参考源码获取 前言 &#x1f497;博主介绍&#xff1a;✌全网粉丝10W,CSDN特邀作者、博客专家、CSDN新星计…...

crypto:摩丝

题目 根据题目所给的压缩包下载后解压&#xff0c;打开文本提示 摩斯密码&#xff0c;对照表可解码得到flag...

Docker最基本使用

1 安装&#xff1a; sudo apt-get -y install docker.io测试&#xff1a; sudo docker run hello-world成功&#xff1a; Hello from Docker! This message shows that your installation appears to be working correctly.2 查看 查看已有镜像&#xff1a; sudo docker i…...

vue2.x 迭代更新项目去掉缓存处理

找到build文件下的webpack.prod.conf.js文件 定义一个常量version const Version new Date().getTime(); 然后在.js和.css前面加上.${Version}就可以了&#xff08;注意得把原本的换成&#xff09;...

Linux高性能服务器编程 学习笔记 第八章 高性能服务器程序框架

TCP/IP协议在设计和实现上没有客户端和服务器的概念&#xff0c;在通信过程中所有机器都是对等的。但由于资源&#xff08;视频、新闻、软件等&#xff09;被数据提供者所垄断&#xff0c;所以几乎所有网络应用程序都采用了下图所示的C/S&#xff08;客户端/服务器&#xff09;…...

技术对比:Flutter vs. 传统桌面应用开发框架

在移动应用开发领域&#xff0c;Flutter已经赢得了广泛的认可和采用&#xff0c;成为了跨平台移动应用开发的瑞士军刀。然而&#xff0c;Flutter的魅力并不仅限于移动平台&#xff0c;它还可以用于开发桌面应用程序&#xff0c;为开发人员提供了一种全新的选择。本文将深入探讨…...

[C++ 网络协议] 异步通知I/O模型

1.什么是异步通知I/O模型 如图是同步I/O函数的调用时间流&#xff1a; 如图是异步I/O函数的调用时间流&#xff1a; 可以看出&#xff0c;同异步的差别主要是在时间流上的不一致。select属于同步I/O模型。epoll不确定是不是属于异步I/O模型&#xff0c;这个在概念上有些混乱&a…...

Postgresql事务测试

参考一个事务中 可以查询自己未提交的数据吗_最详细MySQL事务隔离级别及原理讲解&#xff01;&#xff08;二&#xff09;-CSDN博客 一个事务中 可以查询自己未提交的数据吗_趣说数据库事务隔离级别与原理_weixin_39747293的博客-CSDN博客 【MySql&#xff1a;当前读与快照读…...

【数据结构--排序】冒泡排序,选择排序,插入排序

&#x1f490; &#x1f338; &#x1f337; &#x1f340; &#x1f339; &#x1f33b; &#x1f33a; &#x1f341; &#x1f343; &#x1f342; &#x1f33f; &#x1f344;&#x1f35d; &#x1f35b; &#x1f364; &#x1f4c3;个人主页 &#xff1a;阿然成长日记 …...

vue pc端/手机移动端 — 下载导出当前表格页面pdf格式

一、需求&#xff1a;在手机端/pc端实现一个表格页面&#xff08;缴费单/体检报告单等&#xff09;的导出功能&#xff0c;便于用户在本地浏览打印。 二、实现&#xff1a;之前在pc端做过预览打印的功能&#xff0c;使用的是print.js之类的方法让当前页面直接唤起打印机的打印预…...

125. 验证回文串 【简单题】

题目 如果在将所有大写字符转换为小写字符、并移除所有非字母数字字符之后&#xff0c;短语正着读和反着读都一样。则可以认为该短语是一个 回文串 。 字母和数字都属于字母数字字符。 给你一个字符串 s&#xff0c;如果它是 回文串 &#xff0c;返回 true &#xff1b;否则…...

描述性统计分析

前言&#xff1a; 本专栏参考教材为《SPSS22.0从入门到精通》&#xff0c;由于软件版本原因&#xff0c;部分内容有所改变&#xff0c;为适应软件版本的变化&#xff0c;特此创作此专栏便于大家学习。本专栏使用软件为&#xff1a;SPSS25.0 本专栏所有的数据文件可在个人主页—…...

Visual Studio2019 C++ 编程问题集锦

“const char*” 类型的值不能用于初始化“char*"类型的实体 解决方案一&#xff1a; 点击项目->属性->C/C>语言->符合模式&#xff0c;将原来的“是”改为“否”即可。解决方案二&#xff1a; 在声明变量 char* 时改成 const char *即可...

链表的回文判断

思路: 找中间节点–>逆置->比较 代码&#xff1a; /*** Definition for singly-linked list.* struct ListNode {* int val;* struct ListNode *next;* };*/struct ListNode* middleNode(struct ListNode* head) { struct ListNode*slowhead; struct ListNode*f…...

281_JSON_两段例子的比较,哪一段更简洁、易懂、没有那么多嵌套

《第一份:》//组装Notificationif (bSendAINotification){BOOST_AUTO(iter_flashnotification, documentAll.FindMember("Notification"));if (iter_flashnotification != documentAll....

想要精通算法和SQL的成长之路 - 最长递增子序列 II(线段树的运用)

想要精通算法和SQL的成长之路 - 最长递增子序列 II&#xff08;线段树的运用&#xff09; 前言一. 最长递增子序列 II1.1 向下递推1.2 向上递推1.3 更新操作1.4 查询操作1.5 完整代码&#xff1a; 前言 想要精通算法和SQL的成长之路 - 系列导航 一. 最长递增子序列 II 原题链接…...

java用easyexcel按模版导出

首先在项目的resources下面建一个template包&#xff0c;之后在下面创建一个模版&#xff0c;模版格式如下&#xff1a; 名称为 financeReportBillStandardTemplateExcel.xlsx&#xff1a; {.fee}类型的属性值&#xff0c;是下面实体类的属性&#xff0c;要注意这里面的格式&a…...

Servlet执行流程生命周期方法介绍体系结构、Request和Response的功能详解

&#x1f40c;个人主页&#xff1a; &#x1f40c; 叶落闲庭 &#x1f4a8;我的专栏&#xff1a;&#x1f4a8; c语言 数据结构 javaEE 操作系统 Redis 石可破也&#xff0c;而不可夺坚&#xff1b;丹可磨也&#xff0c;而不可夺赤。 Servlet 一、 Servlet执行流程二、Servlet生…...

FORK客户端与GitHub高效协作指南

1. 为什么选择FORK客户端与GitHub协作 作为一个常年混迹在代码仓库的老司机&#xff0c;我试过几乎所有主流的Git图形化工具。FORK客户端给我的第一印象就是——清爽。没有复杂的界面&#xff0c;没有多余的功能&#xff0c;就像它的名字一样&#xff0c;专注做好代码分支管理…...

cv_unet_image-colorization音乐史料处理:黑白乐谱AI上色与音符语义关联增强

cv_unet_image-colorization音乐史料处理&#xff1a;黑白乐谱AI上色与音符语义关联增强 1. 引言&#xff1a;当黑白乐谱遇见AI色彩 想象一下&#xff0c;你是一位音乐史研究者&#xff0c;面前摊开一本泛黄的、只有黑白线条的19世纪乐谱手稿。那些音符、标记、作曲家的笔迹&…...

3大突破策略:Bypass Paywalls Clean 2024全场景应用指南

3大突破策略&#xff1a;Bypass Paywalls Clean 2024全场景应用指南 【免费下载链接】bypass-paywalls-chrome-clean 项目地址: https://gitcode.com/GitHub_Trending/by/bypass-paywalls-chrome-clean 在信息爆炸的数字时代&#xff0c;付费墙已成为知识获取的主要障碍…...

【智能电网会议】第三届智能电网与人工智能国际学术会议(SGAI 2026)

第三届智能电网与人工智能国际学术会议&#xff08;SGAI 2026) 2026 3rd International Conference on Smart Grid and Artificial Intelligence 往届会后3-4个月检索 华东交通大学主办 IEEE出版&#xff0c;见刊检索有保障 会议官网&#xff1a; 第七届人工智能、网络与信息…...

石家庄整家定制哪个好

在石家庄&#xff0c;寻找合适的整家定制服务&#xff0c;是许多家庭打造理想居住空间的重要一步。今天&#xff0c;我们想为您介绍一个专注于中高端整家定制的品牌——MJ.HOME美境美家木作。关于美境美家木作美境美家木作是一个集整案设计施工与定制家居于一体的品牌。他们致力…...

LFM2.5-1.2B-Thinking效果实测:Ollama中对比Qwen2-1.5B/Llama3-1B生成质量

LFM2.5-1.2B-Thinking效果实测&#xff1a;Ollama中对比Qwen2-1.5B/Llama3-1B生成质量 1. 测试背景与模型介绍 最近在Ollama平台上测试了一款很有意思的小模型——LFM2.5-1.2B-Thinking。这个模型虽然只有12亿参数&#xff0c;但号称能在设备端实现接近大模型的性能。为了验证…...

不用pip也能装!3种方法在Pycharm中配置wxPython(含离线安装技巧)

突破网络限制&#xff1a;PyCharm中wxPython的3种高阶安装方案 在企业开发环境中&#xff0c;网络访问限制常常成为Python包管理的"拦路虎"。特别是像wxPython这样包含二进制扩展的GUI库&#xff0c;传统pip安装方式在离线环境下几乎束手无策。本文将揭秘三种无需依赖…...

从硬编码到动态定义:Qlib表达式引擎如何重构量化因子开发范式

从硬编码到动态定义&#xff1a;Qlib表达式引擎如何重构量化因子开发范式 【免费下载链接】qlib Qlib 是一个面向人工智能的量化投资平台&#xff0c;其目标是通过在量化投资中运用AI技术来发掘潜力、赋能研究并创造价值&#xff0c;从探索投资策略到实现产品化部署。该平台支持…...

Ostrakon-VL-8B实战:基于Transformer架构的视觉问答效果展示

Ostrakon-VL-8B实战&#xff1a;基于Transformer架构的视觉问答效果展示 最近在测试各种多模态模型时&#xff0c;我遇到了一个挺有意思的家伙——Ostrakon-VL-8B。这名字听起来有点拗口&#xff0c;但简单来说&#xff0c;它是一个拥有80亿参数的视觉语言模型&#xff0c;专门…...

如何使用USearch构建自动驾驶传感器数据的实时向量搜索系统

如何使用USearch构建自动驾驶传感器数据的实时向量搜索系统 【免费下载链接】usearch Fastest Open-Source Search & Clustering engine for Vectors & &#x1f51c; Strings in C, C, Python, JavaScript, Rust, Java, Objective-C, Swift, C#, GoLang, and Wolfra…...