Flutter学习(3)一些项目概念以及对Flutter的看法

现在我们需要去搞懂Flutter项目中的一些基础的概念。

我们从Flutter的模板项目(计数器)来看看概念。

1.应用入口

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

和C/C++ Java一样,在Flutter中,main()是程序的入口。这个就做了一件事,runApp(MyApp()) ,使用 => 是如果函数只有一行,可以使用这个表达式。即函数又是这样的:

void main() {
   runApp(MyApp())
}

2.MyApp

上面的App()类即代表Flutter应用:

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        //主题颜色
        primarySwatch: Colors.lightBlue,
      ),
      //应用首页路由
      home: MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

其中App类继承了 StatelessWidget并重写了 build 方法。 Widget是组件。
在Flutter构建页面时,会调用 build(),build()方法用来描述一个界面。
MaterialApp 是材料设计库给Flutter App提供的框架。通过它可以设置应用的名称、主题、语言、首页以及路由列表。MaterialApp也是一个Widget
home 修饰的是Flutter应用的首页

3.首页

上面的home定义了一个首页类,并定义了它的标题,我们来看看这个首页类:

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

  final String title;

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

可以看到这个首页类集成了 StatefulWidget,和刚刚的 StatelessWidget对应。
前者表示有状态组件,后者表示无状态组件。
Stateful Widget 可以拥有状态,这些状态在 widget的生命周期中是可以改变的,而Stateless widget中的状态是不可以改变的。它有两个类组成:

  • StatefulWidget
    StatefulWidget类本身是不便的
  • State
    该类持有的状态在 Widget生命周期中可能会发生改变。

_MyHomePageState 正是MyHomePage的状态类。

4.State类

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 clicked the button this many times:',
            ),
            Text(
              '$_counter',
              style: Theme.of(context).textTheme.display1,
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        child: Icon(Icons.add),
      ), // This trailing comma makes auto-formatting nicer for build methods.
    );
  }

可以看到,同样是继承了 Widget类,首页类却没有实现 build(),而是在State类中实现了。
具体原因可以看 为什么把build()方法写在状态类中
大概就是不写在状态类中也可以,但是对程序来说并不友好。

在状态类中,定义了一个状态 int _counter = 0;
并且实现了自增函数:

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

可以看到自增函数里,先对 counter进行了自增,然后在去使用 setState()这个方法。
其实这个方法看到后面,它就有点像Android中控件的 invalidate()
在build()方法中,返回了一个 Scaffold 类,这个类也是一个Widget,它是Material库中提供能的页面脚手架,提供了默认的导航栏、标题和包含主屏幕的widget树的 body属性,组件树可以很复杂。在后面的实例中,路由默认都是通过 Scaffold类创建
body的组件中包含了一个 Center组件,Center可以将其子组件树对其到屏幕中心。
Center的子组件是 Colum,它的作用是将其所有子组件沿着屏幕垂直方向依次排列。
appBar和 floatingActionButton 是顶部栏,和悬浮按钮
其中悬浮按钮定义了 onPressed,表明如果点击了这个按键,会触发后面的操作,即计数器自增。计数器自增后,会setState,状态变更会让Flutter框架调用其 build方法重新构建ui,最终显示在设备屏幕上

5.路由管理

路由(Route)在Flutter中指为页面,在Android中就是Activity,在IOS指的是ViewController。
而路由管理就是管理页面间如何跳转,和Android一样,路由管理一个路由栈

我们在基础模板计数器上做修改,新建一个路由:

class NewRoute extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("Rikka Route"),
      ),
      body: Center(
        child: Text("This is Rikka Route"),
      ),
    );
  }
}

上面代码中,我们通过继承了一个StatelessWidget来实现一个新的界面,接下来我们要进入到这个界面中,我们在初始的界面_MyHomePageStateColumn下添加一个按键,FaltButton,并设置点击事件实现跳转:

       child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            ....
            FlatButton(
              child: Text("open Rikka route"),
              textColor: Colors.blue,
              onPressed: () {
                //导航到新路由
                Navigator.push(context, MaterialPageRoute(builder: (context) {
                  return NewRoute();
                }));
              },
            )
          ],
  }

