Выровняйте флаттер PageView по левому краю экрана

Я хочу отображать карты с горизонтальной прокруткой и иметь возможность видеть границы предыдущей и следующей карты каждый раз, когда она видна. Виджет 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, который мне не хватает? Или существует какой-то другой компонент, который дает результат, который я ищу?


person Rolando Urquiza    schedule 03.07.2019    source источник


Ответы (1)


Проведя более глубокий анализ моих собственных потребностей и проверив исходный код виджета PageView, я понял, что мне нужен виджет с прокруткой, который работает по элементам, но в то же время мне нужно, чтобы пространство для каждого элемент был таким же, как и обычный скроллер, поэтому мне нужно было изменить ScrollPhysics обычного скроллера. В найденном этом сообщении, описывающем физику прокрутки при флаттере, можно найти до некоторой степени и было близко к моим потребностям, разница заключалась в том, что мне нужно было добавить пространство по бокам текущего видимого виджета, а не только справа.

Итак, я взял 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 - это не размер прокрутки, деленный на количество элемента, мне нужно, чтобы это значение было размером каждого виджета в направлении прокрутки.

Вот что у меня получается:

окончательный результат

Конечно, у этой реализации есть некоторые ограничения:

  • Размер каждого элемента в направлении прокрутки должен быть фиксированным, если один элемент имеет другой размер, тогда вся прокрутка ведет себя хаотично.
  • Размер должен включать отступы, если они есть, иначе это будет иметь тот же эффект, что и виджеты разных размеров.

Я не сосредотачивался на решении этих ограничений и создании более полного виджета, потому что эти ограничения гарантированы в случае, если мне нужен этот виджет. Вот полный код приведенного выше примера.

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

person Rolando Urquiza    schedule 16.08.2019
comment
ГОЛОСОВАТЬ ЭТОГО ЧУВКА. Очень хорошее решение. Я хотел бы, чтобы это было реализовано в PageView - person Koen Van Looveren; 03.10.2019
comment
Спасибо @KoenVanLooveren !! Я согласен с вами, это должно быть реализовано в PageView, я не знаю, почему это не так, я видел такое поведение во многих приложениях - person Rolando Urquiza; 03.10.2019
comment
В самом деле, может быть, вы могли бы попытаться реализовать это в самом просмотре страницы и объединить его с основной веткой? - person Koen Van Looveren; 03.10.2019
comment
Вы правы, и я сделаю это, мне нужно немного почистить и поработать с ограничениями. Кроме того, я думаю, что смена ленты, используемой при установке viewportFraction, может работать без необходимости иметь дело с ScrollPhysics - person Rolando Urquiza; 03.10.2019
comment
Это то, что я пробовал раньше. Я не мог понять этого. Проблема с физикой прокрутки для меня заключалась в переходе на определенную страницу. Это возможно с помощью PageViewController - person Koen Van Looveren; 04.10.2019
comment
Код фрагмента недействителен: itemExtent не определен. Пришлось использовать код из сути. - person Andrey Gordeev; 06.12.2019
comment
@AndreyGordeev, извините, это была ошибка наименования, itemExtent должно быть itemDimension Я редактировал фрагмент кода, спасибо, что заметили. - person Rolando Urquiza; 06.12.2019
comment
Я бы переименовал CustomScrollPhysics в SnapScrollPhysics или что-то в этом роде. - person Juliano; 08.01.2020
comment
На устройствах iOS, где физика по умолчанию - BouncingScrollPhysics, условие возврата super.createBallisticSimulation(position, velocity) должно содержать position.outOfRange, или метод applyBoundaryConditions следует переопределить, чтобы избежать неожиданной ошибки, вызванной эффектом отскока iOS. - person ZhangLei; 03.07.2020