├── .gitignore ├── .metadata ├── CHANGELOG.md ├── LICENSE ├── README.md ├── ReadMe ├── iOS研发.png └── 代码结构.png ├── android ├── .gitignore ├── build.gradle ├── gradle.properties ├── gradle │ └── wrapper │ │ └── gradle-wrapper.properties ├── settings.gradle └── src │ └── main │ ├── AndroidManifest.xml │ └── kotlin │ ├── io │ └── flutter │ │ └── embedding │ │ └── android │ │ ├── DFlutterActivity.java │ │ ├── DFlutterPageDelegate.java │ │ └── DViewUtils.java │ └── tal │ └── com │ └── d_stack │ ├── DStack.java │ ├── DStackPlugin.kt │ ├── action │ ├── DActionManager.java │ └── DOperationManager.java │ ├── channel │ └── DStackMethodHandler.java │ ├── lifecycle │ ├── PageLifecycleManager.java │ ├── PageModel.java │ └── PageState.java │ ├── node │ ├── DNode.java │ ├── DNodeManager.java │ ├── DNodeResponse.java │ └── constants │ │ ├── DNodeActionType.java │ │ └── DNodePageType.java │ ├── observer │ ├── DStackActivityManager.java │ ├── DStackLifecycleObserver.java │ └── FilterActivityManager.java │ ├── router │ ├── INativeRouter.java │ └── INodeOperation.java │ └── utils │ ├── DLog.java │ └── DStackUtils.java ├── example ├── .gitignore ├── .metadata ├── README.md ├── android │ ├── .gitignore │ ├── app │ │ ├── build.gradle │ │ └── src │ │ │ ├── debug │ │ │ └── AndroidManifest.xml │ │ │ ├── main │ │ │ ├── AndroidManifest.xml │ │ │ ├── kotlin │ │ │ │ └── tal │ │ │ │ │ └── com │ │ │ │ │ └── d_stack_example │ │ │ │ │ ├── DStackApplication.java │ │ │ │ │ ├── FlutterContainerActivity.kt │ │ │ │ │ ├── NativeOneActivity.java │ │ │ │ │ ├── NativeThreeActivity.java │ │ │ │ │ └── NativeTwoActivity.java │ │ │ └── res │ │ │ │ ├── drawable │ │ │ │ └── launch_background.xml │ │ │ │ ├── layout │ │ │ │ ├── layout_one.xml │ │ │ │ ├── layout_three.xml │ │ │ │ └── layout_two.xml │ │ │ │ ├── mipmap-hdpi │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-mdpi │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-xhdpi │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-xxhdpi │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-xxxhdpi │ │ │ │ └── ic_launcher.png │ │ │ │ ├── values │ │ │ │ ├── color.xml │ │ │ │ └── styles.xml │ │ │ │ └── xml │ │ │ │ └── network_security_config.xml │ │ │ └── profile │ │ │ └── AndroidManifest.xml │ ├── build.gradle │ ├── gradle.properties │ ├── gradle │ │ └── wrapper │ │ │ └── gradle-wrapper.properties │ └── settings.gradle ├── ios │ ├── .gitignore │ ├── Flutter │ │ ├── .last_build_id │ │ ├── AppFrameworkInfo.plist │ │ ├── Debug.xcconfig │ │ └── Release.xcconfig │ ├── Podfile │ ├── Podfile.lock │ ├── Runner.xcodeproj │ │ ├── project.pbxproj │ │ ├── project.xcworkspace │ │ │ └── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ └── xcschemes │ │ │ └── Runner.xcscheme │ ├── Runner.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ └── IDEWorkspaceChecks.plist │ └── Runner │ │ ├── AppDelegate.h │ │ ├── AppDelegate.m │ │ ├── Assets.xcassets │ │ ├── AppIcon.appiconset │ │ │ ├── Contents.json │ │ │ ├── Icon-App-1024x1024@1x.png │ │ │ ├── Icon-App-20x20@1x.png │ │ │ ├── Icon-App-20x20@2x.png │ │ │ ├── Icon-App-20x20@3x.png │ │ │ ├── Icon-App-29x29@1x.png │ │ │ ├── Icon-App-29x29@2x.png │ │ │ ├── Icon-App-29x29@3x.png │ │ │ ├── Icon-App-40x40@1x.png │ │ │ ├── Icon-App-40x40@2x.png │ │ │ ├── Icon-App-40x40@3x.png │ │ │ ├── Icon-App-60x60@2x.png │ │ │ ├── Icon-App-60x60@3x.png │ │ │ ├── Icon-App-76x76@1x.png │ │ │ ├── Icon-App-76x76@2x.png │ │ │ └── Icon-App-83.5x83.5@2x.png │ │ └── LaunchImage.imageset │ │ │ ├── Contents.json │ │ │ ├── LaunchImage.png │ │ │ ├── LaunchImage@2x.png │ │ │ ├── LaunchImage@3x.png │ │ │ └── README.md │ │ ├── Base.lproj │ │ ├── LaunchScreen.storyboard │ │ └── Main.storyboard │ │ ├── CustomViewController.h │ │ ├── CustomViewController.m │ │ ├── DStackTestCase.h │ │ ├── DStackTestCase.m │ │ ├── DStackViewController.h │ │ ├── DStackViewController.m │ │ ├── DemoFlutterViewController.h │ │ ├── DemoFlutterViewController.m │ │ ├── FiveViewController.h │ │ ├── FiveViewController.m │ │ ├── FourViewController.h │ │ ├── FourViewController.m │ │ ├── HomeViewController.h │ │ ├── HomeViewController.m │ │ ├── Info.plist │ │ ├── SecondViewController.h │ │ ├── SecondViewController.m │ │ ├── SixViewController.h │ │ ├── SixViewController.m │ │ ├── ThirdViewController.h │ │ ├── ThirdViewController.m │ │ └── main.m ├── lib │ ├── main.dart │ ├── page_widgets.dart │ ├── test_case.dart │ └── will_pop_scope_route.dart ├── pubspec.lock ├── pubspec.yaml └── test │ └── widget_test.dart ├── ios ├── .gitignore ├── Assets │ └── .gitkeep ├── Classes │ ├── Node │ │ ├── DActionManager.h │ │ ├── DActionManager.m │ │ ├── DNode.h │ │ ├── DNode.m │ │ ├── DNodeManager.h │ │ └── DNodeManager.m │ └── Stack │ │ ├── DFlutterViewController.h │ │ ├── DFlutterViewController.m │ │ ├── DNavigator.h │ │ ├── DNavigator.m │ │ ├── DStack.h │ │ ├── DStack.m │ │ ├── DStackPlugin.h │ │ ├── DStackPlugin.m │ │ └── DStackProvider.h └── d_stack.podspec ├── lib ├── channel │ └── dchannel.dart ├── constant │ └── constant_config.dart ├── d_stack.dart ├── navigator │ ├── dnavigator_gesture_observer.dart │ ├── dnavigator_manager.dart │ └── node_entity.dart ├── observer │ ├── d_node_observer.dart │ └── life_cycle_observer.dart └── widget │ ├── home_widget.dart │ └── page_route.dart ├── pubspec.lock ├── pubspec.yaml └── test └── d_stack_test.dart /.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | 12 | # IntelliJ related 13 | *.iml 14 | *.ipr 15 | *.iws 16 | .idea/ 17 | 18 | # Visual Studio Code related 19 | .vscode/ 20 | 21 | # Flutter/Dart/Pub related 22 | **/doc/api/ 23 | .dart_tool/ 24 | .flutter-plugins 25 | .packages 26 | .pub-cache/ 27 | .pub/ 28 | /build/ 29 | 30 | # Android related 31 | **/android/**/gradle-wrapper.jar 32 | **/android/.gradle 33 | **/android/captures/ 34 | **/android/gradlew 35 | **/android/gradlew.bat 36 | **/android/local.properties 37 | **/android/**/GeneratedPluginRegistrant.java 38 | 39 | # iOS/XCode related 40 | **/ios/**/*.mode1v3 41 | **/ios/**/*.mode2v3 42 | **/ios/**/*.moved-aside 43 | **/ios/**/*.pbxuser 44 | **/ios/**/*.perspectivev3 45 | **/ios/**/*sync/ 46 | **/ios/**/.sconsign.dblite 47 | **/ios/**/.tags* 48 | **/ios/**/.vagrant/ 49 | **/ios/**/DerivedData/ 50 | **/ios/**/Icon? 51 | **/ios/**/Pods/ 52 | **/ios/**/.symlinks/ 53 | **/ios/**/profile 54 | **/ios/**/xcuserdata 55 | **/ios/.generated/ 56 | **/ios/Flutter/App.framework 57 | **/ios/Flutter/Flutter.framework 58 | **/ios/Flutter/Generated.xcconfig 59 | **/ios/Flutter/app.flx 60 | **/ios/Flutter/app.zip 61 | **/ios/Flutter/flutter_assets/ 62 | **/ios/ServiceDefinitions.json 63 | **/ios/Runner/GeneratedPluginRegistrant.* 64 | 65 | # Exceptions to above rules. 66 | !**/ios/**/default.mode1v3 67 | !**/ios/**/default.mode2v3 68 | !**/ios/**/default.pbxuser 69 | !**/ios/**/default.perspectivev3 70 | !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages 71 | -------------------------------------------------------------------------------- /.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled and should not be manually edited. 5 | 6 | version: 7 | revision: 27321ebbad34b0a3fafe99fac037102196d655ff 8 | channel: stable 9 | 10 | project_type: plugin 11 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 1.3.4+3-nullsafety 2 | * Android侧修改判断appStart条件 3 | ## 1.3.4+2-nullsafety 4 | * Android侧添加打开透明Flutter控制器方法 5 | ## 1.3.4+1-nullsafety 6 | * iOS侧加入防止同一节点重复入栈的容错 7 | ## 1.3.4-nullsafety 8 | * 支持空安全 9 | ## 1.3.3+1 10 | * android修复根节点返回键问题 11 | ## 1.3.3 12 | * 添加pushAndRemoveUntil方法,iOS修复bug 13 | ## 1.3.2 14 | * Android支持flutter2.0 15 | ## 1.3.1+2 16 | * 修复iOS侧gesture可能导致的数组越界 17 | ## 1.3.1+1 18 | * 修复iOS pop参数传递不能传递问题 19 | ## 1.3.1 20 | * 修复android临近节点判断问题 21 | ## 1.3.0 22 | * 解决白屏问题 23 | * flutter支持自定义进场动画手势返回 24 | * flutter侧dialog入栈管理 25 | * 安卓、iOS侧节点重构 26 | ## 1.2.8+4 27 | * 暴露是否开启动画属性,修复iOS13以上dismiss crash 28 | ## 1.2.8+3 29 | * iOS优化pop手势返回和系统类判断 30 | ## 1.2.8+2 31 | * iOS解决crash 32 | ## 1.2.8+1 33 | * iOS修复bug 34 | ## 1.2.8 35 | * DStack增加过滤activity功能 36 | ## 1.2.7 37 | * 修复短间隔快速关闭两个activity引起的节点错乱 38 | ## 1.2.6 39 | * 增加微信微博qq登陆第三方Activity的过滤 40 | ## 1.2.5 41 | * 修复iOS手势页面滑动取消问题 42 | ## 1.2.4 43 | * 修复iOS flutter打开native传参 44 | ## 1.2.3 45 | * 修复android oppo推送bug,iOS13 dismiss兼容,开放present及自定义动画 46 | ## 1.2.2 47 | * 优化iOS节点去重 48 | ## 1.2.1 49 | * 修复android华为推送bug 50 | ## 1.2.0 51 | * 修复android 出栈bug 52 | ## 1.1.9 53 | * 修复android 第三方activity bug 54 | ## 1.1.8 55 | * 修复ios bug 56 | ## 1.1.7 57 | * 修复ios bug 58 | ## 1.1.6 59 | * 修复可能的android类名错误 60 | ## 1.1.5 61 | * 修复 iOS的homePage不能刷新的异常 62 | ## 1.1.4 63 | * iOS新增flutterViewcontroller 的回调 64 | ## 1.1.3 65 | * 修复错误tag 66 | ## 1.1.2 67 | * 修复生命周期回调bug 68 | ## 1.1.1 69 | * 添加flutter侧获取节点列表功能 70 | ## 1.1.0 71 | * 添加生命周期监听功能,android修改集成方式,现在更加简单了 72 | ## 1.0.0 73 | ## 0.0.1 74 | * TODO: Describe initial release. 75 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2020 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 前言 2 | 3 | ![img](https://magicmadegithub.github.io/img/dstacklogo.png) 4 | 5 | Flutter是谷歌的移动UI框架,可以快速在iOS和Android上构建高质量的原生用户界面。 Flutter可以与现有的代码一起工作。 6 | 7 | DStack是为了解决在使用Flutter进行混合开发时,不同类型的页面之间互相跳转时的统一管理和处理。 8 | 9 | 2020年5月,我们对DStack进行整理、封装和推广。 10 | 11 | 2020年8月,集团内部开源,9月,外部开源,以此共建交流。 12 | 13 | 开源并不是我们的终点,我们希望能有更多小伙伴和我们共建DStack,我们一起为Flutter社区做更多的贡献。 14 | 15 | 16 | 17 | ## DStack详细文档 18 | 19 | [中文文档](https://www.yuque.com/tal-tech/ag1kaf) 20 | 21 | 22 | 23 | ## 设计方向 24 | 25 | DStack是基于**节点**进行管理的,**使用简单,易于集成,性能优秀**的混合开发框架。 26 | 27 | - **节点管理**:不同类型页面抽象成节点这种数据结构,便于后期的扩展 28 | - **引擎复用**:利用Flutter引擎复用机制,框架内存性能优秀 29 | - **简单实用**:追求集成和使用简单,对原有工程改动小 30 | - **持续积累**:紧跟Flutter团队每次版本升级,解决新问题,尝试新思路,不断优化 31 | - **开源心态**:开放公开,接受任何源码的贡献,但有比较严格的代码审核 32 | 33 | ## 功能简介 34 | 35 | - 混合页面之间随意跳转 36 | 37 | - 混合页面一致的生命周期管理 38 | 39 | - 页面间数据传递,回传等 40 | 41 | - iOS侧滑返回和android返回键返回 42 | 43 | - 提供一致的页面路由方案 44 | 45 | 46 | 47 | ## 发行版本介绍 48 | 49 | DStack目前有一个版本 50 | 51 | - master分支为tag1.3.1+2 稳定版本 52 | 53 | ### 以下为1.3.1+2 版本安装 54 | 55 | #### 1.引入 56 | 57 | 在 pubspec.yaml 文件中添加依赖: 58 | 59 | ```dart 60 | dependencies: 61 | d_stack: ^1.3.1+2 62 | ``` 63 | 64 | #### 2.安装 65 | 66 | 命令行下执行: 67 | 68 | **flutter pub get** 69 | 70 | ## 软件作者贡献列表 71 | @xiaoyuyouer @whqfor @caven775 72 | 73 | (其他贡献者、请详见文档鸣谢) 74 | 75 | ## 合作伙伴 76 | 77 | ![xes1v1.jpeg](https://magicmadegithub.github.io/img/1v1logo.png) 78 | 79 | ## 联系我们 80 | 81 | 82 | 83 | issue: https://github.com/tal-tech/d_stack/issues 84 | 85 | -------------------------------------------------------------------------------- /ReadMe/iOS研发.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tal-tech/d_stack/c10b77f8f13b1fb9de25daad99b25a50cafebe52/ReadMe/iOS研发.png -------------------------------------------------------------------------------- /ReadMe/代码结构.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tal-tech/d_stack/c10b77f8f13b1fb9de25daad99b25a50cafebe52/ReadMe/代码结构.png -------------------------------------------------------------------------------- /android/.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/workspace.xml 5 | /.idea/libraries 6 | .DS_Store 7 | /build 8 | /captures 9 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | group 'tal.com.d_stack' 2 | version '1.0-SNAPSHOT' 3 | 4 | buildscript { 5 | ext.kotlin_version = '1.3.70' 6 | repositories { 7 | google() 8 | jcenter() 9 | } 10 | 11 | dependencies { 12 | classpath 'com.android.tools.build:gradle:3.6.1' 13 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 14 | } 15 | } 16 | 17 | rootProject.allprojects { 18 | repositories { 19 | google() 20 | jcenter() 21 | } 22 | } 23 | 24 | apply plugin: 'com.android.library' 25 | apply plugin: 'kotlin-android' 26 | 27 | android { 28 | compileSdkVersion 29 29 | 30 | sourceSets { 31 | main.java.srcDirs += 'src/main/kotlin' 32 | } 33 | defaultConfig { 34 | minSdkVersion 21 35 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 36 | } 37 | lintOptions { 38 | disable 'InvalidPackage' 39 | } 40 | } 41 | 42 | dependencies { 43 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 44 | implementation 'androidx.appcompat:appcompat:1.2.0-alpha03' 45 | } 46 | -------------------------------------------------------------------------------- /android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | android.enableR8=true 3 | android.useAndroidX=true 4 | android.enableJetifier=true 5 | -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Fri Mar 27 14:56:17 CST 2020 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.4-all.zip 7 | -------------------------------------------------------------------------------- /android/settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'd_stack' 2 | -------------------------------------------------------------------------------- /android/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | -------------------------------------------------------------------------------- /android/src/main/kotlin/io/flutter/embedding/android/DViewUtils.java: -------------------------------------------------------------------------------- 1 | // Copyright 2013 The Flutter Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style license that can be 3 | // found in the LICENSE file. 4 | 5 | package io.flutter.embedding.android; 6 | 7 | import android.app.Activity; 8 | import android.content.Context; 9 | import android.content.ContextWrapper; 10 | import android.os.Build; 11 | import android.view.View; 12 | 13 | public final class DViewUtils { 14 | /** 15 | * Retrieves the {@link Activity} from a given {@link Context}. 16 | * 17 | *

