Flutter——Widget系列1 基本布局MaterialApp、Scaffold和Container

本文,从最简单的例子开始,然后就是经常一起出现的MaterialApp、Scaffold,最后具有绘制、定位、调整大小功能的Container。

一、一个最简单的实例

一个简单的添加控件的例子

import 'package:flutter/material.dart';
import 'package:english_words/english_words.dart';

void main() {
  runApp(
    Center(
      child: Text(
        'Hello, world!',
        textDirection: TextDirection.ltr,
      ),
    ),
  );
}

是啊,一个HelloWorld居中了。

  • Center就是居中效果

效果图

image.png

再,简单一点点

不带居中了,textDirection: TextDirection.ltr,需要设定下。不然报错、大概就是靠做的意思。l是left的缩写。

import 'package:flutter/material.dart';
import 'package:english_words/english_words.dart';

void main() {
  runApp(

    Text("普通文本",
      textDirection: TextDirection.ltr,
    )
  );
}
image.png

核心点

  • runApp() 函数会持有传入的 Widget,并且使它成为 widget 树中的根节点。

.
.
.

二、MaterialApp 组件

Flutter的wdiget里,MaterialApp 代表使用(Material Design)风格的应用。里面包含了纸墨设计风格应用所需要的基本控件。

其实,这个第二大点,内容稍微有点长,简单来说,就是大概说了MaterialApp和Scaffold。要是觉得太长,完全是可以直接看第三大点的。

二.1、MaterialApp 简单例子

先来看看大概的 MaterialApp 示例代码

import 'package:flutter/material.dart';

void main() {
  runApp(MaterialApp(
    title: 'Flutter Tutorial',
    home: TutorialHome(),
  ));
}

class TutorialHome extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    // Scaffold is a layout for the major Material Components.
    return Scaffold(
      appBar: AppBar(
        leading: IconButton(
          icon: Icon(Icons.menu),
          tooltip: 'Navigation menu',
          onPressed: null,
        ),
        title: Text('Example title'),
        actions: <Widget>[
          IconButton(
            icon: Icon(Icons.search),
            tooltip: 'Search',
            onPressed: null,
          ),
        ],
      ),
      // body is the majority of the screen.
      body: Center(
        child: Text('Hello, world!'),
      ),
      floatingActionButton: FloatingActionButton(
        tooltip: 'Add', // used by assistive technologies
        child: Icon(Icons.add),
        onPressed: null,
      ),
    );
  }
}

代码来源https://flutter-io.cn/docs/development/ui/widgets-intro

突然冒出很多新东西
StatelessWidget是什么?Scaffold是什么?floatingActionButton干嘛的?
没关系,忽略忽略,先来看看MaterialApp有什么属性。

效果图

image.png

核心结构

image.png

二.2、MaterialApp 属性如下

主要属性

  • title : 在任务管理窗口中所显示的应用名字
  • theme : 应用各种 UI 所使用的主题颜色
  • color : 应用的主要颜色值(primary color),也就是安卓任务管理窗口中所显示的应用颜色
  • home : 应用默认所显示的界面 Widget
  • routes : 应用的顶级导航表格,这个是多页面应用用来控制页面跳转的,类似于网页的网址
  • initialRoute :第一个显示的路由名字,默认值为 Window.defaultRouteName
  • onGenerateRoute : 生成路由的回调函数,当导航的命名路由的时候,会使用这个来生成界面
  • onLocaleChanged : 当系统修改语言的时候,会触发å这个回调
  • navigatorObservers : 应用 Navigator 的监听器
  • debugShowMaterialGrid : 是否显示 纸墨设计 基础布局网格,用来调试 UI 的工具
  • showPerformanceOverlay : 显示性能标签,https://flutter.io/debugging/#performanceoverlay
  • checkerboardRasterCacheImages 、showSemanticsDebugger、debugShowCheckedModeBanner 各种调试开关

全部属性

字段 类型
navigatorKey (导航键) GlobalKey<NavigatorState>
home(主页) Widget
routes(路由) Map<String, WidgetBuilder>
initialRoute(初始路由) String
onGenerateRoute(生成路由) RouteFactory
onUnknownRoute(未知路由) RouteFactory
navigatorObservers(导航观察器) List<NavigatorObserver>
builder(建造者) TransitionBuilder
title(标题) String
onGenerateTitle(生成标题) GenerateAppTitle
color(颜色) Color
theme(主题) ThemeData
locale(地点) Locale
localizationsDelegates(本地化委托) Iterable<LocalizationsDelegate<dynamic>>
localeResolutionCallback(区域分辨回调) LocaleResolutionCallback
supportedLocales(支持区域) Iterable<Locale>
debugShowMaterialGrid(调试显示材质网格) bool
showPerformanceOverlay(显示性能叠加) bool
checkerboardRasterCacheImages(棋盘格光栅缓存图像) bool
checkerboardOffscreenLayers(棋盘格层) bool
showSemanticsDebugger(显示语义调试器) bool
debugShowCheckedModeBanner(调试显示检查模式横幅) bool

.
.

1. navigatorKey

navigatorKey.currentState
相当于
Navigator.of(context)

使用
  GlobalKey<NavigatorState> _navigatorKey=new GlobalKey();

  new MaterialApp(
      navigatorKey: _navigatorKey,
    );
