美文网首页
Flutter 优雅封装公共页面(CommPage、BasePa

Flutter 优雅封装公共页面(CommPage、BasePa

作者: 钱先生的瞎比比 | 来源:发表于2023-06-28 19:59 被阅读0次

由于Flutter没有页面的概念,而平时开发很多地方需要有页面维度,如以下场景:

  • 页面埋点
  • 感知页面生命周期,回到页面、页面退出前台、退出等
  • 页面通用方法等

直接上逻辑,三句话搞定~ ~嘿嘿,

1.借用Flutter自带的widget push 层级的感知监听 NavigatorObserver.为了方便自定义我们自己实现NavigatorObserver

///  app_route_observer.dart
///
///  Created by iotjin on 2020/11/07.
///  description: 通过路由监听页面出现或消失

import 'package:flutter/material.dart';

class AppRouteObserver {
  /// 创建路由监听(这是实际上的路由监听器)
  static final CustomRouteObserver<ModalRoute<void>> _routeObserver = CustomRouteObserver<ModalRoute<void>>();

  /// 这是个单例
  static final AppRouteObserver _appRouteObserver = AppRouteObserver._internal();

  AppRouteObserver._internal();

  /// 通过单例的get方法获取路由监听器
  CustomRouteObserver<ModalRoute<void>> get routeObserver {
    return _routeObserver;
  }

  factory AppRouteObserver() {
    return _appRouteObserver;
  }
}



class CustomRouteObserver<R extends Route<dynamic>> extends  NavigatorObserver {
  final Map<R, Set<CustomRouteAware>> _listeners = <R, Set<CustomRouteAware>>{};

  @visibleForTesting
  bool debugObservingRoute(R route) {
    late bool contained;
    assert(() {
      contained = _listeners.containsKey(route);
      return true;
    }());
    return contained;
  }

  void subscribe(CustomRouteAware routeAware, R route) {
    assert(routeAware != null);
    assert(route != null);
    final Set<CustomRouteAware> subscribers = _listeners.putIfAbsent(route, () => <CustomRouteAware>{});
    if (subscribers.add(routeAware)) {
      routeAware.didPush();
    }
  }

  void unsubscribe(CustomRouteAware routeAware) {
    assert(routeAware != null);
    final List<R> routes = _listeners.keys.toList();
    for (final R route in routes) {
      final Set<CustomRouteAware>? subscribers = _listeners[route];
      if (subscribers != null) {
        subscribers.remove(routeAware);
        if (subscribers.isEmpty) {
          _listeners.remove(route);
        }
      }
    }
  }

  @override
  void didPop(Route<dynamic> route, Route<dynamic>? previousRoute) {
    if (route is R && previousRoute is R) {
      final List<CustomRouteAware>? previousSubscribers = _listeners[previousRoute]?.toList();

      if (previousSubscribers != null) {
        for (final CustomRouteAware routeAware in previousSubscribers) {
          routeAware.didPopPrevious(route,previousRoute);
        }
      }

      final List<CustomRouteAware>? subscribers = _listeners[route]?.toList();

      if (subscribers != null) {
        for (final CustomRouteAware routeAware in subscribers) {
          routeAware.didPop(route,previousRoute);
        }
      }
    }
  }

  @override
  void didPush(Route<dynamic> route, Route<dynamic>? previousRoute) {
    if (route is R && previousRoute is R) {
      final Set<CustomRouteAware>? previousSubscribers = _listeners[previousRoute];

      if (previousSubscribers != null) {
        for (final CustomRouteAware routeAware in previousSubscribers) {
          routeAware.didPushNext(route,previousRoute);
        }
      }
    }
  }
}
//自定义生命周期方法
abstract class CustomRouteAware {

  void didPopPrevious(Route<dynamic> route, Route<dynamic>? previousRoute) { }

  void didPush() { }

  void didPop(Route<dynamic> route, Route<dynamic>? previousRoute) { }

  void didPushNext(Route<dynamic> route, Route<dynamic>? previousRoute) { }
}


  1. 添加到 MaterialApp 中
MaterialApp(
         ...
          navigatorObservers: [
            AppRouteObserver().routeObserver
          ], 
        );
  1. 然后编辑CommPage,实现CustomRouteObserver
import 'dart:async';
import 'dart:convert';

import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import '../../support/yu_library.dart';
import '../../utils/app_route_observer.dart';
import '../../utils/storageHelper.dart';
abstract class CommonPage extends StatefulWidget {

  static BuildContext? commTopContext;

  const CommonPage({super.key});

  @override
  CommonPageState createState() => getState();

  ///子类实现
  CommonPageState getState();
}
abstract class CommonPageState<T extends CommonPage> extends State<T>  with CustomRouteAware {

   BuildContext? commContext; //当前页面上下文 为空时表明页面已经销毁
   late String commPageName; //页面路径
   dynamic commPageParams; //页面参数
   Map? commPageSettings; // 页面配置信息
   List<StreamSubscription> commEventSubscriptions = []; //页面事件订阅监听,销毁页面时自动取消订阅
   bool commPageClosed = false; // 页面是否已关闭

   bool commNeedSetState = false; // 返回页面时是否需要刷新,一般用于刷新时被mounted阻塞

  //页面打开
  void onStart(){}
  //从上一个页面退回
  void onPopPrevious(Route<dynamic> route, Route<dynamic>? currentRoute){}
  // 打开一个新页面
  void onPushNext(Route<dynamic> route, Route<dynamic>? currentRoute){}
  //页面关闭
  void onClose(Route<dynamic> currentRoute, Route<dynamic>? previousRoute){}
  //页面销毁
  void onDispose(){}

  //页面初始化
  @override
  void initState() {
    super.initState();
    commContext=context;
    commPageClosed=false;
    commNeedSetState = false;
    CommonPage.commTopContext=commContext;
  }

  //页面显示
  @override
  void didPush() {
    commPageName =  ModalRoute.of(commContext!)?.settings.name ??'';
    commPageParams = ModalRoute.of(commContext!)?.settings.arguments ?? {};
    commPageSettings = AppSettingHelper.getSettingsByPageRoute(commPageName);
    CommonPage.commTopContext=commContext;

    onStart();
    if (kDebugMode) print('=======**=======页面打开-onStart  commPageName:$commPageName  commPageClosed:$commPageClosed  commNeedSetState:$commNeedSetState  commEventSubscriptions:${commEventSubscriptions.length}   $commPageParams $commPageSettings   =======**=======');
  }

  //页面依赖关系变化
  @override
  void didChangeDependencies() {
    super.didChangeDependencies();
    /// 路由订阅
    AppRouteObserver().routeObserver.subscribe(this, ModalRoute.of(commContext!)!);
  }

  //返回当前页面
  @override
  void didPopPrevious(Route<dynamic> route, Route<dynamic>? previousRoute) {
    //目标widget不是页面不做处理
    if(route.settings.name?.isNotEmpty == true){
      CommonPage.commTopContext=commContext;
      onPopPrevious(route,previousRoute);
      print('=======**=======返回当前页面-onPopNext  commNeedSetState:$commNeedSetState  commPageName:$commPageName    currentPage:${previousRoute?.settings.name}    ${route.settings.name}   =======**=======');
      if(commNeedSetState){
        commNeedSetState=false;
        commSetState(() { });
      }
    }
  }

 //打开其他页面
  @override
  void didPushNext(Route<dynamic> route, Route<dynamic>? previousRoute) {
    //目标widget不是页面不做处理
    if(route.settings.name?.isNotEmpty == true) {
      onPushNext(route,previousRoute);
      print('=======**=======打开其他页面-onPushNext  commPageName:$commPageName currentPage:${previousRoute?.settings.name}    ${route.settings.name}   =======**=======');
    }
  }

  //页面退出
  @override
  void didPop(Route<dynamic> route, Route<dynamic>? previousRoute) {
    //目标widget不是页面不做处理
    if(route.settings.name?.isNotEmpty == true) {
      commPageClosed = true;
      onClose(route,previousRoute);
      print('=======**=======页面退出-onDetach commPageName:$commPageName currentPage: ${route.settings.name}    ${previousRoute?.settings.name}  =======**=======');
    }
  }

  //页面销毁
  @override
  void dispose() {
    /// 取消路由订阅
    for (var element in commEventSubscriptions) {
      element.cancel();
    }
    commContext=null;
    super.dispose();
    onDispose();
   print('=======**=======页面销毁-onDispose  commPageName:$commPageName  =======**=======');
    AppRouteObserver().routeObserver.unsubscribe(this);
  }

  //当前页面是否位于前台
  bool commCurrentIsTop(){
   return   CommonPage.commTopContext == commContext;
  }

//page 上所有的setState可以走这里,防止异步调用报错
  void commSetState(VoidCallback fn){
   print("commSetState mounted:$mounted  commPageClosed:$commPageClosed  ${DateTime.now().microsecondsSinceEpoch}");
   if(mounted) {
     setState(fn);
   }else{
     commNeedSetState=true;
   }
  }
}


可以看出我们区分页面的判断 route.settings.name ,所以我们页面push的时候务必要设置这个值,可以封装一个通用的打开页面的方法,例如

  //打开新页面
  static Future<T?> push<T>(BuildContext context, String routeName, {Map<dynamic, dynamic>? arguments, bool isReplaced = false, bool pushAndRemoveUntil = false}) async {

    final Function? buildPage = routes[routeName];

    LogHelper.d("push: $routeName $arguments ");

    if (buildPage == null) {
      return await Navigator.push(context, CupertinoPageRoute(builder: (_) => const NotFoundPage()));
    }

    late Widget page ;
    
    try{
      page = (arguments == null)? buildPage(context) : buildPage(context, arguments: arguments) ;
    } catch(e){
      page= buildPage(context);
    }

    if (page is! CommonPage) {
      LogHelper.e("!!!!! $routeName页面需继承CommPage,请及时处理 !!!!");
      if(AppConfig.isSitEnv) ToastHelper.showToast("$routeName 没有继承CommPage,请及时处理!");
    }
    if (isReplaced) {
      //替换页面
      return await Navigator.pushReplacement(
          context,
          CupertinoPageRoute(
              builder: (context) => page,
              settings: RouteSettings(name: routeName,arguments:arguments)));
    } else if (pushAndRemoveUntil) {
      //跳转并关闭所有页面
      return await Navigator.pushAndRemoveUntil(
          context,
          CupertinoPageRoute(
              builder: (context) => page,
              settings: RouteSettings(name: routeName,arguments:arguments)),
          (route) => false);
    } else {
      return await Navigator.push(
          context,
          CupertinoPageRoute(
              builder: (context) => page,
              settings: RouteSettings(name: routeName,arguments:arguments)));
    }
  }

到这里大功告成,只需要在页面的widget种继承我们的Commpage即可。

class LoginPage extends CommonPage {
  const LoginPage({super.key});

  @override
  CommonPageState<CommonPage> getState() => _LoginPageState();
}

class _LoginPageState extends CommonPageState<LoginPage> {

  @override
  void onPopPrevious(Route route, Route? currentRoute) {
    // TODO: implement onPopPrevious
    super.onPopPrevious(route, currentRoute);
  }

  @override
  void onPushNext(Route route, Route? currentRoute) {
    // TODO: implement onPushNext
    super.onPushNext(route, currentRoute);
  }

}

相关文章

  • WPF

    样式:页面设计,可以将公共样式封装抽象出来。关键字是 引用:StaticResource封装:Setter封装对象...

  • Flutter 封装 dio,支持 Restful Api

    背景 Dio 是 Flutter 社区开源的网络请求库。 为什么要封装 dio? 做一些公共处理。 要做哪些公共处...

  • PageObject模式

    PageObject的核心思想是封装:把页面常用服务封装成函数 封装的好处是复用、逻辑清晰 1.公共方法一定要代表...

  • 集成-现有App集成Flutter(iOS篇)

    一、混合开发场景原生页面可以打开Flutter页面 Flutter页面可以打开原生页面 二、Flutter混合开发...

  • 微信小程序公共页面封装

    小程序在公共全局的基础方法和变量这一块并不支持。已知的全局变量存取也不是很方便。 本文章能解决的问题: 实现在所有...

  • Flutter混合项目实战

    一、在Native页面跳转Flutter页面 Adding a Flutter screen to an iOS ...

  • Flutter 混合开发(Android)路由开发

    Flutter交互 原生跳Flutter页面通过路由传参 原生跳到Flutter页面,由于Flutter没有方法可...

  • Html 实现include 模块引入

    平时在html中,我们通常将项目公共部分如 header 、footer、menu等独立封装,而不是每个页面都去c...

  • 工作总结4.22

    封装可用公共组件, 完成作业报告分享页面的制作, 接入接口数据, 修复了多页面同时打包的问题 下一步 准备微信开发...

  • 如何将 Flutter 优雅的嵌入现有应用

    如何将 Flutter 优雅的嵌入现有应用如何将 Flutter 优雅的嵌入现有应用

网友评论

      本文标题:Flutter 优雅封装公共页面(CommPage、BasePa

      本文链接:https://www.haomeiwen.com/subject/aaooydtx.html