从这开始了解Flutter项目

1,738 阅读3分钟

目录

本人会针对Flutter从入门到相对熟练【具备开发水平】,在掘金上开辟一个专栏,专门讲述掌握Flutter的基本过程,每周会定期更新1-2篇博客!

本人会不断提供优质的内容博客给大家,内容目前涵盖Objective-C、Swift、Flutter、小程序的开发。欢迎点赞博客及关注本人,共同进步~~~

问题

本系列文章基于最新发布的flutter 版本,开发工具为Android Studio,这是flutter从0到1的「工程目录结构解析」。

搭建好Flutter的开发环境,并且创建项目first_flutter。对于first_flutter项目,项目入口是什么?结构又是什么?我们写的代码放在哪里等问题。带着这些问题,我们一一解决!!!

一 功能结构与作用

1.1  功能结构

查看创建的first_flutter项目,项目结构如下图:

1.2 作用

下面查看结构模块中文件夹内容的作用:

  • dart_tool:dart依赖的第三方库,当第三方库下载下来的时候,会生成dart_tool文件。里面有package_config.json【记录某些文件所在的位置和版本信息相关内容,这个文件夹不需要开发人员动】

  • idea文件:Android Studio是谷歌基于IDEA开发的,作用是对当前项目做一些配置。

  • android文件:Andriod 平台相关代码。

  • iOS文件:iOS平台相关代码【是可以直接运行起来的,包括iOS真机的配置也要用到这个文件】

  • lib文件:我们研发写的代码主要都在这里,功能及UI等。

  • test文件:widget_test.dart-组件的测试方面。

  • gitignore:做一些git的忽略【因为在往git服务器提交东西的时候,有些东西需要忽略,这个文件就有用啦】

  • metadata:对flutter版本做一个记录【更新操作,访问能力等,本文件不能手动更改】

  • packages:相关依赖库地址

  • firstflutter:相关flutter配置

  • pubspec.lock:相关依赖的书写

  • pubspec.yaml:相关依赖库的书写

  • readme:项目描述

二 项目代码

2.1 first_flutter代码

当新建完项目时,查看main.dart文件,会看到比较简洁的代码【去除注释后】,如下

程序的入口是main.dart【此文件不能更改文件名字,Flutter内部已经默认从main.dart加载程序】里面的main函数。

import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
        visualDensity: VisualDensity.adaptivePlatformDensity,
      ),
      home: MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);
  final String title;
  @override
  _MyHomePageState createState() => _MyHomePageState();
}

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.headline4,
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        child: Icon(Icons.add),
      ), // This trailing comma makes auto-formatting nicer for build methods.
    );
  }
}

2.2  first_flutter运行效果

  • 这是一个计数器的案例程序,点击右下角的 + 符号,上面显示的数字会递增; 

  •  但是我们第一次接触main.dart中的代码,可能会发现很多不认识的代码,不知道这个内容是如何编写出来的;

作为初学者,我的建议是将其中所有的代码全部删除掉,从零去创建里面的代码,这样我们才能对Flutter应用程序的结构非常清晰,下面开始:

三 进阶一

在这以前,如果对Dart还不熟悉,请先阅读上篇文章 Flutter初体验 -- 五分钟搞定Dart知识

做任何的开发,都是从祖传的Hello World开始,那么现在的需求来了---在界面中心位置,显示一个Hello World 你好

3.1 代码实现

import 'package:flutter/material.dart';

main() {
  runApp(Text("Hello World 你好", textDirection: TextDirection.ltr));
}

3.2 界面如下

3.3 代码疑问点

当然,上面的代码已经实现了在界面上显示Hello World 你好

  • 但是没有居中,字体也有点小;
  •  这些问题,放到后面再来解决,先搞懂目前的几行代码;

上面的代码有一些比较熟悉,有一些并不清楚是什么:

  • 比如知道Dart程序的入口都是main函数,而Flutter是Dart编写的,所以入口也是main函数; 

  • 但是导入的Material是什么呢;

  • 在main函数中调用了一个runApp()函数又是什么呢?

