PageView свайп в одну сторону

Егор Долгов · 15.08.2019
This article was written for an older Flutter version and is being updated. Some code may not compile on the current version.

По умолчанию виджет PageView прокручивается с помощью касания в обе стороны.

Чтобы запретить прокрутку с помощью касания, нужно в параметре physics задать значение NeverScrollableScrollPhysics() .

PageView(
  physics: NeverScrollableScrollPhysics(),
  children: [
    MyPageFirst(),
    MyPageSecond(),
    ...]
)//PageView

И тогда чтобы переходить от страницы к странице нужен объект PageController , заданный в параметре controller .

_pageController = PageController(initialPage: 0);
PageView(
  physics: NeverScrollableScrollPhysics(),
  controller: _pageController,
  children: [
    MyPageFirst(),
    MyPageSecond(),
    ...]
)//PageView

Для перехода к другой странице нужно вызвать метод pageController.jumpToPage()

_pageController.jumpToPage(1);

Для анимированного перехода нужно использовать pageController.animateToPage()

_pageController.animateToPage(1, duration: Duration(milliseconds: 600), curve: Curves.linear);

Односторонняя прокрутка

Но что делать если мы хотим, чтобы пользователь мог прокрутить касанием только в одну сторону - например назад, для возвращения к предыдущему экрану. А к следующему экрану только с помощью бизнес логики приложения.

С помощью метода addListener зарегистрируем обработчик событий для объекта класса PageController .

_pageController.addListener((){
  print('Текущее состояние прокрутки ${_pageController.page}');//тип double
  print('Состояние прокрутки ${_pageController.position.userScrollDirection.index}');
  // 0 - прокрутка происходит с помощью функций jumpToPage или animateToPage
  // 1 - пользователь прокручивает "назад", к предыдущей странице
  // 2 - пользователь прокручивает "вперед", к следующей странице
});

Такой обработчик события будет выводить в консоль сообщения, с текущим состоянием, во время прокрутки PageView.

Т.к. NeverScrollableScrollPhysics() блокирует все пользовательские жесты, мы не будем его использовать, а блокировать свайп вперед будем в обработчике событий.

Код обработчика односторонней прокрутки

_pageController.addListener((){
      if (_pageController.position.userScrollDirection.index == 2 && indexPage < _pageController.page)  _pageController.jumpToPage(indexPage);
      else if (_pageController.position.userScrollDirection.index == 1 && _pageController.page == _pageController.page.roundToDouble()) {
        indexPage = _pageController.page.toInt();
      }
});

Что происходит в обработчике для односторонней прокрутки

indexPage - в этой переменной должен быть номер страницы, которую должен видеть пользователь.

  1. Если пользователь делает свайп для прокрутки вперед
_pageController.position.userScrollDirection.index == 2
  1. И если текущая позиция меньше той к которой может привести свайп вперед
&& indexPage < _pageController.page // это условие нужно чтобы небыло резкого перехода, когда мы делаем не доконца свайп назад, и возможно захотим "отменить действие свайпа"
  1. Делаем переход без анимации к установленной странице, чтобы прервать и отменить свайп вперед
_pageController.jumpToPage(indexPage);
  1. Если пользователь выполняет свайп назад
_pageController.position.userScrollDirection.index == 1
  1. И если текущее состояние положения прокрутки равно целому значения
&& _pageController.page == _pageController.page.roundToDouble() // Значит переход завершен
  1. Изменяем номер страницы которая должна быть открыта
indexPage = _pageController.page.toInt();

Функция бизнес логики для смены страницы

setPage(int page) {
  if (indexPage == page) return;
  indexPage = page;
  _pageController.animateToPage(indexPage, duration: Duration(milliseconds: 600), curve: Curves.linear);
}

Метод onPageChanged в PageView

В виджете PageView можно задать метод onPageChanged

PageView(
  onPageChanged:(int page){indexPage = page;},
  physics: NeverScrollableScrollPhysics(),
  controller: _pageController,
  children: <Widget>[
  MyPageFirst(),
  MyPageSecond(),
  ...]
)//PageView

В данном примере не рекомендуем его использовать, и выполнять все проверки и смену состояния в одном месте: pageController.addListener((){}) .