EdgeUtils

项目地址github.com/JailedBird/…

1、 接入方法

EdgeUtils是根据androidx.core,对edge to edge沉溺式计划封装

接入方法:

  • 增加jitpack库房
maven { url 'https://jitpack.io' }
  • 增加依靠
implementation 'com.github.JailedBird:EdgeUtils:1.0.0'

2、 运用方法

2-1、 布局拓展全屏

Activity中运用API edgeToEdge() 将开发者完成的布局拓展到整个屏幕, 同时为防止抵触, 将状况栏和到导航栏布景色设备为透明;

EdgeUtils:安卓沉溺式计划(edge to edge)封装

留意:edgeToEdge() 的参数withScrim表示是否启用体系默许的反差色维护, 不是很熟悉的情况下直接运用默许true即可;

2-2、 体系栏状况控制

布局拓展之后, 开发者布局内容会显现在状况栏和导航栏区域, 造成布局和体系栏字体重叠(时刻、电量……);

此时为确保体系栏字体可见,应该设置其字体; 设置规则:白色(淡色)布景设置黑色字体(edgeSetSystemBarLight(true)),黑色(深色)布景设置白色字体(注:体系栏字体只要黑色和白色)(edgeSetSystemBarLight(false));

假如未作夜间方式适配, 默许运用 edgeSetSystemBarLight(true)淡色方式即可!

综合1、2咱们的基类能够写成如下的方式:

abstract class BasePosActivity : AppCompatActivity() {
​
   override fun onCreate(savedInstanceState: Bundle?) {
     if (usingEdgeToEdgeTheme()) {
         defaultEdgeToEdge()
     } else {
       customThemeSetting()
     }
     super.onCreate(savedInstanceState)
   }
}
​
protected open fun defaultEdgeToEdge() {
    edgeToEdge(false)
    edgeSetSystemBarLight(true)
}

2-3、 处理视觉抵触

2-3-1、状况栏适配

过程一布局拓展全屏会导致视觉上的抵触, 下面是几种常见的思路:请灵敏运用

  • 布局中增加View(id=”@+id/edge”)运用heightToTopSystemWindowInsets API动态监听并修正View的高度为状况栏的高度

    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
         android:orientation="vertical">
    ​
         <View
           android:id="@+id/edge"
           android:layout_width="match_parent"
           android:layout_height="0dp" />
         xxx
       </LinearLayout>
    binding.edge.heightToTopSystemWindowInsets()
    
  • 直接获取状况栏的高度,API为:edgeStatusBarHeight; 和1不同的是,1中View的height会随状况栏高度改变而改变,2不会; 此外获取状况栏高度需求在View Attached之后才能够(不然高度为0),因而运用suspend函数等待Attached后才回来状况栏,确保在始终能获取到正确的状况栏高度!

    lifecycleScope.launch {
       val height = edgeStatusBarHeight()
       xxx
    }
    
  • 针对有Toolbar的布局, 可直接为Toolbar加padding(or margin), 让padding的高度为状况栏高度!假如无效, 一般都与Toolbar的高度测量有关, 能够直接在Toolbar外层包上FrameLayout,为FrameLayout加padding, 概况阅览下文了解原理,然后灵敏挑选;

    fun View.paddingTopSystemWindowInsets() =
       applySystemWindowInsetsPadding(applyTop = true)
    ​
    fun View.marginTopSystemWindowInsets() =
       applySystemWindowInsetsMargin(applyTop = true)
    

2-3-2、 导航栏适配

导航栏的适配原理和状况栏适配是十分相似的, 需求留意的是 导航栏存在三种方式:

  • 全面屏方式
  • 虚拟导航栏
  • 虚拟导航条

API已经针对导航栏高度、导航栏高度margin和padding适配做好了封装,运用者无需关怀;

fun View.paddingBottomSystemWindowInsets() =
   applySystemWindowInsetsPadding(applyBottom = true)
  
fun View.marginBottomSystemWindowInsets() =
   applySystemWindowInsetsMargin(applyBottom = true)

适配思路是共同的,不再赘述;

2-4、 处理手势抵触

手势抵触和视觉抵触发生的原理是相同的,不过是前者无形而后者有形;体系级别热区(如侧滑回来)优先级是要高于View的侧滑的, 因而有时分需求避开(情况很少)

