Nealyang / PersonalBlog

:memo: Nealyang personal blog

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

flutter入门以及常用Widget介绍

Nealyang opened this issue · comments

前言

关于Flutter入门的文章非常多,常用Widget总结文章也非常多,之前写过相关入门文章,欢迎大家查看:flutter从入门到能寄几玩儿,这里我们还是以上面文章为主,先把项目跑起来,然后走马观花常用布局、常用Widget。

相关文章推荐

Flutter 中文网

Flutter 环境搭建

Flutter widget

Flutter走马观花

关于Flutter环境问题可以查阅上方链接,步骤都非常详细。
此后~大量代码来袭

基础Widget之material版Hello world

国际惯例,hello world

import 'package:flutter/material.dart';

class MyAppBar extends StatelessWidget{
  MyAppBar({this.title});//
  final Widget title;

  @override
  Widget build(BuildContext context){
    return new Container(
      height: 56.0,
      padding: const EdgeInsets.symmetric(horizontal:8.0),
      decoration: new BoxDecoration(
        color:Colors.blue[400]
      ),
      child: Row(
        children: <Widget>[
          new IconButton(
            icon:new Icon(Icons.menu),
            tooltip:'Navigation menu',
            onPressed: (){
              print('点击Menu');
            },
          ),
          new Expanded(
            child:new Center(
              child:title
            )
          ),
          new IconButton(
            icon:Icon(Icons.search),
            tooltip:'Search',
            onPressed: (){
              print('点击搜索按钮');
            },
          )
        ],
      ),
    );
  }
}

class MyScaffold extends StatelessWidget{
  @override 
  Widget build(BuildContext context){
    return Material(
      child: new Column(
        children:<Widget>[
          new MyAppBar(
            title:new Text(
              'Hello World',
              style:Theme.of(context).primaryTextTheme.title
             ),
          ),
          new Expanded(
            child:new Center(
              child:Text('Hello World!!!')
            )
          )
        ]
      ),
    );
  }
}

void main(){
  runApp(
    new MaterialApp(
      title:'My app',
      home:new MyScaffold()
    )
  );
}

img
代码地址:https://github.com/Nealyang/flutter

这个UI的确有些对不起人了,上面的title被挡住了。且先不去适配,后面我们使用Material提供的Scaffold即可

第一个例子,重点说下代码(用过的Widget记住):

  • 一切都是Widget,且Widget前面的new可有可无。
  • 类MyAppBar和MyScaffold中使用了Container、Row、Column、Text、IconButton、Icon、BoxDecoration、Center、Expanded等常用Widget
    • Container一个拥有绘制、定位、调整大小的 widget。类似于div,我们可以用它来创建矩形视图,container 可以装饰为一个BoxDecoration, 如 background、一个边框、或者一个阴影。 Container 也可以具有边距(margins)、填充(padding)和应用于其大小的约束(constraints)。另外, Container可以使用矩阵在三维空间中对其进行变换。
    • RowColumn其实就是flex布局中的flex-direction
    • Expanded它会填充尚未被其他子项占用的的剩余可用空间。Expanded可以拥有多个children。然后使用flex参数来确定他们占用剩余空间的比例。更多细节可以参看:flutter控件Flexible和 Expanded的区别
  • 先定义了一个MyAppBar的类,构造函数中接受一个Widget的title,其实我们也可以接受String title然后在类中自己去new Title(title)
  • runApp函数接受给定的Widget并使用其作为widget根。
  • widget的主要工作是实现一个build函数,用以构建自身。一个widget通常由一些较低级别widget组成。Flutter框架将依次构建这些widget,直到构建到最底层的子widget时,这些最低层的widget通常为RenderObject,它会计算并描述widget的几何形状。

基本交互之material版Hello world

import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  // app的根Widget
  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      title: 'Flutter Demo',
      theme: new ThemeData(
        // 这是设置的app主题
        // 运行后你可以看到app有一个蓝色的toobar,并且在不退出app的情况下修改代码会热更新
        primarySwatch: Colors.blue,
      ),
      home: new MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);

