四、Flutter基础之-界面结构及导航相关

上一篇:三、Flutter基础—ListView入门


  • 这篇主要介绍下应用界面的结构,以及导航相关的小部件,参考王皓的教学视频,这个教程挺好的,一步一步很详细适合我这种小白。

先来看看最终效果:

一、MaterialApp和Scaffold

MaterialApp和Scaffold是Flutter提供的两个Widget

  • MaterialApp是一个方便的Widget,它封装了应用程序实现Material Design所需要的一些Widget。
  • Scaffold组件是Material Design布局结构的基本实现。此类提供了用于显示drawer、snackbar和底部sheet的API。

MaterialApp组件中提供了如下属性:

const MaterialApp({
    Key key,
    this.navigatorKey,
    this.home,
    this.routes = const <String, WidgetBuilder>{},
    this.initialRoute,
    this.onGenerateRoute,
    this.onUnknownRoute,
    this.navigatorObservers = const <NavigatorObserver>[],
    this.builder,
    this.title = '',
    this.onGenerateTitle,
    this.color,
    this.theme,
    this.locale,
    this.localizationsDelegates,
    this.localeListResolutionCallback,
    this.localeResolutionCallback,
    this.supportedLocales = const <Locale>[Locale('en', 'US')],
    this.debugShowMaterialGrid = false,
    this.showPerformanceOverlay = false,
    this.checkerboardRasterCacheImages = false,
    this.checkerboardOffscreenLayers = false,
    this.showSemanticsDebugger = false,
    this.debugShowCheckedModeBanner = true,
  })

首先,我们在main.dart中引用import 'package:flutter/material.dart'并返回一个MaterialApp类型app,设置title、主题样式theme,根视图home等:

void main() => runApp(new MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      debugShowCheckedModeBanner: false,  // 不展示右上角"DEBUG"横幅
      title: 'Olive',
      theme: ThemeData(
        primaryColor: Colors.yellow[300],    // 主题颜色
        highlightColor: Color.fromRGBO(255, 255, 255, 0.5),
        splashColor: Colors.white70,
      ),
      home: MyTabController(),    // 根视图导航控制器
    );
  }
}

其中MyTabController是自定义的一个dart组件,作为App的根视图控制器⤵️

二、DefaultTabController

新建一个MyTabController.dart文件,返回一个有状态控件DefaultTabController

import 'package:flutter/material.dart';
import '../pages/HomePage.dart';
import '../pages/LeftDrawerPage.dart';
import 'BottomBarPage.dart';

class MyTabController extends StatefulWidget {
  @override
  createState() => new MyTabState();
}

class MyTabState extends State<MyTabController>  {
  int _navIndex = 0;

  // 储存切换bottomNavigationBar时的四个界面
  var _body = [
    TabBarView(
      children: <Widget>[
        HomePage(),
        Icon(Icons.local_florist, size: 128.0, color: Colors.black12,),
      ]
    ),
    Icon(Icons.local_airport, size: 128.0, color: Colors.black12,),
    Icon(Icons.local_activity, size: 128.0, color: Colors.black12,),
    Icon(Icons.local_cafe, size: 128.0, color: Colors.black12,)
  ];

  void navChange(int index) {
    setState(() {
      _navIndex = index;
    });
  }

  @override
  Widget build(BuildContext context) {
    return DefaultTabController(
      length: 3,
      child: Scaffold(
        backgroundColor: Colors.grey[100],
        appBar: _myBar(),
        body: _body[_navIndex],
        drawer: LeftDrawer(),
        bottomNavigationBar: BottomBar(navChange),
      ),
    );
  }
}

其中DefaultTabController的child是Scaffold组件。
在该组件中,通过backgroundColor设置界面背景色;_myBar()为自定义的AppBar类型组件,用于配置导航栏相关;drawer为左侧滑抽屉组件,若想从右侧划出,则使用endDrawer组件。

LeftDrawer()为自定义的Drawer组件,BottomBar()为底部导航栏,在下面都会讲到。

  • AppBar

上面的_myBar()是一个AppBar类型组件,该组件有五大部分:

提供了如下属性:

