Flutter 组件总结

7,800 阅读9分钟

前言

该文章只是自己这两天看电子书的总结,如果想看一手知识的可以转到电子书book.flutterchina.club/chapter7/th…。前端开发无非就两步第一是布局第二是请求数据刷新界面,这两天看完感觉至少写一些简单的界面是没问题了。剩下的请求以及一些第三方库有时间再继续。对于没有打过Flutter代码的朋友我建议看完至少还要自己动手打一下,不用全打,因为打跟看是两回事,有时候看懂了打的时候还是要看一下。

StatefulWidget

动态组件,后期需要改变显示状态的需要用动态组件

动态组件的定义

快捷键

  • stful 会自动生成相应最简单的想要代码
  • stanim 会生成带有生命周期的代码
  • 还有其他快捷键可以自己去尝试

生成的代码

class MyHomePage extends StatefulWidget {
  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  @override
  Widget build(BuildContext context) {
    return Container();
  }
}
  • State是一个保存负责控件生命周期的类,还有以下一些方法。包括初始化、构建、销毁等等使用setState方法时会刷新对应的回调
  @override
  void initState() {
    _controller = AnimationController(vsync: this);
    super.initState();
  }

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }
  
  @override
  Widget build(BuildContext context) {
    return Container();
  }
  ...

StatelessWidget

静态组件定义后不会再改变,一般很少用到。

Widget与Element

Widget(组件)是Element(元素)的封装,flutter最后渲染的是Element。不涉及到原理时了解一下即可

组件按功能分类

分为四种,分别是基础类组件、布局类组件、容器类组件、滚动类组件

基础类组件

基础组件分为**文本(Text)、图片(Image)、按钮(xxButton)、输入框(TextField)、单选框(Switch)与复选框(CheckBox)、表单(Form)**等等。基本格式为:

    //文本
    Text("Hello world! I'm Jack. "*4,
            maxLines: 1,
         overflow: TextOverflow.ellipsis,
);
//按钮的种类有很多
RaisedButton(
  child: Text("normal"),
  onPressed: () => {},
);

可以发现格式都是类似的,具体不一一讲述,属性类型跟移动端的控件是类似的。唯一需要看的是表单组件

表单:为了方便上传数据时不用对每个输入框都做判断。flutter提供了表单组件。

简单理解:表单给每个输入框提供一个判断是否验证同的属性validator。并且提供提交按钮一个出发的方法触发回调具体代码可以下面地址的介绍 book.flutterchina.club/chapter3/in…

布局类组件

水平线性布局(Row)、垂直线性布局(Column)、弹性布局(Flex)、流式布局(Wrap|Flow)、层叠布局(Stack|Positioned)

线性布局

水平跟垂直都是基层与Flex。基本格式如下

    Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      mainAxisSize: MainAxisSize.max, //有效,外层Colum高度为整个屏幕
      children: <Widget>[
        Container(
          color: Colors.red,
          child: Column(
            mainAxisSize: MainAxisSize.max,//无效,内层Colum高度为实际高度  
            children: <Widget>[
              Text("hello world "),
              Text("I am Jack "),
            ],
          ),
        )
      ],
    )

基本上跟Android的线性布局一样

  • crossAxisAlignment:觉得子控件的对齐
  • mainAxisSize:自身的大小,如果嵌套里面的会没效
  • children:子控件
  • 还有其他可以看看链接book.flutterchina.club/chapter4/ro…

弹性布局

  • Flex属性与线性布局类似
  • Expanded。在Flex容器中可以设置比例

例如下面的代码会按1:2比例占据屏幕

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

流式布局

  • Wrap直接使用,超过屏幕自动换行
  • Flow需要自己计算,但是性能较好。同时由于是自己计算的,所以换行规则可以自己定。

层叠布局

  • Stack类似于Android里面的FrameLayout、Web中的绝对定位
  • Position 结合Stack使用可以实现绝对定位的效果

容器类组件

容器类与布局类不同的地方在于一般容器类只接受一个子组件。用于修饰、变换、限制大小、设置边距等等

Padding

跟移动端不一样的是,flutter的Padding也是单独抽出来的组件。格式如下

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

限制类容器(ConstrainedBox、SizedBox等等)

用于限制组件的最大最小值,格式如下,一个是限制条件的属性、一个是child放的内容

ConstrainedBox(
    constraints: BoxConstraints(minWidth: 60.0, minHeight: 100.0),  //父
    child: UnconstrainedBox( //“去除”父级限制
      child: ConstrainedBox(
        constraints: BoxConstraints(minWidth: 90.0, minHeight: 20.0),//子
        child: redBox,
      ),
    )
)

