开启Fluter基础之旅<一>-------官网DEMO解读、核心概念了解、常用组件学习之container、image

在2019年底对于Flutter的Dart语言进行了基础的学习,转眼又来到了2020年3月中下旬了,对于Flutter的学习又断了几个月了,今年目标一定得要把它能学透学精通达到能做商业项目的能力,如今在各大招聘JD上对于会Flutter的安卓程序猿都会优先考虑,可见其它的地位是越来越高了,所以接下来脚踏实地的沿着之前打好的基础继续前行。

官网DEMO解读:

这里重新创建一个工程,Flutter的学习从默认的官网DEMO熟悉开始:

 

然后用Android打开运行一下:

经典的官方默认运行就如上面所示,而这个小小的案例中能看到Flutter的一个大致的开发流程,另外不是之前已经对于Dart的基础语法进行学习了嘛,然后通过这个小DEMO正好来复习一下Dart语法,看我们之前学的各种语法在Flutter系统源码中都是如何体现的,这样就能让我们之前学的枯燥的语法给串活了,所以接下来好好的对官方生成的这个示例代码进行一下大概解读:

pubspec.yaml:

对于Flutter工程来说:

这个是一个很重要的配置文件,先来瞅一瞅它:

 

 

这里有一个小细节需要注意,由于注释#后面跟了一个空格,而打开注释进行图片配置的时候这块需要注意:

 

另外有个问题?难道所有的图片都需要在这注册么?是的,但是这里可以直接配置一个文件夹中的图片,也就是:

 

main.dart:

这里面的代码目前还看不太懂,没关系,先整体来读一读,多少会有些收获的,先全部贴出来:

import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        // This is the theme of your application.
        //
        // Try running your application with "flutter run". You'll see the
        // application has a blue toolbar. Then, without quitting the app, try
        // changing the primarySwatch below to Colors.green and then invoke
        // "hot reload" (press "r" in the console where you ran "flutter run",
        // or simply save your changes to "hot reload" in a Flutter IDE).
        // Notice that the counter didn't reset back to zero; the application
        // is not restarted.
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

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

  // This widget is the home page of your application. It is stateful, meaning
  // that it has a State object (defined below) that contains fields that affect
  // how it looks.

  // This class is the configuration for the state. It holds the values (in this
  // case the title) provided by the parent (in this case the App widget) and
  // used by the build method of the State. Fields in a Widget subclass are
  // always marked "final".

  final String title;

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

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

  void _incrementCounter() {
    setState(() {
      // This call to setState tells the Flutter framework that something has
      // changed in this State, which causes it to rerun the build method below
      // so that the display can reflect the updated values. If we changed
      // _counter without calling setState(), then the build method would not be
      // called again, and so nothing would appear to happen.
      _counter++;
    });
  }

  @override
  Widget build(BuildContext context) {
    // This method is rerun every time setState is called, for instance as done
    // by the _incrementCounter method above.
    //
    // The Flutter framework has been optimized to make rerunning build methods
    // fast, so that you can just rebuild anything that needs updating rather
    // than having to individually change instances of widgets.
    return Scaffold(
      appBar: AppBar(
        // Here we take the value from the MyHomePage object that was created by
        // the App.build method, and use it to set our appbar title.
        title: Text(widget.title),
      ),
      body: Center(
        // Center is a layout widget. It takes a single child and positions it
        // in the middle of the parent.
        child: Column(
          // Column is also layout widget. It takes a list of children and
          // arranges them vertically. By default, it sizes itself to fit its
          // children horizontally, and tries to be as tall as its parent.
          //
          // Invoke "debug painting" (press "p" in the console, choose the
          // "Toggle Debug Paint" action from the Flutter Inspector in Android
          // Studio, or the "Toggle Debug Paint" command in Visual Studio Code)
          // to see the wireframe for each widget.
          //
          // Column has various properties to control how it sizes itself and
          // how it positions its children. Here we use mainAxisAlignment to
          // center the children vertically; the main axis here is the vertical
          // axis because Columns are vertical (the cross axis would be
          // horizontal).
          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),
      ), // This trailing comma makes auto-formatting nicer for build methods.
    );
  }
}

接下来走读一下:

那哪些都用了这个库呢,咱们把这个导包的语句去掉看报错就晓得了:

继续往下分析:

所以这里面就定义了一个这样的Widget类:

 

无状态的Widget是个啥呢?这里官方有一句话来描述了:

而这个无状态的Widget里面有个build()方法,一看就是用来进行构建的:

直接返回它,那来看一下这个MaterialApp类:

它当然是承载可变的元素喽,比如说图片的显示,文字的变化,点击事件的响应之类的,基本上跟用户有交互的都要用这个,所以此时则构建了一个Material风格的Widget了,来看一下它的构建细节:

这里就正好可以复习一下我们之前几次学的Dart语法啦,先看一下MaterialApp的构造定义:

这是啥构造函数呀,其实它叫常量构造,可以到这https://www.cnblogs.com/webor2006/p/11981709.html回忆一下:

那它有啥好处呢?

嗯,然后这个构造中的参数是定义在了一对花括号当中了:

这又是什么语法呢?其实是叫可选命名参数,这里可以在这https://www.cnblogs.com/webor2006/p/11975291.html回忆一下Dart语法了:

它有一个注意点就是参数调用时一定得要显示指定参数名,像这样调用是不行的:

哦,原来我们之前学的枯燥的语法最终都是能派上用场的嘛,所以,此时对于这块的写法就不陌生了:

其中这个title是不生效的,因为由home中的MyHomePage进行了设置了,然后theme则是主题,看一下ThemeData是个啥?

其实它叫做工厂构造,复习一下:

有啥用也可以看到了,是一个单例对象,好,再来分析最后一个参数:

先看一下这个参数接收的是个什么类型:

所以此时又定义了一个类,不过这次是一个有状态的,因为准备要构建带变化的视图了:

其中有一个createState()方法,关于这块之后还会讲,这里理解系统会调用它,然后我们需要定义一个State类:

 其中它里面的代码可能看着很怪,因为缺少一些理论的东东,这里就简单看一下便好:

它里面有一个Build()方法,可以理解成Android里面View的onDraw(),所以里面直接用了一个系统库中的Widget:

它的中文意思是脚手架,也就是页面的骨架。

然后此时就要以看到视图的构建细节了,也就是最终运行所看到的,首先是一个AppBar:

也就是它:

看一下AppBar是个什么东东:

其中有一个比较奇怪的,就是它的标题的设置:

那这个Text又是啥呢?

往下再分析:

也就是:

它用了一个Center来构建的,我猜它肯定又是一个Widget:

很明显,这个Widget的作用是为了将里面的元素居中显示:

然后它里面的孩子是一个列布局,有点像Android里面的LinearLayout的垂直布局:

此Column肯定又是Widget啦:

然后它的第一个参数是控制主轴方向的:

也就是y轴方向,很显然咱们的孩子就是上下居中的:

那横轴怎么控制呢?这里可以用这个属性:

此时效果就发生变化了:

而看到的两个文本则是由这块来指定的:

 

其中这里面的写法好奇怪,先来看一下children是啥类型:

它的构建确实就是可以放到[]中来构建的,所以也不足为奇,其中第二个次数显示的则使用了一个表达式,动态的来根据实际count的数量来进行变化:

好,最后一个元素则是那个点击按钮了:

它当然也是一个Widget喽:

第一个参数为onPressed,它其实是一个点击事件的回调方法:

所以这里就指向了count++那个函数了:

然后第一个参数指定的是一个tooltip,其实它就是长按之后的提示:

最后一个则为按钮的图标,由系统提供的,决结一下此DEMO的一个视图树的层次结构:

 

其中对于Flutter来说,记住一句话,一切皆是Widget,这个从上面的DEMO分析就可以看到,而在之前我们学习Dart语言时也有一句话,一句皆是对象!!虽说目前还没有手动编写过Flutter的代码,但是通过细品这官方的代码,也能或多或少的感受到它的一个编写规则,还是很有意义的。

核心概念了解:

上面在分析官方代码时,是不是感觉还是有些吃力,比如说:

要搞清楚这些东东,其实还是需要有一些理论基础才行的,所以下面来了解一些理论化的概念,有助于更好的来学习Flutter。

视图树:

这块就跟Android完全不一样了,稍稍有点不好理解,但是相当的重要,先来看一下图:

如上图所示,视图树是包含三种,一种是Widget树,第二种是Element树,第三种是Render树,其整个过程如下:

创建widget树。调用runApp(rootWidget),将rootWidget传给rootElement,做为rootElement的子节点,生成Element树,由Element树生成Render树,Render树的根是一个RenderView。也就是说我们代码中编写的Widget它是不负责渲染的,最终渲染是得靠Render树,而且每个树结点都会有一个对应的转换结点: 

而对于这三者视图树都发挥啥作用呢?下面描述一下:

  • Widget:存放渲染内容、视图布局信息,widget的属性最好都是immutable。
  • Element:存放上下文,通过Element遍历视图树,Element同时持有Widget和RenderObject。
  • RenderObject:根据Widget的布局属性进行layout,对widget传入的内容进行渲染绘制。

这块需要好好感受一下,跟Android的不同。

树更新:

  • 全局更新:调用runApp(rootWidget),一般flutter启动时调用后不会再调用。
    如咱们程序所示:
  • 局部子树更新:将子树作StatefulWidget的一个子Widget,并创建对应的State类实例,通过调用State.setState()触发子树的刷新。
    有这条做为理解依据,所以官方DEMO的这个代码就可以理解了:

    也就是当我们点击按钮时则会触发_counter++这个动作,而这个动作最终要让TextView进行刷新就必须将这个++放到setState()方法当中了。

StatelessWidget:

这个是指无状态的Widget:

看一下它的理论化描述:

  • StatelessWidget:无中间状态变化的widget,初始状态设置以后就不可再变化。

  • StatelessWidget用于不需要维护组件状态的场景。

  • createElement()创建StatelessElement对象。

  • 一个StatelessWidget对应一个StatelessElement。

提示:Flutter推荐尽量使用StatelessElement,性能好,但是吧也得看实际情况。

StatefulWidget:

看一下它理论化的描述:

  • StatefulWidget:存在中间状态变化的widget。
  • createElement()创建StatelfulElement对象。
  • createState()创建State对象(可能调用多次)。
  • 一个StatefulElement对应一个State实例。

所以Demo中就有这样的代码:

当一个StatefulWidget同时插入到widget树的多个位置时,Flutter framework就会调用createState方法为每一个位置生成一个独立的State实例。

State:

它有两个重要的属性:

  • widget:表示与该State实例关联的widget实例。
    也就是:

  • context:BuildContext的一个实例。

另外关于Widget、State、setState()、BuildContext之间的关系,描述如下:

  • 可以手动调用其setState()方法通知Flutter framework状态发生改变,Flutter framework在收到消息后,会重新调用其build方法重新构建widget树,从而达到更新UI的目的。
  • Widget与State的关联不是永久的。UI树上的某一个节点的widget实例在重新构建时可能会变化,但State实例只会在第一次插入到树中时被创建,当在重新构建时,如果widget被修改了,Flutter framework会动态设置State.widget为新的widget实例。
  • BuildContext表示构建widget的上下文,它是操作widget在树中位置的一个句柄,它包含了一些查找、遍历当前Widget树的一些方法。每一个widget都有一个自己的context对象。

State生命周期【重要】:

任何关于生命周期的知识都是非常重要的,所以下面好好的来看一下,这里分为三个维度来看:

StatefulWidget插入到widget树:

initState:当Widget第一次插入到Widget树时会被调用,对于每一个State对象,Flutter framework只会调用一次该回调,所以,通常在该回调中做一些一次性的操作,如状态初始化、订阅子树的事件通知等。

didChangeDependencies:initState后立刻调用,state依赖的对象发生变化时调用。
build:构建Widget时调用,调用后控件会显示。

点击⚡️按钮热重载:

reassemble:此回调是专门为了开发调试而提供的,在热重载时会被调用,此回调在Release模式下永远不会被调用。
didUpdateWidget:组件状态改变时候调用,可能会调用多次。

从widget中移除StatefulWidget:

deactive:当State对象从树中被移除时,会调用此回调。
dispose:当State对象从树中被永久移除时调用,通常在此回调中释放资源。 

常用组件学习之container、image:

好,在详读了官方的默认DEMO的代码之后,接下来则手动来撸码进行操练了,还记得之前说过Flutte里一切皆组件么?所以这里先从一些学用的组件(Widget)开始学起,就像Android里面的图片,文件,列表,滑动之类的,虽说也有点小枯燥,但是这是打好基础的必经之路。

container: 

