
一个好的应用程序的用户界面是将 用户和其功能之间的摩擦降到最低。减少这种摩擦的一个很好的方法是突出和展示你的应用程序中对其可用性不可或缺的部分。当用户第一次启动你的应用程序或当你用一些新功能更新应用程序时,这一点尤其方便。
Showcaseview是一个可定制的、易于实现的软件包,您可以用它来向您的用户展示您的Flutter应用程序的最重要的功能。
完成的应用程序
完成后的应用程序的外观和行为与下面视频中的一样。当您运行该应用程序时,展示将在主页上立即开始。它也可以通过点击应用栏中的信息按钮来启动。
该展示区将突出我们的应用程序的最重要的功能。当用户点击屏幕时,我们作为展示区一部分的小工具将以预定义的顺序呈现。这种类型的行为也延伸到设置页面,它将有自己的展示区。
当展示区突出显示主页中的设置按钮时,我们将能够点击该按钮并被带到设置页面,在那里第二个展示区将开始。一旦设置页面上的展示结束,我们就可以回到主页,从我们离开的地方继续之前的展示。
入门
在本教程中,我们将讨论showcaseview包的细节问题。我们将进入所有的设置细节,探索不同的定制选项,学习如何设置多页展示功能等等。由于我们想把主要精力放在实现该包上,我们将从一个基本的应用程序用户界面开始。如果你想跟着学习,你可以从下面的链接中获取启动项目。
让我们首先在pubspec.yaml文件中添加软件包的依赖性。在写这个教程的时候,版本是1.1.0,它已经迁移到了null-safety,所以你可以在最新版本的Flutter中使用它。
pubspec.yaml
dependencies:
flutter:
sdk: flutter
showcaseview: ^1.1.0
初始项目概述
我们要做的启动项目是一个水摄入量跟踪应用程序的用户界面。该应用由一个HomePage 和一个SettingsPage 。
HomePage 显示一个AppBar ,上面有一个领先的帮助按钮和一个设置动作按钮,可以带你到SettingsPage 。主体有一个Column ,包含CupsNumberDisplay 和CupsGoal 小部件。此外,这个页面的右下角包含一个FloatingActionButton 。
SettingsPage 比较简单,只有一个Column ,其中有一个SettingsControls 小工具和一些文字。由于我们只专注于展示我们的UI元素,该应用程序的功能仅限于从HomePage 到SettingsPage 的路由。