EdgeUtils主要工作仅仅做了视觉抵触的处理和一些API封装;运用者能够根据封装的API拓展,替换掉WindowInsetCompat.Type为你需求的类型;

fun View.applySystemWindowInsetsPadding(
   applyLeft: Boolean = false,
   applyTop: Boolean = false,
   applyRight: Boolean = false,
   applyBottom: Boolean = false,
) {
   doOnApplyWindowInsets { view, insets, padding, _ ->
       // val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars()) 
       // 替换为Type.SYSTEM_GESTURES即可,其他相似
     val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemGestures())
     val left = if (applyLeft) systemBars.left else 0
     val top = if (applyTop) systemBars.top else 0
     val right = if (applyRight) systemBars.right else 0
     val bottom = if (applyBottom) systemBars.bottom else 0
​
     view.setPadding(
       padding.left + left,
       padding.top + top,
       padding.right + right,
       padding.bottom + bottom
     )
   }
}

3、 Edge教程

3-1 何为edge to edge?

怎么完全理解Edge to edge的思想呢?

或许你需求官方文章 , 也能够看的我写的翻译文章doc1

3-2 底层是怎么完成的?

了解Edge to edge原理后,你或许会好奇他是怎么完成的?

或许你需求Flywith24大佬的文章 , 也可看缩略文章doc2

3-3 其他杂项记载

请看doc3 , 东西多但比较杂没整理

3-4 怎么快速上手?

