名词说明

  • 状态栏:StatusBar,手机上方显示电量、时间的横条
  • 导航栏:NavigationBar,手机下方显示虚拟按键的横条
  • 标题栏:ActionBar,应用顶部显示标题的横条
  • 全面屏:界面内容占屏幕面积超80%以上的屏幕叫做全面屏,想要达到这个屏占比,基本都是没有导航栏的

状态栏和导航栏自定义原理

通过SDK接口可以让状态栏和导航栏浮动并透明,不占布局空间
由于状态栏和导航栏是透明的,我们可以自己定义两个View填充在下面,再给View设置自定义背景,这样就完成了自定义导航栏
但是这样做有另一个难题,由于全面屏是不存在导航栏的,所以我们必须对全面屏进行适配
由于AndroidSDK没有提供判断全面屏的接口,我们需要自己找方案去实现

全面屏判断原理

传统屏幕是有导航栏的,由于导航栏本身也是一个控件,所以Window布局下面,一定有一个和导航栏等高等宽的View
而全面屏没有导航栏,则不会存在这样的一个View,根据这个差异,我们去解析Window布局结构,就能知道手机到底是不是全面屏
但是这样做也有一个难题,就是布局刚创建时,所有View的高度都是0,只有等布局渲染完毕时,才能拿到正确的高度
也就是说,全面屏判断不能在Activity.onCreate时立刻执行,必须等到布局渲染完毕时,才能进行
由于AndroidSDK又没有提供布局渲染完毕的回调,所以我们又需要自己找方案去实现,不得不说,AndroidSDK在细节上确实还不够完美
幸好我们有一个View.post()方法可以利用,这个方法的功能和Handler.post()一致,但是它会等到控件渲染完毕再执行,正好就满足了我们的需求

代码实现和功能封装

通过以上分析,我们基本已经确定,功能是可以实现的,但是代码会比较多且繁琐
为了以后使用方便,我们会对功能进行封装,最好是以后通过单行代码就可以解决适配问题

