Compose 嵌套滑动冲突的解决办法

这篇具有很好参考价值的文章主要介绍了Compose 嵌套滑动冲突的解决办法。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

前言

在最近我利用业余时间使用 Compose 写的 Gihub APP 中,它的首页结构是这样的:

Compose 嵌套滑动冲突的解决办法

采用了 Drawer 嵌套 Pager 的结构。

这就会出现一个问题,那就是 DrawerPager 都需要监听横向滑动手势,从而实现展开 Drawer 和 切换 Pager 的功能。

那么,如果我把他们嵌套在一起使用会发生什么呢?谁能最终拿到手势事件呢?

而在我这个 APP 中其中一个 Pager 页面中又额外嵌套了一个 webview 页面,这个页面也需要获取到横向滑动手势,如果此时我切换到这个页面又会发生什么呢?

实际发生的和我们希望的

在上述的场景中,实际会发生的情况是,如果我们手势滑动的位置是在中间的内容区域的话,触发的会是 Pager 的手势,从而发生页面的切换。

如果我们的手势滑动的区域是在中间内容区域之外,例如顶部菜单栏的话触发的就是 Drawer 的手势,从而发生展开 Drawer 的事件。

至于这个 webview ,显然,无论如何它都不会被触发。

那么,我们期望的处理流程是什么样的呢?

显然,我们期望的是无论滑动哪个地方的手势都应该优先触发 Pager 然后在到达第一页的时候向右滑动改为触发 Drawer 而向左滑动则依旧触发 Pager ,至于 webview 我们期望的是无论它是在哪个页面,只要我们的手指触摸到的是它的内容范围,则应该优先触发它的手势。

那么,怎么才能实现我们的目的呢?

其实在 Compose 1.2.0 版本中就已经提供了官方的嵌套滚动互操作的支持:Nested scrolling interop 。

仔细阅读这篇指南就会发现,虽然 Compose 官方提供了嵌套滚动互操作的 API 支持,大多数情况下只需要添加一个 Modifier.nestedScroll(nestedScrollInterop) 即可实现我们上述所说的需求。

然而,并非所有的组件都支持上述这个 API:

This issue is a result of the expectations built in scrollable composables. Scrollable composables have a “nested-scroll-by-default” rule, which means that any scrollable container must participate in the nested scroll chain, both as a parent via NestedScrollConnection, and as a child via NestedScrollDispatcher. The child would then drive a nested scroll for the parent when the child is at the bound. As an example, this rule allows Compose Pager and Compose LazyRow to work well together. However, when interoperability scrolling is being done with ViewPager2 or RecyclerView, since these don’t implement NestedScrollingParent3, the continuous scrolling from child to parent is not possible.

不幸的是,我这里所需要实现嵌套滚动的恰好是不受支持的组件。

所以,我们只能自己去实现了。

解决 Drawer 和 Pager 的滑动冲突

那么,我们就先从简单的开始,先去解决同为 Compose 组件的 Drawer 和 Pager 的冲突。

在开始之前,我们先明确一下我们需要解决的问题,那就是我们需要实现如果在第一页时,如果向右滑则触发 Drawer 展开侧栏;向左滑则触发 Pager 切换至下一页。如果不在第一页那么就只需要触发 Pager 切换页面。

为了解决这个问题,我首先想到的方法是如同原生 View 那样的,通过拦截触摸事件,然后在按照我们需求去重新分配触摸事件。

但是在我实际尝试过程中发现在 Compose 中想要拦截并重新分配触摸事件似乎不是那么的好实现。

所以我这里使用了一种折中的方法来实现。

由于相对于 Drawer 来说 Pager 是其子界面,所以这里我们选择给 Pager 添加一个 Modifier.draggable() 修饰,使用这个修饰我们可以获取到在 Pager 的单一方向的滑动手势,以及其滑动距离等数据:

HorizontalPager(
    // ……
    state = pagerState,
    modifier = Modifier.draggable(state = rememberDraggableState {offset ->
         // ……
         // 这里的 offset 即获取到的手势滑动的变化值
    },
        orientation = Orientation.Horizontal, // 这里表示只获取水平方向上的手势
        enabled = pagerState.currentPage == 0)
) { page ->
    // ……
}

在这里按照我们的需求,我们也给这个 Modifier.draggable 修饰加上了启用条件 enabled = pagerState.currentPage == 0 即只有当前处于第一页时才启用这个获取手势的修饰。

接下来,我们按照需求,判断手势的变化值来确定是需要展开 Drawer 还是切换 Pager ,其实判断方法也很简单,如果值为正则说明是向右滑动,则应该展开 Drawer ;如果值为负则说明是向左滑动,应该要切换 Pager 的页面:

HorizontalPager(
    // ……
    state = pagerState,
    modifier = Modifier.draggable(state = rememberDraggableState {offset ->
        if (drawerState.isClosed && !drawerState.isAnimationRunning) {
            if (offset >= 5f) {
            		// ……
            		// 在这里触发展开 Drawer
            }
            else if (offset < -5f && pagerState.canScrollForward && !pagerState.isScrollInProgress){
            		// ……
            		// 在这里触发切换页面
            }
        }
    },
        orientation = Orientation.Horizontal,
        enabled = pagerState.currentPage == 0)
) { page ->
    // ……
}

