如何使用Flutter web项目,并使其成为响应式?

2020年8月25日14:10:21 发表评论 294 views

如何开始使用Flutter web项目,并使其成为响应式的。

Flutter网页架构

在开始项目之前,我们先来看看Flutter web的架构。

如果你对移动应用中使用的Flutter的架构不熟悉,这里简单介绍一下。

如何使用Flutter web项目,并使其成为响应式?

Flutter在移动应用中的架构主要包括以下三层。

  1. 框架:这一层是纯用Dart编写的,由Flutter的核心构件组成。
  2. 引擎:下面这一层主要是用C/C++编写的,使用Google的Skia图形库提供低级渲染支持。
  3. Embedder:这一层基本上由所有平台特定的依赖关系组成。

现在,我们来看看Flutter web的架构,以及它与此的不同之处。

如何使用Flutter web项目,并使其成为响应式?

Flutter web的架构只用两层来描述。

  1. 框架:由纯Dart代码组成。
  2. 浏览器:由C++和JavaScript代码组成。

正如你所看到的,最顶层(Framework)包含的组件类型与常规的Flutter架构几乎相同。

主要的区别是在Browser层。实际上,在移动架构中存在的最底层的两个独立层只被一层所取代。它没有使用Skia图形引擎(因为浏览器上不支持),而是使用了一个JavaScript引擎。Flutter web涉及到将Dart编译成JavaScript,而不是用于移动应用的ARM机器代码。它使用DOM、Canvas和CSS的组合,在浏览器中渲染Flutter组件。

入门

正如我在开头提到的,Flutter web目前处于测试阶段。因此,为了创建一个支持web的Flutter应用,你需要进入Flutter的测试频道。

要运行和调试一个Flutter web应用程序,你需要Chrome

按照以下步骤创建一个新的Flutter web项目:

  1. 转到测试版频道:
flutter channel beta
复制代码
  1. 升级Flutter:
flutter upgrade
复制代码
  1. 启用Web支持:
flutter config --enable-web
复制代码
  1. 创建一个新的Flutter项目:
flutter create explore
复制代码

在这里,explorer是我要创建的Flutter Web应用的名字。

  1. 用你喜欢的IDE打开项目。

要使用VS Code打开它,可以使用这个命令。

code explore
复制代码

现在,如果你看一下目录结构,你会注意到有一个叫web的文件夹。

如何使用Flutter web项目,并使其成为响应式?

这意味着您的项目已正确配置为在浏览器上运行。另外,你会看到Chrome是可以作为运行Flutter应用的设备之一的。

您将获得与起始项目相同的Counter应用程序。要从VS Code中运行它,您可以使用F5,或者您可以从终端使用此命令:

flutter run -d chrome
复制代码

目前的状态是这样的。

如何使用Flutter web项目,并使其成为响应式?

有了启动项目,让我们开始构建我们的网络应用。

网页界面设计

网页界面设计 我们要创建的网页界面是受Tubik在Dribbble上的例子启发。

cdn.dribbble.com/users/41818…

转到lib > main.dart,然后用下面的代码替换整个代码。

// main.dart

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Explore',
      theme: ThemeData(
        visualDensity: VisualDensity.adaptivePlatformDensity,
      ),
      home: HomePage(),
    );
  }
}

class HomePage extends StatefulWidget {
  @override
  _HomePageState createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  @override
  Widget build(BuildContext context) {
    var screenSize = MediaQuery.of(context).size;

    return Scaffold();
  }
}
复制代码

现在,我们必须定义HomePage小部件,它将包含应用程序的用户界面。你马上就会明白为什么我把它定义为一个有状态的小部件。

你可能已经注意到,我已经使用MediaQuery来获取屏幕的大小。我这样做是因为我将根据屏幕的大小来调整widget的大小。这将使小组件具有响应性,并将防止在调整浏览器窗口大小时出现大部分的溢出问题。

让我们添加一个顶部栏,看起来像这样。

" data-data-original="https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/79109f6159254f62b28a98698b92d94d~tplv-k3u1fbpfcp-zoom-1.image" src="http://www.cainiaoxueyuan.com/wp-content/themes/begin/img/blank.gif" alt="如何使用Flutter web项目,并使其成为响应式?" data-width="800" data-height="600" />

要做到这一点,你将需要以下代码。

