Flutter 基础学习中(记录一)

852 阅读32分钟

记录学习项目

flutter.dev/docs

在Flutter中,一切皆是Widget(组件),Widget的功能是“描述一个UI元素的配置数据”,它就是说,Widget其实并不是表示最终绘制在设备屏幕上的显示元素,而它只是描述显示元素的一个配置数据。

实际上,Flutter中真正代表屏幕上显示元素的类是 Element,也就是说Widget 只是描述 Element 的配置数据。并且一个 Widget 可以对应多个 Element,因为同一个 Widget 对象可以被添加到 UI树的不同部分,而真正渲染时,UI树的每一个 Element 节点都会对应一个 Widget 对象。

Flutter 完整页面 Widget:Material AppScaffoldAppBar

🍝MaterialApp

🤡 表述:MaterialApp 是我们使用 Flutter 开发中最常用的符合 Material Design 设计理念的入口 Widget。你可以将它类比成为网页中的<html></html>,且它自带路由、主题色,<title>等功能

image

title

🤡 表述:Strig 类型,该属性会在 Android 应用管理器的 App 上方显示,对于 iOS 设备是没有效果的【一般来讲都是 APP 名字】

image.png

home

🤡 表述:Widget 类型,这是在应用程序正常启动时首先显示的 Widget,除非指定了 initialRoute。如果 initialRoute 显示失败,也该显示该 Widget(进入程序后显示的第一个页面,传入的是一个 Widget,但实际上这个 Widget 需要包裹一个 Scaffold 以显示该程序使用 Material Design 风格)

routes

🤡 表述:Map<String, WidgetBuilder>类型,是应用的顶级路由表。当我们再使用 Navigator.pushNamed 进行命名路由的跳转时,会在此路表中进行查找并跳转。如果你的应用程序只有一个页面,则无需使用 routes,直接指定 home 对应的 Widget 即可(参数以键值对的形式传递 key:路由名字 value:对应的 Widget)

routes: {
  "/HomePage": (context) => HomePage(),
  "/AboutPage": (context) => AboutPage(),
},

theme

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

new ThemeData(
    //主题色
    primarySwatch: Colors.blue,
)

如果我们想要自定义一个 HEX 值,那么你可能会想到使用

primarySwatch: Color.fromARGB(a, r, g, b).不过这样是编译不过的。因为primarySwatchMarerialColor类型,而刚才返回的是 Color 类型

color.dart 文件,我们需要把普通的比如 Hex color 转换成MaterialColor

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

//调用的时候需要把hex改一下,比如#223344 needs change to 0xFF223344
//即把#换成0xFF即可

MaterialColor createMaterialColor(Color color) {
List strengths = <double>[.05];
Map swatch = <int, Color>{};
final int r = color.red, g = color.green, b = color.blue;

for (int i = 1; i < 10; i++) {
    strengths.add(0.1 * i);
}
strengths.forEach((strength) {
    final double ds = 0.5 - strength;
    swatch[(strength * 1000).round()] = Color.fromRGBO(
    r + ((ds < 0 ? r : (255 - r)) * ds).round(),
    g + ((ds < 0 ? g : (255 - g)) * ds).round(),
    b + ((ds < 0 ? b : (255 - b)) * ds).round(),
    1,
    );
});
return MaterialColor(color.value, swatch);
}

// 这样子就可以了
primarySwatch: createMaterialColor(Color(0xFF223344)),

这边可以看看ThemeData可以设置那些主题

继承关系: Object -> Diagnosticable -> ThemeData

primarySwatch是主题颜色的一个"样本色",通过这个样本色可以在一些条件下生成一些其它的属性,例如,如果没有指定 primaryColor,并且当前主题不是深色主题,那么 primaryColor 就会默认为 primarySwatch 指定颜色,还有一些相似的属性如 accentColor 、indicatorColor 等也会受 primarySwatch 影响

  • ThemeData(Color 类型属性)

    • accentColor - 前景色(文本、按钮等)。

    • backgroundColor - 与 primaryColor 对比的颜色(例如 用作进度条的剩余部分)。

    • bottomAppBarColor - BottomAppBar 的默认颜色。

    • buttonColor - Material 中 RaisedButtons 使用的默认填充色。

    • canvasColor - MaterialType.canvas Material 的默认颜色。

    • cardColor - Material 被用作 Card 时的颜色。

    • dialogBackgroundColor - Dialog 元素的背景色。

    • disabledColor - 用于 Widget 无效的颜色,无论任何状态。例如禁用复选框。

    • dividerColor - Dividers 和 PopupMenuDividers 的颜色,也用于 ListTiles 中间和 DataTables 的每行中。

    • errorColor - 用于输入验证错误的颜色,例如在 TextField 中。

    • highlightColor - 用于类似墨水喷溅动画或指示菜单被选中的高亮颜色。

    • hintColor - 用于提示文本或占位符文本的颜色,例如在 TextField 中。

    • indicatorColor - TabBar 中选项选中的指示器颜色。

    • primaryColor - App 主要部分的背景色(ToolBar,TabBar 等)。

    • primaryColorDark - primaryColor 的较暗版本。

    • primaryColorLight - primaryColor 的较亮版本。

    • scaffoldBackgroundColor - 作为 Scaffold 基础的 Material 默认颜色,典型 Material 应用或应用内页面的背景颜色。

    • secondaryHeaderColor - 有选定行时 PaginatedDataTable 标题的颜色。

    • selectedRowColor - 选中行时的高亮颜色。

    • splashColor - 墨水喷溅的颜色。

    • textSelectionColor - 文本字段中选中文本的颜色,例如 TextField。

    • textSelectionHandleColor - 用于调整当前文本的哪个部分的句柄颜色。

    • toggleableActiveColor - 用于突出显示切换 Widget(如 Switch,Radio 和 Checkbox)的活动状态的颜色。

    • unselectedWidgetColor - 用于 Widget 处于非活动(但已启用)状态的颜色。 例如,未选中的复选框。 通常与 accentColor 形成对比。

    • focusColor - 焦点获取时的颜色,例如,一些按钮焦点、输入框焦点。

    • hoverColor - 点击之后徘徊中的颜色,例如,按钮长按,按住之后的颜色。

    • cursorColor - 输入框光标颜色。

  • ThemeData(Theme 相关类型属性)

    • accentIconTheme - IconThemeData 类型,与突出颜色对照的图片主题。

    • accentTextTheme - TextTheme 类型,与突出颜色对照的文本主题。

    • chipTheme - ChipThemeData 类型,用于渲染 Chip 的颜色和样式。

    • buttonTheme - ButtonThemeData 类型,定义了按钮等控件的默认配置,像 RaisedButton 和 FlatButton。

    • primaryIconTheme - IconThemeData 类型,一个与主色对比的图片主题。

    • primaryTextTheme - TextThemeData 类型,一个与主色对比的文本主题。

    • iconTheme - IconThemeData 类型,与卡片和画布颜色形成对比的图标主题。

    • inputDecorationTheme - InputDecorationTheme 类型

    • InputDecorator,TextField 和 TextFormField 的默认 InputDecoration 值基于此主题。

    • sliderTheme - SliderThemeData 类型,用于渲染 Slider 的颜色和形状。

    • textTheme - TextTheme 类型,与卡片和画布对比的文本颜色。

    • toggleButtonsTheme - ToggleButtonsThemeData 类型,​Flutter 1.9 全新组件 ToggleButtons 的主题。

    • tabBarTheme - TabBarTheme 类型,TabBar 的主题样式。

    • tooltipTheme - TooltipThemeData 类型,tooltip 提示的主题样式。

    • cardTheme - CardTheme 类型,卡片的主题样式。

    • pageTransitionsTheme - PageTransitionsTheme 类型,页面转场主题样式。

    • appBarTheme - AppBarTheme 类型,AppBar 主题样式。

    • bottomAppBarTheme - BottomAppBarTheme 类型,底部导航主题样式。

    • dialogTheme - DialogTheme 类型,对话框主题样式。

    • floatingActionButtonTheme - FloatingActionButtonThemeData 类型,FloatingActionButton 的主题样式,也就是 Scaffold 属性的那个。

    • cupertinoOverrideTheme - CupertinoThemeData 类型,cupertino 覆盖的主题样式。

    • snackBarTheme - SnackBarThemeData 类型,弹出的 snackBar 的主题样式。

    • bottomSheetTheme - BottomSheetThemeData 类型,底部滑出对话框的主题样式。

    • popupMenuTheme - PopupMenuThemeData 类型,弹出菜单对话框的主题样式。

    • bannerTheme - MaterialBannerThemeData 类型,Material 材质的 Banner 主题样式。

    • dividerTheme - DividerThemeData 类型,Divider 组件的主题样式,也就是那个横向线条组件。

  • ThemeData(其他类型属性)

    • accentColorBrightness - Brightness 类型,

    • accentColor 的亮度。 用于确定放置在突出颜色顶部的文本和图标的颜色(例如 - FloatingButton 上的图标)。

    • brightness - Brightness 类型,应用程序整体主题的亮度。 由按钮等 Widget 使用,以确定在不使用主色或强调色时要选择的颜色。

    • platform - TargetPlatform 类型,Widget 需要适配的目标类型。

    • splashFactory - InteractiveInkFeatureFactory 类型,定义 InkWall 和 InkResponse 生成的墨水喷溅的外观。

    • primaryColorBrightness - Brightness 类型,primaryColor 的亮度。

    • fontFamily - String 类型,字体样式。

    • applyElevationOverlayColor - bool 类型,是否应用 elevation 覆盖颜色。

    • materialTapTargetSize - MaterialTapTargetSize 类型,Chip 等组件的尺寸主题设置,如:设置为 MaterialTapTargetSize.shrinkWrap 时,clip 距顶部距离为 0;设置为 MaterialTapTargetSize.padded 时距顶部有一个距离。

    • colorScheme - ColorScheme 类型,scheme 组颜色,一组 13 种颜色,可用于配置大多数组件的颜色属性。

    • typography - Typography 类型,用于配置 TextTheme、- - primaryTextTheme 和 accentTextTheme 的颜色和几何文本主题值。

    www.pengzhenjin.top/archives/fl…