在这里我们为了避免误触,把判断的阈值设置为了 ± 5 个单位。

另外,为了避免在已经开始展开 Drawer 或切换页面时重复触发,我们首先要确保当前 Drawer 处于关闭状态,且没有处于状态变化中:

if (drawerState.isClosed && !drawerState.isAnimationRunning)

同理,当触发切换页面时也需要保证当前没有在切换过程中,且应当处于可以切换的状态:

if (offset < -5f && pagerState.canScrollForward && !pagerState.isScrollInProgress)

在这里我们分别用 drawerState.open()pagerState.animateScrollToPage(1) 来触发展开 Drawer 和切换页面。

其实这里如果想做的更“友好”一点或者说更“跟手”一点,那么我们应该是根据当前的手势滑动的值实时更新相应的布局变化值,直到达到某个阈值才认为状态变更完成,否则“弹回”未变更前。而不是像现在这样,不管滑了多少距离,直接二话不说就直接触发状态完全变化。

但是目前 DrawerPager 都没有提供相应的 API,所以只能这么粗暴的去实现了。

Draer 虽然有一个 drawerState.offset 参数,但是它是只读的)

至此,虽然不太完美,但是也实现了我们的需求,效果如下:

Compose 嵌套滑动冲突的解决办法

解决 Pager 和 Webview 的冲突

上一节我们讲了同为 Compose 组件之间的嵌套滚动冲突的解决方法,接下来我们讲一讲 Compose 嵌套原生 View 的滑动冲突解决方法。

但是正如我在前言中所说, Compose 并不能很好的拦截并重新分配触摸事件。但是 VIew 是可以很容易的做到这一点的,因此我们的想法很简单,就是在 Webview 中拦截掉所有的触摸事件即可。

只是在这个 APP 中,webview 是被嵌套在多个有可能拦截触摸事件的 Compose 组件中的: webview -> LazyColumn -> SwipeRefresh -> Pager -> Drawer 。

好在,这几个组件都提供了禁用拦截触摸事件的参数:

LazyColumn 中有一个 userScrollEnabled 参数,当将这个参数设置为 false 时,LazyColumn 就不会再拦截触摸事件。

SwipeRefresh 中有一个 swipeEnabled 参数,当将这个参数设置为 false 时,SwipeRefresh 就不会再拦截触摸事件。

HorizontalPager 中有一个名为 userScrollEnabled 的参数,当将这个参数设置为 false 时,Pager 就不会再拦截触摸事件。

ModalNavigationDrawer 的中有一个叫 gesturesEnabled 的参数,将其设置为 false 时也不会再拦截触摸事件。

所以这里我们就从这几个参数下手,首先为 webview 设置触摸监听,如果 webview 接收到了触摸事件就回调给上述几个函数,设置其对应的参数为 false,确保其不会拦截 webview 的触摸事件,当 webview 失去触摸事件时就将其设置会 true,让他们继续相应对应组件的触摸事件。

为了实现这个目的,我们首先给封装的 webview 组件提供一个 onTouchEvent 回调:

@Composable
fun CustomWebView(
    // ……
    onTouchEvent: ((event: MotionEvent) -> Boolean)? = null
) {
    AndroidView(
        factory = { ctx ->
            WebView(ctx).apply {
                // ……

                if (onTouchEvent != null) {
                    setOnTouchListener { v, event ->
                        onTouchEvent(event)
                    }
                }
            }
        }
    )
}

在上述代码中,我们把触摸事件回调给了 onTouchEvent 函数。

因此我们在实际调用 CustomWebView 这样写:

CustomWebView(
    // ……
    onTouchEvent = {
        when (it.action) {
            MotionEvent.ACTION_DOWN -> {
                changeaScrollState(false)
                false
            }
            MotionEvent.ACTION_UP -> {
                changeaScrollState(true)
                false
            }
            else -> {
                false
            }
        }
    }
)

上述代码中,我们通过 changeaScrollState 函数设置我们一开始提到的几个 Compose 组件的触摸事件启用状态。

这里有一个地方需要注意,在 onTouchEvent 中记得一定要返回 false 表示只是获取这个触摸事件但是不消费,否则虽然触摸事件不会被其他 Compose 拦截消费了,但是却被我们自己消费了,webview 依然是接受不到这个触摸事件的。

最终实现效果如下:

Compose 嵌套滑动冲突的解决办法

可以看到,现在 Webview 已经能够自由的滚动而不会受到几个父布局的影响了,并且当我们滑动的是 webview 以外的区域时,其他组件依旧能够正常滚动。

总结

以上就是我目前遇到的在 Compose 中的手势冲突的情况,以及我的解决方案。

完整的代码在这里: GithubAppByCompose

可以看到其实核心思路也是和使用 view 时一样,根据我们自己实际业务需求,重新分配不同的触摸事件给不同的 UI 。