运行后效果如下:
在这里插入图片描述
点击蓝色按钮之后:
在这里插入图片描述

6.MaterialPageRoute

在上面新建一个页面的时候,我们可以知道 Navigator相当于路由栈
我们向其中push一个 MaterialPageRoute(context,build)
MaterialPageRoute继承自 PageRoute类,PageRoute类是一个抽象类,表示占有整个屏幕空间的一个模态路由页面,还定义了过渡动画的接口和属性。MaterialPageRoute是一个组件,它可以针对不同的平台,实现与平台风格一致的路由切换动画。

我们来看看其构造函数的可选参数:

  MaterialPageRoute({
    WidgetBuilder builder,
    RouteSettings settings,
    bool maintainState = true,
    bool fullscreenDialog = false,
  })
  • WidgetBuilder
    它是一个回调函数,作用是构建路由页面的具体内容,返回值是一个widget,而路由器的实例正是一个Widget,所以我们传入新路由的构造函数,其实就是去创建这个路由
  • RouteSettings
    包含路由的配置信息,如路由名称、是否初始路由(首页)
  • maintainState
    默认情况下,当入栈一个新路由时,原来的路由仍然会被保存在内存中,如果想在路由没用的时候释放其占用的所有资源,可以设置 maintainState 为false
  • fullscreenDialog
    表示新的路由是否是一个全屏的对话框。

7.Navigator

Navigator 是一个路由管理的组件,它提供了打开和退出路由的方法。它维护一个栈,管理活动路由集合。通常当前屏幕显示的页面就是栈顶的路由。
这里我们介绍最常用的两个方法:
1.Future push(BuildContext context, Route route)
将给定的路由入栈(即打开新的页面),返回值是一个 Future对象,用以接收新路由出栈(即关闭)时的返回数据。

2.bool pop(BuildContext context, [ result ])
将栈顶路由出栈, result为页面关闭时返回给上一个页面的数据。

8.路由传值

我们很多时候,都在要跳转到新的界面时带入一些值,就和Android Java开发里,需要Intent去带入一些值。一遍下一个界面展示。下面我们来看看路由是怎么传值的:

下面为一个新的路由

class TipRoute extends StatelessWidget {
  TipRoute({
    Key key,
    @required this.text, //@required 接收一个text参数
  }) : super(key: key);
  final String text;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("提示"),
      ),
      body: Padding(
        padding: EdgeInsets.all(18),
        child: Center(
          child: Column(
            children: <Widget>[
              Text(text),
              RaisedButton(
                onPressed: () => Navigator.pop(context, "这里是Rikka 的返回值"),
                child: Text("返回"),
              )
            ],
          ),
        ),
      ),
    );
  }
}

打开的页面如下:

class RouterTestRoute extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Center(
      child: RaisedButton(
          onPressed: () async {
            // 打开 TipRoute,并等待返回结果
            var result = await Navigator.push(context,
                MaterialPageRoute(builder: (context) {
              return TipRoute(text: "我是Rikka");
            }));
            //输出 路由返回结果
            print("路由返回值: $result");
          },
          child: Text("打开提示页")),
    );
  }
}

效果如下:
在这里插入图片描述
点击返回后打印出:
I/flutter (24716): 路由返回值: 这里是Rikka 的返回值

9.命名路由

我们可以先给路由起一个名称,然后就可以通过路由名字直接打开新的路由了,这样的方式更直观更简单。

想要使用命名路由,我们必须先提供并注册一个路由表,这样应用程序才知道哪个名字与哪个路由组件对应。注册路由表就是给路由起名字,路由表的定义如下:

Map<String, WidgetBuilder> routes;

是一个Map,key为路由名称,value为build的回调函数。也就是说,我们通过一个名字打开一个路由,他回去路由表里面找到这个构建函数并调用它。

那么我们如何去注册一个路由表呢?举个例子,我们在MyApp类的build方法中找到MaterialApp,添加routues属性:

 return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        //主题颜色
        primarySwatch: Colors.lightBlue,
      ),
      routes: {
        "rikka_page": (context) => RouterTestRoute(),
       ...//其他路由名称
      },
      //应用首页路由
      home: MyHomePage(title: 'Flutter Demo Home Page'),
    );