This method will recursively traverse up the context chain if it is a {@link ContextWrapper} 18 | * until it finds the first instance of the base context that is an {@link Activity}. 19 | */ 20 | public static Activity getActivity(Context context) { 21 | if (context == null) { 22 | return null; 23 | } 24 | if (context instanceof Activity) { 25 | return (Activity) context; 26 | } 27 | if (context instanceof ContextWrapper) { 28 | // Recurse up chain of base contexts until we find an Activity. 29 | return getActivity(((ContextWrapper) context).getBaseContext()); 30 | } 31 | return null; 32 | } 33 | 34 | /** 35 | * Generates a view id. 36 | * 37 | *

In API level 17 and above, this ID is unique. Below 17, the fallback id is used instead. 38 | * 39 | * @param fallbackId the fallback id. 40 | * @return the view id. 41 | */ 42 | public static int generateViewId(int fallbackId) { 43 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { 44 | return View.generateViewId(); 45 | } 46 | return fallbackId; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /android/src/main/kotlin/tal/com/d_stack/DStackPlugin.kt: -------------------------------------------------------------------------------- 1 | package tal.com.d_stack 2 | 3 | import androidx.annotation.NonNull; 4 | import io.flutter.embedding.engine.plugins.FlutterPlugin 5 | import io.flutter.plugin.common.MethodCall 6 | import io.flutter.plugin.common.MethodChannel 7 | import io.flutter.plugin.common.MethodChannel.MethodCallHandler 8 | import io.flutter.plugin.common.MethodChannel.Result 9 | import io.flutter.plugin.common.PluginRegistry.Registrar 10 | import tal.com.d_stack.channel.DStackMethodHandler 11 | 12 | /** 13 | * DStackPlugin 14 | */ 15 | public class DStackPlugin : FlutterPlugin, MethodCallHandler { 16 | override fun onAttachedToEngine(@NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) { 17 | val channel = MethodChannel(flutterPluginBinding.getFlutterEngine().getDartExecutor(), "d_stack") 18 | channel.setMethodCallHandler(DStackMethodHandler()) 19 | } 20 | 21 | // This static function is optional and equivalent to onAttachedToEngine. It supports the old 22 | // pre-Flutter-1.12 Android projects. You are encouraged to continue supporting 23 | // plugin registration via this function while apps migrate to use the new Android APIs 24 | // post-flutter-1.12 via https://flutter.dev/go/android-project-migration. 25 | // 26 | // It is encouraged to share logic between onAttachedToEngine and registerWith to keep 27 | // them functionally equivalent. Only one of onAttachedToEngine or registerWith will be called 28 | // depending on the user's project. onAttachedToEngine or registerWith must both be defined 29 | // in the same class. 30 | companion object { 31 | @JvmStatic 32 | fun registerWith(registrar: Registrar) { 33 | val channel = MethodChannel(registrar.messenger(), "d_stack") 34 | channel.setMethodCallHandler(DStackPlugin()) 35 | } 36 | } 37 | 38 | override fun onMethodCall(@NonNull call: MethodCall, @NonNull result: Result) { 39 | if (call.method == "getPlatformVersion") { 40 | result.success("Android ${android.os.Build.VERSION.RELEASE}") 41 | } else { 42 | result.notImplemented() 43 | } 44 | } 45 | 46 | override fun onDetachedFromEngine(@NonNull binding: FlutterPlugin.FlutterPluginBinding) { 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /android/src/main/kotlin/tal/com/d_stack/action/DActionManager.java: -------------------------------------------------------------------------------- 1 | package tal.com.d_stack.action; 2 | 3 | import android.os.Handler; 4 | 5 | import java.lang.ref.WeakReference; 6 | import java.util.ArrayList; 7 | import java.util.List; 8 | import java.util.Map; 9 | 10 | import tal.com.d_stack.DStack; 11 | import tal.com.d_stack.channel.DStackMethodHandler; 12 | import tal.com.d_stack.node.DNode; 13 | import tal.com.d_stack.node.DNodeManager; 14 | import tal.com.d_stack.node.DNodeResponse; 15 | import tal.com.d_stack.node.constants.DNodeActionType; 16 | import tal.com.d_stack.node.constants.DNodePageType; 17 | import tal.com.d_stack.observer.DStackActivityManager; 18 | import tal.com.d_stack.utils.DStackUtils; 19 | 20 | /** 21 | * 页面实际跳转动作管理 22 | */ 23 | public class DActionManager { 24 | 25 | /** 26 | * 打开页面 27 | */ 28 | public static void push(DNode node) { 29 | enterPageWithNode(node, DNodeActionType.DNodeActionTypePush, node.isAnimated()); 30 | DOperationManager.operation(node); 31 | } 32 | 33 | /** 34 | * 返回当前页面 35 | */ 36 | public static void pop(DNode node) { 37 | closePageWithNode(node, DNodeActionType.DNodeActionTypePop, node.isAnimated()); 38 | DOperationManager.operation(node); 39 | } 40 | 41 | /** 42 | * 返回指定页面 43 | */ 44 | public static void popTo(DNode node, List removeNodes) { 45 | closePageWithNodes(removeNodes, DNodeActionType.DNodeActionTypePopTo, node.isAnimated()); 46 | DOperationManager.operation(node); 47 | } 48 | 49 | /** 50 | * 返回根页面 51 | */ 52 | public static void popToRoot(DNode node, List removeNodes) { 53 | closePageWithNodes(removeNodes, DNodeActionType.DNodeActionTypePopToRoot, node.isAnimated()); 54 | DOperationManager.operation(node); 55 | } 56 | 57 | /** 58 | * 返回指定模块页面 59 | */ 60 | public static void popSkip(DNode node, List removeNodes) { 61 | closePageWithNodes(removeNodes, DNodeActionType.DNodeActionTypePopSkip, node.isAnimated()); 62 | DOperationManager.operation(node); 63 | } 64 | 65 | /** 66 | * 替换页面 67 | */ 68 | public static void replace(DNode node) { 69 | DOperationManager.operation(node); 70 | } 71 | 72 | /** 73 | * 手势返回页面 74 | */ 75 | public static void gesture(DNode node) { 76 | DOperationManager.operation(node); 77 | } 78 | 79 | /** 80 | * 打开页面,根据页面类型做不同处理 81 | */ 82 | private static void enterPageWithNode(DNode node, String action, boolean animated) { 83 | if (node.isFromFlutter()) { 84 | // 来自flutter消息通道的node 85 | if (node.getPageType().equals(DNodePageType.DNodePageTypeNative)) { 86 | // 打开native页面 87 | // flutter打开native页面,回传给用户侧处理 88 | DStack.getInstance().getNativeRouter().openContainer( 89 | node.getTarget(), 90 | node.getParams() 91 | ); 92 | } else if (node.getPageType().equals(DNodePageType.DNodePageTypeFlutter)) { 93 | // 打开flutter页面 94 | // 给当前flutter节点设置对应的activity 95 | DNode currentNode = DNodeManager.getInstance().getCurrentNode(); 96 | currentNode.setActivity(new WeakReference(DStackActivityManager.getInstance().getTopActivity())); 97 | } 98 | } else { 99 | // 只是来自native的node,并且是需要打开Flutter页面的,发消息至flutter,打开页面 100 | if (node.getPageType().equals(DNodePageType.DNodePageTypeFlutter)) { 101 | if (node.isRootPage()) { 102 | //flutter根节点,不发通知给flutter 103 | return; 104 | } 105 | DNodeResponse nodeResponse = DNodeManager.getInstance().createNodeResponse(node); 106 | DStackMethodHandler.sendNode(nodeResponse, action, animated); 107 | } 108 | } 109 | } 110 | 111 | /** 112 | * 关闭页面 113 | */ 114 | private static void closePageWithNode(DNode node, String action, boolean animated) { 115 | if (node.getPageType().equals(DNodePageType.DNodePageTypeFlutter)) { 116 | if (node.isRootPage() || node.isHomePage()) { 117 | //根节点不移除栈,去判断临界状态 118 | DNodeManager.getInstance().handleNeedRemoveFlutterNode(node); 119 | return; 120 | } 121 | //pop的是flutter页面,发消息至flutter 122 | DNodeResponse nodeResponse = DNodeManager.getInstance().createNodeResponse(node); 123 | DStackMethodHandler.sendNode(nodeResponse, action, animated); 124 | } 125 | } 126 | 127 | /** 128 | * 关闭已移除节点集合的所有页面,包括native和flutter 129 | */ 130 | private static void closePageWithNodes(List nodes, final String action, final boolean animated) { 131 | final List> flutterNodes = new ArrayList<>(); 132 | List nativeNodes = new ArrayList<>(); 133 | int size = nodes.size(); 134 | for (int i = 0; i < size; i++) { 135 | DNode loopNode = nodes.get(i); 136 | if (loopNode.getPageType().equals(DNodePageType.DNodePageTypeFlutter)) { 137 | if (!loopNode.isHomePage()) { 138 | DNodeResponse nodeResponse = DNodeManager.getInstance().createNodeResponse(loopNode); 139 | flutterNodes.add(nodeResponse.toMap()); 140 | } 141 | } else { 142 | nativeNodes.add(loopNode.getIdentifier()); 143 | } 144 | } 145 | //处理需要关闭的控制器 146 | final DNode currentNode = DNodeManager.getInstance().getCurrentNode(); 147 | DStackActivityManager.getInstance().closeActivityWithNode(currentNode); 148 | //发送消息给flutter侧处理 149 | //为了保证native侧页面顺利关闭,此处需要延迟一些时间给flutter发消息 150 | //不然会引起surfaceView的绘制问题 151 | new Handler().postDelayed(new Runnable() { 152 | @Override 153 | public void run() { 154 | DStackMethodHandler.sendNode(flutterNodes, 155 | action, 156 | animated); 157 | } 158 | }, 150); 159 | } 160 | 161 | /** 162 | * 替换当前页面 163 | */ 164 | public static void replace(DNode node, String action, boolean animated) { 165 | if (node.isFromFlutter()) { 166 | if (node.getPageType().equals(DNodePageType.DNodePageTypeFlutter)) { 167 | DNodeResponse nodeResponse = DNodeManager.getInstance().createNodeResponse(node); 168 | DStackMethodHandler.sendNode(nodeResponse, action, animated); 169 | } 170 | } 171 | } 172 | } -------------------------------------------------------------------------------- /android/src/main/kotlin/tal/com/d_stack/action/DOperationManager.java: -------------------------------------------------------------------------------- 1 | package tal.com.d_stack.action; 2 | 3 | import java.util.HashMap; 4 | import java.util.Map; 5 | 6 | import tal.com.d_stack.DStack; 7 | import tal.com.d_stack.channel.DStackMethodHandler; 8 | import tal.com.d_stack.node.DNode; 9 | import tal.com.d_stack.node.DNodeManager; 10 | import tal.com.d_stack.node.DNodeResponse; 11 | import tal.com.d_stack.node.constants.DNodeActionType; 12 | import tal.com.d_stack.node.constants.DNodePageType; 13 | import tal.com.d_stack.router.INodeOperation; 14 | import tal.com.d_stack.utils.DLog; 15 | 16 | /** 17 | * 节点操作行为记录 18 | */ 19 | public class DOperationManager { 20 | 21 | private static Map> popParams = new HashMap<>(); 22 | 23 | public static void operation(DNode node) { 24 | if (!DStack.getInstance().isOpenNodeOperation()) { 25 | return; 26 | } 27 | if (node.getAction().equals(DNodeActionType.DNodeActionTypePush)) { 28 | //页面如果是push行为 29 | if (node.isFromFlutter()) { 30 | //如果是来自flutter的消息 31 | if (node.getPageType().equals(DNodePageType.DNodePageTypeNative)) { 32 | //如果是打开一个native页面,不记录操作,等activity的onCreate方法 33 | return; 34 | } 35 | 36 | } 37 | } 38 | if (node.getAction().equals(DNodeActionType.DNodeActionTypePop)) { 39 | //页面如果是pop行为 40 | if (!node.isBoundary()) { 41 | //不是临界页面 42 | if (node.getPageType().equals(DNodePageType.DNodePageTypeFlutter)) { 43 | //操作一个flutter页面,不记录操作,等didPop消息 44 | //如果返回待参数,需要保留 45 | if (node.getParams() != null && !node.getParams().isEmpty()) { 46 | popParams.put(node.getIdentifier(), node.getParams()); 47 | } 48 | return; 49 | } 50 | } 51 | } 52 | DNodeResponse nodeResponse = DNodeManager.getInstance().createNodeResponse(node); 53 | DLog.logE("$$$$$节点操作$$$$$"); 54 | DLog.logE(nodeResponse.action + "-----" + nodeResponse.target); 55 | DLog.logE("$$$$$节点操作$$$$$"); 56 | if (node.getPageType().equals(DNodePageType.DNodePageTypeFlutter)) { 57 | if (nodeResponse.params == null || nodeResponse.params.isEmpty()) { 58 | if (popParams.containsKey(nodeResponse.identifier)) { 59 | nodeResponse.params = popParams.get(nodeResponse.identifier); 60 | popParams.clear(); 61 | } 62 | } 63 | } 64 | 65 | DStackMethodHandler.sendNodeOperation(nodeResponse); 66 | INodeOperation nodeOperation = DStack.getInstance().getNodeOperation(); 67 | if (nodeOperation != null) { 68 | nodeOperation.operationNode(nodeResponse); 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /android/src/main/kotlin/tal/com/d_stack/lifecycle/PageLifecycleManager.java: -------------------------------------------------------------------------------- 1 | package tal.com.d_stack.lifecycle; 2 | 3 | 4 | import java.util.List; 5 | 6 | import tal.com.d_stack.channel.DStackMethodHandler; 7 | import tal.com.d_stack.node.DNode; 8 | import tal.com.d_stack.node.DNodeManager; 9 | import tal.com.d_stack.node.constants.DNodeActionType; 10 | import tal.com.d_stack.node.constants.DNodePageType; 11 | import tal.com.d_stack.utils.DLog; 12 | 13 | /** 14 | * 页面状态管理 15 | */ 16 | public class PageLifecycleManager { 17 | 18 | /** 19 | * app启动 20 | */ 21 | public static void appCreate() { 22 | DLog.logD("appCreate"); 23 | PageModel pageModel = new PageModel(); 24 | pageModel.setState(PageState.APP_CREATE); 25 | DNode node = DNodeManager.getInstance().getCurrentNode(); 26 | if (node == null) { 27 | pageModel.setCurrentPageRoute("/"); 28 | pageModel.setCurrentPageType(DNodePageType.DNodePageTypeFlutter); 29 | } else { 30 | pageModel.setCurrentPageRoute(node.getTarget()); 31 | pageModel.setCurrentPageType(DNodePageType.DNodePageTypeNative); 32 | } 33 | DStackMethodHandler.sendAppLifeCircle(pageModel); 34 | } 35 | 36 | /** 37 | * app进入前台 38 | */ 39 | public static void appForeground() { 40 | DLog.logD("appForeground"); 41 | PageModel pageModel = new PageModel(); 42 | pageModel.setState(PageState.APP_FOREGROUND); 43 | DNode node = DNodeManager.getInstance().getCurrentNode(); 44 | if (node == null) { 45 | pageModel.setCurrentPageRoute("/"); 46 | pageModel.setCurrentPageType(DNodePageType.DNodePageTypeFlutter); 47 | } else { 48 | pageModel.setCurrentPageRoute(node.getTarget()); 49 | pageModel.setCurrentPageType(node.getPageType()); 50 | } 51 | DStackMethodHandler.sendAppLifeCircle(pageModel); 52 | } 53 | 54 | /** 55 | * app进入后台 56 | */ 57 | public static void appBackground() { 58 | DLog.logD("appBackground"); 59 | PageModel pageModel = new PageModel(); 60 | pageModel.setState(PageState.APP_BACKGROUND); 61 | DNode node = DNodeManager.getInstance().getCurrentNode(); 62 | if (node == null) { 63 | pageModel.setCurrentPageRoute("/"); 64 | pageModel.setCurrentPageType(DNodePageType.DNodePageTypeFlutter); 65 | } else { 66 | pageModel.setCurrentPageRoute(node.getTarget()); 67 | pageModel.setCurrentPageType(node.getPageType()); 68 | } 69 | DStackMethodHandler.sendAppLifeCircle(pageModel); 70 | } 71 | 72 | /** 73 | * 页面出现 74 | */ 75 | public static void pageAppear(DNode node) { 76 | DLog.logD("pageAppear"); 77 | if (node == null) { 78 | return; 79 | } 80 | PageModel pageModel = new PageModel(); 81 | pageModel.setActionType(DNodeActionType.DNodeActionTypePush); 82 | pageModel.setCurrentPageType(node.getPageType()); 83 | pageModel.setCurrentPageRoute(node.getTarget()); 84 | List nodeList = DNodeManager.getInstance().getNodeList(); 85 | DNode secondLastNode = null; 86 | if (nodeList.size() >= 2) { 87 | secondLastNode = nodeList.get(nodeList.size() - 2); 88 | } 89 | if (secondLastNode == null) { 90 | pageModel.setPrePageType(DNodePageType.DNodePageTypeFlutter); 91 | pageModel.setPrePageRoute("/"); 92 | } else { 93 | pageModel.setPrePageType(secondLastNode.getPageType()); 94 | pageModel.setPrePageRoute(secondLastNode.getTarget()); 95 | } 96 | 97 | DStackMethodHandler.sendPageLifeCircle(pageModel); 98 | } 99 | 100 | /** 101 | * 页面消失 102 | */ 103 | public static void pageDisappear(DNode node) { 104 | DLog.logD("pageAppear"); 105 | if (node == null) { 106 | return; 107 | } 108 | PageModel pageModel = new PageModel(); 109 | pageModel.setActionType(DNodeActionType.DNodeActionTypePop); 110 | pageModel.setPrePageType(node.getPageType()); 111 | pageModel.setPrePageRoute(node.getTarget()); 112 | List nodeList = DNodeManager.getInstance().getNodeList(); 113 | DNode currentNode = null; 114 | if (nodeList.size() > 0) { 115 | currentNode = nodeList.get(nodeList.size() - 1); 116 | } 117 | if (currentNode == null) { 118 | pageModel.setCurrentPageType(DNodePageType.DNodePageTypeFlutter); 119 | pageModel.setCurrentPageRoute("/"); 120 | } else { 121 | pageModel.setCurrentPageType(currentNode.getPageType()); 122 | pageModel.setCurrentPageRoute(currentNode.getTarget()); 123 | } 124 | DStackMethodHandler.sendPageLifeCircle(pageModel); 125 | } 126 | 127 | /** 128 | * 页面出现处理节点替换情况 129 | */ 130 | public static void pageAppearWithReplace(DNode preNode, DNode currentNode) { 131 | DLog.logE("pageAppearWithReplace"); 132 | if (preNode == null || currentNode == null) { 133 | return; 134 | } 135 | PageModel pageModel = new PageModel(); 136 | pageModel.setActionType(DNodeActionType.DNodeActionTypePush); 137 | pageModel.setCurrentPageType(currentNode.getPageType()); 138 | pageModel.setCurrentPageRoute(currentNode.getTarget()); 139 | pageModel.setPrePageType(preNode.getPageType()); 140 | pageModel.setPrePageRoute(preNode.getTarget()); 141 | DStackMethodHandler.sendPageLifeCircle(pageModel); 142 | } 143 | 144 | } 145 | -------------------------------------------------------------------------------- /android/src/main/kotlin/tal/com/d_stack/lifecycle/PageModel.java: -------------------------------------------------------------------------------- 1 | package tal.com.d_stack.lifecycle; 2 | 3 | 4 | /** 5 | * 页面状态信息 6 | */ 7 | public class PageModel { 8 | 9 | // 当前展示的页面路由 10 | private String currentPageRoute; 11 | // 前一个展示的页面路由,可能为null 12 | private String prePageRoute; 13 | // 页面类型:Flutter/Native 14 | private String currentPageType; 15 | // 页面类型:Flutter/Native 16 | private String prePageType; 17 | // 操作类型:push/pop 18 | private String actionType; 19 | // 应用状态 0:应用启动 1:前台 2:后台 3:应用杀死 20 | private int state = 0; 21 | 22 | public String getCurrentPageRoute() { 23 | return currentPageRoute; 24 | } 25 | 26 | public void setCurrentPageRoute(String currentPageRoute) { 27 | this.currentPageRoute = currentPageRoute; 28 | } 29 | 30 | public String getPrePageRoute() { 31 | return prePageRoute; 32 | } 33 | 34 | public void setPrePageRoute(String prePageRoute) { 35 | this.prePageRoute = prePageRoute; 36 | } 37 | 38 | public String getCurrentPageType() { 39 | return currentPageType; 40 | } 41 | 42 | public void setCurrentPageType(String currentPageType) { 43 | this.currentPageType = currentPageType; 44 | } 45 | 46 | public String getPrePageType() { 47 | return prePageType; 48 | } 49 | 50 | public void setPrePageType(String prePageType) { 51 | this.prePageType = prePageType; 52 | } 53 | 54 | public String getActionType() { 55 | return actionType; 56 | } 57 | 58 | public void setActionType(String actionType) { 59 | this.actionType = actionType; 60 | } 61 | 62 | public int getState() { 63 | return state; 64 | } 65 | 66 | public void setState(int state) { 67 | this.state = state; 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /android/src/main/kotlin/tal/com/d_stack/lifecycle/PageState.java: -------------------------------------------------------------------------------- 1 | package tal.com.d_stack.lifecycle; 2 | 3 | /** 4 | * 页面状态常量 5 | */ 6 | public class PageState { 7 | public static int APP_CREATE = 0; 8 | public static int APP_FOREGROUND = 1; 9 | public static int APP_BACKGROUND = 2; 10 | public static int APP_STOP = 3; 11 | } 12 | -------------------------------------------------------------------------------- /android/src/main/kotlin/tal/com/d_stack/node/DNode.java: -------------------------------------------------------------------------------- 1 | package tal.com.d_stack.node; 2 | 3 | import android.app.Activity; 4 | 5 | import java.lang.ref.WeakReference; 6 | import java.util.HashMap; 7 | import java.util.Map; 8 | 9 | /** 10 | * 页面节点信息 11 | */ 12 | public class DNode { 13 | 14 | // 页面跳转类型 15 | private String action; 16 | 17 | // 页面类型 18 | private String pageType; 19 | 20 | // 页面唯一标识 21 | // flutter页面时,是route,native唯一id 22 | private String target; 23 | 24 | // 附带参数 25 | private Map params; 26 | 27 | // 是否来自flutter消息通道的node 28 | private boolean fromFlutter; 29 | 30 | // 节点对应的activity 31 | private WeakReference activity; 32 | 33 | // 是否正在执行popTo,popToRoot,popToSkip方法 34 | private boolean popTo; 35 | 36 | // 是否是flutter主页面 37 | private boolean homePage; 38 | 39 | //是否是根页面 40 | private boolean rootPage; 41 | 42 | //flutter临界节点,native打开flutter使用 43 | private boolean boundary; 44 | 45 | //是否开启转场动画 46 | private boolean animated; 47 | 48 | //页面唯一标识 49 | private String identifier; 50 | 51 | public DNode(Builder builder) { 52 | this.action = builder.action; 53 | this.pageType = builder.pageType; 54 | this.target = builder.target; 55 | this.params = builder.params; 56 | this.homePage = builder.homePage; 57 | this.boundary = builder.boundary; 58 | this.animated = builder.animated; 59 | this.fromFlutter = builder.fromFlutter; 60 | this.activity = builder.activity; 61 | this.popTo = builder.popTo; 62 | this.rootPage = builder.rootPage; 63 | this.identifier = builder.identifier; 64 | } 65 | 66 | public String getIdentifier() { 67 | return identifier; 68 | } 69 | 70 | public void setIdentifier(String identifier) { 71 | this.identifier = identifier; 72 | } 73 | 74 | 75 | public static class Builder { 76 | private String action = ""; 77 | private String pageType = ""; 78 | private String target = ""; 79 | private Map params = new HashMap<>(); 80 | private boolean homePage = false; 81 | private boolean boundary = false; 82 | private boolean animated = false; 83 | private boolean fromFlutter = false; 84 | private WeakReference activity = null; 85 | private boolean popTo = false; 86 | private boolean rootPage = false; 87 | private String identifier = ""; 88 | 89 | public Builder action(String action) { 90 | this.action = action; 91 | return this; 92 | } 93 | 94 | public Builder pageType(String pageType) { 95 | this.pageType = pageType; 96 | return this; 97 | } 98 | 99 | public Builder target(String target) { 100 | this.target = target; 101 | return this; 102 | } 103 | 104 | public Builder params(Map params) { 105 | this.params = params; 106 | return this; 107 | } 108 | 109 | public Builder isHomePage(boolean isHomePage) { 110 | this.homePage = isHomePage; 111 | return this; 112 | } 113 | 114 | public Builder boundary(boolean boundary) { 115 | this.boundary = boundary; 116 | return this; 117 | } 118 | 119 | public Builder animated(boolean animated) { 120 | this.animated = animated; 121 | return this; 122 | } 123 | 124 | public Builder fromFlutter(boolean fromFlutter) { 125 | this.fromFlutter = fromFlutter; 126 | return this; 127 | } 128 | 129 | public Builder activity(WeakReference activity) { 130 | this.activity = activity; 131 | return this; 132 | } 133 | 134 | public Builder isPopTo(boolean isPopTo) { 135 | this.popTo = isPopTo; 136 | return this; 137 | } 138 | 139 | public Builder isRootPage(boolean isRootPage) { 140 | this.rootPage = isRootPage; 141 | return this; 142 | } 143 | 144 | public Builder identifier(String identifier) { 145 | this.identifier = identifier; 146 | return this; 147 | } 148 | 149 | public DNode build() { 150 | return new DNode(this); 151 | } 152 | } 153 | 154 | 155 | public String getTarget() { 156 | return target; 157 | } 158 | 159 | public void setTarget(String target) { 160 | this.target = target; 161 | } 162 | 163 | public boolean isFromFlutter() { 164 | return fromFlutter; 165 | } 166 | 167 | public void setFromFlutter(boolean fromFlutter) { 168 | this.fromFlutter = fromFlutter; 169 | } 170 | 171 | public Map getParams() { 172 | return params; 173 | } 174 | 175 | public void setParams(Map params) { 176 | this.params = params; 177 | } 178 | 179 | public String getAction() { 180 | return action; 181 | } 182 | 183 | public void setAction(String action) { 184 | this.action = action; 185 | } 186 | 187 | public String getPageType() { 188 | return pageType; 189 | } 190 | 191 | public void setPageType(String pageType) { 192 | this.pageType = pageType; 193 | } 194 | 195 | public WeakReference getActivity() { 196 | return activity; 197 | } 198 | 199 | public void setActivity(WeakReference activity) { 200 | this.activity = activity; 201 | } 202 | 203 | public boolean isPopTo() { 204 | return popTo; 205 | } 206 | 207 | public void setPopTo(boolean popTo) { 208 | this.popTo = popTo; 209 | } 210 | 211 | public boolean isHomePage() { 212 | return homePage; 213 | } 214 | 215 | public void setHomePage(boolean homePage) { 216 | this.homePage = homePage; 217 | } 218 | 219 | public boolean isRootPage() { 220 | return rootPage; 221 | } 222 | 223 | public void setRootPage(boolean rootPage) { 224 | this.rootPage = rootPage; 225 | } 226 | 227 | public boolean isBoundary() { 228 | return boundary; 229 | } 230 | 231 | public void setBoundary(boolean boundary) { 232 | this.boundary = boundary; 233 | } 234 | 235 | public boolean isAnimated() { 236 | return animated; 237 | } 238 | 239 | public void setAnimated(boolean animated) { 240 | this.animated = animated; 241 | } 242 | 243 | @Override 244 | public String toString() { 245 | return "DNode{" + 246 | "action='" + action + '\'' + 247 | ", pageType='" + pageType + '\'' + 248 | ", target='" + target + '\'' + 249 | ", params=" + params + '\'' + 250 | ", fromFlutter=" + fromFlutter + '\'' + 251 | ", activity=" + activity + '\'' + 252 | ", popTo=" + popTo + '\'' + 253 | ", homePage=" + homePage + '\'' + 254 | ", rootPage=" + rootPage + '\'' + 255 | ", boundary=" + boundary + '\'' + 256 | ", animated=" + animated + '\'' + 257 | ", identifier='" + identifier + '\'' + 258 | '}'; 259 | } 260 | } -------------------------------------------------------------------------------- /android/src/main/kotlin/tal/com/d_stack/node/DNodeResponse.java: -------------------------------------------------------------------------------- 1 | package tal.com.d_stack.node; 2 | 3 | import org.json.JSONException; 4 | import org.json.JSONObject; 5 | 6 | import java.util.HashMap; 7 | import java.util.Map; 8 | 9 | public class DNodeResponse { 10 | public String target = ""; 11 | public String pageType = ""; 12 | public String action = ""; 13 | public Map params; 14 | public boolean homePage = false; 15 | public boolean animated = false; 16 | public boolean boundary = false; 17 | public String identifier = ""; 18 | 19 | @Override 20 | public String toString() { 21 | JSONObject jo = new JSONObject(); 22 | try { 23 | jo.put("target", target); 24 | jo.put("pageType", pageType); 25 | jo.put("action", action); 26 | jo.put("params", params); 27 | jo.put("homePage", homePage); 28 | jo.put("animated", animated); 29 | jo.put("boundary", boundary); 30 | jo.put("identifier", identifier); 31 | } catch (JSONException e) { 32 | e.printStackTrace(); 33 | } 34 | return jo.toString(); 35 | } 36 | 37 | public Map toMap() { 38 | Map map = new HashMap<>(); 39 | map.put("target", target); 40 | map.put("pageType", pageType); 41 | map.put("action", action); 42 | map.put("params", params); 43 | map.put("homePage", homePage); 44 | map.put("animated", animated); 45 | map.put("boundary", boundary); 46 | map.put("identifier", identifier); 47 | return map; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /android/src/main/kotlin/tal/com/d_stack/node/constants/DNodeActionType.java: -------------------------------------------------------------------------------- 1 | package tal.com.d_stack.node.constants; 2 | 3 | // 跳转类型 4 | public class DNodeActionType { 5 | 6 | // push跳转android 7 | public static final String DNodeActionTypePush = "push"; 8 | 9 | // present跳转(ios独有) 10 | public static final String DNodeActionTypePresent = "present"; 11 | 12 | // pop返回android 13 | public static final String DNodeActionTypePop = "pop"; 14 | 15 | // popTo 返回 16 | public static final String DNodeActionTypePopTo = "popTo"; 17 | 18 | // PopToRoot 19 | public static final String DNodeActionTypePopToRoot = "popToRoot"; 20 | 21 | // PopSkip 22 | public static final String DNodeActionTypePopSkip = "popSkip"; 23 | 24 | // 手势 25 | public static final String DNodeActionTypeGesture = "gesture"; 26 | 27 | // Dissmiss返回(ios独有) 28 | public static final String DNodeActionTypeDissmiss = "dissmiss"; 29 | 30 | //replace 31 | public static final String DNodeActionTypeReplace = "replace"; 32 | 33 | //pushAndRemoveUntil 34 | public static final String DNodeActionPushAndRemoveUntil = "pushAndRemoveUntil"; 35 | } 36 | -------------------------------------------------------------------------------- /android/src/main/kotlin/tal/com/d_stack/node/constants/DNodePageType.java: -------------------------------------------------------------------------------- 1 | package tal.com.d_stack.node.constants; 2 | 3 | // 页面类型 4 | public class DNodePageType { 5 | 6 | // 原生页面 7 | public static final String DNodePageTypeNative = "native"; 8 | 9 | // Flutter页面 10 | public static final String DNodePageTypeFlutter = "flutter"; 11 | 12 | } 13 | -------------------------------------------------------------------------------- /android/src/main/kotlin/tal/com/d_stack/observer/DStackActivityManager.java: -------------------------------------------------------------------------------- 1 | package tal.com.d_stack.observer; 2 | 3 | import android.app.Activity; 4 | 5 | import java.util.ArrayList; 6 | import java.util.List; 7 | import java.util.concurrent.atomic.AtomicReference; 8 | 9 | import io.flutter.embedding.android.DFlutterActivity; 10 | import io.flutter.embedding.android.FlutterActivity; 11 | import io.flutter.embedding.android.FlutterFragmentActivity; 12 | import tal.com.d_stack.node.DNode; 13 | import tal.com.d_stack.utils.DLog; 14 | 15 | /** 16 | * activity的栈管理 17 | */ 18 | public class DStackActivityManager { 19 | 20 | private final static AtomicReference INSTANCE = new AtomicReference<>(); 21 | 22 | public static DStackActivityManager getInstance() { 23 | for (; ; ) { 24 | DStackActivityManager factory = INSTANCE.get(); 25 | if (factory != null) { 26 | return factory; 27 | } 28 | factory = new DStackActivityManager(); 29 | if (INSTANCE.compareAndSet(null, factory)) { 30 | return factory; 31 | } 32 | } 33 | } 34 | 35 | //activity栈集合 36 | private List activities; 37 | //栈顶activity 38 | private Activity topActivity; 39 | //栈底activity 40 | private Activity bottomActivity; 41 | //需要移除出栈的activity集合 42 | private List needRemoveActivities; 43 | //正在执行activity栈清除操作 44 | private boolean executeStack; 45 | //是否需要重新attach引擎 46 | private boolean needReAttachEngine = false; 47 | 48 | private DStackActivityManager() { 49 | activities = new ArrayList<>(); 50 | needRemoveActivities = new ArrayList<>(); 51 | } 52 | 53 | /** 54 | * 栈增加activity 55 | */ 56 | public void addActivity(Activity activity) { 57 | if (activity == null) { 58 | return; 59 | } 60 | activities.add(activity); 61 | setBottomAndTopActivity(); 62 | } 63 | 64 | /** 65 | * 栈移除activity 66 | */ 67 | public void removeActivity(Activity activity) { 68 | if (activity == null) { 69 | return; 70 | } 71 | activities.remove(activity); 72 | handleReAttachEngine(activity); 73 | handleNeedRemoveActivities(activity); 74 | setBottomAndTopActivity(); 75 | } 76 | 77 | /** 78 | * 设置栈顶和栈底Activity 79 | */ 80 | public void setBottomAndTopActivity() { 81 | if (activities == null || activities.size() == 0) { 82 | topActivity = null; 83 | bottomActivity = null; 84 | } else { 85 | topActivity = activities.get(activities.size() - 1); 86 | bottomActivity = activities.get(0); 87 | } 88 | } 89 | 90 | /** 91 | * 获取栈顶Activity 92 | */ 93 | public Activity getTopActivity() { 94 | return topActivity; 95 | } 96 | 97 | 98 | /** 99 | * 判断栈顶的activity是否和要打开的activity是同一个activity 100 | */ 101 | public boolean isSameActivity(Class needOpenActivity) { 102 | if (topActivity != null && needOpenActivity != null) { 103 | String topActivityName = topActivity.getClass().getSimpleName(); 104 | String needOpenActivityName = needOpenActivity.getSimpleName(); 105 | return topActivityName.trim().equals(needOpenActivityName.trim()); 106 | } 107 | return false; 108 | } 109 | 110 | /** 111 | * 关闭栈顶activity 112 | */ 113 | public void closeTopActivity() { 114 | if (topActivity == null) { 115 | return; 116 | } 117 | topActivity.finish(); 118 | } 119 | 120 | /** 121 | * 关闭栈顶的flutter控制器activity 122 | */ 123 | public void closeTopFlutterActivity() { 124 | if (topActivity == null) { 125 | return; 126 | } 127 | if (topActivity instanceof FlutterActivity || 128 | topActivity.getParent() instanceof FlutterActivity) { 129 | topActivity.finish(); 130 | return; 131 | } 132 | if (topActivity instanceof DFlutterActivity || 133 | topActivity.getParent() instanceof DFlutterActivity) { 134 | topActivity.finish(); 135 | return; 136 | } 137 | if (topActivity instanceof FlutterFragmentActivity || 138 | topActivity.getParent() instanceof FlutterFragmentActivity) { 139 | topActivity.finish(); 140 | } 141 | } 142 | 143 | /** 144 | * 把该节点对应activity之上的所有activity关闭 145 | */ 146 | public void closeActivityWithNode(DNode node) { 147 | if (node == null || node.getActivity() == null) { 148 | return; 149 | } 150 | boolean find = false; 151 | needRemoveActivities.clear(); 152 | Activity activity = node.getActivity().get(); 153 | for (int i = activities.size() - 1; i >= 0; i--) { 154 | Activity tempActivity = activities.get(i); 155 | if (tempActivity != activity) { 156 | needRemoveActivities.add(tempActivity); 157 | } else { 158 | find = true; 159 | break; 160 | } 161 | } 162 | if (find) { 163 | if (needRemoveActivities.size() == 0) { 164 | return; 165 | } 166 | executeStack = true; 167 | needRemoveActivities.get(0).finish(); 168 | } else { 169 | needRemoveActivities.clear(); 170 | } 171 | } 172 | 173 | /** 174 | * 每次关闭activity后,看看待移除列表是否还有activity,继续执行关闭操作 175 | */ 176 | private void handleNeedRemoveActivities(Activity activity) { 177 | if (activity == null || needRemoveActivities.size() == 0) { 178 | return; 179 | } 180 | DLog.logE("被关闭的Activity是:" + activity.getClass().getName()); 181 | needRemoveActivities.remove(activity); 182 | if (needRemoveActivities.size() == 0) { 183 | //activity栈的处理完成 184 | executeStack = false; 185 | return; 186 | } 187 | //继续取集合第一个activity进行关闭 188 | needRemoveActivities.get(0).finish(); 189 | } 190 | 191 | /** 192 | * activity栈正在执行操作,这个方法被调用说明正在执行popTo,popToRoot,popToSkip方法 193 | * activity正在依次顺序关闭,也不需要在执行其他关闭activity的操作 194 | */ 195 | public boolean isExecuteStack() { 196 | return executeStack; 197 | } 198 | 199 | /** 200 | * 获取栈内Activity数量 201 | */ 202 | public int getActivitiesSize() { 203 | if (activities == null) { 204 | return 0; 205 | } 206 | return activities.size(); 207 | } 208 | /** 209 | * 判断当前工程是否是一个纯Flutter工程 210 | */ 211 | public boolean isFlutterApp() { 212 | if (bottomActivity == null) { 213 | return true; 214 | } 215 | if (bottomActivity instanceof FlutterActivity || 216 | bottomActivity.getParent() instanceof FlutterActivity) { 217 | return true; 218 | } 219 | if (bottomActivity instanceof DFlutterActivity || 220 | bottomActivity.getParent() instanceof DFlutterActivity) { 221 | return true; 222 | } 223 | if (bottomActivity instanceof FlutterFragmentActivity || 224 | bottomActivity.getParent() instanceof FlutterFragmentActivity) { 225 | return true; 226 | } 227 | return false; 228 | } 229 | 230 | /** 231 | * 判断是否是FlutterActivity 232 | */ 233 | public boolean isFlutterActivity(Activity activity) { 234 | if (activity instanceof FlutterActivity) { 235 | return true; 236 | } 237 | if (activity instanceof DFlutterActivity) { 238 | return true; 239 | } 240 | return false; 241 | } 242 | 243 | /** 244 | * 关闭过一个flutterActivity,并且栈里还有flutterActivity,需要重新attach引擎 245 | */ 246 | public void handleReAttachEngine(Activity activity) { 247 | if ( 248 | activity instanceof FlutterActivity || 249 | activity instanceof DFlutterActivity || 250 | activity instanceof FlutterFragmentActivity) { 251 | for (Activity tempActivity : activities) { 252 | if (tempActivity instanceof FlutterActivity || 253 | tempActivity instanceof DFlutterActivity || 254 | tempActivity instanceof FlutterFragmentActivity) { 255 | needReAttachEngine = true; 256 | break; 257 | } 258 | } 259 | } 260 | } 261 | 262 | /** 263 | * 设置是否需要重新attach引擎 264 | */ 265 | public void setNeedReAttachEngine(boolean needReAttachEngine) { 266 | this.needReAttachEngine = needReAttachEngine; 267 | } 268 | 269 | /** 270 | * 是否需要重新attach引擎 271 | */ 272 | public boolean isNeedReAttachEngine() { 273 | return needReAttachEngine; 274 | } 275 | 276 | /** 277 | * 栈里是否有flutter控制器 278 | */ 279 | public boolean haveFlutterContainer() { 280 | for (Activity activity : activities) { 281 | if (isFlutterActivity(activity)) { 282 | return true; 283 | } 284 | } 285 | return false; 286 | } 287 | 288 | } 289 | -------------------------------------------------------------------------------- /android/src/main/kotlin/tal/com/d_stack/observer/DStackLifecycleObserver.java: -------------------------------------------------------------------------------- 1 | package tal.com.d_stack.observer; 2 | 3 | import android.app.Activity; 4 | import android.app.Application; 5 | import android.os.Bundle; 6 | 7 | import androidx.annotation.NonNull; 8 | import androidx.annotation.Nullable; 9 | 10 | import java.lang.ref.WeakReference; 11 | 12 | import io.flutter.embedding.android.FlutterView; 13 | import tal.com.d_stack.lifecycle.PageLifecycleManager; 14 | import tal.com.d_stack.node.DNode; 15 | import tal.com.d_stack.node.DNodeManager; 16 | import tal.com.d_stack.node.constants.DNodeActionType; 17 | import tal.com.d_stack.node.constants.DNodePageType; 18 | import tal.com.d_stack.utils.DLog; 19 | import tal.com.d_stack.utils.DStackUtils; 20 | 21 | /** 22 | * 监控App生命周期,进行Activity栈管理 23 | */ 24 | public class DStackLifecycleObserver implements Application.ActivityLifecycleCallbacks { 25 | 26 | private int appCount = 0; 27 | //app是否在前台 28 | private boolean isFrontApp = true; 29 | //当前活动activity 30 | private Activity activeActivity; 31 | //是否app启动 32 | boolean appStart; 33 | 34 | @Override 35 | public void onActivityCreated(@NonNull Activity activity, @Nullable Bundle savedInstanceState) { 36 | if (!FilterActivityManager.getInstance().canAdd(activity)) { 37 | return; 38 | } 39 | DStackActivityManager.getInstance().addActivity(activity); 40 | activeActivity = activity; 41 | appStart = DStackActivityManager.getInstance().getActivitiesSize() == 1; 42 | if (appStart) { 43 | DNode node; 44 | //应用刚刚启动时 45 | if (DStackActivityManager.getInstance().isFlutterActivity(activity)) { 46 | //是flutter工程,添加根节点 47 | node = new DNode.Builder() 48 | .target("/") 49 | .pageType(DNodePageType.DNodePageTypeFlutter) 50 | .action(DNodeActionType.DNodeActionTypePush) 51 | .identifier(DStackUtils.generateUniqueId()) 52 | .isHomePage(true) 53 | .isRootPage(true) 54 | .build(); 55 | } else { 56 | //是native工程,添加根节点 57 | node = new DNode.Builder() 58 | .target("/") 59 | .pageType(DNodePageType.DNodePageTypeNative) 60 | .action(DNodeActionType.DNodeActionTypePush) 61 | .identifier(DStackUtils.generateUniqueId()) 62 | .isHomePage(true) 63 | .isRootPage(true) 64 | .build(); 65 | } 66 | DNodeManager.getInstance().checkNode(node); 67 | } else { 68 | //应用已经启动,打开新的activity 69 | if (!DStackActivityManager.getInstance().isFlutterActivity(activity)) { 70 | //是native工程,添加普通节点 71 | DNode node = new DNode.Builder() 72 | .target(activity.getClass().getName()) 73 | .pageType(DNodePageType.DNodePageTypeNative) 74 | .action(DNodeActionType.DNodeActionTypePush) 75 | .identifier(DStackUtils.generateUniqueId()) 76 | .build(); 77 | DNodeManager.getInstance().checkNode(node); 78 | } 79 | } 80 | DNodeManager.getInstance().getCurrentNode().setActivity(new WeakReference(activity)); 81 | if (appStart) { 82 | //app启动通知 83 | PageLifecycleManager.appCreate(); 84 | } 85 | } 86 | 87 | @Override 88 | public void onActivityStarted(@NonNull Activity activity) { 89 | if (!FilterActivityManager.getInstance().canAdd(activity)) { 90 | return; 91 | } 92 | appCount++; 93 | if (!isFrontApp) { 94 | isFrontApp = true; 95 | PageLifecycleManager.appForeground(); 96 | } 97 | } 98 | 99 | @Override 100 | public void onActivityResumed(@NonNull Activity activity) { 101 | if (!FilterActivityManager.getInstance().canAdd(activity)) { 102 | return; 103 | } 104 | if (activeActivity != activity) { 105 | //正在执行恢复activity的逻辑,页面返回操作,onCreate,onResumed不是同一个activity 106 | activeActivity = activity; 107 | if (DStackActivityManager.getInstance().isNeedReAttachEngine()) { 108 | //判断是否需要重新attach flutter引擎,1.17以上bug,解决软键盘不能弹出问题 109 | DLog.logE("需要needReAttachEngine"); 110 | FlutterView flutterView = DStackUtils.getFlutterView(activity); 111 | DStackUtils.resetAttachEngine(flutterView); 112 | DStackActivityManager.getInstance().setNeedReAttachEngine(false); 113 | } 114 | } 115 | } 116 | 117 | 118 | @Override 119 | public void onActivityPaused(@NonNull Activity activity) { 120 | if (!FilterActivityManager.getInstance().canAdd(activity)) { 121 | return; 122 | } 123 | } 124 | 125 | @Override 126 | public void onActivityStopped(@NonNull Activity activity) { 127 | if (!FilterActivityManager.getInstance().canAdd(activity)) { 128 | return; 129 | } 130 | appCount--; 131 | if (!isFrontApp()) { 132 | isFrontApp = false; 133 | PageLifecycleManager.appBackground(); 134 | } 135 | } 136 | 137 | @Override 138 | public void onActivityDestroyed(@NonNull Activity activity) { 139 | if (!FilterActivityManager.getInstance().canAdd(activity)) { 140 | return; 141 | } 142 | boolean isPopTo = DStackActivityManager.getInstance().isExecuteStack(); 143 | DStackActivityManager.getInstance().removeActivity(activity); 144 | DNode currentNode = DNodeManager.getInstance().getCurrentNode(); 145 | DNode node = new DNode.Builder() 146 | .target(currentNode.getTarget()) 147 | .pageType(currentNode.getPageType()) 148 | .action(DNodeActionType.DNodeActionTypePop) 149 | .isHomePage(currentNode.isHomePage()) 150 | .isRootPage(currentNode.isRootPage()) 151 | .identifier(currentNode.getIdentifier()) 152 | .isPopTo(isPopTo) 153 | .build(); 154 | DNodeManager.getInstance().checkNode(node); 155 | } 156 | 157 | /** 158 | * 判断App是否在前台 159 | */ 160 | private boolean isFrontApp() { 161 | return appCount > 0; 162 | } 163 | 164 | @Override 165 | public void onActivitySaveInstanceState(@NonNull Activity activity, @NonNull Bundle outState) { 166 | 167 | } 168 | } 169 | -------------------------------------------------------------------------------- /android/src/main/kotlin/tal/com/d_stack/observer/FilterActivityManager.java: -------------------------------------------------------------------------------- 1 | package tal.com.d_stack.observer; 2 | 3 | import android.app.Activity; 4 | 5 | import java.util.ArrayList; 6 | import java.util.List; 7 | import java.util.concurrent.atomic.AtomicReference; 8 | 9 | /** 10 | * 需要过滤的第三方activity管理 11 | */ 12 | public class FilterActivityManager { 13 | 14 | private final static AtomicReference INSTANCE = new AtomicReference<>(); 15 | 16 | public static FilterActivityManager getInstance() { 17 | for (; ; ) { 18 | FilterActivityManager factory = INSTANCE.get(); 19 | if (factory != null) { 20 | return factory; 21 | } 22 | factory = new FilterActivityManager(); 23 | if (INSTANCE.compareAndSet(null, factory)) { 24 | return factory; 25 | } 26 | } 27 | } 28 | 29 | private FilterActivityManager() { 30 | filterActivities.add("rom.huawei"); 31 | filterActivities.add("rom.oppo"); 32 | filterActivities.add("com.tencent"); 33 | filterActivities.add("com.sina"); 34 | filterActivities.add("com.tal.d_stack_spy"); 35 | filterActivities.add("com.yorhp"); 36 | } 37 | 38 | private List filterActivities = new ArrayList<>(); 39 | 40 | private List androidStack = new ArrayList<>(); 41 | 42 | /** 43 | * 判断是否可以添加到混合栈 44 | * @param activity 45 | * @return 46 | */ 47 | public boolean canAdd(Activity activity) { 48 | if (activity == null) { 49 | return false; 50 | } 51 | String fullName = activity.getClass().getName(); 52 | for (String filterName : filterActivities) { 53 | if (fullName.contains(filterName)) { 54 | return false; 55 | } 56 | } 57 | return true; 58 | } 59 | 60 | /** 61 | * 添加过滤器 62 | * 某些功能性Activity,不需要做节点管理的,添加至过滤 63 | * 64 | * @param filterString 过滤字符串 65 | * @return 66 | */ 67 | public boolean addFilter(String filterString) { 68 | return filterActivities.add(filterString); 69 | } 70 | 71 | /** 72 | * 移除已添加的过滤器 73 | * 74 | * @param filterString 75 | * @return 76 | */ 77 | public boolean removeFilter(String filterString) { 78 | return filterActivities.remove(filterString); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /android/src/main/kotlin/tal/com/d_stack/router/INativeRouter.java: -------------------------------------------------------------------------------- 1 | package tal.com.d_stack.router; 2 | 3 | import java.util.Map; 4 | 5 | /** 6 | * 路由信息接口 7 | */ 8 | public interface INativeRouter { 9 | 10 | void openContainer(String routerUrl, Map params); 11 | 12 | } 13 | -------------------------------------------------------------------------------- /android/src/main/kotlin/tal/com/d_stack/router/INodeOperation.java: -------------------------------------------------------------------------------- 1 | package tal.com.d_stack.router; 2 | 3 | import tal.com.d_stack.node.DNodeResponse; 4 | 5 | /** 6 | * 节点操作接口 7 | */ 8 | public interface INodeOperation { 9 | 10 | void operationNode(DNodeResponse node); 11 | 12 | } 13 | -------------------------------------------------------------------------------- /android/src/main/kotlin/tal/com/d_stack/utils/DLog.java: -------------------------------------------------------------------------------- 1 | package tal.com.d_stack.utils; 2 | 3 | import android.util.Log; 4 | 5 | import tal.com.d_stack.BuildConfig; 6 | 7 | /** 8 | * 框架日志打印 9 | */ 10 | public class DLog { 11 | 12 | public static String TAG = "DStack"; 13 | 14 | public static void logE(String log) { 15 | if (BuildConfig.DEBUG) { 16 | Log.e(TAG, log); 17 | } 18 | } 19 | 20 | public static void logD(String log) { 21 | if (BuildConfig.DEBUG) { 22 | Log.d(TAG, log); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /android/src/main/kotlin/tal/com/d_stack/utils/DStackUtils.java: -------------------------------------------------------------------------------- 1 | package tal.com.d_stack.utils; 2 | 3 | import android.app.Activity; 4 | 5 | import java.lang.reflect.Field; 6 | 7 | import io.flutter.embedding.android.DFlutterActivity; 8 | import io.flutter.embedding.android.FlutterActivity; 9 | import io.flutter.embedding.android.FlutterFragmentActivity; 10 | import io.flutter.embedding.android.FlutterView; 11 | import io.flutter.embedding.engine.FlutterEngine; 12 | import io.flutter.embedding.engine.FlutterEngineCache; 13 | import tal.com.d_stack.DStack; 14 | 15 | /** 16 | * 框架常用工具类 17 | * 包括获取flutterView示例和重新attach引擎功能 18 | */ 19 | public class DStackUtils { 20 | 21 | /** 22 | * 通过传入的FlutterActivity获取对应的FlutterView 23 | */ 24 | public static FlutterView getFlutterView(Activity activity) { 25 | if (activity == null) { 26 | return null; 27 | } 28 | FlutterView flutterView = null; 29 | Class c = activity.getClass(); 30 | try { 31 | // 处理FlutterActivity 32 | if (activity instanceof FlutterActivity) { 33 | while (c != FlutterActivity.class) { 34 | c = c.getSuperclass(); 35 | } 36 | Field fieldDelegate = c.getDeclaredField("delegate"); 37 | fieldDelegate.setAccessible(true); 38 | Object objectDelegate = fieldDelegate.get(activity); 39 | Field flutterViewDelegate = objectDelegate.getClass().getDeclaredField("flutterView"); 40 | flutterViewDelegate.setAccessible(true); 41 | flutterView = (FlutterView) flutterViewDelegate.get(objectDelegate); 42 | } else if (activity instanceof DFlutterActivity) { 43 | while (c != DFlutterActivity.class) { 44 | c = c.getSuperclass(); 45 | } 46 | Field fieldDelegate = c.getDeclaredField("delegate"); 47 | fieldDelegate.setAccessible(true); 48 | Object objectDelegate = fieldDelegate.get(activity); 49 | Field flutterViewDelegate = objectDelegate.getClass().getDeclaredField("flutterView"); 50 | flutterViewDelegate.setAccessible(true); 51 | flutterView = (FlutterView) flutterViewDelegate.get(objectDelegate); 52 | } 53 | // 处理FlutterFragmentActivity 54 | else if (activity instanceof FlutterFragmentActivity) { 55 | while (c != FlutterFragmentActivity.class) { 56 | c = c.getSuperclass(); 57 | } 58 | Field fieldFragment = c.getDeclaredField("flutterFragment"); 59 | fieldFragment.setAccessible(true); 60 | Object objectFragment = fieldFragment.get(activity); 61 | Field fieldDelegate = objectFragment.getClass().getDeclaredField("delegate"); 62 | fieldDelegate.setAccessible(true); 63 | Object objectDelegate = fieldDelegate.get(objectFragment); 64 | Field flutterViewDelegate = objectDelegate.getClass().getDeclaredField("flutterView"); 65 | flutterViewDelegate.setAccessible(true); 66 | flutterView = (FlutterView) flutterViewDelegate.get(objectDelegate); 67 | } 68 | } catch (Exception e) { 69 | DLog.logE(e.getMessage()); 70 | } finally { 71 | return flutterView; 72 | } 73 | } 74 | 75 | /** 76 | * 重新绑定当前flutterView对应的flutter引擎 77 | */ 78 | public static void resetAttachEngine(FlutterView flutterView) { 79 | if (flutterView == null) { 80 | return; 81 | } 82 | FlutterEngine flutterEngine = FlutterEngineCache.getInstance().get(DStack.ENGINE_ID); 83 | if (flutterEngine == null) { 84 | return; 85 | } 86 | flutterView.detachFromFlutterEngine(); 87 | flutterView.attachToFlutterEngine(flutterEngine); 88 | } 89 | 90 | /** 91 | * 获取唯一id 92 | */ 93 | public static String generateUniqueId() { 94 | double d = Math.random(); 95 | return (int) (d * 100000) + ""; 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /example/.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | 12 | # IntelliJ related 13 | *.iml 14 | *.ipr 15 | *.iws 16 | .idea/ 17 | 18 | # The .vscode folder contains launch configuration and tasks you configure in 19 | # VS Code which you may wish to be included in version control, so this line 20 | # is commented out by default. 21 | #.vscode/ 22 | 23 | # Flutter/Dart/Pub related 24 | **/doc/api/ 25 | .dart_tool/ 26 | .flutter-plugins 27 | .flutter-plugins-dependencies 28 | .packages 29 | .pub-cache/ 30 | .pub/ 31 | /build/ 32 | 33 | # Web related 34 | lib/generated_plugin_registrant.dart 35 | 36 | # Exceptions to above rules. 37 | !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages 38 | -------------------------------------------------------------------------------- /example/.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled and should not be manually edited. 5 | 6 | version: 7 | revision: 27321ebbad34b0a3fafe99fac037102196d655ff 8 | channel: stable 9 | 10 | project_type: app 11 | -------------------------------------------------------------------------------- /example/README.md: -------------------------------------------------------------------------------- 1 | # d_stack_example 2 | 3 | Demonstrates how to use the d_stack plugin. 4 | 5 | ## Getting Started 6 | 7 | This project is a starting point for a Flutter application. 8 | 9 | A few resources to get you started if this is your first Flutter project: 10 | 11 | - [Lab: Write your first Flutter app](https://flutter.dev/docs/get-started/codelab) 12 | - [Cookbook: Useful Flutter samples](https://flutter.dev/docs/cookbook) 13 | 14 | For help getting started with Flutter, view our 15 | [online documentation](https://flutter.dev/docs), which offers tutorials, 16 | samples, guidance on mobile development, and a full API reference. 17 | -------------------------------------------------------------------------------- /example/android/.gitignore: -------------------------------------------------------------------------------- 1 | gradle-wrapper.jar 2 | /.gradle 3 | /captures/ 4 | /gradlew 5 | /gradlew.bat 6 | /local.properties 7 | GeneratedPluginRegistrant.java 8 | -------------------------------------------------------------------------------- /example/android/app/build.gradle: -------------------------------------------------------------------------------- 1 | def localProperties = new Properties() 2 | def localPropertiesFile = rootProject.file('local.properties') 3 | if (localPropertiesFile.exists()) { 4 | localPropertiesFile.withReader('UTF-8') { reader -> 5 | localProperties.load(reader) 6 | } 7 | } 8 | 9 | def flutterRoot = localProperties.getProperty('flutter.sdk') 10 | if (flutterRoot == null) { 11 | throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") 12 | } 13 | 14 | def flutterVersionCode = localProperties.getProperty('flutter.versionCode') 15 | if (flutterVersionCode == null) { 16 | flutterVersionCode = '1' 17 | } 18 | 19 | def flutterVersionName = localProperties.getProperty('flutter.versionName') 20 | if (flutterVersionName == null) { 21 | flutterVersionName = '1.0' 22 | } 23 | 24 | apply plugin: 'com.android.application' 25 | apply plugin: 'kotlin-android' 26 | apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" 27 | 28 | android { 29 | compileSdkVersion 29 30 | 31 | sourceSets { 32 | main.java.srcDirs += 'src/main/kotlin' 33 | } 34 | 35 | lintOptions { 36 | disable 'InvalidPackage' 37 | } 38 | 39 | defaultConfig { 40 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). 41 | applicationId "tal.com.d_stack_example" 42 | minSdkVersion 21 43 | targetSdkVersion 29 44 | versionCode flutterVersionCode.toInteger() 45 | versionName flutterVersionName 46 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 47 | } 48 | 49 | buildTypes { 50 | release { 51 | // TODO: Add your own signing config for the release build. 52 | // Signing with the debug keys for now, so `flutter run --release` works. 53 | signingConfig signingConfigs.debug 54 | } 55 | } 56 | } 57 | 58 | flutter { 59 | source '../..' 60 | } 61 | 62 | dependencies { 63 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 64 | implementation 'androidx.appcompat:appcompat:1.2.0-alpha03' 65 | } 66 | -------------------------------------------------------------------------------- /example/android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 8 | 14 | 15 | 18 | 19 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 34 | 35 | 36 | 37 | 40 | 41 | 42 | 43 | 46 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /example/android/app/src/main/kotlin/tal/com/d_stack_example/DStackApplication.java: -------------------------------------------------------------------------------- 1 | package tal.com.d_stack_example; 2 | 3 | import android.content.Intent; 4 | 5 | import io.flutter.app.FlutterApplication; 6 | import tal.com.d_stack.DStack; 7 | import tal.com.d_stack.utils.DLog; 8 | 9 | public class DStackApplication extends FlutterApplication { 10 | 11 | DStackApplication application; 12 | 13 | @Override 14 | public void onCreate() { 15 | super.onCreate(); 16 | application = this; 17 | DStack.getInstance().init(this, (routerUrl, params) -> { 18 | if (routerUrl.equals("NativePage")) { 19 | Intent intent = new Intent(); 20 | intent.setClass(application, NativeThreeActivity.class); 21 | intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 22 | application.startActivity(intent); 23 | } 24 | }); 25 | DStack.getInstance().setOpenNodeOperation(true); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /example/android/app/src/main/kotlin/tal/com/d_stack_example/FlutterContainerActivity.kt: -------------------------------------------------------------------------------- 1 | package tal.com.d_stack_example 2 | 3 | import android.content.Context 4 | import io.flutter.embedding.android.DFlutterActivity 5 | import io.flutter.embedding.engine.FlutterEngine 6 | import io.flutter.embedding.engine.FlutterEngineCache 7 | import tal.com.d_stack.DStack 8 | 9 | 10 | /** 11 | * flutter的容器activity,需要重写provideFlutterEngine方法 12 | * 进行引擎复用 13 | */ 14 | class FlutterContainerActivity : DFlutterActivity() { 15 | 16 | override fun provideFlutterEngine(context: Context): FlutterEngine? { 17 | return FlutterEngineCache.getInstance().get(DStack.ENGINE_ID) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /example/android/app/src/main/kotlin/tal/com/d_stack_example/NativeOneActivity.java: -------------------------------------------------------------------------------- 1 | package tal.com.d_stack_example; 2 | 3 | import android.content.Intent; 4 | import android.os.Bundle; 5 | import android.widget.Button; 6 | 7 | import androidx.appcompat.app.AppCompatActivity; 8 | 9 | import tal.com.d_stack.DStack; 10 | 11 | /** 12 | * native页面 13 | */ 14 | public class NativeOneActivity extends AppCompatActivity { 15 | 16 | Button btnOpenNative; 17 | Button btnOpenFlutter; 18 | 19 | @Override 20 | public void onCreate(Bundle savedInstanceState) { 21 | super.onCreate(savedInstanceState); 22 | setContentView(R.layout.layout_one); 23 | btnOpenNative = findViewById(R.id.btn_open_native); 24 | btnOpenFlutter = findViewById(R.id.btn_open_flutter); 25 | 26 | btnOpenNative.setOnClickListener(v -> { 27 | Intent nativeIntent = new Intent(this, NativeTwoActivity.class); 28 | startActivity(nativeIntent); 29 | }); 30 | 31 | btnOpenFlutter.setOnClickListener(v -> { 32 | DStack.getInstance().pushFlutterPage("page1", null, FlutterContainerActivity.class); 33 | }); 34 | } 35 | } -------------------------------------------------------------------------------- /example/android/app/src/main/kotlin/tal/com/d_stack_example/NativeThreeActivity.java: -------------------------------------------------------------------------------- 1 | package tal.com.d_stack_example; 2 | 3 | import android.os.Bundle; 4 | import android.view.View; 5 | import android.widget.Button; 6 | 7 | import androidx.appcompat.app.AppCompatActivity; 8 | 9 | import tal.com.d_stack.DStack; 10 | 11 | 12 | public class NativeThreeActivity extends AppCompatActivity { 13 | 14 | Button btnOpenFlutter; 15 | Button btnPopToRoot; 16 | Button btnPopToFlutter; 17 | 18 | @Override 19 | public void onCreate(Bundle savedInstanceState) { 20 | super.onCreate(savedInstanceState); 21 | setContentView(R.layout.layout_three); 22 | 23 | btnOpenFlutter = findViewById(R.id.btn_open_flutter); 24 | btnPopToRoot = findViewById(R.id.btn_popToRoot); 25 | btnPopToFlutter = findViewById(R.id.btn_popFlutter); 26 | 27 | btnOpenFlutter.setOnClickListener(v -> { 28 | DStack.getInstance().pushFlutterPage("page4", null, FlutterContainerActivity.class); 29 | }); 30 | 31 | btnPopToRoot.setOnClickListener(v -> DStack.getInstance().popToRoot()); 32 | 33 | btnPopToFlutter.setOnClickListener(v -> { 34 | DStack.getInstance().popTo("page2", null); 35 | }); 36 | } 37 | } -------------------------------------------------------------------------------- /example/android/app/src/main/kotlin/tal/com/d_stack_example/NativeTwoActivity.java: -------------------------------------------------------------------------------- 1 | package tal.com.d_stack_example; 2 | 3 | import android.os.Bundle; 4 | 5 | import androidx.appcompat.app.AppCompatActivity; 6 | 7 | 8 | public class NativeTwoActivity extends AppCompatActivity { 9 | 10 | @Override 11 | public void onCreate(Bundle savedInstanceState) { 12 | super.onCreate(savedInstanceState); 13 | setContentView(R.layout.layout_two); 14 | } 15 | } -------------------------------------------------------------------------------- /example/android/app/src/main/res/drawable/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/layout/layout_one.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 12 | 13 | 18 | 19 | 24 | 25 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/layout/layout_three.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 12 | 13 | 18 | 19 | 24 | 25 | 30 | 31 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/layout/layout_two.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tal-tech/d_stack/c10b77f8f13b1fb9de25daad99b25a50cafebe52/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tal-tech/d_stack/c10b77f8f13b1fb9de25daad99b25a50cafebe52/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tal-tech/d_stack/c10b77f8f13b1fb9de25daad99b25a50cafebe52/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tal-tech/d_stack/c10b77f8f13b1fb9de25daad99b25a50cafebe52/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tal-tech/d_stack/c10b77f8f13b1fb9de25daad99b25a50cafebe52/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/values/color.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #00000000 4 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/xml/network_security_config.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/android/app/src/profile/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/android/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | ext.kotlin_version = '1.3.70' 3 | repositories { 4 | google() 5 | jcenter() 6 | } 7 | 8 | dependencies { 9 | classpath 'com.android.tools.build:gradle:4.0.0' 10 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 11 | } 12 | } 13 | 14 | allprojects { 15 | repositories { 16 | google() 17 | jcenter() 18 | } 19 | } 20 | 21 | rootProject.buildDir = '../build' 22 | subprojects { 23 | project.buildDir = "${rootProject.buildDir}/${project.name}" 24 | } 25 | subprojects { 26 | project.evaluationDependsOn(':app') 27 | } 28 | 29 | task clean(type: Delete) { 30 | delete rootProject.buildDir 31 | } 32 | -------------------------------------------------------------------------------- /example/android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | android.enableR8=true 3 | android.useAndroidX=true 4 | android.enableJetifier=true 5 | android.injected.testOnly=false 6 | -------------------------------------------------------------------------------- /example/android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Wed Aug 05 16:54:45 CST 2020 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.1.1-all.zip 7 | -------------------------------------------------------------------------------- /example/android/settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | 3 | def flutterProjectRoot = rootProject.projectDir.parentFile.toPath() 4 | 5 | def plugins = new Properties() 6 | def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins') 7 | if (pluginsFile.exists()) { 8 | pluginsFile.withReader('UTF-8') { reader -> plugins.load(reader) } 9 | } 10 | 11 | plugins.each { name, path -> 12 | def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile() 13 | include ":$name" 14 | project(":$name").projectDir = pluginDirectory 15 | } 16 | -------------------------------------------------------------------------------- /example/ios/.gitignore: -------------------------------------------------------------------------------- 1 | *.mode1v3 2 | *.mode2v3 3 | *.moved-aside 4 | *.pbxuser 5 | *.perspectivev3 6 | **/*sync/ 7 | .sconsign.dblite 8 | .tags* 9 | **/.vagrant/ 10 | **/DerivedData/ 11 | Icon? 12 | **/Pods/ 13 | **/.symlinks/ 14 | profile 15 | xcuserdata 16 | **/.generated/ 17 | Flutter/App.framework 18 | Flutter/Flutter.framework 19 | Flutter/Flutter.podspec 20 | Flutter/Generated.xcconfig 21 | Flutter/app.flx 22 | Flutter/app.zip 23 | Flutter/flutter_assets/ 24 | Flutter/flutter_export_environment.sh 25 | ServiceDefinitions.json 26 | Runner/GeneratedPluginRegistrant.* 27 | 28 | # Exceptions to above rules. 29 | !default.mode1v3 30 | !default.mode2v3 31 | !default.pbxuser 32 | !default.perspectivev3 33 | -------------------------------------------------------------------------------- /example/ios/Flutter/.last_build_id: -------------------------------------------------------------------------------- 1 | 11c5d94d8233a3ac66d1600b04652e8c -------------------------------------------------------------------------------- /example/ios/Flutter/AppFrameworkInfo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | App 9 | CFBundleIdentifier 10 | io.flutter.flutter.app 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | App 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1.0 23 | MinimumOSVersion 24 | 8.0 25 | 26 | 27 | -------------------------------------------------------------------------------- /example/ios/Flutter/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /example/ios/Flutter/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /example/ios/Podfile: -------------------------------------------------------------------------------- 1 | # Uncomment this line to define a global platform for your project 2 | # platform :ios, '9.0' 3 | 4 | # CocoaPods analytics sends network stats synchronously affecting flutter build latency. 5 | ENV['COCOAPODS_DISABLE_STATS'] = 'true' 6 | 7 | project 'Runner', { 8 | 'Debug' => :debug, 9 | 'Profile' => :release, 10 | 'Release' => :release, 11 | } 12 | 13 | def flutter_root 14 | generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) 15 | unless File.exist?(generated_xcode_build_settings_path) 16 | raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" 17 | end 18 | 19 | File.foreach(generated_xcode_build_settings_path) do |line| 20 | matches = line.match(/FLUTTER_ROOT\=(.*)/) 21 | return matches[1].strip if matches 22 | end 23 | raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" 24 | end 25 | 26 | require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) 27 | 28 | flutter_ios_podfile_setup 29 | 30 | target 'Runner' do 31 | flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) 32 | end 33 | 34 | post_install do |installer| 35 | installer.pods_project.targets.each do |target| 36 | flutter_additional_ios_build_settings(target) 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /example/ios/Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - d_stack (0.0.1): 3 | - Flutter 4 | - Flutter (1.0.0) 5 | 6 | DEPENDENCIES: 7 | - d_stack (from `.symlinks/plugins/d_stack/ios`) 8 | - Flutter (from `Flutter`) 9 | 10 | EXTERNAL SOURCES: 11 | d_stack: 12 | :path: ".symlinks/plugins/d_stack/ios" 13 | Flutter: 14 | :path: Flutter 15 | 16 | SPEC CHECKSUMS: 17 | d_stack: 218a394e5759f06ed1f1aacbeea9c78314bdcfd6 18 | Flutter: 434fef37c0980e73bb6479ef766c45957d4b510c 19 | 20 | PODFILE CHECKSUM: 8e679eca47255a8ca8067c4c67aab20e64cb974d 21 | 22 | COCOAPODS: 1.10.1 23 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 39 | 40 | 41 | 42 | 43 | 44 | 54 | 56 | 62 | 63 | 64 | 65 | 66 | 67 | 73 | 75 | 81 | 82 | 83 | 84 | 86 | 87 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /example/ios/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/ios/Runner/AppDelegate.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | @interface AppDelegate : UIResponder 4 | 5 | @property (strong, nonatomic) UIWindow *window; 6 | 7 | @end 8 | -------------------------------------------------------------------------------- /example/ios/Runner/AppDelegate.m: -------------------------------------------------------------------------------- 1 | #import "AppDelegate.h" 2 | #import 3 | #import "GeneratedPluginRegistrant.h" 4 | #import "ThirdViewController.h" 5 | #import "FourViewController.h" 6 | #import "DStackViewController.h" 7 | #import "HomeViewController.h" 8 | #import "DemoFlutterViewController.h" 9 | #import "SixViewController.h" 10 | 11 | @DStackInject(AppDelegate); 12 | 13 | @interface AppDelegate () 14 | 15 | @end 16 | 17 | static BOOL isFlutterProject = YES; 18 | 19 | @implementation AppDelegate 20 | 21 | - (BOOL)application:(UIApplication *)application 22 | didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 23 | 24 | [[DStack sharedInstance] startWithDelegate:self]; 25 | [GeneratedPluginRegistrant registerWithRegistry:[DStack sharedInstance].engine]; 26 | [[DStack sharedInstance] logEnable:YES]; 27 | 28 | UIViewController *rootVC = nil; 29 | if (isFlutterProject) { 30 | DemoFlutterViewController *home = [[DemoFlutterViewController alloc] init]; 31 | DStackViewController *navi = [[DStackViewController alloc] initWithRootViewController:home]; 32 | rootVC = navi; 33 | } else { 34 | HomeViewController *home = [[HomeViewController alloc] init]; 35 | UITabBarController *tab = [[UITabBarController alloc] init]; 36 | DStackViewController *navi0 = [[DStackViewController alloc] initWithRootViewController:home]; 37 | navi0.tabBarItem.title = @"home"; 38 | 39 | DemoFlutterViewController *flutter = [[DemoFlutterViewController alloc] init]; 40 | DStackViewController *navi1 = [[DStackViewController alloc] initWithRootViewController:flutter]; 41 | navi1.tabBarItem.title = @"flutter"; 42 | 43 | // [tab setViewControllers:@[navi1, navi0]]; 44 | [tab setViewControllers:@[navi0, navi1]]; 45 | tab.delegate = self; 46 | 47 | rootVC = tab; 48 | } 49 | self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds]; 50 | self.window.backgroundColor = [UIColor whiteColor]; 51 | self.window.rootViewController = rootVC; 52 | [self.window makeKeyAndVisible]; 53 | [self testChannel]; 54 | return YES; 55 | } 56 | 57 | 58 | - (void)testChannel 59 | { 60 | FlutterMethodChannel *channel = [DStack sharedInstance].engine.navigationChannel; 61 | 62 | } 63 | 64 | 65 | /// 当项目中tabBarController的viewControllers里面有DFlutterViewController 66 | /// 或者NavigationViewController的rootViewController是DFlutterViewController作为入口时 67 | /// 项目中必须实现tabBarController的delegate的, 68 | /// - (BOOL)tabBarController:shouldSelectViewController:并且调用DStack的 69 | /// [[DStack sharedInstance] tabBarController:tabBarController willSelectViewController:viewController]; 70 | - (BOOL)tabBarController:(UITabBarController *)tabBarController 71 | shouldSelectViewController:(UIViewController *)viewController 72 | { 73 | [[DStack sharedInstance] tabBarController:tabBarController willSelectViewController:viewController]; 74 | return YES; 75 | } 76 | 77 | 78 | 79 | + (FlutterEngine *)dStackForFlutterEngine 80 | { 81 | FlutterEngine *engine = [[FlutterEngine alloc] initWithName:@"io.flutter" project:nil]; 82 | [engine run]; 83 | return engine; 84 | } 85 | 86 | - (nonnull UINavigationController *)dStack:(nonnull DStack *)stack navigationControllerForNode:(nonnull DStackNode *)node 87 | { 88 | return [[self currentController] navigationController]; 89 | } 90 | 91 | - (void)dStack:(nonnull DStack *)stack presentWithNode:(nonnull DStackNode *)node 92 | { 93 | UIViewController *didPushController = nil; 94 | // UINavigationController *navi = [self dStack:stack navigationControllerForNode:node]; 95 | if ([node.route isEqualToString:@"NativePage2"]) { 96 | didPushController = [[FourViewController alloc] init]; 97 | [[self currentController] presentViewController:didPushController animated:node.animated completion:nil]; 98 | } 99 | } 100 | 101 | - (void)dStack:(nonnull DStack *)stack pushWithNode:(nonnull DStackNode *)node 102 | { 103 | UIViewController *didPushController = nil; 104 | UINavigationController *navi = [self dStack:stack navigationControllerForNode:node]; 105 | if ([node.route isEqualToString:@"NativePage"]) { 106 | didPushController = [[ThirdViewController alloc] init]; 107 | } else if ([node.route isEqualToString:@"SixViewController"]) { 108 | didPushController = [[SixViewController alloc] init]; 109 | didPushController.hidesBottomBarWhenPushed = YES; 110 | } 111 | [navi pushViewController:didPushController animated:node.animated]; 112 | } 113 | 114 | - (nonnull UIViewController *)visibleControllerForCurrentWindow 115 | { 116 | return [self currentController]; 117 | } 118 | 119 | -(UIViewController *)currentController 120 | { 121 | return [self currentControllerFromController:self.rootController]; 122 | } 123 | 124 | - (UIViewController *)currentControllerFromController:(UIViewController *)controller 125 | { 126 | if (!controller) { return nil;} 127 | UIViewController *presented = controller.presentedViewController; 128 | if (presented) { return [self currentControllerFromController:presented];} 129 | if ([controller isKindOfClass:[UINavigationController class]]) { 130 | UINavigationController *navi = (UINavigationController *)controller; 131 | if (!navi.viewControllers.count) { return navi;} 132 | return [self currentControllerFromController:navi.topViewController]; 133 | } else if ([controller isKindOfClass:[UITabBarController class]]) { 134 | UITabBarController *tab = (UITabBarController *)controller; 135 | if (!tab.viewControllers.count) { return tab;} 136 | return [self currentControllerFromController:tab.selectedViewController]; 137 | } else { 138 | return controller; 139 | } 140 | } 141 | 142 | - (UIViewController *)rootController 143 | { 144 | UIViewController *rootVC = [UIApplication sharedApplication].delegate.window.rootViewController; 145 | if (!rootVC) { 146 | rootVC = [[[UIApplication sharedApplication] keyWindow] rootViewController]; 147 | } 148 | return rootVC; 149 | } 150 | 151 | 152 | @end 153 | 154 | 155 | -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "20x20", 5 | "idiom" : "iphone", 6 | "filename" : "Icon-App-20x20@2x.png", 7 | "scale" : "2x" 8 | }, 9 | { 10 | "size" : "20x20", 11 | "idiom" : "iphone", 12 | "filename" : "Icon-App-20x20@3x.png", 13 | "scale" : "3x" 14 | }, 15 | { 16 | "size" : "29x29", 17 | "idiom" : "iphone", 18 | "filename" : "Icon-App-29x29@1x.png", 19 | "scale" : "1x" 20 | }, 21 | { 22 | "size" : "29x29", 23 | "idiom" : "iphone", 24 | "filename" : "Icon-App-29x29@2x.png", 25 | "scale" : "2x" 26 | }, 27 | { 28 | "size" : "29x29", 29 | "idiom" : "iphone", 30 | "filename" : "Icon-App-29x29@3x.png", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "size" : "40x40", 35 | "idiom" : "iphone", 36 | "filename" : "Icon-App-40x40@2x.png", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "size" : "40x40", 41 | "idiom" : "iphone", 42 | "filename" : "Icon-App-40x40@3x.png", 43 | "scale" : "3x" 44 | }, 45 | { 46 | "size" : "60x60", 47 | "idiom" : "iphone", 48 | "filename" : "Icon-App-60x60@2x.png", 49 | "scale" : "2x" 50 | }, 51 | { 52 | "size" : "60x60", 53 | "idiom" : "iphone", 54 | "filename" : "Icon-App-60x60@3x.png", 55 | "scale" : "3x" 56 | }, 57 | { 58 | "size" : "20x20", 59 | "idiom" : "ipad", 60 | "filename" : "Icon-App-20x20@1x.png", 61 | "scale" : "1x" 62 | }, 63 | { 64 | "size" : "20x20", 65 | "idiom" : "ipad", 66 | "filename" : "Icon-App-20x20@2x.png", 67 | "scale" : "2x" 68 | }, 69 | { 70 | "size" : "29x29", 71 | "idiom" : "ipad", 72 | "filename" : "Icon-App-29x29@1x.png", 73 | "scale" : "1x" 74 | }, 75 | { 76 | "size" : "29x29", 77 | "idiom" : "ipad", 78 | "filename" : "Icon-App-29x29@2x.png", 79 | "scale" : "2x" 80 | }, 81 | { 82 | "size" : "40x40", 83 | "idiom" : "ipad", 84 | "filename" : "Icon-App-40x40@1x.png", 85 | "scale" : "1x" 86 | }, 87 | { 88 | "size" : "40x40", 89 | "idiom" : "ipad", 90 | "filename" : "Icon-App-40x40@2x.png", 91 | "scale" : "2x" 92 | }, 93 | { 94 | "size" : "76x76", 95 | "idiom" : "ipad", 96 | "filename" : "Icon-App-76x76@1x.png", 97 | "scale" : "1x" 98 | }, 99 | { 100 | "size" : "76x76", 101 | "idiom" : "ipad", 102 | "filename" : "Icon-App-76x76@2x.png", 103 | "scale" : "2x" 104 | }, 105 | { 106 | "size" : "83.5x83.5", 107 | "idiom" : "ipad", 108 | "filename" : "Icon-App-83.5x83.5@2x.png", 109 | "scale" : "2x" 110 | }, 111 | { 112 | "size" : "1024x1024", 113 | "idiom" : "ios-marketing", 114 | "filename" : "Icon-App-1024x1024@1x.png", 115 | "scale" : "1x" 116 | } 117 | ], 118 | "info" : { 119 | "version" : 1, 120 | "author" : "xcode" 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tal-tech/d_stack/c10b77f8f13b1fb9de25daad99b25a50cafebe52/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tal-tech/d_stack/c10b77f8f13b1fb9de25daad99b25a50cafebe52/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tal-tech/d_stack/c10b77f8f13b1fb9de25daad99b25a50cafebe52/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tal-tech/d_stack/c10b77f8f13b1fb9de25daad99b25a50cafebe52/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tal-tech/d_stack/c10b77f8f13b1fb9de25daad99b25a50cafebe52/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tal-tech/d_stack/c10b77f8f13b1fb9de25daad99b25a50cafebe52/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tal-tech/d_stack/c10b77f8f13b1fb9de25daad99b25a50cafebe52/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tal-tech/d_stack/c10b77f8f13b1fb9de25daad99b25a50cafebe52/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tal-tech/d_stack/c10b77f8f13b1fb9de25daad99b25a50cafebe52/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tal-tech/d_stack/c10b77f8f13b1fb9de25daad99b25a50cafebe52/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tal-tech/d_stack/c10b77f8f13b1fb9de25daad99b25a50cafebe52/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tal-tech/d_stack/c10b77f8f13b1fb9de25daad99b25a50cafebe52/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tal-tech/d_stack/c10b77f8f13b1fb9de25daad99b25a50cafebe52/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tal-tech/d_stack/c10b77f8f13b1fb9de25daad99b25a50cafebe52/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tal-tech/d_stack/c10b77f8f13b1fb9de25daad99b25a50cafebe52/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "LaunchImage.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "LaunchImage@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "LaunchImage@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tal-tech/d_stack/c10b77f8f13b1fb9de25daad99b25a50cafebe52/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tal-tech/d_stack/c10b77f8f13b1fb9de25daad99b25a50cafebe52/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tal-tech/d_stack/c10b77f8f13b1fb9de25daad99b25a50cafebe52/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md: -------------------------------------------------------------------------------- 1 | # Launch Screen Assets 2 | 3 | You can customize the launch screen with your own desired assets by replacing the image files in this directory. 4 | 5 | You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. -------------------------------------------------------------------------------- /example/ios/Runner/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /example/ios/Runner/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /example/ios/Runner/CustomViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // CustomViewController.h 3 | // Runner 4 | // 5 | // Created by Caven on 2021/1/22. 6 | // Copyright © 2021 The Chromium Authors. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | @interface CustomViewController : UIAlertController 14 | 15 | @end 16 | 17 | NS_ASSUME_NONNULL_END 18 | -------------------------------------------------------------------------------- /example/ios/Runner/CustomViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // CustomViewController.m 3 | // Runner 4 | // 5 | // Created by Caven on 2021/1/22. 6 | // Copyright © 2021 The Chromium Authors. All rights reserved. 7 | // 8 | 9 | #import "CustomViewController.h" 10 | 11 | @interface CustomViewController () 12 | 13 | @end 14 | 15 | @implementation CustomViewController 16 | 17 | - (void)viewDidLoad { 18 | [super viewDidLoad]; 19 | // Do any additional setup after loading the view. 20 | } 21 | 22 | /* 23 | #pragma mark - Navigation 24 | 25 | // In a storyboard-based application, you will often want to do a little preparation before navigation 26 | - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender { 27 | // Get the new view controller using [segue destinationViewController]. 28 | // Pass the selected object to the new view controller. 29 | } 30 | */ 31 | 32 | @end 33 | -------------------------------------------------------------------------------- /example/ios/Runner/DStackTestCase.h: -------------------------------------------------------------------------------- 1 | // 2 | // DStackTestCase.h 3 | // Runner 4 | // 5 | // Created by Caven on 2020/12/4. 6 | // Copyright © 2020 The Chromium Authors. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | @interface DStackTestCase : NSObject 14 | 15 | @property (nonatomic, readonly) NSArray *homeTestCases; 16 | @property (nonatomic, readonly) NSArray *secondVCTestCases; 17 | @property (nonatomic, readonly) NSArray *thirdVCTestCases; 18 | @property (nonatomic, readonly) NSArray *fourVCTestCases; 19 | @property (nonatomic, readonly) NSArray *fiveVCTestCases; 20 | @property (nonatomic, readonly) NSArray *sixVCTestCases; 21 | 22 | @end 23 | 24 | NS_ASSUME_NONNULL_END 25 | -------------------------------------------------------------------------------- /example/ios/Runner/DStackViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // DStackViewController.h 3 | // Runner 4 | // 5 | // Created by TAL on 2020/2/12. 6 | // Copyright © 2020 The Chromium Authors. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | @interface DStackViewController : UINavigationController 14 | 15 | @end 16 | 17 | NS_ASSUME_NONNULL_END 18 | -------------------------------------------------------------------------------- /example/ios/Runner/DStackViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // DStackViewController.m 3 | // Runner 4 | // 5 | // Created by TAL on 2020/2/12. 6 | // Copyright © 2020 The Chromium Authors. All rights reserved. 7 | // 8 | 9 | #import "DStackViewController.h" 10 | 11 | @interface DStackViewController () 12 | 13 | @end 14 | 15 | @implementation DStackViewController 16 | 17 | - (void)viewDidLoad { 18 | [super viewDidLoad]; 19 | // Do any additional setup after loading the view. 20 | self.navigationBarHidden = YES; 21 | } 22 | 23 | /* 24 | #pragma mark - Navigation 25 | 26 | // In a storyboard-based application, you will often want to do a little preparation before navigation 27 | - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender { 28 | // Get the new view controller using [segue destinationViewController]. 29 | // Pass the selected object to the new view controller. 30 | } 31 | */ 32 | 33 | @end 34 | -------------------------------------------------------------------------------- /example/ios/Runner/DemoFlutterViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // DemoFlutterViewController.h 3 | // Runner 4 | // 5 | // Created by Caven on 2020/12/4. 6 | // Copyright © 2020 The Chromium Authors. All rights reserved. 7 | // 8 | 9 | #import "DFlutterViewController.h" 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | @interface DemoFlutterViewController : DFlutterViewController 14 | 15 | @end 16 | 17 | NS_ASSUME_NONNULL_END 18 | -------------------------------------------------------------------------------- /example/ios/Runner/DemoFlutterViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // DemoFlutterViewController.m 3 | // Runner 4 | // 5 | // Created by Caven on 2020/12/4. 6 | // Copyright © 2020 The Chromium Authors. All rights reserved. 7 | // 8 | 9 | #import "DemoFlutterViewController.h" 10 | 11 | @interface DemoFlutterViewController () 12 | 13 | @end 14 | 15 | @implementation DemoFlutterViewController 16 | 17 | - (void)viewDidLoad { 18 | [super viewDidLoad]; 19 | // Do any additional setup after loading the view. 20 | } 21 | 22 | /* 23 | #pragma mark - Navigation 24 | 25 | // In a storyboard-based application, you will often want to do a little preparation before navigation 26 | - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender { 27 | // Get the new view controller using [segue destinationViewController]. 28 | // Pass the selected object to the new view controller. 29 | } 30 | */ 31 | 32 | @end 33 | -------------------------------------------------------------------------------- /example/ios/Runner/FiveViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // FiveViewController.h 3 | // Runner 4 | // 5 | // Created by Caven on 2020/12/8. 6 | // Copyright © 2020 The Chromium Authors. All rights reserved. 7 | // 8 | 9 | #import "HomeViewController.h" 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | @interface FiveViewController : HomeViewController 14 | 15 | @end 16 | 17 | NS_ASSUME_NONNULL_END 18 | -------------------------------------------------------------------------------- /example/ios/Runner/FiveViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // FiveViewController.m 3 | // Runner 4 | // 5 | // Created by Caven on 2020/12/8. 6 | // Copyright © 2020 The Chromium Authors. All rights reserved. 7 | // 8 | 9 | #import "FiveViewController.h" 10 | 11 | @interface FiveViewController () 12 | 13 | @end 14 | 15 | @implementation FiveViewController 16 | 17 | - (void)viewDidLoad { 18 | [super viewDidLoad]; 19 | } 20 | 21 | - (NSArray *)dataSource 22 | { 23 | return self.testCase.fiveVCTestCases; 24 | } 25 | 26 | @end 27 | -------------------------------------------------------------------------------- /example/ios/Runner/FourViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // FourViewController.h 3 | // Runner 4 | // 5 | // Created by Caven on 2020/8/27. 6 | // Copyright © 2020 The Chromium Authors. All rights reserved. 7 | // 8 | 9 | #import "HomeViewController.h" 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | @interface FourViewController : HomeViewController 14 | 15 | @end 16 | 17 | NS_ASSUME_NONNULL_END 18 | -------------------------------------------------------------------------------- /example/ios/Runner/FourViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // FourViewController.m 3 | // Runner 4 | // 5 | // Created by Caven on 2020/8/27. 6 | // Copyright © 2020 The Chromium Authors. All rights reserved. 7 | // 8 | 9 | #import "FourViewController.h" 10 | 11 | @interface FourViewController () 12 | 13 | @end 14 | 15 | @implementation FourViewController 16 | 17 | - (void)viewDidLoad { 18 | [super viewDidLoad]; 19 | } 20 | 21 | - (NSArray *)dataSource 22 | { 23 | return self.testCase.fourVCTestCases; 24 | } 25 | 26 | @end 27 | -------------------------------------------------------------------------------- /example/ios/Runner/HomeViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // HomeViewController.h 3 | // Runner 4 | // 5 | // Created by TAL on 2020/2/11. 6 | // Copyright © 2020 The Chromium Authors. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "DStackTestCase.h" 11 | 12 | NS_ASSUME_NONNULL_BEGIN 13 | 14 | @interface HomeViewController : UIViewController 15 | 16 | @property (nonatomic, strong) DStackTestCase *testCase; 17 | 18 | 19 | - (NSArray *)dataSource; 20 | 21 | @end 22 | 23 | NS_ASSUME_NONNULL_END 24 | -------------------------------------------------------------------------------- /example/ios/Runner/HomeViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // HomeViewController.m 3 | // Runner 4 | // 5 | // Created by TAL on 2020/2/11. 6 | // Copyright © 2020 The Chromium Authors. All rights reserved. 7 | // 8 | 9 | #import "HomeViewController.h" 10 | 11 | @interface HomeViewController () 12 | 13 | @property (nonatomic, strong) UITableView *tableView; 14 | @property (nonatomic, strong) UILabel *titleView; 15 | 16 | @end 17 | 18 | @implementation HomeViewController 19 | 20 | - (void)viewWillAppear:(BOOL)animated 21 | { 22 | [super viewWillAppear:animated]; 23 | [self.navigationController setNavigationBarHidden:YES]; 24 | } 25 | 26 | - (void)viewDidLoad { 27 | [super viewDidLoad]; 28 | // Do any additional setup after loading the view. 29 | [self.view addSubview:self.titleView]; 30 | [self.view addSubview:self.tableView]; 31 | [self setNavigationTitle]; 32 | } 33 | 34 | - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section 35 | { 36 | return [[self dataSource] count]; 37 | } 38 | 39 | - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath 40 | { 41 | UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"cell"]; 42 | if (!cell) { 43 | cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"cell"]; 44 | } 45 | cell.textLabel.textColor = [UIColor blackColor]; 46 | cell.textLabel.textAlignment = NSTextAlignmentCenter; 47 | cell.backgroundColor = [UIColor whiteColor]; 48 | NSDictionary *data = self.dataSource[indexPath.row]; 49 | cell.textLabel.text = data[@"text"]; 50 | return cell; 51 | } 52 | 53 | - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath 54 | { 55 | [tableView deselectRowAtIndexPath:indexPath animated:YES]; 56 | NSDictionary *data = self.dataSource[indexPath.row]; 57 | void (^block)(UIViewController *) = data[@"clicked"]; 58 | if (block) { 59 | block(self); 60 | } 61 | } 62 | 63 | - (void)setNavigationTitle 64 | { 65 | self.titleView.text = NSStringFromClass(self.class); 66 | } 67 | 68 | - (NSArray *)dataSource 69 | { 70 | return self.testCase.homeTestCases; 71 | } 72 | 73 | 74 | - (UITableView *)tableView 75 | { 76 | if (!_tableView) { 77 | CGFloat bottom = self.titleView.frame.size.height; 78 | _tableView = [[UITableView alloc] initWithFrame:CGRectMake(0, bottom, self.view.frame.size.width, self.view.frame.size.height - bottom)]; 79 | _tableView.delegate = self; 80 | _tableView.dataSource = self; 81 | _tableView.rowHeight = 60; 82 | _tableView.backgroundColor = [UIColor whiteColor]; 83 | } 84 | return _tableView; 85 | } 86 | 87 | - (UILabel *)titleView 88 | { 89 | if (!_titleView) { 90 | CGFloat height = [UIApplication sharedApplication].statusBarFrame.size.height + 44; 91 | _titleView = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, self.view.frame.size.width, height)]; 92 | _titleView.textAlignment = NSTextAlignmentCenter; 93 | _titleView.backgroundColor = [UIColor orangeColor]; 94 | } 95 | return _titleView; 96 | } 97 | 98 | - (DStackTestCase *)testCase 99 | { 100 | if (!_testCase) { 101 | _testCase = [[DStackTestCase alloc] init]; 102 | } 103 | return _testCase; 104 | } 105 | 106 | - (void)dealloc 107 | { 108 | NSLog(@"dealloc ==> %@", NSStringFromClass(self.class)); 109 | } 110 | 111 | @end 112 | -------------------------------------------------------------------------------- /example/ios/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | d_stack_example 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | $(FLUTTER_BUILD_NAME) 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(FLUTTER_BUILD_NUMBER) 23 | LSRequiresIPhoneOS 24 | 25 | UIFileSharingEnabled 26 | 27 | UILaunchStoryboardName 28 | LaunchScreen 29 | UISupportedInterfaceOrientations 30 | 31 | UIInterfaceOrientationPortrait 32 | 33 | UISupportedInterfaceOrientations~ipad 34 | 35 | UIInterfaceOrientationPortrait 36 | UIInterfaceOrientationPortraitUpsideDown 37 | UIInterfaceOrientationLandscapeLeft 38 | UIInterfaceOrientationLandscapeRight 39 | 40 | UIViewControllerBasedStatusBarAppearance 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /example/ios/Runner/SecondViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // SecondViewController.h 3 | // Runner 4 | // 5 | // Created by TAL on 2020/2/11. 6 | // Copyright © 2020 The Chromium Authors. All rights reserved. 7 | // 8 | 9 | #import "HomeViewController.h" 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | @interface SecondViewController : HomeViewController 14 | 15 | @end 16 | 17 | NS_ASSUME_NONNULL_END 18 | -------------------------------------------------------------------------------- /example/ios/Runner/SecondViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // SecondViewController.m 3 | // Runner 4 | // 5 | // Created by TAL on 2020/2/11. 6 | // Copyright © 2020 The Chromium Authors. All rights reserved. 7 | // 8 | 9 | #import "SecondViewController.h" 10 | #import "ThirdViewController.h" 11 | 12 | @interface SecondViewController () 13 | 14 | @end 15 | 16 | @implementation SecondViewController 17 | 18 | - (void)viewDidLoad { 19 | [super viewDidLoad]; 20 | } 21 | 22 | - (NSArray *)dataSource 23 | { 24 | return self.testCase.secondVCTestCases; 25 | } 26 | 27 | 28 | 29 | @end 30 | -------------------------------------------------------------------------------- /example/ios/Runner/SixViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // SixViewController.h 3 | // Runner 4 | // 5 | // Created by Caven on 2020/12/9. 6 | // Copyright © 2020 The Chromium Authors. All rights reserved. 7 | // 8 | 9 | #import "HomeViewController.h" 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | @interface SixViewController : HomeViewController 14 | 15 | @end 16 | 17 | NS_ASSUME_NONNULL_END 18 | -------------------------------------------------------------------------------- /example/ios/Runner/SixViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // SixViewController.m 3 | // Runner 4 | // 5 | // Created by Caven on 2020/12/9. 6 | // Copyright © 2020 The Chromium Authors. All rights reserved. 7 | // 8 | 9 | #import "SixViewController.h" 10 | 11 | @interface SixViewController () 12 | 13 | @end 14 | 15 | @implementation SixViewController 16 | 17 | - (void)viewDidLoad { 18 | [super viewDidLoad]; 19 | // Do any additional setup after loading the view. 20 | } 21 | 22 | - (NSArray *)dataSource 23 | { 24 | return self.testCase.sixVCTestCases; 25 | } 26 | 27 | @end 28 | -------------------------------------------------------------------------------- /example/ios/Runner/ThirdViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // ThirdViewController.h 3 | // Runner 4 | // 5 | // Created by Caven on 2020/8/27. 6 | // Copyright © 2020 The Chromium Authors. All rights reserved. 7 | // 8 | 9 | #import "HomeViewController.h" 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | @interface ThirdViewController : HomeViewController 14 | 15 | @end 16 | 17 | NS_ASSUME_NONNULL_END 18 | -------------------------------------------------------------------------------- /example/ios/Runner/ThirdViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // ThirdViewController.m 3 | // Runner 4 | // 5 | // Created by Caven on 2020/8/27. 6 | // Copyright © 2020 The Chromium Authors. All rights reserved. 7 | // 8 | 9 | #import "ThirdViewController.h" 10 | 11 | @interface ThirdViewController () 12 | 13 | @end 14 | 15 | @implementation ThirdViewController 16 | 17 | 18 | - (void)viewDidLoad { 19 | [super viewDidLoad]; 20 | 21 | } 22 | 23 | - (NSArray *)dataSource 24 | { 25 | return self.testCase.thirdVCTestCases; 26 | } 27 | 28 | @end 29 | -------------------------------------------------------------------------------- /example/ios/Runner/main.m: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | #import "AppDelegate.h" 4 | 5 | int main(int argc, char* argv[]) { 6 | @autoreleasepool { 7 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /example/lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'package:d_stack/d_stack.dart'; 2 | import 'package:d_stack/observer/life_cycle_observer.dart'; 3 | import 'package:d_stack/widget/home_widget.dart'; 4 | import 'package:flutter/material.dart'; 5 | 6 | import 'page_widgets.dart'; 7 | 8 | final bool isFlutterProject = true; 9 | 10 | void main() { 11 | // 注册路由builder , 生命周期监听 12 | WidgetsFlutterBinding.ensureInitialized(); 13 | DStack.instance.register( 14 | builders: RouterBuilder.builders(), observer: MyLifeCycleObserver()); 15 | runApp(MyApp()); 16 | } 17 | 18 | class MyApp extends StatefulWidget { 19 | @override 20 | _MyAppState createState() => _MyAppState(); 21 | } 22 | 23 | class _MyAppState extends State with WidgetsBindingObserver { 24 | @override 25 | void initState() { 26 | super.initState(); 27 | WidgetsBinding.instance!.addObserver(this); 28 | } 29 | 30 | @override 31 | void dispose() { 32 | WidgetsBinding.instance!.removeObserver(this); 33 | super.dispose(); 34 | } 35 | 36 | @override 37 | void didChangeAppLifecycleState(AppLifecycleState state) { 38 | print("didChangeAppLifecycleState ==== $state"); 39 | } 40 | 41 | @override 42 | Widget build(BuildContext context) { 43 | return MaterialApp( 44 | navigatorKey: DStack.instance.navigatorKey, 45 | navigatorObservers: [DStack.instance.dStackNavigatorObserver], 46 | home: isFlutterProject 47 | ? DStackWidget( 48 | homePage: Page1(), 49 | homePageRoute: 'page1', 50 | ) 51 | : DStackWidget(), 52 | theme: ThemeData(platform: TargetPlatform.iOS), 53 | ); 54 | } 55 | } 56 | 57 | class RouterBuilder { 58 | static Map builders() { 59 | Map builders = { 60 | 'page1': page1Builder, 61 | 'page2': page2Builder, 62 | 'page3': page3Builder, 63 | 'page4': page4Builder, 64 | 'page5': page5Builder, 65 | 'page6': page6Builder, 66 | 'page7': page7Builder, 67 | }; 68 | return builders; 69 | } 70 | 71 | static DStackWidgetBuilder page1Builder = (Map? params) { 72 | return (BuildContext context) { 73 | return Page1(); 74 | }; 75 | }; 76 | 77 | static DStackWidgetBuilder page2Builder = (Map? params) { 78 | return (BuildContext context) { 79 | return Page2(); 80 | }; 81 | }; 82 | 83 | static DStackWidgetBuilder page3Builder = (Map? params) { 84 | return (BuildContext context) { 85 | return Page3(); 86 | }; 87 | }; 88 | 89 | static DStackWidgetBuilder page4Builder = (Map? params) { 90 | return (BuildContext context) { 91 | return Page4(); 92 | }; 93 | }; 94 | 95 | static DStackWidgetBuilder page5Builder = (Map? params) { 96 | return (BuildContext context) { 97 | return Page5(); 98 | }; 99 | }; 100 | 101 | static DStackWidgetBuilder page6Builder = (Map? params) { 102 | return (BuildContext context) { 103 | return Page6(); 104 | }; 105 | }; 106 | 107 | static DStackWidgetBuilder page7Builder = (Map? params) { 108 | return (BuildContext context) { 109 | return Page7(); 110 | }; 111 | }; 112 | } 113 | 114 | class MyLifeCycleObserver extends DLifeCycleObserver { 115 | @override 116 | void appDidEnterBackground(PageModel model) { 117 | debugPrint( 118 | "MyLifeCycleObserver appDidEnterBackground == ${model.currentPageRoute}"); 119 | } 120 | 121 | @override 122 | void appDidEnterForeground(PageModel model) { 123 | debugPrint( 124 | "MyLifeCycleObserver appDidEnterForeground == ${model.currentPageRoute}"); 125 | } 126 | 127 | @override 128 | void appDidStart(PageModel model) { 129 | debugPrint("MyLifeCycleObserver appDidStart == ${model.currentPageRoute}"); 130 | } 131 | 132 | @override 133 | void pageAppear(PageModel model) { 134 | debugPrint("MyLifeCycleObserver pageAppear model:${model.toString()}"); 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /example/lib/page_widgets.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * Created with Android Studio. 3 | * User: whqfor 4 | * Date: 2019-11-26 5 | * Time: 14:47 6 | * email: wanghuaqiang@100tal.com 7 | * tartget: page_widgets 8 | */ 9 | 10 | import 'package:d_stack/d_stack.dart'; 11 | import 'package:flutter/material.dart'; 12 | 13 | import 'test_case.dart'; 14 | 15 | class Student { 16 | String? name; 17 | int? age; 18 | String? address; 19 | } 20 | 21 | Widget _caseWidget(List items, {BuildContext? context}) { 22 | return Center( 23 | child: ListView.builder( 24 | itemExtent: 60, 25 | itemCount: items.length, 26 | itemBuilder: (BuildContext context, index) { 27 | Map caseMap = items[index]; 28 | return GestureDetector( 29 | behavior: HitTestBehavior.opaque, 30 | child: Container( 31 | alignment: Alignment.center, 32 | decoration: BoxDecoration( 33 | border: 34 | Border(bottom: BorderSide(color: Colors.grey, width: 0.5))), 35 | child: Text(caseMap["text"]), 36 | ), 37 | onTap: () { 38 | caseMap["clicked"](context); 39 | }, 40 | ); 41 | }, 42 | ), 43 | ); 44 | } 45 | 46 | class Page1 extends StatefulWidget { 47 | @override 48 | State createState() { 49 | return _Page1(); 50 | } 51 | } 52 | 53 | // native, 54 | // nativeModal, 55 | // inFromLeft, 56 | // inFromTop, 57 | // inFromRight, 58 | // inFromBottom, 59 | // fadeIn, 60 | // custom, 61 | // material, 62 | // materialFullScreenDialog, 63 | // cupertino, 64 | // cupertinoFullScreenDialog, 65 | // fadeOpaque, ///透明路由类型 66 | // fadeAndScale, 67 | // none, 68 | class _Page1 extends State { 69 | @override 70 | Widget build(BuildContext context) { 71 | final Map? args = 72 | ModalRoute.of(context)!.settings.arguments as Map?; 73 | debugPrint('page1收到前一个页面传来的参数 ==> $args'); 74 | return Scaffold( 75 | appBar: AppBar(title: Text('flutter page1'), leading: Container()), 76 | backgroundColor: Colors.white, 77 | body: _caseWidget(TestCase.openFlutterPageCase), 78 | floatingActionButton: FloatingActionButton.extended( 79 | onPressed: () { 80 | DStack.animatedFlutterPage('page2', transition: TransitionType.fadeAndScale); 81 | }, 82 | label: const Text('animation'), 83 | icon: const Icon(Icons.touch_app_outlined), 84 | backgroundColor: Colors.pink, 85 | ), 86 | ); 87 | } 88 | } 89 | 90 | class Page2 extends StatelessWidget { 91 | @override 92 | Widget build(BuildContext context) { 93 | final Map? args = 94 | ModalRoute.of(context)!.settings.arguments as Map?; 95 | debugPrint('page2收到前一个页面传来的参数 ==> $args'); 96 | 97 | return Scaffold( 98 | appBar: AppBar( 99 | title: Text('flutter page2'), 100 | leading: ElevatedButton( 101 | child: Text('返回'), 102 | onPressed: () { 103 | DStack.pop(result: {"test":"测试page2数据返回"}); 104 | }, 105 | )), 106 | body: _caseWidget(TestCase.closeFlutterPage, context: context), 107 | ); 108 | } 109 | } 110 | 111 | class Page3 extends StatelessWidget { 112 | @override 113 | Widget build(BuildContext context) { 114 | return Scaffold( 115 | appBar: AppBar( 116 | title: Text('flutter page3'), 117 | leading: ElevatedButton( 118 | child: Text('返回'), 119 | onPressed: () { 120 | DStack.pop(); 121 | }, 122 | )), 123 | body: _caseWidget(TestCase.popToPage), 124 | ); 125 | } 126 | } 127 | 128 | class Page4 extends StatelessWidget { 129 | @override 130 | Widget build(BuildContext context) { 131 | return Scaffold( 132 | appBar: AppBar( 133 | title: Text('flutter page4'), 134 | leading: ElevatedButton( 135 | child: Text('返回'), 136 | onPressed: () { 137 | DStack.pop(result: {"test":"测试page4数据返回"}); 138 | }, 139 | ), 140 | ), 141 | body: _caseWidget(TestCase.openFlutterPageCase), 142 | ); 143 | } 144 | } 145 | 146 | class Page5 extends StatelessWidget { 147 | @override 148 | Widget build(BuildContext context) { 149 | return Scaffold( 150 | appBar: AppBar( 151 | title: Text('flutter page5'), 152 | leading: ElevatedButton( 153 | child: Text('返回'), 154 | onPressed: () { 155 | DStack.pop(); 156 | }, 157 | ), 158 | ), 159 | body: _caseWidget(TestCase.page5Cases), 160 | ); 161 | } 162 | } 163 | 164 | class Page6 extends StatelessWidget { 165 | @override 166 | Widget build(BuildContext context) { 167 | return Scaffold( 168 | appBar: AppBar( 169 | title: Text('flutter page6'), 170 | leading: ElevatedButton( 171 | child: Text('返回'), 172 | onPressed: () { 173 | DStack.pop(); 174 | }, 175 | ), 176 | ), 177 | body: _caseWidget(TestCase.page6Cases), 178 | ); 179 | } 180 | } 181 | 182 | class Page7 extends StatelessWidget { 183 | @override 184 | Widget build(BuildContext context) { 185 | return Scaffold( 186 | appBar: AppBar( 187 | title: Text('flutter page7'), 188 | leading: ElevatedButton( 189 | child: Text('返回'), 190 | onPressed: () { 191 | DStack.pop(); 192 | }, 193 | ), 194 | ), 195 | body: _caseWidget(TestCase.page7Cases), 196 | ); 197 | } 198 | } 199 | -------------------------------------------------------------------------------- /example/lib/will_pop_scope_route.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/widgets.dart'; 2 | 3 | class WillPopScopeRoute extends StatefulWidget { 4 | final Widget child; 5 | 6 | WillPopScopeRoute(this.child); 7 | 8 | @override 9 | WillPopScopeRouteState createState() { 10 | return new WillPopScopeRouteState(); 11 | } 12 | } 13 | 14 | class WillPopScopeRouteState extends State { 15 | DateTime? _lastPressedAt; //上次点击时间 16 | 17 | @override 18 | Widget build(BuildContext context) { 19 | return new WillPopScope( 20 | onWillPop: () async { 21 | if (_lastPressedAt == null || 22 | DateTime.now().difference(_lastPressedAt!) > 23 | Duration(seconds: 2)) { 24 | print('2秒后在按一次退出'); 25 | //两次点击间隔超过1秒则重新计时 26 | _lastPressedAt = DateTime.now(); 27 | return false; 28 | } else { 29 | print('退出了'); 30 | return true; 31 | } 32 | }, 33 | child: widget.child); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /example/pubspec.lock: -------------------------------------------------------------------------------- 1 | # Generated by pub 2 | # See https://dart.dev/tools/pub/glossary#lockfile 3 | packages: 4 | async: 5 | dependency: transitive 6 | description: 7 | name: async 8 | url: "https://pub.flutter-io.cn" 9 | source: hosted 10 | version: "2.8.1" 11 | boolean_selector: 12 | dependency: transitive 13 | description: 14 | name: boolean_selector 15 | url: "https://pub.flutter-io.cn" 16 | source: hosted 17 | version: "2.1.0" 18 | characters: 19 | dependency: transitive 20 | description: 21 | name: characters 22 | url: "https://pub.flutter-io.cn" 23 | source: hosted 24 | version: "1.1.0" 25 | charcode: 26 | dependency: transitive 27 | description: 28 | name: charcode 29 | url: "https://pub.flutter-io.cn" 30 | source: hosted 31 | version: "1.3.1" 32 | clock: 33 | dependency: transitive 34 | description: 35 | name: clock 36 | url: "https://pub.flutter-io.cn" 37 | source: hosted 38 | version: "1.1.0" 39 | collection: 40 | dependency: transitive 41 | description: 42 | name: collection 43 | url: "https://pub.flutter-io.cn" 44 | source: hosted 45 | version: "1.15.0" 46 | cupertino_icons: 47 | dependency: "direct main" 48 | description: 49 | name: cupertino_icons 50 | url: "https://pub.flutter-io.cn" 51 | source: hosted 52 | version: "0.1.3" 53 | d_stack: 54 | dependency: "direct main" 55 | description: 56 | path: ".." 57 | relative: true 58 | source: path 59 | version: "1.3.4+3-nullsafety" 60 | fake_async: 61 | dependency: transitive 62 | description: 63 | name: fake_async 64 | url: "https://pub.flutter-io.cn" 65 | source: hosted 66 | version: "1.2.0" 67 | flutter: 68 | dependency: "direct main" 69 | description: flutter 70 | source: sdk 71 | version: "0.0.0" 72 | flutter_test: 73 | dependency: "direct dev" 74 | description: flutter 75 | source: sdk 76 | version: "0.0.0" 77 | matcher: 78 | dependency: transitive 79 | description: 80 | name: matcher 81 | url: "https://pub.flutter-io.cn" 82 | source: hosted 83 | version: "0.12.10" 84 | meta: 85 | dependency: transitive 86 | description: 87 | name: meta 88 | url: "https://pub.flutter-io.cn" 89 | source: hosted 90 | version: "1.7.0" 91 | path: 92 | dependency: transitive 93 | description: 94 | name: path 95 | url: "https://pub.flutter-io.cn" 96 | source: hosted 97 | version: "1.8.0" 98 | sky_engine: 99 | dependency: transitive 100 | description: flutter 101 | source: sdk 102 | version: "0.0.99" 103 | source_span: 104 | dependency: transitive 105 | description: 106 | name: source_span 107 | url: "https://pub.flutter-io.cn" 108 | source: hosted 109 | version: "1.8.1" 110 | stack_trace: 111 | dependency: transitive 112 | description: 113 | name: stack_trace 114 | url: "https://pub.flutter-io.cn" 115 | source: hosted 116 | version: "1.10.0" 117 | stream_channel: 118 | dependency: transitive 119 | description: 120 | name: stream_channel 121 | url: "https://pub.flutter-io.cn" 122 | source: hosted 123 | version: "2.1.0" 124 | string_scanner: 125 | dependency: transitive 126 | description: 127 | name: string_scanner 128 | url: "https://pub.flutter-io.cn" 129 | source: hosted 130 | version: "1.1.0" 131 | term_glyph: 132 | dependency: transitive 133 | description: 134 | name: term_glyph 135 | url: "https://pub.flutter-io.cn" 136 | source: hosted 137 | version: "1.2.0" 138 | test_api: 139 | dependency: transitive 140 | description: 141 | name: test_api 142 | url: "https://pub.flutter-io.cn" 143 | source: hosted 144 | version: "0.4.2" 145 | typed_data: 146 | dependency: transitive 147 | description: 148 | name: typed_data 149 | url: "https://pub.flutter-io.cn" 150 | source: hosted 151 | version: "1.3.0" 152 | vector_math: 153 | dependency: transitive 154 | description: 155 | name: vector_math 156 | url: "https://pub.flutter-io.cn" 157 | source: hosted 158 | version: "2.1.0" 159 | sdks: 160 | dart: ">=2.12.0 <3.0.0" 161 | flutter: ">=2.0.0" 162 | -------------------------------------------------------------------------------- /example/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: d_stack_example 2 | description: Demonstrates how to use the d_stack plugin. 3 | publish_to: 'none' 4 | 5 | environment: 6 | sdk: '>=2.12.0 <3.0.0' 7 | 8 | dependencies: 9 | flutter: 10 | sdk: flutter 11 | d_stack: 12 | path: ../ 13 | 14 | # The following adds the Cupertino Icons font to your application. 15 | # Use with the CupertinoIcons class for iOS style icons. 16 | cupertino_icons: 0.1.3 17 | 18 | dev_dependencies: 19 | flutter_test: 20 | sdk: flutter 21 | 22 | 23 | # For information on the generic Dart part of this file, see the 24 | # following page: https://dart.dev/tools/pub/pubspec 25 | 26 | # The following section is specific to Flutter. 27 | flutter: 28 | 29 | # The following line ensures that the Material Icons font is 30 | # included with your application, so that you can use the icons in 31 | # the material Icons class. 32 | uses-material-design: true 33 | 34 | # To add assets to your application, add an assets section, like this: 35 | # assets: 36 | # - images/a_dot_burr.jpeg 37 | # - images/a_dot_ham.jpeg 38 | 39 | # An image asset can refer to one or more resolution-specific "variants", see 40 | # https://flutter.dev/assets-and-images/#resolution-aware. 41 | 42 | # For details regarding adding assets from package dependencies, see 43 | # https://flutter.dev/assets-and-images/#from-packages 44 | 45 | # To add custom fonts to your application, add a fonts section here, 46 | # in this "flutter" section. Each entry in this list should have a 47 | # "family" key with the font family name, and a "fonts" key with a 48 | # list giving the asset and other descriptors for the font. For 49 | # example: 50 | # fonts: 51 | # - family: Schyler 52 | # fonts: 53 | # - asset: fonts/Schyler-Regular.ttf 54 | # - asset: fonts/Schyler-Italic.ttf 55 | # style: italic 56 | # - family: Trajan Pro 57 | # fonts: 58 | # - asset: fonts/TrajanPro.ttf 59 | # - asset: fonts/TrajanPro_Bold.ttf 60 | # weight: 700 61 | # 62 | # For details regarding fonts from package dependencies, 63 | # see https://flutter.dev/custom-fonts/#from-packages 64 | -------------------------------------------------------------------------------- /example/test/widget_test.dart: -------------------------------------------------------------------------------- 1 | // This is a basic Flutter widget test. 2 | // 3 | // To perform an interaction with a widget in your test, use the WidgetTester 4 | // utility that Flutter provides. For example, you can send tap and scroll 5 | // gestures. You can also use WidgetTester to find child widgets in the widget 6 | // tree, read text, and verify that the values of widget properties are correct. 7 | 8 | import 'package:flutter_test/flutter_test.dart'; 9 | 10 | import '../lib/main.dart'; 11 | 12 | void main() { 13 | testWidgets('Verify Platform version', (WidgetTester tester) async { 14 | // Build our app and trigger a frame. 15 | await tester.pumpWidget(MyApp()); 16 | }); 17 | } 18 | -------------------------------------------------------------------------------- /ios/.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | .vagrant/ 3 | .sconsign.dblite 4 | .svn/ 5 | 6 | .DS_Store 7 | *.swp 8 | profile 9 | 10 | DerivedData/ 11 | build/ 12 | GeneratedPluginRegistrant.h 13 | GeneratedPluginRegistrant.m 14 | 15 | .generated/ 16 | 17 | *.pbxuser 18 | *.mode1v3 19 | *.mode2v3 20 | *.perspectivev3 21 | 22 | !default.pbxuser 23 | !default.mode1v3 24 | !default.mode2v3 25 | !default.perspectivev3 26 | 27 | xcuserdata 28 | 29 | *.moved-aside 30 | 31 | *.pyc 32 | *sync/ 33 | Icon? 34 | .tags* 35 | 36 | /Flutter/Generated.xcconfig 37 | /Flutter/flutter_export_environment.sh -------------------------------------------------------------------------------- /ios/Assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tal-tech/d_stack/c10b77f8f13b1fb9de25daad99b25a50cafebe52/ios/Assets/.gitkeep -------------------------------------------------------------------------------- /ios/Classes/Node/DActionManager.h: -------------------------------------------------------------------------------- 1 | // 2 | // DActionManager.h 3 | // 管理跳转事件 4 | // 5 | // Created by TAL on 2020/1/16. 6 | // 7 | 8 | #import 9 | #import "DNode.h" 10 | 11 | @class DStackNode; 12 | 13 | NS_ASSUME_NONNULL_BEGIN 14 | 15 | /// 页面跳转检测器 16 | /// DNodeManager传递待跳转的节点过来 17 | /// Flutter 进入 Native,发送Node节点至Native 18 | /// Native 进入 Flutter, 发送消息至Flutter 19 | @interface DActionManager : NSObject 20 | 21 | + (void)handlerActionWithNodeList:(NSArray *)nodeList 22 | node:(DNode *)node; 23 | /// rootVC是不是FlutterController 24 | + (BOOL)rootControllerIsFlutterController; 25 | 26 | /// 当前的rootViewController 27 | + (UIViewController *)rootController; 28 | 29 | /// 检查flutter Engine的FlutterViewController是否存在 30 | + (void)checkFlutterViewController; 31 | 32 | /// 当前控制前 33 | + (UIViewController *)currentController; 34 | 35 | /// DNode创建DStackNode 36 | /// @param node DStackNode 37 | + (DStackNode *)stackNodeFromNode:(DNode *)node; 38 | 39 | /// tabBar切换事件 40 | /// @param viewController 将要切换的viewController 41 | /// @param route homePageRoute 42 | + (void)tabBarWillSelectViewController:(UIViewController *)viewController 43 | homePageRoute:(NSString *)route; 44 | 45 | @end 46 | 47 | NS_ASSUME_NONNULL_END 48 | -------------------------------------------------------------------------------- /ios/Classes/Node/DNode.h: -------------------------------------------------------------------------------- 1 | // 2 | // DNode.h 3 | // 节点类 4 | // 记录节点信息,把每个page映射成DNode 5 | // 6 | // Created by TAL on 2020/1/16. 7 | // 8 | 9 | #import 10 | #import "DStackProvider.h" 11 | 12 | NS_ASSUME_NONNULL_BEGIN 13 | 14 | /// 节点类 15 | @interface DNode : NSObject 16 | 17 | /// 页面跳转类型 18 | @property (nonatomic, assign) DNodeActionType action; 19 | 20 | /// 页面类型 21 | @property (nonatomic, assign) DNodePageType pageType; 22 | 23 | /// 页面唯一标识 24 | /// Flutter页面时是route 25 | /// Native页面时是类名 26 | @property (nonatomic, copy) NSString *target; 27 | 28 | /// 附带参数 29 | @property (nonatomic, strong) NSDictionary *params; 30 | 31 | /// 是否来自Flutter消息通道的Node 32 | @property (nonatomic, assign) BOOL fromFlutter; 33 | 34 | /// 是否可以移除节点 35 | /// 收到didPop消息时才是YES,只有flutter页面会有didDop消息 36 | @property (nonatomic, assign) BOOL canRemoveNode; 37 | 38 | /// 是否是flutter的第一个页面 39 | @property (nonatomic, assign) BOOL isFlutterHomePage; 40 | 41 | /// 是否开启进场动画 42 | @property (nonatomic, assign) BOOL animated; 43 | 44 | /// 是否是临界节点 45 | @property (nonatomic, assign) BOOL boundary; 46 | 47 | /// 唯一id 48 | /// 生成规则 controller类名+contorller的内存地址 49 | /// DFlutterViewController_0x28268d1d0 50 | @property (nonatomic, copy) NSString *identifier; 51 | 52 | /// 是否是根页面 53 | @property (nonatomic, assign) BOOL isRootPage; 54 | 55 | - (NSString *)actionTypeString; 56 | - (NSString *)pageTypeString; 57 | - (NSString *)pageString; 58 | - (void)copyWithNode:(DNode *)node; 59 | 60 | + (DNodePageType)pageTypeWithString:(NSString *)string; 61 | + (DNodeActionType)actionTypeWithString:(NSString *)string; 62 | 63 | @end 64 | 65 | NS_ASSUME_NONNULL_END 66 | -------------------------------------------------------------------------------- /ios/Classes/Node/DNode.m: -------------------------------------------------------------------------------- 1 | // 2 | // DNode.m 3 | // 4 | // 5 | // Created by TAL on 2020/1/16. 6 | // 7 | 8 | #import "DNode.h" 9 | 10 | @interface DNode () 11 | 12 | @end 13 | 14 | @implementation DNode 15 | 16 | - (instancetype)init 17 | { 18 | self = [super init]; 19 | if (self) { 20 | _target = @""; 21 | } 22 | return self; 23 | } 24 | 25 | - (NSString *)pageTypeString 26 | { 27 | NSString *type = @""; 28 | if (_pageType == DNodePageTypeFlutter) { 29 | type = @"Flutter"; 30 | } else if (_pageType == DNodePageTypeNative) { 31 | type = @"Native"; 32 | } 33 | return type; 34 | } 35 | 36 | - (NSString *)pageString 37 | { 38 | NSString *type = @""; 39 | if (_pageType == DNodePageTypeFlutter) { 40 | type = @"flutter"; 41 | } else if (_pageType == DNodePageTypeNative) { 42 | type = @"native"; 43 | } 44 | return type; 45 | } 46 | 47 | - (NSString *)actionTypeString 48 | { 49 | NSString *action = @""; 50 | switch (_action) { 51 | case DNodeActionTypePush:{action = @"push";break;} 52 | case DNodeActionTypePresent:{action = @"present";break;} 53 | case DNodeActionTypePop:{action = @"pop";break;} 54 | case DNodeActionTypePopTo:{action = @"popTo";break;} 55 | case DNodeActionTypePopToRoot:{action = @"popToRoot";break;} 56 | case DNodeActionTypePopSkip:{action = @"popSkip";break;} 57 | case DNodeActionTypeGesture:{action = @"gesture";break;} 58 | case DNodeActionTypeDismiss:{action = @"dismiss";break;} 59 | case DNodeActionTypeReplace:{action = @"replace";break;} 60 | case DNodeActionTypeDidPop:{action = @"didPop";break;} 61 | case DNodeActionTypePushAndRemoveUntil:{action = @"pushAndRemoveUntil";break;} 62 | default:break; 63 | } 64 | return action; 65 | } 66 | 67 | - (void)copyWithNode:(DNode *)node 68 | { 69 | self.action = node.action; 70 | self.pageType = node.pageType; 71 | self.target = node.target; 72 | self.params = node.params; 73 | self.identifier = node.identifier; 74 | } 75 | 76 | + (DNodePageType)pageTypeWithString:(NSString *)string 77 | { 78 | if (!string || [string isEqual:NSNull.null] || !string.length) { 79 | return DNodePageTypeUnknow; 80 | } 81 | NSString *_pageType = string; 82 | DNodePageType pageType = DNodePageTypeUnknow; 83 | if ([_pageType isEqualToString:@"native"]) { 84 | pageType = DNodePageTypeNative; 85 | } else if ([_pageType isEqualToString:@"flutter"]) { 86 | pageType = DNodePageTypeFlutter; 87 | } 88 | return pageType; 89 | } 90 | 91 | + (DNodeActionType)actionTypeWithString:(NSString *)string 92 | { 93 | if (!string || [string isEqual:NSNull.null] || !string.length) { 94 | return DNodeActionTypeUnknow; 95 | } 96 | NSString *_actionType = string; 97 | DNodeActionType actionType = DNodeActionTypeUnknow; 98 | if ([_actionType isEqualToString:@"push"]) { 99 | actionType = DNodeActionTypePush; 100 | } else if ([_actionType isEqualToString:@"present"]) { 101 | actionType = DNodeActionTypePresent; 102 | } else if ([_actionType isEqualToString:@"pop"]) { 103 | actionType = DNodeActionTypePop; 104 | } else if ([_actionType isEqualToString:@"popTo"]) { 105 | actionType = DNodeActionTypePopTo; 106 | } else if ([_actionType isEqualToString:@"popSkip"]) { 107 | actionType = DNodeActionTypePopSkip; 108 | } else if ([_actionType isEqualToString:@"popToRoot"]) { 109 | actionType = DNodeActionTypePopToRoot; 110 | } else if ([_actionType isEqualToString:@"dismiss"]) { 111 | actionType = DNodeActionTypeDismiss; 112 | } else if ([_actionType isEqualToString:@"gesture"]) { 113 | actionType = DNodeActionTypeGesture; 114 | } else if ([_actionType isEqualToString:@"replace"]) { 115 | actionType = DNodeActionTypeReplace; 116 | } else if ([_actionType isEqualToString:@"didPop"]) { 117 | actionType = DNodeActionTypeDidPop; 118 | } else if ([_actionType isEqualToString:@"pushAndRemoveUntil"]) { 119 | actionType = DNodeActionTypePushAndRemoveUntil; 120 | } 121 | return actionType; 122 | } 123 | 124 | - (NSString *)target 125 | { 126 | if (_target && [_target isKindOfClass:NSString.class]) { 127 | return _target; 128 | } 129 | return @""; 130 | } 131 | 132 | - (NSString *)description 133 | { 134 | return [NSString stringWithFormat:@"[class:%@ %p][action:%@][pageType:%@][target:%@][params:%@][identifier:%@]", 135 | NSStringFromClass(self.class), 136 | self, 137 | self.actionTypeString, 138 | self.pageTypeString, 139 | _target, 140 | _params, 141 | _identifier]; 142 | } 143 | 144 | - (id)copyWithZone:(NSZone *)zone 145 | { 146 | return [self nodeWithZone:zone]; 147 | } 148 | 149 | - (id)mutableCopyWithZone:(NSZone *)zone 150 | { 151 | return [self nodeWithZone:zone]; 152 | } 153 | 154 | - (id)nodeWithZone:(NSZone *)zone 155 | { 156 | DNode *node = [[DNode allocWithZone:zone] init]; 157 | node.fromFlutter = _fromFlutter; 158 | node.canRemoveNode = _canRemoveNode; 159 | node.isFlutterHomePage = _isFlutterHomePage; 160 | node.animated = _animated; 161 | node.boundary = _boundary; 162 | node.isRootPage = _isRootPage; 163 | node.action = _action; 164 | node.pageType = _pageType; 165 | node.params = _params; 166 | node.target = _target; 167 | node.identifier = _identifier; 168 | return node; 169 | } 170 | 171 | @end 172 | -------------------------------------------------------------------------------- /ios/Classes/Node/DNodeManager.h: -------------------------------------------------------------------------------- 1 | // 2 | // DNodeManager.h 3 | // 管理节点信息 4 | // 5 | // Created by TAL on 2020/1/16. 6 | // 7 | 8 | #import 9 | #import "DNode.h" 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | /// Node节点管理器 14 | /// 全链路全节点记录 15 | /// Flutter页面打开、关闭都会发消息至Native侧进行节点管理 16 | /// Native页面打开、关闭都会发消息至Native侧进行节点管理 17 | @interface DNodeManager : NSObject 18 | 19 | /// 当前的节点列表 20 | @property (nonatomic, strong, readonly) NSArray *currentNodeList; 21 | 22 | /// 当前显示的节点 23 | @property (nonatomic, strong, readonly, nullable) DNode *currentNode; 24 | 25 | /// 当前显示的节点的前一个节点 26 | @property (nonatomic, strong, readonly, nullable) DNode *preNode; 27 | 28 | /// 每次被移除的节点列表 29 | @property (nonatomic, strong, nullable) NSArray *removedNodes; 30 | 31 | /// 当前被显示的FlutterViewControllerID 32 | @property (nonatomic, copy, nullable) NSString *currentFlutterViewControllerID; 33 | 34 | + (instancetype)sharedInstance; 35 | 36 | + (instancetype)new NS_UNAVAILABLE; 37 | - (instancetype)init NS_UNAVAILABLE; 38 | 39 | /// 配置日志信息 40 | - (void)configLogFileWithDebugMode:(BOOL)debugMode; 41 | 42 | /// 清除本地日志 43 | - (void)cleanLogFile; 44 | 45 | /// 检查节点 46 | /// @param node 节点信息 47 | - (NSArray *)checkNode:(DNode *)node; 48 | 49 | /// 根据target从nodeLlist获取,倒序查找 50 | /// @param target target 51 | - (nullable DNode *)nodeWithTarget:(NSString *)target; 52 | 53 | /// 根据identifier从nodeLlist获取,倒序查找 54 | /// @param identifier identifier 55 | - (nullable DNode *)nodeWithIdentifier:(NSString *)identifier; 56 | 57 | /// 原生页面的pop返回手势能否响应 58 | /// 判断逻辑 59 | /// NodeList的最后一个节点就是当前页面正在显示的页面 60 | /// 取出最后一个节点,判断该节点的前面一个节点 61 | /// 如果前一个节点是Flutter页面,则原生页面的pop返回手势不能响应,否则可以响应 62 | - (BOOL)nativePopGestureCanReponse; 63 | 64 | /// 创建节点 65 | /// @param scheme 节点标识 66 | /// @param pageType 下一页面类型 67 | /// @param actionType 跳转类型 68 | /// @param params 附带参数 69 | - (DNode *)nextPageScheme:(NSString *)scheme 70 | pageType:(DNodePageType)pageType 71 | action:(DNodeActionType)actionType 72 | params:(nullable NSDictionary *)params; 73 | 74 | /// 应用生命周期消息 75 | /// @param state 应用的生命周期 76 | - (void)sendAppliccationLifeCicleToFlutter:(DStackApplicationState)state; 77 | 78 | /// 更新临界节点 79 | /// @param nodeInfo nodeInfo 80 | - (void)updateBoundaryNode:(NSDictionary *)nodeInfo; 81 | 82 | /// 更新根节点信息 83 | /// @param node node 84 | - (BOOL)updateRootNode:(DNode *)node; 85 | 86 | /// 获取日志文件内容 87 | - (nullable NSArray *)logFiles; 88 | 89 | @end 90 | 91 | NS_ASSUME_NONNULL_END 92 | -------------------------------------------------------------------------------- /ios/Classes/Stack/DFlutterViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // DFlutterViewController.h 3 | // 4 | // 5 | // Created by TAL on 2020/1/16. 6 | // 7 | 8 | #import 9 | #import 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | /// 当需要打开一个Flutter页面时,必须是DFlutterViewController或者是它的子类 14 | @interface DFlutterViewController : FlutterViewController 15 | 16 | - (void)willUpdateView; 17 | - (void)didUpdateView; 18 | - (void)updateCurrentNode:(id)node; 19 | - (id)currentNode; 20 | 21 | @end 22 | 23 | NS_ASSUME_NONNULL_END 24 | 25 | -------------------------------------------------------------------------------- /ios/Classes/Stack/DFlutterViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // DFlutterViewController.m 3 | // 4 | // 5 | // Created by TAL on 2020/1/16. 6 | // 7 | 8 | #import "DFlutterViewController.h" 9 | #import "DNodeManager.h" 10 | #import "DActionManager.h" 11 | #import 12 | #import "DNavigator.h" 13 | #import "DStack.h" 14 | 15 | @interface DFlutterViewController () 16 | 17 | /// 当前正在被显示的Node 18 | @property (nonatomic, strong) DNode *currentShowNode; 19 | 20 | @end 21 | 22 | @implementation DFlutterViewController 23 | 24 | - (instancetype)init 25 | { 26 | if (self.dStackFlutterEngine.viewController) { 27 | self.dStackFlutterEngine.viewController = nil; 28 | } 29 | if(self = [super initWithEngine:self.dStackFlutterEngine 30 | nibName:nil 31 | bundle:nil]) { 32 | [self config]; 33 | } 34 | return self; 35 | } 36 | 37 | - (instancetype)initWithCoder:(NSCoder *)coder 38 | { 39 | if (self.dStackFlutterEngine.viewController) { 40 | self.dStackFlutterEngine.viewController = nil; 41 | } 42 | if(self = [super initWithEngine:self.dStackFlutterEngine 43 | nibName:nil 44 | bundle:nil]) { 45 | [self config]; 46 | } 47 | return self; 48 | } 49 | 50 | - (void)config 51 | { 52 | self.view.backgroundColor = [UIColor whiteColor]; 53 | [self addNotification]; 54 | } 55 | 56 | - (void)viewWillAppear:(BOOL)animated 57 | { 58 | // 必须在页面显示之前判断engine是否存在FlutterViewController 59 | // 否则会因为FlutterViewController不存在而崩溃 60 | if (self.dStackFlutterEngine.viewController != self) { 61 | self.dStackFlutterEngine.viewController = nil; 62 | self.dStackFlutterEngine.viewController = self; 63 | } 64 | [self checkSelfIsInTabBarController]; 65 | NSString *identifier = [NSString stringWithFormat:@"%p", self]; 66 | [DNodeManager sharedInstance].currentFlutterViewControllerID = identifier; 67 | [super viewWillAppear:animated]; 68 | self.navigationController.navigationBarHidden = YES; 69 | } 70 | 71 | - (void)viewDidAppear:(BOOL)animated 72 | { 73 | // 刷新一下FlutterViewController的页面,保证当前显示的view是最新的 74 | [self _surfaceUpdated:YES]; 75 | DNode *topNode = [DNodeManager sharedInstance].currentNode; 76 | if (topNode.pageType == DNodePageTypeFlutter) { 77 | [self updateCurrentNode:topNode]; 78 | } 79 | [super viewDidAppear:animated]; 80 | } 81 | 82 | - (void)viewDidDisappear:(BOOL)animated 83 | { 84 | [self removeGesturePopNode]; 85 | [super viewDidDisappear:animated]; 86 | } 87 | 88 | /// dismiss手势不会触发页面的viewWillAppear 89 | /// viewDidDisappear里面,flutter会让Engine暂停,不会渲染flutter页面 90 | /// 在dismiss的某些情况下,主动调用viewWillAppear,使Engine进入inactive状态 91 | - (void)willUpdateView 92 | { 93 | [self viewWillAppear:YES]; 94 | } 95 | 96 | /// dismiss手势不会触发页面的viewDidAppear 97 | /// viewDidDisappear里面,flutter会让Engine暂停,不会渲染flutter页面 98 | /// 在dismiss的某些情况下,主动调用viewDidAppear,使Engine进入resumed状态 99 | - (void)didUpdateView 100 | { 101 | [self viewDidAppear:YES]; 102 | /// 调用这个是为了重新计算页面的布局 103 | /// 因为在非全屏present页面时,该页面是没有状态栏的 104 | /// 所以在dismiss的时候,需要重新展示状态栏,就需要刷新flutter的页面 105 | [super viewDidLayoutSubviews]; 106 | } 107 | 108 | - (void)updateCurrentNode:(DNode *)node 109 | { 110 | _currentShowNode = [node copy]; 111 | } 112 | 113 | - (id)currentNode 114 | { 115 | return _currentShowNode; 116 | } 117 | 118 | - (void)checkSelfIsInTabBarController 119 | { 120 | UITabBarController *tabBarController = self.tabBarController; 121 | if (tabBarController) { 122 | UIViewController *selectedViewController = tabBarController.selectedViewController; 123 | if (selectedViewController) { 124 | UIViewController *visibleVC = [self visibleSelectedViewController:selectedViewController]; 125 | if (visibleVC != self) {return;} 126 | [DActionManager tabBarWillSelectViewController:selectedViewController 127 | homePageRoute:[DStack sharedInstance].flutterHomePageRoute]; 128 | } 129 | } else { 130 | // 检查是不是flutter工程 131 | if ([DActionManager rootControllerIsFlutterController]) { 132 | DNode *node = [[DNode alloc] init]; 133 | node.pageType = DNodePageTypeFlutter; 134 | node.action = DNodeActionTypeReplace; 135 | node.target = [DStack sharedInstance].flutterHomePageRoute; 136 | [[DNodeManager sharedInstance] updateRootNode:node]; 137 | } 138 | } 139 | } 140 | 141 | - (UIViewController *)visibleSelectedViewController:(UIViewController *)selectedViewController 142 | { 143 | if ([selectedViewController isKindOfClass:UINavigationController.class]) { 144 | return [[(UINavigationController *)selectedViewController viewControllers] firstObject]; 145 | } 146 | return selectedViewController; 147 | } 148 | 149 | - (void)_surfaceUpdated:(BOOL)appeared 150 | { 151 | SEL sel = NSSelectorFromString(@"surfaceUpdated:"); 152 | if (class_respondsToSelector(self.class, sel)) { 153 | NSMethodSignature *signature = [self methodSignatureForSelector:sel]; 154 | if (signature) { 155 | NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature]; 156 | invocation.selector = sel; 157 | invocation.target = self; 158 | [invocation setArgument:&appeared atIndex:2]; 159 | [invocation invoke]; 160 | } 161 | } 162 | } 163 | 164 | - (void)addNotification 165 | { 166 | [[NSNotificationCenter defaultCenter] addObserver:self 167 | selector:@selector(changeBottomBarVisible:) 168 | name:DStackNotificationNameChangeBottomBarVisible 169 | object:nil]; 170 | } 171 | 172 | - (void)changeBottomBarVisible:(NSNotification *)notification 173 | { 174 | if (!self.tabBarController) {return;} 175 | NSDictionary *userInfo = notification.userInfo; 176 | if (!userInfo) { 177 | if (self.tabBarController.tabBar.hidden == NO) { 178 | [self.tabBarController.tabBar setHidden:YES]; 179 | } 180 | } else { 181 | BOOL hidden = [userInfo[@"hidden"] boolValue]; 182 | if (self.tabBarController.tabBar.hidden) { 183 | [self.tabBarController.tabBar setHidden:hidden]; 184 | } 185 | } 186 | } 187 | 188 | - (FlutterEngine *)dStackFlutterEngine 189 | { 190 | return [DStack sharedInstance].engine; 191 | } 192 | 193 | - (void)dealloc 194 | { 195 | [[NSNotificationCenter defaultCenter] removeObserver:self]; 196 | } 197 | 198 | @end 199 | -------------------------------------------------------------------------------- /ios/Classes/Stack/DNavigator.h: -------------------------------------------------------------------------------- 1 | // 2 | // DNavigator.m 3 | // d_stack 4 | // 5 | // Created by TAL on 2020/2/3. 6 | // 7 | 8 | #import 9 | 10 | NS_ASSUME_NONNULL_BEGIN 11 | 12 | /// 该类主要用于拦截所有push、present、pop、dismiss等事件 13 | /// 用于全链路全节点记录 14 | @interface UIViewController (DStackFullscreenPopGestureCategory) 15 | 16 | /// 是否开启手势返回 17 | @property (nonatomic, assign) BOOL dStack_interactivePopDisabled; 18 | 19 | - (void)removeGesturePopNode; 20 | 21 | @end 22 | 23 | NS_ASSUME_NONNULL_END 24 | -------------------------------------------------------------------------------- /ios/Classes/Stack/DStackPlugin.h: -------------------------------------------------------------------------------- 1 | // 2 | // DStackPlugin.h 3 | // plugin channel 4 | // 5 | // Created by TAL on 2020/1/19. 6 | // 7 | 8 | #import 9 | 10 | NS_ASSUME_NONNULL_BEGIN 11 | 12 | typedef NSString *DStackMethodChannelName; 13 | UIKIT_EXTERN DStackMethodChannelName const DStackMethodChannelPlatformVersion; // 获取版本号 14 | UIKIT_EXTERN DStackMethodChannelName const DStackMethodChannelSendNodeToNative; // flutter发送节点至native 15 | UIKIT_EXTERN DStackMethodChannelName const DStackMethodChannelSendActionToFlutter; // native发送跳转指令至flutter 16 | UIKIT_EXTERN DStackMethodChannelName const DStackMethodChannelSendRemoveFlutterPageNode; // flutter发送移除节点的指令到native 17 | UIKIT_EXTERN DStackMethodChannelName const DStackMethodChannelSendLifeCircle; // 生命周期通道 18 | UIKIT_EXTERN DStackMethodChannelName const DStackMethodChannelSendNodeList; // 节点列表 19 | UIKIT_EXTERN DStackMethodChannelName const DStackMethodChannelSendFlutterRootNode; // 设置flutter根节点 20 | UIKIT_EXTERN DStackMethodChannelName const DStackMethodChannelSendOperationNodeToFlutter; // Operation节点 21 | UIKIT_EXTERN DStackMethodChannelName const DStackMethodChannelSendSendHomePageRoute; // 发送homePageRoute 22 | UIKIT_EXTERN DStackMethodChannelName const DStackMethodChannelSendSendUpdateBoundaryNode; // 更新临界节点信息 23 | 24 | @interface DStackPlugin : NSObject 25 | 26 | + (instancetype)sharedInstance; 27 | 28 | + (instancetype)new NS_UNAVAILABLE; 29 | - (instancetype)init NS_UNAVAILABLE; 30 | 31 | - (void)invokeMethod:(NSString *_Nullable)method 32 | arguments:(id _Nullable)arguments 33 | result:(FlutterResult _Nullable)callback; 34 | @end 35 | 36 | NS_ASSUME_NONNULL_END 37 | -------------------------------------------------------------------------------- /ios/Classes/Stack/DStackPlugin.m: -------------------------------------------------------------------------------- 1 | // 2 | // DStackPlugin.m 3 | // plugin channel 4 | // 5 | // Created by TAL on 2020/1/19. 6 | // 7 | 8 | #import "DStackPlugin.h" 9 | #import "DStack.h" 10 | 11 | DStackMethodChannelName const DStackMethodChannelPlatformVersion = @"getPlatformVersion"; 12 | DStackMethodChannelName const DStackMethodChannelSendNodeToNative = @"sendNodeToNative"; 13 | DStackMethodChannelName const DStackMethodChannelSendActionToFlutter = @"sendActionToFlutter"; 14 | DStackMethodChannelName const DStackMethodChannelSendRemoveFlutterPageNode = @"sendRemoveFlutterPageNode"; 15 | DStackMethodChannelName const DStackMethodChannelSendLifeCircle = @"sendLifeCycle"; 16 | DStackMethodChannelName const DStackMethodChannelSendNodeList = @"sendNodeList"; 17 | DStackMethodChannelName const DStackMethodChannelSendFlutterRootNode = @"sendFlutterRootNode"; 18 | DStackMethodChannelName const DStackMethodChannelSendOperationNodeToFlutter = @"sendOperationNodeToFlutter"; 19 | DStackMethodChannelName const DStackMethodChannelSendSendHomePageRoute = @"sendHomePageRoute"; 20 | DStackMethodChannelName const DStackMethodChannelSendSendUpdateBoundaryNode = @"sendUpdateBoundaryNode"; 21 | 22 | @interface DStackPlugin () 23 | 24 | @property (nonatomic, strong) FlutterMethodChannel *methodChannel; 25 | 26 | @end 27 | 28 | @implementation DStackPlugin 29 | + (void)registerWithRegistrar:(NSObject*)registrar { 30 | 31 | FlutterMethodChannel* channel = [FlutterMethodChannel 32 | methodChannelWithName:@"d_stack" 33 | binaryMessenger:registrar.messenger]; 34 | 35 | DStackPlugin* instance = [DStackPlugin sharedInstance]; 36 | instance.methodChannel = channel; 37 | [registrar addMethodCallDelegate:instance channel:channel]; 38 | } 39 | 40 | - (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result { 41 | DStackLog(@"收到Flutter【%@】的消息\n参数:%@", call.method, call.arguments); 42 | if ([DStackMethodChannelPlatformVersion isEqualToString:call.method]) { 43 | NSString *platform = [@"iOS " stringByAppendingString:[[UIDevice currentDevice] systemVersion]]; 44 | result(platform); 45 | } else if ([DStackMethodChannelSendNodeToNative isEqualToString:call.method]) { 46 | [self handleSendNodeToNativeMessage:call result:result]; 47 | } else if ([DStackMethodChannelSendRemoveFlutterPageNode isEqualToString:call.method]) { 48 | DStack *stack = [DStack sharedInstance]; 49 | if ([stack respondsToSelector:@selector(handleRemoveFlutterPageNode:result:)]) { 50 | [stack handleRemoveFlutterPageNode:call result:result]; 51 | } 52 | } else if ([DStackMethodChannelSendNodeList isEqualToString:call.method]) { 53 | DStack *stack = [DStack sharedInstance]; 54 | if ([stack respondsToSelector:@selector(sendNodeListToFlutter:)]) { 55 | [stack sendNodeListToFlutter:result]; 56 | } 57 | } else if ([DStackMethodChannelSendSendHomePageRoute isEqualToString:call.method]) { 58 | DStack *stack = [DStack sharedInstance]; 59 | if ([stack respondsToSelector:@selector(sendHomePageRoute:)]) { 60 | [stack sendHomePageRoute:call]; 61 | } 62 | } else if ([DStackMethodChannelSendSendUpdateBoundaryNode isEqualToString:call.method]) { 63 | DStack *stack = [DStack sharedInstance]; 64 | if ([stack respondsToSelector:@selector(updateBoundaryNode:)]) { 65 | [stack updateBoundaryNode:call]; 66 | } 67 | } else { 68 | result(FlutterMethodNotImplemented); 69 | } 70 | } 71 | 72 | - (void)handleSendNodeToNativeMessage:(FlutterMethodCall*)call result:(FlutterResult)result 73 | { 74 | DStack *stack = [DStack sharedInstance]; 75 | if ([stack conformsToProtocol:@protocol(DStackPluginProtocol)]) { 76 | if ([stack respondsToSelector:@selector(handleSendNodeToNativeMessage:result:)]) { 77 | [stack handleSendNodeToNativeMessage:call result:result]; 78 | } 79 | } 80 | } 81 | 82 | - (void)invokeMethod:(NSString*)method 83 | arguments:(id _Nullable)arguments 84 | result:(FlutterResult _Nullable)callback 85 | { 86 | DStackPlugin *instance = [DStackPlugin sharedInstance]; 87 | [instance.methodChannel invokeMethod:method arguments:arguments result:callback]; 88 | } 89 | 90 | + (instancetype)sharedInstance 91 | { 92 | static id _instance = nil; 93 | static dispatch_once_t onceToken; 94 | dispatch_once(&onceToken, ^{ 95 | _instance = [self.class new]; 96 | }); 97 | return _instance; 98 | } 99 | 100 | @end 101 | -------------------------------------------------------------------------------- /ios/Classes/Stack/DStackProvider.h: -------------------------------------------------------------------------------- 1 | // 2 | // DStackProvider.h 3 | // 定义 4 | // 5 | // Created by TAL on 2020/1/16. 6 | // 7 | 8 | #ifndef DStackProvider_h 9 | #define DStackProvider_h 10 | 11 | #import 12 | 13 | FOUNDATION_EXPORT void _dStackLog(NSString *msg, NSString *format, ...); 14 | #define DStackLog(format, ...) (_dStackLog(@"", format, ## __VA_ARGS__)) 15 | #define DStackError(format, ...) (_dStackLog(@"!!!!!!!!! DstackError !!!!!!!!! ==> ", format, ## __VA_ARGS__)) 16 | 17 | #define DStackInject(_className_) \ 18 | class DStackInjectClass; \ 19 | char * __DStackInject##_className_##Char \ 20 | __attribute__ ((used, section("__DATA, __DStackEInject "))) = ""#_className_"" 21 | 22 | 23 | #define DStackDeprecated(msg) __attribute__((deprecated(msg))) 24 | 25 | 26 | // 页面类型 27 | typedef NS_ENUM(NSInteger, DNodePageType) { 28 | DNodePageTypeUnknow, 29 | DNodePageTypeNative, // 原生页面 30 | DNodePageTypeFlutter, // Flutter页面 31 | }; 32 | 33 | // 跳转类型 34 | typedef NS_ENUM(NSInteger, DNodeActionType) { 35 | DNodeActionTypeUnknow, 36 | DNodeActionTypePush, // push跳转 37 | DNodeActionTypePresent, // present跳转 38 | DNodeActionTypePop, // pop返回 39 | DNodeActionTypePopTo, // popTo返回 40 | DNodeActionTypePopToRoot, // popToRoot 41 | DNodeActionTypePopSkip, // dopSkip 42 | DNodeActionTypeGesture, // 手势 43 | DNodeActionTypeDismiss, // dismiss返回 44 | DNodeActionTypeReplace, // replace返回 45 | DNodeActionTypeDidPop, // didPop确认 46 | DNodeActionTypePushAndRemoveUntil, // pushAndRemoveUntil 47 | }; 48 | 49 | // 应用的生命周期 50 | typedef NS_ENUM(NSInteger, DStackApplicationState) { 51 | DStackApplicationStateStart, // 应用启动 52 | DStackApplicationStateForeground, // 进入前台 53 | DStackApplicationStateBackground, // 进入后台 54 | }; 55 | 56 | 57 | @protocol DStackPluginProtocol 58 | 59 | @required 60 | /// 处理Flutter发送至nnative的消息 61 | /// @param call call 62 | /// @param result result 63 | - (void)handleSendNodeToNativeMessage:(FlutterMethodCall*)call 64 | result:(FlutterResult)result; 65 | 66 | /// 处理flutter发送过来的移除节点信息 67 | /// @param call call 68 | /// @param result result 69 | - (void)handleRemoveFlutterPageNode:(FlutterMethodCall*)call 70 | result:(FlutterResult)result; 71 | 72 | /// 发送节点列表到flutter 73 | /// @param result result 74 | - (void)sendNodeListToFlutter:(FlutterResult)result; 75 | 76 | /// 发送homePageRoute 77 | /// @param call call 78 | - (void)sendHomePageRoute:(FlutterMethodCall *)call; 79 | 80 | /// 更新临界节点信息 81 | /// @param call call 82 | - (void)updateBoundaryNode:(FlutterMethodCall *)call; 83 | 84 | @end 85 | 86 | #endif /* DStackProvider_h */ 87 | -------------------------------------------------------------------------------- /ios/d_stack.podspec: -------------------------------------------------------------------------------- 1 | # 2 | # To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html. 3 | # Run `pod lib lint d_stack.podspec' to validate before publishing. 4 | # 5 | Pod::Spec.new do |s| 6 | s.name = 'd_stack' 7 | s.version = '0.0.1' 8 | s.summary = 'A plugin solve the problem of native mix flutter.' 9 | s.description = <<-DESC 10 | A plugin solve the problem of native mix flutter. 11 | DESC 12 | s.homepage = 'http://example.com' 13 | s.license = { :file => '../LICENSE' } 14 | s.author = { 'Your Company' => 'email@example.com' } 15 | s.source = { :path => '.' } 16 | s.source_files = 'Classes/**/*' 17 | s.public_header_files = 'Classes/**/*.h' 18 | s.dependency 'Flutter' 19 | s.platform = :ios, '8.0' 20 | 21 | # Flutter.framework does not contain a i386 slice. Only x86_64 simulators are supported. 22 | s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', 'VALID_ARCHS[sdk=iphonesimulator*]' => 'x86_64' } 23 | end 24 | -------------------------------------------------------------------------------- /lib/channel/dchannel.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * Created with Android Studio. 3 | * User: whqfor 4 | * Date: 2020-02-03 5 | * Time: 14:20 6 | * email: wanghuaqiang@tal.com 7 | * tartget: 处理通道相关 8 | */ 9 | 10 | import 'dart:async'; 11 | 12 | import 'package:d_stack/constant/constant_config.dart'; 13 | import 'package:d_stack/navigator/dnavigator_manager.dart'; 14 | import 'package:d_stack/observer/d_node_observer.dart'; 15 | import 'package:d_stack/observer/life_cycle_observer.dart'; 16 | import 'package:flutter/services.dart'; 17 | 18 | import '../d_stack.dart'; 19 | 20 | class DChannel { 21 | late MethodChannel _methodChannel; 22 | 23 | DChannel(MethodChannel methodChannel) { 24 | _methodChannel = methodChannel; 25 | _methodChannel.setMethodCallHandler((MethodCall call) { 26 | // sendActionToFlutter 处理Native发过来的指令 27 | if (DStackConstant.nodeToFlutter == call.method) { 28 | return DNavigatorManager.handleActionToFlutter(call.arguments)!; 29 | } else if (DStackConstant.lifeCycle == call.method) { 30 | return LifeCycleHandler.handleLifecycleMessage(call.arguments); 31 | } else if (DStackConstant.sendOperationNodeToFlutter == call.method) { 32 | return DNodeObserverHandler.handlerNodeMessage(call.arguments); 33 | } 34 | return Future.value(); 35 | }); 36 | } 37 | 38 | Future invokeMethod(String method, [dynamic arguments]) async { 39 | return _methodChannel.invokeMethod(method, arguments); 40 | } 41 | 42 | Future sendNodeToNative(Map arguments) async { 43 | assert(arguments != null); 44 | return _methodChannel.invokeMethod(DStackConstant.nodeToNative, arguments); 45 | } 46 | 47 | Future sendRemoveFlutterPageNode(Map arguments) async { 48 | assert(arguments != null); 49 | return _methodChannel.invokeMethod(DStackConstant.checkRemoved, arguments); 50 | } 51 | 52 | Future> getNodeList() async { 53 | return _methodChannel 54 | .invokeMethod(DStackConstant.nodeList, null) 55 | .then((list) { 56 | if (list is List) { 57 | List nodeList = []; 58 | list.forEach((element) { 59 | DStackNode node = DStackNode( 60 | route: element["route"], pageType: element["pageType"]); 61 | nodeList.add(node); 62 | }); 63 | return Future.value(nodeList); 64 | } 65 | return Future.value([]); 66 | }); 67 | } 68 | 69 | Future sendHomePageRoute(String? route) { 70 | return _methodChannel.invokeMethod( 71 | DStackConstant.sendHomePageRoute, {"homePageRoute": route}); 72 | } 73 | 74 | /// 发送更新临界节点的信息 75 | Future sendUpdateBoundaryNode(Map params) { 76 | return _methodChannel.invokeMethod( 77 | DStackConstant.sendUpdateBoundaryNode, params); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /lib/constant/constant_config.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * Created with Android Studio. 3 | * User: linkewen 4 | * Date: 2020/11/26 5 | * Time: 16:23 6 | * target: 静态配置 7 | */ 8 | 9 | class DStackConstant { 10 | /// action类型 11 | static const String push = "push"; 12 | static const String present = "present"; 13 | static const String dismiss = "dismiss"; 14 | static const String pop = "pop"; 15 | static const String popTo = "popTo"; 16 | static const String popToRoot = "popToRoot"; 17 | static const String popSkip = "popSkip"; 18 | static const String replace = "replace"; 19 | static const String gesture = "gesture"; 20 | static const String pushAndRemoveUntil = "pushAndRemoveUntil"; 21 | 22 | /// channel通道 23 | static const String nodeToFlutter = "sendActionToFlutter"; 24 | static const String nodeToNative = "sendNodeToNative"; 25 | static const String checkRemoved = "sendRemoveFlutterPageNode"; 26 | static const String lifeCycle = "sendLifeCycle"; 27 | static const String nodeList = "sendNodeList"; 28 | static const String sendFlutterRootNode = "sendFlutterRootNode"; 29 | static const String sendOperationNodeToFlutter = 'sendOperationNodeToFlutter'; 30 | static const String sendHomePageRoute = 'sendHomePageRoute'; 31 | static const String sendUpdateBoundaryNode = 'sendUpdateBoundaryNode'; 32 | 33 | /// 其他标识 34 | static const String nativeDidPopGesture = "nativeDidPopGesture"; 35 | static const String flutterDialog = "flutterDialog"; 36 | } 37 | -------------------------------------------------------------------------------- /lib/d_stack.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * Created with Android Studio. 3 | * User: whqfor 4 | * Date: 2020-02-03 5 | * Time: 14:20 6 | * email: wanghuaqiang@tal.com 7 | * tartget: plugin人口 8 | */ 9 | 10 | import 'package:d_stack/channel/dchannel.dart'; 11 | import 'package:d_stack/navigator/dnavigator_gesture_observer.dart'; 12 | import 'package:d_stack/navigator/dnavigator_manager.dart'; 13 | import 'package:d_stack/observer/d_node_observer.dart'; 14 | import 'package:d_stack/observer/life_cycle_observer.dart'; 15 | import 'package:flutter/material.dart'; 16 | import 'package:flutter/services.dart'; 17 | 18 | enum PageType { native, flutter } 19 | 20 | /// The type of transition to use when pushing/popping a route. 21 | /// 22 | /// [TransitionType.custom] must also provide a transition when used. 23 | enum TransitionType { 24 | native, 25 | nativeModal, 26 | inFromLeft, 27 | inFromTop, 28 | inFromRight, 29 | inFromBottom, 30 | fadeIn, // 渐变 31 | custom, // 自定义,需要传transitionsBuilder 32 | material, 33 | materialFullScreenDialog, 34 | cupertino, 35 | cupertinoFullScreenDialog, 36 | fadeOpaque, // 透明 37 | fadeAndScale, // 透明缩放 38 | none, // 无动画 39 | } 40 | 41 | const Duration defaultPushDuration = Duration(milliseconds: 300); 42 | const Duration defaultPopDuration = Duration(milliseconds: 250); 43 | 44 | typedef DStackWidgetBuilder = WidgetBuilder Function(Map? params); 45 | typedef AnimatedPageBuilder = AnimatedWidget Function( 46 | BuildContext context, 47 | Animation animation, 48 | Animation secondaryAnimation, 49 | WidgetBuilder widgetBuilder); 50 | 51 | typedef PushAnimationPageBuilder = AnimatedWidget Function( 52 | BuildContext context, 53 | Animation animation, 54 | Animation secondaryAnimation, 55 | Widget child); 56 | 57 | class DStack { 58 | static DChannel? _stackChannel; 59 | static final DStack _instance = DStack(); 60 | 61 | static DStack get instance { 62 | final MethodChannel _methodChannel = MethodChannel("d_stack"); 63 | _stackChannel = DChannel(_methodChannel); 64 | return _instance; 65 | } 66 | 67 | DChannel? get channel => _stackChannel; 68 | 69 | String? _homePageRoute; 70 | 71 | String? get homeRoute => _homePageRoute; 72 | 73 | set homePageRoute(String? route) { 74 | _homePageRoute = route; 75 | if (_homePageRoute != null) { 76 | channel!.sendHomePageRoute(_homePageRoute); 77 | } 78 | } 79 | 80 | /// navigatorKey 81 | final GlobalKey navigatorKey = GlobalKey(); 82 | 83 | /// 路由observer 84 | final DStackNavigatorObserver dStackNavigatorObserver = 85 | DStackNavigatorObserver(); 86 | 87 | /// 用来监听 应用生命周期 88 | DLifeCycleObserver? dLifeCycleObserver; 89 | 90 | /// 用来监听节点操作 91 | DNodeObserver? dNodeObserver; 92 | 93 | final Map _pageBuilders = 94 | {}; 95 | 96 | /// 注册DStack 97 | /// builders 路由的builder 98 | /// observer 生命周期监听者 99 | void register( 100 | {Map? builders, 101 | DLifeCycleObserver? observer, 102 | DNodeObserver? nodeObserver}) { 103 | if (builders?.isNotEmpty == true) { 104 | _pageBuilders.addAll(builders!); 105 | } 106 | dLifeCycleObserver = observer; 107 | dNodeObserver = nodeObserver; 108 | } 109 | 110 | /// 获取一个 DStackWidgetBuilder 111 | /// pageName 路由 112 | DStackWidgetBuilder pageBuilder(String? pageName) { 113 | DStackWidgetBuilder? builder = _pageBuilders[pageName!]; 114 | if (builder != null) { 115 | return builder; 116 | } else { 117 | throw Exception('not in the PageRoute'); 118 | } 119 | } 120 | 121 | /// 获取节点列表 122 | Future> nodeList() => channel!.getNodeList(); 123 | 124 | /// 推出一个页面 125 | /// routeName 路由名, 126 | /// pageType native或者flutter, 127 | /// params 参数 128 | /// animated 是否有进场动画 129 | static Future push(String routeName, PageType pageType, 130 | {Map? params, bool maintainState = true, bool animated = true}) { 131 | return DNavigatorManager.push(routeName, pageType, 132 | params: params, maintainState: maintainState, animated: animated); 133 | } 134 | 135 | /// 弹出一个页面 136 | /// animated 是否有进场动画 137 | static Future present(String routeName, PageType pageType, 138 | {Map? params, bool maintainState = true, bool animated = true}) { 139 | return DNavigatorManager.present(routeName, pageType, 140 | params: params, maintainState: maintainState, animated: animated); 141 | } 142 | 143 | static Future animatedFlutterPage(String routeName, { 144 | Map? params, 145 | TransitionType? transition, 146 | Duration transitionDuration = const Duration(milliseconds: 250), 147 | RouteTransitionsBuilder? transitionsBuilder, 148 | bool replace = false, 149 | bool clearStack = false 150 | }) { 151 | return DNavigatorManager.animatedFlutterPage(routeName, 152 | params: params, 153 | transition: transition, 154 | transitionDuration: transitionDuration, 155 | transitionsBuilder: transitionsBuilder, 156 | replace: replace, 157 | clearStack: clearStack 158 | ); 159 | } 160 | 161 | /// 自定义进场动画 162 | /// animationBuilder 进场动画的builder 163 | /// pushDuration 进场时间 164 | /// popDuration 退场时间 165 | /// popGesture 是否支持手势返回 166 | /// 只有是popGesture为true并且 167 | /// MaterialApp(ThemeData(platform: TargetPlatform.iOS); 168 | /// popGesture 才有效 169 | static Future pushWithAnimation( 170 | String routeName, 171 | PageType pageType, 172 | PushAnimationPageBuilder animationBuilder, { 173 | Map? params, 174 | bool replace = false, 175 | bool popGesture = false, 176 | Duration pushDuration = defaultPushDuration, 177 | Duration popDuration = defaultPopDuration, 178 | }) { 179 | return DNavigatorManager.pushWithAnimation( 180 | routeName, pageType, animationBuilder, 181 | params: params, 182 | replace: replace, 183 | pushDuration: pushDuration, 184 | popDuration: popDuration, 185 | popGesture: popGesture); 186 | } 187 | 188 | /// 等同push 189 | /// builder 页面builder 190 | /// animated 是否有进场动画 191 | static Future pushBuild( 192 | String routeName, PageType pageType, WidgetBuilder builder, 193 | {Map? params, 194 | bool maintainState = true, 195 | bool fullscreenDialog = false, 196 | bool animated = true}) { 197 | return DNavigatorManager.pushBuild(routeName, pageType, builder, 198 | params: params, 199 | maintainState: maintainState, 200 | fullscreenDialog: fullscreenDialog, 201 | animated: animated); 202 | } 203 | 204 | /// 只支持flutter使用,替换flutter页面 205 | /// animated 是否有进场动画 206 | static Future replace( 207 | String routeName, 208 | PageType pageType, { 209 | Map? params, 210 | bool maintainState = true, 211 | bool fullscreenDialog = false, 212 | bool animated = true, 213 | bool homePage = false, 214 | }) { 215 | return DNavigatorManager.replace(routeName, pageType, 216 | params: params, 217 | maintainState: maintainState, 218 | homePage: homePage, 219 | animated: animated, 220 | fullscreenDialog: fullscreenDialog); 221 | } 222 | 223 | /// 跳转指定页面并清除剩余所有页面 224 | static pushAndRemoveUntil( 225 | String routeName, 226 | PageType pageType, { 227 | Map? params, 228 | bool maintainState = true, 229 | bool fullscreenDialog = false, 230 | bool animated = true, 231 | bool homePage = false, 232 | }) { 233 | return DNavigatorManager.pushAndRemoveUntil(routeName, pageType, 234 | params: params, 235 | maintainState: maintainState, 236 | homePage: homePage, 237 | animated: animated, 238 | fullscreenDialog: fullscreenDialog); 239 | } 240 | 241 | /// pop 242 | /// 可以不传路由信息 243 | /// result 返回值,可为空 244 | static void pop({Map? result, bool animated = true}) { 245 | DNavigatorManager.pop(result: result, animated: animated); 246 | } 247 | 248 | static Future maybePop({Map? result, bool animated = true}) { 249 | return DNavigatorManager.maybePop(result: result, animated: animated); 250 | } 251 | 252 | /// popTo指定页面 253 | /// 无法popTo到根页面 254 | /// 要popTo到根页面请调用popToRoot 255 | static void popTo(String routeName, PageType pageType, 256 | {Map? result, bool animated = true}) { 257 | DNavigatorManager.popTo(routeName, pageType, 258 | result: result, animated: animated); 259 | } 260 | 261 | /// pop同一组页面 262 | static void popSkip(String skipName, {Map? result, bool animated = true}) { 263 | DNavigatorManager.popSkip(skipName, result: result, animated: animated); 264 | } 265 | 266 | /// 关闭一个页面 267 | /// 如果是push进入的,等同pop 268 | /// 如果是present进入的,效果是从上往下缩回去 269 | static void dismiss({Map? result, bool animated = true}) { 270 | DNavigatorManager.dismiss(result: result, animated: animated); 271 | } 272 | 273 | /// 回到根页面 274 | static void popToRoot({bool animated = true}) { 275 | DNavigatorManager.popToRoot(animated: animated); 276 | } 277 | 278 | @Deprecated('已废弃,请调用popToRoot') 279 | static void popToNativeRoot() { 280 | DNavigatorManager.popToRoot(); 281 | } 282 | 283 | /// 自定义转场动画进入页面 284 | /// flutter页面通过animatedBuilder自定义动画 285 | /// native页面会转发到native,由native自行接入实现 286 | /// replace:true flutter的pushReplacement实现 287 | /// replace:false flutter的push实现 288 | @Deprecated('已废弃,请使用pushWithAnimation') 289 | static Future animationPage( 290 | String routeName, 291 | PageType pageType, 292 | AnimatedPageBuilder animatedBuilder, { 293 | Map? params, 294 | Duration? transitionDuration, 295 | bool opaque = true, 296 | bool barrierDismissible = false, 297 | Color? barrierColor, 298 | String? barrierLabel, 299 | bool maintainState = true, 300 | bool fullscreenDialog = false, 301 | bool replace = false, 302 | }) { 303 | return DNavigatorManager.animationPage( 304 | routeName, 305 | pageType, 306 | animatedBuilder, 307 | params, 308 | transitionDuration, 309 | opaque, 310 | barrierDismissible, 311 | barrierColor, 312 | barrierLabel, 313 | maintainState, 314 | fullscreenDialog, 315 | replace); 316 | } 317 | } 318 | 319 | class DStackNode { 320 | final String? route; 321 | final String? pageType; 322 | 323 | DStackNode({this.route, this.pageType}); 324 | } 325 | -------------------------------------------------------------------------------- /lib/navigator/dnavigator_gesture_observer.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * Created with Android Studio. 3 | * User: whqfor 4 | * Date: 2020-02-03 5 | * Time: 14:20 6 | * email: wanghuaqiang@tal.com 7 | * tartget: 拦截flutter手势,生成节点信息,然后将节点信息发送到native侧 8 | */ 9 | 10 | import 'package:d_stack/constant/constant_config.dart'; 11 | import 'package:d_stack/d_stack.dart'; 12 | import 'package:flutter/material.dart'; 13 | 14 | import 'dnavigator_manager.dart'; 15 | 16 | // 路由监听 17 | class DStackNavigatorObserver extends NavigatorObserver { 18 | // 单例 19 | factory DStackNavigatorObserver() => _getInstance()!; 20 | 21 | static DStackNavigatorObserver? get instance => _getInstance(); 22 | static DStackNavigatorObserver? _instance; 23 | // 避免过度pop 24 | int routerCount = 0; 25 | 26 | DStackNavigatorObserver._internal(); 27 | 28 | static DStackNavigatorObserver? _getInstance() { 29 | if (_instance == null) { 30 | _instance = new DStackNavigatorObserver._internal(); 31 | } 32 | return _instance; 33 | } 34 | 35 | // 标识手势引起的pop事件 36 | String? _gesturingRouteName; 37 | String? get gesturingRouteName => this._gesturingRouteName; 38 | void setGesturingRouteName(String? gesturingRouteName) { 39 | this._gesturingRouteName = gesturingRouteName; 40 | } 41 | 42 | Route? _currentRoute; 43 | Route? get currentRoute => _currentRoute; 44 | 45 | /// 页面进入了 46 | /// route 路由目标页面 47 | /// previousRoute 目标页面的上一个页面 48 | @override 49 | void didPush(Route route, Route? previousRoute) { 50 | super.didPush(route, previousRoute); 51 | debugPrint( 52 | ' 【didPush】${route.settings.name}【didPush】'); 53 | routerCount += 1; 54 | _currentRoute = route; 55 | if (route is PopupRoute) { 56 | /// dialog进栈 57 | DNavigatorManager.nodeHandle( 58 | DStackConstant.flutterDialog, PageType.flutter, DStackConstant.push, 59 | route: route); 60 | } 61 | } 62 | 63 | /// 页面退出了(手势返回也会走这个方法) 64 | /// route 当前操作页面 65 | /// previousRoute 操作页面的上一个页面 66 | @override 67 | void didPop(Route route, Route? previousRoute) { 68 | super.didPop(route, previousRoute); 69 | routerCount -= 1; 70 | _currentRoute = previousRoute; 71 | debugPrint( 72 | ' 【didPop】${route.settings.name} 【didPop】'); 73 | if (route is PopupRoute) { 74 | /// dialog出栈 75 | DNavigatorManager.removeFlutterNode(DStackConstant.flutterDialog, 76 | identifier: DNavigatorManager.identifierWithRoute(route)); 77 | } else { 78 | if (gesturingRouteName != null && 79 | gesturingRouteName == route.settings.name) { 80 | // 由手势导致的pop事件 81 | DNavigatorManager.popWithGesture(route); 82 | } else if (gesturingRouteName != null && 83 | gesturingRouteName == DStackConstant.nativeDidPopGesture) { 84 | // native手势引起的didPop,native侧已经删除节点,flutter侧不再removeFlutterNode 85 | DStackNavigatorObserver.instance!.setGesturingRouteName(null); 86 | } else { 87 | if (route.settings.name != null) { 88 | DNavigatorManager.removeFlutterNode(route.settings.name, 89 | identifier: DNavigatorManager.identifierWithRoute(route)); 90 | } 91 | } 92 | } 93 | } 94 | 95 | @override 96 | void didReplace({Route? newRoute, Route? oldRoute}) { 97 | super.didReplace(newRoute: newRoute, oldRoute: oldRoute); 98 | _currentRoute = newRoute; 99 | } 100 | 101 | /// route 路由目标页面 102 | /// previousRoute 目标页面的上一个页面 103 | // 滑动手势开始 104 | @override 105 | void didStartUserGesture(Route route, Route? previousRoute) { 106 | super.didStartUserGesture(route, previousRoute); 107 | DStackNavigatorObserver.instance! 108 | .setGesturingRouteName(route.settings.name); 109 | } 110 | 111 | // 滑动手势结束 112 | @override 113 | void didStopUserGesture() { 114 | super.didStopUserGesture(); 115 | DStackNavigatorObserver.instance!.setGesturingRouteName(null); 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /lib/navigator/node_entity.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * Created with Android Studio. 3 | * User: linkewen 4 | * Date: 2020/12/3 5 | * Time: 17:18 6 | * target: node节点描述 7 | */ 8 | 9 | import 'package:d_stack/d_stack.dart'; 10 | 11 | class DNodeEntity { 12 | /// 节点列表 13 | late List nodeList; 14 | 15 | /// 跳转类型 16 | String? action; 17 | 18 | /// 是否有转场动画 19 | bool? animated; 20 | 21 | DNodeEntity.fromJson(Map json) { 22 | if (json["nodes"] != null) { 23 | final List list = []; 24 | (json["nodes"] as List).forEach((element) { 25 | list.add(DNode.fromJson(element)); 26 | }); 27 | nodeList = list; 28 | action = json["action"] ?? ""; 29 | animated = json["animated"] ?? true; 30 | } 31 | } 32 | 33 | Map toJson() { 34 | final Map json = Map(); 35 | List list = []; 36 | this.nodeList.forEach((element) { 37 | list.add(element.toJson()); 38 | }); 39 | json["nodes"] = list; 40 | json["action"] = this.action; 41 | json["animated"] = this.animated; 42 | return json; 43 | } 44 | } 45 | 46 | class DNode { 47 | /// 页面路由 48 | String? target; 49 | 50 | /// 跳转类型 51 | String? action; 52 | 53 | /// 携带参数 54 | Map? params; 55 | 56 | /// 页面类型 57 | PageType? pageType; 58 | 59 | /// 是否为homePage 60 | bool? homePage; 61 | 62 | /// 是否有转场动画 63 | bool? animated; 64 | 65 | /// 是否为临界节点 66 | bool? boundary; 67 | 68 | /// 页面唯一id 69 | String? identifier; 70 | 71 | DNode.fromJson(Map json) { 72 | target = json["target"]; 73 | action = json["action"]; 74 | params = json["params"]; 75 | homePage = json["homePage"] ?? false; 76 | animated = json["animated"] ?? true; 77 | boundary = json["boundary"] ?? false; 78 | identifier = json["identifier"]; 79 | String pageString = json["pageType"]; 80 | if (pageString.toLowerCase() == "flutter") { 81 | pageType = PageType.flutter; 82 | } else if (pageString.toLowerCase() == "native") { 83 | pageType = PageType.native; 84 | } 85 | } 86 | 87 | Map toJson() { 88 | final Map json = Map(); 89 | json["target"] = this.target; 90 | json["action"] = this.action; 91 | json["params"] = this.params; 92 | json["homePage"] = this.homePage; 93 | json["boundary"] = this.boundary; 94 | json["animated"] = this.animated; 95 | json["identifier"] = this.identifier; 96 | switch (this.pageType) { 97 | case PageType.flutter: 98 | { 99 | json["pageType"] = "flutter"; 100 | break; 101 | } 102 | case PageType.native: 103 | { 104 | json["pageType"] = "native"; 105 | break; 106 | } 107 | } 108 | return json; 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /lib/observer/d_node_observer.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * Created with Android Studio. 3 | * User: whqfor 4 | * Date: 12/5/20 5 | * Time: 6:04 PM 6 | * target: 监听节点的observer 7 | */ 8 | 9 | import 'package:d_stack/d_stack.dart'; 10 | 11 | abstract class DNodeObserver { 12 | /// 用户操作的所有行为都将会从这个api传出,可以基于此做行为回放 13 | /// 将要进行操作的节点 14 | void operationNode(Map? node); 15 | } 16 | 17 | class DNodeObserverHandler { 18 | static handlerNodeMessage(Map? node) { 19 | DStack.instance.dNodeObserver?.operationNode(node); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /lib/observer/life_cycle_observer.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * Created with Android Studio. 3 | * User: sunjian 4 | * Date: 2020/6/29 5 | * Time: 7:45 PM 6 | * target: 应用的生命周期 7 | */ 8 | 9 | import 'package:d_stack/d_stack.dart'; 10 | 11 | /// 应用的生命周期 创建 前台 后台 12 | enum DLifeCycleState { create, foreground, background } 13 | 14 | class PageModel { 15 | String? currentPageRoute; // 当前展示的页面路由 16 | String? prePageRoute; // 前一个展示的页面路由,可能为null 17 | String? currentPageType; // 页面类型:Flutter/Native 18 | String? prePageType; // 页面类型:Flutter/Native 19 | String? actionType; // 操作类型:push/pop 20 | 21 | PageModel( 22 | {this.currentPageRoute, 23 | this.prePageRoute, 24 | this.currentPageType, 25 | this.prePageType, 26 | this.actionType}); 27 | 28 | @override 29 | String toString() { 30 | return "{'currentPageRoute':$currentPageRoute,'prePageRoute':$prePageRoute," 31 | "'currentPageType':$currentPageType,'prePageType':$prePageType," 32 | "'actionType':$actionType}"; 33 | } 34 | } 35 | 36 | abstract class DLifeCycleObserver { 37 | void appDidStart(PageModel model); // 创建 对应 create 38 | 39 | void appDidEnterForeground(PageModel model); // 前台 对应foreground 40 | 41 | void appDidEnterBackground(PageModel model); // 后台 对应background 42 | 43 | void pageAppear(PageModel model); // 页面push/pop会调用 . 44 | } 45 | 46 | /// 处理页面生命周期 47 | class LifeCycleHandler { 48 | static handleLifecycleMessage(Map arguments) { 49 | Map? pageParams = arguments['page']; 50 | Map? appParams = arguments['application']; 51 | if (pageParams != null) { 52 | String? appearRoute = pageParams['appearRoute']; 53 | String? disappearRoute = pageParams['disappearRoute']; 54 | String? appearPageType = pageParams['appearPageType']; 55 | String? disappearPageType = pageParams['disappearPageType']; 56 | String? actionType = pageParams['actionType']; 57 | PageModel model = PageModel( 58 | currentPageRoute: appearRoute, 59 | prePageRoute: disappearRoute, 60 | currentPageType: appearPageType, 61 | prePageType: disappearPageType, 62 | actionType: actionType); 63 | DStack.instance.dLifeCycleObserver?.pageAppear(model); 64 | } 65 | if (appParams != null) { 66 | String? currentRoute = appParams['currentRoute']; 67 | String? pageType = appParams['pageType']; 68 | DLifeCycleState state = DLifeCycleState.values[appParams['state']]; 69 | PageModel model = 70 | PageModel(currentPageRoute: currentRoute, currentPageType: pageType); 71 | switch (state) { 72 | case DLifeCycleState.create: 73 | DStack.instance.dLifeCycleObserver?.appDidStart(model); 74 | break; 75 | case DLifeCycleState.foreground: 76 | DStack.instance.dLifeCycleObserver?.appDidEnterForeground(model); 77 | break; 78 | case DLifeCycleState.background: 79 | DStack.instance.dLifeCycleObserver?.appDidEnterBackground(model); 80 | break; 81 | } 82 | } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /lib/widget/home_widget.dart: -------------------------------------------------------------------------------- 1 | import 'package:d_stack/d_stack.dart'; 2 | import 'package:flutter/material.dart'; 3 | 4 | /* 5 | * DStack里面的homePage实现 6 | * 在MaterialApp的home里面设置该widget 7 | * 当工程是flutter为主工程时,设置home需要把工程中实际的homePage设置进去,比如DStackWidget(homePage: MyHomePage()); 8 | * 当工程是native为主工程时,设置home时直接设置成DStackWidget(),不要填写homePage 9 | * */ 10 | /// DStack入口Widget 11 | class DStackWidget extends StatelessWidget { 12 | /// 默认的homePage 13 | final Widget? homePage; 14 | 15 | /// homepage的route 16 | final String? homePageRoute; 17 | 18 | DStackWidget({Key? key, this.homePage, this.homePageRoute}) : super(key: key); 19 | 20 | @override 21 | Widget build(BuildContext context) { 22 | DStack.instance.homePageRoute = homePageRoute; 23 | return homePage ?? Container(color: Colors.white); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /pubspec.lock: -------------------------------------------------------------------------------- 1 | # Generated by pub 2 | # See https://dart.dev/tools/pub/glossary#lockfile 3 | packages: 4 | async: 5 | dependency: transitive 6 | description: 7 | name: async 8 | url: "https://pub.flutter-io.cn" 9 | source: hosted 10 | version: "2.8.1" 11 | boolean_selector: 12 | dependency: transitive 13 | description: 14 | name: boolean_selector 15 | url: "https://pub.flutter-io.cn" 16 | source: hosted 17 | version: "2.1.0" 18 | characters: 19 | dependency: transitive 20 | description: 21 | name: characters 22 | url: "https://pub.flutter-io.cn" 23 | source: hosted 24 | version: "1.1.0" 25 | charcode: 26 | dependency: transitive 27 | description: 28 | name: charcode 29 | url: "https://pub.flutter-io.cn" 30 | source: hosted 31 | version: "1.3.1" 32 | clock: 33 | dependency: transitive 34 | description: 35 | name: clock 36 | url: "https://pub.flutter-io.cn" 37 | source: hosted 38 | version: "1.1.0" 39 | collection: 40 | dependency: transitive 41 | description: 42 | name: collection 43 | url: "https://pub.flutter-io.cn" 44 | source: hosted 45 | version: "1.15.0" 46 | fake_async: 47 | dependency: transitive 48 | description: 49 | name: fake_async 50 | url: "https://pub.flutter-io.cn" 51 | source: hosted 52 | version: "1.2.0" 53 | flutter: 54 | dependency: "direct main" 55 | description: flutter 56 | source: sdk 57 | version: "0.0.0" 58 | flutter_test: 59 | dependency: "direct dev" 60 | description: flutter 61 | source: sdk 62 | version: "0.0.0" 63 | matcher: 64 | dependency: transitive 65 | description: 66 | name: matcher 67 | url: "https://pub.flutter-io.cn" 68 | source: hosted 69 | version: "0.12.10" 70 | meta: 71 | dependency: transitive 72 | description: 73 | name: meta 74 | url: "https://pub.flutter-io.cn" 75 | source: hosted 76 | version: "1.7.0" 77 | path: 78 | dependency: transitive 79 | description: 80 | name: path 81 | url: "https://pub.flutter-io.cn" 82 | source: hosted 83 | version: "1.8.0" 84 | sky_engine: 85 | dependency: transitive 86 | description: flutter 87 | source: sdk 88 | version: "0.0.99" 89 | source_span: 90 | dependency: transitive 91 | description: 92 | name: source_span 93 | url: "https://pub.flutter-io.cn" 94 | source: hosted 95 | version: "1.8.1" 96 | stack_trace: 97 | dependency: transitive 98 | description: 99 | name: stack_trace 100 | url: "https://pub.flutter-io.cn" 101 | source: hosted 102 | version: "1.10.0" 103 | stream_channel: 104 | dependency: transitive 105 | description: 106 | name: stream_channel 107 | url: "https://pub.flutter-io.cn" 108 | source: hosted 109 | version: "2.1.0" 110 | string_scanner: 111 | dependency: transitive 112 | description: 113 | name: string_scanner 114 | url: "https://pub.flutter-io.cn" 115 | source: hosted 116 | version: "1.1.0" 117 | term_glyph: 118 | dependency: transitive 119 | description: 120 | name: term_glyph 121 | url: "https://pub.flutter-io.cn" 122 | source: hosted 123 | version: "1.2.0" 124 | test_api: 125 | dependency: transitive 126 | description: 127 | name: test_api 128 | url: "https://pub.flutter-io.cn" 129 | source: hosted 130 | version: "0.4.2" 131 | typed_data: 132 | dependency: transitive 133 | description: 134 | name: typed_data 135 | url: "https://pub.flutter-io.cn" 136 | source: hosted 137 | version: "1.3.0" 138 | vector_math: 139 | dependency: transitive 140 | description: 141 | name: vector_math 142 | url: "https://pub.flutter-io.cn" 143 | source: hosted 144 | version: "2.1.0" 145 | sdks: 146 | dart: ">=2.12.0 <3.0.0" 147 | flutter: ">=2.0.0" 148 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: d_stack 2 | description: 混合栈 3 | version: 1.3.4+3-nullsafety 4 | author: peter 5 | homepage: https://github.com/tal-tech/d_stack 6 | 7 | environment: 8 | sdk: ">=2.12.0 <3.0.0" 9 | flutter: ">=2.0.0" 10 | 11 | dependencies: 12 | flutter: 13 | sdk: flutter 14 | 15 | dev_dependencies: 16 | flutter_test: 17 | sdk: flutter 18 | 19 | # For information on the generic Dart part of this file, see the 20 | # following page: https://dart.dev/tools/pub/pubspec 21 | 22 | # The following section is specific to Flutter. 23 | flutter: 24 | # This section identifies this Flutter project as a plugin project. 25 | # The 'pluginClass' and Android 'package' identifiers should not ordinarily 26 | # be modified. They are used by the tooling to maintain consistency when 27 | # adding or updating assets for this project. 28 | plugin: 29 | platforms: 30 | android: 31 | package: tal.com.d_stack 32 | pluginClass: DStackPlugin 33 | ios: 34 | pluginClass: DStackPlugin 35 | 36 | # To add assets to your plugin package, add an assets section, like this: 37 | # assets: 38 | # - images/a_dot_burr.jpeg 39 | # - images/a_dot_ham.jpeg 40 | # 41 | # For details regarding assets in packages, see 42 | # https://flutter.dev/assets-and-images/#from-packages 43 | # 44 | # An image asset can refer to one or more resolution-specific "variants", see 45 | # https://flutter.dev/assets-and-images/#resolution-aware. 46 | 47 | # To add custom fonts to your plugin package, add a fonts section here, 48 | # in this "flutter" section. Each entry in this list should have a 49 | # "family" key with the font family name, and a "fonts" key with a 50 | # list giving the asset and other descriptors for the font. For 51 | # example: 52 | # fonts: 53 | # - family: Schyler 54 | # fonts: 55 | # - asset: fonts/Schyler-Regular.ttf 56 | # - asset: fonts/Schyler-Italic.ttf 57 | # style: italic 58 | # - family: Trajan Pro 59 | # fonts: 60 | # - asset: fonts/TrajanPro.ttf 61 | # - asset: fonts/TrajanPro_Bold.ttf 62 | # weight: 700 63 | # 64 | # For details regarding fonts in packages, see 65 | # https://flutter.dev/custom-fonts/#from-packages 66 | 67 | -------------------------------------------------------------------------------- /test/d_stack_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/services.dart'; 2 | import 'package:flutter_test/flutter_test.dart'; 3 | 4 | void main() { 5 | const MethodChannel channel = MethodChannel('d_stack'); 6 | 7 | TestWidgetsFlutterBinding.ensureInitialized(); 8 | 9 | setUp(() { 10 | channel.setMockMethodCallHandler((MethodCall methodCall) async { 11 | return '42'; 12 | }); 13 | }); 14 | 15 | tearDown(() { 16 | channel.setMockMethodCallHandler(null); 17 | }); 18 | } 19 | --------------------------------------------------------------------------------