2. home

进入程序后显示的第一个页面,传入的是一个Widget,但实际上这个Widget需要包裹一个Scaffold以显示该程序使用Material Design风格

使用
class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      home: new MyHomePage(),
    );
  }
}
//这是一个可改变的Widget
class MyHomePage extends StatefulWidget {
  @override
  _MyHomePageState createState() => new _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
  @override
  Widget build(BuildContext context) {
    return new Scaffold(
      appBar: new AppBar(
        title: Text('Title'),
      ),
      body: Center(
        child: Text('Home'),
      ),
    );
  }
}
3. routes

声明程序中有哪个通过Navigation.of(context).pushNamed跳转的路由
参数以键值对的形式传递
key:路由名字
value:对应的Widget

使用
new MaterialApp(
      routes: {
       '/home':(BuildContext context) => HomePage(),
       '/home/one':(BuildContext context) => OnePage(),
       //....
      },
    );
4 . initialRoute

初始路由,当用户进入程序时,自动打开对应的路由。
(home还是位于一级)
传入的是上面routes的key
跳转的是对应的Widget(如果该Widget有Scaffold.AppBar,并不做任何修改,左上角有返回键)

使用
new MaterialApp(
      routes: {
       '/home':(BuildContext context) => HomePage(),
       '/home/one':(BuildContext context) => OnePage(),
       //....
      },
      initialRoute: '/home/one',
    );
5 . onGenerateRoute

当通过Navigation.of(context).pushNamed跳转路由时,
在routes查找不到时,会调用该方法

使用
new MaterialApp(
      routes: {
       '/home':(BuildContext context) => HomePage(),
       '/home/one':(BuildContext context) => OnePage(),
       //....
      },
      onGenerateRoute: (setting){
        //setting.isInitialRoute; bool类型 是否初始路由
        //setting.name; 要跳转的路由名key
        return new PageRouteBuilder(
            pageBuilder: (BuildContext context, _, __) {
        //这里为返回的Widget
              return HomePage();
            },
            opaque: false,
        //跳转动画
            transitionDuration: new Duration(milliseconds: 200),
            transitionsBuilder:
                (___, Animation<double> animation, ____, Widget child) {
              return new FadeTransition(
                opacity: animation,
                child: new ScaleTransition(
                  scale: new Tween<double>(begin: 0.5, end: 1.0)
                      .animate(animation),
                  child: child,
                ),
              );
            });
      }
    );
6 . onUnknownRoute

效果跟onGenerateRoute一样
调用顺序为onGenerateRoute ==> onUnknownRoute

7 . navigatorObservers

路由观察器,当调用Navigator的相关方法时,会回调相关的操作

使用
new MaterialApp(
      navigatorObservers: [
        MyObserver(),
      ],
    );
//继承NavigatorObserver
class MyObserver extends NavigatorObserver{
  @override
  void didPush(Route route, Route previousRoute) {
    // 当调用Navigator.push时回调
    super.didPush(route, previousRoute);
    //可通过route.settings获取路由相关内容
    //route.currentResult获取返回内容
    //....等等
    print(route.settings.name);
  }
}
8 . builder

当构建一个Widget前调用
一般做字体大小,方向,主题颜色等配置

使用
new MaterialApp(
      builder: (BuildContext context, Widget child) {
        return MediaQuery(
          data: MediaQuery.of(context).copyWith(
            //字体大小
                textScaleFactor: 1.4,
              ),
          child: child,
        );
      },
    );
9 . title

该标题出现在
Android:任务管理器的程序快照之上
IOS: 程序切换管理器中

使用
new MaterialApp(
      title: 'Flutter应用',
    );
10 . onGenerateTitle

跟上面的tiitle一样,但含有一个context参数
用于做本地化

new MaterialApp(
      onGenerateTitle: (context){
        return 'Flutter应用';
      },
    );
11 . color

该颜色为Android中程序切换中应用图标背景的颜色,当应用图标背景为透明时

使用
new MaterialApp(
      color: Colors.blue,
    )
12 . theme

应用程序的主题,各种的定制颜色都可以设置,用于程序主题切换

使用

new MaterialApp(
      theme: new ThemeData(
       //主题色
        primarySwatch: Colors.blue,
      ),
    );
13 . locale

当前区域,如果为null则使用系统区域
一般用于语言切换

使用

//传入两个参数,语言代码,国家代码

new MaterialApp(
      Locale('yy','zh'),
    );