装饰器DecoratedBox

类似于Android的shape,可以设置圆角、渐变、阴影等等。格式如下

 DecoratedBox(
    decoration: BoxDecoration(
      gradient: LinearGradient(colors:[Colors.red,Colors.orange[700]]), //背景渐变
      borderRadius: BorderRadius.circular(3.0), //3像素圆角
      boxShadow: [ //阴影
        BoxShadow(
            color:Colors.black54,
            offset: Offset(2.0,2.0),
            blurRadius: 4.0
        )
      ]
    ),
  child: Padding(padding: EdgeInsets.symmetric(horizontal: 80.0, vertical: 18.0),
    child: Text("Login", style: TextStyle(color: Colors.white),),
  )
)

变换Transform

旋转(rotate)、平移(translate)、缩(scale)

DecoratedBox(
  decoration:BoxDecoration(color: Colors.red),
  child: Transform.rotate(
    angle:90 ,
    child: Text("Hello world"),
  ),
);

这种方式的旋转不会执行build方法,所以背景不会改变性能也较好一些,我的理解是它仅仅改变了child的值,而如果要改变全部则使用RotatedBox

RotatedBox
 DecoratedBox(
      decoration: BoxDecoration(color: Colors.red),
      //将Transform.rotate换成RotatedBox  
      child: RotatedBox(
        quarterTurns: 1, //旋转90度(1/4圈)
        child: Text("Hello world"),
      ),
    

这里没有讲的一个是透明度的变换

Opacity(
        opacity: 0.1,
        child: new Container(
        width: 250.0,
        height: 100.0,
        decoration: new BoxDecoration(
        backgroundColor: const Color(0xff000000),
    ),
)

Container容器

这个容器比较强大的是它有padding跟margin以及变换等等不过底层也是用上面的控件实现的

Container({
  this.alignment,
  this.padding, //容器内补白,属于decoration的装饰范围
  Color color, // 背景色
  Decoration decoration, // 背景装饰
  Decoration foregroundDecoration, //前景装饰
  double width,//容器的宽度
  double height, //容器的高度
  BoxConstraints constraints, //容器大小的限制条件
  this.margin,//容器外补白,不属于decoration的装饰范围
  this.transform, //变换
  this.child,
  padding,
  margin,
})

Scaffold

该容器应该不陌生,项目一创建是就有。它是一个脚手架容器,就是很多容器都定义好了。只要跟着写就有相应的效果,先看看代码

Scaffold(
      appBar: AppBar( //导航栏
        title: Text("App Name"), 
        actions: <Widget>[ //导航栏右侧菜单
          IconButton(icon: Icon(Icons.share), onPressed: () {}),
        ],
      ),
      drawer: new MyDrawer(), //抽屉
      bottomNavigationBar: BottomNavigationBar( // 底部导航
        items: <BottomNavigationBarItem>[
          BottomNavigationBarItem(icon: Icon(Icons.home), title: Text('Home')),
          BottomNavigationBarItem(icon: Icon(Icons.business), title: Text('Business')),
          BottomNavigationBarItem(icon: Icon(Icons.school), title: Text('School')),
        ],
        currentIndex: _selectedIndex,
        fixedColor: Colors.blue,
        onTap: _onItemTapped,
      ),
      floatingActionButton: FloatingActionButton( //悬浮按钮
          child: Icon(Icons.add),
          onPressed:_onAdd
      ),
    );

从Scaffold的下一级子控件来看,有导航栏(appbar)、侧边栏(drawer)、底部导航栏(bottomNavigationBar)、body(内容)

Appbar

Android里面也有appbar,效果可以说是一样的属性也类似。

AppBar({
  Key key,
  this.leading, //导航栏最左侧Widget,常见为抽屉菜单按钮或返回按钮。
  this.automaticallyImplyLeading = true, //如果leading为null,是否自动实现默认的leading按钮
  this.title,// 页面标题
  this.actions, // 导航栏右侧菜单
  this.bottom, // 导航栏底部菜单,通常为Tab按钮组
  this.elevation = 4.0, // 导航栏阴影
  this.centerTitle, //标题是否居中 
  this.backgroundColor,
  ...   //其它属性见源码注释
})

可能用的比较多的是Appbar下的TabBar。与Android中的TabBar是类似的

 bottom: TabBar(
      controller: _tabController,
      tabs: tabs.map((e) => Tab(text: e)).toList())
drawer(侧边栏)、BottomNacigationBar(底部导航栏)

跟正常使用容器组件没什么差别

Drawer(
        child: Container(
          padding: EdgeInsets.zero,
          children: <Widget>[
           
        ),
//中间有圆弧的效果
bottomNavigationBar: BottomAppBar(
  color: Colors.white,
  shape: CircularNotchedRectangle(), // 底部导航栏打一个圆形的洞
  child: Row(
    children: [
      IconButton(icon: Icon(Icons.home)),
      SizedBox(), //中间位置空出
      IconButton(icon: Icon(Icons.business)),
    ],
    mainAxisAlignment: MainAxisAlignment.spaceAround, //均分底部导航栏横向空间
  ),
)

滚动类组件

SingleChildScrollView、ListView、GridView、ConstomScrollView以及滚动监听ScrollController

SingleChildScrollView

SingleChildScrollView({
 this.scrollDirection = Axis.vertical, //滚动方向,默认是垂直方向
 this.reverse=false,//滚动方向是否反向
 this.padding,//边距bool primary,//这是否是与父控件关联的主滚动视图 应该是是否与父控件一起滑动 用来解决滑动冲突
 this.physics,//滑动松手后的滑动方式
 this.controller,
 this.child,//子view
})
Scrollbar(
      child: SingleChildScrollView(
        padding: EdgeInsets.all(16.0),
        child: Center(
          child: Column( 
            //动态创建一个List<Widget>  
            children: str.split("") 
                //每一个字母都用一个Text显示,字体为原来的两倍
                .map((c) => Text(c, textScaleFactor: 2.0,)) 
                .toList(),
          ),
        ),
      )

SingleChildScrollView只能接收一个组件,如果在外面添加Scrollbar的话会有滚动条,不加则没有。

physics
  • const ClampingScrollPhysics():Android下微光效果。
  • const BouncingScrollPhysics():iOS下弹性效果。

ListView

ListView跟Android的Listview是类似的都是用于展示列表数据的

ListView({
  ...  
  //可滚动widget公共参数
  Axis scrollDirection = Axis.vertical,
  bool reverse = false,
  ScrollController controller,
  bool primary,
  ScrollPhysics physics,
  EdgeInsetsGeometry padding,

  //ListView各个构造函数的共同参数  
  double itemExtent,
  bool shrinkWrap = false,
  bool addAutomaticKeepAlives = true,
  bool addRepaintBoundaries = true,
  double cacheExtent,

  //子widget列表
  List<Widget> children = const <Widget>[],
})
  1. 如果数据不多,直接用
ListView(
  shrinkWrap: true, 
  padding: const EdgeInsets.all(20.0),
  children: <Widget>[
    const Text('列表数据'),
  ],
);
  1. 如果数据多,为了方便使用builder或者separated
  • separated 跟builder类似只是可以方便定义分割线
class ListViewDemo extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    //下划线widget预定义以供复用。  
    Widget divider1=Divider(color: Colors.blue,);
    Widget divider2=Divider(color: Colors.green);
    return ListView.separated(
        itemCount: 100,
        //列表项构造器
        itemBuilder: (BuildContext context, int index) {
          return ListTile(title: Text("$index"));
        },
        //分割器构造器
        separatorBuilder: (BuildContext context, int index) {
          return index%2==0?divider1:divider2;
        },
    );
  }
}

GridView

网格布局同样与Android里面的类似。用法也与ListView大致相同

class _GridViewLayoutState extends State<GridViewLayout> {
  @override
  Widget build(BuildContext context) {
    return GridView(
        gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
            crossAxisCount: 3, //横轴三个子widget
            childAspectRatio: 1.0 //宽高比为1
        ),
        children:<Widget>[
          Icon(Icons.ac_unit),
          Icon(Icons.airport_shuttle),
          Icon(Icons.all_inclusive),
          Icon(Icons.beach_access),
          Icon(Icons.cake),
          Icon(Icons.free_breakfast)
        ]
    );
  }
}

不想以后看回来东西太多,点到为止。网格布局只能实现规则的网格。如果要实现瀑布流效果。有个开源的库github.com/letsar/flut…

CustomScrollView

用于把滚动控件放在同一个容器上做到滚动效果一致的胶水控件。它的子控件有对应的滚动控件实现Sliver

Sliver

Sliver有细片、小片之意,在Flutter中,Sliver通常指具有特定滚动效果的可滚动块。可滚动widget,如ListView、GridView等都有对应的Sliver实现如SliverList、SliverGrid等贴上一段md效果的代码,感觉效果还不错

import 'package:flutter/material.dart';

class CustomScrollViewTestRoute extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    //因为本路由没有使用Scaffold,为了让子级Widget(如Text)使用
    //Material Design 默认的样式风格,我们使用Material作为本路由的根。
    return Material(
      child: CustomScrollView(
        slivers: <Widget>[
          //AppBar,包含一个导航栏
          SliverAppBar(
            pinned: true,
            expandedHeight: 250.0,
            flexibleSpace: FlexibleSpaceBar(
              title: const Text('Demo'),
              background: Image.asset(
                "./images/avatar.png", fit: BoxFit.cover,),
            ),
          ),

          SliverPadding(
            padding: const EdgeInsets.all(8.0),
            sliver: new SliverGrid( //Grid
              gridDelegate: new SliverGridDelegateWithFixedCrossAxisCount(
                crossAxisCount: 2, //Grid按两列显示
                mainAxisSpacing: 10.0,
                crossAxisSpacing: 10.0,
                childAspectRatio: 4.0,
              ),
              delegate: new SliverChildBuilderDelegate(
                    (BuildContext context, int index) {
                  //创建子widget      
                  return new Container(
                    alignment: Alignment.center,
                    color: Colors.cyan[100 * (index % 9)],
                    child: new Text('grid item $index'),
                  );
                },
                childCount: 20,
              ),
            ),
          ),
          //List
          new SliverFixedExtentList(
            itemExtent: 50.0,
            delegate: new SliverChildBuilderDelegate(
                    (BuildContext context, int index) {
                  //创建列表项      
                  return new Container(
                    alignment: Alignment.center,
                    color: Colors.lightBlue[100 * (index % 9)],
                    child: new Text('list item $index'),
                  );
                },
                childCount: 50 //50个列表项
            ),
          ),
        ],
      ),
    );
  }
}