3.4 代码分析

3.4.1 runApp和Widget

runApp是Flutter内部提供的一个函数,当启动一个Flutter应用程序时就是从调用这个函数开始的,可以点到runApp的源码,查看到该函数代码如下:

void runApp(Widget app) {
  ...省略代码
}

该函数让传入一个东西:Widget

拓展:先说widget的翻译
1.Widget在国内有很多的翻译;
2.做过Android、iOS等开发的人群,喜欢将它翻译成控件;
3.做过Vue、React等开发的人群,喜欢将它翻译成组件;
4.如果我们使用Google,Widget翻译过来应该是小部件;
5.没有说哪种翻译一定是对的,或者一定是错的,但是我个人更倾向于小部件或者组件;

Widget到底什么东西呢?

  • 学习Flutter,从一开始就可以有一个基本的认识:Flutter中万物皆Widget(万物皆可盘); 

  • 在iOS或者Android开发中,界面有很多种类的划分:应用(Application)、视图控制器(View Controller)、活动(Activity)、View(视图)、Button(按钮)等等; 

  • 但是在Flutter中,这些东西都是不同的Widget而已; 

  • 也就是整个应用程序中所看到的内容几乎都是Widget,甚至是内边距的设置,我们也需要使用一个叫Padding的Widget来做;

runApp函数让我们传入的就是一个Widget:

  • 但是现在没有Widget,怎么办呢?

  • 导入Flutter默认已经给我们提供的Material库,来使用其中的很多内置Widget;

3.4.2 Material设计风格【导入库,runApp全局函数在Material库中】

material是什么呢?

  • material是Google公司推行的一套设计风格,或者叫设计语言、设计规范等;

  • 里面有非常多的设计规范,比如颜色、文字的排版、响应动画与过度、填充等等;

  • 在Flutter中高度集成了Material风格的Widget;

  • 在应用中,可以直接使用这些Widget来创建我们的应用(后面会用到很多);

Text小部件分析:【Text()代码分析

class Text extends StatelessWidget {
  const Text(
    this.data, {
    Key key,
    this.style,
    this.strutStyle,
    this.textAlign,
    this.textDirection,
    this.locale,
    this.softWrap,
    this.overflow,
    this.textScaleFactor,
    this.maxLines,
    this.semanticsLabel,
    this.textWidthBasis,
  });
}
  • 可以使用Text小部件来完成文字的显示;

  • 发现Text小部件继承自StatelessWidget,StatelessWidget继承自Widget;

  • 所以可以将Text小部件传入到runApp函数中

  • 属性非常多,但是已经学习了Dart语法,所以会发现只有this.data属性是必须传入的。

