1. 基础Holle World
首先我们先拿一段官网上的最基础的Hello World代码,之后基于这段代码进行优化,并借此了解相关操作
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
Widget build(BuildContext context) {
return MaterialApp(
title: 'Welcome to Flutter',
home: Scaffold(
appBar: AppBar(
title: const Text('Welcome to Flutter'),
),
body: const Center(
child: Text('Hello World'),
),
),
);
}
}
效果如下图所示
2. 包
学过python和java的读者应该知道,编程中有引入包的操作,在flutter中当然也有,但是有一说一相对python直接 pip install 然后 import 来说肯定是要麻烦点的。
这部分我们跟官网一样,用 english_words(很多开源的包都可以在 pub.dev 中找到)这个开源包示范。这个包里面有含数千个最常用的英文单词以及一些实用功能。
2.1. 依赖
我们说flutter比python引入外部包比python包麻烦一点,跟这个也有关。我们需要先去 pubspec.yaml
(管理 Flutter 工程中的所有资源和依赖的文件)中搭建和查看依赖关系。
点Pub get => 相当于运行了 flutter pub get
2.2. 随机生成单词实例
运用刚刚导入的包,我们将Hello World中的文本替换为包自己随机生成的英文文本。主要关注导包的过程。
main.dart
import 'package:flutter/material.dart';
import 'package:english_words/english_words.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
Widget build(BuildContext context) {
final wordPair = WordPair.random();
return MaterialApp(
title: 'Welcome to Flutter',
home: Scaffold(
appBar: AppBar(
title: const Text('Welcome to Flutter'),
),
// body: const Center(
// child: Text('Hello World'),
// ),
body: Center(
child: Text(wordPair.asPascalCase),
),
),
);
}
}
效果如下:
3. widget
3.1. 特性介绍
widget在flutter中是一个非常重要的概念。widget最重要的工作之一就是提供build()方法,这可以用更低级的widget的特性来渲染自己的特性。
以HelloWorld代码为例,Myapp类本身就是继承了StatelessWidget类,所以Myapp类也是一个widget,因此,它可以用下面的widget渲染自己,这之中就包括几个比较常用的widget:对齐 (alignment)、填充 (padding) 和布局 (layout)等等。
Scaffold 是 Material 库中提供的一个 widget,它提供了默认的导航栏、标题和包含主屏幕 widget 树的 body 属性。 widget 树可以很复杂。但是HelloWorld里的其实还好,下面的树包括Center widget,这下面有有一个Text widget。因为Center的存在,这段文字就出现在屏幕中间了。
此外,widgets还分有状态和无状态两种形式,无状态的widgets内所有的属性都是final性质的,这意味着它的属性不可改变;相对的,有状态的widgets的属性就可以在其生命周期内改变。那怎么区分有状态和无状态?有状态一般有两个类:
StatefulWidget
类,这个类本身不变State
类,这个类本身存在于整个生命周期中
这里对widget的介绍主要是一些浮于表面的东西,更详细的见博主的另一篇博客。
3.2. 实例
我们复现下包中的实例,不过这里是使用 widget 完成的。
main.dart
import 'package:flutter/material.dart';
import 'package:english_words/english_words.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
Widget build(BuildContext context) {
// final wordPair = WordPair.random();
return MaterialApp(
title: 'Welcome to Flutter',
home: Scaffold(
appBar: AppBar(
title: const Text('Welcome to Flutter'),
),
// body: const Center(
// child: Text('Hello World'),
// ),
// body: Center(
// child: Text(wordPair.asPascalCase),
body: const Center(
child: RandomWords(),
),
),
);
}
}
// 输入stful会自动跳提示的,然后就可以在样板上操作了
class RandomWords extends StatefulWidget {
const RandomWords({Key? key}) : super(key: key);
// 状态类继承自 State<RandomWords>
State<RandomWords> createState() => _RandomWordsState();
}
// _是为了增强隐私性,是针对 State 对象推荐的最佳实践写法
// 重写下build的代码,修改为胜场随机字符的Text
class _RandomWordsState extends State<RandomWords> {
Widget build(BuildContext context) {
// return Container();
final wordPair = WordPair.random();
return Text(wordPair.asPascalCase);
}
}
效果同上。
4. 背单词APP实例
4.1. 基础版
先做一个能够无限翻页的实例。
main.dart
import 'package:flutter/material.dart';
import 'package:english_words/english_words.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
Widget build(BuildContext context) {
// final wordPair = WordPair.random();
return MaterialApp(
title: 'Welcome to Flutter',
home: Scaffold(
appBar: AppBar(
title: const Text('Welcome to Flutter'),
),
// body: const Center(
// child: Text('Hello World'),
// ),
// body: Center(
// child: Text(wordPair.asPascalCase),
body: const Center(
child: RandomWords(),
),
),
);
}
}
// 输入stful会自动跳提示的,然后就可以在样板上操作了
//
class RandomWords extends StatefulWidget {
// const RandomWords({Key? key}) : super(key: key);
const RandomWords({super.key});
// 状态类继承自 State<RandomWords>
// State<RandomWords> createState() => _RandomWordsState();
State<RandomWords> createState() => _RandomWordsState();
}
// _是为了增强隐私性,是针对 State 对象推荐的最佳实践写法
// 重写下build的代码,修改为胜场随机字符的Text
class _RandomWordsState extends State<RandomWords> {
//
final _suggestions = <WordPair>[];
// 设置字体
final _biggerFont = const TextStyle(fontSize: 16);
Widget build(BuildContext context) {
// return Container();
// final wordPair = WordPair.random();
// return Text(wordPair.asPascalCase);
return ListView.builder(
padding: const EdgeInsets.all(26.0),
// 对每一个单词都用itemBuilder
itemBuilder: (context, i) {
// 在每行前加分割线,即i为奇数的时候
if (i.isOdd) return const Divider();
// /2向下取整,表实际单词数
final index = i ~/ 2;
// 单词数不够的时候,加10个单词
if (index >= _suggestions.length) {
_suggestions.addAll(generateWordPairs().take(10));
}
// 不是奇数的时候放单词
return ListTile(
title: Text(
// 每次return当前单词
_suggestions[index].asPascalCase,
// 设置style
style: _biggerFont,
),
);
},
);
}
}
4.2. 进阶版
在flutter中,界面切换称作路由切换,进阶版中我们将添加一个主路由切换新路由的操作。flutter的导航器是一个栈,新界面会被push到栈中,当推出这个界面时,会被pop出来。
代码实现如下,注释写的很详细了。
main.dart
import 'package:flutter/material.dart';
import 'package:english_words/english_words.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
Widget build(BuildContext context) {
return MaterialApp(
title: 'Welcome to Flutter',
// 这个歌东西特别有用,是用来决定整个项目的主题的。
theme: ThemeData(
brightness: Brightness.dark,
primaryColor: Colors.blue,
),
home: const Scaffold(
// 效果和上面的title重复了
// appBar: AppBar(
// title: const Text('Welcome to Flutter'),
// ),
body: Center(
child: RandomWords(),
),
),
);
}
}
// 输入stful会自动跳提示的,然后就可以在样板上操作了
//
class RandomWords extends StatefulWidget {
// const RandomWords({Key? key}) : super(key: key);
const RandomWords({super.key});
// 状态类继承自 State<RandomWords>
// State<RandomWords> createState() => _RandomWordsState();
State<RandomWords> createState() => _RandomWordsState();
}
// _是为了增强隐私性,是针对 State 对象推荐的最佳实践写法
// 重写下build的代码,修改为胜场随机字符的Text
class _RandomWordsState extends State<RandomWords> {
// 存储单词
final List<WordPair> _suggestions = <WordPair>[];
// 设置单词是否被标注为收藏,这里必须声明_liked是一个<WordPair>
final Set<WordPair> _liked = Set<WordPair>();
// 设置字体
final _biggerFont = const TextStyle(fontSize: 18);
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Clark\'s Homework'),
// 这里出现新的界面,我们加入图标
actions: <Widget>[
IconButton(icon: const Icon(Icons.list), onPressed: _pushSaved),
],
),
body: _buildWords(),
);
}
// 这里显示
Widget _buildWords() {
// return Container();
// final wordPair = WordPair.random();
// return Text(wordPair.asPascalCase);
return ListView.builder(
padding: const EdgeInsets.all(26.0),
// 对每一个单词都用itemBuilder
itemBuilder: (context, i) {
// 在每行前加分割线,即i为奇数的时候
if (i.isOdd) return const Divider();
// /2向下取整,表实际单词数
final index = i ~/ 2;
// 单词数不够的时候,加10个单词
if (index >= _suggestions.length) {
_suggestions.addAll(generateWordPairs().take(10));
}
// 不是奇数的时候放单词和收藏
return _buildRow(_suggestions[index]);
});
}
// 为了给文字后面加爱心
Widget _buildRow(WordPair pair) {
// 表示这个单词是否被like了
final bool alreadySaved = _liked.contains(pair);
return ListTile(
// 放文字
title: Text(
pair.asPascalCase,
style: _biggerFont,
),
// 放图标
trailing: Icon(
alreadySaved ? Icons.grade : Icons.verified,
color: alreadySaved ? Colors.yellow : null,
),
// 交互:点击前后图标的变换
onTap: () {
setState(() {
if (alreadySaved) {
_liked.remove(pair);
} else {
_liked.add(pair);
}
});
},
);
}
// 这里定义被点击后新的界面出现的流程
void _pushSaved() {
// 入栈
Navigator.of(context).push(
MaterialPageRoute<void>(
builder: (BuildContext context) {
final Iterable<ListTile> tiles = _liked.map(
// (WordPair pair)这部分这样写报错(pair)不报错是因为前面没有定义liked是<WordPair>
(WordPair pair) {
return ListTile(
title: Text(
pair.asPascalCase,
style: _biggerFont,
),
);
},
);
// 负责组成一个容器,将context和tiles以list的形式合到一起,这俩都在builder里生成了
final List<Widget> divided = ListTile
.divideTiles(
context: context,
tiles: tiles,
)
.toList();
// 弹回一个新的界面的Scaffold,这个界面标题是Saved Suggestions,内容是一个devided
return Scaffold(
appBar: AppBar(
title: const Text('Saved Suggestions'),
),
body: ListView(children: divided),
);
},
),
);
}
}