滚动监听

前端开发应该都熟悉,在flutter一般监听代码会放在initState中。因为initState不会多次执行。

ScrollController _controller = new ScrollController();
  bool showToTopBtn = false; //是否显示“返回到顶部”按钮

  @override
  void initState() {
    //监听滚动事件,打印滚动位置
    _controller.addListener(() {
      print(_controller.offset); //打印滚动位置
      if (_controller.offset < 1000 && showToTopBtn) {
        setState(() {
          showToTopBtn = false;
        });
      } else if (_controller.offset >= 1000 && showToTopBtn == false) {
        setState(() {
          showToTopBtn = true;
        });
      }
    });
  }

功能型组件

在flutter中,一切皆组件。所谓的功能型组件其实指的是如事件监听、数据存储等。这种跟界面不是很想关,所以分开理解

1.导航返回拦截WillPopScope

有时候点击两次过快时可能是误点需要做处理,就用到这个。

WillPopScope(
        onWillPop: () async {
          if (_lastPressedAt == null ||
              DateTime.now().difference(_lastPressedAt) > Duration(seconds: 1)) {
            //两次点击间隔超过1秒则重新计时
            _lastPressedAt = DateTime.now();
            return false;
          }
          return true;
        },
        child: Container(
          alignment: Alignment.center,
          child: Text("1秒内连续按两次返回键退出"),
        )
    );