//源码纠正,一些曾经用过的代码(注释后面的日期为不再使用的日期),源码会自动纠正
//来看一下源码
//languageCode 第一个参数
switch (languageCode) {
      case 'in': return 'id'; // Indonesian; deprecated 1989-01-01
      case 'iw': return 'he'; // Hebrew; deprecated 1989-01-01
      case 'ji': return 'yi'; // Yiddish; deprecated 1989-01-01
      case 'jw': return 'jv'; // Javanese; deprecated 2001-08-13
      case 'mo': return 'ro'; // Moldavian, Moldovan; deprecated 2008-11-22
      case 'aam': return 'aas'; // Aramanik; deprecated 2015-02-12
      case 'adp': return 'dz'; // Adap; deprecated 2015-02-12
      case 'aue': return 'ktz'; // =/Kx'au//'ein; deprecated 2015-02-12
      case 'ayx': return 'nun'; // Ayi (China); deprecated 2011-08-16
      case 'bgm': return 'bcg'; // Baga Mboteni; deprecated 2016-05-30
      case 'bjd': return 'drl'; // Bandjigali; deprecated 2012-08-12
      case 'ccq': return 'rki'; // Chaungtha; deprecated 2012-08-12
      case 'cjr': return 'mom'; // Chorotega; deprecated 2010-03-11
      case 'cka': return 'cmr'; // Khumi Awa Chin; deprecated 2012-08-12
      case 'cmk': return 'xch'; // Chimakum; deprecated 2010-03-11
      case 'coy': return 'pij'; // Coyaima; deprecated 2016-05-30
      case 'cqu': return 'quh'; // Chilean Quechua; deprecated 2016-05-30
      case 'drh': return 'khk'; // Darkhat; deprecated 2010-03-11
      case 'drw': return 'prs'; // Darwazi; deprecated 2010-03-11
      case 'gav': return 'dev'; // Gabutamon; deprecated 2010-03-11
      case 'gfx': return 'vaj'; // Mangetti Dune !Xung; deprecated 2015-02-12
      case 'ggn': return 'gvr'; // Eastern Gurung; deprecated 2016-05-30
      case 'gti': return 'nyc'; // Gbati-ri; deprecated 2015-02-12
      case 'guv': return 'duz'; // Gey; deprecated 2016-05-30
      case 'hrr': return 'jal'; // Horuru; deprecated 2012-08-12
      case 'ibi': return 'opa'; // Ibilo; deprecated 2012-08-12
      case 'ilw': return 'gal'; // Talur; deprecated 2013-09-10
      case 'jeg': return 'oyb'; // Jeng; deprecated 2017-02-23
      case 'kgc': return 'tdf'; // Kasseng; deprecated 2016-05-30
      case 'kgh': return 'kml'; // Upper Tanudan Kalinga; deprecated 2012-08-12
      case 'koj': return 'kwv'; // Sara Dunjo; deprecated 2015-02-12
      case 'krm': return 'bmf'; // Krim; deprecated 2017-02-23
      case 'ktr': return 'dtp'; // Kota Marudu Tinagas; deprecated 2016-05-30
      case 'kvs': return 'gdj'; // Kunggara; deprecated 2016-05-30
      case 'kwq': return 'yam'; // Kwak; deprecated 2015-02-12
      case 'kxe': return 'tvd'; // Kakihum; deprecated 2015-02-12
      case 'kzj': return 'dtp'; // Coastal Kadazan; deprecated 2016-05-30
      case 'kzt': return 'dtp'; // Tambunan Dusun; deprecated 2016-05-30
      case 'lii': return 'raq'; // Lingkhim; deprecated 2015-02-12
      case 'lmm': return 'rmx'; // Lamam; deprecated 2014-02-28
      case 'meg': return 'cir'; // Mea; deprecated 2013-09-10
      case 'mst': return 'mry'; // Cataelano Mandaya; deprecated 2010-03-11
      case 'mwj': return 'vaj'; // Maligo; deprecated 2015-02-12
      case 'myt': return 'mry'; // Sangab Mandaya; deprecated 2010-03-11
      case 'nad': return 'xny'; // Nijadali; deprecated 2016-05-30
      case 'nnx': return 'ngv'; // Ngong; deprecated 2015-02-12
      case 'nts': return 'pij'; // Natagaimas; deprecated 2016-05-30
      case 'oun': return 'vaj'; // !O!ung; deprecated 2015-02-12
      case 'pcr': return 'adx'; // Panang; deprecated 2013-09-10
      case 'pmc': return 'huw'; // Palumata; deprecated 2016-05-30
      case 'pmu': return 'phr'; // Mirpur Panjabi; deprecated 2015-02-12
      case 'ppa': return 'bfy'; // Pao; deprecated 2016-05-30
      case 'ppr': return 'lcq'; // Piru; deprecated 2013-09-10
      case 'pry': return 'prt'; // Pray 3; deprecated 2016-05-30
      case 'puz': return 'pub'; // Purum Naga; deprecated 2014-02-28
      case 'sca': return 'hle'; // Sansu; deprecated 2012-08-12
      case 'skk': return 'oyb'; // Sok; deprecated 2017-02-23
      case 'tdu': return 'dtp'; // Tempasuk Dusun; deprecated 2016-05-30
      case 'thc': return 'tpo'; // Tai Hang Tong; deprecated 2016-05-30
      case 'thx': return 'oyb'; // The; deprecated 2015-02-12
      case 'tie': return 'ras'; // Tingal; deprecated 2011-08-16
      case 'tkk': return 'twm'; // Takpa; deprecated 2011-08-16
      case 'tlw': return 'weo'; // South Wemale; deprecated 2012-08-12
      case 'tmp': return 'tyj'; // Tai Mène; deprecated 2016-05-30
      case 'tne': return 'kak'; // Tinoc Kallahan; deprecated 2016-05-30
      case 'tnf': return 'prs'; // Tangshewi; deprecated 2010-03-11
      case 'tsf': return 'taj'; // Southwestern Tamang; deprecated 2015-02-12
      case 'uok': return 'ema'; // Uokha; deprecated 2015-02-12
      case 'xba': return 'cax'; // Kamba (Brazil); deprecated 2016-05-30
      case 'xia': return 'acn'; // Xiandao; deprecated 2013-09-10
      case 'xkh': return 'waw'; // Karahawyana; deprecated 2016-05-30
      case 'xsj': return 'suj'; // Subi; deprecated 2015-02-12
      case 'ybd': return 'rki'; // Yangbye; deprecated 2012-08-12
      case 'yma': return 'lrr'; // Yamphe; deprecated 2012-08-12
      case 'ymt': return 'mtm'; // Mator-Taygi-Karagas; deprecated 2015-02-12
      case 'yos': return 'zom'; // Yos; deprecated 2013-09-10
      case 'yuu': return 'yug'; // Yugh; deprecated 2014-02-28
      default: return languageCode;
    }

