Flutter中PageView的滑动开始监听、完成监听自定义

        最近Flutter项目开发中,用到了PageView的上下滚动,但是正常的PageView的使用,onPageChanged的回调,在页面滑到中间的时候,就会把下一个页面的pageIndex值传递过来。但是实际需求中,我需要知道什么时候页面滑动结束,这时候才去执行页面完全展示的方法。所以就需要对PageView进行自定义改造。

一、尝试了使用系统自带的PageController进行滑动监听,失败

        开始尝试使用系统给的api进行监听

        结果发现,返回的offset和page是Double类型的,更坑爹的是,在某些手机或者滑动距离过长时,返回的数据不够精准,没办法准确判断是否滑动完成。一看不能用,没办法在系统的api里面找到解决办法,就只能看源码了

 

二、查看PageView的源码,自定义pageView

        查看系统源码,看看为什么会出现onPageChanged在中间的时候就返回回调了。在查看源码过程中,发现以下神奇的地方:

根据源码我们能看到,PageView在实际上也是通过NotificationListener进行的监听,而且在onPageChanged的返回前,判断了notification is ScrollUpdateNotification。

ScrollUpdateNotification是什么呢?这就需要我们看一下NotificationListener的回调监听了,也就是返回的ScrollNotification这个类。

点击进去看到ScrollUpdateNotification继承ScrollNotification,那是不是还有其他的监听回调类型,可以实现我们的需求呢?果然最后源码中发现,还有ScrollStartNotification、OverscrollNotification、ScrollEndNotification和UserScrollNotification四种滑动回调类型!

既然我们想要的是滑动完成的回调,就只需要在NotificationListener判断ScrollEndNotification这个滑动类型就行了。

还有OverscrollNotification和UserScrollNotification两种滑动类型没有用到,有其他用到的小伙伴可以告诉我这两个回调可以用在什么场景下。

 

自定义的PageView全部代码如下:

// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import 'dart:async';
import 'dart:math' as math;

import 'package:flutter/cupertino.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/gestures.dart' show DragStartBehavior;
import 'package:flutter/foundation.dart' show precisionErrorTolerance;

///自定义的PageView
///新增了页面加载开始和结束的回调

final PageController _defaultPageController = PageController();
const PageScrollPhysics _kPagePhysics = PageScrollPhysics();

class CustomPageView extends StatefulWidget {
  /// Creates a scrollable list that works page by page from an explicit [List]
  /// of widgets.
  ///
  /// This constructor is appropriate for page views with a small number of
  /// children because constructing the [List] requires doing work for every
  /// child that could possibly be displayed in the page view, instead of just
  /// those children that are actually visible.
  CustomPageView({
    Key key,
    this.scrollDirection = Axis.horizontal,
    this.reverse = false,
    PageController controller,
    this.physics,
    this.pageSnapping = true,
    this.onPageChanged,
    this.onPageEndChanged,
    this.onPageStartChanged,
    List<Widget> children = const <Widget>[],
    this.dragStartBehavior = DragStartBehavior.start,
  })  : controller = controller ?? _defaultPageController,
        childrenDelegate = SliverChildListDelegate(children),
        super(key: key);

  /// Creates a scrollable list that works page by page using widgets that are
  /// created on demand.
  ///
  /// This constructor is appropriate for page views with a large (or infinite)
  /// number of children because the builder is called only for those children
  /// that are actually visible.
  ///
  /// Providing a non-null [itemCount] lets the [CustomPageView] compute the maximum
  /// scroll extent.
  ///
  /// [itemBuilder] will be called only with indices greater than or equal to
  /// zero and less than [itemCount].
  ///
  /// [CustomPageView.builder] by default does not support child reordering. If
  /// you are planning to change child order at a later time, consider using
  /// [CustomPageView] or [CustomPageView.custom].
  CustomPageView.builder({
    Key key,
    this.scrollDirection = Axis.horizontal,
    this.reverse = false,
    PageController controller,
    this.physics,
    this.pageSnapping = true,
    this.onPageChanged,
    this.onPageEndChanged,
    this.onPageStartChanged,
    @required IndexedWidgetBuilder itemBuilder,
    int itemCount,
    this.dragStartBehavior = DragStartBehavior.start,
  })  : controller = controller ?? _defaultPageController,
        childrenDelegate =
            SliverChildBuilderDelegate(itemBuilder, childCount: itemCount),
        super(key: key);