navigatorKey

在构建导航器时使用的键,navigatorKey 定义的是当前 APP 实例的 GlobalKey。GlobalKey 能够跨 Widget 访问状态。

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

GlobalKey<NavigatorState> _navigatorKey=new GlobalKey()
new MaterialApp(
  navigatorKey: _navigatorKey,
);

initialRoute

如果构建了导航器,则显示的第一个路由的名称,通常是 routes 中定义的一个项,会覆盖 home 的定义

return MaterialApp(
    title: '应用名称',
    theme: ThemeData(
        primaryColor: Colors.redAccent
    ),
    // 该 initialRoute 默认值为 /,若不指定该值,则须在routes中的某个 key 替换为 '/'
    // 例如  'second': (BuildContext context) => SecondScreen() 改为  '/': (BuildContext context) => SecondScreen()
    initialRoute: 'second',
    routes: <String, WidgetBuilder>{
        "third": (BuildContext context) => ThirdScreen(),
        'first': (BuildContext context) => FirstScreen(),
        'second': (BuildContext context) => SecondScreen()
    },
    );

onGenerateRoute

注意,onGenerateRoute 只会对命名路由生效。

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

场景:假设我们要开发一个电商 APP,当用户没有登录时可以看店铺、商品等信息,但交易记录、购物车、用户个人信息等页面需要登录后才能看。为了实现上述功能,我们需要在打开每一个路由页前判断用户登录状态!如果每次打开路由前我们都需要去判断一下将会非常麻烦,那有什么更好的办法吗?答案是有!

MaterialApp 有一个 onGenerateRoute 属性,它在打开命名路由时可能会被调用,之所以说可能,是因为当调用 Navigator.pushNamed(...)打开命名路由时,如果指定的路由名在路由表中已注册,则会调用路由表中的 builder 函数来生成路由组件;如果路由表中没有注册,才会调用 onGenerateRoute 来生成路由

有了 onGenerateRoute 回调,要实现上面控制页面权限的功能就非常容易:我们放弃使用路由表,取而代之的是提供一个 onGenerateRoute 回调,然后在该回调中进行统一的权限控制,如:

MaterialApp(
... //省略无关代码
onGenerateRoute:(RouteSettings settings){
	  return MaterialPageRoute(builder: (context){
		String routeName = settings.name;
     // 如果访问的路由页需要登录,但当前未登录,则直接返回登录页路由,
     // 引导用户登录;其它情况则正常打开路由。
   }
 );
}
);

onUnknownRoute

onGenerateRoute 无法生成路由(initialRoute 除外)时调用

* 如果home首页指定了,routes里面就不能有'/'的根路由了,会报错,/指定的根路由就多余了
* 如果没有home指定具体的页面,那routes里面就傲有/来指定根路由
* 路由的顺序按照下面的规则来:
* 1、如果有home,就会从home进入
* 2、如果没有home,有routes,并且routes指定了入口'/',就会从routes的/进入
* 3、如果上面两个都没有,或者路由找不到,如果有 onGenerateRoute,就会进入生成的路由
* 4、如果连上面的生成路由也没有,就会走到onUnknownRoute,不明所以的路由,比如网络连接失败,可以进入断网的页面

navigatorObservers

导航路由在跳转时的回调,比如 push,pop,remove,replace 是,可以拿到当前路由和后面路由的信息。 route.settings.name 可以拿到路由的名字

builder

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

onGenerateTitle

如果非空,则调用此回调函数来生成应用程序的标题字符串,否则使用标题。如果想根据区域显示不同的描述使用 onGenerateTitle(内部回调有 context)

MaterialApp(
title: '老孟',
onGenerateTitle: (context) {
  var local = Localizations.localeOf(context);
  if (local.languageCode == 'zh') {
    return '老孟';
  }
  return 'laomeng';
},
...
)

locale

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

locale、localizationsDelegates、localeListResolutionCallback、localeResolutionCallback、supportedLocales 是区域设置和国际化相关的参数,如果 App 支持多国语言,那么就需要设置这些参数,默认情况下,Flutter 仅支持美国英语,如果想要添加其他语言支持则需要指定其他 MaterialApp 属性,并引入 flutter_localizations 包,到 2019 年 4 月,flutter_localizations 包已经支持 52 种语言,如果你想让你的应用在 iOS 上顺利运行,那么你还必须添加“flutter_cupertino_localizations”包

