Google在今年5月的Google大會上發佈了Flutter1.5.4版本,同時也推出了Flutter for Web的預覽版,並開啓了Flutter的全棧框架之路。同時,今年9月舉行的谷歌開發者大會上,Google宣佈flutter1.9正式發佈,而且flutter_web已經被合到master分支,說明flutter_web愈來愈受到Google的重視。web
首先切換到master並升級flutter到最新版本,或者下載最新的Stable channel版本,使用命令方式升級的命令以下:macos
flutter channel master
flutter upgrade
複製代碼
默認狀況下,flutter_web是沒有啓動的,須要開發者手動啓動它,啓動的命令以下:bash
flutter pub global activate webdev
複製代碼
運行上面的命令可能會提示須要添加環境變量,以下所示:app
export PATH="$PATH":"$HOME/Flutter/flutter/.pub-cache/bin"
複製代碼
添加進去,而後使用source ~/.bash_profile
命令更新環境變量。到這webdev就完事了,命令行敲webdev測試一下,若是沒有任何錯誤,會看到以下幫助信息。框架
Can't load Kernel binary: Invalid kernel binary format version. No active package webdev. 複製代碼
出現這種狀況須要先把dart卸載,而後如前邊所述將flutter內置的dart-sdk添加到環境變量就能夠了,卸載的命令以下:less
brew uninstall dart
複製代碼
而後,使用以下的命令啓動flutter_web。iphone
flutter config --enable-web
複製代碼
出現以下提示,說明咱們尚未建立項目。ide
Setting "enable-web" value to "true".
複製代碼
若是是最新的1.9.0及其以上版本,只須要將分支切換到master便可,切換的命令以下:工具
flutter channel master //切換到master分支
複製代碼
接下來,就能夠使用命令行或者Android Studio、VSCode等可視化工具來建立Flutter Web應用了,以下圖所示。 測試
接下來,咱們修改下默認的示例項目來看下Flutter在桌面和Web的狀況。例以下面是仿網易雲音樂的登陸界面,示例代碼以下: login_page.dart代碼
import 'package:flutter/material.dart';
import 'package:flutter_desk/widgets/common_button.dart';
import 'package:flutter_desk/widgets/v_empty_view.dart';
class LoginPage extends StatefulWidget {
@override
State<StatefulWidget> createState() {
return _LoginPageState();
}
}
class _LoginPageState extends State<LoginPage> with TickerProviderStateMixin {
Animation<double> _animation;
AnimationController _controller;
@override
void initState() {
super.initState();
_controller =
AnimationController(vsync: this, duration: Duration(milliseconds: 300));
_animation = CurvedAnimation(parent: _controller, curve: Curves.linear);
Future.delayed(Duration(milliseconds: 500), () {
_controller.forward();
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
appBar: AppBar(
backgroundColor: Colors.white,
elevation: 0,
brightness: Brightness.light,
),
body: SingleChildScrollView(
child: Container(
padding: EdgeInsets.only(
left: 80,
right:80,
top:30,
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Hero(
tag: 'logo',
child: Image.asset(
'images/icon_logo.png',
width: 90,
height: 90,
),
),
_LoginAnimatedWidget(
animation: _animation,
),
],
),
),
),
);
}
}
class _LoginWidget extends StatefulWidget {
@override
_LoginWidgetState createState() => _LoginWidgetState();
}
class _LoginWidgetState extends State<_LoginWidget> {
final TextEditingController _phoneController = TextEditingController();
final TextEditingController _pwdController = TextEditingController();
@override
Widget build(BuildContext context) {
return Theme(
data: ThemeData(primaryColor: Colors.red),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Container(
margin: EdgeInsets.only(top: 30),
child: Text(
'Welcome Back!',
style: TextStyle(
fontWeight: FontWeight.bold,
color: Colors.black87,
fontSize: 34,
),
),
),
Container(
margin: EdgeInsets.only(top:3),
child: Text(
'The Flutter Netease Cloud Music App',
style: TextStyle(
color: Colors.grey,
fontSize: 14,
),
),
),
VEmptyView(50),
TextField(
controller: _phoneController,
decoration: InputDecoration(
hintText: 'Phone',
prefixIcon: Icon(
Icons.phone_iphone,
color: Colors.grey,
)),
),
VEmptyView(40),
TextField(
obscureText: true,
controller: _pwdController,
decoration: InputDecoration(
hintText: 'Password',
prefixIcon: Icon(
Icons.lock,
color: Colors.grey,
)),
),
VEmptyView(120),
CommonButton(
callback: () {
String phone = _phoneController.text;
String pwd = _pwdController.text;
if (phone.isEmpty || pwd.isEmpty) {
return;
}
},
content: 'Login',
width: double.infinity,
)
],
),
);
}
}
class _LoginAnimatedWidget extends AnimatedWidget {
final Tween<double> _opacityTween = Tween(begin: 0, end: 1);
final Tween<double> _offsetTween = Tween(begin: 40, end: 0);
final Animation animation;
_LoginAnimatedWidget({
@required this.animation,
}) : super(listenable: animation);
@override
Widget build(BuildContext context) {
return Opacity(
opacity: _opacityTween.evaluate(animation),
child: Container(
margin: EdgeInsets.only(top: _offsetTween.evaluate(animation)),
child: _LoginWidget(),
),
);
}
}
複製代碼
common_button.dart代碼
import 'package:flutter/material.dart';
class CommonButton extends StatelessWidget {
final VoidCallback callback;
final String content;
final double width;
final double height;
final double fontSize;
CommonButton({
@required this.callback,
@required this.content,
this.width = 250,
this.height = 50,
this.fontSize = 18,
});
@override
Widget build(BuildContext context) {
return Container(
width: width,
height: height,
child: RaisedButton(
onPressed: callback,
color: Colors.red,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(height / 2))),
child: Text(
content,
style: TextStyle(color: Colors.white, fontSize: fontSize),
),
),
);
}
}
複製代碼
empty_view.dart代碼
import 'package:flutter/material.dart';
class VEmptyView extends StatelessWidget {
final double height;
VEmptyView(this.height);
@override
Widget build(BuildContext context) {
return SizedBox(
height: height,
);
}
}
複製代碼
而後,分別將運行環境改成Chorme和MacOS桌面,便可看到對應的效果,以下圖所示。