不过我的处理方式实在无法称作优雅,所以各位大佬如果有更优雅的处理方式,希望能不吝赐教。文章来源地址https://www.toymoban.com/news/detail-499004.html

到了这里,关于Compose 嵌套滑动冲突的解决办法的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 最近iphone手机的交管12123闪退,打不开的解决办法?

    苹果手机系统和新版软件不配,终极决绝办法:升级IOS系统就好 可能是手机的内存不足了,因为在使用APP时,需要占用手机的内存,如果手机内存不足以支持软件允许,软件就会闪退。车主可以清理一下手机的内存,将不经常使用的软件卸载掉。 可能是软件的版本不对,车

    2024年04月10日
    浏览(101)
  • npm install依赖冲突解决办法

    今天npm的时候发现报错,原来是依赖冲突了 npm后面加上这个指令就可以顺利的安装依赖了。问题主因就是不同开发用了不同版本node导致依赖版本不同,出现了成功冲突,这是段指令;它告诉npm忽略项目中引入的各个依赖模块之间依赖相同但版本不同的问题

    2024年02月09日
    浏览(48)
  • 关于最近VSCode的Python代码格式化失效问题的解决办法

    隔了一段时间再次打开VSCode写Python脚本,Python扩展弹出一条警告 查看日志输出发现Python的代码格式化设置发生了变化 简单来说就是Python扩展已经将原有的默认代码格式化工具 \\\"ms-python.python\\\" 弃用,格式化功能已移交到单独的格式化工具扩展。所有以 \\\"python.linting\\\" 开头的设置都

    2024年02月20日
    浏览(62)
  • uniapp - 完美解决 <swiper> 嵌套 video 视频时卡顿不流畅问题,swiper滑块视图容器里面包含视频播放器和图片共存时滑动切换特别卡,滑动不流畅问题的解决方案(保证解决此问题)

    在uniapp项目开发中,swiper组件内有多个视频、图片和视频混合时会出现滑动不流畅卡顿问题,并且视频自动轮播时没等看完视频就轮播走了。 解决方案全端(H5网页/小程序/app安卓苹果等)兼容适用,uview组件库同理也能用,保证解决卡顿不流畅的问题。

    2024年02月05日
    浏览(100)
  • git 记录一次合并冲突的解决办法

    合并冲突      将远程分支拉到本地,执行 git merge \\\'分支名\\\' 时,报错: CONFLICT (content): Merge conflict in “文件路径名”  // 冲突出现在xx文件里面 Automatic merge failed; fix conflicts and then commit the result.  // 自动合并失败,先解决冲突再提交最终结果 同时在分支名后面会出现  |

    2024年02月11日
    浏览(43)
  • elementUI 表格页面层级嵌套过多不及时刷新/错位的解决办法

    1.强制刷新(可能会影响性能) 2.页面重新布局(适用于表格错位等问题) 前提需要绑定: el-form ref=\\\"table\\\"/el-form

    2024年02月15日
    浏览(59)
  • 编译工程需要Opencv3 与 ROS自带Opencv4冲突解决办法

    在CmakeLists中 屏蔽ROS自带的Opencv库 此时可能 cv_brige 也会发生冲突,因为默认的 cv_brige 也是和 Opencv4 配套使用 需要修改如下内容: 1.头文件目录:修改为安装opencv3的路径 2.库目录:需要什么库链接什么库就够了

    2024年02月08日
    浏览(42)
  • 关于微软Microsoft 365 开发中心 邮箱域 CNAME‎(别名)解析指向记录冲突的问题解决办法!

    关于微软Microsoft 365开发中心 邮箱域的DNS添加 CNAME‎(别名)解析指向记录,CNAME冲突的问题解决办法!  先说明问题,如下图: 我用的是华为平台域名解析:  一直出现   CNAME‎(别名)指向冲突! 按照微软的要求怎么也添加不上   CNAME‎(别名)记录 ----------------试过许

    2024年02月02日
    浏览(45)
  • 在IDEA本地开发时Flink CDC和Flink的guava版本冲突解决办法

    使用Flink CDC 2.2.0版本的时候,会报ThreadFactoryBuilder这个类找不到的错误,如下所示: 因为Flink CDC使用的是guava版本是18.0-13.0,如下所示: 而Flink 1.14.4使用的guava版本是30.1.1-jre-14.0,如下所示: 但是项目中会使用30.1.1-jre-14.0版本的guava,所有会找不到guava 18.0-13.0这个版本,所以

    2023年04月08日
    浏览(35)
  • flutter3使用dio库发送FormData数据格式时候的坑,和get库冲突解决办法

    问题1:当你使用 FormData.from(Flutter3直接不能用) 的时候,可能会提示没有这个方法,或者使用 FormData.fromMap(flutter3的dio支持) 的时候也提示没有,这时候可能就是和get库里面的Formdata冲突了 问题1:The method \\\'fromMap\\\' isn\\\'t defined for the type \\\'FormData\\\'. (Documentation)  Try correcting the name to

    2024年01月19日
    浏览(42)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包