//_countryCode 第二个参数
switch (_countryCode) {
      case 'BU': return 'MM'; // Burma; deprecated 1989-12-05
      case 'DD': return 'DE'; // German Democratic Republic; deprecated 1990-10-30
      case 'FX': return 'FR'; // Metropolitan France; deprecated 1997-07-14
      case 'TP': return 'TL'; // East Timor; deprecated 2002-05-20
      case 'YD': return 'YE'; // Democratic Yemen; deprecated 1990-08-14
      case 'ZR': return 'CD'; // Zaire; deprecated 1997-07-14
      default: return regionCode;
    }

14 . localizationsDelegates

本地化委托,用于更改Flutter Widget默认的提示语,按钮text等

使用
new MaterialApp(
      localizationsDelegates: [
          MyLocalizationsDelegates(),
      ],
      locale: Locale('zh','cn'),
    );

class MyLocalizationsDelegates extends LocalizationsDelegate
<MaterialLocalizations>{
  @override
  bool isSupported(Locale locale) {
//是否支持该locale,如果不支持会报异常
    if(locale == const Locale('zh','cn')){
      return true;
    }
    return false;
  }
  @override//是否需要重载
  bool shouldReload(LocalizationsDelegate old)  => false;

  @override
  Future<MaterialLocalizations> load(Locale locale) {
//加载本地化
    return new SynchronousFuture(new MyLocalizations(locale));
  }
}
//本地化实现,继承DefaultMaterialLocalizations
class MyLocalizations extends DefaultMaterialLocalizations{
  final Locale locale;
  MyLocalizations(this.locale, );
  @override
  String get okButtonLabel {
    if(locale == const Locale('zh','cn')){
      return '好的';
    }else{
      return super.okButtonLabel;
    }
  }
  @override
  String get backButtonTooltip {
    if(locale == const Locale('zh','cn')){
      return '返回';
    }else{
      return super.okButtonLabel;
    }
  }
}
15 . localeResolutionCallback

当传入的是不支持的语种,可以根据这个回调,返回相近,并且支持的语种

使用
new MaterialApp(
      localeResolutionCallback: (local,support){
        if(support.contains(support)){
          print('support');
          return local;
        }
        print('no_support');
        return const Locale('us','uk');
      },
//这个代码是随便填的,有可能出错
      locale: Locale('ze','cn'),
    );
16 . supportedLocales

传入支持的语种数组

new MaterialApp(
      supportedLocales: [
        const Locale('uok'),
        const Locale('meg'),
      ],
    );
17 . debugShowMaterialGrid

debug模式下是否显示材质网格,传入bool类型,使用就不写了

18 . showPerformanceOverlay

当为true时应用程序顶部覆盖一层GPU和UI曲线图,可即时查看当前流畅度情况

19 . checkerboardRasterCacheImages

当为true时,打开光栅缓存图像的棋盘格

20 . checkerboardOffscreenLayers

当为true时,打开呈现到屏幕位图的层的棋盘格

21 . showSemanticsDebugger

当为true时,打开Widget边框,类似Android开发者模式中显示布局边界

22 . debugShowCheckedModeBanner

当为true时,在debug模式下显示右上角的debug字样的横幅,false即为不显示

看了之后,属性好多,没关系关系,大概看看就好。

参考:
https://www.jianshu.com/p/db27e17ecea0
https://www.jianshu.com/p/1d44ae246652

二.3、Scaffold和MaterialApp更配噢

Scaffold呢,本身是脚手架的意思。
MaterialApp经常和Scaffold一起搭配使用。

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

示例一下,再来贴一下代码,虽然上面以上出现过。

经常比如这样子出现:

import 'package:flutter/material.dart';

void main() {
  runApp(MaterialApp(
    title: 'Flutter Tutorial',
    home: TutorialHome(),
  ));
}

class TutorialHome extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    // Scaffold is a layout for the major Material Components.
    return Scaffold(
      appBar: AppBar(
        leading: IconButton(
          icon: Icon(Icons.menu),
          tooltip: 'Navigation menu',
          onPressed: null,
        ),
        title: Text('Example title'),
        actions: <Widget>[
          IconButton(
            icon: Icon(Icons.search),
            tooltip: 'Search',
            onPressed: null,
          ),
        ],
      ),
      // body is the majority of the screen.
      body: Center(
        child: Text('Hello, world!'),
      ),
      floatingActionButton: FloatingActionButton(
        tooltip: 'Add', // used by assistive technologies
        child: Icon(Icons.add),
        onPressed: null,
      ),
    );
  }
}

