阅读 1529

Flutter Widget 三天快速入门

前言

在Flutter中,几乎一切的对象都是widget,不仅是UI元素,还有手势事件的检测,用于APP主题数据传递的Theme等等,可能你会认为widget是绘制到屏幕上的元素,其实不是的,widget只是一种配置信息,是生成element的指令集,真正绘制到屏幕上的是element,在widget第一次build的时候,其实这个时候会给widget树中的每一个widget生成相对应的element,然后这些element会组成一个element树,而且每一个element中还会拥有对应的widget的对象的引用,总之,element才是是绘制在屏幕上的元素

image.png

我们在实际的开发中,面对的最多的是 StatefulWidget和StatelessWidget,他们是Widget的直接子类,我们看到的每一个页面,几乎都是StatefulWidget

image.png

1.StatelessWidget

StatelessWidget是无状态的组件,它不支持调用setState方法实现页面刷新,有自己的生命周期,但是很简单,如下代码

class StatelessTest extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Material(
      child: Container(
        alignment: Alignment.center,
        child: Text("StatelessWidget"),
      ),
    );
  }
}
复制代码

这段代码其实就是一个实现了文字居中的页面,效果图如下

image.png

2.StatefulWidget

StatefulWidget是有状态的组件,在flutter看到的每一个页面几乎都是StatefulWidget,支持调用setState方法实现页面刷新,但在实际开发中不建议使用setState实现页面刷新,因为setState会导致整个页面刷新,可以使用provider替代,实现局部刷新,代码如下

class StatefulTest extends StatefulWidget {
  @override
  State<StatefulWidget> createState() {
    return StatefulState();
  }
}

class StatefulState extends State<StatefulTest> {
  int num = 0;

  @override
  Widget build(BuildContext context) {
    return Material(
      child: Container(
        alignment: Alignment.center,
        child: Column(
          mainAxisSize: MainAxisSize.min,//表示高度自适应
          children: [
            Text("$num"),
            Container(
              width: 100,
              height: 40,
              margin: EdgeInsets.only(top: 50),
              child: RaisedButton(
                onPressed: () {
                  num++;
                  setState(() {
                  });
                },
                child: Text("add"),
              ),
            )
          ],
        ),
      ),
    );
  }
}
复制代码

这其实就是实现一个按钮点击然后增加数字的功能,效果图如下

image.png

点击add按钮数字会增加,每次点击add按钮后将num加1,然后调用setState方法,导致build方法被重新调用,从而刷新页面,实现数字的增加,其实build方法是StatefulWidget生命周期的一个方法,还有其他的生命周期方法

3.StatefulWidget生命周期

以前学习android的时候,就学习过activity的生命周期,而StatefulWidget作为flutter的页面承载类,也有自己的生命周期

1. 流程图

image.png

  1. 启动一个页面的时候会调用initState,didChangeDependencies,build三个生命周期方法
  2. 关闭页面的时候会调用deactivate和dispose 方法
  3. 调用setState的时候build方法会被重新调用

2.生命周期的各个方法含义:

1. initState

这是创建widget时调用的除构造方法外的第一个方法,类似于Android的onCreate() ,经常做 一些初始化的操作

2. didChangeDependencies

当依赖的State对象改变时会调用:

  1. 在第一次构建widget时,在initState()之后立即调用此方法;
  2. 如果的StatefulWidgets依赖于InheritedWidget,那么当前State所依赖InheritedWidget 中的变量改变时会再次调用它,InheritedWidget可以高效的将数据在Widget树中向下传递、共享,可参考:book.flutterchina.club/chapter7/in…

3. build

这里返回页面展示的UI组件,是必须实现的方法,调用setState会重新调用

4. deactivate

很少使用,在组件被移除时调用在dispose之前调用

5. dispose

常用,组件被销毁时被调用,通常在该方法中执行一些资源的释放工作比如,监听器的卸载

3. 实例代码

class WidgetLifecycle extends StatefulWidget {
  ///当我们构建一个新的StatefulWidget时,这个会立即调用
  ///并且这个方法必须被覆盖
  @override
  _WidgetLifecycleState createState() => _WidgetLifecycleState();
}

class _WidgetLifecycleState extends State<WidgetLifecycle> {
  int _count = 0;

  ///这是创建widget时调用的除构造方法外的第一个方法:
  ///类似于Android的:onCreate() 与iOS的 viewDidLoad()
  ///在这个方法中通常会做一些初始化工作,比如channel的初始化,监听器的初始化等
  @override
  void initState() {
    print('----initState----');
    super.initState();
  }