容器组件Container包含一个子widget,自身具备alignment、padding等属性,方便布局过程中摆放child。

下面新建一个新的文件来进行代码的编写:

这里就直接校仿官网的写法,其中这里有个技巧,就是对于这个StatelessWidget类要自己定义稍麻烦,其实是可以有快捷键快速的生成模板的,如下:

这里来试一下效果:

好,继续,这里先来简单显示一下:

import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text('Container示例'),
        ),
      ),
    );
  }
}

这里就直接先弄了个脚手架,之前官网的DMEO也是一样的,只是比这要稍微麻烦一点,运行看下效果:

 

其中右上角有一个DEBUG的标识:

其实它是可以去掉的,这样弄:

 

此时点一下则就消失了:

这里是要学习Container的用法,那接下来内容部分就用一下它:

运行:

Center这个Widget就是它里面的元素是一个居中布局的,然后里面有个Container组件,这里再次强调一下,为啥代码可以这样写,毕境是刚入门学习节奏可以慢一点,重点是要搞懂:

这是因为Container的命名构造有一个child参数,它接收的类型是Widget:

所以这里就靠熟练生巧了,对于组件的学习也是非常之必要的,接下来继续,貌似这个Container的文字太小了,改变一下它的大小:

改完之后,Flutter有一个非常好的功能就是热重载,就像前端开发人员一样,当然在Android中也类似的热重载功能,比如Instant run,但是。。貌似不太好用,而在Flutter上相当好用,像上面改了代码要生效,按control+s等个一两秒立马就能看到效果了,反正我在平常搞Android开发中对于更改是比较痛苦的,基本都得等个几分钟才能运行

设置Container背景色:

好。接下来咱们来改一下Container的颜色:

设置Container宽高和Padding:

设置Container的decoration:

设置边框:

它是干嘛的?下面直接看效果既可:

运行,发现报错了:

也就是说这里的color冲突了,这里就得注意了:

运行:

设置圆角:

其中这里又得复习一下Dart语法了:

还记得这是啥语法么?这里回忆一下:

查看一下它们的定义是否符合命名构造的语法:

 

嗯,确实是,另外为啥要用const来修饰呢?这里还记得const的好处了么?回忆一下:

transform:设置Container的变换矩阵,类型为Matrix4

关于Container的具体用法之后再挖掘。

image:图片组件

这再建一个Dart文件来学习一下图片组件。

 

加载网络图片:

import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text("Image示例"),
        ),
        body: Center(
          child: Column(
            children: <Widget>[
              Image.network(
                "https://dss3.bdstatic.com/70cFv8Sh_Q1YnxGkpoWK1HF6hhy/it/u=3034071742,1954908597&fm=26&gp=0.jpg",
                width: 100.0,
                height: 100.0,
              )
            ],
          ),
        ),
      ),
    );
  }
}

运行:

其中看一下Image的网络的命名构造的定义:

加载assets资源图片:

先定义一张assets的图片:

运行:

另外这里还有一个能配置UI调试的东东:

加载Uint8List内存资源图片:

这里先要将图片转成Uint8List类型,这里打算采用一个有状态的Widget来实现,先定义一个有状态的Widget:

其中initState()就派上用场了,这里回忆一下它的生命周期:

 

另外还用了一个条件判断mouted这又是啥意思呢?

另外还用到了then来监听数据加载完,这块的语法也复习一下:

数据加载好了,接下来则需要更新到图片视图中,也就是处理build()方法:

接下来将其定义到Column中:

运行一下:

从相册中选图片进行加载:

这里还是先来搭建一个有状态的Widget:

 

然后添加到Column中来瞅一下:

运行:

此时咱们点击“选择图片”时,则需要跳到相册中来进行图片的选择,该如何做呢?这里需要用到一个image_picker三方开源库,所以咱们集成一下:

好,接下来咱们则可以来实现这个点击事件了:

其中getImage方法使用了async await异步处理方式,这个在之前Dart也学过了。

运行:

 

那在IOS上的表现如何呢,运行一下,发现报错了。。

貌似是ios找不到我们的这个图片相册选择库,这里可以用终端进入到ios目录执行一下这个命令,然后会下载相关的依赖包:

然后再运行:

posted on 2019-12-06 17:21  cexo  阅读(486)  评论(0编辑  收藏  举报

导航