ShowCaseWidget
在我们实现单个部件的展示之前,我们需要用一个ShowCaseWidget 来包装我们将展示的页面。我们必须设置所需的 builder 参数,它将包含返回我们的Builder 部件HomePage 。让我们到main.dart文件中,在MaterialApp 的参数中实现现在的home 。
main.dart
return MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Water Tracker',
home: ShowCaseWidget(
builder: Builder(
builder: (context) => const HomePage(),
),
),
);
ShowCaseWidget可选参数
ShowCaseWidget 包含几个可选参数。最值得注意的是你可以为 , 和 参数提供回调函数。onStart onFinish onComplete
onStart 函数将在你的展示柜开始 时执行,onFinish 将在所有展示柜结束时执行。onComplete 将在你每次从展示柜中的一个部件移到下一个部件时运行。
还有一个参数autoPlay ,如果你想自动通过展示柜,而不需要用户点击屏幕的话,可以将其设置为true 。我们不会在本教程中配置上述参数,但熟悉一下这些参数是值得的。
创建全局键
为了让ShowCaseWidget 知道我们想要展示哪些小部件,我们需要为每一个小部件创建一个键。在我们的应用程序中,有三个小部件我们希望在用户到达HomePage 。现在让我们在build 方法的正上方创建三个 GlobalKey 对象,HomePageState 。
home_page.dart
class _HomePageState extends State<HomePage> {
final _key1 = GlobalKey();
final _key2 = GlobalKey();
final _key3 = GlobalKey();
...
展示小工具
现在终于到了设置我们的Widget的时候了!Showcase widget,不要和ShowCaseWidget 混淆,它可以包裹你想包含在展示区的每一个widget。让我们先在AppBar ,在我们的设置图标周围添加一个。
主页.dart
actions: [
IconButton(
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (_) => SettingsPage(),
),
);
},
icon: Showcase(
key: _key1,
description: 'Change your water intake goal settings',
shapeBorder: const CircleBorder(),
showcaseBackgroundColor: Colors.indigo,
descTextStyle: const TextStyle(
fontWeight: FontWeight.w500,
color: Colors.white,
fontSize: 16,
),
overlayPadding: const EdgeInsets.all(8),
contentPadding: const EdgeInsets.all(20),
child: const Icon(Icons.settings),
),
),
],
...
关于Showcase widget的配置,有几件事我们应该注意。key,description 和child 参数是必须的,所以我们必须像这里一样提供它们。我们在这里使用之前初始化的_key1 ,因为我们想让它成为展示区中显示的第一个小部件。对于造型,我们要为shapeBorder (突出子的形状)、showcaseBackgroundColor (工具提示的背景颜色)、descTextStyle ,并为覆盖层和工具提示的内容设置填充。
创建第二个Showcase小组件
在我们看到这一切是什么样子的行动之前,让我们在我们的代码中再添加一个Showcase widget。来吧,把它放在位于我们的body 的HomePage 的Column 周围。对于这一个,我们也将添加一个标题。
home_page.dart
body: Center(
child: SingleChildScrollView(
child: Showcase(
key: _key2,
title: 'Total & Goal Water Intake',
description: 'Track your total and goal water intake',
showArrow: false,
overlayPadding: const EdgeInsets.all(8),
showcaseBackgroundColor: Colors.indigo,
textColor: Colors.white,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
CupsNumberDisplay(
size: 200,
),
const SizedBox(
height: 60,
),
CupsGoal(),
],
),
),
),
),
...
开始展示
我们的ShowCaseWidget 和两个 Showcase widgets已经完全配置好了,但是当我们运行应用程序时,什么也没有发生。这是因为我们需要告诉ShowCaseWidget 何时启动展示区。让我们来探索两种可能的方式--通过点击AppBar 中的信息按钮和在页面构建时。
你也可以设置一种方式,只有当用户第一次运行你的应用程序时才启动展示区。一种方法是通过使用shared_preferences包来存储一个值,你可以检查用户是否曾经使用过你的应用程序。这样你就可以有条件地 启动你的展示柜。
在点击按钮时启动展示柜
转到AppBar 中的infoIconButton ,在按钮的onPressed 参数中加入以下代码。
home_page.dart
return Scaffold(
appBar: AppBar(
leading: IconButton(
onPressed: () => setState(() {
ShowCaseWidget.of(context)!.startShowCase([
_key1,
_key2,
_key3,
]);
}),
icon: const Icon(
Icons.help_rounded,
),
),
...
在这里,我们正在设置状态,并在我们之前配置的ShowCaseWidget ,调用startShowCase 方法。我们需要向startShowCase 方法传入先前创建的全局键的List 。这些键需要按照相应的小工具在展示过程中出现的顺序。尽管我们只设置了两个widget,但我们在这里提供了所有的三个key。第三个小组件将在本教程的后面进行配置。
在构建页面时启动展示区
为了在页面构建时开始展示,我们将需要在HomePage 的initState 内调用startShowCase 方法。然而,请注意,仅仅像我们在按钮中那样调用这个方法会产生一个错误。为了防止这种情况发生,我们需要把这个方法的调用放在一个回调函数里面,并把它提供给WidgetsBinding.instance!.addPostFrameCallback() 。这将确保在构建过程中一切都能正确执行。现在,如果你运行该应用程序,我们美丽的展示会将在页面构建后立即开始。点击它,直到展示完毕。然后你可以点击帮助按钮,它就会重新开始。
home_page.dart
@override
void initState() {
super.initState();
WidgetsBinding.instance!.addPostFrameCallback(
(_) => ShowCaseWidget.of(context)!.startShowCase(
[
_key1,
_key2,
_key3,
],
),
);
}
Showcase.withWidget - 创建完全自定义的展示柜
showcaseview包使得创建Showcase widget变得非常容易,它可以在你的目标widget旁边显示一个优雅的工具提示。我们也可以通过使用Showcase.withWidget 构造函数,用我们自己的、自定义的小部件来替换工具提示。让我们为我们的FloatingActionButton ,这将是我们在HomePage 的展示区的最后一个部件。
home_page.dart
floatingActionButton: Showcase.withWidget(
key: _key3,
height: 50,
width: 50,
container: Icon(
Icons.local_drink,
size: 50,
color: Colors.blue[200],
),
shapeBorder: const CircleBorder(),
overlayPadding: const EdgeInsets.all(8),
child: FloatingActionButton(
onPressed: () {},
backgroundColor: Colors.indigo[900],
child: const Icon(Icons.add),
),
),
...
Showcase.withWidget 有一个 参数,接受一个 。对于我们的项目,我们正在添加一个简单的图标,但你可以更有创意。我们还container Widget需要 添加 和 属性,这将影响 widget的显示方式。height width container
onTargetClick
让我们深入了解一下Showcase 小组件的一些额外功能。目前,如果我们的展示柜正在运行,并且活动的部件被点击,什么也不会发生。这个行为可以通过配置onTargetClick 参数进行修改。当设置按钮被点击时,我们希望被带到SettingsPage ,即使展示柜目前正在运行。
当使用onTargetClick ,我们需要 在Showcase 小组件上设置disposeOnTap 参数。它接受一个boolean 的值,这个值决定了当目标部件被点击时,当前的展示柜是被处理掉 还是 继续。由于我们希望在小部件被点击时被导航到SettingsPage ,我们将把disposeOnTap 设置为true 。如果设置为false ,在这种情况下,即使在新的路线上,展示柜也会继续,这绝对不是 我们想要的。现在让我们去实现所有这些。
home_page.dart
...
icon: Showcase(
key: _key1,
description: 'Change your water intake goal settings',
disposeOnTap: true,
onTargetClick: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (_) => SettingsPage(),
),
);
},
...
设置页面展示
现在是时候去SettingsPage ,为该路由实现展示。首先,让我们创建ShowCaseWidget ,从Builder widget返回Scaffold 。这个页面上将只有一个 Showcase ,所以我们现在也可以为它创建GlobalKey 。
设置_page.dart
@override
Widget build(BuildContext context) {
return ShowCaseWidget(
builder: Builder(
builder: (context) {
return Scaffold(...
展示区应该在页面建立时开始,类似于在HomePage 中的设置方式,但有一点不同。为了调用startShowCase 方法,我们需要通过位于我们的SettingsPage build 方法内的ShowCaseWidget 来访问它。为此,我们需要获得该ShowCaseWidget 的BuildContext 。为了访问这个上下文,我们将创建一个可忽略的 变量,myContext 。我们将在Builder widget的builder 函数中用ShowCaseWidget 的BuildContext 来填充它。一旦这样做了,就可以从initState 里面调用startShowCase 方法,方法和HomePage 一样。只要确保使用myContext 变量来代替context 。
settings_page.dart
class _SettingsPageState extends State<SettingsPage> {
final _key1 = GlobalKey();
BuildContext? myContext;
void initState() {
super.initState();
WidgetsBinding.instance!.addPostFrameCallback((_) {
ShowCaseWidget.of(myContext!)!.startShowCase([_key1]);
});
}
@override
Widget build(BuildContext context) {
return ShowCaseWidget(
builder: Builder(
builder: (context) {
myContext = context;
return Scaffold(...
添加SettingsPage展示区小部件
SettingsPage 是时候完成新的展示区了,用一个Showcase 来包装位于我们的body 中的SettingsControls widget。
setting_page.dart
body: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Showcase(
key: _key1,
title: 'Change Your Water Goal',
description:
'Increase or decrease the number of cups for your goal',
showcaseBackgroundColor: Colors.indigo,
textColor: Colors.white,
child: SettingsControls(),
),
const SizedBox(
height: 30,
),
Text(
'Adjust your water intake goal',
style: TextStyle(
fontWeight: FontWeight.w500,
color: Colors.grey[700],
fontSize: 16,
),
),
],
),
...
最后的改进

我们的应用程序中的两个展示柜都已经设置好了,而且功能齐全。然而,有几件事我们可以而且应该改进。
当我们在HomePage 展示中点击设置按钮时,我们会被引导到SettingsPage 。然而,当我们回到HomePage ,之前的展示并没有从我们离开的地方继续。此外,当我们进入SettingsPage ,展示开始时,路由动画仍在发生,这看起来有点奇怪。让我们现在去解决这些问题。
继续展示
我们想在从SettingsPage 路由返回后,从我们离开的地方继续 进行HomePage 展示。要做到这一点,我们需要在我们先前设置的onTargetClick 函数中增加一些代码。由于Navigator.push 返回一个Future ,我们可以使用.then 对其注册一个回调。从这个回调中,我们将在setState 中调用startShowCase ,这一次只提供其余小工具的键。
home_page.dart
...
icon: Showcase(
key: _key1,
description: 'Change your water intake goal settings',
disposeOnTap: true,
onTargetClick: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (_) => SettingsPage(),
),
).then(
(_) {
setState(
() {
ShowCaseWidget.of(context)!.startShowCase(
[
_key2,
_key3,
],
);
},
);
},
);
},
...
改进路由动画
现在只剩下一件事要做。那就是确保SettingsPage 展示只有在路由动画完成 后才开始。
我们可以通过将startShowCase 方法的调用放在Future.delayed 中来轻松实现。通过将展示的开始时间推迟一点,一切都会看起来更好。让我们就这样做,添加一个400毫秒的延迟Duration 。做完这些后,请测试一下,看看它看起来有多好!
settings_page.dart
class _SettingsPageState extends State<SettingsPage> {
final _key1 = GlobalKey();
BuildContext? myContext;
void initState() {
super.initState();
WidgetsBinding.instance!.addPostFrameCallback((_) {
Future.delayed(const Duration(milliseconds: 400), () {
ShowCaseWidget.of(myContext!)!.startShowCase([_key1]);
});
});
}
总结
你已经有了,一个功能齐全的展示柜,以各种方式定制了路由方案。把你在这里学到的东西应用到你自己的项目中。showcaseview包一定会让你的应用程序更容易被新用户学习和浏览。