class _HomePageState extends State<HomePage> {
  @override
  Widget build(BuildContext context) {
    var screenSize = MediaQuery.of(context).size;

    return Scaffold(
      appBar: PreferredSize(
        preferredSize: Size(screenSize.width, 1000),
        child: Container(
          color: Colors.blue,
          child: Padding(
            padding: EdgeInsets.all(20),
            child: Row(
              children: [
                Text('EXPLORE'),
                Expanded(
                  child: Row(
                    mainAxisAlignment: MainAxisAlignment.center,
                    children: [
                      InkWell(
                        onTap: () {},
                        child: Text(
                          'Discover',
                          style: TextStyle(color: Colors.black),
                        ),
                      ),
                      SizedBox(width: screenSize.width / 20),
                      InkWell(
                        onTap: () {},
                        child: Text(
                          'Contact Us',
                          style: TextStyle(color: Colors.black),
                        ),
                      ),
                    ],
                  ),
                ),
                InkWell(
                  onTap: () {},
                  child: Text(
                    'Sign Up',
                    style: TextStyle(color: Colors.black),
                  ),
                ),
                SizedBox(
                  width: screenSize.width / 50,
                ),
                InkWell(
                  onTap: () {},
                  child: Text(
                    'Login',
                    style: TextStyle(color: Colors.black),
                  ),
                ),
              ],
            ),
          ),
        ),
      ),
      body: Container(),
    );
  }
}
复制代码

你会马上发现不对劲,感觉不像是一个web UI。悬停效果在哪里?

是的,Flutter组件默认是没有悬浮效果的。但我会告诉你最简单的实现方法。只是让你知道,添加了悬停效果后,会是这样的。

如何使用Flutter web项目,并使其成为响应式?

InkWell() widget有一个叫做onHover的属性,你可以用它来跟踪鼠标指针进入或离开组件边界的时间。

按照下面的步骤来获得这个效果。

  • 添加一个用于跟踪悬停的booleans列表(booleans的数量是你想应用悬停效果的组件数量)。
List _isHovering = [false, false, false, false];
复制代码
  • 更新组件对应的布尔值,并设置文本颜色(或根据该布尔值进行任何其他你想在悬停时显示的变化)。
InkWell(
  onHover: (value) {
    setState(() {
      _isHovering[0] = value;
    });
  },
  child: Column(
    mainAxisSize: MainAxisSize.min,
    children: [
      Text(
        'Discover',
        style: TextStyle(
          color: _isHovering[0]
              ? Colors.blue[100]
              : Colors.white,
        ),
      ),
      SizedBox(height: 5),
      // For showing an underline on hover
      Visibility(
        maintainAnimation: true,
        maintainState: true,
        maintainSize: true,
        visible: _isHovering[0],
        child: Container(
          height: 2,
          width: 20,
          color: Colors.white,
        ),
      )
    ],
  ),
),
复制代码

如果我们在Scaffoldbody内添加图像,它将从顶栏下方开始。但根据设计,图像应该在顶栏下方流动。

如何使用Flutter web项目,并使其成为响应式?

要实现这个设计其实很简单,你只需要将图片从顶栏的下方开始。

实现这样的设计其实很简单,你只需要定义Scaffold的一个名为extendBodyBehindAppBar的属性,并将其设置为true

Scaffold(
  extendBodyBehindAppBar: true,
  appBar: PreferredSize(
    // ...
  ),

  // ...
);
复制代码

您可能也注意到了,我在图片顶部的底部中心位置添加了一个快速访问栏。

要得到这样的UI,可以在Scaffoldbody中使用Stack widget。

Scaffold(
  // ...
  body: Stack(
    children: [
      Container( //图片在顶栏下方
        child: SizedBox(
          height: screenSize.height * 0.45,
          width: screenSize.width,
          child: Image.asset(
            'assets/images/cover.jpg',
            fit: BoxFit.cover,
          ),
        ),
      ),
      Center(
        heightFactor: 1,
        child: Padding(
          padding: EdgeInsets.only(
            top: screenSize.height * 0.40,
            left: screenSize.width / 5,
            right: screenSize.width / 5,
          ),
          child: Card( //浮动的快速访问栏
              // ...
          ),
        ),
      )
    ],
  ),
);
复制代码

我只是包括解释UI结构的代码。这一部分的完整UI代码可以在这里找到。本文结尾处有整个项目的链接。

现在我们将在网页中再添加一些UI元素。首先,将整个脚手架的body包裹在一个SingleChildScrollView小部件中,使其可以滚动。