StatelessWidget简单介绍:

  • StatelessWidget继承自Widget;

  • 后面会更加详细的介绍它的用法;

    abstract class StatelessWidget extends Widget { // ...省略代码 }

四 进阶二

4.1 优化一:居中

发现现在的代码并不是想要的最终结果:

  • 可能希望文字居中显示,并且可以大一些;

  • 居中显示: 需要使用另外一个Widget,Center;

  • 文字大一些: 需要给Text文本设置一些样式;

修改代码如下:

  • 在Text小部件外层包装了一个Center部件,让Text作为其child;

  • 并且,给Text组件设置了一个属性:style,对应的值是TextStyle类型;

代码实现

import 'package:flutter/material.dart';

void main() {
  // 第一步: 调用runApp【flutter提供的一个全局函数】,runApp函数是在头文件的库中,所以要导入package:flutter/material.dart
  //runApp需要传入Widget app【万物皆Widget】,Widget是抽象类,是不能够实例化的,所以用子类实例化
  runApp(
    Center(
      child: Text(
        "Hello World",
        textDirection: TextDirection.ltr,
        style: TextStyle(
          fontSize: 30,
          color: Colors.orange,
        ),
      ),
    )
  );
}

界面如下

4.2 优化二:改善界面结构

目前虽然可以显示HelloWorld,但是发现最底部的背景是黑色,并且我们的页面并不够结构化。

正常的App页面应该有一定的结构,比如通常都会有导航栏,会有一些背景颜色

在开发当中,并不需要从零去搭建这种结构化的界面,可以使用Material库,直接使用其中的一些封装好的组件来完成一些结构的搭建。

代码实现

import 'package:flutter/material.dart';

 main() {
  // 第一步: 调用runApp【flutter提供的一个全局函数】,runApp函数是在头文件的库中,所以要导入package:flutter/material.dart
  //runApp需要传入Widget app【万物皆Widget】,Widget是抽象类,是不能够实例化的,所以用子类实例化
   runApp(
       MaterialApp(
           home: Scaffold(
             appBar: AppBar(
               title: Text("第一个flutter项目"),
             ),
             body: Center(
               child: Text(
                 "Hello World 你好",
                 textDirection: TextDirection.ltr,
                 style: TextStyle(
                   fontSize: 30,
                   color: Colors.orange,
                 ),
               ),
             ) ,
           )
       )
   );
}

界面如下

代码剖析

在最外层包裹一个MaterialApp

  • 这意味着整个应用都会采用MaterialApp风格的一些东西,方便我们对应用的设计,并且目前使用了其中两个属性;

  • title:这个是定义在Android系统中打开多任务切换窗口时显示的标题;(暂时可以不写) 

  • home:是该应用启动时显示的页面,传入了一个Scaffold;

Scaffold是什么呢?

  • 翻译过来是脚手架,脚手架的作用就是搭建页面的基本结构; 

  • 所以给MaterialApp的home属性传入了一个Scaffold对象,作为启动显示的Widget; Scaffold也有一些属性,比如appBar和body; 

  • appBar是用于设计导航栏的,传入了一个title属性;

  • body是页面的内容部分,传入了之前已经创建好的Center中包裹的一个Text的Widget;

4.3 优化三:界面承载更多元素

我们可以让界面中存在更多的元素:

代码实现

import 'package:flutter/material.dart';

main() {
  // 第一步: 调用runApp【flutter提供的一个全局函数】,runApp函数是在头文件的库中,所以要导入package:flutter/material.dart
  //runApp需要传入Widget app【万物皆Widget】,Widget是抽象类,是不能够实例化的,所以用子类实例化
  runApp(
      MaterialApp(
        home: Scaffold(
          appBar: AppBar(
            title: Text("第一个flutter项目"),
          ),
          body: Center(
            child: Row(
              mainAxisAlignment: MainAxisAlignment.center,
              children: <Widget>[
                Checkbox(
                    value: true,
                    onChanged: (value) => print("Hello World")),
                Text(
                  "同意协议",
                  textDirection: TextDirection.ltr,
                  style: TextStyle(fontSize: 20),
                )
              ],
            ),
          ),
        ),
      )
  );
}

界面如下

看到上面截图是不是右上角都有一个Debug标识,通过在MaterialApp里面加入一行代码:

debugShowCheckedModeBanner: false,

看到上面的代码逻辑,是不是觉得在一个方法里面做了很多的事情,而且代码看起来很难维护和封装,下面我们对代码进行重构!!!

五 项目重构

5.1 创建自己的widget

很多学习Flutter的人,都会被Flutter的嵌套劝退,当代码嵌套过多时,结构很容易看不清晰。

Flutter整个开发过程中就是形成一个Widget树,所以形成嵌套是很正常的。

但是,开发一个这么简单的程序就出现如此多的嵌套,如果应用程序更复杂呢?

  • 可以对我们的代码进行封装,将它们封装到自己的Widget中,创建自己的Widget; 如何创建自己的Widget呢? 

  • 在Flutter开发中,可以继承自StatelessWidget或者StatefulWidget来创建自己的Widget类; 

  • StatelessWidget: 没有状态改变的Widget,通常这种Widget仅仅是做一些展示工作而已; StatefulWidget: 需要保存状态,并且可能出现状态改变的Widget;

在上面的案例中对代码的重构,使用StatelessWidget即可,所以接下来学习一下如果利用StatelessWidget来对我们的代码进行重构;

StatefulWidget放到后面的一个案例中来学习;

5.2 StatelessWidget

StatelessWidget通常是一些没有状态(State,也可以理解成data)需要维护的Widget:

来看一下创建一个StatelessWidget的格式:

  • 让自己创建的Widget继承自StatelessWidget; 

  • StatelessWidget包含一个必须重写的方法:build方法;

build方法什么情况下被执行呢?

  • 当我们的StatelessWidget第一次被插入到Widget树中时(也就是第一次被创建时);

  • 当我们的父Widget(parent widget)发生改变时,子Widget会被重新构建; 

  • 如果我们的Widget依赖InheritedWidget的一些数据,InheritedWidget数据发生改变时;

5.3 重构案例代码

就可以通过StatelessWidget来对我们的代码进行重构了

因为我们的整个代码都是一些数据展示,没有数据的改变,使用StatelessWidget即可;

另外,为了体现更好的封装性,我对代码进行了两层的拆分,让代码结构看起来更加清晰;(具体的拆分方式,在后面的案例中不断的体现出来,目前先拆分两层)

import 'package:flutter/material.dart';

//使用箭头函数,当只有一个表达体时
main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      home: Scaffold(
        appBar: AppBar(
          title: Text("第一个Flutter项目"),
        ),
        body: HomeContent(),
      ),
    );
  }
}

