Здравствуйте, читатели, добро пожаловать в очередную статью. В предыдущей статье мы подробно рассмотрели виджет dismissible, который можно использовать для реализации функции slide to delete, как в приложении Gmail.
Сегодня в этой статье мы подробно рассмотрим виджет Pageview во flutter.
Что такое виджет просмотра страниц?
Виджет Pageview похож на представление списка. Единственное различие заключается в том, что вид списка используется для отображения списка элементов на одном экране, в то время как вид страницы показывает список страниц, которые нужно прокручивать.
Вид страницы полезен, когда вы хотите показать список полноэкранных изображений, видео или любых других данных. Как вы видели в роликах Instagram, мы видим видео на полной странице, а когда мы прокручиваем ролик до другого ролика, это уже другая страница.
Понимание конструктора представления страницы
PageView PageView({
Key? key,
Axis scrollDirection = Axis.horizontal,
bool reverse = false,
PageController? controller,
ScrollPhysics? physics,
bool pageSnapping = true,
void Function(int)? onPageChanged,
List<Widget> children = const <Widget>[],
DragStartBehavior dragStartBehavior = DragStartBehavior.start,
bool allowImplicitScrolling = false,
String? restorationId,
Clip clipBehavior = Clip.hardEdge,
ScrollBehavior? scrollBehavior,
bool padEnds = true,
})
Как вы видите, в конструкторе есть много различных параметров, которые мы можем настроить в представлении страницы.
Давайте рассмотрим каждый из них по очереди.
Направление прокрутки
Как следует из названия, это направление, в котором мы хотим прокручивать представление страницы. Например, горизонтально, вертикально.
Reverse
Это булево значение, которое изменяет направление прокрутки на противоположное, если установлено в true, и по умолчанию, если false.
Контроллер
Контроллер — это контроллер страницы, который мы можем использовать для изменения поведения представления страницы в соответствии с нашими требованиями, как в представлении списка.
Физика
Физика определяет поведение прокрутки представления страницы. Есть несколько вариантов, например
- NoScrollPhysics
- BouncyScrollPhysics
- и многие другие
Привязка страницы
Это также булево значение, при значении true страница будет автоматически перетаскиваться, чтобы соответствовать экрану во время прокрутки, а при значении false страница будет останавливаться между ними.
onPageChange
onPageChange — это функция обратного вызова с одним параметром int. Эта функция вызывается каждый раз, когда страница изменяется в представлении страницы. Параметр int представляет собой индекс страницы.
Здесь мы будем показывать полоску с индексом страницы при каждом изменении страницы в представлении страницы.
onPageChanged: (int) {
ScaffoldMessenger.of(context)
.showSnackBar(SnackBar(content: Text("Page $int")));
},
Дети
Children — это список виджетов, которые должны быть показаны в режиме просмотра страницы.
children: [
Container(
color: Colors.amberAccent,
),
Container(
color: Colors.red,
),
Container(
color: Colors.blue,
),
Container(
color: Colors.green,
)
],
поведение при запуске перетаскивания (из документации flutter)
Определяет способ обработки поведения при запуске перетаскивания.
Если установлено значение [DragStartBehavior.start], поведение перетаскивания при прокрутке начнется с позиции, где жест перетаскивания выиграл арену. Если установлено значение [DragStartBehavior.down], поведение прокрутки начнется с позиции, где впервые будет обнаружено событие падения.
В целом, установка значения [DragStartBehavior.start] сделает анимацию перетаскивания более плавной, а установка значения [DragStartBehavior.down] сделает поведение перетаскивания немного более реактивным.
По умолчанию поведение начала перетаскивания имеет значение [DragStartBehavior.start].
allowImplicitScrolling
Это также булево поле, которое не имеет никакого влияния на обычных пользователей. Оно будет полезно, если пользователь использует приложение через доступность.
Управляет тем, будут ли страницы виджета реагировать на [RenderObject.showOnScreen], позволяя неявную прокрутку доступности.
Если флаг установлен в false, когда фокус доступности достигает конца текущей страницы и пользователь пытается переместить его на следующий элемент, фокус переместится на следующий виджет за пределами представления страницы.
Если этот флаг установлен в true, когда фокус доступности достигнет конца текущей страницы и пользователь попытается переместить его на следующий элемент, фокус переместится на следующую страницу в представлении страницы.
restoreId
Идентификатор восстановления для сохранения и восстановления смещения прокрутки прокручиваемого элемента.
Если указан идентификатор восстановления, прокручиваемый элемент будет сохранять текущее смещение прокрутки и восстанавливать его при восстановлении состояния.
Смещение прокрутки сохраняется в [RestorationBucket], истребуемом из окружающего [RestorationScope] с использованием предоставленного идентификатора восстановления.
clipBehavior
clipBehavior — это параметр, определяющий, что делать, когда содержимое представления страницы выходит за ее границы. Для клипов доступно несколько вариантов, например
1.Clip.antiAlias
2.Clip.antiAliasWithSaveLayer
3.Clip.hardEdge
ScrollBehavior
Поведение прокрутки используется для определения поведения прокрутки, когда вид страницы находится в начале или внизу. Как вы можете видеть в android, мы видим светящийся огонек, когда мы находимся в конце списка.
padEnds
Добавлять ли подложку в оба конца списка.
Если параметр имеет значение true и [PageController.viewportFraction] < 1.0, то подложка будет добавлена таким образом, что первый и последний дочерние слайсы будут находиться в центре области просмотра при прокрутке до начала или конца, соответственно.
Если [PageController.viewportFraction] >= 1.0, это свойство не имеет эффекта.
По умолчанию это свойство имеет значение true и не должно быть null.
Клон Instagram Reels
Мы сделаем клон экрана роликов только в части пользовательского интерфейса и вместо видео будем использовать изображения, чтобы сделать его менее сложным.
Во-первых, мы хотим добавить виджет в верхней части страницы, поэтому нам нужно использовать стек.
Сначала создадим нижнее имя пользователя и описание экрана барабанов.
Для этого мы будем использовать Строку для добавления изображения и имени пользователя.
Row(
children: const [
CircleAvatar(
backgroundColor: Colors.blue,
radius: 20,
),
SizedBox(
width: 20,
),
Text(
"Name of user",
style: TextStyle(color: Colors.white),
),
],
);
Затем мы добавим эту строку в колонку, содержащую описание видео, а также добавим к ней подложку.
Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisSize: MainAxisSize.min,
children: const [
CircleAvatar(
backgroundColor: Colors.blue,
radius: 20,
),
SizedBox(
width: 20,
),
Text(
"Name of user",
style: TextStyle(color: Colors.white),
),
],
),
const Text(
"This is the very long description of the videos which has many lines This is the very long description of the videos which has many lines This is the very long description of the videos which has many lines This is the very long description of the videos which has many lines",
overflow: TextOverflow.ellipsis,
softWrap: true,
maxLines: 2,
style: TextStyle(color: Colors.white),
),
],
),
);
Теперь вы можете увидеть ошибку переполнения рендера, это происходит потому, что текст описания не имеет горизонтальной границы.
Прежде чем решить эту проблему, давайте добавим кнопки like и share с правой стороны. Просто добавьте новый столбец кнопок и добавьте предыдущий столбец в строку. Здесь я использую не совсем те значки кнопок, которые используются в роликах.
class Buttons extends StatelessWidget {
const Buttons({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Column(
mainAxisSize: MainAxisSize.min,
children: [
IconButton(
onPressed: () {},
icon: const Icon(
Icons.heart_broken_outlined,
color: Colors.white,
)),
IconButton(
onPressed: () {},
icon: const Icon(
Icons.message_outlined,
color: Colors.white,
)),
IconButton(
onPressed: () {},
icon: const Icon(
Icons.share,
color: Colors.white,
)),
],
);
}
}
Добавьте это в предыдущую строку. Теперь, если мы на некоторое время уберем текст описания, наш пользовательский интерфейс будет выглядеть следующим образом.
Теперь верните описание и оберните оба дочерних ряда во flex, придав им соотношение 8 и 1.
Теперь общий код выглядит следующим образом.
class SinglePage extends StatelessWidget {
const SinglePage({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Stack(children: [
Container(color: Colors.red),
Align(
alignment: Alignment.bottomLeft,
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Flexible(
flex: 8,
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisSize: MainAxisSize.min,
children: const [
CircleAvatar(
backgroundColor: Colors.blue,
radius: 20,
),
SizedBox(
width: 20,
),
Text(
"Name of user",
style: TextStyle(color: Colors.white),
),
],
),
const Text(
"This is the very long description of the videos which has many lines This is the very long description of the videos which has many lines This is the very long description of the videos which has many lines This is the very long description of the videos which has many lines",
overflow: TextOverflow.ellipsis,
softWrap: true,
maxLines: 2,
style: TextStyle(color: Colors.white),
),
],
),
),
const Flexible(flex: 1, child: Buttons())
],
),
),
)
]);
}
}
class Buttons extends StatelessWidget {
const Buttons({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Column(
mainAxisSize: MainAxisSize.min,
children: [
IconButton(
onPressed: () {},
icon: const Icon(
Icons.heart_broken_outlined,
color: Colors.white,
)),
IconButton(
onPressed: () {},
icon: const Icon(
Icons.message_outlined,
color: Colors.white,
)),
IconButton(
onPressed: () {},
icon: const Icon(
Icons.share,
color: Colors.white,
)),
],
);
}
}
А пользовательский интерфейс выглядит следующим образом.
Теперь осталось добавить эту страницу в режим просмотра и задать произвольные цвета для фона.
Окончательный код
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
class HomeScreen extends StatelessWidget {
HomeScreen({Key? key}) : super(key: key);
final controller = PageController();
@override
Widget build(BuildContext context) {
return PageView(
controller: controller,
scrollDirection: Axis.vertical,
padEnds: false,
children: const [
SinglePage(
color: Colors.red,
),
SinglePage(
color: Colors.green,
),
SinglePage(
color: Colors.yellow,
),
SinglePage(color: Colors.amber),
SinglePage(
color: Colors.purple,
),
],
);
}
}
class SinglePage extends StatelessWidget {
const SinglePage({
Key? key,
required this.color,
}) : super(key: key);
final Color color;
@override
Widget build(BuildContext context) {
return Stack(children: [
Container(color: color),
Align(
alignment: Alignment.bottomLeft,
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Flexible(
flex: 8,
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisSize: MainAxisSize.min,
children: const [
CircleAvatar(
backgroundColor: Colors.blue,
radius: 20,
),
SizedBox(
width: 20,
),
Text(
"Name of user",
style: TextStyle(color: Colors.white),
),
],
),
const Text(
"This is the very long description of the videos which has many lines This is the very long description of the videos which has many lines This is the very long description of the videos which has many lines This is the very long description of the videos which has many lines",
overflow: TextOverflow.ellipsis,
softWrap: true,
maxLines: 2,
style: TextStyle(color: Colors.white),
),
],
),
),
const Flexible(flex: 1, child: Buttons())
],
),
),
)
]);
}
}
class Buttons extends StatelessWidget {
const Buttons({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Column(
mainAxisSize: MainAxisSize.min,
children: [
IconButton(
onPressed: () {},
icon: const Icon(
Icons.heart_broken_outlined,
color: Colors.white,
)),
IconButton(
onPressed: () {},
icon: const Icon(
Icons.message_outlined,
color: Colors.white,
)),
IconButton(
onPressed: () {},
icon: const Icon(
Icons.share,
color: Colors.white,
)),
],
);
}
}
Окончательный пользовательский интерфейс