// Copyright 2014 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'package:flutter/material.dart'; import 'package:flutter/scheduler.dart' show timeDilation; void main() { runApp( const ComplexLayoutApp() ); } enum ScrollMode { complex, tile } class ComplexLayoutApp extends StatefulWidget { const ComplexLayoutApp({super.key}); @override ComplexLayoutAppState createState() => ComplexLayoutAppState(); static ComplexLayoutAppState? of(BuildContext context) => context.findAncestorStateOfType<ComplexLayoutAppState>(); } class ComplexLayoutAppState extends State<ComplexLayoutApp> { @override Widget build(BuildContext context) { return MaterialApp( theme: lightTheme ? ThemeData.light() : ThemeData.dark(), title: 'Advanced Layout', home: scrollMode == ScrollMode.complex ? const ComplexLayout() : const TileScrollLayout()); } bool _lightTheme = true; bool get lightTheme => _lightTheme; set lightTheme(bool value) { setState(() { _lightTheme = value; }); } ScrollMode _scrollMode = ScrollMode.complex; ScrollMode get scrollMode => _scrollMode; set scrollMode(ScrollMode mode) { setState(() { _scrollMode = mode; }); } void toggleAnimationSpeed() { setState(() { timeDilation = (timeDilation != 1.0) ? 1.0 : 5.0; }); } } class TileScrollLayout extends StatelessWidget { const TileScrollLayout({ super.key }); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('Tile Scrolling Layout')), body: ListView.builder( key: const Key('tiles-scroll'), itemCount: 200, itemBuilder: (BuildContext context, int index) { return Padding( padding: const EdgeInsets.all(5.0), child: Material( elevation: (index % 5 + 1).toDouble(), color: Colors.white, child: const IconBar(), ), ); }, ), drawer: const GalleryDrawer(), ); } } class ComplexLayout extends StatefulWidget { const ComplexLayout({ super.key }); @override ComplexLayoutState createState() => ComplexLayoutState(); static ComplexLayoutState? of(BuildContext context) => context.findAncestorStateOfType<ComplexLayoutState>(); } class ComplexLayoutState extends State<ComplexLayout> { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('Advanced Layout'), actions: <Widget>[ IconButton( icon: const Icon(Icons.create), tooltip: 'Search', onPressed: () { print('Pressed search'); }, ), const TopBarMenu(), ], ), body: Column( children: <Widget>[ Expanded( child: ListView.builder( key: const Key('complex-scroll'), // this key is used by the driver test controller: ScrollController(), // So that the scroll offset can be tracked itemBuilder: (BuildContext context, int index) { if (index.isEven) return FancyImageItem(index, key: PageStorageKey<int>(index)); else return FancyGalleryItem(index, key: PageStorageKey<int>(index)); }, ), ), const BottomBar(), ], ), drawer: const GalleryDrawer(), ); } } class TopBarMenu extends StatelessWidget { const TopBarMenu({super.key}); @override Widget build(BuildContext context) { return PopupMenuButton<String>( onSelected: (String value) { print('Selected: $value'); }, itemBuilder: (BuildContext context) => <PopupMenuItem<String>>[ const PopupMenuItem<String>( value: 'Friends', child: MenuItemWithIcon(Icons.people, 'Friends', '5 new'), ), const PopupMenuItem<String>( value: 'Events', child: MenuItemWithIcon(Icons.event, 'Events', '12 upcoming'), ), const PopupMenuItem<String>( value: 'Events', child: MenuItemWithIcon(Icons.group, 'Groups', '14'), ), const PopupMenuItem<String>( value: 'Events', child: MenuItemWithIcon(Icons.image, 'Pictures', '12'), ), const PopupMenuItem<String>( value: 'Events', child: MenuItemWithIcon(Icons.near_me, 'Nearby', '33'), ), const PopupMenuItem<String>( value: 'Friends', child: MenuItemWithIcon(Icons.people, 'Friends', '5'), ), const PopupMenuItem<String>( value: 'Events', child: MenuItemWithIcon(Icons.event, 'Events', '12'), ), const PopupMenuItem<String>( value: 'Events', child: MenuItemWithIcon(Icons.group, 'Groups', '14'), ), const PopupMenuItem<String>( value: 'Events', child: MenuItemWithIcon(Icons.image, 'Pictures', '12'), ), const PopupMenuItem<String>( value: 'Events', child: MenuItemWithIcon(Icons.near_me, 'Nearby', '33'), ), ], ); } } class MenuItemWithIcon extends StatelessWidget { const MenuItemWithIcon(this.icon, this.title, this.subtitle, {super.key}); final IconData icon; final String title; final String subtitle; @override Widget build(BuildContext context) { return Row( children: <Widget>[ Icon(icon), Padding( padding: const EdgeInsets.only(left: 8.0, right: 8.0), child: Text(title), ), Text(subtitle, style: Theme.of(context).textTheme.caption), ], ); } } class FancyImageItem extends StatelessWidget { const FancyImageItem(this.index, {super.key}); final int index; @override Widget build(BuildContext context) { return ListBody( children: <Widget>[ UserHeader('Ali Connors $index'), const ItemDescription(), const ItemImageBox(), const InfoBar(), const Padding( padding: EdgeInsets.symmetric(horizontal: 8.0), child: Divider(), ), const IconBar(), const FatDivider(), ], ); } } class FancyGalleryItem extends StatelessWidget { const FancyGalleryItem(this.index, {super.key}); final int index; @override Widget build(BuildContext context) { return ListBody( children: <Widget>[ const UserHeader('Ali Connors'), ItemGalleryBox(index), const InfoBar(), const Padding( padding: EdgeInsets.symmetric(horizontal: 8.0), child: Divider(), ), const IconBar(), const FatDivider(), ], ); } } class InfoBar extends StatelessWidget { const InfoBar({super.key}); @override Widget build(BuildContext context) { return Padding( padding: const EdgeInsets.all(8.0), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: <Widget>[ const MiniIconWithText(Icons.thumb_up, '42'), Text('3 Comments', style: Theme.of(context).textTheme.caption), ], ), ); } } class IconBar extends StatelessWidget { const IconBar({super.key}); @override Widget build(BuildContext context) { return Padding( padding: const EdgeInsets.only(left: 16.0, right: 16.0), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: const <Widget>[ IconWithText(Icons.thumb_up, 'Like'), IconWithText(Icons.comment, 'Comment'), IconWithText(Icons.share, 'Share'), ], ), ); } } class IconWithText extends StatelessWidget { const IconWithText(this.icon, this.title, {super.key}); final IconData icon; final String title; @override Widget build(BuildContext context) { return Row( mainAxisSize: MainAxisSize.min, children: <Widget>[ IconButton( icon: Icon(icon), onPressed: () { print('Pressed $title button'); }, ), Text(title), ], ); } } class MiniIconWithText extends StatelessWidget { const MiniIconWithText(this.icon, this.title, {super.key}); final IconData icon; final String title; @override Widget build(BuildContext context) { return Row( mainAxisSize: MainAxisSize.min, children: <Widget>[ Padding( padding: const EdgeInsets.only(right: 8.0), child: Container( width: 16.0, height: 16.0, decoration: BoxDecoration( color: Theme.of(context).primaryColor, shape: BoxShape.circle, ), child: Icon(icon, color: Colors.white, size: 12.0), ), ), Text(title, style: Theme.of(context).textTheme.caption), ], ); } } class FatDivider extends StatelessWidget { const FatDivider({super.key}); @override Widget build(BuildContext context) { return Container( height: 8.0, color: Theme.of(context).dividerColor, ); } } class UserHeader extends StatelessWidget { const UserHeader(this.userName, {super.key}); final String userName; @override Widget build(BuildContext context) { return Padding( padding: const EdgeInsets.all(8.0), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: <Widget>[ const Padding( padding: EdgeInsets.only(right: 8.0), child: Image( image: AssetImage('packages/flutter_gallery_assets/people/square/ali.png'), width: 32.0, height: 32.0, ), ), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: <Widget>[ RichText(text: TextSpan( style: Theme.of(context).textTheme.bodyText2, children: <TextSpan>[ TextSpan(text: userName, style: const TextStyle(fontWeight: FontWeight.bold)), const TextSpan(text: ' shared a new '), const TextSpan(text: 'photo', style: TextStyle(fontWeight: FontWeight.bold)), ], )), Row( children: <Widget>[ Text('Yesterday at 11:55 • ', style: Theme.of(context).textTheme.caption), Icon(Icons.people, size: 16.0, color: Theme.of(context).textTheme.caption!.color), ], ), ], ), ), const TopBarMenu(), ], ), ); } } class ItemDescription extends StatelessWidget { const ItemDescription({super.key}); @override Widget build(BuildContext context) { return const Padding( padding: EdgeInsets.all(8.0), child: Text('Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.'), ); } } class ItemImageBox extends StatelessWidget { const ItemImageBox({super.key}); @override Widget build(BuildContext context) { return Padding( padding: const EdgeInsets.all(8.0), child: Card( child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: <Widget>[ Stack( children: <Widget>[ const SizedBox( height: 230.0, child: Image( image: AssetImage('packages/flutter_gallery_assets/places/india_chettinad_silk_maker.png') ), ), Theme( data: ThemeData.dark(), child: Row( mainAxisAlignment: MainAxisAlignment.end, children: <Widget>[ IconButton( icon: const Icon(Icons.edit), onPressed: () { print('Pressed edit button'); }, ), IconButton( icon: const Icon(Icons.zoom_in), onPressed: () { print('Pressed zoom button'); }, ), ], ), ), Positioned( bottom: 4.0, left: 4.0, child: Container( decoration: BoxDecoration( color: Colors.black54, borderRadius: BorderRadius.circular(2.0), ), padding: const EdgeInsets.all(4.0), child: RichText( text: const TextSpan( style: TextStyle(color: Colors.white), children: <TextSpan>[ TextSpan( text: 'Photo by ' ), TextSpan( style: TextStyle(fontWeight: FontWeight.bold), text: 'Chris Godley', ), ], ), ), ), ), ], ) , Padding( padding: const EdgeInsets.all(8.0), child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: <Widget>[ Text('Artisans of Southern India', style: Theme.of(context).textTheme.bodyText1), Text('Silk Spinners', style: Theme.of(context).textTheme.bodyText2), Text('Sivaganga, Tamil Nadu', style: Theme.of(context).textTheme.caption), ], ), ), ], ), ), ); } } class ItemGalleryBox extends StatelessWidget { const ItemGalleryBox(this.index, {super.key}); final int index; @override Widget build(BuildContext context) { final List<String> tabNames = <String>[ 'A', 'B', 'C', 'D', ]; return SizedBox( height: 200.0, child: DefaultTabController( length: tabNames.length, child: Column( children: <Widget>[ Expanded( child: TabBarView( children: tabNames.map<Widget>((String tabName) { return Container( key: PageStorageKey<String>(tabName), child: Padding( padding: const EdgeInsets.all(8.0), child: Card( child: Column( children: <Widget>[ Expanded( child: Container( color: Theme.of(context).primaryColor, child: Center( child: Text(tabName, style: Theme.of(context).textTheme.headline5!.copyWith(color: Colors.white)), ), ), ), Row( children: <Widget>[ IconButton( icon: const Icon(Icons.share), onPressed: () { print('Pressed share'); }, ), IconButton( icon: const Icon(Icons.event), onPressed: () { print('Pressed event'); }, ), Expanded( child: Padding( padding: const EdgeInsets.only(left: 8.0), child: Text('This is item $tabName'), ), ), ], ), ], ), ), ), ); }).toList(), ), ), const TabPageSelector(), ], ), ), ); } } class BottomBar extends StatelessWidget { const BottomBar({super.key}); @override Widget build(BuildContext context) { return Container( decoration: BoxDecoration( border: Border( top: BorderSide( color: Theme.of(context).dividerColor, ), ), ), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: const <Widget>[ BottomBarButton(Icons.new_releases, 'News'), BottomBarButton(Icons.people, 'Requests'), BottomBarButton(Icons.chat, 'Messenger'), BottomBarButton(Icons.bookmark, 'Bookmark'), BottomBarButton(Icons.alarm, 'Alarm'), ], ), ); } } class BottomBarButton extends StatelessWidget { const BottomBarButton(this.icon, this.title, {super.key}); final IconData icon; final String title; @override Widget build(BuildContext context) { return Padding( padding: const EdgeInsets.all(8.0), child: Column( children: <Widget>[ IconButton( icon: Icon(icon), onPressed: () { print('Pressed: $title'); }, ), Text(title, style: Theme.of(context).textTheme.caption), ], ), ); } } class GalleryDrawer extends StatelessWidget { const GalleryDrawer({ super.key }); void _changeTheme(BuildContext context, bool value) { ComplexLayoutApp.of(context)?.lightTheme = value; } void _changeScrollMode(BuildContext context, ScrollMode mode) { ComplexLayoutApp.of(context)?.scrollMode = mode; } @override Widget build(BuildContext context) { final ScrollMode currentMode = ComplexLayoutApp.of(context)!.scrollMode; return Drawer( // Note: for real apps, see the Gallery material Drawer demo. More // typically, a drawer would have a fixed header with a scrolling body // below it. child: ListView( key: const PageStorageKey<String>('gallery-drawer'), padding: EdgeInsets.zero, children: <Widget>[ const FancyDrawerHeader(), ListTile( key: const Key('scroll-switcher'), title: const Text('Scroll Mode'), onTap: () { _changeScrollMode(context, currentMode == ScrollMode.complex ? ScrollMode.tile : ScrollMode.complex); Navigator.pop(context); }, trailing: Text(currentMode == ScrollMode.complex ? 'Tile' : 'Complex'), ), ListTile( leading: const Icon(Icons.brightness_5), title: const Text('Light'), onTap: () { _changeTheme(context, true); }, selected: ComplexLayoutApp.of(context)!.lightTheme, trailing: Radio<bool>( value: true, groupValue: ComplexLayoutApp.of(context)!.lightTheme, onChanged: (bool? value) { _changeTheme(context, value!); }, ), ), ListTile( leading: const Icon(Icons.brightness_7), title: const Text('Dark'), onTap: () { _changeTheme(context, false); }, selected: !ComplexLayoutApp.of(context)!.lightTheme, trailing: Radio<bool>( value: false, groupValue: ComplexLayoutApp.of(context)!.lightTheme, onChanged: (bool? value) { _changeTheme(context, value!); }, ), ), const Divider(), ListTile( leading: const Icon(Icons.hourglass_empty), title: const Text('Animate Slowly'), selected: timeDilation != 1.0, onTap: () { ComplexLayoutApp.of(context)!.toggleAnimationSpeed(); }, trailing: Checkbox( value: timeDilation != 1.0, onChanged: (bool? value) { ComplexLayoutApp.of(context)!.toggleAnimationSpeed(); }, ), ), ], ), ); } } class FancyDrawerHeader extends StatelessWidget { const FancyDrawerHeader({super.key}); @override Widget build(BuildContext context) { return Container( color: Colors.purple, height: 200.0, child: const SafeArea( bottom: false, child: Placeholder(), ), ); } }