class HomeContent extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Center(
      child: Row(
        mainAxisAlignment: MainAxisAlignment.center,
        children: <Widget>[
          Checkbox(
              value: true,
              onChanged: (value) => print("Hello World")),
          Text(
            "同意协议",
            textDirection: TextDirection.ltr,
            style: TextStyle(fontSize: 20),
          )
        ],
      ),
    );
  }
}

通过以上上面的学习和了解,相信再次看first_flutter项目的代码会有比较清晰的认识【起码不会摸不到头绪吧】

六 Go Back

下面是first_flutter项目代码的部分注解【再次 回看代码,会有比较清晰的理解】

import 'package:flutter/material.dart';

// 等价于main() => runApp(MyApp()); 箭头函数的使用
void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      //对于主题颜色设置
      theme: ThemeData(
        primarySwatch: Colors.blue,
        visualDensity: VisualDensity.adaptivePlatformDensity,
      ),
      home: MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}


/**
 * StatefulWidget: 分为两个类
 * 1.集成自StatefulWidget的类【作用:1. 有一个必须实现的方法,如下:createState() 2. 可以接收父widget传过来的数据】
 * 2.对应着有state这个类【作用是保存状态】
 */
class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);
  final String title;
  @override
  _MyHomePageState createState() => _MyHomePageState();
}

//下一篇博客会着重讲述StatefulWidget和StatelessWidget以及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 pushed the button this many times:',
            ),
            Text(
              '$_counter',
              style: Theme.of(context).textTheme.headline4,
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        //点击右下角的按钮,实现自加
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        child: Icon(Icons.add),
      ), // This trailing comma makes auto-formatting nicer for build methods.
    );
  }
}

机会❤️❤️❤️🌹🌹🌹

如果想和我一起共建抖音,成为一名bytedancer,Come on。期待你的加入!!!

截屏2022-06-08 下午6.09.11.png

总结

今天这篇文章,详细介绍了Flutter项目的整体框架和结构以及代码讲述,也讲述了如何从0-1开始书写Flutter项目,以及项目中运用的知识点和技能点。大家可以手动的编写上面的Demo例子,相信通过上面的例子以及进阶,可以加深大家对Flutter项目的认知和感触!!!

下一篇我们将讲述Flutter实现列表的滚动和展示【也会着重讲解StatelessWidget和StatefulWidget】

希望对大家有所帮助,也感谢大家的点赞作品及关注本人,共同进步,共勉!!!