flutter 给列表添加下拉刷新、Toast和Dialog

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 。

上一篇
下一篇