localizationsDelegates

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

localeListResolutionCallback

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

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'),
  );

supportedLocales

传入支持的语种数组

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

localeResolutionCallback

localeResolutionCallbacklocaleListResolutionCallback 的区别是 localeResolutionCallback 返回的第一个参数是当前语言的 Locale,而 localeListResolutionCallback 返回当前手机支持的语言集合

debugShowMaterialGrid

打开绘制基线网格材质应用程序的网格纸覆盖

showPerformanceOverlay

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

checkerboardRasterCacheImages

打开栅格缓存图像的棋盘格

checkerboardOffscreenLayers

打开渲染到屏幕外位图的图层的棋盘格

showSemanticsDebugger

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

debugShowCheckedModeBanner

在选中模式下打开一个小的“DEBUG”横幅,表示应用程序处于选中模式

🍝appBar

AppBar Material风格应用栏,有工具栏和其他的Widget构成 应用栏通常用于Scaffold.appBar属性,该属性将应用栏放置在屏幕顶部的固定高度小部件中。对于可滚动的应用栏,请参阅SliverAppBar,它将一个AppBar嵌入到一个条子中,以便在CustomScrollView中使用

在Flutter中,AppBar的布局主要由三个组件组成:leadingtitleactionsleading位于AppBar的最左边;titleactions出现在右边。

image.png

leading

在title组件前面的组件,可以被分配任何内容——文本、图标,甚至一行中有多个小部件。

AppBar(
  leading: Icon(Icons.account_circle_rounded),
),

image.png

如果没有提供leading, AppBar会自动为我们出现该出现的widget。示例包括返回到上一页的导航箭头或打开抽屉的菜单图标。

当之前的路由可用时,导航箭头自动出现。

class HomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: TextButton(
          child: Text('Push'),
          onPressed: () => Navigator.push(context, MaterialPageRoute(
            builder: (context) {
              return SecondPage();
            },
          )),
        ),
      ),
    );
  }
}

class SecondPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(),
    );
  }
}

image.png

当我们向Scaffold添加一个Drawer时,一个菜单图标被分配用于leading,为了打开该抽屉。

class HomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(),
      drawer: Drawer(),
    );
  }
}

image.png

可以利用属性automaticallyImplyLeading来阻止这种行为

leadingWidth

默认

image.png

AppBar(
  leading: Icon(Icons.account_circle_rounded),
  leadingWidth: 100, // default is 56
),

image.png

automaticallyImplyLeading

配合leading使用,取决于automaticallyImplyLeading == true,存在上一页路由或者抽屉的话,会自动出现相对应的ICON已经事件,false会阻止这种行为

title

appBar的主要部件,类型为Widget,它主要用于显示标题,如应用程序标题或页面标题。

AppBar(
  title: Text('Profile Page'),
),

但你并不局限于此,因为title也包含一个widget。您可以使用它来显示图标、图像、形状,或使用布局小部件(如行和列)来显示这些内容的任何组合。

AppBar(
  title: Container(
    width: 40,
    child: Image.network('https://book.flutterchina.club/logo.png'),
  ),
),

image.png

默认情况下,标题对齐到AppBar的中间,根据Material指南。你可以改变它,让它在中间左边:

AppBar(
  centerTitle: false
)

titleSpacing

centerTitle 为false,也就是标题在左边的时候titleSpacing就会生效

默认centerTitle应该是居中的center

有限度,太大就会失效

action占据足够宽的时候,标题也会居左[这个时候centerTitle也是有用的]

AppBar(
  centerTitle: false,
  titleSpacing:100
)

image.png

actions

title之后显示的部件,对齐到AppBar右侧的widget列表。我们经常在应用中看到它们作为按钮来触发下拉菜单、个人头像等。

AppBar(
    actions: [
        Container(
        width: 30,
        child: Image.network('https://book.flutterchina.club/logo.png'),
        ),
        Icon(Icons.more_vert),
    ]
)

image.png

现在我们已经熟悉了AppBar的布局,让我们通过使用主题选项来进行定制。AppBar包含各种属性,包括颜色、大小、图标主题、文本主题等。

backgroundColor

AppBar(
    backgroundColor: Colors.teal
)

image.png

iconTheme

icon主题设置

AppBar(
    iconTheme: IconThemeData(color: Colors.green, size: 36),
    actionsIconTheme: IconThemeData(color: Colors.red, size: 36)
)

actionsIconTheme会替换iconThemeaction上的样式

textTheme

设置文本的主题

AppBar(
  textTheme: TextTheme(
    headline1: TextStyle(fontSize: 72.0, fontWeight: FontWeight.bold),
    headline6: TextStyle(fontSize: 36.0, fontStyle: FontStyle.italic),
    bodyText2: TextStyle(fontSize: 14.0, fontFamily: 'Hind'),
  )
)

headline6用于flutter的AppBardialogs中的的主要文本(例如 AppBar.title and AlertDialog.title)。--下图右上角没有变化

image.png

elevation

控制下方阴影栏的坐标

AppBar(
  elevation: 102,
)

image.png

primary

app bar是否显示在屏幕顶部。

如果为true,app bar的工具栏元素和[bottom]小部件将被系统状态栏的高度填充在顶部。[flexibleSpace]的布局不受[primary]属性的影响。

shadowColor

AppBar(
  shadowColor: Colors.red,
  elevation: 102,
)

image.png

toolbarHeight

导航栏的高度,默认是56

AppBar(
  toolbarHeight: 120
)

image.png

toolbarOpacity

appBar导航栏的透明程度。值1.0是完全不透明的,值0.0是完全透明的

AppBar(
  toolbarOpacity: 0.5
)

image.png

flexibleSpace

类似于自由人,默认出现在右上角

AppBar(
  title: Text('你三十岁'),
  flexibleSpace: Text('你三十岁'),
)

image.png

bottom

PreferredSize类型

可以看做在appbar的下面再加一块

AppBar(
    bottom: PreferredSize(
      preferredSize: const Size.fromHeight(48.0),
      child: Theme(
        data: Theme.of(context).copyWith(accentColor: Colors.white),
        child: Container(
            height: 48.0, alignment: Alignment.center, child: Text('你三十岁')),
      ),
    )
)

image.png

PreferredSize

自定义底部

此控件不对其子控件施加任何约束,并且不以任何方式影响孩子的布局。

此控件对自定义AppBar.bottom和AppBar非常有用

自定义AppBar,也可以直接设置AppBar的高度(PreferredSize子控件为AppBar)

appBar: PreferredSize(
    preferredSize: Size.fromHeight(200),
    child: Container(
      color: Colors.blue,
    ),
  )

bottomOpacity

bottom部分的的透明度(不多表述)

shape

形状

AppBar(
  shape: BeveledRectangleBorder(
            borderRadius: BorderRadius.circular(18))
)

image.png

brightness

状态栏字体颜色

  • light:黑色
  • dark:白色
AppBar(
  brightness: Brightness.light,
)

backwardsCompatibility

不重要的属性--默认为false(官方不推荐使用吧 舍弃)

foregroundColor

