一. 创建Flutter项目

创建Flutter项目有两种方式:通过命令行创建 和 通过开发工具创建

1.1. 通过命令行创建

  • Flutter的名称不要包含特殊的字符,另外不可以使用驼峰标识

    1. flutter create learn_flutter

    1.2. 通过开发工具创建

    通过Android Studio来进行创建。

    1.3. 默认程序分析

    默认项目分析:

  • 目录下有一个lib文件夹,会存放我们编写的Flutter代码;

  • main.dart,它是我们Flutter启动的入口文件,里面有main函数;

    二. 开始Flutter代码

    2.1. Hello World

    1. import 'package:flutter/material.dart';
    2. main(List<String> args) {
    3. runApp(Text("Hello World", textDirection: TextDirection.ltr));
    4. }

    2.2. 代码分析

    2.2.1. runApp和Widget

    runApp是Flutter内部提供的一个函数,当我们启动一个Flutter应用程序时就是从调用这个函数开始的。
    该函数让我们传入一个东西:Widget?
    Widget的翻译:

  • 小组件或者小控件

Widget到底是什么呢?

  • Flutter中万物皆Widget;
  • 在iOS或者Android开发中,界面有很多种类的划分:应用(Application)、视图控制器(View Controller)、活动(Activity)、View(视图)、Button(按钮)等等;
  • Flutter中,这些东西都是不同的Widget而已;
  • 整个应用程序中所看到的内容几乎都是Widget,甚至是内边距的设置,也需要使用Padding-Widget来做;

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

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

    2.2.2. Material设计风格

    material是什么呢?

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

  • 设计了颜色、文字的排版、响应动画与过度、填充等等规范;
  • 在Flutter中高度集成了Material风格的Widget;

Text小部件分析:

  • Text小部件来完成文字的显示;
  • Text小部件继承自StatelessWidget,StatelessWidget继承自Widget;
  • 所以可以将Text小部件传入到runApp函数中;
  • 属性非常多,只有this.data属性是必须传入的;

    1. class Text extends StatelessWidget {
    2. const Text(
    3. this.data,
    4. {
    5. Key key,
    6. this.style,
    7. this.strutStyle,
    8. this.textAlign,
    9. this.textDirection,
    10. this.locale,
    11. this.softWrap,
    12. this.overflow,
    13. this.textScaleFactor,
    14. this.maxLines,
    15. this.semanticsLabel,
    16. this.textWidthBasis,
    17. }
    18. );
    19. }

    StatelessWidget简单介绍:

  • StatelessWidget继承自Widget;

    2.3. 代码改进

    2.3.1. 改进界面样式

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

  • 文字大一些: 需要给Text文本设置一些样式;
  • 我们在Text小部件外层包装了一个Center部件,让Text作为其child;
  • Text组件设置了一个属性:style,对应的值是TextStyle类型;

    1. import 'package:flutter/material.dart';
    2. main(List<String> args) {
    3. runApp(
    4. Center(
    5. child: Text(
    6. "Hello World",
    7. textDirection: TextDirection.ltr,
    8. style: TextStyle(fontSize: 36),
    9. ),
    10. )
    11. );
    12. }

    2.3.2. 改进界面结构

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

  • 同理可以使用Material库,直接使用其中的一些封装好的组件来完成一些结构的搭建

    1. import 'package:flutter/material.dart';
    2. main(List<String> args) {
    3. runApp(
    4. MaterialApp(
    5. home: Scaffold(
    6. appBar: AppBar(
    7. title: Text("wulei"),
    8. ),
    9. body: Center(
    10. child: Text(
    11. "Hello World",
    12. textDirection: TextDirection.ltr,
    13. style: TextStyle(fontSize: 36),
    14. ),
    15. ),
    16. ),
    17. )
    18. );
    19. }

    在最外层包裹一个MaterialApp

  • 意味整个应用都会采用MaterialApp风格的一些东西,方便我们对应用的设计。

  • title:这个是定义在Android系统中打开多任务切换窗口时显示的标题;
  • home:是该应用启动时显示的页面,我们传入了一个Scaffold;