可以看到除了home之外,都可以进行注册,那么如果我们也想注册首页路由的名称怎么办,可以这样:

return MaterialApp(
     ....
      initialRoute: "main",  //名为"main"的路由作为应用的首页
      routes: {
        "rikka_page": (context) => RouterTestRoute(),
        "main": (context) => MyHomePage(title: 'Flutter Demo Home Page'), //注册首页路由
      },
    );

可以看到我们通过 initialRoute来决定哪个是home,并在 routes中声明,这样,我们就不用在后面定义 home 这个属性了。

接下来通过路由名打开新路由
要通过路由名称来打开新路由,可以使用 Navigator 的 pushNamed方法:
就比如上面的我们给 RouterTestRoute 取了一个"rikka_page"的名称,那我们要去该路由页下通过 RouteSetting对象获取路由参数:

class RouterTestRoute extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    var args = ModalRoute.of(context).settings.arguments;
     print(args);
    ...
  }
}

接着在打开路由时传递参数:

 Navigator.of(context).pushNamed("rikka_page", arguments: "Rikka hi");

最后打开上一个页面后,将会打印出:
在这里插入图片描述

假设我们也想将上面路由传参示例中的TipRoute路由页注册到路由表中,以便也可以通过路由名来打开它。但是,由于TipRoute接受一个text 参数,我们如何在不改变TipRoute源码的前提下适配这种情况?其实很简单:

 routes: {
        "tip_page": (context) {
          return TipRoute(text: ModalRoute.of(context).settings.arguments);
        },
        ...
      },

10.onGenerateRoute

MaterialApp 有一个 onGenerateRoute属性。
如果用 pushNamed 打开命名路由时,如果指定的路由名在路由表中已经注册,则会调用路由表中的 builder函数来生成路由组件。
如果路由表中没有注册,就会调用 onGenerateRoute来生成路由。 onGenerateRoute回调如下:

Route<dynamic> Function(RouteSettings settings)

//使用时:
 onGenerateRoute: (RouteSettings settings) {
        return MaterialPageRoute(builder: (context) {
          ....
        });
      },

onGenerateRoute只会对命名路由生效。
它应用在全局路由跳转前置处理,比如所有的路由在打开前都要检查一些东西,如果你在每个路由build()方法里面做的时候,那面会有些重复、复用性低,如果全部写在开始,则能避免这个麻烦。

使用命名路由,代码会更加好维护,因为如果使用匿名路由,则必须在调用Navigator.push的地方创建新路由页。如果使用命名路由,会更加简洁。

11.包管理

Android使用gradle来管理依赖包,Flutter管理包的配置文件叫做 pubspec.yaml。yaml是一种直观、可读性高的文件格式,而且相比于JSON和XML,它更容易被解析。我们来看项目根目录下的默认配置文件:

///应用名称
name: my_app
//应用或包的描述、简介
description: A new Flutter application.

//应用或包的版本号
version: 1.0.0+1

environment:
  sdk: ">=2.1.0 <3.0.0"

//应用或包依赖的其他包或插件
dependencies:
  flutter:
    sdk: flutter
  cupertino_icons: ^0.1.2

//开发环境依赖的工具包(不是Flutter本身依赖的包)
dev_dependencies:
  flutter_test:
    sdk: flutter

flutter:
  //flutter相关的配置选项
  uses-material-design: true

12.Pub仓库、导入包

Pub仓库 http://pub.dev/ 是google官方的Dart Packages仓库,类似于Android中的jcent。
我们找到一个支持的Flutter的包,并将其导入到项目中。

dependencies:
  flutter:
    sdk: flutter

  cupertino_icons: ^0.1.2
  //新的包
  english_words: ^3.1.3

点击 Packages get后,就能导入新包了

import 'package:english_words/english_words.dart';

我们可以导入git、本地包等,这里就不再阐述。

写在最后

