Хабрахабр

[Перевод] Flutter: прокачиваем AppBar & SliverAppBar

Во Flutter для создания панели инструментов используется хорошо всем известный AppBar, ну а когда нам нужна динамическая панель инструментов, которая покажет контент при свайпе, мы используем отличный виджет SliverAppBar.

Оба виджета позволяют сделать приложение чуточку красивее, что во Flutter, без сомнений, весьма просто.

Я видел много вопросов на StackOverflow и в группах Facebook о том, как можно изменить AppBar и SliverAppBar с точки зрения поведения или дизайна.

Давайте рассмотрим две задачи.

Задача 1

Мы хотим добавить Drawer (боковое меню), на открытие которого AppBar будет реагировать. Мы хотим создать, не привинченный к верху экрана AppBar, но не так как делаем это обычно. Вот и всё: наш собственный AppBar с нужными нам размерами.

Заглянув в исходный код, мы видим параметр appBar в Scaffold, видим, что он принимает виджет типа PreferredSizeWidget, теперь просматриваем исходный код AppBar и узнаём, что это только StatefulWidget, который реализует PreferredSizeWidget. Проблема в том что, как мы знаем, у AppBar есть размер по умолчанию, и изменить его мы не можем.

Дело за малым: просто создать наш собственный виджет, который реализует PreferredSizeWidget.

Вот что мы хотим

Как бы так сделать, чтобы при нажатии кнопки меню нашего AppBar открывалось боковое меню.

Мы можем сделать это двумя способами:

Используя `AppBar`

Вот так AppBar сможет контролировать открытие бокового меню внутри Scaffold.

class Sample1 extends StatelessWidget
} class MyCustomAppBar extends StatelessWidget implements PreferredSizeWidget { final double height; const MyCustomAppBar({ Key key, @required this.height, }) : super(key: key); @override Widget build(BuildContext context) { return Column( children: [ Container( color: Colors.grey[300], child: Padding( padding: EdgeInsets.all(30), child: AppBar( title: Container( color: Colors.white, child: TextField( decoration: InputDecoration( hintText: "Search", contentPadding: EdgeInsets.all(10), ), ), ), actions: [ IconButton( icon: Icon(Icons.verified_user), onPressed: () => null, ), ], ) , ), ), ], ); } @override Size get preferredSize => Size.fromHeight(height);
}

Используя кастомный виджет

Здесь у нас больше гибкости и можно использовать GlobalKey типа ScaffoldState или InheritedWidget от Scaffold, получив таким образом доступ к методам состояния для открытия Drawer.

import 'package:flutter/material.dart'; class Sample1 extends StatelessWidget { @override Widget build(BuildContext context) { return SafeArea( child: Scaffold( drawer: Drawer(), appBar: MyCustomAppBar( height: 150, ), body: Center( child: FlutterLogo( size: MediaQuery.of(context).size.width / 2, ), ), ), ); }
} class MyCustomAppBar extends StatelessWidget implements PreferredSizeWidget { final double height; const MyCustomAppBar({ Key key, @required this.height, }) : super(key: key); @override Widget build(BuildContext context) { return Column( children: [ Container( color: Colors.grey[300], child: Padding( padding: EdgeInsets.all(30), child: Container( color: Colors.red, padding: EdgeInsets.all(5), child: Row(children: [ IconButton( icon: Icon(Icons.menu), onPressed: () { Scaffold.of(context).openDrawer(); }, ), Expanded( child: Container( color: Colors.white, child: TextField( decoration: InputDecoration( hintText: "Search", contentPadding: EdgeInsets.all(10), ), ), ), ), IconButton( icon: Icon(Icons.verified_user), onPressed: () => null, ), ]), ), ), ), ], ); } @override Size get preferredSize => Size.fromHeight(height);
}

Результат

Давайте рассмотрим вторую задачу для SliverAppBar. Просто, не так ли?

Задача 2

Как мы знаем, SliverAppBar работает следующим образом:

Что мы хотим, так это поместить Card, встроенную в наш SliverAppBar, как показано на следующем рисунке.

Подождите, но содержимое внутри SliverAppBar обрезается, поэтому не может выйти за рамки, что же делать?

Без паники, давайте посмотрим исходный код SliverAppBar и, о сюрприз, это StatefulWidget, использующий внутри SliverPersistentHeader, вот и весь секрет.

Мы создадим свой собственный SliverPersistentHeaderDelegate, чтобы использовать SliverPersistentHeader.

class Sample2 extends StatelessWidget { @override Widget build(BuildContext context) { return SafeArea( child: Material( child: CustomScrollView( slivers: [ SliverPersistentHeader( delegate: MySliverAppBar(expandedHeight: 200), pinned: true, ), SliverList( delegate: SliverChildBuilderDelegate( (_, index) => ListTile( title: Text("Index: $index"), ), ), ) ], ), ), ); }
} class MySliverAppBar extends SliverPersistentHeaderDelegate { final double expandedHeight; MySliverAppBar({@required this.expandedHeight}); @override Widget build( BuildContext context, double shrinkOffset, bool overlapsContent) { return Stack( fit: StackFit.expand, overflow: Overflow.visible, children: [ Image.network( "https://images.pexels.com/photos/396547/pexels-photo-396547.jpeg?auto=compress&cs=tinysrgb&dpr=1&w=500", fit: BoxFit.cover, ), Center( child: Opacity( opacity: shrinkOffset / expandedHeight, child: Text( "MySliverAppBar", style: TextStyle( color: Colors.white, fontWeight: FontWeight.w700, fontSize: 23, ), ), ), ), Positioned( top: expandedHeight / 2 - shrinkOffset, left: MediaQuery.of(context).size.width / 4, child: Opacity( opacity: (1 - shrinkOffset / expandedHeight), child: Card( elevation: 10, child: SizedBox( height: expandedHeight, width: MediaQuery.of(context).size.width / 2, child: FlutterLogo(), ), ), ), ), ], ); } @override double get maxExtent => expandedHeight; @override double get minExtent => kToolbarHeight; @override bool shouldRebuild(SliverPersistentHeaderDelegate oldDelegate) => true;
}

Результат

Готово, обе задачи решены.

Примеры лежат в этом репозитории.

Вывод

Часто мы отчаиваемся, когда не находим каких-нибудь свойств у виджета, но надо лишь посмотреть его исходный код, чтобы понять как он реализован во Flutter, открыв для себя таким образом варианты реализации собственных виджетов.

Теги
Показать больше

Похожие статьи

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

Кнопка «Наверх»
Закрыть