Tooltip 提示
https://api.flutter.dev/flutter/material/Tooltip-class.html
一个文本提示工具,帮助解释一个按钮或其他用户界面,当widget长时间按下时(当用户采取其他适当操作时)显示一个提示标签。
Tooltip({
Key key,
@required this.message,
this.height, //高度
this.padding,
this.margin,
this.verticalOffset, //垂直偏移
this.preferBelow,
this.excludeFromSemantics,
this.decoration,
this.textStyle,
this.waitDuration,
this.showDuration,
this.child,
})
示例
Tooltip(
message: '我是提示',
child: RaisedButton(
child: Text('按钮'),
onPressed: () {},
),
),
某些Material组件(如IconButton)已包含工具提示,直接使用即可。
IconButton(
icon: Icon(Icons.access_alarm),
onPressed: () {},
tooltip: '提示文案',
),
showDialog
系统内置的dialog,默认会占满屏幕。返回一个Future对象。
Future<T> showDialog<T>({
@required BuildContext context,
WidgetBuilder builder,
bool barrierDismissible = true, //点击遮罩,是否退出对话框
Color barrierColor,
bool useSafeArea = true,
bool useRootNavigator = true,
RouteSettings routeSettings,
})
import "package:flutter/material.dart";
class DialogDemo extends StatelessWidget {
_alertDialog(context) {
// print(context); //DialogDemo(state: _DialogDemoState#2b533)
showDialog(
context: context,
builder: (context) => Container(
height: 100,
color: Colors.red,
),
);
}
@override
Widget build(BuildContext context) {
return Container(
child: Column(
children: [
RaisedButton(
child: Text('alert弹出框-AlertDialog'),
onPressed: _alertDialog,
),
],
),
);
}
}
AlertDialog 确认框
https://docs.flutter.io/flutter/material/AlertDialog-class.html
一个会中断用户操作的对话款,需要用户确认。
AlertDialog({
Key key,
this.title, //标题
this.titlePadding,
this.titleTextStyle,
this.content, //内容
this.contentPadding = const EdgeInsets.fromLTRB(24.0, 20.0, 24.0, 24.0),
this.contentTextStyle,
this.actions, //底部按钮数组
this.actionsPadding = EdgeInsets.zero,
this.actionsOverflowDirection,
this.actionsOverflowButtonSpacing,
this.buttonPadding,
this.backgroundColor, //背景色
this.elevation,
this.semanticLabel,
this.insetPadding = _defaultInsetPadding,
this.clipBehavior = Clip.none,
this.shape, //可参考RaisedButton控件的shape参数
this.scrollable = false,
})
示例
写法1:
_alertDialog(){
showDialog(
context: context,
builder: (context) {
return AlertDialog(
title: Text('提示信息'),
content: Text('你确定要删除吗?'),
actions: [
FlatButton(
child: Text('取消'),
onPressed: () {
print('取消');
Navigator.pop(context); //关闭确认框
},
),
FlatButton(
child: Text('确定'),
onPressed: () {
print('确定');
Navigator.pop(context);
},
),
],
);
},
);
}
写法2:
_alertDialog() async {
var result = await showDialog(
context: context,
builder: (context) {
return AlertDialog(
title: Text('提示信息'),
content: Text('你确定要删除吗?'),
actions: [
FlatButton(
child: Text('取消'),
onPressed: () {
Navigator.pop(context, 'cancel');
},
),
FlatButton(
child: Text('确定'),
onPressed: () {
Navigator.pop(context, 'ok');
},
),
],
);
},
);
if (result == 'ok') {
print('确定');
} else {
print('取消');
}
}
CupertinoAlertDialog ios风格确认框
showDialog
和AlertDialog
配合使用展示Material风格对话框。showCupertinoDialog
和CupertinoAlertDialog
配合使用展示iOS风格对话框。
showCupertinoDialog
点击空白处是无法退出对话框的,而showDialog
点击空白处默认退出对话框。
showCupertinoDialog(
context: context,
builder: (context) {
return CupertinoAlertDialog(
title: Text('提示'),
content: Text('确认删除吗?'),
actions: [
CupertinoDialogAction(
child: Text('取消'),
onPressed: () {},
),
CupertinoDialogAction(
child: Text('确认'),
onPressed: () {},
),
],
);
},
);
bug: The getter ‘modalBarrierDismissLabel’ was called on null.
问题描述:如果在MaterialApp里调用showCupertinoDialog显示弹出框,可能会遇到这个错误。
原因分析:就是Material主题下Context没有这个Cupertine主题的属性
方法1:
localizationsDelegates: [
...
GlobalCupertinoLocalizations.delegate, //添加这行即可解决问题
],
注意:需要在pubspec.yaml添加localizations依赖,如下:
dependencies:
flutter_localizations:
sdk: flutter
方法2:
在 main.dart 文件中加入 localizationsDelegates 这个属性,并在其中加入FallbackCupertinoLocalisationsDelegate()
,不需要的不要加。并且在该文件下生成对应的类:
localizationsDelegates: [
...
FallbackCupertinoLocalisationsDelegate(),
],
class FallbackCupertinoLocalisationsDelegate extends LocalizationsDelegate<CupertinoLocalizations> {
const FallbackCupertinoLocalisationsDelegate();
@override
bool isSupported(Locale locale) => true;
@override
Future<CupertinoLocalizations> load(Locale locale) => DefaultCupertinoLocalizations.load(locale);
@override
bool shouldReload(FallbackCupertinoLocalisationsDelegate old) => false;
}
SimpleDialog 选择框
https://api.flutter.dev/flutter/material/SimpleDialog-class.html
简单对话框可以显示附加的提示或操作。
void _simpleDialog() async {
showDialog(
context: context,
builder: (context) {
return SimpleDialog(
title: Text('选择内容'),
children: [
SimpleDialogOption(child: Text('Option A'), onPressed: () {}),
Divider(),
SimpleDialogOption(child: Text('Option B'), onPressed: () {}),
Divider(),
SimpleDialogOption(child: Text('Option C'), onPressed: () {}),
],
);
},
);
}
//...
RaisedButton(
child: Text('select弹出框-SimpleDialog'),
onPressed: _simpleDialog,
),
BottomSheet 底部滑框
https://api.flutter.dev/flutter/material/BottomSheet-class.html
BottomSheet是一个从屏幕底部滑起的列表(以显示更多的内容)。你可以调用showBottomSheet()或showModalBottomSheet弹出。
void _modalBottomSheet() async {
// showBottomSheet() 占全屏
// showModalBottomSheet() 默认占底部半屏左右,可以设置高度
var result = await showModalBottomSheet(
context: context,
builder: (context) {
return Container(
height: 200,
child: Column(
children: [
ListTile(
title: Text('分享 A'),
onTap: () {
Navigator.pop(context, 'a');
},
),
ListTile(
title: Text('分享 B'),
onTap: () {
Navigator.pop(context, 'b');
},
),
ListTile(
title: Text('分享 C'),
onTap: () {
Navigator.pop(context, 'c');
},
),
],
),
);
},
);
print(result);
}
//...
aisedButton(
child: Text('ActionSheet底部弹出框-showModalBottomSheet'),
onPressed: _modalBottomSheet,
),
SnackBar 底部轻提示
https://api.flutter.dev/flutter/material/SnackBar-class.html
带有可选操作的轻量级消息,在屏幕底部短暂显示,SnackBar一般不单独使用,而是配合Scaffold.of(context).showSnackBar
进行弹出展示。
定义
SnackBar({
Key key,
@required this.content,
this.backgroundColor, //背景色
this.elevation, //阴影
this.margin,
this.padding,
this.width, //需搭配 behavior: SnackBarBehavior.floating, 模式
this.shape, //形状
this.behavior, //弹出形式。fixed floating
this.action, //搭配 SnackBarAction 使用
this.duration = _snackBarDisplayDuration, //设置显示时间,默认是4秒。duration: Duration(seconds: 1),
this.animation,
this.onVisible, //在显示的时候调用
})
简单使用
Builder(
builder: (context) {
return RaisedButton(
child: Text('click me'),
onPressed: () {
// 查找父级最近的Scaffold对应的ScaffoldState对象
// ScaffoldState _state = context.findAncestorStateOfType<ScaffoldState>();
ScaffoldState _state = Scaffold.of(context);
//调用ScaffoldState的showSnackBar来弹出SnackBar
_state.showSnackBar(
SnackBar(
content: Text('hello world'),
),
);
},
);
},
),
设置样式
SnackBar(
backgroundColor: Colors.red,
elevation: 8,
width: 200,
behavior: SnackBarBehavior.floating,
action: SnackBarAction(
label: 'click me',
onPressed: () {},
),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(100)),
content: Text('hello world'),
),
瞬间多个弹出延迟问题
当短时间内多次调用SnackBar方法时,SnackBar消息将会以队列的形式一个一个的弹出,比如下面的代码:
RaisedButton(
child: Text(
'点我,弹出SnackBar',
),
onPressed: () {
List.generate(10, (index){
Scaffold.of(context).showSnackBar(SnackBar(
content: Text('我是消息:$index'),
));
});
},
)
默认情况下每个显示4秒,如果有10个,那么40秒内会一直弹消息,体验明显不友好,我们希望的效果是如果有新的消息时,旧的都消息立刻消失,显示新的消息,只需在弹出新的SnackBar时移除现在的SnackBar,
Scaffold.of(context).removeCurrentSnackBar();
Scaffold.of(context).showSnackBar(...);
ExpansionPanel 扩展框
https://api.flutter.dev/flutter/material/ExpansionPanel-class.html
ExpansionPanel
一个material 扩展面板。它有一个 header 和一个 body ,可以展开或折叠。面板的 body 仅在展开时可见。 扩展面板仅用作于 ExpansionPanelList。
ExpansionPanel({
@required this.headerBuilder,
@required this.body,
this.isExpanded = false, //是否展开
this.canTapOnHeader = false, //header是否可以点击
})
ExpansionPanelList
material 展开面板列表,用于设置其子项并为展开设置动画。
const ExpansionPanelList({
Key key,
this.children = const <ExpansionPanel>[], //就是 ExpansionPanel
this.expansionCallback, //展开回调,expansionCallback: (int index, bool isExpanded){}
this.animationDuration = kThemeAnimationDuration, //动画的时间
this.expandedHeaderPadding = _kPanelHeaderExpandedDefaultPadding,
this.dividerColor,
this.elevation = 2,
})
示例