应用栏材质的亮度

primary

appbar是否显示在任务栏顶部

🍝Scaffold

Scaffold 实现了基本的 Material 布局。只要是在 Material 中定义了的单个界面显示的布局控件元素,都可以使用 Scaffold 来绘制。

提供展示抽屉(drawers,比如:左边栏)、通知(snack bars) 以及 底部按钮(bottom sheets)。

我们可以将 Scaffold 理解为一个布局的容器。可以在这个容器中绘制我们的用户界面

appBar

页面上方导航条--这个就可以直接看上面的AppBar

body

页面容器--直接布局走起来(下一步就是研究布局)

floatingActionButton

悬浮的按钮

Scaffold(
  floatingActionButton: FloatingActionButton(
        onPressed: ()=>{},
        tooltip: 'Increment',
        child: Icon(Icons.add),
  )
)

image.png

研究一下FloatingActionButton
  • child

子控件,通常为 Icon

FloatingActionButton(
    child: Icon(Icons.add),
)
  • tooltip

长按的结束时候会出现提示语

FloatingActionButton(
  tooltip:"牛逼吧",
  child: Icon(Icons.add),
)

image.png

  • foregroundColor

Icon 与 Text 颜色

FloatingActionButton(
  tooltip:"牛逼吧",
  foregroundColor: Colors.green,
  child: Icon(Icons.add),
)

image.png

其他的属性直接看这里[基本都是样式]:www.jianshu.com/p/6ced97ac7…

floatingActionButtonLocation : 悬浮按钮位置

Scaffold(
  floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked,
  floatingActionButton: FloatingActionButton(
        onPressed: ()=>{},
        tooltip: 'Increment',
        child: Icon(Icons.add),
  )
)

image.png

FloatingActionButtonLocation有很多个位置属性

  • centerDocked:底部中间偏下
  • centerFloat:底部中间偏上
  • centerTop:顶部中间
  • endDocked:右下角偏下
  • endFloat:右下角偏上[ 默认 ]
  • endTop:右上角偏上
  • startDocked:左下角偏下
  • startFloat:左下角偏上
  • startTop:左上角偏上

以上所有的前面加一个mini就是全是小一点的,例如miniEndDocked记得驼峰

floatingActionButtonAnimator

medium.com/@agungsurya…

悬浮按钮动画

Scaffold(
  floatingActionButtonAnimator: FloatingActionButtonAnimator.scaling,
  floatingActionButton: FloatingActionButton(
        onPressed: ()=>{},
        tooltip: 'Increment',
        child: Icon(Icons.add),
  )
)

persistentFooterButtons

在底部呈现一组button,显示于[bottomNavigationBar]之上,[body]之下

它还有一个特性是,会受到Scaffold设置的颜色之类属性的影响。准确来说,是因为persistentFooterButtons只接收一个数组,所以它的任何自身的属性,都是受到Scaffold的其它属性影响的。

image.png

  • drawer : 左侧菜单
Scaffold(
  drawer: Drawer(),
)

chensong.blog.csdn.net/article/det…

www.jianshu.com/p/728692143…

  • endDrawer : 右侧菜单
Scaffold(
  drawer: Drawer(),
)
  • bottomNavigationBar : 底部导航TabBar

  • bottomSheet : 一个持久停留在body下方,底部控件上方的控件

  • backgroundColor : 背景色

  • resizeToAvoidBottomInset : 控制界面内容 body 是否重新布局来避免底部被覆盖了,比如当键盘显示的时候,重新布局避免被键盘盖住内容。默认值为 true。

  • primary : 是否在屏幕顶部显示Appbar, 默认为 true,Appbar 是否向上延伸到状态栏,如电池电量,时间那一栏

  • drawerDragStartBehavior : 控制 drawer 的一些特性

  • extendBody : body 是否延伸到底部控件

  • extendBodyBehindAppBar : 默认 false,为 true 时,body 会置顶到 appbar 后,如appbar 为半透明色,可以有毛玻璃效果

  • drawerScrimColor : 遮罩颜色

  • drawerEdgeDragWidth : 侧滑栏拉出来的宽度

  • drawerEnableOpenDragGesture : 左侧侧滑栏是否可以滑动

  • endDrawerEnableOpenDragGesture : 右侧侧滑栏是否可以滑动

三个Button,TextButtonOutlinedButtonElevatedButton

🍝TextButton

TextButton(
    child: Text('牛逼'),
    onPressed: () {},
    onLongPress : () {}
)

image.png

🍝OutlinedButton

OutlinedButton(
    child: Text("牛逼"),
    onPressed: () {},
    onLongPress : () {}
)

image.png

🍝ElevatedButton

ElevatedButton(
    child: Text('牛逼'),
    onPressed: () {},
    onLongPress : () {}
)

image.png

样式[以ElevatedButton为例]

首先先解决MaterialStateProperty 问题