.又比如这样子:

import 'package:flutter/material.dart';

void main() {
  runApp(MaterialApp(
    title: 'Flutter Tutorial',
    home: MyHomePage(),
  ));
}

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      home: new MyHomePage(),
    );
  }
}
//这是一个可改变的Widget
class MyHomePage extends StatefulWidget {
  @override
  _MyHomePageState createState() => new _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
  @override
  Widget build(BuildContext context) {
    return new Scaffold(
      appBar: new AppBar(
        title: Text('Title'),
      ),
      body: Center(
        child: Text('Home'),
      ),
    );
  }
}

image.png

二.3.1、Scaffold 的属性

属性 说明
appBar 显示在Scaffold 顶部
body 位置如下图
floatingActionButton 默认右下角的悬浮按钮
floatingActionButtonLocation 悬浮按钮位置,默认右下角
floatingActionButtonAnimator 悬浮按钮位置位置变换动画
persistentFooterButtons 位置如下图
bottomNavigationBar 位置如下图
bottomSheet 位置如下图

————————————————

image.png

抽屉效果图:


image.png

参考:
https://blog.csdn.net/mengks1987/article/details/84980059

https://juejin.im/post/5cc174def265da036207adf4

二.3.2、Scaffold 的 demo

https://juejin.im/post/5cc174def265da036207adf4

import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: ScaffoldDemo(),
    );
  }
}


class ScaffoldDemo extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return new Scaffold(
      appBar: _appBar(),
      body: _body(),
      floatingActionButton: new FloatingActionButton(
        tooltip: 'Add', // used by assistive technologies
        child: new Icon(Icons.add),
        onPressed: null,
      ),
      floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat,
      floatingActionButtonAnimator: FloatingActionButtonAnimator.scaling,
      drawer: _drawer(context),
      endDrawer: Drawer(child: Text("右抽屉")),
      bottomNavigationBar: _BottomNavigationBar(),
    );
  }

  AppBar _appBar() {
    return new AppBar(
      // 如果有 侧边栏  右边是默认的事条目,点击默认打开 左侧边栏。 如果没有侧边栏,就显示返回箭头
      title: new Text('Example title'),
      actions: <Widget>[
        new IconButton(
          icon: new Icon(Icons.search),
          tooltip: 'Search', // 长按的一个提示
          onPressed: null,
        ),
        new IconButton(
          icon: new Icon(Icons.access_time),
          tooltip: 'time',
          onPressed: null,
        ),
      ],
    );
  }

  Drawer _drawer(BuildContext context) {
    return Drawer(
      child: ListView(
        padding: EdgeInsets.zero,
        children: <Widget>[
          DrawerHeader(
            decoration: BoxDecoration(
              color: Colors.lightBlueAccent,
            ),
            child: Center(
              child: SizedBox(
                width: 60.0,
                height: 60.0,
                child: CircleAvatar(
                  child: Text('头像'),
                ),
              ),
            ),
          ),
          ListTile(
            title: Text('Item 1'),
            leading: new CircleAvatar(
              child: new Icon(Icons.school),
            ),
            onTap: () {
              Navigator.pop(context);
            },
          ),
          ListTile(
            title: Text('Item 2'),
            leading: new CircleAvatar(
              child: new Text('B2'),
            ),
            onTap: () {
              Navigator.pop(context);
            },
          ),
          ListTile(
            title: Text('Item 3'),
            leading: new CircleAvatar(
              child: new Icon(Icons.list),
            ),
            onTap: () {
              Navigator.pop(context);
            },
          ),
        ],
      ),
    );
  }

  Builder _body() {
    return Builder(builder: (BuildContext context) {
      return Center(
        child: Column(
          children: <Widget>[
            RaisedButton(
              child: Text('显示snackbar (Text 没有 onPressed 属性)'),
              onPressed: () {
                Scaffold.of(context).showSnackBar(SnackBar(
                  content: Text('我是snackbar'),
                ));
              },
            ),
            RaisedButton(
              child: Text('显示BottomSheet (Text 没有 onPressed 属性)'),
              onPressed: () {
                Scaffold.of(context).showBottomSheet((BuildContext context) {
                  return ListView(
                    padding: EdgeInsets.zero,
                    children: <Widget>[
                      ListTile(
                        title: Text('Item 1'),
                        leading: new CircleAvatar(
                          child: new Icon(Icons.school),
                        ),
                        onTap: () {
                          Navigator.pop(context);
                        },
                      ),
                      ListTile(
                        title: Text('Item 2'),
                        leading: new CircleAvatar(
                          child: new Text('B2'),
                        ),
                        onTap: () {
                          Navigator.pop(context);
                        },
                      ),
                      ListTile(
                        title: Text('Item 3'),
                        leading: new CircleAvatar(
                          child: new Icon(Icons.list),
                        ),
                        onTap: () {
                          Navigator.pop(context);
                        },
                      ),
                    ],
                  );
                });
              },
            ),
          ],
        ),
      );
    });
  }

}

// BottomNavigationBar 默认的实例
class _BottomNavigationBar extends StatefulWidget {
  const _BottomNavigationBar() : super();
  @override
  State<StatefulWidget> createState() => _BottomNavigationBarFullDefault();
}