AppBar({
    Key key,
    this.leading,    // 左上角控件,一般放置一个icon
    this.automaticallyImplyLeading = true,
    this.title,      // 标题
    this.actions,    // 右上角一系列组件
    this.flexibleSpace,    // 导航栏与bottom间的间隙,见上图
    this.bottom,             // 底部控件,位置见上图
    this.elevation = 4.0,    // 阴影Z轴
    this.backgroundColor,    // 背景色
    this.brightness,    // 状态栏亮度
    this.iconTheme,    // 图标样式
    this.textTheme,    // 文字样式
    this.primary = true,
    this.centerTitle,    // 标题是否居中显示,默认值根据不同的操作系统,显示方式不一样
    this.titleSpacing = NavigationToolbar.kMiddleSpacing,
    this.toolbarOpacity = 1.0,
    this.bottomOpacity = 1.0,
  })

在本示例中,代码如下:

Widget _myBar () {
    return AppBar(
      title: Text('Olive'),
      elevation: 5.0,   // 阴影
      actions: <Widget>[
        IconButton(
            icon: Icon(Icons.search),
            tooltip: 'Search',
            onPressed: () => debugPrint('Search button is pressed')
        ),
      ],  // 导航栏右侧按钮
      bottom: TabBar(
        tabs: <Widget>[
          Tab(icon: Icon(Icons.home)),
          Tab(icon: Icon(Icons.local_florist)),
        ],
        indicatorColor: Colors.black54,
        indicatorSize: TabBarIndicatorSize.label,
        unselectedLabelColor: Colors.black38,
      ),  // 导航栏下方的选项卡
    );  
  }

若没有自定leading左上角控件,且有drawer左侧抽屉,则默认会创建左上角按钮,且点击事件为展开左侧抽屉。

若要使用自定义左侧按钮来打开抽屉,可使用Scaffold.of(context).openDrawer()方法,具体如下:

leading: new Builder(builder: (BuildContext context){
  return IconButton(
    icon: ClipRRect(
     child: Image.asset('images/nav_user.png', fit: BoxFit.contain, width: 28, height: 28),
     borderRadius: BorderRadius.circular(28/2),
    ),
    onPressed: (){
      Scaffold.of(context).openDrawer();
    },
  );
}),
  • TabBar和TabBarView

TabBar组件为横向标签页,一般结合TabBarView来使用。
TabBar有如下属性:

const TabBar({
    Key key,
    @required this.tabs,    // 一系列Tab对象,当然也可以是其他的Widget
    this.controller,               // TabController对象
    this.isScrollable = false,     // 是否可滚动
    this.indicatorColor,           // 指示器颜色
    this.indicatorWeight = 2.0,    // 指示器厚度
    this.indicatorPadding = EdgeInsets.zero,    // 底部指示器的内间距
    this.indicator,        // 指示器decoration,例如边框等
    this.indicatorSize,    // 指示器大小计算方式
    this.labelColor,       // 选中Tab文字颜色
    this.labelStyle,       // 选中Tab文字Style
    this.labelPadding,     // 文字内间距
    this.unselectedLabelColor,    // 未选中Tab中文字颜色
    this.unselectedLabelStyle,    // 未选中Tab中文字style
  })

本示例中,在上面_myBar()代码中,给TabBar添加了两个按钮:

bottom: TabBar(
   tabs: <Widget>[
     Tab(icon: Icon(Icons.home)),
     Tab(icon: Icon(Icons.local_florist)),
   ],
   indicatorColor: Colors.black54,
   indicatorSize: TabBarIndicatorSize.label,
   unselectedLabelColor: Colors.black38,
), 

DefaultTabController中设置了TabBarView,控制每个选项卡具体展示的页面,点击第一个选项展示HomePage界面,第二个选项卡展示一个Icon,其中HomePage为一个List,这里就不具体说了:

TabBarView(
  children: <Widget>[
    HomePage(),
    Icon(Icons.local_florist, size: 128.0, color: Colors.black12,)
  ]
 ),

三、LeftDrawerPage

新建LeftDrawerPage.dart文件,用于侧滑Drawer布局。废话不多说,直接上代码:

import 'package:flutter/material.dart';

class LeftDrawer extends StatelessWidget {