  CustomPageView.custom({
    Key key,
    this.scrollDirection = Axis.horizontal,
    this.reverse = false,
    PageController controller,
    this.physics,
    this.pageSnapping = true,
    this.onPageChanged,
    this.onPageEndChanged,
    this.onPageStartChanged,
    @required this.childrenDelegate,
    this.dragStartBehavior = DragStartBehavior.start,
  })  : assert(childrenDelegate != null),
        controller = controller ?? _defaultPageController,
        super(key: key);

  /// The axis along which the page view scrolls.
  ///
  /// Defaults to [Axis.horizontal].
  final Axis scrollDirection;

  /// Whether the page view scrolls in the reading direction.
  ///
  /// For example, if the reading direction is left-to-right and
  /// [scrollDirection] is [Axis.horizontal], then the page view scrolls from
  /// left to right when [reverse] is false and from right to left when
  /// [reverse] is true.
  ///
  /// Similarly, if [scrollDirection] is [Axis.vertical], then the page view
  /// scrolls from top to bottom when [reverse] is false and from bottom to top
  /// when [reverse] is true.
  ///
  /// Defaults to false.
  final bool reverse;

  /// An object that can be used to control the position to which this page
  /// view is scrolled.
  final PageController controller;

  /// How the page view should respond to user input.
  ///
  /// For example, determines how the page view continues to animate after the
  /// user stops dragging the page view.
  ///
  /// The physics are modified to snap to page boundaries using
  /// [PageScrollPhysics] prior to being used.
  ///
  /// Defaults to matching platform conventions.
  final ScrollPhysics physics;

  /// Set to false to disable page snapping, useful for custom scroll behavior.
  final bool pageSnapping;

  /// Called whenever the page in the center of the viewport changes.
  final ValueChanged<int> onPageChanged;
  final ValueChanged<int> onPageEndChanged;
  final ValueChanged<int> onPageStartChanged;

  /// A delegate that provides the children for the [CustomPageView].
  ///
  /// The [CustomPageView.custom] constructor lets you specify this delegate
  /// explicitly. The [CustomPageView] and [CustomPageView.builder] constructors create a
  /// [childrenDelegate] that wraps the given [List] and [IndexedWidgetBuilder],
  /// respectively.
  final SliverChildDelegate childrenDelegate;

  /// {@macro flutter.widgets.scrollable.dragStartBehavior}
  final DragStartBehavior dragStartBehavior;

  @override
  _CustomPageViewState createState() => _CustomPageViewState();
}

class _CustomPageViewState extends State<CustomPageView> {
  int _lastReportedPage = 0;
  int _currentReportedPage = 0;

  @override
  void initState() {
    super.initState();
    _lastReportedPage = widget.controller.initialPage;
  }

  AxisDirection _getDirection(BuildContext context) {
    switch (widget.scrollDirection) {
      case Axis.horizontal:
        assert(debugCheckHasDirectionality(context));
        final TextDirection textDirection = Directionality.of(context);
        final AxisDirection axisDirection =
            textDirectionToAxisDirection(textDirection);
        return widget.reverse
            ? flipAxisDirection(axisDirection)
            : axisDirection;
      case Axis.vertical:
        return widget.reverse ? AxisDirection.up : AxisDirection.down;
    }
    return null;
  }

