├── .clang-format ├── .gitignore ├── .images ├── logo.png ├── mixstack_structure.png └── native_replacer_problem.png ├── CHANGELOG.md ├── LICENSE ├── README.md ├── README_cn.md ├── TEST.md ├── android ├── .gitignore ├── build.gradle ├── gradle.properties ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── settings.gradle └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── yuewen │ │ └── mix_stack │ │ ├── component │ │ └── MXFlutterActivityTest.java │ │ ├── core │ │ ├── MXPageManagerTest.java │ │ └── Whitebox.java │ │ └── utils │ │ └── OverlayUtilsTest.java │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── com │ │ └── yuewen │ │ └── mix_stack │ │ ├── MixStackPlugin.java │ │ ├── component │ │ ├── ActivityFragmentDelegate.java │ │ ├── MXFlutterActivity.java │ │ ├── MXFlutterFragment.java │ │ └── MXFlutterView.java │ │ ├── core │ │ ├── LifecycleNotifier.java │ │ ├── MXPageManager.java │ │ ├── MXStackInternal.java │ │ ├── MXStackService.java │ │ └── PageOverlayConfig.java │ │ ├── interfaces │ │ ├── IMXPage.java │ │ ├── IMXPageManager.java │ │ ├── InvokeMethodListener.java │ │ ├── PageHistoryListener.java │ │ └── PageIsRootListener.java │ │ ├── model │ │ ├── AreaInsetsConfig.java │ │ └── MXViewConfig.java │ │ └── utils │ │ ├── CommonUtils.java │ │ ├── InvokePipeline.java │ │ ├── OverlayUtils.java │ │ ├── ReflectionUtil.java │ │ ├── RomUtils.java │ │ └── StatusBarUtil.java │ └── res │ └── layout │ └── flutter_dialog.xml ├── example ├── .gitignore ├── README.md ├── android │ ├── .gitignore │ ├── app │ │ ├── build.gradle │ │ └── src │ │ │ ├── debug │ │ │ └── AndroidManifest.xml │ │ │ ├── main │ │ │ ├── AndroidManifest.xml │ │ │ ├── java │ │ │ │ └── com │ │ │ │ │ └── yuewen │ │ │ │ │ └── mix_stack_example │ │ │ │ │ ├── FlutterMainActivity.java │ │ │ │ │ ├── MoreFunctionActivity.java │ │ │ │ │ ├── MultipleTabActivity.java │ │ │ │ │ ├── NativeActivity.java │ │ │ │ │ ├── NativeFragment.java │ │ │ │ │ └── componet │ │ │ │ │ ├── MainApplication.java │ │ │ │ │ └── NativeRouterPlugin.java │ │ │ └── res │ │ │ │ ├── drawable │ │ │ │ ├── launch_background.xml │ │ │ │ └── logo_splash.png │ │ │ │ ├── layout │ │ │ │ ├── activity_main.xml │ │ │ │ ├── activity_more_function.xml │ │ │ │ ├── activity_native.xml │ │ │ │ ├── activity_tab.xml │ │ │ │ └── layout_native_list.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 │ │ │ │ ├── colors.xml │ │ │ │ └── styles.xml │ │ │ └── profile │ │ │ └── AndroidManifest.xml │ ├── build.gradle │ ├── gradle.properties │ ├── gradle │ │ └── wrapper │ │ │ └── gradle-wrapper.properties │ ├── settings.gradle │ └── settings_aar.gradle ├── images │ ├── bg_timer.png │ └── test.png ├── ios │ ├── .gitignore │ ├── Flutter │ │ ├── .last_build_id │ │ ├── AppFrameworkInfo.plist │ │ ├── Debug.xcconfig │ │ └── Release.xcconfig │ ├── Podfile │ ├── Podfile.lock │ ├── Runner-Bridging-Header.h │ ├── Runner.xcodeproj │ │ ├── project.pbxproj │ │ ├── project.xcworkspace │ │ │ ├── contents.xcworkspacedata │ │ │ └── xcshareddata │ │ │ │ ├── IDEWorkspaceChecks.plist │ │ │ │ └── WorkspaceSettings.xcsettings │ │ └── 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 │ │ │ ├── Contents.json │ │ │ ├── LaunchImage.imageset │ │ │ │ ├── Contents.json │ │ │ │ ├── LaunchImage.png │ │ │ │ ├── LaunchImage@2x.png │ │ │ │ ├── LaunchImage@3x.png │ │ │ │ └── README.md │ │ │ ├── bg_timer.imageset │ │ │ │ ├── Contents.json │ │ │ │ └── bg_timer.png │ │ │ ├── ic_collections.imageset │ │ │ │ ├── Contents.json │ │ │ │ └── ic_collections.pdf │ │ │ └── ic_view_headline.imageset │ │ │ │ ├── Contents.json │ │ │ │ └── ic_view_headline.pdf │ │ ├── Base.lproj │ │ │ ├── LaunchScreen.storyboard │ │ │ └── Main.storyboard │ │ ├── FlutterPageViewController.h │ │ ├── FlutterPageViewController.m │ │ ├── Info.plist │ │ ├── MoreFunctionViewController.h │ │ ├── MoreFunctionViewController.m │ │ ├── NativeExmpleViewController.h │ │ ├── NativeExmpleViewController.m │ │ ├── NativeViewController.h │ │ ├── NativeViewController.m │ │ ├── TabViewController.h │ │ ├── TabViewController.m │ │ ├── TestListController.h │ │ ├── TestListController.m │ │ └── main.m │ └── test.swift ├── lib │ ├── area_inset.dart │ ├── common_utils.dart │ ├── main.dart │ ├── page_controller.dart │ ├── popup_window.dart │ ├── present_flutter_page.dart │ ├── simple_flutter_page.dart │ └── test_main.dart ├── pubspec.lock ├── pubspec.yaml └── test │ └── widget_test.dart ├── fulltest.sh ├── ios ├── .gitignore ├── Assets │ └── .gitkeep ├── Classes │ ├── MXAbstractTabBarController.h │ ├── MXAbstractTabBarController.m │ ├── MXContainerViewController.h │ ├── MXContainerViewController.m │ ├── MXOverlayHandlerProtocol.h │ ├── MXOverlayHandlerProtocol.m │ ├── MXStackExchange.h │ ├── MXStackExchange.m │ ├── MixStackPlugin.h │ ├── MixStackPlugin.m │ ├── UIViewController+FlutterViewHint.h │ ├── UIViewController+FlutterViewHint.m │ └── mix_stack.h └── mix_stack.podspec ├── lib ├── mix_stack.dart └── src │ ├── autofocus_page_smoother.dart │ ├── helper.dart │ ├── mix_stack.dart │ ├── mix_stack_app.dart │ ├── native_overlay_replacer.dart │ ├── pages.dart │ ├── route_observer.dart │ └── stack_exchange.dart ├── mix_stack.iml ├── pubspec.lock ├── pubspec.yaml └── test ├── autofocus_page_smoother_test.dart ├── helper_test.dart ├── mix_stack_app_test.dart ├── mix_stack_test.dart ├── native_overlay_replacer_test.dart ├── pages_test.dart ├── route_observer_test.dart └── stack_exchange_test.dart /.clang-format: -------------------------------------------------------------------------------- 1 | /Users/pepsin/Developer/spacecommander/.clang-format -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .dart_tool/ 3 | 4 | .packages 5 | .pub/ 6 | .idea 7 | .vscode 8 | .gitlab-ci.yml 9 | .metadata 10 | 11 | build/ 12 | settings.json 13 | coverage/ -------------------------------------------------------------------------------- /.images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yuewen/mix_stack/1edd822d25a06e9578c45f677f1147577cf8b131/.images/logo.png -------------------------------------------------------------------------------- /.images/mixstack_structure.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yuewen/mix_stack/1edd822d25a06e9578c45f677f1147577cf8b131/.images/mixstack_structure.png -------------------------------------------------------------------------------- /.images/native_replacer_problem.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yuewen/mix_stack/1edd822d25a06e9578c45f677f1147577cf8b131/.images/native_replacer_problem.png -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # ChangeLog 2 | 3 | ## 1.0.0 4 | 5 | Initial release 6 | 7 | ## 1.0.1 8 | 9 | Update docs and yamls according to pub.dev suggestion 10 | 11 | ## 1.0.2 12 | 13 | Update README 14 | 15 | ## 1.0.3 16 | 17 | Fix example pod file 18 | 19 | ## 1.0.6 20 | 21 | Fix navigator null issue 22 | 23 | ## 1.4.0 24 | 25 | Fully support Null Safety -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD License 2 | Copyright 2021 Yuewen.Inc 3 | 4 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 5 | 6 | 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 7 | 8 | 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 9 | 10 | 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. 11 | 12 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /TEST.md: -------------------------------------------------------------------------------- 1 | # 测试指南 2 | 3 | ## Flutter 测试 4 | 5 | ```shell 6 | #生成数据 7 | flutter test --coverage 8 | 9 | #生成 HTML 可视化页面 10 | genhtml coverage/lcov.info -o coverage/html 11 | 12 | #打开可视化页面 13 | open ./coverage/html/index.html 14 | ``` 15 | -------------------------------------------------------------------------------- /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 'com.yuewen.mix_stack' 2 | version '1.1.1' 3 | 4 | buildscript { 5 | repositories { 6 | google() 7 | mavenCentral() 8 | } 9 | 10 | dependencies { 11 | classpath 'com.android.tools.build:gradle:7.0.3' 12 | } 13 | } 14 | 15 | rootProject.allprojects { 16 | repositories { 17 | google() 18 | mavenCentral() 19 | } 20 | } 21 | 22 | apply plugin: 'com.android.library' 23 | 24 | android { 25 | compileSdkVersion 31 26 | 27 | defaultConfig { 28 | minSdkVersion 21 29 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 30 | } 31 | lintOptions { 32 | disable 'InvalidPackage' 33 | } 34 | compileOptions { 35 | sourceCompatibility JavaVersion.VERSION_1_8 36 | targetCompatibility JavaVersion.VERSION_1_8 37 | } 38 | testOptions { 39 | unitTests.returnDefaultValues = true 40 | } 41 | buildTypes { 42 | debug { 43 | testCoverageEnabled false 44 | minifyEnabled false 45 | } 46 | } 47 | } 48 | dependencies { 49 | implementation 'androidx.appcompat:appcompat:1.2.0' 50 | 51 | androidTestImplementation 'junit:junit:4.12' 52 | 53 | androidTestImplementation 'com.android.support.test:rules:1.0.2' 54 | androidTestImplementation 'com.android.support.test:runner:1.0.2' 55 | 56 | androidTestImplementation 'androidx.test.ext:junit:1.1.2' 57 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0' 58 | androidTestImplementation "org.mockito:mockito-android:2.24.5" 59 | 60 | } 61 | -------------------------------------------------------------------------------- /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.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yuewen/mix_stack/1edd822d25a06e9578c45f677f1147577cf8b131/android/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Tue May 31 18:25:15 CST 2022 2 | distributionBase=GRADLE_USER_HOME 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-bin.zip 4 | distributionPath=wrapper/dists 5 | zipStorePath=wrapper/dists 6 | zipStoreBase=GRADLE_USER_HOME 7 | -------------------------------------------------------------------------------- /android/gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /android/settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'mix_stack' 2 | -------------------------------------------------------------------------------- /android/src/androidTest/java/com/yuewen/mix_stack/component/MXFlutterActivityTest.java: -------------------------------------------------------------------------------- 1 | package com.yuewen.mix_stack.component; 2 | 3 | import androidx.test.core.app.ActivityScenario; 4 | import androidx.test.core.app.ApplicationProvider; 5 | import androidx.test.platform.app.InstrumentationRegistry; 6 | 7 | import com.yuewen.mix_stack.core.MXStackService; 8 | 9 | import org.junit.Before; 10 | import org.junit.Test; 11 | 12 | import io.flutter.embedding.engine.FlutterEngine; 13 | 14 | /******************************************************* 15 | * 16 | * Created by julis.wang on 2020/10/12 15:14 17 | * 18 | * Description : 19 | * History : 20 | * 21 | *******************************************************/ 22 | public class MXFlutterActivityTest { 23 | 24 | @Before 25 | public void setUp() throws Exception { 26 | 27 | // FlutterEngine flutterEngine = Mockito.mock(FlutterEngine.class); 28 | // Whitebox.setInternalState(MXStackService.getInstance(), "flutterEngine", flutterEngine); 29 | // 30 | // Mockito.when(flutterEngine.getRenderer()).thenReturn(Mockito.mock(FlutterRenderer.class)); 31 | 32 | InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() { 33 | @Override 34 | public void run() { 35 | MXStackService.init(ApplicationProvider.getApplicationContext()); 36 | } 37 | }); 38 | 39 | ActivityScenario activityScenario 40 | = ActivityScenario.launch(MXFlutterActivity.class); 41 | 42 | } 43 | 44 | @Test 45 | public void onCreate() { 46 | // Espresso.onView(ViewMatchers.withId(R.id.fl_flutter_container)) 47 | // .check(matches(not(isDisplayed()))); //是否不可见 48 | } 49 | 50 | @Test 51 | public void onDestroy() { 52 | FlutterEngine flutterEngine = new FlutterEngine(ApplicationProvider.getApplicationContext()); 53 | } 54 | 55 | @Test 56 | public void onResume() { 57 | } 58 | 59 | @Test 60 | public void onPause() { 61 | } 62 | 63 | @Test 64 | public void onBackPressed() { 65 | } 66 | 67 | @Test 68 | public void getFlutterView() { 69 | } 70 | 71 | @Test 72 | public void rootRoute() { 73 | } 74 | 75 | @Test 76 | public void onPopNative() { 77 | } 78 | 79 | @Test 80 | public void getPageManager() { 81 | } 82 | 83 | @Test 84 | public void provideSplashScreen() { 85 | } 86 | } -------------------------------------------------------------------------------- /android/src/androidTest/java/com/yuewen/mix_stack/core/Whitebox.java: -------------------------------------------------------------------------------- 1 | package com.yuewen.mix_stack.core; 2 | 3 | import java.lang.reflect.Field; 4 | 5 | /******************************************************* 6 | * 7 | * Created by julis.wang on 2020/10/10 16:29 8 | * 9 | * Description : 10 | * History : 11 | * 12 | *******************************************************/ 13 | 14 | public class Whitebox { 15 | 16 | public static Object getInternalState(Object target, String field) { 17 | Class c = target.getClass(); 18 | try { 19 | Field f = getFieldFromHierarchy(c, field); 20 | f.setAccessible(true); 21 | return f.get(target); 22 | } catch (Exception e) { 23 | throw new RuntimeException("Unable to get internal state on a private field. Please report to mockito mailing list.", e); 24 | } 25 | } 26 | 27 | public static void setInternalState(Object target, String field, Object value) { 28 | Class c = target.getClass(); 29 | try { 30 | Field f = getFieldFromHierarchy(c, field); 31 | f.setAccessible(true); 32 | f.set(target, value); 33 | } catch (Exception e) { 34 | throw new RuntimeException("Unable to set internal state on a private field. Please report to mockito mailing list.", e); 35 | } 36 | } 37 | 38 | private static Field getFieldFromHierarchy(Class clazz, String field) { 39 | Field f = getField(clazz, field); 40 | while (f == null && clazz != Object.class) { 41 | clazz = clazz.getSuperclass(); 42 | f = getField(clazz, field); 43 | } 44 | if (f == null) { 45 | throw new RuntimeException( 46 | "You want me to get this field: '" + field + 47 | "' on this class: '" + clazz.getSimpleName() + 48 | "' but this field is not declared withing hierarchy of this class!"); 49 | } 50 | return f; 51 | } 52 | 53 | private static Field getField(Class clazz, String field) { 54 | try { 55 | return clazz.getDeclaredField(field); 56 | } catch (NoSuchFieldException e) { 57 | return null; 58 | } 59 | } 60 | } -------------------------------------------------------------------------------- /android/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /android/src/main/java/com/yuewen/mix_stack/MixStackPlugin.java: -------------------------------------------------------------------------------- 1 | package com.yuewen.mix_stack; 2 | 3 | import androidx.annotation.NonNull; 4 | 5 | import com.yuewen.mix_stack.core.MXStackService; 6 | import com.yuewen.mix_stack.interfaces.IMXPage; 7 | import com.yuewen.mix_stack.interfaces.InvokeMethodListener; 8 | import com.yuewen.mix_stack.utils.InvokePipeline; 9 | import com.yuewen.mix_stack.utils.OverlayUtils; 10 | 11 | import java.util.Map; 12 | 13 | import io.flutter.embedding.engine.plugins.FlutterPlugin; 14 | import io.flutter.plugin.common.MethodCall; 15 | import io.flutter.plugin.common.MethodChannel; 16 | import io.flutter.plugin.common.MethodChannel.MethodCallHandler; 17 | import io.flutter.plugin.common.MethodChannel.Result; 18 | import io.flutter.plugin.common.PluginRegistry.Registrar; 19 | 20 | /** 21 | * MixStackPlugin 22 | */ 23 | public class MixStackPlugin implements FlutterPlugin, MethodCallHandler { 24 | private static final String TAG = "MixStackPlugin"; 25 | private MethodChannel channel; 26 | 27 | @Override 28 | public void onAttachedToEngine(@NonNull FlutterPluginBinding flutterPluginBinding) { 29 | channel = new MethodChannel(flutterPluginBinding.getFlutterEngine().getDartExecutor(), "mix_stack"); 30 | channel.setMethodCallHandler(this); 31 | InvokePipeline.getInstance().setChannel(channel); 32 | } 33 | 34 | public static void registerWith(Registrar registrar) { 35 | MethodChannel channel = new MethodChannel(registrar.messenger(), "mix_stack"); 36 | channel.setMethodCallHandler(new MixStackPlugin()); 37 | InvokePipeline.getInstance().setChannel(channel); 38 | } 39 | 40 | @Override 41 | public void onMethodCall(@NonNull MethodCall call, @NonNull Result result) { 42 | String method = call.method; 43 | switch (method) { 44 | case "enablePanNavigation": 45 | result.success(null); 46 | break; 47 | case "currentOverlayTexture": 48 | OverlayUtils.getOverlayTexture(call, result); 49 | break; 50 | case "configOverlays": 51 | OverlayUtils.configOverlays(call, result); 52 | break; 53 | case "overlayNames": 54 | OverlayUtils.overlayNames(call, result); 55 | break; 56 | case "popNative": 57 | popNative(call, result); 58 | break; 59 | case "overlayInfos": 60 | OverlayUtils.overlayInfo(call, result); 61 | break; 62 | case "updatePages": 63 | updatePages(); 64 | break; 65 | default: 66 | result.notImplemented(); 67 | break; 68 | } 69 | } 70 | 71 | 72 | /** 73 | * Has no flutter page will call this method. 74 | * 75 | * @param call 76 | * @param result 77 | */ 78 | private void popNative(MethodCall call, Result result) { 79 | IMXPage currentPage = MXStackService.getInstance().getCurrentPage(); 80 | if (currentPage == null) { 81 | result.success(false); 82 | } else { 83 | currentPage.onPopNative(); 84 | } 85 | result.success(true); 86 | } 87 | 88 | /** 89 | * Fix Hot Reload issue in debug status. 90 | */ 91 | private void updatePages() { 92 | InvokePipeline.getInstance().invoke("updatePages", null); 93 | } 94 | 95 | @Override 96 | public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) { 97 | if (channel != null) { 98 | channel.setMethodCallHandler(null); 99 | channel = null; 100 | } 101 | } 102 | 103 | public static void invoke(String method, Map query) { 104 | InvokePipeline.getInstance().invoke(method, query); 105 | } 106 | 107 | public static void invokeWithListener(String method, Map query, InvokeMethodListener listener) { 108 | InvokePipeline.getInstance().invokeWithListener(method, query, listener); 109 | } 110 | 111 | } 112 | -------------------------------------------------------------------------------- /android/src/main/java/com/yuewen/mix_stack/core/LifecycleNotifier.java: -------------------------------------------------------------------------------- 1 | package com.yuewen.mix_stack.core; 2 | 3 | import com.yuewen.mix_stack.utils.InvokePipeline; 4 | 5 | import java.util.HashMap; 6 | import java.util.Map; 7 | 8 | import io.flutter.Log; 9 | 10 | /******************************************************* 11 | * 12 | * Created by julis.wang on 2020/12/29 13:57 13 | * 14 | * Description : 15 | * History : 16 | * 17 | *******************************************************/ 18 | 19 | public class LifecycleNotifier { 20 | private static final String TAG = "LifecycleNotifier"; 21 | 22 | public static final String INACTIVE = "inactive"; 23 | public static final String RESUME = "resumed"; 24 | public static final String PAUSED = "paused"; 25 | public static final String DETACHED = "detached"; 26 | public static Map currentUpdateMap = new HashMap<>(); 27 | 28 | public static void appIsInactive() { 29 | Log.d(TAG, "Sending AppLifecycleState.inactive message."); 30 | updateLifecycle(INACTIVE); 31 | } 32 | 33 | public static void appIsResumed() { 34 | Log.d(TAG, "Sending AppLifecycleState.resumed message."); 35 | updateLifecycle(RESUME); 36 | } 37 | 38 | public static void appIsPaused() { 39 | Log.d(TAG, "Sending AppLifecycleState.paused message."); 40 | updateLifecycle(PAUSED); 41 | } 42 | 43 | public static void appIsDetached() { 44 | Log.d(TAG, "Sending AppLifecycleState.detached message."); 45 | updateLifecycle(DETACHED); 46 | } 47 | 48 | private static void updateLifecycle(String type) { 49 | Map queryMap = new HashMap<>(); 50 | queryMap.put("lifecycle", type); 51 | currentUpdateMap = queryMap; 52 | InvokePipeline.getInstance().invoke("updateLifecycle", queryMap); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /android/src/main/java/com/yuewen/mix_stack/core/PageOverlayConfig.java: -------------------------------------------------------------------------------- 1 | package com.yuewen.mix_stack.core; 2 | 3 | import android.view.View; 4 | import android.view.animation.AlphaAnimation; 5 | import android.view.animation.Animation; 6 | 7 | import com.yuewen.mix_stack.model.AreaInsetsConfig; 8 | import com.yuewen.mix_stack.model.MXViewConfig; 9 | 10 | import java.util.HashMap; 11 | import java.util.List; 12 | import java.util.Map; 13 | import java.util.Set; 14 | 15 | /******************************************************* 16 | * 17 | * Created by julis.wang on 2020/08/19 14:34 18 | * 19 | * Description : 20 | * Sometimes, if we have some interface elements on the Flutter page that need to be attached 21 | * to a tab-like native interface, the Flutter elements need to know the corresponding 22 | * native interface information for the layout. 23 | * 24 | * At this point, MixStack can be used to obtain the information of the corresponding interface, 25 | * which can be properly delayed to avoid the medium situation of native interface animation. 26 | * 27 | * 28 | * 29 | * History : 30 | * 31 | *******************************************************/ 32 | 33 | public abstract class PageOverlayConfig { 34 | private static final int DEFAULT_DURATION = 200; 35 | 36 | public Map overlayViewsForNames(List overlayNames) { 37 | Map nameViews = new HashMap<>(); 38 | for (String name : overlayNames) { 39 | nameViews.put(name, overlayView(name)); 40 | } 41 | return nameViews; 42 | } 43 | 44 | public void configOverlay(Map overlayNamesConfig) { 45 | Set keys = overlayNamesConfig.keySet(); 46 | for (String key : keys) { 47 | final MXViewConfig config = overlayNamesConfig.get(key); 48 | final View view = overlayView(key); 49 | if (config == null) { 50 | continue; 51 | } 52 | if (config.isNeedsAnimation()) { 53 | final float viewAlpha = view.getAlpha(); 54 | final float configAlpha = config.getAlpha(); 55 | 56 | final AlphaAnimation alphaAnimation = new AlphaAnimation(viewAlpha, configAlpha); 57 | alphaAnimation.setDuration(DEFAULT_DURATION); 58 | alphaAnimation.setRepeatCount(0); 59 | view.startAnimation(alphaAnimation); 60 | alphaAnimation.setAnimationListener(new Animation.AnimationListener() { 61 | @Override 62 | public void onAnimationStart(Animation animation) { 63 | if (viewAlpha < configAlpha) { 64 | view.setAlpha(config.getAlpha()); 65 | } 66 | } 67 | 68 | @Override 69 | public void onAnimationEnd(Animation animation) { 70 | view.setAlpha(config.getAlpha()); 71 | } 72 | 73 | @Override 74 | public void onAnimationRepeat(Animation animation) { 75 | 76 | } 77 | }); 78 | } else { 79 | view.setAlpha(config.getAlpha()); 80 | } 81 | view.setVisibility(config.isHidden() ? View.INVISIBLE : View.VISIBLE); 82 | } 83 | } 84 | 85 | public abstract List overlayNames(); 86 | 87 | public abstract View overlayView(String viewName); 88 | 89 | public abstract AreaInsetsConfig ignoreAreaInsetsConfig(); 90 | 91 | 92 | } 93 | -------------------------------------------------------------------------------- /android/src/main/java/com/yuewen/mix_stack/interfaces/IMXPage.java: -------------------------------------------------------------------------------- 1 | package com.yuewen.mix_stack.interfaces; 2 | 3 | /******************************************************* 4 | * 5 | * Created by julis.wang on 2020/08/11 10:33 6 | * 7 | * Description : 8 | * History : 9 | * 10 | *******************************************************/ 11 | 12 | public interface IMXPage { 13 | /** 14 | * Every flutter page must have a route which tells flutter which page will show. 15 | *