ButtonStyle的样式都需要MaterialStateProperty去包裹

  • MaterialStateProperty.all

    是包裹value返回组件需要的样式

  • MaterialStateProperty.resolveAs[辅助函数]

    看源码得知他首先回去判断传进来的值是否是MaterialStateProperty.all包裹之后的值,如果是那就解套返回最初是的value,如果不是,那就直接返回

  • MaterialStateProperty.resolveWith

    这个就是解套的方法,把MaterialStateProperty.all包裹的值再一次接套返回最初的value

  • textStyle       // 字体

      ElevatedButton(
          child: Text("牛逼"),
          style: ButtonStyle(
              textStyle: MaterialStateProperty.all(TextStyle(fontSize: 36)), 
          ),
          onPressed: () {},
      )
    

    image.png

  • fixedSize // 按钮的大小。这个大小仍然受到样式的minimumSize的限制

    ElevatedButton(
          child: Text("牛逼"),
          style: ButtonStyle(
          fixedSize: MaterialStateProperty.all(Size(50, 20)),
          minimumSize: MaterialStateProperty.all(Size(300, 100))),
          onPressed: () {},
      )
    

    image.png

  • backgroundColor // 背景色

      ElevatedButton(
          child: Text("牛逼"),
          style: ButtonStyle(
              backgroundColor: MaterialStateProperty.all(Color(0xff2DF8F2))), 
          ),
          onPressed: () {},
      )
    

    image.png

  • foregroundColor // 字体颜色

      ElevatedButton(
          child: Text("牛逼"),
          style: ButtonStyle(
             foregroundColor: MaterialStateProperty.all(Color(0xff000000)),), 
          ),
          onPressed: () {},
      )
    

    image.png

  • overlayColor    // 高亮色,按钮处于focused, hovered, or pressed时的颜色

      ElevatedButton(
          child: Text("牛逼"),
          style: ButtonStyle(
              overlayColor: MaterialStateProperty.all(Color(0xff31C27C))
              ,), 
          ),
          onPressed: () {},
      )
    

    image.png

  • shadowColor     // 阴影颜色

    点击的时候会更加显眼

      ElevatedButton(
          child: Text("牛逼"),
          style: ButtonStyle(
              shadowColor: MaterialStateProperty.all(Colors.red)),), 
          ),
          onPressed: () {},
      )
    

    image.png

  • elevation       // 阴影值

    一层阴影(shadowColor设置这层阴影的颜色)

      ElevatedButton(
          child: Text("牛逼"),
          style: ButtonStyle(
              elevation: MaterialStateProperty.all(5),), 
          ),
          onPressed: () {},
      )
    

    image.png

  • padding         // padding

    ElevatedButton(
          child: Text("牛逼"),
          style: ButtonStyle(
               padding: MaterialStateProperty.all(EdgeInsets.all(36)),), 
          ),
          onPressed: () {},
      )
    

    image.png

  • minimumSize     // 最小尺寸

    ElevatedButton(
          child: Text("牛逼"),
          style: ButtonStyle(
          minimumSize: MaterialStateProperty.all(Size(300, 100)),
          ),
          onPressed: () {},
      )
    

    image.png

  • side            // 边框

    ElevatedButton(
          child: Text("牛逼"),
          style: ButtonStyle(
          side: MaterialStateProperty.all(
              BorderSide(width: 4, color: Color(0xffffffff))), //边框
          ),
          onPressed: () {},
      )
    

    image.png

  • shape           // 形状

    菱形

    ElevatedButton(
          child: Text("牛逼"),
          style: ButtonStyle(
          shape: MaterialStateProperty.all(BeveledRectangleBorder(
              borderRadius: BorderRadius.circular(8))), //圆角弧度
          ),
          onPressed: () {},
      )
    

    image.png

    圆形

    ElevatedButton(
         child: Text("牛逼"),
         style: ButtonStyle(
         shape: MaterialStateProperty.all(CircleBorder(
             side: BorderSide(
             style: BorderStyle.none,
         ))), //圆角弧度
         ),
         onPressed: () {},
     )
    

    image.png

  • mouseCursor    // 鼠标指针的光标进入或悬停在此按钮的[InkWell]上时(这个用于Web端或PC端)

  • visualDensity   // 按钮布局的紧凑程度

    可以扩展上下左右距离

    ElevatedButton(
          child: Text("牛逼"),
          style: ButtonStyle(
          visualDensity: VisualDensity(
              horizontal: 2.0,
              vertical: 2.0), //set the button's visual density
          ),
          onPressed: () {},
      )
    

    image.png

  • tapTargetSize   // 响应触摸的区域

    置MaterialTapTarget大小。可以设置为:值,填充和收缩包装属性

    MaterialTapTargetSize有2个值,分别为:

    • padded:最小点击区域为48*48。[实际大小小于48,依旧以48算]
    • shrinkWrap:子组件的实际大小。
    ElevatedButton(
          child: Text("牛逼"),
          style: ButtonStyle(
              minimumSize: MaterialStateProperty.all(Size(20, 20)),
              tapTargetSize: MaterialTapTargetSize.padded),
          onPressed: () {},
      )
    

    下图高度没过48--只有20,按钮下面也可以触发按钮

    image.png

  • animationDuration // [shape]和[elevation]的动画更改的持续时间。

    animationDuration: Duration.zero
    
  • enableFeedback   // 检测到的手势是否应提供声音和/或触觉反馈。例如,在Android上,点击会产生咔哒声,启用反馈后,长按会产生短暂的振动。通常,组件默认值为true。

  • alignment // 设置按钮childAlignment

alignment: Alignment.bottomCenter

styleFrom 会构造出 ButtonStyle

无状态组件StatelessWidget和有状态组件StatefulWidget

StatelessWidget

Flutter中的StatelessWidget是一个不需要状态更改的widget - 它没有要管理的内部状态。(有点React的意思啊---其实就是受控组件(外部给的状态)和非受控组件(内部自己管理状态))

IconImageIconDialogAboutDialog, CircleAvatarText 等都是StatelessWidget的子类

class TextRed extends StatelessWidget {
  final String text;

  const TextRed({Key? key, required this.text}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Text(
      text,
      style: TextStyle(color: Colors.red),
    );
  }
}

TextRed从外部接受一个数据源text并将它显示。 无状态的组件的声明周期只有一个:build,它只会在三种情况下被调用:

  • 将widget插入树中的时候,也就是第一次构建(初始化的时候)
  • 当widget的父级更改了其传入的值时,例如,TextRed的父类改变了text的值
  • 当它依赖的InheritedWidget发生变化时(InheritedWidget打一个问号---著名的 Provider 状态管理框架也是基于 InheritedWidget 实现的,因此不管是工作中,还是面试,InheritedWidget 组件的原理及使用场景都是考察的重点。)

使用Statelesswidget更轻量,更节省内存资源。初始化Statelesswidget的时候不会附带一些动态更新UI的方法,这样也会提升我们软件的性能。

StatefulWidget

StatefulWidget 是可变状态的widget。 使用setState方法管理StatefulWidget的状态的改变。调用setState告诉Flutter框架,某个状态发生了变化,Flutter会重新运行build方法,以便应用程序可以应用最新状态。

它实现了一个setState方法,当我们调用这个方法的时候,该Statefulwidget会被重新渲染,注意是重新被渲染,而不是局部更新。 当我们调用setState时,Flutter在收到该消息后,会重新调用其build方法重新构建这个widget,从而达到更新UI的目的。

Checkbox, Radio, Slider, InkWell, Form, 和 TextField 等都是有状态的widget,也是StatefulWidget的子类。

class StatefulWidgetDemoPage extends StatefulWidget {
  @override
  _StatefulWidgetDemoPageState createState() => _StatefulWidgetDemoPageState();
}

class _StatefulWidgetDemoPageState extends State<StatefulWidgetDemoPage> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          setState(() {});
        },
        child: Icon(Icons.add),
      ),
      appBar: AppBar(
        title: Text("StatefuleWidget Demo"),
        centerTitle: true,
        backgroundColor: Colors.blue,
      ),
      body: Column(
        children: [
          Container(
            width: 100,
            height: 100,
            margin: EdgeInsets.all(10),
            /// 颜色一个随机值
            color: _randomColor(),
          ),
        ],
      ),
    );
  }

  /// 获取一个随机的颜色值
  _randomColor() {
    return Color.fromARGB(255, Random().nextInt(255), Random().nextInt(255),
        Random().nextInt(255));
  }
}

我们定义了一个生成随机颜色的方法_randomColor(),它会返回一个Color对象,然后我们又定义了一个Container,Container的初始化参数color的值是_randomColor()的返回值。然后我们在FloatingActionButton的onPressed的方法中调用一下setState方法,这个时候Flutter会重新绘制StatefulWidgetDemoPage,所以每次点击按钮,我们可以看到Container的颜色都是不一样的。

布局

线性布局

所谓线性布局,即指沿水平或垂直方向排布子组件。Flutter中通过Row和Column来实现线性布局,类似于Android中的LinearLayout控件。Row和Column都继承自Flex

Row 水平布局----Column类推

  • children

widget的数组

Drawer(
  child: Row(children: <Widget>[
    Text('This is Drawer'),
    Text('This is Drawer'),
    Text('This is Drawer'),
  ]),
)

image.png

  • textDirection

表示水平方向子组件的布局顺序(是从左往右还是从右往左),默认为系统当前Locale环境的文本方向(如中文、英语都是从左往右,而阿拉伯语是从右往左)