// BottomNavigationBar 默认的实例,有状态
class _BottomNavigationBarFullDefault extends State {
  int _currentIndex = 1;

  void _onItemTapped(int index) {
    if(mounted) {
      setState(() {
        _currentIndex = index;
      });
    }
  }

  @override
  Widget build(BuildContext context) {
    return BottomNavigationBar(
      type: BottomNavigationBarType.fixed, // BottomNavigationBarType 中定义的类型,有 fixed 和 shifting 两种类型
      iconSize: 24.0, // BottomNavigationBarItem 中 icon 的大小
      currentIndex: _currentIndex, // 当前所高亮的按钮index
      onTap: _onItemTapped, // 点击里面的按钮的回调函数,参数为当前点击的按钮 index
      fixedColor:  Colors.blue, // 如果 type 类型为 fixed,则通过 fixedColor 设置选中 item 的颜色
      items: <BottomNavigationBarItem> [

        BottomNavigationBarItem(
            title:  Text("主页"), icon:  Icon(Icons.home)),
        BottomNavigationBarItem(
            title:  Text("发现"), icon:  Icon(Icons.search)),
        BottomNavigationBarItem(
            title:  Text("我的"), icon:  Icon(Icons.image)),

      ],
    );
  }
}

image.png
image.png

另外,附上,一个不错的练手项目:https://juejin.im/post/5cde5870f265da1baa1e4284


.
.
.

三、Container 容器类

Container,本身是容器的意思。

核心点:

  • Container具有绘制、定位、调整大小功能。
  • 通常用来装载其它子控件,假如Container没有子控件,它将自动填充整个屏幕;反之,会根据子控件大小,调整自身大小,从而达到自适应效果。
    • Container只能容纳一个子控件,如果想容纳更多的子控件,可以将子控件设置为Row、Column、Stack(这三个子控件都有一个children属性)
  • 使用Container时,通常要有一个父控件,常用的父控件有Center、Padding、Column、Row、Scaffold。
属性 作用
- Color Property Container的背景颜色
- Child Property 设置子控件,如果Container里面没有子控件,它就会填充整个屏幕;如果有子控件,Container就会自动适应子控件大小。
- Alignment Property 控制child的对齐方式,针对于Container内部的子控件布局。主要有以下几个属性:Alignment、FractionalOffset、AlignmentDirectional
- Constraints Property 添加到child上额外的约束条件。其中经常使用的就是BoxConstraint。BoxConstraint包含minWidth、maxWidth、minHeight、maxHeight
- Margin Property 围绕在decoration和child之外的空白区域,不属于内容区域。
- Padding Property decoration内部的空白区域,如果有child的话,child位于padding内部。padding与margin的不同之处在于,padding是包含在content内,而margin则是外部边界,设置点击事件的话,padding区域会响应,而margin区域不会响应。
- Decoration Property 绘制在child后面的装饰,设置了decoration的话,就不能设置color属性,否则会报错,此时应该在decoration中进行颜色的设置。
- ForegroundDecoration Property 绘制在child前面的装饰。
- Transform Property 设置container的变换矩阵,类型为Matrix4。

.
.

1、color

用于设置Container的背景颜色

import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: Color_Property_Colors(),
    );
  }
}

//Container颜色属性之Colors
class Color_Property_Colors extends StatelessWidget{
  @override
  Widget build(BuildContext context) {
    return Center(
      child: Container(
        //color: Colors.green,//正常颜色
        //color:Colors.green[200],//带有阴影(相当于是透明度)
        color: Colors.green.shade200,//同上
      ),
    );
  }
}

image.png
2、child

如前言所述,如果Container里面没有子控件,它就会填充整个屏幕;如果有子控件,Container就会自动适应子控件大小。
另外,Container只能容纳一个子控件,如果想容纳更多的子控件,可以将子控件设置为Row、Column、Stack(这三个子控件都有一个children属性)

import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: ChildProperty(),
    );
  }
}

class ChildProperty extends StatelessWidget{
  @override
  Widget build(BuildContext context) {
    return Center(
      child: Container(
        color:Color(0xFF00FF00),
        child: Text("Flutter 的一个文本"),
      ),
    );
  }
}

效果图

image.png

3、alignment

Alignment属性主要是针对于Container内部的子控件布局。
主要有以下几个属性:Alignment、FractionalOffset、AlignmentDirectional

Alignment

常用等价属性:
Alignment.bottomCenter 对应 Alignment(0.0, 1.0)
Alignment.bottomLeft 对应 Alignment(-1.0, 1.0)
Alignment.bottomRight 对应 Alignment(1.0, 1.0)
Alignment.center 对应 Alignment(0.0, 0.0)
Alignment.centerLeft 对应 Alignment(-1.0, 0.0)
Alignment.centerRight 对应 Alignment(1.0, 0.0)
Alignment.topCenter 对应 Alignment(0.0, -1.0)
Alignment.topLeft 对应 Alignment(-1.0, -1.0)
Alignment.topRight 对应 Alignment(1.0, -1.0)

import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: new Scaffold(
        appBar: AppBar(
          title: Text("Alignment测试"),
        ),
        body: AlignmentProperty(),


      )
    );
  }
}

