前前语
已经开源,等不及的能够直接去看,去star,去fork! github.com/Cormor/Comp…
全篇已完成,分3期。
中下两期:
Jetpack Compose 完成iOS的回弹作用到底有多简略?跟着我,不难!(中)
Compose 用 nestedScroll 完成iOS的回弹作用,还要帮谷歌修bug?(完结)
前语
说是回弹作用,其实根本点在于怎样完成滑动越界。 之前写过一篇文章:
《# Jetpack Compose 开源实践 – 70行完成与iOS完全一致的滑动越界作用,封装成到处可用的Modifier!》
- 今日终于腾出空处理了 compose 新增 api 导致的 nestedScroll 速度过错。
- ……的一部分,别的的一点点小过错
影响不大,谷歌也一向不修复,我也懒得管了,敲!
- ……的一部分,别的的一点点小过错
- 今日完成了横向越界作用的支持,运用和纵向没什么不同。
- 甚至横向 + 纵向一重用 ——也是能够的
来,一同看看怎样完成。
怎样完成越界作用?
预期作用
假设有界面如下——
首先只考虑画面作用的简略偏移,咱们运用 Modifier.offset
即可让它偏移——
这和 View 中运用 X/Y 轴移动 ,或运用 translationX/Y 是一样的。
作用漏洞
可是会有这样一种可怕的状况:
好么,这才是真实的【越界回弹】作用不是吗?!
但你压服不了自己的良心 (压服不了 产品/UI/动效)。
处理方案
- 在 View 中,咱们能够外界套一个 FrameLayout 处理,由于
ViewGroup
默许 clipChildren = true
- 咱们能够经过
outline
裁剪处理- 见我的文章:小白也能懂的RecyclerView界面动效定制,让ExpandableListView完全退出舞台!(下)
- 咱们能够经过自定义view ,然后
canvas
中 clipRect / clipPath 处理
-
第一种明显不高雅,还增加嵌套层级——谁不知道 在 View 中布局要尽量防止嵌套 啊?
-
这后两者就得结合 translationX/Y 做核算,才能确认 clip 范围了。
淦,幸而咱们是 compose ! ——自带一个 Modifier.clipToBounds()
由于 Modifier
是顺序灵敏的,所以咱们 在偏移之前设置 clipBounds 就能够了.
Modifier
.clipToBounds()
.absoluteOffset { ... }
需求运用到的 Modifier
依据上文——
- 咱们有一个必要的
clipToBounds
以保证作用正确。 - 咱们需求有一个
absoluteOffset
语义的Modifier
,以完成内容偏移。- 它不能是
offset
,它会在 RTL 布局中自动反向。 -
absoluteOffset
需求在 lambda 中传入IntOffset
目标,但咱们在当前场景中的移动要么是笔直方向,要么是水平方向 - 越界滑动的进程,肯定是一堆
Float
变量核算 - ——所以咱们该运用
graphicsLayer
,直接传入单个的 float 值给 translationX/Y ,会舒畅很多
- 它不能是
- 咱们不能只考虑越界作用
- 咱们得支持和可滚动组件一同运用
- 咱们甚至得考虑嵌套滚动 ——
NestedScroll
场景,想想就头大。 - 所以咱们需求赶忙攻读
Modifier.nestedScroll()
的运用说明。
整合
所以依据上文,咱们能够先写出这么一个基本的 Modifier
出来,再填充内容。
fun Modifier.overScrollOutOfBound(
// 是否是笔直方向
isVertical: Boolean = true,
// 当我需求越界时,要不要考虑parent的定见呢?
nestedScrollToParent: Boolean = true,
// 越界产生时,越远越拉不动的阻尼作用怎样完成?预留一个函数在这里,后边完成
scrollEasing: (currentOffset: Float, newOffset: Float) -> Float,
// 回弹时肯定是绷簧嘛,留俩绷簧参数以供自定义
springStiff: Float = OutBoundSpringStiff,
springDamp: Float = OutBoundSpringDamp,
): Modifier = composed { // composed {} 归于 Modifier 标准写法
// ...
}
Modifier 内部脉络
上面 Modifier
写出来后,咱们得往内部写个大约内容了。
// 可变参数改变时需求重组,重组需求按需从头生成目标。
// 所以咱们把这些改变一致成一个参数
// 后边创立的目标一致调查这一个参数即可
val hasChangedParams = remember(nestedScrollToParent, springStiff, springDamp, isVertical) {
// 用Android提供的纳秒吧
SystemClock.elapsedRealtimeNanos()
}
// 偏移值,依据 isVertical 决定自己是x仍是y
var offset by remember(hasChangedParams) {
mutableFloatStateOf(0f)
}
// dispatcher 和 nestedScrollConnection
// Modifier.nestedScroll() 的俩必备参数
val dispatcher = remember(hasChangedParams) {
NestedScrollDispatcher()
}
val nestedConnection = remember(hasChangedParams) {
object : NestedScrollConnection {
override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset
override fun onPostScroll(consumed: Offset, available: Offset, source: NestedScrollSource): Offset
override suspend fun onPreFling(available: Velocity): Velocity
override suspend fun onPostFling(consumed: Velocity, available: Velocity): Velocity
}
}
// this 是 this Modifier,Modifier 标准写法
// 放到最后边作为 return
this
.clipToBounds()
.nestedScroll(nestedConnection, dispatcher)
.graphicsLayer {
// 很好了解吧?
if (isVertical) {
translationY = offset
} else {
translationX = offset
}
}
中期预览
要写的还挺多的,这期讲梗概,下期解析 Modifier.nestedScroll()
的运用。
估计明天搞定。- 已经搞定:/post/726855…
下期预览
假如 中期顺利的话,或许就一同写了,没有下期。 这一期会讲讲 为什么 NestedScroll咱们按照提示写完了,却仍旧不能正常运转。
——原来是谷歌从 1.4.0-alpha02
起引入了 BUG !
(issue提了那么久了还不修复,快去帮我+1)
issuetracker.google.com/issues/2766…
——什么?bug还不止一个?真实导致不能正常运转的是另一个bug?
(这个还没来得及提issue,所以赶忙经过点赞收藏催我更新,我更新完文章了才有精力去提bug)