// 这是应用中一个基类,继承自StateFulWidget,意味着这个类拥有一个state对象,该对象里的一些字段会影响app的UI
// 这个类是state的一些配置项。通过构造函数来获取值,这个值一般在State中消费,并且使用final关键字。其实类似于react中的defaultProps

  final String title;

  @override
  _MyHomePageState createState() => new _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  int _counter = 0;

  void _incrementCounter() {
    setState(() {
      // setState方法告诉Flutter,这个State中有些值发生了变化,以便及时将新值更新到UI上,
      // 如果我不通过setState更改_count字段,那么Flutter并不会调用build匿名函数去更新界面
      _counter++;
    });
  }

  @override
  Widget build(BuildContext context) {
    // build方法会在每次setState的时候重新运行,例如上面的_incrementCounter方法被调用
    //Flutter已经被优化了重新构建的方法,所以你只会去更新需要去更新的部分,不必去单独更新里面的一些更细小的widget,类似于React中diff
    return new Scaffold(
      appBar: new AppBar(
        // 这里我们使用从App.build方法中初始化MyHomePage时候传入的title值来设置我们的title
        title: new Text(widget.title),
      ),
      body: new Center(
        // Center是一个布局Widget,他只有一个child(区分row or cloumn等是children),并且会将child的widget居中显示
        child: new Column(
          // Column也是一个布局widget,他可以有多个子widget
          // Column 有很多的属性去控制他的大小以及子widget的位置,这里我们使用mainAxisAlignment来让children在垂直线上居中,
          // 这里的主轴就是垂直的,因为Column就是垂直方向的,这里可以大概想象为display:flex,flex-directions:column,align-item,justifyContent。。。
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            new Text(
              'Hello World!',
              style:TextStyle(
                fontSize:24.0,
                color: Colors.redAccent,
                decorationStyle:TextDecorationStyle.dotted,
                fontWeight: FontWeight.bold,
                fontStyle: FontStyle.italic,
                decoration: TextDecoration.underline
              )
            ),
            new Text(
              'You have pushed the button this many times:',
            ),
            new Text(
              '$_counter',
              style: Theme.of(context).textTheme.display1,
            ),
          ],
        ),
      ),
      floatingActionButton: new FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        child: new Icon(Icons.add),
      ),//最后这个逗号有利于格式化代码
    );
  }
}

img
注释上基本已经加了,这里重点说下,StatefulWidget和StatelessWidget.

  • Stateless widgets 是不可变的,这意味着它们的属性不能改变——所有的值都是 final
  • Stateful widgets 持有的状态可能在 widget 生命周期中发生变化,实现一个 stateful widget 至少需要两个类:1)一个 StatefulWidget 类;2)一个 State 类,StatefulWidget 类本身是不变的,但是 State 类在 widget 生命周期中始终存在
  • 如果需要变化需要重新创建。StatefulWidget可以保存自己的状态。那问题是既然widget都是immutable的,怎么保存状态?其实Flutter是通过引入了State来保存状态。当State的状态改变时,能重新构建本节点以及孩子的Widget树来进行UI变化。注意:如果需要主动改变State的状态,需要通过setState()方法进行触发,单纯改变数据是不会引发UI改变的。

还有关于key的部分这里就不做介绍了,其实就类似与react中key的概念,便于diff,提高效率的。
具体可以查看 Key

到这里,我们看到了Flutter的一些基本用法,Widget的套用、样式的编写、事件的注册,如果再学习下一些路由、请求、缓存是不是就可以自己开发APP了呢!

OK,强化下编写界面,咱再来些demo吧~

布局Widget

img

自己写的后,发现跟官网实现方式不同,代码地址

具体实现可以参照官网教程

这里不再赘述,下面我们说下对于布局的理解和感受以及常用布局widget。

从一个前端的角度来说,说到画界面,可能还是对布局这块比较敏感

img

img

当然,这里我们还是说下目前常用的flex布局,基本拿到页面从大到小拆分后就是如上图。

所以Widget布局其实也就是Row和Column用的最多,然后由于Flutter一切皆为组件的理念,可能会需要用到别的类css布局的Widget,譬如:Container。其实咱就理解为块元素吧!

下面简单演示下一些常用的Widget,这里就不在赘述Row和Column了。传送门:布局Widget

Container

可以添加padding、margin、border、background color、通常用于装饰其他Widget

img

代码链接 Nealyang/flutter