Scaffold是什么呢?

  • 脚手架的作用就是搭建页面的基本结构;
  • MaterialApp的home属性传入了一个Scaffold对象,作为启动显示的Widget;
  • Scaffold也有一些属性,比如appBar和body;
  • appBar是用于设计导航栏的;
  • body是页面的内容部分,我们传入了之前已经创建好的Center中包裹的一个Text的Widget;

    2.3.3. 进阶案例实现

    1. import 'package:flutter/material.dart';
    2. main(List<String> args) {
    3. runApp(
    4. MaterialApp(
    5. home: Scaffold(
    6. appBar: AppBar(
    7. title: Text("wulei"),
    8. ),
    9. body: Center(
    10. child: Row(
    11. mainAxisAlignment: MainAxisAlignment.center,
    12. children: <Widget>[
    13. Checkbox(
    14. value: true,
    15. onChanged: (value) => print("Hello World")),
    16. Text(
    17. "同意协议",
    18. textDirection: TextDirection.ltr,
    19. style: TextStyle(fontSize: 20),
    20. )
    21. ],
    22. ),
    23. ),
    24. ),
    25. )
    26. );
    27. }

    2.4. 代码重构

    2.4.1. 创建自己的Widget

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

  • Flutter的代码缩进,2/4;
  • 可以封装自己的Widget;
  • Flutter开发中,可以继承自StatelessWidget或者StatefulWidget来创建自己的Widget类;
  • StatelessWidget: 没有状态改变的Widget,通常这种Widget仅仅是做一些展示工作而已;
  • StatefulWidget: 需要保存状态,并且可能出现状态改变的Widget;

    2.4.2. StatelessWidget

    StatelessWidget通常是一些没有状态需要维护的Widget:

  • 数据通常是直接写死(放在Widget中的数据,必须被定义为final);

  • 从parent widget中传入的而且一旦传入就不可以修改;
  • 从InheritedWidget获取来使用的数据;

创建一个StatelessWidget的格式:

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

    1. class MyStatelessWidget extends StatelessWidget {
    2. @override
    3. Widget build(BuildContext context) {
    4. return <返回我们的Widget要渲染的Widget,比如一个Text Widget>;
    5. }
    6. }

    build方法的解析:

  • Flutter在拿到创建的StatelessWidget时,就会执行它的build方法;

  • 需要在build方法中告诉Flutter,自定义的Widget希望渲染什么元素,比如一个Text Widget;
  • StatelessWidget没办法主动去执行build方法,当使用的数据发生改变时,build方法会被重新执行;

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

  • 当StatelessWidget第一次被插入到Widget树中时(也就是第一次被创建时);
  • 当我们的父Widget(parent widget)发生改变时,子Widget会被重新构建;
  • 如果我们的Widget依赖InheritedWidget的一些数据,InheritedWidget数据发生改变时;

    2.4.3. 重构案例代码

    1. import 'package:flutter/material.dart';
    2. main(List<String> args) {
    3. runApp(MyApp());
    4. }
    5. class MyApp extends StatelessWidget {
    6. @override
    7. Widget build(BuildContext context) {
    8. return MaterialApp(
    9. home: Scaffold(
    10. appBar: AppBar(
    11. title: Text("wulei"),
    12. ),
    13. body: HomeContent(),
    14. ),
    15. )
    16. }
    17. }
    18. class HomeContent extends StatelessWidget {
    19. @override
    20. Widget build(BuildContext context) {
    21. return Center(
    22. child: Row(
    23. mainAxisAlignment: MainAxisAlignment.center,
    24. children: <Widget>[
    25. Checkbox(
    26. value: true,
    27. onChanged: (value) => print("Hello World")),
    28. Text(
    29. "同意协议",
    30. textDirection: TextDirection.ltr,
    31. style: TextStyle(fontSize: 20),
    32. )
    33. ],
    34. ),
    35. );
    36. }
    37. }

    三. 案例练习

    3.1. 自定义Widget

    产品的展示就是一个大的Widget,这个Widget包含如下Widget:

  • 标题的Widget:使用一个Text Widget完成;

  • 描述的Widget:使用一个Text Widget完成;
  • 图片的Widget:使用一个Image Widget完成;
  • 上面三个Widget要垂直排列,可以使用一个Column的Widget;