我们将保持这个网站的简单。所以,我们只增加三个部分。

  1. 精选
  2. 目的地
  3. 底部信息

推荐模块

这一部分将包含一个标题和一排三张图片及其标签。它看起来像这样。

如何使用Flutter web项目,并使其成为响应式?

标题和描述的UI代码如下:

Row(
  mainAxisSize: MainAxisSize.max,
  mainAxisAlignment: MainAxisAlignment.start,
  children: [
    Text(
      'Featured',
      style: GoogleFonts.montserrat(
        fontSize: 40,
        fontWeight: FontWeight.w500,
      ),
    ),
    Expanded(
      child: Text(
        'Unique wildlife tours & destinations',
        textAlign: TextAlign.end,
      ),
    ),
  ],
),
复制代码

每张图片及其标签的代码如下。

Column(
  children: [
    SizedBox(
      height: screenSize.width / 6,
      width: screenSize.width / 3.8,
      child: ClipRRect(
        borderRadius: BorderRadius.circular(5.0),
        child: Image.asset(
          'assets/images/trekking.jpg',
          fit: BoxFit.cover,
        ),
      ),
    ),
    Padding(
      padding: EdgeInsets.only(
        top: screenSize.height / 70,
      ),
      child: Text(
        'Trekking',
        style: GoogleFonts.montserrat(
          fontSize: 16,
          fontWeight: FontWeight.w500,
        ),
      ),
    ),
  ],
),
复制代码

如果你现在尝试调整浏览器窗口的大小,你会发现它的反应已经相当灵敏。

如何使用Flutter web项目,并使其成为响应式?

目的地模块

在本节中,我们将添加一个图片轮播,其中有一个浮动的潜在目的地选择器,称为 "目的地多样性"。它看起来像这样。

如何使用Flutter web项目,并使其成为响应式?

对于轮播图,你可以使用名为carousel_slider的Flutter包。

按照下面的步骤来构建轮播:

  • 定义一个图像列表和它们的标签。
final List<String> images = [
  'assets/images/asia.jpg',
  'assets/images/africa.jpg',
  'assets/images/europe.jpg',
  'assets/images/south_america.jpg',
  'assets/images/australia.jpg',
  'assets/images/antarctica.jpg',
];

final List<String> places = [
  'ASIA',
  'AFRICA',
  'EUROPE',
  'SOUTH AMERICA',
  'AUSTRALIA',
  'ANTARCTICA',
];
复制代码
  • 生成一个Widget列表,以显示在旋转木马中。
List<Widget> generateImageTiles(screenSize) {
  return images
      .map(
        (element) => ClipRRect(
          borderRadius: BorderRadius.circular(8.0),
          child: Image.asset(
            element,
            fit: BoxFit.cover,
          ),
        ),
      )
      .toList();
}
复制代码
  • build方法中存储widget的列表,并在旋转木马中显示它们各自的标签。
@override
Widget build(BuildContext context) {
  var screenSize = MediaQuery.of(context).size;
  var imageSliders = generateImageTiles(screenSize);

  return Stack(
    children: [
      CarouselSlider(
        items: imageSliders,
        options: CarouselOptions(
            enlargeCenterPage: true,
            aspectRatio: 18 / 8,
            autoPlay: true,
            onPageChanged: (index, reason) {
              setState(() {
                _current = index;
              });
            }),
        carouselController: _controller,
      ),
      AspectRatio(
        aspectRatio: 18 / 8,
        child: Center(
          child: Text(
            places[_current],
            style: GoogleFonts.electrolize(
              letterSpacing: 8,
              fontSize: screenSize.width / 25,
              color: Colors.white,
            ),
          ),
        ),
      ),
    ],
  );
}
复制代码
如何使用Flutter web项目,并使其成为响应式?

如果你运行该应用程序,旋转木马将看起来像这样。

如何使用Flutter web项目,并使其成为响应式?

要添加浮动选择器,请按照以下步骤进行:

  • 增加两个布尔值列表。
List _isHovering = [false, false, false, false, false, false, false];
List _isSelected = [true, false, false, false, false, false, false];
复制代码
  • 修改CarouselOptions widget的onPageChanged属性。
CarouselOptions(
  // ...
  onPageChanged: (index, reason) {
    setState(() {
      _current = index;

      // add the following
      for (int i = 0; i < imageSliders.length; i++) {
        if (i == index) {
          _isSelected[i] = true;
        } else {
          _isSelected[i] = false;
        }
      }

    });
  },
)
复制代码
  • 在包含rext的Card内显示一行小部件,并显示一个下划线来突出显示被选中的选项。荧光笔可以这样创建。
