【Flutter】解决多级列表嵌套滑动冲突方案选择

本文详细探讨了Vue.js 3的Transition组件,解释了其在DOM元素挂载和卸载时如何添加过渡效果。文章介绍了Transition组件的实现原理,包括setup函数的作用,以及如何通过resolveTransitionHooks和setTransitionHooks设置过渡钩子函数。在不同过渡阶段,如beforeEnter、enter和leave,Transition组件会执行相应的钩子函数来控制过渡效果。

版本:3.2.31

Transition 组件的实现原理

Vue.js 3 内建的 Transition 组件可以为单个元素或单个组件添加过渡效果。它的核心实现原理如下:

  • 当 DOM 元素被挂载时,将动效附加到该 DOM 元素上;
  • 当 DOM 元素被卸载时,不要立即卸载 DOM 元素,而是等到附加到该 DOM 元素上的动效执行完成后再卸载它。

Transition 组件的基本结构

// packages/runtime-core/src/components/BaseTransition.ts

const BaseTransitionImpl: ComponentOptions = {name: `BaseTransition`,props: {mode: String,appear: Boolean,persisted: Boolean,// 省略部分代码},setup(props: BaseTransitionProps, { slots }: SetupContext) { // 省略部分代码}
}

export const BaseTransition = BaseTransitionImpl as any as {new (): {$props: BaseTransitionProps<any>}
} 

从上面的代码可以看出,一个组件就是一个选项对象。Transition 组件上有 name、props、setup 等属性。其中 props 中的属性是用户使用 Transition 组件时需要传递给组件的 Props。setup 函数是组件选项,用于配置组合式API。

Transition 组件的 setup 函数

// packages/runtime-core/src/components/BaseTransition.ts

setup(props: BaseTransitionProps, { slots }: SetupContext) {const instance = getCurrentInstance()!const state = useTransitionState()let prevTransitionKey: anyreturn () => {// 通过默认插槽获取需要过渡的元素const children =slots.default && getTransitionRawChildren(slots.default(), true)if (!children || !children.length) {return}// 省略部分代码// at this point children has a guaranteed length of 1.const child = children[0]// 省略部分代码return child}
} 

setup 函数是 Vue.js 3 新增的组件选项,它通常会返回一个函数或者返回一个对象。在 Transition 组件中,setup 函数返回的是一个函数,该函数将会直接作为组件的render函数,即该render函数将会渲染添加了过渡效果的元素节点。也就是说,Transition 组件本身不会渲染任何额外的内容,它只是通过默认插槽读取过渡元素,并渲染需要过渡的元素。

给过渡元素添加 transition 钩子函数

在 setup 函数中,我们会看到 resolveTransitionHooks 函数和 setTransitionHooks 函数的调用,如下面的代码所示:

// packages/runtime-core/src/components/BaseTransition.ts

setup(props: BaseTransitionProps, { slots }: SetupContext) {const instance = getCurrentInstance()!const state = useTransitionState()let prevTransitionKey: anyreturn () => {// 省略部分代码// in the case of <transition><keep-alive/></transition>, we need to// compare the type of the kept-alive children.// 获取被 <transition> 组件包裹的 <keep-alive/> 组件。然后需要对它们进行比较const innerChild = getKeepAliveChild(child)if (!innerChild) {return emptyPlaceholder(child)}// 初始化 transition 钩子函数const enterHook
Flutter 中,当 `PageView` 嵌套 `SingleChildScrollView` 时,可能会出现滑动冲突的问题。可以通过以下几种方法解决: ### 1. 使用 `NotificationListener` `NotificationListener` 可以监听滚动通知,通过判断滚动方向来决定是否允许 `SingleChildScrollView` 滚动。 ```dart import 'package:flutter/material.dart'; void main() { runApp(MaterialApp( home: Scaffold( body: PageView( children: [ NotificationListener<ScrollNotification>( onNotification: (ScrollNotification notification) { if (notification is ScrollStartNotification) { // 根据滚动方向决定是否允许滚动 return false; } return true; }, child: SingleChildScrollView( child: Column( children: List.generate( 50, (index) => ListTile(title: Text('Item $index')), ), ), ), ), ], ), ), )); } ``` ### 2. 使用 `ScrollConfiguration` 和 `Behavior` 自定义 `ScrollBehavior` 可以控制滚动行为,通过重写 `shouldNotify` 方法来处理滚动冲突。 ```dart import 'package:flutter/material.dart'; class CustomScrollBehavior extends ScrollBehavior { @override Widget buildViewportChrome( BuildContext context, Widget child, AxisDirection axisDirection) { return child; } @override bool shouldNotify(ScrollMetrics oldMetrics, ScrollMetrics newMetrics) { // 根据滚动方向决定是否通知 return false; } } void main() { runApp(MaterialApp( home: Scaffold( body: ScrollConfiguration( behavior: CustomScrollBehavior(), child: PageView( children: [ SingleChildScrollView( child: Column( children: List.generate( 50, (index) => ListTile(title: Text('Item $index')), ), ), ), ], ), ), ), )); } ``` ### 3. 使用 `IgnorePointer` `IgnorePointer` 可以忽略其子组件的指针事件,通过控制 `IgnorePointer` 的 `ignoring` 属性来决定是否允许 `SingleChildScrollView` 滚动。 ```dart import 'package:flutter/material.dart'; void main() { runApp(MaterialApp( home: Scaffold( body: PageView( children: [ StatefulBuilder( builder: (BuildContext context, StateSetter setState) { bool isScrolling = false; return GestureDetector( onHorizontalDragStart: (_) { setState(() { isScrolling = true; }); }, onHorizontalDragEnd: (_) { setState(() { isScrolling = false; }); }, child: IgnorePointer( ignoring: isScrolling, child: SingleChildScrollView( child: Column( children: List.generate( 50, (index) => ListTile(title: Text('Item $index')), ), ), ), ), ); }, ), ], ), ), )); } ```
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值