Drawer(
  Row(
    textDirection: TextDirection.rtl, 
    children: <Widget>[
            Text('This'),
            Text(' is '),
            Text('Drawer'),
          ]),
        )
)

image.png

  • mainAxisAlignment

表示子组件在Row所占用的水平空间内对齐方式,textDirection是mainAxisAlignment的参考系。

Row(
  textDirection:ltr,
  // MainAxisAlignment.end:右对齐
  // MainAxisAlignment.center:居中
  // MainAxisAlignment.start:左对齐
  mainAxisAlignment: MainAxisAlignment.end
)
Row(
  textDirection:rtl,
  // MainAxisAlignment.end:左对齐
  // MainAxisAlignment.center:居中
  // MainAxisAlignment.start:右对齐
  mainAxisAlignment: MainAxisAlignment.end
)
  • verticalDirection

    表示Row纵轴(垂直)的对齐方向,默认是VerticalDirection.down,表示从上到下。

  • mainAxisSize

表示Row在主轴(水平)方向占用的空间,默认是MainAxisSize.max,表示尽可能多的占用水平方向的空间,此时无论子widgets实际占用多少水平空间,Row的宽度始终等于水平方向的最大宽度;而MainAxisSize.min表示尽可能少的占用水平空间,当子组件没有占满水平剩余空间,则Row的实际宽度等于所有子组件占用的的水平空间;

  • crossAxisAlignment

类似于MainAxisAlignment,这个指的是纵轴

  • textBaseline

文本的基准线

弹性布局

弹性布局允许子组件按照一定比例来分配父容器空间。弹性布局的概念在其它UI系统中也都存在,如H5中的弹性盒子布局,Android中的FlexboxLayout等。Flutter中的弹性布局主要通过Flex和Expanded来配合实现

Flex

Flex组件可以沿着水平或垂直方向排列子组件,如果你知道主轴方向,使用Row或Column会方便一些,因为Row和Column都继承自Flex,参数基本相同,所以能使用Flex的地方基本上都可以使用Row或Column。Flex本身功能是很强大的,它也可以和Expanded组件配合实现弹性布局。接下来我们只讨论Flex和弹性布局相关的属性

  • direction 弹性布局的方向, Row默认为水平方向,Column默认为垂直方向
Flex(
  direction: Axis.horizontal,
)
Flex(
  direction: Axis.vertical,
)
  • children:子组件数组

Expanded

类似于html的弹性属性

可以按比例“扩伸” Row、Column和Flex子组件所占用的空间。

flex参数为弹性系数,如果为0或null,则child是没有弹性的,即不会被扩伸占用的空间。如果大于0,所有的Expanded按照其flex的比例来分割主轴的全部空闲空间

Flex(
  direction: Axis.horizontal,
  children: <Widget>[
    Expanded(
      flex: 1,
      child: Container(
        width: 301,
        height: 30.0,
        color: Colors.red,
      ),
    ),
    Expanded(
      flex: 2,
      child: Container(
        height: 30.0,
        color: Colors.green,
      ),
    ),
  ],
)

这一块打算后面再看

流式布局

解决flex布局文本超过边界问题

Wrap

除了超出显示范围后Wrap会折行外,其它行为基本Row相同

Wrap的特有属性

  • spacing

主轴方向子widget的间距

Padding(
  padding: EdgeInsets.only(top: 100),
  child: Wrap(
    spacing: 58.0, // 主轴(水平)方向间距
    alignment: WrapAlignment.center, //沿主轴方向居中
    children: <Widget>[
      new Chip(
        avatar: new CircleAvatar(
            backgroundColor: Colors.blue, child: Text('A')),
        label: new Text('Hamilton'),
      ),
      new Chip(
        avatar: new CircleAvatar(
            backgroundColor: Colors.blue, child: Text('M')),
        label: new Text('Lafayette'),
      ),
      new Chip(
        avatar: new CircleAvatar(
            backgroundColor: Colors.blue, child: Text('H')),
        label: new Text('Mulligan'),
      ),
      new Chip(
        avatar: new CircleAvatar(
            backgroundColor: Colors.blue, child: Text('J')),
        label: new Text('Laurens'),
      ),
    ],
  ),
)

image.png

  • runSpacing

纵轴方向的间距

放到Flex容器里面会失效

Wrap(
    runSpacing: 68.0, // 纵轴方向的间距
    children: [
      // .....
    ],
  )

image.png

  • runAlignment

纵轴方向的对齐方式

Wrap(
    alignment: WrapAlignment.end, //沿主轴方向右对齐
    children: [
      // .....
    ],
  )

image.png

image.png

Flow

Flow的使用挺复杂,需要自己实现子widget的位置转换,在很多场景下首先要考虑的是Wrap是否满足需求。Flow主要用于一些需要自定义布局策略或性能要求较高(如动画中)的场景。Flow有如下优点:

  • 性能好;Flow是一个对子组件尺寸以及位置调整非常高效的控件,Flow用转换矩阵在对子组件进行位置调整的时候进行了优化:在Flow定位过后,如果子组件的尺寸或者位置发生了变化,在FlowDelegate中的paintChildren()方法中调用context.paintChild 进行重绘,而context.paintChild在重绘时使用了转换矩阵,并没有实际调整组件位置。

  • 灵活;由于我们需要自己实现FlowDelegate的paintChildren()方法,所以我们需要自己计算每一个组件的位置,因此,可以自定义布局策略。

class MyFlowDelegate extends FlowDelegate {
  @override
  void paintChildren(FlowPaintingContext context) {
    /*屏幕宽度*/
    var screenW = context.size.width;

    double padding = 5; //间距
    double offsetX = padding; //x坐标
    double offsetY = padding; //y坐标

    for (int i = 0; i < context.childCount; i++) {
      /*如果当前x左边加上子控件宽度小于屏幕宽度  则继续绘制  否则换行*/
      if (offsetX + boxSize < screenW) {
        /*绘制子控件*/
        context.paintChild(i,
            transform: Matrix4.translationValues(offsetX, offsetY, 0));
        /*更改x坐标*/
        offsetX = offsetX + boxSize + padding;
      } else {
        /*将x坐标重置为margin*/
        offsetX = padding;
        /*计算y坐标的值*/
        offsetY = offsetY + boxSize + padding;
        /*绘制子控件*/
        context.paintChild(i,
            transform: Matrix4.translationValues(offsetX, offsetY, 0));
      }
    }
  }

  @override
  bool shouldRepaint(FlowDelegate oldDelegate) {
    return true;
  }
}
Flow(
  delegate: MyFlowDelegate(),
  children: <Widget>[
    new Container(
      width: 80.0,
      height: 80.0,
      color: Colors.red,
    ),
    new Container(
      width: 80.0,
      height: 80.0,
      color: Colors.green,
    ),
    new Container(
      width: 80.0,
      height: 80.0,
      color: Colors.blue,
    ),
  ],
)

层叠布局 Stack、Positioned

按照代码中声明的顺序,允许子组件堆叠起来(子组件可以根据距父容器四个角的位置来确定自身的位置)

层叠布局和Web中的绝对定位、Android中的Frame布局是相似的