//CommonActivity.javaimport android.content.pm.ActivityInfo;import android.os.Bundle;import android.view.View;import androidx.appcompat.app.AppCompatActivity;import java.util.LinkedList;import java.util.List;public class CommonActivity extends AppCompatActivity {//由于导航栏适配工作不能立刻执行//所以我们需要先通过Runnable把它封装起来,到了合适时候再执行Runnable controlBarsAdapter;//记录有无导航栏//现在的非全面屏手机也可以隐藏导航栏,我们的代码并不仅适配全面屏,也适合传统手机Boolean hasNavigationBar;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);//总是竖屏显示,由于横屏的宽高是不一样的,横屏代码也需要适当调整//这里不想让代码变复杂,所以干脆禁用横屏,让逻辑清晰点setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);}//通过解析Window布局,判断有无导航栏public void detectNavigationBar() {int navigationBarHeight = Utils.navigationBarHeight(this);int screenWidth = Utils.getScreenSize(this).getWidth();//获取Window下的全部节点,如果存在View和导航栏等高等宽,则说明存在导航栏List<View> nodes = Utils.allWindowNode(this);nodes = Utils.filter(nodes, node -> {if (node.getClass() != NavigationBarPlaceholder.class)if (node.getMeasuredHeight() == navigationBarHeight)if (node.getMeasuredWidth() == screenWidth)return true;return false;});hasNavigationBar = nodes.size() > 0;//适配状态栏和导航栏//因为适配状态栏和导航栏需要先知道手机是否存在导航栏,所以需要到此再执行if (controlBarsAdapter != null) controlBarsAdapter.run();}//自定义状态栏和导航栏//让状态栏和导航栏浮动并透明,不占布局空间//这样就可以自己定义两个View,分别放在statuBar和navigationBar的位置,来模拟自定义的状态栏和导航栏//默认使用R.id.v_top作为statuBar占位View,使用R.id.v_bottom作为navigationBar占位Viewpublic void adaptControlBars() {//将导航栏适配工作存在Runnable中,延迟到布局加载完毕再调用controlBarsAdapter = () -> {//隐藏标题栏,标题栏会影响状态栏浮动//更好的方法是设置无标题栏的主题,隐藏标题栏会看到一瞬间的闪烁,不是很自然//如果设置了无标题栏的主题,一定要注释这一行,因为ActionBar==nullsuper.getSupportActionBar().hide();//让状态栏和导航栏浮动,不占布局空间super.getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION);//设置状态栏和导航栏透明super.getWindow().setStatusBarColor(Utils.TRANSPARENT);super.getWindow().setNavigationBarColor(Utils.TRANSPARENT);//显示状态栏占位ViewView statuPlaceholder = Utils.getRootView(this).findViewById(R.id.v_top);if (statuPlaceholder != null) {int statuBarHeight = Utils.statuBarHeight(this);Utils.size(statuPlaceholder, null, statuBarHeight);}//显示导航栏占位ViewView navigationBarPlaceholder = Utils.getRootView(this).findViewById(R.id.v_bottom);if (navigationBarPlaceholder != null && hasNavigationBar) {int navigationBarHeight = Utils.navigationBarHeight(this);Utils.size(navigationBarPlaceholder, null, navigationBarHeight);}//去除全面屏底部黑边//由于安卓系统限制了最大宽高比,全面屏一般会超出这个范围//超出部分没有内容,就会显示为黑色,在手机底部产生黑边效果if (!hasNavigationBar) {LinkedList<View> list = Utils.allWindowNode(this);//正常屏幕的第一个节点和第三个节点都是全屏高的//全面屏由于存在黑边,第三个节点高度会比第一个节点小//只要将第三个节点调至全屏大小,黑白就会消失Utils.size(list.get(2), null, list.get(0).getMeasuredHeight());}};}}
//NavigationBarPlaceholder.javaimport android.content.Context;import android.graphics.Canvas;import android.util.AttributeSet;import android.view.View;//自定义一个View,用于自动执行View.post代码,检测是否有导航栏public class NavigationBarPlaceholder extends View {CommonActivity ctx;public NavigationBarPlaceholder(Context context, AttributeSet attrSet) {super(context, attrSet);init(context, attrSet);}@Overrideprotected void onDraw(Canvas canvas) {}private void init(Context context, AttributeSet attrSet) {this.ctx = (CommonActivity) context;//View.post会等布局加载完毕再执行,这时可以正确取得各控件的大小//通过各控件的大小,可以判断出手机是否是导航栏post(() -> {ctx.detectNavigationBar();});}}
//MainActivity.javaimport android.os.Bundle;public class MainActivity extends CommonActivity {@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);super.adaptControlBars();}}
//activity_main.xml<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:app="http://schemas.android.com/apk/res-auto"xmlns:tools="http://schemas.android.com/tools"android:layout_width="match_parent"android:layout_height="match_parent"android:background="#FFFFFF"android:orientation="vertical"><Viewandroid:id="@+id/v_top"android:layout_width="match_parent"android:layout_height="0px"android:background="#FF00FF" /><Viewandroid:layout_width="match_parent"android:layout_height="0px"android:layout_weight="1" /><com.easing.test.screen_adapt.NavigationBarPlaceholderandroid:id="@+id/v_bottom"android:layout_width="match_parent"android:layout_height="0px"android:background="#FF00FF" /></LinearLayout>
//Utils.javaimport android.app.Activity;import android.content.Context;import android.content.res.Resources;import android.util.DisplayMetrics;import android.util.Size;import android.view.View;import android.view.ViewGroup;import android.view.WindowManager;import java.lang.reflect.Field;import java.util.ArrayList;import java.util.LinkedList;import java.util.List;//工具类public class Utils {public static final int TRANSPARENT = 0x00000000;//获取手机状态栏高度public static int statuBarHeight(Context context) {try {Class c = Class.forName("com.android.internal.R$dimen");Object obj = c.newInstance();Field field = c.getField("status_bar_height");int x = Integer.parseInt(field.get(obj).toString());return context.getResources().getDimensionPixelSize(x);} catch (Exception e) {return -1;}}//获取手机导航栏高度public static int navigationBarHeight(Context context) {Resources resources = context.getResources();int resourceId = resources.getIdentifier("navigation_bar_height", "dimen", "android");return resources.getDimensionPixelSize(resourceId);}//获取屏幕大小public static Size getScreenSize(Context context) {WindowManager manager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);DisplayMetrics dm = new DisplayMetrics();manager.getDefaultDisplay().getMetrics(dm);return new Size(dm.widthPixels, dm.heightPixels);}//调整控件大小public static void size(View v, Integer w, Integer h) {ViewGroup.LayoutParams params = v.getLayoutParams();if (params == null)params = new ViewGroup.LayoutParams(0, 0);if (w != null)params.width = w.intValue();if (h != null)params.height = h.intValue();v.setLayoutParams(params);}//获取Activity的根节点Viewpublic static View getRootView(Activity activity) {return activity.findViewById(android.R.id.content);}//获取Window下全部子控件public static LinkedList<View> allWindowNode(Activity ctx) {LinkedList<View> list = allViewNode(ctx.getWindow().getDecorView(), true);return list;}//获取View下全部子控件public static LinkedList<View> allViewNode(View view, boolean recursive) {LinkedList<View> children = new LinkedList();if (view instanceof ViewGroup) {ViewGroup viewGroup = (ViewGroup) view;for (int i = 0; i < viewGroup.getChildCount(); i++) {View child = viewGroup.getChildAt(i);children.add(child);if (recursive)children.addAll(allViewNode(child, recursive));}}return children;}//过滤数据public static <T> List<T> filter(List<T> datas, Predication<T> predication) {List<T> filtered = new ArrayList();for (T data : datas)if (predication.predicate(data))filtered.add(data);return filtered;}public interface Predication<T> {boolean predicate(T v);}}

使用方法

  • 让自己的Activity继承CommonActivity
  • 在布局中使用View作为StatusBar的填充View,并将id设置为R.id.v_top
  • 在布局中使用NavigationBarPlaceholder作为NavigationBar的填充View,并将id设置为R.id.v_bottom
  • 在Activity的onCreate中调用super.adaptControlBars()方法,CommonActivity会自动完成剩下的工作

