希望尽一点点薄力让大家有兴趣学习这门新技术。

这里暂不介绍环境配置等操作,不了解的朋友请先移步官网:https://flutter.io/

开发环境:VS Code

本套课程适合直接上手,无需各种基础,dart基础也不需要~~

项目展示

废话不多说了,看下这次我们需要开发的UI界面吧:

Flutter
Flutter

代码实现

首先我们替换入口文件lib/main.dart下的代码

代码如下:

1
2
3
4
5
6
7
import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {

}
  • import 'package:flutter/material.dart';

    Flutter默认帮我们导入了flutter/material.dart包,这个包也是我们开发FlutterApp必备的包,其实也是一个UI库,其内部实现了大量优秀炫酷的组件(Widgets),有App结构和导航、按钮、输入框和选择框、对话框、Alert、Panel、动画等等等等Material Design风格的控件。

    Material Design:熟悉Android开发的童鞋一定非常了解了,是谷歌推出的一套视觉设计语言。其风格简单大方是我个人非常喜欢的设计风格,有兴趣的同学可以学习了解一下Material Design官方原版Material Design中文翻译版

    在本项目中还需倒入其他哦两个库:

    1
    2
    import 'package:flutter/cupertino.dart';  //IOS风格适配
    import 'package:flutter/foundation.dart'; //flutter核心库之一
  • void main() => runApp(new MyApp());

    是Dart程序的入口,也就是说,Flutter程序在运行的时候,第一个执行的函数就是main()函数,Flutter默认会找到lib目录下的main.dart并运行void main() => runApp(new MyApp());

  • 第一个组件

    1
    2
    3
    class MyApp extends StatelessWidget {

    }

    这是我们在主函数中调用的第一个控件(Widget)。

定义添加两种Theme(主题)分别适配Android和IOS

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart';

final ThemeData iOSTheme = new ThemeData(
primarySwatch: Colors.red,
primaryColor: Colors.grey[400],
primaryColorBrightness: Brightness.dark,
);

final ThemeData androidTheme = new ThemeData(
primarySwatch: Colors.blue,
accentColor: Colors.green,
);

const String defaultUserName = "MeandNi"; //默认用户名

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

class MyApp extends StatelessWidget {

}

关注一下其中的primary颜色对双平台的设置,并且我们将ThemeData变量设置为final不可变的变量。

定义一个无状态StatelessWidget组件

继承 StatelessWidget,通过 build 方法返回一个布局好的静态控件

1
2
3
4
5
6
7
8
9
10
11
12
13

class MyApp extends StatelessWidget {
@override
Widget build(BuildContext ctx) {
return new MaterialApp(
title: "Chat Application",
theme: defaultTargetPlatform == TargetPlatform.iOS
? iOSTheme
: androidTheme,
home: new Chat(),
);
}
}

我们在这里返回了一个MaterialApp,其中可以设置title(标题)theme(主题)home(主页)等属性。

其他属性参考:https://docs.flutter.io/flutter/widgets/WidgetsApp-class.html

title:App标题

theme:App主题

home:App根路径

我们通过判断用户平台给出不同的主题。

定义一个有状态StatefulWidget组件 —— Chat

继承 StatefulWidget,通过 build 方法返回一个布局好的动态控件。所谓动态控件,这里我们主要关注

State 的 `build` 方法去构建控件。在 State 中,你可以动态改变数据,这类似 MVVM 实现,在 `setState` 之后,改变的数据会触发 Widget 重新构建刷新。而下方代码中,我们咋State中定义了_messages、_textController、_isWriting三个变量,我们需要在改变着三个变量时触发 Widget 重新刷新。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36

```dart
class Chat extends StatefulWidget {
@override
State createState() => new ChatWindow();
}

class ChatWindow extends State<Chat> with TickerProviderStateMixin {
final List<Msg> _messages = <Msg>[];
final TextEditingController _textController = new TextEditingController();
bool _isWriting = false;

@override
Widget build(BuildContext ctx) {
return new Scaffold(
appBar: new AppBar(
title: new Text("Chat Application"),
elevation:
Theme.of(ctx).platform == TargetPlatform.iOS ? 0.0 : 6.0,
),
body: new Column(children: <Widget>[
new Flexible(
child: new ListView.builder(
itemBuilder: (_, int index) => _messages[index],
itemCount: _messages.length,
reverse: true,
padding: new EdgeInsets.all(6.0),
)),
new Divider(height: 1.0),
new Container(
child: _buildComposer(),
decoration: new BoxDecoration(color: Theme.of(ctx).cardColor),
),
]),
);
}

定义底部输入框和submit按钮:

此Widget被放在ChatWindow的底部用于用户的输入提交。我们可以放关注点聚焦在TextField组件上,我们通过对输入值的监听修改_isWriting的值并对输入框组件和下方的按钮组件刷新达到一定用户体验。

对于放松按钮,我们在IOS端使用CupertinoButton,android端使用IconButton

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
Widget _buildComposer() {
return new IconTheme(
data: new IconThemeData(color: Theme.of(context).accentColor),
child: new Container(
margin: const EdgeInsets.symmetric(horizontal: 9.0),
child: new Row(
children: <Widget>[
new Flexible(
child: new TextField(
controller: _textController,
onChanged: (String txt) {
setState(() {
_isWriting = txt.length > 0;
});
},
onSubmitted: _submitMsg,
decoration:
new InputDecoration.collapsed(hintText: "Enter some text to send a message"),
),
),
new Container(
margin: new EdgeInsets.symmetric(horizontal: 3.0),
child: Theme.of(context).platform == TargetPlatform.iOS
? new CupertinoButton(
child: new Text("Submit"),
onPressed: _isWriting ? () => _submitMsg(_textController.text)
: null
)
: new IconButton(
icon: new Icon(Icons.message),
onPressed: _isWriting
? () => _submitMsg(_textController.text)
: null,
)
),
],
),
decoration: Theme.of(context).platform == TargetPlatform.iOS
? new BoxDecoration(
border:
new Border(top: new BorderSide(color: Colors.brown))) :
null
),
);
}

定义发送数据的函数

通过_textController清除输入框的旧数据。定义Msg插入到_messages数组中。

这里我们可以关注动画效果的操作,animationController将作为传输传递到Msg组件当中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
void _submitMsg(String txt) {
_textController.clear();
setState(() {
_isWriting = false;
});
Msg msg = new Msg(
txt: txt,
animationController: new AnimationController(
vsync: this,
duration: new Duration(milliseconds: 800)
),
);
setState(() {
_messages.insert(0, msg);
});
msg.animationController.forward();
}

定义Msg消息的模版组件

实际上我们点击Submit消息发送时,发送的就是这样一个StatelessWidget,其中携带两个变量:txt消息内容、animationController动画控制器。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39

class Msg extends StatelessWidget {
Msg({this.txt, this.animationController});
final String txt;
final AnimationController animationController;

@override
Widget build(BuildContext ctx) {
return new SizeTransition(
sizeFactor: new CurvedAnimation(
parent: animationController, curve: Curves.easeOut),
axisAlignment: 0.0,
child: new Container(
margin: const EdgeInsets.symmetric(vertical: 8.0),
child: new Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
new Container(
margin: const EdgeInsets.only(right: 18.0),
child: new CircleAvatar(child: new Text(defaultUserName[0])),
),
new Expanded(
child: new Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
new Text(defaultUserName, style: Theme.of(ctx).textTheme.subhead),
new Container(
margin: const EdgeInsets.only(top: 6.0),
child: new Text(txt),
),
],
),
),
],
),
),
);
}
}

至此我们的第一个实战App旧大功告成了,是不是很简单的样子,没错,就是这么简单就能做出这么漂亮的UI,其中有些Flutter基础可能没有涉及,如果又需要会尽量更新!

完整代码

github demo