В прошлом посте мы разобрали slivers и зачем они нужны. Сегодня поговорим про SliverPersistentHeader.
SliverPersistentHeader — это виджет, который меняет свою высоту при прокрутке. Его можно использовать, например, для создания сжимающихся шапок в профилях пользователей или в деталях товара.
Так как SliverPersistentHeader сам по себе не знает, как отображать содержимое, он использует делегат — SliverPersistentHeaderDelegate. Он управляет его поведением и внешним видом через:
✔️ build — определяет, как выглядит виджет на каждом этапе прокрутки
✔️ maxExtent — задает максимальную высоту заголовка в его развернутом состоянии
✔️ minExtent — определяет минимальную высоту, до которой заголовок сжимается
✔️ shouldRebuild — условие для перерисовки делегата
Внутри метода build доступны параметры:
▪️ double shrinkOffset — показывает, насколько сильно заголовок сжался (от 0 до maxExtent — minExtent)
▪️ bool overlapsContent — указывает, перекрывает ли содержимое последующие сливеры
Для управления поведением заголовка при прокрутке у SliverPersistentHeader есть параметры pinned и floating:
▫️ pinned: true — шапка остается закрепленной, когда прокрутка достигает минимальной высоты
▫️ floating: true — заголовок появляется сразу, как только пользователь начинает прокручивать страницу вверх, даже если прокрутка еще не дошла до самого верха
Пример использования
Давайте реализуем пример «сжимающейся» шапки профиля, где аватар и заголовок плавно меняют свой размер и положение при скролле.
Шаг 1: Подготовка виджетов
Сначала создадим три вспомогательных виджета, которые будут анимироваться:
1. ColoredContainer для затемнения фона
class ColoredContainer extends StatelessWidget {
const ColoredContainer({super.key, required this.progress});
final double progress;
@override
Widget build(BuildContext context) {
return Opacity(
opacity: progress,
child: const ColoredBox(color: Colors.black54),
);
}
}2. HeaderTitle для заголовка, который будет смещаться к центру
class HeaderTitle extends StatelessWidget {
const HeaderTitle({super.key, required this.progress});
final double progress;
@override
Widget build(BuildContext context) {
return AnimatedContainer(
duration: const Duration(milliseconds: 100),
padding: EdgeInsets.lerp(
const EdgeInsets.symmetric(horizontal: 16, vertical: 16),
const EdgeInsets.only(bottom: 16),
progress,
),
alignment: Alignment.lerp(
Alignment.bottomLeft,
Alignment.bottomCenter,
progress,
),
child: Text(
'Profile',
style: TextStyle.lerp(
Theme.of(context).textTheme.titleLarge!.copyWith(color: Colors.white),
Theme.of(context).textTheme.titleMedium!.copyWith(color: Colors.white),
progress,
),
),
);
}
}Продолжение — в комментариях 👇