简单了解Flutter

2020-08-24 11:00:42 浏览数 (1)

距离Flutter正式版出来已经有很长的时间了,目前大家对于Flutter的呼声也是很高,就算是平时不了解移动开发的朋友们也开始好奇Flutter究竟是个什么东西。就连我朋友的老板都开始问,公司产品能不能换成Flutter来开发?

那么Flutter究竟是什么呢?它是一个声明式的移动UI框架,附带了自己的渲染引擎,类似于React框架自带了浏览器渲染引擎的感觉。它可以使app的界面编写更加简单直接,且不必在UI设计上做妥协。自带渲染引擎听起来侵入性比较强,没有使用对应平台的渲染机制,不过从RN的现状看来,通过bridge的形式依附于对应平台的渲染机制在性能上体验不佳,而Flutter会直接编译成native code,从理论上来说,跟我们用Java开发app相比不会有性能上的损失,写界面也变得更简单了。

Flutter使用Dart这门语言进行开发,Flutter本质上也就是个Dart类库。所有的控件,所有的代码都是用Dart编写的。一开始我很拒绝Dart这门语言,一个默默无闻好多年的语言,跟着Flutter才为人所知晓,谷歌推了这么久的kotlin,用kotlin来开发多好啊,我们学习迁移的成本也能大大降低。在这一年多的Flutter学习过程中,我发现谷歌这么做也有自己的考虑。首先Dart是谷歌自己的语言,想想它跟Oracle的官司打了多少年。其次Dart同时支持AOTJIT编译,JIT使得我们可以快速修改原型,我们做的修改一秒不到就可以更新到我们的设备上,而AOT保证我们发布的时候app不会有不必要的性能损失。那为什么不用go呢?算了吧,go只适合系统编程?。

Flutter宣称Everything is Widget,这句话对,也不全对,在Flutter里面,主题,边距,图片,动画等等都是通过widget来实现的,任何UI效果都可以抽象成Widget,其实在Flutter里面是有其它对象的,比如Element,比如RenderBox,(这些东西是什么我们下回再说)只不过对于我们开发者而言,我们日常开发中需要关心的就只是Widget了。Flutter中的Widget基本上可以分为两大类:StatefulWidgetStatelessWidget。这俩的区别可以直接从它们的名字上看出来,一个有状态,一个无状态。有状态的Widget会自己维护一些逻辑数据,而无状态的widget的内容由它的parent传递过来。简单来说,一个Widget如果包含了业务逻辑数据,这些数据在它销毁重建的时候需要做一些处理,那它就是有状态的。比如我们的购物车,它需要记录里面的商品的数量,它就是有状态的,而一个提交订单按钮它就是无状态的,它只关心在被点击的时候执行一个回调。两者在生命周期回调上也有所不同。有的同学第一次接触Flutter的可能觉得很绕,没关系,等会儿我们来简单上手一个小例子感受一下就懂了。

StatelessWidget会调用它的build方法来描述它的view,而StatefulWidget有一个与之配套的State对象,它只会调用createState方法去创建一个State对象,在这个State对象里也有一个build方法来描述view。这些build方法都必须返回另一个Widget

当我们新建一个Flutter项目的时候,默认给我们生成了一个计数器的demo。我们接下来就通过把玩这个项目来感受下flutter的魅力。Flutter提倡组合优于继承,所有的复杂的widget都是通过组合细小的Widget而来,我们只需要在build方法里面组合不同的widget就能定义出自己想要的UI。而Flutter也给我们提供了丰富的控件,我们日常开发就是不断组合Widget来构建我们的app,就连我们的app本身也是一个巨大的widget,不会有什么特殊的类去组合Widget来构建我们的app。

创建完项目,目录下的文件似乎有很大的一坨:

代码语言:javascript复制
counter_app
  |- android
  |- ios
  |- lib
    |- main.dart
  |- test
    |- widget_test.dart
  .gitignore
  pubspec.yaml
  pubspec.lock
  README.md

我们主要的代码目录在lib目录下,android跟ios目录只有在特定平台相关的代码时才会用到。初始项目代码很少但是注释很多,官方给我们详细注释了使用到的Widget的用途。

运行刚创建的项目:

初始界面

点击那个加号,屏幕中央的数字就会增加。

我们来看下我们的代码,在main.dart这个文件里,跟我们的Java的规则一样,程序的入口是一个main函数:

代码语言:javascript复制
void main() => runApp(MyApp());

我们实际项目中main函数不会这么简单,我们会做一些全局的配置,不过现在我们暂且不管,现在这么多就足够啦,这个MyApp就是对应到我们安卓里的Application了,我们来分析一下。

虽然我们的app代码很少,可以学到的东西可不少:

代码语言:javascript复制
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: MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

我们可以看到,我们的app也是一个Widget,而且还是一个StatelessWidget,我们重写了build方法,返回了另一个Widget,它是一个MaterialApp,然后这个Widget还可以有自己的子Widget。通过这种方式我们的app其实就形成了一个Widget树,包含着其它包含子WidgetWidget。然后我们可以尝试修改它,比如把这个primarySwatch的颜色换掉:Colors.orange,然后只要我们按下Ctrl S,修改分分钟在我们的设备上生效,主题颜色立马改变了,这就是Flutter宣传时吹爆的热加载的能力,惊艳吧?

修改主题颜色

