mirror of
https://gitee.com/willfree/min-vpn-client_v2.git
synced 2026-10-04 19:42:00 +08:00
add many:解决了若干bug,成功跑起来了界面。最大的一个改变是将quick_android库由云端库改为了本地库,然后注释掉了一些报错且没有用到的代码。
This commit is contained in:
1 parent
392aec0de9
commit
f4cec991ed
190 files changed
+9646
-33
No files matched your search
+35
-23
@@ -1,6 +1,11 @@
|
||||
plugins {
|
||||
id 'com.android.application'
|
||||
}
|
||||
apply plugin: 'com.android.application'
|
||||
apply plugin: 'kotlin-android'
|
||||
apply plugin: 'kotlin-android-extensions'
|
||||
//plugins {
|
||||
// id 'com.android.application'
|
||||
// id 'kotlin-android'
|
||||
// id 'kotlin-android-extensions'
|
||||
//}
|
||||
|
||||
android {
|
||||
applicationVariants.all { variant ->
|
||||
@@ -71,15 +76,15 @@ android {
|
||||
|
||||
dependencies {
|
||||
|
||||
testImplementation 'junit:junit:4.+'
|
||||
// testImplementation 'junit:junit:4.+'
|
||||
androidTestImplementation 'androidx.test.ext:junit:1.1.2'
|
||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
|
||||
compileOnly 'com.google.android.things:androidthings:1.0'
|
||||
|
||||
// 以下为旧版本VPN客户端的依赖库(去掉kotlin)
|
||||
// 以下为旧版本VPN客户端的依赖库
|
||||
implementation fileTree(dir: "libs", include: ["*.jar"])
|
||||
// implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
|
||||
implementation 'androidx.core:core-ktx:1.3.1'
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
|
||||
implementation 'androidx.core:core-ktx:1.3.0'
|
||||
implementation "androidx.appcompat:appcompat:$androidx_base_version"
|
||||
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
|
||||
implementation "androidx.recyclerview:recyclerview:$androidx_base_version"
|
||||
@@ -87,20 +92,28 @@ dependencies {
|
||||
|
||||
implementation "org.jetbrains.anko:anko-commons:$anko_version"
|
||||
|
||||
// 所有的模块都依赖于 quick_android_base (必选)
|
||||
implementation "com.github.SunnyQjm.quickandroid:quick_android_base:$quick_android_version"
|
||||
implementation "com.github.SunnyQjm.quickandroid:quick_android_custom_view:$quick_android_version"
|
||||
implementation "com.github.SunnyQjm.quickandroid:quick_android_easy_bar:$quick_android_version"
|
||||
// 引入quick_android_mvp模块,来使用网络请求和MVP架构(可选)
|
||||
implementation "com.github.SunnyQjm.quickandroid:quick_android_mvp:$quick_android_version"
|
||||
// 引入quick_android_rx_permission模块,来接入链式调用动态请求权限的功能(可选)
|
||||
implementation "com.github.SunnyQjm.quickandroid:quick_android_rx_permission:$quick_android_version"
|
||||
// 引入quick_android_qrcode模块,来接入扫码功能(可选)
|
||||
implementation "com.github.SunnyQjm.quickandroid:quick_android_qrcode:$quick_android_version"
|
||||
// 引入quick_android_image_picker模块,来接入图片选择,图片裁剪等功能(可选)
|
||||
implementation "com.github.SunnyQjm.quickandroid:quick_android_image_picker:$quick_android_version"
|
||||
// 引入文件选择功能(调用系统的文件管理器)
|
||||
implementation "com.github.SunnyQjm.quickandroid:quick_android_file_picker:$quick_android_version"
|
||||
// // 所有的模块都依赖于 quick_android_base (必选)
|
||||
// implementation "com.github.SunnyQjm.quickandroid:quick_android_base:$quick_android_version"
|
||||
// implementation "com.github.SunnyQjm.quickandroid:quick_android_custom_view:$quick_android_version"
|
||||
// implementation "com.github.SunnyQjm.quickandroid:quick_android_easy_bar:$quick_android_version"
|
||||
// // 引入quick_android_mvp模块,来使用网络请求和MVP架构(可选)
|
||||
// implementation "com.github.SunnyQjm.quickandroid:quick_android_mvp:$quick_android_version"
|
||||
// // 引入quick_android_rx_permission模块,来接入链式调用动态请求权限的功能(可选)
|
||||
// implementation "com.github.SunnyQjm.quickandroid:quick_android_rx_permission:$quick_android_version"
|
||||
// // 引入quick_android_qrcode模块,来接入扫码功能(可选)
|
||||
//// implementation "com.github.SunnyQjm.quickandroid:quick_android_qrcode:$quick_android_version"
|
||||
//// // 引入quick_android_image_picker模块,来接入图片选择,图片裁剪等功能(可选)
|
||||
//// implementation "com.github.SunnyQjm.quickandroid:quick_android_image_picker:$quick_android_version"
|
||||
//// // 引入文件选择功能(调用系统的文件管理器)
|
||||
//// implementation "com.github.SunnyQjm.quickandroid:quick_android_file_picker:$quick_android_version"
|
||||
api project(path: ':quick_android_base')
|
||||
api project(path: ':quick_android_custom_view')
|
||||
api project(path: ':quick_android_easy_bar')
|
||||
api project(path: ':quick_android_mvp')
|
||||
api project(path: ':quick_android_rx_permission')
|
||||
// api project(path: ':quick_android_qrcode')
|
||||
// api project(path: ':quick_android_image_picker')
|
||||
// api project(path: ':quick_android_file_picker')
|
||||
|
||||
// loading button
|
||||
implementation 'br.com.simplepass:loading-button-android:2.2.0'
|
||||
@@ -121,13 +134,12 @@ dependencies {
|
||||
implementation "com.scwang.smartrefresh:SmartRefreshLayout:$smart_refresh_layout_version"
|
||||
implementation "com.scwang.smartrefresh:SmartRefreshHeader:$smart_refresh_layout_version"
|
||||
|
||||
|
||||
// brvah
|
||||
implementation 'com.github.CymChad:BaseRecyclerViewAdapterHelper:2.9.49-androidx'
|
||||
|
||||
implementation 'com.google.android.material:material:1.3.0-alpha01'
|
||||
|
||||
testImplementation 'junit:junit:4.13.2'
|
||||
testImplementation 'junit:junit:4.13'
|
||||
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
|
||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
|
||||
|
||||
|
||||
@@ -1,8 +1,43 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:dist="http://schemas.android.com/apk/distribution"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
package="com.pkusz.min_vpn_client">
|
||||
|
||||
<application android:label="@string/app_name">
|
||||
<uses-library android:name="com.google.android.things" />
|
||||
<application
|
||||
android:name=".APP"
|
||||
android:label="@string/app_name"
|
||||
android:allowBackup="true"
|
||||
android:icon="@drawable/logo"
|
||||
android:requestLegacyExternalStorage="true"
|
||||
android:roundIcon="@drawable/logo"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/AppTheme"
|
||||
tools:ignore="GoogleAppIndexingWarning">
|
||||
<!-- <uses-library android:name="com.google.android.things" />-->
|
||||
<activity android:name=".activity.welcome.WelcomeActivity">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN"/>
|
||||
<category android:name="android.intent.category.LAUNCHER"/>
|
||||
</intent-filter>
|
||||
</activity>
|
||||
</application>
|
||||
|
||||
<!-- 以下为copy旧版本VPN的权限定义部分 -->
|
||||
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
|
||||
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
|
||||
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
||||
<uses-permission
|
||||
android:name="android.permission.READ_PRIVILEGED_PHONE_STATE"
|
||||
tools:ignore="ProtectedPermissions" />
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
|
||||
tools:ignore="ScopedStorage" />
|
||||
<uses-permission android:name="android.permission.CAMERA" />
|
||||
<uses-permission android:name="android.permission.VIBRATE" />
|
||||
<uses-permission android:name="android.permission.USE_BIOMETRIC" />
|
||||
<dist:module dist:instant="true" />
|
||||
</manifest>
|
||||
@@ -0,0 +1,20 @@
|
||||
package com.pkusz.min_vpn_client;
|
||||
/*
|
||||
* @Author: Wang Feng
|
||||
* @Description:
|
||||
* @Version: 1.0.0
|
||||
* @Date: 9:27 2021/6/24
|
||||
* @Copyright: MIN-Group;国家重大科技基础设施——未来网络北大实验室;深圳市信息论与未来网络重点实验室
|
||||
*/
|
||||
|
||||
import android.app.Application;
|
||||
|
||||
public class APP extends Application {
|
||||
@Override
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
|
||||
// 网络接口初始化
|
||||
// ...
|
||||
}
|
||||
}
|
||||
@@ -9,10 +9,14 @@ package com.pkusz.min_vpn_client.activity.base;
|
||||
|
||||
import android.widget.Toast;
|
||||
import org.greenrobot.eventbus.EventBus;
|
||||
import cn.qjm253.quick_android_mvp.base.activity.BaseQuickAndroidMVPActivity;
|
||||
import org.greenrobot.eventbus.Subscribe;
|
||||
import org.greenrobot.eventbus.ThreadMode;
|
||||
|
||||
public abstract class MINVpnBaseActivity<BasePresenter extends cn.qjm253.quick_android_mvp.base.mvp.BasePresenter<?, ?>>
|
||||
extends BaseQuickAndroidMVPActivity<BasePresenter>
|
||||
import cn.qjm253.quick_android_mvp.base.activity.BaseQuickAndroidMVPActivity;
|
||||
import cn.qjm253.quick_android_mvp.base.mvp.BasePresenter;
|
||||
|
||||
public abstract class MINVpnBaseActivity<P extends BasePresenter<?, ?>>
|
||||
extends BaseQuickAndroidMVPActivity<P>
|
||||
implements MINVpnBaseView{
|
||||
|
||||
public void showWarning(String msg){
|
||||
@@ -37,6 +41,11 @@ public abstract class MINVpnBaseActivity<BasePresenter extends cn.qjm253.quick_a
|
||||
EventBus.getDefault().unregister(this);
|
||||
}
|
||||
|
||||
@Subscribe(threadMode = ThreadMode.MAIN)
|
||||
public void onReceiveTest(String test){
|
||||
|
||||
}
|
||||
|
||||
// **************************************************************
|
||||
// cn.qjm253.quick_android_base.extensions.ContextExtension内容重写
|
||||
public void toast(String info) {
|
||||
|
||||
@@ -14,13 +14,18 @@ import androidx.annotation.Nullable;
|
||||
|
||||
import com.pkusz.min_vpn_client.R;
|
||||
import com.pkusz.min_vpn_client.activity.base.MINVpnBaseActivity;
|
||||
import com.pkusz.min_vpn_client.extensions.FileUtilsExtensions;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import cn.qjm253.min_vpn.extensions.FileUtilsExtensionsKt;
|
||||
import cn.qjm253.quick_android_base.util.FileUtils;
|
||||
import cn.qjm253.quick_android_mvp.base.mvp.BaseView;
|
||||
import io.reactivex.functions.Consumer;
|
||||
import io.reactivex.plugins.RxJavaPlugins;
|
||||
|
||||
public class WelcomeActivity extends MINVpnBaseActivity<WelcomeActivityPresenter>
|
||||
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<WelcomeActivityPresenter
|
||||
@Override
|
||||
protected void onStart() {
|
||||
super.onStart();
|
||||
// scan.arcsoft.EncryptedUtil.FileUtils [扫描二维码部分。弃用]
|
||||
FileUtilsExtensionsKt.init(FileUtils.INSTANCE,getApplicationContext());
|
||||
// SafetyInfoUtil [GPS部分。弃用]
|
||||
RxJavaPlugins.setErrorHandler(new Consumer<Throwable>() {
|
||||
@Override
|
||||
public void accept(Throwable throwable) throws Exception {
|
||||
throwable.printStackTrace();
|
||||
}
|
||||
});
|
||||
fingerprintAuthenticate();
|
||||
}
|
||||
|
||||
public void fingerprintAuthenticate(){
|
||||
System.out.println("指纹验证...");
|
||||
}
|
||||
|
||||
@NotNull
|
||||
|
||||
@@ -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<View>(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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
# extensions模块说明
|
||||
由于Kotlin的一些特性,例如可以直接扩展一些类的方法,故保留此部分的Kotlin代码。
|
||||
(旧版本与quick_android耦合得有些严重)
|
||||
+1
-2
@@ -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;
|
||||
+37
-2
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,3 +15,6 @@ org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
|
||||
# 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
|
||||
|
||||
# Automatically convert third-party libraries to use AndroidX
|
||||
android.enableJetifier=true
|
||||
@@ -0,0 +1 @@
|
||||
/build
|
||||
@@ -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<String>) {
|
||||
when(requestCode) {
|
||||
0 -> { // 请求摄像头权限
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 授权失败
|
||||
*/
|
||||
override fun onPermissionsDenied(requestCode: Int, perms: MutableList<String>) {
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
- ### `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}"
|
||||
```
|
||||
|
||||
|
||||
@@ -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}"
|
||||
|
||||
|
||||
}
|
||||
+177
@@ -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.** { <fields>; }
|
||||
|
||||
# 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 <fields>;
|
||||
}
|
||||
|
||||
##---------------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 {
|
||||
<init>(...);
|
||||
}
|
||||
##---------------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.* <methods>;
|
||||
}
|
||||
|
||||
# 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.* <methods>; }
|
||||
-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---------------##
|
||||
|
||||
+26
@@ -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 <a href="http://d.android.com/tools/testing">Testing documentation</a>
|
||||
*/
|
||||
@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());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="cn.qjm253.quick_android_base">
|
||||
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
|
||||
<uses-permission android:name="android.permission.VIBRATE"/>
|
||||
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
|
||||
<uses-permission android:name="android.permission.CAMERA"/>
|
||||
|
||||
|
||||
|
||||
</manifest>
|
||||
@@ -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
|
||||
// }
|
||||
//
|
||||
//
|
||||
//}
|
||||
@@ -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()
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
+110
@@ -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<out String>, grantResults: IntArray) {
|
||||
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
|
||||
EasyPermissions.onRequestPermissionsResult(requestCode, permissions, grantResults, this)
|
||||
}
|
||||
|
||||
/**
|
||||
* 授权成功
|
||||
*/
|
||||
override fun onPermissionsGranted(requestCode: Int, perms: MutableList<String>) {
|
||||
}
|
||||
|
||||
/**
|
||||
* 授权失败
|
||||
*/
|
||||
override fun onPermissionsDenied(requestCode: Int, perms: MutableList<String>) {
|
||||
|
||||
}
|
||||
|
||||
|
||||
open fun easyRequestPermissions(permissions: Array<String>, @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?) {
|
||||
}
|
||||
}
|
||||
+191
@@ -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 <T: Fragment> newInstance_(clazz: Class<T>, 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<out String>, grantResults: IntArray) {
|
||||
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
|
||||
EasyPermissions.onRequestPermissionsResult(requestCode, permissions, grantResults, this)
|
||||
}
|
||||
|
||||
/**
|
||||
* 授权成功
|
||||
*/
|
||||
override fun onPermissionsGranted(requestCode: Int, perms: MutableList<String>) {
|
||||
}
|
||||
|
||||
/**
|
||||
* 授权失败
|
||||
*/
|
||||
override fun onPermissionsDenied(requestCode: Int, perms: MutableList<String>) {
|
||||
|
||||
}
|
||||
|
||||
|
||||
open fun requestPermissions(permissions: Array<String>, @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)
|
||||
}
|
||||
}
|
||||
+41
@@ -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 <T: Fragment> 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
|
||||
}
|
||||
+12
@@ -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)
|
||||
}
|
||||
+149
@@ -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))
|
||||
}
|
||||
|
||||
+20
@@ -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)
|
||||
}
|
||||
+44
@@ -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 <T: Fragment> FragmentManager.getLazySingleton(tag: String, clazz: Class<T>): Lazy<T> {
|
||||
val fragmentManager = this
|
||||
return object : Lazy<T> {
|
||||
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 <T: Fragment> getFragment(tag: String, clazz: Class<T>, 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!!
|
||||
}
|
||||
|
||||
+63
@@ -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)
|
||||
}
|
||||
+29
@@ -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)
|
||||
}
|
||||
+48
@@ -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
|
||||
}
|
||||
+13
@@ -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
|
||||
}
|
||||
+20
@@ -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)
|
||||
}
|
||||
+39
@@ -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 <T> notNullAndOnlyInitFirstTime(): ReadWriteProperty<Any?, T> = NotNullAndOnlyInitFirstTime()
|
||||
}
|
||||
|
||||
/**
|
||||
* 本代理类代理的属性,不可为null,可延迟初始化,但是只会被初始化一次,第一次初始化之后的set操作都将被忽略
|
||||
*/
|
||||
private class NotNullAndOnlyInitFirstTime<T> : ReadWriteProperty<Any?, T> {
|
||||
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
|
||||
}
|
||||
|
||||
}
|
||||
+37
@@ -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)
|
||||
}
|
||||
}
|
||||
+123
@@ -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)
|
||||
}
|
||||
@@ -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 = ""
|
||||
)
|
||||
@@ -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<String> = mutableListOf(),
|
||||
val values: MutableList<Any> = 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<out String>)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+128
@@ -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)
|
||||
}
|
||||
}
|
||||
+366
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 <T> json2Bean(json: String, beanClass: Class<T>): T? {
|
||||
var bean: T? = null
|
||||
try {
|
||||
bean = instance.fromJson(json, beanClass)
|
||||
} catch (e: JsonSyntaxException) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
|
||||
return bean
|
||||
}
|
||||
|
||||
fun <T> json2Bean(json: String): T? {
|
||||
var bean: T? = null
|
||||
val type = object : TypeToken<ArrayList<T>>() {
|
||||
|
||||
}.type
|
||||
try {
|
||||
bean = instance.fromJson<T>(json, type)
|
||||
} catch (e: JsonSyntaxException) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
|
||||
return bean
|
||||
}
|
||||
|
||||
// public static ArrayList<Tag> json2TagList(String json) {
|
||||
// Type type = new TypeToken<ArrayList<Tag>>(){}.getType();
|
||||
// ArrayList<Tag> bean = new ArrayList<>();
|
||||
// try {
|
||||
// bean = getInstance().fromJson(json, type);
|
||||
// } catch (JsonSyntaxException e) {
|
||||
// e.printStackTrace();
|
||||
// }
|
||||
// return bean;
|
||||
// }
|
||||
//
|
||||
// public static ArrayList<Record> json2RecordList(String json) {
|
||||
// Type type = new TypeToken<ArrayList<Record>>(){}.getType();
|
||||
// ArrayList<Record> bean = new ArrayList<>();
|
||||
// try {
|
||||
// bean = getInstance().fromJson(json, type);
|
||||
// } catch (JsonSyntaxException e) {
|
||||
// e.printStackTrace();
|
||||
// }
|
||||
// return bean;
|
||||
// }
|
||||
//
|
||||
// public static ArrayList<Image> json2ImageList(String json) {
|
||||
// Type type = new TypeToken<ArrayList<Image>>(){}.getType();
|
||||
// ArrayList<Image> 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`)
|
||||
}
|
||||
}
|
||||
+41
@@ -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 <T> io_main(): ObservableTransformer<T, T> {
|
||||
return ObservableTransformer { upstream ->
|
||||
upstream
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
}
|
||||
}
|
||||
|
||||
fun <T> io_io(): ObservableTransformer<T, T>{
|
||||
return ObservableTransformer { upstream ->
|
||||
upstream.subscribeOn(Schedulers.io())
|
||||
.observeOn(Schedulers.io())
|
||||
}
|
||||
}
|
||||
fun <T> main_io(): ObservableTransformer<T, T> {
|
||||
return ObservableTransformer { upstream ->
|
||||
upstream.subscribeOn(AndroidSchedulers.mainThread())
|
||||
.observeOn(Schedulers.io())
|
||||
}
|
||||
}
|
||||
|
||||
fun <T> comppute_main(): ObservableTransformer<T, T> {
|
||||
return ObservableTransformer { upstream ->
|
||||
upstream.subscribeOn(Schedulers.computation())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<String, ?> 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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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<out CharSequence>, 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<out Any>) {
|
||||
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<out Any>) {
|
||||
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))
|
||||
}
|
||||
}
|
||||
+88
@@ -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<out Locale> {
|
||||
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()
|
||||
)
|
||||
}
|
||||
}
|
||||
+162
@@ -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
|
||||
}
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 2.8 KiB |
@@ -0,0 +1,3 @@
|
||||
<resources>
|
||||
<string name="camera_permission_require">本功能需要请求摄像头权限</string>
|
||||
</resources>
|
||||
@@ -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 <a href="http://d.android.com/tools/testing">Testing documentation</a>
|
||||
*/
|
||||
public class ExampleUnitTest {
|
||||
@Test
|
||||
public void addition_isCorrect() {
|
||||
assertEquals(4, 2 + 2);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
/build
|
||||
@@ -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')
|
||||
|
||||
}
|
||||
+21
@@ -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
|
||||
+26
@@ -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 <a href="http://d.android.com/tools/testing">Testing documentation</a>
|
||||
*/
|
||||
@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());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="cn.qjm253.quick_android_custom_view"/>
|
||||
+102
@@ -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()
|
||||
}
|
||||
}
|
||||
+75
@@ -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()
|
||||
}
|
||||
}
|
||||
+76
@@ -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()
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
+37
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
+74
@@ -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)
|
||||
}
|
||||
}
|
||||
+37
@@ -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)
|
||||
}
|
||||
+271
@@ -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()
|
||||
}
|
||||
}
|
||||
+154
@@ -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()
|
||||
}
|
||||
}
|
||||
+58
@@ -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
|
||||
}
|
||||
}
|
||||
+112
@@ -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<Drawable?>
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+146
@@ -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<Drawable?>
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
+65
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+114
@@ -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<Drawable?>
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<size android:width="@dimen/switch_height"
|
||||
android:height="@dimen/switch_height"/>
|
||||
<corners android:radius="@dimen/half_of_switch_height"/>
|
||||
<gradient
|
||||
android:startColor="@color/white"
|
||||
android:endColor="@color/white"/>
|
||||
|
||||
<stroke
|
||||
android:width="1dp"
|
||||
android:color="@color/colorPrimary"/>
|
||||
</shape>
|
||||
@@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<size android:height="@dimen/switch_height"
|
||||
android:width="@dimen/switch_width"/>
|
||||
<corners android:radius="15dp"/>
|
||||
<gradient
|
||||
android:startColor="@color/colorPrimary"
|
||||
android:endColor="@color/colorPrimary"/>
|
||||
|
||||
</shape>
|
||||
@@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<size android:width="@dimen/switch_height"
|
||||
android:height="@dimen/switch_height"/>
|
||||
<corners android:radius="@dimen/half_of_switch_height"/>
|
||||
<gradient
|
||||
android:startColor="@color/white"
|
||||
android:endColor="@color/white"/>
|
||||
|
||||
<stroke
|
||||
android:width="1dp"
|
||||
android:color="@color/grey"/>
|
||||
</shape>
|
||||
@@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<size
|
||||
android:width="@dimen/switch_width"
|
||||
android:height="@dimen/switch_height"/>
|
||||
<corners android:radius="15dp"/>
|
||||
<gradient
|
||||
android:endColor="@color/grey"
|
||||
android:startColor="@color/grey"/>
|
||||
</shape>
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 992 B |
Binary file not shown.
|
After Width: | Height: | Size: 1.9 KiB |
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:state_checked="true" android:drawable="@drawable/blue_thumb"/>
|
||||
<item android:drawable="@drawable/gray_thumb"/>
|
||||
</selector>
|
||||
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:state_checked="true" android:drawable="@drawable/blue_track"/>
|
||||
<item android:drawable="@drawable/gray_track"/>
|
||||
</selector>
|
||||
@@ -0,0 +1,89 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<attr name="left_drawable" format="reference"/>
|
||||
<attr name="right_drawable" format="reference"/>
|
||||
<attr name="top_drawable" format="reference"/>
|
||||
<attr name="bottom_drawable" format="reference"/>
|
||||
|
||||
<attr name="drawable_width" format="dimension"/>
|
||||
<attr name="drawable_height" format="dimension"/>
|
||||
|
||||
<attr name="left_drawable_width" format="dimension"/>
|
||||
<attr name="left_drawable_height" format="dimension"/>
|
||||
|
||||
<attr name="right_drawable_width" format="dimension"/>
|
||||
<attr name="right_drawable_height" format="dimension"/>
|
||||
|
||||
<attr name="top_drawable_width" format="dimension"/>
|
||||
<attr name="top_drawable_height" format="dimension"/>
|
||||
|
||||
<attr name="bottom_drawable_width" format="dimension"/>
|
||||
<attr name="bottom_drawable_height" format="dimension"/>
|
||||
|
||||
<declare-styleable name="DrawableEditText">
|
||||
<attr name="left_drawable" />
|
||||
<attr name="right_drawable" />
|
||||
<attr name="top_drawable" />
|
||||
<attr name="bottom_drawable" />
|
||||
|
||||
<attr name="drawable_width" />
|
||||
<attr name="drawable_height" />
|
||||
|
||||
<attr name="left_drawable_width" />
|
||||
<attr name="left_drawable_height" />
|
||||
|
||||
<attr name="right_drawable_width" />
|
||||
<attr name="right_drawable_height" />
|
||||
|
||||
<attr name="top_drawable_width" />
|
||||
<attr name="top_drawable_height" />
|
||||
|
||||
<attr name="bottom_drawable_width" />
|
||||
<attr name="bottom_drawable_height" />
|
||||
|
||||
</declare-styleable>
|
||||
|
||||
<declare-styleable name="DrawableTextView">
|
||||
<attr name="left_drawable" />
|
||||
<attr name="right_drawable" />
|
||||
<attr name="top_drawable" />
|
||||
<attr name="bottom_drawable" />
|
||||
|
||||
<attr name="drawable_width" />
|
||||
<attr name="drawable_height" />
|
||||
|
||||
<attr name="left_drawable_width" />
|
||||
<attr name="left_drawable_height" />
|
||||
|
||||
<attr name="right_drawable_width" />
|
||||
<attr name="right_drawable_height" />
|
||||
|
||||
<attr name="top_drawable_width" />
|
||||
<attr name="top_drawable_height" />
|
||||
|
||||
<attr name="bottom_drawable_width" />
|
||||
<attr name="bottom_drawable_height" />
|
||||
</declare-styleable>
|
||||
|
||||
<declare-styleable name="DrawableButton">
|
||||
<attr name="left_drawable" />
|
||||
<attr name="right_drawable" />
|
||||
<attr name="top_drawable" />
|
||||
<attr name="bottom_drawable" />
|
||||
|
||||
<attr name="drawable_width" />
|
||||
<attr name="drawable_height" />
|
||||
|
||||
<attr name="left_drawable_width" />
|
||||
<attr name="left_drawable_height" />
|
||||
|
||||
<attr name="right_drawable_width" />
|
||||
<attr name="right_drawable_height" />
|
||||
|
||||
<attr name="top_drawable_width" />
|
||||
<attr name="top_drawable_height" />
|
||||
|
||||
<attr name="bottom_drawable_width" />
|
||||
<attr name="bottom_drawable_height" />
|
||||
</declare-styleable>
|
||||
</resources>
|
||||
@@ -0,0 +1,55 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<attr name="menu_title" format="string|reference" />
|
||||
<attr name="menu_title_size" format="dimension" />
|
||||
<attr name="menu_title_color" format="color" />
|
||||
<attr name="right_margin" format="dimension" />
|
||||
<attr name="left_margin" format="dimension" />
|
||||
|
||||
<attr name="menu_value" format="string|reference" />
|
||||
<attr name="menu_value_size" format="dimension" />
|
||||
<attr name="menu_value_color" format="color" />
|
||||
<attr name="left_resource" format="reference" />
|
||||
<attr name="right_resource" format="reference" />
|
||||
<attr name="left_icon_size" format="dimension" />
|
||||
<attr name="right_icon_size" format="dimension" />
|
||||
<attr name="is_right_icon_visible" format="boolean" />
|
||||
<attr name="is_left_icon_visible" format="boolean" />
|
||||
<attr name="left_icon_text_span" format="dimension" />
|
||||
|
||||
<declare-styleable name="LineMenuItem">
|
||||
<attr name="menu_title" />
|
||||
<attr name="menu_title_size" />
|
||||
<attr name="menu_title_color" />
|
||||
<attr name="right_margin" />
|
||||
<attr name="left_margin" />
|
||||
<attr name="menu_value" />
|
||||
<attr name="menu_value_size" />
|
||||
<attr name="menu_value_color" />
|
||||
<attr name="left_resource" />
|
||||
<attr name="right_resource" />
|
||||
<attr name="left_icon_size" />
|
||||
<attr name="right_icon_size" />
|
||||
<attr name="is_right_icon_visible" />
|
||||
<attr name="is_left_icon_visible" />
|
||||
<attr name="left_icon_text_span" />
|
||||
</declare-styleable>
|
||||
|
||||
<declare-styleable name="ExpandableLineMenuItem">
|
||||
<attr name="menu_height" format="dimension"/>
|
||||
<attr name="menu_width" format="dimension"/>
|
||||
<attr name="open" format="boolean"/>
|
||||
<attr name="openedAble" format="boolean"/>
|
||||
<attr name="menu_bg" format="color|reference"/>
|
||||
</declare-styleable>
|
||||
|
||||
<declare-styleable name="LineMenuSwitch">
|
||||
<attr name="menu_title" />
|
||||
<attr name="menu_title_size" />
|
||||
<attr name="menu_title_color" />
|
||||
<attr name="right_margin" />
|
||||
<attr name="left_margin" />
|
||||
<attr name="switch_track" format="reference" />
|
||||
<attr name="switch_thumb" format="reference" />
|
||||
</declare-styleable>
|
||||
</resources>
|
||||
@@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="doyin_black">#161923</color>
|
||||
<color name="light_grey">#eee</color>
|
||||
<color name="grey">#bbb</color>
|
||||
<color name="white">#fff</color>
|
||||
<color name="transparent">#00ffffff</color>
|
||||
<color name="text_gray">#333333</color>
|
||||
<color name="black">#000</color>
|
||||
<color name="red">#f00</color>
|
||||
|
||||
<color name="line_gray">#dddddd</color>
|
||||
</resources>
|
||||
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<dimen name="switch_height">26dp</dimen>
|
||||
<dimen name="half_of_switch_height">13dp</dimen>
|
||||
<dimen name="switch_width">35dp</dimen>
|
||||
</resources>
|
||||
@@ -0,0 +1,2 @@
|
||||
<resources>
|
||||
</resources>
|
||||
@@ -0,0 +1,33 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
|
||||
<attr name="tints" format="enum">
|
||||
<enum name="NONE" value="0x0000"/>
|
||||
<enum name="LEFT" value="0x0001"/>
|
||||
<enum name="TOP" value="0x0002"/>
|
||||
<enum name="RIGHT" value="0x0004"/>
|
||||
<enum name="BOTTOM" value="0x0008"/>
|
||||
<enum name="ALL" value="0x000f"/>
|
||||
</attr>
|
||||
|
||||
<attr name="tinColor" format="color"/>
|
||||
<declare-styleable name="TinEditText">
|
||||
<attr name="tints"/>
|
||||
<attr name="tinColor"/>
|
||||
<attr name="focusedTinColor" format="color"/>
|
||||
</declare-styleable>
|
||||
|
||||
<declare-styleable name="TinTextView">
|
||||
<attr name="tints"/>
|
||||
<attr name="tinColor"/>
|
||||
</declare-styleable>
|
||||
|
||||
<declare-styleable name="TinButton">
|
||||
<attr name="tints"/>
|
||||
<attr name="tinColor"/>
|
||||
</declare-styleable>
|
||||
|
||||
<declare-styleable name="TinImageView">
|
||||
<attr name="tinColor"/>
|
||||
</declare-styleable>
|
||||
</resources>
|
||||
+17
@@ -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 <a href="http://d.android.com/tools/testing">Testing documentation</a>
|
||||
*/
|
||||
public class ExampleUnitTest {
|
||||
@Test
|
||||
public void addition_isCorrect() {
|
||||
assertEquals(4, 2 + 2);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
/build
|
||||
@@ -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'
|
||||
}
|
||||
Whitespace-only changes.
+21
@@ -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
|
||||
+24
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="cn.qjm253.quick_android_easy_bar" />
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+89
@@ -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)
|
||||
}
|
||||
|
||||
})
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 752 B |
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="text_gray">#333333</color>
|
||||
</resources>
|
||||
@@ -0,0 +1,18 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<declare-styleable name="EasyBar">
|
||||
<attr name="title" format="string" />
|
||||
<attr name="title_size" format="dimension" />
|
||||
<attr name="title_color" format="color" />
|
||||
<attr name="left_icon" format="reference" />
|
||||
<attr name="right_icon" format="reference" />
|
||||
<attr name="icon_size" format="dimension" />
|
||||
<attr name="icon_margin" format="dimension" />
|
||||
<attr name="left_text" format="string" />
|
||||
<attr name="right_text" format="string" />
|
||||
<attr name="displayMode">
|
||||
<enum name="ICON" value="0" />
|
||||
<enum name="TEXT" value="1" />
|
||||
</attr>
|
||||
</declare-styleable>
|
||||
</resources>
|
||||
@@ -0,0 +1,2 @@
|
||||
<resources>
|
||||
</resources>
|
||||
+17
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
/build
|
||||
@@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
```
|
||||
Loaded 100 of 190 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user