Web 开发支持

Flutter 对web应用开发支持尚处于早期,目前仍是 beta 版,并未发布正式版。因此,想要开发支持Web的应用,需要将SDK切换到Beta通道。

Flutter Web 架构图

环境准备

运行以下命令切换到Beta通道,使用最新版本的Flutter SDK并启用Web支持

flutter channel beta
flutter upgrade
flutter config --enable-web

确定本机已安装Chrome浏览器,然后执行以下命令,用以检查开发环境是否包含Web ServerChrome

flutter devices

添加支持

要在已有项目中添加Web支持,进入项目根目录下,执行以下命令

flutter create .

判断当前环境

目前没有直接的API用于判断当前是否运行在浏览器上,但我们可以使用以下方法进行封装

创建如下文件

_api.dart内容如下

String getPlatformOS(){
  throw UnsupportedError('No implementation of the connect api provided');
}

_html.dart内容如下

String getPlatformOS(){
  return 'browser';
}

_io.dart内容如下

import 'dart:io';

String getPlatformOS(){
  return Platform.operatingSystem;
}

environment.dart内容如下

/// 借助导包的方式,在不同平台上加载不同的库
import '_api.dart'
if (dart.library.io) '_io.dart'
if (dart.library.html) '_html.dart' as platform;

// 定义各平台枚举类型
enum Env{
  Linux,MacOS,Windows,Android,IOS,Fuchsia,Browser,Unknown
}

class Environment{

  static Env get current {
    var os = platform.getPlatformOS();
    switch(os){
      case "linux":
        return Env.Linux;
      case "macos":
        return Env.MacOS;
      case "windows":
        return Env.Windows;
      case "android":
        return Env.Android;
      case "ios":
        return Env.IOS;
      case "fuchsia":
        return Env.Fuchsia;
      case "browser":
        return Env.Browser;
      default:
        return Env.Unknown;
    }
  }
}

以上封装完成后,我们可以调用Environment.current方法来判断当前的运行平台,从而差异化处理代码

构建响应式UI

响应式应用程序会根据屏幕或窗口的大小和形状来布局其UI。这可以使应用程序在多种设备(如手表,手机,平板、笔记本电脑等)上运行。参见官方文档 创建响应式应用

Flutter中构建响应式UI,有两种基本方法:

class _MyHomePageState extends State<MyHomePage> {

  @override
  Widget build(BuildContext context) {
    return LayoutBuilder(
      builder: (context, constraints) {
        return Scaffold(
          appBar: AppBar(
            title: Text(widget.title),
          ),
          body: constraints.maxWidth < 480
              ? ContentView()
              : Row(
                  children: [
                    SizedBox(
                        width: 200.0,
                        child: MenuBar()
                    ),
                    Expanded(
                      child: ContentView(),
                    )
                  ],
                ),
          drawer: constraints.maxWidth < 480 ? Drawer(
            child:  MenuBar(),
          ) : null,
        );
      });
  }
}

/// 菜单栏
class MenuBar extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return ListView(
        children: [
          ListTile(
            leading: Icon(Icons.looks_one),
            title: Text("First Link"),
          ),
          ListTile(
            leading: Icon(Icons.looks_two),
            title: Text("Second Link"),
          )
        ]
    );
  }
}

/// 主视图
class ContentView extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return GridView.builder(
        itemCount: 100,
        gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent(
          maxCrossAxisExtent: 130.0,
          crossAxisSpacing: 10.0,
          mainAxisSpacing: 10.0,
        ),
        itemBuilder: (context, i) => Card(
            child: Center(
                child: Padding(
                    padding: EdgeInsets.all(8.0), child: Text("grid item $i")
                )
            )
        )
    );
  }
}

除了使用LayoutBuilder,也可以使用MediaQuery.of(context).size.width来获取屏幕的宽度信息,从而实现不同的布局。

另外,我们还可以借助OrientationBuilder控件来处理横竖屏切换的问题,其中orientation参数是一个枚举类型

OrientationBuilder(
  builder: (context, orientation) {
      return Container();
  });
  • Orientation.landscape 横屏(宽大于高)
  • Orientation.portrait 竖屏(高大于宽)

当app运行到PC上时,由于PC的屏幕通常是宽大于高,故而orientation参数的值为Orientation.landscape

当我们想要编写响应式UI,以兼容Web版时,可以根据当前屏幕信息自行实现差异化UI,也可借助第三方库,例如responsive_framework

