从TodoList入门Flutter开发

前言

学习了近三周的 Flutter ,想着总结一下,但无奈 Flutter 知识点太多,从 Dart 语言到 Flutter 布局,基础Widget,列表,异步,网络编程及周边生态,有很多点可以细讲,以我现在的知识储备实在不想误人子弟,本文将会从一个Todolist小例子带你入门Flutter开发,而不是单纯的讲解API。

什么是Flutter

Flutter是一个由谷歌开发的开源移动应用软件开发工具包,用于为Android、iOS、Windows、Mac、Linux、Google Fuchsia开发应用。与 h5+混合开发(Uniapp)和JavaScript开发+原生渲染(React Navite)不一样,Flutter 的底层采用自绘UI+原生的方式,它的性能,开发效率方面都完胜前两者,并且在商业上得到了很好的应用,如闲鱼、淘宝物价版,西瓜视频都采用了Flutter进行开发。

在Flutter当中,一切变量都是 Object,万物皆是 Widget(参考了React组件的设计思想)。

开发TodoList

本文不会教你如何搭建开发环境,如果你想搭建,Flutter官网 讲的更加详细,我们可以直接访问 dartpad 这个网站直接开发Flutter ,如果你有兴趣,可以打开这个网站跟着我一起敲敲代码。

基本结构

import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Center(child: Text('Hello, World!')),
    );
  }
}

运行效果:
image.png
代码解析:

  • import 'package:flutter/material.dart'; 是使用Flutter组件必须要引入的,里面包含了 Text, Image, Center等基础组件等等。
  • main 函数是Flutter 的入口函数,它使用 runApp 运行了 MyAPP 这个weiget。
  • 接下来我们定义了 MyApp 这个 weiget,重写了它的 build 方法,使用了 MaterialApp 这个入口 widget,定义了首页的样子。

MaterialApp还包含了title,routes等属性。

无状态StatelessWidget和有状态StatefulWidget

和React当中的有状态组件和无状态组件一样,有状态组件就是有单独的交互逻辑和业务数据,通过 setState 来更新,而无状态组件则没有这些,所有的数据都是由外部传进来。

开发Todo页面

import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: TodoPage(),
    );
  }
}

// 新增代码
class TodoPage extends StatefulWidget {
  TodoPage({Key key}) : super(key: key);

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

class _TodoPageState extends State<TodoPage> {
  @override
  Widget build(BuildContext context) {
    return Center(child: Text('Hello, World!'));
  }
}

代码解析:

  • 因为我们要有数据和交互逻辑,所以我们使用了 StatefulWidget 来创建一个有状态的 TodoPage widget。
  • 首先我们使用 TodoPage 初始化一些入参以及继承父类的一些东西。
  • 接着我们重写了 createState 这个方法,让它生成一个 _TodoPageState 的数据。
  • _TodoPageState 继承了 state,在里面可以定义一些可响应的变量。

脚手架Scaffold

class _TodoPageState extends State<TodoPage> {
  @override
  Widget build(BuildContext context) {
    //新增代码,省略一些没变动代码。。。
    return Scaffold(
      appBar: AppBar(title: Text('todo list')),
      body: Center(child: Text('Hello, World!'))
    );
  }
}

Scaffold有 appBar,drawer,bottomNavigationBar,body等属性,可以快速搭建一个页面结构

定义数据和Model

//新增代码
class TodoModel {
  TodoModel({
    this.id,
    this.title,
    this.complete = false,
  });

  int id;
  String title;
  bool complete;
}

class _TodoPageState extends State<TodoPage> {
  //新增代码,省略一些没变动代码。。。
  List<TodoModel> todos = [];
  int id = 0;
  //...
}

代码解析:

  • 这里我们使用了 dart 中的 List 类型定义了 todos 变量,int 类型定义了个 id,更多dart类型看dart官方文档
  • TodoModel 这个是什么?如果你写过 Typescript,那你应该清楚了,TodoModel定义了 todos内部的结构类型,在我们使用了就可以有很方便的提示,以及一些类型检测功能。

构建输入框和列表

一个Todo应用,最基本的结构就是一个可输入的表单和一个展示todo的列表,在Flutter中,我们使用 TextField 这个widget来创建输入框,使用 ListView 来构建列表:

class _TodoPageState extends State<TodoPage> {
  List<TodoModel> todos = [];
  int id = 0;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('todo list')),
      body: Column(children: [
      TextField(),
      Expanded(
          child: ListView.builder(
              itemBuilder: (context, index) {
                TodoModel item = todos[index];
                return ListTile(
                  title: Text(item.title),
                );
              },
              itemCount: todos.length)),
    ])
    );
  }
}

image.png
代码解析:

  • Column 是纵向布局widget。
  • ListView 两个重要属性, itemBuilder 和 itemCount ,一个是返回构建列表项的元素,一个是列表的数量,组合就构成了一个可滚动的列表。
  • ListTile 和 Text