【Android】【手机适配】Android自定义导航栏和全面屏适配方案相关推荐

  1. 【Flutter】Flutter 全面屏适配 ( 需要适配的情况 | Android 设置最大宽高比 | 使用 SafeArea 进行全面屏适配 | 使用 MediaQuery 进行全面屏适配 )

    文章目录 一.Flutter 全面屏适配 二.全面屏适配的情况 三.全面屏适配方法 四.反面示例 ( 留海遮挡内容 ) 五.Android 中配置最大宽高比 六.使用 SafeArea 进行全面屏适配 ...

  2. 使用uniapp编译多端,自定义导航栏高度、状态栏的高度

    使用uniapp框架编译多端,在使用自定义导航栏的时候需要适配不同手机,在安卓和ios上手机的状态栏高度是不一样的,尤其是小程序. uni.getSystemInfo(); //在小程序上使用这个方法 ...

  3. 华为 android 菜单键,华为手机怎么设置导航栏?华为手机自定义导航栏教程

    华为手机在屏幕下方没有一般安卓机都有的三个实体按键,整个屏幕浑然一体,非常美观.华为手机这几个常用的功能键是在显示屏下方出现的,既可以出现也可以隐藏,非常方便快捷.那么如何设置屏幕下方的导航栏呢? 华 ...

  4. taro 请务必在小程序页面中完善页面基础信息_小程序自定义导航栏(完美适配所有手机)...

    背景 在做小程序时,关于默认导航栏,我们遇到了以下的问题: Android.IOS手机对于页面title的展示不一致,安卓title的显示不居中 页面的title只支持纯文本级别的样式控制,不能够做更 ...

  5. 微信小程序自定义导航栏组件,完美适配所有手机,可实现各种功能和情况

    背景 在做小程序时,关于默认导航栏,我们遇到了以下的问题: Android.IOS手机对于页面title的展示不一致,安卓title的显示不居中 页面的title只支持纯文本级别的样式控制,不能够做更 ...

  6. 小程序自定义导航栏(完美适配所有手机)解决上下不居中 左右不对齐 高度不协调问题...

    背景 在做小程序时,关于默认导航栏,我们遇到了以下的问题: Android.IOS手机对于页面title的展示不一致,安卓title的显示不居中 页面的title只支持纯文本级别的样式控制,不能够做更 ...

  7. 微信小程序自定义导航栏组件(完美适配所有手机),可自定义实现任何你想要的功能

    背景 在做小程序时,关于默认导航栏,我们遇到了以下的问题: Android.IOS 手机对于页面 title 的展示不一致,安卓 title 的显示不居中 页面的 title 只支持纯文本级别的样式控 ...

  8. Android 全面屏适配及判断是否为全面屏,全面屏手势和虚拟导航栏的判断

    一,全面屏的适配 全面屏出现后,如果不做适配,屏幕上会出现上下黑边,影响视觉效果. 针对此问题,Android官方提供了适配方案,即提高App所支持的最大屏幕纵横比,实现起来也比较简单,在Androi ...

  9. 小程序自定义导航栏(适配不同手机)——拿来就用

    基本思路 写自定义导航组件的时候,需要将组件结构一分为二:状态栏 + 标题栏状态栏高度可通过wx.getSystemInfoSync().statusBarHeight获取标题栏高度:安卓:48px, ...

最新文章

  1. 用耳朵“打字”| 这个设备可以让患者实现用耳朵进行交流
  2. Python遍历列表时删除元素
  3. Oracle 原理:逻辑备份和恢复
  4. django分页功能
  5. linux snmpwalk版本,snmpwalk的Linux的击不返回
  6. 响应式布局(手机端)
  7. 请认真使用没有后悔药的parted分区工具
  8. Java_键盘输入语句
  9. 数值分析——Hermite插值
  10. 项目管理java_java项目管理经验总结
  11. bp神经网络算法的优缺点,bp神经网络缺点及克服
  12. HBase的rowKey设计技巧
  13. java之Collection
  14. 现场总线技术笔记——3、通用串行端口的数据通信(RS232、RS485)
  15. 奇点云数据中台技术汇(四)| DataSimba系列之流式计算
  16. cad编辑节点快捷键是什么_cad删除快捷键(cad删除节点快捷键)
  17. 百度AI—人脸在线比对
  18. excel求四分位数(QUARTILE 函数)
  19. 百度推广年终总结,百度竞价年终总结参考
  20. 计算机专业基础综合408备考经验分享

热门文章

  1. tcpdump linux服务器抓包命令
  2. vim退出内容仍停留在屏幕的问题
  3. 教你如何ai动物绘画
  4. 接口和抽象类有什么区别
  5. 【小伟哥AI之路】Python之sanic异步框架使用精解
  6. 全景拍摄教程:为什么要使用全景云台?
  7. 小游戏基与creator接入VIVO平台之小包模式和远程资源加载
  8. 史上最全CSS命名规范
  9. html四大作用域,jsp的四大作用域是什么
  10. [学习笔记]BP神经网络原理以及训练步骤