0、做内容时候的勘误
首先说firstName是名,lastName是姓。
接下来正文:
1、给ListView添加下拉刷新
上代码!
main.dart
import 'package:day04/main_provider.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
void main() {
runApp(
MultiProvider(
providers: [ChangeNotifierProvider(create: (context) => MainProvider())],
child: MyApp(),
),
);
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
),
home: const MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatelessWidget {
const MyHomePage({super.key, required this.title});
final String title;
@override
Widget build(BuildContext context) {
final provider = Provider.of(context);
return Scaffold(
appBar: AppBar(
backgroundColor: Theme
.of(context)
.colorScheme
.inversePrimary,
title: Text(title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(provider.text),
ElevatedButton(
onPressed: provider.getList,
child: const Text('请求数据'),
),
ElevatedButton(
onPressed: () => provider.addContact(),
child: const Text('添加一个联系人'),
),
Expanded(
child: RefreshIndicator(
onRefresh:() async=>{
provider.getList()
},
child: ListView.builder(
itemCount: provider.dataList.length,
itemBuilder: (context, index) {
return ListTile(
title: Text('${provider.dataList[index]
.firstName} ${provider.dataList[index].lastName}'),
subtitle: Text('${provider.dataList[index]
.phoneNumber}'),
);
},
),
)
)
],
),
),
);
}
}
人话:
Expanded撑开后用RefreshIndicator包裹原先的ListView.Builder
然后在RefreshIndicator的onRefresh事件里去写刷新的事件,也就是调provider的getList()方法
以防有人找不到就放一下代码片段
main_provider.dart 片段
void getList() async {
await DioUtil.getInstance().get(
"http://192.168.5.116:20080/contact/list",
null,
() {
_text = "loading";
notifyListeners();
},
(data) {
BaseBean bean = BaseBean.fromJson(data);
_dataList=List.from(bean.data.map((e) => ContactBean.fromJson(e)));
notifyListeners();
},
(reason) {
_text = reason;
notifyListeners();
},
);
}
然后你就会得到:
一个没法刷新的下拉刷新组件
哈哈哈哈哈哈哈哈哈是不是当你跑起来的时候感觉被命运捉弄了,下拉刷新的时候小圈圈拉下来就回去啦然后你开始上火,随后砸键盘拳头直冲电脑屏幕,老子不去学flutter了!(不是)
来来来下火药递上来
只需要从这样:
void getList() async //有可能甚至连void都没有
改成这样:
Future<void> getList() async
就好啦!
原因呢就是RefreshIndicator的onRefresh事件需要返回一个Future对象,来判断刷新这事干完了没。具体看一下源码内对于该事件参数的说明:
/// A function that's called when the user has dragged the refresh indicator
/// far enough to demonstrate that they want the app to refresh. The returned
/// [Future] must complete when the refresh operation is finished.
final RefreshCallback onRefresh;
其中这句[Future] must complete when the refresh operation is finished. 就告诉你 我组件总得知道这事完了吧。
然后你发现调用是调用了——但是那个RefreshIndicator还是刷一下就跑了
解决办法:给方法面前加上await即可,也就是await provider.getList()
这样就能让RefreshIndicator正确等待方法执行完成再消失。
添加下拉刷新这事算完了。
2、添加toast和dialog
至于toast的话先去pubspec.yaml添加一个:flutter_styled_toast: ^2.2.1
然后flutter pub get下载下来就行
这玩意在pub.dev上看起来蛮简单,也就showToast("hello styled toast",context:context);这么简单,一个文字和一个context就够用
引用了直接这么干就行:
import 'package:day04/main_provider.dart';
import 'package:flutter/material.dart';
import 'package:flutter_styled_toast/flutter_styled_toast.dart';
import 'package:provider/provider.dart';
void main() {
runApp(
MultiProvider(
providers: [ChangeNotifierProvider(create: (context) => MainProvider())],
child: MyApp(),
),
);
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
),
home: const MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatelessWidget {
const MyHomePage({super.key, required this.title});
final String title;
@override
Widget build(BuildContext context) {
final provider = Provider.of<MainProvider>(context);
return Scaffold(
appBar: AppBar(
backgroundColor: Theme
.of(context)
.colorScheme
.inversePrimary,
title: Text(title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(provider.text),
ElevatedButton(
onPressed: provider.getList,
child: const Text('请求数据'),
),
ElevatedButton(
onPressed: () => provider.addContact(),
child: const Text('添加一个联系人'),
),
Expanded(
child: RefreshIndicator(
onRefresh:() async=>{
provider.getList()
},
child: ListView.builder(
itemCount: provider.dataList.length,
itemBuilder: (context, index) {
return ListTile(
title: Text('${provider.dataList[index].lastName} ${provider.dataList[index].firstName}'),
subtitle: Text('${provider.dataList[index].phoneNumber}'),
onTap: ()=>{
showToast('点击了第${index+1}个联系人:${provider.dataList[index].lastName} ${provider.dataList[index].firstName}',context: context)
},
);
},
),
)
)
],
),
),
);
}
}
重点也就这行:showToast('点击了第${index+1}个联系人:${provider.dataList[index].lastName} ${provider.dataList[index].firstName}',context: context)
注:toast实现有很多包,flutter_styled_toast就是文章里的算一个,也有个fluttertoast——不过只支持Android&iOS&web。还有一个是toastification和本文里的flutter_styled_toast一样支持全平台,但那个toast前端同学更熟悉——右上角弹出消息。
这几个用法都很简单,看看pub的example就行
添加dialog
其实添加dialog也没那么难,现在暂时先加一个AlertDialog对话框和按钮。上代码:
main.dart
import 'package:day04/main_provider.dart';
import 'package:flutter/material.dart';
import 'package:flutter_styled_toast/flutter_styled_toast.dart';
import 'package:provider/provider.dart';
void main() {
runApp(
MultiProvider(
providers: [ChangeNotifierProvider(create: (context) => MainProvider())],
child: MyApp(),
),
);
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
),
home: const MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatelessWidget {
const MyHomePage({super.key, required this.title});
final String title;
@override
Widget build(BuildContext context) {
final provider = Provider.of<MainProvider>(context);
return Scaffold(
appBar: AppBar(
backgroundColor: Theme
.of(context)
.colorScheme
.inversePrimary,
title: Text(title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(provider.text),
ElevatedButton(
onPressed: provider.getList,
child: const Text('请求数据'),
),
ElevatedButton(
onPressed: () => provider.addContact(),
child: const Text('添加一个联系人'),
),
Expanded(
child: RefreshIndicator(
onRefresh: () async {
await provider.getList();
},
child: ListView.builder(
itemCount: provider.dataList.length,
itemBuilder: (context, index) {
return ListTile(
title: Text('${provider.dataList[index].lastName} ${provider.dataList[index].firstName}'),
subtitle: Text('${provider.dataList[index].phoneNumber}'),
onTap: () async {
if(index%2==0){
showToast('点击了第${index+1}个联系人:${provider.dataList[index].lastName} ${provider.dataList[index].firstName}',context: context);
} else {
var data =await showADialog(context, '提示',
'点击了第${index + 1}个联系人:${provider
.dataList[index].lastName} ${provider
.dataList[index].firstName}');
showToast(data.toString(), context: context);
}
},
);
},
),
)
)
],
),
),
);
}
Future<dynamic> showADialog(context,title,content){
return showDialog(
context: context,
builder: (context)=>AlertDialog(
title: Text(title),
content: Text(content),
actions: [
TextButton(
onPressed: () => Navigator.of(context).pop(3),
child: Text('某种中立按钮?'),
),
TextButton(
onPressed: () => Navigator.of(context).pop(2),
child: Text('取消'),
),
TextButton(
onPressed: () => Navigator.of(context).pop(1),
child: Text('确定'),
),
],
)
);
}
}
代码里面逻辑是:ListTile的点击事件onTap()里,如果点击是偶数index的就是toast,点击奇数index的就是AlertDialog。
在这把showDialog方法单独拿出来封装了一下,方便看清楚代码。
showDialog方法返回Future<>对象,一般写Future。里面好像能放多个按钮。
关闭对话框用Navigator.of(context).pop()即可,需要返回啥往pop里传啥抖就行。我这里传123,代表确定按钮(positive)、取消按钮(negative)和中立按钮(neutral)方便判断。只不过说拿到我点了哪个按钮后又弹了一个toast 。