Android上的边缘到边缘:做到正确

过去的Google I / O 2019带来了许多轰动性的创新,其中许多将在未来几年影响移动开发行业。 跟随新兴趋势也同样有趣。 首先,机械控制键的使用历史悠久,智能手机的屏幕越来越大,侧框架变得不那么显眼。 手势取代了屏幕上的系统按钮,为内容消费留下了越来越多的空间。 应用程序显示在显示屏的整个可见表面上,从底部到顶部框架,而不必局限于状态栏和导航面板的条件边界。 我们正处于边缘到边缘时代的边缘。



什么是边对边? 从字面上理解,这意味着您的应用程序应该显示在显示屏的整个可见表面上,从下到上框架,而不必局限于状态栏和下导航按钮。


在Android系统Shell的示例中全面介绍。

对于Android而言,一个简单的想法远非总是容易实现。 在本文中,我们将讨论如何最大程度地利用任何设备屏幕上的所有可用空间,而不管制造商,系统版本以及来自中王国(不仅是)的设备制造商喜欢取悦用户的各种设置。 本文中的代码已由我们亲自在30多种设备上进行了测试,并由10万名应用程序用户在231种不同的设备上进行了测试。

创建边缘到边缘接口的问题本身并不是一个新问题,并且早在I / O 2019之前就已经存在。当然,你们每个人都会记住如何首先搜索以下类别的内容: “ android透明状态栏”“ android状态栏渐变” “

应用程序匹配边到边标题的主要标准是:

  • 透明的状态栏;
  • 透明的导航栏。

material.io上阅读有关它们的更多信息。


Deezer App无需担心端到端合规性

重要的是要注意,我们并不是在像“ 全屏模式 ”那样完全删除它们。 我们为用户提供了查看重要系统信息和使用熟悉的导航的机会。

解决方案的同等重要的要求是可伸缩性和可扩展性。 还有其他一些:

  • 正确地将屏幕移到键盘上方,而不中断对Activity的AdjustResize标志的支持;
  • 避免在应用程序的UI元素上覆盖状态栏和导航栏,同时在其下方显示相应的背景;
  • 在使用当前版本的Android的所有设备上均可使用,外观相同。

一点理论


为这种看似简单的任务找到解决方案可能出乎意料地花费了很多时间,这对于项目经理来说并不容易解释。 而当质量检查人员仍然发现命运不佳的智能手机时,您的屏幕看起来就不是“根据经典”了……
在我们的项目中,我们被误认了几次。 仅一个月后,经过一系列的反复试验,我们就彻底解决了这个问题。

首先,您需要了解Android如何绘制系统面板。 从Android 5.0开始,提供了便捷的API,用于处理屏幕水平边缘的系统缩进。 它们被称为WindowInsets,在下面的图片中它们被涂成红色:


此外,Android团队的开发人员已添加了侦听器,这些侦听器可让您订阅这些缩进的更改,例如,在键盘出现时。 严格来说,WindowInsets是布局文件从屏幕边缘开始的边距。 调整“活动”的大小(分屏模式,键盘外观)时,插图也会更改。 因此,为了支持边缘到边缘,我们需要确保这些缩进不存在。 具有空WindowInsets的屏幕将如下所示:


实作


在我们的实现中,我们将积极地对Window及其标志进行操作。
所有示例都将用Kotlin编写,但是您可以使用实用程序而不是扩展功能轻松地在Java中实现它们。

根布局元素的第一件事是显式设置标志:

android:fitsSystemWindows="true" 

必须确保根视图在系统元素下绘制,以及在订购其更改时正确测量Inset。
现在我们来谈谈最重要的事情-删除屏幕边框! 但是,必须非常小心地进行此操作。 这就是为什么:

  1. 将底部的Inset调零,我们可能会失去窗口对键盘外观的反应:StackOverflow上有许多技巧可以重置上方的Inset,而下方的则微妙无声。 因此,NavigationBar不会完全透明。 重置下Inset时,adjustResize标志停止工作。

    解决方案:每次更改Inset时,请确定其中是否包含键盘的较低高度,否则仅将其复位。
  2. 重置插入时,视图的可见部分将落入状态栏和导航栏下。 根据“材料设计”(和常识)的概念,不应在系统区域中放置任何活动元素。 也就是说,在此区域中不应有按钮,用于输入文本的字段,复选框等。

    解决方案:我们将向侦听器添加一个侦听器,以便在更改WindowInsets时,将系统缩进转换为Activity,并通过为View设置正确的填充和边距在内部对其进行响应。


此行为不应被允许(工具栏在状态栏上爬行)。

removeSystemInsets()函数如下所示:

 fun removeSystemInsets(view: View, listener: OnSystemInsetsChangedListener) { ViewCompat.setOnApplyWindowInsetsListener(view) { _, insets -> val desiredBottomInset = calculateDesiredBottomInset( view, insets.systemWindowInsetTop, insets.systemWindowInsetBottom, listener ) ViewCompat.onApplyWindowInsets( view, insets.replaceSystemWindowInsets(0, 0, 0, desiredBottomInset) ) } } 

根据设备的当前配置, calculateDesiredBottomInset()函数可计算使用或不使用键盘底部Inset。

 fun calculateDesiredBottomInset( view: View, topInset: Int, bottomInset: Int, listener: OnSystemInsetsChangedListener ): Int { val hasKeyboard = isKeyboardAppeared(view, bottomInset) val desiredBottomInset = if (hasKeyboard) bottomInset else 0 listener(topInset, if (hasKeyboard) 0 else bottomInset) return desiredBottomInset } 

