- 开发flutter-ui过程当中,遇到了全局弹窗问题
- 友好的交互界面,可以产生更好的用户体验,好比查询接口较久或须要耗时处理程序时,给个loading效果。
- flutter组件中showDialog弹窗组件,能知足弹窗需求,但使用过程可能不太顺手。
- 源码地址
将从如下几点来分析与实现接口请求前的弹窗效果git
本文相关连接github
dependencies:
flutter:
sdk: flutter
dio: ^2.1.0 # dio依赖包 2019/03/30
复制代码
lib
|--http #文件
|--index.dart # dio
|--loading.dart #loading
|--main.dart #入口
复制代码
showDialog{
@required BuildContext context,
bool barrierDismissible = true,
@Deprecated(
'Instead of using the "child" argument, return the child from a closure '
'provided to the "builder" argument. This will ensure that the BuildContext '
'is appropriate for widgets built in the dialog.'
) Widget child,
WidgetBuilder builder,
}
复制代码
查看showDialog源码,调用顺序是
showDialog -> showGeneralDialog -> Navigator.of(context, rootNavigator: true).push() context做为参数,做用是提供给了Navigator.of(context, rootNavigator: true).push使用json
/// The dialog route created by this method is pushed to the root navigator.
/// If the application has multiple [Navigator] objects, it may be necessary to
/// call `Navigator.of(context, rootNavigator: true).pop(result)` to close the
/// dialog rather than just `Navigator.pop(context, result)`.
///
/// See also:
///
/// * [showDialog], which displays a Material-style dialog.
/// * [showCupertinoDialog], which displays an iOS-style dialog.
复制代码
void _incrementCounter() {
showDialog(
context: context,
builder: (context) {
// 用Scaffold返回显示的内容,能跟随主题
return Scaffold(
backgroundColor: Colors.transparent, // 设置透明背影
body: Center( // 居中显示
child: Column( // 定义垂直布局
mainAxisAlignment: MainAxisAlignment.center, // 主轴居中布局,相关介绍能够搜下flutter-ui的内容
children: <Widget>[
// CircularProgressIndicator自带loading效果,须要宽高设置可在外加一层sizedbox,设置宽高便可
CircularProgressIndicator(),
SizedBox(
height: 10,
),
Text('loading'), // 文字
// 触发关闭窗口
RaisedButton(
child: Text('close dialog'),
onPressed: () {
print('close');
},
),
],
), // 自带loading效果,须要宽高设置可在外加一层sizedbox,设置宽高便可
),
);
},
);
}
复制代码
点击后出来了弹窗了,这一切尚未结束,只是个开始。 关闭弹窗,点击物理返回键就后退了。(尴尬不) 在上面showDialog介绍中最后提供了一段关于showGeneralDialog的注释代码,若须要关闭窗口,能够经过调用 Navigator.of(context, rootNavigator: true).pop(result)。 修改下RaisedButton事件内容bash
RaisedButton(
child: Text('close dialog'),
onPressed: () {
Navigator.of(context, rootNavigator: true).pop();
},
),
复制代码
这样弹窗能够经过按钮控制关闭了并发
在触发接口请求时,先调用showDialog触发弹窗,接口请求完成关闭窗口app
import 'package:dio/dio.dart' show Dio, DioError, InterceptorsWrapper, Response;
Dio dio;
class Http {
static Dio instance() {
if (dio != null) {
return dio;// 实例化dio
}
dio = new Dio();
// 增长拦截器
dio.interceptors.add(
InterceptorsWrapper(
// 接口请求前数据处理
onRequest: (options) {
return options;
},
// 接口成功返回时处理
onResponse: (Response resp) {
return resp;
},
// 接口报错时处理
onError: (DioError error) {
return error;
},
),
);
return dio;
}
/**
* get接口请求
* path: 接口地址
*/
static get(path) {
return instance().get(path);
}
}
复制代码
import 'package:flutter/material.dart';
class Loading {
static void before(text) {
// 请求前显示弹窗
// showDialog();
}
static void complete() {
// 完成后关闭loading窗口
// Navigator.of(context, rootNavigator: true).pop();
}
}
// 弹窗内容
class Index extends StatelessWidget {
final String text;
Index({Key key, @required this.text}):super(key: key);
@override
Widget build(BuildContext context) {
return xxx;
}
}
复制代码
解决了showDialog中的context,即能实现弹窗任意调用,不局限于dio请求。context不是任意的,只在Scaffold中可以使Navigator.of(context)中找获得Navigator对象。(刚接触时不少时候会以为一样都是context,为啥调用of(context)会报错。)less
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
print('main${Navigator.of(context)}'); // !!!这里发报错!!!
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
... // 省略其它内容
}
错误内容以下:
I/flutter ( 9137): Navigator operation requested with a context that does not include a Navigator.
I/flutter ( 9137): The context used to push or pop routes from the Navigator must be that of a widget that is a
I/flutter ( 9137): descendant of a Navigator widget.
即在MaterialApp中未能找到。
复制代码
让咱们在_MyHomePageState中查看下build返回Scaffold时,context对象内容是否有Navigator对象ide
class _MyHomePageState extends State<MyHomePage> {
@override
Widget build(BuildContext context) {
print('home${Navigator.of(context)}'); // 正常打印NavigatorState#600dc(tickers: tracking 1 ticker)
}
... // 省略其它内容
}
复制代码
因此全局弹窗的context,须要scaffold中的context。项目初始时在build第一次返回scaffold组件前,把context全局存储起来,提供能showDialog使用。(第一次返回没有局限,只要在调用showDiolog调用前全局保存context便可,自行而定。),至此能够解决了dio中调用showDialog时,context常常运用错误致使报错问题。函数
扩展分析flutter-ui中与provide结合使用后遇到的context。 flutter-ui先经过Store.connect封装provide数据层,这里的context返回的provide实例的上下文,接着return MaterialApp中,这里的上下文也是MaterialApp自己的,这些都无法使用Navigator对象,最终在build Scaffold时,经过Provide数据管理提早setWidgetCtx,全局保存Scaffold提供的context。布局
1 在http/loading.dart文件的Loading类暂存一个context静态变量。
class Loading {
static dynamic ctx;
static void before(text) {
// 请求前显示弹窗
// showDialog(context: ctx, builder: (context) {
// return Index(text:text);
// );
}
static void complete() {
// 完成后关闭loading窗口
// Navigator.of(ctx, rootNavigator: true).pop();
}
}
复制代码
2 在main.dart中_MyHomePageState build函数返回前注入Loading.ctx = context; 为了便于区别,咱们使用ctx来存储
import 'package:flutter_loading/http/loading.dart' show Loading;
... // 省略部分代码
class _MyHomePageState extends State<MyHomePage> {
@override
Widget build(BuildContext context) {
print('home $context');
print('home ${Navigator.of(context)}');
Loading.ctx = context; // 注入context
return ...;
}
复制代码
上述内容解决了context关键点。接下来实现接口交互。点击按钮,调用dio.get接口拉取数据,在onRequest前调用Loading.before(); onResponse调用Loading.complete()进行关闭。
import 'package:flutter/material.dart';
class Loading {
static dynamic ctx;
static void before(text) {
// 请求前显示弹窗
showDialog(
context: ctx,
builder: (context) {
return Index(text: text);
},
);
}
static void complete() {
// 完成后关闭loading窗口
Navigator.of(ctx, rootNavigator: true).pop();
}
}
复制代码
修改下dio的内容,接口请求返回较快时,为了看到loading效果,故在onResponse增长了Future.delayed,延迟3s返回数据。
import 'package:dio/dio.dart' show Dio, DioError, InterceptorsWrapper, Response;
import 'loading.dart' show Loading;
Dio dio;
class Http {
static Dio instance() {
if (dio != null) {
return dio;// 实例化dio
}
dio = new Dio();
// 增长拦截器
dio.interceptors.add(
InterceptorsWrapper(
// 接口请求前数据处理
onRequest: (options) {
Loading.before('正在加速中...');
return options;
},
// 接口成功返回时处理
onResponse: (Response resp) {
// 这里为了让数据接口返回慢一点,增长了3秒的延时
Future.delayed(Duration(seconds: 3), () {
Loading.complete();
return resp;
});
},
// 接口报错时处理
onError: (DioError error) {
return error;
},
),
);
return dio;
}
/**
* get接口请求
* path: 接口地址
*/
static get(path) {
return instance().get(path);
}
}
复制代码
修改下_incrementCounter函数的内容为经过http.get触发接口调用
import 'package:flutter/material.dart';
import 'package:flutter_loading/http/loading.dart' show Loading;
import 'http/index.dart' show Http;
... // 省略代码
void _incrementCounter() {
// Loading.before('loading...');
Http.get('https://raw.githubusercontent.com/efoxTeam/flutter-ui/master/version.json');
}
... // 省略代码
复制代码
ok. 你将会看到以下效果。
并发请求,loading只须要保证有一个在当前运行。接口返回结束,只须要保证最后一个完成时,关闭loading。
import 'package:flutter/material.dart';
Set dict = Set();
bool loadingStatus = false;
class Loading {
static dynamic ctx;
static void before(uri, text) {
dict.add(uri); // 放入set变量中
// 已有弹窗,则再也不显示弹窗, dict.length >= 2 保证了有一个执行弹窗便可,
if (loadingStatus == true || dict.length >= 2) {
return ;
}
loadingStatus = true; // 修改状态
// 请求前显示弹窗
showDialog(
context: ctx,
builder: (context) {
return Index(text: text);
},
);
}
static void complete(uri) {
dict.remove(uri);
// 全部接口接口返回并有弹窗
if (dict.length == 0 && loadingStatus == true) {
loadingStatus = false; // 修改状态
// 完成后关闭loading窗口
Navigator.of(ctx, rootNavigator: true).pop();
}
}
}
复制代码
http/index.dart
onReuest: Loading.before(options.uri, '正在加速中...');
onReponse: Loading.complete(resp.request.uri);
onError: Loading.complete(error.request.uri );
复制代码
欢迎你们交流~