现在开始并不会每天都去学Flutter,首先Flutter并不是我项目中用到的东西,之所以这几天写,是因为有必要了解Flutter的特性、优势。以及有无一定要去学习的必要,这样我就能整理一下今年在Android领域上的学习方向。
今天学完之后,我认真的浏览一遍其特性,说下一点看法:

  1. 热重载 ≠热修复
    热重载快对开发来说确实爽!
    并不是指热修复。热修复是指App版本上线后出现bug,能够第一时间进行修复。而Flutter并不支持这一特性
    热修复对App影响这一块来说是一个很值得探究的问题。严重的bug会影响用户体验,而因此其频繁的发版更会引起用户的不满(本人切身体会,情感流露极其真实TAT)。所以如果在可以保证测试流程覆盖全面的情况下,那热修复这个概念就微乎其微了,但实际情况,测试不可能覆盖到所有情况。
  2. 使用情况
    github、码云上,已经有许多开源项目。一款App需要的功能95%以上Dart都有第三方框架的支持。做为19年爆火的框架,社区成长如雨后春笋,所以不用担心生态不好、没什么人用的情况
  3. 关于语言的使用
    谁不喜欢简洁又有观赏性的代码呢?
    Dart这点上非常Ok
  4. 项目需要程度
    这点非常的重要,直接决定了其优先级。我浏览了一遍求职招聘和一些同届同移动端的同学情况,总所周知咸鱼已经开始使用Flutter作为开发框架,美团、快手有研究Flutter的团队,一些中小公司,已经真正的开始研究并使用Flutter了。求职招聘上,像腾讯、字节跳动(除开美团)一线大厂等的一些开发岗,并未要求一定要会Flutter,而是说,多学一门语言更好。相比于Flutter,Kotlin更加被Android开发岗所接受(毕竟Kotlin是原生)

所以我最后得出的结论是, 我前半年不会把重点放在学习Flutter上面,而取而代之的是我将把学习Flutter时间替换成Kotlin。
这里顺便整理一下2020今年的学习重点:

  1. 回归算法
    12月份因为项目需求,我使用了很久不用的BFS、扫描线、手写队列(我人傻了Swift没有LinkedList)等高效率的代码。
    在一番仔细的研究过后,我发现对代码吹毛求疵真的很重要,一个for循环少new一次变量,你可能就为内存节省了几百B,上Kb,多写一个过滤语句的if去节省不必要的计算,for循环可能就快几百ms。
    所以说算法题对时间要求极其严苛并不是没有道理的。在新的一年里,我需要继续刷点题,并且向部门推广、分享算法使用。并且提高做题效率,不要一股脑的把时间怼在想算法上,如果真研究不出来也不折磨自己了,但是思路一定要通透,这是程序员的自我修养。
  2. 性能调优
    性能调优有多个方面,如上面所说,同样的写for循环就有优劣之分。
    要把ADT、MAT、Profile等一系列工具加入到自己的武器库中。学会热更新热修复。
    在功能开发完成后,要学会使用这些东西去查看App性能,对review代码有帮助。
  3. 使用插件化、组件化
    概念我都懂,主要是要学会运用到项目中
  4. Kotlin
    Kotlin语言学习的7788,但还没有实际操作过。上半年要用Kotlin写一个项目挂GitHub上,能上线就更好。
  5. 多读框架、多用框架,力所能及写轮子
    我在2019年做到这一点,但是还是远远不够,今年继续加油

别的领域不知道,大前端这个职位竞争大、市场饱和,无论你是几年经验,只要你不认真对待自己的事业,公司随便找个人就能把你替了。如果你努力认真,你也能替代比你多做几年的人。毕业半年了,感受到了职场上的一些事情,有些人确实混,上班只为下班,有些人确实拿命拼,有些人确实拿命拼完后得了各种职业病影响到了身体健康,有些人确实能力配不上职位。令人憋屈的事情数不清,只有在开发时,写出优秀的代码,改好了难改的bug才能让自己感觉到开心。今年要坚持学习,也要坚持锻炼!

对于现在的我来说,每一行代码,都已经是生活了。

发布了263 篇原创文章 · 获赞 110 · 访问量 12万+

猜你喜欢

转载自blog.csdn.net/rikkatheworld/article/details/104022626