├── .gitignore ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── net │ │ └── angrycode │ │ └── componentization │ │ └── MainActivity.java │ └── res │ ├── layout │ └── activity_main.xml │ ├── mipmap-hdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-mdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-xhdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-xxhdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-xxxhdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ └── values │ ├── colors.xml │ ├── strings.xml │ └── styles.xml ├── build.gradle ├── buildsystem └── dependencies.gradle ├── core ├── .gitignore ├── build.gradle ├── maven-release-aar.gradle ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── net │ │ └── angrycode │ │ ├── core │ │ └── network │ │ │ ├── BaseMultipartRequest.java │ │ │ ├── BaseTextRequest.java │ │ │ ├── IRequest.java │ │ │ ├── OnRequestCallback.java │ │ │ ├── Request.java │ │ │ ├── RequestWrapper.java │ │ │ └── SimpleTextRequest.java │ │ ├── mvp │ │ ├── BasePresenter.java │ │ ├── DemoActivity.java │ │ ├── DemoContract.java │ │ ├── DemoPresenter.java │ │ ├── IPresenter.java │ │ └── IView.java │ │ └── toolkit │ │ ├── AndroidShare.java │ │ ├── App.java │ │ ├── DateUtils.java │ │ ├── DeviceDimensionsHelper.java │ │ ├── GooglePlayStore.java │ │ ├── Keyboard.java │ │ ├── KeyboardVisibilityEvent.java │ │ ├── PermissionsUtils.java │ │ ├── PhoneUtils.java │ │ ├── PreferenceHelper.java │ │ ├── ProcessUtils.java │ │ ├── ShareUtils.java │ │ ├── StringUtils.java │ │ ├── SystemTool.java │ │ ├── UrlUtils.java │ │ ├── Utils.java │ │ ├── constant │ │ └── Regexs.java │ │ └── widget │ │ ├── CheckedImageView.java │ │ ├── ClickableImageView.java │ │ ├── Dialogs.java │ │ ├── LinkTextView.java │ │ ├── QuickClearEditText.java │ │ ├── SimpleDrawingView.java │ │ ├── SimpleTextWatcher.java │ │ ├── TintableImageView.java │ │ └── ViewUtils.java │ └── res │ ├── drawable │ ├── layer_divider.xml │ ├── selector_pwd_check.xml │ ├── shape_divider.xml │ └── shape_text_input_bg.xml │ ├── layout │ └── layout_text_input.xml │ ├── mipmap-hdpi │ ├── ic_pwd_hide.png │ └── ic_pwd_visible.png │ ├── mipmap-xhdpi │ ├── ic_clear.png │ ├── ic_pwd_hide.png │ └── ic_pwd_visible.png │ └── values │ ├── attrs.xml │ ├── colors.xml │ ├── strings.xml │ └── styles.xml ├── data ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── net │ │ └── angrycode │ │ └── data │ │ └── repository │ │ ├── Data.java │ │ ├── IDataSource.java │ │ ├── RepositoryFactory.java │ │ ├── local │ │ ├── DBHelper.java │ │ └── LocalRepository.java │ │ └── remote │ │ ├── DataApi.java │ │ └── RemoteRepository.java │ └── res │ └── values │ └── strings.xml ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea 5 | .DS_Store 6 | /build 7 | /captures 8 | .externalNativeBuild 9 | 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## App 组件化/模块化开发架构思路 2 | 3 | 随着业务的发展 App 开发技术也越来越成熟,对开发者来说 App 代码量也迅速地增长到一个数量级。对于如何架构 App 已经每个开发者面临的实际问题。好的架构可以提高开发者的效率,降低维护成本。 4 | 5 | 由于业务增长引起项目中代码量激增,以及历史遗留问题和结构混乱,作为一个有代码洁癖的程序员,很早就开始思考如何组织 App 架构的问题了。目前遇到的主要有以下几点问题: 6 | 7 | 1. 代码量激增引起结构混乱 8 | 2. 各个模块相互引用且耦合度高 9 | 3. 无法独立开发或者调试组件代码 10 | 4. 无法应对组件插拔的需求(例如:产品经理今天把这个功能加上,第二天又去掉,第三天又加回来T_T) 11 | 12 | ### App 架构图 13 | 14 | 在阅读了大量的文档之后,根据实际项目开发遇到的问题,我总结了以下架构。由于水平有限,有不合理的欢迎拍砖 15 | 16 | ![App 架构图][1] 17 | 18 | 自下而上将 App 分为: 19 | 20 | * **内核层** 21 | * **业务层** 22 | * **应用层** 23 | 24 | #### 内核层 25 | 26 | 内核层是包含了为 App 提供公共服务的的一些库。例如:公共资源、网络库、日志工具、数据库、图片加载等核心库。这些是整个 App 基础库。 27 | 28 | #### 业务层 29 | 30 | 我认为这一层是整个 App 架构的关键。因为根据实际业务需求,这一层会分离出许多独立组件(其实就是对应于 Android Studio 的 Module),但这些组件可以独立运行,相当于一个小应用(组件如何独立运行将在应用层中会详细解析)。并且这些组件不再像传统的方式进行相互引用,而是采用了**组件路由**进行各个组件的通信。 31 | 32 | 比如组件 A 中需要跳转到组件 B 中的一个 Activity 页面,传统的做法是在 `ModuleAActivity` 中 33 | 34 | ```java 35 | Intent intent = new Intent(this,ModuleBActivity.class); 36 | intent.putExtra("data", data); 37 | startActivity(intent); 38 | ``` 39 | 40 | 这样 Module A 与 Module B 耦合度就很强 41 | 42 | 比较好的做法应该是 43 | 44 | ```java 45 | Intent intent = Router.route(context,"BPackageName.ModuleBActivity",data); 46 | startActivity(intent); 47 | ``` 48 | 49 | 当然实现上面的路由原理也有很多方式,例如可以使用 Android 系统的**隐式调用**实现跳转通信。 50 | 51 | 在 Manifest 文件中 52 | 53 | ```java 54 | 55 | 56 | 60 | 61 | 62 | 63 | 64 | 65 | ``` 66 | 67 | 实际调用 68 | 69 | ```java 70 | String url = "router://moduleb/entry"; 71 | Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url)); 72 | intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 73 | PackageManager packageManager = getPackageManager(); 74 | List activities = packageManager.queryIntentActivities(intent, 0); 75 | if (!activities.isEmpty()) { 76 | startActivity(intent); 77 | } 78 | ``` 79 | 80 | Router 层目前有一个比较好的开源框架可以参考,来自 alibaba 的开源项目:[ARouter][2] 81 | 82 | ##### SDK 编码思维 83 | 84 | 业务层要实现比较好组件分离,对程序猿现在编码思维要转换一下,要切换到 **SDK 思维**。 85 | 86 | 那什么是 **SDK 思维**呢? 87 | 88 | 想想项目中引用他人编写的库的接口使用方式,就不难理解了。**即站在使用者的角度上思考:如何使用接口才是最方便的?**例如公司现有好几个 App 产品,每个 App 都需要使用同样的授权登录。那么这个授权登录模块就可以独立成一个组件。 89 | 90 | 假设将授权登录组件命名为**auth**。那么其它组件在使用的时候可能类似以下代码片段 91 | 92 | ```java 93 | AuthApi.authorize(context,userId,password).onAuthorizeFinished( 94 | authInfo->doAuthorizeWorks(authInfo)//处理登录后的逻辑,把授权码保存用于请求其他业务接口,例如请求用户信息等 95 | ); 96 | ``` 97 | 98 | 所以,作者觉得接口设计或者提供应该是**利他主义**的。当然这纯粹是作者的一家之言,欢迎继续拍砖。 99 | 100 | #### 应用层 101 | 102 | 顾名思义,这一层是对整个 App 的整合,也是 App 的入口。这里有 **Main** 和 **Dev**。其中 **Main** 是对各个业务组件的整合,是最终打包的产品的上层应用。而组件入口是独立运行和调试各个组件的子应用。 103 | 104 | **Dev** 在 Android Studio 中是对应一个 **Application** 。在 gradle 中配置为 105 | 106 | ```groovy 107 | apply plugin: 'com.android.application' 108 | ``` 109 | 110 | 它是一个可以独立运行的子工程,要调试 Module A 那么在 **Dev** 中将引用该组件 111 | 112 | ```groovy 113 | dependencies { 114 | compile fileTree(dir: 'libs', include: ['*.jar']) 115 | compile project(':moduleA') 116 | ... 117 | } 118 | ``` 119 | 120 | 这就是一个大概的思路,可以看出这个框架关键的部分是在于**业务层**的分离。需要把原来项目中的**基础模块**抽取出来,放在**内核层**中。那么下一步就开始构建我们的内核层组件。可持续关注 [wecodexyz/Componentization][3] 项目的更新。 121 | 122 | [1]:http://angrycode.qiniudn.com/App%E6%9E%B6%E6%9E%84%E5%9B%BE.png "App 架构图" 123 | [2]:https://github.com/alibaba/ARouter "ARouter" 124 | [3]:https://github.com/wecodexyz/Componentization "Componentization" -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 25 5 | buildToolsVersion "25.0.2" 6 | defaultConfig { 7 | applicationId "net.angrycode.componentization" 8 | minSdkVersion 15 9 | targetSdkVersion 25 10 | versionCode 1 11 | versionName "1.0" 12 | } 13 | buildTypes { 14 | release { 15 | minifyEnabled true 16 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 17 | //这里配置String常量,可以用BuildConfig类引用 18 | buildConfigField "String", "APP_USER", "\"${app_user}\"" 19 | //这里配置string资源,使用@string可以在manifest等文件中引用 20 | resValue "string", "app_key", "${app_key}" 21 | resValue "string", "app_secret", "${app_secret}" 22 | } 23 | 24 | debug { 25 | //这里配置String常量,可以用BuildConfig类引用 26 | buildConfigField "String", "APP_USER", "\"${app_user}\"" 27 | //这里配置string资源,使用@string可以在manifest等文件中引用 28 | resValue "string", "app_key", "${app_key}" 29 | resValue "string", "app_secret", "${app_secret}" 30 | } 31 | } 32 | } 33 | 34 | dependencies { 35 | compile fileTree(dir: 'libs', include: ['*.jar']) 36 | compile 'com.android.support:appcompat-v7:25.3.1' 37 | compile 'com.android.support.constraint:constraint-layout:1.0.2' 38 | compile project(':core') 39 | compile project(':data') 40 | } 41 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /Users/lancelot/Library/Android/sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | 19 | # Uncomment this to preserve the line number information for 20 | # debugging stack traces. 21 | -keepattributes SourceFile,LineNumberTable 22 | 23 | # If you keep the line number information, uncomment this to 24 | # hide the original source file name. 25 | -renamesourcefileattribute SourceFile 26 | # okhttp and okio 27 | -dontwarn okhttp3.** 28 | -dontwarn okio.** 29 | -dontwarn javax.annotation.** -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /app/src/main/java/net/angrycode/componentization/MainActivity.java: -------------------------------------------------------------------------------- 1 | package net.angrycode.componentization; 2 | 3 | import android.support.v7.app.AppCompatActivity; 4 | import android.os.Bundle; 5 | import android.widget.TextView; 6 | 7 | import net.angrycode.core.network.SimpleTextRequest; 8 | 9 | public class MainActivity extends AppCompatActivity { 10 | 11 | public static final String TAG = MainActivity.class.getSimpleName(); 12 | TextView textView; 13 | SimpleTextRequest request; 14 | 15 | @Override 16 | protected void onCreate(Bundle savedInstanceState) { 17 | super.onCreate(savedInstanceState); 18 | setContentView(R.layout.activity_main); 19 | // textView = (TextView) findViewById(R.id.tv_content); 20 | // request = new SimpleTextRequest(this, null); 21 | // request.request() 22 | // .subscribeOn(Schedulers.computation()) 23 | // .observeOn(AndroidSchedulers.mainThread()) 24 | // .subscribe(new Consumer() { 25 | // @Override 26 | // public void accept(@NonNull String s) throws Exception { 27 | // textView.setText(s); 28 | // } 29 | // }, new Consumer() { 30 | // @Override 31 | // public void accept(@NonNull Throwable throwable) throws Exception { 32 | // textView.setText(throwable.getMessage()); 33 | // } 34 | // }); 35 | // Flowable.fromCallable(new Callable>() { 36 | // @Override 37 | // public List call() throws Exception { 38 | // List dataList = RepositoryFactory.get().queryAll(); 39 | // return dataList; 40 | // } 41 | // }).observeOn(AndroidSchedulers.mainThread()) 42 | // .subscribeOn(Schedulers.io()) 43 | // .subscribe(new Consumer>() { 44 | // @Override 45 | // public void accept(@NonNull List datas) throws Exception { 46 | // textView.setText("data size:" + datas.size()); 47 | // } 48 | // }, new Consumer() { 49 | // @Override 50 | // public void accept(@NonNull Throwable throwable) throws Exception { 51 | // textView.setText(throwable.getMessage()); 52 | // } 53 | // }); 54 | // Flowable.just("one", "two", "three", "four", "five", "six") 55 | // .subscribeOn(Schedulers.computation()) 56 | // .observeOn(Schedulers.single()) 57 | // .subscribe(new Consumer() { 58 | // @Override 59 | // public void accept(@NonNull String s) throws Exception { 60 | // Log.e(TAG, s); 61 | // } 62 | // }, new Consumer() { 63 | // @Override 64 | // public void accept(@NonNull Throwable throwable) throws Exception { 65 | // Log.e(TAG, throwable.getMessage()); 66 | // } 67 | // }); 68 | // 69 | // Flowable.fromCallable(new Callable() { 70 | // @Override 71 | // public String call() throws Exception { 72 | // Thread.sleep(3000); 73 | // return "from callable"; 74 | // } 75 | // }).flatMap(new Function>() { 76 | // @Override 77 | // public Publisher apply(@NonNull String s) throws Exception { 78 | // return Flowable.just(s + " flat map"); 79 | // } 80 | // }).subscribeOn(Schedulers.computation()) 81 | // .observeOn(AndroidSchedulers.mainThread()) 82 | // .subscribe(new Consumer() { 83 | // @Override 84 | // public void accept(@NonNull String s) throws Exception { 85 | // Log.e(TAG, s); 86 | // textView.setText(s); 87 | // } 88 | // }, new Consumer() { 89 | // @Override 90 | // public void accept(@NonNull Throwable throwable) throws Exception { 91 | // Log.e(TAG, throwable.getMessage()); 92 | // } 93 | // }); 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 11 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hylinux1024/Componentization/52c188b675f53dae26a445e2447fecd360b5880a/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hylinux1024/Componentization/52c188b675f53dae26a445e2447fecd360b5880a/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hylinux1024/Componentization/52c188b675f53dae26a445e2447fecd360b5880a/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hylinux1024/Componentization/52c188b675f53dae26a445e2447fecd360b5880a/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hylinux1024/Componentization/52c188b675f53dae26a445e2447fecd360b5880a/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hylinux1024/Componentization/52c188b675f53dae26a445e2447fecd360b5880a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hylinux1024/Componentization/52c188b675f53dae26a445e2447fecd360b5880a/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hylinux1024/Componentization/52c188b675f53dae26a445e2447fecd360b5880a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hylinux1024/Componentization/52c188b675f53dae26a445e2447fecd360b5880a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hylinux1024/Componentization/52c188b675f53dae26a445e2447fecd360b5880a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #3F51B5 4 | #303F9F 5 | #FF4081 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | Componentization 3 | 4 | let us know if you have feedback on this or if 6 | you would like to log in with another identity service. Thanks! This is a longer string! 7 | ]]> 8 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | 3 | buildscript { 4 | repositories { 5 | jcenter() 6 | } 7 | dependencies { 8 | 9 | classpath 'com.android.tools.build:gradle:2.3.3' 10 | classpath 'com.github.dcendents:android-maven-gradle-plugin:1.5' 11 | // NOTE: Do not place your application dependencies here; they belong 12 | // in the individual module build.gradle files 13 | } 14 | } 15 | 16 | allprojects { 17 | repositories { 18 | jcenter() 19 | maven { url "https://jitpack.io" } 20 | } 21 | } 22 | 23 | task clean(type: Delete) { 24 | delete rootProject.buildDir 25 | } 26 | -------------------------------------------------------------------------------- /buildsystem/dependencies.gradle: -------------------------------------------------------------------------------- 1 | ext { 2 | //Version 3 | supportLibrary = '25.3.1' 4 | 5 | //libraries dependencies 6 | supportDependencies = [ 7 | design : "com.android.support:design:${supportLibrary}", 8 | recyclerView : "com.android.support:recyclerview-v7:${supportLibrary}", 9 | cardView : "com.android.support:cardview-v7:${supportLibrary}", 10 | appCompat : "com.android.support:appcompat-v7:${supportLibrary}", 11 | supportAnnotation: "com.android.support:support-annotations:${supportLibrary}", 12 | ] 13 | } -------------------------------------------------------------------------------- /core/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /core/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | apply plugin: 'com.github.dcendents.android-maven' 3 | 4 | group='com.github.wecodexyz' 5 | android { 6 | compileSdkVersion 25 7 | buildToolsVersion "25.0.3" 8 | 9 | defaultConfig { 10 | minSdkVersion 14 11 | targetSdkVersion 25 12 | versionCode 1 13 | versionName "1.0" 14 | 15 | } 16 | buildTypes { 17 | release { 18 | minifyEnabled false 19 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 20 | } 21 | } 22 | } 23 | 24 | dependencies { 25 | compile fileTree(dir: 'libs', include: ['*.jar']) 26 | compile 'com.android.support:appcompat-v7:25.3.1' 27 | compile 'com.squareup.okhttp3:okhttp:3.8.1' 28 | compile 'io.reactivex.rxjava2:rxjava:2.1.1' 29 | compile 'io.reactivex.rxjava2:rxandroid:2.0.1' 30 | } 31 | 32 | //apply from: 'maven-release-aar.gradle' 33 | -------------------------------------------------------------------------------- /core/maven-release-aar.gradle: -------------------------------------------------------------------------------- 1 | // 1.maven-插件 2 | apply plugin: 'maven' 3 | 4 | // 2.maven-信息 5 | ext {// ext is a gradle closure allowing the declaration of global properties 6 | PUBLISH_GROUP_ID = 'net.angrycode' 7 | PUBLISH_ARTIFACT_ID = 'component' 8 | PUBLISH_VERSION = android.defaultConfig.versionName 9 | } 10 | 11 | // 3.maven-输出路径 12 | uploadArchives { 13 | repositories.mavenDeployer { 14 | //这里就是最后输出地址,在自己电脑上新建个文件夹,把文件夹路径粘贴在此 15 | //注意”file://“ + 路径,有三个斜杠,别漏了 16 | repository(url: "file:///Users/wecodexyz") 17 | 18 | pom.project { 19 | groupId project.PUBLISH_GROUP_ID 20 | artifactId project.PUBLISH_ARTIFACT_ID 21 | version project.PUBLISH_VERSION 22 | } 23 | } 24 | } 25 | 26 | //以下代码会生成jar包源文件,如果是不开源码,请不要输入这段 27 | //aar包内包含注释 28 | task androidSourcesJar(type: Jar) { 29 | classifier = 'sources' 30 | from android.sourceSets.main.java.sourceFiles 31 | } 32 | 33 | artifacts { 34 | archives androidSourcesJar 35 | } 36 | -------------------------------------------------------------------------------- /core/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /Users/lancelot/Library/Android/sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | 19 | # Uncomment this to preserve the line number information for 20 | # debugging stack traces. 21 | -keepattributes SourceFile,LineNumberTable 22 | 23 | # If you keep the line number information, uncomment this to 24 | # hide the original source file name. 25 | -renamesourcefileattribute SourceFile 26 | 27 | -keep class net.angrycode.core.network.** {*;} 28 | -keep class net.angrycode.mvp.** {*;} 29 | -keep class net.angrycode.toolkit.** {*;} 30 | -------------------------------------------------------------------------------- /core/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /core/src/main/java/net/angrycode/core/network/BaseMultipartRequest.java: -------------------------------------------------------------------------------- 1 | package net.angrycode.core.network; 2 | 3 | import android.content.Context; 4 | 5 | import java.io.File; 6 | 7 | import okhttp3.MediaType; 8 | import okhttp3.MultipartBody; 9 | import okhttp3.RequestBody; 10 | 11 | /** 12 | * Created by wecodexyz@gmail.com on 2017/10/26 上午11:07. 13 | * GitHub - https://github.com/wecodexyz 14 | * Description: 15 | */ 16 | 17 | public abstract class BaseMultipartRequest extends RequestWrapper { 18 | private static final MediaType MEDIA_TYPE_PNG = MediaType.parse("image/png"); 19 | 20 | public BaseMultipartRequest(Context context) { 21 | super(context); 22 | } 23 | 24 | protected RequestBody requestBody() { 25 | MultipartBody.Builder builder = new MultipartBody.Builder() 26 | .setType(MultipartBody.FORM) 27 | .addPart(super.requestBody()) 28 | .addFormDataPart(getName(), getFileName(), 29 | RequestBody.create(MEDIA_TYPE_PNG, new File(getFilePath()))); 30 | return builder.build(); 31 | } 32 | 33 | protected abstract String getFilePath(); 34 | 35 | protected abstract String getName(); 36 | 37 | protected abstract String getFileName(); 38 | 39 | @Override 40 | public HttpMethod getHttpMethod() { 41 | return HttpMethod.POST; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /core/src/main/java/net/angrycode/core/network/BaseTextRequest.java: -------------------------------------------------------------------------------- 1 | package net.angrycode.core.network; 2 | 3 | import android.content.Context; 4 | import android.util.Pair; 5 | 6 | import org.reactivestreams.Publisher; 7 | 8 | import java.util.concurrent.Callable; 9 | 10 | import io.reactivex.Flowable; 11 | import io.reactivex.Observable; 12 | import io.reactivex.ObservableSource; 13 | import io.reactivex.android.schedulers.AndroidSchedulers; 14 | import io.reactivex.annotations.NonNull; 15 | import io.reactivex.functions.Consumer; 16 | import io.reactivex.functions.Function; 17 | import io.reactivex.schedulers.Schedulers; 18 | 19 | /** 20 | * Network request using rxjava2 21 | * Created by lancelot on 2017/7/7. 22 | */ 23 | 24 | public abstract class BaseTextRequest extends RequestWrapper { 25 | 26 | public BaseTextRequest(Context context) { 27 | super(context); 28 | } 29 | 30 | public Flowable request() { 31 | return Flowable.fromCallable(new Callable>() { 32 | @Override 33 | public Pair call() throws Exception { 34 | Pair result = doRequest(); 35 | return result; 36 | } 37 | }).flatMap(new Function, Publisher>() { 38 | @Override 39 | public Publisher apply(@NonNull Pair pair) throws Exception { 40 | if (isSuccessful(pair.first)) { 41 | return Flowable.just(onRequestFinish(pair.second)); 42 | } 43 | return Flowable.just(onRequestError(pair.first, pair.second)); 44 | } 45 | }); 46 | 47 | } 48 | 49 | public void request(final OnRequestCallback callback) { 50 | request().observeOn(AndroidSchedulers.mainThread()) 51 | .subscribeOn(Schedulers.io()) 52 | .subscribe(new Consumer() { 53 | @Override 54 | public void accept(@NonNull T t) throws Exception { 55 | callback.callback(t); 56 | } 57 | }, new Consumer() { 58 | @Override 59 | public void accept(@NonNull Throwable throwable) throws Exception { 60 | callback.onError(new Exception(throwable)); 61 | } 62 | }); 63 | } 64 | 65 | 66 | @Override 67 | public boolean isSupportCache() { 68 | return true; 69 | } 70 | 71 | protected abstract T onRequestFinish(String result); 72 | 73 | protected abstract T onRequestError(int code, String message); 74 | 75 | 76 | 77 | } -------------------------------------------------------------------------------- /core/src/main/java/net/angrycode/core/network/IRequest.java: -------------------------------------------------------------------------------- 1 | package net.angrycode.core.network; 2 | 3 | import android.util.Pair; 4 | 5 | import java.util.Map; 6 | 7 | /** 8 | * IRequest 9 | * Network request interface 10 | * Created by lancelot on 2017/7/7. 11 | */ 12 | 13 | public interface IRequest { 14 | enum HttpMethod { 15 | GET, POST, PUT, DELETE 16 | } 17 | 18 | Map getParams(); 19 | 20 | void setParams(Map params); 21 | 22 | void addParams(Map params); 23 | 24 | void putParam(String key, String value); 25 | 26 | void putParam(String key, int value); 27 | String getUrl(); 28 | 29 | Pair doRequest(); 30 | 31 | boolean isSupportCache(); 32 | 33 | void setMaxRetries(int retry); 34 | 35 | void setCookie(String cookie); 36 | 37 | void removeCookie(); 38 | 39 | void addHeader(String key, String value); 40 | 41 | void removeHeader(String key); 42 | 43 | void setContentType(String contentType); 44 | 45 | void setUserAgent(String userAgent); 46 | 47 | String getUserAgent(); 48 | 49 | HttpMethod getHttpMethod(); 50 | 51 | void setProxy(String host, int port); 52 | 53 | } -------------------------------------------------------------------------------- /core/src/main/java/net/angrycode/core/network/OnRequestCallback.java: -------------------------------------------------------------------------------- 1 | package net.angrycode.core.network; 2 | 3 | public interface OnRequestCallback { 4 | void callback(T t); 5 | 6 | void onError(Exception e); 7 | } -------------------------------------------------------------------------------- /core/src/main/java/net/angrycode/core/network/Request.java: -------------------------------------------------------------------------------- 1 | package net.angrycode.core.network; 2 | 3 | import android.support.annotation.NonNull; 4 | import android.util.Log; 5 | 6 | import java.net.URLEncoder; 7 | import java.util.HashMap; 8 | import java.util.Map; 9 | 10 | /** 11 | * Network request architecture 12 | * Created by lancelot on 2017/7/7. 13 | */ 14 | 15 | public abstract class Request implements IRequest { 16 | 17 | public static final String TAG = Request.class.getSimpleName(); 18 | 19 | Map mHeaders = new HashMap<>(); 20 | Map mParams = new HashMap<>(); 21 | int mMaxRetries = 0; 22 | String mCookie; 23 | String mContentType; 24 | String mUserAgent; 25 | 26 | public Request() { 27 | } 28 | 29 | @Override 30 | public Map getParams() { 31 | return mParams; 32 | } 33 | 34 | @Override 35 | public void setParams(Map params) { 36 | if (params != null) { 37 | mParams.clear(); 38 | mParams.putAll(params); 39 | } 40 | } 41 | 42 | @Override 43 | public void addParams(Map params) { 44 | if (params != null) 45 | mParams.putAll(params); 46 | } 47 | 48 | @Override 49 | public void putParam(String key, String value) { 50 | mParams.put(key, value); 51 | } 52 | 53 | @Override 54 | public void putParam(String key, int value) { 55 | mParams.put(key, String.valueOf(value)); 56 | } 57 | 58 | @Override 59 | public void removeCookie() { 60 | mCookie = null; 61 | } 62 | 63 | @Override 64 | public boolean isSupportCache() { 65 | return false; 66 | } 67 | 68 | @Override 69 | public void setMaxRetries(int retry) { 70 | mMaxRetries = retry; 71 | } 72 | 73 | @Override 74 | public void setCookie(String cookie) { 75 | mCookie = cookie; 76 | } 77 | 78 | @Override 79 | public void addHeader(@NonNull String key, @NonNull String value) { 80 | mHeaders.put(key, value); 81 | } 82 | 83 | @Override 84 | public void removeHeader(String key) { 85 | mHeaders.remove(key); 86 | } 87 | 88 | @Override 89 | public void setContentType(String contentType) { 90 | mContentType = contentType; 91 | } 92 | 93 | @Override 94 | public void setUserAgent(String userAgent) { 95 | mUserAgent = userAgent; 96 | } 97 | 98 | @Override 99 | public String getUserAgent() { 100 | return mUserAgent; 101 | } 102 | 103 | 104 | public String getUrlWithParams() { 105 | StringBuilder builder = new StringBuilder(getUrl()); 106 | try { 107 | if (mParams.size() > 0) { 108 | builder.append("?"); 109 | } 110 | int pos = 0; 111 | for (String key : mParams.keySet()) { 112 | if (pos > 0) { 113 | builder.append("&"); 114 | } 115 | builder.append(String.format("%s=%s", key, URLEncoder.encode(mParams.get(key), "utf-8"))); 116 | pos++; 117 | } 118 | } catch (Exception e) { 119 | Log.e(TAG, e.getMessage()); 120 | } 121 | return builder.toString(); 122 | } 123 | 124 | public class Builder { 125 | String url; 126 | Map params = new HashMap<>(); 127 | int maxRetries = 0; 128 | String cookie; 129 | } 130 | } -------------------------------------------------------------------------------- /core/src/main/java/net/angrycode/core/network/RequestWrapper.java: -------------------------------------------------------------------------------- 1 | package net.angrycode.core.network; 2 | 3 | import android.content.Context; 4 | import android.support.annotation.Nullable; 5 | import android.util.Log; 6 | import android.util.Pair; 7 | 8 | import java.io.File; 9 | import java.io.IOException; 10 | import java.lang.ref.WeakReference; 11 | import java.net.InetSocketAddress; 12 | import java.net.Proxy; 13 | import java.util.List; 14 | import java.util.concurrent.TimeUnit; 15 | 16 | import okhttp3.Cache; 17 | import okhttp3.Cookie; 18 | import okhttp3.CookieJar; 19 | import okhttp3.FormBody; 20 | import okhttp3.HttpUrl; 21 | import okhttp3.Interceptor; 22 | import okhttp3.MultipartBody; 23 | import okhttp3.OkHttpClient; 24 | import okhttp3.RequestBody; 25 | import okhttp3.Response; 26 | import okhttp3.ResponseBody; 27 | 28 | /** 29 | * OkHttpClient Wrapper 30 | * Created by lancelot on 2017/7/7. 31 | */ 32 | 33 | public abstract class RequestWrapper extends Request { 34 | 35 | protected static final int ERROR_NETWORK = 404; 36 | 37 | protected static final int CACHE_SIZE = 10 * 1024 * 1024; // 10 MiB 38 | 39 | private WeakReference mContextRef; 40 | 41 | OkHttpClient mClient; 42 | Proxy mProxy; 43 | 44 | Cache mCache; 45 | 46 | public RequestWrapper(Context context) { 47 | mContextRef = new WeakReference<>(context); 48 | if (isSupportCache()) { 49 | mCache = new Cache(context.getCacheDir(), CACHE_SIZE); 50 | } 51 | OkHttpClient.Builder builder = new OkHttpClient.Builder() 52 | .connectTimeout(30, TimeUnit.SECONDS) 53 | .readTimeout(30, TimeUnit.SECONDS) 54 | .writeTimeout(30, TimeUnit.SECONDS) 55 | .proxy(mProxy) 56 | .cache(mCache); 57 | mClient = builder.build(); 58 | } 59 | 60 | @Override 61 | public Pair doRequest() { 62 | Pair result = new Pair<>(ERROR_NETWORK, "network is not connect!"); 63 | okhttp3.Request request = null; 64 | 65 | if (getHttpMethod() == HttpMethod.POST) { 66 | request = requestBuilder().url(getUrl()).post(requestBody()).build(); 67 | } else { 68 | request = requestBuilder().url(getUrlWithParams()).build(); 69 | } 70 | try { 71 | Response response = mClient.newCall(request).execute(); 72 | if (response.isSuccessful()) { 73 | ResponseBody body = response.body(); 74 | String content = ""; 75 | if (body != null) { 76 | content = body.string(); 77 | } 78 | result = new Pair<>(response.code(), content); 79 | } else { 80 | result = new Pair<>(response.code(), response.message()); 81 | } 82 | 83 | } catch (IOException e) { 84 | Log.d(TAG, "" + e.getMessage()); 85 | } 86 | Log.d(TAG, "request url - " + getUrlWithParams()); 87 | Log.d(TAG, "response result - " + result.second); 88 | return result; 89 | } 90 | 91 | public boolean isSuccessful(int code) { 92 | return code >= 200 && code < 300; 93 | } 94 | 95 | protected RequestBody requestBody() { 96 | FormBody.Builder builder = new FormBody.Builder(); 97 | for (String key : mParams.keySet()) { 98 | builder.add(key, mParams.get(key)); 99 | } 100 | return builder.build(); 101 | } 102 | 103 | private okhttp3.Request.Builder requestBuilder() { 104 | okhttp3.Request.Builder builder = new okhttp3.Request.Builder(); 105 | for (String key : mHeaders.keySet()) { 106 | builder.addHeader(key, mHeaders.get(key)); 107 | } 108 | return builder; 109 | } 110 | 111 | @Override 112 | public void setProxy(String host, int port) { 113 | mProxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress(host, port)); 114 | } 115 | 116 | public 117 | @Nullable 118 | Context getContext() { 119 | return mContextRef.get(); 120 | } 121 | 122 | } 123 | -------------------------------------------------------------------------------- /core/src/main/java/net/angrycode/core/network/SimpleTextRequest.java: -------------------------------------------------------------------------------- 1 | package net.angrycode.core.network; 2 | 3 | import android.content.Context; 4 | 5 | import java.util.Map; 6 | 7 | /** 8 | * Created by lancelot on 2017/7/8. 9 | */ 10 | 11 | public class SimpleTextRequest extends BaseTextRequest { 12 | 13 | public SimpleTextRequest(Context context, Map params) { 14 | super(context); 15 | addParams(params); 16 | } 17 | 18 | @Override 19 | public String getUrl() { 20 | return "https://raw.githubusercontent.com/wecodexyz/Componentization/master/README.md"; 21 | } 22 | 23 | @Override 24 | public HttpMethod getHttpMethod() { 25 | return HttpMethod.GET; 26 | } 27 | 28 | @Override 29 | protected String onRequestFinish(String result) { 30 | return result; 31 | } 32 | 33 | @Override 34 | protected String onRequestError(int code, String message) { 35 | return message; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /core/src/main/java/net/angrycode/mvp/BasePresenter.java: -------------------------------------------------------------------------------- 1 | package net.angrycode.mvp; 2 | 3 | import android.app.Activity; 4 | 5 | import net.angrycode.core.R; 6 | 7 | /** 8 | * Created by wecodexyz on 2017/8/16. 9 | */ 10 | 11 | public abstract class BasePresenter implements IPresenter { 12 | 13 | public static final int ERROR_RX = 2000; 14 | 15 | public T view; 16 | 17 | // public DisposableContainer subscription = new CompositeDisposable(); 18 | 19 | public BasePresenter(T view) { 20 | this.view = view; 21 | } 22 | 23 | @Override 24 | public void onCreate() { 25 | 26 | } 27 | 28 | Activity getActivity() { 29 | return view.getActivity(); 30 | } 31 | 32 | /** 33 | * 用于判断当前view是否已经退出 34 | * 35 | * @return 36 | */ 37 | public boolean isViewDetached() { 38 | if (view == null) { 39 | return true; 40 | } 41 | if (view.getActivity() == null) { 42 | return true; 43 | } 44 | 45 | if (view.getActivity().isFinishing()) { 46 | return true; 47 | } 48 | // if (subscription == null) { 49 | // return true; 50 | // } 51 | // if (subscription.) { 52 | // return true; 53 | // } 54 | return false; 55 | } 56 | 57 | public String getRxErrorText() { 58 | return view.getActivity().getString(R.string.error_network); 59 | } 60 | 61 | @Override 62 | public void onDestroy() { 63 | view = null; 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /core/src/main/java/net/angrycode/mvp/DemoActivity.java: -------------------------------------------------------------------------------- 1 | package net.angrycode.mvp; 2 | 3 | import android.app.Activity; 4 | import android.os.Bundle; 5 | import android.support.annotation.Nullable; 6 | import android.support.v7.app.AppCompatActivity; 7 | 8 | /** 9 | * Created by wecodexyz on 2017/8/18. 10 | */ 11 | 12 | public class DemoActivity extends AppCompatActivity implements DemoContract.View { 13 | 14 | DemoPresenter mPresenter; 15 | 16 | @Override 17 | protected void onCreate(@Nullable Bundle savedInstanceState) { 18 | super.onCreate(savedInstanceState); 19 | mPresenter = new DemoPresenter(this); 20 | mPresenter.getData();//请求数据 21 | } 22 | 23 | @Override 24 | protected void onDestroy() { 25 | super.onDestroy(); 26 | mPresenter.onDestroy(); 27 | } 28 | 29 | @Override 30 | public Activity getActivity() { 31 | return this; 32 | } 33 | 34 | @Override 35 | public void onGetDataFinished(String data) { 36 | // 这里获取到数据 37 | } 38 | 39 | @Override 40 | public void onBegin() { 41 | //请求开始,可以显示loading等操作 42 | } 43 | 44 | @Override 45 | public void onFinished() { 46 | //请求结束,取消loading等操作 47 | } 48 | 49 | @Override 50 | public void onError(int errorCode, String message) { 51 | //处理出错 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /core/src/main/java/net/angrycode/mvp/DemoContract.java: -------------------------------------------------------------------------------- 1 | package net.angrycode.mvp; 2 | 3 | /** 4 | * Created by huangyanglin on 2017/8/17. 5 | */ 6 | 7 | public interface DemoContract { 8 | 9 | interface View extends IView { 10 | void onGetDataFinished(String data); 11 | //other callbacks 12 | } 13 | 14 | interface Presenter extends IPresenter { 15 | void getData(); 16 | //other mehtods 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /core/src/main/java/net/angrycode/mvp/DemoPresenter.java: -------------------------------------------------------------------------------- 1 | package net.angrycode.mvp; 2 | 3 | /** 4 | * Created by wecodexyz on 2017/8/17. 5 | */ 6 | 7 | public class DemoPresenter extends BasePresenter implements DemoContract.Presenter { 8 | public DemoPresenter(DemoContract.View view) { 9 | super(view); 10 | } 11 | 12 | @Override 13 | public void getData() { 14 | view.onBegin(); 15 | view.onGetDataFinished(""); 16 | view.onFinished(); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /core/src/main/java/net/angrycode/mvp/IPresenter.java: -------------------------------------------------------------------------------- 1 | package net.angrycode.mvp; 2 | 3 | /** 4 | * Created by wecodexyz on 2017/8/16. 5 | */ 6 | 7 | public interface IPresenter { 8 | 9 | void onCreate(); 10 | 11 | void onDestroy(); 12 | } 13 | -------------------------------------------------------------------------------- /core/src/main/java/net/angrycode/mvp/IView.java: -------------------------------------------------------------------------------- 1 | package net.angrycode.mvp; 2 | 3 | import android.app.Activity; 4 | 5 | /** 6 | * Created by wecodexyz on 2017/8/16. 7 | */ 8 | 9 | public interface IView { 10 | Activity getActivity(); 11 | 12 | /** 13 | * 请求开始 14 | */ 15 | void onBegin(); 16 | 17 | /** 18 | * 请求结束 19 | */ 20 | void onFinished(); 21 | 22 | /** 23 | * 请求出错 24 | * @param errorCode 25 | * @param message 26 | */ 27 | void onError(int errorCode, String message); 28 | } 29 | -------------------------------------------------------------------------------- /core/src/main/java/net/angrycode/toolkit/AndroidShare.java: -------------------------------------------------------------------------------- 1 | package net.angrycode.toolkit; 2 | 3 | import android.annotation.SuppressLint; 4 | import android.content.ComponentName; 5 | import android.content.Context; 6 | import android.content.Intent; 7 | import android.content.pm.PackageInfo; 8 | import android.content.pm.PackageManager; 9 | import android.graphics.Bitmap; 10 | import android.graphics.drawable.BitmapDrawable; 11 | import android.graphics.drawable.Drawable; 12 | import android.net.Uri; 13 | import android.provider.MediaStore; 14 | import android.text.TextUtils; 15 | import android.util.Log; 16 | import android.widget.Toast; 17 | 18 | import net.angrycode.core.R; 19 | 20 | import java.util.List; 21 | 22 | /** 23 | * 不经过第三方SDK分享 24 | */ 25 | public class AndroidShare { 26 | 27 | public static final String TAG = "AndroidShare"; 28 | 29 | private Context context; 30 | /** 31 | * 文本类型 32 | */ 33 | public static int TEXT = 0; 34 | /** 35 | * 图片类型 36 | */ 37 | public static int DRAWABLE = 1; 38 | 39 | public AndroidShare(Context context) { 40 | this.context = context; 41 | } 42 | 43 | /** 44 | * 分享到QQ好友 45 | * 46 | * @param msgTitle (分享标题) 47 | * @param msgText (分享内容) 48 | * @param type (分享类型) 49 | * @param drawable (分享图片,若分享类型为AndroidShare.TEXT,则可以为null) 50 | */ 51 | public void shareQQFriend(String msgTitle, String msgText, int type, 52 | Drawable drawable) { 53 | shareMsg("com.tencent.mobileqq", 54 | "com.tencent.mobileqq.activity.JumpActivity", "QQ", msgTitle, 55 | msgText, type, drawable); 56 | } 57 | 58 | public void shareImgToQQFriend(String msgTitle, String msgText, String imgUrl) { 59 | // Glide.with(context).load(imgUrl).asBitmap().into(new SimpleTarget() { 60 | // 61 | // @Override 62 | // public void onResourceReady(Bitmap resource, GlideAnimation glideAnimation) { 63 | // if (resource != null) { 64 | // shareQQFriend(msgTitle, msgText, DRAWABLE, new BitmapDrawable(context.getResources(), resource)); 65 | // } 66 | // } 67 | // }); 68 | } 69 | 70 | /** 71 | * 分享到微信好友 72 | * 73 | * @param msgTitle (分享标题) 74 | * @param msgText (分享内容) 75 | * @param type (分享类型) 76 | * @param drawable (分享图片,若分享类型为AndroidShare.TEXT,则可以为null) 77 | */ 78 | public void shareWeChatFriend(String msgTitle, String msgText, int type, 79 | Drawable drawable) { 80 | 81 | shareMsg("com.tencent.mm", "com.tencent.mm.ui.tools.ShareImgUI", "微信", 82 | msgTitle, msgText, type, drawable); 83 | } 84 | 85 | /** 86 | * 分享到微信朋友圈(分享朋友圈一定需要图片) 87 | * 88 | * @param msgTitle (分享标题) 89 | * @param msgText (分享内容) 90 | * @param drawable (分享图片) 91 | */ 92 | public void shareWeChatFriendCircle(String msgTitle, String msgText, 93 | Drawable drawable) { 94 | 95 | shareMsg("com.tencent.mm", "com.tencent.mm.ui.tools.ShareToTimeLineUI", 96 | "微信", msgTitle, msgText, AndroidShare.DRAWABLE, drawable); 97 | } 98 | 99 | /** 100 | * 分享到其它 101 | * 102 | * @param msgTitle (分享标题) 103 | * @param msgText (分享内容) 104 | * @param type (分享类型) 105 | * @param drawable (分享图片,若分享类型为AndroidShare.TEXT,则可以为null) 106 | */ 107 | public void shareOthers(String msgTitle, String msgText, int type, 108 | Drawable drawable) { 109 | 110 | shareMsg("", "", "其他", msgTitle, msgText, type, drawable); 111 | } 112 | 113 | /** 114 | * 点击分享的代码 115 | * 116 | * @param packageName (包名) 117 | * @param activityName (类名) 118 | * @param appname (应用名) 119 | * @param msgTitle (标题) 120 | * @param msgText (内容) 121 | * @param type (发送类型:text or pic 微信朋友圈只支持pic) 122 | */ 123 | @SuppressLint("NewApi") 124 | private void shareMsg(String packageName, String activityName, 125 | String appname, String msgTitle, String msgText, int type, 126 | Drawable drawable) { 127 | if (!packageName.isEmpty() && !isAppInstalled(context, packageName)) {// 判断APP是否存在 128 | Toast.makeText(context, "请先安装" + appname, Toast.LENGTH_SHORT) 129 | .show(); 130 | return; 131 | } 132 | 133 | String content = context.getString(R.string.share_format, msgTitle, msgText); 134 | try { 135 | Intent intent = new Intent("android.intent.action.SEND"); 136 | if (type == AndroidShare.TEXT) { 137 | intent.setType("text/plain"); 138 | 139 | } else if (type == AndroidShare.DRAWABLE) { 140 | intent.setType("image/jpeg"); 141 | BitmapDrawable bd = (BitmapDrawable) drawable; 142 | Bitmap bt = bd.getBitmap(); 143 | // Bitmap bt = BitmapFactory.decodeResource(context.getResources(), 144 | // R.drawable.white); 145 | 146 | // if ((imgPath == null) || (imgPath.equals(""))) { 147 | // intent.setType("text/plain"); 148 | // } else { 149 | // File f = new File(imgPath); 150 | // if ((f != null) && (f.exists()) && (f.isFile())) { 151 | // intent.setType("image/png"); 152 | // intent.putExtra(Intent.EXTRA_STREAM, Uri.fromFile(f)); 153 | // } 154 | // } 155 | final Uri uri = Uri.parse(MediaStore.Images.Media.insertImage( 156 | context.getContentResolver(), bt, null, null)); 157 | intent.putExtra(Intent.EXTRA_STREAM, uri); 158 | } 159 | 160 | intent.putExtra(Intent.EXTRA_SUBJECT, msgTitle); 161 | intent.putExtra(Intent.EXTRA_TEXT, TextUtils.isEmpty(msgTitle) ? msgText : content); 162 | if (!packageName.isEmpty()) { 163 | intent.setComponent(new ComponentName(packageName, activityName)); 164 | context.startActivity(intent); 165 | } else { 166 | context.startActivity(Intent.createChooser(intent, msgTitle)); 167 | } 168 | } catch (Exception e) { 169 | Log.e(TAG, e.getMessage()); 170 | Toast.makeText(context, R.string.error_share_fail, Toast.LENGTH_SHORT).show(); 171 | } 172 | } 173 | 174 | /** 175 | * 判断相对应的APP是否存在 176 | * 177 | * @param context 178 | * @param packageName 179 | * @return 180 | */ 181 | public static boolean isAppInstalled(Context context, String packageName) { 182 | PackageManager packageManager; 183 | packageManager = context.getPackageManager(); 184 | 185 | List pinfo = packageManager.getInstalledPackages(0); 186 | for (int i = 0; i < pinfo.size(); i++) { 187 | if (((PackageInfo) pinfo.get(i)).packageName 188 | .equalsIgnoreCase(packageName)) 189 | return true; 190 | } 191 | return false; 192 | } 193 | } -------------------------------------------------------------------------------- /core/src/main/java/net/angrycode/toolkit/App.java: -------------------------------------------------------------------------------- 1 | package net.angrycode.toolkit; 2 | 3 | import android.app.Application; 4 | import android.util.Log; 5 | 6 | public class App { 7 | public static final String TAG = App.class.getSimpleName(); 8 | public static final Application INSTANCE; 9 | 10 | static { 11 | Application app = null; 12 | try { 13 | app = (Application) Class.forName("android.app.AppGlobals").getMethod("getInitialApplication").invoke(null); 14 | if (app == null) 15 | throw new IllegalStateException("Static initialization of Applications must be on main thread."); 16 | } catch (final Exception e) { 17 | Log.e(TAG, "Failed to get current application from AppGlobals." + e.getMessage()); 18 | try { 19 | app = (Application) Class.forName("android.app.ActivityThread").getMethod("currentApplication").invoke(null); 20 | } catch (final Exception ex) { 21 | Log.e(TAG, "Failed to get current application from ActivityThread." + e.getMessage()); 22 | } 23 | } finally { 24 | INSTANCE = app; 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /core/src/main/java/net/angrycode/toolkit/DateUtils.java: -------------------------------------------------------------------------------- 1 | package net.angrycode.toolkit; 2 | 3 | import android.annotation.TargetApi; 4 | import android.os.Build; 5 | 6 | import java.text.SimpleDateFormat; 7 | import java.util.Date; 8 | import java.util.Locale; 9 | 10 | public class DateUtils { 11 | 12 | /** 13 | * 指定格式返回当前系统时间 14 | */ 15 | public static String getDataTime(String format) { 16 | SimpleDateFormat df = new SimpleDateFormat(format, Locale.getDefault()); 17 | return df.format(new Date()); 18 | } 19 | 20 | /** 21 | * 返回当前系统时间(格式以HH:mm形式) 22 | */ 23 | public static String getDataTime() { 24 | return getDataTime("HH:mm"); 25 | } 26 | 27 | @TargetApi(Build.VERSION_CODES.CUPCAKE) 28 | public static boolean isToday(long when) { 29 | android.text.format.Time time = new android.text.format.Time(); 30 | time.set(when); 31 | 32 | int thenYear = time.year; 33 | int thenMonth = time.month; 34 | int thenMonthDay = time.monthDay; 35 | 36 | time.set(System.currentTimeMillis()); 37 | return (thenYear == time.year) 38 | && (thenMonth == time.month) 39 | && (time.monthDay == thenMonthDay); 40 | } 41 | 42 | public static String getFriendlyTime2(Date date) { 43 | String showStr = ""; 44 | if (isToday(date.getTime())) { 45 | showStr = "今天"; 46 | } else { 47 | showStr = getFriendlyTime(date); 48 | } 49 | return showStr; 50 | } 51 | 52 | /** 53 | * 转换日期到指定格式方便查看的描述说明 54 | * 55 | * @return 几秒前,几分钟前,几小时前,几天前,几个月前,几年前,很久以前(10年前),如果出现之后的时间,则提示:未知 56 | */ 57 | public static String getFriendlyTime(Date date) { 58 | String showStr = ""; 59 | long yearSeconds = 31536000L;//365 * 24 * 60 * 60; 60 | long monthSeconds = 2592000L;//30 * 24 * 60 * 60; 61 | long daySeconds = 86400L;//24 * 60 * 60; 62 | long hourSeconds = 3600L;//60 * 60; 63 | long minuteSeconds = 60L; 64 | 65 | long time = (System.currentTimeMillis() - date.getTime()) / 1000; 66 | if (time <= 50) { 67 | showStr = "刚刚"; 68 | return showStr; 69 | } 70 | if (time / yearSeconds > 0) { 71 | int year = (int) (time / yearSeconds); 72 | if (year > 10) 73 | showStr = "很久以前"; 74 | else { 75 | showStr = year + "年前"; 76 | } 77 | } else if (time / monthSeconds > 0) { 78 | showStr = time / monthSeconds + "个月前"; 79 | } else if (time / daySeconds > 7) { 80 | SimpleDateFormat formatter = new SimpleDateFormat("MM-dd", Locale.getDefault()); 81 | showStr = formatter.format(date); 82 | } else if (time / daySeconds > 0) { 83 | showStr = time / daySeconds + "天前"; 84 | } else if (time / hourSeconds > 0) { 85 | showStr = time / hourSeconds + "小时前"; 86 | } else if (time / minuteSeconds > 0) { 87 | showStr = time / minuteSeconds + "分钟前"; 88 | } else if (time > 0) { 89 | showStr = time + "秒前"; 90 | } 91 | return showStr; 92 | } 93 | } -------------------------------------------------------------------------------- /core/src/main/java/net/angrycode/toolkit/DeviceDimensionsHelper.java: -------------------------------------------------------------------------------- 1 | package net.angrycode.toolkit; 2 | 3 | import android.content.Context; 4 | import android.content.res.Resources; 5 | import android.util.DisplayMetrics; 6 | import android.util.TypedValue; 7 | 8 | public class DeviceDimensionsHelper { 9 | // DeviceDimensionsHelper.getDisplayWidth(context) => (display width in pixels) 10 | public static int getDisplayWidth(Context context) { 11 | DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics(); 12 | return displayMetrics.widthPixels; 13 | } 14 | 15 | // DeviceDimensionsHelper.getDisplayHeight(context) => (display height in pixels) 16 | public static int getDisplayHeight(Context context) { 17 | DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics(); 18 | return displayMetrics.heightPixels; 19 | } 20 | 21 | // DeviceDimensionsHelper.convertDpToPixel(25f, context) => (25dp converted to pixels) 22 | public static float convertDpToPixel(Context context, float dp) { 23 | Resources r = context.getResources(); 24 | return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, r.getDisplayMetrics()); 25 | } 26 | 27 | // DeviceDimensionsHelper.convertPixelsToDp(25f, context) => (25px converted to dp) 28 | public static float convertPixelsToDp(Context context, float px) { 29 | Resources r = context.getResources(); 30 | DisplayMetrics metrics = r.getDisplayMetrics(); 31 | float dp = px / (metrics.densityDpi / 160f); 32 | return dp; 33 | } 34 | } -------------------------------------------------------------------------------- /core/src/main/java/net/angrycode/toolkit/GooglePlayStore.java: -------------------------------------------------------------------------------- 1 | package net.angrycode.toolkit; 2 | 3 | import android.content.ActivityNotFoundException; 4 | import android.content.ComponentName; 5 | import android.content.Context; 6 | import android.content.Intent; 7 | import android.net.Uri; 8 | import android.preference.Preference; 9 | 10 | /** 11 | * @author Oasis 12 | */ 13 | public class GooglePlayStore { 14 | 15 | public static final String PACKAGE_NAME = "com.android.vending"; 16 | private static final String APP_URL_PREFIX = "https://play.google.com/store/apps/details?id="; 17 | 18 | public static void showApp(final Context context, final String pkg) { 19 | final Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(APP_URL_PREFIX + pkg)); 20 | updatePlayUrlIntent(context, intent); 21 | try { 22 | context.startActivity(intent); 23 | } catch (final ActivityNotFoundException e) { /* In case of Google Play malfunction */ } 24 | } 25 | 26 | public static void updatePreferenceIntent(final Context context, final Preference preference) { 27 | final Intent intent = preference.getIntent(); 28 | updatePlayUrlIntent(context, intent); 29 | } 30 | 31 | /** 32 | * Modify intent to launch Google Play Store directly if possible (without opener-app selector) 33 | */ 34 | private static void updatePlayUrlIntent(final Context context, final Intent intent) { 35 | if (intent == null || intent.getPackage() != null) 36 | return; // Skip intent with explicit target package 37 | final Uri uri = intent.getData(); 38 | if (uri == null) return; 39 | intent.setPackage(PACKAGE_NAME); 40 | final ComponentName component = intent.resolveActivity(context.getPackageManager()); 41 | if (component != null) intent.setComponent(component); 42 | else intent.setPackage(null); 43 | } 44 | } -------------------------------------------------------------------------------- /core/src/main/java/net/angrycode/toolkit/Keyboard.java: -------------------------------------------------------------------------------- 1 | package net.angrycode.toolkit; 2 | 3 | import android.os.Build; 4 | import android.view.View; 5 | import android.view.ViewTreeObserver; 6 | import android.view.inputmethod.InputMethodManager; 7 | 8 | import static android.content.Context.INPUT_METHOD_SERVICE; 9 | 10 | /** 11 | * Keyboard utilities 12 | */ 13 | public class Keyboard { 14 | 15 | public static void show(final View view, final long delay) { 16 | if (view == null) { 17 | return; 18 | } 19 | final InputMethodManager manager = (InputMethodManager) view.getContext().getSystemService(INPUT_METHOD_SERVICE); 20 | if (view.getWidth() == 0 && view.getHeight() == 0) { 21 | view.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { 22 | @Override 23 | public void onGlobalLayout() { 24 | removeViewTreeObserver(view, this); 25 | if (delay <= 0) { 26 | manager.showSoftInput(view, InputMethodManager.SHOW_IMPLICIT); 27 | } else { 28 | view.postDelayed(new Runnable() { 29 | @Override 30 | public void run() { 31 | manager.showSoftInput(view, InputMethodManager.SHOW_IMPLICIT); 32 | } 33 | }, delay); 34 | } 35 | } 36 | }); 37 | } else { 38 | if (delay <= 0) { 39 | manager.showSoftInput(view, InputMethodManager.SHOW_IMPLICIT); 40 | } else { 41 | view.postDelayed(new Runnable() { 42 | @Override 43 | public void run() { 44 | manager.showSoftInput(view, InputMethodManager.SHOW_IMPLICIT); 45 | } 46 | }, delay); 47 | } 48 | } 49 | 50 | } 51 | 52 | public static void hide(final View view) { 53 | InputMethodManager manager = (InputMethodManager) view.getContext().getSystemService(INPUT_METHOD_SERVICE); 54 | if (manager != null) { 55 | manager.hideSoftInputFromWindow(view.getWindowToken(), 0); 56 | } 57 | } 58 | 59 | /** 60 | * 移除View上的ViewTreeObserver,兼容方法 61 | */ 62 | public static void removeViewTreeObserver(View view, ViewTreeObserver.OnGlobalLayoutListener l) { 63 | if (view != null && view.getViewTreeObserver() != null) { 64 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { 65 | view.getViewTreeObserver().removeOnGlobalLayoutListener(l); 66 | } else { 67 | view.getViewTreeObserver().removeGlobalOnLayoutListener(l); 68 | } 69 | } 70 | } 71 | 72 | /** 73 | * 点击屏幕空白区域隐藏软键盘(方法2) 74 | * 根据EditText所在坐标和用户点击的坐标相对比,来判断是否隐藏键盘 75 | * 需重写dispatchTouchEvent 76 | * 参照以下注释代码 77 | */ 78 | public static void clickBlankArea2HideSoftInput1() { 79 | /* 80 | @Override 81 | public boolean dispatchTouchEvent(MotionEvent ev) { 82 | if (ev.getAction() == MotionEvent.ACTION_DOWN) { 83 | View v = getCurrentFocus(); 84 | if (isShouldHideKeyboard(v, ev)) { 85 | hideKeyboard(v.getWindowToken()); 86 | } 87 | } 88 | return super.dispatchTouchEvent(ev); 89 | } 90 | // 根据EditText所在坐标和用户点击的坐标相对比,来判断是否隐藏键盘 91 | private boolean isShouldHideKeyboard(View v, MotionEvent event) { 92 | if (v != null && (v instanceof EditText)) { 93 | int[] l = {0, 0}; 94 | v.getLocationInWindow(l); 95 | int left = l[0], 96 | top = l[1], 97 | bottom = top + v.getHeight(), 98 | right = left + v.getWidth(); 99 | return !(event.getX() > left && event.getX() < right 100 | && event.getY() > top && event.getY() < bottom); 101 | } 102 | return false; 103 | } 104 | // 获取InputMethodManager,隐藏软键盘 105 | private void hideKeyboard(IBinder token) { 106 | if (token != null) { 107 | InputMethodManager im = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); 108 | im.hideSoftInputFromWindow(token, InputMethodManager.HIDE_NOT_ALWAYS); 109 | } 110 | } 111 | */ 112 | } 113 | 114 | } -------------------------------------------------------------------------------- /core/src/main/java/net/angrycode/toolkit/KeyboardVisibilityEvent.java: -------------------------------------------------------------------------------- 1 | package net.angrycode.toolkit; 2 | 3 | import android.app.Activity; 4 | import android.graphics.Rect; 5 | import android.support.annotation.NonNull; 6 | import android.view.View; 7 | import android.view.ViewGroup; 8 | import android.view.ViewTreeObserver; 9 | 10 | public class KeyboardVisibilityEvent { 11 | 12 | private final static int KEYBOARD_VISIBLE_THRESHOLD_DP = 100; 13 | 14 | /** 15 | * Set keyboard visiblity change event listener. 16 | * 17 | * @param activity Activity 18 | * @param listener KeyboardVisibilityEventListener 19 | */ 20 | public static void setEventListener( 21 | @NonNull final Activity activity, 22 | @NonNull final KeyboardVisibilityEventListener listener) { 23 | 24 | 25 | setEventListener(activity, false, listener); 26 | } 27 | 28 | /*** 29 | * 30 | * @param activity 31 | * @param checkAlways https://yun.115.com/5/T291465.html#[Bug][内测版][115+][V5.1][事务][Android]搜索事务后退至后台再进入底部的统计小黑条会显示在输入法的上方 32 | * @param listener 33 | */ 34 | public static void setEventListener( 35 | @NonNull final Activity activity, final boolean checkAlways, 36 | @NonNull final KeyboardVisibilityEventListener listener) { 37 | 38 | 39 | final View activityRoot = getActivityRoot(activity); 40 | 41 | 42 | activityRoot.getViewTreeObserver().addOnGlobalLayoutListener( 43 | new ViewTreeObserver.OnGlobalLayoutListener() { 44 | 45 | 46 | private final Rect r = new Rect(); 47 | 48 | 49 | private final int visibleThreshold = Math.round( 50 | DeviceDimensionsHelper.convertDpToPixel(activity, KEYBOARD_VISIBLE_THRESHOLD_DP)); 51 | 52 | 53 | private boolean wasOpened = false; 54 | 55 | 56 | @Override 57 | public void onGlobalLayout() { 58 | activityRoot.getWindowVisibleDisplayFrame(r); 59 | 60 | 61 | int heightDiff = activityRoot.getRootView().getHeight() - r.height(); 62 | 63 | 64 | boolean isOpen = heightDiff > visibleThreshold; 65 | 66 | if (!checkAlways) {//每次都检查,默认为false 67 | if (isOpen == wasOpened) { 68 | // keyboard state has not changed 69 | return; 70 | } 71 | } 72 | 73 | 74 | wasOpened = isOpen; 75 | 76 | 77 | listener.onVisibilityChanged(isOpen); 78 | } 79 | }); 80 | } 81 | 82 | /** 83 | * Determine if keyboard is visible 84 | * 85 | * @param activity Activity 86 | * @return Whether keyboard is visible or not 87 | */ 88 | public static boolean isKeyboardVisible(Activity activity) { 89 | Rect r = new Rect(); 90 | 91 | 92 | View activityRoot = getActivityRoot(activity); 93 | int visibleThreshold = Math 94 | .round(DeviceDimensionsHelper.convertDpToPixel(activity, KEYBOARD_VISIBLE_THRESHOLD_DP)); 95 | 96 | 97 | activityRoot.getWindowVisibleDisplayFrame(r); 98 | 99 | 100 | int heightDiff = activityRoot.getRootView().getHeight() - r.height(); 101 | 102 | 103 | return heightDiff > visibleThreshold; 104 | } 105 | 106 | private static View getActivityRoot(Activity activity) { 107 | return ((ViewGroup) activity.findViewById(android.R.id.content)).getChildAt(0); 108 | } 109 | 110 | 111 | public interface KeyboardVisibilityEventListener { 112 | 113 | void onVisibilityChanged(boolean isOpen); 114 | } 115 | 116 | 117 | } -------------------------------------------------------------------------------- /core/src/main/java/net/angrycode/toolkit/PermissionsUtils.java: -------------------------------------------------------------------------------- 1 | package net.angrycode.toolkit; 2 | 3 | import android.app.Activity; 4 | import android.support.annotation.StringRes; 5 | import android.support.v4.app.ActivityCompat; 6 | import android.support.v4.app.Fragment; 7 | 8 | /** 9 | * Created by savio on 2017-01-08. 10 | */ 11 | public class PermissionsUtils { 12 | 13 | public static void askForPermission(final Activity activity, final int requestCode, 14 | final String permission, @StringRes final int askTitle, 15 | @StringRes final int askContent) { 16 | 17 | if (ActivityCompat.shouldShowRequestPermissionRationale(activity, permission)) { 18 | // MaterialDialog.Builder builder = new MaterialDialog.Builder(activity) 19 | // .title(askTitle) 20 | // .content(askContent) 21 | // .positiveText(android.R.string.yes) 22 | // .negativeText(android.R.string.no) 23 | // .onPositive((dialog, which) -> 24 | // ActivityCompat.requestPermissions(activity, 25 | // new String[]{permission}, requestCode)); 26 | // 27 | // builder.show(); 28 | } else { 29 | ActivityCompat.requestPermissions(activity, new String[]{permission}, requestCode); 30 | } 31 | } 32 | 33 | public static void askForPermission(final Fragment fragment, final int requestCode, 34 | final String permission, @StringRes final int askTitle, 35 | @StringRes final int askContent) { 36 | 37 | if (fragment.shouldShowRequestPermissionRationale(permission)) { 38 | // MaterialDialog.Builder builder = new MaterialDialog.Builder(fragment.getActivity()) 39 | // .title(askTitle) 40 | // .content(askContent) 41 | // .positiveText(android.R.string.yes) 42 | // .negativeText(android.R.string.no) 43 | // .onPositive((dialog, which) -> 44 | // fragment.requestPermissions(new String[]{permission}, requestCode)); 45 | // 46 | // builder.show(); 47 | } else { 48 | fragment.requestPermissions(new String[]{permission}, requestCode); 49 | } 50 | } 51 | } -------------------------------------------------------------------------------- /core/src/main/java/net/angrycode/toolkit/PhoneUtils.java: -------------------------------------------------------------------------------- 1 | package net.angrycode.toolkit; 2 | 3 | import android.app.Activity; 4 | import android.content.Context; 5 | import android.content.Intent; 6 | import android.net.Uri; 7 | import android.provider.MediaStore; 8 | import android.text.Html; 9 | 10 | import java.io.File; 11 | 12 | /** 13 | * Created by wecodexyz on 2017/8/21. 14 | */ 15 | 16 | public class PhoneUtils { 17 | /** 18 | * 19 | * do not use it 20 | * @param context 21 | * @param phoneNumber 22 | */ 23 | private static void call(Context context, String phoneNumber) { 24 | Intent callIntent = new Intent(Intent.ACTION_CALL); 25 | callIntent.setData(Uri.parse("tel:" + phoneNumber)); 26 | if (callIntent.resolveActivity(context.getPackageManager()) != null) { 27 | if (context instanceof Activity) { 28 | 29 | } else { 30 | callIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 31 | } 32 | context.startActivity(callIntent); 33 | } 34 | } 35 | 36 | /** 37 | * open phone app,dont need permission 38 | * 39 | * @param context 40 | * @param phoneNumber 41 | */ 42 | public static void dial(Context context, String phoneNumber) { 43 | Intent callIntent = new Intent(Intent.ACTION_DIAL); 44 | callIntent.setData(Uri.parse("tel:" + phoneNumber)); 45 | if (callIntent.resolveActivity(context.getPackageManager()) != null) { 46 | if (context instanceof Activity) { 47 | 48 | } else { 49 | callIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 50 | } 51 | context.startActivity(callIntent); 52 | } 53 | } 54 | 55 | /** 56 | * open email client 57 | * 58 | * @param context 59 | * @param email 60 | * @param subject 61 | * @param body 62 | */ 63 | public static void sendMail(Context context, String email, String subject, String body) { 64 | Intent intent = new Intent(Intent.ACTION_SEND); 65 | intent.setType("plain/text"); 66 | intent.putExtra(Intent.EXTRA_EMAIL, new String[]{email}); 67 | intent.putExtra(Intent.EXTRA_SUBJECT, subject); 68 | intent.putExtra(Intent.EXTRA_TEXT, body); 69 | if (intent.resolveActivity(context.getPackageManager()) != null) { 70 | if (context instanceof Activity) { 71 | 72 | } else { 73 | intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 74 | } 75 | context.startActivity(Intent.createChooser(intent, "")); 76 | } 77 | } 78 | 79 | /** 80 | * open email client 81 | * 82 | * @param context 83 | * @param email 84 | * @param subject 85 | * @param body 86 | */ 87 | public static void mailto(Context context, String email, String subject, String body) { 88 | String uriText = 89 | "mailto:" + email + 90 | "?subject=" + Uri.encode(subject) + 91 | "&body=" + Uri.encode(body); 92 | 93 | Uri uri = Uri.parse(uriText); 94 | 95 | Intent sendIntent = new Intent(Intent.ACTION_SENDTO); 96 | sendIntent.setData(uri); 97 | if (sendIntent.resolveActivity(context.getPackageManager()) != null) { 98 | if (context instanceof Activity) { 99 | 100 | } else { 101 | sendIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 102 | } 103 | context.startActivity(Intent.createChooser(sendIntent, "Send email")); 104 | } 105 | } 106 | 107 | /** 108 | * Launch a website in the phone browser 109 | * 110 | * @param context 111 | * @param url 112 | */ 113 | public static void launchWebsite(Context context, String url) { 114 | Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(url)); 115 | if (browserIntent.resolveActivity(context.getPackageManager()) != null) { 116 | if (context instanceof Activity) { 117 | 118 | } else { 119 | browserIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 120 | } 121 | context.startActivity(browserIntent); 122 | } 123 | } 124 | 125 | /** 126 | * @param context 127 | */ 128 | public static void openOnGooglePlay(Context context) { 129 | Intent intent = new Intent(Intent.ACTION_VIEW, 130 | Uri.parse("market://details?id=" + context.getPackageName())); 131 | if (intent.resolveActivity(context.getPackageManager()) != null) { 132 | if (context instanceof Activity) { 133 | 134 | } else { 135 | intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 136 | } 137 | context.startActivity(intent); 138 | } 139 | } 140 | 141 | /** 142 | * @param context 143 | * @param latitude 144 | * @param longitude 145 | */ 146 | public static void showLocationInMaps(Context context, String latitude, String longitude) { 147 | Intent intent = new Intent(); 148 | intent.setAction(Intent.ACTION_VIEW); 149 | String data = String.format("geo:%s,%s", latitude, longitude); 150 | // if (zoomLevel != null) { 151 | // data = String.format("%s?z=%s", data, zoomLevel); 152 | // } 153 | intent.setData(Uri.parse(data)); 154 | if (intent.resolveActivity(context.getPackageManager()) != null) { 155 | if (context instanceof Activity) { 156 | 157 | } else { 158 | intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 159 | } 160 | context.startActivity(intent); 161 | } 162 | } 163 | 164 | /** 165 | * @param context 166 | * @param file 167 | */ 168 | public static void capturePhoto(Context context, String file) { 169 | Uri uri = Uri.fromFile(new File(file)); 170 | Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); 171 | intent.putExtra(MediaStore.EXTRA_OUTPUT, uri); 172 | if (intent.resolveActivity(context.getPackageManager()) != null) { 173 | if (context instanceof Activity) { 174 | 175 | } else { 176 | intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 177 | } 178 | context.startActivity(intent); 179 | } 180 | } 181 | 182 | /** 183 | * String text = "Look at my awesome picture"; 184 | * Uri pictureUri = Uri.parse("file://my_picture"); 185 | * Intent shareIntent = new Intent(); 186 | * shareIntent.setAction(Intent.ACTION_SEND); 187 | * shareIntent.putExtra(Intent.EXTRA_TEXT, text); 188 | * shareIntent.putExtra(Intent.EXTRA_STREAM, imageUri); 189 | * shareIntent.setType("image/*"); 190 | * shareIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); 191 | * startActivity(Intent.createChooser(shareIntent, "Share images...")); 192 | */ 193 | public static void shareImage(Context context, String filePath, String fileName) { 194 | Intent sharingIntent = new Intent(Intent.ACTION_SEND); 195 | sharingIntent.setType("image/jpg"); 196 | Uri uri = Uri.fromFile(new File(filePath, fileName)); 197 | sharingIntent.putExtra(Intent.EXTRA_STREAM, uri.toString()); 198 | if (sharingIntent.resolveActivity(context.getPackageManager()) != null) { 199 | if (context instanceof Activity) { 200 | 201 | } else { 202 | sharingIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 203 | } 204 | context.startActivity(Intent.createChooser(sharingIntent, "Share image using")); 205 | } 206 | 207 | } 208 | 209 | public static void shareText(Context context, String text) { 210 | Intent sharingIntent = new Intent(Intent.ACTION_SEND); 211 | sharingIntent.setType("text/html"); 212 | sharingIntent.putExtra(android.content.Intent.EXTRA_TEXT, Html.fromHtml("