  ///当依赖的State对象改变时会调用:
  ///a.在第一次构建widget时,在initState()之后立即调用此方法;
  ///b.如果的StatefulWidgets依赖于InheritedWidget,那么当当前State所依赖InheritedWidget中的变量改变时会再次调用它
  ///拓展:InheritedWidget可以高效的将数据在Widget树中向下传递、共享,可参考:https://book.flutterchina.club/chapter7/inherited_widget.html
  @override
  void didChangeDependencies() {
    print('---didChangeDependencies----');
    super.didChangeDependencies();
  }

  ///这是一个必须实现的方法,在这里实现你要呈现的页面内容:
  ///它会在在didChangeDependencies()之后立即调用;
  ///另外当调用setState后也会再次调用该方法;
  @override
  Widget build(BuildContext context) {
    print('---build-----');
    return Scaffold(
      appBar: AppBar(
        title: Text('Flutter页面生命周期'),
        leading: BackButton(),
      ),
      body: Center(
        child: Column(
          children: <Widget>[
            RaisedButton(
              onPressed: () {
                setState(() {
                  _count += 1;
                });
              },
              child: Text(
                '点我',
                style: TextStyle(fontSize: 26),
              ),
            ),
            Text(_count.toString())
          ],
        ),
      ),
    );
  }

  ///很少使用,在组件被移除时调用在dispose之前调用
  @override
  void deactivate() {
    print('-----deactivate------');
    super.deactivate();
  }

  ///常用,组件被销毁时调用:
  ///通常在该方法中执行一些资源的释放工作比如,监听器的卸载,channel的销毁等
  @override
  void dispose() {
    print('-----dispose-----');
    super.dispose();
  }
}
复制代码

4.常用的widget

我们看到的flutter页面其实就是一个个widget互相嵌套组装而成的,在实际项目开发中,难免会出现大量的widget 互相嵌套的情况

1. MaterialApp

在学习Flutter的过程中我们第一个看见的控件应该就是MaterialApp,毕竟创建一个新的Flutter项目的时候,项目第一个组件就是MaterialApp,这是一个Material风格的根控件,不仅可以设置路由,还可以设置主题颜色,展示Material风格的页面等等,基本使用如下:

class MaterialAppTest extends StatefulWidget {
  @override
  State<StatefulWidget> createState() {
    return new RouterTestState();
  }
}

class RouterTestState extends State {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      theme: ThemeData(primarySwatch: Colors.blue), ////主题颜色为蓝色
      home: Scaffold(
        appBar: AppBar(title: Text('MaterialAppTest1')), //标题栏
        body: Container(
          alignment: Alignment.center,
            child: Column(
              children: <Widget>[
                Container(
                    child: RaisedButton(
                        onPressed: () => {Navigator.pushNamed(context, "newPage")},
                        child: Text("跳转新页面")))
              ],
            )),
      ),
      routes: <String, WidgetBuilder>{
        //设置路由
        "newPage": (BuildContext context) => FuJuStudy(),
      },
    );
  }

}
复制代码

效果如下,主题颜色为蓝色,标题栏标题为MaterialAppTest,下面有一个按钮,跳转可以到一个新的页面

image.png

2. Scaffold

Scaffold提供了Material风格的基本控件,例如从左边和右边出现的抽屉式控件,还有底部导航等,一般和MaterialApp结合使用,使用如下:

Scaffold(
  drawer: Drawer(),
)
复制代码

效果如下:左边出现的抽屉式控件

image.png

3. Text

文本控件,相当于android中的TextView,使用简单,控制文本样式使用TextStyle,代码如下

Text(
  "文字使用",
  style: TextStyle(
      fontSize: 15,
      color: Colors.white,
      backgroundColor: Colors.black),
)       
复制代码

效果如下

image.png

4. Image

mage用于加载图片,相当于安卓中的ImageView,可以加载网络图片,assest图片,本地图片,使用方法如下,加载一张网络图片,并且设置宽高为100*100

 Image.network(
  "http://www.devio.org/img/avatar.png",
  width: 100,
  height: 100,
)
复制代码

效果如下:

image.png

5. Container

容器类widget,用于设置widget的背景,外边距,内边距,对齐方式,控件的间隔,分割线等等,很常用的一个widget,使用如下

Container(
  margin: EdgeInsets.all(15),
  padding: EdgeInsets.all(15),
  color: Colors.blue,
  child: Container(
    color: Colors.yellow,
    width: 200,
    height: 200,
    alignment: Alignment.center,
    child: Text(
      'Container',
      style: TextStyle(fontSize: 20, color: Colors.white),
    ),
  ),
  alignment: Alignment.center,
  height: 100,
)
复制代码

效果图如下,一个蓝色背景的Container包含了一个黄色背景的Container,黄色背景的Container又包含了一个白色的Text

image.png

6. Column

顾名思义,列,是一个类似于竖直方向的LinearLayout,实现子控件的竖直排列,可以设置子控件在水平和竖直方向上的对齐方式 ,代码如下

