天宁寺网站建设外贸网站个性设计
天宁寺网站建设,外贸网站个性设计,网站开发文档要求,网站开发软件三剑客1. 为什么我们需要一个隐藏导航栏的按钮#xff1f;
你可能也遇到过这种情况#xff1a;在Android平板上全屏看视频或者玩游戏的时候#xff0c;底部的导航栏#xff08;就是那个有返回、主页、最近任务三个按钮的区域#xff09;总是杵在那里#xff0c;有点碍眼。特别是…1. 为什么我们需要一个隐藏导航栏的按钮你可能也遇到过这种情况在Android平板上全屏看视频或者玩游戏的时候底部的导航栏就是那个有返回、主页、最近任务三个按钮的区域总是杵在那里有点碍眼。特别是现在很多应用都支持沉浸式体验这个导航栏就显得有点“不合群”了。虽然Android系统本身提供了通过手势从屏幕底部上滑来隐藏导航栏的功能但这个操作需要用户知道这个“隐藏技能”而且有时候手势操作并不那么直观或者在某些特定场景下比如连接了鼠标键盘并不方便。所以最近我接到了一个项目需求客户明确要求在Android 13系统的导航栏里直接添加一个看得见、摸得着的“隐藏”按钮。用户点一下整个导航栏就优雅地消失把屏幕空间完全还给内容需要的时候再通过一个简单的系统手势比如从屏幕底部上滑把它召唤回来。这个需求听起来很直接但真要在系统层面去实现尤其是要保证稳定性和兼容性里面还是有不少门道的。今天我就把自己在Android 13源码上折腾这个功能的完整过程、踩过的坑以及优化心得毫无保留地分享给你。这个功能特别适合那些做定制化ROM的开发者、系统集成工程师或者是对Android Framework层开发感兴趣、想深入理解SystemUI如何工作的朋友。即使你之前没有太多修改AOSP源码的经验跟着我的步骤走也能一步步实现它。我们会从最基础的布局修改开始讲到核心的交互逻辑最后再深入探讨横竖屏适配、动画优化这些让体验更上一层楼的细节。准备好了吗我们开始吧。2. 第一步在导航栏布局里“挖个坑”万事开头难但第一步往往是最简单的。我们要做的第一件事就是在导航栏的布局文件里给我们的隐藏按钮预留一个位置。你可以把导航栏想象成一块画布我们现在要在这块画布上新增一个图标。这个布局文件位于AOSP源码的frameworks/base/packages/SystemUI/res/layout/navigation_bar.xml。打开它你会看到里面定义了导航栏的整体结构包括返回键、主页键等按钮的容器。我们需要做的就是在合适的位置插入一个ImageView控件。我当时的修改是这样的以diff格式展示这样更清晰diff --git a/packages/SystemUI/res/layout/navigation_bar.xml b/packages/SystemUI/res/layout/navigation_bar.xml --- a/packages/SystemUI/res/layout/navigation_bar.xml b/packages/SystemUI/res/layout/navigation_bar.xml -33,4 33,12 android:clipChildrenfalse android:clipToPaddingfalse / ImageView android:layout_width30dp android:layout_height30dp android:idid/navigation_hide_iv android:srcdrawable/ic_sysbar_hide android:layout_gravitystart|center_vertical android:layout_marginStartdimen/navigation_hide_button_start_margin / /com.android.systemui.navigationbar.NavigationBarView我来解释一下这几行代码的含义。我们添加了一个ImageView宽高都设为30dp这是一个比较适合与旁边其他系统按钮如返回键保持视觉一致的尺寸。android:id是我们给这个按钮起的“名字”后面在Java代码里就要靠这个id来找到它。android:src指定了按钮的图标这里指向一个名为ic_sysbar_hide的drawable资源这个图标我们稍后自己创建。最关键的是android:layout_gravity属性。我把它设置为start|center_vertical。start在从左到右阅读的语言环境下就是“左”这意味着按钮会靠在导航栏的左侧。center_vertical让它垂直居中。这样按钮就会出现在导航栏的最左边并且垂直居中看起来就像原生的一部分。最后android:layout_marginStart给了它一个左边距这个边距值我们定义在dimens资源文件中这样便于为横竖屏设置不同的值后面会讲到。这里有个小坑需要注意导航栏的布局结构可能因设备类型手机、平板、导航模式三键导航、手势导航而略有不同。上面的修改是基于最常见的三键导航布局。如果你的系统默认是手势导航或者布局结构有调整你需要先仔细查看一下navigation_bar.xml的完整内容找到NavigationBarView这个根布局确保把ImageView添加在正确的位置通常是放在其他按钮的容器比如某个LinearLayout或FrameLayout里并设置好对应的布局参数避免按钮位置错乱或者根本显示不出来。3. 第二步给按钮一个好看的“脸”按钮的位置定好了我们得给它配个图标。系统导航栏的图标通常使用矢量图Vector Drawable因为矢量图可以无损缩放在不同屏幕密度下都能保持清晰。我们需要在frameworks/base/packages/SystemUI/res/drawable/目录下创建一个新的XML文件比如就叫ic_sysbar_hide.xml。这个图标的设计很有讲究。它需要直观地表达“隐藏”或“向下收起”的含义同时风格要与系统原有的返回、主页等图标保持一致通常是简洁的线条、特定的颜色和粗细。我设计了一个向下的小箭头代码如下?xml version1.0 encodingutf-8? vector xmlns:androidhttp://schemas.android.com/apk/res/android android:width18dp android:height18dp android:viewportWidth35 android:viewportHeight35 path android:fillColor#66cccccc android:pathDataM21.314,25.837l9.898-9.9c0.782-0.782,2.049-0.782,2.829,0c0.78,0.781,0.781,2.049,0,2.831L22.73,30.079 c-0.001,0.002-0.001,0.002-0.001,0.002c-0.39,0.391-0.902,0.586-1.415,0.586s-1.024-0.195-1.415-0.586L8.585,18.768 c-0.782-0.781-0.779-2.049,0-2.831c0.781-0.782,2.049-0.782,2.831,0L21.314,25.837z/ /vector我来拆解一下这个矢量图。android:width/height定义了图标的固有尺寸viewportWidth/Height定义了一个35x35的虚拟画布所有的路径数据都在这个画布坐标系下绘制。pathData里的那一串字母和数字就是描述箭头形状的SVG路径命令它画出了一个指向下方的箭头。android:fillColor设置了填充颜色为#66cccccc这是一个带透明度的浅灰色#CCCCCC是颜色前面的66表示大约40%的透明度这个颜色和系统导航栏按钮在默认状态下的颜色风格很接近。这里有个重要的经验图标颜色最好不要写死。在实际项目中导航栏按钮的颜色可能会随着主题深色/浅色模式、当前应用的颜色Android 12的材料你动态取色而变化。更专业的做法是像系统其他按钮一样使用android:tint属性并指向一个颜色资源如?attr/darkIconTheme或?attr/lightIconTheme让系统主题引擎去管理颜色。但为了初次实现的简单明了我们先使用固定色值。等你熟悉了整个流程后可以再尝试把它改成主题化的这样你的隐藏按钮就能完美融入系统的动态主题了。4. 第三步让按钮在不同屏幕方向上都好看我们的按钮在竖屏下看起来不错左边距也合适。但是当设备旋转到横屏时导航栏的布局通常会发生变化比如从底部移到侧边按钮的边距可能就需要调整否则可能会贴边太近或者和其他元素重叠。这就是为什么我们在布局里使用了dimen/navigation_hide_button_start_margin这个引用而不是直接写死24dp。我们需要在 dimens 资源文件中定义这个边距值并且为横屏land提供一个特定的值。打开frameworks/base/packages/SystemUI/res/values/dimens.xml在文件末尾添加dimen namenavigation_hide_button_start_margin24dp/dimen这个24dp是针对竖屏portrait的默认左边距。然后我们还需要为横屏创建或修改对应的文件。通常横屏的dimens文件在frameworks/base/packages/SystemUI/res/values-land/dimens.xml。如果这个文件不存在你就创建一个如果存在就在里面添加dimen namenavigation_hide_button_start_margin40dp/dimen我把横屏下的左边距设为了40dp。为什么横屏要更大一些因为在横屏模式下导航栏可能变得更窄特别是放在侧边时或者为了在横屏握持时避免误触需要更大的安全边距。这个值没有绝对标准你需要根据自己的UI设计稿或者实际视觉效果来调整。你可以编译后分别在横竖屏下观察按钮的位置反复调整这个数值直到看起来和谐统一为止。这一步是很多新手容易忽略的但却是保证功能在不同场景下都“可用”且“好看”的关键。Android的资源系统会自动根据屏幕方向、尺寸、密度等选择最合适的值我们只需要把不同场景下的值定义好就行了。5. 第四步注入灵魂——让按钮“活”起来布局和图标都准备好了现在这个按钮还只是个“花瓶”点它没任何反应。接下来就是最核心的部分编写Java代码为这个按钮添加点击事件实现隐藏导航栏的逻辑。我们需要修改导航栏的控制类。主要改动在frameworks/base/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java这个文件里。这个类负责管理多个显示屏上的导航栏实例。我们的思路是在导航栏创建完成后找到我们添加的那个ImageView给它设置一个点击监听器。当用户点击时就调用方法移除当前显示屏的导航栏。5.1 第一步找到按钮并绑定点击事件首先我们需要一个方法来初始化这个按钮。我在NavigationBarController类里添加了一个initHideButton()方法// 在类中添加一个成员变量来记录当前displayId private int displayId; // ... 在创建导航栏的方法中记得把 displayId 赋值 ... // 例如在 addNavigationBar 方法里displayId display.getDisplayId(); public void initHideButton() { NavigationBarView navigationBarView getNavigationBarView(displayId); if (navigationBarView ! null) { View navigationHideIv navigationBarView.findViewById(R.id.navigation_hide_iv); if (navigationHideIv ! null) { navigationHideIv.setOnClickListener(v - removeNavigationBar(displayId)); } } }这段代码做了几件事通过getNavigationBarView(displayId)获取到指定显示屏的导航栏视图对象。使用findViewById通过我们之前定义的R.id.navigation_hide_iv找到那个ImageView。给它设置一个OnClickListener点击时触发removeNavigationBar(displayId)方法。removeNavigationBar方法也是我们需要添加的它的作用就是从系统中移除指定显示屏的导航栏视图使其消失。void removeNavigationBar(int displayId) { NavigationBar navBar mNavigationBars.get(displayId); if (navBar ! null) { // 这里调用导航栏自身的销毁或隐藏方法 // 具体实现可能因Android版本略有不同核心是让导航栏View不可见并从窗口管理器移除 navBar.destroy(); mNavigationBars.remove(displayId); } }5.2 第二步在合适的时机调用初始化按钮的初始化需要在导航栏视图完全创建并添加到窗口之后进行。我们不能在addNavigationBar方法里直接调用initHideButton()因为那时视图可能还未完全初始化好。一个可靠的方式是通过广播。我在CentralSurfacesImpl.java这是SystemUI状态栏的核心实现类里定义了两个广播Actionpublic static final String ACTION_HIDE_NAVIGATIONBAR action_hide_navigationbar; public static final String ACTION_SHOW_NAVIGATIONBAR action_show_navigationbar;然后在NavigationBarController的addNavigationBar方法末尾发送一个ACTION_HIDE_NAVIGATIONBAR广播context.sendBroadcast(new Intent(CentralSurfacesImpl.ACTION_HIDE_NAVIGATIONBAR));同时在CentralSurfacesImpl的广播接收器mBroadcastReceiver里添加对这个Action的处理} else if (ACTION_HIDE_NAVIGATIONBAR.equals(action)){ mNavigationBarController.initHideButton(); }这样每当一个新的导航栏被创建出来就会发送广播触发initHideButton()来绑定我们的隐藏按钮。这是一种比较解耦的方式避免了在复杂的创建流程中硬编码初始化逻辑。5.3 第三步实现导航栏的重新显示隐藏之后我们还得能让导航栏回来。原生的手势操作从屏幕底部上滑本身就能唤出导航栏但那是系统手势识别模块SystemGesturesPointerEventListener的工作。为了让我们的实现更完整并且确保手势触发后能正确响应我们需要让手势模块在检测到上滑手势时发送一个显示导航栏的广播。修改frameworks/base/services/core/java/com/android/server/wm/SystemGesturesPointerEventListener.java// 在 detectSwipe 方法处理滑动结果的地方 if (swipe SWIPE_FROM_TOP) { // ... 原有代码 ... mContext.sendBroadcast(new Intent(action_show_navigationbar)); } else if (swipe SWIPE_FROM_BOTTOM) { // ... 原有代码 ... mContext.sendBroadcast(new Intent(action_show_navigationbar)); }然后在CentralSurfacesImpl的广播接收器里处理这个Action} else if (ACTION_SHOW_NAVIGATIONBAR.equals(action)){ if (mNavigationBarController.canShowNavigationBar()){ createNavigationBar(mBarRresult); } }这里我添加了一个canShowNavigationBar()方法到NavigationBarController用于判断当前是否没有导航栏即处于隐藏状态只有隐藏状态下才执行重新创建的逻辑防止重复创建。public boolean canShowNavigationBar() { NavigationBar navBar mNavigationBars.get(displayId); return navBar null; }至此一个基本的“点击隐藏、手势唤出”的循环就完成了。编译你的系统镜像刷入设备或模拟器你应该就能在导航栏左侧看到一个新增的按钮点击它导航栏会立刻消失。再从屏幕底部上滑导航栏又会重新出现。6. 第五步从“能用”到“好用”——优化实战基础功能跑通了但作为有追求的开发我们肯定不满足于此。直接让导航栏“闪现”或“闪退”的体验太生硬了而且横竖屏、不同设备尺寸下的细节也需要打磨。下面分享几个我实战中做的优化点。6.1 给隐藏和显示加上动画没有动画的界面交互就像没有加盐的菜总差点意思。我们可以为导航栏的隐藏和显示添加一个平滑的过渡动画。Android的View类提供了丰富的动画API我们可以使用属性动画来实现。修改removeNavigationBar方法不要直接销毁先让它执行一个向下滑出屏幕的动画动画结束后再执行清理。同时在createNavigationBar或显示导航栏时让它从屏幕下方滑入。这里以隐藏动画为例在NavigationBarView中或者在你的NavigationBarController中操作View添加private void animateHideNavigationBar(final NavigationBarView view) { if (view null || view.getHeight() 0) return; ValueAnimator animator ValueAnimator.ofFloat(0f, 1f); animator.setDuration(200); // 动画时长200毫秒 animator.setInterpolator(new AccelerateInterpolator()); // 使用加速插值器感觉更“快” final float startY view.getTranslationY(); final float endY view.getHeight(); // 向下移动一个导航栏的高度 animator.addUpdateListener(animation - { float fraction animation.getAnimatedFraction(); view.setTranslationY(startY (endY * fraction)); view.setAlpha(1f - fraction * 0.5f); // 同时稍微淡出一点 }); animator.addListener(new AnimatorListenerAdapter() { Override public void onAnimationEnd(Animator animation) { super.onAnimationEnd(animation); // 动画结束后真正执行移除操作 view.setVisibility(View.GONE); // 调用原有的remove逻辑... } }); animator.start(); }显示动画则是相反的过程从translationY view.getHeight()开始动画到translationY 0同时alpha从0.5f恢复到1f。注意操作导航栏的显示隐藏是系统级行为直接设置View.GONE可能还不够可能需要同步更新窗口管理器的状态WindowManager的LayoutParams确保输入事件等能正确传递。这部分的系统调用可能更复杂需要参考原生导航栏隐藏例如进入沉浸模式的实现方式确保动画和系统状态同步。6.2 处理横屏与手势导航的兼容性我们之前已经通过不同的dimens值处理了横屏布局。但还有更复杂的情况手势导航模式。在全面屏手势模式下导航栏通常只有一个很细的横条或称“小白条”我们的隐藏按钮放在哪里直接放上去可能会破坏手势区域或者显得非常突兀。一个更合理的方案是在手势导航模式下不显示这个隐藏按钮。因为手势导航本身就是为了最大化屏幕空间而且用户可以通过上滑进入全屏沉浸模式。我们可以通过检查当前的导航模式来决定是否显示我们的按钮。在NavigationBarController或NavigationBarView中可以获取系统设置int navigationMode Settings.Global.getInt(context.getContentResolver(), Settings.Global.NAVIGATION_MODE, Settings.Global.NAVIGATION_MODE_GESTURAL);如果navigationMode是NAVIGATION_MODE_GESTURAL手势导航那么我们就在initHideButton中不进行绑定或者将按钮的可见性设为View.GONE。6.3 状态持久化与系统设置集成目前我们的实现是“一次性”的点击隐藏手势唤出。但在真实产品中用户可能希望有一个“锁定隐藏”的选项比如在看电影时即使不小心从底部上滑导航栏也不会出现直到用户再次点击某个物理按键或特定区域才恢复。这需要将“导航栏隐藏状态”持久化。我们可以利用Settings.Secure或Settings.Global存储一个标志位。当用户点击隐藏按钮时不仅执行动画移除还将一个标志如HIDE_NAVIGATION_BAR_LOCKED设为1。在SystemGesturesPointerEventListener中检测到上滑手势时先检查这个标志位如果是锁定状态就不发送显示广播。同时我们需要提供另一种解锁方式例如监听电源键双击、音量键组合或者在状态栏下拉菜单中添加一个快捷开关。将这个功能与系统设置集成会让它看起来更“原生”。你可以在“设置”-“系统”-“手势”或“显示”里添加一个“锁定导航栏”的选项开关背后控制的正是我们上面说的那个持久化标志位。6.4 性能与内存考量在系统UI中添加常驻组件一定要考虑性能。我们的ImageView本身资源消耗很小。但要注意广播的使用sendBroadcast是系统级的相对较重。在我们的场景下导航栏创建和手势触发频率并不高所以可以接受。但如果要做更精细的控制比如动画每一帧都通信就应该考虑使用本地广播LocalBroadcastManager或者更轻量的回调接口如观察者模式在SystemUI组件内部传递消息。另外确保在导航栏被销毁destroy时解除对按钮的引用和监听器避免内存泄漏。7. 调试技巧与常见问题排查修改系统源码并编译刷机是个相对漫长的过程高效的调试能节省大量时间。1. 善用Log在关键位置添加Log.d(TAG, message)。例如在initHideButton方法里可以打印是否找到了View在广播接收器里打印收到的Action。查看Logcat输出时可以使用adb logcat | grep -E “(你的TAG|SystemUI)”来过滤信息。记得在正式发布版本中移除或关闭这些调试Log。2. 使用模拟器快速验证对于布局修改、简单逻辑可以先在Android Studio的布局编辑器中预览或者使用支持高版本Android的模拟器。编译一个eng或userdebug版本的镜像刷到模拟器里可以开启adb root权限方便调试。3. 常见问题按钮不显示首先检查布局文件修改是否正确有没有编译进镜像。检查图标的drawable资源是否存在且格式正确。检查按钮的id是否在对应的R.java中生成。点击没反应检查findViewById是否成功Log确认。检查点击事件监听器是否被正确设置。检查removeNavigationBar方法是否被调用以及它内部的逻辑是否能真正移除导航栏窗口。横竖屏布局错乱检查values-land/dimens.xml文件是否正确添加并编译。确认横屏下导航栏的根布局是否还是同一个navigation_bar.xml通常是的系统会根据方向自动选择布局但有时会有专门的土地布局文件需要确认。动画卡顿或位置不对确保动画是在UI线程执行的。检查translationY的起始和结束值计算是否正确特别是view.getHeight()是否在视图完成布局onLayout之后才获取到有效值。修改Android系统UI是一个需要耐心和细心的工作它要求你对Android的框架层特别是View系统、窗口管理和SystemUI组件有较好的理解。通过实现这个自定义隐藏按钮的功能你不仅能满足一个具体的产品需求更能深入理解导航栏的工作机制为以后处理更复杂的系统定制需求打下坚实的基础。希望这篇详细的指南能帮到你。如果在实践过程中遇到其他问题欢迎随时交流讨论。