Flutter 已被广泛采用,因为它可以灵活地构建应用程序,以使用一个代码库在您的 Android、iOS、macOS 和 Windows 机器上运行。 在大多数情况下,这些设备具有不同的尺寸,尽管 Flutter 具有跨平台功能,但您不能保证应用程序的界面默认情况下会在这些设备上按预期呈现。 这是您在开发过程中必须考虑的功能。
本文将演示如何使用 Flutter 创建自适应应用。 您将学习如何构建适应给定设备尺寸的基本电子商务应用程序,而无需随着屏幕尺寸的变化重新构建应用程序。 我们将介绍:
什么是自适应应用程序?
构建自适应应用程序时要考虑什么
使用颤振 LayoutBuilder创建自适应应用程序
项目概况
教程:使用 Flutter 创建自适应电子商务应用
配置
建设 Product小部件
构建导航抽屉
渲染手机屏幕
渲染桌面屏幕
实施 LayoutBuilder班级
当应用程序设计为在运行时根据特定参数更改其属性值以响应不同的输入或条件时,它被认为是自适应的。 例如,自适应应用程序使用相同的代码库在移动和桌面视图上呈现不同的界面。
重要的是要注意自适应设计不应与 响应式设计 相混淆。 响应式应用程序会根据可用的屏幕尺寸动态更改其界面布局,但会保持布局设计。 应用程序的内容只是重新排列以适应屏幕大小。
另一方面,自适应设计要求您创建特定于平台的设计、内容和模板。 也就是说,您的应用程序在移动设备上的设计在更大的屏幕(例如桌面设备)上会有所不同。
为了改善用户体验,您应该设计您的应用程序以适应具有不同设计、尺寸、形状和操作系统的不同设备(手机、可折叠设备、平板电脑、智能手表和 PC)。 您的用户应该能够在这些不同的设备上探索您的应用程序,并且仍然享受设备和操作系统的原生体验。
您在设计或开发阶段必须做出的一个重要决定是确定应用程序应该切换其布局的阈值。 在 Android 文档的一部分中, 有一组推荐值,可帮助您决定应用程序在不同屏幕尺寸上的布局。
的影响,屏幕的可用宽度可能比可用高度更重要 在大多数情况下,由于垂直滚动 。 您还需要考虑每个平台特有的鼠标、键盘、触摸输入和其他特性。 您应该自定义应用程序的体验和界面以适应主机设备。
Flutter 提供了 许多 可以在构建自适应应用程序时使用的小部件。 还有一些外部包,例如 Flutter 自适应 UI 和 自适应布局 ,您可以将它们集成到您的应用程序中。 在本文中,我们将重点介绍使用 Flutter 提供的 LayoutBuilder widget构建自适应应用程序。
这 LayoutBuilder类对于构建响应式和自适应应用程序很有用,因为它有一个在布局时调用的构建器函数。 此函数返回一个小部件,接收应用程序上下文和约束,并根据约束的值执行特定操作。 例如,当设备宽度满足通过 constraints.maxWidth财产:
超过 20 万开发人员使用 LogRocket 来创造更好的数字体验 了解更多 →
LayoutBuilder( builder: (ctx, constraints) { if (constraints.maxWidth < 600) { return widget; }else{ return anotherWidget; } } );
本教程的演示应用程序是一个电子商务应用程序,它在可滚动视图中显示其产品。 在移动设备上,每个产品都会占用可用的宽度,用户可以垂直滚动屏幕以查看每个可用的产品。 移动视图还将包含一个带有导航按钮的应用程序抽屉。
在更大的屏幕上,例如在桌面设备上,产品分布在屏幕的宽度和高度上。 桌面视图还将在屏幕顶部包含一个导航栏,以替换移动视图中的应用程序抽屉。
如前所述,应用程序将使用 Flutter LayoutBuilder class确保它在运行时根据可用的屏幕大小呈现指定的界面,而无需重新构建应用程序。
这是应用程序在不同屏幕尺寸上的预期输出:
基础知识 Flutter
上 Flutter SDK 安装在你的机器
您选择的任何合适的 IDE; 我将使用 Android Studio 进行此演示
打开您的终端并运行以下命令以创建引导的 Flutter 项目,然后在您首选的 IDE 中打开该项目。
flutter create shop_app
在里面 lib文件夹,创建两个包—— widgets和 screens— 将分别包含应用程序小部件和屏幕的 Dart 文件。
创建一个 dummy_data.dart文件中 lib文件夹,然后将 本教程的 GitHub 存储库中的内容 复制到该文件中。 您将使用此文件生成 Product后续部分中的小部件。
不要错过 The Replay 来自 LogRocket 的精选时事通讯
使用 React 的 useEffect 优化应用程序的性能
之间切换 在多个 Node 版本
了解如何 使用 AnimXYZ 为您的 React 应用程序制作动画
探索 Tauri ,一个用于构建二进制文件的新框架
比较 NestJS 与 Express.js
发现 TypeScript 领域中使用的流行 ORM
这 Product小部件代表应用程序上每个产品项目的模板。 在里面 widgets打包,创建一个 product.dart文件并添加以下代码:
import 'package:flutter/material.dart'; class Product extends StatelessWidget { final String id; final String name; final Color color; final String imageUrl; final double price; const Product({ required this.id, required this.name, this.color = Colors.amberAccent, required this.imageUrl, required this.price }); }
这里是 build小部件的方法:
@override Widget build(BuildContext context) { return InkWell( onTap: () => (){}, child: Card( shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(15) ), elevation: 4, margin: EdgeInsets.fromLTRB(15, 20, 15, 20), child: Column( children: [ Expanded( child: Stack( children: [ ClipRRect( borderRadius: const BorderRadius.only( topLeft: Radius.circular(15), topRight: Radius.circular(15)), child: Image.network( imageUrl, height: 250, width: double.infinity, fit: BoxFit.cover, ), ), Positioned( bottom: 20, right: 10, child: Container( color: Colors.black54, width: 300, padding: const EdgeInsets.symmetric(vertical: 5, horizontal: 20), child: Text(name, style: const TextStyle( fontSize: 18, color: Colors.white, fontWeight: FontWeight.bold ), softWrap: true, overflow: TextOverflow.fade, ), ), ) ], ), ), Padding( padding: const EdgeInsets.all(20.0), child: Row( children: [ const Icon(Icons.attach_money), const SizedBox(width: 6), Text('$price') ], ), ), ], ), ), ); }
上面的小部件使用了 Flutter 类和小部件的组合,包括:
InkWell 类 增强应用程序用户体验的
将 叠放的堆栈 小部件相互
的 Positioned小部件 将产品名称放置在特定位置
这是输出 Product小部件:
将 导航抽屉 专门用于应用程序的移动视图中; 也就是说,应用程序将在移动设备上打开时呈现抽屉。 内 widgets打包,创建一个名为的 Dart 文件 navigation_drawer并在文件中包含以下代码:
import 'package:flutter/material.dart'; class NavigationDrawer extends StatelessWidget { const NavigationDrawer({Key? key}) : super(key: key); Widget buildListTile(String title, IconData icon){ return ListTile( leading: Icon(icon, size: 26,), title: Text( title, style: const TextStyle( fontSize: 14, fontWeight: FontWeight.bold ), ), onTap: () {}, ); } @override Widget build(BuildContext context) { return Drawer( child: Column( children: [ Container( height: 100, padding: EdgeInsets.all(20), alignment: Alignment.bottomCenter, color: Theme.of(context).accentColor, child: Text('App Menu', style: TextStyle( fontWeight: FontWeight.w900, fontSize: 20, color: Theme.of(context).primaryColor, ), ), ), const SizedBox(height: 20), buildListTile('Home', Icons.home), buildListTile('About', Icons.info_outline), buildListTile('Contact', Icons.contact_phone_outlined), buildListTile('Order', Icons.shopping_cart), ], ), ); } }
从上面的代码中, buildListTile方法为抽屉中的特定项目构建一个 ListTile 小部件 ,在这种情况下, Home, About, Contact, 和 Order.
Next, create the screen to render the mobile view of the application. In the screens package, create a Dart file with the name mobile_product_view并在文件中包含以下代码片段:
import 'package:flutter/material.dart'; import 'package:shop_app/widgets/product.dart'; import '../dummy_data.dart'; import '../widgets/navigation_drawer.dart'; class MobileProductView extends StatelessWidget { final String title; const MobileProductView({Key? key, required this.title}) : super(key: key); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text(title), ), drawer: NavigationDrawer(), body: GridView.count( crossAxisCount: 1, children: DUMMY_DATA.map((product) => Product(id: product.id, name: product.name, imageUrl: product.imageUrl, color: product.color, price: product.price)).toList(), ) ); } }
上面的代码显示了应用程序使用来自 dummy_data.dart文件来建立一个列表 Product呈现为 GridView 的小部件。
本节将包含应用程序桌面视图的实现。 创建一个名称为 Dart 的文件 desktop_product_view在里面 screens包裹。 将以下代码添加到文件中:
import 'package:flutter/material.dart'; import 'package:shop_app/widgets/product.dart'; import '../dummy_data.dart'; class DesktopProductView extends StatelessWidget { final String title; final int crossAxisCount; const DesktopProductView({Key? key, required this.title, required this.crossAxisCount}) : super(key: key); @override Widget build(BuildContext context) { Widget getAction(String actionName){ return TextButton( onPressed: () {}, child: Text( actionName, style: TextStyle( color: Theme.of(context).accentColor, fontWeight: FontWeight.w500 ), ), ); } return Scaffold( appBar: AppBar( title: Text(title), actions: [ Padding( padding: const EdgeInsets.only(right: 100.0), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ getAction('Home'), getAction('About'), getAction('Contact'), getAction('Order'), ], ), ) ], ), body: Padding( padding: const EdgeInsets.all(18.0), child: GridView.count( crossAxisCount: crossAxisCount, children: DUMMY_DATA.map((product) => Product( id: product.id, name: product.name, imageUrl: product.imageUrl, color: product.color, price: product.price) ).toList(), ), ), ); } }
从上面的代码中,小部件接收到 crossAxisCount值通过它的构造函数。 使用此值,它指定的数量 Product要在其中呈现的小部件 GridView在运行时。
中的动作 AppBar小部件将替换您为移动视图创建的抽屉。 使用 getAction方法,您为 AppBar.
的实施 LayoutBuilder课程将在 main.dart文件。 这是因为我们将应用程序的整个屏幕设计为自适应的。 因此, LayoutBuilder应该从小部件的最外层构建界面,在这种情况下,它被放置在 main.dart文件。
这是您应该添加到 main.dart文件:
import 'package:flutter/material.dart'; import 'package:shop_app/screens/mobile_product_view.dart'; import 'screens/desktop_product_view.dart'; void main() { runApp(const MyApp()); } class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context) { return MaterialApp( title: 'Flutter Demo', theme: ThemeData( primarySwatch: Colors.purple, accentColor: Colors.amber ), home: const MyHomePage(title: 'Flutter adaptive app'), ); } } class MyHomePage extends StatefulWidget { const MyHomePage({super.key, required this.title}); final String title; @override StatecreateState() => _MyHomePageState(); } class _MyHomePageState extends State { @override Widget build(BuildContext context) { return LayoutBuilder( builder: (ctx, constraints){ if(constraints.maxWidth < 600){ return MobileProductView(title: widget.title); } int crossAxisCount = 0; if(constraints.maxWidth > 1400) { crossAxisCount = 4; } else if(constraints.maxWidth > 900) { crossAxisCount = 3; } else if (constraints.maxWidth >= 600) { crossAxisCount = 2; } return DesktopProductView(title: widget.title, crossAxisCount: crossAxisCount); } ); } }
这 build的方法 MyHomePage小部件使用 LayoutBuilder基于以下约束:
如果设备屏幕的宽度小于 600 像素,则构建 MobileProductView,它代表您在上一节中为移动设备创建的界面
如果设备屏幕的宽度大于 600 像素,则构建 DesktopProductView但与指定 crossAxisCount,取决于设备宽度,如代码所示
如果您打算只使应用程序的特定部分而不是如上所示的整个屏幕自适应,则必须实现 LayoutBuilder在小部件的所需部分。
使您的应用程序适应不同的操作系统和设备屏幕尺寸是改善其用户体验的好方法。 本教程演示了如何使用 Flutter 使您的应用程序自适应 LayoutBuilder班级。 其他选项,例如 自适应布局包。 如文章开头所述,在构建自适应应用程序时,您还可以采用
本教程中构建的项目可在 GitHub 上找到。 我还建议您浏览 Flutter 文档,以更广泛地了解如何在应用程序的不同部分构建自适应布局。