使用isKeyboardAppeared()方法检查键盘的高度 我们相信键盘不能占据屏幕高度的四分之一的假设。 如果需要,您可以根据需要修改验证逻辑。

 private fun View.isKeyboardAppeared(bottomInset: Int) = bottomInset / resourdisplayMetrics.heightPixels.toDouble() > .25 

removeSystemInsets()方法使用侦听器。 实际上,这些只是lambda表达式的类型别名。 其完整代码:

 typealias OnSystemBarsSizeChangedListener = (statusBarSize: Int, navigationBarSize: Int) -> Unit 

下一步是设置系统栏的透明度:

 window.statusBarColor = Color.TRANSPARENT window.navigationBarColor = Color.TRANSPARENT 

对以上所有内容进行编译后,我们获得以下方法:

 fun Activity.setWindowTransparency( listener: OnSystemInsetsChangedListener = { _, _ -> } ) { InsetUtil.removeSystemInsets(window.decorView, listener) window.navigationBarColor = Color.TRANSPARENT window.statusBarColor = Color.TRANSPARENT } 

现在,要启用所需活动的边缘到边缘模式,只需在onCreate()方法中调用以下函数:

 setWindowTransparency { statusBarSize, navigationBarSize -> //  } 

因此,在不到30行代码的情况下,我们实现了“边到边”的效果,同时没有违反任何UX原理,并且没有使用户失去常规的系统控件。 对于某人来说,这样的实现看起来简单而琐碎,但这是确保您的应用程序在任何设备上的可靠运行。
您可以通过大约一百种不同的方式来实现“边缘到边缘”的效果(StackOverflow上此类提示的数量明确证明了这一点),但是其中许多提示会导致在不同版本的Android上出现不正确的行为,或者未考虑诸如需要长时间显示等参数列表,或者在显示键盘时中断屏幕尺寸调整。




美中不足


本文介绍的解决方案适用于所有当前设备。 实际是指Android Lollipop(5.0)及更高版本上的设备。 对于他们来说,上面的解决方案将完美地工作。 但是对于较旧版本的Android,您将需要自己的实现,因为那时候对WindowInsets一无所知。

好消息是,在Android KitKat(4.4)上,仍支持系统面板的透明性。 但是旧版本根本不支持这种美,您甚至无法尝试。

让我们集中讨论Android 4.4中Insets的转变。 这可以在fitSystemWindows()方法中完成。 因此,布局中的主要元素应该是带有重写的fitSystemWindows方法的容器,该方法包含与当前版本的Android示例中的侦听器完全相同的实现。

 class KitkatTransparentSystemBarsFrame @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = -1 ) : FrameLayout(context, attrs, defStyleAttr), KitkatTransparentSystemBarsContainer { override var onSystemInsetsChangedListener: OnSystemInsetsChangedListener = { _, _ -> } override fun fitSystemWindows(insets: Rect?): Boolean { insets ?: return false val desiredBottomInset = InsetUtil.calculateDesiredBottomInset( this, insets.top, insets.bottom, onSystemInsetsChangedListener ) return super.fitSystemWindows(Rect(0, 0, 0, desiredBottomInset)) } 

在装有Android 4.4的设备上,通过设置半透明标志只能实现部分透明:

 window.addFlags( WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS or WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION ) 

这些标志使系统栏变得半透明,给它们增加了一个轻微的渐变,但不幸的是无法删除。 但是,可以使用以下库将渐变变成半透明的颜色条: https : //github.com/jgilfelt/SystemBarTint 。 她过去多次救过我们。 5年前对图书馆进行了最新更改,因此它只会在真正的逆行城市中展现其魅力。

Kitkat的整个标记过程如下所示:

 fun Activity.setWindowTransparencyKitkat( rootView: KitkatTransparentSystemBarsContainer, listener: OnSystemBarsSizeChangedListener = { _, _ -> } ) { rootView.onSystemBarsSizeChangedListener = listener window.addFlags( WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS or WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION ) } 

考虑到这一点,我们正在编写一种通用方法,该方法可以使系统栏透明(或至少半透明),而不管哪个应用程序在哪个版本的Android设备上运行:

 when { Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP -> setWindowTransparency(::updateMargins) Build.VERSION.SDK_INT == Build.VERSION_CODES.KITKAT -> setWindowTransparencyKitkat(root_container, ::updateMargins) else -> { /*do nothing*/ } } 

在下面的扰流器下,您可以看到本文中提供的示例在某些有问题的设备上的外观:

屏幕截图
华为荣誉8,Android 7.0



小米Redmi注意事项4,Android 6.1



HTC Desire Dual Sim,Android 4.4.2



三星J3,Android 7.0



魅族M3s,Android 5.1



华硕Zenfone 3 Max,Android 6.0



Umi Rome,Android 5.0



Nexus 5X,Android 8.0



三星Galaxy S8,Android 9.0





总而言之,我想说的是,即使对于看似简单的任务(如为系统UI的元素设置透明度)的解决方案,也可能使您陷入各种陷阱,并最终不会导致期望的结果,而会导致令人讨厌的错误。 好东西,你现在有这篇文章。

您可以在我们的git存储库中找到该程序的完整列表和工作示例。

该材料的灵感来自克里斯·巴内斯(Chris Banes)的报告“成为一名熟练的窗户装配工”。


我感谢Surf Studio和Evgeny Saturov在准备材料方面的帮助。

Source: https://habr.com/ru/post/zh-CN464373/


All Articles