onWillPop是一个回调函数,当用户点击返回按钮时调用(包括导航返回按钮及Android物理返回按钮),该回调需要返回一个Future对象,如果返回的Future最终值为false时,则当前路由不出栈(不会返回),最终值为true时,当前路由出栈退出。我们需要提供这个回调来决定是否退出。

2.数据共享InheritedWidget

感觉就是全局变量

didChangeDependencies这个回调函数当数值变化时会回调。如果封装一下即可实现切换语言和主题等等。具体例子看book.flutterchina.club/chapter7/in…

3.主题Theme

主题有些代码中会用到,记录一下不会陌生

ThemeData({
  Brightness brightness, //深色还是浅色
  MaterialColor primarySwatch, //主题颜色样本,见下面介绍
  Color primaryColor, //主色,决定导航栏颜色
  Color accentColor, //次级色,决定大多数Widget的颜色,如进度条、开关等。
  Color cardColor, //卡片颜色
  Color dividerColor, //分割线颜色
  ButtonThemeData buttonTheme, //按钮主题
  Color cursorColor, //输入框光标颜色
  Color dialogBackgroundColor,//对话框背景颜色
  String fontFamily, //文字字体
  TextTheme textTheme,// 字体主题,包括标题、body等文字样式
  IconThemeData iconTheme, // Icon的默认样式
  TargetPlatform platform, //指定平台,应用特定平台控件风格
  ...
})

最开始坚持的地方,记录学习与生活的点点滴滴