注意:在使用Column时,经常会出现一些报错,那是因为Flutter容器的限制,在这个我们就使用 Expanded来包裹ListView,防止出现类似的错误。

添加Todo

class _TodoPageState extends State<TodoPage> {
  //...
  void addTodo(text) {
    TodoModel item = TodoModel(
      id: id++,
      title: text,
      complete: false,
    );

    setState(() {
      _todos.add(item);
    });
  }
  //新增代码,省略一些没变动代码。。。
  TextField(onSubmitted: addTodo)
  // ...
}

代码解析:

  • 定义了一个添加 todo 的方法。
  • TextField 提交事件绑定了addTodo方法。

删除Todo

class _TodoPageState extends State<TodoPage> {
  //...
  void deleteTodo(index) {
    List newTodos = todos;
    newTodos.remove(newTodos[index]);
    setState(() {
      todos = newTodos;
    });
  }
  //新增代码,省略一些没变动代码。。。
    ListTile(
    title: Text(item.title),
    trailing: InkWell(
      onTap: () {
        deleteTodo(index);
      },
      child: Icon(Icons.close)),
  )
  // ...
}

代码解析:

  • 定义了 deleteTodo 删除todo的方法
  • ListTitile新增 trailing 属性,子组件是一个InkWell,用来绑定删除事件,再下面就是一个Icon widget。
image.png

更新Todo

class _TodoPageState extends State<TodoPage> {
  //...
  void updateTodo(index) {
    List<TodoModel> newTodo = todos;
    TodoModel item = newTodo[index];
    item.complete = !item.complete;
    setState(() {
      todos = newTodo;
    });
  }
  //新增代码,省略一些没变动代码。。。
    ListTile(
    onTap: () {
      updateTodo(index);
    },
    title: Text(
      item.title,
      style: TextStyle(
        decoration: item.complete
        ? TextDecoration.lineThrough
        : TextDecoration.none),
    ),
    trailing: InkWell(
      onTap: () {
        deleteTodo(index);
      },
      child: Icon(Icons.close)),
  )
  // ...
}

代码解析:

  • 定义 updateTodo 方法。
  • 给 ListTile 绑定一个点击事件onTap,用来更新Todo。
  • 给 title 的文本widget加上样式,如果它完成了就显示删除线,反之不显示。
image.png

完整代码

import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: TodoPage(),
    );
  }
}

class TodoModel {
  TodoModel({
    this.id,
    this.title,
    this.complete = false,
  });

  int id;
  String title;
  bool complete;
}

class TodoPage extends StatefulWidget {
  TodoPage({Key key}) : super(key: key);

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

class _TodoPageState extends State<TodoPage> {
  List<TodoModel> todos = [];
  int id = 0;

  void addTodo(text) {
    TodoModel item = TodoModel(
      id: id++,
      title: text,
      complete: false,
    );

    setState(() {
      todos.add(item);
    });
  }

  void deleteTodo(index) {
    List newTodos = todos;
    newTodos.remove(newTodos[index]);
    setState(() {
      todos = newTodos;
    });
  }

  void updateTodo(index) {
    List<TodoModel> newTodo = todos;
    TodoModel item = newTodo[index];
    item.complete = !item.complete;
    setState(() {
      todos = newTodo;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(title: Text('todo list')),
        body: Column(children: [
          TextField(onSubmitted: addTodo),
          Expanded(
              child: ListView.builder(
                  itemBuilder: (context, index) {
                    TodoModel item = todos[index];
                    return ListTile(
                      onTap: () {
                        updateTodo(index);
                      },
                      title: Text(
                        item.title,
                        style: TextStyle(
                            decoration: item.complete
                                ? TextDecoration.lineThrough
                                : TextDecoration.none),
                      ),
                      trailing: InkWell(
                          onTap: () {
                            deleteTodo(index);
                          },
                          child: Icon(Icons.close)),
                    );
                  },
                  itemCount: todos.length)),
        ]));
  }
}

总结

本文我们制作了一个非常简陋的TodoList,实现了增删查改的功能,通过这个例子,我们学习到了Flutter基本结构,基本组件Text,Column,ListView,以及Dart函数,类型,model等基础。

在Flutter当中,还有一些重点本文没有提及,如路由管理,网络请求,异步编程,JSON解析,而这些都是开发项目必备的,在后续我会出个更加完善的教程,包括上面这些以及Flutter常用widget API的使用,Dart语言总结和开发当中遇到的一些坑,方便自己对Flutter知识点做个总结,也希望可以帮助到有兴趣学习Flutter的你,共勉!!!

本文系作者 @ 原创发布在 极客猿小兵的博客。未经许可,禁止转载。


极客猿小兵 » 从TodoList入门Flutter开发

发表回复