8 | implements MainContract.Presenter {
9 |
10 | MainPresenter(MainContract.View view) {
11 | super(view);
12 | }
13 |
14 | @Override
15 | public void onDestroy(LifecycleOwner owner) {
16 | super.onDestroy(owner);
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/lib_base/src/main/java/com/quyunshuo/base/capsulation/eventbus/EventBusBinding.java:
--------------------------------------------------------------------------------
1 | package com.quyunshuo.base.capsulation.eventbus;
2 |
3 | import java.lang.annotation.ElementType;
4 | import java.lang.annotation.Retention;
5 | import java.lang.annotation.RetentionPolicy;
6 | import java.lang.annotation.Target;
7 |
8 | /**
9 | * @Author: QuYunShuo
10 | * @Time: 2020/4/12
11 | * @Class: EventBusBinding
12 | * @Remark: EventBus注册与解除注册的注解封装
13 | */
14 | @Target(ElementType.TYPE)
15 | @Retention(RetentionPolicy.RUNTIME)
16 | public @interface EventBusBinding {
17 | }
18 |
--------------------------------------------------------------------------------
/lib_main/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/lib_xxx/src/main/res/layout/xxx_activity_debug.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
13 |
--------------------------------------------------------------------------------
/lib_main/src/main/res/layout/main_activity_debug.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
13 |
--------------------------------------------------------------------------------
/lib_xxx/build.gradle:
--------------------------------------------------------------------------------
1 | apply from: '../module.gradle'
2 |
3 | android {
4 |
5 | resourcePrefix "xxx_"
6 |
7 | // 打开ViewBinding
8 | viewBinding {
9 | enabled = true
10 | }
11 | defaultConfig {
12 | javaCompileOptions {
13 | annotationProcessorOptions {
14 | arguments = [AROUTER_MODULE_NAME: project.getName(),
15 | eventBusIndex : 'com.quyunshuo.eventbus.index.XxxEventBusIndex']
16 | }
17 | }
18 | }
19 | }
20 |
21 | dependencies {
22 | testImplementation 'junit:junit:4.12'
23 | androidTestImplementation 'androidx.test.ext:junit:1.1.1'
24 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
25 | }
26 |
--------------------------------------------------------------------------------
/lib_xxx/src/main/debug/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/lib_main/src/main/debug/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .cxx
2 | # Built application files
3 | *.apk
4 | *.ap_
5 |
6 | # Files for the ART/Dalvik VM
7 | *.dex
8 |
9 | # Java class files
10 | *.class
11 |
12 | # Generated files
13 | bin/
14 | gen/
15 | out/
16 |
17 | # Gradle files
18 | .gradle/
19 | build/
20 | bugly/
21 |
22 | # Local configuration file (sdk path, etc)
23 | local.properties
24 |
25 | # Proguard folder generated by Eclipse
26 | proguard/
27 |
28 | # Log Files
29 | *.log
30 |
31 | # Android Studio Navigation editor temp files
32 | .navigation/
33 |
34 | # Android Studio captures folder
35 | captures/
36 |
37 | # Intellij
38 | *.iml
39 | .idea/
40 |
41 | # Keystore files
42 | /local.properties
43 | app/.externalNativeBuild/
44 | app/.DS_Store
45 | app/class_files.txt
46 | */mapping.txt
47 | */seeds.txt
--------------------------------------------------------------------------------
/lib_base/src/main/java/com/quyunshuo/base/utils/TimeConstants.java:
--------------------------------------------------------------------------------
1 | package com.quyunshuo.base.utils;
2 |
3 | import androidx.annotation.IntDef;
4 |
5 | import java.lang.annotation.Retention;
6 | import java.lang.annotation.RetentionPolicy;
7 |
8 | /**
9 | * author: Blankj
10 | * blog : http://blankj.com
11 | * time : 2017/03/13
12 | * desc : constants of time
13 | */
14 | public final class TimeConstants {
15 |
16 | public static final int MSEC = 1;
17 | public static final int SEC = 1000;
18 | public static final int MIN = 60000;
19 | public static final int HOUR = 3600000;
20 | public static final int DAY = 86400000;
21 |
22 | @IntDef({MSEC, SEC, MIN, HOUR, DAY})
23 | @Retention(RetentionPolicy.SOURCE)
24 | public @interface Unit {
25 | }
26 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/quyunshuo/androidbaseframe/AppApplication.java:
--------------------------------------------------------------------------------
1 | package com.quyunshuo.androidbaseframe;
2 |
3 | import com.quyunshuo.comm.CommApplication;
4 | import com.quyunshuo.eventbus.index.MainEventBusIndex;
5 | import com.quyunshuo.eventbus.index.XxxEventBusIndex;
6 |
7 | import org.greenrobot.eventbus.EventBus;
8 |
9 | /**
10 | * @Author: QuYunShuo
11 | * @Time: 2020/4/12
12 | * @Class: AppApplication
13 | * @Remark:
14 | */
15 | public class AppApplication extends CommApplication {
16 |
17 | @Override
18 | public void onCreate() {
19 | super.onCreate();
20 | // 使用EventBus索引 优化性能
21 | EventBus.builder()
22 | .addIndex(new MainEventBusIndex())
23 | .addIndex(new XxxEventBusIndex())
24 | .installDefaultEventBus();
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/lib_xxx/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 |
--------------------------------------------------------------------------------
/lib_base/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 |
--------------------------------------------------------------------------------
/lib_comm/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 |
--------------------------------------------------------------------------------
/lib_main/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 |
--------------------------------------------------------------------------------
/lib_xxx/src/main/res/layout/xxx_fragment.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
17 |
--------------------------------------------------------------------------------
/lib_main/src/main/res/layout/main_home_fragment.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
17 |
--------------------------------------------------------------------------------
/lib_main/build.gradle:
--------------------------------------------------------------------------------
1 | apply from: '../module.gradle'
2 |
3 | android {
4 |
5 | resourcePrefix "main_"
6 |
7 | // 打开ViewBinding
8 | viewBinding {
9 | enabled = true
10 | }
11 | defaultConfig {
12 | javaCompileOptions {
13 | annotationProcessorOptions {
14 | arguments = [AROUTER_MODULE_NAME: project.getName(),
15 | eventBusIndex : 'com.quyunshuo.eventbus.index.MainEventBusIndex']
16 | }
17 | }
18 | }
19 | }
20 |
21 | dependencies {
22 | implementation 'androidx.appcompat:appcompat:1.1.0'
23 | implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
24 | testImplementation 'junit:junit:4.12'
25 | androidTestImplementation 'androidx.test.ext:junit:1.1.1'
26 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
27 | }
28 |
--------------------------------------------------------------------------------
/lib_xxx/src/androidTest/java/com/quyunshuo/xxx/ExampleInstrumentedTest.java:
--------------------------------------------------------------------------------
1 | package com.quyunshuo.xxx;
2 |
3 | import android.content.Context;
4 |
5 | import androidx.test.platform.app.InstrumentationRegistry;
6 | import androidx.test.ext.junit.runners.AndroidJUnit4;
7 |
8 | import org.junit.Test;
9 | import org.junit.runner.RunWith;
10 |
11 | import static org.junit.Assert.*;
12 |
13 | /**
14 | * Instrumented test, which will execute on an Android device.
15 | *
16 | * @see Testing documentation
17 | */
18 | @RunWith(AndroidJUnit4.class)
19 | public class ExampleInstrumentedTest {
20 | @Test
21 | public void useAppContext() {
22 | // Context of the app under test.
23 | Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
24 |
25 | assertEquals("com.quyunshuo.lib_xxx.test", appContext.getPackageName());
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/lib_base/src/androidTest/java/com/quyunshuo/base/ExampleInstrumentedTest.java:
--------------------------------------------------------------------------------
1 | package com.quyunshuo.base;
2 |
3 | import android.content.Context;
4 |
5 | import androidx.test.platform.app.InstrumentationRegistry;
6 | import androidx.test.ext.junit.runners.AndroidJUnit4;
7 |
8 | import org.junit.Test;
9 | import org.junit.runner.RunWith;
10 |
11 | import static org.junit.Assert.*;
12 |
13 | /**
14 | * Instrumented test, which will execute on an Android device.
15 | *
16 | * @see Testing documentation
17 | */
18 | @RunWith(AndroidJUnit4.class)
19 | public class ExampleInstrumentedTest {
20 | @Test
21 | public void useAppContext() {
22 | // Context of the app under test.
23 | Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
24 |
25 | assertEquals("com.quyunshuo.lib_base.test", appContext.getPackageName());
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/lib_comm/src/androidTest/java/com/quyunshuo/comm/ExampleInstrumentedTest.java:
--------------------------------------------------------------------------------
1 | package com.quyunshuo.comm;
2 |
3 | import android.content.Context;
4 |
5 | import androidx.test.platform.app.InstrumentationRegistry;
6 | import androidx.test.ext.junit.runners.AndroidJUnit4;
7 |
8 | import org.junit.Test;
9 | import org.junit.runner.RunWith;
10 |
11 | import static org.junit.Assert.*;
12 |
13 | /**
14 | * Instrumented test, which will execute on an Android device.
15 | *
16 | * @see Testing documentation
17 | */
18 | @RunWith(AndroidJUnit4.class)
19 | public class ExampleInstrumentedTest {
20 | @Test
21 | public void useAppContext() {
22 | // Context of the app under test.
23 | Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
24 |
25 | assertEquals("com.quyunshuo.lib_comm.test", appContext.getPackageName());
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/lib_main/src/androidTest/java/com/quyunshuo/main/ExampleInstrumentedTest.java:
--------------------------------------------------------------------------------
1 | package com.quyunshuo.main;
2 |
3 | import android.content.Context;
4 |
5 | import androidx.test.platform.app.InstrumentationRegistry;
6 | import androidx.test.ext.junit.runners.AndroidJUnit4;
7 |
8 | import org.junit.Test;
9 | import org.junit.runner.RunWith;
10 |
11 | import static org.junit.Assert.*;
12 |
13 | /**
14 | * Instrumented test, which will execute on an Android device.
15 | *
16 | * @see Testing documentation
17 | */
18 | @RunWith(AndroidJUnit4.class)
19 | public class ExampleInstrumentedTest {
20 | @Test
21 | public void useAppContext() {
22 | // Context of the app under test.
23 | Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
24 |
25 | assertEquals("com.quyunshuo.lib_main.test", appContext.getPackageName());
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/app/src/androidTest/java/com/quyunshuo/androidbaseframe/ExampleInstrumentedTest.java:
--------------------------------------------------------------------------------
1 | package com.quyunshuo.androidbaseframe;
2 |
3 | import android.content.Context;
4 |
5 | import androidx.test.platform.app.InstrumentationRegistry;
6 | import androidx.test.ext.junit.runners.AndroidJUnit4;
7 |
8 | import org.junit.Test;
9 | import org.junit.runner.RunWith;
10 |
11 | import static org.junit.Assert.*;
12 |
13 | /**
14 | * Instrumented test, which will execute on an Android device.
15 | *
16 | * @see Testing documentation
17 | */
18 | @RunWith(AndroidJUnit4.class)
19 | public class ExampleInstrumentedTest {
20 | @Test
21 | public void useAppContext() {
22 | // Context of the app under test.
23 | Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
24 |
25 | assertEquals("com.quyunshuo.androidbaseframe", appContext.getPackageName());
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/lib_base/src/main/java/com/quyunshuo/base/base/mvp/BaseLifecycleFragment.java:
--------------------------------------------------------------------------------
1 | package com.quyunshuo.base.base.mvp;
2 |
3 | import android.os.Bundle;
4 | import android.view.View;
5 |
6 | import androidx.annotation.NonNull;
7 | import androidx.annotation.Nullable;
8 |
9 | import com.quyunshuo.base.base.BaseFragment;
10 | import com.quyunshuo.base.base.mvp.im.IPresenter;
11 |
12 | /**
13 | * @Author: QuYunShuo
14 | * @Time: 2020/4/12
15 | * @Class: BaseLifecycleFragment
16 | * @Remark:
17 | */
18 | public abstract class BaseLifecycleFragment extends BaseFragment {
19 |
20 | protected P mPresenter;
21 |
22 | @Override
23 | public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
24 | mPresenter = getPresenter();
25 | if (mPresenter != null) {
26 | getLifecycle().addObserver(mPresenter);
27 | }
28 | super.onViewCreated(view, savedInstanceState);
29 | }
30 |
31 | protected abstract P getPresenter();
32 |
33 | }
34 |
--------------------------------------------------------------------------------
/lib_base/src/main/java/com/quyunshuo/base/BaseApplication.java:
--------------------------------------------------------------------------------
1 | package com.quyunshuo.base;
2 |
3 | import android.app.Application;
4 |
5 | import com.alibaba.android.arouter.launcher.ARouter;
6 | import com.tencent.mmkv.MMKV;
7 |
8 | /**
9 | * @Author: QuYunShuo
10 | * @Time: 2020/4/12
11 | * @Class: BaseApplication
12 | * @Remark:
13 | */
14 | public class BaseApplication extends Application {
15 |
16 | private static BaseApplication mApplication;
17 |
18 | public static BaseApplication getApplication() {
19 | return mApplication;
20 | }
21 |
22 | @Override
23 | public void onCreate() {
24 | super.onCreate();
25 | mApplication = this;
26 | if (BuildConfig.IS_DEBUG) { // 这两行必须写在init之前,否则这些配置在init过程中将无效
27 | ARouter.openLog(); // 打印日志
28 | ARouter.openDebug(); // 开启调试模式(如果在InstantRun模式下运行,必须开启调试模式!线上版本需要关闭,否则有安全风险)
29 | }
30 | ARouter.init(mApplication); // 尽可能早,推荐在Application中初始化
31 | MMKV.initialize(this); // 初始化MMKV
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/lib_base/src/main/java/com/quyunshuo/base/base/mvp/im/IPresenter.java:
--------------------------------------------------------------------------------
1 | package com.quyunshuo.base.base.mvp.im;
2 |
3 | import androidx.lifecycle.Lifecycle;
4 | import androidx.lifecycle.LifecycleObserver;
5 | import androidx.lifecycle.LifecycleOwner;
6 | import androidx.lifecycle.OnLifecycleEvent;
7 |
8 | /**
9 | * @Author: QuYunShuo
10 | * @Time: 2020/4/12
11 | * @Class: BaseLifecyclePresenter
12 | * @Remark:
13 | */
14 | public interface IPresenter extends LifecycleObserver {
15 |
16 | @OnLifecycleEvent(Lifecycle.Event.ON_CREATE)
17 | void onCreate(LifecycleOwner owner);
18 |
19 | @OnLifecycleEvent(Lifecycle.Event.ON_START)
20 | void onStart(LifecycleOwner owner);
21 |
22 | @OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
23 | void onResume(LifecycleOwner owner);
24 |
25 | @OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
26 | void onPause(LifecycleOwner owner);
27 |
28 | @OnLifecycleEvent(Lifecycle.Event.ON_STOP)
29 | void onStop(LifecycleOwner owner);
30 |
31 | @OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
32 | void onDestroy(LifecycleOwner owner);
33 | }
34 |
--------------------------------------------------------------------------------
/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 | # AndroidX package structure to make it clearer which packages are bundled with the
15 | # Android operating system, and which are packaged with your app's APK
16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn
17 | android.useAndroidX=true
18 | # Automatically convert third-party libraries to use AndroidX
19 | android.enableJetifier=true
20 |
21 |
--------------------------------------------------------------------------------
/lib_base/src/main/java/com/quyunshuo/base/base/mvp/BaseLifecycleActivity.java:
--------------------------------------------------------------------------------
1 | package com.quyunshuo.base.base.mvp;
2 |
3 | import android.content.Context;
4 | import android.os.Bundle;
5 |
6 | import androidx.annotation.Nullable;
7 |
8 | import com.quyunshuo.base.base.BaseActivity;
9 | import com.quyunshuo.base.base.mvp.im.IPresenter;
10 | import com.quyunshuo.base.base.mvp.im.IView;
11 |
12 | /**
13 | * @Author: QuYunShuo
14 | * @Time: 2020/4/12
15 | * @Class: BaseLifecycleActivity
16 | * @Remark: ActivityMVP基类
17 | */
18 | public abstract class BaseLifecycleActivity
extends BaseActivity implements IView {
19 |
20 | /**
21 | * P层接口
22 | */
23 | protected P mPresenter;
24 |
25 | @Override
26 | protected void onCreate(@Nullable Bundle savedInstanceState) {
27 | mPresenter = getPresenter();
28 | if (mPresenter != null) {
29 | getLifecycle().addObserver(mPresenter);
30 | }
31 | super.onCreate(savedInstanceState);
32 | }
33 |
34 | @Override
35 | public Context getContext() {
36 | return this;
37 | }
38 |
39 | protected abstract P getPresenter();
40 |
41 | }
42 |
--------------------------------------------------------------------------------
/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 |
23 | #EventBus
24 | -keepattributes *Annotation*
25 | -keepclassmembers class * {
26 | @org.greenrobot.eventbus.Subscribe ;
27 | }
28 | -keep enum org.greenrobot.eventbus.ThreadMode { *; }
29 | # And if you use AsyncExecutor:
30 | -keepclassmembers class * extends org.greenrobot.eventbus.util.ThrowableFailureEvent {
31 | (java.lang.Throwable);
32 | }
--------------------------------------------------------------------------------
/lib_main/src/main/java/debug/DebugMainActivity.java:
--------------------------------------------------------------------------------
1 | package debug;
2 |
3 | import android.view.View;
4 |
5 | import androidx.fragment.app.FragmentManager;
6 | import androidx.fragment.app.FragmentTransaction;
7 |
8 | import com.alibaba.android.arouter.launcher.ARouter;
9 | import com.quyunshuo.base.base.BaseActivity;
10 | import com.quyunshuo.comm.route.RouteUrl;
11 | import com.quyunshuo.main.R;
12 | import com.quyunshuo.main.databinding.MainActivityDebugBinding;
13 | import com.quyunshuo.main.ui.HomeFragment;
14 |
15 | public class DebugMainActivity extends BaseActivity {
16 |
17 | private MainActivityDebugBinding mViewBinding;
18 |
19 | @Override
20 | protected View getViewBinding() {
21 | mViewBinding = MainActivityDebugBinding.inflate(getLayoutInflater());
22 | return mViewBinding.getRoot();
23 | }
24 |
25 | @Override
26 | protected void initView() {
27 | HomeFragment homeFragment = (HomeFragment) ARouter.getInstance().build(RouteUrl.HOME_FRAGMENT).navigation();
28 |
29 | FragmentManager supportFragmentManager = getSupportFragmentManager();
30 | FragmentTransaction fragmentTransaction = supportFragmentManager.beginTransaction();
31 | fragmentTransaction.add(R.id.fragment_layout, homeFragment);
32 | fragmentTransaction.commit();
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/lib_base/src/main/java/com/quyunshuo/base/capsulation/eventbus/EventBusUtil.java:
--------------------------------------------------------------------------------
1 | package com.quyunshuo.base.capsulation.eventbus;
2 |
3 | import org.greenrobot.eventbus.EventBus;
4 |
5 | /**
6 | * @Author: QuYunShuo
7 | * @Time: 2020/4/12
8 | * @Class: EventBusUtil
9 | * @Remark: EventBus用法封装
10 | */
11 | public class EventBusUtil {
12 |
13 | /**
14 | * 注册
15 | */
16 | public static void register(Object object) {
17 | EventBus.getDefault().register(object);
18 | }
19 |
20 | /**
21 | * 解除注册
22 | */
23 | public static void unRegister(Object object) {
24 | EventBus.getDefault().unregister(object);
25 | }
26 |
27 | /**
28 | * 发送普通事件
29 | */
30 | public static void postEvent(Object event) {
31 | EventBus.getDefault().post(event);
32 | }
33 |
34 | /**
35 | * 发送粘性事件
36 | */
37 | public static void postStickyEvent(Object event) {
38 | EventBus.getDefault().postSticky(event);
39 | }
40 |
41 | /**
42 | * 手动获取粘性事件
43 | */
44 | public static T getStickyEvent(Class eventType) {
45 | return EventBus.getDefault().getStickyEvent(eventType);
46 | }
47 |
48 | /**
49 | * 手动删除粘性事件
50 | */
51 | public static T removeStickyEvent(Class eventType) {
52 | return EventBus.getDefault().removeStickyEvent(eventType);
53 | }
54 | }
--------------------------------------------------------------------------------
/lib_main/src/main/res/layout/main_activity.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
18 |
19 |
28 |
--------------------------------------------------------------------------------
/lib_xxx/src/main/java/debug/DebugXXXActivity.java:
--------------------------------------------------------------------------------
1 | package debug;
2 |
3 | import android.view.View;
4 |
5 | import androidx.fragment.app.FragmentManager;
6 | import androidx.fragment.app.FragmentTransaction;
7 |
8 | import com.alibaba.android.arouter.launcher.ARouter;
9 | import com.quyunshuo.base.base.BaseActivity;
10 | import com.quyunshuo.comm.route.RouteUrl;
11 | import com.quyunshuo.xxx.R;
12 | import com.quyunshuo.xxx.databinding.XxxActivityDebugBinding;
13 | import com.quyunshuo.xxx.ui.XxxFragment;
14 |
15 | /**
16 | * @Author: QuYunShuo
17 | * @Time: 2020/4/12
18 | * @Class: DebugXXXActivity
19 | * @Remark:
20 | */
21 | public class DebugXXXActivity extends BaseActivity {
22 |
23 | private XxxActivityDebugBinding mViewBinding;
24 |
25 | @Override
26 | protected View getViewBinding() {
27 | mViewBinding = XxxActivityDebugBinding.inflate(getLayoutInflater());
28 | return mViewBinding.getRoot();
29 | }
30 |
31 | @Override
32 | protected void initView() {
33 | XxxFragment homeFragment = (XxxFragment) ARouter.getInstance().build(RouteUrl.XXX_FRAGMENT).navigation();
34 |
35 | FragmentManager supportFragmentManager = getSupportFragmentManager();
36 | FragmentTransaction fragmentTransaction = supportFragmentManager.beginTransaction();
37 | fragmentTransaction.add(R.id.fragment_layout, homeFragment);
38 | fragmentTransaction.commit();
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/lib_xxx/src/main/java/com/quyunshuo/xxx/ui/XxxFragment.java:
--------------------------------------------------------------------------------
1 | package com.quyunshuo.xxx.ui;
2 |
3 | import android.view.LayoutInflater;
4 | import android.view.View;
5 | import android.widget.Toast;
6 |
7 | import androidx.annotation.NonNull;
8 |
9 | import com.alibaba.android.arouter.facade.annotation.Route;
10 | import com.quyunshuo.base.base.mvp.BaseLifecycleFragment;
11 | import com.quyunshuo.base.capsulation.eventbus.EventBusBinding;
12 | import com.quyunshuo.comm.route.RouteUrl;
13 | import com.quyunshuo.xxx.databinding.XxxFragmentBinding;
14 |
15 | import org.greenrobot.eventbus.Subscribe;
16 |
17 | /**
18 | * @Author: QuYunShuo
19 | * @Time: 2020/4/12
20 | * @Class: XxxFragment
21 | * @Remark:
22 | */
23 | @EventBusBinding
24 | @Route(path = RouteUrl.XXX_FRAGMENT)
25 | public class XxxFragment extends BaseLifecycleFragment implements XxxContract.View {
26 |
27 | private XxxFragmentBinding mViewBinding;
28 |
29 | @Override
30 | protected XxxContract.Presenter getPresenter() {
31 | return new XxxPresenter(this);
32 | }
33 |
34 | @Override
35 | protected View initViewBinding(@NonNull LayoutInflater inflater) {
36 | mViewBinding = XxxFragmentBinding.inflate(inflater);
37 | return mViewBinding.getRoot();
38 | }
39 |
40 | @Override
41 | protected void initView() {
42 | mViewBinding.tv.setOnClickListener(v -> Toast.makeText(getContext(), "Test", Toast.LENGTH_SHORT).show());
43 | }
44 |
45 | /**
46 | * 为了生成索引类 每个lib必须要有一个订阅方法 如果有了其他的订阅方法 此订阅方法可以删除
47 | */
48 | @Subscribe
49 | public void testEventIndex(String event) {
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/lib_main/src/main/java/com/quyunshuo/main/ui/HomeFragment.java:
--------------------------------------------------------------------------------
1 | package com.quyunshuo.main.ui;
2 |
3 | import android.view.LayoutInflater;
4 | import android.view.View;
5 | import android.widget.Toast;
6 |
7 | import androidx.annotation.NonNull;
8 |
9 | import com.alibaba.android.arouter.facade.annotation.Route;
10 | import com.quyunshuo.base.base.mvp.BaseLifecycleFragment;
11 | import com.quyunshuo.base.capsulation.eventbus.EventBusBinding;
12 | import com.quyunshuo.comm.route.RouteUrl;
13 | import com.quyunshuo.main.databinding.MainHomeFragmentBinding;
14 |
15 | import org.greenrobot.eventbus.Subscribe;
16 |
17 | /**
18 | * @Author: QuYunShuo
19 | * @Time: 2020/4/12
20 | * @Class: HomeFragment
21 | * @Remark:
22 | */
23 | @EventBusBinding
24 | @Route(path = RouteUrl.HOME_FRAGMENT)
25 | public class HomeFragment extends BaseLifecycleFragment implements HomeContract.View {
26 |
27 | private MainHomeFragmentBinding mViewBinding;
28 |
29 | @Override
30 | protected HomeContract.Presenter getPresenter() {
31 | return new HomePresenter(this);
32 | }
33 |
34 | @Override
35 | protected View initViewBinding(@NonNull LayoutInflater inflater) {
36 | mViewBinding = MainHomeFragmentBinding.inflate(inflater);
37 | return mViewBinding.getRoot();
38 | }
39 |
40 | @Override
41 | protected void initView() {
42 | mViewBinding.tv.setOnClickListener(v -> Toast.makeText(getContext(), "Test", Toast.LENGTH_SHORT).show());
43 | }
44 |
45 | /**
46 | * 为了生成索引类 每个lib必须要有一个订阅方法 如果有了其他的订阅方法 此订阅方法可以删除
47 | */
48 | @Subscribe
49 | public void testEventIndex(String event) {
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable-v24/ic_launcher_foreground.xml:
--------------------------------------------------------------------------------
1 |
7 |
8 |
9 |
15 |
18 |
21 |
22 |
23 |
24 |
30 |
--------------------------------------------------------------------------------
/lib_comm/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.library'
2 |
3 | def config = rootProject.ext
4 |
5 | android {
6 | compileSdkVersion config.android.compileSdkVersion
7 | buildToolsVersion config.android.buildToolsVersion
8 |
9 | defaultConfig {
10 | minSdkVersion config.android.minSdkVersion
11 | targetSdkVersion config.android.targetSdkVersion
12 | versionCode config.android.versionCode
13 | versionName config.android.versionName
14 |
15 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
16 | consumerProguardFiles 'consumer-rules.pro'
17 |
18 | javaCompileOptions {
19 | annotationProcessorOptions {
20 | arguments = [AROUTER_MODULE_NAME: project.getName()]
21 | }
22 | }
23 | }
24 |
25 | buildTypes {
26 | debug {
27 | minifyEnabled false
28 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
29 | }
30 |
31 | alpha {
32 | minifyEnabled false
33 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
34 | }
35 |
36 | release {
37 | minifyEnabled false
38 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
39 | }
40 | }
41 |
42 | compileOptions {
43 | sourceCompatibility JavaVersion.VERSION_1_8
44 | targetCompatibility JavaVersion.VERSION_1_8
45 | }
46 |
47 | resourcePrefix "comm_"
48 | }
49 |
50 | dependencies {
51 | implementation fileTree(dir: 'libs', include: ['*.jar'])
52 |
53 | testImplementation 'junit:junit:4.12'
54 | androidTestImplementation 'androidx.test.ext:junit:1.1.1'
55 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
56 |
57 | api project(path: ':lib_base')
58 | }
59 |
--------------------------------------------------------------------------------
/lib_base/src/main/java/com/quyunshuo/base/base/mvp/BaseLifecyclePresenter.java:
--------------------------------------------------------------------------------
1 | package com.quyunshuo.base.base.mvp;
2 |
3 | import android.util.Log;
4 |
5 | import androidx.annotation.StringRes;
6 | import androidx.lifecycle.LifecycleOwner;
7 |
8 | import com.quyunshuo.base.base.mvp.im.IPresenter;
9 | import com.quyunshuo.base.base.mvp.im.IView;
10 |
11 | import java.lang.ref.WeakReference;
12 |
13 | /**
14 | * @Author: QuYunShuo
15 | * @Time: 2020/4/12
16 | * @Class: BaseLifecyclePresenter
17 | * @Remark:
18 | */
19 | public abstract class BaseLifecyclePresenter implements IPresenter {
20 |
21 | private WeakReference mWeakReference;
22 |
23 | public BaseLifecyclePresenter(V view) {
24 | mWeakReference = new WeakReference(view);
25 | }
26 |
27 | protected V getView() {
28 | if (mWeakReference != null && mWeakReference.get() != null) {
29 | return mWeakReference.get();
30 | }
31 | return null;
32 | }
33 |
34 | protected String getString(@StringRes int id) {
35 | return getView().getContext().getString(id);
36 | }
37 |
38 | protected String getString(@StringRes int id, Object... formatArgs) {
39 | return getView().getContext().getString(id, formatArgs);
40 | }
41 |
42 | @Override
43 | public void onCreate(LifecycleOwner owner) {
44 |
45 | }
46 |
47 | @Override
48 | public void onStart(LifecycleOwner owner) {
49 |
50 | }
51 |
52 | @Override
53 | public void onResume(LifecycleOwner owner) {
54 |
55 | }
56 |
57 | @Override
58 | public void onPause(LifecycleOwner owner) {
59 |
60 | }
61 |
62 | @Override
63 | public void onStop(LifecycleOwner owner) {
64 |
65 | }
66 |
67 | @Override
68 | public void onDestroy(LifecycleOwner owner) {
69 | mWeakReference.clear();
70 | mWeakReference = null;
71 | Log.d("miyan", "super.onDestroy: ");
72 |
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 |
3 | def config = rootProject.ext
4 |
5 | android {
6 | compileSdkVersion config.android.compileSdkVersion
7 | buildToolsVersion config.android.buildToolsVersion
8 |
9 | defaultConfig {
10 | applicationId config.android.applicationId
11 | minSdkVersion config.android.minSdkVersion
12 | targetSdkVersion config.android.targetSdkVersion
13 | versionCode config.android.versionCode
14 | versionName config.android.versionName
15 |
16 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
17 |
18 | javaCompileOptions {
19 | annotationProcessorOptions {
20 | arguments = [AROUTER_MODULE_NAME: project.getName()]
21 | }
22 | }
23 | }
24 |
25 | buildTypes {
26 | debug {
27 | minifyEnabled false
28 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
29 | }
30 |
31 | alpha {
32 | minifyEnabled false
33 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
34 | }
35 |
36 | release {
37 | minifyEnabled false
38 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
39 | }
40 | }
41 |
42 | compileOptions {
43 | sourceCompatibility JavaVersion.VERSION_1_8
44 | targetCompatibility JavaVersion.VERSION_1_8
45 | }
46 | }
47 |
48 | dependencies {
49 | implementation fileTree(dir: 'libs', include: ['*.jar'])
50 |
51 | testImplementation 'junit:junit:4.12'
52 | androidTestImplementation 'androidx.test.ext:junit:1.1.1'
53 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
54 |
55 | if (!rootProject.ext.isApp) {
56 | implementation project(path: ':lib_main')
57 | implementation project(path: ':lib_xxx')
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/config.gradle:
--------------------------------------------------------------------------------
1 | ext {
2 | // 控制lib的模式
3 | isApp = false
4 |
5 | // 项目配置版本相关
6 | android = [
7 | applicationId : "com.quyunshuo.androidbaseframe",
8 | compileSdkVersion: 29,
9 | buildToolsVersion: "29.0.3",
10 | minSdkVersion : 19,
11 | targetSdkVersion : 29,
12 | versionCode : 1,
13 | versionName : "1.0",
14 | ]
15 |
16 | // 全局依赖版本相关
17 | base = [
18 | "AppCompat" : 'androidx.appcompat:appcompat:1.1.0',
19 | "ConstraintLayout" : 'androidx.constraintlayout:constraintlayout:1.1.3',
20 | "EventBus" : 'org.greenrobot:eventbus:3.2.0',
21 | "ARouter" : 'com.alibaba:arouter-api:1.5.0',
22 | "MMKV" : 'com.tencent:mmkv-static:1.1.0',
23 | "RecyclerView" : 'androidx.recyclerview:recyclerview:1.0.0',
24 | "AdapterHelper" : 'com.github.CymChad:BaseRecyclerViewAdapterHelper:3.0.2',
25 | "RxAndroid" : 'io.reactivex.rxjava2:rxandroid:2.1.1',
26 | "RxJava" : 'io.reactivex.rxjava2:rxjava:2.1.1',
27 | "OkHttp" : 'com.squareup.okhttp3:okhttp:3.10.0',
28 | "Retrofit" : 'com.squareup.retrofit2:retrofit:2.4.0',
29 | "Converter-Gson" : 'com.squareup.retrofit2:converter-gson:2.4.0',
30 | "Adapter-RxJava" : 'com.squareup.retrofit2:adapter-rxjava2:2.4.0',
31 | "Logging-Interceptor": 'com.squareup.okhttp3:logging-interceptor:3.9.1',
32 | "Gson" : 'com.google.code.gson:gson:2.8.5',
33 | "Glide" : 'com.github.bumptech.glide:glide:4.10.0',
34 | ]
35 |
36 | // 注解相关
37 | annotationProcessors = [
38 | "Arouter-Compiler" : 'com.alibaba:arouter-compiler:1.2.2',
39 | "Glide" : 'com.github.bumptech.glide:compiler:4.10.0',
40 | "EventBus-Annotation-Processor": 'org.greenrobot:eventbus-annotation-processor:3.2.0'
41 | ]
42 | }
--------------------------------------------------------------------------------
/lib_base/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.library'
2 |
3 | def config = rootProject.ext
4 |
5 | android {
6 | compileSdkVersion config.android.compileSdkVersion
7 | buildToolsVersion config.android.buildToolsVersion
8 |
9 | defaultConfig {
10 | minSdkVersion config.android.minSdkVersion
11 | targetSdkVersion config.android.targetSdkVersion
12 | versionCode config.android.versionCode
13 | versionName config.android.versionName
14 |
15 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
16 | consumerProguardFiles 'consumer-rules.pro'
17 |
18 | javaCompileOptions {
19 | annotationProcessorOptions {
20 | arguments = [AROUTER_MODULE_NAME: project.getName()]
21 | }
22 | }
23 | }
24 |
25 | buildTypes {
26 | debug {
27 | buildConfigField "boolean", "IS_DEBUG", "true"
28 | minifyEnabled false
29 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
30 | }
31 |
32 | alpha {
33 | buildConfigField "boolean", "IS_DEBUG", "true"
34 | minifyEnabled false
35 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
36 | }
37 |
38 | release {
39 | buildConfigField "boolean", "IS_DEBUG", "false"
40 | minifyEnabled false
41 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
42 | }
43 | }
44 |
45 | compileOptions {
46 | sourceCompatibility JavaVersion.VERSION_1_8
47 | targetCompatibility JavaVersion.VERSION_1_8
48 | }
49 |
50 | resourcePrefix "base_"
51 | }
52 |
53 | dependencies {
54 | implementation fileTree(dir: 'libs', include: ['*.jar'])
55 |
56 | testImplementation 'junit:junit:4.12'
57 | androidTestImplementation 'androidx.test.ext:junit:1.1.1'
58 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
59 |
60 | config.base.each { key, value -> api value }
61 | }
62 |
--------------------------------------------------------------------------------
/lib_main/src/main/java/com/quyunshuo/main/ui/MainActivity.java:
--------------------------------------------------------------------------------
1 | package com.quyunshuo.main.ui;
2 |
3 | import android.view.View;
4 |
5 | import androidx.fragment.app.FragmentManager;
6 | import androidx.fragment.app.FragmentTransaction;
7 |
8 | import com.alibaba.android.arouter.facade.annotation.Route;
9 | import com.alibaba.android.arouter.launcher.ARouter;
10 | import com.quyunshuo.base.base.BaseFragment;
11 | import com.quyunshuo.base.base.mvp.BaseLifecycleActivity;
12 | import com.quyunshuo.base.capsulation.eventbus.EventBusBinding;
13 | import com.quyunshuo.comm.route.RouteUrl;
14 | import com.quyunshuo.main.R;
15 | import com.quyunshuo.main.databinding.MainActivityBinding;
16 |
17 | /**
18 | * @Author: QuYunShuo
19 | * @Time: 2020/4/12
20 | * @Class: MainActivity
21 | * @Remark:
22 | */
23 | @EventBusBinding
24 | @Route(path = RouteUrl.MAIN_ACTIVITY)
25 | public class MainActivity extends BaseLifecycleActivity implements MainContract.View {
26 |
27 | private MainActivityBinding mViewBinding;
28 |
29 | @Override
30 | protected View getViewBinding() {
31 | mViewBinding = MainActivityBinding.inflate(getLayoutInflater());
32 | return mViewBinding.getRoot();
33 | }
34 |
35 | @Override
36 | protected void initView() {
37 | BaseFragment homeFragment = (BaseFragment) ARouter.getInstance().build(RouteUrl.HOME_FRAGMENT).navigation();
38 | BaseFragment xxxFragment = (BaseFragment) ARouter.getInstance().build(RouteUrl.XXX_FRAGMENT).navigation();
39 |
40 | FragmentManager supportFragmentManager = getSupportFragmentManager();
41 | FragmentTransaction fragmentTransaction = supportFragmentManager.beginTransaction();
42 | fragmentTransaction.add(R.id.frame_layout_1, homeFragment);
43 | fragmentTransaction.add(R.id.frame_layout_2, xxxFragment);
44 |
45 | fragmentTransaction.commit();
46 | }
47 |
48 | @Override
49 | protected MainContract.Presenter getPresenter() {
50 | return new MainPresenter(this);
51 | }
52 |
53 | @Override
54 | protected void onDestroy() {
55 | super.onDestroy();
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/module.gradle:
--------------------------------------------------------------------------------
1 | // 业务lib公共配置
2 | if (rootProject.ext.isApp) {
3 | apply plugin: 'com.android.application'
4 | } else {
5 | apply plugin: 'com.android.library'
6 | }
7 |
8 | def config = rootProject.ext
9 |
10 | android {
11 | compileSdkVersion config.android.compileSdkVersion
12 | buildToolsVersion config.android.buildToolsVersion
13 |
14 | defaultConfig {
15 | minSdkVersion config.android.minSdkVersion
16 | targetSdkVersion config.android.targetSdkVersion
17 | versionCode config.android.versionCode
18 | versionName config.android.versionName
19 |
20 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
21 | consumerProguardFiles 'consumer-rules.pro'
22 | }
23 |
24 | buildTypes {
25 | debug {
26 | minifyEnabled false
27 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
28 | }
29 |
30 | alpha {
31 | minifyEnabled false
32 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
33 | }
34 |
35 | release {
36 | minifyEnabled false
37 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
38 | }
39 | }
40 |
41 | compileOptions {
42 | sourceCompatibility JavaVersion.VERSION_1_8
43 | targetCompatibility JavaVersion.VERSION_1_8
44 | }
45 |
46 | //根据不同的模式加载不同的AndroidManifest文件
47 | sourceSets {
48 | main {
49 | if (rootProject.ext.isApp) {
50 | manifest.srcFile 'src/main/debug/AndroidManifest.xml'
51 | } else {
52 | manifest.srcFile 'src/main/AndroidManifest.xml'
53 | java {
54 | //排除debug文件夹下的所有文件
55 | exclude 'debug/**'
56 | }
57 | }
58 | }
59 | }
60 | }
61 |
62 | dependencies {
63 | implementation fileTree(dir: 'libs', include: ['*.jar'])
64 |
65 | api project(path: ':lib_comm')
66 |
67 | config.annotationProcessors.each { key, value -> annotationProcessor value }
68 | }
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/lib_base/src/main/java/com/quyunshuo/base/base/BaseActivity.java:
--------------------------------------------------------------------------------
1 | package com.quyunshuo.base.base;
2 |
3 | import android.animation.Animator;
4 | import android.animation.AnimatorSet;
5 | import android.content.Context;
6 | import android.os.Bundle;
7 | import android.view.View;
8 |
9 | import androidx.annotation.Nullable;
10 | import androidx.appcompat.app.AppCompatActivity;
11 |
12 | import com.quyunshuo.base.capsulation.eventbus.BaseEvent;
13 | import com.quyunshuo.base.capsulation.eventbus.EventBusBinding;
14 | import com.quyunshuo.base.capsulation.eventbus.EventBusUtil;
15 |
16 | import org.greenrobot.eventbus.Subscribe;
17 |
18 | import java.util.ArrayList;
19 |
20 | /**
21 | * @Author: QuYunShuo
22 | * @Time: 2020/4/12
23 | * @Class: BaseActivity
24 | * @Remark: Activity基类
25 | */
26 | public abstract class BaseActivity extends AppCompatActivity {
27 |
28 | /**
29 | * AnimatorList
30 | */
31 | protected ArrayList mAnimators = new ArrayList<>();
32 |
33 | /**
34 | * AnimatorSetList
35 | */
36 | protected ArrayList mAnimatorSets = new ArrayList<>();
37 |
38 | @Override
39 | protected void onCreate(@Nullable Bundle savedInstanceState) {
40 | super.onCreate(savedInstanceState);
41 | // setContentView
42 | if (isOpenViewBinding()) {
43 | setContentView(getViewBinding());
44 | } else {
45 | setContentView(getLayoutId());
46 | }
47 | // EventBus注册
48 | if (this.getClass().isAnnotationPresent(EventBusBinding.class)) {
49 | EventBusUtil.register(this);
50 | }
51 | initView();
52 | }
53 |
54 | /**
55 | * 获取ViewBinding
56 | */
57 | protected abstract View getViewBinding();
58 |
59 | /**
60 | * 初始化View
61 | */
62 | protected abstract void initView();
63 |
64 | /**
65 | * 如果不使用ViewBinding,需要重写此方法传入布局Id,并重写isOpenViewBinding返回false
66 | */
67 | protected int getLayoutId() {
68 | return 0;
69 | }
70 |
71 | /**
72 | * 是否开启了ViewBinding
73 | */
74 | protected boolean isOpenViewBinding() {
75 | return true;
76 | }
77 |
78 | /**
79 | * 简化findViewById
80 | *
81 | * @param id 控件id
82 | * @param 控件类型
83 | * @return view
84 | */
85 | protected T find(int id) {
86 | return findViewById(id);
87 | }
88 |
89 | public Context getContext() {
90 | return this;
91 | }
92 |
93 | @Subscribe
94 | public void getEvent(BaseEvent even) {
95 | }
96 |
97 | @Override
98 | protected void onDestroy() {
99 | for (Animator animator : mAnimators) {
100 | animator.removeAllListeners();
101 | animator.cancel();
102 | }
103 | mAnimators.clear();
104 | mAnimators = null;
105 |
106 | for (AnimatorSet animatorSet : mAnimatorSets) {
107 | animatorSet.removeAllListeners();
108 | animatorSet.cancel();
109 | }
110 | mAnimatorSets.clear();
111 | mAnimatorSets = null;
112 | super.onDestroy();
113 | // EventBus解除注册
114 | if (this.getClass().isAnnotationPresent(EventBusBinding.class)) {
115 | EventBusUtil.unRegister(this);
116 | }
117 | }
118 | }
119 |
--------------------------------------------------------------------------------
/lib_base/src/main/java/com/quyunshuo/base/capsulation/mmkv/SPUtil.java:
--------------------------------------------------------------------------------
1 | package com.quyunshuo.base.capsulation.mmkv;
2 |
3 | import com.tencent.mmkv.MMKV;
4 |
5 | /**
6 | * @Author: QuYunShuo
7 | * @Time: 2020/4/13
8 | * @Class: MMKVUtil
9 | * @Remark: 使用MMKV替代SharedPreferences 对使用进行简单封装
10 | */
11 | public final class SPUtil {
12 |
13 | public static void put(String key, Object value) {
14 | if (value instanceof String) {
15 | putString(key, (String) value);
16 | } else if (value instanceof Integer) {
17 | putInt(key, (int) value);
18 | } else if (value instanceof Long) {
19 | putLong(key, (long) value);
20 | } else if (value instanceof Float) {
21 | putFloat(key, (float) value);
22 | } else if (value instanceof Double) {
23 | putDouble(key, (double) value);
24 | } else if (value instanceof Boolean) {
25 | putBoolean(key, (boolean) value);
26 | }
27 | }
28 |
29 | public static void putString(String key, String value) {
30 | MMKV.defaultMMKV().encode(key, value);
31 | }
32 |
33 | public static String getString(String key) {
34 | return MMKV.defaultMMKV().decodeString(key, "");
35 | }
36 |
37 | public static String getString(String key, String s) {
38 | return MMKV.defaultMMKV().decodeString(key, s);
39 | }
40 |
41 | public static void putInt(String key, int value) {
42 | MMKV.defaultMMKV().encode(key, value);
43 | }
44 |
45 | public static int getInt(String key) {
46 | return MMKV.defaultMMKV().decodeInt(key, -1);
47 | }
48 |
49 | public static int getInt(String key, int def) {
50 | return MMKV.defaultMMKV().decodeInt(key, def);
51 | }
52 |
53 | public static void putLong(String key, long value) {
54 | MMKV.defaultMMKV().encode(key, value);
55 | }
56 |
57 | public static long getLong(String key) {
58 | return MMKV.defaultMMKV().decodeLong(key, -1L);
59 | }
60 |
61 | public static long getLong(String key, long defValue) {
62 | return MMKV.defaultMMKV().decodeLong(key, defValue);
63 | }
64 |
65 | public static void putFloat(String key, float value) {
66 | MMKV.defaultMMKV().encode(key, value);
67 | }
68 |
69 | public static float getFloat(String key) {
70 | return MMKV.defaultMMKV().decodeFloat(key, -1.0f);
71 | }
72 |
73 | public static float getFloat(String key, float def) {
74 | return MMKV.defaultMMKV().decodeFloat(key, def);
75 | }
76 |
77 | public static void putDouble(String key, double value) {
78 | MMKV.defaultMMKV().encode(key, value);
79 | }
80 |
81 | public static double getDouble(String key) {
82 | return MMKV.defaultMMKV().decodeDouble(key, -1.0D);
83 | }
84 |
85 | public static double getDouble(String key, double def) {
86 | return MMKV.defaultMMKV().decodeDouble(key, def);
87 | }
88 |
89 | public static void putBoolean(String key, boolean value) {
90 | MMKV.defaultMMKV().encode(key, value);
91 | }
92 |
93 | public static boolean getBoolean(String key) {
94 | return MMKV.defaultMMKV().decodeBool(key, false);
95 | }
96 |
97 | public static boolean getBoolean(String key, boolean defaultValue) {
98 | return MMKV.defaultMMKV().decodeBool(key, defaultValue);
99 | }
100 | }
101 |
--------------------------------------------------------------------------------
/lib_base/src/main/java/com/quyunshuo/base/base/BaseFragment.java:
--------------------------------------------------------------------------------
1 | package com.quyunshuo.base.base;
2 |
3 | import android.animation.Animator;
4 | import android.animation.AnimatorSet;
5 | import android.os.Bundle;
6 | import android.view.LayoutInflater;
7 | import android.view.View;
8 | import android.view.ViewGroup;
9 |
10 | import androidx.annotation.NonNull;
11 | import androidx.annotation.Nullable;
12 | import androidx.fragment.app.Fragment;
13 |
14 | import com.quyunshuo.base.capsulation.eventbus.BaseEvent;
15 | import com.quyunshuo.base.capsulation.eventbus.EventBusBinding;
16 | import com.quyunshuo.base.capsulation.eventbus.EventBusUtil;
17 |
18 | import org.greenrobot.eventbus.Subscribe;
19 |
20 | import java.util.ArrayList;
21 |
22 | /**
23 | * @Author: QuYunShuo
24 | * @Time: 2020/4/12
25 | * @Class: BaseFragment
26 | * @Remark: Fragment基类
27 | */
28 | public abstract class BaseFragment extends Fragment {
29 |
30 | protected ArrayList mAnimators = new ArrayList<>();
31 |
32 | protected ArrayList mAnimatorSets = new ArrayList<>();
33 |
34 | protected View mFragmentView;
35 |
36 | @Override
37 | public void onCreate(@Nullable Bundle savedInstanceState) {
38 | super.onCreate(savedInstanceState);
39 | // EventBus注册
40 | if (this.getClass().isAnnotationPresent(EventBusBinding.class)) {
41 | EventBusUtil.register(this);
42 | }
43 | }
44 |
45 | @Nullable
46 | @Override
47 | public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
48 | if (isFragmentOpenViewBinding()) {
49 | mFragmentView = initViewBinding(inflater);
50 | } else {
51 | mFragmentView = inflater.inflate(getLayoutId(), container, false);
52 | }
53 | return mFragmentView;
54 | }
55 |
56 | @Override
57 | public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
58 | super.onViewCreated(view, savedInstanceState);
59 | initView();
60 | }
61 |
62 | /**
63 | * 初始化ViewBinding
64 | * 示例:
65 | * protected View initViewBinding(@NonNull LayoutInflater inflater) {
66 | * mBinding = FragmentBlankBinding.inflate(inflater);
67 | * return mBinding.getRoot();
68 | * }
69 | */
70 | protected abstract View initViewBinding(@NonNull LayoutInflater inflater);
71 |
72 | /**
73 | * 初始化View
74 | */
75 | protected abstract void initView();
76 |
77 | /**
78 | * 如果不使用ViewBinding,需要重写此方法传入布局Id,并重写isFragmentOpenViewBinding返回false
79 | */
80 | protected int getLayoutId() {
81 | return 0;
82 | }
83 |
84 | /**
85 | * 是否开启了ViewBinding
86 | */
87 | protected boolean isFragmentOpenViewBinding() {
88 | return true;
89 | }
90 |
91 | /**
92 | * 简化findViewById
93 | *
94 | * @param id 控件id
95 | * @param 控件类型
96 | * @return view
97 | */
98 | protected T find(int id) {
99 | return mFragmentView.findViewById(id);
100 | }
101 |
102 | @Subscribe
103 | public void getEvent(BaseEvent event) {
104 | }
105 |
106 | @Override
107 | public void onDestroyView() {
108 | for (Animator animator : mAnimators) {
109 | animator.removeAllListeners();
110 | animator.cancel();
111 | }
112 | mAnimators.clear();
113 | mAnimators = null;
114 |
115 | for (AnimatorSet animatorSet : mAnimatorSets) {
116 | animatorSet.removeAllListeners();
117 | animatorSet.cancel();
118 | }
119 | mAnimatorSets.clear();
120 | mAnimatorSets = null;
121 | super.onDestroyView();
122 | }
123 |
124 | @Override
125 | public void onDestroy() {
126 | super.onDestroy();
127 | // EventBus解除注册
128 | if (this.getClass().isAnnotationPresent(EventBusBinding.class)) {
129 | EventBusUtil.unRegister(this);
130 | }
131 | }
132 | }
133 |
--------------------------------------------------------------------------------
/lib_base/src/main/java/com/quyunshuo/base/utils/SizeUtils.java:
--------------------------------------------------------------------------------
1 | package com.quyunshuo.base.utils;
2 |
3 | import android.content.res.Resources;
4 | import android.util.DisplayMetrics;
5 | import android.util.TypedValue;
6 | import android.view.View;
7 | import android.view.ViewGroup;
8 |
9 | /**
10 | * 大小单位转换
11 | */
12 | public final class SizeUtils {
13 |
14 | private SizeUtils() {
15 | throw new UnsupportedOperationException("u can't instantiate me...");
16 | }
17 |
18 | /**
19 | * Value of dp to value of px.
20 | *
21 | * @param dpValue The value of dp.
22 | * @return value of px
23 | */
24 | public static int dp2px(final float dpValue) {
25 | final float scale = Resources.getSystem().getDisplayMetrics().density;
26 | return (int) (dpValue * scale + 0.5f);
27 | }
28 |
29 | /**
30 | * Value of px to value of dp.
31 | *
32 | * @param pxValue The value of px.
33 | * @return value of dp
34 | */
35 | public static int px2dp(final float pxValue) {
36 | final float scale = Resources.getSystem().getDisplayMetrics().density;
37 | return (int) (pxValue / scale + 0.5f);
38 | }
39 |
40 | /**
41 | * Value of sp to value of px.
42 | *
43 | * @param spValue The value of sp.
44 | * @return value of px
45 | */
46 | public static int sp2px(final float spValue) {
47 | final float fontScale = Resources.getSystem().getDisplayMetrics().scaledDensity;
48 | return (int) (spValue * fontScale + 0.5f);
49 | }
50 |
51 | /**
52 | * Value of px to value of sp.
53 | *
54 | * @param pxValue The value of px.
55 | * @return value of sp
56 | */
57 | public static int px2sp(final float pxValue) {
58 | final float fontScale = Resources.getSystem().getDisplayMetrics().scaledDensity;
59 | return (int) (pxValue / fontScale + 0.5f);
60 | }
61 |
62 | /**
63 | * Converts an unpacked complex data value holding a dimension to its final floating
64 | * point value. The two parameters unit and value
65 | * are as in {@link TypedValue#TYPE_DIMENSION}.
66 | *
67 | * @param value The value to apply the unit to.
68 | * @param unit The unit to convert from.
69 | * @return The complex floating point value multiplied by the appropriate
70 | * metrics depending on its unit.
71 | */
72 | public static float applyDimension(final float value, final int unit) {
73 | DisplayMetrics metrics = Resources.getSystem().getDisplayMetrics();
74 | switch (unit) {
75 | case TypedValue.COMPLEX_UNIT_PX:
76 | return value;
77 | case TypedValue.COMPLEX_UNIT_DIP:
78 | return value * metrics.density;
79 | case TypedValue.COMPLEX_UNIT_SP:
80 | return value * metrics.scaledDensity;
81 | case TypedValue.COMPLEX_UNIT_PT:
82 | return value * metrics.xdpi * (1.0f / 72);
83 | case TypedValue.COMPLEX_UNIT_IN:
84 | return value * metrics.xdpi;
85 | case TypedValue.COMPLEX_UNIT_MM:
86 | return value * metrics.xdpi * (1.0f / 25.4f);
87 | }
88 | return 0;
89 | }
90 |
91 | /**
92 | * Force get the size of view.
93 | * e.g.
94 | *
95 | * SizeUtils.forceGetViewSize(view, new SizeUtils.onGetSizeListener() {
96 | * Override
97 | * public void onGetSize(final View view) {
98 | * view.getWidth();
99 | * }
100 | * });
101 | *
102 | *
103 | * @param view The view.
104 | * @param listener The get size listener.
105 | */
106 | public static void forceGetViewSize(final View view, final onGetSizeListener listener) {
107 | view.post(new Runnable() {
108 | @Override
109 | public void run() {
110 | if (listener != null) {
111 | listener.onGetSize(view);
112 | }
113 | }
114 | });
115 | }
116 |
117 | /**
118 | * Return the width of view.
119 | *
120 | * @param view The view.
121 | * @return the width of view
122 | */
123 | public static int getMeasuredWidth(final View view) {
124 | return measureView(view)[0];
125 | }
126 |
127 | /**
128 | * Return the height of view.
129 | *
130 | * @param view The view.
131 | * @return the height of view
132 | */
133 | public static int getMeasuredHeight(final View view) {
134 | return measureView(view)[1];
135 | }
136 |
137 | /**
138 | * Measure the view.
139 | *
140 | * @param view The view.
141 | * @return arr[0]: view's width, arr[1]: view's height
142 | */
143 | public static int[] measureView(final View view) {
144 | ViewGroup.LayoutParams lp = view.getLayoutParams();
145 | if (lp == null) {
146 | lp = new ViewGroup.LayoutParams(
147 | ViewGroup.LayoutParams.MATCH_PARENT,
148 | ViewGroup.LayoutParams.WRAP_CONTENT
149 | );
150 | }
151 | int widthSpec = ViewGroup.getChildMeasureSpec(0, 0, lp.width);
152 | int lpHeight = lp.height;
153 | int heightSpec;
154 | if (lpHeight > 0) {
155 | heightSpec = View.MeasureSpec.makeMeasureSpec(lpHeight, View.MeasureSpec.EXACTLY);
156 | } else {
157 | heightSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
158 | }
159 | view.measure(widthSpec, heightSpec);
160 | return new int[]{view.getMeasuredWidth(), view.getMeasuredHeight()};
161 | }
162 |
163 | ///////////////////////////////////////////////////////////////////////////
164 | // interface
165 | ///////////////////////////////////////////////////////////////////////////
166 |
167 | public interface onGetSizeListener {
168 | void onGetSize(View view);
169 | }
170 | }
171 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_launcher_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
10 |
15 |
20 |
25 |
30 |
35 |
40 |
45 |
50 |
55 |
60 |
65 |
70 |
75 |
80 |
85 |
90 |
95 |
100 |
105 |
110 |
115 |
120 |
125 |
130 |
135 |
140 |
145 |
150 |
155 |
160 |
165 |
170 |
171 |
--------------------------------------------------------------------------------
/lib_base/src/main/java/com/quyunshuo/base/utils/ClickUtils.java:
--------------------------------------------------------------------------------
1 | package com.quyunshuo.base.utils;
2 |
3 | import android.graphics.ColorMatrix;
4 | import android.graphics.ColorMatrixColorFilter;
5 | import android.graphics.Rect;
6 | import android.util.Log;
7 | import android.view.MotionEvent;
8 | import android.view.TouchDelegate;
9 | import android.view.View;
10 |
11 | import androidx.annotation.IntRange;
12 | import androidx.annotation.NonNull;
13 |
14 | /**
15 | * author: Blankj
16 | * blog : http://blankj.com
17 | * time : 2019/06/12
18 | * desc : 点击相关
19 | */
20 | public class ClickUtils {
21 | /**
22 | * applyPressedViewScale : 应用点击后对视图缩放
23 | * applyPressedViewAlpha : 应用点击后对视图改变透明度
24 | * applySingleDebouncing : 对单视图应用防抖点击
25 | * applyGlobalDebouncing : 对所有设置 GlobalDebouncing 的视图应用防抖点击
26 | * expandClickArea : 扩大点击区域
27 | * ClickUtils#OnDebouncingClickListener: 防抖点击监听器
28 | * ClickUtils#OnMultiClickListener : 连续点击监听器
29 | */
30 | private static final int PRESSED_VIEW_SCALE_TAG = -1;
31 | private static final float PRESSED_VIEW_SCALE_DEFAULT_VALUE = -0.06f;
32 |
33 | private static final int PRESSED_VIEW_ALPHA_TAG = -2;
34 | private static final int PRESSED_VIEW_ALPHA_SRC_TAG = -3;
35 | private static final float PRESSED_VIEW_ALPHA_DEFAULT_VALUE = 0.8f;
36 |
37 | private static final int DEBOUNCING_TAG = -7;
38 | private static final long DEBOUNCING_DEFAULT_VALUE = 200;
39 |
40 | private ClickUtils() {
41 | throw new UnsupportedOperationException("u can't instantiate me...");
42 | }
43 |
44 | /**
45 | * Apply scale animation for the views' click.
46 | *
47 | * @param views The views.
48 | */
49 | public static void applyPressedViewScale(final View... views) {
50 | applyPressedViewScale(views, null);
51 | }
52 |
53 | /**
54 | * Apply scale animation for the views' click.
55 | *
56 | * @param views The views.
57 | * @param scaleFactors The factors of scale for the views.
58 | */
59 | public static void applyPressedViewScale(final View[] views, final float[] scaleFactors) {
60 | if (views == null || views.length == 0) {
61 | return;
62 | }
63 | for (int i = 0; i < views.length; i++) {
64 | if (scaleFactors == null || i >= scaleFactors.length) {
65 | applyPressedViewScale(views[i], PRESSED_VIEW_SCALE_DEFAULT_VALUE);
66 | } else {
67 | applyPressedViewScale(views[i], scaleFactors[i]);
68 | }
69 | }
70 | }
71 |
72 | /**
73 | * Apply scale animation for the views' click.
74 | *
75 | * @param view The view.
76 | * @param scaleFactor The factor of scale for the view.
77 | */
78 | public static void applyPressedViewScale(final View view, final float scaleFactor) {
79 | if (view == null) {
80 | return;
81 | }
82 | view.setTag(PRESSED_VIEW_SCALE_TAG, scaleFactor);
83 | view.setClickable(true);
84 | view.setOnTouchListener(OnUtilsTouchListener.getInstance());
85 | }
86 |
87 | /**
88 | * Apply alpha for the views' click.
89 | *
90 | * @param views The views.
91 | */
92 | public static void applyPressedViewAlpha(final View... views) {
93 | applyPressedViewAlpha(views, null);
94 | }
95 |
96 | /**
97 | * Apply alpha for the views' click.
98 | *
99 | * @param views The views.
100 | * @param alphas The alphas for the views.
101 | */
102 | public static void applyPressedViewAlpha(final View[] views, final float[] alphas) {
103 | if (views == null || views.length == 0) return;
104 | for (int i = 0; i < views.length; i++) {
105 | if (alphas == null || i >= alphas.length) {
106 | applyPressedViewAlpha(views[i], PRESSED_VIEW_ALPHA_DEFAULT_VALUE);
107 | } else {
108 | applyPressedViewAlpha(views[i], alphas[i]);
109 | }
110 | }
111 | }
112 |
113 |
114 | /**
115 | * Apply scale animation for the views' click.
116 | *
117 | * @param view The view.
118 | * @param alpha The alpha for the view.
119 | */
120 | public static void applyPressedViewAlpha(final View view, final float alpha) {
121 | if (view == null) {
122 | return;
123 | }
124 | view.setTag(PRESSED_VIEW_ALPHA_TAG, alpha);
125 | view.setTag(PRESSED_VIEW_ALPHA_SRC_TAG, view.getAlpha());
126 | view.setClickable(true);
127 | view.setOnTouchListener(OnUtilsTouchListener.getInstance());
128 | }
129 |
130 | private static ColorMatrixColorFilter getDarkColorFilter(float darkAlpha) {
131 | return new ColorMatrixColorFilter(new ColorMatrix(new float[]{
132 | darkAlpha, 0, 0, 0, 0,
133 | 0, darkAlpha, 0, 0, 0,
134 | 0, 0, darkAlpha, 0, 0,
135 | 0, 0, 0, 2, 0
136 | }));
137 | }
138 |
139 | /**
140 | * Apply single debouncing for the view's click.
141 | *
142 | * @param view The view.
143 | * @param listener The listener.
144 | */
145 | public static void applySingleDebouncing(final View view, final View.OnClickListener listener) {
146 | applySingleDebouncing(new View[]{view}, listener);
147 | }
148 |
149 | /**
150 | * Apply single debouncing for the view's click.
151 | *
152 | * @param view The view.
153 | * @param duration The duration of debouncing.
154 | * @param listener The listener.
155 | */
156 | public static void applySingleDebouncing(final View view, @IntRange(from = 0) long duration,
157 | final View.OnClickListener listener) {
158 | applySingleDebouncing(new View[]{view}, duration, listener);
159 | }
160 |
161 | /**
162 | * Apply single debouncing for the views' click.
163 | *
164 | * @param views The views.
165 | * @param listener The listener.
166 | */
167 | public static void applySingleDebouncing(final View[] views, final View.OnClickListener listener) {
168 | applySingleDebouncing(views, DEBOUNCING_DEFAULT_VALUE, listener);
169 | }
170 |
171 | /**
172 | * Apply single debouncing for the views' click.
173 | *
174 | * @param views The views.
175 | * @param duration The duration of debouncing.
176 | * @param listener The listener.
177 | */
178 | public static void applySingleDebouncing(final View[] views,
179 | @IntRange(from = 0) long duration,
180 | final View.OnClickListener listener) {
181 | applyDebouncing(views, false, duration, listener);
182 | }
183 |
184 | /**
185 | * Apply global debouncing for the view's click.
186 | *
187 | * @param view The view.
188 | * @param listener The listener.
189 | */
190 | public static void applyGlobalDebouncing(final View view, final View.OnClickListener listener) {
191 | applyGlobalDebouncing(new View[]{view}, listener);
192 | }
193 |
194 | /**
195 | * Apply global debouncing for the view's click.
196 | *
197 | * @param view The view.
198 | * @param duration The duration of debouncing.
199 | * @param listener The listener.
200 | */
201 | public static void applyGlobalDebouncing(final View view, @IntRange(from = 0) long duration,
202 | final View.OnClickListener listener) {
203 | applyGlobalDebouncing(new View[]{view}, duration, listener);
204 | }
205 |
206 |
207 | /**
208 | * Apply global debouncing for the views' click.
209 | *
210 | * @param views The views.
211 | * @param listener The listener.
212 | */
213 | public static void applyGlobalDebouncing(final View[] views, final View.OnClickListener listener) {
214 | applyGlobalDebouncing(views, DEBOUNCING_DEFAULT_VALUE, listener);
215 | }
216 |
217 | /**
218 | * Apply global debouncing for the views' click.
219 | *
220 | * @param views The views.
221 | * @param duration The duration of debouncing.
222 | * @param listener The listener.
223 | */
224 | public static void applyGlobalDebouncing(final View[] views,
225 | @IntRange(from = 0) long duration,
226 | final View.OnClickListener listener) {
227 | applyDebouncing(views, true, duration, listener);
228 | }
229 |
230 | private static void applyDebouncing(final View[] views,
231 | final boolean isGlobal,
232 | @IntRange(from = 0) long duration,
233 | final View.OnClickListener listener) {
234 | if (views == null || views.length == 0 || listener == null) return;
235 | for (View view : views) {
236 | if (view == null) continue;
237 | view.setOnClickListener(new OnDebouncingClickListener(isGlobal, duration) {
238 | @Override
239 | public void onDebouncingClick(View v) {
240 | listener.onClick(v);
241 | }
242 | });
243 | }
244 | }
245 |
246 | /**
247 | * Expand the click area of the view
248 | *
249 | * @param view The view.
250 | * @param expandSize The size.
251 | */
252 | public static void expandClickArea(@NonNull final View view, final int expandSize) {
253 | expandClickArea(view, expandSize, expandSize, expandSize, expandSize);
254 | }
255 |
256 | public static void expandClickArea(@NonNull final View view,
257 | final int expandSizeTop,
258 | final int expandSizeLeft,
259 | final int expandSizeRight,
260 | final int expandSizeBottom) {
261 | final View parentView = (View) view.getParent();
262 | if (parentView == null) {
263 | Log.e("ClickUtils", "expandClickArea must have parent view.");
264 | return;
265 | }
266 | parentView.post(new Runnable() {
267 | @Override
268 | public void run() {
269 | final Rect rect = new Rect();
270 | view.getHitRect(rect);
271 | rect.top -= expandSizeTop;
272 | rect.bottom += expandSizeBottom;
273 | rect.left -= expandSizeLeft;
274 | rect.right += expandSizeRight;
275 | parentView.setTouchDelegate(new TouchDelegate(rect, view));
276 | }
277 | });
278 | }
279 |
280 | public static abstract class OnDebouncingClickListener implements View.OnClickListener {
281 |
282 | private static boolean mEnabled = true;
283 |
284 | private static final Runnable ENABLE_AGAIN = new Runnable() {
285 | @Override
286 | public void run() {
287 | mEnabled = true;
288 | }
289 | };
290 |
291 | private static boolean isValid(@NonNull final View view, final long duration) {
292 | long curTime = System.currentTimeMillis();
293 | Object tag = view.getTag(DEBOUNCING_TAG);
294 | if (!(tag instanceof Long)) {
295 | view.setTag(DEBOUNCING_TAG, curTime);
296 | return true;
297 | }
298 | long preTime = (Long) tag;
299 | if (curTime - preTime < 0) {
300 | view.setTag(DEBOUNCING_TAG, curTime);
301 | return false;
302 | } else if (curTime - preTime <= duration) {
303 | return false;
304 | }
305 | view.setTag(DEBOUNCING_TAG, curTime);
306 | return true;
307 | }
308 |
309 | private long mDuration;
310 | private boolean mIsGlobal;
311 |
312 | public OnDebouncingClickListener() {
313 | this(true, DEBOUNCING_DEFAULT_VALUE);
314 | }
315 |
316 | public OnDebouncingClickListener(final boolean isGlobal) {
317 | this(isGlobal, DEBOUNCING_DEFAULT_VALUE);
318 | }
319 |
320 | public OnDebouncingClickListener(final long duration) {
321 | this(true, duration);
322 | }
323 |
324 | public OnDebouncingClickListener(final boolean isGlobal, final long duration) {
325 | mIsGlobal = isGlobal;
326 | mDuration = duration;
327 | }
328 |
329 | public abstract void onDebouncingClick(View v);
330 |
331 | @Override
332 | public final void onClick(View v) {
333 | if (mIsGlobal) {
334 | if (mEnabled) {
335 | mEnabled = false;
336 | v.postDelayed(ENABLE_AGAIN, mDuration);
337 | onDebouncingClick(v);
338 | }
339 | } else {
340 | if (isValid(v, mDuration)) {
341 | onDebouncingClick(v);
342 | }
343 | }
344 | }
345 | }
346 |
347 | public static abstract class OnMultiClickListener implements View.OnClickListener {
348 |
349 | private static final long INTERVAL_DEFAULT_VALUE = 666;
350 |
351 | private final int mTriggerClickCount;
352 | private final long mClickInterval;
353 |
354 | private long mLastClickTime;
355 | private int mClickCount;
356 |
357 | public OnMultiClickListener(int triggerClickCount) {
358 | this(triggerClickCount, INTERVAL_DEFAULT_VALUE);
359 | }
360 |
361 | public OnMultiClickListener(int triggerClickCount, long clickInterval) {
362 | this.mTriggerClickCount = triggerClickCount;
363 | this.mClickInterval = clickInterval;
364 | }
365 |
366 | public abstract void onTriggerClick(View v);
367 |
368 | public abstract void onBeforeTriggerClick(View v, int count);
369 |
370 | @Override
371 | public void onClick(View v) {
372 | if (mTriggerClickCount <= 1) {
373 | onTriggerClick(v);
374 | return;
375 | }
376 | long curTime = System.currentTimeMillis();
377 |
378 | if (curTime - mLastClickTime < mClickInterval) {
379 | mClickCount++;
380 | if (mClickCount == mTriggerClickCount) {
381 | onTriggerClick(v);
382 | } else if (mClickCount < mTriggerClickCount) {
383 | onBeforeTriggerClick(v, mClickCount);
384 | } else {
385 | mClickCount = 1;
386 | onBeforeTriggerClick(v, mClickCount);
387 | }
388 | } else {
389 | mClickCount = 1;
390 | onBeforeTriggerClick(v, mClickCount);
391 | }
392 | mLastClickTime = curTime;
393 | }
394 | }
395 |
396 | private static class OnUtilsTouchListener implements View.OnTouchListener {
397 |
398 | public static OnUtilsTouchListener getInstance() {
399 | return LazyHolder.INSTANCE;
400 | }
401 |
402 | private OnUtilsTouchListener() {/**/}
403 |
404 | @Override
405 | public boolean onTouch(final View v, MotionEvent event) {
406 | int action = event.getAction();
407 | if (action == MotionEvent.ACTION_DOWN) {
408 | processScale(v, true);
409 | processAlpha(v, true);
410 | } else if (action == MotionEvent.ACTION_UP
411 | || action == MotionEvent.ACTION_CANCEL) {
412 | processScale(v, false);
413 | processAlpha(v, false);
414 | }
415 | return false;
416 | }
417 |
418 | private void processScale(final View view, boolean isDown) {
419 | Object tag = view.getTag(PRESSED_VIEW_SCALE_TAG);
420 | if (!(tag instanceof Float)) return;
421 | float value = isDown ? 1 + (Float) tag : 1;
422 | view.animate()
423 | .scaleX(value)
424 | .scaleY(value)
425 | .setDuration(200)
426 | .start();
427 | }
428 |
429 | private void processAlpha(final View view, boolean isDown) {
430 | Object tag = view.getTag(isDown ? PRESSED_VIEW_ALPHA_TAG : PRESSED_VIEW_ALPHA_SRC_TAG);
431 | if (!(tag instanceof Float)) return;
432 | view.setAlpha((Float) tag);
433 | }
434 |
435 | private static class LazyHolder {
436 | private static final OnUtilsTouchListener INSTANCE = new OnUtilsTouchListener();
437 | }
438 | }
439 | }
--------------------------------------------------------------------------------
/lib_base/src/main/java/com/quyunshuo/base/utils/SpanUtils.java:
--------------------------------------------------------------------------------
1 | package com.quyunshuo.base.utils;
2 |
3 | import android.annotation.SuppressLint;
4 | import android.graphics.Bitmap;
5 | import android.graphics.BitmapFactory;
6 | import android.graphics.BlurMaskFilter;
7 | import android.graphics.Canvas;
8 | import android.graphics.Color;
9 | import android.graphics.Paint;
10 | import android.graphics.Path;
11 | import android.graphics.Rect;
12 | import android.graphics.Shader;
13 | import android.graphics.Typeface;
14 | import android.graphics.drawable.BitmapDrawable;
15 | import android.graphics.drawable.Drawable;
16 | import android.net.Uri;
17 | import android.text.Layout;
18 | import android.text.Layout.Alignment;
19 | import android.text.SpannableStringBuilder;
20 | import android.text.Spanned;
21 | import android.text.TextPaint;
22 | import android.text.method.LinkMovementMethod;
23 | import android.text.style.AbsoluteSizeSpan;
24 | import android.text.style.AlignmentSpan;
25 | import android.text.style.BackgroundColorSpan;
26 | import android.text.style.CharacterStyle;
27 | import android.text.style.ClickableSpan;
28 | import android.text.style.ForegroundColorSpan;
29 | import android.text.style.LeadingMarginSpan;
30 | import android.text.style.LineHeightSpan;
31 | import android.text.style.MaskFilterSpan;
32 | import android.text.style.RelativeSizeSpan;
33 | import android.text.style.ReplacementSpan;
34 | import android.text.style.ScaleXSpan;
35 | import android.text.style.StrikethroughSpan;
36 | import android.text.style.StyleSpan;
37 | import android.text.style.SubscriptSpan;
38 | import android.text.style.SuperscriptSpan;
39 | import android.text.style.TypefaceSpan;
40 | import android.text.style.URLSpan;
41 | import android.text.style.UnderlineSpan;
42 | import android.text.style.UpdateAppearance;
43 | import android.util.Log;
44 | import android.widget.TextView;
45 |
46 | import androidx.annotation.ColorInt;
47 | import androidx.annotation.DrawableRes;
48 | import androidx.annotation.FloatRange;
49 | import androidx.annotation.IntDef;
50 | import androidx.annotation.IntRange;
51 | import androidx.annotation.NonNull;
52 | import androidx.annotation.Nullable;
53 | import androidx.core.content.ContextCompat;
54 |
55 | import com.quyunshuo.base.BaseApplication;
56 |
57 | import java.io.InputStream;
58 | import java.io.Serializable;
59 | import java.lang.annotation.Retention;
60 | import java.lang.annotation.RetentionPolicy;
61 | import java.lang.ref.WeakReference;
62 |
63 | import static android.graphics.BlurMaskFilter.Blur;
64 |
65 | /**
66 | * 文字多彩样式工具类
67 | * with : 设置控件
68 | * setFlag : 设置标识
69 | * setForegroundColor: 设置前景色
70 | * setBackgroundColor: 设置背景色
71 | * setLineHeight : 设置行高
72 | * setQuoteColor : 设置引用线的颜色
73 | * setLeadingMargin : 设置缩进
74 | * setBullet : 设置列表标记
75 | * setFontSize : 设置字体尺寸
76 | * setFontProportion : 设置字体比例
77 | * setFontXProportion: 设置字体横向比例
78 | * setStrikethrough : 设置删除线
79 | * setUnderline : 设置下划线
80 | * setSuperscript : 设置上标
81 | * setSubscript : 设置下标
82 | * setBold : 设置粗体
83 | * setItalic : 设置斜体
84 | * setBoldItalic : 设置粗斜体
85 | * setFontFamily : 设置字体系列
86 | * setTypeface : 设置字体
87 | * setAlign : 设置对齐
88 | * setClickSpan : 设置点击事件
89 | * setUrl : 设置超链接
90 | * setBlur : 设置模糊
91 | * setShader : 设置着色器
92 | * setShadow : 设置阴影
93 | * setSpans : 设置样式
94 | * append : 追加样式字符串
95 | * appendLine : 追加一行样式字符串
96 | * appendImage : 追加图片
97 | * appendSpace : 追加空白
98 | * create : 创建样式字符串
99 | */
100 | public final class SpanUtils {
101 |
102 | private static final int COLOR_DEFAULT = 0xFEFFFFFF;
103 |
104 | public static final int ALIGN_BOTTOM = 0;
105 | public static final int ALIGN_BASELINE = 1;
106 | public static final int ALIGN_CENTER = 2;
107 | public static final int ALIGN_TOP = 3;
108 |
109 | @IntDef({ALIGN_BOTTOM, ALIGN_BASELINE, ALIGN_CENTER, ALIGN_TOP})
110 | @Retention(RetentionPolicy.SOURCE)
111 | public @interface Align {
112 | }
113 |
114 | private static final String LINE_SEPARATOR = System.getProperty("line.separator");
115 |
116 | private TextView mTextView;
117 | private CharSequence mText;
118 | private int flag;
119 | private int foregroundColor;
120 | private int backgroundColor;
121 | private int lineHeight;
122 | private int alignLine;
123 | private int quoteColor;
124 | private int stripeWidth;
125 | private int quoteGapWidth;
126 | private int first;
127 | private int rest;
128 | private int bulletColor;
129 | private int bulletRadius;
130 | private int bulletGapWidth;
131 | private int fontSize;
132 | private boolean fontSizeIsDp;
133 | private float proportion;
134 | private float xProportion;
135 | private boolean isStrikethrough;
136 | private boolean isUnderline;
137 | private boolean isSuperscript;
138 | private boolean isSubscript;
139 | private boolean isBold;
140 | private boolean isItalic;
141 | private boolean isBoldItalic;
142 | private String fontFamily;
143 | private Typeface typeface;
144 | private Alignment alignment;
145 | private int verticalAlign;
146 | private ClickableSpan clickSpan;
147 | private String url;
148 | private float blurRadius;
149 | private Blur style;
150 | private Shader shader;
151 | private float shadowRadius;
152 | private float shadowDx;
153 | private float shadowDy;
154 | private int shadowColor;
155 | private Object[] spans;
156 |
157 | private Bitmap imageBitmap;
158 | private Drawable imageDrawable;
159 | private Uri imageUri;
160 | private int imageResourceId;
161 | private int alignImage;
162 |
163 | private int spaceSize;
164 | private int spaceColor;
165 |
166 | private SerializableSpannableStringBuilder mBuilder;
167 |
168 | private int mType;
169 | private final int mTypeCharSequence = 0;
170 | private final int mTypeImage = 1;
171 | private final int mTypeSpace = 2;
172 |
173 | private SpanUtils(TextView textView) {
174 | this();
175 | mTextView = textView;
176 | }
177 |
178 | public SpanUtils() {
179 | mBuilder = new SerializableSpannableStringBuilder();
180 | mText = "";
181 | mType = -1;
182 | setDefault();
183 | }
184 |
185 | private void setDefault() {
186 | flag = Spanned.SPAN_EXCLUSIVE_EXCLUSIVE;
187 | foregroundColor = COLOR_DEFAULT;
188 | backgroundColor = COLOR_DEFAULT;
189 | lineHeight = -1;
190 | quoteColor = COLOR_DEFAULT;
191 | first = -1;
192 | bulletColor = COLOR_DEFAULT;
193 | fontSize = -1;
194 | proportion = -1;
195 | xProportion = -1;
196 | isStrikethrough = false;
197 | isUnderline = false;
198 | isSuperscript = false;
199 | isSubscript = false;
200 | isBold = false;
201 | isItalic = false;
202 | isBoldItalic = false;
203 | fontFamily = null;
204 | typeface = null;
205 | alignment = null;
206 | verticalAlign = -1;
207 | clickSpan = null;
208 | url = null;
209 | blurRadius = -1;
210 | shader = null;
211 | shadowRadius = -1;
212 | spans = null;
213 |
214 | imageBitmap = null;
215 | imageDrawable = null;
216 | imageUri = null;
217 | imageResourceId = -1;
218 |
219 | spaceSize = -1;
220 | }
221 |
222 | /**
223 | * Set the span of flag.
224 | *
225 | * @param flag The flag.
226 | *
227 | * - {@link Spanned#SPAN_INCLUSIVE_EXCLUSIVE}
228 | * - {@link Spanned#SPAN_INCLUSIVE_INCLUSIVE}
229 | * - {@link Spanned#SPAN_EXCLUSIVE_EXCLUSIVE}
230 | * - {@link Spanned#SPAN_EXCLUSIVE_INCLUSIVE}
231 | *
232 | * @return the single {@link SpanUtils} instance
233 | */
234 | public SpanUtils setFlag(final int flag) {
235 | this.flag = flag;
236 | return this;
237 | }
238 |
239 | /**
240 | * Set the span of foreground's color.
241 | *
242 | * @param color The color of foreground
243 | * @return the single {@link SpanUtils} instance
244 | */
245 | public SpanUtils setForegroundColor(@ColorInt final int color) {
246 | this.foregroundColor = color;
247 | return this;
248 | }
249 |
250 | /**
251 | * Set the span of background's color.
252 | *
253 | * @param color The color of background
254 | * @return the single {@link SpanUtils} instance
255 | */
256 | public SpanUtils setBackgroundColor(@ColorInt final int color) {
257 | this.backgroundColor = color;
258 | return this;
259 | }
260 |
261 | /**
262 | * Set the span of line height.
263 | *
264 | * @param lineHeight The line height, in pixel.
265 | * @return the single {@link SpanUtils} instance
266 | */
267 | public SpanUtils setLineHeight(@IntRange(from = 0) final int lineHeight) {
268 | return setLineHeight(lineHeight, ALIGN_CENTER);
269 | }
270 |
271 | /**
272 | * Set the span of line height.
273 | *
274 | * @param lineHeight The line height, in pixel.
275 | * @param align The alignment.
276 | *
277 | * - {@link Align#ALIGN_TOP }
278 | * - {@link Align#ALIGN_CENTER}
279 | * - {@link Align#ALIGN_BOTTOM}
280 | *
281 | * @return the single {@link SpanUtils} instance
282 | */
283 | public SpanUtils setLineHeight(@IntRange(from = 0) final int lineHeight,
284 | @Align final int align) {
285 | this.lineHeight = lineHeight;
286 | this.alignLine = align;
287 | return this;
288 | }
289 |
290 | /**
291 | * Set the span of quote's color.
292 | *
293 | * @param color The color of quote
294 | * @return the single {@link SpanUtils} instance
295 | */
296 | public SpanUtils setQuoteColor(@ColorInt final int color) {
297 | return setQuoteColor(color, 2, 2);
298 | }
299 |
300 | /**
301 | * Set the span of quote's color.
302 | *
303 | * @param color The color of quote.
304 | * @param stripeWidth The width of stripe, in pixel.
305 | * @param gapWidth The width of gap, in pixel.
306 | * @return the single {@link SpanUtils} instance
307 | */
308 | public SpanUtils setQuoteColor(@ColorInt final int color,
309 | @IntRange(from = 1) final int stripeWidth,
310 | @IntRange(from = 0) final int gapWidth) {
311 | this.quoteColor = color;
312 | this.stripeWidth = stripeWidth;
313 | this.quoteGapWidth = gapWidth;
314 | return this;
315 | }
316 |
317 | /**
318 | * Set the span of leading margin.
319 | *
320 | * @param first The indent for the first line of the paragraph.
321 | * @param rest The indent for the remaining lines of the paragraph.
322 | * @return the single {@link SpanUtils} instance
323 | */
324 | public SpanUtils setLeadingMargin(@IntRange(from = 0) final int first,
325 | @IntRange(from = 0) final int rest) {
326 | this.first = first;
327 | this.rest = rest;
328 | return this;
329 | }
330 |
331 | /**
332 | * Set the span of bullet.
333 | *
334 | * @param gapWidth The width of gap, in pixel.
335 | * @return the single {@link SpanUtils} instance
336 | */
337 | public SpanUtils setBullet(@IntRange(from = 0) final int gapWidth) {
338 | return setBullet(0, 3, gapWidth);
339 | }
340 |
341 | /**
342 | * Set the span of bullet.
343 | *
344 | * @param color The color of bullet.
345 | * @param radius The radius of bullet, in pixel.
346 | * @param gapWidth The width of gap, in pixel.
347 | * @return the single {@link SpanUtils} instance
348 | */
349 | public SpanUtils setBullet(@ColorInt final int color,
350 | @IntRange(from = 0) final int radius,
351 | @IntRange(from = 0) final int gapWidth) {
352 | this.bulletColor = color;
353 | this.bulletRadius = radius;
354 | this.bulletGapWidth = gapWidth;
355 | return this;
356 | }
357 |
358 | /**
359 | * Set the span of font's size.
360 | *
361 | * @param size The size of font.
362 | * @return the single {@link SpanUtils} instance
363 | */
364 | public SpanUtils setFontSize(@IntRange(from = 0) final int size) {
365 | return setFontSize(size, false);
366 | }
367 |
368 | /**
369 | * Set the span of size of font.
370 | *
371 | * @param size The size of font.
372 | * @param isSp True to use sp, false to use pixel.
373 | * @return the single {@link SpanUtils} instance
374 | */
375 | public SpanUtils setFontSize(@IntRange(from = 0) final int size, final boolean isSp) {
376 | this.fontSize = size;
377 | this.fontSizeIsDp = isSp;
378 | return this;
379 | }
380 |
381 | /**
382 | * Set the span of proportion of font.
383 | *
384 | * @param proportion The proportion of font.
385 | * @return the single {@link SpanUtils} instance
386 | */
387 | public SpanUtils setFontProportion(final float proportion) {
388 | this.proportion = proportion;
389 | return this;
390 | }
391 |
392 | /**
393 | * Set the span of transverse proportion of font.
394 | *
395 | * @param proportion The transverse proportion of font.
396 | * @return the single {@link SpanUtils} instance
397 | */
398 | public SpanUtils setFontXProportion(final float proportion) {
399 | this.xProportion = proportion;
400 | return this;
401 | }
402 |
403 | /**
404 | * Set the span of strikethrough.
405 | *
406 | * @return the single {@link SpanUtils} instance
407 | */
408 | public SpanUtils setStrikethrough() {
409 | this.isStrikethrough = true;
410 | return this;
411 | }
412 |
413 | /**
414 | * Set the span of underline.
415 | *
416 | * @return the single {@link SpanUtils} instance
417 | */
418 | public SpanUtils setUnderline() {
419 | this.isUnderline = true;
420 | return this;
421 | }
422 |
423 | /**
424 | * Set the span of superscript.
425 | *
426 | * @return the single {@link SpanUtils} instance
427 | */
428 | public SpanUtils setSuperscript() {
429 | this.isSuperscript = true;
430 | return this;
431 | }
432 |
433 | /**
434 | * Set the span of subscript.
435 | *
436 | * @return the single {@link SpanUtils} instance
437 | */
438 | public SpanUtils setSubscript() {
439 | this.isSubscript = true;
440 | return this;
441 | }
442 |
443 | /**
444 | * Set the span of bold.
445 | *
446 | * @return the single {@link SpanUtils} instance
447 | */
448 | public SpanUtils setBold() {
449 | isBold = true;
450 | return this;
451 | }
452 |
453 | /**
454 | * Set the span of italic.
455 | *
456 | * @return the single {@link SpanUtils} instance
457 | */
458 | public SpanUtils setItalic() {
459 | isItalic = true;
460 | return this;
461 | }
462 |
463 | /**
464 | * Set the span of bold italic.
465 | *
466 | * @return the single {@link SpanUtils} instance
467 | */
468 | public SpanUtils setBoldItalic() {
469 | isBoldItalic = true;
470 | return this;
471 | }
472 |
473 | /**
474 | * Set the span of font family.
475 | *
476 | * @param fontFamily The font family.
477 | *
478 | * - monospace
479 | * - serif
480 | * - sans-serif
481 | *
482 | * @return the single {@link SpanUtils} instance
483 | */
484 | public SpanUtils setFontFamily(@NonNull final String fontFamily) {
485 | this.fontFamily = fontFamily;
486 | return this;
487 | }
488 |
489 | /**
490 | * Set the span of typeface.
491 | *
492 | * @param typeface The typeface.
493 | * @return the single {@link SpanUtils} instance
494 | */
495 | public SpanUtils setTypeface(@NonNull final Typeface typeface) {
496 | this.typeface = typeface;
497 | return this;
498 | }
499 |
500 | /**
501 | * Set the span of horizontal alignment.
502 | *
503 | * @param alignment The alignment.
504 | *
505 | * - {@link Alignment#ALIGN_NORMAL }
506 | * - {@link Alignment#ALIGN_OPPOSITE}
507 | * - {@link Alignment#ALIGN_CENTER }
508 | *
509 | * @return the single {@link SpanUtils} instance
510 | */
511 | public SpanUtils setHorizontalAlign(@NonNull final Alignment alignment) {
512 | this.alignment = alignment;
513 | return this;
514 | }
515 |
516 | /**
517 | * Set the span of vertical alignment.
518 | *
519 | * @param align The alignment.
520 | *
521 | * - {@link Align#ALIGN_TOP }
522 | * - {@link Align#ALIGN_CENTER }
523 | * - {@link Align#ALIGN_BASELINE}
524 | * - {@link Align#ALIGN_BOTTOM }
525 | *
526 | * @return the single {@link SpanUtils} instance
527 | */
528 | public SpanUtils setVerticalAlign(@Align final int align) {
529 | this.verticalAlign = align;
530 | return this;
531 | }
532 |
533 | /**
534 | * Set the span of click.
535 | * Must set {@code view.setMovementMethod(LinkMovementMethod.getInstance())}
536 | *
537 | * @param clickSpan The span of click.
538 | * @return the single {@link SpanUtils} instance
539 | */
540 | public SpanUtils setClickSpan(@NonNull final ClickableSpan clickSpan) {
541 | if (mTextView != null && mTextView.getMovementMethod() == null) {
542 | mTextView.setMovementMethod(LinkMovementMethod.getInstance());
543 | }
544 | this.clickSpan = clickSpan;
545 | return this;
546 | }
547 |
548 | /**
549 | * Set the span of url.
550 | * Must set {@code view.setMovementMethod(LinkMovementMethod.getInstance())}
551 | *
552 | * @param url The url.
553 | * @return the single {@link SpanUtils} instance
554 | */
555 | public SpanUtils setUrl(@NonNull final String url) {
556 | if (mTextView != null && mTextView.getMovementMethod() == null) {
557 | mTextView.setMovementMethod(LinkMovementMethod.getInstance());
558 | }
559 | this.url = url;
560 | return this;
561 | }
562 |
563 | /**
564 | * Set the span of blur.
565 | *
566 | * @param radius The radius of blur.
567 | * @param style The style.
568 | *
569 | * - {@link Blur#NORMAL}
570 | * - {@link Blur#SOLID}
571 | * - {@link Blur#OUTER}
572 | * - {@link Blur#INNER}
573 | *
574 | * @return the single {@link SpanUtils} instance
575 | */
576 | public SpanUtils setBlur(@FloatRange(from = 0, fromInclusive = false) final float radius,
577 | final Blur style) {
578 | this.blurRadius = radius;
579 | this.style = style;
580 | return this;
581 | }
582 |
583 | /**
584 | * Set the span of shader.
585 | *
586 | * @param shader The shader.
587 | * @return the single {@link SpanUtils} instance
588 | */
589 | public SpanUtils setShader(@NonNull final Shader shader) {
590 | this.shader = shader;
591 | return this;
592 | }
593 |
594 | /**
595 | * Set the span of shadow.
596 | *
597 | * @param radius The radius of shadow.
598 | * @param dx X-axis offset, in pixel.
599 | * @param dy Y-axis offset, in pixel.
600 | * @param shadowColor The color of shadow.
601 | * @return the single {@link SpanUtils} instance
602 | */
603 | public SpanUtils setShadow(@FloatRange(from = 0, fromInclusive = false) final float radius,
604 | final float dx,
605 | final float dy,
606 | final int shadowColor) {
607 | this.shadowRadius = radius;
608 | this.shadowDx = dx;
609 | this.shadowDy = dy;
610 | this.shadowColor = shadowColor;
611 | return this;
612 | }
613 |
614 |
615 | /**
616 | * Set the spans.
617 | *
618 | * @param spans The spans.
619 | * @return the single {@link SpanUtils} instance
620 | */
621 | public SpanUtils setSpans(@NonNull final Object... spans) {
622 | if (spans.length > 0) {
623 | this.spans = spans;
624 | }
625 | return this;
626 | }
627 |
628 | /**
629 | * Append the text text.
630 | *
631 | * @param text The text.
632 | * @return the single {@link SpanUtils} instance
633 | */
634 | public SpanUtils append(@NonNull final CharSequence text) {
635 | apply(mTypeCharSequence);
636 | mText = text;
637 | return this;
638 | }
639 |
640 | /**
641 | * Append one line.
642 | *
643 | * @return the single {@link SpanUtils} instance
644 | */
645 | public SpanUtils appendLine() {
646 | apply(mTypeCharSequence);
647 | mText = LINE_SEPARATOR;
648 | return this;
649 | }
650 |
651 | /**
652 | * Append text and one line.
653 | *
654 | * @return the single {@link SpanUtils} instance
655 | */
656 | public SpanUtils appendLine(@NonNull final CharSequence text) {
657 | apply(mTypeCharSequence);
658 | mText = text + LINE_SEPARATOR;
659 | return this;
660 | }
661 |
662 | /**
663 | * Append one image.
664 | *
665 | * @param bitmap The bitmap of image.
666 | * @return the single {@link SpanUtils} instance
667 | */
668 | public SpanUtils appendImage(@NonNull final Bitmap bitmap) {
669 | return appendImage(bitmap, ALIGN_BOTTOM);
670 | }
671 |
672 | /**
673 | * Append one image.
674 | *
675 | * @param bitmap The bitmap.
676 | * @param align The alignment.
677 | *
678 | * - {@link Align#ALIGN_TOP }
679 | * - {@link Align#ALIGN_CENTER }
680 | * - {@link Align#ALIGN_BASELINE}
681 | * - {@link Align#ALIGN_BOTTOM }
682 | *
683 | * @return the single {@link SpanUtils} instance
684 | */
685 | public SpanUtils appendImage(@NonNull final Bitmap bitmap, @Align final int align) {
686 | apply(mTypeImage);
687 | this.imageBitmap = bitmap;
688 | this.alignImage = align;
689 | return this;
690 | }
691 |
692 | /**
693 | * Append one image.
694 | *
695 | * @param drawable The drawable of image.
696 | * @return the single {@link SpanUtils} instance
697 | */
698 | public SpanUtils appendImage(@NonNull final Drawable drawable) {
699 | return appendImage(drawable, ALIGN_BOTTOM);
700 | }
701 |
702 | /**
703 | * Append one image.
704 | *
705 | * @param drawable The drawable of image.
706 | * @param align The alignment.
707 | *
708 | * - {@link Align#ALIGN_TOP }
709 | * - {@link Align#ALIGN_CENTER }
710 | * - {@link Align#ALIGN_BASELINE}
711 | * - {@link Align#ALIGN_BOTTOM }
712 | *
713 | * @return the single {@link SpanUtils} instance
714 | */
715 | public SpanUtils appendImage(@NonNull final Drawable drawable, @Align final int align) {
716 | apply(mTypeImage);
717 | this.imageDrawable = drawable;
718 | this.alignImage = align;
719 | return this;
720 | }
721 |
722 | /**
723 | * Append one image.
724 | *
725 | * @param uri The uri of image.
726 | * @return the single {@link SpanUtils} instance
727 | */
728 | public SpanUtils appendImage(@NonNull final Uri uri) {
729 | return appendImage(uri, ALIGN_BOTTOM);
730 | }
731 |
732 | /**
733 | * Append one image.
734 | *
735 | * @param uri The uri of image.
736 | * @param align The alignment.
737 | *
738 | * - {@link Align#ALIGN_TOP }
739 | * - {@link Align#ALIGN_CENTER }
740 | * - {@link Align#ALIGN_BASELINE}
741 | * - {@link Align#ALIGN_BOTTOM }
742 | *
743 | * @return the single {@link SpanUtils} instance
744 | */
745 | public SpanUtils appendImage(@NonNull final Uri uri, @Align final int align) {
746 | apply(mTypeImage);
747 | this.imageUri = uri;
748 | this.alignImage = align;
749 | return this;
750 | }
751 |
752 | /**
753 | * Append one image.
754 | *
755 | * @param resourceId The resource id of image.
756 | * @return the single {@link SpanUtils} instance
757 | */
758 | public SpanUtils appendImage(@DrawableRes final int resourceId) {
759 | return appendImage(resourceId, ALIGN_BOTTOM);
760 | }
761 |
762 | /**
763 | * Append one image.
764 | *
765 | * @param resourceId The resource id of image.
766 | * @param align The alignment.
767 | *
768 | * - {@link Align#ALIGN_TOP }
769 | * - {@link Align#ALIGN_CENTER }
770 | * - {@link Align#ALIGN_BASELINE}
771 | * - {@link Align#ALIGN_BOTTOM }
772 | *
773 | * @return the single {@link SpanUtils} instance
774 | */
775 | public SpanUtils appendImage(@DrawableRes final int resourceId, @Align final int align) {
776 | apply(mTypeImage);
777 | this.imageResourceId = resourceId;
778 | this.alignImage = align;
779 | return this;
780 | }
781 |
782 | /**
783 | * Append space.
784 | *
785 | * @param size The size of space.
786 | * @return the single {@link SpanUtils} instance
787 | */
788 | public SpanUtils appendSpace(@IntRange(from = 0) final int size) {
789 | return appendSpace(size, Color.TRANSPARENT);
790 | }
791 |
792 | /**
793 | * Append space.
794 | *
795 | * @param size The size of space.
796 | * @param color The color of space.
797 | * @return the single {@link SpanUtils} instance
798 | */
799 | public SpanUtils appendSpace(@IntRange(from = 0) final int size, @ColorInt final int color) {
800 | apply(mTypeSpace);
801 | spaceSize = size;
802 | spaceColor = color;
803 | return this;
804 | }
805 |
806 | private void apply(final int type) {
807 | applyLast();
808 | mType = type;
809 | }
810 |
811 | public SpannableStringBuilder get() {
812 | append("");
813 | return mBuilder;
814 | }
815 |
816 | /**
817 | * 替换文字
818 | *
819 | * @param needRepeat 需要替换的原始文字
820 | * @param repeat 将要替换成的文字
821 | * @return
822 | */
823 | public SpanUtils replace(CharSequence needRepeat, CharSequence repeat) {
824 | append("");
825 | SerializableSpannableStringBuilder builder = new SerializableSpannableStringBuilder();
826 | builder = mBuilder;
827 | String s = builder.toString();
828 |
829 | int i = s.indexOf(needRepeat.toString());
830 |
831 | mBuilder.replace(i, i + needRepeat.length(), repeat);
832 |
833 | return this;
834 | }
835 |
836 | /**
837 | * Create the span string.
838 | *
839 | * @return the span string
840 | */
841 | public SpannableStringBuilder create() {
842 | applyLast();
843 | if (mTextView != null) {
844 | mTextView.setText(mBuilder);
845 | }
846 | return mBuilder;
847 | }
848 |
849 | private void applyLast() {
850 | if (mType == mTypeCharSequence) {
851 | updateCharCharSequence();
852 | } else if (mType == mTypeImage) {
853 | updateImage();
854 | } else if (mType == mTypeSpace) {
855 | updateSpace();
856 | }
857 | setDefault();
858 | }
859 |
860 | private void updateCharCharSequence() {
861 | if (mText.length() == 0) return;
862 | int start = mBuilder.length();
863 | if (start == 0 && lineHeight != -1) {// bug of LineHeightSpan when first line
864 | mBuilder.append(Character.toString((char) 2))
865 | .append("\n")
866 | .setSpan(new AbsoluteSizeSpan(0), 0, 2, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
867 | start = 2;
868 | }
869 | mBuilder.append(mText);
870 | int end = mBuilder.length();
871 | if (verticalAlign != -1) {
872 | mBuilder.setSpan(new VerticalAlignSpan(verticalAlign), start, end, flag);
873 | }
874 | if (foregroundColor != COLOR_DEFAULT) {
875 | mBuilder.setSpan(new ForegroundColorSpan(foregroundColor), start, end, flag);
876 | }
877 | if (backgroundColor != COLOR_DEFAULT) {
878 | mBuilder.setSpan(new BackgroundColorSpan(backgroundColor), start, end, flag);
879 | }
880 | if (first != -1) {
881 | mBuilder.setSpan(new LeadingMarginSpan.Standard(first, rest), start, end, flag);
882 | }
883 | if (quoteColor != COLOR_DEFAULT) {
884 | mBuilder.setSpan(
885 | new CustomQuoteSpan(quoteColor, stripeWidth, quoteGapWidth),
886 | start,
887 | end,
888 | flag
889 | );
890 | }
891 | if (bulletColor != COLOR_DEFAULT) {
892 | mBuilder.setSpan(
893 | new CustomBulletSpan(bulletColor, bulletRadius, bulletGapWidth),
894 | start,
895 | end,
896 | flag
897 | );
898 | }
899 | if (fontSize != -1) {
900 | mBuilder.setSpan(new AbsoluteSizeSpan(fontSize, fontSizeIsDp), start, end, flag);
901 | }
902 | if (proportion != -1) {
903 | mBuilder.setSpan(new RelativeSizeSpan(proportion), start, end, flag);
904 | }
905 | if (xProportion != -1) {
906 | mBuilder.setSpan(new ScaleXSpan(xProportion), start, end, flag);
907 | }
908 | if (lineHeight != -1) {
909 | mBuilder.setSpan(new CustomLineHeightSpan(lineHeight, alignLine), start, end, flag);
910 | }
911 | if (isStrikethrough) {
912 | mBuilder.setSpan(new StrikethroughSpan(), start, end, flag);
913 | }
914 | if (isUnderline) {
915 | mBuilder.setSpan(new UnderlineSpan(), start, end, flag);
916 | }
917 | if (isSuperscript) {
918 | mBuilder.setSpan(new SuperscriptSpan(), start, end, flag);
919 | }
920 | if (isSubscript) {
921 | mBuilder.setSpan(new SubscriptSpan(), start, end, flag);
922 | }
923 | if (isBold) {
924 | mBuilder.setSpan(new StyleSpan(Typeface.BOLD), start, end, flag);
925 | }
926 | if (isItalic) {
927 | mBuilder.setSpan(new StyleSpan(Typeface.ITALIC), start, end, flag);
928 | }
929 | if (isBoldItalic) {
930 | mBuilder.setSpan(new StyleSpan(Typeface.BOLD_ITALIC), start, end, flag);
931 | }
932 | if (fontFamily != null) {
933 | mBuilder.setSpan(new TypefaceSpan(fontFamily), start, end, flag);
934 | }
935 | if (typeface != null) {
936 | mBuilder.setSpan(new CustomTypefaceSpan(typeface), start, end, flag);
937 | }
938 | if (alignment != null) {
939 | mBuilder.setSpan(new AlignmentSpan.Standard(alignment), start, end, flag);
940 | }
941 | if (clickSpan != null) {
942 | mBuilder.setSpan(clickSpan, start, end, flag);
943 | }
944 | if (url != null) {
945 | mBuilder.setSpan(new URLSpan(url), start, end, flag);
946 | }
947 | if (blurRadius != -1) {
948 | mBuilder.setSpan(
949 | new MaskFilterSpan(new BlurMaskFilter(blurRadius, style)),
950 | start,
951 | end,
952 | flag
953 | );
954 | }
955 | if (shader != null) {
956 | mBuilder.setSpan(new ShaderSpan(shader), start, end, flag);
957 | }
958 | if (shadowRadius != -1) {
959 | mBuilder.setSpan(
960 | new ShadowSpan(shadowRadius, shadowDx, shadowDy, shadowColor),
961 | start,
962 | end,
963 | flag
964 | );
965 | }
966 | if (spans != null) {
967 | for (Object span : spans) {
968 | mBuilder.setSpan(span, start, end, flag);
969 | }
970 | }
971 | }
972 |
973 | private void updateImage() {
974 | int start = mBuilder.length();
975 | mText = "
";
976 | updateCharCharSequence();
977 | int end = mBuilder.length();
978 | if (imageBitmap != null) {
979 | mBuilder.setSpan(new CustomImageSpan(imageBitmap, alignImage), start, end, flag);
980 | } else if (imageDrawable != null) {
981 | mBuilder.setSpan(new CustomImageSpan(imageDrawable, alignImage), start, end, flag);
982 | } else if (imageUri != null) {
983 | mBuilder.setSpan(new CustomImageSpan(imageUri, alignImage), start, end, flag);
984 | } else if (imageResourceId != -1) {
985 | mBuilder.setSpan(new CustomImageSpan(imageResourceId, alignImage), start, end, flag);
986 | }
987 | }
988 |
989 | private void updateSpace() {
990 | int start = mBuilder.length();
991 | mText = "< >";
992 | updateCharCharSequence();
993 | int end = mBuilder.length();
994 | mBuilder.setSpan(new SpaceSpan(spaceSize, spaceColor), start, end, flag);
995 | }
996 |
997 | static class VerticalAlignSpan extends ReplacementSpan {
998 |
999 | static final int ALIGN_CENTER = 2;
1000 | static final int ALIGN_TOP = 3;
1001 |
1002 | final int mVerticalAlignment;
1003 |
1004 | VerticalAlignSpan(int verticalAlignment) {
1005 | mVerticalAlignment = verticalAlignment;
1006 | }
1007 |
1008 | @Override
1009 | public int getSize(@NonNull Paint paint, CharSequence text, int start, int end, @Nullable Paint.FontMetricsInt fm) {
1010 | text = text.subSequence(start, end);
1011 | return (int) paint.measureText(text.toString());
1012 | }
1013 |
1014 | @Override
1015 | public void draw(@NonNull Canvas canvas, CharSequence text, int start, int end, float x, int top, int y, int bottom, @NonNull Paint paint) {
1016 | text = text.subSequence(start, end);
1017 | Paint.FontMetricsInt fm = paint.getFontMetricsInt();
1018 | // int need = height - (v + fm.descent - fm.ascent - spanstartv);
1019 | // if (need > 0) {
1020 | // if (mVerticalAlignment == ALIGN_TOP) {
1021 | // fm.descent += need;
1022 | // } else if (mVerticalAlignment == ALIGN_CENTER) {
1023 | // fm.descent += need / 2;
1024 | // fm.ascent -= need / 2;
1025 | // } else {
1026 | // fm.ascent -= need;
1027 | // }
1028 | // }
1029 | // need = height - (v + fm.bottom - fm.top - spanstartv);
1030 | // if (need > 0) {
1031 | // if (mVerticalAlignment == ALIGN_TOP) {
1032 | // fm.bottom += need;
1033 | // } else if (mVerticalAlignment == ALIGN_CENTER) {
1034 | // fm.bottom += need / 2;
1035 | // fm.top -= need / 2;
1036 | // } else {
1037 | // fm.top -= need;
1038 | // }
1039 | // }
1040 |
1041 | canvas.drawText(text.toString(), x, y - ((y + fm.descent + y + fm.ascent) / 2 - (bottom + top) / 2), paint);
1042 | }
1043 | }
1044 |
1045 | static class CustomLineHeightSpan implements LineHeightSpan {
1046 |
1047 | private final int height;
1048 |
1049 | static final int ALIGN_CENTER = 2;
1050 | static final int ALIGN_TOP = 3;
1051 |
1052 | final int mVerticalAlignment;
1053 | static Paint.FontMetricsInt sfm;
1054 |
1055 | CustomLineHeightSpan(int height, int verticalAlignment) {
1056 | this.height = height;
1057 | mVerticalAlignment = verticalAlignment;
1058 | }
1059 |
1060 | @Override
1061 | public void chooseHeight(final CharSequence text, final int start, final int end,
1062 | final int spanstartv, final int v, final Paint.FontMetricsInt fm) {
1063 | if (sfm == null) {
1064 | sfm = new Paint.FontMetricsInt();
1065 | sfm.top = fm.top;
1066 | sfm.ascent = fm.ascent;
1067 | sfm.descent = fm.descent;
1068 | sfm.bottom = fm.bottom;
1069 | sfm.leading = fm.leading;
1070 | } else {
1071 | fm.top = sfm.top;
1072 | fm.ascent = sfm.ascent;
1073 | fm.descent = sfm.descent;
1074 | fm.bottom = sfm.bottom;
1075 | fm.leading = sfm.leading;
1076 | }
1077 | int need = height - (v + fm.descent - fm.ascent - spanstartv);
1078 | if (need > 0) {
1079 | if (mVerticalAlignment == ALIGN_TOP) {
1080 | fm.descent += need;
1081 | } else if (mVerticalAlignment == ALIGN_CENTER) {
1082 | fm.descent += need / 2;
1083 | fm.ascent -= need / 2;
1084 | } else {
1085 | fm.ascent -= need;
1086 | }
1087 | }
1088 | need = height - (v + fm.bottom - fm.top - spanstartv);
1089 | if (need > 0) {
1090 | if (mVerticalAlignment == ALIGN_TOP) {
1091 | fm.bottom += need;
1092 | } else if (mVerticalAlignment == ALIGN_CENTER) {
1093 | fm.bottom += need / 2;
1094 | fm.top -= need / 2;
1095 | } else {
1096 | fm.top -= need;
1097 | }
1098 | }
1099 | if (end == ((Spanned) text).getSpanEnd(this)) {
1100 | sfm = null;
1101 | }
1102 | }
1103 | }
1104 |
1105 | static class SpaceSpan extends ReplacementSpan {
1106 |
1107 | private final int width;
1108 | private final Paint paint = new Paint();
1109 |
1110 | private SpaceSpan(final int width) {
1111 | this(width, Color.TRANSPARENT);
1112 | }
1113 |
1114 | private SpaceSpan(final int width, final int color) {
1115 | super();
1116 | this.width = width;
1117 | paint.setColor(color);
1118 | paint.setStyle(Paint.Style.FILL);
1119 | }
1120 |
1121 | @Override
1122 | public int getSize(@NonNull final Paint paint, final CharSequence text,
1123 | @IntRange(from = 0) final int start,
1124 | @IntRange(from = 0) final int end,
1125 | @Nullable final Paint.FontMetricsInt fm) {
1126 | return width;
1127 | }
1128 |
1129 | @Override
1130 | public void draw(@NonNull final Canvas canvas, final CharSequence text,
1131 | @IntRange(from = 0) final int start,
1132 | @IntRange(from = 0) final int end,
1133 | final float x, final int top, final int y, final int bottom,
1134 | @NonNull final Paint paint) {
1135 | canvas.drawRect(x, top, x + width, bottom, this.paint);
1136 | }
1137 | }
1138 |
1139 | static class CustomQuoteSpan implements LeadingMarginSpan {
1140 |
1141 | private final int color;
1142 | private final int stripeWidth;
1143 | private final int gapWidth;
1144 |
1145 | private CustomQuoteSpan(final int color, final int stripeWidth, final int gapWidth) {
1146 | super();
1147 | this.color = color;
1148 | this.stripeWidth = stripeWidth;
1149 | this.gapWidth = gapWidth;
1150 | }
1151 |
1152 | public int getLeadingMargin(final boolean first) {
1153 | return stripeWidth + gapWidth;
1154 | }
1155 |
1156 | public void drawLeadingMargin(final Canvas c, final Paint p, final int x, final int dir,
1157 | final int top, final int baseline, final int bottom,
1158 | final CharSequence text, final int start, final int end,
1159 | final boolean first, final Layout layout) {
1160 | Paint.Style style = p.getStyle();
1161 | int color = p.getColor();
1162 |
1163 | p.setStyle(Paint.Style.FILL);
1164 | p.setColor(this.color);
1165 |
1166 | c.drawRect(x, top, x + dir * stripeWidth, bottom, p);
1167 |
1168 | p.setStyle(style);
1169 | p.setColor(color);
1170 | }
1171 | }
1172 |
1173 | static class CustomBulletSpan implements LeadingMarginSpan {
1174 |
1175 | private final int color;
1176 | private final int radius;
1177 | private final int gapWidth;
1178 |
1179 | private Path sBulletPath = null;
1180 |
1181 | private CustomBulletSpan(final int color, final int radius, final int gapWidth) {
1182 | this.color = color;
1183 | this.radius = radius;
1184 | this.gapWidth = gapWidth;
1185 | }
1186 |
1187 | public int getLeadingMargin(final boolean first) {
1188 | return 2 * radius + gapWidth;
1189 | }
1190 |
1191 | public void drawLeadingMargin(final Canvas c, final Paint p, final int x, final int dir,
1192 | final int top, final int baseline, final int bottom,
1193 | final CharSequence text, final int start, final int end,
1194 | final boolean first, final Layout l) {
1195 | if (((Spanned) text).getSpanStart(this) == start) {
1196 | Paint.Style style = p.getStyle();
1197 | int oldColor = 0;
1198 | oldColor = p.getColor();
1199 | p.setColor(color);
1200 | p.setStyle(Paint.Style.FILL);
1201 | if (c.isHardwareAccelerated()) {
1202 | if (sBulletPath == null) {
1203 | sBulletPath = new Path();
1204 | // Bullet is slightly better to avoid aliasing artifacts on mdpi devices.
1205 | sBulletPath.addCircle(0.0f, 0.0f, radius, Path.Direction.CW);
1206 | }
1207 | c.save();
1208 | c.translate(x + dir * radius, (top + bottom) / 2.0f);
1209 | c.drawPath(sBulletPath, p);
1210 | c.restore();
1211 | } else {
1212 | c.drawCircle(x + dir * radius, (top + bottom) / 2.0f, radius, p);
1213 | }
1214 | p.setColor(oldColor);
1215 | p.setStyle(style);
1216 | }
1217 | }
1218 | }
1219 |
1220 | @SuppressLint("ParcelCreator")
1221 | static class CustomTypefaceSpan extends TypefaceSpan {
1222 |
1223 | private final Typeface newType;
1224 |
1225 | private CustomTypefaceSpan(final Typeface type) {
1226 | super("");
1227 | newType = type;
1228 | }
1229 |
1230 | @Override
1231 | public void updateDrawState(final TextPaint textPaint) {
1232 | apply(textPaint, newType);
1233 | }
1234 |
1235 | @Override
1236 | public void updateMeasureState(final TextPaint paint) {
1237 | apply(paint, newType);
1238 | }
1239 |
1240 | private void apply(final Paint paint, final Typeface tf) {
1241 | int oldStyle;
1242 | Typeface old = paint.getTypeface();
1243 | if (old == null) {
1244 | oldStyle = 0;
1245 | } else {
1246 | oldStyle = old.getStyle();
1247 | }
1248 |
1249 | int fake = oldStyle & ~tf.getStyle();
1250 | if ((fake & Typeface.BOLD) != 0) {
1251 | paint.setFakeBoldText(true);
1252 | }
1253 |
1254 | if ((fake & Typeface.ITALIC) != 0) {
1255 | paint.setTextSkewX(-0.25f);
1256 | }
1257 |
1258 | paint.getShader();
1259 |
1260 | paint.setTypeface(tf);
1261 | }
1262 | }
1263 |
1264 | static class CustomImageSpan extends CustomDynamicDrawableSpan {
1265 | private Drawable mDrawable;
1266 | private Uri mContentUri;
1267 | private int mResourceId;
1268 |
1269 | private CustomImageSpan(final Bitmap b, final int verticalAlignment) {
1270 | super(verticalAlignment);
1271 | mDrawable = new BitmapDrawable(BaseApplication.getApplication().getResources(), b);
1272 | mDrawable.setBounds(
1273 | 0, 0, mDrawable.getIntrinsicWidth(), mDrawable.getIntrinsicHeight()
1274 | );
1275 | }
1276 |
1277 | private CustomImageSpan(final Drawable d, final int verticalAlignment) {
1278 | super(verticalAlignment);
1279 | mDrawable = d;
1280 | mDrawable.setBounds(
1281 | 0, 0, mDrawable.getIntrinsicWidth(), mDrawable.getIntrinsicHeight()
1282 | );
1283 | }
1284 |
1285 | private CustomImageSpan(final Uri uri, final int verticalAlignment) {
1286 | super(verticalAlignment);
1287 | mContentUri = uri;
1288 | }
1289 |
1290 | private CustomImageSpan(@DrawableRes final int resourceId, final int verticalAlignment) {
1291 | super(verticalAlignment);
1292 | mResourceId = resourceId;
1293 | }
1294 |
1295 | @Override
1296 | public Drawable getDrawable() {
1297 | Drawable drawable = null;
1298 | if (mDrawable != null) {
1299 | drawable = mDrawable;
1300 | } else if (mContentUri != null) {
1301 | Bitmap bitmap;
1302 | try {
1303 | InputStream is =
1304 | BaseApplication.getApplication().getContentResolver().openInputStream(mContentUri);
1305 | bitmap = BitmapFactory.decodeStream(is);
1306 | drawable = new BitmapDrawable(BaseApplication.getApplication().getResources(), bitmap);
1307 | drawable.setBounds(
1308 | 0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight()
1309 | );
1310 | if (is != null) {
1311 | is.close();
1312 | }
1313 | } catch (Exception e) {
1314 | Log.e("sms", "Failed to loaded content " + mContentUri, e);
1315 | }
1316 | } else {
1317 | try {
1318 | drawable = ContextCompat.getDrawable(BaseApplication.getApplication(), mResourceId);
1319 | drawable.setBounds(
1320 | 0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight()
1321 | );
1322 | } catch (Exception e) {
1323 | Log.e("sms", "Unable to find resource: " + mResourceId);
1324 | }
1325 | }
1326 | return drawable;
1327 | }
1328 | }
1329 |
1330 | static abstract class CustomDynamicDrawableSpan extends ReplacementSpan {
1331 |
1332 | static final int ALIGN_BOTTOM = 0;
1333 |
1334 | static final int ALIGN_BASELINE = 1;
1335 |
1336 | static final int ALIGN_CENTER = 2;
1337 |
1338 | static final int ALIGN_TOP = 3;
1339 |
1340 | final int mVerticalAlignment;
1341 |
1342 | private CustomDynamicDrawableSpan() {
1343 | mVerticalAlignment = ALIGN_BOTTOM;
1344 | }
1345 |
1346 | private CustomDynamicDrawableSpan(final int verticalAlignment) {
1347 | mVerticalAlignment = verticalAlignment;
1348 | }
1349 |
1350 | public abstract Drawable getDrawable();
1351 |
1352 | @Override
1353 | public int getSize(@NonNull final Paint paint, final CharSequence text,
1354 | final int start, final int end, final Paint.FontMetricsInt fm) {
1355 | Drawable d = getCachedDrawable();
1356 | Rect rect = d.getBounds();
1357 | if (fm != null) {
1358 | // LogUtils.d("fm.top: " + fm.top,
1359 | // "fm.ascent: " + fm.ascent,
1360 | // "fm.descent: " + fm.descent,
1361 | // "fm.bottom: " + fm.bottom,
1362 | // "lineHeight: " + (fm.bottom - fm.top));
1363 | int lineHeight = fm.bottom - fm.top;
1364 | if (lineHeight < rect.height()) {
1365 | if (mVerticalAlignment == ALIGN_TOP) {
1366 | fm.top = fm.top;
1367 | fm.bottom = rect.height() + fm.top;
1368 | } else if (mVerticalAlignment == ALIGN_CENTER) {
1369 | fm.top = -rect.height() / 2 - lineHeight / 4;
1370 | fm.bottom = rect.height() / 2 - lineHeight / 4;
1371 | } else {
1372 | fm.top = -rect.height() + fm.bottom;
1373 | fm.bottom = fm.bottom;
1374 | }
1375 | fm.ascent = fm.top;
1376 | fm.descent = fm.bottom;
1377 | }
1378 | }
1379 | return rect.right;
1380 | }
1381 |
1382 | @Override
1383 | public void draw(@NonNull final Canvas canvas, final CharSequence text,
1384 | final int start, final int end, final float x,
1385 | final int top, final int y, final int bottom, @NonNull final Paint paint) {
1386 | Drawable d = getCachedDrawable();
1387 | Rect rect = d.getBounds();
1388 | canvas.save();
1389 | float transY;
1390 | int lineHeight = bottom - top;
1391 | // LogUtils.d("rectHeight: " + rect.height(),
1392 | // "lineHeight: " + (bottom - top));
1393 | if (rect.height() < lineHeight) {
1394 | if (mVerticalAlignment == ALIGN_TOP) {
1395 | transY = top;
1396 | } else if (mVerticalAlignment == ALIGN_CENTER) {
1397 | transY = (bottom + top - rect.height()) / 2;
1398 | } else if (mVerticalAlignment == ALIGN_BASELINE) {
1399 | transY = y - rect.height();
1400 | } else {
1401 | transY = bottom - rect.height();
1402 | }
1403 | canvas.translate(x, transY);
1404 | } else {
1405 | canvas.translate(x, top);
1406 | }
1407 | d.draw(canvas);
1408 | canvas.restore();
1409 | }
1410 |
1411 | private Drawable getCachedDrawable() {
1412 | WeakReference wr = mDrawableRef;
1413 | Drawable d = null;
1414 | if (wr != null) {
1415 | d = wr.get();
1416 | }
1417 | if (d == null) {
1418 | d = getDrawable();
1419 | mDrawableRef = new WeakReference<>(d);
1420 | }
1421 | return d;
1422 | }
1423 |
1424 | private WeakReference mDrawableRef;
1425 | }
1426 |
1427 | static class ShaderSpan extends CharacterStyle implements UpdateAppearance {
1428 | private Shader mShader;
1429 |
1430 | private ShaderSpan(final Shader shader) {
1431 | this.mShader = shader;
1432 | }
1433 |
1434 | @Override
1435 | public void updateDrawState(final TextPaint tp) {
1436 | tp.setShader(mShader);
1437 | }
1438 | }
1439 |
1440 | static class ShadowSpan extends CharacterStyle implements UpdateAppearance {
1441 | private float radius;
1442 | private float dx, dy;
1443 | private int shadowColor;
1444 |
1445 | private ShadowSpan(final float radius,
1446 | final float dx,
1447 | final float dy,
1448 | final int shadowColor) {
1449 | this.radius = radius;
1450 | this.dx = dx;
1451 | this.dy = dy;
1452 | this.shadowColor = shadowColor;
1453 | }
1454 |
1455 | @Override
1456 | public void updateDrawState(final TextPaint tp) {
1457 | tp.setShadowLayer(radius, dx, dy, shadowColor);
1458 | }
1459 | }
1460 |
1461 | private static class SerializableSpannableStringBuilder extends SpannableStringBuilder
1462 | implements Serializable {
1463 |
1464 | private static final long serialVersionUID = 4909567650765875771L;
1465 | }
1466 |
1467 | ///////////////////////////////////////////////////////////////////////////
1468 | // static
1469 | ///////////////////////////////////////////////////////////////////////////
1470 |
1471 | public static SpanUtils with(final TextView textView) {
1472 | return new SpanUtils(textView);
1473 | }
1474 | }
1475 |
--------------------------------------------------------------------------------