EdgeUtils此结构根据androidx.core, 对WindowInsets等常见API进行封装,提供了安稳的API和细节处理;封装的API函数名称通俗易懂,理解起来很容易, 难点是需求结合 [Edge-to-edge](#Edge to edge) 的原理去进行灵敏适配各种界面

项目中存在三个demo对于各种常见的场景进行了处理和演示

  • navigation-sample 根据Navigation的官方demo, 此demo展现了Navigation结构下这种单Activity多Fragment的沉溺式缺点
  • navigation-edge-sample 运用此结构优化navigation-sample, 使其达到沉溺式的作用
  • immersion-sample 根据开源项目immersionbar中的demo进行EdgeUtils的替换处理, 完成大部分功能的替换 (注:已替换的会符号[展现OK],部分未完成)

4、 留意事项

4-1、 Toolbar经过paddingTop适配statusbar失效的问题

很多时分, 状况栏的色彩和ToolBar的色彩是共同的, 这种情况下咱们能够想到为ToolBar加 paddingTop = status_bar_height可是留意假如你的Toolbar高度为固定、或许测量的时分没处理好padding,那么他就或许失真;

快速判断技巧:xml布局预览中(假设状况栏高度预估为40dp),运用tools:padding = 40dp, 经过预览查看这40dp的padding是否对预览变成预期之外的变形,假如OK那么直接运用paddingTopSystemWindowInsets为ToolBar大多是没问题的

能够看下下面的2个例子:

  • paddingTop = 0时分, 如下的代码:
<androidx.appcompat.widget.Toolbar
    android:id="@+id/toolbar"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:background="@color/teal_200"
    android:paddingTop="0dp"
    app:layout_constraintEnd_toEndOf="parent"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintTop_toTopOf="parent"
    app:title="高度测验" />
  • UI预览能够看到是这个样子的:

EdgeUtils:安卓沉溺式计划(edge to edge)封装

  • paddingTop = 20时分, 如下的代码:
<androidx.appcompat.widget.Toolbar
    android:id="@+id/toolbar"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:background="@color/teal_200"
    android:paddingTop="20dp"
    app:layout_constraintEnd_toEndOf="parent"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintTop_toTopOf="parent"
    app:title="高度测验" />
  • 能够看到, Toolbar的总高度是不变的,内容高度下移20dp,这显然是不合理的;实践运行时动态为ToolBar增加statusbar的paddingTop必定也会导致这样的问题

EdgeUtils:安卓沉溺式计划(edge to edge)封装

处理计划:

1、 运用FrameLayout等常见ViewGroup包住ToolBar,将paddingTop高度设置到FrameLayout中, 将色彩teal_200设置到FrameLayout

<FrameLayout
    android:id="@+id/layout_tool"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:paddingTop="20dp"
    android:background="@color/teal_200">
    <androidx.appcompat.widget.Toolbar
        android:id="@+id/toolbar"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="@color/teal_200"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:title="高度测验" />
</FrameLayout>

如下:

EdgeUtils:安卓沉溺式计划(edge to edge)封装

2、 在ToolBar外层直接封装FrameLayout(LinearLayout等也可, 下文统一用FrameLayout替代);

我信任咱们一般都不会直接运用原生的Toolbar, 每个公司或多或少的都封装了一些自定义ToolBar;按照上述1的思路, 咱们不难发现:

  • 假如自定义ToolBar承继自FrameLayout(或许说Toolbar最外层被FrameLayout包住), 直接将paddingTop加到自定义ToolBar即可;
  • 当然有些做的好的公司或许会直接经过承继ViewGroup(如原生ToolBar), 这个时分或许就只能用计划1了;

当然上述几点都是具体问题具体分析, 咱们能够在预览界面临时加paddingTop,看看实践是什么样的, 便于咱们尽早发现问题;能够参阅下BottomNavigationView的源码, 它直接承继自FrameLayout, 内部对paddingBottom自动适配了navigation_bar_height;

这个思路和ImmersionBar的 状况栏与布局顶部重叠处理计划 相似,不同的是,ImmersionBar运用的是固定的高度,而计划1是动态监听状况栏的高度并设置FrameLayout的paddingTop;

注:上述的paddingTop = 20dp, 仅仅便利预览增加的, 运行时请经过API动态设置paddingTop = statusBar

3、 增加空白View,经过代码设置View高度为导航栏、状况栏高度时,存在坑;约束布局中0dp有特殊意义,或许导致UI变形,需求留意哈!特别是处理导航栏的时分,全屏时导航栏高度为0,就会导致View高度为0,假如有组件依靠他,或许会呈现古怪问题,因而最好现在布局预览中排查下

4-2、 Bug&兼容性(结构已修复)

直接运用Edge to edge(参照google官方文档)存在一个大坑:调用hide隐藏状况栏后会导致状况栏变黑, 而且内容区域无法铺满

详细描述看这儿:point_right: WindowInsetsControllerCompat.hide makes status bar background undrawable

private fun setWindowEdgeToEdge(window: Window) {
        WindowCompat.setDecorFitsSystemWindows(window, false)
        window.statusBarColor = Color.TRANSPARENT
        window.navigationBarColor = Color.TRANSPARENT
    }
WindowCompat.getInsetsController(this, this.decorView)?.let {
            it.systemBarsBehavior = behavior
            it.hide(WindowInsetsCompat.Type.statusBars())
        }    

具体表现下图这个样子:

EdgeUtils:安卓沉溺式计划(edge to edge)封装

处理计划如下 :point_down: How to remove top status bar black background

object EdgeUtils {
    /** To fix hide status bar black background please using this post
     * youtube: https://www.youtube.com/watch?v=yukwno2GBoI
     * stackoverflow: https://stackoverflow.com/a/72773422/15859474
     * */
    private fun Activity.edgeToEdge() {
        requestWindowFeature(Window.FEATURE_NO_TITLE)
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
            window.attributes.layoutInDisplayCutoutMode = WindowManager
                .LayoutParams
                .LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES
        }
        setWindowEdgeToEdge(this.window)
    }
    private fun setWindowEdgeToEdge(window: Window) {
        WindowCompat.setDecorFitsSystemWindows(window, false)
        window.statusBarColor = Color.TRANSPARENT
        window.navigationBarColor = Color.TRANSPARENT
    }

4-3、 怎么去掉scrim?

在导航栏设置为全透明时, 部分机型就会呈现scrim半透明遮罩,考虑到样式有点丑恶, 直接将其修正为#01000000, 这样看起来也是完全透明的, 可是体系判定其alpha不为0, 不会自动增加scrim的; 【具体请看官方文档】

private fun setWindowEdgeToEdge(window: Window) {
        WindowCompat.setDecorFitsSystemWindows(window, false)
        /** using not transparent avoid scrim*/
        Color.parseColor("#01000000").let { color ->
            window.statusBarColor = color
            window.navigationBarColor = color
        }
    }

4-4 、 禁止View的屡次监听

一个View只能绑定一次ApplyWindowInset的监听,屡次绑定或许会导致之前的失效或许呈现古怪问题!!!

5、 参阅资料