Flutter: прокачиваем AppBar & SliverAppBar

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

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

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

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

Задача 1

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

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

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

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

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

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

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

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

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: 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, открыв для себя таким образом варианты реализации собственных виджетов.

FavoriteLoadingДобавить в избранное
Posted in Без рубрики

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

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