再来看看这个MyHomePage,它是MaterialApp的home属性的值,MaterialApp是一个应用骨架,提供了很多MD风格的配置,这个配置会通过Widget树往下传给所有子Widget,它的home属性代表着我们的主界面,我们来看看:

代码语言:javascript复制
class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);

  final String title;

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

代码量很少,它是一个StatefulWidget,只是重写了createState方法。在这个_MyHomePageState里,重写了build方法实际描述了它的view。StatefulWidget的生命周期比较有意思:

StatefulWidget 生命周期

创建后会立即调用createState方法,在这里会调用State的构造器,然后走initState方法,然后调用build方法去描述它的view。而且Flutter是一个响应式的框架,我们通过setState方法去更新一些状态,每当setState方法被调用的时候,状态会被标记为dirty,然后Flutter会重新绘制。也就是说,我们可以通过setState方法去通知界面更新。

代码语言:javascript复制
class _MyHomePageState extends State<MyHomePage> {
  int _counter = 0;

  void _incrementCounter() {
    setState(() {
      _counter  ;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text(
              'You have pushed the button this many times:',
            ),
            Text(
              '$_counter',
              style: Theme.of(context).textTheme.display1,
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        child: Icon(Icons.add),
      ), 
  );
  }
}

我们可以看到,我们想要UI成什么样子我们就声明出来就好了,想要居中就声明,想要竖直排列就声明,很直观,对于UI的描述都在Statebuild方法里了,在这儿一切都是WidgetCenter是让子Widget居中的WidgetColunm是让其它Widget纵向排列的WidgetText是展示文字的Widget。为什么这么做呢,怎么不在Widget里面去实现build方法呢?因为Widget都是不可修改(immutable)的,StatelessWidget能够实现build方法是因为它所有的信息都来自于外界,它本身木有什么状态可修改,而StatefulWidget则需要维护自己的状态,状态更新的时候还需要重新绘制,所以State对象才存在并且还有一系列方法通知系统去重新绘制。

再来看看这个FAB,点击它会增加屏幕中间的数字。

代码语言:javascript复制
floatingActionButton: FloatingActionButton(
  onPressed: _incrementCounter,
  tooltip: 'Increment',
  child: Icon(Icons.add),
)

使用起来也很简单,我们知道Flutter是个声明式的UI框架,我们只需要声明它的子Widget,在这儿就是个Icon,以及被点击的回调就好了。在这里我们声明了点击调用_incrementCounter这个方法,这个方法里会通过setState去更新状态并通知系统重绘,那么所有依赖_counter这个变量的view都会重绘。Flutter给我们内置了很多MD的图标,如果大家对MD的图标比较满意,那直接通过Icons这个类就可以获取,省的UI再切图了。

瞧瞧,简单这些点代码,我们就实现了一个美观的计数器app。但是感觉功能有点单调,可能我们还有点手痒痒想动手试试,来,我们来自己增加些功能,实现增加,减少,重置的功能。

首先我们在FAB上方添加两个按钮来实现数字的加减,我们知道我们的UI整体在一个叫Column的widget里面,我们的按钮横向排列,当然得放在一个Row里面啦。

代码语言:javascript复制
Row(
  children: <Widget>[
    RaisedButton(
      child: Text('增加'),
      onPressed: _incrementCounter,
    ),
    RaisedButton(
      child: Text('减少'),
      onPressed: _decrementCounter,
    ),
  ],
)

这边的_decrementCounter_incrementCounter代码类似,不过是减少数值。

增加按钮

按钮是加上来了,但是颜色有点单调,而且太靠左了,居中显示更加好看一点。这是因为Row是一个flex Widget,默认在横向上占有它能占有的所有空间,占据整个屏幕的宽度无所谓,我们得让这俩按钮居中,这里我们就用到RowmainAxisAlignment属性了,用它来分配主轴上的空白空间。我们这里使用MainAxisAlignment.spaceAround,它会把空白空间分成两边小中间大的样子,大家可以自由的尝试一下这个枚举类的不同的值,感受他们的效果。

代码语言:javascript复制
Row(
  mainAxisAlignment: MainAxisAlignment.spaceAround,
  children: <Widget>[
    RaisedButton(
      color: Colors.orange,
      child: Text('增加'),
      onPressed: _incrementCounter,
    ),
    RaisedButton(
      color: Colors.amber,
      child: Text('减少'),
      onPressed: _decrementCounter,
    ),
  ],
)

最后我们需要把FAB改成重置的效果,只需要写一个_resetCounter方法把_counter设为0就好啦。

代码语言:javascript复制
void _resetCounter() {
  setState(() => _counter = 0);
}

图标也要替换掉,好在Flutter给我们内置了好多MD图标,Icons里面就有刷新图标:Icons.refresh。再次Ctrl S:

最终效果

好了,我们的最终效果已经实现了,增加了加减,重置功能,顺便也体验了一把编写Flutter应用有多爽。Flutter代码的编写流程就是这么简单,就是组合!组合小的形成大的Widget,组合已有的形成之前没有的Widget。在初步了解了Flutter之后,有些同学可能好奇,Flutter不停地销毁Widget再重建,它是怎么做到快速绘制如原生般流畅的?放心,下次我们就来了解一下Flutter的渲染流程,了解它为什么不停地创建销毁Widget却仍然丝滑。

0 人点赞