将颤动的PageView与屏幕左对齐

时间:2019-07-03 14:03:08

标签: flutter dart flutter-layout flutter-sliver

我想用水平分页滚动呈现卡,并且每当看到一个卡时,便能够看到上一张和下一张卡的边框。颤抖的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),
      ),
    );
  }
}

这是上面的代码产生的结果 enter image description here

我希望将PageView对齐到屏幕的左侧,或者至少对齐第一页,即要删除Page 0左侧的空白。我有缺少的PageView参数吗?还是存在一些其他组件,这些组件会产生我想要的结果?

2 个答案:

答案 0 :(得分:3)

在根据自己的需要进行了更深入的分析并检查了PageView小部件的源代码之后,我意识到我需要一个滚动小部件,该滚动小部件可以逐项工作,但与此同时,需要给每个项目分配的空间与普通滚动相同,因此我需要更改普通滚动器的ScrollPhysics。在发现的this帖子中某种程度上描述了颤振中的滚动物理现象,并且与我的需求非常接近,不同之处在于,我需要在当前可见窗口小部件的咬合侧增加空间,而不仅仅是在右侧。

因此,我在帖子中使用了CustomScrollPhysics并以此方式进行了修改(邮政编码中的更改部分被<---->注释所包围:

class CustomScrollPhysics extends ScrollPhysics {
  final double itemDimension;

  CustomScrollPhysics({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(
      ScrollPosition 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

答案 1 :(得分:0)

尝试根据这样的页面添加边距:

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: i == 0
              ? const EdgeInsets.only(right: 10)
              : const EdgeInsets.only(left: 10, right: 10),
          child: Center(child: Text("Page $i")),
        ),
        controller: PageController(),
      ),
    );
  }
}

结果:

enter image description here