Flutter - PageView - 如果用户仍然触摸屏幕,则不要更改页面

Adr*_*rek 5 dart flutter flutter-layout flutter-pageview

如何更新PageView为仅在特定条件下触发onPageChange

在这里,如果用户仍在触摸屏幕,我不想更改当前页面。除此之外,一切都应该保持不变(弹道滚动模拟、页面限制)

看来它必须处理ScrollPhysics附加到 的对象PageView,但我不知道如何正确扩展它。

如果您需要一些代码,请告诉我,但问题非常笼统,可以引用任何PageView,因此您不需要任何上下文。

最小可重现示例

这是上面文本的dart翻译。请随意更新此代码以使其实现目标。

// main.dart
import 'package:flutter/material.dart';

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  static const String _title = 'Flutter Code Sample';

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(title: _title, home: MyPageView());
  }
}

class MyPageView extends StatefulWidget {
  const MyPageView({Key? key}) : super(key: key);

  @override
  State<MyPageView> createState() => _MyPageViewState();
}

class _MyPageViewState extends State<MyPageView> {
  @override
  Widget build(BuildContext context) {
    final PageController controller = PageController();
    return Scaffold(
        body: SafeArea(
            child: PageView.builder(
      onPageChanged: (int index) {
        // TODO: Don't trigger this function if you still touch the screen
        print('onPageChanged index $index, ${controller.page}');
      },
      allowImplicitScrolling: false,
      controller: controller,
      itemBuilder: (BuildContext context, int index) {
        print('Build Sliver');
        return Center(
          child: Text('Page $index'),
        );
      },
    )));
  }
}
Run Code Online (Sandbox Code Playgroud)

(糟糕的)解决方案示例

import 'package:flutter/material.dart';

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  static const String _title = 'Flutter Code Sample';

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(title: _title, home: MyPageView());
  }
}

class MyPageView extends StatefulWidget {
  const MyPageView({Key? key}) : super(key: key);

  @override
  State<MyPageView> createState() => _MyPageViewState();
}

class _MyPageViewState extends State<MyPageView> {
  @override
  Widget build(BuildContext context) {
    final PageController controller = PageController();
    return Scaffold(
        body: SafeArea(
      child: Listener(
        onPointerUp: (PointerUpEvent event) {
          if (controller.page == null) {
            return;
          }

          if (controller.page! > 0.5) {
            //TODO: update the time so it fits the end of the animation
            Future.delayed(const Duration(milliseconds: 700), () {
              print('Do your custom action onPageChange action here');
            });
          }
        },
        child: PageView.builder(
          controller: controller,
          itemBuilder: (BuildContext context, int index) {
            print('Build Sliver');
            return Center(
              child: Text('Page $index'),
            );
          },
        ),
      ),
    ));
  }
}
Run Code Online (Sandbox Code Playgroud)

该解决方案在用户停止触摸屏幕700 毫秒后触发下一页的操作。

它确实有效,但这是一项糟糕的工作

  1. 如何考虑不同的屏幕尺寸?700 毫秒是iPhone SE上页之间动画的最长时间。
  2. 如何调整这个任意数字(700),因此它会根据controller.page(越接近下一页,您需要等待的时间越小)而变化。
  3. 它不使用onHorizontalDragEnd或类似的拖动检测器,这可能会导致不需要的行为。

lep*_*sch 2

您应该使用 完全禁用滚动PageView,并physics: NeverScrollableScrollPhysics()使用 自行检测左右滚动GestureDetector。通过检查参数的属性,将GestureDetector.onHorizontalDragEnd告诉用户拖动的方向是向左还是向右。如果该值为负,则用户向右拖动;如果用户向左拖动,则该值为正。DragEndDetailsprimaryVelocity

要手动更改页面,只需使用PageController方法nextPagepreviousPage

看看下面的屏幕截图和DartPad 上的现场演示

截屏

import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const MyHomePage(),
      debugShowCheckedModeBanner: false,
      scrollBehavior: MyCustomScrollBehavior(),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({Key? key}) : super(key: key);

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  late PageController _pageController;

  @override
  void initState() {
    super.initState();
    _pageController = PageController();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: GestureDetector(
        onHorizontalDragEnd: (details) => (details.primaryVelocity ?? 0) < 0
            ? _pageController.nextPage(
                duration: const Duration(seconds: 1), curve: Curves.easeInOut)
            : _pageController.previousPage(
                duration: const Duration(seconds: 1), curve: Curves.easeInOut),
        child: PageView(
          physics: const NeverScrollableScrollPhysics(),
          controller: _pageController,
          children: [
            Container(
              color: const Color.fromARGB(255, 0, 91, 187),
            ),
            Container(
              color: const Color.fromARGB(255, 255, 213, 0),
            ),
          ],
        ),
      ),
    );
  }
}

class MyCustomScrollBehavior extends MaterialScrollBehavior {
  @override
  Set<PointerDeviceKind> get dragDevices => {
        PointerDeviceKind.touch,
        PointerDeviceKind.mouse,
      };
}
Run Code Online (Sandbox Code Playgroud)