class MyHomePage extends StatelessWidget{
  @override
  Widget build(BuildContext context){
    Container cell (String imgSrc){
      return new Container(
        decoration: new BoxDecoration(
          border:Border.all(width:6.0,color:Colors.black38),
          borderRadius: BorderRadius.all(const Radius.circular(8.0))
        ),
        child: Image.asset(
          'images/$imgSrc',
          width: 180.0,
          height: 180.0,
          fit: BoxFit.cover,
        ),
      );
    }

    return Container(
      padding: const EdgeInsets.all(10.0),
      color: Colors.grey,
      child: new Column(
        mainAxisSize: MainAxisSize.min,
        children:<Widget>[
          new Container(
            margin: const EdgeInsets.only(bottom:10.0),
            child: new Row(
              mainAxisAlignment: MainAxisAlignment.spaceAround,
              children:<Widget>[
                cell('1.jpg'),
                cell('2.jpg')
              ]
            ),
          ),
          new Container(
            margin: const EdgeInsets.only(bottom:10.0),
            child: new Row(
              mainAxisAlignment: MainAxisAlignment.spaceAround,
              children:<Widget>[
                cell('3.jpg'),
                cell('4.jpg')
              ]
            ),
          ),
        ]
      ),
    );
  }
}

该布局中每个图像使用一个Container来添加一个圆形的灰色边框和边距。然后使用容器将列背景颜色更改为浅灰色。

GridView

可滚动的网格布局,理解为display:grid

GridView提供两个预制list,当GridView检测到内容太长时,会自动滚动。如果需要构建自定义grid,可是使用GridView.countGridView.extent来指定允许设置的列数以及指定项最大像素宽度。

img

代码链接 Nealyang/flutter

List<Container> _buildGridTileList(int count) {

  return new List<Container>.generate(
      count,
      (int index) =>
          new Container(child: new Image.asset('images/${index+1}.jpg')));
}

Widget buildGrid() {
  return new GridView.extent(
      maxCrossAxisExtent: 150.0,
      padding: const EdgeInsets.all(4.0),
      mainAxisSpacing: 4.0,
      crossAxisSpacing: 4.0,
      children: _buildGridTileList(10));
}

class MyHomePage extends StatelessWidget{
  @override
  Widget build(BuildContext context){
    return  new Center(
        child: buildGrid(),
      );
  }
}

如上是指定maxCrossAxisExtent,我们可以直接去指定列数,例如官网的代码实例:

new GridView.count(
  primary: false,
  padding: const EdgeInsets.all(20.0),
  crossAxisSpacing: 10.0,
  crossAxisCount: 3,
  children: <Widget>[
    const Text('He\'d have you all unravel at the'),
    const Text('Heed not the rabble'),
    const Text('Sound of screams but the'),
    const Text('Who scream'),
    const Text('Revolution is coming...'),
    const Text('Revolution, they...'),
  ],
)

通过crossAxisCount直接指定列数。

Stack

层叠布局,position为absolute的感jio~

使用Stack来组织需要重叠的widget。widget可以完全或部分重叠底部widget。子列表中的第一个widget是base widget; 随后的子widget被覆盖在基础widget的顶部。Stack的内容不能滚动。有点类似于weex中的设置了absolute的感觉。底部组件永远在上面组件的上面。

ListView

可滚动的长列表,可以水平或者垂直。

Card

Material风格组件,卡片,AntD啥的组件库经常会出现的那种组件。

在flutter中,Card具有圆角和阴影,更改Card的elevation属性可以控制阴影效果。

ListTile

Material风格组件,我理解为常用的列表Item的样式,最多三行文字,可选的行前、行尾的图标

img

代码链接 Nealyang/flutter

总结

撇开框架设计原理和**不谈,其实技术就是一个工具,所谓实践出真知,我们需要的更多是掌握查阅这些widget的技巧以及常用widget在项目实践中的熟练掌握。

对于画界面,更多的还可以参看下官网教程:Flutter for Web开发者

下面让我们正式开始我们的《Flutter入门实战:从0到1仿写web版掘金App》吧!!!

图片全部够挂了,全局代理都打不开

图片全部够挂了,全局代理都打不开

图片服务到期了吧,不如直接用GitHub