" + text + "

")); 213 | if (sharingIntent.resolveActivity(context.getPackageManager()) != null) { 214 | if (context instanceof Activity) { 215 | 216 | } else { 217 | sharingIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 218 | } 219 | context.startActivity(Intent.createChooser(sharingIntent, "Share using")); 220 | } 221 | } 222 | } 223 | -------------------------------------------------------------------------------- /core/src/main/java/net/angrycode/toolkit/PreferenceHelper.java: -------------------------------------------------------------------------------- 1 | package net.angrycode.toolkit; 2 | 3 | import android.annotation.TargetApi; 4 | import android.content.Context; 5 | import android.content.SharedPreferences; 6 | import android.os.Build; 7 | 8 | /** 9 | * SharedPreferences操作工具包
10 | * 说明 本工具包只能在单进程项目下使用 11 | * 12 | * @author kymjs (https://github.com/kymjs) 13 | */ 14 | public class PreferenceHelper { 15 | @TargetApi(Build.VERSION_CODES.GINGERBREAD) 16 | public static void write(Context context, String fileName, String k, int v) { 17 | SharedPreferences preference = context.getSharedPreferences(fileName, 18 | Context.MODE_PRIVATE); 19 | SharedPreferences.Editor editor = preference.edit(); 20 | editor.putInt(k, v); 21 | editor.apply(); 22 | } 23 | 24 | @TargetApi(Build.VERSION_CODES.GINGERBREAD) 25 | public static void write(Context context, String fileName, String k, 26 | boolean v) { 27 | SharedPreferences preference = context.getSharedPreferences(fileName, 28 | Context.MODE_PRIVATE); 29 | SharedPreferences.Editor editor = preference.edit(); 30 | editor.putBoolean(k, v); 31 | editor.apply(); 32 | } 33 | 34 | @TargetApi(Build.VERSION_CODES.GINGERBREAD) 35 | public static void write(Context context, String fileName, String k, 36 | String v) { 37 | SharedPreferences preference = context.getSharedPreferences(fileName, 38 | Context.MODE_PRIVATE); 39 | SharedPreferences.Editor editor = preference.edit(); 40 | editor.putString(k, v); 41 | editor.apply(); 42 | } 43 | 44 | public static int readInt(Context context, String fileName, String k) { 45 | SharedPreferences preference = context.getSharedPreferences(fileName, 46 | Context.MODE_PRIVATE); 47 | return preference.getInt(k, 0); 48 | } 49 | 50 | public static int readInt(Context context, String fileName, String k, 51 | int defv) { 52 | SharedPreferences preference = context.getSharedPreferences(fileName, 53 | Context.MODE_PRIVATE); 54 | return preference.getInt(k, defv); 55 | } 56 | 57 | public static boolean readBoolean(Context context, String fileName, String k) { 58 | SharedPreferences preference = context.getSharedPreferences(fileName, 59 | Context.MODE_PRIVATE); 60 | return preference.getBoolean(k, false); 61 | } 62 | 63 | public static boolean readBoolean(Context context, String fileName, 64 | String k, boolean defBool) { 65 | SharedPreferences preference = context.getSharedPreferences(fileName, 66 | Context.MODE_PRIVATE); 67 | return preference.getBoolean(k, defBool); 68 | } 69 | 70 | public static String readString(Context context, String fileName, String k) { 71 | SharedPreferences preference = context.getSharedPreferences(fileName, 72 | Context.MODE_PRIVATE); 73 | return preference.getString(k, null); 74 | } 75 | 76 | public static String readString(Context context, String fileName, String k, 77 | String defV) { 78 | SharedPreferences preference = context.getSharedPreferences(fileName, 79 | Context.MODE_PRIVATE); 80 | return preference.getString(k, defV); 81 | } 82 | 83 | @TargetApi(Build.VERSION_CODES.GINGERBREAD) 84 | public static void remove(Context context, String fileName, String k) { 85 | SharedPreferences preference = context.getSharedPreferences(fileName, 86 | Context.MODE_PRIVATE); 87 | SharedPreferences.Editor editor = preference.edit(); 88 | editor.remove(k); 89 | editor.apply(); 90 | } 91 | 92 | @TargetApi(Build.VERSION_CODES.GINGERBREAD) 93 | public static void clean(Context cxt, String fileName) { 94 | SharedPreferences preference = cxt.getSharedPreferences(fileName, 95 | Context.MODE_PRIVATE); 96 | SharedPreferences.Editor editor = preference.edit(); 97 | editor.clear(); 98 | editor.apply(); 99 | } 100 | } -------------------------------------------------------------------------------- /core/src/main/java/net/angrycode/toolkit/ProcessUtils.java: -------------------------------------------------------------------------------- 1 | package net.angrycode.toolkit; 2 | 3 | import android.app.ActivityManager; 4 | import android.app.AppOpsManager; 5 | import android.app.usage.UsageStats; 6 | import android.app.usage.UsageStatsManager; 7 | import android.content.Context; 8 | import android.content.Intent; 9 | import android.content.pm.ApplicationInfo; 10 | import android.content.pm.PackageManager; 11 | import android.content.pm.ResolveInfo; 12 | import android.provider.Settings; 13 | import android.support.annotation.NonNull; 14 | import android.util.Log; 15 | 16 | import java.util.Arrays; 17 | import java.util.Collections; 18 | import java.util.HashSet; 19 | import java.util.List; 20 | import java.util.Set; 21 | /** 22 | *
 23 |  *     author: Blankj
 24 |  *     blog  : http://blankj.com
 25 |  *     time  : 2016/10/18
 26 |  *     desc  : 进程相关工具类
 27 |  * 
28 | */ 29 | public final class ProcessUtils { 30 | 31 | private ProcessUtils() { 32 | throw new UnsupportedOperationException("u can't instantiate me..."); 33 | } 34 | 35 | /** 36 | * 获取前台线程包名 37 | *

当不是查看当前App,且SDK大于21时, 38 | * 需添加权限 {@code }

39 | * 40 | * @return 前台应用包名 41 | */ 42 | public static String getForegroundProcessName() { 43 | ActivityManager manager = (ActivityManager) App.INSTANCE.getSystemService(Context.ACTIVITY_SERVICE); 44 | List pInfo = manager.getRunningAppProcesses(); 45 | if (pInfo != null && pInfo.size() != 0) { 46 | for (ActivityManager.RunningAppProcessInfo aInfo : pInfo) { 47 | if (aInfo.importance == ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND) { 48 | return aInfo.processName; 49 | } 50 | } 51 | } 52 | if (android.os.Build.VERSION.SDK_INT > android.os.Build.VERSION_CODES.LOLLIPOP) { 53 | PackageManager packageManager = App.INSTANCE.getPackageManager(); 54 | Intent intent = new Intent(Settings.ACTION_USAGE_ACCESS_SETTINGS); 55 | List list = packageManager.queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY); 56 | System.out.println(list); 57 | if (list.size() > 0) {// 有"有权查看使用权限的应用"选项 58 | try { 59 | ApplicationInfo info = packageManager.getApplicationInfo(App.INSTANCE.getPackageName(), 0); 60 | AppOpsManager aom = (AppOpsManager) App.INSTANCE.getSystemService(Context.APP_OPS_SERVICE); 61 | if (aom.checkOpNoThrow(AppOpsManager.OPSTR_GET_USAGE_STATS, info.uid, info.packageName) != AppOpsManager.MODE_ALLOWED) { 62 | App.INSTANCE.startActivity(intent); 63 | } 64 | if (aom.checkOpNoThrow(AppOpsManager.OPSTR_GET_USAGE_STATS, info.uid, info.packageName) != AppOpsManager.MODE_ALLOWED) { 65 | Log.d("getForegroundApp", "没有打开\"有权查看使用权限的应用\"选项"); 66 | return null; 67 | } 68 | UsageStatsManager usageStatsManager = (UsageStatsManager) App.INSTANCE.getSystemService(Context.USAGE_STATS_SERVICE); 69 | long endTime = System.currentTimeMillis(); 70 | long beginTime = endTime - 86400000 * 7; 71 | List usageStatses = usageStatsManager.queryUsageStats(UsageStatsManager.INTERVAL_BEST, beginTime, endTime); 72 | if (usageStatses == null || usageStatses.isEmpty()) return null; 73 | UsageStats recentStats = null; 74 | for (UsageStats usageStats : usageStatses) { 75 | if (recentStats == null || usageStats.getLastTimeUsed() > recentStats.getLastTimeUsed()) { 76 | recentStats = usageStats; 77 | } 78 | } 79 | return recentStats == null ? null : recentStats.getPackageName(); 80 | } catch (PackageManager.NameNotFoundException e) { 81 | e.printStackTrace(); 82 | } 83 | } else { 84 | Log.d("ProcessUtils", "getForegroundProcessName() called" + ": 无\"有权查看使用权限的应用\"选项"); 85 | } 86 | } 87 | return null; 88 | } 89 | 90 | /** 91 | * 获取后台服务进程 92 | *

需添加权限 {@code }

93 | * 94 | * @return 后台服务进程 95 | */ 96 | public static Set getAllBackgroundProcesses() { 97 | ActivityManager am = (ActivityManager) App.INSTANCE.getSystemService(Context.ACTIVITY_SERVICE); 98 | List info = am.getRunningAppProcesses(); 99 | Set set = new HashSet<>(); 100 | for (ActivityManager.RunningAppProcessInfo aInfo : info) { 101 | Collections.addAll(set, aInfo.pkgList); 102 | } 103 | return set; 104 | } 105 | 106 | /** 107 | * 杀死所有的后台服务进程 108 | *

需添加权限 {@code }

109 | * 110 | * @return 被暂时杀死的服务集合 111 | */ 112 | public static Set killAllBackgroundProcesses() { 113 | ActivityManager am = (ActivityManager) App.INSTANCE.getSystemService(Context.ACTIVITY_SERVICE); 114 | List info = am.getRunningAppProcesses(); 115 | Set set = new HashSet<>(); 116 | for (ActivityManager.RunningAppProcessInfo aInfo : info) { 117 | for (String pkg : aInfo.pkgList) { 118 | am.killBackgroundProcesses(pkg); 119 | set.add(pkg); 120 | } 121 | } 122 | info = am.getRunningAppProcesses(); 123 | for (ActivityManager.RunningAppProcessInfo aInfo : info) { 124 | for (String pkg : aInfo.pkgList) { 125 | set.remove(pkg); 126 | } 127 | } 128 | return set; 129 | } 130 | 131 | /** 132 | * 杀死后台服务进程 133 | *

需添加权限 {@code }

134 | * 135 | * @param packageName 包名 136 | * @return {@code true}: 杀死成功
{@code false}: 杀死失败 137 | */ 138 | public static boolean killBackgroundProcesses(@NonNull final String packageName) { 139 | ActivityManager am = (ActivityManager) App.INSTANCE.getSystemService(Context.ACTIVITY_SERVICE); 140 | List info = am.getRunningAppProcesses(); 141 | if (info == null || info.size() == 0) return true; 142 | for (ActivityManager.RunningAppProcessInfo aInfo : info) { 143 | if (Arrays.asList(aInfo.pkgList).contains(packageName)) { 144 | am.killBackgroundProcesses(packageName); 145 | } 146 | } 147 | info = am.getRunningAppProcesses(); 148 | if (info == null || info.size() == 0) return true; 149 | for (ActivityManager.RunningAppProcessInfo aInfo : info) { 150 | if (Arrays.asList(aInfo.pkgList).contains(packageName)) { 151 | return false; 152 | } 153 | } 154 | return true; 155 | } 156 | } -------------------------------------------------------------------------------- /core/src/main/java/net/angrycode/toolkit/ShareUtils.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015 PocketHub 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package net.angrycode.toolkit; 17 | 18 | import android.content.Intent; 19 | import android.text.TextUtils; 20 | 21 | import static android.content.Intent.ACTION_SEND; 22 | import static android.content.Intent.EXTRA_SUBJECT; 23 | import static android.content.Intent.EXTRA_TEXT; 24 | 25 | /** 26 | * Utilities for creating a share intent 27 | */ 28 | public class ShareUtils { 29 | 30 | /** 31 | * Create intent with subject and body 32 | * 33 | * @param subject 34 | * @param body 35 | * @return intent 36 | */ 37 | public static Intent create(final CharSequence subject, 38 | final CharSequence body) { 39 | Intent intent = new Intent(ACTION_SEND); 40 | intent.setType("text/plain"); 41 | if (!TextUtils.isEmpty(subject)) { 42 | intent.putExtra(EXTRA_SUBJECT, subject); 43 | } 44 | intent.putExtra(EXTRA_TEXT, body); 45 | return intent; 46 | } 47 | 48 | /** 49 | * Get body from intent 50 | * 51 | * @param intent 52 | * @return body 53 | */ 54 | public static String getBody(final Intent intent) { 55 | return intent != null ? intent.getStringExtra(EXTRA_TEXT) : null; 56 | } 57 | 58 | /** 59 | * Get subject from intent 60 | * 61 | * @param intent 62 | * @return subject 63 | */ 64 | public static String getSubject(final Intent intent) { 65 | return intent != null ? intent.getStringExtra(EXTRA_SUBJECT) : null; 66 | } 67 | } -------------------------------------------------------------------------------- /core/src/main/java/net/angrycode/toolkit/StringUtils.java: -------------------------------------------------------------------------------- 1 | package net.angrycode.toolkit; 2 | 3 | import java.util.Locale; 4 | 5 | public class StringUtils { 6 | 7 | /** 8 | * 判断给定字符串是否空白串 空白串是指由空格、制表符、回车符、换行符组成的字符串 若输入字符串为null或空字符串,返回true 9 | */ 10 | public static boolean isEmpty(CharSequence input) { 11 | if (input == null || "".equals(input)) 12 | return true; 13 | 14 | for (int i = 0; i < input.length(); i++) { 15 | char c = input.charAt(i); 16 | if (c != ' ' && c != '\t' && c != '\r' && c != '\n') { 17 | return false; 18 | } 19 | } 20 | return true; 21 | } 22 | 23 | /** 24 | * 判断给定字符串是否空白串 空白串是指由空格、制表符、回车符、换行符组成的字符串 若输入字符串为null或空字符串,返回true 25 | */ 26 | public static boolean isEmpty(CharSequence... strs) { 27 | for (CharSequence str : strs) { 28 | if (isEmpty(str)) { 29 | return true; 30 | } 31 | } 32 | return false; 33 | } 34 | 35 | /** 36 | * 字符串转整数 37 | * 38 | * @param str 39 | * @param defValue 40 | * @return 41 | */ 42 | public static int toInt(String str, int defValue) { 43 | try { 44 | return Integer.parseInt(str); 45 | } catch (Exception e) { 46 | } 47 | return defValue; 48 | } 49 | 50 | /** 51 | * 对象转整 52 | * 53 | * @param obj 54 | * @return 转换异常返回 0 55 | */ 56 | public static int toInt(Object obj) { 57 | if (obj == null) 58 | return 0; 59 | return toInt(obj.toString(), 0); 60 | } 61 | 62 | /** 63 | * String转long 64 | * 65 | * @param obj 66 | * @return 转换异常返回 0 67 | */ 68 | public static long toLong(String obj) { 69 | try { 70 | return Long.parseLong(obj); 71 | } catch (Exception e) { 72 | } 73 | return 0; 74 | } 75 | 76 | /** 77 | * String转double 78 | * 79 | * @param obj 80 | * @return 转换异常返回 0 81 | */ 82 | public static double toDouble(String obj) { 83 | try { 84 | return Double.parseDouble(obj); 85 | } catch (Exception e) { 86 | } 87 | return 0D; 88 | } 89 | 90 | /** 91 | * 字符串转布尔 92 | * 93 | * @param b 94 | * @return 转换异常返回 false 95 | */ 96 | public static boolean toBool(String b) { 97 | try { 98 | return Boolean.parseBoolean(b); 99 | } catch (Exception e) { 100 | } 101 | return false; 102 | } 103 | 104 | /** 105 | * 判断一个字符串是不是数字 106 | */ 107 | public static boolean isNumber(CharSequence str) { 108 | try { 109 | Integer.parseInt(str.toString()); 110 | } catch (Exception e) { 111 | return false; 112 | } 113 | return true; 114 | } 115 | 116 | /** 117 | * byte[]数组转换为16进制的字符串。 118 | * 119 | * @param data 要转换的字节数组。 120 | * @return 转换后的结果。 121 | */ 122 | public static final String byteArrayToHexString(byte[] data) { 123 | StringBuilder sb = new StringBuilder(data.length * 2); 124 | for (byte b : data) { 125 | int v = b & 0xff; 126 | if (v < 16) { 127 | sb.append('0'); 128 | } 129 | sb.append(Integer.toHexString(v)); 130 | } 131 | return sb.toString().toUpperCase(Locale.getDefault()); 132 | } 133 | 134 | /** 135 | * 16进制表示的字符串转换为字节数组。 136 | * 137 | * @param s 16进制表示的字符串 138 | * @return byte[] 字节数组 139 | */ 140 | public static byte[] hexStringToByteArray(String s) { 141 | int len = s.length(); 142 | byte[] d = new byte[len / 2]; 143 | for (int i = 0; i < len; i += 2) { 144 | // 两位一组,表示一个字节,把这样表示的16进制字符串,还原成一个进制字节 145 | d[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4) + Character 146 | .digit(s.charAt(i + 1), 16)); 147 | } 148 | return d; 149 | } 150 | } -------------------------------------------------------------------------------- /core/src/main/java/net/angrycode/toolkit/SystemTool.java: -------------------------------------------------------------------------------- 1 | package net.angrycode.toolkit; 2 | 3 | import android.annotation.TargetApi; 4 | import android.app.ActivityManager; 5 | import android.app.KeyguardManager; 6 | import android.content.Context; 7 | import android.content.Intent; 8 | import android.content.pm.ApplicationInfo; 9 | import android.content.pm.PackageInfo; 10 | import android.content.pm.PackageManager; 11 | import android.net.ConnectivityManager; 12 | import android.net.NetworkInfo; 13 | import android.net.Uri; 14 | import android.os.Build; 15 | import android.telephony.TelephonyManager; 16 | 17 | import java.io.File; 18 | import java.security.MessageDigest; 19 | 20 | public final class SystemTool { 21 | /** 22 | * 获取手机IMEI码 23 | */ 24 | public static String getPhoneIMEI(Context cxt) { 25 | TelephonyManager tm = (TelephonyManager) cxt 26 | .getSystemService(Context.TELEPHONY_SERVICE); 27 | return tm.getDeviceId(); 28 | } 29 | 30 | /** 31 | * 获取手机系统SDK版本 32 | * 33 | * @return 如API 17 则返回 17 34 | */ 35 | @TargetApi(Build.VERSION_CODES.DONUT) 36 | public static int getSDKVersion() { 37 | return android.os.Build.VERSION.SDK_INT; 38 | } 39 | 40 | /** 41 | * 获取系统版本 42 | * 43 | * @return 形如2.3.3 44 | */ 45 | public static String getSystemVersion() { 46 | return android.os.Build.VERSION.RELEASE; 47 | } 48 | 49 | /** 50 | * 调用系统发送短信 51 | */ 52 | public static void sendSMS(Context cxt, String smsBody) { 53 | Uri smsToUri = Uri.parse("smsto:"); 54 | Intent intent = new Intent(Intent.ACTION_SENDTO, smsToUri); 55 | intent.putExtra("sms_body", smsBody); 56 | cxt.startActivity(intent); 57 | } 58 | 59 | /** 60 | * 判断网络是否连接 61 | */ 62 | public static boolean checkNet(Context context) { 63 | ConnectivityManager cm = (ConnectivityManager) context 64 | .getSystemService(Context.CONNECTIVITY_SERVICE); 65 | NetworkInfo info = cm.getActiveNetworkInfo(); 66 | return info != null;// 网络是否连接 67 | } 68 | 69 | /** 70 | * 判断是否为wifi联网 71 | */ 72 | public static boolean isWiFi(Context cxt) { 73 | ConnectivityManager cm = (ConnectivityManager) cxt 74 | .getSystemService(Context.CONNECTIVITY_SERVICE); 75 | // wifi的状态:ConnectivityManager.TYPE_WIFI 76 | // 3G的状态:ConnectivityManager.TYPE_MOBILE 77 | NetworkInfo.State state = cm.getNetworkInfo(ConnectivityManager.TYPE_WIFI) 78 | .getState(); 79 | return NetworkInfo.State.CONNECTED == state; 80 | } 81 | 82 | /** 83 | * 判断手机是否处理睡眠 84 | */ 85 | public static boolean isSleeping(Context context) { 86 | KeyguardManager kgMgr = (KeyguardManager) context 87 | .getSystemService(Context.KEYGUARD_SERVICE); 88 | boolean isSleeping = kgMgr.inKeyguardRestrictedInputMode(); 89 | return isSleeping; 90 | } 91 | 92 | /** 93 | * 安装apk 94 | * 95 | * @param context 96 | * @param file 97 | */ 98 | public static void installApk(Context context, File file) { 99 | try { 100 | Process p = Runtime.getRuntime().exec("chmod 777 " + file.getAbsolutePath()); 101 | p.waitFor(); 102 | } catch (Exception e) { 103 | e.printStackTrace(); 104 | } 105 | 106 | Intent intent = new Intent(); 107 | intent.setAction("android.intent.action.VIEW"); 108 | intent.addCategory("android.intent.category.DEFAULT"); 109 | intent.setType("application/vnd.android.package-archive"); 110 | intent.setData(Uri.fromFile(file)); 111 | intent.setDataAndType(Uri.fromFile(file), 112 | "application/vnd.android.package-archive"); 113 | intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 114 | context.startActivity(intent); 115 | } 116 | 117 | /** 118 | * 获取当前应用程序的版本号 119 | */ 120 | public static String getAppVersionName(Context context) { 121 | String version = "0"; 122 | try { 123 | version = context.getPackageManager().getPackageInfo( 124 | context.getPackageName(), 0).versionName; 125 | } catch (PackageManager.NameNotFoundException e) { 126 | throw new RuntimeException(SystemTool.class.getName() 127 | + "the application not found"); 128 | } 129 | return version; 130 | } 131 | 132 | /** 133 | * 获取当前应用程序的版本号 134 | */ 135 | public static int getAppVersionCode(Context context) { 136 | int version = 0; 137 | try { 138 | version = context.getPackageManager().getPackageInfo( 139 | context.getPackageName(), 0).versionCode; 140 | } catch (PackageManager.NameNotFoundException e) { 141 | throw new RuntimeException(SystemTool.class.getName() 142 | + "the application not found"); 143 | } 144 | return version; 145 | } 146 | 147 | /** 148 | * 回到home,后台运行 149 | */ 150 | public static void goHome(Context context) { 151 | Intent mHomeIntent = new Intent(Intent.ACTION_MAIN); 152 | mHomeIntent.addCategory(Intent.CATEGORY_HOME); 153 | mHomeIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK 154 | | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED); 155 | context.startActivity(mHomeIntent); 156 | } 157 | 158 | /** 159 | * 获取应用签名 160 | * 161 | * @param context 162 | * @param pkgName 163 | */ 164 | public static String getSign(Context context, String pkgName) { 165 | try { 166 | PackageInfo pis = context.getPackageManager().getPackageInfo( 167 | pkgName, PackageManager.GET_SIGNATURES); 168 | return hexdigest(pis.signatures[0].toByteArray()); 169 | } catch (PackageManager.NameNotFoundException e) { 170 | throw new RuntimeException(SystemTool.class.getName() + "the " 171 | + pkgName + "'s application not found"); 172 | } 173 | } 174 | 175 | /** 176 | * 将签名字符串转换成需要的32位签名 177 | */ 178 | private static String hexdigest(byte[] paramArrayOfByte) { 179 | final char[] hexDigits = {48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 97, 180 | 98, 99, 100, 101, 102}; 181 | try { 182 | MessageDigest localMessageDigest = MessageDigest.getInstance("MD5"); 183 | localMessageDigest.update(paramArrayOfByte); 184 | byte[] arrayOfByte = localMessageDigest.digest(); 185 | char[] arrayOfChar = new char[32]; 186 | for (int i = 0, j = 0; ; i++, j++) { 187 | if (i >= 16) { 188 | return new String(arrayOfChar); 189 | } 190 | int k = arrayOfByte[i]; 191 | arrayOfChar[j] = hexDigits[(0xF & k >>> 4)]; 192 | arrayOfChar[++j] = hexDigits[(k & 0xF)]; 193 | } 194 | } catch (Exception e) { 195 | } 196 | return ""; 197 | } 198 | 199 | /** 200 | * 获取设备的可用内存大小 201 | * 202 | * @param cxt 应用上下文对象context 203 | * @return 当前内存大小 204 | */ 205 | public static int getDeviceUsableMemory(Context cxt) { 206 | ActivityManager am = (ActivityManager) cxt 207 | .getSystemService(Context.ACTIVITY_SERVICE); 208 | ActivityManager.MemoryInfo mi = new ActivityManager.MemoryInfo(); 209 | am.getMemoryInfo(mi); 210 | // 返回当前系统的可用内存 211 | return (int) (mi.availMem / (1024 * 1024)); 212 | } 213 | 214 | /** 215 | * 读取application 节点 meta-data 信息 216 | * 217 | * @return values 218 | */ 219 | public static String getMetaData(String key) { 220 | try { 221 | ApplicationInfo appInfo = App.INSTANCE.getPackageManager() 222 | .getApplicationInfo(App.INSTANCE.getPackageName(), 223 | PackageManager.GET_META_DATA); 224 | return appInfo.metaData.getString(key); 225 | } catch (PackageManager.NameNotFoundException e) { 226 | e.printStackTrace(); 227 | } 228 | return ""; 229 | } 230 | } -------------------------------------------------------------------------------- /core/src/main/java/net/angrycode/toolkit/UrlUtils.java: -------------------------------------------------------------------------------- 1 | package net.angrycode.toolkit; 2 | 3 | import android.content.Context; 4 | import android.content.Intent; 5 | import android.net.Uri; 6 | import android.text.TextUtils; 7 | import android.widget.Toast; 8 | 9 | import net.angrycode.core.R; 10 | 11 | public class UrlUtils { 12 | /** 13 | * 默认的scheme跳转逻辑 14 | */ 15 | public void defaultSchemeJump(Context context, String scheme) { 16 | if (!TextUtils.isEmpty(scheme)) { 17 | Intent in = new Intent(Intent.ACTION_VIEW, Uri.parse(scheme)); 18 | if (in.resolveActivity(context.getPackageManager()) != null) { 19 | context.startActivity(in); 20 | return; 21 | } 22 | } 23 | Toast.makeText(context, R.string.error_app_not_install, Toast.LENGTH_SHORT).show(); 24 | } 25 | 26 | /** 27 | * 打开网址 28 | */ 29 | public static void openUrl(Context context, String url) { 30 | Uri uri = Uri.parse(url); 31 | Intent intent = new Intent(Intent.ACTION_VIEW, uri); 32 | context.startActivity(intent); 33 | } 34 | } -------------------------------------------------------------------------------- /core/src/main/java/net/angrycode/toolkit/Utils.java: -------------------------------------------------------------------------------- 1 | package net.angrycode.toolkit; 2 | 3 | /** 4 | * Created by wecodexyz on 2017/8/17. 5 | */ 6 | 7 | public class Utils { 8 | 9 | } 10 | -------------------------------------------------------------------------------- /core/src/main/java/net/angrycode/toolkit/constant/Regexs.java: -------------------------------------------------------------------------------- 1 | package net.angrycode.toolkit.constant; 2 | 3 | public final class Regexs { 4 | 5 | /** 6 | * 正则:手机号(简单) 7 | */ 8 | public static final String REGEX_MOBILE_SIMPLE = "^[1]\\d{10}$"; 9 | /** 10 | * 正则:手机号(精确) 11 | *

移动:134(0-8)、135、136、137、138、139、147、150、151、152、157、158、159、178、182、183、184、187、188

12 | *

联通:130、131、132、145、155、156、171、175、176、185、186

13 | *

电信:133、153、173、177、180、181、189

14 | *

全球星:1349

15 | *

虚拟运营商:170

16 | */ 17 | public static final String REGEX_MOBILE_EXACT = "^((13[0-9])|(14[5,7])|(15[0-3,5-9])|(17[0,1,3,5-8])|(18[0-9])|(147))\\d{8}$"; 18 | /** 19 | * 正则:电话号码 20 | */ 21 | public static final String REGEX_TEL = "^0\\d{2,3}[- ]?\\d{7,8}"; 22 | /** 23 | * 正则:身份证号码15位 24 | */ 25 | public static final String REGEX_ID_CARD15 = "^[1-9]\\d{7}((0\\d)|(1[0-2]))(([0|1|2]\\d)|3[0-1])\\d{3}$"; 26 | /** 27 | * 正则:身份证号码18位 28 | */ 29 | public static final String REGEX_ID_CARD18 = "^[1-9]\\d{5}[1-9]\\d{3}((0\\d)|(1[0-2]))(([0|1|2]\\d)|3[0-1])\\d{3}([0-9Xx])$"; 30 | /** 31 | * 正则:邮箱 32 | */ 33 | public static final String REGEX_EMAIL = "^\\w+([-+.]\\w+)*@\\w+([-.]\\w+)*\\.\\w+([-.]\\w+)*$"; 34 | /** 35 | * 正则:URL 36 | */ 37 | public static final String REGEX_URL = "[a-zA-z]+://[^\\s]*"; 38 | /** 39 | * 正则:汉字 40 | */ 41 | public static final String REGEX_ZH = "^[\\u4e00-\\u9fa5]+$"; 42 | /** 43 | * 正则:用户名,取值范围为a-z,A-Z,0-9,"_",汉字,不能以"_"结尾,用户名必须是6-20位 44 | */ 45 | public static final String REGEX_USERNAME = "^[\\w\\u4e00-\\u9fa5]{6,20}(? 0 ? (x > layout.getLineMax(line) ? offset : offset - 1) : offset; 82 | } 83 | 84 | @Override 85 | public boolean onTouchEvent(MotionEvent event) { 86 | boolean superResult = super.onTouchEvent(event); 87 | 88 | switch (event.getAction()) { 89 | case MotionEvent.ACTION_UP: 90 | case MotionEvent.ACTION_CANCEL: 91 | tryRemoveLinkSpan(); 92 | break; 93 | 94 | case MotionEvent.ACTION_MOVE: 95 | if (getMovementMethod() != null) { 96 | Spannable buffer = (Spannable) getText(); 97 | int x = (int) event.getX(); 98 | int y = (int) event.getY(); 99 | 100 | x -= this.getTotalPaddingLeft(); 101 | y -= this.getTotalPaddingTop(); 102 | 103 | x += this.getScrollX(); 104 | y += this.getScrollY(); 105 | 106 | Layout layout = this.getLayout(); 107 | int line = layout.getLineForVertical(y); 108 | int off = layout.getOffsetForHorizontal(line, x); 109 | 110 | ClickableSpan[] link = buffer.getSpans(off, off, ClickableSpan.class); 111 | if (link.length != 0) { 112 | 113 | ClickableSpan cs = link[0]; 114 | int csStart = buffer.getSpanStart(cs); 115 | int csEnd = buffer.getSpanEnd(cs); 116 | 117 | int top = layout.getLineTop(line); 118 | int bottom = layout.getLineBottom(line); 119 | float eventY = event.getY() - this.getTotalPaddingTop(); 120 | 121 | 122 | if (off < csStart || off >= csEnd || eventY < top || eventY > bottom) { 123 | // System.out.println("点击之前:" + line + " " + csStart + " " + csEnd + " " + off + " " + link.length + " " + top + " " + bottom + " " + eventY); 124 | tryRemoveLinkSpan(); 125 | } 126 | } 127 | } 128 | break; 129 | } 130 | 131 | return superResult; 132 | } 133 | 134 | @Override 135 | protected void onDetachedFromWindow() { 136 | super.onDetachedFromWindow(); 137 | // if (mLocalLinkMovementMethod != null) { 138 | // mLocalLinkMovementMethod.setLinkTextView(null); 139 | // mLocalLinkMovementMethod.setOnLinkClickListener(null); 140 | // } 141 | } 142 | 143 | private void tryRemoveLinkSpan() { 144 | if (getMovementMethod() != null) { 145 | Spannable buffer = (Spannable) getText(); 146 | //移除按下的背景颜色 147 | BackgroundColorSpan[] backgroundColorSpans = buffer.getSpans(0, buffer.length(), 148 | BackgroundColorSpan.class); 149 | for (BackgroundColorSpan span : backgroundColorSpans) { 150 | buffer.removeSpan(span); 151 | this.setText(buffer); 152 | } 153 | } 154 | } 155 | 156 | public interface OnLinkClickListener { 157 | boolean onLinkClick(LinkTextView textView, String link, MotionEvent event); 158 | 159 | boolean onNormalClick(LinkTextView textView, MotionEvent event); 160 | } 161 | 162 | public static class LocalLinkMovementMethod extends LinkMovementMethod { 163 | 164 | private static LocalLinkMovementMethod sInstance; 165 | 166 | private LinkTextView mLinkTextView; 167 | private OnLinkClickListener mOnLinkClickListener; 168 | 169 | public static LocalLinkMovementMethod getInstance() { 170 | if (sInstance == null) { 171 | sInstance = new LocalLinkMovementMethod(); 172 | } 173 | return sInstance; 174 | } 175 | 176 | public void setLinkTextView(LinkTextView linkTextView) { 177 | mLinkTextView = linkTextView; 178 | } 179 | 180 | public void setOnLinkClickListener(OnLinkClickListener listener) { 181 | mOnLinkClickListener = listener; 182 | } 183 | 184 | @Override 185 | public boolean onTouchEvent(TextView widget, Spannable buffer, MotionEvent event) { 186 | int action = event.getAction(); 187 | 188 | switch (action) { 189 | case MotionEvent.ACTION_CANCEL: 190 | //移除按下的背景颜色 191 | BackgroundColorSpan[] backgroundColorSpans = buffer.getSpans(0, buffer.length(), 192 | BackgroundColorSpan.class); 193 | for (BackgroundColorSpan span : backgroundColorSpans) { 194 | buffer.removeSpan(span); 195 | widget.setText(buffer); 196 | } 197 | return true; 198 | 199 | case MotionEvent.ACTION_MOVE: 200 | // return super.onTouchEvent(widget, buffer, event); 201 | return true; 202 | 203 | case MotionEvent.ACTION_DOWN: 204 | case MotionEvent.ACTION_UP: 205 | 206 | int x = (int) event.getX(); 207 | int y = (int) event.getY(); 208 | 209 | x -= widget.getTotalPaddingLeft(); 210 | y -= widget.getTotalPaddingTop(); 211 | 212 | x += widget.getScrollX(); 213 | y += widget.getScrollY(); 214 | 215 | Layout layout = widget.getLayout(); 216 | int line = layout.getLineForVertical(y); 217 | int off = layout.getOffsetForHorizontal(line, x); 218 | 219 | ClickableSpan[] link = buffer.getSpans(off, off, ClickableSpan.class); 220 | 221 | 222 | if (link.length != 0) { 223 | 224 | ClickableSpan cs = link[0]; 225 | int csStart = buffer.getSpanStart(cs); 226 | int csEnd = buffer.getSpanEnd(cs); 227 | 228 | int top = layout.getLineTop(line); 229 | int bottom = layout.getLineBottom(line); 230 | float eventY = event.getY() - widget.getTotalPaddingTop(); 231 | 232 | // System.out.println("点击之前:" + line + " " + csStart + " " + csEnd + " " + off + " " + link.length + " " + top + " " + bottom + " " + eventY); 233 | if (off >= csStart && off < csEnd && eventY >= top && eventY <= bottom) { 234 | if (action == MotionEvent.ACTION_UP) { 235 | //移除按下的背景颜色 236 | BackgroundColorSpan[] spans = buffer.getSpans(0, buffer.length(), 237 | BackgroundColorSpan.class); 238 | for (BackgroundColorSpan span : spans) { 239 | buffer.removeSpan(span); 240 | widget.setText(buffer); 241 | } 242 | 243 | 244 | String url = null; 245 | if (link[0] instanceof LinkClickable) { 246 | url = ((LinkClickable) link[0]).getUrl(); 247 | } 248 | 249 | if (mOnLinkClickListener == null || !mOnLinkClickListener.onLinkClick(mLinkTextView, url, event)) { 250 | link[0].onClick(widget); 251 | } 252 | 253 | // System.out.println("点击到链接:" + url + " " + csStart + " " + csEnd + " " + off + " " + link.length + " " + top + " " + bottom); 254 | } else { 255 | //增加按下的背景颜色 256 | BackgroundColorSpan span = new BackgroundColorSpan( 257 | widget.getResources().getColor(R.color.color_selected)); 258 | buffer.setSpan(span, csStart, csEnd, Spanned.SPAN_INCLUSIVE_INCLUSIVE); 259 | widget.setText(buffer); 260 | Selection.setSelection(buffer, 261 | buffer.getSpanStart(link[0]), 262 | buffer.getSpanEnd(link[0])); 263 | } 264 | 265 | 266 | } else { 267 | if (action == MotionEvent.ACTION_UP) { 268 | if (mOnLinkClickListener == null || !mOnLinkClickListener.onNormalClick(mLinkTextView, event)) { 269 | 270 | } 271 | } 272 | } 273 | 274 | return true; 275 | } else { 276 | Selection.removeSelection(buffer); 277 | Touch.onTouchEvent(widget, buffer, event); 278 | if (action == MotionEvent.ACTION_UP) { 279 | if (mOnLinkClickListener == null || !mOnLinkClickListener.onNormalClick(mLinkTextView, event)) { 280 | 281 | } 282 | } 283 | return false; 284 | } 285 | 286 | } 287 | 288 | return Touch.onTouchEvent(widget, buffer, event); 289 | } 290 | } 291 | 292 | public static class LinkSpannableString extends SpannableString { 293 | 294 | public LinkSpannableString(CharSequence source) { 295 | super(source); 296 | String s = source.toString(); 297 | if (s.contains("@")) { 298 | parseEmailLink(s); 299 | } else { 300 | gatherLink(source); 301 | } 302 | } 303 | 304 | private void parseEmailLink(String source) { 305 | String[] ss = source.split(" " + "|" + "\\n"); 306 | Matcher matcher; 307 | for (String s1 : ss) { 308 | if (s1.contains("@")) { 309 | Pattern emailPattern = Pattern.compile(_EMIAL); 310 | matcher = emailPattern.matcher(s1); 311 | } else { 312 | Pattern emailPattern = Pattern.compile(URL_REGEX); 313 | matcher = emailPattern.matcher(s1); 314 | } 315 | boolean result = matcher.find(); 316 | while (result) { 317 | final String find = matcher.group(); 318 | int start = matcher.start(); 319 | int end = matcher.end(); 320 | int index = source.indexOf(s1); 321 | final String url = find; 322 | setSpan(new LinkClickable(url, new OnClickListener() { 323 | @Override 324 | public void onClick(View v) { 325 | if (url.contains("@")) { 326 | //TODO 327 | // Utils.sendMail( , url , null , null); 328 | } else { 329 | // WebUtils.openUrlInCommon(, url); 330 | } 331 | } 332 | }), start + index, end + index, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); 333 | 334 | result = matcher.find(); 335 | } 336 | } 337 | } 338 | 339 | private void gatherLink(CharSequence source) { 340 | Pattern defaultPattern = Pattern.compile(URL_REGEX); 341 | Matcher matcher = defaultPattern.matcher(source); 342 | 343 | boolean result = matcher.find(); 344 | while (result) { 345 | final String find = matcher.group(); 346 | 347 | int start = matcher.start(); 348 | int end = matcher.end(); 349 | 350 | int length = find.length(); 351 | int index = -1; 352 | for (int i = 0; i < length; i++) { 353 | if (find.charAt(i) >= 127) { 354 | index = i; 355 | break; 356 | } 357 | } 358 | final String url; 359 | if (index > 0) { 360 | url = find.substring(0, index); 361 | end = start + url.length(); 362 | } else { 363 | url = find; 364 | } 365 | 366 | setSpan(new LinkClickable(url, new OnClickListener() { 367 | @Override 368 | public void onClick(View v) { 369 | // TODO 370 | // v -> WebUtils.openUrlInCommon(, url) 371 | } 372 | }), start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); 373 | 374 | result = matcher.find(); 375 | } 376 | 377 | } 378 | 379 | } 380 | 381 | public static class LinkClickable extends ClickableSpan { 382 | 383 | private final View.OnClickListener mListener; 384 | 385 | private String mUrl; 386 | 387 | public LinkClickable(String url, View.OnClickListener l) { 388 | mUrl = url; 389 | mListener = l; 390 | } 391 | 392 | public String getUrl() { 393 | return mUrl; 394 | } 395 | 396 | @Override 397 | public void onClick(View widget) { 398 | mListener.onClick(widget); 399 | } 400 | 401 | @Override 402 | public void updateDrawState(TextPaint ds) { 403 | super.updateDrawState(ds); 404 | ds.setUnderlineText(false); 405 | ds.setColor(Color.parseColor("#FF00a8ff")); 406 | } 407 | } 408 | 409 | } -------------------------------------------------------------------------------- /core/src/main/java/net/angrycode/toolkit/widget/QuickClearEditText.java: -------------------------------------------------------------------------------- 1 | package net.angrycode.toolkit.widget; 2 | 3 | import android.content.Context; 4 | import android.content.res.TypedArray; 5 | import android.graphics.drawable.Drawable; 6 | import android.os.Build; 7 | import android.support.v4.content.ContextCompat; 8 | import android.text.Editable; 9 | import android.text.InputType; 10 | import android.text.TextWatcher; 11 | import android.util.AttributeSet; 12 | import android.util.TypedValue; 13 | import android.view.Gravity; 14 | import android.view.View; 15 | import android.view.ViewGroup; 16 | import android.widget.EditText; 17 | import android.widget.FrameLayout; 18 | import android.widget.ImageView; 19 | 20 | import net.angrycode.core.R; 21 | 22 | public class QuickClearEditText extends FrameLayout { 23 | private static final String LOG_TAG = "QuickClearEditText"; 24 | 25 | /** 26 | * 输入内容距离清除按钮之间的距离 27 | */ 28 | private static final int QUICK_CLEAR_MARGIN = 0; 29 | 30 | protected EditText mEditText; 31 | 32 | private FrameLayout mQuickClearBtnWrapper; 33 | private ImageView mQuickClearBtn; 34 | 35 | /** 36 | * 允许使用快速清除功能 37 | */ 38 | private boolean mAllowQuickClear; 39 | 40 | private Drawable mQuickClearDrawable; 41 | 42 | private int mHeight = 0; 43 | 44 | public QuickClearEditText(Context context) { 45 | this(context, null); 46 | } 47 | 48 | public QuickClearEditText(Context context, AttributeSet attrs) { 49 | this(context, attrs, 0); 50 | } 51 | 52 | public QuickClearEditText(Context context, AttributeSet attrs, int defStyleAttr) { 53 | super(context, attrs, defStyleAttr); 54 | init(context, attrs); 55 | } 56 | 57 | private void init(Context context, AttributeSet attrs) { 58 | // 让设置的背景作用于EditText上 59 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { 60 | setBackground(null); 61 | } else { 62 | setBackgroundDrawable(null); 63 | } 64 | setPadding(0, 0, 0, 0); 65 | 66 | TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.QuickClearEditText); 67 | final boolean allowQuickClear = array.getBoolean(R.styleable.QuickClearEditText_quickClear, true); 68 | mQuickClearDrawable = array.getDrawable(R.styleable.QuickClearEditText_quickClearIcon); 69 | array.recycle(); 70 | 71 | // 设置一个默认的清除按钮 72 | if (mQuickClearDrawable == null) { 73 | mQuickClearDrawable = ContextCompat.getDrawable(getContext(), R.mipmap.ic_clear); 74 | } 75 | 76 | addEditText(context, attrs); 77 | 78 | setAllowQuickClear(allowQuickClear); 79 | 80 | } 81 | 82 | private void addEditText(Context context, AttributeSet attrs) { 83 | mEditText = onCreateEditText(context, attrs); 84 | mEditText.setId(ViewUtils.generateViewId()); 85 | 86 | FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT); 87 | addView(mEditText, lp); 88 | mEditText.addTextChangedListener(new InputContentChangeListener()); 89 | } 90 | 91 | private void addQuickClearBtn(Context context) { 92 | mQuickClearBtn = new ImageView(context); 93 | mQuickClearBtnWrapper = new FrameLayout(context); 94 | 95 | TypedValue outValue = new TypedValue(); 96 | context.getTheme().resolveAttribute(R.attr.selectableItemBackgroundBorderless, outValue, true); 97 | if (outValue.resourceId != -1) { 98 | mQuickClearBtn.setBackgroundResource(outValue.resourceId); 99 | } 100 | 101 | mQuickClearBtn.setScaleType(ImageView.ScaleType.CENTER); 102 | mQuickClearBtn.setImageDrawable(mQuickClearDrawable); 103 | 104 | int w = mHeight != 0 ? mHeight / 2 : LayoutParams.WRAP_CONTENT; 105 | int h = mHeight != 0 ? mHeight / 2 : LayoutParams.WRAP_CONTENT; 106 | FrameLayout.LayoutParams btnLp = new FrameLayout.LayoutParams(w, h); 107 | btnLp.gravity = Gravity.CENTER; 108 | mQuickClearBtnWrapper.addView(mQuickClearBtn, btnLp); 109 | 110 | int width = mHeight != 0 ? mHeight : LayoutParams.WRAP_CONTENT; 111 | int height = mHeight != 0 ? mHeight : LayoutParams.WRAP_CONTENT; 112 | FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(width, height); 113 | lp.gravity = Gravity.CENTER_VERTICAL | Gravity.RIGHT; 114 | addView(mQuickClearBtnWrapper, lp); 115 | 116 | if (mEditText.getText().length() > 0) { 117 | mQuickClearBtnWrapper.setVisibility(View.VISIBLE); 118 | } else { 119 | mQuickClearBtnWrapper.setVisibility(View.INVISIBLE); 120 | } 121 | 122 | setEditTextPadding(); 123 | 124 | mQuickClearBtnWrapper.setOnClickListener(new QuickClearClickListener()); 125 | } 126 | 127 | private void removeQuickClearBtn() { 128 | if (mQuickClearBtn != null) { 129 | removeView(mQuickClearBtn); 130 | mQuickClearBtn = null; 131 | } 132 | } 133 | 134 | private void resizeQuickClearBtnIfNeed() { 135 | if (mAllowQuickClear && mQuickClearBtnWrapper != null) { 136 | ViewGroup.LayoutParams lp = mQuickClearBtn.getLayoutParams(); 137 | if (lp != null) { 138 | lp.width = mHeight / 2; 139 | lp.height = mHeight / 2; 140 | mQuickClearBtn.setLayoutParams(lp); 141 | } 142 | 143 | lp = mQuickClearBtnWrapper.getLayoutParams(); 144 | if (lp != null) { 145 | lp.width = mHeight; 146 | lp.height = mHeight; 147 | mQuickClearBtnWrapper.setLayoutParams(lp); 148 | } 149 | setEditTextPadding(); 150 | } 151 | } 152 | 153 | private void setEditTextPadding() { 154 | if (mHeight > 0) { 155 | int l = mEditText.getPaddingLeft(); 156 | int t = mEditText.getPaddingTop(); 157 | int r = mEditText.getPaddingRight() + mHeight + (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, QUICK_CLEAR_MARGIN, getResources().getDisplayMetrics()); 158 | int b = mEditText.getPaddingBottom(); 159 | mEditText.setPadding(l, t, r, b); 160 | } 161 | } 162 | 163 | public void setAllowQuickClear(boolean allow) { 164 | if (allow != mAllowQuickClear) { 165 | mAllowQuickClear = allow; 166 | if (mAllowQuickClear) { 167 | addQuickClearBtn(getContext()); 168 | } else { 169 | removeQuickClearBtn(); 170 | } 171 | } 172 | } 173 | 174 | public void setText(final CharSequence text) { 175 | mEditText.setText(text); 176 | } 177 | 178 | public int length() { 179 | return mEditText.length(); 180 | } 181 | 182 | public void setSelection(int index) { 183 | mEditText.setSelection(index); 184 | } 185 | 186 | public Editable getText() { 187 | return mEditText.getText(); 188 | } 189 | 190 | public EditText getEditText() { 191 | return mEditText; 192 | } 193 | 194 | public void append(final CharSequence text) { 195 | mEditText.append(text); 196 | } 197 | 198 | @Override 199 | protected void onSizeChanged(int w, int h, int oldw, int oldh) { 200 | super.onSizeChanged(w, h, oldw, oldh); 201 | mHeight = h; 202 | resizeQuickClearBtnIfNeed(); 203 | // Logger.d(LOG_TAG, "height:" + mHeight); 204 | } 205 | 206 | public void setPasswordVisible(boolean visible) { 207 | if (visible) {//显示明文 208 | mEditText.setInputType(InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD); 209 | } else {//显示密文 210 | mEditText.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD); 211 | } 212 | mEditText.setSelection(mEditText.getText().length()); 213 | 214 | } 215 | 216 | protected EditText onCreateEditText(Context context, AttributeSet attrs) { 217 | 218 | return new EditText(context, attrs); 219 | } 220 | 221 | /** 222 | * 供子类使用,免得子类再去添加一个监听器 223 | */ 224 | protected void onTextChanged(CharSequence s, int start, int before, int count) { 225 | } 226 | 227 | private class InputContentChangeListener implements TextWatcher { 228 | 229 | @Override 230 | public void beforeTextChanged(CharSequence s, int start, int count, int after) { 231 | if (mTextWatcher != null) { 232 | mTextWatcher.beforeTextChanged(s, start, count, after); 233 | } 234 | } 235 | 236 | @Override 237 | public void onTextChanged(CharSequence s, int start, int before, int count) { 238 | QuickClearEditText.this.onTextChanged(s, start, before, count); 239 | if (mTextWatcher != null) { 240 | mTextWatcher.onTextChanged(s, start, before, count); 241 | } 242 | if (mAllowQuickClear && mQuickClearBtnWrapper != null) { 243 | if (s != null && s.length() > 0) { 244 | mQuickClearBtnWrapper.post(new Runnable() { 245 | @Override 246 | public void run() { 247 | mQuickClearBtnWrapper.setVisibility(View.VISIBLE); 248 | } 249 | }); 250 | 251 | } else { 252 | mQuickClearBtnWrapper.setVisibility(View.INVISIBLE); 253 | } 254 | } 255 | } 256 | 257 | @Override 258 | public void afterTextChanged(Editable s) { 259 | if (mTextWatcher != null) { 260 | mTextWatcher.afterTextChanged(s); 261 | } 262 | } 263 | } 264 | 265 | private class QuickClearClickListener implements OnClickListener { 266 | 267 | @Override 268 | public void onClick(View v) { 269 | if (mEditText != null) { 270 | mEditText.setText(null); 271 | mEditText.requestFocus(); 272 | } 273 | } 274 | } 275 | 276 | public TextWatcher mTextWatcher; 277 | 278 | public void setOnTextChangeListener(TextWatcher textWatcher) { 279 | mTextWatcher = textWatcher; 280 | } 281 | 282 | } -------------------------------------------------------------------------------- /core/src/main/java/net/angrycode/toolkit/widget/SimpleDrawingView.java: -------------------------------------------------------------------------------- 1 | package net.angrycode.toolkit.widget; 2 | 3 | import android.annotation.TargetApi; 4 | import android.content.Context; 5 | import android.graphics.Canvas; 6 | import android.graphics.Color; 7 | import android.graphics.Paint; 8 | import android.graphics.Path; 9 | import android.support.annotation.Nullable; 10 | import android.util.AttributeSet; 11 | import android.view.MotionEvent; 12 | import android.view.View; 13 | 14 | public class SimpleDrawingView extends View { 15 | // setup initial color 16 | private final int paintColor = Color.BLACK; 17 | // defines paint and canvas 18 | private Paint drawPaint; 19 | // stores next circle 20 | private Path path = new Path(); 21 | 22 | public SimpleDrawingView(Context context) { 23 | super(context); 24 | } 25 | 26 | public SimpleDrawingView(Context context, AttributeSet attrs) { 27 | super(context, attrs); 28 | setFocusable(true); 29 | setFocusableInTouchMode(true); 30 | setupPaint(); 31 | } 32 | 33 | public SimpleDrawingView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { 34 | super(context, attrs, defStyleAttr); 35 | } 36 | @TargetApi(21) 37 | public SimpleDrawingView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) { 38 | super(context, attrs, defStyleAttr, defStyleRes); 39 | } 40 | 41 | private void setupPaint() { 42 | // Setup paint with color and stroke styles 43 | drawPaint = new Paint(); 44 | drawPaint.setColor(paintColor); 45 | drawPaint.setAntiAlias(true); 46 | drawPaint.setStrokeWidth(5); 47 | drawPaint.setStyle(Paint.Style.STROKE); 48 | drawPaint.setStrokeJoin(Paint.Join.ROUND); 49 | drawPaint.setStrokeCap(Paint.Cap.ROUND); 50 | } 51 | 52 | @Override 53 | protected void onDraw(Canvas canvas) { 54 | canvas.drawPath(path, drawPaint); 55 | } 56 | 57 | @Override 58 | public boolean onTouchEvent(MotionEvent event) { 59 | float pointX = event.getX(); 60 | float pointY = event.getY(); 61 | // Checks for the event that occurs 62 | switch (event.getAction()) { 63 | case MotionEvent.ACTION_DOWN: 64 | path.moveTo(pointX, pointY); 65 | return true; 66 | case MotionEvent.ACTION_MOVE: 67 | path.lineTo(pointX, pointY); 68 | break; 69 | default: 70 | return false; 71 | } 72 | // Force a view to draw again 73 | postInvalidate(); 74 | return true; 75 | } 76 | } -------------------------------------------------------------------------------- /core/src/main/java/net/angrycode/toolkit/widget/SimpleTextWatcher.java: -------------------------------------------------------------------------------- 1 | package net.angrycode.toolkit.widget; 2 | 3 | import android.text.Editable; 4 | import android.text.TextWatcher; 5 | 6 | /** 7 | * Created by wecodexyz on 2017/8/19. 8 | */ 9 | 10 | public class SimpleTextWatcher implements TextWatcher { 11 | public SimpleTextWatcher() { 12 | } 13 | 14 | @Override 15 | public void beforeTextChanged(CharSequence s, int start, int count, int after) { 16 | 17 | } 18 | 19 | @Override 20 | public void onTextChanged(CharSequence s, int start, int before, int count) { 21 | 22 | } 23 | 24 | @Override 25 | public void afterTextChanged(Editable s) { 26 | 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /core/src/main/java/net/angrycode/toolkit/widget/TintableImageView.java: -------------------------------------------------------------------------------- 1 | package net.angrycode.toolkit.widget; 2 | 3 | import android.content.Context; 4 | import android.content.res.ColorStateList; 5 | import android.content.res.TypedArray; 6 | import android.util.AttributeSet; 7 | import android.widget.ImageView; 8 | 9 | import net.angrycode.core.R; 10 | 11 | 12 | /** 13 | * https://gist.github.com/tylerchesley/5d15d859be4f3ce31213 14 | */ 15 | public class TintableImageView extends ImageView { 16 | 17 | private ColorStateList tint; 18 | 19 | public TintableImageView(Context context) { 20 | super(context); 21 | } 22 | 23 | public TintableImageView(Context context, AttributeSet attrs) { 24 | super(context, attrs); 25 | init(context, attrs, 0); 26 | } 27 | 28 | public TintableImageView(Context context, AttributeSet attrs, int defStyle) { 29 | super(context, attrs, defStyle); 30 | init(context, attrs, defStyle); 31 | } 32 | 33 | private void init(Context context, AttributeSet attrs, int defStyle) { 34 | TypedArray a = context.obtainStyledAttributes( 35 | attrs, R.styleable.TintableImageView, defStyle, 0); 36 | tint = a.getColorStateList( 37 | R.styleable.TintableImageView_tintColor); 38 | a.recycle(); 39 | } 40 | 41 | @Override 42 | protected void drawableStateChanged() { 43 | super.drawableStateChanged(); 44 | if (tint != null && tint.isStateful()) { 45 | updateTintColor(); 46 | } 47 | } 48 | 49 | public void setColorFilter(ColorStateList tint) { 50 | this.tint = tint; 51 | super.setColorFilter(tint.getColorForState(getDrawableState(), 0)); 52 | } 53 | 54 | private void updateTintColor() { 55 | int color = tint.getColorForState(getDrawableState(), 0); 56 | setColorFilter(color); 57 | } 58 | 59 | } 60 | -------------------------------------------------------------------------------- /core/src/main/java/net/angrycode/toolkit/widget/ViewUtils.java: -------------------------------------------------------------------------------- 1 | package net.angrycode.toolkit.widget; 2 | 3 | import android.app.Activity; 4 | import android.content.Context; 5 | import android.graphics.Bitmap; 6 | import android.graphics.Canvas; 7 | import android.os.Build; 8 | import android.util.Log; 9 | import android.view.View; 10 | import android.view.inputmethod.InputMethodManager; 11 | import android.webkit.WebView; 12 | import android.widget.TextView; 13 | 14 | import java.util.concurrent.atomic.AtomicInteger; 15 | 16 | import static android.view.View.GONE; 17 | import static android.view.View.INVISIBLE; 18 | import static android.view.View.VISIBLE; 19 | 20 | /** 21 | *

Utility methods that make working with views easier, less error prone, and more concise.

22 | *

23 | *

Common uses:

24 | * TextView textView = ViewUtils.{@link #findViewById findViewById}(this, R.id.my_text_view); //no more casting!
25 | * TextView textView = ViewUtils.{@link #findViewById findViewById}(parentView, R.id.my_text_view); //no more casting!
26 | * String text = ViewUtils.{@link #getText getText}(this, R.id.my_text_view);
27 | * ViewUtils.{@link #setText setText}(this, R.id.my_text_vew, "new text");
28 | * ViewUtils.{@link #setText setText}(textView, "new text");
29 | * ViewUtils.{@link #appendText appendText}(textView, "appended");
30 | * ViewUtils.{@link #hideView hideView}(this, R.id.my_text_view);
31 | * ViewUtils.{@link #showView showView}(this, R.id.my_text_view);
32 | * Bitmap bitmap = ViewUtils.{@link #viewToImage viewToImage}(this, R.id.my_layout);
33 | * ViewUtils.{@link #closeKeyboard showKeyboard}(this, R.id.my_text_view);
34 | * ViewUtils.{@link #closeKeyboard closeKeyboard}(this, R.id.my_text_view);
35 | *
36 | */ 37 | public class ViewUtils { 38 | 39 | /** 40 | * Utility method to make getting a View via findViewById() more safe & simple. 41 | *

42 | * - Casts view to appropriate type based on expected return value 43 | * - Handles & logs invalid casts 44 | * 45 | * @param context The current Context or Activity that this method is called from 46 | * @param id R.id value for view 47 | * @return View object, cast to appropriate type based on expected return value. 48 | * @throws ClassCastException if cast to the expected type breaks. 49 | */ 50 | @SuppressWarnings("unchecked") 51 | public static T findViewById(Activity context, int id) { 52 | T view = null; 53 | View genericView = context.findViewById(id); 54 | try { 55 | view = (T) (genericView); 56 | } catch (Exception ex) { 57 | String message = "Can't cast view (" + id + ") to a " + view.getClass() + ". Is actually a " + genericView.getClass() + "."; 58 | Log.e("PercolateAndroidUtils", message); 59 | throw new ClassCastException(message); 60 | } 61 | 62 | return view; 63 | } 64 | 65 | /** 66 | * Utility method to make getting a View via findViewById() more safe & simple. 67 | *

68 | * - Casts view to appropriate type based on expected return value 69 | * - Handles & logs invalid casts 70 | * 71 | * @param parentView Parent View containing the view we are trying to get 72 | * @param id R.id value for view 73 | * @return View object, cast to appropriate type based on expected return value. 74 | * @throws ClassCastException if cast to the expected type breaks. 75 | */ 76 | @SuppressWarnings("unchecked") 77 | public static T findViewById(View parentView, int id) { 78 | T view = null; 79 | View genericView = parentView.findViewById(id); 80 | try { 81 | view = (T) (genericView); 82 | } catch (Exception ex) { 83 | String message = "Can't cast view (" + id + ") to a " + view.getClass() + ". Is actually a " + genericView.getClass() + "."; 84 | Log.e("PercolateAndroidUtils", message); 85 | throw new ClassCastException(message); 86 | } 87 | 88 | return view; 89 | } 90 | 91 | /** 92 | * Get text as String from EditView. 93 | * Note: returns "" for null EditText, not a NullPointerException 94 | * 95 | * @param view EditView to get text from 96 | * @return the text 97 | */ 98 | public static String getText(TextView view) { 99 | String text = ""; 100 | if (view != null) { 101 | text = view.getText().toString(); 102 | } else { 103 | Log.e("PercolateAndroidUtils", "Null view given to getText(). \"\" will be returned."); 104 | } 105 | return text; 106 | } 107 | 108 | /** 109 | * Get text as String from EditView. 110 | * Note: returns "" for null EditText, not a NullPointerException 111 | * 112 | * @param context The current Context or Activity that this method is called from 113 | * @param id Id for the TextView/EditView to get text from 114 | * @return the text 115 | */ 116 | public static String getText(Activity context, int id) { 117 | TextView view = findViewById(context, id); 118 | 119 | String text = ""; 120 | if (view != null) { 121 | text = view.getText().toString(); 122 | } else { 123 | Log.e("PercolateAndroidUtils", "Null view given to getText(). \"\" will be returned."); 124 | } 125 | return text; 126 | } 127 | 128 | /** 129 | * Append given text String to the provided view (one of TextView or EditText). 130 | * 131 | * @param view View to update 132 | * @param toAppend String text 133 | */ 134 | public static void appendText(TextView view, String toAppend) { 135 | String currentText = getText(view); 136 | view.setText(currentText + toAppend); 137 | } 138 | 139 | /** 140 | * Go away keyboard, nobody likes you. 141 | * 142 | * @param context The current Context or Activity that this method is called from 143 | * @param field field that holds the keyboard focus 144 | */ 145 | public static void closeKeyboard(Context context, View field) { 146 | try { 147 | InputMethodManager imm = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE); 148 | imm.hideSoftInputFromWindow(field.getWindowToken(), 0); 149 | } catch (Exception ex) { 150 | Log.e("PercolateAndroidUtils", "Error occurred trying to hide the keyboard. Exception=" + ex); 151 | } 152 | } 153 | 154 | /** 155 | * Convert view to an image. Can be used to make animations smoother. 156 | * 157 | * @param context The current Context or Activity that this method is called from 158 | * @param viewToBeConverted View to convert to a Bitmap 159 | * @return Bitmap object that can be put in an ImageView. Will look like the converted viewToBeConverted. 160 | */ 161 | public static Bitmap viewToImage(Context context, WebView viewToBeConverted) { 162 | int extraSpace = 2000; //because getContentHeight doesn't always return the full screen height. 163 | int height = viewToBeConverted.getContentHeight() + extraSpace; 164 | 165 | Bitmap viewBitmap = Bitmap.createBitmap(viewToBeConverted.getWidth(), height, Bitmap.Config.ARGB_8888); 166 | Canvas canvas = new Canvas(viewBitmap); 167 | viewToBeConverted.draw(canvas); 168 | 169 | //If the view is scrolled, cut off the top part that is off the screen. 170 | try { 171 | int scrollY = viewToBeConverted.getScrollY(); 172 | if (scrollY > 0) { 173 | viewBitmap = Bitmap.createBitmap(viewBitmap, 0, scrollY, viewToBeConverted.getWidth(), height - scrollY); 174 | } 175 | } catch (Exception ex) { 176 | Log.e("PercolateAndroidUtils", "Could not remove top part of the webview image. ex=" + ex); 177 | } 178 | 179 | return viewBitmap; 180 | } 181 | 182 | /** 183 | * Method used to set text for a TextView 184 | * 185 | * @param context The current Context or Activity that this method is called from 186 | * @param field R.id.xxxx value for the text field. 187 | * @param text Text to place in the text field. 188 | */ 189 | public static void setText(Activity context, int field, String text) { 190 | View view = context.findViewById(field); 191 | if (view instanceof TextView) { 192 | ((TextView) view).setText(text); 193 | } else { 194 | Log.e("PercolateAndroidUtils", "ViewUtils.setText() given a field that is not a TextView"); 195 | } 196 | } 197 | 198 | /** 199 | * Method used to set text for a TextView 200 | * 201 | * @param parentView The View used to call findViewId() on 202 | * @param field R.id.xxxx value for the text field. 203 | * @param text Text to place in the text field. 204 | */ 205 | public static void setText(View parentView, int field, String text) { 206 | View view = parentView.findViewById(field); 207 | if (view instanceof TextView) { 208 | ((TextView) view).setText(text); 209 | } else { 210 | Log.e("PercolateAndroidUtils", "ViewUtils.setText() given a field that is not a TextView"); 211 | } 212 | } 213 | 214 | /** 215 | * Sets visibility of the given view to View.GONE. 216 | * 217 | * @param context The current Context or Activity that this method is called from 218 | * @param id R.id.xxxx value for the view to hide"expected textView to throw a ClassCastException" + textView 219 | */ 220 | public static void hideView(Activity context, int id) { 221 | if (context != null) { 222 | View view = context.findViewById(id); 223 | if (view != null) { 224 | view.setVisibility(View.GONE); 225 | } else { 226 | Log.e("PercolateAndroidUtils", "View does not exist. Could not hide it."); 227 | } 228 | } 229 | } 230 | 231 | /** 232 | * Sets visibility of the given view to View.VISIBLE. 233 | * 234 | * @param context The current Context or Activity that this method is called from 235 | * @param id R.id.xxxx value for the view to show 236 | */ 237 | public static void showView(Activity context, int id) { 238 | if (context != null) { 239 | View view = context.findViewById(id); 240 | if (view != null) { 241 | view.setVisibility(View.VISIBLE); 242 | } else { 243 | Log.e("PercolateAndroidUtils", "View does not exist. Could not hide it."); 244 | } 245 | } 246 | } 247 | 248 | /** 249 | * Set visibility of given view to be gone or visible 250 | *

251 | * This method has no effect if the view visibility is currently invisible 252 | * 253 | * @param view 254 | * @param gone 255 | * @return view 256 | */ 257 | public static V setGone(final V view, final boolean gone) { 258 | if (view != null) 259 | if (gone) { 260 | if (GONE != view.getVisibility()) 261 | view.setVisibility(GONE); 262 | } else { 263 | if (VISIBLE != view.getVisibility()) 264 | view.setVisibility(VISIBLE); 265 | } 266 | return view; 267 | } 268 | 269 | /** 270 | * Set visibility of given view to be invisible or visible 271 | *

272 | * This method has no effect if the view visibility is currently gone 273 | * 274 | * @param view 275 | * @param invisible 276 | * @return view 277 | */ 278 | public static V setInvisible(final V view, 279 | final boolean invisible) { 280 | if (view != null) 281 | if (invisible) { 282 | if (INVISIBLE != view.getVisibility()) 283 | view.setVisibility(INVISIBLE); 284 | } else { 285 | if (VISIBLE != view.getVisibility()) 286 | view.setVisibility(VISIBLE); 287 | } 288 | return view; 289 | } 290 | 291 | private static final AtomicInteger sNextGeneratedId = new AtomicInteger(1); 292 | 293 | /** 294 | * Generate a value suitable for use in {@link #setId(int)}. 295 | * This value will not collide with ID values generated at build time by aapt for R.id. 296 | * 297 | * @return a generated ID value 298 | */ 299 | public static int generateViewId() { 300 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { 301 | return View.generateViewId(); 302 | } 303 | for (;;) { 304 | final int result = sNextGeneratedId.get(); 305 | // aapt-generated IDs have the high byte nonzero; clamp to the range under that. 306 | int newValue = result + 1; 307 | if (newValue > 0x00FFFFFF) newValue = 1; // Roll over to 1, not 0. 308 | if (sNextGeneratedId.compareAndSet(result, newValue)) { 309 | return result; 310 | } 311 | } 312 | } 313 | public static final int OPGL_MAX_TEXTURE = 4096; 314 | private static long lastLauchTime; 315 | 316 | /** 317 | * 防止快速点击动态缩略图后显示空白 318 | */ 319 | public static boolean isFastClickWhenTransition(){ 320 | long now = System.currentTimeMillis(); 321 | boolean isFast = now - lastLauchTime < 1000; 322 | lastLauchTime = now; 323 | return isFast; 324 | } 325 | } 326 | -------------------------------------------------------------------------------- /core/src/main/res/drawable/layer_divider.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /core/src/main/res/drawable/selector_pwd_check.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /core/src/main/res/drawable/shape_divider.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /core/src/main/res/drawable/shape_text_input_bg.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /core/src/main/res/layout/layout_text_input.xml: -------------------------------------------------------------------------------- 1 | 2 | 14 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /core/src/main/res/mipmap-hdpi/ic_pwd_hide.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hylinux1024/Componentization/52c188b675f53dae26a445e2447fecd360b5880a/core/src/main/res/mipmap-hdpi/ic_pwd_hide.png -------------------------------------------------------------------------------- /core/src/main/res/mipmap-hdpi/ic_pwd_visible.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hylinux1024/Componentization/52c188b675f53dae26a445e2447fecd360b5880a/core/src/main/res/mipmap-hdpi/ic_pwd_visible.png -------------------------------------------------------------------------------- /core/src/main/res/mipmap-xhdpi/ic_clear.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hylinux1024/Componentization/52c188b675f53dae26a445e2447fecd360b5880a/core/src/main/res/mipmap-xhdpi/ic_clear.png -------------------------------------------------------------------------------- /core/src/main/res/mipmap-xhdpi/ic_pwd_hide.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hylinux1024/Componentization/52c188b675f53dae26a445e2447fecd360b5880a/core/src/main/res/mipmap-xhdpi/ic_pwd_hide.png -------------------------------------------------------------------------------- /core/src/main/res/mipmap-xhdpi/ic_pwd_visible.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hylinux1024/Componentization/52c188b675f53dae26a445e2447fecd360b5880a/core/src/main/res/mipmap-xhdpi/ic_pwd_visible.png -------------------------------------------------------------------------------- /core/src/main/res/values/attrs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /core/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #E5E5E5 4 | #ebebeb 5 | -------------------------------------------------------------------------------- /core/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | core 3 | 4 | 网络好像有点问题哦T_T 5 | 应用未安装 6 | 《%1$s》,链接:%2$s 7 | 分享失败 8 | 9 | -------------------------------------------------------------------------------- /core/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 14 | -------------------------------------------------------------------------------- /data/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /data/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | 3 | android { 4 | compileSdkVersion 25 5 | buildToolsVersion "25.0.2" 6 | 7 | defaultConfig { 8 | minSdkVersion 14 9 | targetSdkVersion 25 10 | versionCode 1 11 | versionName "1.0" 12 | 13 | } 14 | buildTypes { 15 | release { 16 | minifyEnabled false 17 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 18 | } 19 | } 20 | } 21 | 22 | dependencies { 23 | compile fileTree(dir: 'libs', include: ['*.jar']) 24 | compile 'com.android.support:appcompat-v7:25.3.1' 25 | compile project(':core') 26 | 27 | } 28 | -------------------------------------------------------------------------------- /data/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in C:\AndroidDev\Android\sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | 19 | # Uncomment this to preserve the line number information for 20 | # debugging stack traces. 21 | #-keepattributes SourceFile,LineNumberTable 22 | 23 | # If you keep the line number information, uncomment this to 24 | # hide the original source file name. 25 | #-renamesourcefileattribute SourceFile 26 | -------------------------------------------------------------------------------- /data/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /data/src/main/java/net/angrycode/data/repository/Data.java: -------------------------------------------------------------------------------- 1 | package net.angrycode.data.repository; 2 | 3 | /** 4 | * Created by wecodexyz on 2017/7/28. 5 | */ 6 | 7 | public class Data { 8 | public int id; 9 | public String text; 10 | public String image; 11 | 12 | @Override 13 | public boolean equals(Object o) { 14 | if (this == o) return true; 15 | if (o == null || getClass() != o.getClass()) return false; 16 | 17 | Data data = (Data) o; 18 | 19 | return id == data.id; 20 | 21 | } 22 | 23 | @Override 24 | public int hashCode() { 25 | return id; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /data/src/main/java/net/angrycode/data/repository/IDataSource.java: -------------------------------------------------------------------------------- 1 | package net.angrycode.data.repository; 2 | 3 | import java.util.List; 4 | 5 | /** 6 | * Created by wecodexyz on 2017/7/28. 7 | */ 8 | 9 | public interface IDataSource { 10 | void add(T t); 11 | 12 | void delete(T t); 13 | 14 | void update(T t); 15 | 16 | List queryAll(); 17 | 18 | T queryById(int id); 19 | } 20 | -------------------------------------------------------------------------------- /data/src/main/java/net/angrycode/data/repository/RepositoryFactory.java: -------------------------------------------------------------------------------- 1 | package net.angrycode.data.repository; 2 | 3 | import android.support.annotation.NonNull; 4 | 5 | import net.angrycode.data.repository.local.LocalRepository; 6 | import net.angrycode.data.repository.remote.RemoteRepository; 7 | 8 | import java.util.HashMap; 9 | import java.util.List; 10 | 11 | import io.reactivex.Observable; 12 | 13 | /** 14 | * Created by wecodexyz on 2017/7/28. 15 | */ 16 | 17 | public class RepositoryFactory implements IDataSource { 18 | private IDataSource local; 19 | private IDataSource remote; 20 | 21 | private static RepositoryFactory INSTANCE; 22 | /** 23 | * 使用Map实现一个内存缓存 24 | */ 25 | HashMap mCache = new HashMap<>(); 26 | 27 | private RepositoryFactory(@NonNull IDataSource local, @NonNull IDataSource remote) { 28 | this.local = local; 29 | this.remote = remote; 30 | } 31 | 32 | public static RepositoryFactory get(@NonNull IDataSource local, @NonNull IDataSource remote) { 33 | if (INSTANCE == null) { 34 | INSTANCE = new RepositoryFactory(local, remote); 35 | } 36 | return INSTANCE; 37 | } 38 | 39 | public static RepositoryFactory get() { 40 | if (INSTANCE == null) { 41 | INSTANCE = new RepositoryFactory(new LocalRepository(), new RemoteRepository()); 42 | } 43 | return INSTANCE; 44 | } 45 | 46 | public void destory() { 47 | INSTANCE = null; 48 | } 49 | 50 | @Override 51 | public void add(Data data) { 52 | local.add(data); 53 | remote.add(data); 54 | mCache.put(String.valueOf(data.id), data); 55 | } 56 | 57 | @Override 58 | public void delete(Data data) { 59 | local.delete(data); 60 | remote.delete(data); 61 | mCache.remove(String.valueOf(data.id)); 62 | } 63 | 64 | @Override 65 | public void update(Data data) { 66 | local.update(data); 67 | remote.update(data); 68 | mCache.put(String.valueOf(data.id), data); 69 | } 70 | 71 | /** 72 | * @return 73 | */ 74 | @Override 75 | public List queryAll() { 76 | List list = local.queryAll(); 77 | if (list.isEmpty()) { 78 | list = remote.queryAll(); 79 | } 80 | return list; 81 | } 82 | 83 | /** 84 | * 这里使用三级缓存获取一个Data对象 85 | * 86 | * @param id 87 | * @return 88 | */ 89 | @Override 90 | public Data queryById(int id) { 91 | 92 | Data data = mCache.get(String.valueOf(id)); 93 | if (data == null) { 94 | data = local.queryById(id); 95 | } 96 | if (data == null) { 97 | data = remote.queryById(id); 98 | } 99 | if (data != null) { 100 | mCache.put(String.valueOf(id), data); 101 | } 102 | return data; 103 | } 104 | 105 | } 106 | -------------------------------------------------------------------------------- /data/src/main/java/net/angrycode/data/repository/local/DBHelper.java: -------------------------------------------------------------------------------- 1 | package net.angrycode.data.repository.local; 2 | 3 | import net.angrycode.data.repository.Data; 4 | 5 | import java.util.ArrayList; 6 | import java.util.List; 7 | 8 | /** 9 | * 数据库封装工具类,这里是简单展示,不是实际数据库操作 10 | * Created by wecodexyz on 2017/7/28. 11 | */ 12 | 13 | public class DBHelper { 14 | /** 15 | * 这里简单展示存储,不是实际数据库操作 16 | */ 17 | private List dataList = new ArrayList<>(); 18 | 19 | private DBHelper() { 20 | 21 | } 22 | 23 | private static final class Singleton { 24 | static final DBHelper INSTANCE = new DBHelper(); 25 | } 26 | 27 | public static DBHelper get() { 28 | return Singleton.INSTANCE; 29 | } 30 | 31 | public boolean add(Data data) { 32 | return dataList.add(data); 33 | } 34 | 35 | public boolean delete(Data data) { 36 | return dataList.remove(data); 37 | } 38 | 39 | public boolean update(Data data) { 40 | if (dataList.contains(data)) { 41 | dataList.remove(data); 42 | dataList.add(data); 43 | } else { 44 | dataList.add(data); 45 | } 46 | return true; 47 | } 48 | 49 | public List queryAll() { 50 | return dataList; 51 | } 52 | 53 | public Data queryById(int id) { 54 | for (Data data : dataList) { 55 | if (data.id == id) { 56 | return data; 57 | } 58 | } 59 | return null; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /data/src/main/java/net/angrycode/data/repository/local/LocalRepository.java: -------------------------------------------------------------------------------- 1 | package net.angrycode.data.repository.local; 2 | 3 | import net.angrycode.data.repository.Data; 4 | import net.angrycode.data.repository.IDataSource; 5 | 6 | import java.util.List; 7 | 8 | /** 9 | * Created by wecodexyz on 2017/7/28. 10 | */ 11 | 12 | public class LocalRepository implements IDataSource { 13 | 14 | 15 | public LocalRepository() { 16 | } 17 | 18 | @Override 19 | public void add(Data data) { 20 | DBHelper.get().add(data); 21 | } 22 | 23 | @Override 24 | public void delete(Data data) { 25 | DBHelper.get().delete(data); 26 | } 27 | 28 | @Override 29 | public void update(Data data) { 30 | DBHelper.get().update(data); 31 | } 32 | 33 | @Override 34 | public List queryAll() { 35 | return DBHelper.get().queryAll(); 36 | } 37 | 38 | @Override 39 | public Data queryById(int id) { 40 | return DBHelper.get().queryById(id); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /data/src/main/java/net/angrycode/data/repository/remote/DataApi.java: -------------------------------------------------------------------------------- 1 | package net.angrycode.data.repository.remote; 2 | 3 | import net.angrycode.data.repository.Data; 4 | 5 | import java.util.ArrayList; 6 | import java.util.List; 7 | 8 | /** 9 | * Created by wecodexyz on 2017/7/28. 10 | */ 11 | 12 | public class DataApi { 13 | /** 14 | * 这里简单展示存储,不是实际数据库操作 15 | */ 16 | private List dataList = new ArrayList<>(); 17 | 18 | private DataApi() { 19 | 20 | } 21 | 22 | public static final class SingleTon { 23 | private static final DataApi INSTANCE = new DataApi(); 24 | } 25 | 26 | public static DataApi get() { 27 | return SingleTon.INSTANCE; 28 | } 29 | 30 | public boolean add(Data data) { 31 | return dataList.add(data); 32 | } 33 | 34 | public boolean delete(Data data) { 35 | return dataList.remove(data); 36 | } 37 | 38 | public boolean update(Data data) { 39 | if (dataList.contains(data)) { 40 | dataList.remove(data); 41 | dataList.add(data); 42 | } else { 43 | dataList.add(data); 44 | } 45 | return true; 46 | } 47 | 48 | public List queryAll() { 49 | List list = new ArrayList<>(); 50 | for (int i = 0; i < 25; ++i) { 51 | Data data = new Data(); 52 | data.id = i; 53 | data.text = "测试-" + i; 54 | list.add(data); 55 | } 56 | return list; 57 | } 58 | 59 | public Data queryById(int id) { 60 | for (Data data : dataList) { 61 | if (data.id == id) { 62 | return data; 63 | } 64 | } 65 | return null; 66 | } 67 | 68 | 69 | } 70 | -------------------------------------------------------------------------------- /data/src/main/java/net/angrycode/data/repository/remote/RemoteRepository.java: -------------------------------------------------------------------------------- 1 | package net.angrycode.data.repository.remote; 2 | 3 | import net.angrycode.data.repository.Data; 4 | import net.angrycode.data.repository.IDataSource; 5 | 6 | import java.util.List; 7 | 8 | /** 9 | * 模拟远程访问数据 10 | * Created by huangyanglin on 2017/7/28. 11 | */ 12 | 13 | public class RemoteRepository implements IDataSource { 14 | @Override 15 | public void add(Data data) { 16 | DataApi.get().add(data); 17 | } 18 | 19 | @Override 20 | public void delete(Data data) { 21 | DataApi.get().delete(data); 22 | } 23 | 24 | @Override 25 | public void update(Data data) { 26 | DataApi.get().update(data); 27 | } 28 | 29 | @Override 30 | public List queryAll() { 31 | return DataApi.get().queryAll(); 32 | } 33 | 34 | @Override 35 | public Data queryById(int id) { 36 | return DataApi.get().queryById(id); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /data/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | data 3 | 4 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hylinux1024/Componentization/52c188b675f53dae26a445e2447fecd360b5880a/gradle.properties -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hylinux1024/Componentization/52c188b675f53dae26a445e2447fecd360b5880a/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Thu Sep 14 13:11:13 CST 2017 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-3.5-all.zip 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 10 | DEFAULT_JVM_OPTS="" 11 | 12 | APP_NAME="Gradle" 13 | APP_BASE_NAME=`basename "$0"` 14 | 15 | # Use the maximum available, or set MAX_FD != -1 to use that value. 16 | MAX_FD="maximum" 17 | 18 | warn ( ) { 19 | echo "$*" 20 | } 21 | 22 | die ( ) { 23 | echo 24 | echo "$*" 25 | echo 26 | exit 1 27 | } 28 | 29 | # OS specific support (must be 'true' or 'false'). 30 | cygwin=false 31 | msys=false 32 | darwin=false 33 | case "`uname`" in 34 | CYGWIN* ) 35 | cygwin=true 36 | ;; 37 | Darwin* ) 38 | darwin=true 39 | ;; 40 | MINGW* ) 41 | msys=true 42 | ;; 43 | esac 44 | 45 | # Attempt to set APP_HOME 46 | # Resolve links: $0 may be a link 47 | PRG="$0" 48 | # Need this for relative symlinks. 49 | while [ -h "$PRG" ] ; do 50 | ls=`ls -ld "$PRG"` 51 | link=`expr "$ls" : '.*-> \(.*\)$'` 52 | if expr "$link" : '/.*' > /dev/null; then 53 | PRG="$link" 54 | else 55 | PRG=`dirname "$PRG"`"/$link" 56 | fi 57 | done 58 | SAVED="`pwd`" 59 | cd "`dirname \"$PRG\"`/" >/dev/null 60 | APP_HOME="`pwd -P`" 61 | cd "$SAVED" >/dev/null 62 | 63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 64 | 65 | # Determine the Java command to use to start the JVM. 66 | if [ -n "$JAVA_HOME" ] ; then 67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 68 | # IBM's JDK on AIX uses strange locations for the executables 69 | JAVACMD="$JAVA_HOME/jre/sh/java" 70 | else 71 | JAVACMD="$JAVA_HOME/bin/java" 72 | fi 73 | if [ ! -x "$JAVACMD" ] ; then 74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 75 | 76 | Please set the JAVA_HOME variable in your environment to match the 77 | location of your Java installation." 78 | fi 79 | else 80 | JAVACMD="java" 81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 82 | 83 | Please set the JAVA_HOME variable in your environment to match the 84 | location of your Java installation." 85 | fi 86 | 87 | # Increase the maximum file descriptors if we can. 88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 89 | MAX_FD_LIMIT=`ulimit -H -n` 90 | if [ $? -eq 0 ] ; then 91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 92 | MAX_FD="$MAX_FD_LIMIT" 93 | fi 94 | ulimit -n $MAX_FD 95 | if [ $? -ne 0 ] ; then 96 | warn "Could not set maximum file descriptor limit: $MAX_FD" 97 | fi 98 | else 99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 100 | fi 101 | fi 102 | 103 | # For Darwin, add options to specify how the application appears in the dock 104 | if $darwin; then 105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 106 | fi 107 | 108 | # For Cygwin, switch paths to Windows format before running java 109 | if $cygwin ; then 110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 112 | JAVACMD=`cygpath --unix "$JAVACMD"` 113 | 114 | # We build the pattern for arguments to be converted via cygpath 115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 116 | SEP="" 117 | for dir in $ROOTDIRSRAW ; do 118 | ROOTDIRS="$ROOTDIRS$SEP$dir" 119 | SEP="|" 120 | done 121 | OURCYGPATTERN="(^($ROOTDIRS))" 122 | # Add a user-defined pattern to the cygpath arguments 123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 125 | fi 126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 127 | i=0 128 | for arg in "$@" ; do 129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 131 | 132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 134 | else 135 | eval `echo args$i`="\"$arg\"" 136 | fi 137 | i=$((i+1)) 138 | done 139 | case $i in 140 | (0) set -- ;; 141 | (1) set -- "$args0" ;; 142 | (2) set -- "$args0" "$args1" ;; 143 | (3) set -- "$args0" "$args1" "$args2" ;; 144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 150 | esac 151 | fi 152 | 153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 154 | function splitJvmOpts() { 155 | JVM_OPTS=("$@") 156 | } 157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 159 | 160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 161 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app', ':core', ':data' 2 | --------------------------------------------------------------------------------