Visibility(
  maintainSize: true,
  maintainAnimation: true,
  maintainState: true,
  visible: _isSelected[i],
  child: Container(
    height: 5,
    decoration: BoxDecoration(
      color: Colors.blueGrey,
      borderRadius: BorderRadius.all(
        Radius.circular(10),
      ),
    ),
    width: screenSize.width / 10,
  ),
)
复制代码

浮动选择器会是这样的:

如何使用Flutter web项目,并使其成为响应式?

底部信息部分

这只是一个简单的信息部分,会是这样的。

如何使用Flutter web项目,并使其成为响应式?

这一部分的UI代码在这里

提高响应速度

虽然网页应用的响应速度相当快,但你还是会发现一些溢出。另外,UI设计也不方便移动设备的小屏幕。为了解决这个问题,我们将使用响应式布局,根据设备屏幕大小来构建和调整widget的大小。

我们将为smallScreenmediumScreenlargeScreen设置一些断点,当达到该断点时,用新的布局重建widget。你可以使用下面的代码来实现这一点。

import 'package:flutter/material.dart';

class ResponsiveWidget extends StatelessWidget {
  final Widget largeScreen;
  final Widget mediumScreen;
  final Widget smallScreen;

  const ResponsiveWidget(
      {Key key,
      @required this.largeScreen,
      this.mediumScreen,
      this.smallScreen})
      : super(key: key);

  static bool isSmallScreen(BuildContext context) {
    return MediaQuery.of(context).size.width < 800;
  }

  static bool isLargeScreen(BuildContext context) {
    return MediaQuery.of(context).size.width > 1200;
  }

  static bool isMediumScreen(BuildContext context) {
    return MediaQuery.of(context).size.width >= 800 &&
        MediaQuery.of(context).size.width <= 1200;
  }

  @override
  Widget build(BuildContext context) {
    return LayoutBuilder(
      builder: (context, constraints) {
        if (constraints.maxWidth > 1200) {
          return largeScreen;
        } else if (constraints.maxWidth <= 1200 &&
            constraints.maxWidth >= 800) {
          return mediumScreen ?? largeScreen;
        } else {
          return smallScreen ?? largeScreen;
        }
      },
    );
  }
}
复制代码

现在您可以为不同的屏幕尺寸定义不同的小组件布局。对于较小的屏幕,最好是显示一个带有顶部栏选项的drawer。为此,我们将使用不同的AppBar widget。如果我们在Scaffold的drawer属性中定义了汉堡包图标,那么汉堡包图标将默认在应用程序栏中可见。

Scaffold(
  appBar: ResponsiveWidget.isSmallScreen(context)
      ? AppBar( // for smaller screen sizes
          backgroundColor: Colors.transparent,
          elevation: 0,
          title: Text(
            'EXPLORE',
            style: TextStyle(
              color: Colors.blueGrey[100],
              fontSize: 20,
              fontFamily: 'Montserrat',
              fontWeight: FontWeight.w400,
              letterSpacing: 3,
            ),
          ),
        )
      : PreferredSize( // for larger & medium screen sizes
          preferredSize: Size(screenSize.width, 1000),
          child: TopBarContents(_opacity),
        ),
  drawer: ExploreDrawer(), // The drawer widget

  // ...

);
复制代码

抽屉的UI将是这样的。

如何使用Flutter web项目,并使其成为响应式?

抽屉的UI代码在这里

通过这种方式,你可以为不同的屏幕尺寸定义不同的布局。

最终的版本在桌面和移动浏览器上会是这个样子。

如何使用Flutter web项目,并使其成为响应式?

结束语

希望这篇文章能帮助你开始使用Flutter web。在Flutter web系列的下一部分,我们将尝试通过添加一些动画和主题来改进这个UI。

有用的链接和参考资料


Souvik Biswas是一个充满激情的移动应用开发者(Android和Flutter)。他在整个旅程中参与了大量的移动应用。喜欢开源贡献到GitHub。他目前正在印度信息技术学院Kalyani攻读计算机科学与工程专业的技术学士学位。他还在Flutter Community上写Flutter文章。

作者:Sunbreak
链接:https://juejin.im/post/6855532468121190408
来源:掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

发表评论

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: