Google在今年5月的Google大會上釋出了Flutter1.5.4版本,同時也推出了Flutter for Web的預覽版,並開啟了Flutter的全棧框架之路。同時,今年9月舉行的谷歌開發者大會上,Google宣佈flutter1.9正式釋出,並且flutter_web已經被合到master分支,說明flutter_web越來越受到Google的重視。
首先切換到master並升級flutter到最新版本,或者下載最新的Stable channel版本,使用命令方式升級的命令如下:
flutter channel master
flutter upgrade
複製程式碼
預設情況下,flutter_web是沒有啟動的,需要開發者手動啟動它,啟動的命令如下:
flutter pub global activate webdev
複製程式碼
執行上面的命令可能會提示需要新增環境變數,如下所示:
按照提示,開啟~ > .bash_profile檔案把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新增到環境變數就可以了,解除安裝的命令如下:
brew uninstall dart
複製程式碼
然後,使用如下的命令啟動flutter_web。
flutter config --enable-web
複製程式碼
出現如下提示,說明我們還沒有建立專案。
Setting "enable-web" value to "true".
複製程式碼
如果是最新的1.9.0及其以上版本,只需要將分支切換到master即可,切換的命令如下:
flutter channel master //切換到master分支
複製程式碼
接下來,就可以使用命令列或者Android Studio、VSCode等視覺化工具來建立Flutter Web應用了,如下圖所示。
可以發現,新建的Flutter Web專案比原來的專案會多兩個包,即web包和macOS包。要執行Flutter Web應用或者桌面,只需要點選工具欄上對應的裝置即可,如下圖所示。 選擇執行環境為Chrome(web),然後執行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桌面,即可看到對應的效果,如下圖所示。