当前位置: 首页 > 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生…...

SkyWalking 10.2.0 SWCK 配置过程

SkyWalking 10.2.0 & SWCK 配置过程 skywalking oap-server & ui 使用Docker安装在K8S集群以外&#xff0c;K8S集群中的微服务使用initContainer按命名空间将skywalking-java-agent注入到业务容器中。 SWCK有整套的解决方案&#xff0c;全安装在K8S群集中。 具体可参…...

Spark 之 入门讲解详细版(1)

1、简介 1.1 Spark简介 Spark是加州大学伯克利分校AMP实验室&#xff08;Algorithms, Machines, and People Lab&#xff09;开发通用内存并行计算框架。Spark在2013年6月进入Apache成为孵化项目&#xff0c;8个月后成为Apache顶级项目&#xff0c;速度之快足见过人之处&…...

安宝特方案丨XRSOP人员作业标准化管理平台:AR智慧点检验收套件

在选煤厂、化工厂、钢铁厂等过程生产型企业&#xff0c;其生产设备的运行效率和非计划停机对工业制造效益有较大影响。 随着企业自动化和智能化建设的推进&#xff0c;需提前预防假检、错检、漏检&#xff0c;推动智慧生产运维系统数据的流动和现场赋能应用。同时&#xff0c;…...

《Playwright:微软的自动化测试工具详解》

Playwright 简介:声明内容来自网络&#xff0c;将内容拼接整理出来的文档 Playwright 是微软开发的自动化测试工具&#xff0c;支持 Chrome、Firefox、Safari 等主流浏览器&#xff0c;提供多语言 API&#xff08;Python、JavaScript、Java、.NET&#xff09;。它的特点包括&a…...

华为OD机试-食堂供餐-二分法

import java.util.Arrays; import java.util.Scanner;public class DemoTest3 {public static void main(String[] args) {Scanner in new Scanner(System.in);// 注意 hasNext 和 hasNextLine 的区别while (in.hasNextLine()) { // 注意 while 处理多个 caseint a in.nextIn…...

JUC笔记(上)-复习 涉及死锁 volatile synchronized CAS 原子操作

一、上下文切换 即使单核CPU也可以进行多线程执行代码&#xff0c;CPU会给每个线程分配CPU时间片来实现这个机制。时间片非常短&#xff0c;所以CPU会不断地切换线程执行&#xff0c;从而让我们感觉多个线程是同时执行的。时间片一般是十几毫秒(ms)。通过时间片分配算法执行。…...

20个超级好用的 CSS 动画库

分享 20 个最佳 CSS 动画库。 它们中的大多数将生成纯 CSS 代码&#xff0c;而不需要任何外部库。 1.Animate.css 一个开箱即用型的跨浏览器动画库&#xff0c;可供你在项目中使用。 2.Magic Animations CSS3 一组简单的动画&#xff0c;可以包含在你的网页或应用项目中。 3.An…...

GO协程(Goroutine)问题总结

在使用Go语言来编写代码时&#xff0c;遇到的一些问题总结一下 [参考文档]&#xff1a;https://www.topgoer.com/%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B/goroutine.html 1. main()函数默认的Goroutine 场景再现&#xff1a; 今天在看到这个教程的时候&#xff0c;在自己的电…...

基于Java+VUE+MariaDB实现(Web)仿小米商城

仿小米商城 环境安装 nodejs maven JDK11 运行 mvn clean install -DskipTestscd adminmvn spring-boot:runcd ../webmvn spring-boot:runcd ../xiaomi-store-admin-vuenpm installnpm run servecd ../xiaomi-store-vuenpm installnpm run serve 注意&#xff1a;运行前…...

libfmt: 现代C++的格式化工具库介绍与酷炫功能

libfmt: 现代C的格式化工具库介绍与酷炫功能 libfmt 是一个开源的C格式化库&#xff0c;提供了高效、安全的文本格式化功能&#xff0c;是C20中引入的std::format的基础实现。它比传统的printf和iostream更安全、更灵活、性能更好。 基本介绍 主要特点 类型安全&#xff1a…...