如何解决Fluke Web在PageView中对WheelEvent进行“平滑滚动”
使用下面的代码
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),);
}
使用鼠标滚轮滚动PageView
可提供中等水平的体验(充其量),
这是一个已知问题#35687 #32120,但我正在尝试找到解决方法
要么实现PageView
的平滑滚动,要么至少防止“口吃”。
有人可以帮我或指出正确的方向吗?
我不确定问题出在PageScrollPhysics
;
我有种直觉,认为问题可能出在WheelEvent
因为使用多点触控滚动可以完美工作
解决方法
问题源于一系列事件:
- 用户将鼠标滚轮旋转一个档次,
-
Scrollable
接收PointerSignal
和callsjumpTo
方法, -
_PagePosition
的{{3}}方法(源自ScrollPositionWithSingleContext
)更新滚动位置并调用goBallistic
方法, - jumpTo将位置恢复为初始值,因为一个缺口偏移量产生的尺寸太小,无法翻页,
- 从步骤(1)开始重复另一个切口和过程。
PageScrollPhysics
模拟中的解决问题的一种方法是在调用goBallistic
方法之前执行延迟。这可以在requested类中完成,但是该类是私有的,我们必须修补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;
});
}
// ...
}
另一种方法是用animateTo替换jumpTo。无需修补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;
}
}
_PagePosition在CodePen上。
,非常相似但更容易设置:
将 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)),],);
}
}
谢谢hobbister!
请参阅 Github 上的 flutter's issue #32120。
,问题在于用户设置,即最终用户如何设置鼠标滚动的方式。我有一个Logitech鼠标,它允许我通过Logitech Options打开或关闭平滑滚动功能。当我启用平滑滚动时,它可以正常工作并按要求滚动,但是在禁用平滑滚动的情况下,它也会在项目上被禁用。行为由最终用户设置。
仍然,如果需要强制滚动使其平滑滚动,而这只能通过设置相关动画来完成。到目前为止,还没有直接的方法。
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。