Flutter 中是使用Stack和Positioned这两个组件来配合实现绝对定位

  • alignment

    此参数决定如何去对齐没有定位(没有使用Positioned)或部分定位的子组件。

    所谓部分定位,在这里特指没有在某一个轴上定位[没有left、right、top、bottom其中一个属性]:left、right为横轴,top、bottom为纵轴,只要包含某个轴上的一个定位属性就算在该轴上有定位。

    Stack(
      alignment:Alignment.center , //指定未定位或部分定位widget的对齐方式
      children: <Widget>[
        Container(child: Text("Hello world",style: TextStyle(color: Colors.white)),
          color: Colors.red,
        ),
        Positioned(
          left: 18.0,
          child: Text("I am Jack"),
        ),
        Positioned(
          top: 18.0,
          child: Text("Your friend"),
        )        
      ],
    )
    

    image.png

    我们可以看到Your friend顶部18的中间位置,I am Jack左边18的中间位置,Hello world在正中间位置--没毛病

对齐与相对定位

Center 居中

Center(
  child: Text('Hello Flutter')
),

Align(对齐布局)

  • alignment = Alignment.center:
  • widthFactor:宽度因子
  • heightFactor:高度因子
Align(
  alignment: FractionalOffset.topRight,
  child: Text('Hello'),
)

widthFactor和heightFactor是用于确定Align 组件本身宽高的属性;它们是两个缩放因子,会分别乘以子元素的宽、高,最终的结果就是Align 组件的宽高

Align(
  widthFactor: 2,
  heightFactor: 2,
  alignment: Alignment.topRight,
  child: FlutterLogo(
    size: 60,
  ),
)

因为FlutterLogo的宽高为60,则Align的最终宽高都为2*60=120

  • Alignment 属性

它有两个常用的子类:Alignment和 FractionalOffset

  • 内置的位置属性
topLeft = Alignment(-1.0, -1.0)
topCenter = Alignment(0.0, -1.0)
topRight = Alignment(1.0, -1.0)
centerLeft = Alignment(-1.0, 0.0)
center = Alignment(0.0, 0.0)
centerRight = Alignment(1.0, 0.0)
bottomLeft = Alignment(-1.0, 1.0)
bottomCenter = Alignment(0.0, 1.0)
bottomRight = Alignment(1.0, 1.0)
  • 自定义
(Alignment.x*childWidth/2+childWidth/2, Alignment.y*childHeight/2+childHeight/2)

Alignment(-0.5,0.5)
  • 实例
 Align(
  widthFactor: 2,
  heightFactor: 2,
  alignment: Alignment(2,0.0),
  child: FlutterLogo(
    size: 60,
  ),
)

将Alignment(2,0.0)带入上述坐标转换公式,可以得到FlutterLogo的实际偏移坐标为(90,30)

  • FractionalOffset

FractionalOffset 继承 Alignment,他们 2 个区别就是坐标系不一样,Alignment 的原点是中心,而 FractionalOffset 原点是左上角。

  • 内置的位置属性
topLeft = FractionalOffset(0.0, 0.0)
topCenter = FractionalOffset(0.5, 0.0)
topRight = FractionalOffset(1.0, 0.0)
centerLeft = FractionalOffset(0.0, 0.5)
center = FractionalOffset(0.5, 0.5)
centerRight = FractionalOffset(1, 0.5)
bottomLeft = FractionalOffset(0.0, 1.0)
bottomCenter = FractionalOffset(0.5, 1.0)
bottomRight = FractionalOffset(1.0, 1.0)
  • 自定义
实际偏移 = (FractionalOffse.x * childWidth, FractionalOffse.y * childHeight)

FractionalOffset(0.5,0.5)
  • 实例
Container(
  height: 120.0,
  width: 120.0,
  color: Colors.blue[50],
  child: Align(
    alignment: FractionalOffset(0.2, 0.6),
    child: FlutterLogo(
      size: 60,
    ),
  ),
)

将FractionalOffset(0.2, 0.6)带入坐标转换公式得FlutterLogo实际偏移为(12,36)

  • AlignmentDirectional

AlignmentDirectional 的坐标系和 Alignment 比较像,原点在中心,不过 AlignmentDirectional 的起始位置和书写(TextDirection)方向有关

TextDirection可以决定AlignmentDirectional坐标系

同上

容器(常用的盒子)

Padding

Padding 在 Flutter 中用的也挺多的,作为一个基础的控件,功能非常单一,给子节点设置 padding 属性。写过其他端的都了解这个属性,就是设置内边距属性,内边距的空白区域,也是 widget 的一部分。

Flutter 中并没有单独的 Margin 控件,在 Container 中有 margin 属性,看源码关于 margin 的实现。

if (margin != null)
  current = new Padding(padding: margin, child: current);

EdgeInsets

Padding(
  //上下左右各添加16像素补白
  padding: EdgeInsets.all(16.0),
  child:Text("Hello world"),
)

Padding(
  //左边添加8像素补白
  padding: const EdgeInsets.only(left: 8.0),
  child: Text("Hello world"),
),

Padding(
  //上下各添加8像素补白
  padding: const EdgeInsets.symmetric(vertical: 8.0),
  child: Text("Hello world"),
)

Padding(
  //左右各添加8像素补白
  padding: const EdgeInsets.symmetric(horizontal: 8.0),
  child: Text("Hello world"),
)

Padding(
  //分别指定四个方向的补白
  padding: const EdgeInsets.fromLTRB(20.0,.0,20.0,20.0),
  child: Text("Hello world"),
)

尺寸容器

  • ConstrainedBox:适用于需要设置最大/小宽高,组件大小以来子组件大小,但不能超过设置的界限。

  • UnconstrainedBox:用到情况不多,当作- ConstrainedBox的子组件可以“突破”ConstrainedBox的限制,超出界限的部分会被截取。

  • SizedBox:适用于固定宽高的情况,常用于当作2个组件之间间隙组件。

  • AspectRatio:适用于固定宽高比的情况。

  • FractionallySizedBox:适用于占父组件百分比的情况。

  • LimitedBox:适用于没有父组件约束的情况。

  • Container:适用于不仅有尺寸的约束,还有装饰(颜色、边框、等)、内外边距等需求的情况。

ConstrainedBox

ConstrainedBox组件约束子组件的最大宽高和最小宽高,假如一个组件宽高都是300,包裹在ConstrainedBox中,并给ConstrainedBox添加最大宽高约束,用法如下:

ConstrainedBox(
  constraints: BoxConstraints(maxHeight: 60, maxWidth: 200),
  child: Container(height: 300, width: 300, color: Colors.red),
)

这时子组件是无法突破BoxConstraints设置的最大宽高:

image.png

如果BoxConstraints嵌套使用,有2个ConstrainedBox

ConstrainedBox(
  constraints: BoxConstraints(maxHeight: 60, maxWidth: 200),
  child: ConstrainedBox(
    constraints: BoxConstraints(maxHeight: 100, maxWidth: 240),
    child: Container(height: 300, width: 300, color: Colors.red),
  ),
)

以最大宽为例,第一个BoxConstraints的maxHeight值是60,也就是约束其子控件最大高是60,第二个BoxConstraints的maxHeight值是100,由于第二个BoxConstraints也受第一个的约束,所以第二个BoxConstraints最大高也只能是60,最终子组件的最大高是60,同理最大宽是200,因此多级BoxConstraints嵌套约束最大值最终值等于多个BoxConstraints约束中的最小值。同理嵌套约束最小值等于多个BoxConstraints约束中的最大值

  • UnconstrainedBox