三个展示的标题、描述、图片都是不一样的,所以我们可以让Parent Widget来决定内容:

  • 创建三个成员变量保存父Widget传入的数据

    1. class ProductItem extends StatelessWidget {
    2. final String title;
    3. final String desc;
    4. final String imageURL;
    5. ProductItem(this.title, this.desc, this.imageURL);
    6. @override
    7. Widget build(BuildContext context) {
    8. return Column(
    9. children: <Widget>[
    10. Text(title, style: TextStyle(fontSize: 24)),
    11. Text(desc, style: TextStyle(fontSize: 18)),
    12. Image.network(imageURL)
    13. ],
    14. );
    15. }
    16. }

    3.3. 列表数据展示

    可以创建三个ProductItem来展示了:

  • 这里采取上面的代码结构进行修改

    1. class MyApp extends StatelessWidget {
    2. @override
    3. Widget build(BuildContext context) {
    4. return MaterialApp(
    5. theme: ThemeData(
    6. primaryColor: Colors.blueAccent
    7. ),
    8. home: Scaffold(
    9. appBar: AppBar(
    10. title: Text("wulei"),
    11. ),
    12. body: HomeContent(),
    13. ),
    14. );
    15. }
    16. }
    17. class HomeContent extends StatelessWidget {
    18. @override
    19. Widget build(BuildContext context) {
    20. return Column(
    21. children: <Widget>[
    22. ProductItem("Apple1", "Macbook Product1", "https://tva1.sinaimg.cn/large/006y8mN6gy1g72j6nk1d4j30u00k0n0j.jpg"),
    23. ProductItem("Apple2", "Macbook Product2", "https://tva1.sinaimg.cn/large/006y8mN6gy1g72imm9u5zj30u00k0adf.jpg"),
    24. ProductItem("Apple3", "Macbook Product3", "https://tva1.sinaimg.cn/large/006y8mN6gy1g72imqlouhj30u00k00v0.jpg"),
    25. ],
    26. );
    27. }
    28. }
  • 页面超出了可视区域出现报错

如何解决?

  • 将Column换成ListView即可;
  • ListView可以让自己的子Widget变成滚动的效果;

    3.4. 案例细节调整

    包裹快捷键: alt+ enter

    3.4.1. 界面整体边距

    希望整个内容距离屏幕的边缘有一定的间距,怎么做呢?

  • 我们需要使用另外一个Widget:Padding,它有一个padding属性用于设置边距大小;

  • 设置内边距也是使用Widget,Padding;

    3.4.2. 内边距和边框

    添加一个内边距,并且还有边框,怎么做呢?

  • 我们可以使用一个Container的Widget,它里面有padding属性,并且可以通过decoration来设置边框;

    3.4.3. 文字图片的间距

    我们希望给图片和文字之间添加一些间距,怎么做呢?

  • 方式一:给图片或者文字添加一个向上的内边距或者向下的内边距;

  • 方式二:使用SizedBox的Widget,设置一个height属性,可以增加一些距离;

    3.5. 最终实现代码

    1. import 'package:flutter/material.dart';
    2. main(List<String> args) {
    3. runApp(MyApp());
    4. }
    5. class MyApp extends StatelessWidget {
    6. @override
    7. Widget build(BuildContext context) {
    8. return MaterialApp(
    9. theme: ThemeData(
    10. primaryColor: Colors.blueAccent
    11. ),
    12. home: Scaffold(
    13. appBar: AppBar(
    14. title: Text("wulei"),
    15. ),
    16. body: HomeContent(),
    17. ),
    18. );
    19. }
    20. }
    21. class HomeContent extends StatelessWidget {
    22. @override
    23. Widget build(BuildContext context) {
    24. return Padding(
    25. padding: const EdgeInsets.all(8.0),
    26. child: ListView(
    27. children: <Widget>[
    28. ProductItem("Apple1", "Macbook Product1", "https://tva1.sinaimg.cn/large/006y8mN6gy1g72j6nk1d4j30u00k0n0j.jpg"),
    29. ProductItem("Apple2", "Macbook Product2", "https://tva1.sinaimg.cn/large/006y8mN6gy1g72imm9u5zj30u00k0adf.jpg"),
    30. ProductItem("Apple3", "Macbook Product3", "https://tva1.sinaimg.cn/large/006y8mN6gy1g72imqlouhj30u00k00v0.jpg"),
    31. ],
    32. ),
    33. );
    34. }
    35. }
    36. class ProductItem extends StatelessWidget {
    37. final String title;
    38. final String desc;
    39. final String imageURL;
    40. ProductItem(this.title, this.desc, this.imageURL);
    41. @override
    42. Widget build(BuildContext context) {
    43. return Container(
    44. padding: EdgeInsets.all(20),
    45. decoration: BoxDecoration(
    46. border: Border.all()
    47. ),
    48. child: Column(
    49. children: <Widget>[
    50. Text(title, style: TextStyle(fontSize: 24)),
    51. Text(desc, style: TextStyle(fontSize: 18)),
    52. SizedBox(height: 10,),
    53. Image.network(imageURL)
    54. ],
    55. ),
    56. );
    57. }
    58. }