  @override
  Widget build(BuildContext context) {
    final AxisDirection axisDirection = _getDirection(context);
    final ScrollPhysics physics = widget.pageSnapping
        ? _kPagePhysics.applyTo(widget.physics)
        : widget.physics;

    return NotificationListener<ScrollNotification>(
      onNotification: (ScrollNotification notification) {
        ///滑动开始
        if (notification is ScrollStartNotification) {
          if (widget.onPageStartChanged != null) {
            widget.onPageStartChanged(_currentReportedPage);
          }
          //print('返回的开始的页码=' + _currentReportedPage.toString());
        }

        ///滑动中
        if (notification.depth == 0 &&
            notification is ScrollUpdateNotification) {
          final PageMetrics metrics = notification.metrics;
          final int currentPage = metrics.page.round();
          _currentReportedPage = currentPage;
          //print('外部返回的页码=' + currentPage.toString());
          //print('外部返回的pixels=' + metrics.pixels.toString());
          if (currentPage != _lastReportedPage) {
            _lastReportedPage = currentPage;
            if (widget.onPageChanged != null) {
              widget.onPageChanged(currentPage);
            }
            //print('返回的页码=' + currentPage.toString());
          }
        }

        ///滑动结束
        if (notification.depth == 0 && notification is ScrollEndNotification) {
          if (widget.onPageEndChanged != null) {
            widget.onPageEndChanged(_currentReportedPage);
          }
          print('返回的结束的页码=' + _currentReportedPage.toString());
        }
        return false;
      },
      child: Scrollable(
        dragStartBehavior: widget.dragStartBehavior,
        axisDirection: axisDirection,
        controller: widget.controller,
        physics: physics,
        viewportBuilder: (BuildContext context, ViewportOffset position) {
          return Viewport(
            cacheExtent: 0.0,
            axisDirection: axisDirection,
            offset: position,
            slivers: <Widget>[
              SliverFillViewport(
                viewportFraction: widget.controller.viewportFraction,
                delegate: widget.childrenDelegate,
              ),
            ],
          );
        },
      ),
    );
  }

  @override
  void debugFillProperties(DiagnosticPropertiesBuilder description) {
    super.debugFillProperties(description);
    description
        .add(EnumProperty<Axis>('scrollDirection', widget.scrollDirection));
    description.add(
        FlagProperty('reverse', value: widget.reverse, ifTrue: 'reversed'));
    description.add(DiagnosticsProperty<PageController>(
        'controller', widget.controller,
        showName: false));
    description.add(DiagnosticsProperty<ScrollPhysics>(
        'physics', widget.physics,
        showName: false));
    description.add(FlagProperty('pageSnapping',
        value: widget.pageSnapping, ifFalse: 'snapping disabled'));
  }
}

 

Flutter ,可以通过使用 `TabBarView` 和 `PageView` 来实现 TabBar 的左右滑动事件监听。具体步骤如下: 1. 创建一个 `TabController` 对象,并将其与 `TabBar` 和 `TabBarView` 绑定: ```dart TabController _tabController; @override void initState() { _tabController = TabController(length: 3, vsync: this); super.initState(); } @override void dispose() { _tabController.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text('Tab Bar Example'), bottom: TabBar( controller: _tabController, tabs: [ Tab(text: 'Tab 1'), Tab(text: 'Tab 2'), Tab(text: 'Tab 3'), ], ), ), body: TabBarView( controller: _tabController, children: [ Container(color: Colors.red), Container(color: Colors.green), Container(color: Colors.blue), ], ), ); } ``` 2. 使用 `PageView` 包裹 `TabBarView`,并监听 `PageView` 的 `onPageChanged` 事件,以获取左右滑动事件: ```dart PageController _pageController; @override void initState() { _tabController = TabController(length: 3, vsync: this); _pageController = PageController(initialPage: 0); super.initState(); } @override void dispose() { _tabController.dispose(); _pageController.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text('Tab Bar Example'), bottom: TabBar( controller: _tabController, tabs: [ Tab(text: 'Tab 1'), Tab(text: 'Tab 2'), Tab(text: 'Tab 3'), ], ), ), body: PageView( controller: _pageController, children: [ Container(color: Colors.red), Container(color: Colors.green), Container(color: Colors.blue), ], onPageChanged: (index) { _tabController.animateTo(index); }, ), ); } ``` 这样,就可以通过监听 `PageView` 的 `onPageChanged` 事件来获取 TabBar 的左右滑动事件了。注意,需要在 `onPageChanged` 事件手动调用 `_tabController.animateTo(index)` 来更新 TabBar 的状态。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值