将Flutter的PageView对齐到屏幕左侧

17

我想使用水平页面滚动来渲染卡片,并且能够在每次可见时看到前一个和后一个卡片的边框。Flutter的PageView小部件几乎可以产生我想要的结果,但它没有按照我想要的方式对齐页面。这是我的代码:

import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'PageView Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(title: 'PageView Alignment'),
    );
  }
}

class MyHomePage extends StatelessWidget {
  const MyHomePage({Key key, this.title}) : super(key: key);

  final String title;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text(title)),
      body: PageView.builder(
        itemCount: 5,
        itemBuilder: (context, i) => Container(
              color: Colors.blue,
              margin: const EdgeInsets.only(right: 10),
              child: Center(child: Text("Page $i")),
            ),
        controller: PageController(viewportFraction: .7),
      ),
    );
  }
}

这是上面代码所产生的结果: 输入图像描述

我希望PageView对齐到屏幕左侧,或者至少对于第一页,即去掉Page 0左侧的空白空间。我是否遗漏了任何PageView参数?或者是否存在其他组件可以实现我想要的结果?

3个回答

35

PageViewpadEnds 属性设置为 false 应该就可以解决问题了。

......这个属性可能过去并不存在。


4
老兄,你救了我的一天。 - Nantaphop
正确答案! - undefined

30

在深入分析了自己的需求并检查了 PageView 组件的源代码后,我发现需要一个逐项滚动的组件,但与此同时我需要每个项目获得与正常滚动相同的空间,因此我需要更改正常滚动器的 ScrollPhysics。我找到了这篇文章,其中介绍了 Flutter 中的滚动物理效果并且与我的需求比较接近,不同之处在于我需要在当前可见小部件的两侧都添加间距,而不仅仅是右侧。

因此,我按照这篇文章中的 CustomScrollPhysics 并对其进行了修改,如下所示(从文章中更改的部分用 <----> 注释标出):

class CustomScrollPhysics extends ScrollPhysics {
  final double itemDimension;

  const CustomScrollPhysics(
      {required this.itemDimension, ScrollPhysics? parent})
      : super(parent: parent);

  @override
  CustomScrollPhysics applyTo(ScrollPhysics? ancestor) {
    return CustomScrollPhysics(
        itemDimension: itemDimension, parent: buildParent(ancestor));
  }

  double _getPage(ScrollMetrics position, double portion) {
    // <--
    return (position.pixels + portion) / itemDimension;
    // -->
  }

  double _getPixels(double page, double portion) {
    // <--
    return (page * itemDimension) - portion;
    // -->
  }

  double _getTargetPixels(
    ScrollMetrics position,
    Tolerance tolerance,
    double velocity,
    double portion,
  ) {
    // <--
    double page = _getPage(position, portion);
    // -->
    if (velocity < -tolerance.velocity) {
      page -= 0.5;
    } else if (velocity > tolerance.velocity) {
      page += 0.5;
    }
    // <--
    return _getPixels(page.roundToDouble(), portion);
    // -->
  }

  @override
  Simulation? createBallisticSimulation(
      ScrollMetrics position, double velocity) {
    // If we're out of range and not headed back in range, defer to the parent
    // ballistics, which should put us back in range at a page boundary.
    if ((velocity <= 0.0 && position.pixels <= position.minScrollExtent) ||
        (velocity >= 0.0 && position.pixels >= position.maxScrollExtent)) {
      return super.createBallisticSimulation(position, velocity);
    }

    final Tolerance tolerance = this.tolerance;
    // <--
    final portion = (position.extentInside - itemDimension) / 2;
    final double target =
        _getTargetPixels(position, tolerance, velocity, portion);
    // -->
    if (target != position.pixels) {
      return ScrollSpringSimulation(spring, position.pixels, target, velocity,
          tolerance: tolerance);
    }
    return null;
  }

  @override
  bool get allowImplicitScrolling => false;
}

总之,我的做法是利用当前可见小部件留下的额外空间的一半(即(position.extentInside - itemDimension) / 2),将其添加到基于滚动位置的页面计算中,允许小部件比可见滚动大小更小,但将整个范围视为单个页面,并将其减去基于页面的滚动像素计算,防止在其两侧的小部件的一半可见部分之前或之后放置“页面”。

另一个变化是itemDimension不是滚动范围除以元素数量,我需要这个值成为滚动方向上每个小部件的大小。

这就是我最终得到的结果:

final result

当然,这种实现有一些限制:

  • 滚动方向上每个元素的大小必须固定,如果单个元素大小不同,则整个滚动行为不稳定
  • 大小必须包括填充,以防有填充,否则它会产生与具有不同大小的小部件相同的效果

我没有专注于解决这些限制并拥有更完整的小部件,因为这些限制在我需要此小部件的情况下是确保的。这里是上面示例的完整代码。

https://gist.github.com/rolurq/5db4c0cb7db66cf8f5a59396faeec7fa


2
点赞这位大佬。非常好的解决方案。我希望能在PageView中看到它被实现。 - vanlooverenkoen
谢谢@KoenVanLooveren!! 我同意你的观点,这应该在PageView中实现,我不知道为什么它没有被实现,在许多应用程序中我都看到了这种行为。 - Rolando Urquiza
那是我之前尝试过的。我无法解决它。对我来说,ScrollPhysics的问题在于导航到特定页面。这可以通过PageViewController实现。 - vanlooverenkoen
1
我会将CustomScrollPhysics重命名为SnapScrollPhysics或类似的名称。 - Juliano
1
在iOS设备上,默认物理效果为BouncingScrollPhysics,返回super.createBallisticSimulation(position, velocity)的条件应包含position.outOfRange,或者应重写方法applyBoundaryConditions以避免iOS弹跳效果引起的意外错误。 - ZhangLei
显示剩余4条评论

0

Flutter新增了一个名为padEnds的属性,将其设置为False


以上答案的抄袭 https://dev59.com/lFMI5IYBdhLWcg3wLYQu#69151759 - STA
你的回答可以通过提供更多支持信息来改进。请编辑以添加进一步的细节,例如引用或文档,以便他人可以确认你的答案是正确的。您可以在帮助中心找到有关如何编写良好答案的更多信息。 - Community

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接