16 | * It implements in {@link com.yuewen.mix_stack.component.MXFlutterFragment} and 17 | * {@link com.yuewen.mix_stack.component.MXFlutterActivity},and called by 18 | * {@link com.yuewen.mix_stack.core.MXPageManager#setCurrentShowPage(Object)} 19 | * 20 | * @return the init root. 21 | */ 22 | String rootRoute(); 23 | 24 | 25 | /** 26 | * Has no flutter page will call this method. 27 | * Default implement is finish current Activity. 28 | */ 29 | void onPopNative(); 30 | } 31 | -------------------------------------------------------------------------------- /android/src/main/java/com/yuewen/mix_stack/interfaces/IMXPageManager.java: -------------------------------------------------------------------------------- 1 | package com.yuewen.mix_stack.interfaces; 2 | 3 | import com.yuewen.mix_stack.core.MXPageManager; 4 | 5 | /******************************************************* 6 | * 7 | * Created by julis.wang on 2020/08/11 10:33 8 | * 9 | * Description : 10 | * History : 11 | * 12 | *******************************************************/ 13 | 14 | public interface IMXPageManager { 15 | MXPageManager getPageManager(); 16 | } 17 | -------------------------------------------------------------------------------- /android/src/main/java/com/yuewen/mix_stack/interfaces/InvokeMethodListener.java: -------------------------------------------------------------------------------- 1 | package com.yuewen.mix_stack.interfaces; 2 | 3 | /******************************************************* 4 | * 5 | * Created by julis.wang on 2020/09/03 19:18 6 | * 7 | * Description : 8 | * History : 9 | * 10 | *******************************************************/ 11 | 12 | public interface InvokeMethodListener { 13 | void onCompleted(Object result); 14 | } 15 | -------------------------------------------------------------------------------- /android/src/main/java/com/yuewen/mix_stack/interfaces/PageHistoryListener.java: -------------------------------------------------------------------------------- 1 | package com.yuewen.mix_stack.interfaces; 2 | 3 | import java.util.List; 4 | 5 | /******************************************************* 6 | * 7 | * Created by julis.wang on 2020/09/03 19:18 8 | * 9 | * Description : 10 | * History : 11 | * 12 | *******************************************************/ 13 | 14 | public interface PageHistoryListener { 15 | void pageHistory(List history); 16 | } 17 | -------------------------------------------------------------------------------- /android/src/main/java/com/yuewen/mix_stack/interfaces/PageIsRootListener.java: -------------------------------------------------------------------------------- 1 | package com.yuewen.mix_stack.interfaces; 2 | 3 | /******************************************************* 4 | * 5 | * Created by julis.wang on 2020/09/03 19:18 6 | * 7 | * Description : 8 | * History : 9 | * 10 | *******************************************************/ 11 | 12 | public interface PageIsRootListener { 13 | void isInRootPage(boolean isInRootPage); 14 | } 15 | -------------------------------------------------------------------------------- /android/src/main/java/com/yuewen/mix_stack/model/AreaInsetsConfig.java: -------------------------------------------------------------------------------- 1 | package com.yuewen.mix_stack.model; 2 | 3 | import android.view.View; 4 | 5 | import com.yuewen.mix_stack.core.PageOverlayConfig; 6 | import com.yuewen.mix_stack.utils.CommonUtils; 7 | 8 | import java.util.ArrayList; 9 | import java.util.List; 10 | 11 | /******************************************************* 12 | * 13 | * Created by julis.wang on 2020/09/22 11:18 14 | * 15 | * Description : 16 | * History : 17 | * 18 | *******************************************************/ 19 | 20 | public class AreaInsetsConfig { 21 | private List leftIgnoreNames; 22 | private List topIgnoreNames; 23 | private List rightIgnoreNames; 24 | private List bottomIgnoreNames; 25 | 26 | public AreaInsetsConfig() { 27 | leftIgnoreNames = new ArrayList<>(); 28 | topIgnoreNames = new ArrayList<>(); 29 | rightIgnoreNames = new ArrayList<>(); 30 | bottomIgnoreNames = new ArrayList<>(); 31 | } 32 | 33 | public void setLeftIgnoreNames(List leftIgnoreNames) { 34 | this.leftIgnoreNames = leftIgnoreNames; 35 | } 36 | 37 | public void setTopIgnoreNames(List topIgnoreNames) { 38 | this.topIgnoreNames = topIgnoreNames; 39 | } 40 | 41 | public void setRightIgnoreNames(List rightIgnoreNames) { 42 | this.rightIgnoreNames = rightIgnoreNames; 43 | } 44 | 45 | public void setBottomIgnoreNames(List bottomIgnoreNames) { 46 | this.bottomIgnoreNames = bottomIgnoreNames; 47 | } 48 | 49 | public void addLeftIgnoreNames(String leftIgnoreName) { 50 | leftIgnoreNames.add(leftIgnoreName); 51 | } 52 | 53 | public void addTopIgnoreNames(String topIgnoreName) { 54 | topIgnoreNames.add(topIgnoreName); 55 | } 56 | 57 | public void addRightIgnoreNames(String rightIgnoreName) { 58 | rightIgnoreNames.add(rightIgnoreName); 59 | } 60 | 61 | public void addBottomIgnoreNames(String bottomIgnoreName) { 62 | bottomIgnoreNames.add(bottomIgnoreName); 63 | } 64 | 65 | public MXViewConfig.InsetInfo areaInsetsForOverlayHandler(PageOverlayConfig config) { 66 | MXViewConfig.InsetInfo containerInsetInfo = new MXViewConfig.InsetInfo(); 67 | float topInset = 0; 68 | float leftInset = 0; 69 | int rightViewCount = 0; 70 | int bottomViewCount = 0; 71 | float rightInset = Float.MAX_VALUE; 72 | float bottomInset = Float.MAX_VALUE; 73 | 74 | for (String name : leftIgnoreNames) { 75 | View view = config.overlayView(name); 76 | if (view.getVisibility() == View.VISIBLE) { 77 | leftInset = Math.max(view.getX(), leftInset); 78 | } 79 | } 80 | 81 | for (String name : topIgnoreNames) { 82 | View view = config.overlayView(name); 83 | if (view.getVisibility() == View.VISIBLE) { 84 | topInset = Math.max(view.getY(), topInset); 85 | } 86 | } 87 | 88 | for (String name : rightIgnoreNames) { 89 | View view = config.overlayView(name); 90 | if (view.getVisibility() == View.VISIBLE) { 91 | rightInset = Math.min(view.getX(), rightInset); 92 | rightViewCount++; 93 | } 94 | } 95 | 96 | for (String name : bottomIgnoreNames) { 97 | View view = config.overlayView(name); 98 | if (view.getVisibility() == View.VISIBLE) { 99 | bottomInset = Math.min(view.getY(), bottomInset); 100 | bottomViewCount++; 101 | } 102 | } 103 | rightInset = rightViewCount == 0 ? 0 : CommonUtils.getActivityWidth() - rightInset; 104 | bottomInset = bottomViewCount == 0 ? 0 : CommonUtils.getActivityContentViewHeight() - bottomInset; 105 | 106 | containerInsetInfo.left = CommonUtils.px2dip(leftInset); 107 | containerInsetInfo.top = CommonUtils.px2dip(topInset); 108 | containerInsetInfo.right = CommonUtils.px2dip(rightInset); 109 | containerInsetInfo.bottom = CommonUtils.px2dip(bottomInset); 110 | return containerInsetInfo; 111 | } 112 | } 113 | 114 | -------------------------------------------------------------------------------- /android/src/main/java/com/yuewen/mix_stack/model/MXViewConfig.java: -------------------------------------------------------------------------------- 1 | package com.yuewen.mix_stack.model; 2 | 3 | import android.os.Build; 4 | 5 | import java.util.HashMap; 6 | import java.util.Map; 7 | import java.util.Objects; 8 | 9 | /******************************************************* 10 | * 11 | * Created by julis.wang on 2020/08/14 11:05 12 | * 13 | * Description : 14 | * History : 15 | * 16 | *******************************************************/ 17 | 18 | public class MXViewConfig { 19 | private boolean hidden; 20 | private float alpha; 21 | private boolean needsAnimation; 22 | 23 | public MXViewConfig(boolean hidden, float alpha, boolean needsAnimation) { 24 | this.hidden = hidden; 25 | this.alpha = alpha; 26 | this.needsAnimation = needsAnimation; 27 | } 28 | 29 | public MXViewConfig(boolean hidden, float alpha) { 30 | this(hidden, alpha, false); 31 | } 32 | 33 | public boolean isHidden() { 34 | return hidden; 35 | } 36 | 37 | public float getAlpha() { 38 | return alpha; 39 | } 40 | 41 | public boolean isNeedsAnimation() { 42 | return needsAnimation; 43 | } 44 | 45 | public static class InsetInfo { 46 | public float left; 47 | public float top; 48 | public float right; 49 | public float bottom; 50 | 51 | public Map toMap() { 52 | Map map = new HashMap<>(); 53 | map.put("left", left); 54 | map.put("right", right); 55 | map.put("top", top); 56 | map.put("bottom", bottom); 57 | return map; 58 | } 59 | 60 | @Override 61 | public boolean equals(Object o) { 62 | if (this == o) return true; 63 | if (o == null || getClass() != o.getClass()) return false; 64 | InsetInfo insetInfo = (InsetInfo) o; 65 | return Float.compare(insetInfo.left, left) == 0 66 | && Float.compare(insetInfo.top, top) == 0 67 | && Float.compare(insetInfo.right, right) == 0 68 | && Float.compare(insetInfo.bottom, bottom) == 0; 69 | } 70 | 71 | @Override 72 | public int hashCode() { 73 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { 74 | return Objects.hash(left, top, right, bottom); 75 | } 76 | return 0; 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /android/src/main/java/com/yuewen/mix_stack/utils/CommonUtils.java: -------------------------------------------------------------------------------- 1 | package com.yuewen.mix_stack.utils; 2 | 3 | import android.app.Activity; 4 | import android.graphics.Bitmap; 5 | import android.graphics.Rect; 6 | import android.view.View; 7 | 8 | import com.yuewen.mix_stack.core.MXStackService; 9 | 10 | /******************************************************* 11 | * 12 | * Created by julis.wang on 2020/09/22 15:16 13 | * 14 | * Description : 15 | * History : 16 | * 17 | *******************************************************/ 18 | 19 | public class CommonUtils { 20 | public static float px2dip(float pxValue) { 21 | final float scale = MXStackService.getInstance().getApplication() 22 | .getResources().getDisplayMetrics().density; 23 | return pxValue / scale + 0.5f; 24 | } 25 | 26 | public static float getActivityWidth() { 27 | Activity activity = MXStackService.getCurrentActivity(); 28 | Rect outRect = new Rect(); 29 | activity.getWindow().getDecorView().getWindowVisibleDisplayFrame(outRect); 30 | return outRect.width(); 31 | } 32 | 33 | public static float getActivityContentViewHeight() { 34 | Activity activity = MXStackService.getCurrentActivity(); 35 | View decorView = activity.getWindow().getDecorView(); 36 | View contentView = decorView.findViewById(android.R.id.content); 37 | return contentView.getHeight(); 38 | } 39 | 40 | public static Bitmap screenShot(Activity activity) { 41 | View decorView = activity.getWindow().getDecorView(); 42 | View contentView = decorView.findViewById(android.R.id.content); 43 | contentView.setDrawingCacheEnabled(true); 44 | contentView.buildDrawingCache(); 45 | 46 | Rect outRect = new Rect(); 47 | contentView.getWindowVisibleDisplayFrame(outRect); 48 | 49 | Bitmap bitmap = contentView.getDrawingCache(); 50 | Bitmap newBitmap = Bitmap.createBitmap(bitmap, 51 | 0, 0, bitmap.getWidth(), bitmap.getHeight()); 52 | 53 | contentView.setDrawingCacheEnabled(false); 54 | contentView.destroyDrawingCache(); 55 | 56 | return newBitmap; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /android/src/main/java/com/yuewen/mix_stack/utils/ReflectionUtil.java: -------------------------------------------------------------------------------- 1 | package com.yuewen.mix_stack.utils; 2 | 3 | import java.lang.reflect.Field; 4 | import java.lang.reflect.Method; 5 | 6 | /******************************************************* 7 | * 8 | * Created by julis.wang on 2020/09/01 16:05 9 | * 10 | * Description : 11 | * History : 12 | * 13 | *******************************************************/ 14 | 15 | public final class ReflectionUtil { 16 | 17 | public static Field getField(Class clazz, String fieldName) { 18 | try { 19 | Field f = clazz.getDeclaredField(fieldName); 20 | f.setAccessible(true); 21 | return f; 22 | } catch (NoSuchFieldException var3) { 23 | return null; 24 | } 25 | } 26 | 27 | public static Object getValue(Field field, Object obj) { 28 | try { 29 | return field.get(obj); 30 | } catch (IllegalAccessException var3) { 31 | return null; 32 | } 33 | } 34 | 35 | public static void setValue(Field field, Object obj, Object value) { 36 | try { 37 | field.set(obj, value); 38 | } catch (IllegalAccessException var4) { 39 | } 40 | 41 | } 42 | 43 | public static Method getMethod(Class clazz, String methodName) { 44 | Method[] methods = clazz.getMethods(); 45 | Method[] var3 = methods; 46 | int var4 = methods.length; 47 | 48 | for (int var5 = 0; var5 < var4; ++var5) { 49 | Method method = var3[var5]; 50 | if (method.getName().equals(methodName)) { 51 | method.setAccessible(true); 52 | return method; 53 | } 54 | } 55 | 56 | return null; 57 | } 58 | 59 | public static void invokeMethod(Object object, Method method, Object... args) { 60 | try { 61 | if (method == null) { 62 | return; 63 | } 64 | 65 | method.invoke(object, args); 66 | } catch (Exception var4) { 67 | } 68 | 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /android/src/main/java/com/yuewen/mix_stack/utils/RomUtils.java: -------------------------------------------------------------------------------- 1 | package com.yuewen.mix_stack.utils; 2 | 3 | import android.os.Build; 4 | import android.os.Environment; 5 | import android.text.TextUtils; 6 | 7 | import java.io.File; 8 | import java.io.FileInputStream; 9 | import java.util.Properties; 10 | 11 | /******************************************************* 12 | * 13 | * Created by julis.wang on 2020/11/05 14:11 14 | * 15 | * Description : 16 | * History : 17 | * 18 | *******************************************************/ 19 | 20 | public class RomUtils { 21 | static class AvailableRomType { 22 | public static final int MIUI = 1; 23 | public static final int FLYME = 2; 24 | public static final int ANDROID_NATIVE = 3; 25 | public static final int NA = 4; 26 | } 27 | 28 | public static int getLightStatusBarAvailableRomType() { 29 | if (isFlymeV4OrAbove()) { 30 | return AvailableRomType.FLYME; 31 | } 32 | if (isMiUIV7OrAbove()) { 33 | return AvailableRomType.ANDROID_NATIVE; 34 | } 35 | 36 | if (isMiUIV6OrAbove()) { 37 | return AvailableRomType.MIUI; 38 | } 39 | 40 | if (isAndroidMOrAbove()) { 41 | return AvailableRomType.ANDROID_NATIVE; 42 | } 43 | 44 | return AvailableRomType.NA; 45 | } 46 | 47 | private static boolean isFlymeV4OrAbove() { 48 | String displayId = Build.DISPLAY; 49 | if (!TextUtils.isEmpty(displayId) && displayId.contains("Flyme")) { 50 | String[] displayIdArray = displayId.split(" "); 51 | for (String temp : displayIdArray) { 52 | //版本号4以上,形如4.x. 53 | if (temp.matches("^[4-9]\\.(\\d+\\.)+\\S*")) { 54 | return true; 55 | } 56 | } 57 | } 58 | return false; 59 | } 60 | 61 | private static boolean isAndroidMOrAbove() { 62 | return Build.VERSION.SDK_INT >= Build.VERSION_CODES.M; 63 | } 64 | 65 | private static final String KEY_MIUI_VERSION_CODE = "ro.miui.ui.version.code"; 66 | 67 | private static boolean isMiUIV6OrAbove() { 68 | try { 69 | final Properties properties = new Properties(); 70 | properties.load(new FileInputStream(new File(Environment.getRootDirectory(), "build.prop"))); 71 | String uiCode = properties.getProperty(KEY_MIUI_VERSION_CODE, null); 72 | if (uiCode != null) { 73 | int code = Integer.parseInt(uiCode); 74 | return code >= 4; 75 | } else { 76 | return false; 77 | } 78 | 79 | } catch (final Exception e) { 80 | return false; 81 | } 82 | 83 | } 84 | 85 | static boolean isMiUIV7OrAbove() { 86 | try { 87 | final Properties properties = new Properties(); 88 | properties.load(new FileInputStream(new File(Environment.getRootDirectory(), "build.prop"))); 89 | String uiCode = properties.getProperty(KEY_MIUI_VERSION_CODE, null); 90 | if (uiCode != null) { 91 | int code = Integer.parseInt(uiCode); 92 | return code >= 5; 93 | } else { 94 | return false; 95 | } 96 | 97 | } catch (final Exception e) { 98 | return false; 99 | } 100 | 101 | } 102 | 103 | } 104 | -------------------------------------------------------------------------------- /android/src/main/res/layout/flutter_dialog.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 11 | 12 | 16 | 17 | -------------------------------------------------------------------------------- /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 | # Symbolication related 37 | app.*.symbols 38 | 39 | # Obfuscation related 40 | app.*.map.json 41 | 42 | # Exceptions to above rules. 43 | !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages 44 | -------------------------------------------------------------------------------- /example/README.md: -------------------------------------------------------------------------------- 1 | # MixStack 混合栈示例 2 | 3 | 本示例提供了 mix_stack 的全部能力展示,请运行起来体验体验,一些常见用法及具体实现机制也可以随意鼓捣鼓捣探索原理。 -------------------------------------------------------------------------------- /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 from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" 26 | 27 | android { 28 | compileSdk 31 29 | 30 | lintOptions { 31 | disable 'InvalidPackage' 32 | } 33 | 34 | defaultConfig { 35 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). 36 | applicationId "com.yuewen.mix_stack_example" 37 | minSdkVersion 21 38 | targetSdkVersion 31 39 | versionCode flutterVersionCode.toInteger() 40 | versionName flutterVersionName 41 | } 42 | 43 | buildTypes { 44 | release { 45 | // TODO: Add your own signing config for the release build. 46 | // Signing with the debug keys for now, so `flutter run --release` works. 47 | signingConfig signingConfigs.debug 48 | } 49 | } 50 | } 51 | 52 | flutter { 53 | source '../..' 54 | } 55 | dependencies { 56 | implementation 'androidx.appcompat:appcompat:1.2.0' 57 | debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.4' 58 | } 59 | -------------------------------------------------------------------------------- /example/android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 4 | 9 | 14 | 22 | 26 | 29 | 34 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 52 | 54 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /example/android/app/src/main/java/com/yuewen/mix_stack_example/FlutterMainActivity.java: -------------------------------------------------------------------------------- 1 | package com.yuewen.mix_stack_example; 2 | 3 | import android.content.Intent; 4 | import android.os.Bundle; 5 | import android.text.TextUtils; 6 | 7 | import androidx.annotation.Nullable; 8 | 9 | import com.yuewen.mix_stack.component.MXFlutterActivity; 10 | import com.yuewen.mix_stack.core.MXStackService; 11 | 12 | import io.flutter.embedding.engine.FlutterEngine; 13 | import io.flutter.plugin.common.MethodChannel; 14 | 15 | /******************************************************* 16 | * 17 | * Created by julis.wang on 2021/01/20 14:46 18 | * 19 | * Description : 20 | * 21 | * History : 22 | * 23 | *******************************************************/ 24 | 25 | public class FlutterMainActivity extends MXFlutterActivity { 26 | String route; //default 27 | 28 | @Override 29 | protected void onCreate(@Nullable Bundle savedInstanceState) { 30 | super.onCreate(savedInstanceState); 31 | Intent intent = getIntent(); 32 | if (intent != null) { 33 | route = intent.getStringExtra("route"); 34 | } 35 | 36 | if (TextUtils.equals(route, "/clear_stack")) { 37 | initEventChannel(); 38 | } 39 | 40 | if (TextUtils.isEmpty(route)) { 41 | route = "/test_main"; 42 | } 43 | } 44 | 45 | @Override 46 | public String rootRoute() { 47 | return route; 48 | } 49 | 50 | // ======== Test clear stack start ======== 51 | private MethodChannel channel; 52 | 53 | private void initEventChannel() { 54 | if (channel != null) { 55 | return; 56 | } 57 | FlutterEngine flutterEngine = MXStackService.getInstance().getFlutterEngine(); 58 | channel = new MethodChannel( 59 | flutterEngine.getDartExecutor().getBinaryMessenger(), "eventChannel"); 60 | channel.setMethodCallHandler((call, result) -> { 61 | String method = call.method; 62 | if ("go_to_tab".equals(method)) { 63 | Intent intent = new Intent(FlutterMainActivity.this, MultipleTabActivity.class); 64 | intent.putExtra("action", "go_to_tab"); 65 | intent.putExtra("route", route); 66 | startActivity(intent); 67 | } 68 | }); 69 | } 70 | 71 | @Override 72 | protected void onDestroy() { 73 | super.onDestroy(); 74 | if (channel != null) { 75 | channel.setMethodCallHandler(null); 76 | channel = null; 77 | } 78 | } 79 | // ======== Test clear stack End ======== 80 | } 81 | -------------------------------------------------------------------------------- /example/android/app/src/main/java/com/yuewen/mix_stack_example/MoreFunctionActivity.java: -------------------------------------------------------------------------------- 1 | package com.yuewen.mix_stack_example; 2 | 3 | import android.content.Intent; 4 | import android.os.Bundle; 5 | import android.text.TextUtils; 6 | import android.view.View; 7 | import android.view.ViewGroup; 8 | import android.widget.LinearLayout; 9 | 10 | import androidx.annotation.Nullable; 11 | import androidx.appcompat.app.AppCompatActivity; 12 | import androidx.fragment.app.FragmentTransaction; 13 | 14 | import com.yuewen.mix_stack.component.MXFlutterFragment; 15 | import com.yuewen.mix_stack.core.MXPageManager; 16 | import com.yuewen.mix_stack.interfaces.IMXPageManager; 17 | import com.yuewen.mix_stack.model.AreaInsetsConfig; 18 | import com.yuewen.mix_stack.model.MXViewConfig; 19 | 20 | import java.util.ArrayList; 21 | import java.util.List; 22 | import java.util.Map; 23 | 24 | /******************************************************* 25 | * 26 | * Created by julis.wang on 2021/01/21 10:37 27 | * 28 | * Description : 29 | * 30 | * History : 31 | * 32 | *******************************************************/ 33 | 34 | public class MoreFunctionActivity extends AppCompatActivity implements IMXPageManager { 35 | private String route; 36 | private LinearLayout llTabBar; 37 | 38 | @Override 39 | protected void onCreate(@Nullable Bundle savedInstanceState) { 40 | super.onCreate(savedInstanceState); 41 | setContentView(R.layout.activity_more_function); 42 | Intent intent = getIntent(); 43 | if (intent != null) { 44 | route = intent.getStringExtra("route"); 45 | } 46 | initView(); 47 | showFlutterFragment(); 48 | } 49 | 50 | private void initView() { 51 | llTabBar = findViewById(R.id.ll_tab_container); 52 | ViewGroup.LayoutParams layoutParams = llTabBar.getLayoutParams(); 53 | if (TextUtils.equals(route, "/popup_window")) { 54 | findViewById(R.id.ll_container).setVisibility(View.GONE); 55 | } 56 | findViewById(R.id.btn_add).setOnClickListener(v -> { 57 | layoutParams.height += 20; 58 | llTabBar.setLayoutParams(layoutParams); 59 | 60 | }); 61 | findViewById(R.id.btn_sub).setOnClickListener(v -> { 62 | layoutParams.height -= 20; 63 | if (layoutParams.height < 0) { 64 | return; 65 | } 66 | llTabBar.setLayoutParams(layoutParams); 67 | }); 68 | 69 | findViewById(R.id.btn_show).setOnClickListener(v -> llTabBar.setVisibility(View.VISIBLE)); 70 | findViewById(R.id.btn_hide).setOnClickListener(v -> llTabBar.setVisibility(View.INVISIBLE)); 71 | } 72 | 73 | private void showFlutterFragment() { 74 | MXFlutterFragment fg = new MXFlutterFragment(); 75 | FragmentTransaction transaction = getSupportFragmentManager().beginTransaction(); 76 | transaction.add(R.id.fl_container, fg, fg.getClass().getName()); 77 | transaction.commit(); 78 | Bundle bundle = new Bundle(); 79 | bundle.putString(MXFlutterFragment.ROUTE, route); 80 | fg.setArguments(bundle); 81 | pageManager.setCurrentShowPage(fg); 82 | 83 | } 84 | 85 | MXPageManager pageManager = new MXPageManager() { 86 | @Override 87 | public List overlayNames() { 88 | List overlayNames = new ArrayList<>(); 89 | overlayNames.add("tabBar"); 90 | return overlayNames; 91 | } 92 | 93 | @Override 94 | public View overlayView(String viewName) { 95 | if ("tabBar".equals(viewName)) { 96 | return llTabBar; 97 | } else { 98 | return null; 99 | } 100 | } 101 | 102 | @Override 103 | public AreaInsetsConfig ignoreAreaInsetsConfig() { 104 | AreaInsetsConfig config = new AreaInsetsConfig(); 105 | config.addBottomIgnoreNames("tabBar"); 106 | return config; 107 | } 108 | 109 | @Override 110 | public void configOverlay(Map overlayNamesConfig) { 111 | super.configOverlay(overlayNamesConfig); 112 | 113 | } 114 | }; 115 | 116 | @Override 117 | public MXPageManager getPageManager() { 118 | return pageManager; 119 | } 120 | 121 | } 122 | -------------------------------------------------------------------------------- /example/android/app/src/main/java/com/yuewen/mix_stack_example/NativeActivity.java: -------------------------------------------------------------------------------- 1 | package com.yuewen.mix_stack_example; 2 | 3 | import android.os.Bundle; 4 | 5 | import androidx.annotation.Nullable; 6 | import androidx.appcompat.app.AppCompatActivity; 7 | import androidx.fragment.app.Fragment; 8 | import androidx.fragment.app.FragmentTransaction; 9 | 10 | /******************************************************* 11 | * 12 | * Created by julis.wang on 2021/01/20 16:16 13 | * 14 | * Description : 15 | * 16 | * History : 17 | * 18 | *******************************************************/ 19 | 20 | public class NativeActivity extends AppCompatActivity { 21 | 22 | @Override 23 | protected void onCreate(@Nullable Bundle savedInstanceState) { 24 | super.onCreate(savedInstanceState); 25 | setContentView(R.layout.activity_native); 26 | initView(); 27 | 28 | } 29 | 30 | private void initView() { 31 | Fragment fg = new NativeFragment(); 32 | FragmentTransaction transaction = getSupportFragmentManager().beginTransaction(); 33 | transaction.add(R.id.fl_container, fg, fg.getClass().getName()); 34 | transaction.commit(); 35 | } 36 | } 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /example/android/app/src/main/java/com/yuewen/mix_stack_example/NativeFragment.java: -------------------------------------------------------------------------------- 1 | package com.yuewen.mix_stack_example; 2 | 3 | import android.annotation.SuppressLint; 4 | import android.app.Activity; 5 | import android.content.Intent; 6 | import android.os.Bundle; 7 | import android.view.LayoutInflater; 8 | import android.view.View; 9 | import android.view.ViewGroup; 10 | import android.widget.Button; 11 | import android.widget.TextView; 12 | 13 | import androidx.annotation.Nullable; 14 | import androidx.fragment.app.Fragment; 15 | 16 | import java.util.Arrays; 17 | import java.util.List; 18 | import java.util.Random; 19 | 20 | /******************************************************* 21 | * 22 | * Created by julis.wang on 2020/08/10 18:19 23 | * 24 | * Description : 25 | * History : 26 | * 27 | *******************************************************/ 28 | 29 | public class NativeFragment extends Fragment { 30 | 31 | private String testRoute; 32 | @Override 33 | public void onCreate(@Nullable Bundle savedInstanceState) { 34 | super.onCreate(savedInstanceState); 35 | Bundle bundle = getArguments(); 36 | if (bundle != null) { 37 | testRoute = bundle.getString("route"); 38 | } 39 | } 40 | 41 | @Override 42 | public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { 43 | View view = inflater.inflate(R.layout.layout_native_list, container, false); 44 | initView(view); 45 | return view; 46 | } 47 | 48 | @SuppressLint("SetTextI18n") 49 | private void initView(View view) { 50 | Button btnGoFlutter = view.findViewById(R.id.btn_go_flutter); 51 | Button btnGoNative = view.findViewById(R.id.btn_go_native); 52 | Button btnGoBack = view.findViewById(R.id.btn_go_back); 53 | TextView textView = view.findViewById(R.id.tv_hash_code); 54 | int randomIndex = new Random().nextInt(primariesColor.size()); 55 | view.findViewById(R.id.ll_container) 56 | .setBackgroundColor(getResources() 57 | .getColor(primariesColor.get(randomIndex))); 58 | Activity activity = getActivity(); 59 | textView.setText("hashCode:" + this.hashCode()); 60 | btnGoFlutter.setOnClickListener(v -> { 61 | Intent intent = new Intent(activity, FlutterMainActivity.class); 62 | if ("/clear_stack".equals(testRoute)) { 63 | intent.putExtra("route", testRoute); 64 | } else { 65 | intent.putExtra("route", "simple_flutter_page"); 66 | } 67 | startActivity(intent); 68 | }); 69 | btnGoNative.setOnClickListener(v -> { 70 | Intent intent = new Intent(activity, NativeActivity.class); 71 | startActivity(intent); 72 | }); 73 | btnGoBack.setOnClickListener(v -> activity.finish()); 74 | } 75 | 76 | private static final List primariesColor = Arrays.asList( 77 | R.color.red, 78 | R.color.pink, 79 | R.color.purple, 80 | R.color.deeppink, 81 | R.color.indigo, 82 | R.color.blue, 83 | R.color.mediumorchid, 84 | R.color.blueviolet, 85 | R.color.teal, 86 | R.color.green, 87 | R.color.lightgreen, 88 | R.color.aqua, 89 | R.color.gray, 90 | R.color.black, 91 | R.color.orange, 92 | R.color.orangered, 93 | R.color.brown); 94 | 95 | 96 | } 97 | -------------------------------------------------------------------------------- /example/android/app/src/main/java/com/yuewen/mix_stack_example/componet/MainApplication.java: -------------------------------------------------------------------------------- 1 | package com.yuewen.mix_stack_example.componet; 2 | 3 | 4 | import com.yuewen.mix_stack.core.MXStackService; 5 | 6 | import io.flutter.app.FlutterApplication; 7 | 8 | 9 | /******************************************************* 10 | * 11 | * Created by julis.wang on 2020/08/11 13:40 12 | * 13 | * Description : 14 | * History : 15 | * 16 | *******************************************************/ 17 | 18 | public class MainApplication extends FlutterApplication { 19 | @Override 20 | public void onCreate() { 21 | super.onCreate(); 22 | MXStackService.init(this); 23 | 24 | //for example 25 | MXStackService.getInstance().getFlutterEngine().getPlugins().add(new NativeRouterPlugin(this)); 26 | } 27 | } 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /example/android/app/src/main/java/com/yuewen/mix_stack_example/componet/NativeRouterPlugin.java: -------------------------------------------------------------------------------- 1 | package com.yuewen.mix_stack_example.componet; 2 | 3 | import android.app.Application; 4 | import android.content.Intent; 5 | import android.text.TextUtils; 6 | import android.util.Log; 7 | 8 | import androidx.annotation.NonNull; 9 | 10 | import com.yuewen.mix_stack_example.FlutterMainActivity; 11 | import com.yuewen.mix_stack_example.MoreFunctionActivity; 12 | import com.yuewen.mix_stack_example.MultipleTabActivity; 13 | import com.yuewen.mix_stack_example.NativeActivity; 14 | 15 | import java.util.Map; 16 | 17 | import io.flutter.embedding.engine.plugins.FlutterPlugin; 18 | import io.flutter.plugin.common.MethodCall; 19 | import io.flutter.plugin.common.MethodChannel; 20 | 21 | /******************************************************* 22 | * 23 | * Created by julis.wang on 2021/01/20 14:11 24 | * 25 | * Description : For example, in your project may use own router. 26 | * 27 | * History : 28 | * 29 | *******************************************************/ 30 | 31 | public class NativeRouterPlugin implements FlutterPlugin, MethodChannel.MethodCallHandler { 32 | private Application context; 33 | 34 | public NativeRouterPlugin(Application context) { 35 | this.context = context; 36 | } 37 | 38 | @Override 39 | public void onAttachedToEngine(@NonNull FlutterPluginBinding binding) { 40 | MethodChannel channel = new MethodChannel(binding.getBinaryMessenger(), "goto_native_channel"); 41 | channel.setMethodCallHandler(this); 42 | } 43 | 44 | @Override 45 | public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) { 46 | 47 | } 48 | 49 | @Override 50 | public void onMethodCall(@NonNull MethodCall call, @NonNull MethodChannel.Result result) { 51 | String method = call.method; 52 | if ("go".equals(method)) { 53 | Map arguments = (Map) call.arguments; 54 | String route = (String) arguments.get("route"); 55 | jumpNativePage(route); 56 | } 57 | } 58 | 59 | private void jumpNativePage(String route) { 60 | if (TextUtils.isEmpty(route)) { 61 | return; 62 | } 63 | 64 | Intent intent; 65 | switch (route) { 66 | case "/simple_flutter_page": 67 | intent = new Intent(context, FlutterMainActivity.class); 68 | break; 69 | case "/native": 70 | intent = new Intent(context, NativeActivity.class); 71 | break; 72 | case "/tab": 73 | case "/clear_stack": 74 | intent = new Intent(context, MultipleTabActivity.class); 75 | break; 76 | case "/popup_window": 77 | case "/area_inset": 78 | intent = new Intent(context, MoreFunctionActivity.class); 79 | break; 80 | default: 81 | Log.d("MixStack", "Not found route:" + route); 82 | return; 83 | } 84 | 85 | intent.putExtra("route", route); 86 | intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 87 | context.startActivity(intent); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /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/drawable/logo_splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yuewen/mix_stack/1edd822d25a06e9578c45f677f1147577cf8b131/example/android/app/src/main/res/drawable/logo_splash.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 12 | 13 | 23 | 24 | 30 | 31 | 40 | 41 | 50 | 51 | 52 | 61 | 62 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/layout/activity_native.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/layout/activity_tab.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 12 | 13 | 23 | 24 | 30 | 31 | 40 | 41 | 50 | 51 | 52 | 61 | 62 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/layout/layout_native_list.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 13 | 14 | 23 | 24 | 25 | 35 | 36 | 43 | 44 | 52 | 53 | 54 | 55 | 61 | 62 |