关于Navigator 2.0 详细内容可以参考 此文章,今天就对此文章进行一个封装 Flutter Navigator 2.0 指南与原理解析
Navigator 2.0 之后,Flutter 也提供了 MaterialApp 的新构造函数 router 来帮助我们直接在应用顶层构造出全局的 Router 组件,使用方式如下
MaterialApp.router(
title: 'Flutter Navigator 2.0 Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
//必传项目 路由返回 用来解析浏览器路径
routeInformationParser: MyRouteParser(),
//必传项目 路由代理
routerDelegate: delegate,
)
注意 delegate的初始化要在build之前完成
class MyApp extends StatelessWidget {
final MyRouterDelegate routerDelegate=MyRouterDelegate();
MyApp({Key? key}) : super(key: key);
class MyRouteParser extends RouteInformationParser<RouteSettings> {
@override
///parseRouteInformation() 方法的作用就是接受系统传递给我们的路由信息 routeInformation
Future<RouteSettings> parseRouteInformation(
RouteInformation routeInformation) {
// Uri uri = Uri.parse(routeInformation.location??"/");
return SynchronousFuture(RouteSettings(name: routeInformation.location));
}
@override
///恢复路由信息
RouteInformation restoreRouteInformation(RouteSettings configuration) {
return RouteInformation(location: configuration.name);
}
}
RouteInformationParser 传递过来的RouteInformation 被 RouterDelegate所接受 执行setInitialRoutePath方法 他调用setNewRoutePath 创建新的路由
///继承RouterDelegate并混合PopNavigatorRouterDelegateMixin和ChangeNotifier
///ChangeNotifier 用来通知路由改变
///backButtonDispatcher 发出回退按钮事件时,会调用 RouterDelegate 的 popRoute() 方法,由混入的 PopNavigatorRouterDelegateMixin 实现。
class MyRouterDelegate extends RouterDelegate<RouteSettings>
with PopNavigatorRouterDelegateMixin<RouteSettings>, ChangeNotifier {
static MyRouterDelegate? _instance;
factory MyRouterDelegate() => _instance ??= MyRouterDelegate._();
MyRouterDelegate._();
///页面栈
List<Page> _stack = [];
//当前页面信息
RouteSettings? _setting;
//重写navigatorKey
@override
GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
@override
// TODO: implement currentConfiguration
RouteSettings? get currentConfiguration => _stack.last;
///如果发现showModelBottomSheet不能受返回键控制 请注释掉popRoute这方法 将首页的Widegt包裹WillPopScope --4.22新加此注释
@override
Future<bool> popRoute() {
if (_stack.length > 1) {
_stack.removeLast();
_setting = _stack.last;
changePage();
//非最后一个页面
return Future.value(true);
}
//最后一个页面确认退出操作
return _confirmExit();
}
Future<bool> _confirmExit() async {
bool result = doubleCheckExit(navigatorKey.currentContext!);
// bool result = await ExitUtil.backToDesktop();
return !result;
}
//添加页面的操作
void addPage({required name, arguments}) {
_setting = RouteSettings(name: name, arguments: arguments);
changePage();
notifyListeners();
}
@override
Widget build(BuildContext context) {
Navigator(
key: navigatorKey,
pages: _stack,
onPopPage: _onPopPage,
);
}
/// 按下返回的回调
bool _onPopPage(Route<dynamic> route, dynamic result) {
if (!route.didPop(result)) {
return false;
}
return true;
}
changePage() {
int index = getCurrentIndex(_stack, _setting!);
List<Page> tempPages = _stack;
if (index != -1) {
// 要求栈中只允许有一个同样的页面的实例 否则开发模式热更新会报错
// 要打开的页面在栈中已存在,则将该页面和它上面的所有页面进行出栈
tempPages = tempPages.sublist(0, index);
// 或者删除之前存在栈里的页面,重新创建
// tempPages.removeAt(index);
}
Page page;
if (_setting?.name == RouterName.rootPage) {
_stack.clear();
}
page = buildPage(name: _setting?.name, arguments: _setting?.arguments);
tempPages = [...tempPages, page];
_stack = tempPages;
}
///获取当前页面位于栈的位置
int getCurrentIndex(List<Page> pages, RouteSettings setting) {
for (int i = 0; i < pages.length; i++) {
Page page = pages[i];
if (page.name == setting.name) {
return i;
}
}
return -1;
}
///首次进入时会通过这个方法来配置路由站
@override
Future<void> setNewRoutePath(RouteSettings configuration) async {
if (configuration.name == RouterName.rootPage) {
_stack.clear();
}
addPage(name: configuration.name, arguments: configuration.arguments);
}
//建造页面
buildPage({required name, arguments}) {
return MaterialPage(
child: getPageChild(name: name, arguments: arguments),
arguments: arguments,
name: name,
key: ValueKey(name));
}
static DateTime? _lastTapDt;
/// 双击退出 单机提示再按一次
static bool doubleCheckExit(context) {
if (_lastTapDt == null ||
DateTime.now().difference(_lastTapDt!) > const Duration(seconds: 1)) {
ScaffoldMessenger.of(context).clearMaterialBanners();
ScaffoldMessenger.of(context)
.showSnackBar(const SnackBar(content: Text("再按一次退出")));
_lastTapDt = DateTime.now();
return false;
}
_lastTapDt = null;
return true;
}
//通过routerName获取页面
Widget getPageChild({required name, arguments}) {
Widget page;
Map? arg;
if (arguments is Map) {
arg = arguments;
}
switch (name) {
case RouterName.rootPage:
page = RootPage(arguments: arg);
break;
default:
page = const UndefinedPage();
}
return page;
}
}
Center(
child: TextButton(
child: const Text("点我跳转"),
onPressed: () {
MyRouterDelegate().addPage(name: RouterName.splashPage);
},
),
),
当项目文件比较多时将内容拆分成多个
class RouterName {
static const String rootPage = "/"; //默认MyRouteParser传回来的地址是/您可以在返回之前做修改或者把默认修改成/
static const String splashPage = "SPLASH_PAGE";
}
///通过Router的名字获取页面
///[name] 路由名称
///[arguments] 可空 路由传递参数
Widget getPageChild({required name, arguments}) {
Widget page;
Map? arg;
if (arguments is Map) {
arg = arguments;
}
switch (name) {
case RouterName.rootPage:
page = RootPage(arguments: arg);
break;
default:
page = const UndefinedPage();
}
return page;
}
///继承RouterDelegate并混合PopNavigatorRouterDelegateMixin和ChangeNotifier
///ChangeNotifier 用来通知路由改变
///backButtonDispatcher 发出回退按钮事件时,会调用 RouterDelegate 的 popRoute() 方法,由混入的 PopNavigatorRouterDelegateMixin 实现。
class MyRouterDelegate extends RouterDelegate<RouteSettings>
with PopNavigatorRouterDelegateMixin<RouteSettings>, ChangeNotifier {
///页面栈
List<Page> _stack = [];
//当前的界面信息
RouteSettings? _setting = const RouteSettings(name: RouterName.rootPage);
//重写navigatorKey
@override
GlobalKey<NavigatorState> navigatorKey;
MyRouterDelegate() : navigatorKey = GlobalKey<NavigatorState>() {
//初始化两个方法 一个是push页面 另一个是替换页面
NavigatorUtil()
.registerRouteJump(RouteJumpFunction(onJumpTo: (RouteSettings setting) {
_setting = setting;
changePage();
}, onReplaceAndJumpTo: (RouteSettings setting) {
if (_stack.isNotEmpty) {
_stack.removeLast();
}
_setting = setting;
changePage();
}));
}
@override
// TODO: implement currentConfiguration
RouteSettings? get currentConfiguration => _stack.last;
@override
Future<bool> popRoute() {
if (_stack.length > 1) {
_stack.removeLast();
_setting = _stack.last;
changePage();
//非最后一个页面
return Future.value(true);
}
//最后一个页面确认退出操作
return _confirmExit();
}
Future<bool> _confirmExit() async {
bool result = ExitUtil.doubleCheckExit(navigatorKey.currentContext!);
// bool result = await ExitUtil.backToDesktop();
return !result;
}
void addPage({required name, arguments}) {
_setting = RouteSettings(name: name, arguments: arguments);
changePage();
}
@override
Widget build(BuildContext context) {
return WillPopScope(
//解决物理返回建无效的问题
onWillPop: () async => !await navigatorKey.currentState!.maybePop(),
child: Navigator(
key: navigatorKey,
pages: _stack,
onPopPage: _onPopPage,
),
);
}
/// 按下返回的回调
bool _onPopPage(Route<dynamic> route, dynamic result) {
debugPrint("这里的试试");
if (!route.didPop(result)) {
return false;
}
return true;
}
changePage() {
int index = getCurrentIndex(_stack, _setting!);
List<Page> tempPages = _stack;
if (index != -1) {
// 要求栈中只允许有一个同样的页面的实例 否则开发模式热更新会报错
// 要打开的页面在栈中已存在,则将该页面和它上面的所有页面进行出栈
tempPages = tempPages.sublist(0, index);
// 或者删除之前存在栈里的页面,重新创建
// tempPages.removeAt(index);
}
Page page;
if (_setting?.name == RouterName.rootPage) {
_stack.clear();
}
page = buildPage(name: _setting?.name, arguments: _setting?.arguments);
tempPages = [...tempPages, page];
NavigatorUtil().notify(tempPages, _stack);
_stack = tempPages;
notifyListeners();
}
@override
Future<void> setNewRoutePath(RouteSettings configuration) async {
debugPrint("setNewRoutePath${configuration}");
if (configuration.name == RouterName.rootPage) {
_stack.clear();
}
addPage(name: configuration.name, arguments: configuration.arguments);
}
}
typedef OnJumpTo = void Function(RouteSettings settings);
///创建跳转方法的抽象类
///传递一个Function(routeSetting)
abstract class _RouteJumpFunction {
///跳转到
void onJumpTo({required name, Map? arguments});
///替换当前页面的跳转
void onReplaceAndJumpTo({required name, Map? arguments});
}
class RouteJumpFunction {
OnJumpTo? onJumpTo;
OnJumpTo? onReplaceAndJumpTo;
RouteJumpFunction({this.onJumpTo, this.onReplaceAndJumpTo});
}
class NavigatorUtil extends _RouteJumpFunction {
//创造工厂方法
static NavigatorUtil? _instance;
factory NavigatorUtil() => _instance ??= NavigatorUtil._();
NavigatorUtil._();
//跳转方法
RouteJumpFunction? _function;
//当前页面
RouteSettings? _current;
///注册路由跳转逻辑
void registerRouteJump(RouteJumpFunction function) {
_function = function;
}
//编写跳转逻辑
@override
void onJumpTo({required name, Map? arguments}) {
RouteSettings settings = RouteSettings(name: name, arguments: arguments);
return _function!.onJumpTo!(settings);
}
//编写替换当前页面的跳转逻辑
@override
void onReplaceAndJumpTo({required name, Map? arguments}) {
RouteSettings setting = RouteSettings(name: name, arguments: arguments);
_function?.onReplaceAndJumpTo!(setting);
}
void notify(List<Page> currentPages, List<Page> prePages) {
if (currentPages == prePages) {
return;
}
RouteSettings setting = RouteSettings(
name: currentPages.last.name, arguments: currentPages.last.arguments);
_notify(setting);
}
void _notify(RouteSettings setting) {
debugPrint("当前的页面是:${setting.name}");
debugPrint("上一个页面是:${_current?.name}");
_current = setting;
}
}
//建造页面
buildPage({required name, arguments}) {
return MaterialPage(
child: getPageChild(name: name, arguments: arguments),
arguments: arguments,
name: name,
key: ValueKey(name));
}
///获取当前页面在栈中的位置
int getCurrentIndex(List<Page> pages, RouteSettings setting) {
for (int i = 0; i < pages.length; i++) {
Page page = pages[i];
if (page.name == setting.name) {
return i;
}
}
return -1;
}
import 'package:flutter/material.dart';
class ExitUtil{
static DateTime? _lastTapDt;
/// 双击退出 单击提示再按一次
static bool doubleCheckExit(context) {
if (_lastTapDt == null ||
DateTime.now().difference(_lastTapDt!) > const Duration(seconds: 1)) {
ScaffoldMessenger.of(context).clearMaterialBanners();
ScaffoldMessenger.of(context)
.showSnackBar(const SnackBar(content: Text("再按一次退出")));
_lastTapDt = DateTime.now();
return false;
}
_lastTapDt = null;
return true;
}
}
Center(
child: TextButton(
child: const Text("点我跳转"),
onPressed: () {
NavigatorUtil().onJumpTo(name: RouterName.splashPage);
},
),
),
项目demo地址链接:https://pan.baidu.com/s/1BqIdxlAO7M9Q9wDyJwamdA
提取码:r6ob
我正在学习如何使用Nokogiri,根据这段代码我遇到了一些问题:require'rubygems'require'mechanize'post_agent=WWW::Mechanize.newpost_page=post_agent.get('http://www.vbulletin.org/forum/showthread.php?t=230708')puts"\nabsolutepathwithtbodygivesnil"putspost_page.parser.xpath('/html/body/div/div/div/div/div/table/tbody/tr/td/div
我有一个Ruby程序,它使用rubyzip压缩XML文件的目录树。gem。我的问题是文件开始变得很重,我想提高压缩级别,因为压缩时间不是问题。我在rubyzipdocumentation中找不到一种为创建的ZIP文件指定压缩级别的方法。有人知道如何更改此设置吗?是否有另一个允许指定压缩级别的Ruby库? 最佳答案 这是我通过查看rubyzip内部创建的代码。level=Zlib::BEST_COMPRESSIONZip::ZipOutputStream.open(zip_file)do|zip|Dir.glob("**/*")d
类classAprivatedeffooputs:fooendpublicdefbarputs:barendprivatedefzimputs:zimendprotecteddefdibputs:dibendendA的实例a=A.new测试a.foorescueputs:faila.barrescueputs:faila.zimrescueputs:faila.dibrescueputs:faila.gazrescueputs:fail测试输出failbarfailfailfail.发送测试[:foo,:bar,:zim,:dib,:gaz].each{|m|a.send(m)resc
很好奇,就使用rubyonrails自动化单元测试而言,你们正在做什么?您是否创建了一个脚本来在cron中运行rake作业并将结果邮寄给您?git中的预提交Hook?只是手动调用?我完全理解测试,但想知道在错误发生之前捕获错误的最佳实践是什么。让我们理所当然地认为测试本身是完美无缺的,并且可以正常工作。下一步是什么以确保他们在正确的时间将可能有害的结果传达给您? 最佳答案 不确定您到底想听什么,但是有几个级别的自动代码库控制:在处理某项功能时,您可以使用类似autotest的内容获得关于哪些有效,哪些无效的即时反馈。要确保您的提
假设我做了一个模块如下:m=Module.newdoclassCendend三个问题:除了对m的引用之外,还有什么方法可以访问C和m中的其他内容?我可以在创建匿名模块后为其命名吗(就像我输入“module...”一样)?如何在使用完匿名模块后将其删除,使其定义的常量不再存在? 最佳答案 三个答案:是的,使用ObjectSpace.此代码使c引用你的类(class)C不引用m:c=nilObjectSpace.each_object{|obj|c=objif(Class===objandobj.name=~/::C$/)}当然这取决于
我正在尝试使用ruby和Savon来使用网络服务。测试服务为http://www.webservicex.net/WS/WSDetails.aspx?WSID=9&CATID=2require'rubygems'require'savon'client=Savon::Client.new"http://www.webservicex.net/stockquote.asmx?WSDL"client.get_quotedo|soap|soap.body={:symbol=>"AAPL"}end返回SOAP异常。检查soap信封,在我看来soap请求没有正确的命名空间。任何人都可以建议我
关闭。这个问题是opinion-based.它目前不接受答案。想要改进这个问题?更新问题,以便editingthispost可以用事实和引用来回答它.关闭4年前。Improvethisquestion我想在固定时间创建一系列低音和高音调的哔哔声。例如:在150毫秒时发出高音调的蜂鸣声在151毫秒时发出低音调的蜂鸣声200毫秒时发出低音调的蜂鸣声250毫秒的高音调蜂鸣声有没有办法在Ruby或Python中做到这一点?我真的不在乎输出编码是什么(.wav、.mp3、.ogg等等),但我确实想创建一个输出文件。
我在我的项目目录中完成了compasscreate.和compassinitrails。几个问题:我已将我的.sass文件放在public/stylesheets中。这是放置它们的正确位置吗?当我运行compasswatch时,它不会自动编译这些.sass文件。我必须手动指定文件:compasswatchpublic/stylesheets/myfile.sass等。如何让它自动运行?文件ie.css、print.css和screen.css已放在stylesheets/compiled。如何在编译后不让它们重新出现的情况下删除它们?我自己编译的.sass文件编译成compiled/t
我想将html转换为纯文本。不过,我不想只删除标签,我想智能地保留尽可能多的格式。为插入换行符标签,检测段落并格式化它们等。输入非常简单,通常是格式良好的html(不是整个文档,只是一堆内容,通常没有anchor或图像)。我可以将几个正则表达式放在一起,让我达到80%,但我认为可能有一些现有的解决方案更智能。 最佳答案 首先,不要尝试为此使用正则表达式。很有可能你会想出一个脆弱/脆弱的解决方案,它会随着HTML的变化而崩溃,或者很难管理和维护。您可以使用Nokogiri快速解析HTML并提取文本:require'nokogiri'h
我想为Heroku构建一个Rails3应用程序。他们使用Postgres作为他们的数据库,所以我通过MacPorts安装了postgres9.0。现在我需要一个postgresgem并且共识是出于性能原因你想要pggem。但是我对我得到的错误感到非常困惑当我尝试在rvm下通过geminstall安装pg时。我已经非常明确地指定了所有postgres目录的位置可以找到但仍然无法完成安装:$envARCHFLAGS='-archx86_64'geminstallpg--\--with-pg-config=/opt/local/var/db/postgresql90/defaultdb/po