import 'package:flutter/material.dart';
class Item {
Item({
this.expandedValue,
this.headerValue,
this.isExpanded = false,
});
String expandedValue; //展开的title值
String headerValue; //header的值
bool isExpanded; //是否已经展开
}
// 生成指定数量的List
List<Item> generateItems(int counter) {
return List.generate(counter, (int index) {
return Item(
headerValue: 'Panel $index',
expandedValue: 'This is item number $index',
);
});
}
class ExpansionPanelPage extends StatefulWidget {
ExpansionPanelPage({Key key}) : super(key: key);
@override
_ExpansionPanelPageState createState() => _ExpansionPanelPageState();
}
class _ExpansionPanelPageState extends State<ExpansionPanelPage> {
List<Item> _data = generateItems(8);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('ExpansionPanelPage demo')),
body: SingleChildScrollView(
child: Container(
child: _buildPanel(),
),
),
);
}
Widget _buildPanel() {
return ExpansionPanelList(
expansionCallback: (int index, bool isExpanded) {
setState(() {
_data[index].isExpanded = !isExpanded;
});
},
children: _data.map<ExpansionPanel>((Item item) {
return ExpansionPanel(
headerBuilder: (BuildContext context, bool isExpanded) {
return ListTile(title: Text(item.headerValue));
},
body: ListTile(
title: Text(item.expandedValue),
subtitle: Text('To delete this panel, tap the trash can icon'),
trailing: Icon(Icons.delete),
onTap: () {
setState(() {
_data.removeWhere((currentItem) => item == currentItem);
});
}),
isExpanded: item.isExpanded,
);
}).toList(),
);
}
}
什么要用 SingleChildScrollView 包起来?
我们先把 SingleChildScrollView 去掉来看一下效果:
发现什么都没有了,看一下log:
flutter: The following assertion was thrown during performLayout(): flutter: RenderListBody must have unlimited space along its main axis. flutter: RenderListBody does not clip or resize its children, so it must be placed in a parent that does not flutter: constrain the main axis. You probably want to put the RenderListBody inside a RenderViewport with a matching main axis.
大致意思就是说:
RenderListBody所在的主轴必须要有无线的空间,因为RenderListBody 要不断的调整children 的大小,所以必须把它放在不约束主轴的 parent 中。
body is ListView
在我们实际业务中,可能最多的业务为展开是一个列表,那需要 body 是ListView。
其实和官方Demo差不多,需要注意的一点就是 shrinkWrap & physics 这两个字段:
return ListView.builder(
shrinkWrap: true,
physics: NeverScrollableScrollPhysics(),
);
只能展开一个
有时我们也会遇到只能展开一个,点击其他的时候要关闭已经展开的。
代码如下,需使用 ExpansionPanelList.radio
ExpansionPanelList.radio
的 children 也需要改变为: ExpansionPanelRadio
。ExpansionPanelRadio
和 ExpansionPanel
的区别就是一个 value。ExpansionPanelRadio
是用 value 来区分的,所以每一个要是唯一的。
Widget _buildPanel() {
return ExpansionPanelList.radio(
expansionCallback: (int index, bool isExpanded) {
setState(() {
_data[index].isExpanded = !isExpanded;
});
},
children: _data.map<ExpansionPanel>((Item item) {
return ExpansionPanelRadio(
canTapOnHeader: true,
headerBuilder: (BuildContext context, bool isExpanded) {
return ListTile(
title: Text(item.headerValue),
);
},
body: ListTile(
title: Text(item.expandedValue),
subtitle: Text('To delete this panel, tap the trash can icon'),
trailing: Icon(Icons.delete),
onTap: () {
setState(() {
_data.removeWhere((currentItem) => item == currentItem);
});
}),
value: item.headerValue,
);
}).toList(),
);
}
自定义Dialog
自定义 Dialog 对象,需要继承 Dialog 类,尽管 Dialog 提供了child 参数可以用来写视图界面,但是往往达不到想要的效果,因为默认的 Dialog 背景框是满屏的。如果我们想完全定义界面,就需要重写 build 函数。
示例:简单对话框
import 'package:app1/coms/dialogs/MyDialog.dart';
//...
RaisedButton(
child: Text('自定义Dialog'),
onPressed: () {
showDialog(
context: context,
builder: (context) {
return MyDialog(
title: '我是标题',
content: '我是内容',
);
},
);
},
),
import 'package:flutter/material.dart';
class MyDialog extends StatelessWidget {
String title;
String content;
MyDialog({
key,
this.title = null,
this.content = null,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Material(
type: MaterialType.transparency, //背景透明
child: Center(
child: Container(
width: 300,
constraints: BoxConstraints(minHeight: 200),
color: Colors.white,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Padding(
padding: EdgeInsets.all(10),
child: Stack(
children: [
Align(
alignment: Alignment.bottomCenter,
child: Text(this.title),
),
Align(
alignment: Alignment.centerRight,
child: InkWell(
child: Icon(Icons.close),
onTap: () {
Navigator.pop(context);
},
),
),
],
),
),
Divider(),
Container(
width: double.infinity,
padding: EdgeInsets.all(10),
child: Text(this.content),
),
],
),
),
),
);
}
}
示例:loading对话框
showDialog(
context: context,
builder: (context) {
return MyDialog(
title: '数据请求中',
);
},
);
import 'dart:async';
import 'package:flutter/material.dart';
class MyDialog extends StatelessWidget {
String title;
MyDialog({
key,
this.title = null,
}) : super(key: key);
_showTimer(context) {
var timer = null;
timer = Timer.periodic(Duration(milliseconds: 3000), (t) {
Navigator.pop(context);
t.cancel(); //取消定时器
});
}
@override
Widget build(BuildContext context) {
_showTimer(context);
return Material(
type: MaterialType.transparency,
child: Center(
child: Container(
width: 200,
padding: EdgeInsets.all(20),
color: Colors.white,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Container(
width: 70,
height: 70,
child: CircularProgressIndicator(
backgroundColor: Colors.grey,
valueColor: AlwaysStoppedAnimation(Colors.green),
strokeWidth: 6.0,
),
),
Container(
padding: EdgeInsets.only(top: 20, bottom: 10),
child: Text(this.title),
),
],
),
),
),
);
}
}
库 fluttertoast
https://pub.dev/packages/fluttertoast
import "package:flutter/material.dart";
import 'package:fluttertoast/fluttertoast.dart';
class DialogDemo extends StatelessWidget {
void _toast() {
Fluttertoast.showToast(
msg: "This is Center Short Toast",
toastLength: Toast.LENGTH_SHORT, //LENGTH_SHORT LENGTH_LONG
gravity: ToastGravity.CENTER, //位置 TOP BOTTOM CENTER
timeInSecForIosWeb: 1, //只在ios上有效果
backgroundColor: Colors.blue,
textColor: Colors.white,
fontSize: 16.0,
);
}
@override
Widget build(BuildContext context) {
return Container(
child: Column(
children: [
RaisedButton(
child: Text('toast-fluttertoast第三方库'),
onPressed: _toast,
),
],
),
);
}
}
属性
property | description | default |
---|---|---|
msg | String (Not Null)(required) | required |
toastLength | Toast.LENGTH_SHORT or Toast.LENGTH_LONG (optional) | Toast.LENGTH_SHORT |
gravity | ToastGravity.TOP (or) ToastGravity.CENTER (or) ToastGravity.BOTTOM (Web Only supports top, bottom) | ToastGravity.BOTTOM |
timeInSecForIosWeb | int (for ios & web) | 1 (sec) |
backgroundColor | Colors.red | null |
textcolor | Colors.white | null |
fontSize | 16.0 (float) | null |
webShowClose | false (bool) | false |
webBgColor | String (hex Color) | linear-gradient(to right, #00b09b, #96c93d) |
webPosition | String (left , center or right ) |
right |
To cancel all the toasts call
Fluttertoast.cancel()
库 flutter_easyloading
https://pub.dev/packages/flutter_easyloading
中文说明:https://github.com/kokohuang/flutter_easyloading/blob/master/README-zh_CN.md
安装依赖
dependencies:
flutter_easyloading: ^2.2.1
导入
import 'package:flutter_easyloading/flutter_easyloading.dart';
使用
首先, 在MaterialApp
/CupertinoApp
中初始化FlutterEasyLoading
:
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter EasyLoading',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Flutter EasyLoading'),
builder: EasyLoading.init(),
);
}
}
调用
EasyLoading.show(status: 'loading...');
EasyLoading.showProgress(0.3, status: 'downloading...');
EasyLoading.showSuccess('Great Success!');
EasyLoading.showError('Failed with Error');
EasyLoading.showInfo('Useful Information.');
EasyLoading.showToast('Toast');
//关闭
EasyLoading.dismiss();
添加 Loading 状态回调
EasyLoading.show(status: 'loading...');
Timer(Duration(milliseconds: 1000), () {
EasyLoading.dismiss();
});
EasyLoading.addStatusCallback((status) {
print('EasyLoading Status: $status');
//EasyLoading Status: EasyLoadingStatus.show
// 1秒后输出:EasyLoading Status: EasyLoadingStatus.dismiss
});
移除 Loading 状态回调
EasyLoading.removeCallback(statusCallback);
EasyLoading.removeAllCallbacks();
自定义
textColor
、indicatorColor
、progressColor
、backgroundColor
仅对EasyLoadingStyle.custom
有效。maskColor
仅对EasyLoadingMaskType.custom
有效。
//使用示例
EasyLoading.instance.loadingStyle = EasyLoadingStyle.light;
EasyLoading.instance.indicatorType = EasyLoadingIndicatorType.threeBounce;
EasyLoading.show(status: 'loading...');
loadingStyle //loading的样式,默认[EasyLoadingStyle.dark] light
indicatorType //loading的指示器类型, 默认[EasyLoadingIndicatorType.fadingCircle]
maskType //loading的遮罩类型,默认[EasyLoadingMaskType.none]
toastPosition //toast的位置,默认[EasyLoadingToastPosition.center]
animationStyle //动画类型, 默认[EasyLoadingAnimationStyle.opacity]
EasyLoadingAnimation customAnimation; // 自定义动画, 默认 null.
TextAlign textAlign // 文本的对齐方式, 默认[TextAlign.center]
TextStyle textStyle // 文本的样式, 默认 null
EdgeInsets contentPadding // loading内容区域的内边距.
EdgeInsets textPadding // 文本的内边距.
double indicatorSize; // 指示器的大小, 默认40.0.
double radius; // loading的圆角大小, 默认5.0.
double fontSize; // 文本大小, 默认15.0.
double progressWidth; // 进度条指示器的宽度, 默认2.0.
double lineWidth; // 指示器的宽度, 默认4.0,
//仅对[EasyLoadingIndicatorType.ring, EasyLoadingIndicatorType.dualRing]有效.
Duration displayDuration; // [showSuccess] [showError] [showInfo]的展示时间, 默认2000ms.
Duration animationDuration; // 动画时间, 默认200ms.
Color textColor; // 文本的颜色, 仅对[EasyLoadingStyle.custom]有效.
Color indicatorColor; // 指示器的颜色, 仅对[EasyLoadingStyle.custom]有效.
Color progressColor; // 进度条指示器的颜色, 仅对[EasyLoadingStyle.custom]有效.
Color backgroundColor; // loading的背景色, 仅对[EasyLoadingStyle.custom]有效.
Color maskColor; // 遮罩的背景色, 仅对[EasyLoadingMaskType.custom]有效.
bool userInteractions; // 当loading展示的时候,是否允许用户操作.
bool dismissOnTap; // 点击背景是否关闭.
Widget indicatorWidget; // 指示器自定义组件
Widget successWidget; // 展示成功状态的自定义组件
Widget errorWidget; // 展示失败状态的自定义组件
Widget infoWidget; // 展示信息状态的自定义组件
因为 EasyLoading
是一个全局单例, 所以你可以在任意一个地方自定义它的样式:
EasyLoading.instance
..displayDuration = const Duration(milliseconds: 2000)
..indicatorType = EasyLoadingIndicatorType.fadingCircle
..loadingStyle = EasyLoadingStyle.dark
..indicatorSize = 45.0
..radius = 10.0
..progressColor = Colors.yellow
..backgroundColor = Colors.green
..indicatorColor = Colors.yellow
..textColor = Colors.yellow
..maskColor = Colors.blue.withOpacity(0.5)
..userInteractions = true
..dismissOnTap = false
..customAnimation = CustomAnimation();
更多的指示器类型可查看 👉 flutter_spinkit showcase
自定义动画
例子: 👉 Custom Animation
loading 封装
ELoading.dart
import 'package:flutter/material.dart';
import 'package:flutter_easyloading/flutter_easyloading.dart';
class ELoading {
// 卸载
static destroy() {
EasyLoading.dismiss();
}
// 加载框
static show({String status = 'Loading...'}) {
EasyLoading.instance
..indicatorType = EasyLoadingIndicatorType.threeBounce
..indicatorSize = 30.0;
EasyLoading.show(status: status);
}
// 进度加载框
static showProgress({
@required double progress,
String status,
}) {
EasyLoading.instance.indicatorSize = 45.0;
EasyLoading.showProgress(progress, status: status);
}
// 成功信息框
static showSuccess({String status}) {
EasyLoading.instance.indicatorSize = 45.0;
EasyLoading.showSuccess(status);
}
// 失败信息框
static showError({String status}) {
EasyLoading.instance.indicatorSize = 45.0;
EasyLoading.showError(status);
}
// 普通信息框
static showInfo({String status}) {
EasyLoading.showInfo(status);
}
// toast轻提示
static showToast({String status}) {
EasyLoading.showToast(status);
}
}