使用NavigationRail

NavigationRail是一个侧边导航条,在Flutter 1.17 版本加入,使用前请升级SDK。与BottomNavigationBar不同,这个控件主要用于大屏设备的UI布局,如桌面程序、Web 或者平板设备等,而像手机这样的竖屏小屏设备,更适合使用 BottomNavigationBar 来实现导航栏。

手机上的效果

NavigationRail属性列表

属性 类型 简述
backgroundColor Color 背景色
extended bool 表示NavigationRail是否展开。minExtendedWidth可设置展开状态下的最小宽度。如果处于展开状态,那么标签类型必须为NavigationRailLabelType.none
leading Widget 位于NavigationRail上方的小控件
trailing Widget 位于NavigationRail下方的小控件
destinations List<NavigationRailDestination> 定义排列在导航栏中的导航条
selectedIndex int 当前选中的导航条索引
onDestinationSelected ValueChanged<int> 选择导航条时回调
elevation double z 轴方向上的高度
groupAlignment double 垂直方向上的对齐方式
labelType NavigationRailLabelType 为未展开的默认NavigationRail定义标签的布局和行为
unselectedLabelTextStyle TextStyle 标签未选中时的文字样式
selectedLabelTextStyle TextStyle 标签选中时的文字样式
unselectedIconTheme IconThemeData 图标未选中时的样式
selectedIconTheme IconThemeData 图标选中时的样式
minWidth double 最小宽度
minExtendedWidth double 展开时的最小宽度

示例

class _MyStatefulWidgetState extends State<MyStatefulWidget> {
  int _selectedIndex = 0;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Row(
        children: <Widget>[
          NavigationRail(
            selectedIndex: _selectedIndex,
            onDestinationSelected: (int index) {
              setState(() {
                _selectedIndex = index;
              });
            },
            labelType: NavigationRailLabelType.selected,
            destinations: [
              NavigationRailDestination(
                icon: Icon(Icons.favorite_border),
                selectedIcon: Icon(Icons.favorite),
                label: Text('First'),
              ),
              NavigationRailDestination(
                icon: Icon(Icons.bookmark_border),
                selectedIcon: Icon(Icons.book),
                label: Text('Second'),
              ),
              NavigationRailDestination(
                icon: Icon(Icons.star_border),
                selectedIcon: Icon(Icons.star),
                label: Text('Third'),
              ),
            ],
          ),
          VerticalDivider(thickness: 1, width: 1),
          // 主视图
          Expanded(
            child: Center(
              child: Text('selectedIndex: $_selectedIndex'),
            ),
          )
        ],
      ),
    );
  }
}

提示:如果要实现上图中的文字效果,可以使用旋转布局RotatedBox,对Text进行旋转

RotatedBox(
  quarterTurns: 1,
  child: const Text('Hello World!'),
)

RotatedBox是一个将其子控件旋转四分之一圈的整数倍的布局控件(quarterTurns: 1表示顺时针旋转90度)。与Transform不同的是,Transform在绘制时进行变换,而该控件在布局之前就进行了旋转,这意味着整个旋转后的框体只占用旋转后的子控件所需的空间。

Web 开发案例

简单博客管理系统。共四大主页面,一个详情页面

  • 首页
  • 文章管理
    • 查看
  • 分类管理
  • 写博客

注意,目前Web版使用多行输入框时,存在鼠标光标无法定位的问题,需要开启基于画布的文本测量功能,见issues 33523

添加编译命令--release --dart-define=FLUTTER_WEB_USE_EXPERIMENTAL_CANVAS_TEXT=true

关于Flutter中的图表库

  • charts_flutter 用Dart原生编写的Material Design数据可视化库,由Google提供

  • fl_chart 一个更炫酷的数据可视化库

  • syncfusion_flutter_charts 用Dart原生编写的数据可视化库,用于创建美观,高性能的图表。但这是一个收费的库!

关于Flutter的Markdown支持

Web 版路径处理

使用Flutter开发web版时,有时候需要通过浏览器URL路径来跳转相关的路由页面,这时候就需要实现onGenerateRoute方法来处理路由问题。另一方面,当需要实现超链接跳转时,还需要使用官方提供的url_launcher


公众号“编程之路从0到1”

20190301102949549

Copyright © Arcticfox 2020 all right reserved,powered by Gitbook文档修订于: 2022-05-01 12:00:54

results matching ""

    No results matching ""