希望尽一点点薄力让大家有兴趣学习这门新技术。
这里暂不介绍环境配置等操作,不了解的朋友请先移步官网:https://flutter.io/
开发环境:VS Code
本套课程适合直接上手,无需各种基础,dart基础也不需要~~
项目展示
废话不多说了,看下这次我们需要开发的UI界面吧:

代码实现
首先我们替换入口文件lib/main.dart
下的代码
代码如下:
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中文翻译版。
在本项目中还需倒入其他哦两个库:
import 'package:flutter/cupertino.dart'; import 'package:flutter/foundation.dart';
|
void main() => runApp(new MyApp());
是Dart程序的入口,也就是说,Flutter程序在运行的时候,第一个执行的函数就是main()函数,Flutter默认会找到lib
目录下的main.dart
并运行void main() => runApp(new MyApp());
第一个组件
class MyApp extends StatelessWidget {
}
|
这是我们在主函数中调用的第一个控件(Widget)。
定义添加两种Theme(主题)分别适配Android和IOS
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,通过 build 方法返回一个布局好的静态控件。
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
,通过 build 方法返回一个布局好的动态控件。所谓动态控件,这里我们主要关注State
,通过 State 的 build
方法去构建控件。在 State 中,你可以动态改变数据,这类似 MVVM 实现,在 setState
之后,改变的数据会触发 Widget 重新构建刷新。而下方代码中,我们咋State中定义了_messages、_textController、_isWriting三个变量,我们需要在改变着三个变量时触发 Widget 重新刷新。
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
。
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
组件当中。
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
动画控制器。
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