├── app
├── .gitignore
├── src
│ ├── main
│ │ ├── res
│ │ │ ├── values
│ │ │ │ ├── strings.xml
│ │ │ │ ├── colors.xml
│ │ │ │ └── styles.xml
│ │ │ ├── mipmap-hdpi
│ │ │ │ ├── ic_launcher.png
│ │ │ │ └── ic_launcher_round.png
│ │ │ ├── mipmap-mdpi
│ │ │ │ ├── ic_launcher.png
│ │ │ │ └── ic_launcher_round.png
│ │ │ ├── mipmap-xhdpi
│ │ │ │ ├── ic_launcher.png
│ │ │ │ └── ic_launcher_round.png
│ │ │ ├── mipmap-xxhdpi
│ │ │ │ ├── ic_launcher.png
│ │ │ │ └── ic_launcher_round.png
│ │ │ ├── mipmap-xxxhdpi
│ │ │ │ ├── ic_launcher.png
│ │ │ │ └── ic_launcher_round.png
│ │ │ ├── mipmap-anydpi-v26
│ │ │ │ ├── ic_launcher.xml
│ │ │ │ └── ic_launcher_round.xml
│ │ │ ├── layout
│ │ │ │ ├── activity_news_list.xml
│ │ │ │ ├── fragment_msg.xml
│ │ │ │ ├── fragment_test.xml
│ │ │ │ ├── activity_news_detail.xml
│ │ │ │ └── activity_main.xml
│ │ │ ├── drawable-v24
│ │ │ │ └── ic_launcher_foreground.xml
│ │ │ └── drawable
│ │ │ │ └── ic_launcher_background.xml
│ │ ├── java
│ │ │ └── com
│ │ │ │ └── kaixuan
│ │ │ │ └── windowtree
│ │ │ │ ├── WindowTag.kt
│ │ │ │ ├── KotlinCommon.kt
│ │ │ │ ├── MyApp.kt
│ │ │ │ ├── activity
│ │ │ │ ├── EmptyActivity.kt
│ │ │ │ ├── BaseActivity.kt
│ │ │ │ ├── NewsDetailActivity.kt
│ │ │ │ └── NewsListActivity.kt
│ │ │ │ ├── fragment
│ │ │ │ ├── dynamic
│ │ │ │ │ └── GoodFriendDynamicFragment.kt
│ │ │ │ ├── DynamicFragment.kt
│ │ │ │ ├── VipFragment.kt
│ │ │ │ ├── ContactsFragment.kt
│ │ │ │ └── MainFragment.kt
│ │ │ │ └── MainActivity.kt
│ │ └── AndroidManifest.xml
│ ├── test
│ │ └── java
│ │ │ └── com
│ │ │ └── kaixuan
│ │ │ └── windowtree
│ │ │ └── ExampleUnitTest.kt
│ └── androidTest
│ │ └── java
│ │ └── com
│ │ └── kaixuan
│ │ └── windowtree
│ │ └── ExampleInstrumentedTest.kt
├── proguard-rules.pro
└── build.gradle
├── windowtree_annotation
├── .gitignore
├── src
│ └── main
│ │ └── java
│ │ └── com
│ │ └── kaixuan
│ │ └── windowtree_annotation
│ │ ├── MyClass.java
│ │ ├── model
│ │ ├── WindowData.java
│ │ └── WindowMeta.java
│ │ ├── annotation
│ │ ├── WindowTypeAnnotation.java
│ │ └── Window.java
│ │ └── enums
│ │ └── WindowType.java
└── build.gradle
├── windowtree_compiler
├── .gitignore
├── build.gradle
└── src
│ └── main
│ └── java
│ └── com
│ └── kaixuan
│ └── compiler
│ └── WindowProcessor.java
├── windowtree_library
├── .gitignore
├── src
│ ├── main
│ │ ├── res
│ │ │ └── values
│ │ │ │ └── strings.xml
│ │ ├── AndroidManifest.xml
│ │ └── java
│ │ │ └── com
│ │ │ └── kaixuan
│ │ │ └── windowtreelibrary
│ │ │ ├── model
│ │ │ └── UnReadCountEvent.kt
│ │ │ ├── template
│ │ │ ├── IMain.java
│ │ │ ├── IJumpAdapter.kt
│ │ │ ├── IWindowTreeLoad.java
│ │ │ └── ILogger.java
│ │ │ ├── util
│ │ │ ├── Consts.kt
│ │ │ ├── WindowTreeUtil.kt
│ │ │ ├── TextUtils.java
│ │ │ ├── DefaultLogger.java
│ │ │ └── ClassUtils.java
│ │ │ ├── thread
│ │ │ ├── DefaultThreadFactory.java
│ │ │ └── DefaultPoolExecutor.java
│ │ │ ├── adapter
│ │ │ └── DefaultJumpAdapter.kt
│ │ │ ├── WindowTree.kt
│ │ │ └── WindowInfo.kt
│ ├── test
│ │ └── java
│ │ │ └── com
│ │ │ └── kaixuan
│ │ │ └── windowtreelibrary
│ │ │ └── ExampleUnitTest.java
│ └── androidTest
│ │ └── java
│ │ └── com
│ │ └── kaixuan
│ │ └── windowtreelibrary
│ │ └── ExampleInstrumentedTest.java
├── proguard-rules.pro
└── build.gradle
├── settings.gradle
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── .idea
├── codeStyles
│ ├── codeStyleConfig.xml
│ └── Project.xml
├── vcs.xml
├── runConfigurations.xml
├── gradle.xml
└── misc.xml
├── .gitignore
├── gradle.properties
├── gradlew.bat
├── README.md
└── gradlew
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/windowtree_annotation/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/windowtree_compiler/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/windowtree_library/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app', ':windowtree_library', ':windowtree_compiler', ':windowtree_annotation'
2 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | WindowTree
3 |
4 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KaiXuan666/WindowTree/HEAD/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/windowtree_library/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | WindowTreeLibrary
3 |
4 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KaiXuan666/WindowTree/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KaiXuan666/WindowTree/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KaiXuan666/WindowTree/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KaiXuan666/WindowTree/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KaiXuan666/WindowTree/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KaiXuan666/WindowTree/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KaiXuan666/WindowTree/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KaiXuan666/WindowTree/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KaiXuan666/WindowTree/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KaiXuan666/WindowTree/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/windowtree_annotation/src/main/java/com/kaixuan/windowtree_annotation/MyClass.java:
--------------------------------------------------------------------------------
1 | package com.kaixuan.windowtree_annotation;
2 |
3 | public class MyClass {
4 | }
5 |
--------------------------------------------------------------------------------
/windowtree_library/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
--------------------------------------------------------------------------------
/windowtree_annotation/src/main/java/com/kaixuan/windowtree_annotation/model/WindowData.java:
--------------------------------------------------------------------------------
1 | package com.kaixuan.windowtree_annotation.model;
2 |
3 | public class WindowData {
4 | }
5 |
--------------------------------------------------------------------------------
/.idea/codeStyles/codeStyleConfig.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/java/com/kaixuan/windowtree/WindowTag.kt:
--------------------------------------------------------------------------------
1 | package com.kaixuan.windowtree
2 |
3 |
4 | class WindowTag(
5 | var unReadMsgCount : Int = 0,
6 | var pageAuthority : Long = 0
7 | ){
8 |
9 | }
10 |
--------------------------------------------------------------------------------
/windowtree_annotation/src/main/java/com/kaixuan/windowtree_annotation/annotation/WindowTypeAnnotation.java:
--------------------------------------------------------------------------------
1 | package com.kaixuan.windowtree_annotation.annotation;
2 |
3 |
4 | public @interface WindowTypeAnnotation {
5 | }
6 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | /local.properties
4 | /.idea/caches/build_file_checksums.ser
5 | /.idea/libraries
6 | /.idea/modules.xml
7 | /.idea/workspace.xml
8 | .DS_Store
9 | /build
10 | /captures
11 | .externalNativeBuild
12 |
--------------------------------------------------------------------------------
/app/src/main/java/com/kaixuan/windowtree/KotlinCommon.kt:
--------------------------------------------------------------------------------
1 | package com.kaixuan.windowtree
2 |
3 | import android.widget.Toast
4 |
5 |
6 | fun showToast(msg : String){
7 | Toast.makeText(MyApp.instance,msg,Toast.LENGTH_SHORT).show()
8 | }
9 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | distributionBase=GRADLE_USER_HOME
2 | distributionPath=wrapper/dists
3 | distributionUrl=https\://services.gradle.org/distributions/gradle-4.6-all.zip
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 |
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #008577
4 | #00574B
5 | #D81B60
6 |
7 |
--------------------------------------------------------------------------------
/windowtree_library/src/main/java/com/kaixuan/windowtreelibrary/model/UnReadCountEvent.kt:
--------------------------------------------------------------------------------
1 | package com.kaixuan.windowtreelibrary.model
2 |
3 | import com.kaixuan.windowtreelibrary.WindowInfo
4 |
5 | data class UnReadCountEvent(val fromWindowInfo: WindowInfo<*>,val change : Int)
--------------------------------------------------------------------------------
/windowtree_library/src/main/java/com/kaixuan/windowtreelibrary/template/IMain.java:
--------------------------------------------------------------------------------
1 | package com.kaixuan.windowtreelibrary.template;
2 |
3 | import java.util.Map;
4 |
5 | public interface IMain {
6 |
7 | public Map> getAllGeneratedFile();
8 | }
9 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/windowtree_library/src/main/java/com/kaixuan/windowtreelibrary/template/IJumpAdapter.kt:
--------------------------------------------------------------------------------
1 | package com.kaixuan.windowtreelibrary.template
2 |
3 | import android.content.Context
4 | import com.kaixuan.windowtreelibrary.WindowInfo
5 |
6 | interface IJumpAdapter{
7 |
8 | fun jump(formContext: Context, to: WindowInfo<*>) : Boolean
9 |
10 | }
--------------------------------------------------------------------------------
/windowtree_library/src/main/java/com/kaixuan/windowtreelibrary/template/IWindowTreeLoad.java:
--------------------------------------------------------------------------------
1 | package com.kaixuan.windowtreelibrary.template;
2 |
3 | import com.kaixuan.windowtreelibrary.WindowInfo;
4 |
5 | public interface IWindowTreeLoad {
6 |
7 | public void loadWindowTree(WindowInfo currentWindowInfo) throws ClassNotFoundException;
8 | }
9 |
--------------------------------------------------------------------------------
/app/src/main/java/com/kaixuan/windowtree/MyApp.kt:
--------------------------------------------------------------------------------
1 | package com.kaixuan.windowtree
2 |
3 | import android.app.Application
4 |
5 | class MyApp : Application() {
6 |
7 | companion object {
8 | lateinit var instance : MyApp;
9 | }
10 |
11 | override fun onCreate() {
12 | super.onCreate()
13 | instance = this
14 | }
15 | }
--------------------------------------------------------------------------------
/windowtree_library/src/main/java/com/kaixuan/windowtreelibrary/util/Consts.kt:
--------------------------------------------------------------------------------
1 | package com.kaixuan.windowtreelibrary.util
2 |
3 | class Consts{
4 | companion object {
5 | @JvmField
6 | val TAG = "WindowTree"
7 | val GENERATE_FILE_PATH = "com.kaixuan.windowtree.windows"
8 | val GENERATE_FILE_NAME_END = "\$Gen"
9 | }
10 | }
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/app/src/test/java/com/kaixuan/windowtree/ExampleUnitTest.kt:
--------------------------------------------------------------------------------
1 | package com.kaixuan.windowtree
2 |
3 | import org.junit.Test
4 |
5 | import org.junit.Assert.*
6 |
7 | /**
8 | * Example local unit test, which will execute on the development machine (host).
9 | *
10 | * See [testing documentation](http://d.android.com/tools/testing).
11 | */
12 | class ExampleUnitTest {
13 | @Test
14 | fun addition_isCorrect() {
15 | assertEquals(4, 2 + 2)
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/windowtree_library/src/test/java/com/kaixuan/windowtreelibrary/ExampleUnitTest.java:
--------------------------------------------------------------------------------
1 | package com.kaixuan.windowtreelibrary;
2 |
3 | import org.junit.Test;
4 |
5 | import static org.junit.Assert.*;
6 |
7 | /**
8 | * Example local unit test, which will execute on the development machine (host).
9 | *
10 | * @see Testing documentation
11 | */
12 | public class ExampleUnitTest {
13 | @Test
14 | public void addition_isCorrect() {
15 | assertEquals(4, 2 + 2);
16 | }
17 | }
--------------------------------------------------------------------------------
/.idea/runConfigurations.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
11 |
12 |
--------------------------------------------------------------------------------
/app/src/main/java/com/kaixuan/windowtree/activity/EmptyActivity.kt:
--------------------------------------------------------------------------------
1 | package com.kaixuan.windowtree.activity
2 |
3 | import android.support.v7.app.AppCompatActivity
4 | import android.os.Bundle
5 | import com.kaixuan.windowtree.R
6 | import com.kaixuan.windowtree_annotation.annotation.Window
7 | import com.kaixuan.windowtreelibrary.mWindowInfo
8 |
9 | @Window(parentClassName = "com.kaixuan.windowtree.MainActivity",name = "空白界面",index = 2)
10 | class EmptyActivity : BaseActivity() {
11 |
12 | override fun onCreate(savedInstanceState: Bundle?) {
13 | super.onCreate(savedInstanceState)
14 | setContentView(R.layout.fragment_test)
15 | supportActionBar!!.title = mWindowInfo.name
16 | }
17 |
18 | }
19 |
--------------------------------------------------------------------------------
/windowtree_annotation/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'java-library'
2 | apply plugin: 'com.novoda.bintray-release'
3 | dependencies {
4 | implementation fileTree(dir: 'libs', include: ['*.jar'])
5 | }
6 |
7 | sourceCompatibility = "7"
8 | targetCompatibility = "7"
9 | tasks.withType(Javadoc) {
10 | options.encoding = "UTF-8"
11 | }
12 | //添加
13 | publish {
14 | repoName = rootProject.repoName
15 | artifactId = 'windowtree-annotation'
16 | userOrg = rootProject.userOrg
17 | groupId = rootProject.groupId
18 | uploadName = rootProject.uploadName
19 | publishVersion = rootProject.publishVersion
20 | desc = rootProject.desc
21 | website = rootProject.website
22 | licences = rootProject.licences
23 | }
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_news_list.xml:
--------------------------------------------------------------------------------
1 |
2 |
11 |
12 |
16 |
17 |
--------------------------------------------------------------------------------
/app/src/androidTest/java/com/kaixuan/windowtree/ExampleInstrumentedTest.kt:
--------------------------------------------------------------------------------
1 | package com.kaixuan.windowtree
2 |
3 | import android.support.test.InstrumentationRegistry
4 | import android.support.test.runner.AndroidJUnit4
5 |
6 | import org.junit.Test
7 | import org.junit.runner.RunWith
8 |
9 | import org.junit.Assert.*
10 |
11 | /**
12 | * Instrumented test, which will execute on an Android device.
13 | *
14 | * See [testing documentation](http://d.android.com/tools/testing).
15 | */
16 | @RunWith(AndroidJUnit4::class)
17 | class ExampleInstrumentedTest {
18 | @Test
19 | fun useAppContext() {
20 | // Context of the app under test.
21 | val appContext = InstrumentationRegistry.getTargetContext()
22 | assertEquals("com.kaixuan.windowtree", appContext.packageName)
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
22 |
--------------------------------------------------------------------------------
/windowtree_library/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
22 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 | # IDE (e.g. Android Studio) users:
3 | # Gradle settings configured through the IDE *will override*
4 | # any settings specified in this file.
5 | # For more details on how to configure your build environment visit
6 | # http://www.gradle.org/docs/current/userguide/build_environment.html
7 | # Specifies the JVM arguments used for the daemon process.
8 | # The setting is particularly useful for tweaking memory settings.
9 | org.gradle.jvmargs=-Xmx1536m
10 | # When configured, Gradle will run in incubating parallel mode.
11 | # This option should only be used with decoupled projects. More details, visit
12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
13 | # org.gradle.parallel=true
14 | # Kotlin code style for this project: "official" or "obsolete":
15 | kotlin.code.style=official
16 |
--------------------------------------------------------------------------------
/.idea/gradle.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/windowtree_compiler/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'java-library'
2 | apply plugin:'java'
3 | apply plugin: 'com.novoda.bintray-release'
4 | dependencies {
5 | implementation fileTree(dir: 'libs', include: ['*.jar'])
6 | implementation 'com.google.auto.service:auto-service:1.0-rc3'
7 | implementation project(path: ':windowtree_annotation')
8 | implementation 'com.squareup:javapoet:1.8.0'
9 | }
10 |
11 | sourceCompatibility = "7"
12 | targetCompatibility = "7"
13 | tasks.withType(Javadoc) {
14 | options.encoding = "UTF-8"
15 | }
16 | //添加
17 | publish {
18 | repoName = rootProject.repoName
19 | artifactId = 'windowtree-compiler'
20 | userOrg = rootProject.userOrg
21 | groupId = rootProject.groupId
22 | uploadName = rootProject.uploadName
23 | publishVersion = rootProject.publishVersion
24 | desc = rootProject.desc
25 | website = rootProject.website
26 | licences = rootProject.licences
27 | }
--------------------------------------------------------------------------------
/windowtree_library/src/androidTest/java/com/kaixuan/windowtreelibrary/ExampleInstrumentedTest.java:
--------------------------------------------------------------------------------
1 | package com.kaixuan.windowtreelibrary;
2 |
3 | import android.content.Context;
4 | import android.support.test.InstrumentationRegistry;
5 | import android.support.test.runner.AndroidJUnit4;
6 |
7 | import org.junit.Test;
8 | import org.junit.runner.RunWith;
9 |
10 | import static org.junit.Assert.*;
11 |
12 | /**
13 | * Instrumented test, which will execute on an Android device.
14 | *
15 | * @see Testing documentation
16 | */
17 | @RunWith(AndroidJUnit4.class)
18 | public class ExampleInstrumentedTest {
19 | @Test
20 | public void useAppContext() {
21 | // Context of the app under test.
22 | Context appContext = InstrumentationRegistry.getTargetContext();
23 |
24 | assertEquals("com.kaixuan.windowtreelibrary.test", appContext.getPackageName());
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/windowtree_library/src/main/java/com/kaixuan/windowtreelibrary/template/ILogger.java:
--------------------------------------------------------------------------------
1 | package com.kaixuan.windowtreelibrary.template;
2 |
3 | import com.kaixuan.windowtreelibrary.util.Consts;
4 |
5 | /**
6 | * Logger
7 | *
8 | * @author 正纬 Contact me.
9 | * @version 1.0
10 | * @since 16/5/16 下午5:39
11 | */
12 | public interface ILogger {
13 |
14 | boolean isShowLog = false;
15 | boolean isShowStackTrace = false;
16 | String defaultTag = Consts.TAG;
17 |
18 | void showLog(boolean isShowLog);
19 |
20 | void showStackTrace(boolean isShowStackTrace);
21 |
22 | void debug(String tag, String message);
23 |
24 | void info(String tag, String message);
25 |
26 | void warning(String tag, String message);
27 |
28 | void error(String tag, String message);
29 |
30 | void monitor(String message);
31 |
32 | boolean isMonitorMode();
33 |
34 | String getDefaultTag();
35 | }
36 |
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/app/src/main/java/com/kaixuan/windowtree/activity/BaseActivity.kt:
--------------------------------------------------------------------------------
1 | package com.kaixuan.windowtree.activity
2 |
3 | import android.os.Bundle
4 | import android.support.v7.app.AppCompatActivity
5 | import android.util.Log
6 |
7 | open class BaseActivity : AppCompatActivity(){
8 |
9 | override fun onCreate(savedInstanceState: Bundle?) {
10 | super.onCreate(savedInstanceState)
11 | Log.i(this.javaClass.simpleName,"onCreate")
12 | }
13 |
14 | override fun onStart() {
15 | super.onStart()
16 | Log.i(this.javaClass.simpleName,"onStart")
17 | }
18 |
19 | override fun onResume() {
20 | super.onResume()
21 | Log.i(this.javaClass.simpleName,"onResume")
22 | }
23 |
24 | override fun onPause() {
25 | super.onPause()
26 | Log.i(this.javaClass.simpleName,"onPause")
27 | }
28 |
29 | override fun onStop() {
30 | super.onStop()
31 | Log.i(this.javaClass.simpleName,"onStop")
32 | }
33 |
34 | override fun onDestroy() {
35 | super.onDestroy()
36 | Log.i(this.javaClass.simpleName,"onDestroy")
37 | }
38 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/kaixuan/windowtree/fragment/dynamic/GoodFriendDynamicFragment.kt:
--------------------------------------------------------------------------------
1 | package com.kaixuan.windowtree.fragment.dynamic
2 |
3 | import android.os.Bundle
4 | import android.support.v4.app.Fragment
5 | import android.view.LayoutInflater
6 | import android.view.View
7 | import android.view.ViewGroup
8 | import com.kaixuan.windowtree.R
9 | import com.kaixuan.windowtreelibrary.mWindowInfo
10 | import kotlinx.android.synthetic.main.fragment_msg.*
11 |
12 | class GoodFriendDynamicFragment : Fragment() {
13 | var mView : View? = null
14 |
15 | override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? =
16 | mView ?: inflater.inflate(R.layout.fragment_msg,container,false).apply { mView = this }
17 |
18 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
19 | updateMsgCount()
20 | mWindowInfo.setEventListener { sender, sendData ->
21 | mWindowInfo.unReadMsgCount++
22 | updateMsgCount()
23 | }
24 | }
25 |
26 | fun updateMsgCount(){
27 | tv_msg_tips.text = "我有${mWindowInfo.unReadMsgCount}条未读消息"
28 | }
29 |
30 | }
--------------------------------------------------------------------------------
/windowtree_annotation/src/main/java/com/kaixuan/windowtree_annotation/annotation/Window.java:
--------------------------------------------------------------------------------
1 | package com.kaixuan.windowtree_annotation.annotation;
2 |
3 | import com.kaixuan.windowtree_annotation.model.WindowData;
4 |
5 | import java.lang.annotation.ElementType;
6 | import java.lang.annotation.Retention;
7 | import java.lang.annotation.RetentionPolicy;
8 | import java.lang.annotation.Target;
9 |
10 | @Target({ElementType.TYPE})
11 | @Retention(RetentionPolicy.CLASS)
12 | public @interface Window {
13 |
14 | /**
15 | *
16 | * @return 父class
17 | */
18 | Class parentClass() default Window.class;
19 |
20 | /**
21 | *
22 | * @return 父class完整类名,该属性和parentClass只需要任选实现一个即可,该属性便于模块化场景下无法直接引用目标类的情况
23 | */
24 | String parentClassName() default "";
25 |
26 | /**
27 | * 功能名称 可空
28 | * @return
29 | */
30 | String name() default "";
31 |
32 | /**
33 | * 该窗口是父节点的第几个子窗口
34 | * @return
35 | */
36 | int index() default 0;
37 |
38 | /**
39 | * 该窗口在你的项目中,页面权限id是多少
40 | * 该值默认为-1时,即该页面无权限控制,任何人都允许加载
41 | * @return
42 | */
43 | long pageAuthority() default -1;
44 | }
45 |
--------------------------------------------------------------------------------
/windowtree_library/src/main/java/com/kaixuan/windowtreelibrary/util/WindowTreeUtil.kt:
--------------------------------------------------------------------------------
1 | package com.kaixuan.windowtreelibrary.util
2 |
3 | import android.content.Context
4 | import com.kaixuan.windowtreelibrary.WindowInfo
5 | import com.kaixuan.windowtreelibrary.WindowTree
6 |
7 | class WindowTreeUtil{
8 |
9 | companion object {
10 |
11 | fun findKeyByValue(map: Map,value : V): K? {
12 | return map.entries.find { it.value == value }?.key
13 | }
14 |
15 | fun findContextByInfo(windowInfo: WindowInfo<*>): Context? {
16 | val findKeyByValue = findKeyByValue(WindowTree.instance.weakHashMap, windowInfo) ?: return null
17 | return getContext(findKeyByValue)
18 | }
19 |
20 | fun getContext(any: Any): Context? {
21 | when(any){
22 | is android.app.Activity -> return any
23 | is android.app.Fragment -> return any.activity
24 | is android.support.v4.app.Fragment -> return any.context
25 | is android.view.View -> return any.context
26 | is android.app.Dialog -> return any.context
27 | is android.widget.PopupWindow -> return any.contentView.context
28 | else -> return null
29 | }
30 | }
31 | }
32 | }
--------------------------------------------------------------------------------
/app/src/main/res/layout/fragment_msg.xml:
--------------------------------------------------------------------------------
1 |
2 |
11 |
12 |
17 |
23 |
29 |
30 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/windowtree_annotation/src/main/java/com/kaixuan/windowtree_annotation/enums/WindowType.java:
--------------------------------------------------------------------------------
1 | package com.kaixuan.windowtree_annotation.enums;
2 |
3 | public enum WindowType {
4 |
5 | ACTIVITY(0, "android.app.Activity"),
6 | FRAGMENT(1, "android.app.Fragment"),
7 | FRAGMENTV4(2, "android.support.v4.app.Fragment"),
8 | VIEW(3, "android.view.View"),
9 | DIALOG(4, "android.app.Dialog"),
10 | POPUPWINDOW(5, "android.widget.PopupWindow"),
11 | UNKNOWN(-1, "Unknown route type");
12 |
13 | int id;
14 | String className;
15 |
16 | WindowType(int id, String className) {
17 | this.id = id;
18 | this.className = className;
19 | }
20 |
21 | public int getId() {
22 | return id;
23 | }
24 |
25 | public WindowType setId(int id) {
26 | this.id = id;
27 | return this;
28 | }
29 |
30 | public String getClassName() {
31 | return className;
32 | }
33 |
34 | public WindowType setClassName(String className) {
35 | this.className = className;
36 | return this;
37 | }
38 |
39 | public static WindowType parse(String name) {
40 | for (WindowType windowType : WindowType.values()) {
41 | if (windowType.getClassName().equals(name)) {
42 | return windowType;
43 | }
44 | }
45 | return UNKNOWN;
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/app/src/main/java/com/kaixuan/windowtree/activity/NewsDetailActivity.kt:
--------------------------------------------------------------------------------
1 | package com.kaixuan.windowtree.activity
2 |
3 | import android.os.Bundle
4 | import com.kaixuan.windowtree.R
5 | import com.kaixuan.windowtree_annotation.annotation.Window
6 | import com.kaixuan.windowtreelibrary.mWindowInfo
7 | import com.kaixuan.windowtreelibrary.model.UnReadCountEvent
8 | import kotlinx.android.synthetic.main.activity_news_detail.*
9 |
10 | @Window(parentClass = NewsListActivity::class,name = "新闻详情",index = 3)
11 | class NewsDetailActivity : BaseActivity() {
12 |
13 | override fun onCreate(savedInstanceState: Bundle?) {
14 | super.onCreate(savedInstanceState)
15 | setContentView(R.layout.activity_news_detail)
16 | supportActionBar!!.title = mWindowInfo.name + mWindowInfo.getTag()
17 | tv_title.text = mWindowInfo.getTag() as String
18 | mWindowInfo.unReadMsgCount = mWindowInfo.bundle.getInt("unReadCount")
19 | tv_unReadCount.text = "未读数量:${mWindowInfo.unReadMsgCount}"
20 | mWindowInfo.setEventListener { sender, sendData ->
21 | if (sendData is UnReadCountEvent){
22 | tv_unReadCount.text = "未读数量:${mWindowInfo.unReadMsgCount}"
23 | }
24 | }
25 | btn_unReadAdd.setOnClickListener { mWindowInfo.unReadMsgCount ++ }
26 | btn_dealUnRead.setOnClickListener { mWindowInfo.unReadMsgCount = 0 }
27 | }
28 |
29 | override fun onDestroy() {
30 | mWindowInfo.release()
31 | super.onDestroy()
32 | }
33 |
34 | }
35 |
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 |
3 | apply plugin: 'kotlin-android'
4 | apply plugin: 'kotlin-kapt'
5 | apply plugin: 'kotlin-android-extensions'
6 |
7 | android {
8 | compileSdkVersion 28
9 | defaultConfig {
10 | applicationId "com.kaixuan.windowtree"
11 | minSdkVersion 15
12 | targetSdkVersion 28
13 | versionCode 1
14 | versionName "1.0"
15 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
16 | }
17 | buildTypes {
18 | release {
19 | minifyEnabled false
20 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
21 | }
22 | }
23 |
24 | }
25 |
26 | dependencies {
27 | implementation fileTree(include: ['*.jar'], dir: 'libs')
28 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
29 | implementation 'com.android.support:appcompat-v7:28.0.0'
30 | implementation 'com.android.support.constraint:constraint-layout:1.1.3'
31 | testImplementation 'junit:junit:4.12'
32 | androidTestImplementation 'com.android.support.test:runner:1.0.2'
33 | androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
34 | implementation project(path: ':windowtree_annotation')
35 | kapt project(':windowtree_compiler')
36 | implementation project(':windowtree_library')
37 | implementation 'com.android.support:design:28.0.0'
38 | // kapt 'com.kaixuan:windowtree-compiler:1.0.0'
39 | // implementation 'com.kaixuan:windowtree-library:1.0.0'
40 |
41 | }
42 |
--------------------------------------------------------------------------------
/app/src/main/java/com/kaixuan/windowtree/fragment/DynamicFragment.kt:
--------------------------------------------------------------------------------
1 | package com.kaixuan.windowtree.fragment
2 |
3 | import android.os.Bundle
4 | import android.support.v4.app.Fragment
5 | import android.text.method.ScrollingMovementMethod
6 | import android.view.LayoutInflater
7 | import android.view.View
8 | import android.view.ViewGroup
9 | import com.kaixuan.windowtree.MainActivity
10 | import com.kaixuan.windowtree.R
11 | import com.kaixuan.windowtree_annotation.annotation.Window
12 | import com.kaixuan.windowtreelibrary.mWindowInfo
13 | import kotlinx.android.synthetic.main.fragment_test.*
14 |
15 | @Window(parentClass = MainActivity::class,index = 3,name = "动态")
16 | class DynamicFragment : Fragment() {
17 |
18 | var mView : View? = null
19 |
20 | override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? =
21 | mView ?: inflater.inflate(R.layout.fragment_test,container,false).apply { mView = this }
22 |
23 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
24 | tv_log.movementMethod = ScrollingMovementMethod.getInstance();
25 | tv_title.text = "我是" + mWindowInfo.getClazz()!!.simpleName
26 | btn_send.setOnClickListener {
27 | mWindowInfo.unReadMsgCount ++
28 | val response = mWindowInfo.sendData("hello,我是${javaClass.simpleName}", mWindowInfo.parent!!)
29 | tv_log.append("收到了回信:${mWindowInfo.parent!!.getClazz()!!.simpleName}:$response\n")
30 | }
31 | btn_resetUnReadCount.setOnClickListener { mWindowInfo.unReadMsgCount = 0 }
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/app/src/main/java/com/kaixuan/windowtree/fragment/VipFragment.kt:
--------------------------------------------------------------------------------
1 | package com.kaixuan.windowtree.fragment
2 |
3 | import android.os.Bundle
4 | import android.support.v4.app.Fragment
5 | import android.text.method.ScrollingMovementMethod
6 | import android.view.LayoutInflater
7 | import android.view.View
8 | import android.view.ViewGroup
9 | import com.kaixuan.windowtree.MainActivity
10 | import com.kaixuan.windowtree.R
11 | import com.kaixuan.windowtree_annotation.annotation.Window
12 | import com.kaixuan.windowtreelibrary.mWindowInfo
13 | import kotlinx.android.synthetic.main.fragment_test.*
14 |
15 | @Window(parentClass = MainActivity::class,index = 3,name = "Vip隐藏界面",pageAuthority = 1)
16 | class VipFragment : Fragment() {
17 |
18 | var mView : View? = null
19 |
20 | override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? =
21 | mView ?: inflater.inflate(R.layout.fragment_test,container,false).apply { mView = this }
22 |
23 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
24 | tv_log.movementMethod = ScrollingMovementMethod.getInstance();
25 | tv_title.text = "我是Vip界面,一般人没有权限打开我"
26 | btn_send.setOnClickListener {
27 | mWindowInfo.unReadMsgCount ++
28 | val response = mWindowInfo.sendData("hello,我是${javaClass.simpleName},pageAuthority=${mWindowInfo.pageAuthority}", mWindowInfo.parent!!)
29 | tv_log.append( "收到了回信:${mWindowInfo.parent!!.getClazz()!!.simpleName}:$response\n")
30 | }
31 | btn_resetUnReadCount.setOnClickListener { mWindowInfo.unReadMsgCount = 0 }
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/app/src/main/java/com/kaixuan/windowtree/fragment/ContactsFragment.kt:
--------------------------------------------------------------------------------
1 | package com.kaixuan.windowtree.fragment
2 |
3 | import android.os.Bundle
4 | import android.support.v4.app.Fragment
5 | import android.view.LayoutInflater
6 | import android.view.View
7 | import android.view.ViewGroup
8 | import com.kaixuan.windowtree.MainActivity
9 | import com.kaixuan.windowtree.R
10 | import com.kaixuan.windowtree_annotation.annotation.Window
11 | import com.kaixuan.windowtreelibrary.mWindowInfo
12 | import kotlinx.android.synthetic.main.fragment_test.*
13 |
14 | @Window(parentClass = MainActivity::class,index = 2,name = "联系人")
15 | class ContactsFragment : Fragment() {
16 |
17 | var mView : View? = null
18 |
19 | override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? =
20 | mView ?: inflater.inflate(R.layout.fragment_test,container,false).apply { mView = this }
21 |
22 |
23 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
24 | super.onViewCreated(view, savedInstanceState)
25 | tv_title.text = "我是" + mWindowInfo.getClazz()!!.simpleName
26 | btn_send.setOnClickListener {
27 | mWindowInfo.unReadMsgCount ++
28 | val response = mWindowInfo.sendData("hello,我是${javaClass.simpleName}", mWindowInfo.parent!!)
29 | tv_log.append("收到了回信:${mWindowInfo.parent!!.getClazz()!!.simpleName}:$response\n")
30 | }
31 | btn_resetUnReadCount.setOnClickListener { mWindowInfo.unReadMsgCount = 0 }
32 | }
33 |
34 | override fun onDestroyView() {
35 | mWindowInfo.release()
36 | super.onDestroyView()
37 | }
38 |
39 | }
40 |
--------------------------------------------------------------------------------
/windowtree_library/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.library'
2 | apply plugin: 'kotlin-android'
3 | apply plugin: 'com.novoda.bintray-release'
4 |
5 | android {
6 | compileSdkVersion 28
7 |
8 |
9 |
10 | defaultConfig {
11 | minSdkVersion 15
12 | targetSdkVersion 28
13 | versionCode 1
14 | versionName "1.0"
15 |
16 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
17 |
18 | }
19 |
20 | buildTypes {
21 | release {
22 | minifyEnabled false
23 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
24 | }
25 | }
26 |
27 | }
28 |
29 | dependencies {
30 | implementation fileTree(include: ['*.jar'], dir: 'libs')
31 | implementation 'com.android.support:appcompat-v7:28.0.0'
32 | testImplementation 'junit:junit:4.12'
33 | androidTestImplementation 'com.android.support.test:runner:1.0.2'
34 | androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
35 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
36 | implementation project(':windowtree_annotation')
37 | }
38 | repositories {
39 | mavenCentral()
40 | }
41 | tasks.withType(Javadoc) {
42 | options.encoding = "UTF-8"
43 | }
44 | //添加
45 | publish {
46 | repoName = rootProject.repoName
47 | artifactId = 'windowtree-library'
48 | userOrg = rootProject.userOrg
49 | groupId = rootProject.groupId
50 | uploadName = rootProject.uploadName
51 | publishVersion = rootProject.publishVersion
52 | desc = rootProject.desc
53 | website = rootProject.website
54 | licences = rootProject.licences
55 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/kaixuan/windowtree/fragment/MainFragment.kt:
--------------------------------------------------------------------------------
1 | package com.kaixuan.windowtree.fragment
2 |
3 | import android.os.Bundle
4 | import android.support.v4.app.Fragment
5 | import android.text.method.ScrollingMovementMethod
6 | import android.view.LayoutInflater
7 | import android.view.View
8 | import android.view.ViewGroup
9 | import com.kaixuan.windowtree.MainActivity
10 | import com.kaixuan.windowtree.R
11 | import com.kaixuan.windowtree_annotation.annotation.Window
12 | import com.kaixuan.windowtreelibrary.WindowInfo
13 | import com.kaixuan.windowtreelibrary.WindowTree
14 | import com.kaixuan.windowtreelibrary.mWindowInfo
15 | import kotlinx.android.synthetic.main.fragment_test.*
16 |
17 | @Window(parentClass = MainActivity::class,index = 1,name = "主页")
18 | class MainFragment : Fragment() {
19 |
20 | lateinit var with : WindowInfo
21 | var mView : View? = null
22 |
23 | override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? =
24 | mView ?: inflater.inflate(R.layout.fragment_test,container,false).apply { mView = this }
25 |
26 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
27 | with = WindowTree.with(this)!!
28 | val t = with.setTag("")
29 | tv_log.movementMethod = ScrollingMovementMethod.getInstance();
30 | tv_title.text = "我是" + mWindowInfo!!.getClazz()!!.simpleName
31 | btn_send.setOnClickListener {
32 | mWindowInfo.unReadMsgCount ++
33 | val response = with.sendData("hello,我是${javaClass.simpleName}", with.parent!!)
34 | tv_log.append("收到了回信:${with.parent!!.getClazz()!!.simpleName}:${response}\n")
35 | }
36 | btn_resetUnReadCount.setOnClickListener { mWindowInfo.unReadMsgCount = 0 }
37 |
38 | }
39 |
40 |
41 | }
42 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/fragment_test.xml:
--------------------------------------------------------------------------------
1 |
2 |
11 |
12 |
17 |
23 |
29 |
35 |
40 |
41 |
42 |
43 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_news_detail.xml:
--------------------------------------------------------------------------------
1 |
2 |
11 |
12 |
17 |
23 |
29 |
35 |
41 |
42 |
43 |
44 |
45 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable-v24/ic_launcher_foreground.xml:
--------------------------------------------------------------------------------
1 |
7 |
12 |
13 |
19 |
22 |
25 |
26 |
27 |
28 |
34 |
35 |
--------------------------------------------------------------------------------
/.idea/codeStyles/Project.xml:
--------------------------------------------------------------------------------
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 |
--------------------------------------------------------------------------------
/windowtree_library/src/main/java/com/kaixuan/windowtreelibrary/thread/DefaultThreadFactory.java:
--------------------------------------------------------------------------------
1 | package com.kaixuan.windowtreelibrary.thread;
2 |
3 | import android.support.annotation.NonNull;
4 | import com.kaixuan.windowtreelibrary.WindowTree;
5 | import com.kaixuan.windowtreelibrary.util.Consts;
6 |
7 | import java.util.concurrent.ThreadFactory;
8 | import java.util.concurrent.atomic.AtomicInteger;
9 |
10 | /**
11 | * 线程池工厂类
12 | *
13 | * @author zhilong Contact me.
14 | * @version 1.0
15 | * @since 15/12/25 上午10:51
16 | */
17 | public class DefaultThreadFactory implements ThreadFactory {
18 | private static final AtomicInteger poolNumber = new AtomicInteger(1);
19 |
20 | private final AtomicInteger threadNumber = new AtomicInteger(1);
21 | private final ThreadGroup group;
22 | private final String namePrefix;
23 |
24 | public DefaultThreadFactory() {
25 | SecurityManager s = System.getSecurityManager();
26 | group = (s != null) ? s.getThreadGroup() :
27 | Thread.currentThread().getThreadGroup();
28 | namePrefix = "ARouter task pool No." + poolNumber.getAndIncrement() + ", thread No.";
29 | }
30 |
31 | public Thread newThread(@NonNull Runnable runnable) {
32 | String threadName = namePrefix + threadNumber.getAndIncrement();
33 | WindowTree.logger.info(Consts.TAG, "Thread production, name is [" + threadName + "]");
34 | Thread thread = new Thread(group, runnable, threadName, 0);
35 | if (thread.isDaemon()) { //设为非后台线程
36 | thread.setDaemon(false);
37 | }
38 | if (thread.getPriority() != Thread.NORM_PRIORITY) { //优先级为normal
39 | thread.setPriority(Thread.NORM_PRIORITY);
40 | }
41 |
42 | // 捕获多线程处理中的异常
43 | thread.setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
44 | @Override
45 | public void uncaughtException(Thread thread, Throwable ex) {
46 | WindowTree.logger.info(Consts.TAG, "Running task appeared exception! Thread [" + thread.getName() + "], because [" + ex.getMessage() + "]");
47 | }
48 | });
49 | return thread;
50 | }
51 | }
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
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 |
--------------------------------------------------------------------------------
/app/src/main/java/com/kaixuan/windowtree/activity/NewsListActivity.kt:
--------------------------------------------------------------------------------
1 | package com.kaixuan.windowtree.activity
2 |
3 | import android.os.Bundle
4 | import android.support.v7.recyclerview.extensions.ListAdapter
5 | import android.support.v7.widget.LinearLayoutManager
6 | import android.support.v7.widget.RecyclerView
7 | import android.view.LayoutInflater
8 | import android.view.ViewGroup
9 | import android.widget.TextView
10 | import com.kaixuan.windowtree.MainActivity
11 | import com.kaixuan.windowtree.R
12 | import com.kaixuan.windowtree_annotation.annotation.Window
13 | import com.kaixuan.windowtreelibrary.mWindowInfo
14 | import kotlinx.android.synthetic.main.activity_news_list.*
15 |
16 | @Window(parentClass = MainActivity::class,name = "新闻列表",index = 1)
17 | class NewsListActivity : BaseActivity() {
18 |
19 | val newsList = 0..200
20 |
21 | override fun onCreate(savedInstanceState: Bundle?) {
22 | super.onCreate(savedInstanceState)
23 | setContentView(R.layout.activity_news_list)
24 | supportActionBar!!.title = mWindowInfo.name
25 | recyclerView.layoutManager = LinearLayoutManager(this,LinearLayoutManager.VERTICAL,false)
26 | recyclerView.adapter = object : RecyclerView.Adapter(){
27 | override fun onCreateViewHolder(p0: ViewGroup, p1: Int): RecyclerView.ViewHolder {
28 | return object : RecyclerView.ViewHolder(LayoutInflater.from(this@NewsListActivity).inflate(R.layout.support_simple_spinner_dropdown_item,p0,false)){}
29 | }
30 |
31 | override fun getItemCount(): Int = newsList.count()
32 |
33 | override fun onBindViewHolder(p0: RecyclerView.ViewHolder, p1: Int) {
34 | (p0.itemView as TextView).let {textView ->
35 | textView.text = "Hello,这里是新闻啊啊啊啊啊啊啊啊 ${p1}"
36 | textView.setOnClickListener {
37 | val childByIndex = mWindowInfo.findChildByIndex(0)!!
38 | childByIndex.setTag(textView.text.toString())
39 | childByIndex.bundle.putInt("unReadCount",p1)
40 | mWindowInfo.jump(childByIndex) }
41 | }
42 | }
43 | }
44 | }
45 |
46 | override fun onDestroy() {
47 | mWindowInfo.release()
48 | super.onDestroy()
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/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 | set DIRNAME=%~dp0
12 | if "%DIRNAME%" == "" set DIRNAME=.
13 | set APP_BASE_NAME=%~n0
14 | set APP_HOME=%DIRNAME%
15 |
16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
17 | set DEFAULT_JVM_OPTS=
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 Windows variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 |
53 | :win9xME_args
54 | @rem Slurp the command line arguments.
55 | set CMD_LINE_ARGS=
56 | set _SKIP=2
57 |
58 | :win9xME_args_slurp
59 | if "x%~1" == "x" goto execute
60 |
61 | set CMD_LINE_ARGS=%*
62 |
63 | :execute
64 | @rem Setup the command line
65 |
66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
67 |
68 | @rem Execute Gradle
69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
70 |
71 | :end
72 | @rem End local scope for the variables with windows NT shell
73 | if "%ERRORLEVEL%"=="0" goto mainEnd
74 |
75 | :fail
76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
77 | rem the _cmd.exe /c_ return code!
78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
79 | exit /b 1
80 |
81 | :mainEnd
82 | if "%OS%"=="Windows_NT" endlocal
83 |
84 | :omega
85 |
--------------------------------------------------------------------------------
/windowtree_library/src/main/java/com/kaixuan/windowtreelibrary/adapter/DefaultJumpAdapter.kt:
--------------------------------------------------------------------------------
1 | package com.kaixuan.windowtreelibrary.adapter
2 |
3 | import android.app.Activity
4 | import android.content.Context
5 | import android.content.Intent
6 | import android.support.v4.app.Fragment
7 | import android.support.v4.app.FragmentActivity
8 | import com.kaixuan.windowtree_annotation.enums.WindowType
9 | import com.kaixuan.windowtreelibrary.WindowInfo
10 | import com.kaixuan.windowtreelibrary.WindowTree
11 | import com.kaixuan.windowtreelibrary.template.IJumpAdapter
12 | import java.lang.ref.WeakReference
13 |
14 | class DefaultJumpAdapter : IJumpAdapter {
15 |
16 | // val weakHashMapFrag : WeakHashMap by lazy { WeakHashMap() }
17 | val weakReferenceMap: HashMap> by lazy { HashMap>() }
18 |
19 | fun getCache(className: String, create: () -> T): T {
20 |
21 | val weakReference = weakReferenceMap[className]
22 | if (weakReference?.get() == null) {
23 | weakReferenceMap[className] = WeakReference(create()) as WeakReference
24 | }
25 | return weakReferenceMap[className]!!.get() as T
26 | }
27 |
28 | override fun jump(formContext: Context, to: WindowInfo<*>): Boolean {
29 | val with = WindowTree.with(formContext)
30 | ?: throw RuntimeException("找不到与该FormContext对应的WindowInfo; WindowInfo corresponding to FormContext could not be found")
31 | when (to.windowType) {
32 | WindowType.ACTIVITY -> {
33 | formContext.startActivity(Intent(formContext, to.getClazz()).apply { putExtras(to.bundle) })
34 | return true
35 | }
36 | WindowType.FRAGMENT -> {
37 | (formContext as Activity).fragmentManager.beginTransaction().replace(with.frameLayoutId,
38 | getCache(to.getClazzName()) { to.getClazz()!!.newInstance() } as android.app.Fragment
39 | ).commit()
40 | return true
41 | }
42 | WindowType.FRAGMENTV4 -> {
43 | (formContext as FragmentActivity).supportFragmentManager.run {
44 | beginTransaction().replace(with.frameLayoutId,
45 | getCache(to.getClazzName()) { to.getClazz()!!.newInstance() } as Fragment
46 | ).commit()
47 | }
48 | return true
49 | }
50 | else -> {
51 | throw RuntimeException("暂时不支持自动跳转至${to.windowType}类型窗口,您可以自己实现IJumpAdapter接口,自定义跳转方法")
52 | }
53 | }
54 | return false
55 | }
56 |
57 | fun clear() {
58 | weakReferenceMap.clear()
59 | }
60 |
61 | }
--------------------------------------------------------------------------------
/windowtree_annotation/src/main/java/com/kaixuan/windowtree_annotation/model/WindowMeta.java:
--------------------------------------------------------------------------------
1 | package com.kaixuan.windowtree_annotation.model;
2 |
3 |
4 | import com.kaixuan.windowtree_annotation.enums.WindowType;
5 |
6 | import java.util.ArrayList;
7 | import java.util.List;
8 |
9 |
10 | public class WindowMeta {
11 |
12 | private Class> clazz = null;
13 | private String clazzName = "";
14 | public WindowMeta parent = null;
15 | public List child = new ArrayList<>();
16 | public String name;
17 | public int index;
18 | private WindowType windowType;
19 | public long pageAuthority;
20 |
21 | public WindowMeta(Class> clazz, String clazzName, WindowMeta parent, String name, int index) {
22 | this(clazz,clazzName,parent,name,index,WindowType.UNKNOWN);
23 | }
24 | public WindowMeta(Class> clazz, String clazzName, WindowMeta parent, String name, int index, WindowType windowType) {
25 | this(clazz,clazzName,parent,name,index,WindowType.UNKNOWN,-1);
26 | }
27 | public WindowMeta(Class> clazz, String clazzName, WindowMeta parent, String name, int index, WindowType windowType,long pageAuthority) {
28 | this.index = index;
29 | this.name = name;
30 | this.clazz = clazz;
31 | this.clazzName = clazzName;
32 | this.parent = parent;
33 | this.windowType = windowType;
34 | this.pageAuthority = pageAuthority;
35 | }
36 |
37 | public void addChild(String clazzName,String name,int index,WindowType windowType){
38 | try {
39 | child.add(new WindowMeta(Class.forName(clazzName),clazzName,this,name,index,windowType));
40 | } catch (ClassNotFoundException e) {
41 | e.printStackTrace();
42 | }
43 | }
44 |
45 | public Class> getClazz() {
46 | return clazz;
47 | }
48 |
49 | public WindowMeta setClazz(Class> clazz) {
50 | this.clazz = clazz;
51 | return this;
52 | }
53 |
54 | public String getClazzName() {
55 | return clazzName;
56 | }
57 |
58 | public WindowMeta setClazzName(String clazzName) {
59 | this.clazzName = clazzName;
60 | return this;
61 | }
62 |
63 | public WindowType getWindowType() {
64 | return windowType;
65 | }
66 |
67 | public WindowMeta setWindowType(WindowType windowType) {
68 | this.windowType = windowType;
69 | return this;
70 | }
71 |
72 | @Override
73 | public String toString() {
74 | return "WindowMeta{" +
75 | "clazz=" + clazz +
76 | ", clazzName='" + clazzName + '\'' +
77 | ", name='" + name + '\'' +
78 | ", index=" + index +
79 | ", windowType=" + windowType +
80 | ", pageAuthority=" + pageAuthority +
81 | '}';
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/windowtree_library/src/main/java/com/kaixuan/windowtreelibrary/thread/DefaultPoolExecutor.java:
--------------------------------------------------------------------------------
1 | package com.kaixuan.windowtreelibrary.thread;
2 |
3 | import com.kaixuan.windowtreelibrary.WindowTree;
4 | import com.kaixuan.windowtreelibrary.util.Consts;
5 | import com.kaixuan.windowtreelibrary.util.TextUtils;
6 |
7 | import java.util.concurrent.*;
8 |
9 | /**
10 | * Executors
11 | *
12 | * @author 正纬 Contact me.
13 | * @version 1.0
14 | * @since 16/4/28 下午4:07
15 | */
16 | public class DefaultPoolExecutor extends ThreadPoolExecutor {
17 | // Thread args
18 | private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();
19 | private static final int INIT_THREAD_COUNT = CPU_COUNT + 1;
20 | private static final int MAX_THREAD_COUNT = INIT_THREAD_COUNT;
21 | private static final long SURPLUS_THREAD_LIFE = 30L;
22 |
23 | private static volatile DefaultPoolExecutor instance;
24 |
25 | public static DefaultPoolExecutor getInstance() {
26 | if (null == instance) {
27 | synchronized (DefaultPoolExecutor.class) {
28 | if (null == instance) {
29 | instance = new DefaultPoolExecutor(
30 | INIT_THREAD_COUNT,
31 | MAX_THREAD_COUNT,
32 | SURPLUS_THREAD_LIFE,
33 | TimeUnit.SECONDS,
34 | new ArrayBlockingQueue(64),
35 | new DefaultThreadFactory());
36 | }
37 | }
38 | }
39 | return instance;
40 | }
41 |
42 | private DefaultPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue workQueue, ThreadFactory threadFactory) {
43 | super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory, new RejectedExecutionHandler() {
44 | @Override
45 | public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
46 | WindowTree.logger.error(Consts.TAG, "Task rejected, too many task!");
47 | }
48 | });
49 | }
50 |
51 | /*
52 | * 线程执行结束,顺便看一下有么有什么乱七八糟的异常
53 | *
54 | * @param r the runnable that has completed
55 | * @param t the exception that caused termination, or null if
56 | */
57 | @Override
58 | protected void afterExecute(Runnable r, Throwable t) {
59 | super.afterExecute(r, t);
60 | if (t == null && r instanceof Future>) {
61 | try {
62 | ((Future>) r).get();
63 | } catch (CancellationException ce) {
64 | t = ce;
65 | } catch (ExecutionException ee) {
66 | t = ee.getCause();
67 | } catch (InterruptedException ie) {
68 | Thread.currentThread().interrupt(); // ignore/reset
69 | }
70 | }
71 | if (t != null) {
72 | WindowTree.logger.warning(Consts.TAG, "Running task appeared exception! Thread [" + Thread.currentThread().getName() + "], because [" + t.getMessage() + "]\n" + TextUtils.formatStackTrace(t.getStackTrace()));
73 | }
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/windowtree_library/src/main/java/com/kaixuan/windowtreelibrary/util/TextUtils.java:
--------------------------------------------------------------------------------
1 | package com.kaixuan.windowtreelibrary.util;
2 |
3 | import android.net.Uri;
4 |
5 | import java.util.Collections;
6 | import java.util.LinkedHashMap;
7 | import java.util.Map;
8 |
9 | /**
10 | * Text utils
11 | *
12 | * @author Alex Contact me.
13 | * @version 1.0
14 | * @since 16/9/9 14:40
15 | */
16 | public class TextUtils {
17 |
18 | public static boolean isEmpty(final CharSequence cs) {
19 | return cs == null || cs.length() == 0;
20 | }
21 |
22 | /**
23 | * Print thread stack
24 | */
25 | public static String formatStackTrace(StackTraceElement[] stackTrace) {
26 | StringBuilder sb = new StringBuilder();
27 | for (StackTraceElement element : stackTrace) {
28 | sb.append(" at ").append(element.toString());
29 | sb.append("\n");
30 | }
31 | return sb.toString();
32 | }
33 |
34 | /**
35 | * Split query parameters
36 | * @param rawUri raw uri
37 | * @return map with params
38 | */
39 | public static Map splitQueryParameters(Uri rawUri) {
40 | String query = rawUri.getEncodedQuery();
41 |
42 | if (query == null) {
43 | return Collections.emptyMap();
44 | }
45 |
46 | Map paramMap = new LinkedHashMap<>();
47 | int start = 0;
48 | do {
49 | int next = query.indexOf('&', start);
50 | int end = (next == -1) ? query.length() : next;
51 |
52 | int separator = query.indexOf('=', start);
53 | if (separator > end || separator == -1) {
54 | separator = end;
55 | }
56 |
57 | String name = query.substring(start, separator);
58 |
59 | if (!android.text.TextUtils.isEmpty(name)) {
60 | String value = (separator == end ? "" : query.substring(separator + 1, end));
61 | paramMap.put(Uri.decode(name), Uri.decode(value));
62 | }
63 |
64 | // Move start to end of name.
65 | start = end + 1;
66 | } while (start < query.length());
67 |
68 | return Collections.unmodifiableMap(paramMap);
69 | }
70 |
71 | /**
72 | * Split key with |
73 | *
74 | * @param key raw key
75 | * @return left key
76 | */
77 | public static String getLeft(String key) {
78 | if (key.contains("|") && !key.endsWith("|")) {
79 | return key.substring(0, key.indexOf("|"));
80 | } else {
81 | return key;
82 | }
83 | }
84 |
85 | /**
86 | * Split key with |
87 | *
88 | * @param key raw key
89 | * @return right key
90 | */
91 | public static String getRight(String key) {
92 | if (key.contains("|") && !key.startsWith("|")) {
93 | return key.substring(key.indexOf("|") + 1);
94 | } else {
95 | return key;
96 | }
97 | }
98 | /**
99 | * Split key with |
100 | *
101 | * @param key raw key
102 | * @return right key
103 | */
104 | public static String getRight(String key,String sub) {
105 | if (key.contains(sub) && !key.startsWith(sub)) {
106 | return key.substring(key.indexOf(sub) + 1);
107 | } else {
108 | return key;
109 | }
110 | }
111 | }
112 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
13 |
16 |
22 |
23 |
28 |
29 |
31 |
37 |
43 |
44 |
45 |
46 |
52 |
53 |
54 |
55 |
56 |
61 |
62 |
65 |
69 |
70 |
76 |
77 |
78 |
79 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # WindowTree
2 | 只需使用注解,帮助你轻松维护树形界面的层级关系,管理你的界面结构,当你处于界面的任何位置时,都可以知道,我在哪里,我的父界面是谁,我的子界面是谁。甚至能够自动构建你的界面结构。
3 |
4 | ## 我们经常会遇一些这样的问题:
5 |
6 | home界面下面有三个子界面A,B,C,他们三个业务功能内都会出现一个或多个未读消息或者通知,那么我们的home界面需要展示的未读消息数就应该是三个子界面未读消息数之和。
7 |
8 | 刚看到这个问题的时候会感觉很简单,我只需要在每次触发未读消息增减变化的时候,将本级界面和上一级界面的未读数量重新统计即可。当然,简单的需求是可以这么做,但是你有没有考虑过:
9 |
10 | 1. 当子界面A下级又有三个子界面A1,A2,A3的时候该怎么办?
11 |
12 | 2. 当用户没有一些界面的查看权限时,缺少了B和A2界面的浏览权限时,该怎么办?
13 |
14 | 3. 在界面构建时,如果发现用户没有A1,A2,A3的权限,那么其实A界面也不需要被构建,这样的工作,框架可以帮助我完成吗?
15 |
16 | 考虑到以上问题,如果用面向过程的思考方式出现一个解决一个,必然是事倍功半的,所以WindowTree就这样诞生了。
17 |
18 | # WindowTree结构
19 | 
20 |
21 | windowTree将应用内的所有界面都视为一个Window,每个window都拥有与之关联的WindowInfo,借助WindowInfo,你可以完成灵活、强大的工作。
22 |
23 | 目前支持的功能:
24 |
25 | 1. 维护了父子界面的层级关系,你在应用的任意位置,都可以知道我的父界面是谁,我的子界面有几个等
26 | 2. 灵活的消息通讯,你可以在任意位置发送消息给任意界面,支持双向通讯,你可以知道接收者处理该消息的结果
27 | 3. 支持贯穿全局的未读消息小红点,一个父节点能够智能计算出他所拥有的所有子节点的未读消息之和,借助Kotlin的委托属性实现了数量变化实时通知更新!
28 | 4. 智能的权限管理,当用户未拥有a的子界面a1,a2的权限时,也将失去a的界面权限
29 | 5. 界面自动化搭建,你只需要使用注解定义好界面的层级关系,便可以调用api获取所有子界面,一个循环全部建立
30 | 6. 统一管理界面跳转,便于管理界面和埋点统计,减少重复代码
31 | 7. 支持Activity、Fragment、View、Dialog、PopupWindow...
32 |
33 |
34 |
35 | # WindowTree使用
36 |
37 | 添加依赖项:
38 |
39 | ```
40 | apply plugin: 'kotlin-kapt'
41 |
42 | ...
43 | dependencies {
44 | kapt 'com.kaixuan:windowtree-compiler:1.0.0'
45 | implementation 'com.kaixuan:windowtree-library:1.0.0'
46 | ...
47 | }
48 | ```
49 |
50 | 1. 在你应用的所有界面添加注解@Window,参数parentClass指定该界面的父节点(如当前是顶级节点,则不需要设置该属性),可选添加其他属性,index表示当前界面是父界面的第几个同类界面,name表示当前节点名字
51 |
52 |
53 | ```
54 | @Window(parentClass = MainActivity::class,index = 2,name = "联系人")
55 | class ContactsFragment : Fragment()
56 | ```
57 |
58 |
59 | 2. 在应用启动时,初始化WindowTree
60 |
61 |
62 |
63 | ```
64 | WindowTree.init(MyApp.instance)
65 | ```
66 |
67 |
68 |
69 | 3.
70 | - 使用kotlin时,可直接在界面类(Activity、Fragment等)中使用扩展属性mWindowInfo拿到当前界面对应的WindowInfo
71 | - 使用java时,使用WindowTree.with(this)获取当前界面对应的WindowInfo
72 |
73 |
74 |
75 | 4. 如需接收消息,需要给当前节点设置setEventListener
76 |
77 | ```
78 | mWindowInfo.setEventListener { sender, sendData ->
79 | if (sendData is UnReadCountEvent){
80 | tv_log.append("未读消息:${sender.name}的未读消息发生变化,数量变化=${sendData.change}\n")
81 | updateUnReadCount()
82 | return@setEventListener "ok 我已收到并处理完毕" // 支持返回给消息发送者一个回信
83 | }
84 | return@setEventListener null
85 | }
86 | ```
87 | windowTree支持消息接收者返回一个结果的应答,告知事件处理结果
88 |
89 |
90 |
91 |
92 | 5. 拿到我的父节点或子节点对象,使用当前的WindowInfo对象可以轻松找到父节点和子节点,如:
93 | ```
94 | mWindowInfo.parent
95 | mWindowInfo.child
96 | ```
97 |
98 |
99 | 6. 界面自动构建,如果你使用TabLayout管理你的界面,只需要以下代码即可将当前界面的所有子界面加入到Tab
100 |
101 |
102 | ```
103 | tabLayout.addOnTabSelectedListener(object : TabLayout.OnTabSelectedListener{
104 | override fun onTabReselected(tab: TabLayout.Tab?) {
105 | }
106 |
107 | override fun onTabUnselected(tab: TabLayout.Tab?) {
108 | }
109 |
110 | override fun onTabSelected(tab: TabLayout.Tab?) {
111 | mWindowInfo.jump(tab!!.position,WindowType.FRAGMENTV4)
112 | }
113 | })
114 | mWindowInfo.filterChildByWindowType(WindowType.FRAGMENTV4).forEach { window ->
115 | tabLayout.addTab(tabLayout.newTab().setText(window.name).setTag(window))
116 | }
117 | ```
118 |
119 |
120 |
121 | 7. 界面跳转控制
122 |
123 |
124 | ```
125 | mWindowInfo.jump(0,WindowType.FRAGMENTV4)
126 | ```
127 | 这段代码表示,跳转到我的第1个Fragment类型的子界面
128 |
129 |
130 | DefaultJumpAdapter中默认实现了一些跳转逻辑,你可以继承它实现自己的特殊逻辑
131 |
132 |
133 |
134 |
135 | 如果你有更好的建议欢迎与我联系!thank you!kaixuanapp@163.com
136 |
137 | github:https://github.com/KaiXuan666/WindowTree
138 | csdn:https://blog.csdn.net/kaixuan_dashen
139 |
140 |
--------------------------------------------------------------------------------
/windowtree_library/src/main/java/com/kaixuan/windowtreelibrary/util/DefaultLogger.java:
--------------------------------------------------------------------------------
1 | package com.kaixuan.windowtreelibrary.util;
2 |
3 | import android.text.TextUtils;
4 | import android.util.Log;
5 | import com.kaixuan.windowtreelibrary.template.ILogger;
6 |
7 | /**
8 | * Default logger
9 | *
10 | * @author zhilong Contact me.
11 | * @version 1.0
12 | * @since 2015-12-08 21:44:10
13 | */
14 | public class DefaultLogger implements ILogger {
15 |
16 | private static boolean isShowLog = false;
17 | private static boolean isShowStackTrace = false;
18 | private static boolean isMonitorMode = false;
19 |
20 | private String defaultTag = "WindowTree";
21 |
22 | public void showLog(boolean showLog) {
23 | isShowLog = showLog;
24 | }
25 |
26 | public void showStackTrace(boolean showStackTrace) {
27 | isShowStackTrace = showStackTrace;
28 | }
29 |
30 | public void showMonitor(boolean showMonitor) {
31 | isMonitorMode = showMonitor;
32 | }
33 |
34 | public DefaultLogger() {
35 | }
36 |
37 | public DefaultLogger(String defaultTag) {
38 | this.defaultTag = defaultTag;
39 | }
40 |
41 | @Override
42 | public void debug(String tag, String message) {
43 | if (isShowLog) {
44 | StackTraceElement stackTraceElement = Thread.currentThread().getStackTrace()[3];
45 | Log.d(TextUtils.isEmpty(tag) ? getDefaultTag() : tag, message + getExtInfo(stackTraceElement));
46 | }
47 | }
48 |
49 | @Override
50 | public void info(String tag, String message) {
51 | if (isShowLog) {
52 | StackTraceElement stackTraceElement = Thread.currentThread().getStackTrace()[3];
53 | Log.i(TextUtils.isEmpty(tag) ? getDefaultTag() : tag, message + getExtInfo(stackTraceElement));
54 | }
55 | }
56 |
57 | @Override
58 | public void warning(String tag, String message) {
59 | if (isShowLog) {
60 | StackTraceElement stackTraceElement = Thread.currentThread().getStackTrace()[3];
61 | Log.w(TextUtils.isEmpty(tag) ? getDefaultTag() : tag, message + getExtInfo(stackTraceElement));
62 | }
63 | }
64 |
65 | @Override
66 | public void error(String tag, String message) {
67 | if (isShowLog) {
68 | StackTraceElement stackTraceElement = Thread.currentThread().getStackTrace()[3];
69 | Log.e(TextUtils.isEmpty(tag) ? getDefaultTag() : tag, message + getExtInfo(stackTraceElement));
70 | }
71 | }
72 |
73 | @Override
74 | public void monitor(String message) {
75 | if (isShowLog && isMonitorMode()) {
76 | StackTraceElement stackTraceElement = Thread.currentThread().getStackTrace()[3];
77 | Log.d(defaultTag + "::monitor", message + getExtInfo(stackTraceElement));
78 | }
79 | }
80 |
81 | @Override
82 | public boolean isMonitorMode() {
83 | return isMonitorMode;
84 | }
85 |
86 | @Override
87 | public String getDefaultTag() {
88 | return defaultTag;
89 | }
90 |
91 | public static String getExtInfo(StackTraceElement stackTraceElement) {
92 |
93 | String separator = " & ";
94 | StringBuilder sb = new StringBuilder("[");
95 |
96 | if (isShowStackTrace) {
97 | String threadName = Thread.currentThread().getName();
98 | String fileName = stackTraceElement.getFileName();
99 | String className = stackTraceElement.getClassName();
100 | String methodName = stackTraceElement.getMethodName();
101 | long threadID = Thread.currentThread().getId();
102 | int lineNumber = stackTraceElement.getLineNumber();
103 |
104 | sb.append("ThreadId=").append(threadID).append(separator);
105 | sb.append("ThreadName=").append(threadName).append(separator);
106 | sb.append("FileName=").append(fileName).append(separator);
107 | sb.append("ClassName=").append(className).append(separator);
108 | sb.append("MethodName=").append(methodName).append(separator);
109 | sb.append("LineNumber=").append(lineNumber);
110 | }
111 |
112 | sb.append(" ] ");
113 | return sb.toString();
114 | }
115 | }
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_launcher_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
10 |
12 |
14 |
16 |
18 |
20 |
22 |
24 |
26 |
28 |
30 |
32 |
34 |
36 |
38 |
40 |
42 |
44 |
46 |
48 |
50 |
52 |
54 |
56 |
58 |
60 |
62 |
64 |
66 |
68 |
70 |
72 |
74 |
75 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Attempt to set APP_HOME
10 | # Resolve links: $0 may be a link
11 | PRG="$0"
12 | # Need this for relative symlinks.
13 | while [ -h "$PRG" ] ; do
14 | ls=`ls -ld "$PRG"`
15 | link=`expr "$ls" : '.*-> \(.*\)$'`
16 | if expr "$link" : '/.*' > /dev/null; then
17 | PRG="$link"
18 | else
19 | PRG=`dirname "$PRG"`"/$link"
20 | fi
21 | done
22 | SAVED="`pwd`"
23 | cd "`dirname \"$PRG\"`/" >/dev/null
24 | APP_HOME="`pwd -P`"
25 | cd "$SAVED" >/dev/null
26 |
27 | APP_NAME="Gradle"
28 | APP_BASE_NAME=`basename "$0"`
29 |
30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
31 | DEFAULT_JVM_OPTS=""
32 |
33 | # Use the maximum available, or set MAX_FD != -1 to use that value.
34 | MAX_FD="maximum"
35 |
36 | warn () {
37 | echo "$*"
38 | }
39 |
40 | die () {
41 | echo
42 | echo "$*"
43 | echo
44 | exit 1
45 | }
46 |
47 | # OS specific support (must be 'true' or 'false').
48 | cygwin=false
49 | msys=false
50 | darwin=false
51 | nonstop=false
52 | case "`uname`" in
53 | CYGWIN* )
54 | cygwin=true
55 | ;;
56 | Darwin* )
57 | darwin=true
58 | ;;
59 | MINGW* )
60 | msys=true
61 | ;;
62 | NONSTOP* )
63 | nonstop=true
64 | ;;
65 | esac
66 |
67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
68 |
69 | # Determine the Java command to use to start the JVM.
70 | if [ -n "$JAVA_HOME" ] ; then
71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
72 | # IBM's JDK on AIX uses strange locations for the executables
73 | JAVACMD="$JAVA_HOME/jre/sh/java"
74 | else
75 | JAVACMD="$JAVA_HOME/bin/java"
76 | fi
77 | if [ ! -x "$JAVACMD" ] ; then
78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
79 |
80 | Please set the JAVA_HOME variable in your environment to match the
81 | location of your Java installation."
82 | fi
83 | else
84 | JAVACMD="java"
85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
86 |
87 | Please set the JAVA_HOME variable in your environment to match the
88 | location of your Java installation."
89 | fi
90 |
91 | # Increase the maximum file descriptors if we can.
92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
93 | MAX_FD_LIMIT=`ulimit -H -n`
94 | if [ $? -eq 0 ] ; then
95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
96 | MAX_FD="$MAX_FD_LIMIT"
97 | fi
98 | ulimit -n $MAX_FD
99 | if [ $? -ne 0 ] ; then
100 | warn "Could not set maximum file descriptor limit: $MAX_FD"
101 | fi
102 | else
103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
104 | fi
105 | fi
106 |
107 | # For Darwin, add options to specify how the application appears in the dock
108 | if $darwin; then
109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
110 | fi
111 |
112 | # For Cygwin, switch paths to Windows format before running java
113 | if $cygwin ; then
114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
116 | JAVACMD=`cygpath --unix "$JAVACMD"`
117 |
118 | # We build the pattern for arguments to be converted via cygpath
119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
120 | SEP=""
121 | for dir in $ROOTDIRSRAW ; do
122 | ROOTDIRS="$ROOTDIRS$SEP$dir"
123 | SEP="|"
124 | done
125 | OURCYGPATTERN="(^($ROOTDIRS))"
126 | # Add a user-defined pattern to the cygpath arguments
127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
129 | fi
130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
131 | i=0
132 | for arg in "$@" ; do
133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
135 |
136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
138 | else
139 | eval `echo args$i`="\"$arg\""
140 | fi
141 | i=$((i+1))
142 | done
143 | case $i in
144 | (0) set -- ;;
145 | (1) set -- "$args0" ;;
146 | (2) set -- "$args0" "$args1" ;;
147 | (3) set -- "$args0" "$args1" "$args2" ;;
148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
154 | esac
155 | fi
156 |
157 | # Escape application args
158 | save () {
159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
160 | echo " "
161 | }
162 | APP_ARGS=$(save "$@")
163 |
164 | # Collect all arguments for the java command, following the shell quoting and substitution rules
165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
166 |
167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
169 | cd "$(dirname "$0")"
170 | fi
171 |
172 | exec "$JAVACMD" "$@"
173 |
--------------------------------------------------------------------------------
/windowtree_library/src/main/java/com/kaixuan/windowtreelibrary/WindowTree.kt:
--------------------------------------------------------------------------------
1 | package com.kaixuan.windowtreelibrary
2 |
3 | import android.app.Activity
4 | import android.app.Application
5 | import android.app.Dialog
6 | import android.content.Context
7 | import android.support.v4.app.Fragment
8 | import android.view.View
9 | import android.widget.PopupWindow
10 | import com.kaixuan.windowtree_annotation.annotation.Window
11 | import com.kaixuan.windowtreelibrary.adapter.DefaultJumpAdapter
12 | import com.kaixuan.windowtreelibrary.template.IJumpAdapter
13 | import com.kaixuan.windowtreelibrary.template.IMain
14 | import com.kaixuan.windowtreelibrary.template.IWindowTreeLoad
15 | import com.kaixuan.windowtreelibrary.util.Consts
16 | import com.kaixuan.windowtreelibrary.util.DefaultLogger
17 | import java.util.*
18 |
19 | class WindowTree {
20 |
21 | lateinit var allGeneratedFile:
22 | Map>;
23 |
24 | val defaultJumpAdapter: DefaultJumpAdapter by lazy { DefaultJumpAdapter() }
25 | private var jumpAdapter: IJumpAdapter? = null
26 | var hasPageAuthorityFun: ((Long) -> Boolean)? = null
27 |
28 | /**
29 | * 最顶级的WindowInfo
30 | */
31 | var windowMeta: WindowInfo? = null;
32 |
33 | val weakHashMap: WeakHashMap> = WeakHashMap()
34 |
35 | companion object {
36 | lateinit var mContext: Context
37 | @JvmField
38 | @Volatile
39 | var debuggable = false
40 | @Volatile
41 | var hasInit = false
42 | @JvmField
43 | val logger = DefaultLogger(Consts.TAG)
44 | val instance: WindowTree by lazy(LazyThreadSafetyMode.SYNCHRONIZED) { WindowTree() }
45 |
46 | fun init(application: Application) {
47 | if (!hasInit) {
48 | hasInit = true
49 | mContext = application.applicationContext
50 | logger.showLog(true)
51 | val newInstance =
52 | Class.forName(Consts.GENERATE_FILE_PATH + ".Main" + Consts.GENERATE_FILE_NAME_END).getConstructor()
53 | .newInstance()
54 | instance.allGeneratedFile = (newInstance as IMain).allGeneratedFile
55 | val rootWindow =
56 | instance.allGeneratedFile[Consts.GENERATE_FILE_PATH + ".Window" + Consts.GENERATE_FILE_NAME_END]
57 | instance.windowMeta = WindowInfo(
58 | Window::class.java,
59 | Window::class.java.name,
60 | null
61 | );
62 | rootWindow!!.getConstructor().newInstance().loadWindowTree(instance.windowMeta)
63 | bindWindowTree(instance.windowMeta!!)
64 | logger.info(Consts.TAG, instance.allGeneratedFile.toString())
65 | }
66 | }
67 |
68 | fun bindWindowTree(windowMeta: WindowInfo<*>) {
69 | windowMeta.child.forEach { forItem ->
70 | val clazz =
71 | instance.allGeneratedFile[Consts.GENERATE_FILE_PATH + "." + forItem.getClazz()!!.simpleName + Consts.GENERATE_FILE_NAME_END]
72 | if (clazz == null) {
73 | // 找不到,跳过
74 |
75 | } else {
76 | // 找到,继续递归
77 | clazz.getConstructor().newInstance().loadWindowTree(forItem)
78 | bindWindowTree(forItem)
79 | }
80 | }
81 | }
82 |
83 | fun destroy() {
84 | if (hasInit) {
85 | hasInit = false
86 | instance.windowMeta!!.findWindowInfoByCondition {
87 | it.release(true)
88 | return@findWindowInfoByCondition false
89 | }
90 | instance.weakHashMap.clear()
91 | instance.windowMeta = null
92 | instance.hasPageAuthorityFun = null
93 | instance.defaultJumpAdapter.clear()
94 | instance.setJumpAdapter(null)
95 | }
96 | }
97 |
98 | @JvmStatic
99 | fun with(obj: Any): WindowInfo? {
100 | if (!hasInit) {
101 | throw RuntimeException("WindowTree未初始化")
102 | }
103 | return instance.weakHashMap[obj] as WindowInfo?
104 | ?: instance.windowMeta!!.findWindowInfoByClass(obj.javaClass)?.apply {
105 | instance.weakHashMap[obj] = this
106 | } as WindowInfo?
107 | }
108 |
109 | @JvmStatic
110 | fun hasAuthority(pageAuthorityId: Long): Boolean {
111 | if (!hasInit) {
112 | throw RuntimeException("WindowTree未初始化")
113 | }
114 | return instance.hasPageAuthorityFun?.invoke(pageAuthorityId)
115 | ?: (pageAuthorityId == -1L) // 不设置权限判断器的情况下,只拥有默认权限的页面
116 | }
117 | }
118 |
119 | fun setJumpAdapter(jumpAdapter: IJumpAdapter?) {
120 | this.jumpAdapter = jumpAdapter
121 | }
122 |
123 | fun setJumpAdapter(jumpAdapter: (Context, WindowInfo<*>) -> Boolean) {
124 | this.jumpAdapter = jumpAdapter.toJumpAdapter()
125 | }
126 |
127 | fun getJumpAdapter(): IJumpAdapter? = this.jumpAdapter
128 |
129 | }
130 |
131 | /**
132 | * 简化调用者写法,不需要object :
133 | */
134 | fun ) -> Boolean)> T.toJumpAdapter(): IJumpAdapter = object : IJumpAdapter {
135 | override fun jump(formContext: Context, to: WindowInfo<*>): Boolean = this@toJumpAdapter.invoke(formContext, to)
136 | }
137 |
138 | fun getWindowInfo(any: Any): WindowInfo = WindowTree.with(any)
139 | ?: throw RuntimeException("找不到与${any}对应的WindowInfo,请检查。在匿名内部类调用本方法时应谨慎检查this关键字的指代对象。")
140 |
141 | val Activity.mWindowInfo: WindowInfo
142 | get() = getWindowInfo(this)
143 | val View.mWindowInfo: WindowInfo
144 | get() = getWindowInfo(this)
145 | val Fragment.mWindowInfo: WindowInfo
146 | get() = getWindowInfo(this)
147 | val android.app.Fragment.mWindowInfo: WindowInfo
148 | get() = getWindowInfo(this)
149 | val Dialog.mWindowInfo: WindowInfo
150 | get() = getWindowInfo(this)
151 | val PopupWindow.mWindowInfo: WindowInfo
152 | get() = getWindowInfo(this)
153 |
--------------------------------------------------------------------------------
/app/src/main/java/com/kaixuan/windowtree/MainActivity.kt:
--------------------------------------------------------------------------------
1 | package com.kaixuan.windowtree
2 |
3 | import android.os.Bundle
4 | import android.support.design.widget.TabLayout
5 | import android.text.method.ScrollingMovementMethod
6 | import android.util.Log
7 | import android.view.View
8 | import android.view.ViewGroup
9 | import android.widget.Button
10 | import com.kaixuan.windowtree.activity.BaseActivity
11 | import com.kaixuan.windowtree_annotation.annotation.Window
12 | import com.kaixuan.windowtree_annotation.enums.WindowType
13 | import com.kaixuan.windowtreelibrary.WindowInfo
14 | import com.kaixuan.windowtreelibrary.WindowTree
15 | import com.kaixuan.windowtreelibrary.mWindowInfo
16 | import com.kaixuan.windowtreelibrary.model.UnReadCountEvent
17 | import kotlinx.android.synthetic.main.activity_main.*
18 |
19 | @Window
20 | class MainActivity : BaseActivity() {
21 |
22 | lateinit var with: WindowInfo<*>
23 | override fun onCreate(savedInstanceState: Bundle?) {
24 | super.onCreate(savedInstanceState)
25 | setContentView(R.layout.activity_main)
26 |
27 | tv_log.movementMethod = ScrollingMovementMethod.getInstance();
28 | btnInit.setOnClickListener {
29 | init()
30 | }
31 | btnCreateLayout.setOnClickListener { _ ->
32 | WindowTree.instance.hasPageAuthorityFun = { it == -1L }
33 | if (WindowTree.hasInit) {
34 | bindTabLayout()
35 | initActivityButton()
36 | } else {
37 | showToast("请先初始化")
38 | }
39 | }
40 | btnCreateLayout.setOnLongClickListener { _ ->
41 | // 用户的页面权限,一般通过登录接口获取
42 | val userAuthority = listOf(-1L, 1L, 2L, 3L, 4L)
43 | WindowTree.instance.hasPageAuthorityFun = { userAuthority.contains(it) }
44 | if (WindowTree.hasInit) {
45 | bindTabLayout()
46 | initActivityButton()
47 | } else {
48 | showToast("请先初始化")
49 | }
50 | true
51 | }
52 | btnGc.setOnClickListener { System.gc() }
53 | btnDestroy.setOnClickListener {
54 | release()
55 | }
56 | }
57 |
58 | fun init() {
59 | if (WindowTree.hasInit) {
60 | showToast("已经初始化过")
61 | return
62 | }
63 | WindowTree.init(MyApp.instance)
64 | with = mWindowInfo
65 | mWindowInfo.frameLayoutId = frameLayout.id
66 | initEventListener()
67 |
68 | }
69 |
70 | fun initEventListener() {
71 | with.setEventListener { sender, sendData ->
72 |
73 | if (sendData is UnReadCountEvent) {
74 | tv_log.append("未读消息:${sender.name}的未读消息发生变化,数量变化=${sendData.change}\n")
75 | updateUnReadCount()
76 | return@setEventListener null
77 | }
78 | when (sender) {
79 | // 1、判断是自己的孩子发来的消息
80 | in with.child -> {
81 | when (sendData) {
82 | // 2、判断发来消息的数据类型,你也可以定义msgCode或其他数据类型来进行判断,此处我为了偷懒
83 | is String -> {
84 | tv_log.append("子模块${sender.name}发来了消息,内容=${sendData}\n")
85 | }
86 | is Int -> {
87 |
88 | }
89 | }
90 | }
91 | }
92 |
93 | return@setEventListener "ok 我已收到并处理完毕" // 支持返回给消息发送者一个回信
94 | }
95 | }
96 |
97 | fun bindTabLayout() {
98 | if (tabLayout.tabCount != 0) {
99 | showToast("不能重复添加tab")
100 | return
101 | }
102 | tabLayout.addOnTabSelectedListener(object : TabLayout.OnTabSelectedListener {
103 | override fun onTabReselected(tab: TabLayout.Tab?) {
104 | }
105 |
106 | override fun onTabUnselected(tab: TabLayout.Tab?) {
107 | }
108 |
109 | override fun onTabSelected(tab: TabLayout.Tab?) {
110 | Log.e("onTabSelected", tab!!.tag.toString())
111 | mWindowInfo.jump(tab!!.position, WindowType.FRAGMENTV4)
112 | }
113 | })
114 |
115 | // 自动布局到TabLayout,两种方式都可以实现
116 | // with.child.forEach {
117 | // if (it.windowType == WindowType.FRAGMENTV4){
118 | // tabLayout.addTab(tabLayout.newTab().setText(it.name))
119 | // }
120 | // }
121 |
122 | mWindowInfo.filterChildByWindowType(WindowType.FRAGMENTV4).filter { WindowTree.hasAuthority(it.pageAuthority) }
123 | .forEach { window ->
124 | tabLayout.addTab(tabLayout.newTab().setText(window.name).setTag(window))
125 | }
126 | }
127 |
128 | fun initActivityButton() {
129 | llActivity.visibility = View.VISIBLE
130 | if (llActivity.childCount == 0) {
131 | // 过滤子Window自动进行布局
132 | mWindowInfo.filterChildByWindowType(WindowType.ACTIVITY).forEach { window ->
133 | llActivity.addView(Button(this).apply {
134 | text = "打开 ${window.name}"
135 | setOnClickListener { with.jump(window) } // 注:此处不能使用mWindowInfo获取当前windowInfo对象,因为此处的this指代的是View Button
136 | }, ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)
137 | }
138 | }
139 | }
140 |
141 | /**
142 | * 更新未读消息显示
143 | */
144 | fun updateUnReadCount() {
145 | val calcChildUnReadCount = mWindowInfo.calcChildUnReadCount()
146 | supportActionBar!!.title = if (calcChildUnReadCount != 0) "新消息:$calcChildUnReadCount" else "WindowTree"
147 | (0 until tabLayout.tabCount).forEach {
148 | val windowInfo = tabLayout.getTabAt(it)!!.tag as WindowInfo<*>
149 | val count = windowInfo.calcChildUnReadCount()
150 | tabLayout.getTabAt(it)!!.text = windowInfo.name + if (count == 0) "" else "($count)"
151 | }
152 | (0 until llActivity.childCount).forEach {
153 | val findChildByIndex = mWindowInfo.findChildByIndex(it, WindowType.ACTIVITY)!!
154 | (llActivity.getChildAt(it) as Button).apply {
155 | text = "打开 ${findChildByIndex.name}"
156 | val count = findChildByIndex.calcChildUnReadCount()
157 | if (count != 0) {
158 | append("($count)")
159 | }
160 | }
161 | }
162 |
163 | }
164 |
165 | override fun onBackPressed() {
166 | super.onBackPressed()
167 | }
168 |
169 | fun release() {
170 | supportActionBar!!.title = "WindowTree"
171 | WindowTree.destroy()
172 | llActivity.visibility = View.GONE
173 | llActivity.removeAllViews()
174 | tabLayout.removeAllTabs()
175 | tabLayout.clearOnTabSelectedListeners()
176 | frameLayout.removeAllViews()
177 | System.gc()
178 | tv_log.text = ""
179 | }
180 |
181 | override fun onDestroy() {
182 | release()
183 | super.onDestroy()
184 | }
185 | }
186 |
--------------------------------------------------------------------------------
/windowtree_library/src/main/java/com/kaixuan/windowtreelibrary/WindowInfo.kt:
--------------------------------------------------------------------------------
1 | package com.kaixuan.windowtreelibrary
2 |
3 |
4 | import android.content.Context
5 | import android.os.Bundle
6 | import com.kaixuan.windowtree_annotation.enums.WindowType
7 | import com.kaixuan.windowtreelibrary.model.UnReadCountEvent
8 | import com.kaixuan.windowtreelibrary.util.WindowTreeUtil
9 | import java.util.*
10 | import kotlin.properties.Delegates
11 |
12 |
13 | class WindowInfo @JvmOverloads constructor(
14 |
15 | clazz: Class<*>,
16 | clazzName: String,
17 | parent: WindowInfo<*>?,
18 | var name: String? = null,
19 | var index: Int = 0,
20 | var windowType: WindowType = WindowType.UNKNOWN
21 | ) {
22 |
23 | companion object {
24 | const val TAG = "WindowInfo"
25 | }
26 |
27 | private var clazz: Class<*>? = null
28 | private var clazzName = ""
29 | var parent: WindowInfo<*>? = null
30 | val child: MutableList> = ArrayList()
31 | val bundle: Bundle by lazy { Bundle() }
32 | private var tag: T? = null
33 | /**
34 | * 如当前节点需跳转到Fragment或填充View,则需设置该属性
35 | */
36 | var frameLayoutId = -1
37 | /**
38 | * 当前节点需要接收其他窗口发来的消息时,请设置该监听
39 | */
40 | private var onEventListener: ((
41 | sender: WindowInfo<*>,
42 | sendData: Any?
43 | ) -> Any?)? = null
44 |
45 | /**
46 | * 该值发生任意变化时,都会通知到当前窗口以及当前窗口的所有父节点
47 | */
48 | var unReadMsgCount: Int by Delegates.observable(0) { prop, old, new ->
49 | if (!WindowTree.hasInit) return@observable // WindowTree销毁时不再发送未读消息事件
50 | var notifyTaget: WindowInfo<*>?
51 | notifyTaget = this
52 | while (notifyTaget != null) {
53 | this.sendData(UnReadCountEvent(this, new - old), notifyTaget)
54 | notifyTaget = notifyTaget.parent
55 | }
56 | }
57 |
58 | var pageAuthority: Long = -1
59 |
60 | init {
61 | this.clazz = clazz
62 | this.clazzName = clazzName
63 | this.parent = parent
64 | }
65 |
66 | fun addChild(clazzName: String, name: String, index: Int, windowType: WindowType, pageAuthority: Long) {
67 | try {
68 | child.add(
69 | WindowInfo(
70 | Class.forName(clazzName),
71 | clazzName,
72 | this,
73 | name,
74 | index,
75 | windowType
76 | ).apply { this.pageAuthority = pageAuthority })
77 | } catch (e: ClassNotFoundException) {
78 | WindowTree.logger.error("addChild", e.toString())
79 | e.printStackTrace()
80 | }
81 |
82 | }
83 |
84 | fun getClazz(): Class<*>? {
85 | return clazz
86 | }
87 |
88 | fun setClazz(clazz: Class<*>): WindowInfo<*> {
89 | this.clazz = clazz
90 | return this
91 | }
92 |
93 | fun getClazzName(): String {
94 | return clazzName
95 | }
96 |
97 | fun setClazzName(clazzName: String): WindowInfo<*> {
98 | this.clazzName = clazzName
99 | return this
100 | }
101 |
102 | fun getTag(): T? {
103 | return tag
104 | }
105 |
106 | fun setTag(tag: T): WindowInfo {
107 | this.tag = tag
108 | return this
109 | }
110 |
111 | fun sendData(data: Any, receiverClass: Class<*>): Any? {
112 | val findWindowInfoByClass =
113 | WindowTree.instance.windowMeta!!.findWindowInfoByClass(receiverClass) ?: WindowTree.logger.error(
114 | TAG,
115 | "receiverClass,找不到$receiverClass"
116 | ).run { return null }
117 | findWindowInfoByClass.onEventListener ?: WindowTree.logger.error(
118 | TAG,
119 | "发送失败,目标${receiverClass}未设置监听"
120 | ).run { return null }
121 | return findWindowInfoByClass.onEventListener!!.invoke(this, data)
122 | }
123 |
124 | fun sendData(data: Any, receiverClazzName: String): Any? {
125 | val findWindowInfoByClass =
126 | WindowTree.instance.windowMeta!!.findWindowInfoByClass(receiverClazzName) ?: WindowTree.logger.error(
127 | TAG,
128 | "发送失败,找不到$receiverClazzName"
129 | ).run { return null }
130 | findWindowInfoByClass.onEventListener ?: WindowTree.logger.error(
131 | TAG,
132 | "发送失败,目标${receiverClazzName}未设置监听"
133 | ).run { return null }
134 | return findWindowInfoByClass.onEventListener!!.invoke(this, data)
135 | }
136 |
137 | fun sendData(data: Any, receiver: WindowInfo<*>): Any? {
138 | receiver.onEventListener ?: WindowTree.logger.info(TAG, "发送失败,目标${receiver}未设置监听").run { return null }
139 | WindowTree.logger.info("sendData", "this = ${this.clazzName}, receiver = ${receiver.clazzName}, data = $data")
140 | return receiver.onEventListener!!.invoke(this, data)
141 | }
142 |
143 | fun setEventListener(a: ((sender: WindowInfo<*>, sendData: Any?) -> Any?)?) {
144 | onEventListener = a
145 | }
146 |
147 | fun getEventListener() = onEventListener
148 |
149 | /**
150 | * 跳转至第几个什么类型的子界面
151 | * @param index 第几个子界面,该索引值受windowType影响
152 | * @param windowType 什么类型的子界面,如不传入windowType,默认以所有类型的子界面获取index
153 | */
154 | fun jump(index: Int, windowType: WindowType = WindowType.UNKNOWN): Boolean {
155 | val tempAdapter = WindowTree.instance.getJumpAdapter() ?: WindowTree.instance.defaultJumpAdapter
156 | val taget =
157 | findChildByIndex(index, windowType) ?: throw RuntimeException("找不到${windowType}类型的第${index}个窗口")
158 | val context = WindowTreeUtil.findContextByInfo(this)
159 | ?: throw RuntimeException("当前${this}对应的window未处于打开状态,无法从当前节点获取context,无法跳转")
160 | return tempAdapter.jump(context, taget)
161 | }
162 |
163 | fun jump(windowInfo: WindowInfo<*>): Boolean {
164 | val tempAdapter = WindowTree.instance.getJumpAdapter() ?: WindowTree.instance.defaultJumpAdapter
165 | val context = WindowTreeUtil.findContextByInfo(this)
166 | ?: throw RuntimeException("当前${this}对应的window未处于打开状态,无法从当前节点获取context,无法跳转")
167 | return tempAdapter.jump(context, windowInfo)
168 | }
169 |
170 | fun findChildByIndex(index: Int, windowType: WindowType = WindowType.UNKNOWN): WindowInfo? {
171 | val filter = filterChildByWindowType(windowType)
172 | return if (index >= filter.size) null else filter[index] as WindowInfo
173 | }
174 |
175 | fun filterChildByWindowType(windowType: WindowType): List> {
176 | return child.filter { windowType == WindowType.UNKNOWN || it.windowType == windowType }
177 | }
178 |
179 | fun findWindowInfoByClass(clazz: Class<*>?): WindowInfo<*>? {
180 | return findWindowInfoByClass(clazz!!.name)
181 | }
182 |
183 | /**
184 | * 计算当前的所有子节点的未读消息数量(包含当前节点)
185 | */
186 | fun calcChildUnReadCount(): Int {
187 | var count = 0
188 | findWindowInfoByCondition {
189 | count += it.unReadMsgCount
190 | return@findWindowInfoByCondition false
191 | }
192 | return count
193 | }
194 |
195 | fun getContext(): Context? = WindowTreeUtil.findContextByInfo(this)
196 |
197 | fun findWindowInfoByClass(clazzName: String): WindowInfo<*>? {
198 | return findWindowInfoByCondition {
199 | return@findWindowInfoByCondition it.clazzName == clazzName
200 | }
201 | }
202 |
203 | tailrec fun findWindowInfoByCondition(condition: (WindowInfo<*>) -> Boolean): WindowInfo<*>? {
204 | WindowTree.logger.info("findWindowInfoByCondition", this.toString())
205 | if (condition(this)) {
206 | return this
207 | }
208 | for (windowMeta in child) {
209 | val findWindowInfoByClass = windowMeta.findWindowInfoByCondition(condition)
210 | if (findWindowInfoByClass == null) {
211 |
212 | } else {
213 | return findWindowInfoByClass
214 | }
215 | }
216 | return null
217 | }
218 |
219 | tailrec fun findWindowInfoByConditionUp(condition: (WindowInfo<*>) -> Boolean): WindowInfo<*>? {
220 | WindowTree.logger.info("findWindowInfoByCondition", this.toString())
221 | if (condition(this)) {
222 | return this
223 | }
224 |
225 | if (parent == null) {
226 | return null
227 | } else {
228 | return parent!!.findWindowInfoByConditionUp(condition)
229 | }
230 | }
231 |
232 | /**
233 | * 如果当前节点有设置过onEventListener则需要手动调用该方法释放资源
234 | *
235 | * @param clearReadCount 是否清理未读消息数量,可根据业务需求传参
236 | */
237 | fun release(clearReadCount: Boolean = false) {
238 | if (clearReadCount) unReadMsgCount = 0
239 | setEventListener(null)
240 | }
241 |
242 | /**
243 | * 切忌不要打印this或父或子节点,否则将造成
244 | */
245 | override fun toString(): String {
246 | var parentClazzName = "null str"
247 | parent?.run { parentClazzName = this.clazzName }
248 | return "WindowMeta{" +
249 | "clazz=" + clazz +
250 | ", clazzName='" + clazzName + '\''.toString() +
251 | ", parent=" + parentClazzName +
252 | ", child.size=" + child.size +
253 | ", index=" + index +
254 | ", windowType=" + windowType +
255 | ", tag=" + tag +
256 | '}'.toString()
257 | }
258 | }
259 |
260 |
--------------------------------------------------------------------------------
/windowtree_compiler/src/main/java/com/kaixuan/compiler/WindowProcessor.java:
--------------------------------------------------------------------------------
1 | package com.kaixuan.compiler;
2 |
3 |
4 | import com.google.auto.service.AutoService;
5 |
6 | import javax.annotation.processing.*;
7 | import javax.lang.model.SourceVersion;
8 | import javax.lang.model.element.Element;
9 | import javax.lang.model.element.ElementKind;
10 | import javax.lang.model.element.Modifier;
11 | import javax.lang.model.element.TypeElement;
12 | import javax.lang.model.type.MirroredTypeException;
13 | import javax.lang.model.type.TypeMirror;
14 | import javax.lang.model.util.Elements;
15 | import javax.lang.model.util.Types;
16 | import javax.tools.Diagnostic;
17 |
18 | import com.kaixuan.windowtree_annotation.enums.WindowType;
19 | import com.kaixuan.windowtree_annotation.model.WindowMeta;
20 | import com.kaixuan.windowtree_annotation.annotation.Window;
21 | import com.squareup.javapoet.*;
22 |
23 | import java.io.IOException;
24 | import java.util.*;
25 |
26 | @AutoService(Processor.class)
27 | @SupportedSourceVersion(SourceVersion.RELEASE_7)
28 | @SupportedAnnotationTypes("com.kaixuan.windowtree_annotation.annotation.Window")
29 | public class WindowProcessor extends AbstractProcessor {
30 |
31 | /**
32 | * 文件相关的辅助类
33 | */
34 | private Filer mFiler;
35 | /**
36 | * 元素相关的辅助类
37 | */
38 | private Elements mElementUtils;
39 | /**
40 | * 日志相关的辅助类
41 | */
42 | private Messager mMessager;
43 | /**
44 | * 日志相关的辅助类
45 | */
46 | private Types types;
47 |
48 |
49 | @Override
50 | public synchronized void init(ProcessingEnvironment processingEnvironment) {
51 | super.init(processingEnvironment);
52 | mFiler = processingEnvironment.getFiler();
53 | types = processingEnvironment.getTypeUtils();
54 | mElementUtils = processingEnvironment.getElementUtils();
55 | mMessager = processingEnvironment.getMessager();
56 | mMessager.printMessage(Diagnostic.Kind.WARNING, "init : ");
57 | }
58 |
59 | @Override
60 | public boolean process(Set extends TypeElement> set, RoundEnvironment roundEnvironment) {
61 | Map> stringListMap = parseWindows(roundEnvironment.getElementsAnnotatedWith(Window.class));
62 | List main = new ArrayList<>();
63 |
64 |
65 | for (String s : stringListMap.keySet()) {
66 | MethodSpec.Builder builder = MethodSpec.methodBuilder("loadWindowTree")//定义方面名
67 | .addModifiers(Modifier.PUBLIC)//定义修饰符
68 | .addAnnotation(Override.class)
69 | .returns(void.class)//定义返回结果
70 | .addParameter(ClassName.get("com.kaixuan.windowtreelibrary","WindowInfo"), "currentWindowMeta");//添加方法参数
71 | List windowMetas = stringListMap.get(s);
72 | windowMetas.sort(new Comparator() {
73 | @Override
74 | public int compare(WindowMeta windowMeta, WindowMeta t1) {
75 | return windowMeta.index - t1.index;
76 | }
77 | });
78 | mMessager.printMessage(Diagnostic.Kind.WARNING, "windowMetas windowMetas : " + windowMetas.toString());
79 | for (WindowMeta meta : windowMetas) {
80 | mMessager.printMessage(Diagnostic.Kind.WARNING, "windowMetas meta.index : " + meta.index);
81 | builder.addStatement("currentWindowMeta.addChild($S,$S,$L,$L,$L)", meta.getClazzName(),meta.name,meta.index,meta.getWindowType(),meta.pageAuthority);//添加方法内容
82 | }
83 | MethodSpec methodSpec = builder.addException(ClassName.get(ClassNotFoundException.class))
84 | .build();
85 | String tempClass = s.substring(s.lastIndexOf(".") + 1) + "$Gen";
86 | TypeSpec finderClass = TypeSpec.classBuilder(tempClass)
87 | .addSuperinterface(ClassName.get(mElementUtils.getTypeElement("com.kaixuan.windowtreelibrary.template.IWindowTreeLoad")))
88 | .addModifiers(Modifier.PUBLIC)
89 | // .addSuperinterface(ParameterizedTypeName.get(TypeUtil.INJECTOR, TypeName.get(mClassElement.asType())))
90 | .addMethod(methodSpec)
91 | .build();
92 | // 创建Java文件
93 | main.add("com.kaixuan.windowtree.windows." + tempClass );
94 | JavaFile javaFile = JavaFile.builder("com.kaixuan.windowtree.windows", finderClass)
95 | .addStaticImport(WindowType.class, "*").build();
96 | try {
97 | javaFile.writeTo(mFiler);
98 | } catch (IOException e) {
99 | e.printStackTrace();
100 | }
101 | }
102 | ParameterizedTypeName map = ParameterizedTypeName.get(ClassName.get(Map.class)
103 | , ClassName.get(String.class)
104 | , ParameterizedTypeName.get(
105 | ClassName.get(Class.class),
106 | WildcardTypeName.subtypeOf(ClassName.get(mElementUtils.getTypeElement("com.kaixuan.windowtreelibrary.template.IWindowTreeLoad")))
107 | ));
108 | MethodSpec.Builder builder = MethodSpec.methodBuilder("getAllGeneratedFile")
109 | .addModifiers(Modifier.PUBLIC)//定义修饰符
110 | .addAnnotation(Override.class)
111 | .returns(ParameterizedTypeName.get(ClassName.get(Map.class)
112 | , ClassName.get(String.class)
113 | , ParameterizedTypeName.get(
114 | ClassName.get(Class.class),
115 | WildcardTypeName.subtypeOf(ClassName.get(mElementUtils.getTypeElement("com.kaixuan.windowtreelibrary.template.IWindowTreeLoad")))
116 | )));//定义返回结果
117 |
118 | ClassName mapNew = ClassName.get("java.util", "HashMap");
119 |
120 | builder.addStatement("$T map = new $T()",map,mapNew);
121 | for (String s : main) {
122 | builder.addStatement("map.put($S,$L.class)",s,s);
123 | }
124 | builder.addStatement("return map");
125 |
126 | TypeSpec mainClass = TypeSpec.classBuilder( "Main$Gen")
127 | .addSuperinterface(ClassName.get(mElementUtils.getTypeElement("com.kaixuan.windowtreelibrary.template.IMain")))
128 | .addModifiers(Modifier.PUBLIC)
129 | // .addSuperinterface(ParameterizedTypeName.get(TypeUtil.INJECTOR, TypeName.get(mClassElement.asType())))
130 | .addMethod(builder.build())
131 | .build();
132 | JavaFile javaFile = JavaFile.builder("com.kaixuan.windowtree.windows", mainClass).build();
133 | try {
134 | javaFile.writeTo(mFiler);
135 | } catch (IOException e) {
136 | e.printStackTrace();
137 | }
138 | return true;
139 | }
140 |
141 | private Map> parseWindows(Set extends Element> routeElements) {
142 | // 以父节点类名为key平铺存储所有WindowMeta
143 | Map> map = new HashMap<>();
144 |
145 | TypeMirror type_Activity = mElementUtils.getTypeElement(WindowType.ACTIVITY.getClassName()).asType();
146 | TypeMirror type_Fragment = mElementUtils.getTypeElement(WindowType.FRAGMENT.getClassName()).asType();
147 | TypeMirror type_Fragmentv4 = mElementUtils.getTypeElement(WindowType.FRAGMENTV4.getClassName()).asType();
148 | TypeMirror type_View = mElementUtils.getTypeElement(WindowType.VIEW.getClassName()).asType();
149 | TypeMirror type_Dialog = mElementUtils.getTypeElement(WindowType.DIALOG.getClassName()).asType();
150 | TypeMirror type_PopupWindow = mElementUtils.getTypeElement(WindowType.POPUPWINDOW.getClassName()).asType();
151 |
152 | for (Element routeElement : routeElements) {
153 | Window annotation = routeElement.getAnnotation(Window.class);
154 | if (routeElement.getKind() == ElementKind.CLASS) {
155 | mMessager.printMessage(Diagnostic.Kind.WARNING, "annotation : " + annotation.toString());
156 | mMessager.printMessage(Diagnostic.Kind.WARNING, "annotation info : " + annotation.name() + annotation.index());
157 | String parentName = annotation.parentClassName();
158 | if (parentName.isEmpty()) {
159 | try {
160 | mMessager.printMessage(Diagnostic.Kind.WARNING, "parentClass : " + annotation.parentClass());
161 | } catch (MirroredTypeException e) {
162 | parentName = e.getTypeMirror().toString();
163 | // mMessager.printMessage(Diagnostic.Kind.WARNING,"e.getTypeMirror() : " + e.getTypeMirror().toString());
164 | }
165 | }
166 | List windowMetas = map.get(parentName);
167 | if (windowMetas == null) {
168 | windowMetas = new ArrayList<>();
169 | map.put(parentName, windowMetas);
170 | }
171 |
172 | // routeElement.toString()
173 | WindowMeta windowMeta = new WindowMeta