在 PageView 中的 WheelEvent 上 Flutter Web “平滑滚动”
Posted
技术标签:
【中文标题】在 PageView 中的 WheelEvent 上 Flutter Web “平滑滚动”【英文标题】:Flutter Web "smooth scrolling" on WheelEvent within a PageView 【发布时间】:2020-12-14 07:30:01 【问题描述】:下面的代码
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget
const MyApp(Key key) : super(key: key);
@override
Widget build(BuildContext context) => MaterialApp(
home: const MyHomePage(),
);
class MyHomePage extends StatelessWidget
const MyHomePage(Key key) : super(key: key);
@override
Widget build(BuildContext context) => DefaultTabController(
length: 2,
child: Scaffold(
appBar: AppBar(
title: const Center(
child: Text('use the mouse wheel to scroll')),
bottom: TabBar(
tabs: const [
Center(child: Text('ScrollView')),
Center(child: Text('PageView'))
],
),
),
body: TabBarView(
children: [
SingleChildScrollView(
child: Column(
children: [
for (int i = 0; i < 10; i++)
Container(
height: MediaQuery.of(context).size.height,
child: const Center(
child: FlutterLogo(size: 80),
),
),
],
),
),
PageView(
scrollDirection: Axis.vertical,
children: [
for (int i = 0; i < 10; ++i)
const Center(
child: FlutterLogo(size: 80),
),
],
),
],
),
),
);
您可以看到,在dartpad 或从这个video 上运行它,
使用鼠标滚轮滚动PageView
提供平庸的体验(充其量),
这是一个已知问题#35687#32120,但我正在尝试寻找解决方法
实现PageView
的平滑滚动或至少防止“卡顿”。
有人可以帮助我或指出正确的方向吗?
我不确定问题出在PageScrollPhysics
;
我有一种直觉认为问题可能出在WheelEvent
因为使用多点触控滚动滑动效果很好
【问题讨论】:
这是一个超级蹩脚的黑客dartpad.dartlang.org/c3476573514298b9885b78a0275c744c 【参考方案1】:非常相似但更容易设置:
将smooth_scroll_web^0.0.4添加到您的pubspec.yaml
...
dependencies:
...
smooth_scroll_web: ^0.0.4
...
用法:
import 'package:smooth_scroll_web/smooth_scroll_web.dart';
import 'package:flutter/material.dart';
import 'dart:math'; // only for demo
class Page extends StatefulWidget
@override
PageState createState() => PageState();
class PageState extends State<Page>
final ScrollController _controller = new ScrollController();
@override
Widget build(BuildContext context)
return Scaffold(
appBar: AppBar(
title: Text("SmoothScroll Example"),
),
body: SmoothScrollWeb(
controller: controller,
child: Container(
height: 1000,
child: ListView(
physics: NeverScrollableScrollPhysics(),
controller: _controller,
children: [
// Your content goes here, thoses children are only for demo
for (int i = 0; i < 100; i++)
Container(
height: 60,
color: Color.fromARGB(1,
Random.secure().nextInt(255),
Random.secure().nextInt(255),
Random.secure().nextInt(255)),
),
],
),
),
),
);
谢谢hobbister!
参考 Github 上的flutter's issue #32120。
【讨论】:
谢谢,我知道这个问题github.com/flutter/flutter/issues/32120#issuecomment-725913608 我也强烈建议不要使用没有测试的包gitlab.com/dezso15/smoothscrollweb/-/tree/master/… 我喜欢这个库,它让颤动滚动变得更好,但就在今天我注意到如果我使用滚动控制器滚动,平滑滚动不会“注意到”,当我尝试滚动时我的鼠标滚轮,它将我传送回我单击按钮的位置(使用滚动控制器进行滚动)【参考方案2】:问题源于一系列事件:
-
用户将鼠标滚轮旋转一格,
Scrollable
接收 PointerSignal
和 calls jumpTo
方法,
_PagePosition
的jumpTo方法(派生自ScrollPositionWithSingleContext
)更新滚动位置并调用goBallistic
方法,
requested from PageScrollPhysics
模拟将位置恢复到初始值,因为一个缺口偏移量太小而无法翻页,
从步骤 (1) 开始重复另一个缺口和过程。
解决问题的一种方法是在调用 goBallistic
方法之前执行延迟。这可以在_PagePosition 类中完成,但是类是私有的,我们必须修补 Flutter SDK:
// <FlutterSDK>/packages/flutter/lib/src/widgets/page_view.dart
// ...
class _PagePosition extends ScrollPositionWithSingleContext implements PageMetrics
//...
// add this code to fix issue (mostly borrowed from ScrollPositionWithSingleContext):
Timer timer;
@override
void jumpTo(double value)
goIdle();
if (pixels != value)
final double oldPixels = pixels;
forcePixels(value);
didStartScroll();
didUpdateScrollPositionBy(pixels - oldPixels);
didEndScroll();
if (timer != null) timer.cancel();
timer = Timer(Duration(milliseconds: 200), ()
goBallistic(0.0);
timer = null;
);
// ...
另一种方法是将 jumpTo 替换为 animateTo。这可以在不修补 Flutter SDK 的情况下完成,但看起来更复杂,因为我们需要禁用默认的 PointerSignalEvent
监听器:
import 'dart:async';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
class PageViewLab extends StatefulWidget
@override
_PageViewLabState createState() => _PageViewLabState();
class _PageViewLabState extends State<PageViewLab>
final sink = StreamController<double>();
final pager = PageController();
@override
void initState()
super.initState();
throttle(sink.stream).listen((offset)
pager.animateTo(
offset,
duration: Duration(milliseconds: 200),
curve: Curves.ease,
);
);
@override
void dispose()
sink.close();
pager.dispose();
super.dispose();
@override
Widget build(BuildContext context)
return Scaffold(
appBar: AppBar(
title: Text('Mouse Wheel with PageView'),
),
body: Container(
constraints: BoxConstraints.expand(),
child: Listener(
onPointerSignal: _handlePointerSignal,
child: _IgnorePointerSignal(
child: PageView.builder(
controller: pager,
scrollDirection: Axis.vertical,
itemCount: Colors.primaries.length,
itemBuilder: (context, index)
return Padding(
padding: const EdgeInsets.all(8.0),
child: Container(color: Colors.primaries[index]),
);
,
),
),
),
),
);
Stream<double> throttle(Stream<double> src) async*
double offset = pager.position.pixels;
DateTime dt = DateTime.now();
await for (var delta in src)
if (DateTime.now().difference(dt) > Duration(milliseconds: 200))
offset = pager.position.pixels;
dt = DateTime.now();
offset += delta;
yield offset;
void _handlePointerSignal(PointerSignalEvent e)
if (e is PointerScrollEvent && e.scrollDelta.dy != 0)
sink.add(e.scrollDelta.dy);
// workaround https://github.com/flutter/flutter/issues/35723
class _IgnorePointerSignal extends SingleChildRenderObjectWidget
_IgnorePointerSignal(Key key, Widget child) : super(key: key, child: child);
@override
RenderObject createRenderObject(_) => _IgnorePointerSignalRenderObject();
class _IgnorePointerSignalRenderObject extends RenderProxyBox
@override
bool hitTest(BoxHitTestResult result, Offset position)
final res = super.hitTest(result, position: position);
result.path.forEach((item)
final target = item.target;
if (target is RenderPointerListener)
target.onPointerSignal = null;
);
return res;
Here is demo 在 CodePen 上。
【讨论】:
我尝试使用 animateTo 但我收到此错误:在 page_view._PagePosition.new.get 像素 [as pixel] (localhost:49511/packages/flutter/src/widgets/…) at main_layout._MainLayoutState.new.throttle (localhost:49511/packages/one_page/…) at throttle.next () at _AsyncStarImpl.new.runBody (localhost:49511/dart_sdk.js:31858:40) at Object._microtaskLoop dart_sdk.js:39175:13) at _startMicrotaskLoop (dart_sdk.js:39181:13) at localhost:49511/dart_sdk.js:34688:9【参考方案3】:问题在于用户设置,即最终用户如何将滚动设置为使用鼠标进行。我有一个罗技鼠标,它允许我通过罗技选项打开或关闭平滑滚动功能。当我启用平滑滚动时,它可以完美运行并根据需要滚动,但如果禁用平滑滚动,它也会在项目中被禁用。行为由最终用户设置。
不过,如果需要强制滚动平滑滚动,则只能通过设置相关动画来完成。目前还没有直接的方法。
【讨论】:
对不起,伙计,但我担心你错过了我的观点,正如你从我上面链接的两个问题中看到的那样,“WheelEvent”或“PageViewScrollPhysics”行为不正常,这是一个断言,而不是一个问题。我确信最终会修复错误/实现的功能,我正在寻找一种解决方法或一些有用的输入来说明如何自己修复它,甚至只是缓解问题。告诉用户更改他们的鼠标设置不是解决方案以上是关于在 PageView 中的 WheelEvent 上 Flutter Web “平滑滚动”的主要内容,如果未能解决你的问题,请参考以下文章
带有 PageView 构建器的 Flutter 中的 Tinder swiper