区别是不做截取操作

image.png

因此在开发中会出现这个黄色越界边框

  • SizedBox

SizedBox是具有固定宽高的组件,直接指定具体的宽高

SizedBox(
  height: 60,
  width: 200,
  child: RaisedButton(
    child: Text('this is SizedBox'),
  ),
)

我们也可以设置尺寸无限大

SizedBox(
  height: double.infinity,
  width: double.infinity,
  ...
)

SizedBox可以没有子组件,但仍然会占用空间,所以SizedBox非常适合控制2个组件之间的空隙

Column(
  children: <Widget>[
    Container(height: 30,),
    SizedBox(height: 10,),
    Container(height: 30,),
  ],
)
  • AspectRatio

是固定宽高比的组件

如果组件的宽度固定,希望高是宽的1/2,可以用AspectRatio实现此效果:

AspectRatio(
  aspectRatio: 2 / 1, // 可以直接写成分数的形式--可读性更高
  child: Container(color: Colors.red),
)
  • FractionallySizedBox

当我们需要一个控件的尺寸是相对尺寸时,比如当前按钮的宽度占父组件的70%,可以使用FractionallySizedBox来实现此效果。

使用FractionallySizedBox包裹子控件,设置widthFactor宽度系数或者heightFactor高度系数,系数值的范围是0-1,0.7表示占父组件的70%,用法如下:

FractionallySizedBox(
  widthFactor: .7,
  child: RaisedButton(
    child: Text('button'),
  ),
)

通过alignment参数控制子组件显示的位置,默认为center:

FractionallySizedBox(
  alignment: Alignment.centerLeft,
  ...
)

间隔父元素的百分之10

Column(
    children: <Widget>[
      Container(
        height: 50,
        color: Colors.red,
      ),
      Flexible(
        child: FractionallySizedBox(
          heightFactor: .1,
        ),
      ),
      Container(
        height: 50,
        color: Colors.blue,
      ),
    ],
  )
  • Container

Container组件应该是最常用的组件之一,Container组件可以直接设置其宽高

Container(
  height: 100,
  width: 100,
  ...
)

Container组件是这些组件里面属性最多的一个,当然也是用法最复杂的一个

  • this.alignment 对齐方式
Container(
  alignment: Alignment.bottomRight,
  transform: Matrix4.rotationX(1),
  child: Text('牛了啊'),
)

image.png

  • this.padding Container和子元素之间添加空白可以使用padding属性
  • this.color 背景颜色
  • this.decoration decoration属性可以设置子控件的背景颜色、形状等
Container(
  child: Text('牛了啊'),
    decoration: BoxDecoration(
      shape: BoxShape.circle,
        color: Colors.red
  ),
)

image.png

deoration和 color: 背景颜色不能共存,二者同时只能有一个
  • this.foregroundDecoration 绘制在child前面的装饰
Container(
    child: Text('牛了啊'),
    foregroundDecoration: BoxDecoration(
        shape: BoxShape.rectangle, color: Colors.indigo),
    decoration:
        BoxDecoration(shape: BoxShape.circle, color: Colors.red),
  )

image.png

Container(
    child: Text('牛了啊'),
    foregroundDecoration: BoxDecoration(
        shape: BoxShape.rectangle, color: Colors.transparent),
    decoration:
        BoxDecoration(shape: BoxShape.circle, color: Colors.red),
  )

image.png

  • double? width 宽度
  • double? height 高度
  • BoxConstraints? constraints,
  • this.margin 外边距,用法与padding一样
  • this.transform 通过transform可以旋转、平移、缩放Container
Container(
  transform: Matrix4.rotationX(1),// 倾斜变换
  child: Text('牛了啊'),
)
  • this.transformAlignment,

旋转、平移、变化的对齐方式

image.png

上图不存在 transformAlignment 属性

Container(
  transform: Matrix4.rotationX(1),
  transformAlignment: Alignment.bottomRight,
  child: Text('牛了啊'),
)

image.png

与alignment的区别的父容器的区别

  • this.child 子组件

  • this.clipBehavior 剪切行为--默认不剪切超出边界的内容

  • LimitedBox

LimitedBox组件是当不受父组件约束时,可以用LimitedBox限制尺寸

如果LimitedBox的父组件受到约束,此时LimitedBox将会不做任何操作,我们可以认为没有这个组件,代码如下:

children:[
  Container(
    height: 100,
    width: 100,
    child: LimitedBox(
      maxHeight: 50,
      maxWidth: 100,
      child: Container(
        color: Colors.green,
      ),
    ),
  )
]

image.png

LimitedBox设置的宽高不是正方形,此时效果时正方形,说明LimitedBox没有起作用。

在ListView中直接添加Container组件:

ListView(
  children: <Widget>[
    Container(
      color: Colors.green,
    ),
    Container(
      color: Colors.red,
    ),
  ],
)

这时你会发现什么也没有,因为在容器不受约束时,大小将会设置0,只需将Container包裹在LimitedBox(SizedBox都可以--反正就是需要宽高,在没有约束的富容器中不能使用Container)中即可:

image.png

ListView(
  children: <Widget>[
    LimitedBox(
      maxHeight: 100,
      child: Container(
        color: Colors.green,
      ),
    ),
    LimitedBox(
      maxHeight: 100,
      child: Container(
        color: Colors.red,
      ),
    ),
  ],
)
  • Clip (剪裁)

    • ClipOval : 子组件为正方形时剪裁为内贴圆形,为矩形时,剪裁为内贴椭圆
    SizedBox(
      height: 100,
      child: ClipOval(
          child: Container(
        color: Colors.green,
      )), //剪裁为圆形
    )
    

    image.png

    • ClipRRect : 将子组件剪裁为圆角矩形
    ClipRRect(
      borderRadius: BorderRadius.all(Radius.elliptical(30, 30)),
      child: Container(
        color: Colors.green,
    ))
    ClipRRect(
      //剪裁为圆角矩形
      borderRadius: BorderRadius.circular(5.0),
      child: Container(
        color: Colors.green,
      ),
    )
    
    • ClipRect : 剪裁子组件到实际占用的矩形大小(溢出部分剪裁)
    Row(
      mainAxisAlignment: MainAxisAlignment.center,
      children: <Widget>[
        ClipRect(
          //将溢出部分剪裁
          child: Align(
            alignment: Alignment.topLeft,
            widthFactor: .5, //宽度设为原来宽度一半
            child: Text("冲冲冲"),
          ),
        ),
        Text("你好世界", style: TextStyle(color: Colors.green))
      ],
    )
    

    image.png

    • CustomClipper

    剪裁子组件的特定区域

    class MyClipper extends CustomClipper<Rect> {
      @override
      Rect getClip(Size size) => Rect.fromLTWH(10.0, 15.0, 40.0, 30.0);
    
      @override
      bool shouldReclip(CustomClipper<Rect> oldClipper) => false;
    }
    
    ClipRect(
        clipper: MyClipper(), //使用自定义的clipper
        child: avatar
    )