四、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 {},
    this.initialRoute,
    this.onGenerateRoute,
    this.onUnknownRoute,
    this.navigatorObservers = const [],
    this.builder,
    this.title = '',
    this.onGenerateTitle,
    this.color,
    this.theme,
    this.locale,
    this.localizationsDelegates,
    this.localeListResolutionCallback,
    this.localeResolutionCallback,
    this.supportedLocales = const [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  {
  int _navIndex = 0;

  // 储存切换bottomNavigationBar时的四个界面
  var _body = [
    TabBarView(
      children: [
        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: [
        IconButton(
            icon: Icon(Icons.search),
            tooltip: 'Search',
            onPressed: () => debugPrint('Search button is pressed')
        ),
      ],  // 导航栏右侧按钮
      bottom: TabBar(
        tabs: [
          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: [
     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: [
    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 _iconItems = [
    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 _titleItems = [
    '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('[email protected]'),
      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 _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 _didClickNav;
  BottomBar(this._didClickNav);

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

class BottomBarState extends State {
  int _currentIndex = 0;
  ValueChanged _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')
        ),
      ],
    );
  }
}

你可能感兴趣的:(四、Flutter基础之-界面结构及导航相关)