Column(
  mainAxisAlignment: MainAxisAlignment.center,//(主轴)竖直方向居中
  crossAxisAlignment: CrossAxisAlignment.start,//(交叉轴)水平方向左对齐
  mainAxisSize: MainAxisSize.max,//竖直方向填充父类,如果改成MainAxisSize.min,那么Column高度变成自适应
  children: [
    Container(
      height: 50,
      color: Colors.green,
    ),
    Container(
      color: Colors.blue,
      width: 100,
      height: 100,
      margin: EdgeInsets.only(top: 15),
    )
  ],
)
复制代码

效果如下,其实就是一个Column,设置水平方向左对齐,竖直方向居中对齐,所以就会呈现出下面的效果

image.png

7. SingleChildScrollView

只有一个child的 ScrollView,类似于原生安卓的ScrollView,使用如下

SingleChildScrollView(
  child: Column(
    mainAxisAlignment: MainAxisAlignment.center,
    crossAxisAlignment: CrossAxisAlignment.center,
    children: [
      Container(
        color: Colors.blue,
        margin: EdgeInsets.only(top: 10),
        height: 100,
        width: 100,
      ),
      Container(
        color: Colors.green,
        margin: EdgeInsets.only(top: 10),
        height: 100,
      ),
      Container(
        color: Colors.blue,
        margin: EdgeInsets.only(top: 10),
        height: 100,
        width: 100,
      ),
      Container(
        color: Colors.green,
        margin: EdgeInsets.only(top: 10),
        height: 100,
      ),
      Container(
        color: Colors.blue,
        margin: EdgeInsets.only(top: 10),
        height: 100,
        width: 100,
      ),
      Container(
        color: Colors.green,
        margin: EdgeInsets.only(top: 10),
        height: 100,
      ),
      Container(
        color: Colors.blue,
        margin: EdgeInsets.only(top: 10),
        height: 100,
        width: 100,
      ),
      Container(
        color: Colors.green,
        margin: EdgeInsets.only(top: 10),
        height: 100,
      ),
    ],
  ),
)
复制代码

效果如下,可以看到蓝色和绿色的方块一直向下排列,并且可以滚动

image.png

8. Stack

作用类似于android的FrameLayout,子组件可以根据父组件四个角的位置来确定组件的位置,但是需要结合Position组件来确定子组件的位置,Positioned内部提供了left,right,bottom,top四个参数来确定子组件距离 Stack上下左右的距离,使用如下

Stack(
    children: [
      Positioned(
        left: 50,
        top: 50,
        child: Container(
          color: Colors.blue,
          width: 50,
          height: 50,
        ),
      ),
      Positioned(
        left: 100,
        top: 100,
        child: Container(
          color: Colors.red,
          width: 50,
          height: 50,
        ),
      )
    ],
  )
复制代码

效果如下,蓝色方块宽高各50,距离左边和上边都是50,红色方块宽高各50,距离左边和上边都是100

image.png

9. Row

顾名思义,行,类似于android的水平的LinearLayout,实现控件的水平排列,使用如下

 Container(
  color: Colors.green,
  child: Row(
    mainAxisAlignment: MainAxisAlignment.start,// 水平方向为左对齐
    crossAxisAlignment: CrossAxisAlignment.center,//竖直方向居中对齐
    mainAxisSize: MainAxisSize.min,//水平方向宽度自适应,改成MainAxisSize.max宽度变成填充屏幕
    children: [
      Container(
        width: 100,
        height: 500,
        color: Colors.red,
      ),
      Container(
        width: 100,
        height: 100,
        color: Colors.blue,
      ),
      Container(
        width: 100,
        height: 100,
        color: Colors.yellow,
      )
    ],
  ),
)
复制代码

效果如下,绿色的Container包着Row,Row的宽度自适应,水平方向左对齐,竖直方向居中对齐,内部包着红色的Container,蓝色的Container,黄色的Container,从左往右排列

image.png

10. ElevatedButton

类似于android的Button,可以设置点击事件和长按事件,使用如下

 ElevatedButton(
  child: Text("ElevatedButton"),
  style: ButtonStyle(
    backgroundColor: MaterialStateProperty.resolveWith((states) {
      //设置按下时的背景颜色
      if (states.contains(MaterialState.pressed)) {
        return Colors.blue[200];
      }
      //默认不使用背景颜色
      return Colors.green;
    }),
    //设置水波纹颜色
    overlayColor: MaterialStateProperty.all(Colors.yellow),
    //设置阴影  不适用于这里的TextButton
    elevation: MaterialStateProperty.all(0),
    //设置按钮内边距
    padding: MaterialStateProperty.all(EdgeInsets.all(10)),
    //设置按钮的大小
    minimumSize: MaterialStateProperty.all(Size(200, 50)),
    //设置圆角
    shape: MaterialStateProperty.resolveWith((states) => RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(30)))),
  ),
  onPressed: () {
    print("点击");
  },
  onLongPress: () {
    print("长按");
  },
)
复制代码

