add many:解决了若干bug,成功跑起来了界面。最大的一个改变是将quick_android库由云端库改为了本地库,然后注释掉了一些报错且没有用到的代码。

This commit is contained in:
free will committed 2021-06-24 19:21:29 +08:00
1 parent 392aec0de9
commit f4cec991ed
190 files changed
+9646 -33

No files matched your search

+35 -23
View File
@@ -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'
+37 -2
View File
@@ -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,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
View File
@@ -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
}
}
+3
View File
@@ -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
+1
View File
@@ -0,0 +1 @@
/build
+161
View File
@@ -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}"
```
+84
View File
@@ -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
View File
@@ -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---------------##
@@ -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
}
}
@@ -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?) {
}
}
@@ -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)
}
}
@@ -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
}
@@ -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)
}
@@ -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))
}
@@ -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)
}
@@ -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!!
}
@@ -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)
}
@@ -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)
}
@@ -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
}
@@ -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
}
@@ -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)
}
@@ -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
}
}
@@ -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)
}
}
@@ -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)
}
}
}
}
@@ -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)
}
}
@@ -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`)
}
}
@@ -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))
}
}
@@ -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()
)
}
}
@@ -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);
}
}
+1
View File
@@ -0,0 +1 @@
/build
+40
View File
@@ -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
View File
@@ -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
@@ -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"/>
@@ -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()
}
}
@@ -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()
}
}
@@ -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()
}
}
@@ -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
}
}
}
@@ -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)
}
}
@@ -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)
}
@@ -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()
}
}
@@ -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()
}
}
@@ -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
}
}
@@ -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)
}
}
}
@@ -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)
}
}
}
@@ -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)
}
}
}
}
@@ -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>
@@ -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);
}
}
+1
View File
@@ -0,0 +1 @@
/build
+38
View File
@@ -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
View File
@@ -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
@@ -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
}
}
}
}
@@ -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>
@@ -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)
}
}
+1
View File
@@ -0,0 +1 @@
/build
+67
View File
@@ -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