如何使用 alertdialogs 创建入职 ui?
How to create onboarding ui with alertdialogs?
我正在构建一个应用程序,我希望它具有非常简单的 walk-through/on-boarding 体验,它会显示一些消息,指示屏幕上每个项目的功能。消息必须有下一个按钮,显示下一条消息并关闭前一条。
像这样:
我一直在寻找许多入门小部件,但其中 none 允许我展示我描述的内容,所以我想我可以通过在应用程序 UI 上使用 AlertDialog 来实现。
我希望它在屏幕加载后立即自动执行,所以我在 initstate 中有这个:
@override
void initState() {
WidgetsBinding.instance.addPostFrameCallback(_showOpenDialog);
}
_showOpenDialog(_) {
showDialog(
barrierDismissible: false,
context: context,
builder: (context) {
return keyboardDialog(context);
});
}
这会调用我为自定义消息创建的小部件,它基本上返回一个带有 texbutton 的 AlertDialog。这个按钮应该让我进入下一个警报对话框并关闭前一个,所以我虽然这可能有效但它不会在按下时做任何事情:
TextButton(
onPressed: () => {
Navigator.push(context,
MaterialPageRoute(builder: (context) {
return keyboardDialog(
displayHeight, displayWidth, context);
})),
Navigator.of(context).pop(),
},
我试着把 pop() 指令放在第一位:
TextButton(
onPressed: () => {
Navigator.of(context).pop(),
Navigator.push(context,
MaterialPageRoute(builder: (context) {
return keyboardDialog(
displayHeight, displayWidth, context);
})),
},
但实际情况是它会关闭警报对话框和背景布局并显示下一条对话框消息,如下所示:
¿我该怎么做才能实现所需的过渡?
--------更新----------------------------
我更改了传递给第二个 AlertDialog 的上下文的名称,但行为是一样的。
class Home extends StatefulWidget {
@override
_HomeState createState() => _HomeState();
}
class _HomeState extends State<Home> {
@override
void initState() {
WidgetsBinding.instance.addPostFrameCallback(_showOpenDialog);
}
_showOpenDialog(_) {
showDialog(
barrierDismissible: false,
context: context,
builder: (context) {
var displayHeight = MediaQuery.of(context).size.height;
var displayWidth = MediaQuery.of(context).size.width;
return micDialog(displayHeight, displayWidth, context);
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
body: Stack(
children: [
//capa inferior, donde se muestran los botones
Container(
child: Container(
alignment: Alignment.bottomCenter,
//color: Colors.white,
child: ButtonBar(
alignment: MainAxisAlignment.spaceEvenly,
children: [
IconButton(
icon: Icon(
Icons.settings_outlined,
color: Colors.black87,
),
onPressed: () {}),
Container(
padding: EdgeInsets.fromLTRB(0, 0, 0, 20),
child: micButton()),
IconButton(
icon: Icon(CupertinoIcons.keyboard), onPressed: () {})
],
),
),
),
//capa superior donde se muestrean los mensajes
Scaffold(
backgroundColor: Colors.transparent,
body: Center(
child: Text("Hi!, Test APP"),
),
),
],
),
);
}
Widget micButton() {
return ConstrainedBox(
constraints: BoxConstraints.tightFor(width: 70, height: 70),
child: ElevatedButton(
style: ElevatedButton.styleFrom(
primary: Colors.white,
shape: CircleBorder(),
),
onPressed: () {},
child: Icon(
CupertinoIcons.mic,
color: Colors.black87,
size: 40,
)),
);
}
Widget micDialog(displayHeight, displayWidth, context) {
return Stack(
children: [
Positioned(
top: displayHeight * 0.63,
width: 350,
left: displayWidth * 0.01,
child: AlertDialog(
backgroundColor: Colors.black,
content: Text("Mic Description ",
style: TextStyle(color: Colors.white)),
actions: [
TextButton(
onPressed: () => {
Navigator.of(context).pop(),
Navigator.push(context,
MaterialPageRoute(builder: (keyboardContext) {
return keyboardDialog(
displayHeight, displayWidth, keyboardContext);
})),
},
child: Text("NEXT", style: TextStyle(color: Colors.white))),
],
),
),
Positioned(
top: displayHeight * 0.77,
left: displayWidth * 0.375,
child: Icon(
Icons.arrow_drop_down,
size: 90,
),
)
],
);
}
Widget keyboardDialog(displayHeight, displayWidth, keyboardContext) {
return Stack(
children: [
Positioned(
top: displayHeight * 0.63,
width: 350,
left: displayWidth * 0.15,
child: AlertDialog(
backgroundColor: Colors.black,
content: Text("Keyboard Description",
style: TextStyle(color: Colors.white)),
actions: [
TextButton(
onPressed: () => Navigator.of(keyboardContext).pop(),
child: Text("NEXT", style: TextStyle(color: Colors.white))),
],
),
),
Positioned(
top: displayHeight * 0.77,
left: displayWidth * 0.68,
child: Icon(
Icons.arrow_drop_down,
size: 90,
),
)
],
);
}
}
您需要的是这样的东西,tutorial_coach_mark,它是演练而不是入门:
import 'package:flutter/material.dart';
import 'package:tutorial_coach_mark/tutorial_coach_mark.dart';
List<TargetFocus> targets = List();
@override
void initState() {
targets.add(
TargetFocus(
identify: "Target 1",
keyTarget: keyButton,
contents: [
TargetContent(
align: ContentAlign.bottom,
child: Container(
child:Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(
"Titulo lorem ipsum",
style: TextStyle(
fontWeight: FontWeight.bold,
color: Colors.white,
fontSize: 20.0
),
),
Padding(
padding: const EdgeInsets.only(top: 10.0),
child: Text("Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin pulvinar tortor eget maximus iaculis.",
style: TextStyle(
color: Colors.white
),),
)
],
),
)
)
]
)
);
targets.add(
TargetFocus(
identify: "Target 2",
keyTarget: keyButton4,
contents: [
TargetContent(
align: ContentAlign.left,
child: Container(
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(
"Multiples content",
style: TextStyle(
fontWeight: FontWeight.bold,
color: Colors.white,
fontSize: 20.0
),
),
Padding(
padding: const EdgeInsets.only(top: 10.0),
child: Text("Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin pulvinar tortor eget maximus iaculis.",
style: TextStyle(
color: Colors.white
),),
)
],
),
)
),
TargetContent(
align: ContentAlign.top,
child: Container(
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(
"Multiples content",
style: TextStyle(
fontWeight: FontWeight.bold,
color: Colors.white,
fontSize: 20.0
),
),
Padding(
padding: const EdgeInsets.only(top: 10.0),
child: Text("Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin pulvinar tortor eget maximus iaculis.",
style: TextStyle(
color: Colors.white
),),
)
],
),
)
)
]
)
);
targets.add(
TargetFocus(
identify: "Target 3",
keyTarget: keyButton5,
contents: [
TargetContent(
align: ContentAlign.right,
child: Container(
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(
"Title lorem ipsum",
style: TextStyle(
fontWeight: FontWeight.bold,
color: Colors.white,
fontSize: 20.0
),
),
Padding(
padding: const EdgeInsets.only(top: 10.0),
child: Text("Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin pulvinar tortor eget maximus iaculis.",
style: TextStyle(
color: Colors.white
),),
)
],
),
)
)
]
)
);
}
void showTutorial() {
TutorialCoachMark(
context,
targets: targets, // List<TargetFocus>
colorShadow: Colors.red, // DEFAULT Colors.black
// alignSkip: Alignment.bottomRight,
// textSkip: "SKIP",
// paddingFocus: 10,
// opacityShadow: 0.8,
onClickTarget: (target){
print(target);
},
onClickOverlay: (target){
print(target);
},
onSkip: (){
print("skip");
},
onFinish: (){
print("finish");
},
)..show();
}
如果我有完整的代码,我可以尝试向您展示一个工作示例,但是,尽管如此,我认为问题在于您将哪个上下文传递给导航器。
让我解释一下:
TextButton(
onPressed: () => {
Navigator.push(context,
MaterialPageRoute(builder: (context) {
return keyboardDialog(
displayHeight, displayWidth, context);
})),
Navigator.of(context).pop(),
},
此处您正在推送对话框并立即弹出它,因此没有像您所说的那样进行更改。
现在在第二种情况下,弹出的上下文是按预期插入对话框的前一个页面路由。
我的建议是仔细检查您传递给导航器的上下文,注意将每个新的 BuildContext 调用为 context
,因为您可以隐藏其他变量,尝试使用上下文,然后是 keyboardDialogContext 等等。
上下文是小部件在树中给定位置的快照,如果您传递的位置高于创建第一个对话框的位置,它遇到的第一个 PageRoute 就是出现在背景。
编辑
TextButton(
onPressed: () => {
Navigator.of(context).pop(),
showDialog(
barrierDismissible: false,
context: context,
builder: (keyboardContext) {
return keyboardDialog(displayHeight, displayWidth, keyboardContext);
})
},
所以我能够重现该问题并认为它有点奇怪,经过一番思考后我意识到这是预期的行为。
问题出在您使用导航器推送新路线
Navigator.push(context,
MaterialPageRoute(builder: (keyboardContext) {
return keyboardDialog(
displayHeight, displayWidth, keyboardContext);
})),
所以屏幕变黑了,因为一个完整的新页面被推送了,所以如果你将 keyboardDialog 的背景变成红色,那么:
您可以看到使用对话框创建了一个空页面。
相反,您需要 showDialog
此方法创建一个叠加层,该叠加层位于您预期目的的先例路线之上。
我正在构建一个应用程序,我希望它具有非常简单的 walk-through/on-boarding 体验,它会显示一些消息,指示屏幕上每个项目的功能。消息必须有下一个按钮,显示下一条消息并关闭前一条。
像这样:
我一直在寻找许多入门小部件,但其中 none 允许我展示我描述的内容,所以我想我可以通过在应用程序 UI 上使用 AlertDialog 来实现。
我希望它在屏幕加载后立即自动执行,所以我在 initstate 中有这个:
@override
void initState() {
WidgetsBinding.instance.addPostFrameCallback(_showOpenDialog);
}
_showOpenDialog(_) {
showDialog(
barrierDismissible: false,
context: context,
builder: (context) {
return keyboardDialog(context);
});
}
这会调用我为自定义消息创建的小部件,它基本上返回一个带有 texbutton 的 AlertDialog。这个按钮应该让我进入下一个警报对话框并关闭前一个,所以我虽然这可能有效但它不会在按下时做任何事情:
TextButton(
onPressed: () => {
Navigator.push(context,
MaterialPageRoute(builder: (context) {
return keyboardDialog(
displayHeight, displayWidth, context);
})),
Navigator.of(context).pop(),
},
我试着把 pop() 指令放在第一位:
TextButton(
onPressed: () => {
Navigator.of(context).pop(),
Navigator.push(context,
MaterialPageRoute(builder: (context) {
return keyboardDialog(
displayHeight, displayWidth, context);
})),
},
但实际情况是它会关闭警报对话框和背景布局并显示下一条对话框消息,如下所示:
¿我该怎么做才能实现所需的过渡?
--------更新----------------------------
我更改了传递给第二个 AlertDialog 的上下文的名称,但行为是一样的。
class Home extends StatefulWidget {
@override
_HomeState createState() => _HomeState();
}
class _HomeState extends State<Home> {
@override
void initState() {
WidgetsBinding.instance.addPostFrameCallback(_showOpenDialog);
}
_showOpenDialog(_) {
showDialog(
barrierDismissible: false,
context: context,
builder: (context) {
var displayHeight = MediaQuery.of(context).size.height;
var displayWidth = MediaQuery.of(context).size.width;
return micDialog(displayHeight, displayWidth, context);
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
body: Stack(
children: [
//capa inferior, donde se muestran los botones
Container(
child: Container(
alignment: Alignment.bottomCenter,
//color: Colors.white,
child: ButtonBar(
alignment: MainAxisAlignment.spaceEvenly,
children: [
IconButton(
icon: Icon(
Icons.settings_outlined,
color: Colors.black87,
),
onPressed: () {}),
Container(
padding: EdgeInsets.fromLTRB(0, 0, 0, 20),
child: micButton()),
IconButton(
icon: Icon(CupertinoIcons.keyboard), onPressed: () {})
],
),
),
),
//capa superior donde se muestrean los mensajes
Scaffold(
backgroundColor: Colors.transparent,
body: Center(
child: Text("Hi!, Test APP"),
),
),
],
),
);
}
Widget micButton() {
return ConstrainedBox(
constraints: BoxConstraints.tightFor(width: 70, height: 70),
child: ElevatedButton(
style: ElevatedButton.styleFrom(
primary: Colors.white,
shape: CircleBorder(),
),
onPressed: () {},
child: Icon(
CupertinoIcons.mic,
color: Colors.black87,
size: 40,
)),
);
}
Widget micDialog(displayHeight, displayWidth, context) {
return Stack(
children: [
Positioned(
top: displayHeight * 0.63,
width: 350,
left: displayWidth * 0.01,
child: AlertDialog(
backgroundColor: Colors.black,
content: Text("Mic Description ",
style: TextStyle(color: Colors.white)),
actions: [
TextButton(
onPressed: () => {
Navigator.of(context).pop(),
Navigator.push(context,
MaterialPageRoute(builder: (keyboardContext) {
return keyboardDialog(
displayHeight, displayWidth, keyboardContext);
})),
},
child: Text("NEXT", style: TextStyle(color: Colors.white))),
],
),
),
Positioned(
top: displayHeight * 0.77,
left: displayWidth * 0.375,
child: Icon(
Icons.arrow_drop_down,
size: 90,
),
)
],
);
}
Widget keyboardDialog(displayHeight, displayWidth, keyboardContext) {
return Stack(
children: [
Positioned(
top: displayHeight * 0.63,
width: 350,
left: displayWidth * 0.15,
child: AlertDialog(
backgroundColor: Colors.black,
content: Text("Keyboard Description",
style: TextStyle(color: Colors.white)),
actions: [
TextButton(
onPressed: () => Navigator.of(keyboardContext).pop(),
child: Text("NEXT", style: TextStyle(color: Colors.white))),
],
),
),
Positioned(
top: displayHeight * 0.77,
left: displayWidth * 0.68,
child: Icon(
Icons.arrow_drop_down,
size: 90,
),
)
],
);
}
}
您需要的是这样的东西,tutorial_coach_mark,它是演练而不是入门:
import 'package:flutter/material.dart';
import 'package:tutorial_coach_mark/tutorial_coach_mark.dart';
List<TargetFocus> targets = List();
@override
void initState() {
targets.add(
TargetFocus(
identify: "Target 1",
keyTarget: keyButton,
contents: [
TargetContent(
align: ContentAlign.bottom,
child: Container(
child:Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(
"Titulo lorem ipsum",
style: TextStyle(
fontWeight: FontWeight.bold,
color: Colors.white,
fontSize: 20.0
),
),
Padding(
padding: const EdgeInsets.only(top: 10.0),
child: Text("Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin pulvinar tortor eget maximus iaculis.",
style: TextStyle(
color: Colors.white
),),
)
],
),
)
)
]
)
);
targets.add(
TargetFocus(
identify: "Target 2",
keyTarget: keyButton4,
contents: [
TargetContent(
align: ContentAlign.left,
child: Container(
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(
"Multiples content",
style: TextStyle(
fontWeight: FontWeight.bold,
color: Colors.white,
fontSize: 20.0
),
),
Padding(
padding: const EdgeInsets.only(top: 10.0),
child: Text("Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin pulvinar tortor eget maximus iaculis.",
style: TextStyle(
color: Colors.white
),),
)
],
),
)
),
TargetContent(
align: ContentAlign.top,
child: Container(
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(
"Multiples content",
style: TextStyle(
fontWeight: FontWeight.bold,
color: Colors.white,
fontSize: 20.0
),
),
Padding(
padding: const EdgeInsets.only(top: 10.0),
child: Text("Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin pulvinar tortor eget maximus iaculis.",
style: TextStyle(
color: Colors.white
),),
)
],
),
)
)
]
)
);
targets.add(
TargetFocus(
identify: "Target 3",
keyTarget: keyButton5,
contents: [
TargetContent(
align: ContentAlign.right,
child: Container(
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(
"Title lorem ipsum",
style: TextStyle(
fontWeight: FontWeight.bold,
color: Colors.white,
fontSize: 20.0
),
),
Padding(
padding: const EdgeInsets.only(top: 10.0),
child: Text("Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin pulvinar tortor eget maximus iaculis.",
style: TextStyle(
color: Colors.white
),),
)
],
),
)
)
]
)
);
}
void showTutorial() {
TutorialCoachMark(
context,
targets: targets, // List<TargetFocus>
colorShadow: Colors.red, // DEFAULT Colors.black
// alignSkip: Alignment.bottomRight,
// textSkip: "SKIP",
// paddingFocus: 10,
// opacityShadow: 0.8,
onClickTarget: (target){
print(target);
},
onClickOverlay: (target){
print(target);
},
onSkip: (){
print("skip");
},
onFinish: (){
print("finish");
},
)..show();
}
如果我有完整的代码,我可以尝试向您展示一个工作示例,但是,尽管如此,我认为问题在于您将哪个上下文传递给导航器。 让我解释一下:
TextButton(
onPressed: () => {
Navigator.push(context,
MaterialPageRoute(builder: (context) {
return keyboardDialog(
displayHeight, displayWidth, context);
})),
Navigator.of(context).pop(),
},
此处您正在推送对话框并立即弹出它,因此没有像您所说的那样进行更改。
现在在第二种情况下,弹出的上下文是按预期插入对话框的前一个页面路由。
我的建议是仔细检查您传递给导航器的上下文,注意将每个新的 BuildContext 调用为 context
,因为您可以隐藏其他变量,尝试使用上下文,然后是 keyboardDialogContext 等等。
上下文是小部件在树中给定位置的快照,如果您传递的位置高于创建第一个对话框的位置,它遇到的第一个 PageRoute 就是出现在背景。
编辑
TextButton(
onPressed: () => {
Navigator.of(context).pop(),
showDialog(
barrierDismissible: false,
context: context,
builder: (keyboardContext) {
return keyboardDialog(displayHeight, displayWidth, keyboardContext);
})
},
所以我能够重现该问题并认为它有点奇怪,经过一番思考后我意识到这是预期的行为。
问题出在您使用导航器推送新路线
Navigator.push(context,
MaterialPageRoute(builder: (keyboardContext) {
return keyboardDialog(
displayHeight, displayWidth, keyboardContext);
})),
所以屏幕变黑了,因为一个完整的新页面被推送了,所以如果你将 keyboardDialog 的背景变成红色,那么:
您可以看到使用对话框创建了一个空页面。
相反,您需要 showDialog
此方法创建一个叠加层,该叠加层位于您预期目的的先例路线之上。