Flutter For Web入門實戰

xiangzhihong發表於2019-12-13

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測試一下,如果沒有任何錯誤,會看到如下幫助資訊。

在這裡插入圖片描述
需要說明的是,如果沒有用flutter自帶的dart-sdk而是單獨安裝,這裡可能會因為dart版本與flutter版本不匹配而出現如下提示。

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桌面,即可看到對應的效果,如下圖所示。

在這裡插入圖片描述

Flutter For Web入門實戰

相關文章