class AlignmentProperty extends StatelessWidget{
  @override
  Widget build(BuildContext context) {
    // TODO: implement build
    return Center(
      child: Container(
        color: Color.fromARGB(255, 66, 165, 245),
        child: Text("Flutter 的一个文本",
          style: TextStyle(
              fontSize: 30.0
          ),
        ),

        //不加这句话,Container会自适应child大小;加上以后会充满屏幕
        //alignment: Alignment(0.0, 0.0),
        
        // 加上以后会充满屏幕
        alignment: Alignment.center,//等价于上面
      ),
    );
  }
  
}

效果图

image.png

如果把alignment: Alignment.center,//等价于上面 备注掉

备注掉的效果


image.png

.
.

如果设置成为alignment: Alignment.bottomLeft,

image.png

FractionalOffset

常用等价属性如下
FractionalOffset.bottomCenter 对应 FractionalOffset(0.5, 1.0)
FractionalOffset.bottomLeft 对应 FractionalOffset(0.0, 1.0)
FractionalOffset.bottomRight 对应 FractionalOffset(1.0, 1.0)
FractionalOffset.center 对应 FractionalOffset(0.5, 0.5)
FractionalOffset.centerLeft 对应 FractionalOffset(0.0, 0.5)
FractionalOffset.centerRight 对应 FractionalOffset(1.0, 0.5)
FractionalOffset.topCenter 对应 FractionalOffset(0.5, 0.0)
FractionalOffset.topLeft 对应 FractionalOffset(0.0, 0.0)
FractionalOffset.topRight 对应 FractionalOffset(1.0, 0.0)

对比 坐标系
Alignment坐标系是X:-1.0--1.0;Y:-1.0--1.0,
FractionalOffset坐标系是X:0.0--1.0,Y:0.0--1.0。

Alignment坐标系图如下


image.png

FractionalOffset坐标系图如下:


image.png
4、constraints

布局属性,主要讲的是怎么确定控件的大小;其中经常使用的就是BoxConstraint。BoxConstraint包含minWidth、maxWidth、minHeight、maxHeight,详细介绍如下:

场景一:无子控件

import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: new Scaffold(
        appBar: AppBar(
          title: Text("测试"),
        ),
        body: ConstraintsProperty(),
      )
    );
  }
}

class ConstraintsProperty extends StatelessWidget{
  @override
  Widget build(BuildContext context) {
    // TODO: implement build
    return new Center(
      child: new Container(
        color: Color.fromARGB(255, 66, 165, 245),
        alignment: AlignmentDirectional(0.0, 0.0),
        child: Container(
          color: Colors.green,
          constraints: BoxConstraints(
            maxHeight: 300,
            maxWidth: 200,
            minWidth: 150,
            minHeight: 150,
          ),
        ),

      ),

    );
  }
}

image.png

前面说过如果Container没有子控件,Container将填充整个屏幕,但是这里设置maxHeight、maxWidth。
.
.
Container有子控件(场景2)

import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: new Scaffold(
        appBar: AppBar(
          title: Text("测试"),
        ),
        body: ConstraintsPropertyHasChild(),
      )
    );
  }
}

//Container属性之Constraints(有子控件)
class ConstraintsPropertyHasChild extends StatelessWidget{
  @override
  Widget build(BuildContext context) {
    return Center(
      child: Container(
        color: Color.fromARGB(255, 66, 165, 245),
        alignment: AlignmentDirectional(0.0, 0.0),
        child: Container(
          color: Colors.green,
          child: Text("Flutter"),
          constraints: BoxConstraints(
            maxHeight: 300,
            maxWidth: 200,
            minWidth: 150,
            minHeight: 150,
          ),
        ),
      ),
    );
  }
}
image.png

这里虽然有一个子控件,Container会调整自身大小来适应内部子控件,但是由于设置了min-width和min-height。所以Container不会完全和子控件一样大,除非子控件尺寸大于min-width和min-height。

尝试更改子控件大小,代码如下:

import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: new Scaffold(
        appBar: AppBar(
          title: Text("测试"),
        ),
        body: Constraints_Property_HasChild(),
      )
    );
  }
}

//Container属性之Constraints(有子控件)
class Constraints_Property_HasChild extends StatelessWidget{
  @override
  Widget build(BuildContext context) {
    return Center(
      child: Container(
        color: Color.fromARGB(255, 66, 165, 245),
        alignment: AlignmentDirectional(0.0, 0.0),
        child: Container(
          color: Colors.green,
          //child: Text("Flutter"),
          child: Text("Flutter Cheatsheet Flutter Cheatsheet",
            style: TextStyle(
              fontSize: 60.0
          ),),
          constraints: BoxConstraints(
            maxHeight: 300,
            maxWidth: 200,
            minWidth: 150,
            minHeight: 150,
          ),
        ),
      ),
    );
  }
}
image.png

从上面这张图可以看出Container不能超出max-width和max-height。

Container有子控件(场景3)
当Container有子控件,我们想让Container不去适应子控件,而是充满整个屏幕或父控件,怎么办?
答案就是使用BoxConstraints.expand()。
代码如下

import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: new Scaffold(
        appBar: AppBar(
          title: Text("测试"),
        ),
        body: Constraints_Property_HasChild_AllScreen(),
      )
    );
  }
}