  final String avatarUrl = 'https://upload.jianshu.io/users/upload_avatars/2650319/becf3e53-9113-43e5-8241-de68bcf8b15f.jpg?imageMogr2/auto-orient/strip|imageView2/1/w/240/h/240';
  final String headerBgImgUrl = 'https://images.unsplash.com/photo-1548693316-8ec65a5f2192?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=1225&q=80';

  List<Icon> _iconItems = <Icon>[
    Icon(Icons.message, color: Colors.black12, size: 22,),
    Icon(Icons.favorite, color: Colors.black12, size: 22,),
    Icon(Icons.settings, color: Colors.black12, size: 22,),
  ];

  List<String> _titleItems = <String>[
    'Message', 'Favorite', 'Settings'
  ];

  Widget _listItemBuilder(BuildContext context, int index) {
    return new ListTile(
      title: Text(
        _titleItems[index],
        textAlign: TextAlign.right,
      ),
      trailing: _iconItems[index],
      onTap: () => Navigator.pop(context),
    );
  }

  Widget _listHeaderBuilder() {
    return new UserAccountsDrawerHeader(
      accountName: Text(
        'Olive',
        style: TextStyle(fontWeight: FontWeight.bold),
      ),
      accountEmail: Text('461485900@qq.com'),
      currentAccountPicture: CircleAvatar(
        backgroundImage: NetworkImage(avatarUrl),
      ),
      decoration: BoxDecoration(
          color: Colors.yellow[400],
          image: DecorationImage(
            image: NetworkImage(headerBgImgUrl),
            fit: BoxFit.cover,
            colorFilter: ColorFilter.mode(
                Colors.yellow[400].withOpacity(0.6),
                BlendMode.hardLight
            ),
          )
      ),
    );
  }

  @override
  Widget build(BuildContext context) {
    List<Widget> _list = new List();
    _list.add(_listHeaderBuilder());
    for (int i = 0; i < _titleItems.length; i++) {
      _list.add(_listItemBuilder(context, i));
    }

    return Drawer(
      child: ListView(
        padding: EdgeInsets.zero,
        children: _list,
      ),
    );
  }
}

示例中Drawer的child中其实是个ListView,具体用法在上一篇文章中有记录。这里要提的是,竟然有UserAccountsDrawerHeader这种组件!真的很方便,不知道是不是我孤落寡闻哈哈~

UserAccountsDrawerHeader 能快速设置用户头像、用户名、Email 等信息,显示一个符合纸墨设计规范的 drawer header。

四、BottomNavigationBar底部导航栏

创建BottomBarPage.dart文件,代码如下:

import 'package:flutter/material.dart';

class BottomBar extends StatefulWidget {

  ValueChanged<int> _didClickNav;
  BottomBar(this._didClickNav);

  @override
  createState() => new BottomBarState(_didClickNav);
}

class BottomBarState extends State<BottomBar> {
  int _currentIndex = 0;
  ValueChanged<int> _didClickNav;

  BottomBarState(this._didClickNav);
  
  void _onTapHandler (int index) {
    setState(() {
      _currentIndex = index;
      _didClickNav(index);
    });
  }

  @override
  Widget build(BuildContext context) {
    return BottomNavigationBar(
      type: BottomNavigationBarType.fixed,
      fixedColor: Colors.black,
      currentIndex: _currentIndex,
      onTap: _onTapHandler,
      items: [
        BottomNavigationBarItem(
            icon: Icon(Icons.explore),
            title: Text('Explore')
        ),
        BottomNavigationBarItem(
            icon: Icon(Icons.history),
            title: Text('History')
        ),
        BottomNavigationBarItem(
            icon: Icon(Icons.list),
            title: Text('List')
        ),
        BottomNavigationBarItem(
            icon: Icon(Icons.person),
            title: Text('My')
        ),
      ],
    );
  }
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 157,298评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,701评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 107,078评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,687评论 0 202
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,018评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,410评论 1 211
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,729评论 2 310
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,412评论 0 194
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,124评论 1 239
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,379评论 2 242
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,903评论 1 257
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,268评论 2 251
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,894评论 3 233
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,014评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,770评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,435评论 2 269
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,312评论 2 260

推荐阅读更多精彩内容