效果如下:

image.png

11. ListView

用于展示列表数据,在数据较少的时候可以使用以下方式生成一个列表

ListView(
  children: <Widget>[
    widget1, widget2, widget3,
  ],
)
复制代码

以上的方式是一次性加载所有数据的,没有懒加载,不适合加载大量数据,加载大量数据的时候使用下面的方式,用ListView.builder()方法生成一个列表

 ListView.builder(
  itemExtent: 50,//确定每一个item的高度,列表在滑动的时候才不会去计算item高度,可以让列表滑动更加高效
  itemBuilder: (BuildContext context, int index) {
    return Container(
      alignment: Alignment.center,
      height: 50,
      child: Column(
        mainAxisSize: MainAxisSize.min,
        children: [
          Container(
            alignment: Alignment.center,
            height: 49,
            child: Text('第$index个child'),
          ),
          Container(
            height: 1,
            color: Colors.grey,
          )
        ],
      ),
    );
  },
)
复制代码

效果如下

image.png

12. GridView

用于展示网格布局,少量的数据可以通过以下方式展示,但是大量数据就不合适,原因是这种方式没有懒加载, gridDelegate设置子控件排列方式,crossAxisCount设置主轴方向(水平方向)子控件数量, crossAxisSpacing控制交叉轴(竖直方向)子控件间距,mainAxisSpacing设置主轴方向子控件间距

  GridView(
  scrollDirection: Axis.vertical, //竖直方向滑动
  controller: scrollController, //监听GridView滚动
  gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
      crossAxisCount: 3, crossAxisSpacing: 3, mainAxisSpacing: 2),
  children: [
    _createGridViewItem(Colors.primaries[0]),
    _createGridViewItem(Colors.primaries[1]),
    _createGridViewItem(Colors.primaries[2]),
    _createGridViewItem(Colors.primaries[3]),
    _createGridViewItem(Colors.primaries[4]),
    _createGridViewItem(Colors.primaries[5]),
    _createGridViewItem(Colors.primaries[6]),
    _createGridViewItem(Colors.primaries[7]),
    _createGridViewItem(Colors.primaries[0]),
    _createGridViewItem(Colors.primaries[1]),
    _createGridViewItem(Colors.primaries[2]),
    _createGridViewItem(Colors.primaries[3]),
    _createGridViewItem(Colors.primaries[4]),
    _createGridViewItem(Colors.primaries[5]),
    _createGridViewItem(Colors.primaries[6]),
    _createGridViewItem(Colors.primaries[7]),
  ],
)

_createGridViewItem(Color color) {
  return Container(
    height: 200,
    color: color,
  );
}
复制代码

效果如下

image.png

展示大量数据的时候,需要通过下面这种方式来展示数据,使用builder方法获取GridView对象

 GridView.builder(
  gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
    crossAxisCount: 3,
  ),
  itemBuilder: (context, index) {
    return Container(
      height: 80,
      color: Colors.primaries[index % Colors.primaries.length],
    );
  },
  itemCount: 50,
)
复制代码

效果如下

image.png

13. TextField

文本输入框,使用如下

TextField(
  obscureText: true,//设置为密文
  style: TextStyle(color: Colors.white, fontSize: 15),
  decoration: InputDecoration(
      hintText: "请输入登录密码",
      hintStyle: TextStyle(color: Colors.white, fontSize: 15),
      focusedBorder: OutlineInputBorder(//获取到焦点的时候的背景
          borderSide: BorderSide(color: Colors.white),
          borderRadius:
              BorderRadius.all(Radius.circular(100))),
      enabledBorder: OutlineInputBorder(//正常情况下的背景
          borderSide: BorderSide(color: Colors.white),
          borderRadius:
              BorderRadius.all(Radius.circular(100)))),
  onChanged: (text) {
    //监听输入
  },
)
复制代码

效果如下

image.png

14. AppBar

页面标题栏,一般和Scaffold结合使用,可以设置标题,后退按钮等,使用如下

Scaffold(
  appBar: AppBar(
      backgroundColor: Colors.blue,//背景色
      toolbarHeight: 45,//标题栏高度
      leading: GestureDetector(//后退按钮
        child: Icon(Icons.arrow_back),
        onTap: () => {Navigator.pop(context)},
      ),
      centerTitle: true,//设置标题居中
      title: Text(//标题
        'TweenStudy',
        style: TextStyle(color: Colors.black87, fontSize: 18),
      ))
  )
复制代码

效果如下

image.png

5.QQ交流群

770892444

文章分类
Android
文章标签