//Container属性之Constraints(有子控件、充满整个屏幕)
class Constraints_Property_HasChild_AllScreen extends StatelessWidget{
  @override
  Widget build(BuildContext context) {
    return Center(
      child: Container(
        color: Color.fromARGB(255, 66, 165, 245),
        alignment: AlignmentDirectional(0.0, 0.0),
        child: Container(
          color: Colors.green,
          //child: Text("Flutter"),
          child: Text("Flutter"),
          constraints: BoxConstraints.expand(),
        ),
      ),
    );
  }
}
image.png

此时发现Container是充满整个屏幕的,我们也可以限制充满屏幕大小,比如说1/2宽、1/3高等。

5、margin
EdgeInsets.all()

代码如下:

import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: new Scaffold(
        appBar: AppBar(
          title: Text("测试"),
        ),
        body: Margin_Property_EdgeInsets_all(),
      )
    );
  }
}


//EdgeInsets.all()
class Margin_Property_EdgeInsets_all extends StatelessWidget{
  @override
  Widget build(BuildContext context) {
    return Center(
      child: Container(
        color: Color.fromARGB(255, 66, 165, 245),
        alignment: AlignmentDirectional(0.0, 0.0),
        child: Container(
          color: Colors.green,
          margin: EdgeInsets.all(20.0),
        ),
      ),
    );
  }
}
image.png
EdgeInsets.symmetric()

这个主要用于添加垂直和水平方向上的约束。

类似于水平和垂直的margin

代码如下:

import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: new Scaffold(
        appBar: AppBar(
          title: Text("测试"),
        ),
        body: Margin_Property_EdgeInsets_symmetric(),
      )
    );
  }
}

//EdgeInsets.symmetric()
class Margin_Property_EdgeInsets_symmetric extends StatelessWidget{
  @override
  Widget build(BuildContext context) {
    return Center(
      child: Container(
        color: Color.fromARGB(255, 66, 165, 245),
        alignment: AlignmentDirectional(0.0, 0.0),
        child: Container(
          color: Colors.green,
          margin: EdgeInsets.symmetric(
            vertical: 20.0,
            horizontal: 50.0,
          ),
        ),
      ),
    );
  }
}
image.png
EdgeInsets.fromLTRB()

这个主要设置left, top, right,bottom边距。
代码如下:

import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: new Scaffold(
        appBar: AppBar(
          title: Text("测试"),
        ),
        body: Margin_Property_EdgeInsets_fromLTRB(),
      )
    );
  }
}

//EdgeInsets.fromLTRB()
class Margin_Property_EdgeInsets_fromLTRB extends StatelessWidget{
  @override
  Widget build(BuildContext context) {
    return Center(
      child: Container(
        color: Color.fromARGB(255, 66, 165, 245),
        alignment: AlignmentDirectional(0.0, 0.0),
        child: Container(
            color: Colors.green,
            margin: EdgeInsets.fromLTRB(20.0, 30.0, 40.0, 50.0)
        ),
      ),
    );
  }
}
image.png
EdgeInsets.only()

用于设置哪些是非零的,不设置默认是零。
代码如下:

import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: new Scaffold(
        appBar: AppBar(
          title: Text("测试"),
        ),
        body: Margin_Property_EdgeInsets_only(),
      )
    );
  }
}

//EdgeInsets.only()
class Margin_Property_EdgeInsets_only extends StatelessWidget{
  @override
  Widget build(BuildContext context) {
    return Center(
      child: Container(
        color: Color.fromARGB(255, 66, 165, 245),
        alignment: AlignmentDirectional(0.0, 0.0),
        child: Container(
            color: Colors.green,
            margin: EdgeInsets.only(
              left: 20.0,
              bottom: 40.0,
              top: 50.0,
            )
        ),
      ),
    );
  }
}
image.png
6、padding

这个用于设置主控件内部子控件之间的间距。和Margin一样,利用到EdgeInsets。

7、decoration
  • BoxDecoration Class
  • FlutterLogoDecoration Class
  • ShapeDecoration Class
  • UnderlineTabIndicator Class
8、foregroundDecoration
  • BoxDecoration Class
  • FlutterLogoDecoration Class
  • ShapeDecoration Class
  • UnderlineTabIndicator Class
9、transform

在Container属性中添加transform属性,并使用Matrix类设置transform的值,即可达到各种变换效果,代码如下:

import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: new Scaffold(
        appBar: AppBar(
          title: Text("测试"),
        ),
        body: Transform_Property(),
      )
    );
  }
}

//Transform Property
class Transform_Property extends StatelessWidget{
  @override
  Widget build(BuildContext context) {
    return Container(
      color: Color.fromARGB(255, 66, 165, 245),
      alignment: AlignmentDirectional(0.0, 0.0),
      child: Container(
        padding: EdgeInsets.all(40.0),
        color: Colors.green,
        child: Text("Flutter Cheatsheet"),
        transform: Matrix4.rotationZ(0.5),
      ),
    );
  }
}
image.png

参考:
https://www.jianshu.com/p/b837c1b0da6f

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,029评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,395评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,570评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,535评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,650评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,850评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,006评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,747评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,207评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,536评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,683评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,342评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,964评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,772评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,004评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,401评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,566评论 2 349

推荐阅读更多精彩内容