+import cn.qjm253.quick_android_mvp.base.activity.BaseQuickAndroidMVPActivity;
+import cn.qjm253.quick_android_mvp.base.mvp.BasePresenter;
+
+public abstract class MINVpnBaseActivity>
+ extends BaseQuickAndroidMVPActivity
implements MINVpnBaseView{
public void showWarning(String msg){
@@ -37,6 +41,11 @@ public abstract class MINVpnBaseActivity
- implements WelcomeActivityContract.View, BaseView {
+ implements WelcomeActivityContract.View{
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@@ -30,7 +35,20 @@ public class WelcomeActivity extends MINVpnBaseActivity() {
+ @Override
+ public void accept(Throwable throwable) throws Exception {
+ throwable.printStackTrace();
+ }
+ });
+ fingerprintAuthenticate();
+ }
+ public void fingerprintAuthenticate(){
+ System.out.println("指纹验证...");
}
@NotNull
diff --git a/app/src/main/java/com/pkusz/min_vpn_client/extensions/ActivityExtensions.kt b/app/src/main/java/com/pkusz/min_vpn_client/extensions/ActivityExtensions.kt
new file mode 100644
index 0000000..8ae2e94
--- /dev/null
+++ b/app/src/main/java/com/pkusz/min_vpn_client/extensions/ActivityExtensions.kt
@@ -0,0 +1,26 @@
+package cn.qjm253.min_vpn.extensions
+
+import android.app.Activity
+import android.graphics.Rect
+import android.view.View
+import cn.qjm253.quick_android_base.util.DisplayUtils
+
+fun Activity.listenKeyboardVisible(onChange: (isOpen: Boolean) -> Unit) {
+ val contentView = findViewById(android.R.id.content)
+ val activityRoot = window.decorView
+
+ val rectR = Rect()
+ var lastOpenState = false
+ val visibleThreshold = DisplayUtils.dp2px(this, 100)
+ contentView.viewTreeObserver.addOnGlobalLayoutListener {
+ activityRoot.getWindowVisibleDisplayFrame(rectR)
+ val heightDiff = activityRoot.rootView.height - rectR.height()
+
+ // 键盘是否弹出
+ val isOpen = heightDiff > visibleThreshold
+ if (lastOpenState != isOpen) {
+ onChange(isOpen)
+ lastOpenState = isOpen
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/pkusz/min_vpn_client/extensions/ContextExtensions.kt b/app/src/main/java/com/pkusz/min_vpn_client/extensions/ContextExtensions.kt
new file mode 100644
index 0000000..c6093f9
--- /dev/null
+++ b/app/src/main/java/com/pkusz/min_vpn_client/extensions/ContextExtensions.kt
@@ -0,0 +1,10 @@
+package cn.qjm253.min_vpn.extensions
+
+import android.content.Context
+import androidx.annotation.ColorRes
+import androidx.core.content.ContextCompat
+
+
+fun Context.easyGetColor(@ColorRes id: Int): Int {
+ return ContextCompat.getColor(this, id)
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/pkusz/min_vpn_client/extensions/FileUtilsExtensions.kt b/app/src/main/java/com/pkusz/min_vpn_client/extensions/FileUtilsExtensions.kt
new file mode 100644
index 0000000..df9b785
--- /dev/null
+++ b/app/src/main/java/com/pkusz/min_vpn_client/extensions/FileUtilsExtensions.kt
@@ -0,0 +1,37 @@
+package cn.qjm253.min_vpn.extensions
+
+import android.content.Context
+import android.os.Environment
+import cn.qjm253.quick_android_base.QuickAndroid
+import cn.qjm253.quick_android_base.util.FileUtils;
+import java.io.File
+
+val FileUtils.appDataPath: String
+ get() {
+ //val sdDir = File(Environment.getDataDirectory().toString() + "/" + "MIN_VPN" + "/")
+ if(appContext == null) {
+ val sdDir = File(Environment.getDataDirectory().toString() + "/" + "MIN_VPN" + "/")
+ if (!sdDir.exists()) {
+ sdDir.mkdirs()
+ }
+ return sdDir.toString()
+ } else {
+ val sdDir = File(appContext!!.getDir("MIN_VPN", Context.MODE_PRIVATE).absolutePath)
+ if (!sdDir.exists()) {
+ sdDir.mkdirs()
+ }
+ return sdDir.toString()
+ }
+ }
+
+var appContext: Context? = null
+ get() {
+ return field
+ }
+ set(context: Context?) {
+ field = context
+ }
+
+fun FileUtils.init(context: Context) {
+ appContext = context
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/pkusz/min_vpn_client/extensions/README.md b/app/src/main/java/com/pkusz/min_vpn_client/extensions/README.md
new file mode 100644
index 0000000..31f2264
--- /dev/null
+++ b/app/src/main/java/com/pkusz/min_vpn_client/extensions/README.md
@@ -0,0 +1,3 @@
+# extensions模块说明
+由于Kotlin的一些特性,例如可以直接扩展一些类的方法,故保留此部分的Kotlin代码。
+(旧版本与quick_android耦合得有些严重)
\ No newline at end of file
diff --git a/app/src/main/java/com/pkusz/min_vpn_client/Request_API.java b/app/src/main/java/com/pkusz/min_vpn_client/model/Request_API.java
similarity index 98%
rename from app/src/main/java/com/pkusz/min_vpn_client/Request_API.java
rename to app/src/main/java/com/pkusz/min_vpn_client/model/Request_API.java
index 5f3ef99..0df942d 100644
--- a/app/src/main/java/com/pkusz/min_vpn_client/Request_API.java
+++ b/app/src/main/java/com/pkusz/min_vpn_client/model/Request_API.java
@@ -1,4 +1,4 @@
-package com.pkusz.min_vpn_client;
+package com.pkusz.min_vpn_client.model;
/*
* @Author: Wang Feng
* @Description:
@@ -15,7 +15,6 @@ import java.net.InetSocketAddress;
import java.net.Socket;
import java.nio.charset.StandardCharsets;
-//import cn.qjm253.min_vpn.model.request.BaseRequest;
import com.pkusz.min_vpn_client.model.request.BaseRequest;
import com.pkusz.min_vpn_client.utils.*;
import common.LoggerHelper;
diff --git a/build.gradle b/build.gradle
index b705c87..ff99cc5 100644
--- a/build.gradle
+++ b/build.gradle
@@ -1,5 +1,39 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
+ // ----------start: 导入quickandroid到本地----------
+ ext.kotlin_version = '1.3.72'
+ ext.okhttp_version = '4.0.0'
+ ext.persistent_cookie_jar_version = 'v1.0.1'
+ ext.retrofit_version = '2.6.0'
+ ext.gson_version = '2.8.6'
+ ext.glide_version = '4.9.0'
+ ext.easypermission_version = '3.0.0'
+ ext.bag_qrcode_zxing_version = '1.3.6'
+ ext.immersion_bar_version = '3.0.0-beta05'
+ ext.anko_version = '0.10.8'
+ ext.parallax_layout_version = '1.0'
+ ext.logger_vesion = '2.2.0'
+ ext.brvah_version = '2.9.46'
+ ext.quick_android_version = '0.1.2'
+
+ ext.rx_android_version = '2.1.1'
+ ext.rx_java = '2.2.10'
+ ext.rx_kotlin = '2.2.0'
+ ext.rx_image_picker_version = '2.5.2'
+
+ ext.appcompact_version = '1.1.0'
+ ext.constraintlayout_version = '1.1.3'
+ ext.junit_version = '4.13'
+ ext.test_runner_version = '1.2.0'
+ ext.espresso_core_version = '3.2.0'
+ ext.core_ktx_version = '1.3.0'
+
+ ext.compile_sdk_version = 30
+ ext.target_sdk_version = 30
+ ext.min_sdk_version = 19
+ // -------------end----------------
+
+ ext.kotlin_version = "1.3.72"
ext.quick_android_version = 'v0.1.9'
ext.anko_version = '0.10.8'
ext.smart_refresh_layout_version = '1.1.0'
@@ -11,10 +45,11 @@ buildscript {
maven { url 'https://jitpack.io' }
google()
// jcenter()
-// mavenCentral()
+ mavenCentral()
}
dependencies {
classpath "com.android.tools.build:gradle:4.2.1"
+ classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
@@ -27,7 +62,7 @@ allprojects {
maven { url 'http://maven.aliyun.com/nexus/content/groups/public'}
maven { url 'https://jitpack.io' }
google()
-// mavenCentral()
+ mavenCentral()
// jcenter() // Warning: this repository is going to shut down soon
}
}
diff --git a/gradle.properties b/gradle.properties
index 6826e61..6ac4212 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -14,4 +14,7 @@ org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
# AndroidX package structure to make it clearer which packages are bundled with the
# Android operating system, and which are packaged with your app"s APK
# https://developer.android.com/topic/libraries/support-library/androidx-rn
-android.useAndroidX=true
\ No newline at end of file
+android.useAndroidX=true
+
+# Automatically convert third-party libraries to use AndroidX
+android.enableJetifier=true
\ No newline at end of file
diff --git a/quick_android_base/.gitignore b/quick_android_base/.gitignore
new file mode 100644
index 0000000..796b96d
--- /dev/null
+++ b/quick_android_base/.gitignore
@@ -0,0 +1 @@
+/build
diff --git a/quick_android_base/README.md b/quick_android_base/README.md
new file mode 100644
index 0000000..45c9593
--- /dev/null
+++ b/quick_android_base/README.md
@@ -0,0 +1,161 @@
+> ## quick_android_base 模块
+>
+> 本模块提供了一些常用的工具库,一些常用的Kotlin扩展,以及一些基本功能的封装
+
+- ### 初始化
+
+ ```kotlin
+ // 在Application中初始化
+ QuickAndroid.init()
+ // 下面这一行输入你应用的英文名,便于为你的应用创建对应名字的应用文件夹等等
+ .appName("your_app_name")
+ // 执行下面一行可以开启应用Activity的侧滑返回支持
+ .enableParallaxBack(context)
+ ```
+
+
+
+- ### 侧滑返回支持说明
+
+ 1. 所有需要支持侧滑返回的`Activity`都要继承 [BaseQuickAndroidActivity](https://github.com/SunnyQjm/quickandroid/blob/master/quick_android_base/src/main/java/cn/qjm253/quick_android_base/base/activity/BaseQuickAndroidActivity.kt>) ;
+
+ 2. 如果继承自[BaseQuickAndroidActivity](https://github.com/SunnyQjm/quickandroid/blob/master/quick_android_base/src/main/java/cn/qjm253/quick_android_base/base/activity/BaseQuickAndroidActivity.kt>) 的`Activity`需要按业务需求开启和关闭业务需求,可以使用下面两个函数(下面两个函数定义在[BaseQuickAndroidActivity](https://github.com/SunnyQjm/quickandroid/blob/master/quick_android_base/src/main/java/cn/qjm253/quick_android_base/base/activity/BaseQuickAndroidActivity.kt>) 当中,所有集成自该类的Activity均可调用):
+
+ ```kotlin
+ // 禁止侧滑返回
+ disableParallaxBack()
+
+ // 开启侧滑返回
+ enableParallaxBack()
+ ```
+
+ 3. 本侧滑返回使用的是[ParallaxBackLayout](https://github.com/anzewei/ParallaxBackLayout),如果需要自定义侧滑模式,以及触发侧滑返回的位置等等,可以参考该库提供的配置。
+
+- ### Glide支持
+
+ 1. `quick_android_base`库中已经包含了Glide依赖,引入本依赖库之后不需要再另外引入
+
+ 2. 当前`quick_android_base`中Glide的版本为`4.9.0`
+
+ 3. 已经定义了[MyAppGlideModule](https://github.com/SunnyQjm/quickandroid/blob/master/quick_android_base/src/main/java/cn/qjm253/quick_android_base/MyAppGlideModule.kt),不需要再定义,可以通过下面的方式使用
+
+ ```kotlin
+ GlideApp.with(context)
+ .load(url)
+ .into(imageView)
+ ```
+
+- ### 动态权限请求支持
+
+ 本功能基于google开源的[easypermissions](https://github.com/googlesamples/easypermissions)进行,在所有继承自[BaseQuickAndroidActivity](https://github.com/SunnyQjm/quickandroid/blob/master/quick_android_base/src/main/java/cn/qjm253/quick_android_base/base/activity/BaseQuickAndroidActivity.kt>) 的Activity或者继承自[BaseQuickAndroidFragment](https://github.com/SunnyQjm/quickandroid/blob/master/quick_android_base/src/main/java/cn/qjm253/quick_android_base/base/fragment/BaseQuickAndroidFragment.kt)的Fragment均可使用
+
+ - 动态请求权限
+
+ ```kotlin
+ easyRequestPermissions(
+ arrayof(Manifest.permissions.CAMERA),
+ R.string.require_camera_permission_tip,
+ 0
+ )
+ ```
+
+ - 复写如下函数
+
+ ```kotlin
+ /**
+ * 授权成功
+ */
+ override fun onPermissionsGranted(requestCode: Int, perms: MutableList) {
+ when(requestCode) {
+ 0 -> { // 请求摄像头权限
+
+ }
+ }
+ }
+
+ /**
+ * 授权失败
+ */
+ override fun onPermissionsDenied(requestCode: Int, perms: MutableList) {
+
+ }
+ ```
+
+- ### `quick_android_base`支持的其它特性
+
+ - 一些有用的工具类 [utils](https://github.com/SunnyQjm/quickandroid/tree/master/quick_android_base/src/main/java/cn/qjm253/quick_android_base/util)
+
+ - 一些有用的扩展 [extensions](https://github.com/SunnyQjm/quickandroid/tree/master/quick_android_base/src/main/java/cn/qjm253/quick_android_base/extensions)
+
+ - 便捷的页面跳转扩展(下面所有的扩展函数均可在所有Activity和Fragment中使用,只可在Kotlin项目中使用)
+
+ ```kotlin
+ // 简单使用,跳转到一个Activity
+ jumpTo(MainActivity::class.java)
+
+ // 跳转到一个Activity,并处理返回结果(requestCode = 0)
+ jumpForResult(MainACtivity::class.java, 0)
+
+ // 跳转到一个Activity并传递参数
+ jumpTo(MainActivity::class.java,
+ IntentParam()
+ .add("param1", 1)
+ .add("param2", "value2")
+ .add("param3", arrayof("1", "2", "3"))
+ )
+
+ // 跳转到一个Activity,传递参数并处理返回结果
+ jumpTo(MainActivity::class.java,
+ 0,
+ IntentParam()
+ .add("param1", 1)
+ .add("param2", "value2")
+ .add("param3", arrayof("1", "2", "3"))
+ )
+ ```
+
+
+
+- ### `quick_android_base`引入的所有支持库
+
+ ```groovy
+ // gson
+ api "com.google.code.gson:gson:${gson_version}"
+
+ // Glide
+ api "com.github.bumptech.glide:glide:${glide_version}"
+ kapt "com.github.bumptech.glide:compiler:${glide_version}"
+
+ // Easypermission
+ api "pub.devrel:easypermissions:${easypermission_version}"
+
+ // RxJava
+ // RxAndroid
+ api "io.reactivex.rxjava2:rxandroid:${rx_android_version}"
+ api "io.reactivex.rxjava2:rxjava:${rx_java}"
+ // RxKotlin
+ api "io.reactivex.rxjava2:rxkotlin:${rx_kotlin}"
+
+ // BRVAHquick_android_base
+ api "com.github.CymChad:BaseRecyclerViewAdapterHelper:${brvah_version}"
+
+ // Retrofit
+ api "com.squareup.retrofit2:retrofit:${retrofit_version}"
+ api "com.squareup.retrofit2:retrofit:${retrofit_version}"
+ api "com.squareup.retrofit2:adapter-rxjava2:${retrofit_version}"
+ api "com.squareup.retrofit2:converter-gson:${retrofit_version}"
+
+ // Okhttp
+ api "com.squareup.okhttp3:okhttp:${okhttp_version}"
+
+ // Logger
+ api "com.orhanobut:logger:${logger_vesion}"
+
+ // ParallaxBackLayout
+ api "com.github.SunnyQjm:ParallaxBackLayout:${parallax_layout_version}"
+
+ // ImmersionBar
+ api "com.gyf.immersionbar:immersionbar:${immersion_bar_version}"
+ ```
+
+
\ No newline at end of file
diff --git a/quick_android_base/build.gradle b/quick_android_base/build.gradle
new file mode 100644
index 0000000..82ca341
--- /dev/null
+++ b/quick_android_base/build.gradle
@@ -0,0 +1,84 @@
+apply plugin: 'com.android.library'
+
+apply plugin: 'kotlin-android'
+
+apply plugin: 'kotlin-android-extensions'
+
+apply plugin: 'kotlin-kapt'
+
+android {
+ compileSdkVersion compile_sdk_version
+
+ defaultConfig {
+ minSdkVersion min_sdk_version
+ targetSdkVersion target_sdk_version
+ versionCode 1
+ versionName "1.0"
+
+ testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
+
+ }
+
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+ }
+ }
+
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_1_8
+ targetCompatibility JavaVersion.VERSION_1_8
+ }
+
+}
+
+dependencies {
+ implementation fileTree(dir: 'libs', include: ['*.jar'])
+ implementation "androidx.appcompat:appcompat:${appcompact_version}"
+ testImplementation "junit:junit:${junit_version}"
+ implementation "androidx.constraintlayout:constraintlayout:${constraintlayout_version}"
+ androidTestImplementation "androidx.test:runner:${test_runner_version}"
+ androidTestImplementation "androidx.test.espresso:espresso-core:${espresso_core_version}"
+
+ // gson
+ api "com.google.code.gson:gson:${gson_version}"
+
+ // Glide
+ api "com.github.bumptech.glide:glide:${glide_version}"
+ kapt "com.github.bumptech.glide:compiler:${glide_version}"
+
+
+ // Easypermission
+ api "pub.devrel:easypermissions:${easypermission_version}"
+
+ // RxJava
+ // RxAndroid
+ api "io.reactivex.rxjava2:rxandroid:${rx_android_version}"
+ api "io.reactivex.rxjava2:rxjava:${rx_java}"
+ // RxKotlin
+ api "io.reactivex.rxjava2:rxkotlin:${rx_kotlin}"
+
+ // BRVAH
+ api "com.github.CymChad:BaseRecyclerViewAdapterHelper:${brvah_version}"
+
+ // Retrofit
+ api "com.squareup.retrofit2:retrofit:${retrofit_version}"
+ api "com.squareup.retrofit2:retrofit:${retrofit_version}"
+ api "com.squareup.retrofit2:adapter-rxjava2:${retrofit_version}"
+ api "com.squareup.retrofit2:converter-gson:${retrofit_version}"
+
+ // Okhttp
+ api "com.squareup.okhttp3:okhttp:${okhttp_version}"
+
+ // Logger
+ api "com.orhanobut:logger:${logger_vesion}"
+
+ // ParallaxBackLayout
+ api "com.github.SunnyQjm:ParallaxBackLayout:${parallax_layout_version}"
+
+ // ImmersionBar
+ api "com.gyf.immersionbar:immersionbar:${immersion_bar_version}"
+
+
+}
diff --git a/quick_android_base/proguard-rules.pro b/quick_android_base/proguard-rules.pro
new file mode 100644
index 0000000..a9f634f
--- /dev/null
+++ b/quick_android_base/proguard-rules.pro
@@ -0,0 +1,177 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
+
+# AndroidX
+-keep class com.google.android.material.** {*;}
+-keep class androidx.** {*;}
+-keep public class * extends androidx.**
+-keep interface androidx.** {*;}
+-dontwarn com.google.android.material.**
+-dontnote com.google.android.material.**
+-dontwarn androidx.**
+
+# Glide
+-keep public class * implements com.bumptech.glide.module.GlideModule
+-keep public class * extends com.bumptech.glide.module.AppGlideModule
+-keep public enum com.bumptech.glide.load.ImageHeaderParser$** {
+ **[] $VALUES;
+ public *;
+}
+
+# for DexGuard only
+-keepresourcexmlelements manifest/application/meta-data@value=GlideModule
+
+
+
+
+##---------------Begin: proguard configuration for Gson ----------
+# Gson uses generic type information stored in a class file when working with fields. Proguard
+# removes such information by default, so configure it to keep all of it.
+-keepattributes Signature
+
+# For using GSON @Expose annotation
+-keepattributes *Annotation*
+
+# Gson specific classes
+-dontwarn sun.misc.**
+#-keep class com.google.gson.stream.** { *; }
+
+# Application classes that will be serialized/deserialized over Gson
+#-keep class com.google.gson.examples.android.model.** { ; }
+
+# Prevent proguard from stripping interface information from TypeAdapterFactory,
+# JsonSerializer, JsonDeserializer instances (so they can be used in @JsonAdapter)
+-keep class * implements com.google.gson.TypeAdapterFactory
+-keep class * implements com.google.gson.JsonSerializer
+-keep class * implements com.google.gson.JsonDeserializer
+
+# Prevent R8 from leaving Data object members always null
+-keepclassmembers,allowobfuscation class * {
+ @com.google.gson.annotations.SerializedName ;
+}
+
+##---------------End: proguard configuration for Gson ----------
+
+
+##---------------Begin: BRVAH---------------##
+-keep class com.chad.library.adapter.** {
+*;
+}
+-keep public class * extends com.chad.library.adapter.base.BaseQuickAdapter
+-keep public class * extends com.chad.library.adapter.base.BaseViewHolder
+-keepclassmembers class **$** extends com.chad.library.adapter.base.BaseViewHolder {
+ (...);
+}
+##---------------End: BRVAH---------------##
+
+
+
+##---------------Begin: Okhttp---------------##
+-dontwarn com.squareup.okhttp3.**
+-keep class com.squareup.okhttp3.** { *;}
+-dontwarn okio.**
+##---------------End: Okhttp---------------##
+
+
+##---------------Begin: RxJava RxAndroid---------------##
+-dontwarn sun.misc.**
+-keepclassmembers class rx.internal.util.unsafe.*ArrayQueue*Field* {
+ long producerIndex;
+ long consumerIndex;
+}
+-keepclassmembers class rx.internal.util.unsafe.BaseLinkedQueueProducerNodeRef {
+ rx.internal.util.atomic.LinkedQueueNode producerNode;
+}
+-keepclassmembers class rx.internal.util.unsafe.BaseLinkedQueueConsumerNodeRef {
+ rx.internal.util.atomic.LinkedQueueNode consumerNode;
+}
+-dontnote rx.internal.util.PlatformDependent
+##---------------End: RxJava RxAndroid---------------##
+
+
+##---------------Begin: Junit---------------##
+-keep class org.junit.** { *; }
+-dontwarn org.junit.**
+
+-keep class junit.** { *; }
+-dontwarn junit.**
+##---------------End: Junit---------------##
+
+
+
+##---------------Begin: Retrofit---------------##
+
+# Retrofit does reflection on generic parameters. InnerClasses is required to use Signature and
+# EnclosingMethod is required to use InnerClasses.
+-keepattributes Signature, InnerClasses, EnclosingMethod
+
+# Retrofit does reflection on method and parameter annotations.
+-keepattributes RuntimeVisibleAnnotations, RuntimeVisibleParameterAnnotations
+
+# Retain service method parameters when optimizing.
+-keepclassmembers,allowshrinking,allowobfuscation interface * {
+ @retrofit2.http.* ;
+}
+
+# Ignore annotation used for build tooling.
+-dontwarn org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement
+
+# Ignore JSR 305 annotations for embedding nullability information.
+-dontwarn javax.annotation.**
+
+# Guarded by a NoClassDefFoundError try/catch and only used when on the classpath.
+-dontwarn kotlin.Unit
+
+# Top-level functions that can only be used by Kotlin.
+-dontwarn retrofit2.KotlinExtensions
+
+# With R8 full mode, it sees no subtypes of Retrofit interfaces since they are created with a Proxy
+# and replaces all potential values with null. Explicitly keeping the interfaces prevents this.
+-if interface * { @retrofit2.http.* ; }
+-keep,allowobfuscation interface <1>
+
+##---------------End: Retrofit---------------##
+
+
+##---------------Begin: Okhttp---------------##
+
+# JSR 305 annotations are for embedding nullability information.
+-dontwarn javax.annotation.**
+
+# A resource is loaded with a relative path so the package of this class must be preserved.
+-keepnames class okhttp3.internal.publicsuffix.PublicSuffixDatabase
+
+# Animal Sniffer compileOnly dependency to ensure APIs are compatible with older versions of Java.
+-dontwarn org.codehaus.mojo.animal_sniffer.*
+
+# OkHttp platform used only on JVM and when Conscrypt dependency is available.
+-dontwarn okhttp3.internal.platform.ConscryptPlatform
+
+##---------------Begin: End---------------##
+
+##---------------Begin: ParallaxBackLayout---------------##
+-keep public enum com.github.anzewei.parallaxbacklayout.ParallaxBack$** {
+ **[] $VALUES;
+ public *;
+}
+##---------------End: ParallaxBackLayout---------------##
+
diff --git a/quick_android_base/src/androidTest/java/cn/qjm253/quick_android_base/ExampleInstrumentedTest.java b/quick_android_base/src/androidTest/java/cn/qjm253/quick_android_base/ExampleInstrumentedTest.java
new file mode 100644
index 0000000..ac98954
--- /dev/null
+++ b/quick_android_base/src/androidTest/java/cn/qjm253/quick_android_base/ExampleInstrumentedTest.java
@@ -0,0 +1,26 @@
+package cn.qjm253.quick_android_base;
+
+import android.content.Context;
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import static org.junit.Assert.*;
+
+/**
+ * Instrumented test, which will execute on an Android device.
+ *
+ * @see Testing documentation
+ */
+@RunWith(AndroidJUnit4.class)
+public class ExampleInstrumentedTest {
+ @Test
+ public void useAppContext() {
+ // Context of the app under test.
+ Context appContext = InstrumentationRegistry.getTargetContext();
+
+ assertEquals("cn.qjm253.quick_android_base.test", appContext.getPackageName());
+ }
+}
diff --git a/quick_android_base/src/main/AndroidManifest.xml b/quick_android_base/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..66c424a
--- /dev/null
+++ b/quick_android_base/src/main/AndroidManifest.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/quick_android_base/src/main/java/NetworkUtil.kt b/quick_android_base/src/main/java/NetworkUtil.kt
new file mode 100644
index 0000000..202977a
--- /dev/null
+++ b/quick_android_base/src/main/java/NetworkUtil.kt
@@ -0,0 +1,69 @@
+//package cn.qjm253.quick_android_base.util
+//
+//import android.content.Context
+//import android.net.ConnectivityManager
+//import android.net.NetworkInfo
+//
+//
+///**
+// * 网络工具类
+// * Created by sunny on 18-2-23.
+// */
+//
+//class NetworkUtil(context: Context){
+//
+// val manager = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
+//
+// /**
+// * 检测网络是否连接
+// * @return
+// */
+// private fun checkNetworkState(): Boolean {
+// var flag = false
+// //得到网络连接信息
+// //去进行判断网络是否连接
+// if (manager.activeNetworkInfo != null) {
+// flag = manager.activeNetworkInfo.isAvailable
+// }
+// return flag
+// }
+//
+// /**
+// * 判断是否连了wifi
+// */
+// fun isWifiConnect(): Boolean{
+// val wifiState = manager.getNetworkInfo(ConnectivityManager.TYPE_WIFI).state
+// return wifiState == NetworkInfo.State.CONNECTED
+// }
+//
+// /**
+// * 获取当前网络的类型
+// */
+// fun getConnectedType(context: Context?): Int {
+// if (context != null) {
+// val mConnectivityManager = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
+// val mNetworkInfo = mConnectivityManager.activeNetworkInfo
+// if (mNetworkInfo != null && mNetworkInfo.isAvailable) {
+// return mNetworkInfo.type
+// }
+// }
+// return -1
+// }
+//
+//
+// /**
+// * 是否是移动数据连接
+// */
+// fun isMobileConnected(context: Context?): Boolean {
+// if (context != null) {
+// val mConnectivityManager = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
+// val mMobileNetworkInfo = mConnectivityManager.getNetworkInfo(ConnectivityManager.TYPE_MOBILE)
+// if (mMobileNetworkInfo != null) {
+// return mMobileNetworkInfo.isAvailable
+// }
+// }
+// return false
+// }
+//
+//
+//}
\ No newline at end of file
diff --git a/quick_android_base/src/main/java/cn/qjm253/quick_android_base/MyAppGlideModule.kt b/quick_android_base/src/main/java/cn/qjm253/quick_android_base/MyAppGlideModule.kt
new file mode 100644
index 0000000..983b0f5
--- /dev/null
+++ b/quick_android_base/src/main/java/cn/qjm253/quick_android_base/MyAppGlideModule.kt
@@ -0,0 +1,7 @@
+package cn.qjm253.quick_android_base
+
+import com.bumptech.glide.annotation.GlideModule
+import com.bumptech.glide.module.AppGlideModule
+
+@GlideModule
+class MyAppGlideModule : AppGlideModule()
\ No newline at end of file
diff --git a/quick_android_base/src/main/java/cn/qjm253/quick_android_base/QuickAndroid.kt b/quick_android_base/src/main/java/cn/qjm253/quick_android_base/QuickAndroid.kt
new file mode 100644
index 0000000..860c45e
--- /dev/null
+++ b/quick_android_base/src/main/java/cn/qjm253/quick_android_base/QuickAndroid.kt
@@ -0,0 +1,41 @@
+package cn.qjm253.quick_android_base
+
+import android.app.Application
+import android.content.Context
+import com.github.anzewei.parallaxbacklayout.ParallaxHelper
+import com.orhanobut.logger.AndroidLogAdapter
+import com.orhanobut.logger.Logger
+
+/**
+ * @author SunnyQjm
+ * @date 19-7-7 上午8:43
+ */
+object QuickAndroid {
+ var APP_NAME = "QuickAndroid"
+ var enableParallaxBack = false
+
+ fun appName(appName: String = "quick_android"): QuickAndroid {
+ APP_NAME = appName
+ return this
+ }
+
+ fun init(): QuickAndroid {
+ Logger.addLogAdapter(AndroidLogAdapter())
+ return this
+ }
+
+ /**
+ * 开启侧滑
+ * 如需修改侧滑触发的模式和位置,参看BaseQuickAndroidActivity
+ * @see cn.qjm253.quick_android_mvp.base.activity.BaseQuickAndroidActivity
+ */
+ fun enableParallaxBack(context: Application): QuickAndroid {
+ /**
+ * 初始化侧滑操作
+ * 需要在Application当中初始化
+ */
+ context.registerActivityLifecycleCallbacks(ParallaxHelper.getInstance())
+ enableParallaxBack = true
+ return this
+ }
+}
\ No newline at end of file
diff --git a/quick_android_base/src/main/java/cn/qjm253/quick_android_base/base/activity/BaseQuickAndroidActivity.kt b/quick_android_base/src/main/java/cn/qjm253/quick_android_base/base/activity/BaseQuickAndroidActivity.kt
new file mode 100644
index 0000000..5dfa23e
--- /dev/null
+++ b/quick_android_base/src/main/java/cn/qjm253/quick_android_base/base/activity/BaseQuickAndroidActivity.kt
@@ -0,0 +1,110 @@
+package cn.qjm253.quick_android_base.base.activity
+
+import android.net.Uri
+import android.os.Bundle
+import androidx.annotation.StringRes
+import androidx.appcompat.app.AppCompatActivity
+import cn.qjm253.quick_android_base.QuickAndroid
+import cn.qjm253.quick_android_base.base.fragment.BaseQuickAndroidFragment
+import com.github.anzewei.parallaxbacklayout.ParallaxBack
+import com.github.anzewei.parallaxbacklayout.ParallaxHelper
+import com.github.anzewei.parallaxbacklayout.widget.ParallaxBackLayout
+import pub.devrel.easypermissions.EasyPermissions
+
+/**
+ * @author SunnyQjm
+ * @date 19-7-7 上午8:43
+ */
+@ParallaxBack
+abstract class BaseQuickAndroidActivity : AppCompatActivity(),
+ EasyPermissions.PermissionCallbacks, BaseQuickAndroidFragment.OnFragmentInteractionListener {
+
+ companion object {
+ const val EP_CAMERA = 1
+ }
+
+ /**
+ * 是否需要支持侧滑返回
+ */
+ open fun isNeedScrollBack() = true
+
+
+ /**
+ * 侧滑是否响应全屏
+ * true => 全屏滑动都会触发侧滑
+ * false => 边缘滑动才会触发侧滑
+ */
+ open fun isParallaxBackLayoutFullScreen() = false
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+
+ if (QuickAndroid.enableParallaxBack) { // 手动开启侧滑才执行侧滑相关代码
+ ParallaxHelper.getParallaxBackLayout(this, true)?.let { layout ->
+ // 设置侧滑的响应模式
+ if (isParallaxBackLayoutFullScreen()) {
+ layout.setEdgeMode(ParallaxBackLayout.EDGE_MODE_FULL)
+ } else {
+ layout.setEdgeMode(ParallaxBackLayout.EDGE_MODE_DEFAULT)
+ }
+
+ // 设置是否允许触发侧滑返回
+ if (isNeedScrollBack()) {
+ layout.setEnableGesture(true)
+ } else {
+ layout.setEnableGesture(false)
+ }
+ }
+ }
+ }
+
+ /**
+ * 禁止侧滑返回
+ */
+ fun disableParallaxBack() {
+ if(QuickAndroid.enableParallaxBack) {
+ ParallaxHelper.getParallaxBackLayout(this)
+ ?.setEnableGesture(false)
+ }
+ }
+
+ /**
+ * 开启侧滑返回
+ */
+ fun enableParallaxBack() {
+ if(QuickAndroid.enableParallaxBack){
+ ParallaxHelper.getParallaxBackLayout(this)
+ ?.setEnableGesture(true)
+ }
+ }
+
+ override fun onRequestPermissionsResult(requestCode: Int, permissions: Array, grantResults: IntArray) {
+ super.onRequestPermissionsResult(requestCode, permissions, grantResults)
+ EasyPermissions.onRequestPermissionsResult(requestCode, permissions, grantResults, this)
+ }
+
+ /**
+ * 授权成功
+ */
+ override fun onPermissionsGranted(requestCode: Int, perms: MutableList) {
+ }
+
+ /**
+ * 授权失败
+ */
+ override fun onPermissionsDenied(requestCode: Int, perms: MutableList) {
+
+ }
+
+
+ open fun easyRequestPermissions(permissions: Array, @StringRes tip: Int, requestCode: Int) {
+ if (EasyPermissions.hasPermissions(this, *permissions)) {
+ onPermissionsGranted(requestCode, permissions.toMutableList())
+ } else {
+ EasyPermissions.requestPermissions(this, getString(tip), requestCode, *permissions)
+ }
+ }
+
+ override fun onFragmentInteraction(uri: Uri?) {
+ }
+}
\ No newline at end of file
diff --git a/quick_android_base/src/main/java/cn/qjm253/quick_android_base/base/fragment/BaseQuickAndroidFragment.kt b/quick_android_base/src/main/java/cn/qjm253/quick_android_base/base/fragment/BaseQuickAndroidFragment.kt
new file mode 100644
index 0000000..58534c7
--- /dev/null
+++ b/quick_android_base/src/main/java/cn/qjm253/quick_android_base/base/fragment/BaseQuickAndroidFragment.kt
@@ -0,0 +1,191 @@
+package cn.qjm253.quick_android_base.base.fragment
+
+import android.Manifest
+import android.content.Context
+import android.net.Uri
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.annotation.StringRes
+import androidx.fragment.app.Fragment
+import cn.qjm253.quick_android_base.R
+import cn.qjm253.quick_android_base.base.activity.BaseQuickAndroidActivity
+import cn.qjm253.quick_android_base.params.IntentParam
+import pub.devrel.easypermissions.EasyPermissions
+
+/**
+ * @author SunnyQjm
+ * @date 19-7-7 上午8:43
+ */
+
+/**
+ * 支持在第一次创建视图时初始化,且不会重复添加导致重叠
+ */
+abstract class BaseQuickAndroidFragment : Fragment(), EasyPermissions.PermissionCallbacks {
+ companion object {
+ private const val STATE_SAVE_IS_HIDDEN = "STATE_SAVE_IS_HIDDEN"
+
+ fun newInstance_(clazz: Class, intentParam: IntentParam?): T {
+ val args = Bundle()
+ intentParam?.applyBundle(args)
+ val fragment = clazz.newInstance()
+ fragment.arguments = args
+ return fragment
+ }
+ }
+
+ protected var mView: View? = null
+ protected var mListener: OnFragmentInteractionListener? = null
+
+ // 当前视图是否构建完成
+ private var prepare: Boolean = false
+ // 是否是第一次加载数据
+ private var first: Boolean = true
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+
+ //当页面重启时,Fragment会被保存回复,而此时再加载Fragment便会导致重叠
+ if (savedInstanceState != null) {
+ val isHidden = savedInstanceState.getBoolean(STATE_SAVE_IS_HIDDEN)
+ val ft = fragmentManager?.beginTransaction()
+ if (isHidden)
+ ft?.hide(this)
+ else
+ ft?.show(this)
+ ft?.commit()
+ }
+
+ }
+
+ override fun onSaveInstanceState(outState: Bundle) {
+ super.onSaveInstanceState(outState)
+ outState.putBoolean(STATE_SAVE_IS_HIDDEN, isHidden)
+ }
+
+ override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
+ if(mView == null) {
+ mView = inflater.inflate(getRes(), container, false)
+ }
+
+ // 如果之前被添加过,则先移除
+ if(mView?.parent != null) {
+ (mView?.parent as ViewGroup).removeView(mView)
+ }
+ return mView
+ }
+
+
+
+ /**
+ * 当视图构建完成时会调用这个回调
+ */
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+ if(!prepare) {
+ initView()
+ prepare = true
+ }
+ }
+
+
+ /**
+ * 下面的函数由系统调用
+ * 在Fragment可见时加载数据
+ *
+ * @param isVisibleToUser
+ */
+ override fun setUserVisibleHint(isVisibleToUser: Boolean) {
+ super.setUserVisibleHint(isVisibleToUser)
+ if(isVisibleToUser) {
+ onVisible()
+ } else {
+ onInvisible()
+ }
+ }
+
+
+
+ protected open fun onInvisible() {}
+
+ protected open fun onVisible() {
+ //加载数据
+ if (prepare && first) {
+ first = false
+ initialLoadData()
+ }
+ }
+
+ override fun onAttach(context: Context) {
+ super.onAttach(context)
+ if (context is OnFragmentInteractionListener) {
+ mListener = context
+ } else {
+ throw RuntimeException("$context must implement OnFragmentInteractionListener")
+ }
+ }
+
+ /**
+ * Fragment页面的布局文件
+ */
+ abstract fun getRes(): Int
+
+ /**
+ * 视图初始化操作
+ */
+ abstract fun initView()
+
+ /**
+ * 页面第一次加载时加载数据
+ */
+ abstract fun initialLoadData()
+
+ /**
+ * This interface must be implemented by activities that contain this
+ * fragment to allow an interaction in this fragment to be communicated
+ * to the activity and potentially other fragments contained in that
+ * activity.
+ *
+ *
+ * See the Android Training lesson [Communicating with Other Fragments](http://developer.android.com/training/basics/fragments/communicating.html) for more information.
+ */
+ interface OnFragmentInteractionListener {
+ // TODO: Update argument type and name
+ fun onFragmentInteraction(uri: Uri?)
+ }
+
+ override fun onRequestPermissionsResult(requestCode: Int, permissions: Array, grantResults: IntArray) {
+ super.onRequestPermissionsResult(requestCode, permissions, grantResults)
+ EasyPermissions.onRequestPermissionsResult(requestCode, permissions, grantResults, this)
+ }
+
+ /**
+ * 授权成功
+ */
+ override fun onPermissionsGranted(requestCode: Int, perms: MutableList) {
+ }
+
+ /**
+ * 授权失败
+ */
+ override fun onPermissionsDenied(requestCode: Int, perms: MutableList) {
+
+ }
+
+
+ open fun requestPermissions(permissions: Array, @StringRes tip: Int, requestCode: Int) {
+ activity?.let {
+ if(EasyPermissions.hasPermissions(it, *permissions)) {
+ onPermissionsGranted(requestCode, permissions.toMutableList())
+ } else {
+ EasyPermissions.requestPermissions(this, getString(tip), requestCode, *permissions)
+ }
+ }
+ }
+
+
+ open fun requestCamera(@StringRes tip: Int = R.string.camera_permission_require) {
+ requestPermissions(arrayOf(Manifest.permission.CAMERA), tip, BaseQuickAndroidActivity.EP_CAMERA)
+ }
+}
\ No newline at end of file
diff --git a/quick_android_base/src/main/java/cn/qjm253/quick_android_base/extensions/ActivityExtensions.kt b/quick_android_base/src/main/java/cn/qjm253/quick_android_base/extensions/ActivityExtensions.kt
new file mode 100644
index 0000000..fdd3254
--- /dev/null
+++ b/quick_android_base/src/main/java/cn/qjm253/quick_android_base/extensions/ActivityExtensions.kt
@@ -0,0 +1,41 @@
+package cn.qjm253.quick_android_base.extensions
+
+import android.app.Activity
+import android.content.Context
+import android.os.Build
+import android.os.VibrationEffect
+import android.os.Vibrator
+import androidx.annotation.IdRes
+import androidx.fragment.app.Fragment
+import androidx.fragment.app.FragmentActivity
+
+/**
+ * @author SunnyQjm
+ * @date 19-7-7 上午8:52
+ */
+
+/**
+ * Activity触发震动的扩展
+ * @param duration 震动持续时间
+ * @param amplitude 震动的强度,限制在 1~255 之间
+ */
+fun Activity.vibrator(duration: Long = 200, amplitude: Int = 100) {
+ val vibrator = getSystemService(Context.VIBRATOR_SERVICE) as Vibrator
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ vibrator.vibrate(VibrationEffect.createOneShot(duration, amplitude))
+ } else {
+ vibrator.vibrate(duration)
+ }
+}
+
+
+fun FragmentActivity.addFragmentToActivity(@IdRes fragmentLayoutId: Int, fragmentGenerator: () -> T): T {
+ var fragment = this.supportFragmentManager.findFragmentById(fragmentLayoutId) as? T
+ if(fragment == null) {
+ fragment = fragmentGenerator()
+ val transaction = supportFragmentManager.beginTransaction()
+ transaction.replace(fragmentLayoutId, fragment)
+ transaction.commit()
+ }
+ return fragment
+}
\ No newline at end of file
diff --git a/quick_android_base/src/main/java/cn/qjm253/quick_android_base/extensions/AnyExtensions.kt b/quick_android_base/src/main/java/cn/qjm253/quick_android_base/extensions/AnyExtensions.kt
new file mode 100644
index 0000000..52617a0
--- /dev/null
+++ b/quick_android_base/src/main/java/cn/qjm253/quick_android_base/extensions/AnyExtensions.kt
@@ -0,0 +1,12 @@
+package cn.qjm253.quick_android_base.extensions
+
+import cn.qjm253.quick_android_base.util.GsonUtil
+
+/**
+ * @author SunnyQjm
+ * @date 19-7-7 上午8:43
+ */
+
+fun Any.toJson(): String {
+ return GsonUtil.bean2Json(this)
+}
\ No newline at end of file
diff --git a/quick_android_base/src/main/java/cn/qjm253/quick_android_base/extensions/ContextExtension.kt b/quick_android_base/src/main/java/cn/qjm253/quick_android_base/extensions/ContextExtension.kt
new file mode 100644
index 0000000..791a406
--- /dev/null
+++ b/quick_android_base/src/main/java/cn/qjm253/quick_android_base/extensions/ContextExtension.kt
@@ -0,0 +1,149 @@
+package cn.qjm253.quick_android_base.extensions
+
+import android.app.Activity
+import android.app.DatePickerDialog
+import android.content.Context
+import android.content.Intent
+import android.content.pm.PackageManager
+import androidx.annotation.StringRes
+import androidx.annotation.StyleRes
+import androidx.core.content.ContextCompat
+import android.view.View
+import android.view.inputmethod.InputMethodManager
+import android.widget.Toast
+import androidx.fragment.app.Fragment
+import cn.qjm253.quick_android_base.params.IntentParam
+import java.util.*
+
+/**
+ * @author SunnyQjm
+ * @date 19-7-7 上午8:43
+ */
+
+fun Context.toast(info: String, duration: Int = Toast.LENGTH_SHORT) {
+ Toast.makeText(this, info, duration)
+ .show()
+}
+
+fun Context.toast(@StringRes stringRes: Int, duration: Int = Toast.LENGTH_SHORT) {
+ Toast.makeText(this, stringRes, duration)
+ .show()
+}
+
+
+fun Context.jumpTo(cls: Class<*>, vararg flags: Int) {
+ val intent = Intent(this, cls)
+ flags.forEach {
+ intent.addFlags(it)
+ }
+ startActivity(intent)
+}
+
+
+fun Context.jumpTo(cls: Class<*>, intentParam: IntentParam? = null, vararg flags: Int) {
+ val intent = Intent(this, cls)
+ flags.forEach {
+ intent.addFlags(it)
+ }
+ intentParam?.applyParam(intent)
+ startActivity(intent)
+}
+
+fun Activity.jumpForResult(cls: Class<*>, requestCode: Int = 0, intentParam: IntentParam? = null) {
+ val intent = Intent(this, cls)
+ intentParam?.applyParam(intent)
+ startActivityForResult(intent, requestCode)
+}
+
+
+fun Fragment.jumpForResult(cls: Class<*>, requestCode: Int, intentParam: IntentParam? = null) {
+ val intent = Intent(activity, cls)
+ intentParam?.applyParam(intent)
+ startActivityForResult(intent, requestCode)
+}
+
+/////////////////////////////////////////////////////////
+///////// 下面是软键盘相关的扩展
+////////////////////////////////////////////////////////
+
+/**
+ * 切换状态,打开则关闭,关闭则打开
+ */
+fun Context.changeSoftKeyboard() {
+ val im = this.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
+
+ if (!im.isActive)
+ return
+ im.toggleSoftInput(0, 0)
+}
+
+/**
+ * 隐藏软键盘
+ */
+fun Context.hideSoftKeyboard(view: View) {
+ val im = this.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
+ if (!im.isActive)
+ return
+ //隐藏软键盘 //
+ im.hideSoftInputFromWindow(view.windowToken, 0)
+}
+
+fun Context.showSoftKeyboard(view: View) {
+ val im = this.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
+ if (im.isActive)
+ return
+ im.showSoftInputFromInputMethod(view.windowToken, 0)
+}
+
+
+fun Context.showSelectDateDialog(
+ @StyleRes style: Int = android.R.style.Theme_DeviceDefault_Dialog,
+ listener: (View, Int, Int, Int) -> Unit =
+ { view, year, month, dayOfMonth -> }
+) {
+ val c = Calendar.getInstance()
+ DatePickerDialog(
+ this, style, listener, c.get(Calendar.YEAR),
+ c.get(Calendar.MONTH), c.get(Calendar.DAY_OF_MONTH)
+ )
+ .show()
+}
+
+fun Context.showSelectDateDialog(
+ @StyleRes style: Int = android.R.style.Theme_DeviceDefault_Dialog,
+ listener: (Long) -> Unit =
+ { time -> }
+) {
+ val c = Calendar.getInstance()
+ DatePickerDialog(
+ this, style, { view, year, month, dayOfMonth ->
+ c.timeInMillis = 0
+ c.set(Calendar.YEAR, year)
+ c.set(Calendar.MONTH, month)
+ c.set(Calendar.DAY_OF_MONTH, dayOfMonth)
+ listener(c.timeInMillis)
+ }, c.get(Calendar.YEAR),
+ c.get(Calendar.MONTH), c.get(Calendar.DAY_OF_MONTH)
+ )
+ .show()
+}
+
+
+fun Context.checkMyPermission(permission: String, granted: () -> Unit = {}, deny: () -> Unit = {}) {
+ if (ContextCompat.checkSelfPermission(this, permission) == PackageManager.PERMISSION_GRANTED) {
+ granted()
+ } else {
+ deny()
+ }
+}
+
+
+/**
+ * 不够位数的在前面补0,保留num的长度位数字
+ * @param code
+ * @return
+ */
+private fun autoGenericCode(code: String, num: Int): String {
+ return String.format("%0" + num + "d", Integer.parseInt(code))
+}
+
diff --git a/quick_android_base/src/main/java/cn/qjm253/quick_android_base/extensions/FloatExtensions.kt b/quick_android_base/src/main/java/cn/qjm253/quick_android_base/extensions/FloatExtensions.kt
new file mode 100644
index 0000000..956c6e8
--- /dev/null
+++ b/quick_android_base/src/main/java/cn/qjm253/quick_android_base/extensions/FloatExtensions.kt
@@ -0,0 +1,20 @@
+package cn.qjm253.quick_android_base.extensions
+
+import java.lang.StringBuilder
+import java.math.RoundingMode
+import java.text.DecimalFormat
+
+/**
+ * @author SunnyQjm
+ * @date 19-7-7 上午8:43
+ */
+
+fun Double.toFixed(num: Int): String {
+ val sb = StringBuilder("0.")
+ for(i in 1..num){
+ sb.append("0")
+ }
+ val df = DecimalFormat(sb.toString())
+ df.roundingMode = RoundingMode.HALF_UP;
+ return df.format(this)
+}
\ No newline at end of file
diff --git a/quick_android_base/src/main/java/cn/qjm253/quick_android_base/extensions/FragmentManagerExtensions.kt b/quick_android_base/src/main/java/cn/qjm253/quick_android_base/extensions/FragmentManagerExtensions.kt
new file mode 100644
index 0000000..9e1ceb7
--- /dev/null
+++ b/quick_android_base/src/main/java/cn/qjm253/quick_android_base/extensions/FragmentManagerExtensions.kt
@@ -0,0 +1,44 @@
+package cn.qjm253.quick_android_base.extensions
+
+import androidx.fragment.app.Fragment
+import androidx.fragment.app.FragmentManager
+
+/**
+ * @author SunnyQjm
+ * @date 19-7-8 下午3:42
+ */
+
+fun FragmentManager.getLazySingleton(tag: String, clazz: Class): Lazy {
+ val fragmentManager = this
+ return object : Lazy {
+ private var t: T? = null
+
+ private fun _getFragment(): T {
+ if(t == null) {
+ t = getFragment(tag, clazz, fragmentManager)
+ }
+ return t!!
+ }
+
+ override val value: T
+ get() = _getFragment()
+
+ override fun isInitialized(): Boolean {
+ return t != null
+ }
+ }
+}
+
+
+private fun getFragment(tag: String, clazz: Class, fragmentManager: FragmentManager): T {
+ var fragment: T? = fragmentManager.findFragmentByTag(tag) as? T
+ if (fragment == null) {
+ fragment = clazz.newInstance()
+ fragmentManager
+ .beginTransaction()
+ .add(fragment, tag)
+ .commitNow()
+ }
+ return fragment!!
+}
+
diff --git a/quick_android_base/src/main/java/cn/qjm253/quick_android_base/extensions/ImageViewExtensions.kt b/quick_android_base/src/main/java/cn/qjm253/quick_android_base/extensions/ImageViewExtensions.kt
new file mode 100644
index 0000000..cce0168
--- /dev/null
+++ b/quick_android_base/src/main/java/cn/qjm253/quick_android_base/extensions/ImageViewExtensions.kt
@@ -0,0 +1,63 @@
+package cn.qjm253.quick_android_base.extensions
+
+import android.annotation.SuppressLint
+import android.graphics.Bitmap
+import android.media.MediaMetadataRetriever
+import androidx.annotation.DrawableRes
+import android.widget.ImageView
+import cn.qjm253.quick_android_base.GlideApp
+import com.bumptech.glide.load.resource.bitmap.VideoDecoder.FRAME_OPTION
+import com.bumptech.glide.request.RequestOptions
+import com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool
+import com.bumptech.glide.load.resource.bitmap.BitmapTransformation
+import java.security.MessageDigest
+
+/**
+ * @author SunnyQjm
+ * @date 19-7-7 上午8:43
+ */
+
+
+fun ImageView.load(url: String) {
+ GlideApp.with(this)
+ .load(url)
+ .into(this)
+}
+
+fun ImageView.loadCircle(url: String) {
+ GlideApp.with(this)
+ .load(url)
+ .circleCrop()
+ .into(this)
+}
+
+fun ImageView.loadCircle(@DrawableRes res: Int) {
+ GlideApp.with(this)
+ .load(res)
+ .circleCrop()
+ .into(this)
+}
+
+@SuppressLint("CheckResult")
+fun ImageView.loadVideoFrame(url: String, frameTimeMicros: Long) {
+ val requestOptions = RequestOptions.frameOf(frameTimeMicros)
+ requestOptions.set(FRAME_OPTION, MediaMetadataRetriever.OPTION_CLOSEST);
+ requestOptions.transform(object : BitmapTransformation() {
+ override fun transform(pool: BitmapPool, toTransform: Bitmap, outWidth: Int, outHeight: Int): Bitmap {
+ return toTransform
+ }
+
+ override fun updateDiskCacheKey(messageDigest: MessageDigest) {
+ try {
+ messageDigest.update((context.packageName + "RotateTransform").toByteArray(charset("utf-8")))
+ } catch (e: Exception) {
+ e.printStackTrace()
+ }
+ }
+
+ })
+ GlideApp.with(this)
+ .load(url)
+ .apply(requestOptions)
+ .into(this)
+}
\ No newline at end of file
diff --git a/quick_android_base/src/main/java/cn/qjm253/quick_android_base/extensions/LoggerExtensions.kt b/quick_android_base/src/main/java/cn/qjm253/quick_android_base/extensions/LoggerExtensions.kt
new file mode 100644
index 0000000..3934e91
--- /dev/null
+++ b/quick_android_base/src/main/java/cn/qjm253/quick_android_base/extensions/LoggerExtensions.kt
@@ -0,0 +1,29 @@
+package cn.qjm253.quick_android_base.extensions
+
+import com.orhanobut.logger.Logger
+
+/**
+ * @author SunnyQjm
+ * @date 19-7-7 上午8:43
+ */
+
+fun String.e(vararg args: Any) {
+ Logger.e(this, args)
+}
+
+fun Throwable.e(vararg args: Any) {
+ Logger.e(this, this.message ?: "", args)
+}
+
+
+fun String.i(vararg args: Any) {
+ Logger.i(this, args)
+}
+
+fun String.d(vararg args: Any) {
+ Logger.d(this, args)
+}
+
+fun String.json(vararg args: Any) {
+ Logger.json(this)
+}
\ No newline at end of file
diff --git a/quick_android_base/src/main/java/cn/qjm253/quick_android_base/extensions/LongExtensions.kt b/quick_android_base/src/main/java/cn/qjm253/quick_android_base/extensions/LongExtensions.kt
new file mode 100644
index 0000000..b13403c
--- /dev/null
+++ b/quick_android_base/src/main/java/cn/qjm253/quick_android_base/extensions/LongExtensions.kt
@@ -0,0 +1,48 @@
+package cn.qjm253.quick_android_base.extensions
+
+import java.util.*
+
+/**
+ * @author SunnyQjm
+ * @date 19-7-7 上午8:43
+ */
+
+
+fun Long.toYMD(): String {
+ val instance = Calendar.getInstance()
+ instance.timeInMillis = this
+ return "${instance.get(Calendar.YEAR)}" +
+ "-${instance.get(Calendar.MONTH) + 1}" +
+ "-${instance.get(Calendar.DAY_OF_MONTH)}"
+}
+
+fun Long.toYMD_HM(): String {
+ val instance = Calendar.getInstance()
+ instance.timeInMillis = this
+ return "${instance.get(Calendar.YEAR)}" +
+ "-${instance.get(Calendar.MONTH) + 1}" +
+ "-${instance.get(Calendar.DAY_OF_MONTH)}" +
+ " ${instance.get(Calendar.HOUR_OF_DAY)}" +
+ ":${instance.get(Calendar.MINUTE)}"
+}
+
+fun Long.toYMD_HMS(): String {
+ val instance = Calendar.getInstance()
+ instance.timeInMillis = this
+ return "${instance.get(Calendar.YEAR)}" +
+ "-${instance.get(Calendar.MONTH) + 1}" +
+ "-${instance.get(Calendar.DAY_OF_MONTH)}" +
+ " ${instance.get(Calendar.HOUR_OF_DAY)}" +
+ ":${instance.get(Calendar.MINUTE)}" +
+ ":${instance.get(Calendar.SECOND)}"
+}
+
+fun String.toTimeStamp(): Long {
+ val arr = this.split('-')
+ val instance = Calendar.getInstance()
+ instance.timeInMillis = 0
+ instance.set(Calendar.YEAR, arr[0].toInt())
+ instance.set(Calendar.MONTH, arr[1].toInt() - 1)
+ instance.set(Calendar.DAY_OF_MONTH, arr[2].toInt())
+ return instance.timeInMillis
+}
\ No newline at end of file
diff --git a/quick_android_base/src/main/java/cn/qjm253/quick_android_base/extensions/MathExtension.kt b/quick_android_base/src/main/java/cn/qjm253/quick_android_base/extensions/MathExtension.kt
new file mode 100644
index 0000000..5981753
--- /dev/null
+++ b/quick_android_base/src/main/java/cn/qjm253/quick_android_base/extensions/MathExtension.kt
@@ -0,0 +1,13 @@
+package cn.qjm253.quick_android_base.extensions
+
+/**
+ * Created by sunny on 17-12-29.
+ */
+
+fun easySin(angle: Float, radius:Int = 1): Double {
+ return Math.sin(Math.PI * angle / 180) * radius
+}
+
+fun easyCos(angle: Float, radius: Int = 1): Double {
+ return Math.cos(Math.PI * angle / 180) * radius
+}
\ No newline at end of file
diff --git a/quick_android_base/src/main/java/cn/qjm253/quick_android_base/extensions/MotionEventExtension.kt b/quick_android_base/src/main/java/cn/qjm253/quick_android_base/extensions/MotionEventExtension.kt
new file mode 100644
index 0000000..203e8fe
--- /dev/null
+++ b/quick_android_base/src/main/java/cn/qjm253/quick_android_base/extensions/MotionEventExtension.kt
@@ -0,0 +1,20 @@
+package cn.qjm253.quick_android_base.extensions
+
+import android.view.MotionEvent
+import android.view.View
+
+/**
+ *
+ * Created by sunny on 18-1-2.
+ */
+
+/**
+ * 该函数用来判断一个触摸事件是否发生在某个具体View的范围内
+ */
+fun MotionEvent.isInView(view: View): Boolean {
+ val location = IntArray(2)
+ view.getLocationOnScreen(location)
+ val x = location[0]
+ val y = location[1]
+ return !(this.x < x || this.x > x + view.width || this.y < y || this.y > y + view.height)
+}
diff --git a/quick_android_base/src/main/java/cn/qjm253/quick_android_base/extensions/MyDelegates.kt b/quick_android_base/src/main/java/cn/qjm253/quick_android_base/extensions/MyDelegates.kt
new file mode 100644
index 0000000..2bd83f6
--- /dev/null
+++ b/quick_android_base/src/main/java/cn/qjm253/quick_android_base/extensions/MyDelegates.kt
@@ -0,0 +1,39 @@
+package cn.qjm253.quick_android_base.extensions
+
+import kotlin.properties.ReadWriteProperty
+import kotlin.reflect.KProperty
+
+/**
+ * Created by sunny on 17-12-28.
+ */
+
+object MyDelegates{
+ fun notNullAndOnlyInitFirstTime(): ReadWriteProperty = NotNullAndOnlyInitFirstTime()
+}
+
+/**
+ * 本代理类代理的属性,不可为null,可延迟初始化,但是只会被初始化一次,第一次初始化之后的set操作都将被忽略
+ */
+private class NotNullAndOnlyInitFirstTime : ReadWriteProperty {
+ private var value: T? = null
+
+ /**
+ * get代理
+ * @param thisRef 被代理的对象
+ * @param property 保存了被代理对象的名字等
+ */
+ override fun getValue(thisRef: Any?, property: KProperty<*>): T =
+ value ?: throw IllegalStateException("${property.name} is not initialized!")
+
+ /**
+ * set代理
+ * @param thisRef 被代理的对象
+ * @param property 保存了被代理对象的名字等
+ * @param value 元数据
+ */
+ override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
+ if(this.value == null)
+ this.value = value
+ }
+
+}
\ No newline at end of file
diff --git a/quick_android_base/src/main/java/cn/qjm253/quick_android_base/extensions/TextViewExtensions.kt b/quick_android_base/src/main/java/cn/qjm253/quick_android_base/extensions/TextViewExtensions.kt
new file mode 100644
index 0000000..82f29a8
--- /dev/null
+++ b/quick_android_base/src/main/java/cn/qjm253/quick_android_base/extensions/TextViewExtensions.kt
@@ -0,0 +1,37 @@
+package cn.qjm253.quick_android_base.extensions
+
+import android.annotation.SuppressLint
+import android.os.Build
+import android.text.Html
+import android.widget.TextView
+import androidx.annotation.ColorInt
+import androidx.annotation.ColorRes
+import androidx.annotation.StringRes
+import cn.qjm253.quick_android_base.util.SpannableUtil
+
+@SuppressLint("ResourceType")
+fun TextView.setStyleText(text: String = this.text.toString(), @StringRes textRes: Int = -1,
+ isBold: Boolean = false, isItalic: Boolean = false, @ColorInt color: Int = -1,
+ @ColorRes colorRes: Int = -1) {
+ val t = if (textRes > 0)
+ context.getString(textRes)
+ else
+ text
+ var result: CharSequence = t
+ if (isBold)
+ result = SpannableUtil.bold(result)
+ if (isItalic)
+ result = SpannableUtil.italic(result)
+ if (color > 0 || colorRes > 0)
+ result = SpannableUtil.color(if (colorRes > 0) resources.getColor(colorRes) else color, result)
+ this.text = result
+}
+
+
+fun TextView.showHtml(html: String) {
+ this.text = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
+ Html.fromHtml(html, Html.FROM_HTML_MODE_COMPACT)
+ } else {
+ Html.fromHtml(html)
+ }
+}
\ No newline at end of file
diff --git a/quick_android_base/src/main/java/cn/qjm253/quick_android_base/extensions/ViewExtension.kt b/quick_android_base/src/main/java/cn/qjm253/quick_android_base/extensions/ViewExtension.kt
new file mode 100644
index 0000000..ea04d95
--- /dev/null
+++ b/quick_android_base/src/main/java/cn/qjm253/quick_android_base/extensions/ViewExtension.kt
@@ -0,0 +1,123 @@
+package cn.qjm253.quick_android_base.extensions
+
+import android.animation.Animator
+import android.animation.AnimatorSet
+import android.animation.ObjectAnimator
+import android.animation.ValueAnimator
+import android.view.View
+
+/**
+ * Created by sunny on 17-12-29.
+ */
+
+/*************************************************************************
+ * ObjectAnimator生成
+ **************************************************************************/
+
+fun View.scaleXAnimation(vararg values: Float): ObjectAnimator {
+ return ObjectAnimator.ofFloat(this, "scaleX", *values)
+}
+
+fun View.scaleYAnimation(vararg values: Float): ObjectAnimator {
+ return ObjectAnimator.ofFloat(this, "scaleY", *values)
+}
+
+fun View.rotationAnimation(vararg values: Float): ObjectAnimator {
+ return ObjectAnimator.ofFloat(this, "rotation", *values)
+}
+
+fun View.transXAnimation(vararg values: Float): ObjectAnimator {
+ return ObjectAnimator.ofFloat(this, "translationX", *values)
+}
+
+fun View.transYAnimation(vararg values: Float): ObjectAnimator {
+ return ObjectAnimator.ofFloat(this, "translationY", *values)
+}
+
+fun View.alphaAnimation(vararg values: Float): ObjectAnimator {
+ return ObjectAnimator.ofFloat(this, "alpha", *values)
+}
+/*************************************************************************
+ * 属性动画
+ **************************************************************************/
+
+/**
+ * XY轴同比例缩放
+ */
+fun View.scaleXY(vararg values: Float, duration: Long = 500): View {
+ val animatorSet = AnimatorSet()
+ animatorSet.setDuration(duration)
+ .play(this.scaleXAnimation(*values))
+ .with(this.scaleYAnimation(*values))
+ animatorSet.start()
+ return this
+}
+
+
+
+/**
+ * 旋转
+ */
+fun View.rotate(vararg values: Float, duration: Long = 500, repeatCount: Int = 0,
+ repeatMode: Int = ValueAnimator.RESTART): ObjectAnimator {
+ val animation = this.rotationAnimation(*values)
+ with(animation
+ .setDuration(duration)){
+ this.repeatCount = repeatCount
+ this.repeatMode = repeatMode
+ start()
+ }
+ return animation
+// return this
+}
+
+/**
+ * 平移
+ */
+fun View.transXY(transX: FloatArray = floatArrayOf(0f, 0f), transY: FloatArray = floatArrayOf(0f, 0f)
+ , duration: Long = 500): View {
+ val animatorSet = AnimatorSet()
+ animatorSet.setDuration(duration)
+ .play(this.transXAnimation(*transX))
+ .with(this.transYAnimation(*transY))
+ animatorSet
+ .start()
+ return this
+}
+
+fun View.doAnim(floats: FloatArray = floatArrayOf(0f, 0f), callback: (value: Float) -> Unit = {}) {
+ val objAnim = ObjectAnimator.ofFloat(*floats)
+ objAnim.addUpdateListener {
+ val value = it.animatedValue as Float
+ callback(value)
+ }
+ objAnim.start()
+}
+
+/**
+ * 多个属性动画并行运行
+ */
+fun View.animer(vararg animations: ObjectAnimator, duration: Long, listener: Animator.AnimatorListener? = null) {
+ if (animations.isEmpty())
+ return
+ val animatorSet = AnimatorSet()
+ val builder = animatorSet.setDuration(duration)
+ .play(animations[0])
+ animations.filterIndexed { index, _ -> index != 0 }
+ .forEach { builder.with(it) }
+ if (listener != null)
+ animatorSet.addListener(listener)
+ animatorSet.start()
+
+}
+
+/**
+ * 平移旋转渐变组合动画
+ */
+fun View.transRotateAlpha(transX: FloatArray = floatArrayOf(0f, 0f), transY: FloatArray = floatArrayOf(0f, 0f),
+ rotation: FloatArray = floatArrayOf(this.rotation, this.rotation),
+ alpha: FloatArray = floatArrayOf(this.alpha, this.alpha), duration: Long = 500,
+ listener: Animator.AnimatorListener? = null) {
+ this.animer(transXAnimation(*transX), transYAnimation(*transY), rotationAnimation(*rotation),
+ alphaAnimation(*alpha), duration = duration, listener = listener)
+}
\ No newline at end of file
diff --git a/quick_android_base/src/main/java/cn/qjm253/quick_android_base/model/SystemInfo.kt b/quick_android_base/src/main/java/cn/qjm253/quick_android_base/model/SystemInfo.kt
new file mode 100644
index 0000000..bb90251
--- /dev/null
+++ b/quick_android_base/src/main/java/cn/qjm253/quick_android_base/model/SystemInfo.kt
@@ -0,0 +1,16 @@
+package cn.qjm253.quick_android_base.model
+
+/**
+ * @author SunnyQjm
+ * @date 19-7-10 上午9:25
+ */
+
+data class SystemInfo(
+ val brand: String = "",
+ val model: String = "",
+ val hardware: String = "",
+ val sdkInt: Int = 22,
+ val release: String = "",
+ val statusBarHeight: Int = 0,
+ val systemLanguage: String = ""
+)
\ No newline at end of file
diff --git a/quick_android_base/src/main/java/cn/qjm253/quick_android_base/params/IntentParam.kt b/quick_android_base/src/main/java/cn/qjm253/quick_android_base/params/IntentParam.kt
new file mode 100644
index 0000000..3329d72
--- /dev/null
+++ b/quick_android_base/src/main/java/cn/qjm253/quick_android_base/params/IntentParam.kt
@@ -0,0 +1,76 @@
+package cn.qjm253.quick_android_base.params
+
+import android.content.Intent
+import android.os.Bundle
+import android.os.Parcelable
+import java.io.Serializable
+
+/**
+ * Created by sunny on 18-1-28.
+ */
+class IntentParam(val keys: MutableList = mutableListOf(),
+ val values: MutableList = mutableListOf()) {
+
+ fun add(key: String, value: Any?): IntentParam {
+ if(value == null)
+ return this
+ keys.add(key)
+ values.add(value)
+ return this
+ }
+
+ fun applyParam(intent: Intent){
+ for(i in 0 until keys.size){
+ when(values[i]){
+ is Boolean -> intent.putExtra(keys[i], values[i] as Boolean)
+ is Byte -> intent.putExtra(keys[i], values[i] as Byte)
+ is Char -> intent.putExtra(keys[i], values[i] as Char)
+ is Short -> intent.putExtra(keys[i], values[i] as Short)
+ is Int -> intent.putExtra(keys[i], values[i] as Int)
+ is Long -> intent.putExtra(keys[i], values[i] as Long)
+ is Float -> intent.putExtra(keys[i], values[i] as Float)
+ is Double -> intent.putExtra(keys[i], values[i] as Double)
+ is String -> intent.putExtra(keys[i], values[i] as String)
+ is CharSequence -> intent.putExtra(keys[i], values[i] as CharSequence)
+ is Parcelable -> intent.putExtra(keys[i], values[i] as Parcelable)
+ is BooleanArray -> intent.putExtra(keys[i], values[i] as BooleanArray)
+ is ByteArray -> intent.putExtra(keys[i], values[i] as ByteArray)
+ is CharArray -> intent.putExtra(keys[i], values[i] as CharArray)
+ is ShortArray -> intent.putExtra(keys[i], values[i] as ShortArray)
+ is IntArray -> intent.putExtra(keys[i], values[i] as IntArray)
+ is LongArray -> intent.putExtra(keys[i], values[i] as LongArray)
+ is FloatArray -> intent.putExtra(keys[i], values[i] as FloatArray)
+ is DoubleArray -> intent.putExtra(keys[i], values[i] as DoubleArray)
+ is Serializable -> intent.putExtra(keys[i], values[i] as Serializable)
+ is Array<*> -> intent.putExtra(keys[i], values[i] as Array)
+ }
+ }
+ }
+
+ fun applyBundle(bundle: Bundle) {
+ for(i in 0 until keys.size){
+ when(values[i]){
+ is Boolean -> bundle.putBoolean(keys[i], values[i] as Boolean)
+ is Byte -> bundle.putByte(keys[i], values[i] as Byte)
+ is Char -> bundle.putChar(keys[i], values[i] as Char)
+ is Short -> bundle.putShort(keys[i], values[i] as Short)
+ is Int -> bundle.putInt(keys[i], values[i] as Int)
+ is Long -> bundle.putLong(keys[i], values[i] as Long)
+ is Float -> bundle.putFloat(keys[i], values[i] as Float)
+ is Double -> bundle.putDouble(keys[i], values[i] as Double)
+ is String -> bundle.putString(keys[i], values[i] as String)
+ is CharSequence -> bundle.putCharSequence(keys[i], values[i] as CharSequence)
+ is Parcelable -> bundle.putParcelable(keys[i], values[i] as Parcelable)
+ is BooleanArray -> bundle.putBooleanArray(keys[i], values[i] as BooleanArray)
+ is ByteArray -> bundle.putByteArray(keys[i], values[i] as ByteArray)
+ is CharArray -> bundle.putCharArray(keys[i], values[i] as CharArray)
+ is ShortArray -> bundle.putShortArray(keys[i], values[i] as ShortArray)
+ is IntArray -> bundle.putIntArray(keys[i], values[i] as IntArray)
+ is LongArray -> bundle.putLongArray(keys[i], values[i] as LongArray)
+ is FloatArray -> bundle.putFloatArray(keys[i], values[i] as FloatArray)
+ is DoubleArray -> bundle.putDoubleArray(keys[i], values[i] as DoubleArray)
+ is Serializable -> bundle.putSerializable(keys[i], values[i] as Serializable)
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/quick_android_base/src/main/java/cn/qjm253/quick_android_base/util/AccountValidatorUtil.kt b/quick_android_base/src/main/java/cn/qjm253/quick_android_base/util/AccountValidatorUtil.kt
new file mode 100644
index 0000000..a9867c6
--- /dev/null
+++ b/quick_android_base/src/main/java/cn/qjm253/quick_android_base/util/AccountValidatorUtil.kt
@@ -0,0 +1,128 @@
+package cn.qjm253.quick_android_base.util
+
+import java.util.regex.Pattern
+
+/**
+ * 账户相关属性验证工具
+ */
+object AccountValidatorUtil {
+ /**
+ * 正则表达式:验证用户名
+ */
+ val REGEX_USERNAME = "^[a-zA-Z]\\w{5,20}$"
+
+ /**
+ * 正则表达式:验证密码
+ */
+ val REGEX_PASSWORD = "^[a-zA-Z0-9]{6,20}$"
+
+ /**
+ * 正则表达式:验证手机号
+ */
+ val REGEX_MOBILE = "^((16[0-9])|(19[0-9])(17[0-9])|(14[0-9])|(13[0-9])|(15[^4,\\D])|(18[0-9]))\\d{8}$"
+
+ /**
+ * 正则表达式:验证邮箱
+ */
+ val REGEX_EMAIL = "^([a-z0-9A-Z]+[-|\\.]?)+[a-z0-9A-Z]@([a-z0-9A-Z]+(-[a-z0-9A-Z]+)?\\.)+[a-zA-Z]{2,}$"
+
+ /**
+ * 正则表达式:验证汉字
+ */
+ val REGEX_CHINESE = "^[\u4e00-\u9fa5],{0,}$"
+
+ /**
+ * 正则表达式:验证身份证
+ */
+ val REGEX_ID_CARD = "(^\\d{18}$)|(^\\d{15}$)"
+
+ /**
+ * 正则表达式:验证URL
+ */
+ val REGEX_URL = "http(s)?://([\\w-]+\\.)+[\\w-]+(/[\\w- ./?%&=]*)?"
+
+ /**
+ * 正则表达式:验证IP地址
+ */
+ val REGEX_IP_ADDR = "(25[0-5]|2[0-4]\\d|[0-1]\\d{2}|[1-9]?\\d)"
+
+ /**
+ * 校验用户名
+ *
+ * @param username
+ * @return 校验通过返回true,否则返回false
+ */
+ fun isUsername(username: String): Boolean {
+ return Pattern.matches(REGEX_USERNAME, username)
+ }
+
+ /**
+ * 校验密码
+ *
+ * @param password
+ * @return 校验通过返回true,否则返回false
+ */
+ fun isPassword(password: String): Boolean {
+ return Pattern.matches(REGEX_PASSWORD, password)
+ }
+
+ /**
+ * 校验手机号
+ *
+ * @param mobile
+ * @return 校验通过返回true,否则返回false
+ */
+ fun isMobile(mobile: String): Boolean {
+ return Pattern.matches(REGEX_MOBILE, mobile)
+ }
+
+ /**
+ * 校验邮箱
+ *
+ * @param email
+ * @return 校验通过返回true,否则返回false
+ */
+ fun isEmail(email: String): Boolean {
+ return Pattern.matches(REGEX_EMAIL, email)
+ }
+
+ /**
+ * 校验汉字
+ *
+ * @param chinese
+ * @return 校验通过返回true,否则返回false
+ */
+ fun isChinese(chinese: String): Boolean {
+ return Pattern.matches(REGEX_CHINESE, chinese)
+ }
+
+ /**
+ * 校验身份证
+ *
+ * @param idCard
+ * @return 校验通过返回true,否则返回false
+ */
+ fun isIDCard(idCard: String): Boolean {
+ return Pattern.matches(REGEX_ID_CARD, idCard)
+ }
+
+ /**
+ * 校验URL
+ *
+ * @param url
+ * @return 校验通过返回true,否则返回false
+ */
+ fun isUrl(url: String): Boolean {
+ return Pattern.matches(REGEX_URL, url)
+ }
+
+ /**
+ * 校验IP地址
+ *
+ * @param ipAddr
+ * @return
+ */
+ fun isIPAddr(ipAddr: String): Boolean {
+ return Pattern.matches(REGEX_IP_ADDR, ipAddr)
+ }
+}
\ No newline at end of file
diff --git a/quick_android_base/src/main/java/cn/qjm253/quick_android_base/util/ContentUriUtil.java b/quick_android_base/src/main/java/cn/qjm253/quick_android_base/util/ContentUriUtil.java
new file mode 100644
index 0000000..69f6da2
--- /dev/null
+++ b/quick_android_base/src/main/java/cn/qjm253/quick_android_base/util/ContentUriUtil.java
@@ -0,0 +1,366 @@
+package cn.qjm253.quick_android_base.util;
+
+import android.annotation.SuppressLint;
+import android.content.ContentUris;
+import android.content.Context;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Build;
+import android.os.Environment;
+import android.provider.DocumentsContract;
+import android.provider.MediaStore;
+
+import androidx.loader.content.CursorLoader;
+
+import java.io.File;
+
+public class ContentUriUtil {
+ /**
+ * // * Get a file path from a Uri. This will get the the path for Storage Access
+ * // * Framework Documents, as well as the _data field for the MediaStore and
+ * // * other file-based ContentProviders.
+ * // *
+ * // * @param context The context.
+ * // * @param uri The Uri to query.
+ * // * @author paulburke
+ * //
+ */
+// public static String getPath(final Context context, final Uri uri) {
+//
+// final boolean isKitKat = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT;
+//
+// // DocumentProvider
+// if (isKitKat && DocumentsContract.isDocumentUri(context, uri)) {
+// // ExternalStorageProvider
+// if (isExternalStorageDocument(uri)) {
+// final String docId = DocumentsContract.getDocumentId(uri);
+// final String[] split = docId.split(":");
+// final String type = split[0];
+//
+// if ("primary".equalsIgnoreCase(type)) {
+// return Environment.getExternalStorageDirectory() + "/" + split[1];
+// }
+//
+// // TODO handle non-primary volumes
+// }
+// // DownloadsProvider
+// else if (isDownloadsDocument(uri)) {
+// final String id = DocumentsContract.getDocumentId(uri);
+// if (id.startsWith("raw:")) {
+// return uri.getPath();
+// }
+// final Uri contentUri = ContentUris.withAppendedId(
+// Uri.parse("content://downloads/public_downloads"), Long.valueOf(id));
+// return getDataColumn(context, contentUri, null, null);
+// }
+// // MediaProvider
+// else if (isMediaDocument(uri)) {
+// final String docId = DocumentsContract.getDocumentId(uri);
+// final String[] split = docId.split(":");
+// final String type = split[0];
+//
+// Uri contentUri = null;
+// if ("image".equals(type)) {
+// contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
+// } else if ("video".equals(type)) {
+// contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
+// } else if ("audio".equals(type)) {
+// contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
+// }
+//
+// final String selection = "_id=?";
+// final String[] selectionArgs = new String[]{
+// split[1]
+// };
+//
+// return getDataColumn(context, contentUri, selection, selectionArgs);
+// }
+// }
+// // MediaStore (and general)
+// else if ("content".equalsIgnoreCase(uri.getScheme())) {
+//
+// // Return the remote address
+// if (isGooglePhotosUri(uri))
+// return uri.getLastPathSegment();
+//
+// return getDataColumn(context, uri, null, null);
+// }
+// // File
+// else if ("file".equalsIgnoreCase(uri.getScheme())) {
+// return uri.getPath();
+// }
+//
+// return null;
+// }
+//
+// /**
+// * *
+// * Get the value of the data column for this Uri. This is useful for
+// * MediaStore Uris, and other file-based ContentProviders.
+// *
+// * @param context The context.
+// * @param uri The Uri to query.
+// * @param selection (Optional) Filter used in the query.
+// * @param selectionArgs (Optional) Selection arguments used in the query.
+// * @return The value of the _data column, which is typically a file path.
+// */
+// public static String getDataColumn(Context context, Uri uri, String selection,
+// String[] selectionArgs) {
+//
+// Cursor cursor = null;
+// final String column = "_data";
+// final String[] projection = {
+// column
+// };
+//
+// try {
+// cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs,
+// null);
+// if (cursor != null && cursor.moveToFirst()) {
+// final int index = cursor.getColumnIndexOrThrow(column);
+// return cursor.getString(index);
+// }
+// } finally {
+// if (cursor != null)
+// cursor.close();
+// }
+// return null;
+// }
+//
+//
+// /**
+// * @param uri The Uri to check.
+// * @return Whether the Uri authority is ExternalStorageProvider.
+// */
+// public static boolean isExternalStorageDocument(Uri uri) {
+// return "com.android.externalstorage.documents".equals(uri.getAuthority());
+// }
+//
+// /**
+// * @param uri The Uri to check.
+// * @return Whether the Uri authority is DownloadsProvider.
+// */
+// public static boolean isDownloadsDocument(Uri uri) {
+// return "com.android.providers.downloads.documents".equals(uri.getAuthority());
+// }
+//
+// /**
+// * @param uri The Uri to check.
+// * @return Whether the Uri authority is MediaProvider.
+// */
+// public static boolean isMediaDocument(Uri uri) {
+// return "com.android.providers.media.documents".equals(uri.getAuthority());
+// }
+//
+// /**
+// * @param uri The Uri to check.
+// * @return Whether the Uri authority is Google Photos.
+// */
+// public static boolean isGooglePhotosUri(Uri uri) {
+// return "com.google.android.apps.photos.content".equals(uri.getAuthority());
+// }
+
+ private static String failReason;
+
+ static String errorReason() {
+ return failReason;
+ }
+
+ public static String getPath(final Context context, final Uri uri) {
+ if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.KITKAT) {
+ return getRealPathFromURI_BelowAPI19(context, uri);
+ }
+ return getRealPathFromURI_API19(context, uri);
+ }
+
+
+ @SuppressLint("NewApi")
+ public static String getRealPathFromURI_API19(final Context context, final Uri uri) {
+
+ final boolean isKitKat = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT;
+
+ if (isKitKat && DocumentsContract.isDocumentUri(context, uri)) {
+
+ if (isExternalStorageDocument(uri)) {
+ final String docId = DocumentsContract.getDocumentId(uri);
+ final String[] split = docId.split(":");
+ final String type = split[0];
+
+ if ("primary".equalsIgnoreCase(type)) {
+ if (split.length > 1) {
+ return Environment.getExternalStorageDirectory() + "/" + split[1];
+ } else {
+ return Environment.getExternalStorageDirectory() + "/";
+ }
+ } else {
+ return "storage" + "/" + docId.replace(":", "/");
+ }
+
+ } else if (isRawDownloadsDocument(uri)) {
+ String fileName = getFilePath(context, uri);
+ String subFolderName = getSubFolders(uri);
+
+ if (fileName != null) {
+ return Environment.getExternalStorageDirectory().toString() + "/Download/" + subFolderName + fileName;
+ }
+ String id = DocumentsContract.getDocumentId(uri);
+
+ final Uri contentUri = ContentUris.withAppendedId(Uri.parse("content://downloads/public_downloads"), Long.valueOf(id));
+ return getDataColumn(context, contentUri, null, null);
+ } else if (isDownloadsDocument(uri)) {
+
+ System.out.println("downloadsDocument");
+ String fileName = getFilePath(context, uri);
+
+ if (fileName != null) {
+ return Environment.getExternalStorageDirectory().toString() + "/Download/" + fileName;
+ }
+ String id = DocumentsContract.getDocumentId(uri);
+ if (id.startsWith("raw:")) {
+ id = id.replaceFirst("raw:", "");
+ File file = new File(id);
+ if (file.exists())
+ return id;
+ }
+ if (id.startsWith("raw%3A%2F")) {
+ id = id.replaceFirst("raw%3A%2F", "");
+ File file = new File(id);
+ if (file.exists())
+ return id;
+ }
+ final Uri contentUri = ContentUris.withAppendedId(Uri.parse("content://downloads/public_downloads"), Long.valueOf(id));
+ return getDataColumn(context, contentUri, null, null);
+ } else if (isMediaDocument(uri)) {
+ final String docId = DocumentsContract.getDocumentId(uri);
+ final String[] split = docId.split(":");
+ final String type = split[0];
+
+ Uri contentUri = null;
+ if ("image".equals(type)) {
+ contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
+ } else if ("video".equals(type)) {
+ contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
+ } else if ("audio".equals(type)) {
+ contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
+ }
+
+ final String selection = "_id=?";
+ final String[] selectionArgs = new String[]{
+ split[1]
+ };
+
+ return getDataColumn(context, contentUri, selection, selectionArgs);
+ }
+ } else if ("content".equalsIgnoreCase(uri.getScheme())) {
+ if (isGooglePhotosUri(uri)) {
+ return uri.getLastPathSegment();
+ }
+ if (getDataColumn(context, uri, null, null) == null) {
+ failReason = "dataReturnedNull";
+ }
+ return getDataColumn(context, uri, null, null);
+ } else if ("file".equalsIgnoreCase(uri.getScheme())) {
+ return uri.getPath();
+ }
+
+ return null;
+ }
+
+ private static String getSubFolders(Uri uri) {
+ String replaceChars = String.valueOf(uri).replace("%2F", "/").replace("%20", " ").replace("%3A", ":");
+ String[] bits = replaceChars.split("/");
+ String sub5 = bits[bits.length - 2];
+ String sub4 = bits[bits.length - 3];
+ String sub3 = bits[bits.length - 4];
+ String sub2 = bits[bits.length - 5];
+ String sub1 = bits[bits.length - 6];
+ if (sub1.equals("Download")) {
+ return sub2 + "/" + sub3 + "/" + sub4 + "/" + sub5 + "/";
+ } else if (sub2.equals("Download")) {
+ return sub3 + "/" + sub4 + "/" + sub5 + "/";
+ } else if (sub3.equals("Download")) {
+ return sub4 + "/" + sub5 + "/";
+ } else if (sub4.equals("Download")) {
+ return sub5 + "/";
+ } else {
+ return "";
+ }
+ }
+
+ static String getRealPathFromURI_BelowAPI19(Context context, Uri contentUri) {
+ String[] proj = {MediaStore.Video.Media.DATA};
+ CursorLoader loader = new CursorLoader(context, contentUri, proj, null, null, null);
+ Cursor cursor = loader.loadInBackground();
+ assert cursor != null;
+ int column_index = cursor.getColumnIndexOrThrow(MediaStore.Video.Media.DATA);
+ cursor.moveToFirst();
+ String result = cursor.getString(column_index);
+ cursor.close();
+ return result;
+ }
+
+ @SuppressWarnings("TryFinallyCanBeTryWithResources")
+ private static String getDataColumn(Context context, Uri uri, String selection, String[] selectionArgs) {
+ Cursor cursor = null;
+ final String column = "_data";
+ final String[] projection = {column};
+ try {
+ cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs, null);
+ if (cursor != null && cursor.moveToFirst()) {
+ final int index = cursor.getColumnIndexOrThrow(column);
+ return cursor.getString(index);
+ }
+ } catch (Exception e) {
+ failReason = e.getMessage();
+ } finally {
+ if (cursor != null)
+ cursor.close();
+ }
+ return null;
+ }
+
+
+ @SuppressWarnings("TryFinallyCanBeTryWithResources")
+ private static String getFilePath(Context context, Uri uri) {
+ Cursor cursor = null;
+ final String[] projection = {MediaStore.Files.FileColumns.DISPLAY_NAME};
+ try {
+ cursor = context.getContentResolver().query(uri, projection, null, null,
+ null);
+ if (cursor != null && cursor.moveToFirst()) {
+ final int index = cursor.getColumnIndexOrThrow(MediaStore.Files.FileColumns.DISPLAY_NAME);
+ return cursor.getString(index);
+ }
+ } catch (Exception e) {
+ failReason = e.getMessage();
+ } finally {
+ if (cursor != null)
+ cursor.close();
+ }
+ return null;
+ }
+
+
+ private static boolean isExternalStorageDocument(Uri uri) {
+ return "com.android.externalstorage.documents".equals(uri.getAuthority());
+ }
+
+
+ private static boolean isDownloadsDocument(Uri uri) {
+ return "com.android.providers.downloads.documents".equals(uri.getAuthority());
+ }
+
+ private static boolean isRawDownloadsDocument(Uri uri) {
+ String uriToString = String.valueOf(uri);
+ return uriToString.contains("com.android.providers.downloads.documents/document/raw");
+ }
+
+ private static boolean isMediaDocument(Uri uri) {
+ return "com.android.providers.media.documents".equals(uri.getAuthority());
+ }
+
+
+ private static boolean isGooglePhotosUri(Uri uri) {
+ return "com.google.android.apps.photos.content".equals(uri.getAuthority());
+ }
+}
\ No newline at end of file
diff --git a/quick_android_base/src/main/java/cn/qjm253/quick_android_base/util/DisplayUtils.java b/quick_android_base/src/main/java/cn/qjm253/quick_android_base/util/DisplayUtils.java
new file mode 100644
index 0000000..918b1d1
--- /dev/null
+++ b/quick_android_base/src/main/java/cn/qjm253/quick_android_base/util/DisplayUtils.java
@@ -0,0 +1,105 @@
+package cn.qjm253.quick_android_base.util;
+
+import android.app.Activity;
+import android.content.Context;
+import android.graphics.Rect;
+import android.util.TypedValue;
+import android.view.WindowManager;
+
+/**
+ * Created by liufengkai on 15/11/8.
+ */
+public class DisplayUtils {
+ /**
+ * px to dip 尺寸不变
+ *
+ * @param context
+ * @param pxValue
+ * @return
+ */
+ public static int px2dip(Context context, float pxValue) {
+ final float scale = context.getResources().getDisplayMetrics().density;
+ return (int) (pxValue / scale + 0.5f);
+ }
+
+
+ /**
+ * dip to px 尺寸不变
+ *
+ * @param context
+ * @param dipValue
+ * @return
+ */
+ public static int dip2px(Context context, float dipValue) {
+ final float scale = context.getResources().getDisplayMetrics().density;
+ return (int) (dipValue * scale + 0.5f);
+ }
+
+ /**
+ * dp to px
+ *
+ * @param context
+ * @param dp
+ * @return
+ */
+ public static int dp2px(Context context, int dp) {
+ return (int) TypedValue.applyDimension(
+ TypedValue.COMPLEX_UNIT_DIP,
+ dp,
+ context.getResources().getDisplayMetrics());
+ }
+
+ /**
+ * px to sp 文字不变
+ *
+ * @param context
+ * @param pxValue
+ * @return
+ */
+ public static int px2sp(Context context, float pxValue) {
+ final float fontScale = context.getResources().getDisplayMetrics().scaledDensity;
+ return (int) (pxValue / fontScale + 0.5f);
+ }
+
+ /**
+ * sp to px 文字不变
+ *
+ * @param context
+ * @param spValue
+ * @return
+ */
+ public static int sp2px(Context context, float spValue) {
+ final float fontScale = context.getResources().getDisplayMetrics().scaledDensity;
+ return (int) (spValue * fontScale + 0.5f);
+ }
+
+ /**
+ * sp to px
+ *
+ * @param context
+ * @param px
+ * @return
+ */
+ public static int sp2px(Context context, int px) {
+ return (int) TypedValue.applyDimension(
+ TypedValue.COMPLEX_UNIT_SP,
+ px,
+ context.getResources().getDisplayMetrics());
+ }
+
+ public static int[] getScreenParams(Context context) {
+
+ WindowManager manager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
+
+ int width = manager.getDefaultDisplay().getWidth();
+ int height = manager.getDefaultDisplay().getHeight();
+
+ return new int[]{width, height};
+ }
+
+ public static int getStatusBarHeight(Activity context) {
+ Rect frame = new Rect();
+ context.getWindow().getDecorView().getWindowVisibleDisplayFrame(frame);
+ return frame.top;
+ }
+}
diff --git a/quick_android_base/src/main/java/cn/qjm253/quick_android_base/util/FileUtils.kt b/quick_android_base/src/main/java/cn/qjm253/quick_android_base/util/FileUtils.kt
new file mode 100644
index 0000000..170123d
--- /dev/null
+++ b/quick_android_base/src/main/java/cn/qjm253/quick_android_base/util/FileUtils.kt
@@ -0,0 +1,118 @@
+package cn.qjm253.quick_android_base.util
+
+import android.os.Environment
+import cn.qjm253.quick_android_base.QuickAndroid
+import java.io.File
+
+
+/**
+ * Created by tanshunwang on 2016/9/21 0021.
+ */
+object FileUtils {
+
+ /**
+ * 获得应用的根目录
+ *
+ * @return
+ */
+ //判断sd卡是否存在
+ //获取根目录
+ val appPath: String
+ get() {
+ val sdCardExist = Environment.getExternalStorageState() == Environment.MEDIA_MOUNTED
+ val sdDir = if (sdCardExist) {
+ File(Environment.getExternalStorageDirectory().path + "/" + QuickAndroid.APP_NAME + "/")
+ } else {
+ File(Environment.getDataDirectory().toString() + "/" + QuickAndroid.APP_NAME + "/")
+ }
+ if (!sdDir.exists()) {
+ sdDir.mkdirs()
+ }
+ return sdDir.toString()
+ }
+
+ val cachePath: String
+ get() {
+ val targetPath = "$appPath/Cache/"
+ val file = File(targetPath)
+ if (!file.exists())
+ file.mkdirs()
+ return targetPath
+ }
+
+ val recordPath: String
+ get() {
+ val targetPath = "$appPath/Record/"
+ val file = File(targetPath)
+ if (!file.exists())
+ file.mkdirs()
+ return targetPath
+ }
+
+ val picturePath: String
+ get() {
+ val targetPath = "$appPath/Picture/"
+ val file = File(targetPath)
+ if (!file.exists())
+ file.mkdirs()
+ return targetPath
+ }
+
+ val downloadPath: String
+ get() {
+ val targetPath = "$appPath/Download/"
+ val file = File(targetPath)
+ if (!file.exists())
+ file.mkdirs()
+ return targetPath
+ }
+
+ val wifiDirectPath: String
+ get() {
+ val path = "$appPath/WifiDirectFile/"
+ val file = File(path)
+ if (!file.exists()) {
+ file.mkdirs()
+ }
+ return path
+ }
+
+
+ /**
+ * 获得SDCard的根目录
+ *
+ * @return
+ */
+ //判断sd卡是否存在
+ val sdPath: String?
+ get() {
+ val sdCardExist = Environment.getExternalStorageState() == Environment.MEDIA_MOUNTED
+ return if (sdCardExist) {
+ Environment.getExternalStorageDirectory().path
+ } else {
+ null
+ }
+ }
+
+
+ val path: String
+ get() {
+ val path = "$appPath/header/"
+ val file = File(path)
+ if (!file.exists()) {
+ file.mkdirs()
+ println("54555")
+ }
+ return path
+ }
+
+ fun generateRecordFile(fileName: String): File {
+ return File(recordPath + File.separator + fileName)
+ }
+
+ fun generatePictureFile(fileName: String): File {
+ return File(picturePath + File.separator + fileName)
+ }
+
+
+}
diff --git a/quick_android_base/src/main/java/cn/qjm253/quick_android_base/util/GenerateId.kt b/quick_android_base/src/main/java/cn/qjm253/quick_android_base/util/GenerateId.kt
new file mode 100644
index 0000000..b2ab782
--- /dev/null
+++ b/quick_android_base/src/main/java/cn/qjm253/quick_android_base/util/GenerateId.kt
@@ -0,0 +1,30 @@
+package cn.qjm253.quick_android_base.util
+
+import java.util.concurrent.atomic.AtomicInteger
+
+/**
+ * @author SunnyQjm
+ * @date 19-7-14 下午8:16
+ */
+
+object GeneratedId {
+ private val sNextGeneratedId = AtomicInteger(20000)
+
+ /**
+ * Generate a value suitable for use in setId
+ * This value will not collide with ID values generated at build time by aapt for R.id.
+ *
+ * @return a generated ID value
+ */
+ fun generateViewId(): Int {
+ while (true) {
+ val result = sNextGeneratedId.get()
+ // aapt-generated IDs have the high byte nonzero; clamp to the range under that.
+ var newValue = result + 1
+ if (newValue > 0x00FFFFFF) newValue = 1 // Roll over to 1, not 0.
+ if (sNextGeneratedId.compareAndSet(result, newValue)) {
+ return result
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/quick_android_base/src/main/java/cn/qjm253/quick_android_base/util/GsonUtil.kt b/quick_android_base/src/main/java/cn/qjm253/quick_android_base/util/GsonUtil.kt
new file mode 100644
index 0000000..7ddbdb9
--- /dev/null
+++ b/quick_android_base/src/main/java/cn/qjm253/quick_android_base/util/GsonUtil.kt
@@ -0,0 +1,78 @@
+package cn.qjm253.quick_android_base.util
+
+import com.google.gson.Gson
+import com.google.gson.JsonSyntaxException
+import com.google.gson.reflect.TypeToken
+
+import java.util.ArrayList
+
+/**
+ * Created by Sunny on 2017/4/19 0019.
+ */
+object GsonUtil {
+ val instance: Gson = Gson()
+
+
+ fun json2Bean(json: String, beanClass: Class): T? {
+ var bean: T? = null
+ try {
+ bean = instance.fromJson(json, beanClass)
+ } catch (e: JsonSyntaxException) {
+ e.printStackTrace()
+ }
+
+ return bean
+ }
+
+ fun json2Bean(json: String): T? {
+ var bean: T? = null
+ val type = object : TypeToken>() {
+
+ }.type
+ try {
+ bean = instance.fromJson(json, type)
+ } catch (e: JsonSyntaxException) {
+ e.printStackTrace()
+ }
+
+ return bean
+ }
+
+ // public static ArrayList json2TagList(String json) {
+ // Type type = new TypeToken>(){}.getType();
+ // ArrayList bean = new ArrayList<>();
+ // try {
+ // bean = getInstance().fromJson(json, type);
+ // } catch (JsonSyntaxException e) {
+ // e.printStackTrace();
+ // }
+ // return bean;
+ // }
+ //
+ // public static ArrayList json2RecordList(String json) {
+ // Type type = new TypeToken>(){}.getType();
+ // ArrayList bean = new ArrayList<>();
+ // try {
+ // bean = getInstance().fromJson(json, type);
+ // } catch (JsonSyntaxException e) {
+ // e.printStackTrace();
+ // }
+ // return bean;
+ // }
+ //
+ // public static ArrayList json2ImageList(String json) {
+ // Type type = new TypeToken>(){}.getType();
+ // ArrayList bean = new ArrayList<>();
+ // try {
+ // bean = getInstance().fromJson(json, type);
+ // } catch (JsonSyntaxException e) {
+ // e.printStackTrace();
+ // }
+ // return bean;
+ // }
+
+
+ fun bean2Json(`object`: Any): String {
+ return instance.toJson(`object`)
+ }
+}
\ No newline at end of file
diff --git a/quick_android_base/src/main/java/cn/qjm253/quick_android_base/util/RxSchedulersHelper.kt b/quick_android_base/src/main/java/cn/qjm253/quick_android_base/util/RxSchedulersHelper.kt
new file mode 100644
index 0000000..976c8f4
--- /dev/null
+++ b/quick_android_base/src/main/java/cn/qjm253/quick_android_base/util/RxSchedulersHelper.kt
@@ -0,0 +1,41 @@
+package cn.qjm253.quick_android_base.util
+
+import io.reactivex.ObservableTransformer
+import io.reactivex.android.schedulers.AndroidSchedulers
+import io.reactivex.schedulers.Schedulers
+
+/**
+ * 处理Rx线程转换
+ * @author SunnyQjm
+ * @date 19-7-7 上午8:43
+ */
+object RxSchedulersHelper {
+
+ fun io_main(): ObservableTransformer {
+ return ObservableTransformer { upstream ->
+ upstream
+ .subscribeOn(Schedulers.io())
+ .observeOn(AndroidSchedulers.mainThread())
+ }
+ }
+
+ fun io_io(): ObservableTransformer{
+ return ObservableTransformer { upstream ->
+ upstream.subscribeOn(Schedulers.io())
+ .observeOn(Schedulers.io())
+ }
+ }
+ fun main_io(): ObservableTransformer {
+ return ObservableTransformer { upstream ->
+ upstream.subscribeOn(AndroidSchedulers.mainThread())
+ .observeOn(Schedulers.io())
+ }
+ }
+
+ fun comppute_main(): ObservableTransformer {
+ return ObservableTransformer { upstream ->
+ upstream.subscribeOn(Schedulers.computation())
+ .observeOn(AndroidSchedulers.mainThread())
+ }
+ }
+}
\ No newline at end of file
diff --git a/quick_android_base/src/main/java/cn/qjm253/quick_android_base/util/SpUtils.java b/quick_android_base/src/main/java/cn/qjm253/quick_android_base/util/SpUtils.java
new file mode 100644
index 0000000..7272bca
--- /dev/null
+++ b/quick_android_base/src/main/java/cn/qjm253/quick_android_base/util/SpUtils.java
@@ -0,0 +1,303 @@
+package cn.qjm253.quick_android_base.util;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * 简化Sp存储的工具类
+ *
+ * @author liufengkai
+ */
+public class SpUtils {
+
+ public SpUtils() {
+ /* cannot be instantiated */
+ throw new UnsupportedOperationException("cannot be instantiated");
+ }
+
+ /**
+ * Sp文件名
+ */
+ public static final String FILE_NAME = "share_data";
+
+ /**
+ * 保存数据的方法,添加具体数据类型
+ *
+ * @param context
+ * @param key
+ * @param object
+ */
+ public static void put(Context context, String key, Object object) {
+
+ SharedPreferences sp = context.getSharedPreferences(FILE_NAME,
+ Context.MODE_PRIVATE);
+ SharedPreferences.Editor editor = sp.edit();
+
+ // 判断数据类型
+ if (object instanceof String) {
+ editor.putString(key, (String) object);
+ } else if (object instanceof Integer) {
+ editor.putInt(key, (Integer) object);
+ } else if (object instanceof Boolean) {
+ editor.putBoolean(key, (Boolean) object);
+ } else if (object instanceof Float) {
+ editor.putFloat(key, (Float) object);
+ } else if (object instanceof Long) {
+ editor.putLong(key, (Long) object);
+ } else {
+ editor.putString(key, object.toString());
+ }
+
+ SharedPreferencesCompat.apply(editor);
+ }
+
+ public static void put(Context context, String key, String value) {
+
+ SharedPreferences sp = context.getSharedPreferences(FILE_NAME,
+ Context.MODE_PRIVATE);
+ SharedPreferences.Editor editor = sp.edit();
+ editor.putString(key, value);
+ SharedPreferencesCompat.apply(editor);
+ }
+
+ public static void put(Context context, String key, int value) {
+
+ SharedPreferences sp = context.getSharedPreferences(FILE_NAME,
+ Context.MODE_PRIVATE);
+ SharedPreferences.Editor editor = sp.edit();
+ editor.putInt(key, value);
+ SharedPreferencesCompat.apply(editor);
+ }
+
+ public static void put(Context context, String key, float value) {
+
+ SharedPreferences sp = context.getSharedPreferences(FILE_NAME,
+ Context.MODE_PRIVATE);
+ SharedPreferences.Editor editor = sp.edit();
+ editor.putFloat(key, value);
+ SharedPreferencesCompat.apply(editor);
+ }
+
+ public static void put(Context context, String key, long value) {
+
+ SharedPreferences sp = context.getSharedPreferences(FILE_NAME,
+ Context.MODE_PRIVATE);
+ SharedPreferences.Editor editor = sp.edit();
+ editor.putLong(key, value);
+ SharedPreferencesCompat.apply(editor);
+ }
+
+ public static String get(Context context, String key, String defaultValue) {
+ SharedPreferences sp = context.getSharedPreferences(FILE_NAME,
+ Context.MODE_PRIVATE);
+ return sp.getString(key, defaultValue);
+ }
+
+ public static int get(Context context, String key, int defaultValue) {
+ SharedPreferences sp = context.getSharedPreferences(FILE_NAME,
+ Context.MODE_PRIVATE);
+ return sp.getInt(key, defaultValue);
+ }
+
+ public static float get(Context context, String key, float defaultValue) {
+ SharedPreferences sp = context.getSharedPreferences(FILE_NAME,
+ Context.MODE_PRIVATE);
+ return sp.getFloat(key, defaultValue);
+ }
+
+ public static long get(Context context, String key, long defaultValue) {
+ SharedPreferences sp = context.getSharedPreferences(FILE_NAME,
+ Context.MODE_PRIVATE);
+ return sp.getLong(key, defaultValue);
+ }
+
+ public static boolean get(Context context, String key, boolean judge) {
+ SharedPreferences sp = context.getSharedPreferences(FILE_NAME,
+ Context.MODE_PRIVATE);
+ return sp.getBoolean(key, judge);
+ }
+
+ /**
+ * 获取保存数据的方法,添加具体数据类型
+ *
+ * @param context
+ * @param key
+ * @param defaultObject
+ * @return object
+ */
+ public static Object get(Context context, String key, Object defaultObject) {
+ SharedPreferences sp = context.getSharedPreferences(FILE_NAME,
+ Context.MODE_PRIVATE);
+
+ if (defaultObject instanceof String) {
+ return sp.getString(key, (String) defaultObject);
+ } else if (defaultObject instanceof Integer) {
+ return sp.getInt(key, (Integer) defaultObject);
+ } else if (defaultObject instanceof Boolean) {
+ return sp.getBoolean(key, (Boolean) defaultObject);
+ } else if (defaultObject instanceof Float) {
+ return sp.getFloat(key, (Float) defaultObject);
+ } else if (defaultObject instanceof Long) {
+ return sp.getLong(key, (Long) defaultObject);
+ }
+
+ return null;
+ }
+
+ /**
+ * 存入哈希表
+ *
+ * @param context
+ * @param map
+ * @param key
+ */
+ public static void putMap(Context context, Map, ?> map, String key) {
+ JSONArray jsonArray = new JSONArray();
+ Set set = map.entrySet();
+ Iterator iterator = set.iterator();
+ for (int i = 0; i < map.size(); i++) {
+ Map.Entry mapEntry = (Map.Entry) iterator.next();
+ try {
+ JSONObject object = new JSONObject();
+ object.put("key", mapEntry.getKey());
+ object.put("value", mapEntry.getValue());
+ jsonArray.put(object);
+ } catch (JSONException e) {
+ e.printStackTrace();
+ }
+ }
+ put(context, key, jsonArray.toString());
+ }
+
+ /**
+ * 读取哈希表
+ *
+ * @param context
+ * @param key
+ * @return map
+ */
+ public static Map getMap(Context context, String key) {
+ Map map = new HashMap();
+ try {
+ JSONArray jsonArray = new JSONArray((String) get(context, key, ""));
+ for (int i = 0; i < jsonArray.length(); i++) {
+ try {
+ JSONObject object = jsonArray.getJSONObject(i);
+ map.put(object.getString("key"), object.getString("value"));
+ } catch (JSONException e) {
+ e.printStackTrace();
+ }
+ }
+ } catch (JSONException e) {
+ e.printStackTrace();
+ }
+ return map;
+ }
+
+ /**
+ * 移除某个key值已经对应的值
+ *
+ * @param context
+ * @param key
+ */
+ public static void remove(Context context, String key) {
+ SharedPreferences sp = context.getSharedPreferences(FILE_NAME,
+ Context.MODE_PRIVATE);
+ SharedPreferences.Editor editor = sp.edit();
+ editor.remove(key);
+ SharedPreferencesCompat.apply(editor);
+ }
+
+ /**
+ * 清除所有数据
+ *
+ * @param context
+ */
+ public static void clear(Context context) {
+ SharedPreferences sp = context.getSharedPreferences(FILE_NAME,
+ Context.MODE_PRIVATE);
+ SharedPreferences.Editor editor = sp.edit();
+ editor.clear();
+ SharedPreferencesCompat.apply(editor);
+ }
+
+ /**
+ * 查询某个key是否存在
+ *
+ * @param context
+ * @param key
+ * @return
+ */
+ public static boolean contains(Context context, String key) {
+ SharedPreferences sp = context.getSharedPreferences(FILE_NAME,
+ Context.MODE_PRIVATE);
+ return sp.contains(key);
+ }
+
+ /**
+ * 返回所有的键值对
+ *
+ * @param context
+ * @return map
+ */
+ public static Map getAll(Context context) {
+ SharedPreferences sp = context.getSharedPreferences(FILE_NAME,
+ Context.MODE_PRIVATE);
+ return sp.getAll();
+ }
+
+ /**
+ * 解决SharedPreferencesCompat.apply方法的兼容类
+ *
+ * @author liufengkai
+ */
+ private static class SharedPreferencesCompat {
+
+ private static final Method sApplyMethod = findApplyMethod();
+
+ /**
+ * 反射查找apply的方法
+ *
+ * @return method
+ */
+ @SuppressWarnings({"unchecked", "rawtypes"})
+ private static Method findApplyMethod() {
+ try {
+ Class clz = SharedPreferences.Editor.class;
+ return clz.getMethod("apply");
+ } catch (NoSuchMethodException e) {
+ e.printStackTrace();
+ }
+ return null;
+ }
+
+ /**
+ * 如果找到则使用apply执行,否则使用commit
+ *
+ * @param editor
+ */
+ public static void apply(SharedPreferences.Editor editor) {
+ try {
+ if (sApplyMethod != null) {
+ sApplyMethod.invoke(editor);
+ return;
+ }
+ } catch (IllegalArgumentException | IllegalAccessException | InvocationTargetException e) {
+ e.printStackTrace();
+ }
+ editor.commit();
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/quick_android_base/src/main/java/cn/qjm253/quick_android_base/util/SpannableUtil.kt b/quick_android_base/src/main/java/cn/qjm253/quick_android_base/util/SpannableUtil.kt
new file mode 100644
index 0000000..f50c86c
--- /dev/null
+++ b/quick_android_base/src/main/java/cn/qjm253/quick_android_base/util/SpannableUtil.kt
@@ -0,0 +1,85 @@
+package cn.qjm253.quick_android_base.util
+
+import android.graphics.Typeface
+import android.text.Spannable
+import android.text.SpannableStringBuilder
+import android.text.Spanned
+import android.text.style.ForegroundColorSpan
+import android.text.style.StyleSpan
+
+
+/**
+ * SpannableText的工具类,可以设置文本的显示样式
+ * Created by sunny on 18-1-27.
+ */
+
+object SpannableUtil {
+ /**
+ * Returns a CharSequence that concatenates the specified array of CharSequence
+ * objects and then applies a list of zero or more tags to the entire range.
+ *
+ * @param content an array of character sequences to apply a style to
+ * @param tags the styled span objects to apply to the content
+ * such as android.text.style.StyleSpan
+ */
+ private fun apply(content: Array, vararg tags: Any): CharSequence {
+ val text = SpannableStringBuilder()
+ openTags(text, tags)
+ for (item in content) {
+ text.append(item)
+ }
+ closeTags(text, tags)
+ return text
+ }
+
+ /**
+ * Iterates over an array of tags and applies them to the beginning of the specified
+ * Spannable object so that future text appended to the text will have the styling
+ * applied to it. Do not call this method directly.
+ */
+ private fun openTags(text: Spannable, tags: Array) {
+ for (tag in tags) {
+ text.setSpan(tag, 0, 0, Spannable.SPAN_MARK_MARK)
+ }
+ }
+
+ /**
+ * "Closes" the specified tags on a Spannable by updating the spans to be
+ * endpoint-exclusive so that future text appended to the end will not take
+ * on the same styling. Do not call this method directly.
+ */
+ private fun closeTags(text: Spannable, tags: Array) {
+ val len = text.length
+ for (tag in tags) {
+ if (len > 0) {
+ text.setSpan(tag, 0, len, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
+ } else {
+ text.removeSpan(tag)
+ }
+ }
+ }
+
+ /**
+ * Returns a CharSequence that applies boldface to the concatenation
+ * of the specified CharSequence objects.
+ */
+ fun bold(vararg content: CharSequence): CharSequence {
+ return apply(content, StyleSpan(Typeface.BOLD))
+ }
+
+ /**
+ * Returns a CharSequence that applies italics to the concatenation
+ * of the specified CharSequence objects.
+ */
+ fun italic(vararg content: CharSequence): CharSequence {
+ return apply(content, StyleSpan(Typeface.ITALIC))
+ }
+
+ /**
+ * Returns a CharSequence that applies a foreground color to the
+ * concatenation of the specified CharSequence objects.
+ */
+ fun color(color: Int, vararg content: CharSequence): CharSequence {
+ return apply(content, ForegroundColorSpan(color))
+ }
+}
\ No newline at end of file
diff --git a/quick_android_base/src/main/java/cn/qjm253/quick_android_base/util/SystemInfoHelper.kt b/quick_android_base/src/main/java/cn/qjm253/quick_android_base/util/SystemInfoHelper.kt
new file mode 100644
index 0000000..1f6fb81
--- /dev/null
+++ b/quick_android_base/src/main/java/cn/qjm253/quick_android_base/util/SystemInfoHelper.kt
@@ -0,0 +1,88 @@
+package cn.qjm253.quick_android_base.util
+
+import android.content.Context
+import cn.qjm253.quick_android_base.model.SystemInfo
+import java.util.*
+
+/**
+ * 获取系统信息的工具类
+ *
+ * @author SunnyQjm
+ * @date 19-7-10 上午9:02
+ */
+
+object SystemInfoHelper {
+
+ /**
+ * 获取系统状态栏的高度
+ */
+ fun getStatusBarHeight(context: Context): Int {
+ val resourceId = context.resources.getIdentifier("status_bar_height", "dimen", "android")
+ return context.resources.getDimensionPixelSize(resourceId)
+ }
+
+ /**
+ * 获取手机型号
+ */
+ fun getSystemModel(): String {
+ return android.os.Build.MODEL ?: ""
+ }
+
+ /**
+ * 获取手机厂商
+ */
+ fun getDeviceBrand(): String {
+ return android.os.Build.BRAND ?: ""
+ }
+
+ /**
+ * 获取手机的版本号
+ */
+ fun getSystemVersion(): String {
+ return android.os.Build.VERSION.RELEASE
+ }
+
+ /**
+ * 获取当前手机的语言
+ */
+ fun getSystemLanguage(): String {
+ return Locale.getDefault().language ?: ""
+ }
+
+ /**
+ * 获取当前系统上的语言列表
+ */
+ fun getSystemLanguageLists(): Array {
+ return Locale.getAvailableLocales() ?: arrayOf()
+ }
+
+ /**
+ * 获取系统硬件信息
+ */
+ fun getSystemHardware(): String {
+ return android.os.Build.HARDWARE ?: ""
+ }
+
+ /**
+ * 获取当前系统的SDK版本
+ */
+ fun getSdkInt(): Int {
+ return android.os.Build.VERSION.SDK_INT
+ }
+
+
+ /**
+ * 构建系统信息
+ */
+ fun buildSystemInfo(context: Context): SystemInfo {
+ return SystemInfo(
+ getDeviceBrand(),
+ getSystemModel(),
+ getSystemHardware(),
+ getSdkInt(),
+ getSystemVersion(),
+ getStatusBarHeight(context),
+ getSystemLanguage()
+ )
+ }
+}
\ No newline at end of file
diff --git a/quick_android_base/src/main/java/cn/qjm253/quick_android_base/util/notification/MyNotificationUtil.kt b/quick_android_base/src/main/java/cn/qjm253/quick_android_base/util/notification/MyNotificationUtil.kt
new file mode 100644
index 0000000..0617f93
--- /dev/null
+++ b/quick_android_base/src/main/java/cn/qjm253/quick_android_base/util/notification/MyNotificationUtil.kt
@@ -0,0 +1,162 @@
+package cn.qjm253.quick_android_base.util.notification
+
+import android.app.Notification
+import android.app.NotificationChannel
+import android.app.NotificationManager
+import android.app.PendingIntent
+import android.content.Context
+import android.content.Intent
+import android.graphics.BitmapFactory
+import android.graphics.Color
+import android.os.Build
+import androidx.annotation.DrawableRes
+import androidx.core.app.NotificationCompat
+import cn.qjm253.quick_android_base.QuickAndroid
+import cn.qjm253.quick_android_base.R
+import cn.qjm253.quick_android_base.params.IntentParam
+
+/**
+ * 通知显示工具类
+ * Created by sunny on 18-1-30.
+ */
+
+class MyNotificationUtil private constructor(val context: Context, channelId: String = QuickAndroid.APP_NAME) {
+
+ private var notification: Notification? = null
+ private var notificationManager: NotificationManager =
+ context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
+ private var builder = NotificationCompat.Builder(context, channelId)
+
+ companion object {
+ private var default_notifyId = 0
+ fun newInstance(context: Context): MyNotificationUtil {
+ return MyNotificationUtil(context)
+ }
+
+ @Synchronized
+ fun generateNotifyId(): Int {
+ default_notifyId = (default_notifyId + 1) % 100
+ return default_notifyId
+ }
+
+ fun createChannelForOreo(context: Context, channelId: String, channelName: String) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
+ val channel = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ NotificationChannel(channelId, channelName, NotificationManager.IMPORTANCE_LOW)
+ } else {
+ TODO("VERSION.SDK_INT < O")
+ }
+ channel.enableLights(true)
+ channel.setShowBadge(true)
+ channel.lightColor = Color.GREEN
+ channel.lockscreenVisibility = Notification.VISIBILITY_PUBLIC
+
+ notificationManager.createNotificationChannel(channel)
+ }
+ }
+ }
+
+ fun notify(notification: Notification, notificationId: Int){
+ notificationManager.notify(notificationId, notification)
+ }
+
+ fun cancel(notificationId: Int){
+ notificationManager.cancel(notificationId)
+ }
+
+
+ fun initProgress(progress: Int, progressMax: Int = 100, indeterminate: Boolean = true,
+ title: String = "", content: String = "", ticker: String = ""): MyNotificationUtil {
+ initNotification(progress = progress, progressMax = progressMax, indeterminate = indeterminate,
+ title = title, content = content, ticker = ticker, isProgress = true, isSound = false,
+ isVibrate = false, autoCancel = false)
+ return this
+ }
+
+
+ fun initNotification(@DrawableRes smallIcon: Int = R.drawable.logo,
+ @DrawableRes bigIcon: Int = R.drawable.logo, title: String = "",
+ content: String = "", ticker: String = "", time: Long = System.currentTimeMillis(),
+ /**
+ * 1.将AutoCancel设为true后,当你点击通知栏的notification后,它会自动被取消消失,
+ * 不设置的话点击消息后也不清除,但可以滑动删除
+ * 2.将Ongoing设为true 那么notification将不能滑动删除
+ */
+ autoCancel: Boolean = true, onGoing: Boolean = false,
+ /**
+ * 从Android4.1开始,可以通过以下方法,设置notification的优先级,
+ * 优先级越高的,通知排的越靠前,优先级低的,不会在手机最顶部的状态栏显示图标
+ */
+ priority: Int = NotificationCompat.PRIORITY_DEFAULT,
+ /**
+ * Notification.DEFAULT_ALL:铃声、闪光、震动均系统默认。
+ * Notification.DEFAULT_SOUND:系统默认铃声。
+ * Notification.DEFAULT_VIBRATE:系统默认震动。
+ * Notification.DEFAULT_LIGHTS:系统默认闪光。
+ * notifyBuilder.setDefaults(Notification.DEFAULT_ALL);
+ */
+ isSound: Boolean = true, isVibrate: Boolean = true, isLight: Boolean = true,
+ /**
+ * 下面是进度相关设置
+ */
+ progress: Int = 0, progressMax: Int = 100, indeterminate: Boolean = true,
+ /**
+ * 是否显示进度条
+ */
+ isProgress: Boolean = false,
+ /**
+ * 点击跳转相关设置
+ * 注意事项: setContentIntent一定要在build之前调用,不然无效
+ */
+ requestCode: Int = 0, target: Class<*>? = null,
+ pendingIntentFlag: Int = PendingIntent.FLAG_UPDATE_CURRENT,
+ pendingIntentParam: IntentParam? = null,
+
+ // 适配Android8.0+ 通知必须配置渠道,要不然无法在通知栏显示
+ channelId: String = QuickAndroid.APP_NAME,
+ channelName: String = QuickAndroid.APP_NAME
+ )
+ : MyNotificationUtil {
+ // 适配Android8.0+ 通知必须配置渠道,要不然无法在通知栏显示
+ createChannelForOreo(context, channelId, channelName)
+
+ builder.setAutoCancel(autoCancel)
+ builder.setSmallIcon(smallIcon)
+ val bitmap = BitmapFactory.decodeResource(context.resources, bigIcon)
+ builder.setLargeIcon(bitmap)
+ builder.setContentText(content)
+ builder.setContentTitle(title)
+ builder.setTicker(ticker)
+ builder.setWhen(time)
+ builder.priority = priority
+ builder.setOngoing(onGoing)
+ var defaults = 0
+ if (isSound) {
+ defaults = defaults or Notification.DEFAULT_SOUND
+ }
+ if (isVibrate) {
+ defaults = defaults or Notification.DEFAULT_VIBRATE
+ }
+ if (isLight) {
+ defaults = defaults or Notification.DEFAULT_LIGHTS
+ }
+
+ if (isProgress)
+ builder.setProgress(progressMax, progress, indeterminate)
+ if (target != null) {
+ val intent = Intent(context, target)
+ pendingIntentParam?.applyParam(intent)
+ builder.setContentIntent(PendingIntent.getActivity(context, requestCode,
+ intent, pendingIntentFlag))
+ }
+ builder.setDefaults(defaults)
+ return this
+ }
+
+ fun show(notificationId: Int = generateNotifyId()): Int {
+ notification = builder.build()
+ notificationManager.notify(notificationId, notification)
+ return notificationId
+ }
+}
diff --git a/quick_android_base/src/main/res/drawable/logo.png b/quick_android_base/src/main/res/drawable/logo.png
new file mode 100644
index 0000000..bdc6af1
Binary files /dev/null and b/quick_android_base/src/main/res/drawable/logo.png differ
diff --git a/quick_android_base/src/main/res/values/strings.xml b/quick_android_base/src/main/res/values/strings.xml
new file mode 100644
index 0000000..5c77f4e
--- /dev/null
+++ b/quick_android_base/src/main/res/values/strings.xml
@@ -0,0 +1,3 @@
+
+ 本功能需要请求摄像头权限
+
diff --git a/quick_android_base/src/test/java/cn/qjm253/quick_android_base/ExampleUnitTest.java b/quick_android_base/src/test/java/cn/qjm253/quick_android_base/ExampleUnitTest.java
new file mode 100644
index 0000000..26d7a37
--- /dev/null
+++ b/quick_android_base/src/test/java/cn/qjm253/quick_android_base/ExampleUnitTest.java
@@ -0,0 +1,17 @@
+package cn.qjm253.quick_android_base;
+
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+/**
+ * Example local unit test, which will execute on the development machine (host).
+ *
+ * @see Testing documentation
+ */
+public class ExampleUnitTest {
+ @Test
+ public void addition_isCorrect() {
+ assertEquals(4, 2 + 2);
+ }
+}
\ No newline at end of file
diff --git a/quick_android_custom_view/.gitignore b/quick_android_custom_view/.gitignore
new file mode 100644
index 0000000..796b96d
--- /dev/null
+++ b/quick_android_custom_view/.gitignore
@@ -0,0 +1 @@
+/build
diff --git a/quick_android_custom_view/build.gradle b/quick_android_custom_view/build.gradle
new file mode 100644
index 0000000..136d981
--- /dev/null
+++ b/quick_android_custom_view/build.gradle
@@ -0,0 +1,40 @@
+apply plugin: 'com.android.library'
+
+apply plugin: 'kotlin-android'
+
+apply plugin: 'kotlin-android-extensions'
+
+
+android {
+ compileSdkVersion compile_sdk_version
+
+ defaultConfig {
+ minSdkVersion min_sdk_version
+ targetSdkVersion target_sdk_version
+ versionCode 1
+ versionName "1.0"
+
+ testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
+
+ }
+
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+ }
+ }
+
+}
+
+dependencies {
+ implementation fileTree(dir: 'libs', include: ['*.jar'])
+
+ implementation "androidx.appcompat:appcompat:${appcompact_version}"
+ testImplementation "junit:junit:${junit_version}"
+ androidTestImplementation "androidx.test:runner:${test_runner_version}"
+ androidTestImplementation "androidx.test.espresso:espresso-core:${espresso_core_version}"
+
+ implementation project(path: ':quick_android_base')
+
+}
diff --git a/quick_android_custom_view/proguard-rules.pro b/quick_android_custom_view/proguard-rules.pro
new file mode 100644
index 0000000..f1b4245
--- /dev/null
+++ b/quick_android_custom_view/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
diff --git a/quick_android_custom_view/src/androidTest/java/cn/qjm253/quick_android_custom_view/ExampleInstrumentedTest.java b/quick_android_custom_view/src/androidTest/java/cn/qjm253/quick_android_custom_view/ExampleInstrumentedTest.java
new file mode 100644
index 0000000..00362ce
--- /dev/null
+++ b/quick_android_custom_view/src/androidTest/java/cn/qjm253/quick_android_custom_view/ExampleInstrumentedTest.java
@@ -0,0 +1,26 @@
+package cn.qjm253.quick_android_custom_view;
+
+import android.content.Context;
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import static org.junit.Assert.*;
+
+/**
+ * Instrumented test, which will execute on an Android device.
+ *
+ * @see Testing documentation
+ */
+@RunWith(AndroidJUnit4.class)
+public class ExampleInstrumentedTest {
+ @Test
+ public void useAppContext() {
+ // Context of the app under test.
+ Context appContext = InstrumentationRegistry.getTargetContext();
+
+ assertEquals("cn.qjm253.quick_android_custom_view.test", appContext.getPackageName());
+ }
+}
diff --git a/quick_android_custom_view/src/main/AndroidManifest.xml b/quick_android_custom_view/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..521a706
--- /dev/null
+++ b/quick_android_custom_view/src/main/AndroidManifest.xml
@@ -0,0 +1,2 @@
+
diff --git a/quick_android_custom_view/src/main/java/cn/qjm253/quick_android_custom_view/custom_drawable_size_view/DrawableButton.kt b/quick_android_custom_view/src/main/java/cn/qjm253/quick_android_custom_view/custom_drawable_size_view/DrawableButton.kt
new file mode 100644
index 0000000..00858e4
--- /dev/null
+++ b/quick_android_custom_view/src/main/java/cn/qjm253/quick_android_custom_view/custom_drawable_size_view/DrawableButton.kt
@@ -0,0 +1,102 @@
+package cn.qjm253.quick_android_custom_view.custom_drawable_size_view
+
+import android.content.Context
+import android.util.AttributeSet
+import androidx.appcompat.widget.AppCompatButton
+import cn.qjm253.quick_android_custom_view.R
+
+/**
+ * 这是一个Button的封装组件,支持自定义四个方向的drawable的大小
+ *
+ * 1. 使用 'left_drawable', 'right_drawable', 'top_drawable', 'bottom_drawable' 属性来设置四个方向的drawable资源
+ *
+ * 2. 使用 'drawable_width', 'drawable_height' 统一设置四个drawable的大小
+ *
+ * 3. 使用 'left_drawable_width', 'left_drawable_height', 'right_drawable_width', 'right_drawable_height',
+ * 'top_drawable_width', 'top_drawable_height', 'bottom_drawable_width', 'bottom_drawable_height'
+ * 来定制指定方向drawable的大小
+ *
+ * @author SunnyQjm
+ * @date 19-7-10 下午9:09
+ */
+
+
+open class DrawableButton @JvmOverloads constructor(
+ context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = android.R.attr.buttonStyle
+) : AppCompatButton(context, attrs, defStyleAttr) {
+ companion object {
+ const val DEFAULT_SIZE = 20
+ }
+
+ init {
+ val ta = context.obtainStyledAttributes(attrs, R.styleable.DrawableButton)
+
+ val width = ta.getDimensionPixelOffset(
+ R.styleable.DrawableButton_drawable_width,
+ DEFAULT_SIZE
+ )
+ val height = ta.getDimensionPixelOffset(
+ R.styleable.DrawableButton_drawable_height,
+ DEFAULT_SIZE
+ )
+
+ val sizeWrap = SizeWrap()
+
+ val letfD = ta.getDrawable(R.styleable.DrawableButton_left_drawable)
+ ?.apply {
+ val lwidth = ta.getDimensionPixelOffset(
+ R.styleable.DrawableButton_left_drawable_width,
+ DEFAULT_SIZE
+ )
+ val lheight = ta.getDimensionPixelOffset(
+ R.styleable.DrawableButton_left_drawable_height,
+ DEFAULT_SIZE
+ )
+ sizeWrap.checkSize(width, height, lwidth, lheight)
+ setBounds(0, 0, sizeWrap.width, sizeWrap.height)
+ }
+ val rightD = ta.getDrawable(R.styleable.DrawableButton_right_drawable)
+ ?.apply {
+ val lwidth = ta.getDimensionPixelOffset(
+ R.styleable.DrawableButton_right_drawable_width,
+ DEFAULT_SIZE
+ )
+ val lheight = ta.getDimensionPixelOffset(
+ R.styleable.DrawableButton_right_drawable_height,
+ DEFAULT_SIZE
+ )
+ sizeWrap.checkSize(width, height, lwidth, lheight)
+ setBounds(0, 0, sizeWrap.width, sizeWrap.height)
+ }
+ val topD = ta.getDrawable(R.styleable.DrawableButton_top_drawable)
+ ?.apply {
+ val lwidth = ta.getDimensionPixelOffset(
+ R.styleable.DrawableButton_top_drawable_width,
+ DEFAULT_SIZE
+ )
+ val lheight = ta.getDimensionPixelOffset(
+ R.styleable.DrawableButton_top_drawable_height,
+ DEFAULT_SIZE
+ )
+ sizeWrap.checkSize(width, height, lwidth, lheight)
+ setBounds(0, 0, sizeWrap.width, sizeWrap.height)
+ }
+ val bottomD = ta.getDrawable(R.styleable.DrawableButton_bottom_drawable)
+ ?.apply {
+ val lwidth = ta.getDimensionPixelOffset(
+ R.styleable.DrawableButton_bottom_drawable_width,
+ DEFAULT_SIZE
+ )
+ val lheight = ta.getDimensionPixelOffset(
+ R.styleable.DrawableButton_bottom_drawable_height,
+ DEFAULT_SIZE
+ )
+ sizeWrap.checkSize(width, height, lwidth, lheight)
+ setBounds(0, 0, sizeWrap.width, sizeWrap.height)
+ }
+
+ this.setCompoundDrawables(letfD, topD, rightD, bottomD)
+
+ ta.recycle()
+ }
+}
\ No newline at end of file
diff --git a/quick_android_custom_view/src/main/java/cn/qjm253/quick_android_custom_view/custom_drawable_size_view/DrawableEditText.kt b/quick_android_custom_view/src/main/java/cn/qjm253/quick_android_custom_view/custom_drawable_size_view/DrawableEditText.kt
new file mode 100644
index 0000000..49fac98
--- /dev/null
+++ b/quick_android_custom_view/src/main/java/cn/qjm253/quick_android_custom_view/custom_drawable_size_view/DrawableEditText.kt
@@ -0,0 +1,75 @@
+package cn.qjm253.quick_android_custom_view.custom_drawable_size_view
+
+import android.content.Context
+import android.util.AttributeSet
+import androidx.appcompat.widget.AppCompatEditText
+import cn.qjm253.quick_android_custom_view.R
+
+/**
+ * 这是一个EditText的封装组件,支持自定义四个方向的drawable的大小
+ *
+ * 1. 使用 'left_drawable', 'right_drawable', 'top_drawable', 'bottom_drawable' 属性来设置四个方向的drawable资源
+ *
+ * 2. 使用 'drawable_width', 'drawable_height' 统一设置四个drawable的大小
+ *
+ * 3. 使用 'left_drawable_width', 'left_drawable_height', 'right_drawable_width', 'right_drawable_height',
+ * 'top_drawable_width', 'top_drawable_height', 'bottom_drawable_width', 'bottom_drawable_height'
+ * 来定制指定方向drawable的大小
+ *
+ * @author SunnyQjm
+ * @date 19-7-10 下午9:04
+ */
+
+open class DrawableEditText @JvmOverloads constructor(
+ context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = android.R.attr.editTextStyle
+) : AppCompatEditText(context, attrs, defStyleAttr) {
+ companion object {
+ const val DEFAULT_SIZE = 20
+ }
+
+ init {
+ val ta = context.obtainStyledAttributes(attrs, R.styleable.DrawableEditText)
+
+ val width = ta.getDimensionPixelOffset(R.styleable.DrawableEditText_drawable_width, DEFAULT_SIZE)
+ val height = ta.getDimensionPixelOffset(R.styleable.DrawableEditText_drawable_height, DEFAULT_SIZE)
+
+ val sizeWrap = SizeWrap()
+
+ val letfD = ta.getDrawable(R.styleable.DrawableEditText_left_drawable)
+ ?.apply {
+ val lwidth = ta.getDimensionPixelOffset(R.styleable.DrawableEditText_left_drawable_width, DEFAULT_SIZE)
+ val lheight =
+ ta.getDimensionPixelOffset(R.styleable.DrawableEditText_left_drawable_height, DEFAULT_SIZE)
+ sizeWrap.checkSize(width, height, lwidth, lheight)
+ setBounds(0, 0, sizeWrap.width, sizeWrap.height)
+ }
+ val rightD = ta.getDrawable(R.styleable.DrawableEditText_right_drawable)
+ ?.apply {
+ val lwidth = ta.getDimensionPixelOffset(R.styleable.DrawableEditText_right_drawable_width, DEFAULT_SIZE)
+ val lheight =
+ ta.getDimensionPixelOffset(R.styleable.DrawableEditText_right_drawable_height, DEFAULT_SIZE)
+ sizeWrap.checkSize(width, height, lwidth, lheight)
+ setBounds(0, 0, sizeWrap.width, sizeWrap.height)
+ }
+ val topD = ta.getDrawable(R.styleable.DrawableEditText_top_drawable)
+ ?.apply {
+ val lwidth = ta.getDimensionPixelOffset(R.styleable.DrawableEditText_top_drawable_width, DEFAULT_SIZE)
+ val lheight = ta.getDimensionPixelOffset(R.styleable.DrawableEditText_top_drawable_height, DEFAULT_SIZE)
+ sizeWrap.checkSize(width, height, lwidth, lheight)
+ setBounds(0, 0, sizeWrap.width, sizeWrap.height)
+ }
+ val bottomD = ta.getDrawable(R.styleable.DrawableEditText_bottom_drawable)
+ ?.apply {
+ val lwidth =
+ ta.getDimensionPixelOffset(R.styleable.DrawableEditText_bottom_drawable_width, DEFAULT_SIZE)
+ val lheight =
+ ta.getDimensionPixelOffset(R.styleable.DrawableEditText_bottom_drawable_height, DEFAULT_SIZE)
+ sizeWrap.checkSize(width, height, lwidth, lheight)
+ setBounds(0, 0, sizeWrap.width, sizeWrap.height)
+ }
+
+ this.setCompoundDrawables(letfD, topD, rightD, bottomD)
+
+ ta.recycle()
+ }
+}
\ No newline at end of file
diff --git a/quick_android_custom_view/src/main/java/cn/qjm253/quick_android_custom_view/custom_drawable_size_view/DrawableTextView.kt b/quick_android_custom_view/src/main/java/cn/qjm253/quick_android_custom_view/custom_drawable_size_view/DrawableTextView.kt
new file mode 100644
index 0000000..a6045d5
--- /dev/null
+++ b/quick_android_custom_view/src/main/java/cn/qjm253/quick_android_custom_view/custom_drawable_size_view/DrawableTextView.kt
@@ -0,0 +1,76 @@
+package cn.qjm253.quick_android_custom_view.custom_drawable_size_view
+
+import android.content.Context
+import android.util.AttributeSet
+import androidx.appcompat.widget.AppCompatTextView
+import cn.qjm253.quick_android_custom_view.R
+
+/**
+ * 这是一个TextView的封装组件,支持自定义四个方向的drawable的大小
+ *
+ * 1. 使用 'left_drawable', 'right_drawable', 'top_drawable', 'bottom_drawable' 属性来设置四个方向的drawable资源
+ *
+ * 2. 使用 'drawable_width', 'drawable_height' 统一设置四个drawable的大小
+ *
+ * 3. 使用 'left_drawable_width', 'left_drawable_height', 'right_drawable_width', 'right_drawable_height',
+ * 'top_drawable_width', 'top_drawable_height', 'bottom_drawable_width', 'bottom_drawable_height'
+ * 来定制指定方向drawable的大小
+ *
+ * @author SunnyQjm
+ * @date 19-7-10 下午8:48
+ */
+
+open class DrawableTextView @JvmOverloads constructor(
+ context: Context, attrs: AttributeSet? = null,
+ defStyleAttr: Int = android.R.attr.textViewStyle
+) : AppCompatTextView(context, attrs, defStyleAttr) {
+
+ companion object {
+ const val DRAWABLE_DEFAULT_SIZE = 20
+ }
+
+ init {
+ val ta = context.obtainStyledAttributes(attrs, R.styleable.DrawableTextView)
+
+ val width = ta.getDimensionPixelOffset(R.styleable.DrawableTextView_drawable_width, DRAWABLE_DEFAULT_SIZE)
+ val height = ta.getDimensionPixelOffset(R.styleable.DrawableTextView_drawable_height, DRAWABLE_DEFAULT_SIZE)
+
+ val sizeWrap = SizeWrap()
+
+ val letfD = ta.getDrawable(R.styleable.DrawableTextView_left_drawable)
+ ?.apply {
+ val lwidth = ta.getDimensionPixelOffset(R.styleable.DrawableTextView_left_drawable_width, DRAWABLE_DEFAULT_SIZE)
+ val lheight = ta.getDimensionPixelOffset(R.styleable.DrawableTextView_left_drawable_height, DRAWABLE_DEFAULT_SIZE)
+ sizeWrap.checkSize(width, height, lwidth, lheight)
+ setBounds(0, 0, sizeWrap.width, sizeWrap.height)
+ }
+ val rightD = ta.getDrawable(R.styleable.DrawableTextView_right_drawable)
+ ?.apply {
+ val lwidth = ta.getDimensionPixelOffset(R.styleable.DrawableTextView_right_drawable_width, DRAWABLE_DEFAULT_SIZE)
+ val lheight = ta.getDimensionPixelOffset(R.styleable.DrawableTextView_right_drawable_height, DRAWABLE_DEFAULT_SIZE)
+ sizeWrap.checkSize(width, height, lwidth, lheight)
+ setBounds(0, 0, sizeWrap.width, sizeWrap.height)
+ }
+ val topD = ta.getDrawable(R.styleable.DrawableTextView_top_drawable)
+ ?.apply {
+ val lwidth = ta.getDimensionPixelOffset(R.styleable.DrawableTextView_top_drawable_width, DRAWABLE_DEFAULT_SIZE)
+ val lheight = ta.getDimensionPixelOffset(R.styleable.DrawableTextView_top_drawable_height, DRAWABLE_DEFAULT_SIZE)
+ sizeWrap.checkSize(width, height, lwidth, lheight)
+ setBounds(0, 0, sizeWrap.width, sizeWrap.height)
+ }
+ val bottomD = ta.getDrawable(R.styleable.DrawableTextView_bottom_drawable)
+ ?.apply {
+ val lwidth = ta.getDimensionPixelOffset(R.styleable.DrawableTextView_bottom_drawable_width, DRAWABLE_DEFAULT_SIZE)
+ val lheight = ta.getDimensionPixelOffset(R.styleable.DrawableTextView_bottom_drawable_height, DRAWABLE_DEFAULT_SIZE)
+ sizeWrap.checkSize(width, height, lwidth, lheight)
+ setBounds(0, 0, sizeWrap.width, sizeWrap.height)
+ }
+
+ this.setCompoundDrawables(letfD, topD, rightD, bottomD)
+
+ ta.recycle()
+ }
+
+
+
+}
\ No newline at end of file
diff --git a/quick_android_custom_view/src/main/java/cn/qjm253/quick_android_custom_view/custom_drawable_size_view/SizeWrap.kt b/quick_android_custom_view/src/main/java/cn/qjm253/quick_android_custom_view/custom_drawable_size_view/SizeWrap.kt
new file mode 100644
index 0000000..2cdbd3e
--- /dev/null
+++ b/quick_android_custom_view/src/main/java/cn/qjm253/quick_android_custom_view/custom_drawable_size_view/SizeWrap.kt
@@ -0,0 +1,37 @@
+package cn.qjm253.quick_android_custom_view.custom_drawable_size_view
+
+/**
+ * 这是一个处理drawable自定义大小设置的工具类
+ *
+ * 1. 四个方向的drawable都有一个默认大小 DRAWABLE_DEFAULT_SIZE;
+ *
+ * 2. 使用 'drawable_width' 和 'drawable_height' 来设置全局大小,则四个方向的drawable均被设置;
+ *
+ * 3. 如果使用 'left_drawable_width' 和 'left_drawable_height' 这种单独设置某个方向的drawable会覆盖默认大小和全局大小
+ *
+ * @author SunnyQjm
+ * @date 19-7-10 下午9:00
+ */
+class SizeWrap(var width: Int = -1, var height: Int = -1) {
+
+ /**
+ * 检查用户是否自定义了drawable的size,按上述规则进行判断
+ */
+ fun checkSize(globalWidth: Int, globalHeight: Int, localWidth: Int, localHeight: Int) {
+ width = DrawableTextView.DRAWABLE_DEFAULT_SIZE
+ height = DrawableTextView.DRAWABLE_DEFAULT_SIZE
+
+ // 对某一个具体方向图标大小的设置会覆盖对全局图标大小的设置
+ if (localHeight > 0 && localWidth > 0) {
+ width = localWidth
+ height = localHeight
+ }
+
+
+ if (localHeight == DrawableTextView.DRAWABLE_DEFAULT_SIZE && localWidth == DrawableTextView.DRAWABLE_DEFAULT_SIZE
+ && globalHeight > 0 && globalWidth > 0) {
+ width = globalWidth
+ height = globalHeight
+ }
+ }
+}
\ No newline at end of file
diff --git a/quick_android_custom_view/src/main/java/cn/qjm253/quick_android_custom_view/other/ClearAbleEditText.kt b/quick_android_custom_view/src/main/java/cn/qjm253/quick_android_custom_view/other/ClearAbleEditText.kt
new file mode 100644
index 0000000..165d02a
--- /dev/null
+++ b/quick_android_custom_view/src/main/java/cn/qjm253/quick_android_custom_view/other/ClearAbleEditText.kt
@@ -0,0 +1,74 @@
+package cn.qjm253.quick_android_custom_view.other
+
+import android.content.Context
+import android.graphics.drawable.Drawable
+import android.text.Editable
+import android.text.TextWatcher
+import android.util.AttributeSet
+import android.view.MotionEvent
+import android.view.View
+import cn.qjm253.quick_android_custom_view.tin_view.TinEditText
+
+/**
+ * 这是一个可以显示清空按钮的EditText组件,right_drawable一般设置成有清空含义的图标
+ *
+ * @author SunnyQjm
+ * @date 19-7-14 下午8:01
+ */
+
+class ClearAbleEditText @JvmOverloads constructor(
+ context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = android.R.attr.editTextStyle
+) : TinEditText(context, attrs, defStyleAttr) {
+
+
+ init {
+ changeDrawables(right = null)
+ addTextChangedListener(object : TextWatcher {
+ override fun afterTextChanged(s: Editable?) {
+ }
+
+ override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {
+ }
+
+ override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
+ if (s?.length ?: 0 > 0) {
+ changeDrawables(right = mDrawables[2])
+ } else {
+ changeDrawables(right = null)
+ }
+ }
+
+ })
+ }
+
+ override fun onFocusChange(v: View?, hasFocus: Boolean) {
+ super.onFocusChange(v, hasFocus)
+ if (!hasFocus || text?.isEmpty() == true)
+ changeDrawables(right = null)
+ else
+ changeDrawables(right = mDrawables[2])
+ }
+
+ override fun onTouchEvent(event: MotionEvent?): Boolean {
+ if (event?.action == MotionEvent.ACTION_UP) {
+ //getTotalPaddingRight()图标左边缘至控件右边缘的距离
+ //getWidth() - getTotalPaddingRight()表示从最左边到图标左边缘的位置
+ //getWidth() - getPaddingRight()表示最左边到图标右边缘的位置
+ val touchable = event.x > width - totalPaddingRight && event.x < width - paddingRight
+
+ if (touchable) {
+ this.setText("")
+ }
+ }
+ return super.onTouchEvent(event)
+ }
+
+ private fun changeDrawables(
+ left: Drawable? = mDrawables[0],
+ top: Drawable? = mDrawables[1],
+ right: Drawable? = mDrawables[2],
+ bottom: Drawable? = mDrawables[3]
+ ) {
+ setCompoundDrawables(left, top, right, bottom)
+ }
+}
\ No newline at end of file
diff --git a/quick_android_custom_view/src/main/java/cn/qjm253/quick_android_custom_view/other/LineMenu.kt b/quick_android_custom_view/src/main/java/cn/qjm253/quick_android_custom_view/other/LineMenu.kt
new file mode 100644
index 0000000..20e1ea5
--- /dev/null
+++ b/quick_android_custom_view/src/main/java/cn/qjm253/quick_android_custom_view/other/LineMenu.kt
@@ -0,0 +1,37 @@
+package cn.qjm253.quick_android_custom_view.other
+
+import android.widget.ImageView
+import android.widget.TextView
+import androidx.annotation.DrawableRes
+
+
+/**
+ * @author SunnyQjm
+ * @date 19-7-14 下午8:13
+ */
+interface LineMenu {
+ fun getLeft_icon(): ImageView?
+
+ fun getRight_icon(): ImageView?
+
+ fun getTv_title(): TextView?
+
+ fun getTv_value(): TextView?
+
+ fun setLeftIcon(@DrawableRes res: Int)
+
+ fun setRightIcon(@DrawableRes res: Int)
+
+
+ fun setRightIconVisible(visible: Int)
+
+ fun setLeftIconVisible(visible: Int)
+
+ fun setTitleIconVisible(visible: Int)
+
+ fun setValueIconVisible(visible: Int)
+
+ fun setTitle(title: String)
+
+ fun setValue(value: String)
+}
\ No newline at end of file
diff --git a/quick_android_custom_view/src/main/java/cn/qjm253/quick_android_custom_view/other/LineMenuItem.kt b/quick_android_custom_view/src/main/java/cn/qjm253/quick_android_custom_view/other/LineMenuItem.kt
new file mode 100644
index 0000000..312a091
--- /dev/null
+++ b/quick_android_custom_view/src/main/java/cn/qjm253/quick_android_custom_view/other/LineMenuItem.kt
@@ -0,0 +1,271 @@
+package cn.qjm253.quick_android_custom_view.other
+
+import android.content.Context
+import android.util.AttributeSet
+import android.view.View
+import android.view.ViewGroup
+import android.widget.ImageView
+import android.widget.RelativeLayout
+import android.widget.TextView
+import androidx.annotation.ColorInt
+import androidx.annotation.DrawableRes
+import cn.qjm253.quick_android_base.util.GeneratedId
+import cn.qjm253.quick_android_custom_view.R
+
+/**
+ * 菜单(可以设置图标)
+ *
+ * @author SunnyQjm
+ * @date 19-7-14 下午8:14
+ */
+
+class LineMenuItem @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null,
+ defStyleAttr: Int = 0) : RelativeLayout(context, attrs, defStyleAttr), LineMenu {
+
+ private var myContext: Context? = null
+ private var menuTitle: String? = null
+ private var menuValue: String? = null
+ private var menuTitleSize: Float = 0.toFloat()
+ @ColorInt
+ private var menuTitleColor: Int = 0
+ private var menuValueSize: Float = 0.toFloat()
+ @ColorInt
+ private var menuValueColor: Int = 0
+ @DrawableRes
+ private var leftResource: Int = 0
+ private var rightResource: Int = 0
+
+ private var isRightIconVisible: Boolean = false
+ private var isLeftIconVisible: Boolean = false
+
+ private var leftIconSize: Int = 0
+ private var rightIconSize: Int = 0
+
+ private var leftMargin: Int = 0
+ private var rightMargin: Int = 0
+ private var leftIconTextSpan: Int = 0
+
+ private var leftIcon: ImageView? = null
+ private var rightIcon: ImageView? = null
+ private var tvTitle: TextView? = null
+ private var tvValue: TextView? = null
+
+ init {
+ this.myContext = context
+ getAttr(context, attrs, defStyleAttr)
+ initView()
+ }
+
+ private fun getAttr(context: Context, attrs: AttributeSet?, defStyleAttr: Int) {
+ val ta = context.obtainStyledAttributes(attrs, R.styleable.LineMenuItem)
+ menuTitle = ta.getString(R.styleable.LineMenuItem_menu_title)
+ menuValue = ta.getString(R.styleable.LineMenuItem_menu_value)
+ menuTitleSize = px2sp(context,
+ ta.getDimensionPixelSize(R.styleable.LineMenuItem_menu_title_size,
+ sp2px(context, DEFAULT_TEXT_SIZE.toFloat())).toFloat()).toFloat()
+ menuTitleColor = ta.getColor(R.styleable.LineMenuItem_menu_title_color,
+ resources.getColor(DEFAULT_TITLE_COLOR))
+ menuValueSize = px2sp(context,
+ ta.getDimension(R.styleable.LineMenuItem_menu_value_size,
+ sp2px(context, DEFAULT_TEXT_SIZE.toFloat()).toFloat())).toFloat()
+ menuValueColor = ta.getColor(R.styleable.LineMenuItem_menu_value_color,
+ resources.getColor(DEFAULT_TITLE_COLOR))
+ leftResource = ta.getResourceId(R.styleable.LineMenuItem_left_resource, DEFAULT_LEFT_RESOURCE)
+ rightResource = ta.getResourceId(R.styleable.LineMenuItem_right_resource, DEFAULT_RIGHT_RESOURCE)
+ leftIconSize = ta.getDimensionPixelSize(R.styleable.LineMenuItem_left_icon_size,
+ dip2px(context, DEFAULT_LEFT_ICON_SIZE.toFloat()))
+ rightIconSize = ta.getDimensionPixelSize(R.styleable.LineMenuItem_right_icon_size,
+ dip2px(context, DEFAULT_RIGHT_ICON_SIZE.toFloat()))
+ isRightIconVisible = ta
+ .getBoolean(R.styleable.LineMenuItem_is_right_icon_visible, true)
+ isLeftIconVisible = ta
+ .getBoolean(R.styleable.LineMenuItem_is_left_icon_visible, false)
+ leftMargin = ta.getDimensionPixelSize(R.styleable.LineMenuItem_left_margin, dip2px(context, DEFAULT_MARGIN))
+ rightMargin = ta.getDimensionPixelOffset(R.styleable.LineMenuItem_right_margin, dip2px(context, DEFAULT_MARGIN))
+ leftIconTextSpan = ta.getDimensionPixelOffset(R.styleable.LineMenuItem_left_icon_text_span,
+ dip2px(context, DEFAULT_MARGIN))
+ ta.recycle()
+ }
+
+ private fun initView() {
+ leftIcon = ImageView(myContext)
+ leftIcon!!.setImageResource(leftResource)
+ leftIcon!!.scaleType = ImageView.ScaleType.FIT_CENTER
+ leftIcon!!.setBackgroundResource(R.color.transparent)
+ val left_icon_param = LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
+ ViewGroup.LayoutParams.WRAP_CONTENT)
+ left_icon_param.addRule(CENTER_VERTICAL)
+ left_icon_param.width = leftIconSize
+ left_icon_param.height = leftIconSize
+ left_icon_param.leftMargin = leftMargin
+ leftIcon!!.layoutParams = left_icon_param
+ addView(leftIcon, left_icon_param)
+ if (!isLeftIconVisible) {
+ leftIcon!!.visibility = View.GONE
+ }
+ val left_icon_id = GeneratedId.generateViewId()
+ leftIcon!!.id = left_icon_id
+
+ tvTitle = TextView(myContext)
+ tvTitle!!.text = menuTitle
+ tvTitle!!.setTextColor(menuTitleColor)
+ tvTitle!!.textSize = menuTitleSize
+ tvTitle!!.setBackgroundResource(R.color.transparent)
+ val title_param = LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
+ ViewGroup.LayoutParams.WRAP_CONTENT)
+ title_param.addRule(CENTER_VERTICAL)
+ title_param.leftMargin = leftIconTextSpan
+ title_param.addRule(RIGHT_OF, left_icon_id)
+ tvTitle!!.layoutParams = title_param
+ addView(tvTitle, title_param)
+
+
+ rightIcon = ImageView(myContext)
+ rightIcon!!.setImageResource(rightResource)
+ rightIcon!!.scaleType = ImageView.ScaleType.FIT_CENTER
+ rightIcon!!.setBackgroundResource(R.color.transparent)
+ val right_icon_param = LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
+ ViewGroup.LayoutParams.WRAP_CONTENT)
+ right_icon_param.addRule(CENTER_VERTICAL)
+ right_icon_param.addRule(ALIGN_PARENT_RIGHT)
+ right_icon_param.width = rightIconSize
+ right_icon_param.height = rightIconSize
+ right_icon_param.rightMargin = rightMargin
+ rightIcon!!.layoutParams = right_icon_param
+ addView(rightIcon, right_icon_param)
+ val right_icon_id = GeneratedId.generateViewId()
+ rightIcon!!.id = right_icon_id
+
+ tvValue = TextView(myContext)
+ tvValue!!.text = menuValue
+ tvValue!!.setTextColor(menuValueColor)
+ tvValue!!.textSize = menuValueSize
+ tvValue!!.setBackgroundResource(R.color.transparent)
+ val tv_value_param = LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
+ ViewGroup.LayoutParams.WRAP_CONTENT)
+ tv_value_param.addRule(CENTER_VERTICAL)
+ if (isRightIconVisible)
+ tv_value_param.addRule(LEFT_OF, right_icon_id)
+ else
+ tv_value_param.addRule(ALIGN_PARENT_RIGHT)
+ tv_value_param.rightMargin = dip2px(myContext!!, DEFAULT_MARGIN)
+ tvValue!!.layoutParams = tv_value_param
+ addView(tvValue, tv_value_param)
+
+ if (isRightIconVisible)
+ setRightIconVisible(View.VISIBLE)
+ else
+ setRightIconVisible(View.INVISIBLE)
+ }
+
+ override fun getLeft_icon(): ImageView? {
+ return leftIcon
+ }
+
+ override fun getRight_icon(): ImageView? {
+ return rightIcon
+ }
+
+ override fun getTv_title(): TextView? {
+ return tvTitle
+ }
+
+ override fun getTv_value(): TextView? {
+ return tvValue
+ }
+
+ fun value(): String {
+ return getTv_value()?.text?.toString() ?: ""
+ }
+
+ fun title(): String {
+ return getTv_title()?.text?.toString() ?: ""
+ }
+
+ override fun setLeftIcon(@DrawableRes res: Int) {
+ this.post {
+ leftResource = res
+ leftIcon!!.setImageResource(leftResource)
+ }
+ }
+
+ override fun setRightIcon(@DrawableRes res: Int) {
+ this.post {
+ rightResource = res
+ rightIcon!!.setImageResource(rightResource)
+ }
+ }
+
+ override fun setRightIconVisible(visible: Int) {
+ if (rightIcon != null)
+ rightIcon!!.visibility = visible
+ }
+
+ override fun setLeftIconVisible(visible: Int) {
+ if (leftIcon != null)
+ leftIcon!!.visibility = visible
+ }
+
+ override fun setTitleIconVisible(visible: Int) {
+ if (tvTitle != null)
+ tvTitle!!.visibility = visible
+ }
+
+ override fun setValueIconVisible(visible: Int) {
+ if (tvValue != null)
+ tvValue!!.visibility = visible
+ }
+
+ override fun setTitle(title: String) {
+ if (tvTitle != null)
+ tvTitle!!.text = title
+ }
+
+ override fun setValue(value: String) {
+ if (tvValue != null)
+ tvValue!!.text = value
+ }
+
+ companion object {
+
+ private val DEFAULT_TEXT_SIZE = 14
+ private val DEFAULT_MARGIN = 13f
+
+ private val DEFAULT_TITLE_COLOR = R.color.text_gray
+ private val DEFAULT_LEFT_RESOURCE = R.drawable.menu
+ private val DEFAULT_RIGHT_RESOURCE = R.drawable.icon_arrow
+ private val DEFAULT_LEFT_ICON_SIZE = 24
+ private val DEFAULT_RIGHT_ICON_SIZE = 24
+ }
+
+
+ /**
+ * 根据手机的分辨率从 dp 的单位 转成为 px(像素)
+ */
+ fun dip2px(context: Context, dpValue: Float): Int {
+ val scale = context.resources.displayMetrics.density
+ return (dpValue * scale + 0.5f).toInt()
+ }
+
+ /**
+ * 将px值转换为sp值,保证文字大小不变
+ *
+ * @param pxValue (DisplayMetrics类中属性scaledDensity)
+ * @return
+ */
+ fun px2sp(context: Context, pxValue: Float): Int {
+ val fontScale = context.resources.displayMetrics.scaledDensity
+ return (pxValue / fontScale + 0.5f).toInt()
+ }
+
+ /**
+ * 将sp值转换为px值,保证文字大小不变
+ *
+ * @param spValue (DisplayMetrics类中属性scaledDensity)
+ * @return
+ */
+ fun sp2px(context: Context, spValue: Float): Int {
+ val fontScale = context.resources.displayMetrics.scaledDensity
+ return (spValue * fontScale + 0.5f).toInt()
+ }
+}
\ No newline at end of file
diff --git a/quick_android_custom_view/src/main/java/cn/qjm253/quick_android_custom_view/other/LineMenuSwitch.kt b/quick_android_custom_view/src/main/java/cn/qjm253/quick_android_custom_view/other/LineMenuSwitch.kt
new file mode 100644
index 0000000..46d6a85
--- /dev/null
+++ b/quick_android_custom_view/src/main/java/cn/qjm253/quick_android_custom_view/other/LineMenuSwitch.kt
@@ -0,0 +1,154 @@
+package cn.qjm253.quick_android_custom_view.other
+
+import android.widget.CompoundButton
+import android.content.Context
+import android.widget.RelativeLayout
+import android.os.Build
+import android.util.AttributeSet
+import android.view.ViewGroup
+import android.widget.Switch
+import android.widget.TextView
+import androidx.annotation.ColorInt
+import androidx.annotation.DrawableRes
+import cn.qjm253.quick_android_custom_view.R
+
+
+/**
+ * @author SunnyQjm
+ * @date 19-7-14 下午8:30
+ */
+class LineMenuSwitch @JvmOverloads constructor(
+ context: Context,
+ attrs: AttributeSet? = null,
+ defStyleAttr: Int = 0
+) : RelativeLayout(context, attrs, defStyleAttr) {
+ private var menuTitle: String? = null
+ @ColorInt
+ private var menuTitleColor: Int = 0
+ private var menuTitleSize: Float = 0.toFloat()
+
+ @DrawableRes
+ private var switchThumb: Int = 0
+ @DrawableRes
+ private var switchTrack: Int = 0
+
+ private var leftMargin: Int = 0
+ private var rightMargin: Int = 0
+
+ private var tv_title: TextView? = null
+ private var aSwitch: Switch? = null
+ private var checked = false
+ private var onCheckedChangeListener: CompoundButton.OnCheckedChangeListener? = null
+
+ init {
+ getAttr(context, attrs, defStyleAttr)
+ initView()
+ }
+
+ private fun initView() {
+ tv_title = TextView(context)
+ tv_title!!.text = menuTitle
+ tv_title!!.setTextColor(menuTitleColor)
+ tv_title!!.textSize = menuTitleSize
+ tv_title!!.setBackgroundResource(R.color.transparent)
+ val title_param = LayoutParams(
+ ViewGroup.LayoutParams.WRAP_CONTENT,
+ ViewGroup.LayoutParams.WRAP_CONTENT
+ )
+ title_param.addRule(CENTER_VERTICAL)
+ title_param.leftMargin = leftMargin
+ tv_title!!.layoutParams = title_param
+ addView(tv_title, title_param)
+
+ aSwitch = Switch(context)
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
+ aSwitch!!.setThumbResource(switchThumb)
+ aSwitch!!.setTrackResource(switchTrack)
+ }
+ val switch_param = LayoutParams(
+ ViewGroup.LayoutParams.WRAP_CONTENT,
+ ViewGroup.LayoutParams.WRAP_CONTENT
+ )
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
+ switch_param.addRule(ALIGN_PARENT_END)
+ }
+ switch_param.addRule(ALIGN_PARENT_RIGHT)
+ switch_param.addRule(CENTER_VERTICAL)
+ switch_param.rightMargin = rightMargin
+ addView(aSwitch, switch_param)
+ aSwitch!!.isChecked = checked
+ aSwitch!!.setOnCheckedChangeListener(onCheckedChangeListener)
+ }
+
+ private fun getAttr(context: Context, attrs: AttributeSet?, defStyleAttr: Int) {
+ val ta = context.obtainStyledAttributes(attrs, R.styleable.LineMenuSwitch)
+ menuTitle = ta.getString(R.styleable.LineMenuSwitch_menu_title)
+ menuTitleSize = ta.getFloat(R.styleable.LineMenuSwitch_menu_title_size, DEFAULT_TEXT_SIZE.toFloat())
+ menuTitleColor = ta.getColor(
+ R.styleable.LineMenuSwitch_menu_title_color,
+ resources.getColor(DEFAULT_TITLE_COLOR)
+ )
+ leftMargin = ta.getDimensionPixelSize(
+ R.styleable.LineMenuSwitch_left_margin,
+ dip2px(context, DEFAULT_MARGIN)
+ )
+ rightMargin = ta.getDimensionPixelOffset(
+ R.styleable.LineMenuSwitch_right_margin,
+ dip2px(context, DEFAULT_MARGIN)
+ )
+ switchThumb = ta.getResourceId(R.styleable.LineMenuSwitch_switch_thumb, R.drawable.switch_thumb)
+ switchTrack = ta.getResourceId(R.styleable.LineMenuSwitch_switch_track, R.drawable.switch_track)
+ ta.recycle()
+ }
+
+ fun setChecked(checked: Boolean) {
+ this.checked = checked
+ if (aSwitch != null)
+ aSwitch!!.isChecked = checked
+ }
+
+ fun setOnCheckedChangeListener(onCheckedChangeListener: CompoundButton.OnCheckedChangeListener) {
+ this.onCheckedChangeListener = onCheckedChangeListener
+ if (aSwitch != null)
+ aSwitch!!.setOnCheckedChangeListener(onCheckedChangeListener)
+ }
+
+ companion object {
+
+ private val DEFAULT_TEXT_SIZE = 14
+ private val DEFAULT_MARGIN = 13f
+
+ private val DEFAULT_TITLE_COLOR = R.color.text_gray
+ }
+
+
+ /**
+ * 根据手机的分辨率从 dp 的单位 转成为 px(像素)
+ */
+ fun dip2px(context: Context, dpValue: Float): Int {
+ val scale = context.resources.displayMetrics.density
+ return (dpValue * scale + 0.5f).toInt()
+ }
+
+ /**
+ * 将px值转换为sp值,保证文字大小不变
+ *
+ * @param pxValue (DisplayMetrics类中属性scaledDensity)
+ * @return
+ */
+ fun px2sp(context: Context, pxValue: Float): Int {
+ val fontScale = context.resources.displayMetrics.scaledDensity
+ return (pxValue / fontScale + 0.5f).toInt()
+ }
+
+ /**
+ * 将sp值转换为px值,保证文字大小不变
+ *
+ * @param spValue (DisplayMetrics类中属性scaledDensity)
+ * @return
+ */
+ fun sp2px(context: Context, spValue: Float): Int {
+ val fontScale = context.resources.displayMetrics.scaledDensity
+ return (spValue * fontScale + 0.5f).toInt()
+ }
+}
\ No newline at end of file
diff --git a/quick_android_custom_view/src/main/java/cn/qjm253/quick_android_custom_view/other/NoneScrollViewPager.kt b/quick_android_custom_view/src/main/java/cn/qjm253/quick_android_custom_view/other/NoneScrollViewPager.kt
new file mode 100644
index 0000000..bfb8e69
--- /dev/null
+++ b/quick_android_custom_view/src/main/java/cn/qjm253/quick_android_custom_view/other/NoneScrollViewPager.kt
@@ -0,0 +1,58 @@
+package cn.qjm253.quick_android_custom_view.other
+
+import android.content.Context
+import android.util.AttributeSet
+import androidx.viewpager.widget.ViewPager
+import android.view.MotionEvent
+
+
+
+/**
+ * @author SunnyQjm
+ * @date 19-7-14 下午7:52
+ */
+
+class NoneScrollViewPager(context: Context, attrs: AttributeSet? = null) : ViewPager(context, attrs) {
+ private var isScroll = false
+ init {
+
+ }
+
+ /**
+ * 是否拦截
+ * 拦截:会走到自己的onTouchEvent方法里面来
+ * 不拦截:事件传递给子孩子
+ */
+ override fun onInterceptTouchEvent(ev: MotionEvent): Boolean {
+ // return false;//可行,不拦截事件,
+ // return true;//不行,孩子无法处理事件
+ //return super.onInterceptTouchEvent(ev);//不行,会有细微移动
+ return if (isScroll) {
+ super.onInterceptTouchEvent(ev)
+ } else {
+ false
+ }
+ }
+
+ /**
+ * 是否消费事件
+ * 消费:事件就结束
+ * 不消费:往父控件传
+ */
+ override fun onTouchEvent(ev: MotionEvent): Boolean {
+ //return false;// 可行,不消费,传给父控件
+ //return true;// 可行,消费,拦截事件
+ //super.onTouchEvent(ev); //不行,
+ //虽然onInterceptTouchEvent中拦截了,
+ //但是如果viewpage里面子控件不是viewgroup,还是会调用这个方法.
+ return if (isScroll) {
+ super.onTouchEvent(ev)
+ } else {
+ true// 可行,消费,拦截事件
+ }
+ }
+
+ fun setScroll(scroll: Boolean) {
+ isScroll = scroll
+ }
+}
\ No newline at end of file
diff --git a/quick_android_custom_view/src/main/java/cn/qjm253/quick_android_custom_view/tin_view/TinButton.kt b/quick_android_custom_view/src/main/java/cn/qjm253/quick_android_custom_view/tin_view/TinButton.kt
new file mode 100644
index 0000000..dd1ef02
--- /dev/null
+++ b/quick_android_custom_view/src/main/java/cn/qjm253/quick_android_custom_view/tin_view/TinButton.kt
@@ -0,0 +1,112 @@
+package cn.qjm253.quick_android_custom_view.tin_view
+
+import android.content.Context
+import android.content.res.ColorStateList
+import android.graphics.drawable.Drawable
+import android.util.AttributeSet
+import androidx.annotation.ColorRes
+import androidx.core.content.ContextCompat
+import androidx.core.graphics.drawable.DrawableCompat
+import cn.qjm253.quick_android_custom_view.R
+import cn.qjm253.quick_android_custom_view.custom_drawable_size_view.DrawableButton
+
+/**
+ * 这是一个 DrawableButton 的封装组件,在DrawableButton的基础上支持设置drawable的tinColor
+ *
+ * @see DrawableButton
+ * @author SunnyQjm
+ * @date 19-7-10 下午10:33
+ */
+class TinButton @JvmOverloads constructor(
+ context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = android.R.attr.buttonStyle
+): DrawableButton(context, attrs, defStyleAttr) {
+ companion object {
+ const val NONE = 0x0000
+ const val LEFT = 0x0001
+ const val TOP = 0x0002
+ const val RIGHT = 0x0004
+ const val BOTTOM = 0x0008
+ const val ALL = 0x000f
+ }
+
+
+ private var tints: Int = TinTextView.NONE
+ private lateinit var mDrawables: Array
+
+ init {
+ if (!isInEditMode) {
+ mDrawables = compoundDrawables
+ loadFromAttribute(context, attrs, defStyleAttr)
+ }
+ }
+
+ /**
+ * 从布局文件中加载属性配置
+ */
+ private fun loadFromAttribute(context: Context, attrs: AttributeSet?, defStyleAttr: Int) {
+ attrs?.let {
+ val ta = context.obtainStyledAttributes(it, R.styleable.TinButton)
+ tints = ta.getInt(R.styleable.TinButton_tints, ALL)
+ ta.getColorStateList(R.styleable.TinButton_tinColor)?.let { tinColorStateList ->
+ updateDrawablesTinColor(tinColorStateList)
+ }
+ ta.recycle()
+ }
+ }
+
+ /**
+ * 设置哪些方向的drawable需要设置tinColor
+ *
+ * @see TinButton.NONE -> 四个方向都不着色
+ * @see TinButton.LEFT -> 左边drawable着色
+ * @see TinButton.RIGHT -> 右边drawable着色
+ * @see TinButton.TOP -> 顶部drawable着色
+ * @see TinButton.BOTTOM -> 底部drawable着色
+ * @see TinButton.ALL -> 四个方向都着色
+ *
+ * 如果需要单独设置两个方向可以 => TinButton.LEFT or TinButton.RIGHT
+ *
+ */
+ fun updateTints(tints: Int) {
+ if (tints < 0 || tints > 8)
+ return
+ this.tints = tints
+ }
+
+ /**
+ * 设置tinColor的颜色
+ */
+ private fun updateDrawablesTinColor(colorStateList: ColorStateList) {
+ //设置icon
+ if (mDrawables[0] != null && (tints and TinTextView.LEFT) == TinTextView.LEFT) {
+ mDrawables[0] = DrawableCompat.wrap(mDrawables[0]!!.mutate())
+ DrawableCompat.setTintList(mDrawables[0]!!, colorStateList)
+ }
+
+ if (mDrawables[1] != null && (tints and TinTextView.TOP) == TinTextView.TOP) {
+ mDrawables[1] = DrawableCompat.wrap(mDrawables[1]!!.mutate())
+ DrawableCompat.setTintList(mDrawables[1]!!, colorStateList)
+ }
+
+ if (mDrawables[2] != null && (tints and TinTextView.RIGHT) == TinTextView.RIGHT) {
+ mDrawables[2] = DrawableCompat.wrap(mDrawables[2]!!.mutate())
+ DrawableCompat.setTintList(mDrawables[2]!!, colorStateList)
+ }
+
+ if (mDrawables[3] != null && (tints and TinTextView.BOTTOM) == TinTextView.BOTTOM) {
+ mDrawables[3] = DrawableCompat.wrap(mDrawables[3]!!.mutate())
+ DrawableCompat.setTintList(mDrawables[3]!!, colorStateList)
+ }
+ setCompoundDrawables(mDrawables[0], mDrawables[1], mDrawables[2], mDrawables[3])
+ }
+
+ /**
+ * 设置tinColor的颜色
+ */
+ fun updateDrawablesTinColor(@ColorRes color: Int) {
+ ContextCompat.getColorStateList(context, color)?.let {
+ updateDrawablesTinColor(it)
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/quick_android_custom_view/src/main/java/cn/qjm253/quick_android_custom_view/tin_view/TinEditText.kt b/quick_android_custom_view/src/main/java/cn/qjm253/quick_android_custom_view/tin_view/TinEditText.kt
new file mode 100644
index 0000000..2fb7053
--- /dev/null
+++ b/quick_android_custom_view/src/main/java/cn/qjm253/quick_android_custom_view/tin_view/TinEditText.kt
@@ -0,0 +1,146 @@
+package cn.qjm253.quick_android_custom_view.tin_view
+
+import android.content.Context
+import android.content.res.ColorStateList
+import android.graphics.drawable.Drawable
+import android.util.AttributeSet
+import android.view.View
+import androidx.annotation.ColorRes
+import androidx.core.content.ContextCompat
+import androidx.core.graphics.drawable.DrawableCompat
+import cn.qjm253.quick_android_custom_view.R
+import cn.qjm253.quick_android_custom_view.custom_drawable_size_view.DrawableEditText
+
+/**
+ * 这是一个 DrawableEditText 的封装组件,在 DrawableEditText 的基础上支持设置drawable的tinColor
+ *
+ * @see DrawableEditText
+ * @author SunnyQjm
+ * @date 19-7-10 下午10:15
+ */
+
+open class TinEditText @JvmOverloads constructor(
+ context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = android.R.attr.editTextStyle
+) : DrawableEditText(context, attrs, defStyleAttr), View.OnFocusChangeListener {
+
+
+ companion object {
+ const val NONE = 0x0000
+ const val LEFT = 0x0001
+ const val TOP = 0x0002
+ const val RIGHT = 0x0004
+ const val BOTTOM = 0x0008
+ const val ALL = 0x000f
+ }
+
+
+ private var tints: Int = NONE
+
+ private var tinColor: ColorStateList? = null
+ private var focusedTinColor: ColorStateList? = null
+
+ protected lateinit var mDrawables: Array
+
+ init {
+
+ if (!isInEditMode) {
+ mDrawables = compoundDrawables
+ loadFromAttribute(context, attrs, defStyleAttr)
+ this.onFocusChangeListener = this
+ }
+ }
+
+ /**
+ * 从布局文件中加载属性配置
+ */
+ private fun loadFromAttribute(context: Context, attrs: AttributeSet?, defStyleAttr: Int) {
+ attrs?.let {
+ val ta = context.obtainStyledAttributes(it, R.styleable.TinEditText)
+ tints = ta.getInt(R.styleable.TinEditText_tints, ALL)
+ tinColor = ta.getColorStateList(R.styleable.TinEditText_tinColor)
+ focusedTinColor = ta.getColorStateList(R.styleable.TinEditText_focusedTinColor)
+ ta.recycle()
+ }
+
+ // 根绝当前是否聚焦,修改tinColor
+ onFocusChange(this, isFocused)
+ }
+
+ /**
+ * 设置哪些方向的drawable需要设置tinColor
+ *
+ * @see TinEditText.NONE -> 四个方向都不着色
+ * @see TinEditText.LEFT -> 左边drawable着色
+ * @see TinEditText.RIGHT -> 右边drawable着色
+ * @see TinEditText.TOP -> 顶部drawable着色
+ * @see TinEditText.BOTTOM -> 底部drawable着色
+ * @see TinEditText.ALL -> 四个方向都着色
+ *
+ * 如果需要单独设置两个方向可以 => TinEditText.LEFT or TinEditText.RIGHT
+ *
+ */
+ fun updateTints(tints: Int) {
+ if (tints < 0 || tints > 8)
+ return
+ this.tints = tints
+ }
+
+ /**
+ * 设置tinColor的颜色
+ */
+ private fun updateDrawablesTinColor(colorStateList: ColorStateList?) {
+ colorStateList?.let {
+ //设置icon
+ if (mDrawables[0] != null && (tints and LEFT) == LEFT) {
+ mDrawables[0] = DrawableCompat.wrap(mDrawables[0]!!.mutate())
+ DrawableCompat.setTintList(mDrawables[0]!!, colorStateList)
+ }
+
+ if (mDrawables[1] != null && (tints and TinTextView.TOP) == TinTextView.TOP) {
+ mDrawables[1] = DrawableCompat.wrap(mDrawables[1]!!.mutate())
+ DrawableCompat.setTintList(mDrawables[1]!!, colorStateList)
+ }
+
+ if (mDrawables[2] != null && (tints and RIGHT) == RIGHT) {
+ mDrawables[2] = DrawableCompat.wrap(mDrawables[2]!!.mutate())
+ DrawableCompat.setTintList(mDrawables[2]!!, colorStateList)
+ }
+
+ if (mDrawables[3] != null && (tints and BOTTOM) == BOTTOM) {
+ mDrawables[3] = DrawableCompat.wrap(mDrawables[3]!!.mutate())
+ DrawableCompat.setTintList(mDrawables[3]!!, colorStateList)
+ }
+ setCompoundDrawables(mDrawables[0], mDrawables[1], mDrawables[2], mDrawables[3])
+ }
+ }
+
+
+ /**
+ * 设置tinColor的颜色
+ */
+ fun updateDrawablesTinColor(@ColorRes color: Int) {
+ this.tinColor = ContextCompat.getColorStateList(context, color)
+ onFocusChange(this, isFocused)
+ }
+
+
+ /**
+ * 更新聚焦状态下 drawable 的颜色
+ */
+ fun updateDrawablesFocusedTinColor(@ColorRes color: Int) {
+ this.focusedTinColor = ContextCompat.getColorStateList(context, color)
+ onFocusChange(this, isFocused)
+ }
+
+
+ /**
+ * 处理EditText焦点变化的时候修改 drawable 的 tinColor
+ */
+ override fun onFocusChange(v: View?, hasFocus: Boolean) {
+ if (hasFocus) {
+ updateDrawablesTinColor(focusedTinColor)
+ } else {
+ updateDrawablesTinColor(tinColor)
+ }
+ }
+}
\ No newline at end of file
diff --git a/quick_android_custom_view/src/main/java/cn/qjm253/quick_android_custom_view/tin_view/TinImageView.kt b/quick_android_custom_view/src/main/java/cn/qjm253/quick_android_custom_view/tin_view/TinImageView.kt
new file mode 100644
index 0000000..6bd69b2
--- /dev/null
+++ b/quick_android_custom_view/src/main/java/cn/qjm253/quick_android_custom_view/tin_view/TinImageView.kt
@@ -0,0 +1,65 @@
+package cn.qjm253.quick_android_custom_view.tin_view
+
+import android.content.Context
+import android.content.res.ColorStateList
+import android.graphics.drawable.Drawable
+import android.util.AttributeSet
+import androidx.annotation.ColorRes
+import androidx.appcompat.widget.AppCompatImageView
+import androidx.core.content.ContextCompat
+import androidx.core.graphics.drawable.DrawableCompat
+import cn.qjm253.quick_android_custom_view.R
+
+/**
+ * 这是一个 DrawableButton 的封装组件,在AppCompatImageView的基础上支持设置drawable的tinColor
+ *
+ * @author SunnyQjm
+ * @date 19-7-11 下午4:46
+ */
+
+class TinImageView @JvmOverloads constructor(
+ context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
+) : AppCompatImageView(context, attrs, defStyleAttr) {
+
+ private var mDrawable: Drawable? = drawable
+
+ init {
+ loadFromAttribute(context, attrs, defStyleAttr)
+ }
+
+ /**
+ * 从布局文件中加载属性配置
+ */
+ private fun loadFromAttribute(context: Context, attrs: AttributeSet?, defStyleAttr: Int) {
+ attrs?.let {
+ val ta = context.obtainStyledAttributes(it, R.styleable.TinImageView)
+ ta.getColorStateList(R.styleable.TinImageView_tinColor)?.let { tinColorStateList ->
+ updateDrawablesTinColor(tinColorStateList)
+ }
+ ta.recycle()
+ }
+ }
+
+ /**
+ * 设置tinColor的颜色
+ */
+ fun updateDrawablesTinColor(@ColorRes color: Int) {
+ updateDrawablesTinColor(ContextCompat.getColorStateList(context, color))
+ }
+
+ /**
+ * 设置tinColor的颜色
+ */
+ private fun updateDrawablesTinColor(colorStateList: ColorStateList?) {
+ if (mDrawable == null) {
+ mDrawable = drawable
+ }
+ colorStateList?.let {
+ mDrawable?.let { _mDrawable ->
+ mDrawable = DrawableCompat.wrap(_mDrawable.mutate())
+ DrawableCompat.setTintList(mDrawable!!, colorStateList)
+ setImageDrawable(mDrawable)
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/quick_android_custom_view/src/main/java/cn/qjm253/quick_android_custom_view/tin_view/TinTextView.kt b/quick_android_custom_view/src/main/java/cn/qjm253/quick_android_custom_view/tin_view/TinTextView.kt
new file mode 100644
index 0000000..57fa80d
--- /dev/null
+++ b/quick_android_custom_view/src/main/java/cn/qjm253/quick_android_custom_view/tin_view/TinTextView.kt
@@ -0,0 +1,114 @@
+package cn.qjm253.quick_android_custom_view.tin_view
+
+import android.content.Context
+import android.content.res.ColorStateList
+import android.graphics.drawable.Drawable
+import android.util.AttributeSet
+import androidx.annotation.ColorRes
+import androidx.core.content.ContextCompat
+import androidx.core.graphics.drawable.DrawableCompat
+import cn.qjm253.quick_android_custom_view.R
+import cn.qjm253.quick_android_custom_view.custom_drawable_size_view.DrawableTextView
+
+/**
+ * 这是一个 DrawableTextView 的封装组件,在DrawableTextView的基础上支持设置drawable的tinColor
+ *
+ * @see DrawableTextView
+ * @author SunnyQjm
+ * @date 19-7-10 下午9:12
+ */
+
+class TinTextView @JvmOverloads constructor(
+ context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = android.R.attr.textViewStyle
+) : DrawableTextView(context, attrs, defStyleAttr) {
+ companion object {
+ const val NONE = 0x0000
+ const val LEFT = 0x0001
+ const val TOP = 0x0002
+ const val RIGHT = 0x0004
+ const val BOTTOM = 0x0008
+ const val ALL = 0x000f
+ }
+
+
+ private var tints: Int = NONE
+ private lateinit var mDrawables: Array
+
+ init {
+ if (!isInEditMode) {
+ mDrawables = compoundDrawables
+ loadFromAttribute(context, attrs, defStyleAttr)
+ }
+ }
+
+ /**
+ * 从布局文件中加载属性配置
+ */
+ private fun loadFromAttribute(context: Context, attrs: AttributeSet?, defStyleAttr: Int) {
+ attrs?.let {
+ val ta = context.obtainStyledAttributes(it, R.styleable.TinTextView)
+ tints = ta.getInt(R.styleable.TinTextView_tints, ALL)
+ ta.getColorStateList(R.styleable.TinTextView_tinColor)?.let { tinColorStateList ->
+ updateDrawablesTinColor(tinColorStateList)
+ }
+ ta.recycle()
+ }
+ }
+
+
+ /**
+ * 设置哪些方向的drawable需要设置tinColor
+ *
+ * @see TinTextView.NONE -> 四个方向都不着色
+ * @see TinTextView.LEFT -> 左边drawable着色
+ * @see TinTextView.RIGHT -> 右边drawable着色
+ * @see TinTextView.TOP -> 顶部drawable着色
+ * @see TinTextView.BOTTOM -> 底部drawable着色
+ * @see TinTextView.ALL -> 四个方向都着色
+ *
+ * 如果需要单独设置两个方向可以 => TinTextView.LEFT or TinTextView.RIGHT
+ *
+ */
+ fun updateTints(tints: Int) {
+ if (tints < 0 || tints > 8)
+ return
+ this.tints = tints
+ }
+
+ /**
+ * 设置tinColor的颜色
+ */
+ private fun updateDrawablesTinColor(colorStateList: ColorStateList) {
+ //设置icon
+ if (mDrawables[0] != null && (tints and LEFT) == LEFT) {
+ mDrawables[0] = DrawableCompat.wrap(mDrawables[0]!!.mutate())
+ DrawableCompat.setTintList(mDrawables[0]!!, colorStateList)
+ }
+
+ if (mDrawables[1] != null && (tints and TOP) == TOP) {
+ mDrawables[1] = DrawableCompat.wrap(mDrawables[1]!!.mutate())
+ DrawableCompat.setTintList(mDrawables[1]!!, colorStateList)
+ }
+
+ if (mDrawables[2] != null && (tints and RIGHT) == RIGHT) {
+ mDrawables[2] = DrawableCompat.wrap(mDrawables[2]!!.mutate())
+ DrawableCompat.setTintList(mDrawables[2]!!, colorStateList)
+ }
+
+ if (mDrawables[3] != null && (tints and BOTTOM) == BOTTOM) {
+ mDrawables[3] = DrawableCompat.wrap(mDrawables[3]!!.mutate())
+ DrawableCompat.setTintList(mDrawables[3]!!, colorStateList)
+ }
+ setCompoundDrawables(mDrawables[0], mDrawables[1], mDrawables[2], mDrawables[3])
+ }
+
+
+ /**
+ * 设置tinColor的颜色
+ */
+ fun updateDrawablesTinColor(@ColorRes color: Int) {
+ ContextCompat.getColorStateList(context, color)?.let {
+ updateDrawablesTinColor(it)
+ }
+ }
+}
\ No newline at end of file
diff --git a/quick_android_custom_view/src/main/res/drawable/blue_thumb.xml b/quick_android_custom_view/src/main/res/drawable/blue_thumb.xml
new file mode 100644
index 0000000..8a37987
--- /dev/null
+++ b/quick_android_custom_view/src/main/res/drawable/blue_thumb.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/quick_android_custom_view/src/main/res/drawable/blue_track.xml b/quick_android_custom_view/src/main/res/drawable/blue_track.xml
new file mode 100644
index 0000000..7111385
--- /dev/null
+++ b/quick_android_custom_view/src/main/res/drawable/blue_track.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/quick_android_custom_view/src/main/res/drawable/gray_thumb.xml b/quick_android_custom_view/src/main/res/drawable/gray_thumb.xml
new file mode 100644
index 0000000..872df99
--- /dev/null
+++ b/quick_android_custom_view/src/main/res/drawable/gray_thumb.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/quick_android_custom_view/src/main/res/drawable/gray_track.xml b/quick_android_custom_view/src/main/res/drawable/gray_track.xml
new file mode 100644
index 0000000..ade52a6
--- /dev/null
+++ b/quick_android_custom_view/src/main/res/drawable/gray_track.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/quick_android_custom_view/src/main/res/drawable/icon_arrow.png b/quick_android_custom_view/src/main/res/drawable/icon_arrow.png
new file mode 100644
index 0000000..d6d2924
Binary files /dev/null and b/quick_android_custom_view/src/main/res/drawable/icon_arrow.png differ
diff --git a/quick_android_custom_view/src/main/res/drawable/menu.png b/quick_android_custom_view/src/main/res/drawable/menu.png
new file mode 100644
index 0000000..ac272f4
Binary files /dev/null and b/quick_android_custom_view/src/main/res/drawable/menu.png differ
diff --git a/quick_android_custom_view/src/main/res/drawable/switch_thumb.xml b/quick_android_custom_view/src/main/res/drawable/switch_thumb.xml
new file mode 100644
index 0000000..4f619f7
--- /dev/null
+++ b/quick_android_custom_view/src/main/res/drawable/switch_thumb.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/quick_android_custom_view/src/main/res/drawable/switch_track.xml b/quick_android_custom_view/src/main/res/drawable/switch_track.xml
new file mode 100644
index 0000000..9453a17
--- /dev/null
+++ b/quick_android_custom_view/src/main/res/drawable/switch_track.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/quick_android_custom_view/src/main/res/values/custom_drawable_size_view.xml b/quick_android_custom_view/src/main/res/values/custom_drawable_size_view.xml
new file mode 100644
index 0000000..0e4145b
--- /dev/null
+++ b/quick_android_custom_view/src/main/res/values/custom_drawable_size_view.xml
@@ -0,0 +1,89 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/quick_android_custom_view/src/main/res/values/line_menu.xml b/quick_android_custom_view/src/main/res/values/line_menu.xml
new file mode 100644
index 0000000..7e3db22
--- /dev/null
+++ b/quick_android_custom_view/src/main/res/values/line_menu.xml
@@ -0,0 +1,55 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/quick_android_custom_view/src/main/res/values/quick_android_custom_view_color.xml b/quick_android_custom_view/src/main/res/values/quick_android_custom_view_color.xml
new file mode 100644
index 0000000..f93779b
--- /dev/null
+++ b/quick_android_custom_view/src/main/res/values/quick_android_custom_view_color.xml
@@ -0,0 +1,13 @@
+
+
+ #161923
+ #eee
+ #bbb
+ #fff
+ #00ffffff
+ #333333
+ #000
+ #f00
+
+ #dddddd
+
\ No newline at end of file
diff --git a/quick_android_custom_view/src/main/res/values/quick_android_custom_view_dimen.xml b/quick_android_custom_view/src/main/res/values/quick_android_custom_view_dimen.xml
new file mode 100644
index 0000000..e73e92e
--- /dev/null
+++ b/quick_android_custom_view/src/main/res/values/quick_android_custom_view_dimen.xml
@@ -0,0 +1,6 @@
+
+
+ 26dp
+ 13dp
+ 35dp
+
\ No newline at end of file
diff --git a/quick_android_custom_view/src/main/res/values/strings.xml b/quick_android_custom_view/src/main/res/values/strings.xml
new file mode 100644
index 0000000..8542005
--- /dev/null
+++ b/quick_android_custom_view/src/main/res/values/strings.xml
@@ -0,0 +1,2 @@
+
+
diff --git a/quick_android_custom_view/src/main/res/values/tin_view.xml b/quick_android_custom_view/src/main/res/values/tin_view.xml
new file mode 100644
index 0000000..82b4828
--- /dev/null
+++ b/quick_android_custom_view/src/main/res/values/tin_view.xml
@@ -0,0 +1,33 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/quick_android_custom_view/src/test/java/cn/qjm253/quick_android_custom_view/ExampleUnitTest.java b/quick_android_custom_view/src/test/java/cn/qjm253/quick_android_custom_view/ExampleUnitTest.java
new file mode 100644
index 0000000..0f21fc1
--- /dev/null
+++ b/quick_android_custom_view/src/test/java/cn/qjm253/quick_android_custom_view/ExampleUnitTest.java
@@ -0,0 +1,17 @@
+package cn.qjm253.quick_android_custom_view;
+
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+/**
+ * Example local unit test, which will execute on the development machine (host).
+ *
+ * @see Testing documentation
+ */
+public class ExampleUnitTest {
+ @Test
+ public void addition_isCorrect() {
+ assertEquals(4, 2 + 2);
+ }
+}
\ No newline at end of file
diff --git a/quick_android_easy_bar/.gitignore b/quick_android_easy_bar/.gitignore
new file mode 100644
index 0000000..796b96d
--- /dev/null
+++ b/quick_android_easy_bar/.gitignore
@@ -0,0 +1 @@
+/build
diff --git a/quick_android_easy_bar/build.gradle b/quick_android_easy_bar/build.gradle
new file mode 100644
index 0000000..4c207f8
--- /dev/null
+++ b/quick_android_easy_bar/build.gradle
@@ -0,0 +1,38 @@
+apply plugin: 'com.android.library'
+apply plugin: 'kotlin-android'
+apply plugin: 'kotlin-android-extensions'
+android {
+ compileSdkVersion compile_sdk_version
+
+ defaultConfig {
+ minSdkVersion min_sdk_version
+ targetSdkVersion target_sdk_version
+ versionCode 1
+ versionName "1.0"
+
+ testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
+ consumerProguardFiles 'consumer-rules.pro'
+ }
+
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+ }
+ }
+
+}
+
+dependencies {
+ implementation fileTree(dir: 'libs', include: ['*.jar'])
+ implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
+ implementation "androidx.appcompat:appcompat:${appcompact_version}"
+ implementation "androidx.core:core-ktx:${core_ktx_version}"
+ testImplementation "junit:junit:${junit_version}"
+
+ implementation project(path: ':quick_android_base')
+ implementation project(path: ':quick_android_custom_view')
+
+ androidTestImplementation 'androidx.test.ext:junit:1.1.1'
+ androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
+}
diff --git a/quick_android_easy_bar/consumer-rules.pro b/quick_android_easy_bar/consumer-rules.pro
new file mode 100644
index 0000000..e69de29
diff --git a/quick_android_easy_bar/proguard-rules.pro b/quick_android_easy_bar/proguard-rules.pro
new file mode 100644
index 0000000..f1b4245
--- /dev/null
+++ b/quick_android_easy_bar/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
diff --git a/quick_android_easy_bar/src/androidTest/java/cn/qjm253/quick_android_easy_bar/ExampleInstrumentedTest.kt b/quick_android_easy_bar/src/androidTest/java/cn/qjm253/quick_android_easy_bar/ExampleInstrumentedTest.kt
new file mode 100644
index 0000000..e54a232
--- /dev/null
+++ b/quick_android_easy_bar/src/androidTest/java/cn/qjm253/quick_android_easy_bar/ExampleInstrumentedTest.kt
@@ -0,0 +1,24 @@
+package cn.qjm253.quick_android_easy_bar
+
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.ext.junit.runners.AndroidJUnit4
+
+import org.junit.Test
+import org.junit.runner.RunWith
+
+import org.junit.Assert.*
+
+/**
+ * Instrumented test, which will execute on an Android device.
+ *
+ * See [testing documentation](http://d.android.com/tools/testing).
+ */
+@RunWith(AndroidJUnit4::class)
+class ExampleInstrumentedTest {
+ @Test
+ fun useAppContext() {
+ // Context of the app under test.
+ val appContext = InstrumentationRegistry.getInstrumentation().targetContext
+ assertEquals("cn.qjm253.quick_android_easy_bar.test", appContext.packageName)
+ }
+}
diff --git a/quick_android_easy_bar/src/main/AndroidManifest.xml b/quick_android_easy_bar/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..6a731b4
--- /dev/null
+++ b/quick_android_easy_bar/src/main/AndroidManifest.xml
@@ -0,0 +1,2 @@
+
diff --git a/quick_android_easy_bar/src/main/java/cn/qjm253/quick_android_easy_bar/EasyBar.kt b/quick_android_easy_bar/src/main/java/cn/qjm253/quick_android_easy_bar/EasyBar.kt
new file mode 100644
index 0000000..4ea1087
--- /dev/null
+++ b/quick_android_easy_bar/src/main/java/cn/qjm253/quick_android_easy_bar/EasyBar.kt
@@ -0,0 +1,354 @@
+package cn.qjm253.quick_android_easy_bar
+
+import android.annotation.SuppressLint
+import android.content.Context
+import android.graphics.Bitmap
+import android.graphics.drawable.Drawable
+import android.os.Build
+import android.text.TextUtils
+import android.util.AttributeSet
+import android.util.TypedValue
+import android.view.View
+import android.view.ViewGroup
+import android.widget.ImageView
+import android.widget.RelativeLayout
+import android.widget.TextView
+import androidx.annotation.ColorInt
+import androidx.annotation.Dimension
+import androidx.annotation.DrawableRes
+import androidx.annotation.StringRes
+import cn.qjm253.quick_android_base.util.DisplayUtils
+import cn.qjm253.quick_android_custom_view.tin_view.TinImageView
+
+
+/**
+ * Created by Sunny on 2017/8/17 0017.
+ */
+
+class EasyBar @JvmOverloads constructor(
+ context: Context,
+ attrs: AttributeSet? = null,
+ defStyleAttr: Int = 0
+) : RelativeLayout(context, attrs, defStyleAttr) {
+ private var title: String? = null
+ @Dimension
+ private var titleSize: Int = 0
+ @ColorInt
+ private var titleColor: Int = 0
+ @DrawableRes
+ private var leftIcon: Int = 0
+ @DrawableRes
+ private var rightIcon: Int = 0
+ @Dimension
+ private var iconSize: Int = 0
+ @Dimension
+ private var iconMargin: Int = 0
+
+ private var leftText: String = ""
+ private var rightText: String = ""
+ private var displayMode: Mode = Mode.TEXT
+
+
+ private lateinit var imgLeft: TinImageView
+ private lateinit var imgRight: TinImageView
+ private lateinit var tvTitle: TextView
+ private lateinit var tvLeftText: TextView
+ private lateinit var tvRightText: TextView
+ private var mListener: OnEasyBarClickListener? = null
+
+ init {
+ initAttr(context, attrs)
+ initView(context)
+ }
+
+ @SuppressLint("ObsoleteSdkInt")
+ private fun initView(context: Context) {
+
+ //left icon
+ imgLeft = TinImageView(context)
+ imgLeft.setImageResource(leftIcon)
+ imgLeft.scaleType = ImageView.ScaleType.FIT_CENTER
+ val imgLeftParam = RelativeLayout.LayoutParams(
+ ViewGroup.LayoutParams.WRAP_CONTENT,
+ ViewGroup.LayoutParams.WRAP_CONTENT
+ )
+ imgLeftParam.addRule(RelativeLayout.CENTER_VERTICAL)
+ imgLeftParam.addRule(RelativeLayout.ALIGN_PARENT_LEFT)
+ imgLeftParam.width = iconSize
+ imgLeftParam.height = iconSize
+ imgLeft.layoutParams = imgLeftParam
+ imgLeft.setPadding(iconMargin, iconMargin, iconMargin, iconMargin)
+ addView(imgLeft, imgLeftParam)
+
+ imgLeft.setOnClickListener { v ->
+ mListener?.onLeftIconClick(v)
+ }
+
+ //center title
+ tvTitle = TextView(context)
+ tvTitle.ellipsize = TextUtils.TruncateAt.END
+ tvTitle.setSingleLine()
+ tvTitle.text = title
+ tvTitle.textSize = titleSize.toFloat()
+ tvTitle.setTextColor(titleColor)
+ val tvTitleParam = RelativeLayout.LayoutParams(
+ ViewGroup.LayoutParams.WRAP_CONTENT,
+ ViewGroup.LayoutParams.WRAP_CONTENT
+ )
+ val titleMargin = iconSize
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
+ tvTitleParam.marginStart = titleMargin
+ tvTitleParam.marginEnd = titleMargin
+ } else {
+ tvTitleParam.leftMargin = titleMargin
+ tvTitleParam.rightMargin = titleMargin
+ }
+ tvTitleParam.addRule(RelativeLayout.CENTER_IN_PARENT)
+ tvTitle.layoutParams = tvTitleParam
+ addView(tvTitle, tvTitleParam)
+
+ //left text
+ tvLeftText = TextView(context)
+ tvLeftText.textSize = titleSize.toFloat()
+ tvLeftText.setTextColor(titleColor)
+ val tvLeftTextParam = RelativeLayout.LayoutParams(
+ ViewGroup.LayoutParams.WRAP_CONTENT,
+ ViewGroup.LayoutParams.WRAP_CONTENT
+ )
+ tvLeftTextParam.addRule(RelativeLayout.ALIGN_PARENT_LEFT)
+ tvLeftTextParam.addRule(RelativeLayout.CENTER_VERTICAL)
+ tvLeftTextParam.leftMargin = iconMargin
+ tvLeftText.layoutParams = tvLeftTextParam
+ addView(tvLeftText)
+ tvLeftText.text = leftText
+ tvLeftText.visibility = View.INVISIBLE
+ tvLeftText.setOnClickListener { v ->
+ mListener?.onLeftIconClick(v)
+ }
+
+
+ //right text
+ tvRightText = TextView(context)
+ tvRightText.textSize = titleSize.toFloat()
+ tvRightText.setTextColor(titleColor)
+ val tvRightTextParam = RelativeLayout.LayoutParams(
+ ViewGroup.LayoutParams.WRAP_CONTENT,
+ ViewGroup.LayoutParams.WRAP_CONTENT
+ )
+ tvRightTextParam.addRule(RelativeLayout.ALIGN_PARENT_RIGHT)
+ tvRightTextParam.addRule(RelativeLayout.CENTER_VERTICAL)
+ tvRightTextParam.rightMargin = iconMargin
+ tvRightText.layoutParams = tvRightTextParam
+ addView(tvRightText)
+ tvRightText.text = rightText
+ tvRightText.visibility = View.INVISIBLE
+ tvRightText.setOnClickListener { v ->
+ mListener?.onRightIconClick(v)
+ }
+
+ //right icon
+ imgRight = TinImageView(context)
+ imgRight.setImageResource(rightIcon)
+ imgRight.scaleType = ImageView.ScaleType.FIT_CENTER
+ val imgRightParam = RelativeLayout.LayoutParams(
+ ViewGroup.LayoutParams.WRAP_CONTENT,
+ ViewGroup.LayoutParams.WRAP_CONTENT
+ )
+ imgRightParam.addRule(RelativeLayout.CENTER_VERTICAL)
+ imgRightParam.addRule(RelativeLayout.ALIGN_PARENT_RIGHT)
+ imgRightParam.width = iconSize
+ imgRightParam.height = iconSize
+ imgRight.layoutParams = imgRightParam
+ imgRight.setPadding(iconMargin, iconMargin, iconMargin, iconMargin)
+ addView(imgRight, imgRightParam)
+
+ imgRight.visibility = View.INVISIBLE
+ imgRight.setOnClickListener { v ->
+ mListener?.onRightIconClick(v)
+ }
+
+ setDisplayMode(displayMode)
+ }
+
+ private fun initAttr(context: Context, attrs: AttributeSet?) {
+ val ta = context.obtainStyledAttributes(attrs, R.styleable.EasyBar)
+ title = ta.getString(R.styleable.EasyBar_title)
+ titleSize = DisplayUtils.px2sp(
+ context,
+ ta.getDimensionPixelSize(
+ R.styleable.EasyBar_title_size,
+ TypedValue.applyDimension(
+ TypedValue.COMPLEX_UNIT_SP,
+ 14f,
+ resources.displayMetrics
+ ).toInt()
+ ).toFloat()
+ )
+ titleColor =
+ ta.getColor(R.styleable.EasyBar_title_color, resources.getColor(R.color.text_gray))
+ leftIcon = ta.getResourceId(R.styleable.EasyBar_left_icon, R.drawable.back)
+// rightIcon = ta.getResourceId(R.styleable.EasyBar_right_icon, R.drawable.back)
+ iconSize = ta.getDimensionPixelSize(
+ R.styleable.EasyBar_icon_size,
+ DisplayUtils.dip2px(context, 24f)
+ )
+ iconMargin = ta.getDimensionPixelOffset(
+ R.styleable.EasyBar_icon_margin,
+ DisplayUtils.dip2px(context, 12f)
+ )
+ leftText = ta.getString(R.styleable.EasyBar_left_text).toString()
+ rightText = ta.getString(R.styleable.EasyBar_right_text).toString()
+ val mode = ta.getInt(R.styleable.EasyBar_displayMode, 0)
+ displayMode = when (mode) {
+ 0 -> Mode.ICON
+ 1 -> Mode.TEXT
+ else -> Mode.ICON
+ }
+ ta.recycle()
+ }
+
+ fun setTitle(title: String) {
+ this.title = title
+ tvTitle.text = title
+ }
+
+ fun setTitle(@StringRes titleRes: Int) {
+ this.title = context.getString(titleRes)
+ tvTitle.setText(titleRes)
+ }
+
+ fun setLeftText(text: String) {
+ this.leftText = text
+ tvLeftText.text = text
+ }
+
+ fun setRightText(text: String) {
+ this.rightText = text
+ tvRightText.text = text
+ }
+
+ fun setOnEasyBarClickListener(listener: OnEasyBarClickListener) {
+ this.mListener = listener
+ }
+
+
+ fun setRightIcon(drawable: Drawable) {
+ imgRight.setImageDrawable(drawable)
+ }
+
+ fun setRightIcon(bitmap: Bitmap) {
+ imgRight.setImageBitmap(bitmap)
+ }
+
+ fun setRightIcon(@DrawableRes res: Int) {
+ imgRight.setImageResource(res)
+ }
+
+ fun getRightIcon(): TinImageView? {
+ return imgRight
+ }
+
+ fun getRightText(): String? {
+ return tvRightText.text.toString()
+ }
+
+ fun setLeftIcon(drawable: Drawable) {
+ imgLeft.setImageDrawable(drawable)
+ }
+
+ fun setLeftIcon(bitmap: Bitmap) {
+ imgLeft.setImageBitmap(bitmap)
+ }
+
+ fun setLeftIcon(@DrawableRes res: Int) {
+ imgLeft.setImageResource(res)
+ println("drawable: ${imgLeft.drawable}")
+ println("drawable state: ${imgLeft.drawableState}")
+ println("drawing time: ${imgLeft.drawingTime}")
+ }
+
+ fun getLeftIcon(): TinImageView? {
+ return imgLeft
+ }
+
+ interface OnEasyBarClickListener {
+ /**
+ * 如果是图标模式,则响应左图标点击,否则响应左文字点击
+ *
+ * @param view
+ */
+ fun onLeftIconClick(view: View)
+
+ /**
+ * 如果是图标模式,则响应右图标点击,否则响应右文字点击
+ *
+ * @param view
+ */
+ fun onRightIconClick(view: View)
+ }
+
+
+ enum class Mode {
+ ICON, TEXT, ICON_TEXT, TEXT_ICON, ICON_, TEXT_, _ICON, _TEXT, NONE
+ }
+
+ fun setDisplayMode(mode: Mode?) {
+ when (mode) {
+ Mode.ICON -> {
+ tvLeftText.visibility = View.INVISIBLE
+ tvRightText.visibility = View.INVISIBLE
+ imgLeft.visibility = View.VISIBLE
+ imgRight.visibility = View.VISIBLE
+
+ }
+ Mode.TEXT -> {
+ tvLeftText.visibility = View.VISIBLE
+ tvRightText.visibility = View.VISIBLE
+ imgLeft.visibility = View.INVISIBLE
+ imgRight.visibility = View.INVISIBLE
+ }
+ Mode.ICON_TEXT -> {
+ tvLeftText.visibility = View.INVISIBLE
+ tvRightText.visibility = View.VISIBLE
+ imgLeft.visibility = View.VISIBLE
+ imgRight.visibility = View.INVISIBLE
+ }
+ Mode.TEXT_ICON -> {
+ tvLeftText.visibility = View.VISIBLE
+ tvRightText.visibility = View.INVISIBLE
+ imgLeft.visibility = View.INVISIBLE
+ imgRight.visibility = View.VISIBLE
+ }
+ Mode.TEXT_ -> {
+ tvLeftText.visibility = View.VISIBLE
+ tvRightText.visibility = View.INVISIBLE
+ imgLeft.visibility = View.INVISIBLE
+ imgRight.visibility = View.INVISIBLE
+ }
+ Mode.ICON_ -> {
+ tvLeftText.visibility = View.INVISIBLE
+ tvRightText.visibility = View.INVISIBLE
+ imgLeft.visibility = View.VISIBLE
+ imgRight.visibility = View.INVISIBLE
+ }
+ Mode._ICON -> {
+ tvLeftText.visibility = View.INVISIBLE
+ tvRightText.visibility = View.INVISIBLE
+ imgLeft.visibility = View.INVISIBLE
+ imgRight.visibility = View.VISIBLE
+ }
+ Mode._TEXT -> {
+ tvLeftText.visibility = View.INVISIBLE
+ tvRightText.visibility = View.VISIBLE
+ imgLeft.visibility = View.INVISIBLE
+ imgRight.visibility = View.INVISIBLE
+ }
+ Mode.NONE -> {
+ tvLeftText.visibility = View.INVISIBLE
+ tvRightText.visibility = View.INVISIBLE
+ imgLeft.visibility = View.INVISIBLE
+ imgRight.visibility = View.INVISIBLE
+ }
+ }
+ }
+}
diff --git a/quick_android_easy_bar/src/main/java/cn/qjm253/quick_android_easy_bar/EasyBarExtensions.kt b/quick_android_easy_bar/src/main/java/cn/qjm253/quick_android_easy_bar/EasyBarExtensions.kt
new file mode 100644
index 0000000..a1bd07a
--- /dev/null
+++ b/quick_android_easy_bar/src/main/java/cn/qjm253/quick_android_easy_bar/EasyBarExtensions.kt
@@ -0,0 +1,89 @@
+package cn.qjm253.quick_android_easy_bar
+
+
+import android.annotation.SuppressLint
+import android.view.View
+import androidx.annotation.ColorInt
+import androidx.annotation.ColorRes
+import androidx.annotation.DrawableRes
+import androidx.annotation.StringRes
+
+/**
+ * Created by sunny on 18-1-25.
+ */
+
+class EasyBarParams(
+ val mode: EasyBar.Mode = EasyBar.Mode.ICON_,
+ val title: String = "",
+ val leftCallback: (v: View) -> Unit = {},
+ val rightCallback: (v: View) -> Unit = {
+ println("empty right")
+ }, @DrawableRes val leftRes: Int = R.drawable.back,
+ @DrawableRes val rightRes: Int = 0,
+ val leftText: String = "",
+ val rightText: String = "", @StringRes val titleRes: Int = -1,
+ val isCoverCallback: Boolean = true,
+ @ColorRes
+ val tinColor: Int = -1
+) {
+}
+
+fun EasyBar.init(easyBarParams: EasyBarParams) {
+ init(
+ easyBarParams.mode,
+ easyBarParams.title,
+ easyBarParams.leftCallback,
+ easyBarParams.rightCallback,
+ easyBarParams.leftRes,
+ easyBarParams.rightRes,
+ easyBarParams.leftText,
+ easyBarParams.rightText,
+ easyBarParams.titleRes,
+ easyBarParams.isCoverCallback,
+ easyBarParams.tinColor
+ )
+}
+
+/**
+ * 标题栏初始化
+ */
+@SuppressLint("ResourceType")
+fun EasyBar.init(
+ mode: EasyBar.Mode = EasyBar.Mode.ICON_,
+ title: String = "",
+ leftCallback: (v: View) -> Unit = {},
+ rightCallback: (v: View) -> Unit = {
+ println("empty right")
+ }, @DrawableRes leftRes: Int = R.drawable.back,
+ @DrawableRes rightRes: Int = 0,
+ leftText: String = "",
+ rightText: String = "", @StringRes titleRes: Int = -1,
+ isCoverCallback: Boolean = true,
+ @ColorRes tinColor: Int = -1
+
+) {
+ this.setDisplayMode(mode)
+ if (titleRes > 0)
+ this.setTitle(titleRes)
+ else
+ this.setTitle(title)
+ this.setRightIcon(rightRes)
+ this.setLeftText(leftText)
+ this.setRightText(rightText)
+ this.setLeftIcon(leftRes)
+ if (tinColor > 0) {
+ this.getLeftIcon()?.updateDrawablesTinColor(tinColor)
+ this.getRightIcon()?.updateDrawablesTinColor(tinColor)
+ }
+ if (isCoverCallback)
+ this.setOnEasyBarClickListener(object : EasyBar.OnEasyBarClickListener {
+ override fun onLeftIconClick(view: View) {
+ leftCallback(view)
+ }
+
+ override fun onRightIconClick(view: View) {
+ rightCallback.invoke(view)
+ }
+
+ })
+}
\ No newline at end of file
diff --git a/quick_android_easy_bar/src/main/res/drawable/back.png b/quick_android_easy_bar/src/main/res/drawable/back.png
new file mode 100644
index 0000000..cab12e2
Binary files /dev/null and b/quick_android_easy_bar/src/main/res/drawable/back.png differ
diff --git a/quick_android_easy_bar/src/main/res/values/colors.xml b/quick_android_easy_bar/src/main/res/values/colors.xml
new file mode 100644
index 0000000..b779011
--- /dev/null
+++ b/quick_android_easy_bar/src/main/res/values/colors.xml
@@ -0,0 +1,4 @@
+
+
+ #333333
+
\ No newline at end of file
diff --git a/quick_android_easy_bar/src/main/res/values/quick_android_easy_bar_attrs.xml b/quick_android_easy_bar/src/main/res/values/quick_android_easy_bar_attrs.xml
new file mode 100644
index 0000000..8268eda
--- /dev/null
+++ b/quick_android_easy_bar/src/main/res/values/quick_android_easy_bar_attrs.xml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/quick_android_easy_bar/src/main/res/values/strings.xml b/quick_android_easy_bar/src/main/res/values/strings.xml
new file mode 100644
index 0000000..8542005
--- /dev/null
+++ b/quick_android_easy_bar/src/main/res/values/strings.xml
@@ -0,0 +1,2 @@
+
+
diff --git a/quick_android_easy_bar/src/test/java/cn/qjm253/quick_android_easy_bar/ExampleUnitTest.kt b/quick_android_easy_bar/src/test/java/cn/qjm253/quick_android_easy_bar/ExampleUnitTest.kt
new file mode 100644
index 0000000..3d28083
--- /dev/null
+++ b/quick_android_easy_bar/src/test/java/cn/qjm253/quick_android_easy_bar/ExampleUnitTest.kt
@@ -0,0 +1,17 @@
+package cn.qjm253.quick_android_easy_bar
+
+import org.junit.Test
+
+import org.junit.Assert.*
+
+/**
+ * Example local unit test, which will execute on the development machine (host).
+ *
+ * See [testing documentation](http://d.android.com/tools/testing).
+ */
+class ExampleUnitTest {
+ @Test
+ fun addition_isCorrect() {
+ assertEquals(4, 2 + 2)
+ }
+}
diff --git a/quick_android_file_picker/.gitignore b/quick_android_file_picker/.gitignore
new file mode 100644
index 0000000..796b96d
--- /dev/null
+++ b/quick_android_file_picker/.gitignore
@@ -0,0 +1 @@
+/build
diff --git a/quick_android_file_picker/README.md b/quick_android_file_picker/README.md
new file mode 100644
index 0000000..332d2ff
--- /dev/null
+++ b/quick_android_file_picker/README.md
@@ -0,0 +1,67 @@
+> ## quick_android_file_picker 模块说明文档
+>
+> 本模块简单封装了打开系统的文件管理器选择文件的功能,并返回文件的原始Uri以及经过处理后文件的绝对路径。
+>
+> 通过RxJava用简单的链式调用即可获得选择文件的结果
+
+- 返回数据结构
+
+ ```kotlin
+ data class FilePickerResult (
+ val path: String, // 文件的绝对路径
+ val uri: Uri // 文件的Uri
+ )
+ ```
+
+- ### 使用示例
+
+ - 本接口需要读取文件的权限 => `READ_EXTERNAL_STORAGE`,权限的获取可以参考本库的另一个模块 [quick_android_rx_permission模块文档](https://github.com/SunnyQjm/quickandroid/tree/master/quick_android_rx_permission)
+
+ - 包含权限请求的使用示例:
+
+ ps: `checkMyPermission` 为 [quick_android_base模块文档](https://github.com/SunnyQjm/quickandroid/tree/master/quick_android_base) 模块提供的用于检查权限是否存在的 `Context` 类的扩展方法
+
+ ```kotlin
+ QuickAndroidFilePicker
+ .create(this)
+ .request(
+ arrayOf(
+ Manifest.permission.READ_EXTERNAL_STORAGE
+ )
+ )
+ .subscribe {
+ if (it.granted) {
+ checkMyPermission(Manifest.permission.READ_EXTERNAL_STORAGE, {
+ QuickAndroid
+ .createFilePicker(this)
+ .pickerFile()
+ .subscribe { fpr ->
+ toast(fpr.path)
+ }
+ })
+ }
+ }
+
+ //////////////////////////////////////////////////////////////
+ //// 使用 QuickAndroid 扩展的用法
+ //////////////////////////////////////////////////////////////
+ QuickAndroid
+ .createRxPermission(this)
+ .request(
+ arrayOf(
+ Manifest.permission.READ_EXTERNAL_STORAGE
+ )
+ )
+ .subscribe {
+ if (it.granted) {
+ checkMyPermission(Manifest.permission.READ_EXTERNAL_STORAGE, {
+ QuickAndroid
+ .createFilePicker(this)
+ .pickerFile()
+ .subscribe { fpr ->
+ toast(fpr.path)
+ }
+ })
+ }
+ }
+ ```
\ No newline at end of file
diff --git a/quick_android_file_picker/build.gradle b/quick_android_file_picker/build.gradle
new file mode 100644
index 0000000..ae1af08
--- /dev/null
+++ b/quick_android_file_picker/build.gradle
@@ -0,0 +1,45 @@
+apply plugin: 'com.android.library'
+
+apply plugin: 'kotlin-android'
+
+apply plugin: 'kotlin-android-extensions'
+
+apply plugin: 'kotlin-kapt'
+
+android {
+ compileSdkVersion compile_sdk_version
+
+ defaultConfig {
+ minSdkVersion min_sdk_version
+ targetSdkVersion target_sdk_version
+ versionCode 1
+ versionName "1.0"
+
+ testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
+
+ }
+
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+ }
+ }
+
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_1_8
+ targetCompatibility JavaVersion.VERSION_1_8
+ }
+
+
+}
+
+dependencies {
+ implementation fileTree(dir: 'libs', include: ['*.jar'])
+ implementation "androidx.appcompat:appcompat:${appcompact_version}"
+ testImplementation "junit:junit:${junit_version}"
+ androidTestImplementation "androidx.test:runner:${test_runner_version}"
+ androidTestImplementation "androidx.test.espresso:espresso-core:${espresso_core_version}"
+
+ implementation project(path: ':quick_android_base')
+}
diff --git a/quick_android_file_picker/consumer-rules.pro b/quick_android_file_picker/consumer-rules.pro
new file mode 100644
index 0000000..e69de29
diff --git a/quick_android_file_picker/proguard-rules.pro b/quick_android_file_picker/proguard-rules.pro
new file mode 100644
index 0000000..f1b4245
--- /dev/null
+++ b/quick_android_file_picker/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
diff --git a/quick_android_file_picker/src/androidTest/java/com/example/quick_android_file_picker/ExampleInstrumentedTest.kt b/quick_android_file_picker/src/androidTest/java/com/example/quick_android_file_picker/ExampleInstrumentedTest.kt
new file mode 100644
index 0000000..2bbecd9
--- /dev/null
+++ b/quick_android_file_picker/src/androidTest/java/com/example/quick_android_file_picker/ExampleInstrumentedTest.kt
@@ -0,0 +1,24 @@
+package com.example.quick_android_file_picker
+
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.ext.junit.runners.AndroidJUnit4
+
+import org.junit.Test
+import org.junit.runner.RunWith
+
+import org.junit.Assert.*
+
+/**
+ * Instrumented test, which will execute on an Android device.
+ *
+ * See [testing documentation](http://d.android.com/tools/testing).
+ */
+@RunWith(AndroidJUnit4::class)
+class ExampleInstrumentedTest {
+ @Test
+ fun useAppContext() {
+ // Context of the app under test.
+ val appContext = InstrumentationRegistry.getInstrumentation().targetContext
+ assertEquals("com.example.quick_android_file_picker.test", appContext.packageName)
+ }
+}
diff --git a/quick_android_file_picker/src/main/AndroidManifest.xml b/quick_android_file_picker/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..fac2a43
--- /dev/null
+++ b/quick_android_file_picker/src/main/AndroidManifest.xml
@@ -0,0 +1,2 @@
+
diff --git a/quick_android_file_picker/src/main/java/com/example/quick_android_file_picker/QuickAndroidFilePicker.kt b/quick_android_file_picker/src/main/java/com/example/quick_android_file_picker/QuickAndroidFilePicker.kt
new file mode 100644
index 0000000..4a13da5
--- /dev/null
+++ b/quick_android_file_picker/src/main/java/com/example/quick_android_file_picker/QuickAndroidFilePicker.kt
@@ -0,0 +1,50 @@
+package com.example.quick_android_file_picker
+
+import androidx.annotation.RequiresPermission
+import androidx.fragment.app.Fragment
+import androidx.fragment.app.FragmentActivity
+import androidx.fragment.app.FragmentManager
+import cn.qjm253.quick_android_base.QuickAndroid
+import cn.qjm253.quick_android_base.extensions.getLazySingleton
+import com.example.quick_android_file_picker.model.FilePickerResult
+import io.reactivex.Observable
+
+class QuickAndroidFilePicker(fragmentManager: FragmentManager) {
+ companion object {
+ const val TAG = "QuickAndroidFilePicker"
+
+ fun create(activity: FragmentActivity): QuickAndroidFilePicker {
+ return QuickAndroidFilePicker(activity.supportFragmentManager)
+ }
+
+ fun create(fragment: Fragment): QuickAndroidFilePicker {
+ return QuickAndroidFilePicker(fragment.childFragmentManager)
+ }
+ }
+
+ private var mRxAndroidFilePickerFragment: Lazy
+
+ init {
+ mRxAndroidFilePickerFragment =
+ fragmentManager.getLazySingleton(TAG, QuickAndroidFilePickerFragment::class.java)
+ }
+
+ @RequiresPermission(android.Manifest.permission.READ_EXTERNAL_STORAGE)
+ fun pickerFile(fileType: String = "*/*"): Observable {
+ return mRxAndroidFilePickerFragment
+ .value
+ .pickerFile(fileType)
+ }
+}
+
+//////////////////////////////////////////////////////////
+////////// QuickAndroid 扩展
+//////////////////////////////////////////////////////////
+
+fun QuickAndroid.createFilePicker(activity: FragmentActivity): QuickAndroidFilePicker {
+ return QuickAndroidFilePicker(activity.supportFragmentManager)
+}
+
+fun QuickAndroid.createFilePicker(fragment: Fragment): QuickAndroidFilePicker {
+ return QuickAndroidFilePicker(fragment.childFragmentManager)
+}
\ No newline at end of file
diff --git a/quick_android_file_picker/src/main/java/com/example/quick_android_file_picker/QuickAndroidFilePickerFragment.kt b/quick_android_file_picker/src/main/java/com/example/quick_android_file_picker/QuickAndroidFilePickerFragment.kt
new file mode 100644
index 0000000..249fa12
--- /dev/null
+++ b/quick_android_file_picker/src/main/java/com/example/quick_android_file_picker/QuickAndroidFilePickerFragment.kt
@@ -0,0 +1,49 @@
+package com.example.quick_android_file_picker
+
+import android.content.Intent
+import android.os.Bundle
+import androidx.annotation.RequiresPermission
+import androidx.fragment.app.Fragment
+import cn.qjm253.quick_android_base.extensions.i
+import cn.qjm253.quick_android_base.util.ContentUriUtil
+import com.example.quick_android_file_picker.model.FilePickerResult
+import io.reactivex.Observable
+import io.reactivex.ObservableEmitter
+
+/**
+ * 这个fragment用于接收文件选择的结果
+ */
+class QuickAndroidFilePickerFragment : Fragment() {
+ private var emitter: ObservableEmitter? = null
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ retainInstance = true
+ }
+
+ @RequiresPermission(android.Manifest.permission.READ_EXTERNAL_STORAGE)
+ fun pickerFile(fileType: String = "*/*"): Observable {
+ return Observable.create { _emitter ->
+ this.emitter = _emitter
+
+ // 调用系统的文件选择器
+ Intent(Intent.ACTION_GET_CONTENT).apply {
+ addCategory(Intent.CATEGORY_OPENABLE)
+ type = fileType
+ this@QuickAndroidFilePickerFragment.startActivityForResult(this, 0)
+ }
+ }
+
+ }
+
+ override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
+ super.onActivityResult(requestCode, resultCode, data)
+ data?.data?.let {
+ it.path?.i()
+ val path = ContentUriUtil.getPath(context, it)
+ println(it.toString())
+ emitter?.onNext(FilePickerResult(path, it))
+ emitter?.onComplete()
+ }
+ }
+}
\ No newline at end of file
diff --git a/quick_android_file_picker/src/main/java/com/example/quick_android_file_picker/model/FilePickerResult.kt b/quick_android_file_picker/src/main/java/com/example/quick_android_file_picker/model/FilePickerResult.kt
new file mode 100644
index 0000000..352227f
--- /dev/null
+++ b/quick_android_file_picker/src/main/java/com/example/quick_android_file_picker/model/FilePickerResult.kt
@@ -0,0 +1,8 @@
+package com.example.quick_android_file_picker.model
+
+import android.net.Uri
+
+data class FilePickerResult (
+ val path: String,
+ val uri: Uri
+)
\ No newline at end of file
diff --git a/quick_android_file_picker/src/main/res/values/strings.xml b/quick_android_file_picker/src/main/res/values/strings.xml
new file mode 100644
index 0000000..8542005
--- /dev/null
+++ b/quick_android_file_picker/src/main/res/values/strings.xml
@@ -0,0 +1,2 @@
+
+
diff --git a/quick_android_file_picker/src/test/java/com/example/quick_android_file_picker/ExampleUnitTest.kt b/quick_android_file_picker/src/test/java/com/example/quick_android_file_picker/ExampleUnitTest.kt
new file mode 100644
index 0000000..98c2794
--- /dev/null
+++ b/quick_android_file_picker/src/test/java/com/example/quick_android_file_picker/ExampleUnitTest.kt
@@ -0,0 +1,17 @@
+package com.example.quick_android_file_picker
+
+import org.junit.Test
+
+import org.junit.Assert.*
+
+/**
+ * Example local unit test, which will execute on the development machine (host).
+ *
+ * See [testing documentation](http://d.android.com/tools/testing).
+ */
+class ExampleUnitTest {
+ @Test
+ fun addition_isCorrect() {
+ assertEquals(4, 2 + 2)
+ }
+}
diff --git a/quick_android_image_picker/.gitignore b/quick_android_image_picker/.gitignore
new file mode 100644
index 0000000..796b96d
--- /dev/null
+++ b/quick_android_image_picker/.gitignore
@@ -0,0 +1 @@
+/build
diff --git a/quick_android_image_picker/README.md b/quick_android_image_picker/README.md
new file mode 100644
index 0000000..8a15efa
--- /dev/null
+++ b/quick_android_image_picker/README.md
@@ -0,0 +1,85 @@
+> ## quick_android_image_picker 模块使用说明
+>
+> 本模块基于[RxImagePicker](https://github.com/qingmei2/RxImagePicker)进行封装,并额外提供图片裁剪功能,提供简单的调用接口。关于`RxImagePicker`的更多使用说明,请参考[RxImagePicker](https://github.com/qingmei2/RxImagePicker)
+
+- ### 使用
+
+ - 简单使用
+
+ ```kotlin
+
+ /**
+ * 打开知乎样式的图片选择器
+ */
+ QuickAndroidImagePicker
+ .openZhihuStyleGallery(
+ context,
+ ZhihuConfigurationBuilder(
+ MimeType.ofImage(),
+ false
+ )
+ .maxSelectable(1)
+ .countable(true)
+ .spanCount(4)
+ .build() // ICustomPickerConfiguration 类型
+ )
+ .subscribe({ result ->
+ // 将结果传给图片裁剪界面进行图片裁剪
+ QuickAndroidImagePicker.startClipImage(context, result.uri)
+ })
+
+ ```
+ - 其他功能
+ ```kotlin
+ /**
+ * 打开知乎样式的图片选择器
+ */
+ QuickAndroidImagePicker
+ .openWechatStyleGallery(
+ context,
+ WechatConfigrationBuilder(
+ MimeType.ofImage(),
+ false
+ )
+ .maxSelectable(1)
+ .countable(true)
+ .spanCount(4)
+ .build() // ICustomPickerConfiguration 类型
+ )
+ .subscribe({ result ->
+
+ })
+
+ /**
+ * 打开摄像头拍摄
+ */
+ QuickAndroidImagePicker
+ .openCamera(context)
+ .subscribe({ result ->
+ })
+
+ /**
+ * 跳转到裁剪页面裁剪图片
+ * @param context
+ * @param uri Uri类型
+ * @param multiple 裁剪后文件的压缩强度
+ * 0.2 -> 50 ~ 100 KB
+ * 0.3 -> 100 ~ 200 KB
+ */
+ QuickAndroidImagePicker
+ .startClipImage(context, uri, 0.3)
+
+ /**
+ * 跳转到裁剪页面裁剪图片
+ * @param context
+ * @param path 文件的绝对路径
+ * @param multiple 裁剪后文件的压缩强度
+ * 0.2 -> 50 ~ 100 KB
+ * 0.3 -> 100 ~ 200 KB
+ */
+ QuickANdroidImagePicker
+ .startClipImage(context, path, 0.3)
+
+ ```
+
+ 当然,上面的所有接口都有`QuickAndroid`对应的扩展,都可以用`QuickAndroid.xxx`来调用
diff --git a/quick_android_image_picker/build.gradle b/quick_android_image_picker/build.gradle
new file mode 100644
index 0000000..75fb167
--- /dev/null
+++ b/quick_android_image_picker/build.gradle
@@ -0,0 +1,50 @@
+apply plugin: 'com.android.library'
+apply plugin: 'kotlin-android'
+apply plugin: 'kotlin-android-extensions'
+
+
+android {
+ compileSdkVersion compile_sdk_version
+
+ defaultConfig {
+ minSdkVersion min_sdk_version
+ targetSdkVersion target_sdk_version
+ versionCode 1
+ versionName "1.0"
+
+ testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
+
+ }
+
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+ }
+ }
+
+}
+
+dependencies {
+ implementation fileTree(dir: 'libs', include: ['*.jar'])
+ implementation "androidx.appcompat:appcompat:${appcompact_version}"
+ testImplementation "junit:junit:${junit_version}"
+ implementation "androidx.constraintlayout:constraintlayout:${constraintlayout_version}"
+ androidTestImplementation "androidx.test:runner:${test_runner_version}"
+ androidTestImplementation "androidx.test.espresso:espresso-core:${espresso_core_version}"
+
+ // RxImagePicker
+ api "com.github.qingmei2:rximagepicker:${rx_image_picker_version}"
+ api "com.github.qingmei2:rximagepicker_support:${rx_image_picker_version}"
+ // Zhihu picture selector
+ api "com.github.qingmei2:rximagepicker_support_zhihu:${rx_image_picker_version}"
+ // WeChat picture selector
+ api "com.github.qingmei2:rximagepicker_support_wechat:${rx_image_picker_version}"
+
+
+ implementation project(path: ':quick_android_base')
+
+}
+repositories {
+ mavenCentral()
+}
diff --git a/quick_android_image_picker/proguard-rules.pro b/quick_android_image_picker/proguard-rules.pro
new file mode 100644
index 0000000..af8464c
--- /dev/null
+++ b/quick_android_image_picker/proguard-rules.pro
@@ -0,0 +1,26 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
+
+
+##------------- Begin RxImagePicker-------------##
+-keep class com.qingmei2.rximagepicker.**{*;}
+##------------- Begin RxImagePicker-------------##
diff --git a/quick_android_image_picker/src/androidTest/java/cn/qjm253/quick_android_image_picker/ExampleInstrumentedTest.java b/quick_android_image_picker/src/androidTest/java/cn/qjm253/quick_android_image_picker/ExampleInstrumentedTest.java
new file mode 100644
index 0000000..54b7655
--- /dev/null
+++ b/quick_android_image_picker/src/androidTest/java/cn/qjm253/quick_android_image_picker/ExampleInstrumentedTest.java
@@ -0,0 +1,26 @@
+package cn.qjm253.quick_android_image_picker;
+
+import android.content.Context;
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import static org.junit.Assert.*;
+
+/**
+ * Instrumented test, which will execute on an Android device.
+ *
+ * @see Testing documentation
+ */
+@RunWith(AndroidJUnit4.class)
+public class ExampleInstrumentedTest {
+ @Test
+ public void useAppContext() {
+ // Context of the app under test.
+ Context appContext = InstrumentationRegistry.getTargetContext();
+
+ assertEquals("cn.qjm253.quick_android_image_picker.test", appContext.getPackageName());
+ }
+}
diff --git a/quick_android_image_picker/src/main/AndroidManifest.xml b/quick_android_image_picker/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..e2df19f
--- /dev/null
+++ b/quick_android_image_picker/src/main/AndroidManifest.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/quick_android_image_picker/src/main/java/cn/qjm253/quick_android_image_picker/MyImagePicker.kt b/quick_android_image_picker/src/main/java/cn/qjm253/quick_android_image_picker/MyImagePicker.kt
new file mode 100644
index 0000000..1295aee
--- /dev/null
+++ b/quick_android_image_picker/src/main/java/cn/qjm253/quick_android_image_picker/MyImagePicker.kt
@@ -0,0 +1,35 @@
+package cn.qjm253.quick_android_image_picker
+
+import android.content.Context
+import com.qingmei2.rximagepicker.entity.Result
+import com.qingmei2.rximagepicker.entity.sources.Camera
+import com.qingmei2.rximagepicker.entity.sources.Gallery
+import com.qingmei2.rximagepicker.ui.ICustomPickerConfiguration
+import com.qingmei2.rximagepicker_extension_wechat.ui.WechatImagePickerActivity
+import com.qingmei2.rximagepicker_extension_zhihu.ui.ZhihuImagePickerActivity
+import io.reactivex.Observable
+
+/**
+ * @author SunnyQjm
+ * @date 19-7-7 下午1:52
+ */
+
+interface MyImagePicker {
+
+ /**
+ * 打开类知乎的图片选择器
+ */
+ @Gallery(componentClazz = ZhihuImagePickerActivity::class, openAsFragment = false)
+ fun openZhihuStyleGallery(context: Context,
+ config: ICustomPickerConfiguration): Observable
+
+ /**
+ * 打开类微信的图片选择器
+ */
+ @Gallery(componentClazz = WechatImagePickerActivity::class, openAsFragment = false)
+ fun openWechatStyleGallery(context: Context,
+ config: ICustomPickerConfiguration): Observable
+
+ @Camera
+ fun openCamera(context: Context): Observable
+}
\ No newline at end of file
diff --git a/quick_android_image_picker/src/main/java/cn/qjm253/quick_android_image_picker/QuickAndroidImagePicker.kt b/quick_android_image_picker/src/main/java/cn/qjm253/quick_android_image_picker/QuickAndroidImagePicker.kt
new file mode 100644
index 0000000..130bd1a
--- /dev/null
+++ b/quick_android_image_picker/src/main/java/cn/qjm253/quick_android_image_picker/QuickAndroidImagePicker.kt
@@ -0,0 +1,174 @@
+package cn.qjm253.quick_android_image_picker
+
+import android.content.Context
+import android.net.Uri
+import cn.qjm253.quick_android_base.QuickAndroid
+import cn.qjm253.quick_android_base.extensions.jumpTo
+import cn.qjm253.quick_android_base.params.IntentParam
+import cn.qjm253.quick_android_base.util.ContentUriUtil
+import cn.qjm253.quick_android_image_picker.activity.QuickAndroidClipImageActivity
+import com.qingmei2.rximagepicker.core.RxImagePicker
+import com.qingmei2.rximagepicker.entity.Result
+import com.qingmei2.rximagepicker.ui.ICustomPickerConfiguration
+import com.qingmei2.rximagepicker_extension.MimeType
+import com.qingmei2.rximagepicker_extension_wechat.WechatConfigrationBuilder
+import com.qingmei2.rximagepicker_extension_zhihu.ZhihuConfigurationBuilder
+import io.reactivex.Observable
+
+/**
+ * @author SunnyQjm
+ * @date 19-7-7 下午2:25
+ */
+object QuickAndroidImagePicker {
+
+ /**
+ * 打开知乎样式的图片选择器
+ */
+ fun openZhihuStyleGallery(
+ context: Context,
+ config: ICustomPickerConfiguration = ZhihuConfigurationBuilder(MimeType.ofImage(), false)
+ .maxSelectable(9)
+ .countable(true)
+ .spanCount(4)
+ .theme(R.style.Zhihu_Normal)
+ .build()
+ ): Observable {
+ return RxImagePicker
+ .create(MyImagePicker::class.java)
+ .openZhihuStyleGallery(context, config)
+ }
+
+ /**
+ * 打开微信样式的图片选择器
+ */
+ fun openWechatStyleGallery(
+ context: Context,
+ config: ICustomPickerConfiguration = WechatConfigrationBuilder(MimeType.ofImage(), false)
+ .maxSelectable(9)
+ .countable(true)
+ .spanCount(4)
+ .build()
+ ): Observable {
+ return RxImagePicker
+ .create(MyImagePicker::class.java)
+ .openWechatStyleGallery(context, config)
+ }
+
+
+ /**
+ * 打开摄像头拍摄
+ */
+ fun openCamera(context: Context): Observable {
+ return RxImagePicker
+ .create()
+ .openCamera(context)
+ }
+
+ /**
+ * 跳转到裁剪页面裁剪图片
+ * @param context
+ * @param path 文件的绝对路径
+ * @param multiple 裁剪后文件的压缩强度
+ * 0.2 -> 50 ~ 100 KB
+ * 0.3 -> 100 ~ 200 KB
+ */
+ fun startClipImage(context: Context, path: String, multiple: Float = 0.3f): QuickAndroidImagePicker {
+ context.jumpTo(
+ QuickAndroidClipImageActivity::class.java, IntentParam()
+ .add(QuickAndroidClipImageActivity.QUICK_ANDROID_CLIP_IMAGE_IMAGE_PATH, path)
+ .add(QuickAndroidClipImageActivity.QUICK_ANDROID_CLIP_IMAGE_MULTIPLE, multiple)
+ )
+ return this
+ }
+
+ /**
+ * 跳转到裁剪页面裁剪图片
+ * @param context
+ * @param uri Uri类型
+ * @param multiple 裁剪后文件的压缩强度
+ * 0.2 -> 50 ~ 100 KB
+ * 0.3 -> 100 ~ 200 KB
+ */
+ fun startClipImage(context: Context, uri: Uri, multiple: Float = 0.3f): QuickAndroidImagePicker {
+ startClipImage(context, ContentUriUtil.getPath(context, uri), multiple)
+ return this
+ }
+
+}
+
+//////////////////////////////////////////////////////////
+////////// QuickAndroid 扩展
+//////////////////////////////////////////////////////////
+
+fun QuickAndroid.openZhihuStyleGallery(
+ context: Context,
+ config: ICustomPickerConfiguration = ZhihuConfigurationBuilder(MimeType.ofImage(), false)
+ .maxSelectable(9)
+ .countable(true)
+ .spanCount(4)
+ .theme(R.style.Zhihu_Normal)
+ .build()
+): Observable {
+ return QuickAndroidImagePicker.openZhihuStyleGallery(context, config)
+}
+
+fun QuickAndroid.openWechatStyleGallery(
+ context: Context,
+ config: ICustomPickerConfiguration = WechatConfigrationBuilder(MimeType.ofImage(), false)
+ .maxSelectable(9)
+ .countable(true)
+ .spanCount(4)
+ .build()
+): Observable {
+ return QuickAndroidImagePicker.openWechatStyleGallery(context, config)
+}
+
+fun QuickAndroid.openCamera(context: Context): Observable {
+ return QuickAndroidImagePicker.openCamera(context)
+}
+
+fun QuickAndroid.startClipImage(context: Context, path: String, multiple: Float = 0.3f): QuickAndroidImagePicker {
+ return QuickAndroidImagePicker.startClipImage(context, path, multiple)
+}
+
+fun QuickAndroid.startClipImage(context: Context, uri: Uri, multiple: Float = 0.3f): QuickAndroidImagePicker {
+ return QuickAndroidImagePicker.startClipImage(context, uri, multiple)
+}
+
+
+//////////////////////////////////////////////////////////
+////////// Context 扩展
+//////////////////////////////////////////////////////////
+
+fun Context.openZhihuStyleGallery(
+ config: ICustomPickerConfiguration = ZhihuConfigurationBuilder(MimeType.ofImage(), false)
+ .maxSelectable(9)
+ .countable(true)
+ .spanCount(4)
+ .theme(R.style.Zhihu_Normal)
+ .build()
+): Observable {
+ return QuickAndroidImagePicker.openZhihuStyleGallery(this, config)
+}
+
+fun Context.openWechatStyleGallery(
+ config: ICustomPickerConfiguration = WechatConfigrationBuilder(MimeType.ofImage(), false)
+ .maxSelectable(9)
+ .countable(true)
+ .spanCount(4)
+ .build()
+): Observable {
+ return QuickAndroidImagePicker.openWechatStyleGallery(this, config)
+}
+
+fun Context.openCamera(): Observable {
+ return QuickAndroidImagePicker.openCamera(this)
+}
+
+fun Context.startClipImage(path: String, multiple: Float = 0.3f): QuickAndroidImagePicker {
+ return QuickAndroidImagePicker.startClipImage(this, path, multiple)
+}
+
+fun Context.startClipImage(uri: Uri, multiple: Float = 0.3f): QuickAndroidImagePicker {
+ return QuickAndroidImagePicker.startClipImage(this, uri, multiple)
+}
\ No newline at end of file
diff --git a/quick_android_image_picker/src/main/java/cn/qjm253/quick_android_image_picker/activity/QuickAndroidClipImageActivity.kt b/quick_android_image_picker/src/main/java/cn/qjm253/quick_android_image_picker/activity/QuickAndroidClipImageActivity.kt
new file mode 100644
index 0000000..fde883e
--- /dev/null
+++ b/quick_android_image_picker/src/main/java/cn/qjm253/quick_android_image_picker/activity/QuickAndroidClipImageActivity.kt
@@ -0,0 +1,42 @@
+package cn.qjm253.quick_android_image_picker.activity
+
+import android.os.Bundle
+import cn.qjm253.quick_android_base.base.activity.BaseQuickAndroidActivity
+import cn.qjm253.quick_android_base.base.fragment.BaseQuickAndroidFragment
+import cn.qjm253.quick_android_base.extensions.addFragmentToActivity
+import cn.qjm253.quick_android_image_picker.R
+import cn.qjm253.quick_android_image_picker.fragment.QuickAndroidClipImageFragment
+import com.gyf.immersionbar.BarHide
+import com.gyf.immersionbar.ImmersionBar
+import kotlinx.android.synthetic.main.activity_quick_android_clip_image.*
+
+class QuickAndroidClipImageActivity : BaseQuickAndroidActivity(), BaseQuickAndroidFragment.OnFragmentInteractionListener {
+
+ companion object {
+ const val QUICK_ANDROID_CLIP_IMAGE_IMAGE_PATH = QuickAndroidClipImageFragment.QUICK_ANDROID_CLIP_IMAGE_IMAGE_PATH
+ const val QUICK_ANDROID_CLIP_IMAGE_MULTIPLE = QuickAndroidClipImageFragment.QUICK_ANDROID_CLIP_IMAGE_MULTIPLE
+ }
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ ImmersionBar.with(this)
+ .fullScreen(true)
+ .hideBar(BarHide.FLAG_HIDE_NAVIGATION_BAR)
+ .init()
+ setContentView(R.layout.activity_quick_android_clip_image)
+
+ // 如果有的话隐藏ActionBar
+ supportActionBar?.hide()
+
+ val path = intent.getStringExtra(QUICK_ANDROID_CLIP_IMAGE_IMAGE_PATH)
+ val multiple = intent.getFloatExtra(QUICK_ANDROID_CLIP_IMAGE_MULTIPLE, QuickAndroidClipImageFragment.DEFAULT_MULTIPLE)
+
+ val fragment = addFragmentToActivity(R.id.quickAndroidClipImageFragmentContainer) {
+ return@addFragmentToActivity QuickAndroidClipImageFragment.newInstance(path, multiple)
+ }
+
+ tvFinish.setOnClickListener {
+ fragment.doClip()
+ }
+ }
+}
diff --git a/quick_android_image_picker/src/main/java/cn/qjm253/quick_android_image_picker/fragment/QuickAndroidClipImageFragment.kt b/quick_android_image_picker/src/main/java/cn/qjm253/quick_android_image_picker/fragment/QuickAndroidClipImageFragment.kt
new file mode 100644
index 0000000..e076da3
--- /dev/null
+++ b/quick_android_image_picker/src/main/java/cn/qjm253/quick_android_image_picker/fragment/QuickAndroidClipImageFragment.kt
@@ -0,0 +1,118 @@
+package cn.qjm253.quick_android_image_picker.fragment
+
+import android.Manifest
+import android.annotation.SuppressLint
+import android.content.Intent
+import android.graphics.Bitmap
+import cn.qjm253.quick_android_base.base.fragment.BaseQuickAndroidFragment
+import cn.qjm253.quick_android_base.extensions.e
+import cn.qjm253.quick_android_base.extensions.i
+import cn.qjm253.quick_android_base.params.IntentParam
+import cn.qjm253.quick_android_base.util.FileUtils
+import cn.qjm253.quick_android_base.util.RxSchedulersHelper
+import cn.qjm253.quick_android_image_picker.R
+import io.reactivex.Observable
+import io.reactivex.ObservableEmitter
+import kotlinx.android.synthetic.main.fragment_quick_android_clip_image.*
+import java.io.File
+import java.lang.Exception
+
+/**
+ * 图片裁剪Fragment
+ *
+ * @author SunnyQjm
+ * @date 19-7-7 下午3:58
+ */
+class QuickAndroidClipImageFragment : BaseQuickAndroidFragment() {
+
+ companion object {
+ // 需要裁剪的原图片路径
+ const val QUICK_ANDROID_CLIP_IMAGE_IMAGE_PATH = "QUICK_ANDROID_CLIP_IMAGE_IMAGE_PATH"
+
+ // 压缩强度
+ const val QUICK_ANDROID_CLIP_IMAGE_MULTIPLE = "QUICK_ANDROID_CLIP_IMAGE_MULTIPLE"
+
+ // 裁剪后的图片路径
+ const val QUICK_ANDROID_CLIP_IMAGE_RESULT = "QUICK_ANDROID_CLIP_IMAGE_RESULT"
+
+ const val QUICK_ANDROID_CLIP_IMAGE_RW_PERMISSION_REQUEST_CODE = 1
+
+ const val DEFAULT_MULTIPLE = 0.3f
+ fun newInstance(path: String, multiple: Float = 0.3f): QuickAndroidClipImageFragment {
+ return newInstance_(
+ QuickAndroidClipImageFragment::class.java, IntentParam()
+ .add(QUICK_ANDROID_CLIP_IMAGE_IMAGE_PATH, path)
+ .add(QUICK_ANDROID_CLIP_IMAGE_MULTIPLE, multiple)
+ )
+ }
+ }
+
+
+ private var multiple: Float = DEFAULT_MULTIPLE
+
+ override fun getRes(): Int = R.layout.fragment_quick_android_clip_image
+
+ override fun initView() {
+ val imagePath = arguments?.getString(QUICK_ANDROID_CLIP_IMAGE_IMAGE_PATH) ?: ""
+ multiple = arguments?.getFloat(QUICK_ANDROID_CLIP_IMAGE_MULTIPLE, DEFAULT_MULTIPLE) ?: DEFAULT_MULTIPLE
+
+ // 检查边界,限制在 0~1之间
+ if (multiple < 0) {
+ multiple = 0.1f
+ } else if (multiple > 1f) {
+ multiple = 1f
+ }
+ clipImageLayout.setImagePath(imagePath)
+ }
+
+ @SuppressLint("CheckResult")
+ private fun doCLip_() {
+ Observable.create { emitter: ObservableEmitter ->
+ try {
+ FileUtils.generatePictureFile("clip.png").apply {
+ if (!exists())
+ createNewFile()
+ outputStream().use {
+ clipImageLayout.clip(multiple)
+ .compress(Bitmap.CompressFormat.PNG, 100, it)
+ }
+ emitter.onNext(this)
+ emitter.onComplete()
+ }
+ } catch (err: Exception) {
+ emitter.onError(err)
+ }
+ }
+ .compose(RxSchedulersHelper.io_main())
+ .subscribe({ file ->
+ Intent().let {
+ it.putExtra(QUICK_ANDROID_CLIP_IMAGE_RESULT, file.absolutePath)
+ activity?.setResult(0, it)
+ activity?.finish()
+ }
+ "${file.absolutePath} 压缩成功 (${file.length()})".i()
+ }, {
+ it.e()
+ })
+ }
+
+ fun doClip() {
+ requestPermissions(
+ arrayOf(Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE),
+ R.string.clip_image_permission_tip, QUICK_ANDROID_CLIP_IMAGE_RW_PERMISSION_REQUEST_CODE
+ )
+ }
+
+ override fun onPermissionsGranted(requestCode: Int, perms: MutableList) {
+ super.onPermissionsGranted(requestCode, perms)
+ when (requestCode) {
+ QUICK_ANDROID_CLIP_IMAGE_RW_PERMISSION_REQUEST_CODE -> {
+ doCLip_()
+ }
+ }
+ }
+
+ override fun initialLoadData() {
+ }
+
+}
\ No newline at end of file
diff --git a/quick_android_image_picker/src/main/java/cn/qjm253/quick_android_image_picker/views/ClipImageBorderView.kt b/quick_android_image_picker/src/main/java/cn/qjm253/quick_android_image_picker/views/ClipImageBorderView.kt
new file mode 100644
index 0000000..a20d011
--- /dev/null
+++ b/quick_android_image_picker/src/main/java/cn/qjm253/quick_android_image_picker/views/ClipImageBorderView.kt
@@ -0,0 +1,107 @@
+package cn.qjm253.quick_android_image_picker.views
+
+import android.content.Context
+import android.graphics.Canvas
+import android.graphics.Color
+import android.graphics.Paint
+import android.graphics.Paint.Style
+import android.util.AttributeSet
+import android.util.TypedValue
+import android.view.View
+
+/**
+ * @author fucker
+ */
+class ClipImageBorderView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyle: Int = 0) :
+ View(context, attrs, defStyle) {
+ /**
+ * 水平方向与View的边距
+ */
+ private var mHorizontalPadding: Int = 0
+ /**
+ * 垂直方向与View的边距
+ */
+ private var mVerticalPadding: Int = 0
+ /**
+ * 裁剪框的纵横比,mVerticalPadding通过该值计算
+ * 默认是正方形,所以等于1
+ */
+ private var aspectRatio = 1f
+ /**
+ * 绘制的矩形的宽度
+ */
+ private var mWidth: Int = 0
+ /**
+ * 边框的颜色,默认为白色
+ */
+ private val mBorderColor = Color.parseColor("#FFFFFF")
+ /**
+ * 边框的宽度单位dp
+ */
+ private var mBorderWidth = 1
+
+ private val mPaint: Paint
+
+ init {
+
+ mBorderWidth = TypedValue.applyDimension(
+ TypedValue.COMPLEX_UNIT_DIP, mBorderWidth.toFloat(), resources
+ .displayMetrics
+ ).toInt()
+ mPaint = Paint()
+ mPaint.isAntiAlias = true
+ }
+
+ override fun onDraw(canvas: Canvas) {
+ super.onDraw(canvas)
+ // 计算矩形区域的宽度
+ mWidth = width - 2 * mHorizontalPadding
+ // 计算距离屏幕垂直边界的边距
+ mVerticalPadding = ((height - mWidth * aspectRatio) / 2).toInt()
+ mPaint.color = Color.parseColor("#000000")
+ mPaint.alpha = (255 * 0.48f).toInt()
+ mPaint.style = Style.FILL
+ // 绘制左边1
+ canvas.drawRect(0f, 0f, mHorizontalPadding.toFloat(), height.toFloat(), mPaint)
+ // 绘制右边2
+ canvas.drawRect(
+ (width - mHorizontalPadding).toFloat(), 0f, width.toFloat(),
+ height.toFloat(), mPaint
+ )
+ // 绘制上边3
+ canvas.drawRect(
+ mHorizontalPadding.toFloat(), 0f, (width - mHorizontalPadding).toFloat(),
+ mVerticalPadding.toFloat(), mPaint
+ )
+ // 绘制下边4
+ canvas.drawRect(
+ mHorizontalPadding.toFloat(), (height - mVerticalPadding).toFloat(),
+ (width - mHorizontalPadding).toFloat(), height.toFloat(), mPaint
+ )
+ // 绘制外边距
+ mPaint.color = mBorderColor
+ mPaint.strokeWidth = mBorderWidth.toFloat()
+ mPaint.style = Style.STROKE
+ canvas.drawRect(
+ mHorizontalPadding.toFloat(),
+ mVerticalPadding.toFloat(),
+ (width - mHorizontalPadding).toFloat(),
+ (height - mVerticalPadding).toFloat(),
+ mPaint
+ )
+
+ }
+
+ fun setHorizontalPadding(mHorizontalPadding: Int) {
+ this.mHorizontalPadding = mHorizontalPadding
+ }
+
+ /**
+ * 设置clip框的纵横比
+ *
+ * @param aspectRatio
+ */
+ fun setAspectRatio(aspectRatio: Float) {
+ this.aspectRatio = aspectRatio
+ }
+}
\ No newline at end of file
diff --git a/quick_android_image_picker/src/main/java/cn/qjm253/quick_android_image_picker/views/ClipImageLayout.kt b/quick_android_image_picker/src/main/java/cn/qjm253/quick_android_image_picker/views/ClipImageLayout.kt
new file mode 100644
index 0000000..a1e92f4
--- /dev/null
+++ b/quick_android_image_picker/src/main/java/cn/qjm253/quick_android_image_picker/views/ClipImageLayout.kt
@@ -0,0 +1,104 @@
+package cn.qjm253.quick_android_image_picker.views
+
+import android.content.Context
+import android.graphics.Bitmap
+import android.graphics.BitmapFactory
+import android.util.AttributeSet
+import android.util.TypedValue
+import android.widget.RelativeLayout
+import cn.qjm253.quick_android_base.util.DisplayUtils
+import kotlin.math.roundToInt
+
+
+/**
+ * @author fucker
+ */
+class ClipImageLayout(context: Context, attrs: AttributeSet) : RelativeLayout(context, attrs) {
+
+ private val mZoomImageView: ClipZoomImageView = ClipZoomImageView(context)
+ private val mClipImageView: ClipImageBorderView = ClipImageBorderView(context)
+
+ private var mHorizontalPadding = 24
+ private var aspectRatio = 1f
+
+ init {
+
+ val lp = LayoutParams(
+ android.view.ViewGroup.LayoutParams.MATCH_PARENT,
+ android.view.ViewGroup.LayoutParams.MATCH_PARENT
+ )
+
+ this.addView(mZoomImageView, lp)
+ this.addView(mClipImageView, lp)
+
+ setHorizontalPadding(mHorizontalPadding)
+ setAspectRatio(aspectRatio)
+ }
+
+ fun setImageBitmap(bm: Bitmap) {
+ mZoomImageView.setImageBitmap(bm)
+ }
+
+ fun setImagePath(path: String) {
+ val screen = DisplayUtils.getScreenParams(context)
+ val width = screen[0]
+ val height = screen[1]
+
+ val options = BitmapFactory.Options()
+ options.inJustDecodeBounds = true
+ BitmapFactory.decodeFile(path, options)
+ var scale = (if (options.outWidth * 1.0 / width < 1)
+ 1f
+ else
+ options.outWidth * 1.0f / width)
+ val scale2 = (if (options.outHeight * 1.0 / height < 1)
+ 1f
+ else
+ options.outHeight * 1.0f / height)
+ scale = if (scale < scale2) scale2 else scale//select a bigger scale number.
+ options.outHeight = (options.outHeight / scale).toInt()
+ options.outWidth = (options.outWidth / scale).toInt()
+ options.inSampleSize = scale.roundToInt()
+ options.inJustDecodeBounds = false
+ val bm = BitmapFactory.decodeFile(path, options)
+ setImageBitmap(bm)
+ }
+
+ /**
+ * 设置左右边距的方法
+ *
+ * @param mHorizontalPadding dp
+ */
+ fun setHorizontalPadding(mHorizontalPadding: Int) {
+ var mHorizontalPadding = mHorizontalPadding
+ this.mHorizontalPadding = mHorizontalPadding
+ // 计算padding的px
+ mHorizontalPadding = TypedValue.applyDimension(
+ TypedValue.COMPLEX_UNIT_DIP, mHorizontalPadding.toFloat(), resources
+ .displayMetrics
+ ).toInt()
+ mZoomImageView.setHorizontalPadding(mHorizontalPadding)
+ mClipImageView.setHorizontalPadding(mHorizontalPadding)
+ }
+
+ /**
+ * 设置裁剪图像的纵横比
+ *
+ * @param aspectRatio = height / width
+ */
+ fun setAspectRatio(aspectRatio: Float) {
+ this.aspectRatio = aspectRatio
+ mZoomImageView.setAspectRatio(aspectRatio)
+ mClipImageView.setAspectRatio(aspectRatio)
+ }
+
+ /**
+ * 裁切图片,multiple 0~1.0f
+ *
+ * @return
+ */
+ fun clip(multiple: Float): Bitmap {
+ return mZoomImageView.clip(multiple)
+ }
+
+}
\ No newline at end of file
diff --git a/quick_android_image_picker/src/main/java/cn/qjm253/quick_android_image_picker/views/ClipZoomImageView.kt b/quick_android_image_picker/src/main/java/cn/qjm253/quick_android_image_picker/views/ClipZoomImageView.kt
new file mode 100644
index 0000000..563b724
--- /dev/null
+++ b/quick_android_image_picker/src/main/java/cn/qjm253/quick_android_image_picker/views/ClipZoomImageView.kt
@@ -0,0 +1,361 @@
+package cn.qjm253.quick_android_image_picker.views
+
+
+import android.annotation.SuppressLint
+import android.content.Context
+import android.graphics.Bitmap
+import android.graphics.Canvas
+import android.graphics.Matrix
+import android.graphics.RectF
+import android.util.AttributeSet
+import android.view.*
+import androidx.appcompat.widget.AppCompatImageView
+import kotlin.math.sqrt
+
+/**
+ * @author fucker
+ */
+@SuppressLint("ClickableViewAccessibility")
+class ClipZoomImageView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) :
+ AppCompatImageView(context, attrs), ScaleGestureDetector.OnScaleGestureListener, View.OnTouchListener,
+ ViewTreeObserver.OnGlobalLayoutListener {
+
+ private var initScale = 0.5f
+ private var once = true
+
+ private val matrixValues = FloatArray(9)
+
+ private var mScaleGestureDetector: ScaleGestureDetector? = null
+ private val mScaleMatrix = Matrix()
+
+ /**
+ */
+ private val mGestureDetector: GestureDetector
+ private var isAutoScale: Boolean = false
+
+ private val mTouchSlop: Int = 0
+
+ private var mLastX: Float = 0.toFloat()
+ private var mLastY: Float = 0.toFloat()
+
+ private var isCanDrag: Boolean = false
+ private var lastPointerCount: Int = 0
+
+ private val matrixRectF: RectF
+ get() {
+ val matrix = mScaleMatrix
+ val rect = RectF()
+ val d = drawable
+ if (null != d) {
+ rect.set(0f, 0f, d.intrinsicWidth.toFloat(), d.intrinsicHeight.toFloat())
+ matrix.mapRect(rect)
+ }
+ return rect
+ }
+
+ val scale: Float
+ get() {
+ mScaleMatrix.getValues(matrixValues)
+ return matrixValues[Matrix.MSCALE_X]
+ }
+
+ private var mHorizontalPadding: Int = 0
+ private var mVerticalPadding: Int = 0
+ /**
+ * 裁剪框的纵横比,mVerticalPadding通过该值计算
+ * 默认是正方形,所以等于1
+ */
+ private var aspectRatio = 1f
+
+ init {
+
+ scaleType = ScaleType.MATRIX
+ mGestureDetector = GestureDetector(context,
+ object : GestureDetector.SimpleOnGestureListener() {
+ override fun onDoubleTap(e: MotionEvent): Boolean {
+ if (isAutoScale == true)
+ return true
+
+ val x = e.x
+ val y = e.y
+ if (scale < SCALE_MID) {
+ this@ClipZoomImageView.postDelayed(
+ AutoScaleRunnable(SCALE_MID, x, y), 16
+ )
+ isAutoScale = true
+ } else {
+ this@ClipZoomImageView.postDelayed(
+ AutoScaleRunnable(initScale, x, y), 16
+ )
+ isAutoScale = true
+ }
+
+ return true
+ }
+ })
+ mScaleGestureDetector = ScaleGestureDetector(context, this)
+ this.setOnTouchListener(this)
+ }
+
+ fun setInitScale(scale: Float) {
+ if (drawable != null) {
+ this.initScale = scale
+ }
+ }
+
+ /**
+ * @author zhy
+ */
+ private inner class AutoScaleRunnable
+ /**
+ * @param targetScale
+ */
+ (private val mTargetScale: Float, private val x: Float, private val y: Float) : Runnable {
+ private var tmpScale: Float = 0.toFloat()
+
+ init {
+ if (scale < mTargetScale) {
+ tmpScale = 1.07f
+ } else {
+ tmpScale = 0.93f
+ }
+
+ }
+
+ override fun run() {
+ mScaleMatrix.postScale(tmpScale, tmpScale, x, y)
+ checkBorder()
+ imageMatrix = mScaleMatrix
+
+ val currentScale = scale
+ if (tmpScale > 1f && currentScale < mTargetScale || tmpScale < 1f && mTargetScale < currentScale) {
+ this@ClipZoomImageView.postDelayed(this, 16)
+ } else {
+ val deltaScale = mTargetScale / currentScale
+ mScaleMatrix.postScale(deltaScale, deltaScale, x, y)
+ checkBorder()
+ imageMatrix = mScaleMatrix
+ isAutoScale = false
+ }
+
+ }
+
+ }
+
+ override fun onScale(detector: ScaleGestureDetector): Boolean {
+ val scale = scale
+ var scaleFactor = detector.scaleFactor
+
+ if (drawable == null)
+ return true
+
+ if (scale < SCALE_MAX && scaleFactor > 1.0f || scale > initScale && scaleFactor <= 1.0f) {
+ if (scaleFactor * scale < initScale) {
+ scaleFactor = initScale / scale
+ }
+ if (scaleFactor * scale > SCALE_MAX) {
+ scaleFactor = SCALE_MAX / scale
+ }
+ mScaleMatrix.postScale(
+ scaleFactor, scaleFactor,
+ detector.focusX, detector.focusY
+ )
+ checkBorder()
+ imageMatrix = mScaleMatrix
+ }
+ return true
+
+ }
+
+ override fun onScaleBegin(detector: ScaleGestureDetector): Boolean {
+ return true
+ }
+
+ @SuppressLint("ClickableViewAccessibility")
+ override fun onScaleEnd(detector: ScaleGestureDetector) {
+ }
+
+ override fun onTouch(v: View, event: MotionEvent): Boolean {
+
+ if (mGestureDetector.onTouchEvent(event))
+ return true
+ mScaleGestureDetector!!.onTouchEvent(event)
+
+ var x = 0f
+ var y = 0f
+ val pointerCount = event.pointerCount
+ for (i in 0 until pointerCount) {
+ x += event.getX(i)
+ y += event.getY(i)
+ }
+ x /= pointerCount
+ y /= pointerCount
+
+ if (pointerCount != lastPointerCount) {
+ isCanDrag = false
+ mLastX = x
+ mLastY = y
+ }
+
+ lastPointerCount = pointerCount
+ when (event.action) {
+ MotionEvent.ACTION_MOVE -> {
+ var dx = x - mLastX
+ var dy = y - mLastY
+
+ if (!isCanDrag) {
+ isCanDrag = isCanDrag(dx, dy)
+ }
+ if (isCanDrag) {
+ if (drawable != null) {
+
+ val rectF = matrixRectF
+
+ if (rectF.width() <= width - mHorizontalPadding * 2) {
+ dx = 0f
+ }
+ if (rectF.height() <= height - mVerticalPadding * 2) {
+ dy = 0f
+ }
+ mScaleMatrix.postTranslate(dx, dy)
+ checkBorder()
+ imageMatrix = mScaleMatrix
+ }
+ }
+ mLastX = x
+ mLastY = y
+ }
+
+ MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> lastPointerCount = 0
+ }
+
+ return true
+ }
+
+ override fun onAttachedToWindow() {
+ super.onAttachedToWindow()
+ viewTreeObserver.addOnGlobalLayoutListener(this)
+ }
+
+ override fun onDetachedFromWindow() {
+ super.onDetachedFromWindow()
+ viewTreeObserver.removeGlobalOnLayoutListener(this)
+ }
+
+ override fun onGlobalLayout() {
+ if (once) {
+ val d = drawable ?: return
+ mVerticalPadding = ((height - aspectRatio * (width - 2 * mHorizontalPadding)) / 2).toInt()
+
+ val width = width
+ val height = height
+
+ val dw = d.intrinsicWidth
+ val dh = d.intrinsicHeight
+ var scale = 1.0f
+ if (dw < getWidth() - mHorizontalPadding * 2 && dh > getHeight() - mVerticalPadding * 2) {
+ scale = (getWidth() * 1.0f - mHorizontalPadding * 2) / dw
+ }
+
+ if (dh < getHeight() - mVerticalPadding * 2 && dw > getWidth() - mHorizontalPadding * 2) {
+ scale = (getHeight() * 1.0f - mVerticalPadding * 2) / dh
+ }
+
+ if (dw < getWidth() - mHorizontalPadding * 2 && dh < getHeight() - mVerticalPadding * 2) {
+ val scaleW = (getWidth() * 1.0f - mHorizontalPadding * 2) / dw
+ val scaleH = (getHeight() * 1.0f - mVerticalPadding * 2) / dh
+ scale = Math.max(scaleW, scaleH)
+ }
+ if (dw >= getWidth() - mHorizontalPadding * 2 && dh >= getHeight() - mVerticalPadding * 2) {
+ val scaleW = (getWidth() * 1.0f - mHorizontalPadding * 2) / dw
+ val scaleH = (getHeight() * 1.0f - mVerticalPadding * 2) / dh
+ scale = Math.max(scaleW, scaleH)
+ }
+
+ initScale = scale
+ SCALE_MID = initScale * 2
+ SCALE_MAX = initScale * 4
+ mScaleMatrix.postTranslate(((width - dw) / 2).toFloat(), ((height - dh) / 2).toFloat())
+ mScaleMatrix.postScale(
+ scale, scale, (getWidth() / 2).toFloat(),
+ (getHeight() / 2).toFloat()
+ )
+
+ imageMatrix = mScaleMatrix
+ once = false
+ }
+
+ }
+
+ fun clip(multiple: Float): Bitmap {
+ var multiple = multiple
+ multiple = if (multiple <= 0) 1.0f else multiple
+ multiple = if (multiple > 1) 1.0f else multiple
+ val width = width
+ val height = height
+ val bitmap = Bitmap.createBitmap(
+ width, height,
+ Bitmap.Config.ARGB_8888
+ )
+ val canvas = Canvas(bitmap)
+ draw(canvas)
+ val matrix = Matrix()
+ matrix.postScale(multiple, multiple)
+ return Bitmap.createBitmap(
+ bitmap, mHorizontalPadding,
+ mVerticalPadding, width - 2 * mHorizontalPadding, height - 2 * mVerticalPadding, matrix, true
+ )
+ }
+
+ private fun checkBorder() {
+
+ val rect = matrixRectF
+ var deltaX = 0f
+ var deltaY = 0f
+
+ val width = width
+ val height = height
+ if (rect.width() + 0.01 >= width - 2 * mHorizontalPadding) {
+ if (rect.left > mHorizontalPadding) {
+ deltaX = -rect.left + mHorizontalPadding
+ }
+ if (rect.right < width - mHorizontalPadding) {
+ deltaX = width.toFloat() - mHorizontalPadding.toFloat() - rect.right
+ }
+ }
+ if (rect.height() + 0.01 >= height - 2 * mVerticalPadding) {
+ if (rect.top > mVerticalPadding) {
+ deltaY = -rect.top + mVerticalPadding
+ }
+ if (rect.bottom < height - mVerticalPadding) {
+ deltaY = height.toFloat() - mVerticalPadding.toFloat() - rect.bottom
+ }
+ }
+ mScaleMatrix.postTranslate(deltaX, deltaY)
+
+ }
+
+ private fun isCanDrag(dx: Float, dy: Float): Boolean {
+ return sqrt((dx * dx + dy * dy).toDouble()) >= mTouchSlop
+ }
+
+ fun setHorizontalPadding(mHorizontalPadding: Int) {
+ this.mHorizontalPadding = mHorizontalPadding
+ }
+
+ /**
+ * 设置clip框的纵横比
+ *
+ * @param aspectRatio
+ */
+ fun setAspectRatio(aspectRatio: Float) {
+ this.aspectRatio = aspectRatio
+ }
+
+ companion object {
+
+ var SCALE_MAX = 4.0f
+ private var SCALE_MID = 2.0f
+ }
+
+}
\ No newline at end of file
diff --git a/quick_android_image_picker/src/main/res/layout/activity_quick_android_clip_image.xml b/quick_android_image_picker/src/main/res/layout/activity_quick_android_clip_image.xml
new file mode 100644
index 0000000..7f8f6b6
--- /dev/null
+++ b/quick_android_image_picker/src/main/res/layout/activity_quick_android_clip_image.xml
@@ -0,0 +1,29 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/quick_android_image_picker/src/main/res/layout/fragment_quick_android_clip_image.xml b/quick_android_image_picker/src/main/res/layout/fragment_quick_android_clip_image.xml
new file mode 100644
index 0000000..3fede61
--- /dev/null
+++ b/quick_android_image_picker/src/main/res/layout/fragment_quick_android_clip_image.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/quick_android_image_picker/src/main/res/values/strings.xml b/quick_android_image_picker/src/main/res/values/strings.xml
new file mode 100644
index 0000000..1a2ada8
--- /dev/null
+++ b/quick_android_image_picker/src/main/res/values/strings.xml
@@ -0,0 +1,3 @@
+
+ 裁剪图片需要获取读写文件的权限
+
diff --git a/quick_android_image_picker/src/test/java/cn/qjm253/quick_android_image_picker/ExampleUnitTest.java b/quick_android_image_picker/src/test/java/cn/qjm253/quick_android_image_picker/ExampleUnitTest.java
new file mode 100644
index 0000000..aea8878
--- /dev/null
+++ b/quick_android_image_picker/src/test/java/cn/qjm253/quick_android_image_picker/ExampleUnitTest.java
@@ -0,0 +1,17 @@
+package cn.qjm253.quick_android_image_picker;
+
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+/**
+ * Example local unit test, which will execute on the development machine (host).
+ *
+ * @see Testing documentation
+ */
+public class ExampleUnitTest {
+ @Test
+ public void addition_isCorrect() {
+ assertEquals(4, 2 + 2);
+ }
+}
\ No newline at end of file
diff --git a/quick_android_mvp/.gitignore b/quick_android_mvp/.gitignore
new file mode 100644
index 0000000..796b96d
--- /dev/null
+++ b/quick_android_mvp/.gitignore
@@ -0,0 +1 @@
+/build
diff --git a/quick_android_mvp/README.md b/quick_android_mvp/README.md
new file mode 100644
index 0000000..1b700d8
--- /dev/null
+++ b/quick_android_mvp/README.md
@@ -0,0 +1,172 @@
+> ## quick_android_mvp模块
+>
+> 本模块提供了一个快速实现MVP架构的框架,并且封装了网络请求(Retrofit + RxJava)
+
+- ### 说明
+
+ 本模块提供提供了一系列MVP模式实现代码的封装,并且使用[retrofit](https://github.com/square/retrofit) + [RxJava2](https://github.com/ReactiveX/RxJava)
+ 实现了网络请求。默认使用[AutoDispose](https://github.com/uber/AutoDispose)来实现订阅对象随Activity的销毁而自动取消,防止内存泄漏
+
+- ### 使用
+
+ - 初始化
+
+ ```kotlin
+ /**
+ * QuickAndroidMVP 模块初始化操作
+ *
+ * @param context Application 对象
+ * @param baseUrl 网络请求的前缀, eg.: https://example.cn/
+ * @param enablePersistentCookieJar 是否开启Cookie的自动持久化和发送
+ */
+ QuickAndroidMVP
+ .init(
+ context, baseUrl, enablePersistentCookieJar
+ )
+
+ // 当然,如果使用Kotlin开发也可以使用下面的统一扩展接口,参数说明同上
+ QuickAndroid.initMVP(
+ context, baseUrl, enablePersistentCookieJar
+ )
+ ```
+
+ 具体MVP架构的使用方式参考本项目的[demo](https://github.com/SunnyQjm/quickandroid/tree/master/app/src/main/java/cn/qjm253/quick_android/mvp_demo)
+
+- ### 如何使用网络请求?
+
+ - 首先定义网络请求的Service接口(具体定义规则参看[Retrofit 官网](https://square.github.io/retrofit/))
+
+ ```kotlin
+ interface TestServices {
+
+ @GET("api")
+ fun getWeather(
+ @Query("city") city: String = "成都",
+ @Query("version") version: String = "v1"
+ ): Observable
+ }
+ ```
+
+ - 使用
+
+ ```kotlin
+ QuickAndroidMVPAPIManager.getService(TestServices::class.java)
+ .getWeather("成都")
+ .compose(RxScheduler.io_main())
+ .subscribe({ // onNext
+
+ }, { // onError
+
+ }, { // onComplete
+
+ })
+
+ ```
+
+ 轻松两步便可实现网络请求
+
+- ### 如何对每一个网络请求添加监听?
+
+ 下面的所有接口都有对应 `QuickAndroid` 的扩展,如果使用`kotlin`开发的话,也可以使用`QuickAndroid.xxx`的方式来调用。**所有的网络请求,只有使用`qaSubscribe`扩展进行订阅的才会调用下面的回调**
+
+ - 添加一个可以监听所有网络请求的onComplete事件的监听器
+
+ ```kotlin
+ /**
+ * 添加一个网络请求 onComplete 的拦截监听
+ * 每一个使用 Observable.qaSubscribe 进行订阅的网络请求的onComplete执行的时候都会触发
+ *
+ * @see cn.qjm253.quick_android_mvp.model.inernet.rx.qaSubscribe
+ */
+ QuickAndroidMVP.addOnAPICompleteListener(onComplete)
+
+ // 可以通过下面的方法清空所有对onComplete事件的监听器
+ QuickAndroidMVP.clearOnAPICompleteListeners()
+ ```
+
+ - 添加一个可以监听所有网络请求的onNext事件的监听器
+
+ ```kotlin
+ /**
+ * 添加一个网络请求 onNext 的拦截监听
+ * 每一个使用 Observable.qaSubscribe 进行订阅的网络请求的onNext执行的之前都会触发
+ *
+ * @see cn.qjm253.quick_android_mvp.model.inernet.rx.qaSubscribe
+ */
+ QuickAndroidMVP.addOnAPICompleteListener(onBeforeNextListener)
+
+ QuickAndroidMVP.clearOnAPIBeforeNextListeners()
+ ```
+
+ - 添加一个可以监听所有网络请求的onError事件的监听器
+
+ ```kotlin
+ /**
+ * 添加一个网络请求 onError 的拦截监听
+ * 每一个使用 Observable.qaSubscribe 进行订阅的网络请求的onError执行的时候都会触发
+ *
+ * @see cn.qjm253.quick_android_mvp.model.inernet.rx.qaSubscribe
+ */
+ QuickAndroidMVP.addOnAPIErrorListener(onErrorListener)
+
+ // 可以通过下面的方法清空所有对onError事件的监听器
+ QuickAndroidMVP.clearOnAPIErrorListeners()
+ ```
+
+- ### 如何对网络请求的结果进行统一的处理
+
+ - 使用要求
+
+ - 所有的网络请求都必须使用`qaHandleResult()`扩展
+
+ ```kotlin
+ Observable.just(BaseQuickAndroidResponseBody(-2))
+ .qaHandleResult()
+ .qaSubscribe(mView, {
+
+ })
+ ```
+
+ - 网络请求的Model数据类需要继承自[BaseQuickAndroidResponseBody](https://github.com/SunnyQjm/quickandroid/blob/master/quick_android_mvp/src/main/java/cn/qjm253/quick_android_mvp/model/response/BaseQuickAndroidResponseBody.kt),里面包含一个`code`属性标识网络请求的状态码
+
+ - 使用
+
+ 网络请求成功的情况下,通常返回的结果中会包含状态码(状态码一般又分为请求正常、请求错误等等),如果需要对网络请求的结果进行统一处理,便可以使用如下的方式
+
+ ```kotlin
+ QuickAndroidMVP
+ .registerAPIResultDeal(object : QuickAndroidMVPResultDeal(
+ // 包含所有网络请求失败的code
+ arrayOf(
+ -1 to "网络请求失败"
+ ),
+
+ // 包含所有请求成功的code
+ arrayOf(
+ 0 to "网络请求成功"
+ ),
+
+ // token 失效,需要重新获取token之后重新发起请求的code
+ arrayOf(
+ -2 to "Token失效"
+ )
+ ) {
+ override fun dealRetry(t: Throwable): Observable {
+ t.e("dealRetry")
+ return Observable.error(t)
+ }
+
+ override fun normalDeal(code: Int, description: String) {
+ "normalDeal => code: $code, description: $description".i()
+ }
+
+ override fun errorDeal(code: Int, description: String) {
+ "errorDeal => code: $code, description: $description".e()
+ }
+
+ })
+ ```
+
+
+
+
\ No newline at end of file
diff --git a/quick_android_mvp/build.gradle b/quick_android_mvp/build.gradle
new file mode 100644
index 0000000..4dcc7d8
--- /dev/null
+++ b/quick_android_mvp/build.gradle
@@ -0,0 +1,59 @@
+apply plugin: 'com.android.library'
+
+apply plugin: 'kotlin-android'
+
+apply plugin: 'kotlin-android-extensions'
+
+apply plugin: 'kotlin-kapt'
+
+def AUTO_DISPOSE_VERSION = "1.3.0"
+
+android {
+ compileSdkVersion compile_sdk_version
+
+ defaultConfig {
+ minSdkVersion min_sdk_version
+ targetSdkVersion target_sdk_version
+ versionCode 1
+ versionName "1.0"
+
+ testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
+
+ }
+
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+ }
+ }
+
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_1_8
+ targetCompatibility JavaVersion.VERSION_1_8
+ }
+
+}
+
+dependencies {
+ implementation fileTree(dir: 'libs', include: ['*.jar'])
+
+ implementation "androidx.appcompat:appcompat:${appcompact_version}"
+ testImplementation "junit:junit:${junit_version}"
+ androidTestImplementation "androidx.test:runner:${test_runner_version}"
+ androidTestImplementation "androidx.test.espresso:espresso-core:${espresso_core_version}"
+
+ // AutoDispose
+ implementation "com.uber.autodispose:autodispose:${AUTO_DISPOSE_VERSION}"
+ implementation "com.uber.autodispose:autodispose-android:${AUTO_DISPOSE_VERSION}"
+ implementation "com.uber.autodispose:autodispose-lifecycle:${AUTO_DISPOSE_VERSION}"
+ implementation "com.uber.autodispose:autodispose-android-archcomponents:${AUTO_DISPOSE_VERSION}"
+
+
+
+ // PersistentCookieJar
+ implementation "com.github.franmontiel:PersistentCookieJar:${persistent_cookie_jar_version}"
+
+
+ implementation project(path: ':quick_android_base')
+}
diff --git a/quick_android_mvp/proguard-rules.pro b/quick_android_mvp/proguard-rules.pro
new file mode 100644
index 0000000..1eb290a
--- /dev/null
+++ b/quick_android_mvp/proguard-rules.pro
@@ -0,0 +1,34 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
+
+##---------------Begin: AndroidX---------------##
+-keep class com.google.android.material.** {*;}
+-keep class androidx.** {*;}
+-keep public class * extends androidx.**
+-keep interface androidx.** {*;}
+-dontwarn com.google.android.material.**
+-dontnote com.google.android.material.**
+-dontwarn androidx.**
+##---------------End: AndroidX---------------##
+
+
+
diff --git a/quick_android_mvp/src/androidTest/java/cn/qjm253/quick_android_mvp/ExampleInstrumentedTest.java b/quick_android_mvp/src/androidTest/java/cn/qjm253/quick_android_mvp/ExampleInstrumentedTest.java
new file mode 100644
index 0000000..904e2dd
--- /dev/null
+++ b/quick_android_mvp/src/androidTest/java/cn/qjm253/quick_android_mvp/ExampleInstrumentedTest.java
@@ -0,0 +1,26 @@
+package cn.qjm253.quick_android_mvp;
+
+import android.content.Context;
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import static org.junit.Assert.*;
+
+/**
+ * Instrumented test, which will execute on an Android device.
+ *
+ * @see Testing documentation
+ */
+@RunWith(AndroidJUnit4.class)
+public class ExampleInstrumentedTest {
+ @Test
+ public void useAppContext() {
+ // Context of the app under test.
+ Context appContext = InstrumentationRegistry.getTargetContext();
+
+ assertEquals("cn.qjm253.quick_android_mvp.test", appContext.getPackageName());
+ }
+}
diff --git a/quick_android_mvp/src/main/AndroidManifest.xml b/quick_android_mvp/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..6415375
--- /dev/null
+++ b/quick_android_mvp/src/main/AndroidManifest.xml
@@ -0,0 +1,2 @@
+
diff --git a/quick_android_mvp/src/main/java/cn/qjm253/quick_android_mvp/QuickAndroidMVP.kt b/quick_android_mvp/src/main/java/cn/qjm253/quick_android_mvp/QuickAndroidMVP.kt
new file mode 100644
index 0000000..64525f1
--- /dev/null
+++ b/quick_android_mvp/src/main/java/cn/qjm253/quick_android_mvp/QuickAndroidMVP.kt
@@ -0,0 +1,196 @@
+package cn.qjm253.quick_android_mvp
+
+import android.app.Application
+import cn.qjm253.quick_android_base.QuickAndroid
+import cn.qjm253.quick_android_mvp.model.inernet.QuickAndroidMVPAPIManager
+import cn.qjm253.quick_android_mvp.model.inernet.rx.QuickAndroidMVPResultDeal
+import com.github.anzewei.parallaxbacklayout.ParallaxHelper
+
+/**
+ * @author SunnyQjm
+ * @date 19-7-7 上午8:43
+ */
+object QuickAndroidMVP {
+
+ //////////////////////////////////////////
+ /////// 初始化操作
+ //////////////////////////////////////////
+
+ /**
+ * QuickAndroidMVP 模块初始化操作
+ *
+ * @param context Application 对象
+ * @param baseUrl 网络请求的前缀, eg.: https://example.cn/
+ * @param enablePersistentCookieJar 是否开启Cookie的自动持久化和发送
+ */
+ fun init(
+ context: Application,
+ baseUrl: String = "",
+ enablePersistentCookieJar: Boolean = true
+ ): QuickAndroidMVP {
+ QuickAndroidMVPAPIManager.init(context, enablePersistentCookieJar, baseUrl)
+ return this
+ }
+
+
+
+
+
+ ////////////////////////////////////////////
+ //////// 网络请求统一处理
+ ////////////////////////////////////////////
+ val onCompleteListeners = mutableListOf<() -> Unit>()
+
+
+ /**
+ * 添加一个网络请求 onComplete 的拦截监听
+ * 每一个使用 Observable.qaSubscribe 进行订阅的网络请求的onComplete执行的时候都会触发
+ *
+ * @see cn.qjm253.quick_android_mvp.model.inernet.rx.qaSubscribe
+ */
+ fun addOnAPICompleteListener(onComplete: () -> Unit): QuickAndroidMVP {
+ onCompleteListeners.add(onComplete)
+ return this
+ }
+
+
+
+ fun clearOnAPICompleteListeners(): QuickAndroidMVP {
+ onCompleteListeners.clear()
+ return this
+ }
+
+
+ val onBeforeNextListeners = mutableListOf<() -> Unit>()
+
+ /**
+ * 添加一个网络请求 onNext 的拦截监听
+ * 每一个使用 Observable.qaSubscribe 进行订阅的网络请求的onNext执行的之前都会触发
+ *
+ * @see cn.qjm253.quick_android_mvp.model.inernet.rx.qaSubscribe
+ */
+ fun addOnAPIBeforeNextListener(onBeforeNextListener: () -> Unit): QuickAndroidMVP {
+ onBeforeNextListeners.add(onBeforeNextListener)
+ return this
+ }
+
+ fun clearOnAPIBeforeNextListeners(): QuickAndroidMVP {
+ onBeforeNextListeners.clear()
+ return this
+ }
+
+ val onErrorListeners = mutableListOf<(Throwable) -> Unit>()
+
+ /**
+ * 添加一个网络请求 onError 的拦截监听
+ * 每一个使用 Observable.qaSubscribe 进行订阅的网络请求的onError执行的时候都会触发
+ *
+ * @see cn.qjm253.quick_android_mvp.model.inernet.rx.qaSubscribe
+ */
+ fun addOnAPIErrorListener(onErrorListener: (Throwable) -> Unit): QuickAndroidMVP {
+ onErrorListeners.add(onErrorListener)
+ return this
+ }
+
+ fun clearOnAPIErrorListeners() {
+ onErrorListeners.clear()
+ }
+
+
+ /////////////////////////////////////////////////////
+ /////////// 网络请求结果统一处理
+ /////////////////////////////////////////////////////
+
+
+ var quickAndroidMVPResultDeal: QuickAndroidMVPResultDeal? = null
+
+ /**
+ * 注册App网络请求结果的统一处理
+ *
+ * @see QuickAndroidMVPResultDeal
+ */
+ fun registerAPIResultDeal(quickAndroidMVPResultDeal: QuickAndroidMVPResultDeal): QuickAndroidMVP {
+ this.quickAndroidMVPResultDeal = quickAndroidMVPResultDeal
+ return this
+ }
+
+}
+
+
+
+/*******************************************************
+ * 给 QuickAndroid 添加的扩展
+ *
+ * @see cn.qjm253.quick_android_base.QuickAndroid
+ *
+ ******************************************************/
+
+
+/**
+ * @see QuickAndroidMVP.init
+ */
+fun QuickAndroid.initMVP(
+ context: Application,
+ baseUrl: String = "",
+ enablePersistentCookieJar: Boolean = true
+): QuickAndroid {
+ QuickAndroidMVP.init(context, baseUrl, enablePersistentCookieJar)
+ return this
+}
+
+/**
+ * @see QuickAndroidMVP.addOnAPICompleteListener
+ */
+fun QuickAndroid.addOnAPICompleteListener(onComplete: () -> Unit): QuickAndroid {
+ QuickAndroidMVP.addOnAPICompleteListener(onComplete)
+ return this
+}
+
+
+/**
+ * @see QuickAndroidMVP.clearOnAPICompleteListeners
+ */
+fun QuickAndroid.clearOnAPICompleteListeners(): QuickAndroid {
+ QuickAndroidMVP.clearOnAPICompleteListeners()
+ return this
+}
+
+/**
+ * @see QuickAndroidMVP.addOnAPIBeforeNextListener
+ */
+fun QuickAndroid.addOnAPIBeforeNextListener(onBeforeNextListener: () -> Unit): QuickAndroid {
+ QuickAndroidMVP.addOnAPIBeforeNextListener(onBeforeNextListener)
+ return this
+}
+
+/**
+ * @see QuickAndroidMVP.clearOnAPIBeforeNextListeners
+ */
+fun QuickAndroid.clearOnAPIBeforeNextListeners(): QuickAndroid {
+ QuickAndroidMVP.clearOnAPIBeforeNextListeners()
+ return this
+}
+
+/**
+ * @see QuickAndroidMVP.addOnAPIErrorListener
+ */
+fun QuickAndroid.addOnAPIErrorListener(onErrorListener: (Throwable) -> Unit): QuickAndroid {
+ QuickAndroidMVP.addOnAPIErrorListener(onErrorListener)
+ return this
+}
+
+/**
+ * @see QuickAndroidMVP.clearOnAPIErrorListeners
+ */
+fun QuickAndroid.clearOnAPIErrorListeners(): QuickAndroid {
+ QuickAndroidMVP.clearOnAPIErrorListeners()
+ return this
+}
+
+/**
+ * @see QuickAndroidMVP.registerAPIResultDeal
+ */
+fun QuickAndroid.registerAPIResultDeal(quickAndroidMVPResultDeal: QuickAndroidMVPResultDeal): QuickAndroid {
+ QuickAndroidMVP.registerAPIResultDeal(quickAndroidMVPResultDeal)
+ return this
+}
diff --git a/quick_android_mvp/src/main/java/cn/qjm253/quick_android_mvp/base/activity/BaseQuickAndroidMVPActivity.kt b/quick_android_mvp/src/main/java/cn/qjm253/quick_android_mvp/base/activity/BaseQuickAndroidMVPActivity.kt
new file mode 100644
index 0000000..93cc1fc
--- /dev/null
+++ b/quick_android_mvp/src/main/java/cn/qjm253/quick_android_mvp/base/activity/BaseQuickAndroidMVPActivity.kt
@@ -0,0 +1,69 @@
+package cn.qjm253.quick_android_mvp.base.activity
+
+import android.os.Bundle
+import androidx.lifecycle.Lifecycle
+import cn.qjm253.quick_android_base.base.activity.BaseQuickAndroidActivity
+import cn.qjm253.quick_android_base.extensions.toast
+import cn.qjm253.quick_android_mvp.base.mvp.BasePresenter
+import cn.qjm253.quick_android_mvp.base.mvp.BaseView
+import com.orhanobut.logger.Logger
+//import com.uber.autodispose.AutoDispose
+//import com.uber.autodispose.AutoDisposeConverter
+//import com.uber.autodispose.android.lifecycle.AndroidLifecycleScopeProvider
+
+/**
+ * MVP 模式的Activity基类
+ *
+ * @author SunnyQjm
+ * @date 19-7-7 上午8:43
+ */
+abstract class BaseQuickAndroidMVPActivity> : BaseQuickAndroidActivity(), BaseView {
+
+ protected lateinit var mPresenter: P
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ mPresenter = onCreatePresenter()
+ }
+
+
+ override fun doToast(res: Int, duration: Int) {
+ toast(res, duration)
+ }
+
+ override fun doToast(message: String, duration: Int) {
+ toast(message, duration)
+ }
+
+
+ override fun onError(e: Throwable) {
+ // 打印错误
+ Logger.e(e, e.message ?: "")
+ }
+
+
+ override fun showLoading() {
+ }
+
+ override fun hideLoading() {
+ }
+
+
+ /**
+ * RxJava订阅绑定到Activity的生命周期中,触发onDestroy生命周期函数的时候自动取消订阅
+// */
+// override fun bindAutoDispose(): AutoDisposeConverter {
+// return AutoDispose.autoDisposable(
+// AndroidLifecycleScopeProvider
+// .from(this, Lifecycle.Event.ON_DESTROY)
+// )
+// }
+
+
+ /**
+ * 创建Presenter的接口,每个实现本类的Activity应该都要实现本方法,返回对应的Presenter
+ */
+ abstract fun onCreatePresenter(): P
+
+
+}
\ No newline at end of file
diff --git a/quick_android_mvp/src/main/java/cn/qjm253/quick_android_mvp/base/fragment/BaseQuickAndroidMVPFragment.kt b/quick_android_mvp/src/main/java/cn/qjm253/quick_android_mvp/base/fragment/BaseQuickAndroidMVPFragment.kt
new file mode 100644
index 0000000..4212dfc
--- /dev/null
+++ b/quick_android_mvp/src/main/java/cn/qjm253/quick_android_mvp/base/fragment/BaseQuickAndroidMVPFragment.kt
@@ -0,0 +1,63 @@
+package cn.qjm253.quick_android_mvp.base.fragment
+
+import android.os.Bundle
+import androidx.lifecycle.Lifecycle
+import cn.qjm253.quick_android_base.base.fragment.BaseQuickAndroidFragment
+import cn.qjm253.quick_android_base.extensions.toast
+import cn.qjm253.quick_android_mvp.base.mvp.BasePresenter
+import cn.qjm253.quick_android_mvp.base.mvp.BaseView
+import com.orhanobut.logger.Logger
+//import com.uber.autodispose.AutoDispose
+//import com.uber.autodispose.AutoDisposeConverter
+//import com.uber.autodispose.android.lifecycle.AndroidLifecycleScopeProvider
+
+/**
+ * MVP 模式的Fragment基类
+ * @author SunnyQjm
+ * @date 19-7-7 上午8:43
+ */
+abstract class BaseQuickAndroidMVPFragment>
+ : BaseQuickAndroidFragment(), BaseView {
+
+
+
+ protected lateinit var mPresenter: P
+
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ mPresenter = onCreatePresenter()
+ }
+
+ /**
+ * 创建Presenter的接口,每个实现本类的Activity应该都要实现本方法,返回对应的Presenter
+ */
+ abstract fun onCreatePresenter(): P
+
+
+
+
+ override fun onError(e: Throwable) {
+ Logger.e(e, e.message ?: "")
+ }
+
+ override fun doToast(message: String, duration: Int) {
+ context?.toast(message, duration)
+ }
+
+ override fun doToast(res: Int, duration: Int) {
+ context?.toast(res, duration)
+ }
+
+ override fun showLoading() {
+ }
+
+ override fun hideLoading() {
+ }
+
+// override fun bindAutoDispose(): AutoDisposeConverter {
+// return AutoDispose.autoDisposable(
+// AndroidLifecycleScopeProvider.from(this, Lifecycle.Event.ON_DESTROY)
+// )
+// }
+}
\ No newline at end of file
diff --git a/quick_android_mvp/src/main/java/cn/qjm253/quick_android_mvp/base/mvp/BaseModel.kt b/quick_android_mvp/src/main/java/cn/qjm253/quick_android_mvp/base/mvp/BaseModel.kt
new file mode 100644
index 0000000..e3df554
--- /dev/null
+++ b/quick_android_mvp/src/main/java/cn/qjm253/quick_android_mvp/base/mvp/BaseModel.kt
@@ -0,0 +1,7 @@
+package cn.qjm253.quick_android_mvp.base.mvp
+
+/**
+ * @author SunnyQjm
+ * @date 19-7-7 上午8:43
+ */
+interface BaseModel
\ No newline at end of file
diff --git a/quick_android_mvp/src/main/java/cn/qjm253/quick_android_mvp/base/mvp/BasePresenter.kt b/quick_android_mvp/src/main/java/cn/qjm253/quick_android_mvp/base/mvp/BasePresenter.kt
new file mode 100644
index 0000000..a2d9acd
--- /dev/null
+++ b/quick_android_mvp/src/main/java/cn/qjm253/quick_android_mvp/base/mvp/BasePresenter.kt
@@ -0,0 +1,10 @@
+package cn.qjm253.quick_android_mvp.base.mvp
+
+/**
+ * @author SunnyQjm
+ * @date 19-7-7 上午8:43
+ */
+abstract class BasePresenter {
+ protected lateinit var mView: V
+ protected lateinit var mModel: M
+}
\ No newline at end of file
diff --git a/quick_android_mvp/src/main/java/cn/qjm253/quick_android_mvp/base/mvp/BaseView.kt b/quick_android_mvp/src/main/java/cn/qjm253/quick_android_mvp/base/mvp/BaseView.kt
new file mode 100644
index 0000000..28f6875
--- /dev/null
+++ b/quick_android_mvp/src/main/java/cn/qjm253/quick_android_mvp/base/mvp/BaseView.kt
@@ -0,0 +1,43 @@
+package cn.qjm253.quick_android_mvp.base.mvp
+
+import android.widget.Toast
+import androidx.annotation.StringRes
+import cn.qjm253.quick_android_mvp.exceptions.MVPExceptionWrapper
+//import com.uber.autodispose.AutoDisposeConverter
+
+/**
+ * @author SunnyQjm
+ * @date 19-7-7 上午8:43
+ */
+interface BaseView {
+
+ /**
+ * 通用的错误回调,应在Activity或者Fragment中实现
+ */
+ fun onError(e: Throwable)
+
+ /**
+ * 显示吐司弹窗
+ */
+ fun doToast(message: String, duration: Int = Toast.LENGTH_SHORT)
+ fun doToast(@StringRes res: Int, duration: Int = Toast.LENGTH_SHORT)
+
+
+ /**
+ * 现在正在加载状态
+ */
+ fun showLoading()
+
+ /**
+ * 取消正在加载状态
+ */
+ fun hideLoading()
+
+ /**
+ * 绑定Android生命周期 防止RxJava内存泄漏
+ *
+ * @param
+ * @return
+ */
+// fun bindAutoDispose(): AutoDisposeConverter
+}
\ No newline at end of file
diff --git a/quick_android_mvp/src/main/java/cn/qjm253/quick_android_mvp/exceptions/MVPAPIResultDealException.kt b/quick_android_mvp/src/main/java/cn/qjm253/quick_android_mvp/exceptions/MVPAPIResultDealException.kt
new file mode 100644
index 0000000..a4b765f
--- /dev/null
+++ b/quick_android_mvp/src/main/java/cn/qjm253/quick_android_mvp/exceptions/MVPAPIResultDealException.kt
@@ -0,0 +1,9 @@
+package cn.qjm253.quick_android_mvp.exceptions
+
+import java.lang.Exception
+
+/**
+ * @author SunnyQjm
+ * @date 19-7-7 上午8:43
+ */
+class MVPAPIResultDealException(msg: String) : Exception(msg)
\ No newline at end of file
diff --git a/quick_android_mvp/src/main/java/cn/qjm253/quick_android_mvp/exceptions/MVPAPIResultDealNeedRetryException.kt b/quick_android_mvp/src/main/java/cn/qjm253/quick_android_mvp/exceptions/MVPAPIResultDealNeedRetryException.kt
new file mode 100644
index 0000000..6150329
--- /dev/null
+++ b/quick_android_mvp/src/main/java/cn/qjm253/quick_android_mvp/exceptions/MVPAPIResultDealNeedRetryException.kt
@@ -0,0 +1,9 @@
+package cn.qjm253.quick_android_mvp.exceptions
+
+import java.lang.Exception
+
+/**
+ * @author SunnyQjm
+ * @date 19-7-7 上午8:43
+ */
+class MVPAPIResultDealNeedRetryException(msg: String) : Exception(msg)
\ No newline at end of file
diff --git a/quick_android_mvp/src/main/java/cn/qjm253/quick_android_mvp/exceptions/MVPExceptionWrapper.kt b/quick_android_mvp/src/main/java/cn/qjm253/quick_android_mvp/exceptions/MVPExceptionWrapper.kt
new file mode 100644
index 0000000..f1aa0eb
--- /dev/null
+++ b/quick_android_mvp/src/main/java/cn/qjm253/quick_android_mvp/exceptions/MVPExceptionWrapper.kt
@@ -0,0 +1,16 @@
+package cn.qjm253.quick_android_mvp.exceptions
+
+import cn.qjm253.quick_android_base.util.GsonUtil
+import java.lang.Exception
+
+/**
+ * @author SunnyQjm
+ * @date 19-7-7 上午8:43
+ */
+class MVPExceptionWrapper(
+ val originException: Exception
+)
+
+fun test() {
+
+}
\ No newline at end of file
diff --git a/quick_android_mvp/src/main/java/cn/qjm253/quick_android_mvp/model/inernet/QuickAndroidMVPAPIManager.kt b/quick_android_mvp/src/main/java/cn/qjm253/quick_android_mvp/model/inernet/QuickAndroidMVPAPIManager.kt
new file mode 100644
index 0000000..89977f7
--- /dev/null
+++ b/quick_android_mvp/src/main/java/cn/qjm253/quick_android_mvp/model/inernet/QuickAndroidMVPAPIManager.kt
@@ -0,0 +1,84 @@
+package cn.qjm253.quick_android_mvp.model.inernet
+
+import android.app.Application
+import com.franmontiel.persistentcookiejar.PersistentCookieJar
+import com.franmontiel.persistentcookiejar.cache.SetCookieCache
+import com.franmontiel.persistentcookiejar.persistence.SharedPrefsCookiePersistor
+import okhttp3.OkHttpClient
+import retrofit2.Converter
+import retrofit2.Retrofit
+import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory
+import retrofit2.converter.gson.GsonConverterFactory
+import java.nio.charset.Charset
+import java.util.concurrent.TimeUnit
+
+
+/**
+ * @author SunnyQjm
+ * @date 19-7-7 上午8:43
+ */
+object QuickAndroidMVPAPIManager {
+
+ val UTF8 = Charset.forName("UTF-8")
+ val DEFAULT_CACHE_SIZE = 20 * 1024 * 1024
+ val DEFAULT_TIME_OUT = 10L
+ val services = mutableListOf()
+ lateinit var client: OkHttpClient
+ lateinit var baseUrl: String
+
+
+ /**
+ * 网络请求工具类初始化
+ */
+ fun init(
+ application: Application,
+ enablePersistentCookieJar: Boolean = true, // 是否开启自动cookie保存和提交
+ baseUrl: String = ""
+ ) {
+ val okhttpBuilder = OkHttpClient.Builder()
+ .connectTimeout(DEFAULT_TIME_OUT, TimeUnit.SECONDS)
+ .writeTimeout(DEFAULT_TIME_OUT, TimeUnit.SECONDS)
+ .readTimeout(DEFAULT_TIME_OUT, TimeUnit.SECONDS)
+
+ if (enablePersistentCookieJar) {
+ okhttpBuilder.cookieJar(PersistentCookieJar(SetCookieCache(), SharedPrefsCookiePersistor(application)))
+ }
+
+ this.baseUrl = baseUrl
+ client = okhttpBuilder
+ .build()
+ }
+
+ /**
+ * 注册一个服务,支持Json解析
+ */
+ fun registerService(serviceClass: Class): T {
+ val newService = createService(serviceClass, GsonConverterFactory.create())
+ services.add(newService!!)
+ return newService
+ }
+
+ fun getService(serviceClass: Class): T {
+ var result: T? = null
+ services.forEach {
+ if(it as? T != null ) {
+ result = it
+ }
+ }
+ return result ?: registerService(serviceClass)
+ }
+
+ fun createService(serviceClass: Class, vararg factory: Converter.Factory): T {
+ val build = Retrofit.Builder()
+ .baseUrl(baseUrl)
+ .client(client)
+ for (aFactory in factory) {
+ build.addConverterFactory(aFactory)
+ }
+
+ val retrofit = build
+ .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
+ .build()
+ return retrofit.create(serviceClass)
+ }
+}
\ No newline at end of file
diff --git a/quick_android_mvp/src/main/java/cn/qjm253/quick_android_mvp/model/inernet/rx/QuickAndroidMVPObserver.kt b/quick_android_mvp/src/main/java/cn/qjm253/quick_android_mvp/model/inernet/rx/QuickAndroidMVPObserver.kt
new file mode 100644
index 0000000..e0152d4
--- /dev/null
+++ b/quick_android_mvp/src/main/java/cn/qjm253/quick_android_mvp/model/inernet/rx/QuickAndroidMVPObserver.kt
@@ -0,0 +1,43 @@
+package cn.qjm253.quick_android_mvp.model.inernet.rx
+
+import androidx.annotation.CallSuper
+import cn.qjm253.quick_android_mvp.QuickAndroidMVP
+import cn.qjm253.quick_android_mvp.base.mvp.BaseView
+import io.reactivex.Observer
+import io.reactivex.disposables.Disposable
+
+/**
+ * @author SunnyQjm
+ * @date 19-7-7 上午8:43
+ */
+
+abstract class QuickAndroidMVPObserver(private val mView: BaseView) : Observer {
+
+ @CallSuper
+ override fun onComplete() {
+ QuickAndroidMVP.onCompleteListeners.forEach {
+ it()
+ }
+ }
+
+ @CallSuper
+ override fun onNext(t: T) {
+ QuickAndroidMVP.onBeforeNextListeners.forEach {
+ it()
+ }
+
+ }
+
+ @CallSuper
+ override fun onError(e: Throwable) {
+ QuickAndroidMVP.onErrorListeners.forEach {
+ it(e)
+ }
+ mView.onError(e)
+ }
+
+ @CallSuper
+ override fun onSubscribe(d: Disposable) {
+ }
+
+}
\ No newline at end of file
diff --git a/quick_android_mvp/src/main/java/cn/qjm253/quick_android_mvp/model/inernet/rx/QuickAndroidMVPResultDeal.kt b/quick_android_mvp/src/main/java/cn/qjm253/quick_android_mvp/model/inernet/rx/QuickAndroidMVPResultDeal.kt
new file mode 100644
index 0000000..f4e2661
--- /dev/null
+++ b/quick_android_mvp/src/main/java/cn/qjm253/quick_android_mvp/model/inernet/rx/QuickAndroidMVPResultDeal.kt
@@ -0,0 +1,69 @@
+package cn.qjm253.quick_android_mvp.model.inernet.rx
+
+import io.reactivex.Observable
+
+
+/**
+ * @author SunnyQjm
+ * @date 19-7-7 上午8:43
+ */
+
+abstract class QuickAndroidMVPResultDeal (
+ exceptionCodes: Array>, // 错误的状态码对应表
+ normalCodes: Array>, // 除错误以外的状态码
+ needRetryCodes: Array> // 需要重试的状态码
+) {
+
+ enum class Type {
+ TYPE_NORMAL, TYPE_ERROR, TYPE_ERROR_NEED_RETRY
+ }
+
+ class QuickAndroidMVPResultDealItem(
+ val code: Int,
+ val description: String,
+ val type: Type
+ )
+
+
+ private val exceptionCodeMap: MutableMap = mutableMapOf(*exceptionCodes)
+ private val normalCodeMap: MutableMap = mutableMapOf(*normalCodes)
+ private val needRetryCodeMap: MutableMap = mutableMapOf(*needRetryCodes)
+
+
+ fun getQuickAndroidMVPResultDealItem(code: Int): QuickAndroidMVPResultDealItem? {
+ var item: QuickAndroidMVPResultDealItem? = null
+ when {
+ exceptionCodeMap.keys.contains(code) -> {
+ item = QuickAndroidMVPResultDealItem(code, exceptionCodeMap[code] ?: "", Type.TYPE_ERROR)
+ }
+ normalCodeMap.keys.contains(code) -> {
+ item = QuickAndroidMVPResultDealItem(code, normalCodeMap[code] ?: "", Type.TYPE_NORMAL)
+ }
+ needRetryCodeMap.keys.contains(code) -> {
+ item = QuickAndroidMVPResultDealItem(code, needRetryCodeMap[code] ?: "", Type.TYPE_ERROR_NEED_RETRY)
+ }
+ }
+ return item
+ }
+
+
+ /**
+ * 正常状态码下的统一处理回调
+ */
+ abstract fun normalDeal(code: Int, description: String)
+
+
+ /**
+ * 错误状态码下的统一处理回调
+ */
+ abstract fun errorDeal(code: Int, description: String)
+
+ /**
+ * 处理后重试
+ *
+ * eg.: 应用场景 => Token失效之后重新获取Token,新的Token获取成功后重新发起原先失败的请求
+ * @return 返回的Observable触发onComplete 或者 onError, 则不会重新订阅原Observable
+ * 返回的Observable触发onNext,则会重新订阅原Observable
+ */
+ abstract fun dealRetry(t: Throwable): Observable
+}
\ No newline at end of file
diff --git a/quick_android_mvp/src/main/java/cn/qjm253/quick_android_mvp/model/inernet/rx/RxExtensions.kt b/quick_android_mvp/src/main/java/cn/qjm253/quick_android_mvp/model/inernet/rx/RxExtensions.kt
new file mode 100644
index 0000000..709dd75
--- /dev/null
+++ b/quick_android_mvp/src/main/java/cn/qjm253/quick_android_mvp/model/inernet/rx/RxExtensions.kt
@@ -0,0 +1,82 @@
+package cn.qjm253.quick_android_mvp.model.inernet.rx
+
+import cn.qjm253.quick_android_mvp.QuickAndroidMVP
+import cn.qjm253.quick_android_mvp.base.mvp.BaseView
+import cn.qjm253.quick_android_mvp.exceptions.MVPAPIResultDealException
+import cn.qjm253.quick_android_mvp.exceptions.MVPAPIResultDealNeedRetryException
+import cn.qjm253.quick_android_mvp.model.response.BaseQuickAndroidResponseBody
+import io.reactivex.Observable
+import io.reactivex.disposables.Disposable
+
+
+/**
+ * 网络请求结果统一处理
+ * @author SunnyQjm
+ * @date 19-7-7 上午8:43
+ */
+fun Observable.qaHandleResult(): Observable {
+ return this.compose { upstream ->
+ upstream.flatMap {
+ val code = it.code
+
+ val item = QuickAndroidMVP.quickAndroidMVPResultDeal?.getQuickAndroidMVPResultDealItem(code)
+ when (item?.type) {
+ QuickAndroidMVPResultDeal.Type.TYPE_NORMAL -> {
+ QuickAndroidMVP.quickAndroidMVPResultDeal?.normalDeal(item.code, item.description)
+ return@flatMap Observable.just(it)
+ }
+ QuickAndroidMVPResultDeal.Type.TYPE_ERROR -> {
+ QuickAndroidMVP.quickAndroidMVPResultDeal?.errorDeal(item.code, item.description)
+ return@flatMap Observable.error(MVPAPIResultDealException(item.description))
+ }
+ QuickAndroidMVPResultDeal.Type.TYPE_ERROR_NEED_RETRY -> {
+ return@flatMap Observable.error(MVPAPIResultDealNeedRetryException(item.description))
+ }
+ }
+ return@flatMap Observable.just(it)
+ }.retryWhen { t: Observable ->
+ t.flatMap {
+ if(QuickAndroidMVP.quickAndroidMVPResultDeal != null &&
+ it is MVPAPIResultDealNeedRetryException) {
+ return@flatMap QuickAndroidMVP.quickAndroidMVPResultDeal?.dealRetry(it)
+ }
+ return@flatMap Observable.error(it)
+ }
+
+ }
+ }
+}
+
+
+/**
+ * 网络请求订阅封装
+ */
+fun Observable.qaSubscribe(
+ mView: BaseView,
+ onNext_: (T) -> Unit,
+ onError_: (Throwable) -> Unit = {},
+ onComplete_: () -> Unit = {},
+ onSubscribe_: (Disposable) -> Unit = {}
+) {
+ this.subscribe(object : QuickAndroidMVPObserver(mView) {
+ override fun onNext(t: T) {
+ super.onNext(t)
+ onNext_(t)
+ }
+
+ override fun onError(e: Throwable) {
+ super.onError(e)
+ onError_(e)
+ }
+
+ override fun onComplete() {
+ super.onComplete()
+ onComplete_()
+ }
+
+ override fun onSubscribe(d: Disposable) {
+ super.onSubscribe(d)
+ onSubscribe_(d)
+ }
+ })
+}
\ No newline at end of file
diff --git a/quick_android_mvp/src/main/java/cn/qjm253/quick_android_mvp/model/response/BaseQuickAndroidResponseBody.kt b/quick_android_mvp/src/main/java/cn/qjm253/quick_android_mvp/model/response/BaseQuickAndroidResponseBody.kt
new file mode 100644
index 0000000..db6f0bb
--- /dev/null
+++ b/quick_android_mvp/src/main/java/cn/qjm253/quick_android_mvp/model/response/BaseQuickAndroidResponseBody.kt
@@ -0,0 +1,9 @@
+package cn.qjm253.quick_android_mvp.model.response
+
+/**
+ * @author SunnyQjm
+ * @date 19-7-7 上午8:43
+ */
+open class BaseQuickAndroidResponseBody(
+ val code: Int
+)
\ No newline at end of file
diff --git a/quick_android_mvp/src/main/res/values/strings.xml b/quick_android_mvp/src/main/res/values/strings.xml
new file mode 100644
index 0000000..8542005
--- /dev/null
+++ b/quick_android_mvp/src/main/res/values/strings.xml
@@ -0,0 +1,2 @@
+
+
diff --git a/quick_android_mvp/src/test/java/cn/qjm253/quick_android_mvp/ExampleUnitTest.java b/quick_android_mvp/src/test/java/cn/qjm253/quick_android_mvp/ExampleUnitTest.java
new file mode 100644
index 0000000..a8a9b64
--- /dev/null
+++ b/quick_android_mvp/src/test/java/cn/qjm253/quick_android_mvp/ExampleUnitTest.java
@@ -0,0 +1,17 @@
+package cn.qjm253.quick_android_mvp;
+
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+/**
+ * Example local unit test, which will execute on the development machine (host).
+ *
+ * @see Testing documentation
+ */
+public class ExampleUnitTest {
+ @Test
+ public void addition_isCorrect() {
+ assertEquals(4, 2 + 2);
+ }
+}
\ No newline at end of file
diff --git a/quick_android_qrcode/.gitignore b/quick_android_qrcode/.gitignore
new file mode 100644
index 0000000..796b96d
--- /dev/null
+++ b/quick_android_qrcode/.gitignore
@@ -0,0 +1 @@
+/build
diff --git a/quick_android_qrcode/README.md b/quick_android_qrcode/README.md
new file mode 100644
index 0000000..3f01e17
--- /dev/null
+++ b/quick_android_qrcode/README.md
@@ -0,0 +1,51 @@
+> ## quick_android_qrcode 模块说明文档
+>
+> 本模块基于[**BGAQRCode-Android**](https://github.com/bingoogolapple/BGAQRCode-Android)进行进一步的封装,支持使用RxJava链式调用的方式进行扫码并获得扫码的结果(不用复写`onActivityResult`来手动获取结果)
+
+
+
+- ### 基本使用
+
+```kotlin
+QuickAndroidQrCode
+ .create(context) // context 可以是Activity或者Fragment
+ .scanQrCode()
+ .subscribe({qrCodeResult-> // 扫码成功
+
+ }, { err-> // 扫码失败(权限被拒绝,摄像头打开失败等)
+
+ })
+
+// 当然,如果使用Kotlin开发也可以使用下面的统一扩展接口
+QuickAndroid
+ .scanQrCode(context)
+ .subscribe({qrCodeResult-> // 扫码成功
+
+ }, { err-> // 扫码失败(权限被拒绝,摄像头打开失败等)
+
+ })
+```
+
+- ### 进阶
+ - **修改返回按钮的样式**
+ ```kotlin
+ QuickAndroidQrCode
+ .create(context) // context 可以是Activity或者Fragment
+ .setBackIconRes(R.drawable.back) // 设置返回按钮的资源
+ .setBackIconSize(30f) // 设置返回按钮的大小
+ .setBackIconMargin(top, right, bottom, left) // 设置返回按钮的边距
+ .scanQrCode()
+ .subscribe({qrCodeResult-> // 扫码成功
+
+ }, { err-> // 扫码失败(权限被拒绝,摄像头打开失败等)
+
+ })
+ ```
+
+
+
+- ### 模块说明
+
+ - 本模块提供了一个简单的[QuickAndroidQrCodeActivity](https://github.com/SunnyQjm/quickandroid/blob/master/quick_android_qrcode/src/main/java/cn/qjm253/quick_android_qrcode/activity/QuickAndroidQrCodeActivity.kt)实现扫码界面,可以直接使用
+ - 如果希望自定义扫码界面,也可以参考[QuickAndroidQrCodeActivity](https://github.com/SunnyQjm/quickandroid/blob/master/quick_android_qrcode/src/main/java/cn/qjm253/quick_android_qrcode/activity/QuickAndroidQrCodeActivity.kt)复用本模块提供的[QuickAndroidQrCodeFragment](https://github.com/SunnyQjm/quickandroid/blob/master/quick_android_qrcode/src/main/java/cn/qjm253/quick_android_qrcode/fragment/QuickAndroidQrCodeFragment.kt)来实现自定义的界面
+
diff --git a/quick_android_qrcode/build.gradle b/quick_android_qrcode/build.gradle
new file mode 100644
index 0000000..cc041a3
--- /dev/null
+++ b/quick_android_qrcode/build.gradle
@@ -0,0 +1,42 @@
+apply plugin: 'com.android.library'
+
+apply plugin: 'kotlin-android'
+
+apply plugin: 'kotlin-android-extensions'
+
+apply plugin: 'kotlin-kapt'
+
+android {
+ compileSdkVersion compile_sdk_version
+
+ defaultConfig {
+ minSdkVersion min_sdk_version
+ targetSdkVersion target_sdk_version
+ versionCode 1
+ versionName "1.0"
+
+ testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
+
+ }
+
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+ }
+ }
+
+}
+
+dependencies {
+ implementation fileTree(dir: 'libs', include: ['*.jar'])
+ implementation "androidx.appcompat:appcompat:${appcompact_version}"
+ testImplementation "junit:junit:${junit_version}"
+ androidTestImplementation "androidx.test:runner:${test_runner_version}"
+ androidTestImplementation "androidx.test.espresso:espresso-core:${espresso_core_version}"
+
+ implementation project(path: ':quick_android_base')
+
+ // zxing 二维码识别和生成
+ implementation "cn.bingoogolapple:bga-qrcode-zxing:${bag_qrcode_zxing_version}"
+}
diff --git a/quick_android_qrcode/proguard-rules.pro b/quick_android_qrcode/proguard-rules.pro
new file mode 100644
index 0000000..f1b4245
--- /dev/null
+++ b/quick_android_qrcode/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
diff --git a/quick_android_qrcode/src/androidTest/java/cn/qjm253/quick_android_qrcode/ExampleInstrumentedTest.java b/quick_android_qrcode/src/androidTest/java/cn/qjm253/quick_android_qrcode/ExampleInstrumentedTest.java
new file mode 100644
index 0000000..c7240b6
--- /dev/null
+++ b/quick_android_qrcode/src/androidTest/java/cn/qjm253/quick_android_qrcode/ExampleInstrumentedTest.java
@@ -0,0 +1,26 @@
+package cn.qjm253.quick_android_qrcode;
+
+import android.content.Context;
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import static org.junit.Assert.*;
+
+/**
+ * Instrumented test, which will execute on an Android device.
+ *
+ * @see Testing documentation
+ */
+@RunWith(AndroidJUnit4.class)
+public class ExampleInstrumentedTest {
+ @Test
+ public void useAppContext() {
+ // Context of the app under test.
+ Context appContext = InstrumentationRegistry.getTargetContext();
+
+ assertEquals("cn.qjm253.quick_android_qrcode.test", appContext.getPackageName());
+ }
+}
diff --git a/quick_android_qrcode/src/main/AndroidManifest.xml b/quick_android_qrcode/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..c3c81da
--- /dev/null
+++ b/quick_android_qrcode/src/main/AndroidManifest.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
diff --git a/quick_android_qrcode/src/main/java/cn/qjm253/quick_android_qrcode/QuickAndroidQrCode.kt b/quick_android_qrcode/src/main/java/cn/qjm253/quick_android_qrcode/QuickAndroidQrCode.kt
new file mode 100644
index 0000000..fa2f510
--- /dev/null
+++ b/quick_android_qrcode/src/main/java/cn/qjm253/quick_android_qrcode/QuickAndroidQrCode.kt
@@ -0,0 +1,131 @@
+package cn.qjm253.quick_android_qrcode
+
+import androidx.annotation.DrawableRes
+import androidx.fragment.app.Fragment
+import androidx.fragment.app.FragmentActivity
+import cn.qjm253.quick_android_base.QuickAndroid
+import cn.qjm253.quick_android_base.extensions.getLazySingleton
+import cn.qjm253.quick_android_base.util.RxSchedulersHelper
+import cn.qjm253.quick_android_qrcode.activity.QuickAndroidQrCodeActivity
+import cn.qjm253.quick_android_qrcode.fragment.QuickAndroidQrCodeTransFragment
+import cn.qjm253.quick_android_qrcode.model.QrCodeResult
+import io.reactivex.Observable
+
+/**
+ * @author SunnyQjm
+ * @date 19-7-7 下午10:11
+ */
+
+class QuickAndroidQrCode {
+
+ private var mQrCodeTransFragment: Lazy
+ private var backIconRes: Int = R.drawable.quick_android_qr_code_default_back
+ private var backIconSize: Float = 30f
+ private var backIconTopMargin: Float = 15f
+ private var backIconRightMargin: Float = 0f
+ private var backIconBottomMargin: Float = 0f
+ private var backIconLeftMargin: Float = 15f
+
+ companion object {
+ const val TAG = "QuickAndroidQrCode"
+
+ fun create(activity: FragmentActivity): QuickAndroidQrCode {
+ return QuickAndroidQrCode(activity)
+ }
+
+ fun create(fragment: Fragment): QuickAndroidQrCode {
+ return QuickAndroidQrCode(fragment)
+ }
+ }
+
+ constructor(activity: FragmentActivity) {
+ mQrCodeTransFragment = activity.supportFragmentManager
+ .getLazySingleton(TAG, QuickAndroidQrCodeTransFragment::class.java)
+ }
+
+ constructor(fragment: Fragment) {
+ mQrCodeTransFragment = fragment.childFragmentManager
+ .getLazySingleton(TAG, QuickAndroidQrCodeTransFragment::class.java)
+ }
+
+ /**
+ * 设置返回按钮的资源
+ */
+ fun setBackIconRes(@DrawableRes icon: Int): QuickAndroidQrCode {
+ backIconRes = icon
+ return this
+ }
+
+ /**
+ * 设置返回按钮的大小
+ * ps: 单位为dp
+ */
+ fun setBackIconSize(size: Float): QuickAndroidQrCode {
+ backIconSize = size
+ return this
+ }
+
+ /**
+ * 设置返回按钮的边距
+ * 1. 单位为dp
+ * 2. 代码中已经自动去掉了状态栏的高度,不用考虑状态栏
+ */
+ fun setBackIconMargin(top: Float, right: Float, bottom: Float, left: Float): QuickAndroidQrCode {
+ backIconTopMargin = top
+ backIconRightMargin = right
+ backIconBottomMargin = bottom
+ backIconLeftMargin = left
+ return this
+ }
+
+ /**
+ * 跳转到扫码界面,并返回一个Observable对象用于接收扫码结果
+ */
+ fun scanQrCode(): Observable {
+ return mQrCodeTransFragment.value
+ .setBackIconRes(backIconRes)
+ .setBackIconSize(backIconSize)
+ .setBackIconMargin(
+ backIconTopMargin,
+ backIconRightMargin,
+ backIconBottomMargin,
+ backIconLeftMargin
+ )
+ .scanCode(QuickAndroidQrCodeActivity::class.java)
+ .compose(RxSchedulersHelper.io_main())
+ }
+}
+
+
+//////////////////////////////////////////////////////////
+////////// QuickAndroid 扩展
+//////////////////////////////////////////////////////////
+
+fun QuickAndroid.scanQrCode(activity: FragmentActivity): Observable {
+ return QuickAndroidQrCode.create(activity)
+ .scanQrCode()
+}
+
+fun QuickAndroid.scanQrCode(fragment: Fragment): Observable {
+ return QuickAndroidQrCode.create(fragment)
+ .scanQrCode()
+}
+
+
+//////////////////////////////////////////////////////////
+////////// FragmentActivity 扩展
+//////////////////////////////////////////////////////////
+
+fun FragmentActivity.scanCode(): Observable {
+ return QuickAndroidQrCode.create(this)
+ .scanQrCode()
+}
+
+//////////////////////////////////////////////////////////
+////////// Fragment 扩展
+//////////////////////////////////////////////////////////
+
+fun Fragment.scanCode(): Observable {
+ return QuickAndroidQrCode.create(this)
+ .scanQrCode()
+}
\ No newline at end of file
diff --git a/quick_android_qrcode/src/main/java/cn/qjm253/quick_android_qrcode/activity/QuickAndroidQrCodeActivity.kt b/quick_android_qrcode/src/main/java/cn/qjm253/quick_android_qrcode/activity/QuickAndroidQrCodeActivity.kt
new file mode 100644
index 0000000..68c0632
--- /dev/null
+++ b/quick_android_qrcode/src/main/java/cn/qjm253/quick_android_qrcode/activity/QuickAndroidQrCodeActivity.kt
@@ -0,0 +1,32 @@
+package cn.qjm253.quick_android_qrcode.activity
+
+import android.os.Bundle
+import cn.qjm253.quick_android_base.base.activity.BaseQuickAndroidActivity
+import cn.qjm253.quick_android_base.extensions.addFragmentToActivity
+import cn.qjm253.quick_android_qrcode.R
+import cn.qjm253.quick_android_qrcode.fragment.QuickAndroidQrCodeFragment
+import com.gyf.immersionbar.BarHide
+import com.gyf.immersionbar.ImmersionBar
+
+class QuickAndroidQrCodeActivity : BaseQuickAndroidActivity() {
+
+ companion object {
+ const val QR_CODE_RESULT = QuickAndroidQrCodeFragment.QR_CODE_RESULT
+ const val QR_CODE_ERROR = QuickAndroidQrCodeFragment.QR_CODE_ERROR
+ }
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ ImmersionBar.with(this)
+ .fullScreen(true)
+ .hideBar(BarHide.FLAG_HIDE_NAVIGATION_BAR)
+ .init()
+ setContentView(R.layout.activity_quick_android_qr_code)
+
+ // 如果有的话隐藏ActionBar
+ supportActionBar?.hide()
+ addFragmentToActivity(R.id.quickAndroidQrCodeContainer) {
+ return@addFragmentToActivity QuickAndroidQrCodeFragment.newInstance()
+ }
+ }
+}
diff --git a/quick_android_qrcode/src/main/java/cn/qjm253/quick_android_qrcode/fragment/QuickAndroidQrCodeFragment.kt b/quick_android_qrcode/src/main/java/cn/qjm253/quick_android_qrcode/fragment/QuickAndroidQrCodeFragment.kt
new file mode 100644
index 0000000..6366496
--- /dev/null
+++ b/quick_android_qrcode/src/main/java/cn/qjm253/quick_android_qrcode/fragment/QuickAndroidQrCodeFragment.kt
@@ -0,0 +1,132 @@
+package cn.qjm253.quick_android_qrcode.fragment
+
+import android.content.Intent
+import android.widget.RelativeLayout
+import cn.bingoogolapple.qrcode.core.QRCodeView
+import cn.qjm253.quick_android_base.base.fragment.BaseQuickAndroidFragment
+import cn.qjm253.quick_android_base.extensions.e
+import cn.qjm253.quick_android_base.extensions.i
+import cn.qjm253.quick_android_base.extensions.vibrator
+import cn.qjm253.quick_android_base.params.IntentParam
+import cn.qjm253.quick_android_base.util.DisplayUtils
+import cn.qjm253.quick_android_qrcode.R
+import com.gyf.immersionbar.ImmersionBar
+import kotlinx.android.synthetic.main.fragment_quick_android_qrcode.*
+
+
+/**
+ * @author SunnyQjm
+ * @date 19-7-7 上午8:43
+ */
+
+class QuickAndroidQrCodeFragment : BaseQuickAndroidFragment() {
+
+ companion object {
+ const val QR_CODE_RESULT = "QR_CODE_RESULT"
+ const val QR_CODE_ERROR = "QR_CODE_ERROR"
+ const val QR_CODE_BACK_ICON_RES = "QR_CODE_BACK_ICON_RES"
+ const val QR_CODE_BACK_ICON_SIZE = "QR_CODE_BACK_ICON_SIZE"
+ const val QR_CODE_BAKC_ICON_MARGIN = "QR_CODE_BAKC_ICON_MARGIN"
+
+ fun newInstance(intentParam: IntentParam? = null): QuickAndroidQrCodeFragment {
+ return newInstance_(QuickAndroidQrCodeFragment::class.java, intentParam)
+ }
+ }
+
+ override fun getRes(): Int = R.layout.fragment_quick_android_qrcode
+
+ override fun initView() {
+ zxingview.setDelegate(object : QRCodeView.Delegate {
+ override fun onScanQRCodeSuccess(result: String?) {
+ // 识别成功,震动
+ activity?.vibrator()
+
+ Intent()
+ .putExtra(QR_CODE_RESULT, result).let {
+ activity?.setResult(0, it)
+ activity?.finish()
+ }
+
+ }
+
+ override fun onCameraAmbientBrightnessChanged(isDark: Boolean) {
+// "onCameraAmbientBrightnessChanged => isDark: $isDark".i()
+ }
+
+ override fun onScanQRCodeOpenCameraError() {
+ Intent()
+ .putExtra(QR_CODE_RESULT, "打开相机失败").let {
+ activity?.setResult(0, it)
+ activity?.finish()
+ }
+ "打开相机失败".e()
+ }
+
+ })
+
+ val statusBarHeight = ImmersionBar.getStatusBarHeight(this);
+
+ activity?.intent?.let {
+ val backIconRes =
+ it.getIntExtra(QR_CODE_BACK_ICON_RES, R.drawable.quick_android_qr_code_default_back)
+ val backIconSize = it.getFloatExtra(QR_CODE_BACK_ICON_SIZE, 30f)
+ val backIconMargin: FloatArray =
+ it.getFloatArrayExtra(QR_CODE_BAKC_ICON_MARGIN) ?: floatArrayOf(15f, 0f, 0f, 15f)
+ RelativeLayout.LayoutParams(imgBack.layoutParams).let { lp ->
+ lp.setMargins(
+ DisplayUtils.dip2px(context, backIconMargin[3]),
+ DisplayUtils.dip2px(context, backIconMargin[0]) + statusBarHeight,
+ DisplayUtils.dip2px(context, backIconMargin[1]),
+ DisplayUtils.dip2px(context, backIconMargin[2])
+ )
+ lp.width = DisplayUtils.dip2px(context, backIconSize)
+ lp.height = DisplayUtils.dip2px(context, backIconSize)
+ imgBack.layoutParams = lp
+ }
+ imgBack.setImageResource(backIconRes)
+ }
+
+ imgBack.setOnClickListener {
+ activity?.onBackPressed()
+ }
+
+ requestCamera()
+ }
+
+
+ override fun onPermissionsGranted(requestCode: Int, perms: MutableList) {
+ super.onPermissionsGranted(requestCode, perms)
+
+ // 打开后摄像头开始预览,但是并没有开始识别
+ zxingview.startCamera()
+
+ // 显示扫描框,并开始识别
+ zxingview.startSpotAndShowRect()
+ }
+
+
+ override fun onPermissionsDenied(requestCode: Int, perms: MutableList) {
+ super.onPermissionsDenied(requestCode, perms)
+ Intent()
+ .putExtra(QR_CODE_RESULT, "获取相机权限失败").let {
+ activity?.setResult(0, it)
+ activity?.finish()
+ }
+ }
+
+ override fun onStop() {
+ super.onStop()
+ zxingview.stopCamera()
+ }
+
+ override fun onDestroy() {
+ zxingview?.onDestroy()
+ super.onDestroy()
+ }
+
+
+ override fun initialLoadData() {
+ }
+
+
+}
\ No newline at end of file
diff --git a/quick_android_qrcode/src/main/java/cn/qjm253/quick_android_qrcode/fragment/QuickAndroidQrCodeTransFragment.kt b/quick_android_qrcode/src/main/java/cn/qjm253/quick_android_qrcode/fragment/QuickAndroidQrCodeTransFragment.kt
new file mode 100644
index 0000000..0ec7e3c
--- /dev/null
+++ b/quick_android_qrcode/src/main/java/cn/qjm253/quick_android_qrcode/fragment/QuickAndroidQrCodeTransFragment.kt
@@ -0,0 +1,114 @@
+package cn.qjm253.quick_android_qrcode.fragment
+
+import android.content.Intent
+import android.os.Bundle
+import androidx.annotation.DrawableRes
+import androidx.fragment.app.Fragment
+import cn.qjm253.quick_android_base.base.activity.BaseQuickAndroidActivity
+import cn.qjm253.quick_android_base.extensions.e
+import cn.qjm253.quick_android_base.extensions.i
+import cn.qjm253.quick_android_base.extensions.jumpForResult
+import cn.qjm253.quick_android_base.extensions.toJson
+import cn.qjm253.quick_android_base.params.IntentParam
+import cn.qjm253.quick_android_qrcode.R
+import cn.qjm253.quick_android_qrcode.model.QrCodeResult
+import io.reactivex.Observable
+import io.reactivex.ObservableEmitter
+
+/**
+ * 这个Fragment用于跳转到扫码界面,并接收其返回的结果,然后使用RxJava进行封装,便于链式调用
+ *
+ * @author SunnyQjm
+ * @date 19-7-8 下午3:22
+ */
+class QuickAndroidQrCodeTransFragment : Fragment() {
+
+ private var emitter: ObservableEmitter? = null
+
+ private var backIconRes: Int = R.drawable.quick_android_qr_code_default_back
+ private var backIconSize: Float = 30f
+ private var backIconTopMargin: Float = 15f
+ private var backIconRightMargin: Float = 0f
+ private var backIconBottomMargin: Float = 0f
+ private var backIconLeftMargin: Float = 15f
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ retainInstance = true
+ }
+
+ /**
+ * 设置返回按钮的资源
+ */
+ fun setBackIconRes(@DrawableRes icon: Int): QuickAndroidQrCodeTransFragment {
+ backIconRes = icon
+ return this
+ }
+
+ /**
+ * 设置返回按钮的大小
+ * ps: 单位为dp
+ */
+ fun setBackIconSize(size: Float): QuickAndroidQrCodeTransFragment {
+ backIconSize = size
+ return this
+ }
+
+ /**
+ * 设置返回按钮的边距
+ * 1. 单位为dp
+ * 2. 代码中已经自动去掉了状态栏的高度,不用考虑状态栏
+ */
+ fun setBackIconMargin(
+ top: Float,
+ right: Float,
+ bottom: Float,
+ left: Float
+ ): QuickAndroidQrCodeTransFragment {
+ backIconTopMargin = top
+ backIconRightMargin = right
+ backIconBottomMargin = bottom
+ backIconLeftMargin = left
+ return this
+ }
+
+ fun scanCode(clazz: Class): Observable {
+ return Observable.create { emitter ->
+ // 保存事件发送器
+ this.emitter = emitter
+ jumpForResult(
+ clazz, 0, IntentParam()
+ .add(QuickAndroidQrCodeFragment.QR_CODE_BACK_ICON_RES, backIconRes)
+ .add(QuickAndroidQrCodeFragment.QR_CODE_BACK_ICON_SIZE, backIconSize)
+ .add(
+ QuickAndroidQrCodeFragment.QR_CODE_BAKC_ICON_MARGIN, floatArrayOf(
+ backIconTopMargin, backIconRightMargin,
+ backIconBottomMargin, backIconLeftMargin
+ )
+ )
+ )
+ }
+ }
+
+ override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
+ super.onActivityResult(requestCode, resultCode, data)
+ when (requestCode) {
+ 0 -> { // 扫码结果处理
+ data?.let {
+ val qrCodeResult = it.getStringExtra(QuickAndroidQrCodeFragment.QR_CODE_RESULT)
+ val qrCodeError = it.getStringExtra(QuickAndroidQrCodeFragment.QR_CODE_ERROR)
+ qrCodeResult?.i()
+ qrCodeError?.e()
+ when {
+ qrCodeResult != null -> this.emitter?.onNext(QrCodeResult(qrCodeResult))
+ qrCodeError != null -> this.emitter?.onError(Throwable(qrCodeError))
+ else -> {
+
+ }
+ }
+ this.emitter?.onComplete()
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/quick_android_qrcode/src/main/java/cn/qjm253/quick_android_qrcode/model/QrCodeResult.kt b/quick_android_qrcode/src/main/java/cn/qjm253/quick_android_qrcode/model/QrCodeResult.kt
new file mode 100644
index 0000000..87853a0
--- /dev/null
+++ b/quick_android_qrcode/src/main/java/cn/qjm253/quick_android_qrcode/model/QrCodeResult.kt
@@ -0,0 +1,9 @@
+package cn.qjm253.quick_android_qrcode.model
+
+/**
+ * @author SunnyQjm
+ * @date 19-7-8 下午3:32
+ */
+class QrCodeResult (
+ val content: String
+)
\ No newline at end of file
diff --git a/quick_android_qrcode/src/main/res/drawable/quick_android_qr_code_default_back.png b/quick_android_qrcode/src/main/res/drawable/quick_android_qr_code_default_back.png
new file mode 100644
index 0000000..cab12e2
Binary files /dev/null and b/quick_android_qrcode/src/main/res/drawable/quick_android_qr_code_default_back.png differ
diff --git a/quick_android_qrcode/src/main/res/layout/activity_quick_android_qr_code.xml b/quick_android_qrcode/src/main/res/layout/activity_quick_android_qr_code.xml
new file mode 100644
index 0000000..22ac660
--- /dev/null
+++ b/quick_android_qrcode/src/main/res/layout/activity_quick_android_qr_code.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/quick_android_qrcode/src/main/res/layout/fragment_quick_android_qrcode.xml b/quick_android_qrcode/src/main/res/layout/fragment_quick_android_qrcode.xml
new file mode 100644
index 0000000..e444d95
--- /dev/null
+++ b/quick_android_qrcode/src/main/res/layout/fragment_quick_android_qrcode.xml
@@ -0,0 +1,38 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/quick_android_qrcode/src/main/res/values/strings.xml b/quick_android_qrcode/src/main/res/values/strings.xml
new file mode 100644
index 0000000..8542005
--- /dev/null
+++ b/quick_android_qrcode/src/main/res/values/strings.xml
@@ -0,0 +1,2 @@
+
+
diff --git a/quick_android_qrcode/src/test/java/cn/qjm253/quick_android_qrcode/ExampleUnitTest.java b/quick_android_qrcode/src/test/java/cn/qjm253/quick_android_qrcode/ExampleUnitTest.java
new file mode 100644
index 0000000..5486f0f
--- /dev/null
+++ b/quick_android_qrcode/src/test/java/cn/qjm253/quick_android_qrcode/ExampleUnitTest.java
@@ -0,0 +1,17 @@
+package cn.qjm253.quick_android_qrcode;
+
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+/**
+ * Example local unit test, which will execute on the development machine (host).
+ *
+ * @see Testing documentation
+ */
+public class ExampleUnitTest {
+ @Test
+ public void addition_isCorrect() {
+ assertEquals(4, 2 + 2);
+ }
+}
\ No newline at end of file
diff --git a/quick_android_rx_permission/.gitignore b/quick_android_rx_permission/.gitignore
new file mode 100644
index 0000000..796b96d
--- /dev/null
+++ b/quick_android_rx_permission/.gitignore
@@ -0,0 +1 @@
+/build
diff --git a/quick_android_rx_permission/README.md b/quick_android_rx_permission/README.md
new file mode 100644
index 0000000..510bc39
--- /dev/null
+++ b/quick_android_rx_permission/README.md
@@ -0,0 +1,37 @@
+> ## quick_android_rx_permission 模块说明文档
+>
+> 本模块基于[easypermissions](https://github.com/googlesamples/easypermissions)进行封装,实现了RxJava链式调用进行动态权限请求,不需要显示的复写权限请求回调,请求逻辑和请求结果可以在同一代码段,不用分开编写。(封装实现时参考了[RxPermissions](https://github.com/tbruyelle/RxPermissions)项目)
+
+
+
+- ### 使用示例
+
+```kotlin
+QuickAndroidRxPermission
+ .create(context) // 这个context可以是Activity,也可以是Fragment
+ .request(
+ arrayof(Manifest.android.CAMERA)
+ )
+ .subscribe {
+ if(it.granted) { // 权限请求通过
+
+ } else { // 权限请求拒绝
+
+ }
+ }
+
+// 当然,如果使用Kotlin开发也可以使用下面的统一扩展接口
+QuickAndroid
+ .createRxPermission(context)
+ .request(
+ arrayof(Manifest.android.CAMERA)
+ )
+ .subscribe {
+ if(it.granted) { // 权限请求通过
+
+ } else { // 权限请求拒绝
+
+ }
+ }
+```
+
diff --git a/quick_android_rx_permission/build.gradle b/quick_android_rx_permission/build.gradle
new file mode 100644
index 0000000..be7d677
--- /dev/null
+++ b/quick_android_rx_permission/build.gradle
@@ -0,0 +1,40 @@
+apply plugin: 'com.android.library'
+apply plugin: 'kotlin-android'
+
+apply plugin: 'kotlin-android-extensions'
+
+apply plugin: 'kotlin-kapt'
+
+android {
+ compileSdkVersion compile_sdk_version
+
+ defaultConfig {
+ minSdkVersion min_sdk_version
+ targetSdkVersion target_sdk_version
+ versionCode 1
+ versionName "1.0"
+
+ testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
+
+ }
+
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+ }
+ }
+
+}
+
+dependencies {
+ implementation fileTree(dir: 'libs', include: ['*.jar'])
+
+
+ implementation "androidx.appcompat:appcompat:${appcompact_version}"
+ testImplementation "junit:junit:${junit_version}"
+ androidTestImplementation "androidx.test:runner:${test_runner_version}"
+ androidTestImplementation "androidx.test.espresso:espresso-core:${espresso_core_version}"
+
+ implementation project(path: ':quick_android_base')
+}
diff --git a/quick_android_rx_permission/proguard-rules.pro b/quick_android_rx_permission/proguard-rules.pro
new file mode 100644
index 0000000..f1b4245
--- /dev/null
+++ b/quick_android_rx_permission/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
diff --git a/quick_android_rx_permission/src/androidTest/java/cn/qjm253/quick_android_rx_permission/ExampleInstrumentedTest.java b/quick_android_rx_permission/src/androidTest/java/cn/qjm253/quick_android_rx_permission/ExampleInstrumentedTest.java
new file mode 100644
index 0000000..36cd7a1
--- /dev/null
+++ b/quick_android_rx_permission/src/androidTest/java/cn/qjm253/quick_android_rx_permission/ExampleInstrumentedTest.java
@@ -0,0 +1,26 @@
+package cn.qjm253.quick_android_rx_permission;
+
+import android.content.Context;
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import static org.junit.Assert.*;
+
+/**
+ * Instrumented test, which will execute on an Android device.
+ *
+ * @see Testing documentation
+ */
+@RunWith(AndroidJUnit4.class)
+public class ExampleInstrumentedTest {
+ @Test
+ public void useAppContext() {
+ // Context of the app under test.
+ Context appContext = InstrumentationRegistry.getTargetContext();
+
+ assertEquals("cn.qjm253.quick_android_rx_permission.test", appContext.getPackageName());
+ }
+}
diff --git a/quick_android_rx_permission/src/main/AndroidManifest.xml b/quick_android_rx_permission/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..3d9ae9b
--- /dev/null
+++ b/quick_android_rx_permission/src/main/AndroidManifest.xml
@@ -0,0 +1,2 @@
+
diff --git a/quick_android_rx_permission/src/main/java/cn/qjm253/quick_android_rx_permission/QuickAndroidRxPermission.kt b/quick_android_rx_permission/src/main/java/cn/qjm253/quick_android_rx_permission/QuickAndroidRxPermission.kt
new file mode 100644
index 0000000..139bbbd
--- /dev/null
+++ b/quick_android_rx_permission/src/main/java/cn/qjm253/quick_android_rx_permission/QuickAndroidRxPermission.kt
@@ -0,0 +1,65 @@
+package cn.qjm253.quick_android_rx_permission
+
+import androidx.annotation.StringRes
+import androidx.fragment.app.Fragment
+import androidx.fragment.app.FragmentActivity
+import cn.qjm253.quick_android_base.QuickAndroid
+import cn.qjm253.quick_android_base.extensions.getLazySingleton
+import cn.qjm253.quick_android_base.util.RxSchedulersHelper
+import io.reactivex.Observable
+
+/**
+ * @author SunnyQjm
+ * @date 19-7-8 上午9:03
+ */
+
+class QuickAndroidRxPermission {
+ companion object {
+ const val TAG = "QuickAndroidRxPermission"
+
+ fun create(activity: FragmentActivity): QuickAndroidRxPermission {
+ return QuickAndroidRxPermission(activity)
+ }
+
+ fun create(fragment: Fragment): QuickAndroidRxPermission {
+ return QuickAndroidRxPermission(fragment)
+ }
+ }
+
+ private var mRxPermissionFragment: Lazy
+ private var requestCode = 0
+
+ constructor(activity: FragmentActivity) {
+ mRxPermissionFragment = activity.supportFragmentManager
+ .getLazySingleton(TAG, QuickAndroidRxPermissionFragment::class.java)
+ }
+
+ constructor(fragment: Fragment) {
+ mRxPermissionFragment = fragment.childFragmentManager
+ .getLazySingleton(TAG, QuickAndroidRxPermissionFragment::class.java)
+ }
+
+
+ fun request(permissions: Array, @StringRes tip: Int): Observable {
+ requestCode++
+ return mRxPermissionFragment.value.requestPermissions(permissions, tip, requestCode)
+ .compose(RxSchedulersHelper.io_main())
+ }
+
+ fun request(permissions: Array, tip: String = "请求权限"): Observable {
+ requestCode++
+ return mRxPermissionFragment.value.requestPermissions(permissions, tip, requestCode)
+ .compose(RxSchedulersHelper.io_main())
+ }
+}
+
+//////////////////////////////////////////////////////////
+////////// QuickAndroid 扩展
+//////////////////////////////////////////////////////////
+fun QuickAndroid.createRxPermission(activity: FragmentActivity): QuickAndroidRxPermission {
+ return QuickAndroidRxPermission.create(activity)
+}
+
+fun QuickAndroid.createRxPermission(fragment: Fragment): QuickAndroidRxPermission {
+ return QuickAndroidRxPermission.create(fragment)
+}
\ No newline at end of file
diff --git a/quick_android_rx_permission/src/main/java/cn/qjm253/quick_android_rx_permission/QuickAndroidRxPermissionFragment.kt b/quick_android_rx_permission/src/main/java/cn/qjm253/quick_android_rx_permission/QuickAndroidRxPermissionFragment.kt
new file mode 100644
index 0000000..e4b5033
--- /dev/null
+++ b/quick_android_rx_permission/src/main/java/cn/qjm253/quick_android_rx_permission/QuickAndroidRxPermissionFragment.kt
@@ -0,0 +1,77 @@
+package cn.qjm253.quick_android_rx_permission
+
+import android.os.Bundle
+import androidx.annotation.StringRes
+import androidx.fragment.app.Fragment
+import io.reactivex.Observable
+import io.reactivex.ObservableEmitter
+import pub.devrel.easypermissions.EasyPermissions
+
+/**
+ * @author SunnyQjm
+ * @date 19-7-8 上午9:13
+ */
+
+class QuickAndroidRxPermissionFragment : Fragment(), EasyPermissions.PermissionCallbacks {
+
+ private val permissionsMap =
+ mutableMapOf>()
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ retainInstance = true
+ }
+
+ override fun onRequestPermissionsResult(requestCode: Int, permissions: Array, grantResults: IntArray) {
+
+ super.onRequestPermissionsResult(requestCode, permissions, grantResults)
+ EasyPermissions.onRequestPermissionsResult(requestCode, permissions, grantResults, this)
+ }
+
+ /**
+ * 授权成功
+ */
+ override fun onPermissionsGranted(requestCode: Int, perms: MutableList) {
+ permissionsMap[requestCode]?.let {
+ it.onNext(QuickAndroidRxPermissionItem(true, requestCode, perms))
+ it.onComplete()
+ permissionsMap.remove(requestCode)
+ }
+ }
+
+ /**
+ * 授权失败
+ */
+ override fun onPermissionsDenied(requestCode: Int, perms: MutableList) {
+ permissionsMap[requestCode]?.let {
+ it.onNext(QuickAndroidRxPermissionItem(false, requestCode, perms))
+ it.onComplete()
+ permissionsMap.remove(requestCode)
+ }
+ }
+
+
+ /**
+ * 请求权限
+ */
+ fun requestPermissions(permissions: Array, @StringRes tip: Int, requestCode: Int)
+ : Observable {
+ return requestPermissions(permissions, getString(tip), requestCode)
+ }
+
+
+ fun requestPermissions(permissions: Array, tip: String, requestCode: Int)
+ : Observable {
+ return Observable.create { emitter ->
+ permissionsMap[requestCode] = emitter
+ activity?.let {
+ if (EasyPermissions.hasPermissions(it, *permissions)) {
+ onPermissionsGranted(requestCode, permissions.toMutableList())
+ } else {
+ EasyPermissions.requestPermissions(this, tip, requestCode, *permissions)
+ }
+ }
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/quick_android_rx_permission/src/main/java/cn/qjm253/quick_android_rx_permission/QuickAndroidRxPermissionItem.kt b/quick_android_rx_permission/src/main/java/cn/qjm253/quick_android_rx_permission/QuickAndroidRxPermissionItem.kt
new file mode 100644
index 0000000..eaa55e9
--- /dev/null
+++ b/quick_android_rx_permission/src/main/java/cn/qjm253/quick_android_rx_permission/QuickAndroidRxPermissionItem.kt
@@ -0,0 +1,12 @@
+package cn.qjm253.quick_android_rx_permission
+
+/**
+ * @author SunnyQjm
+ * @date 19-7-8 上午9:34
+ */
+
+class QuickAndroidRxPermissionItem(
+ val granted: Boolean,
+ val requestCode: Int,
+ val perms: MutableList
+)
\ No newline at end of file
diff --git a/quick_android_rx_permission/src/main/res/values/strings.xml b/quick_android_rx_permission/src/main/res/values/strings.xml
new file mode 100644
index 0000000..8542005
--- /dev/null
+++ b/quick_android_rx_permission/src/main/res/values/strings.xml
@@ -0,0 +1,2 @@
+
+
diff --git a/quick_android_rx_permission/src/test/java/cn/qjm253/quick_android_rx_permission/ExampleUnitTest.java b/quick_android_rx_permission/src/test/java/cn/qjm253/quick_android_rx_permission/ExampleUnitTest.java
new file mode 100644
index 0000000..7bb747a
--- /dev/null
+++ b/quick_android_rx_permission/src/test/java/cn/qjm253/quick_android_rx_permission/ExampleUnitTest.java
@@ -0,0 +1,17 @@
+package cn.qjm253.quick_android_rx_permission;
+
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+/**
+ * Example local unit test, which will execute on the development machine (host).
+ *
+ * @see Testing documentation
+ */
+public class ExampleUnitTest {
+ @Test
+ public void addition_isCorrect() {
+ assertEquals(4, 2 + 2);
+ }
+}
\ No newline at end of file
diff --git a/quick_android_webview/.gitignore b/quick_android_webview/.gitignore
new file mode 100644
index 0000000..796b96d
--- /dev/null
+++ b/quick_android_webview/.gitignore
@@ -0,0 +1 @@
+/build
diff --git a/quick_android_webview/README.md b/quick_android_webview/README.md
new file mode 100644
index 0000000..41ed848
--- /dev/null
+++ b/quick_android_webview/README.md
@@ -0,0 +1,117 @@
+> ## quick_android_webview 模块说明文档
+>
+> 本模块提供一个简单的**H5容器**实现方案。
+>
+> - 接入腾讯的[X5浏览器](https://x5.tencent.com/)内核,比原生的更安全且对js的新特性有更好的支持,兼容性问题较少,同时支持和QQ、微信等共享内核,可以大大减少APP包的体积。
+> - 本模块实现一个简单的 `Android Webview Bridge` **QuickAndroidWebBridge**,可以实现js调用原生的扫码、图片选择、获取设备信息等功能
+
+
+
+- ### 使用说明
+
+ - 首先在你的作为H5容器的Acivity中使用 `QuickAndroidWebview` 替代原生的Webview进行布局
+
+ ```xml
+
+
+
+ ```
+
+ - 接着在Activity中使用
+
+ ```kotlin
+ // 初始化操作,绑定通信桥
+ QuickAndroidWV
+ .init(this, quickAndroidWebview)
+
+ // 加载URL
+ quickAndroidWebview.loadUrl("file:///android_asset/www/test.html")
+ ```
+
+ - 完成上面两步,基本上H5容器就可以使用了,H5端搭配使用[quick_android_web_bridge](https://github.com/SunnyQjm/quick_android_web_bridge) 这一JS库便可进行原生功能的调用
+
+- ### 提供的原生调用能力如下:
+
+ > H5端具体如何接入,参考[quick_android_web_bridge](https://github.com/SunnyQjm/quick_android_web_bridge)
+
+ - #### toast(吐司弹窗)
+
+ H5端可以使用[quick_android_web_bridge](https://github.com/SunnyQjm/quick_android_web_bridge)调用原生的toast
+
+ ```javascript
+ /**
+ * 调用Android原生的toast接口
+ *
+ * @param msg 要显示的信息
+ */
+ function toast(msg: string)
+
+ // 使用示例
+ QuickAndroidWebBridge.scanCode()
+ ```
+
+ - #### scanCode(扫码)
+
+ H5端可以使用[quick_android_web_bridge](https://github.com/SunnyQjm/quick_android_web_bridge)调用原生的扫码功能,并得到扫码的结果
+
+ ```javascript
+ /**
+ * 调用原生 的扫码功能,并得到扫码的结果
+ *
+ * @return 返回一个Promise对象,可以异步的得到扫码结果
+ */
+ function scanCode(): Promise
+
+
+
+ // 使用示例
+ QuickAndroidWebBridge.scanCode()
+ .then(result => { // 扫码成功,在此处理扫码的结果
+
+ })
+ .catch(err => { // 发生错误,在此处理错误
+
+ })
+ ```
+
+ - #### getSystemInfo(获取Android设备的信息)
+
+ H5端可以使用[quick_android_web_bridge](https://github.com/SunnyQjm/quick_android_web_bridge)获取到当前Android手机的设备信息
+
+ ```javascript
+ /**
+ * 获取当前手机系统的信息
+ *
+ * @return 返回一个Promise对象,可以异步得到扫码结果
+ */
+ function getSystemInfo(): Promise
+
+ // 使用示例
+ QuickAndroidWebBridge.getSystemInfo()
+ .then(result => { // 获取系统信息成功,在此处理结果
+
+ })
+ .catch(err => { // 发生错误,在此处理错误
+
+ })
+ ```
+
+
+
+ - 获取到的信息如下所示:
+
+ | 属性名 | 属性类型 | 属性说明 |
+ | :-------------: | :------: | :------------------------: |
+ | brand | string | 手机品牌 |
+ | model | string | 手机型号 |
+ | hardware | string | 手机硬件信息 |
+ | sdkInt | number | Android SDK 版本 |
+ | release | string | 手机系统版本号 |
+ | statusBarHeight | number | 手机系统状态栏的高度(px) |
+ | systemLanguage | string | 当前手机上使用的语言 |
+
+
+
diff --git a/quick_android_webview/build.gradle b/quick_android_webview/build.gradle
new file mode 100644
index 0000000..440f20f
--- /dev/null
+++ b/quick_android_webview/build.gradle
@@ -0,0 +1,53 @@
+apply plugin: 'com.android.library'
+
+apply plugin: 'kotlin-android'
+
+apply plugin: 'kotlin-android-extensions'
+
+apply plugin: 'kotlin-kapt'
+android {
+ compileSdkVersion compile_sdk_version
+
+ defaultConfig {
+ minSdkVersion min_sdk_version
+ targetSdkVersion target_sdk_version
+ versionCode 1
+ versionName "1.0"
+
+ testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
+
+ ndk {
+ abiFilters "armeabi", "armeabi-v7a", "x86", "mips"
+ }
+ }
+
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+ }
+ }
+
+ sourceSets {
+ main {
+ jniLibs.srcDirs = ['libs']
+ }
+ }
+}
+
+dependencies {
+ implementation fileTree(dir: 'libs', include: ['*.jar'])
+
+ implementation "androidx.appcompat:appcompat:${appcompact_version}"
+ testImplementation "junit:junit:${junit_version}"
+ androidTestImplementation "androidx.test:runner:${test_runner_version}"
+ androidTestImplementation "androidx.test.espresso:espresso-core:${espresso_core_version}"
+
+ implementation project(path: ':quick_android_base')
+ implementation project(path: ':quick_android_rx_permission')
+ implementation project(path: ':quick_android_image_picker')
+ implementation project(path: ':quick_android_qrcode')
+ api files('libs/tbs_sdk.jar')
+ implementation "org.jetbrains.anko:anko-commons:${anko_version}"
+
+}
diff --git a/quick_android_webview/libs/armeabi/liblbs.so b/quick_android_webview/libs/armeabi/liblbs.so
new file mode 100644
index 0000000..b558c45
Binary files /dev/null and b/quick_android_webview/libs/armeabi/liblbs.so differ
diff --git a/quick_android_webview/libs/tbs_sdk.jar b/quick_android_webview/libs/tbs_sdk.jar
new file mode 100644
index 0000000..79c58bd
Binary files /dev/null and b/quick_android_webview/libs/tbs_sdk.jar differ
diff --git a/quick_android_webview/proguard-rules.pro b/quick_android_webview/proguard-rules.pro
new file mode 100644
index 0000000..f1b4245
--- /dev/null
+++ b/quick_android_webview/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
diff --git a/quick_android_webview/src/androidTest/java/cn/qjm253/quick_android_webview/ExampleInstrumentedTest.java b/quick_android_webview/src/androidTest/java/cn/qjm253/quick_android_webview/ExampleInstrumentedTest.java
new file mode 100644
index 0000000..0163320
--- /dev/null
+++ b/quick_android_webview/src/androidTest/java/cn/qjm253/quick_android_webview/ExampleInstrumentedTest.java
@@ -0,0 +1,26 @@
+package cn.qjm253.quick_android_webview;
+
+import android.content.Context;
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import static org.junit.Assert.*;
+
+/**
+ * Instrumented test, which will execute on an Android device.
+ *
+ * @see Testing documentation
+ */
+@RunWith(AndroidJUnit4.class)
+public class ExampleInstrumentedTest {
+ @Test
+ public void useAppContext() {
+ // Context of the app under test.
+ Context appContext = InstrumentationRegistry.getTargetContext();
+
+ assertEquals("cn.qjm253.quick_android_webview.test", appContext.getPackageName());
+ }
+}
diff --git a/quick_android_webview/src/main/AndroidManifest.xml b/quick_android_webview/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..bc94372
--- /dev/null
+++ b/quick_android_webview/src/main/AndroidManifest.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/quick_android_webview/src/main/java/cn/qjm253/quick_android_webview/QuickAndroidWebview.kt b/quick_android_webview/src/main/java/cn/qjm253/quick_android_webview/QuickAndroidWebview.kt
new file mode 100644
index 0000000..fd88df2
--- /dev/null
+++ b/quick_android_webview/src/main/java/cn/qjm253/quick_android_webview/QuickAndroidWebview.kt
@@ -0,0 +1,18 @@
+package cn.qjm253.quick_android_webview
+
+import androidx.fragment.app.FragmentActivity
+import cn.qjm253.quick_android_webview.views.QuickAndroidWebview
+import cn.qjm253.quick_android_webview.web_bridge.QuickAndroidWebBridge
+
+/**
+ * @author SunnyQjm
+ * @date 19-7-10 下午5:29
+ */
+object QuickAndroidWV{
+
+ fun init(context: FragmentActivity, quickAndroidWebview: QuickAndroidWebview): QuickAndroidWV {
+ QuickAndroidWebBridge(context)
+ .bindWebView(quickAndroidWebview)
+ return this
+ }
+}
\ No newline at end of file
diff --git a/quick_android_webview/src/main/java/cn/qjm253/quick_android_webview/views/QuickAndroidWebview.kt b/quick_android_webview/src/main/java/cn/qjm253/quick_android_webview/views/QuickAndroidWebview.kt
new file mode 100644
index 0000000..f46485d
--- /dev/null
+++ b/quick_android_webview/src/main/java/cn/qjm253/quick_android_webview/views/QuickAndroidWebview.kt
@@ -0,0 +1,180 @@
+package cn.qjm253.quick_android_webview.views
+
+/**
+ * @author SunnyQjm
+ * @date 19-7-7 下午11:24
+ */
+
+import android.Manifest
+import android.annotation.SuppressLint
+import android.content.Context
+import android.net.Uri
+import android.util.AttributeSet
+import androidx.fragment.app.FragmentActivity
+import cn.qjm253.quick_android_image_picker.MyImagePicker
+import cn.qjm253.quick_android_rx_permission.QuickAndroidRxPermission
+import com.qingmei2.rximagepicker.core.RxImagePicker
+import com.qingmei2.rximagepicker_extension.MimeType
+import com.qingmei2.rximagepicker_extension_wechat.WechatConfigrationBuilder
+import com.tencent.smtt.export.external.interfaces.WebResourceError
+import com.tencent.smtt.export.external.interfaces.WebResourceRequest
+import com.tencent.smtt.sdk.*
+import org.jetbrains.anko.selector
+
+/**
+ * 腾讯X5浏览器内核封装
+ * 1. 支持图片选择
+ */
+class QuickAndroidWebview(context: Context, attributeSet: AttributeSet) :
+ WebView(context, attributeSet) {
+
+ var onWebViewLoadListener: OnWebViewLoadListener? = null
+
+ init {
+
+ val rxPermission = QuickAndroidRxPermission(context as FragmentActivity)
+
+ this.webViewClient = object : WebViewClient() {
+
+ /**
+ * 防止加载网页时调起系统浏览器
+ */
+ override fun shouldOverrideUrlLoading(p0: WebView?, p1: String?): Boolean {
+ p0?.loadUrl(p1 ?: "")
+ return true
+ }
+
+ override fun onReceivedError(p0: WebView?, p1: WebResourceRequest?, p2: WebResourceError?) {
+ super.onReceivedError(p0, p1, p2)
+ p2?.let {
+ onWebViewLoadListener?.onLoadFail(p2)
+ }
+ }
+
+ override fun onPageFinished(p0: WebView?, p1: String?) {
+ super.onPageFinished(p0, p1)
+ onWebViewLoadListener?.onLoadSuccess()
+ }
+ }
+ this.webChromeClient = object : WebChromeClient() {
+ override fun onProgressChanged(p0: WebView?, p1: Int) {
+ super.onProgressChanged(p0, p1)
+ onWebViewLoadListener?.onProgress(p1)
+ }
+
+ @SuppressLint("CheckResult")
+ override fun openFileChooser(p0: ValueCallback?, p1: String?, p2: String?) {
+ rxPermission
+ .request(arrayOf(Manifest.permission.CAMERA, Manifest.permission.READ_EXTERNAL_STORAGE))
+ .subscribe {
+ if (it.granted) {
+ openImageChooserActivity(context, p0)
+ }
+ }
+ }
+
+ @SuppressLint("CheckResult")
+ override fun onShowFileChooser(
+ p0: WebView?,
+ p1: ValueCallback>?,
+ p2: FileChooserParams?
+ ): Boolean {
+ rxPermission
+ .request(arrayOf(Manifest.permission.CAMERA, Manifest.permission.READ_EXTERNAL_STORAGE))
+ .subscribe {
+ if (it.granted) {
+ openImageChooserActivity(context, null, p1)
+ }
+ }
+ return true
+ }
+ }
+ initWebViewSettings()
+ }
+
+ /**
+ * 打开图片选择
+ */
+ private fun openImageChooserActivity(
+ context: Context, valueCallback: ValueCallback? = null,
+ valueCallback2: ValueCallback>? = null
+ ) {
+ context.selector("选择获取图片方式", listOf("拍照", "相册")) { _, i ->
+ when (i) {
+ 0 -> { // 拍照
+ // 使用摄像头拍摄
+ RxImagePicker.create()
+ .openCamera(context)
+ .subscribe { result ->
+ valueCallback?.onReceiveValue(result.uri)
+ valueCallback2?.onReceiveValue(arrayOf(result.uri))
+ }
+ }
+ 1 -> { // 相册选择
+ val result = mutableListOf()
+ // 打开相册选择
+ RxImagePicker.create(MyImagePicker::class.java)
+ .openZhihuStyleGallery(
+ context,
+ WechatConfigrationBuilder(MimeType.INSTANCE.ofImage(), false)
+ .maxSelectable(9)
+ .countable(true)
+ .spanCount(4)
+ .countable(false)
+ .build()
+ )
+ .subscribe({ res ->
+ // onNext
+ result.add(res.uri)
+ }, {
+ // onError
+
+ }, {
+ // onComplete
+ if (result.size <= 0) {
+ valueCallback2?.onReceiveValue(null)
+ } else {
+ valueCallback2?.onReceiveValue(result.toTypedArray())
+ }
+ })
+
+ }
+ }
+ }
+ }
+
+ @SuppressLint("SetJavaScriptEnabled")
+ private fun initWebViewSettings() {
+ val webSetting = this.settings
+ webSetting.javaScriptEnabled = true
+ webSetting.javaScriptCanOpenWindowsAutomatically = true
+ webSetting.allowFileAccess = true
+ webSetting.layoutAlgorithm = WebSettings.LayoutAlgorithm.NARROW_COLUMNS
+ webSetting.setSupportZoom(true)
+ webSetting.builtInZoomControls = true
+ webSetting.useWideViewPort = true
+ webSetting.setSupportMultipleWindows(true)
+ // webSetting.setLoadWithOverviewMode(true);
+ webSetting.setAppCacheEnabled(true)
+ // webSetting.setDatabaseEnabled(true);
+ webSetting.domStorageEnabled = true
+ webSetting.setGeolocationEnabled(true)
+ webSetting.setAppCacheMaxSize(java.lang.Long.MAX_VALUE)
+ // webSetting.setPageCacheCapacity(IX5WebSettings.DEFAULT_CACHE_CAPACITY);
+ webSetting.pluginState = WebSettings.PluginState.ON_DEMAND
+ // webSetting.setRenderPriority(WebSettings.RenderPriority.HIGH);
+ webSetting.cacheMode = WebSettings.LOAD_NORMAL
+
+ webSetting.setAllowUniversalAccessFromFileURLs(true)
+
+ // this.getSettingsExtension().setPageCacheCapacity(IX5WebSettings.DEFAULT_CACHE_CAPACITY);//extension
+ // settings 的设计
+ }
+
+
+ interface OnWebViewLoadListener {
+ fun onLoadSuccess()
+ fun onLoadFail(error: WebResourceError)
+ fun onProgress(progress: Int)
+ }
+}
\ No newline at end of file
diff --git a/quick_android_webview/src/main/java/cn/qjm253/quick_android_webview/web_bridge/QuickAndroidWebBridge.kt b/quick_android_webview/src/main/java/cn/qjm253/quick_android_webview/web_bridge/QuickAndroidWebBridge.kt
new file mode 100644
index 0000000..98ac1c1
--- /dev/null
+++ b/quick_android_webview/src/main/java/cn/qjm253/quick_android_webview/web_bridge/QuickAndroidWebBridge.kt
@@ -0,0 +1,109 @@
+package cn.qjm253.quick_android_webview.web_bridge
+
+import android.annotation.SuppressLint
+import android.webkit.JavascriptInterface
+import androidx.fragment.app.FragmentActivity
+import cn.qjm253.quick_android_base.extensions.toJson
+import cn.qjm253.quick_android_base.extensions.toast
+import cn.qjm253.quick_android_base.util.SystemInfoHelper
+import cn.qjm253.quick_android_qrcode.QuickAndroidQrCode
+import cn.qjm253.quick_android_webview.views.QuickAndroidWebview
+import com.tencent.smtt.sdk.ValueCallback
+import java.lang.Exception
+
+/**
+ * 与WebView中的Js相互通信的工具类
+ *
+ * @author SunnyQjm
+ * @date 19-7-8 下午2:48
+ */
+
+class QuickAndroidWebBridge(
+ private val context: FragmentActivity
+) {
+ companion object {
+ const val JS_CALLER_NAME = "QuickAndroidJsCallEr"
+
+
+ //////////////////////////////////////////////
+ /////// 回调名称定义
+ //////////////////////////////////////////////
+ const val CALLBACK_BASE_NAME = "Quick_Android_Js_Callback_"
+
+ /**
+ * 扫码相关回调
+ */
+ const val CALLBACK_SCAN_CODE_SUCCESS = "${CALLBACK_BASE_NAME}scan_code_success" // 扫码成功回调名
+ const val CALLBACK_SCAN_CODE_FAIL = "${CALLBACK_BASE_NAME}scan_code_fail" // 扫码失败回调名
+ const val CALLBACK_GET_SYSTEM_INFO_SUCCESS = "${CALLBACK_BASE_NAME}get_system_info_success" // 获取系统信息成功
+ const val CALLBACK_GET_SYSTEM_INFO_FAIL = "${CALLBACK_BASE_NAME}get_system_info_fail" // 获取系统信息失败
+ }
+
+ private var webView: QuickAndroidWebview? = null
+
+ /**
+ * 绑定到WebView上
+ */
+ fun bindWebView(webview: QuickAndroidWebview) {
+ this.webView = webview
+ webview.addJavascriptInterface(this, JS_CALLER_NAME)
+ }
+
+ /**
+ * 提供给Js调用的显示toast接口
+ */
+ @JavascriptInterface
+ fun toast(msg: String) {
+ context.toast(msg)
+ }
+
+
+ /**
+ * 提供给Js调用的扫码接口
+ */
+ @SuppressLint("CheckResult")
+ @JavascriptInterface
+ fun scanCode() {
+ // 调用原生的扫码接口
+ webView?.post {
+ // 需要在主线程中执行
+ QuickAndroidQrCode.create(context)
+ .scanQrCode()
+ .subscribe({
+ // 扫码结果
+ callJs(CALLBACK_SCAN_CODE_SUCCESS, it.content)
+ }, {
+ // 扫码出错
+ callJs(CALLBACK_SCAN_CODE_FAIL, it.message ?: "扫码出错")
+ })
+ }
+
+ }
+
+ /**
+ * 提供给Js调用,得到系统的信息
+ */
+ @JavascriptInterface
+ fun getSystemInfo() {
+ try {
+ val systemInfo = SystemInfoHelper
+ .buildSystemInfo(context)
+ callJs(CALLBACK_GET_SYSTEM_INFO_SUCCESS, systemInfo.toJson())
+ } catch (e: Exception) {
+ callJs(CALLBACK_GET_SYSTEM_INFO_FAIL, e.message ?: "获取系统信息失败")
+ }
+ }
+
+ /**
+ * 调用Js的回调函数,并且支持传递一个字符串参数
+ */
+ fun callJs(callbackName: String, param: String = "", callback: ValueCallback = ValueCallback {}) {
+ webView?.post {
+ // 执行JS的代码,需要在主线程中进行
+ webView?.evaluateJavascript("javascript:$callbackName('$param')", callback)
+ }
+
+ }
+}
+
+
diff --git a/quick_android_webview/src/main/res/values/strings.xml b/quick_android_webview/src/main/res/values/strings.xml
new file mode 100644
index 0000000..8542005
--- /dev/null
+++ b/quick_android_webview/src/main/res/values/strings.xml
@@ -0,0 +1,2 @@
+
+
diff --git a/quick_android_webview/src/test/java/cn/qjm253/quick_android_webview/ExampleUnitTest.java b/quick_android_webview/src/test/java/cn/qjm253/quick_android_webview/ExampleUnitTest.java
new file mode 100644
index 0000000..862f1a3
--- /dev/null
+++ b/quick_android_webview/src/test/java/cn/qjm253/quick_android_webview/ExampleUnitTest.java
@@ -0,0 +1,17 @@
+package cn.qjm253.quick_android_webview;
+
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+/**
+ * Example local unit test, which will execute on the development machine (host).
+ *
+ * @see Testing documentation
+ */
+public class ExampleUnitTest {
+ @Test
+ public void addition_isCorrect() {
+ assertEquals(4, 2 + 2);
+ }
+}
\ No newline at end of file
diff --git a/settings.gradle b/settings.gradle
index 38427ab..2e38dff 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -1,2 +1,11 @@
rootProject.name = "MIN-VPN-Client_V2"
include ':app'
+include ':quick_android_base'
+include ':quick_android_custom_view'
+include ':quick_android_easy_bar'
+include ':quick_android_file_picker'
+include ':quick_android_image_picker'
+include ':quick_android_mvp'
+include ':quick_android_qrcode'
+include ':quick_android_rx_permission'
+include ':quick_android_webview'