部分问题修复
这个提交包含在:
父节点
194d27a6e7
当前提交
d780cb9660
2
base/.gitattributes
vendored
普通文件
2
base/.gitattributes
vendored
普通文件
@ -0,0 +1,2 @@
|
|||||||
|
# Auto detect text files and perform LF normalization
|
||||||
|
* text=auto
|
||||||
83
base/.gitignore
vendored
普通文件
83
base/.gitignore
vendored
普通文件
@ -0,0 +1,83 @@
|
|||||||
|
# Built application files
|
||||||
|
*.apk
|
||||||
|
*.ap_
|
||||||
|
*.aab
|
||||||
|
|
||||||
|
# Files for the ART/Dalvik VM
|
||||||
|
*.dex
|
||||||
|
|
||||||
|
# Java class files
|
||||||
|
*.class
|
||||||
|
|
||||||
|
# Generated files
|
||||||
|
bin/
|
||||||
|
gen/
|
||||||
|
out/
|
||||||
|
# Uncomment the following line in case you need and you don't have the release build type files in your app
|
||||||
|
# release/
|
||||||
|
|
||||||
|
# Gradle files
|
||||||
|
.gradle/
|
||||||
|
build/
|
||||||
|
|
||||||
|
# Local configuration file (sdk path, etc)
|
||||||
|
local.properties
|
||||||
|
|
||||||
|
# Proguard folder generated by Eclipse
|
||||||
|
proguard/
|
||||||
|
|
||||||
|
# Log Files
|
||||||
|
*.log
|
||||||
|
|
||||||
|
# Android Studio Navigation editor temp files
|
||||||
|
.navigation/
|
||||||
|
|
||||||
|
# Android Studio captures folder
|
||||||
|
captures/
|
||||||
|
|
||||||
|
# IntelliJ
|
||||||
|
*.iml
|
||||||
|
.idea/workspace.xml
|
||||||
|
.idea/tasks.xml
|
||||||
|
.idea/gradle.xml
|
||||||
|
.idea/assetWizardSettings.xml
|
||||||
|
.idea/dictionaries
|
||||||
|
.idea/libraries
|
||||||
|
# Android Studio 3 in .gitignore file.
|
||||||
|
.idea/caches
|
||||||
|
.idea/modules.xml
|
||||||
|
# Comment next line if keeping position of elements in Navigation Editor is relevant for you
|
||||||
|
.idea/navEditor.xml
|
||||||
|
|
||||||
|
# Keystore files
|
||||||
|
# Uncomment the following lines if you do not want to check your keystore files in.
|
||||||
|
#*.jks
|
||||||
|
#*.keystore
|
||||||
|
|
||||||
|
# External native build folder generated in Android Studio 2.2 and later
|
||||||
|
.externalNativeBuild
|
||||||
|
|
||||||
|
# Google Services (e.g. APIs or Firebase)
|
||||||
|
# google-services.json
|
||||||
|
|
||||||
|
# Freeline
|
||||||
|
freeline.py
|
||||||
|
freeline/
|
||||||
|
freeline_project_description.json
|
||||||
|
|
||||||
|
# fastlane
|
||||||
|
fastlane/report.xml
|
||||||
|
fastlane/Preview.html
|
||||||
|
fastlane/screenshots
|
||||||
|
fastlane/test_output
|
||||||
|
fastlane/readme.md
|
||||||
|
|
||||||
|
# Version control
|
||||||
|
vcs.xml
|
||||||
|
|
||||||
|
# lint
|
||||||
|
lint/intermediates/
|
||||||
|
lint/generated/
|
||||||
|
lint/outputs/
|
||||||
|
lint/tmp/
|
||||||
|
# lint/reports/
|
||||||
184
base/README.md
普通文件
184
base/README.md
普通文件
@ -0,0 +1,184 @@
|
|||||||
|
[TOC]
|
||||||
|
|
||||||
|
|
||||||
|
# WebSocket
|
||||||
|
|
||||||
|
``````kotlin
|
||||||
|
WebSocketHandler.getInstance("ws://192.168.3.20:8765")
|
||||||
|
``````
|
||||||
|
|
||||||
|
# 线程
|
||||||
|
|
||||||
|
## UI线程执行
|
||||||
|
|
||||||
|
````kotlin
|
||||||
|
runOnUiThread { "提示信息".showMessage() }
|
||||||
|
````
|
||||||
|
|
||||||
|
````kotlin
|
||||||
|
App.getInstance().runOnUiThread() {}
|
||||||
|
````
|
||||||
|
|
||||||
|
## 延时执行
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
App.getInstance().runOnUiThreadDelay({},1100)
|
||||||
|
```
|
||||||
|
|
||||||
|
# 常用工具
|
||||||
|
|
||||||
|
## Toast
|
||||||
|
|
||||||
|
````kotlin
|
||||||
|
"连接完成".showMessage()
|
||||||
|
````
|
||||||
|
|
||||||
|
````kotlin
|
||||||
|
ToolsHelper.showMessage("")
|
||||||
|
````
|
||||||
|
|
||||||
|
## Log
|
||||||
|
|
||||||
|
````kotlin
|
||||||
|
"".loge()
|
||||||
|
````
|
||||||
|
|
||||||
|
````kotlin
|
||||||
|
"".log()
|
||||||
|
````
|
||||||
|
|
||||||
|
````kotlin
|
||||||
|
LogHelper.d("")
|
||||||
|
````
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# 常用方法
|
||||||
|
|
||||||
|
## 双击退出
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
|
||||||
|
private var oldTime = 0L
|
||||||
|
override fun onBackPressed() {
|
||||||
|
val newTime = System.currentTimeMillis()
|
||||||
|
if (newTime - oldTime < 1500 && oldTime != 0L)
|
||||||
|
AppManager.getInstance().exit()
|
||||||
|
else {
|
||||||
|
oldTime = newTime
|
||||||
|
ToolsHelper.showMessage("双击退出")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# 界面
|
||||||
|
|
||||||
|
> 所有界面继承`BaseFragment`,`BaseActivity`,`BaseListActivity`等
|
||||||
|
>
|
||||||
|
> 页面`layout`跟节点必须为`layout`
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<layout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||||
|
</layout>
|
||||||
|
```
|
||||||
|
|
||||||
|
## 列表页面
|
||||||
|
|
||||||
|
### 纯列表
|
||||||
|
|
||||||
|
> `BaseListActivity`
|
||||||
|
|
||||||
|
### 自定义布局列表
|
||||||
|
|
||||||
|
> `BaseListFormLayoutActivity`
|
||||||
|
>
|
||||||
|
> 布局列表部分必须使用下面的方法和id
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
||||||
|
android:id="@+id/baseRefreshLayout"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<com.xuqm.base.view.EmptyView
|
||||||
|
android:id="@+id/baseEmptyView"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
|
android:id="@+id/baseRecyclerView"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:overScrollMode="never" />
|
||||||
|
|
||||||
|
</com.xuqm.base.view.EmptyView>
|
||||||
|
|
||||||
|
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
|
||||||
|
```
|
||||||
|
|
||||||
|
## 界面控件使用
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
binding.btn1.setOnClickListener {
|
||||||
|
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 导航栏
|
||||||
|
|
||||||
|
> 使用base自带导航栏的情况下,可以操控对应控件
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
baseBinding.baseToolbar.backBtn.setOnClickListener {}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
94
base/build.gradle
普通文件
94
base/build.gradle
普通文件
@ -0,0 +1,94 @@
|
|||||||
|
apply plugin: 'com.android.library'
|
||||||
|
apply plugin: 'kotlin-android'
|
||||||
|
apply plugin: 'maven-publish'
|
||||||
|
|
||||||
|
// 声明aar包的版本号
|
||||||
|
def aarVersion = "0.0.1.101"
|
||||||
|
|
||||||
|
|
||||||
|
android {
|
||||||
|
compileSdkVersion versions.compileSdk
|
||||||
|
buildToolsVersion versions.buildTools
|
||||||
|
|
||||||
|
defaultConfig {
|
||||||
|
minSdkVersion versions.minSdk
|
||||||
|
targetSdkVersion versions.targetSdk
|
||||||
|
flavorDimensions "versioncode"
|
||||||
|
buildConfigField("String", "APP_ID", "\"" + apps.applicationId + "\"")
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
buildTypes {
|
||||||
|
release {
|
||||||
|
minifyEnabled false
|
||||||
|
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
productFlavors productF
|
||||||
|
namespace 'com.xuqm.base'
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
||||||
|
|
||||||
|
api androidxDependencies
|
||||||
|
implementation 'androidx.appcompat:appcompat:1.3.0'
|
||||||
|
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
|
||||||
|
implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
|
||||||
|
|
||||||
|
|
||||||
|
implementation 'org.jetbrains.anko:anko-commons:0.10.5'
|
||||||
|
|
||||||
|
annotationProcessor compilerDependencies
|
||||||
|
commonDependencies.each { k, v ->
|
||||||
|
api(v) {
|
||||||
|
//去除第三方的重复support库
|
||||||
|
exclude group: 'com.android.support'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//扫码二维码
|
||||||
|
api 'com.huawei.hms:scanplus:1.1.1.301'
|
||||||
|
}
|
||||||
|
// 这个是把源码打入aar包中的任务
|
||||||
|
task sourceJar(type: Jar) {
|
||||||
|
archiveClassifier.set('sources')
|
||||||
|
from android.sourceSets.main.java.srcDirs
|
||||||
|
}
|
||||||
|
afterEvaluate {
|
||||||
|
publishing {
|
||||||
|
publications {
|
||||||
|
// 这里的debug名字是自己起的
|
||||||
|
release(MavenPublication) {
|
||||||
|
groupId = 'cn.org.bjca.trust.android'
|
||||||
|
artifactId = 'base'
|
||||||
|
version = aarVersion
|
||||||
|
// 这里除了有debug 还有release
|
||||||
|
from components.release
|
||||||
|
// 运行任务,把源码打进去
|
||||||
|
artifact sourceJar
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 添加仓库地址
|
||||||
|
repositories {
|
||||||
|
// 本地仓库
|
||||||
|
// mavenLocal()
|
||||||
|
// 当上传到远端仓库
|
||||||
|
// maven {
|
||||||
|
// allowInsecureProtocol true
|
||||||
|
// url("http://nexus.51trust.net/repository/android-hosted/")
|
||||||
|
// credentials {
|
||||||
|
// username = "deployment"
|
||||||
|
// password = "deployment123"
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
maven {
|
||||||
|
allowInsecureProtocol true
|
||||||
|
url("http://xuqinmin.com.cn:8081/repository/android-releases/")
|
||||||
|
credentials {
|
||||||
|
username = "admin"
|
||||||
|
password = "xuqinmin1022"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
0
base/consumer-rules.pro
普通文件
0
base/consumer-rules.pro
普通文件
21
base/proguard-rules.pro
vendored
普通文件
21
base/proguard-rules.pro
vendored
普通文件
@ -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 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<!-- 7.0之后安装apk、需要权限 -->
|
||||||
|
<uses-permission android:name="android.permission.READ_PRIVILEGED_PHONE_STATE" />
|
||||||
|
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
|
||||||
|
|
||||||
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
|
<uses-permission android:name="android.webkit.PermissionRequest" />
|
||||||
|
<uses-permission android:name="android.permission.ACCESS_NETWORK_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.WRITE_EXTERNAL_STORAGE" />
|
||||||
|
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
|
||||||
|
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
|
||||||
|
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
|
||||||
|
<uses-permission android:name="android.permission.CAMERA" />
|
||||||
|
<uses-permission android:name="android.permission.RECORD_AUDIO" />
|
||||||
|
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
|
||||||
|
|
||||||
|
<uses-permission android:name="android.webkit.resource.VIDEO_CAPTURE" />
|
||||||
|
|
||||||
|
<application>
|
||||||
|
<activity android:name=".web.XWebViewActivity" />
|
||||||
|
</application>
|
||||||
|
|
||||||
|
</manifest>
|
||||||
@ -0,0 +1,155 @@
|
|||||||
|
package com.livinglifetechway.quickpermissions_kotlin
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.util.Log
|
||||||
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
import androidx.fragment.app.Fragment
|
||||||
|
import com.livinglifetechway.quickpermissions_kotlin.util.PermissionCheckerFragment
|
||||||
|
import com.livinglifetechway.quickpermissions_kotlin.util.PermissionsUtil
|
||||||
|
import com.livinglifetechway.quickpermissions_kotlin.util.QuickPermissionsRequest
|
||||||
|
import com.livinglifetechway.quickpermissions_kotlin.util.QuickPermissionsOptions
|
||||||
|
|
||||||
|
private const val TAG = "runWithPermissions"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Injects code to ask for permissions before executing any code that requires permissions
|
||||||
|
* defined in the annotation
|
||||||
|
*/
|
||||||
|
fun Context?.runWithPermissions(
|
||||||
|
vararg permissions: String,
|
||||||
|
options: QuickPermissionsOptions = QuickPermissionsOptions(),
|
||||||
|
callback: () -> Unit
|
||||||
|
): Any? {
|
||||||
|
return runWithPermissionsHandler(this, permissions, callback, options)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Injects code to ask for permissions before executing any code that requires permissions
|
||||||
|
* defined in the annotation
|
||||||
|
*/
|
||||||
|
fun Fragment?.runWithPermissions(
|
||||||
|
vararg permissions: String,
|
||||||
|
options: QuickPermissionsOptions = QuickPermissionsOptions(),
|
||||||
|
callback: () -> Unit
|
||||||
|
): Any? {
|
||||||
|
return runWithPermissionsHandler(this, permissions, callback, options)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun runWithPermissionsHandler(target: Any?, permissions: Array<out String>, callback: () -> Unit, options: QuickPermissionsOptions): Nothing? {
|
||||||
|
Log.d(TAG, "runWithPermissions: start")
|
||||||
|
|
||||||
|
// get the permissions defined in annotation
|
||||||
|
Log.d(TAG, "runWithPermissions: permissions to check: $permissions")
|
||||||
|
|
||||||
|
// get target
|
||||||
|
if (target is AppCompatActivity || target is Fragment) {
|
||||||
|
Log.d(TAG, "runWithPermissions: context found")
|
||||||
|
|
||||||
|
val context = when (target) {
|
||||||
|
is Context -> target
|
||||||
|
is Fragment -> target.context
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if we have the permissions
|
||||||
|
if (PermissionsUtil.hasSelfPermission(context, arrayOf(*permissions))) {
|
||||||
|
Log.d(TAG, "runWithPermissions: already has required permissions. Proceed with the execution.")
|
||||||
|
callback()
|
||||||
|
} else {
|
||||||
|
// we don't have required permissions
|
||||||
|
// begin the permission request flow
|
||||||
|
|
||||||
|
Log.d(TAG, "runWithPermissions: doesn't have required permissions")
|
||||||
|
|
||||||
|
// check if we have permission checker fragment already attached
|
||||||
|
|
||||||
|
// support for AppCompatActivity and Activity
|
||||||
|
var permissionCheckerFragment = when (context) {
|
||||||
|
// for app compat activity
|
||||||
|
is AppCompatActivity -> context.supportFragmentManager?.findFragmentByTag(PermissionCheckerFragment::class.java.canonicalName) as PermissionCheckerFragment?
|
||||||
|
// for support fragment
|
||||||
|
is Fragment -> context.childFragmentManager.findFragmentByTag(PermissionCheckerFragment::class.java.canonicalName) as PermissionCheckerFragment?
|
||||||
|
// else return null
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if permission check fragment is added or not
|
||||||
|
// if not, add that fragment
|
||||||
|
if (permissionCheckerFragment == null) {
|
||||||
|
Log.d(TAG, "runWithPermissions: adding headless fragment for asking permissions")
|
||||||
|
permissionCheckerFragment = PermissionCheckerFragment.newInstance()
|
||||||
|
when (context) {
|
||||||
|
is AppCompatActivity -> {
|
||||||
|
context.supportFragmentManager.beginTransaction().apply {
|
||||||
|
add(permissionCheckerFragment, PermissionCheckerFragment::class.java.canonicalName)
|
||||||
|
commit()
|
||||||
|
}
|
||||||
|
// make sure fragment is added before we do any context based operations
|
||||||
|
context.supportFragmentManager?.executePendingTransactions()
|
||||||
|
}
|
||||||
|
is Fragment -> {
|
||||||
|
// this does not work at the moment
|
||||||
|
context.childFragmentManager.beginTransaction().apply {
|
||||||
|
add(permissionCheckerFragment, PermissionCheckerFragment::class.java.canonicalName)
|
||||||
|
commit()
|
||||||
|
}
|
||||||
|
// make sure fragment is added before we do any context based operations
|
||||||
|
context.childFragmentManager.executePendingTransactions()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// set callback to permission checker fragment
|
||||||
|
permissionCheckerFragment.setListener(object : PermissionCheckerFragment.QuickPermissionsCallback {
|
||||||
|
override fun onPermissionsGranted(quickPermissionsRequest: QuickPermissionsRequest?) {
|
||||||
|
Log.d(TAG, "runWithPermissions: got permissions")
|
||||||
|
try {
|
||||||
|
callback()
|
||||||
|
} catch (throwable: Throwable) {
|
||||||
|
throwable.printStackTrace()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onPermissionsDenied(quickPermissionsRequest: QuickPermissionsRequest?) {
|
||||||
|
quickPermissionsRequest?.permissionsDeniedMethod?.invoke(quickPermissionsRequest)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun shouldShowRequestPermissionsRationale(quickPermissionsRequest: QuickPermissionsRequest?) {
|
||||||
|
quickPermissionsRequest?.rationaleMethod?.invoke(quickPermissionsRequest)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onPermissionsPermanentlyDenied(quickPermissionsRequest: QuickPermissionsRequest?) {
|
||||||
|
quickPermissionsRequest?.permanentDeniedMethod?.invoke(quickPermissionsRequest)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// create permission request instance
|
||||||
|
val permissionRequest = QuickPermissionsRequest(permissionCheckerFragment, arrayOf(*permissions))
|
||||||
|
permissionRequest.handleRationale = options.handleRationale
|
||||||
|
permissionRequest.handlePermanentlyDenied = options.handlePermanentlyDenied
|
||||||
|
permissionRequest.rationaleMessage = if (options.rationaleMessage.isBlank())
|
||||||
|
"These permissions are required to perform this feature. Please allow us to use this feature. "
|
||||||
|
else
|
||||||
|
options.rationaleMessage
|
||||||
|
permissionRequest.permanentlyDeniedMessage = if (options.permanentlyDeniedMessage.isBlank())
|
||||||
|
"Some permissions are permanently denied which are required to perform this operation. Please open app settings to grant these permissions."
|
||||||
|
else
|
||||||
|
options.permanentlyDeniedMessage
|
||||||
|
permissionRequest.rationaleMethod = options.rationaleMethod
|
||||||
|
permissionRequest.permanentDeniedMethod = options.permanentDeniedMethod
|
||||||
|
permissionRequest.permissionsDeniedMethod = options.permissionsDeniedMethod
|
||||||
|
|
||||||
|
// begin the flow by requesting permissions
|
||||||
|
permissionCheckerFragment.setRequestPermissionsRequest(permissionRequest)
|
||||||
|
|
||||||
|
// start requesting permissions for the first time
|
||||||
|
permissionCheckerFragment.requestPermissionsFromUser()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// context is null
|
||||||
|
// cannot handle the permission checking from the any class other than AppCompatActivity/Fragment
|
||||||
|
// crash the app RIGHT NOW!
|
||||||
|
throw IllegalStateException("Found " + target!!::class.java.canonicalName + " : No support from any classes other than AppCompatActivity/Fragment")
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
@ -0,0 +1,237 @@
|
|||||||
|
package com.livinglifetechway.quickpermissions_kotlin.util
|
||||||
|
|
||||||
|
|
||||||
|
import android.content.Intent
|
||||||
|
import android.content.pm.PackageManager
|
||||||
|
import android.net.Uri.fromParts
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.provider.Settings.ACTION_APPLICATION_DETAILS_SETTINGS
|
||||||
|
import android.util.Log
|
||||||
|
import androidx.core.app.ActivityCompat
|
||||||
|
import androidx.fragment.app.Fragment
|
||||||
|
import org.jetbrains.anko.alert
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This fragment holds the single permission request and holds it until the flow is completed
|
||||||
|
*/
|
||||||
|
class PermissionCheckerFragment : Fragment() {
|
||||||
|
|
||||||
|
private var quickPermissionsRequest: QuickPermissionsRequest? = null
|
||||||
|
|
||||||
|
interface QuickPermissionsCallback {
|
||||||
|
fun shouldShowRequestPermissionsRationale(quickPermissionsRequest: QuickPermissionsRequest?)
|
||||||
|
fun onPermissionsGranted(quickPermissionsRequest: QuickPermissionsRequest?)
|
||||||
|
fun onPermissionsPermanentlyDenied(quickPermissionsRequest: QuickPermissionsRequest?)
|
||||||
|
fun onPermissionsDenied(quickPermissionsRequest: QuickPermissionsRequest?)
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val TAG = "QuickPermissionsKotlin"
|
||||||
|
private const val PERMISSIONS_REQUEST_CODE = 199
|
||||||
|
fun newInstance(): PermissionCheckerFragment = PermissionCheckerFragment()
|
||||||
|
}
|
||||||
|
|
||||||
|
private var mListener: QuickPermissionsCallback? = null
|
||||||
|
|
||||||
|
fun setListener(listener: QuickPermissionsCallback) {
|
||||||
|
mListener = listener
|
||||||
|
Log.d(TAG, "onCreate: listeners set")
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun removeListener() {
|
||||||
|
mListener = null
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
|
Log.d(TAG, "onCreate: permission fragment created")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setRequestPermissionsRequest(quickPermissionsRequest: QuickPermissionsRequest?) {
|
||||||
|
this.quickPermissionsRequest = quickPermissionsRequest
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun removeRequestPermissionsRequest() {
|
||||||
|
quickPermissionsRequest = null
|
||||||
|
}
|
||||||
|
|
||||||
|
fun clean() {
|
||||||
|
if (quickPermissionsRequest != null) {
|
||||||
|
// permission request flow is finishing
|
||||||
|
// let the caller receive callback about it
|
||||||
|
if (quickPermissionsRequest?.deniedPermissions?.size ?: 0 > 0)
|
||||||
|
mListener?.onPermissionsDenied(quickPermissionsRequest)
|
||||||
|
|
||||||
|
removeRequestPermissionsRequest()
|
||||||
|
removeListener()
|
||||||
|
} else {
|
||||||
|
Log.w(
|
||||||
|
TAG, "clean: QuickPermissionsRequest has already completed its flow. " +
|
||||||
|
"No further callbacks will be called for the current flow."
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun requestPermissionsFromUser() {
|
||||||
|
if (quickPermissionsRequest != null) {
|
||||||
|
Log.d(TAG, "requestPermissionsFromUser: requesting permissions")
|
||||||
|
requestPermissions(
|
||||||
|
quickPermissionsRequest?.permissions.orEmpty(),
|
||||||
|
PERMISSIONS_REQUEST_CODE
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
Log.w(
|
||||||
|
TAG,
|
||||||
|
"requestPermissionsFromUser: QuickPermissionsRequest has already completed its flow. " +
|
||||||
|
"Cannot request permissions again from the request received from the callback. " +
|
||||||
|
"You can start the new flow by calling runWithPermissions() { } again."
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onRequestPermissionsResult(
|
||||||
|
requestCode: Int,
|
||||||
|
permissions: Array<String>,
|
||||||
|
grantResults: IntArray
|
||||||
|
) {
|
||||||
|
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
|
||||||
|
Log.d(TAG, "passing callback")
|
||||||
|
|
||||||
|
// check if permissions granted
|
||||||
|
handlePermissionResult(permissions, grantResults)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks and takes the action based on permission results retrieved from onRequestPermissionsResult
|
||||||
|
* and from the settings activity
|
||||||
|
*
|
||||||
|
* @param permissions List of Permissions
|
||||||
|
* @param grantResults A list of permission result <b>Granted</b> or <b>Denied</b>
|
||||||
|
*/
|
||||||
|
private fun handlePermissionResult(permissions: Array<String>, grantResults: IntArray) {
|
||||||
|
// add a check with the permissions list
|
||||||
|
// if the permissions list is empty, that means system has told that permissions request
|
||||||
|
// is invalid somehow or discarded the previous request
|
||||||
|
// this can happen in case when the multiple permissions requests are sent
|
||||||
|
// simultaneously to the system
|
||||||
|
if (permissions.isEmpty()) {
|
||||||
|
Log.w(
|
||||||
|
TAG,
|
||||||
|
"handlePermissionResult: Permissions result discarded. You might have called multiple permissions request simultaneously"
|
||||||
|
)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (PermissionsUtil.hasSelfPermission(context, permissions)) {
|
||||||
|
|
||||||
|
// set the denied permissions to empty as all the permissions are granted
|
||||||
|
// this is required as clean will be called which can invoke on permissions denied
|
||||||
|
// if it finds some permissions in the denied list
|
||||||
|
quickPermissionsRequest?.deniedPermissions = emptyArray()
|
||||||
|
|
||||||
|
// we are good to go!
|
||||||
|
mListener?.onPermissionsGranted(quickPermissionsRequest)
|
||||||
|
|
||||||
|
// flow complete
|
||||||
|
clean()
|
||||||
|
} else {
|
||||||
|
// we are still missing permissions
|
||||||
|
val deniedPermissions = PermissionsUtil.getDeniedPermissions(permissions, grantResults)
|
||||||
|
quickPermissionsRequest?.deniedPermissions = deniedPermissions
|
||||||
|
|
||||||
|
// check if rationale dialog should be shown or not
|
||||||
|
var shouldShowRationale = true
|
||||||
|
var isPermanentlyDenied = false
|
||||||
|
for (i in 0 until deniedPermissions.size) {
|
||||||
|
val deniedPermission = deniedPermissions[i]
|
||||||
|
val rationale = shouldShowRequestPermissionRationale(deniedPermission)
|
||||||
|
if (!rationale) {
|
||||||
|
shouldShowRationale = false
|
||||||
|
isPermanentlyDenied = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (quickPermissionsRequest?.handlePermanentlyDenied == true && isPermanentlyDenied) {
|
||||||
|
|
||||||
|
quickPermissionsRequest?.permanentDeniedMethod?.let {
|
||||||
|
// get list of permanently denied methods
|
||||||
|
quickPermissionsRequest?.permanentlyDeniedPermissions =
|
||||||
|
PermissionsUtil.getPermanentlyDeniedPermissions(
|
||||||
|
this,
|
||||||
|
permissions,
|
||||||
|
grantResults
|
||||||
|
)
|
||||||
|
mListener?.onPermissionsPermanentlyDenied(quickPermissionsRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
activity?.alert {
|
||||||
|
message = quickPermissionsRequest?.permanentlyDeniedMessage.orEmpty()
|
||||||
|
positiveButton("SETTINGS") {
|
||||||
|
openAppSettings()
|
||||||
|
}
|
||||||
|
negativeButton("CANCEL") {
|
||||||
|
clean()
|
||||||
|
}
|
||||||
|
}?.apply { isCancelable = false }?.show()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// if should show rationale dialog
|
||||||
|
if (quickPermissionsRequest?.handleRationale == true && shouldShowRationale) {
|
||||||
|
|
||||||
|
quickPermissionsRequest?.rationaleMethod?.let {
|
||||||
|
mListener?.shouldShowRequestPermissionsRationale(quickPermissionsRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
activity?.alert {
|
||||||
|
message = quickPermissionsRequest?.rationaleMessage.orEmpty()
|
||||||
|
positiveButton("TRY AGAIN") {
|
||||||
|
requestPermissionsFromUser()
|
||||||
|
}
|
||||||
|
negativeButton("CANCEL") {
|
||||||
|
clean()
|
||||||
|
}
|
||||||
|
}?.apply { isCancelable = false }?.show()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// if handlePermanentlyDenied = false and handleRationale = false
|
||||||
|
// This will call permissionsDenied method
|
||||||
|
clean()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun openAppSettings() {
|
||||||
|
if (quickPermissionsRequest != null) {
|
||||||
|
val intent = Intent(
|
||||||
|
ACTION_APPLICATION_DETAILS_SETTINGS,
|
||||||
|
fromParts("package", activity?.packageName, null)
|
||||||
|
)
|
||||||
|
// intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||||
|
startActivityForResult(intent, PERMISSIONS_REQUEST_CODE)
|
||||||
|
} else {
|
||||||
|
Log.w(
|
||||||
|
TAG,
|
||||||
|
"openAppSettings: QuickPermissionsRequest has already completed its flow. Cannot open app settings"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||||
|
super.onActivityResult(requestCode, resultCode, data)
|
||||||
|
if (requestCode == PERMISSIONS_REQUEST_CODE) {
|
||||||
|
val permissions = quickPermissionsRequest?.permissions ?: emptyArray()
|
||||||
|
val grantResults = IntArray(permissions.size)
|
||||||
|
permissions.forEachIndexed { index, s ->
|
||||||
|
grantResults[index] = context?.let { ActivityCompat.checkSelfPermission(it, s) }
|
||||||
|
?: PackageManager.PERMISSION_DENIED
|
||||||
|
}
|
||||||
|
|
||||||
|
handlePermissionResult(permissions, grantResults)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,54 @@
|
|||||||
|
package com.livinglifetechway.quickpermissions_kotlin.util
|
||||||
|
|
||||||
|
import android.app.Activity
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.pm.PackageManager
|
||||||
|
import androidx.core.app.ActivityCompat
|
||||||
|
import androidx.fragment.app.Fragment
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utility class that wraps access to the runtime permissions API in M and provides basic helper
|
||||||
|
* methods.
|
||||||
|
*/
|
||||||
|
object PermissionsUtil {
|
||||||
|
|
||||||
|
fun getDeniedPermissions(permissions: Array<String>, grantResults: IntArray): Array<String> =
|
||||||
|
permissions.filterIndexed { index, s ->
|
||||||
|
grantResults[index] == PackageManager.PERMISSION_DENIED
|
||||||
|
}.toTypedArray()
|
||||||
|
|
||||||
|
fun getPermanentlyDeniedPermissions(
|
||||||
|
fragment: Fragment,
|
||||||
|
permissions: Array<String>,
|
||||||
|
grantResults: IntArray
|
||||||
|
): Array<String> =
|
||||||
|
permissions.filterIndexed { index, s ->
|
||||||
|
grantResults[index] == PackageManager.PERMISSION_DENIED && !fragment.shouldShowRequestPermissionRationale(
|
||||||
|
s
|
||||||
|
)
|
||||||
|
}.toTypedArray()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if the Activity has access to all given permissions.
|
||||||
|
* Always returns true on platforms below M.
|
||||||
|
*
|
||||||
|
* @see Activity.checkSelfPermission
|
||||||
|
*/
|
||||||
|
fun hasSelfPermission(activity: Context?, permissions: Array<String>): Boolean {
|
||||||
|
// Verify that all required permissions have been granted
|
||||||
|
activity?.let {
|
||||||
|
for (permission in permissions) {
|
||||||
|
if (ActivityCompat.checkSelfPermission(
|
||||||
|
activity,
|
||||||
|
permission
|
||||||
|
) != PackageManager.PERMISSION_GRANTED
|
||||||
|
) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,11 @@
|
|||||||
|
package com.livinglifetechway.quickpermissions_kotlin.util
|
||||||
|
|
||||||
|
data class QuickPermissionsOptions(
|
||||||
|
var handleRationale: Boolean = true,
|
||||||
|
var rationaleMessage: String = "",
|
||||||
|
var handlePermanentlyDenied: Boolean = true,
|
||||||
|
var permanentlyDeniedMessage: String = "",
|
||||||
|
var rationaleMethod: ((QuickPermissionsRequest) -> Unit)? = null,
|
||||||
|
var permanentDeniedMethod: ((QuickPermissionsRequest) -> Unit)? = null,
|
||||||
|
var permissionsDeniedMethod: ((QuickPermissionsRequest) -> Unit)? = null
|
||||||
|
)
|
||||||
@ -0,0 +1,30 @@
|
|||||||
|
package com.livinglifetechway.quickpermissions_kotlin.util
|
||||||
|
|
||||||
|
data class QuickPermissionsRequest(
|
||||||
|
private var target: PermissionCheckerFragment,
|
||||||
|
var permissions: Array<String> = emptyArray(),
|
||||||
|
var handleRationale: Boolean = true,
|
||||||
|
var rationaleMessage: String = "",
|
||||||
|
var handlePermanentlyDenied: Boolean = true,
|
||||||
|
var permanentlyDeniedMessage: String = "",
|
||||||
|
internal var rationaleMethod: ((QuickPermissionsRequest) -> Unit)? = null,
|
||||||
|
internal var permanentDeniedMethod: ((QuickPermissionsRequest) -> Unit)? = null,
|
||||||
|
internal var permissionsDeniedMethod: ((QuickPermissionsRequest) -> Unit)? = null,
|
||||||
|
var deniedPermissions: Array<String> = emptyArray(),
|
||||||
|
var permanentlyDeniedPermissions: Array<String> = emptyArray()
|
||||||
|
) {
|
||||||
|
/**
|
||||||
|
* Proceed with requesting permissions again with user request
|
||||||
|
*/
|
||||||
|
fun proceed() = target.requestPermissionsFromUser()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cancels the current permissions request flow
|
||||||
|
*/
|
||||||
|
fun cancel() = target.clean()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* In case of permissions permanently denied, request user to enable from app settings
|
||||||
|
*/
|
||||||
|
fun openAppSettings() = target.openAppSettings()
|
||||||
|
}
|
||||||
@ -0,0 +1,111 @@
|
|||||||
|
package com.xuqm.base;
|
||||||
|
|
||||||
|
import android.app.Application;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.os.Handler;
|
||||||
|
import android.util.DisplayMetrics;
|
||||||
|
import android.view.WindowManager;
|
||||||
|
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
import com.orhanobut.logger.AndroidLogAdapter;
|
||||||
|
import com.orhanobut.logger.FormatStrategy;
|
||||||
|
import com.orhanobut.logger.Logger;
|
||||||
|
import com.orhanobut.logger.PrettyFormatStrategy;
|
||||||
|
import com.xuqm.base.di.component.AppComponent;
|
||||||
|
import com.xuqm.base.di.manager.HttpManager;
|
||||||
|
|
||||||
|
public class App extends Application {
|
||||||
|
|
||||||
|
public AppComponent appComponent;
|
||||||
|
// 宽高
|
||||||
|
private int width = 0, height = 0;
|
||||||
|
|
||||||
|
|
||||||
|
private static App instance;
|
||||||
|
|
||||||
|
public static App getInstance() {
|
||||||
|
if (null == instance) {
|
||||||
|
synchronized (App.class) {
|
||||||
|
if (null == instance)
|
||||||
|
instance = new App();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
public App() {
|
||||||
|
instance = this;
|
||||||
|
handler = new Handler();
|
||||||
|
appComponent = HttpManager.getAppComponent("");
|
||||||
|
}
|
||||||
|
|
||||||
|
//https://www.wanandroid.com/wxarticle/list/408/1/json
|
||||||
|
@Override
|
||||||
|
public void onCreate() {
|
||||||
|
super.onCreate();
|
||||||
|
DisplayMetrics dm = new DisplayMetrics();
|
||||||
|
WindowManager wm = (WindowManager) this.getSystemService(Context.WINDOW_SERVICE);
|
||||||
|
if (null != wm) {
|
||||||
|
wm.getDefaultDisplay().getMetrics(dm);
|
||||||
|
width = dm.widthPixels;// 屏幕宽度
|
||||||
|
height = dm.heightPixels;// 屏幕高度
|
||||||
|
}
|
||||||
|
|
||||||
|
FormatStrategy formatStrategy = PrettyFormatStrategy.newBuilder()
|
||||||
|
.showThreadInfo(false) // (Optional) Whether to show thread info or not. Default true
|
||||||
|
.methodCount(0) // (Optional) How many method line to show. Default 2
|
||||||
|
.methodOffset(2) // (Optional) Hides internal method calls up to offset. Default 5
|
||||||
|
//.logStrategy(customLog) // (Optional) Changes the log strategy to print out. Default LogCat
|
||||||
|
.tag("LogHttpInfo") // (Optional) Global tag for every log. Default PRETTY_LOGGER
|
||||||
|
.build();
|
||||||
|
|
||||||
|
Logger.addLogAdapter(new AndroidLogAdapter(formatStrategy) {
|
||||||
|
@Override
|
||||||
|
public boolean isLoggable(int priority, @Nullable String tag) {
|
||||||
|
return showLog();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否打印日志
|
||||||
|
*
|
||||||
|
* @return true-开启日志
|
||||||
|
*/
|
||||||
|
public boolean showLog() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getHeight() {
|
||||||
|
return height;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getWidth() {
|
||||||
|
return width;
|
||||||
|
}
|
||||||
|
|
||||||
|
private final Handler handler;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 提交主线程处理
|
||||||
|
*
|
||||||
|
* @param runnable runnable
|
||||||
|
*/
|
||||||
|
public void runOnUiThread(final Runnable runnable) {
|
||||||
|
handler.post(runnable);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 提交主线程,延时后处理
|
||||||
|
*
|
||||||
|
* @param runnable runnable
|
||||||
|
* @param delayMillis 延时时间
|
||||||
|
*/
|
||||||
|
public void runOnUiThreadDelay(final Runnable runnable, long delayMillis) {
|
||||||
|
handler.postDelayed(runnable, delayMillis);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,222 @@
|
|||||||
|
package com.xuqm.base;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.pm.PackageInfo;
|
||||||
|
import android.content.pm.PackageManager;
|
||||||
|
import android.content.pm.PackageManager.NameNotFoundException;
|
||||||
|
import android.os.Build;
|
||||||
|
import android.os.Environment;
|
||||||
|
import android.os.Looper;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
import com.xuqm.base.common.FileHelper;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.PrintWriter;
|
||||||
|
import java.io.StringWriter;
|
||||||
|
import java.io.Writer;
|
||||||
|
import java.lang.Thread.UncaughtExceptionHandler;
|
||||||
|
import java.lang.reflect.Field;
|
||||||
|
import java.text.DateFormat;
|
||||||
|
import java.text.SimpleDateFormat;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* UncaughtException处理类,当程序发生Uncaught异常的时候,有该类来接管程序,并记录发送错误报告.
|
||||||
|
*
|
||||||
|
* @author user
|
||||||
|
*/
|
||||||
|
public class CrashHandler implements UncaughtExceptionHandler {
|
||||||
|
|
||||||
|
public static final String TAG = "CrashHandler";
|
||||||
|
|
||||||
|
// CrashHandler 实例
|
||||||
|
private static CrashHandler INSTANCE = new CrashHandler();
|
||||||
|
|
||||||
|
// 程序的 Context 对象
|
||||||
|
private Context mContext;
|
||||||
|
|
||||||
|
// 系统默认的 UncaughtException 处理类
|
||||||
|
private UncaughtExceptionHandler mDefaultHandler;
|
||||||
|
|
||||||
|
// 用来存储设备信息和异常信息
|
||||||
|
private Map<String, String> infos = new HashMap<String, String>();
|
||||||
|
|
||||||
|
// 用于格式化日期,作为日志文件名的一部分
|
||||||
|
private DateFormat formatter = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss", Locale.getDefault());
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 保证只有一个 CrashHandler 实例
|
||||||
|
*/
|
||||||
|
private CrashHandler() {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取 CrashHandler 实例 ,单例模式
|
||||||
|
*/
|
||||||
|
public static CrashHandler getInstance() {
|
||||||
|
return INSTANCE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始化
|
||||||
|
*
|
||||||
|
* @param context
|
||||||
|
*/
|
||||||
|
public void init(Context context) {
|
||||||
|
mContext = context;
|
||||||
|
// 获取系统默认的 UncaughtException 处理器
|
||||||
|
mDefaultHandler = Thread.getDefaultUncaughtExceptionHandler();
|
||||||
|
// 设置该 CrashHandler 为程序的默认处理器
|
||||||
|
Thread.setDefaultUncaughtExceptionHandler(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 当 UncaughtException 发生时会转入该函数来处理
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void uncaughtException(Thread thread, Throwable ex) {
|
||||||
|
if (!handleException(ex) && mDefaultHandler != null) {
|
||||||
|
// 如果用户没有处理则让系统默认的异常处理器来处理
|
||||||
|
mDefaultHandler.uncaughtException(thread, ex);
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
Thread.sleep(2000);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
Log.e(TAG, "error : ", e);
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Intent mIntent = new Intent(mContext, WelcomeActivity.class);
|
||||||
|
// mIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||||
|
// mContext.startActivity(mIntent);
|
||||||
|
android.os.Process.killProcess(android.os.Process.myPid());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 自定义错误处理,收集错误信息,发送错误报告等操作均在此完成
|
||||||
|
*
|
||||||
|
* @param ex
|
||||||
|
* @return true:如果处理了该异常信息;否则返回 false
|
||||||
|
*/
|
||||||
|
private boolean handleException(Throwable ex) {
|
||||||
|
if (ex == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 使用 Toast 来显示异常信息
|
||||||
|
new Thread() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
Looper.prepare();
|
||||||
|
Toast.makeText(mContext, "好像出了点问题~~~", Toast.LENGTH_LONG).show();
|
||||||
|
Looper.loop();
|
||||||
|
}
|
||||||
|
}.start();
|
||||||
|
|
||||||
|
// 收集设备参数信息
|
||||||
|
collectDeviceInfo(mContext);
|
||||||
|
// 保存日志文件
|
||||||
|
saveCrashInfo2File(ex);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 收集设备参数信息
|
||||||
|
*
|
||||||
|
* @param ctx
|
||||||
|
*/
|
||||||
|
public void collectDeviceInfo(Context ctx) {
|
||||||
|
try {
|
||||||
|
PackageManager pm = ctx.getPackageManager();
|
||||||
|
PackageInfo pi = pm.getPackageInfo(ctx.getPackageName(), PackageManager.GET_ACTIVITIES);
|
||||||
|
|
||||||
|
if (pi != null) {
|
||||||
|
String versionName = pi.versionName == null ? "null" : pi.versionName;
|
||||||
|
String versionCode = pi.versionCode + "";
|
||||||
|
infos.put("versionName", versionName);
|
||||||
|
infos.put("versionCode", versionCode);
|
||||||
|
}
|
||||||
|
} catch (NameNotFoundException e) {
|
||||||
|
Log.e(TAG, "an error occured when collect package info", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
Field[] fields = Build.class.getDeclaredFields();
|
||||||
|
for (Field field : fields) {
|
||||||
|
try {
|
||||||
|
field.setAccessible(true);
|
||||||
|
infos.put(field.getName(), field.get(null).toString());
|
||||||
|
Log.d(TAG, field.getName() + " : " + field.get(null));
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.e(TAG, "an error occured when collect crash info", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 保存错误信息到文件中 *
|
||||||
|
*
|
||||||
|
* @param ex
|
||||||
|
* @return 返回文件名称, 便于将文件传送到服务器
|
||||||
|
*/
|
||||||
|
private String saveCrashInfo2File(Throwable ex) {
|
||||||
|
StringBuffer sb = new StringBuffer();
|
||||||
|
for (Map.Entry<String, String> entry : infos.entrySet()) {
|
||||||
|
String key = entry.getKey();
|
||||||
|
String value = entry.getValue();
|
||||||
|
sb.append(String.format("%s=%s\n", key, value));
|
||||||
|
}
|
||||||
|
|
||||||
|
Writer writer = new StringWriter();
|
||||||
|
PrintWriter printWriter = new PrintWriter(writer);
|
||||||
|
ex.printStackTrace(printWriter);
|
||||||
|
Throwable cause = ex.getCause();
|
||||||
|
while (cause != null) {
|
||||||
|
cause.printStackTrace(printWriter);
|
||||||
|
cause = cause.getCause();
|
||||||
|
}
|
||||||
|
printWriter.close();
|
||||||
|
|
||||||
|
String result = writer.toString();
|
||||||
|
sb.append(result);
|
||||||
|
|
||||||
|
long timestamp = System.currentTimeMillis();
|
||||||
|
String time = formatter.format(new Date());
|
||||||
|
String fileName = "crash-" + time + "-" + timestamp + ".log";
|
||||||
|
|
||||||
|
if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
|
||||||
|
String path = String.format("%scrash/", FileHelper.getRootFilePath());
|
||||||
|
File dir = new File(path);
|
||||||
|
if (!dir.exists()) {
|
||||||
|
dir.mkdirs();
|
||||||
|
}
|
||||||
|
FileOutputStream fos = null;
|
||||||
|
try {
|
||||||
|
fos = new FileOutputStream(path + fileName);
|
||||||
|
fos.write(sb.toString().getBytes());
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.e(TAG, "an error occured while writing file...", e);
|
||||||
|
} finally {
|
||||||
|
try {
|
||||||
|
if (fos != null) {
|
||||||
|
fos.close();
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return fileName;
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,17 @@
|
|||||||
|
package com.xuqm.base.adapter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 所有用到{@link com.xuqm.base.ui.BaseListActivity}来做的列表页
|
||||||
|
* 数据model都要继承BaseItem
|
||||||
|
*/
|
||||||
|
public class BaseItem {
|
||||||
|
private int s_id;
|
||||||
|
|
||||||
|
public int getS_id() {
|
||||||
|
return s_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setS_id(int s_id) {
|
||||||
|
this.s_id = s_id;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,186 @@
|
|||||||
|
package com.xuqm.base.adapter;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
|
||||||
|
import com.xuqm.base.adapter.callback.AdapterItemClickListener;
|
||||||
|
import com.xuqm.base.adapter.callback.AdapterItemLongClickListener;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 不用{@link BasePagedAdapter}的时候,可以用这个
|
||||||
|
* <p>
|
||||||
|
*
|
||||||
|
* @param <T> 数据各式
|
||||||
|
*/
|
||||||
|
public abstract class BaseNormalAdapter<T> extends RecyclerView.Adapter<ViewHolder> {
|
||||||
|
private Context context;
|
||||||
|
private AdapterItemClickListener<T> itemClickListener;//item的点击事件
|
||||||
|
private AdapterItemLongClickListener<T> itemLongClickListener;//item的长按事件
|
||||||
|
private ItemViewDelegateManager<T> mItemViewDelegateManager;//ItemViewDelegate的管理类
|
||||||
|
|
||||||
|
private List<T> list;
|
||||||
|
|
||||||
|
private AdapterItemClickListener<T> listener;
|
||||||
|
|
||||||
|
public BaseNormalAdapter() {
|
||||||
|
this.list = new ArrayList<>();
|
||||||
|
mItemViewDelegateManager = new ItemViewDelegateManager<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public BaseNormalAdapter(List<T> list) {
|
||||||
|
this.list = null == list ? new ArrayList<>() : list;
|
||||||
|
mItemViewDelegateManager = new ItemViewDelegateManager<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setmDatas(List<T> mDatas) {
|
||||||
|
this.list.clear();
|
||||||
|
this.addmDatas(null == mDatas ? new ArrayList<>() : mDatas);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<T> getDatas() {
|
||||||
|
return this.list;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addmDatas(List<T> mDatas) {
|
||||||
|
this.list.addAll(mDatas);
|
||||||
|
notifyDataSetChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addItem(T item) {
|
||||||
|
this.list.add(item);
|
||||||
|
notifyDataSetChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void removeItem(T item) {
|
||||||
|
this.list.remove(item);
|
||||||
|
notifyDataSetChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getItemViewType(int position) {
|
||||||
|
if (!useItemViewDelegateManager()) return super.getItemViewType(position);
|
||||||
|
return mItemViewDelegateManager.getItemViewType(list.get(position), position);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断是否有多种ItemViewType
|
||||||
|
* 根据mItemViewDelegateManager 里面存储的数量决定
|
||||||
|
*
|
||||||
|
* @return true 有多种ItemViewType
|
||||||
|
*/
|
||||||
|
private boolean useItemViewDelegateManager() {
|
||||||
|
return mItemViewDelegateManager.getItemViewDelegateCount() > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 添加不同的item样式
|
||||||
|
*
|
||||||
|
* @param itemViewDelegate 自定义的item
|
||||||
|
* @return this
|
||||||
|
*/
|
||||||
|
public BaseNormalAdapter addItemViewDelegate(ItemViewDelegate<T> itemViewDelegate) {
|
||||||
|
mItemViewDelegateManager.addDelegate(itemViewDelegate);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 添加不同的item样式
|
||||||
|
*
|
||||||
|
* @param viewType 自定义的item type 不能重复
|
||||||
|
* @param itemViewDelegate 自定义的item
|
||||||
|
* @return this
|
||||||
|
*/
|
||||||
|
public BaseNormalAdapter addItemViewDelegate(int viewType, ItemViewDelegate<T> itemViewDelegate) {
|
||||||
|
mItemViewDelegateManager.addDelegate(viewType, itemViewDelegate);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||||
|
ItemViewDelegate itemViewDelegate = mItemViewDelegateManager.getItemViewDelegate(viewType);
|
||||||
|
int layoutId = itemViewDelegate.getItemViewLayoutId();//这里拿到自定义的layoutId
|
||||||
|
context = parent.getContext();//context没用传递过来,这里自己获取到
|
||||||
|
return new ViewHolder(context, parent, layoutId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
|
||||||
|
if (null != itemClickListener) {
|
||||||
|
holder.itemView.setOnClickListener(v -> itemClickListener.onClick(holder.itemView, list.get(position), position));
|
||||||
|
}
|
||||||
|
if (null != itemLongClickListener) {
|
||||||
|
holder.itemView.setOnLongClickListener(v -> itemLongClickListener.onClick(holder.itemView, list.get(position), position));
|
||||||
|
}
|
||||||
|
convert(holder, list.get(position));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getItemCount() {
|
||||||
|
return list.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置item点击监听
|
||||||
|
*
|
||||||
|
* @param itemClickListener item的点击事件
|
||||||
|
*/
|
||||||
|
public void setItemClickListener(AdapterItemClickListener<T> itemClickListener) {
|
||||||
|
this.itemClickListener = itemClickListener;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置item长按监听
|
||||||
|
*
|
||||||
|
* @param itemLongClickListener item的长按事件
|
||||||
|
*/
|
||||||
|
public void setItemLongClickListener(AdapterItemLongClickListener<T> itemLongClickListener) {
|
||||||
|
this.itemLongClickListener = itemLongClickListener;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 部分情况可以需要用到这个,比如item里面元素想要和item使用同一个回调处理
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
protected AdapterItemClickListener<T> getItemClickListener() {
|
||||||
|
return itemClickListener;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ui绘制的事件,分发给ItemViewDelegate自己处理
|
||||||
|
* 比如settext() setOnClickListener()这些
|
||||||
|
*
|
||||||
|
* @param holder holder
|
||||||
|
* @param item item
|
||||||
|
*/
|
||||||
|
public void convert(ViewHolder holder, T item) {
|
||||||
|
mItemViewDelegateManager.convert(holder, item, holder.getAdapterPosition());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 刷新知道item
|
||||||
|
*
|
||||||
|
* @param position position
|
||||||
|
*/
|
||||||
|
public void changeItem(int position) {
|
||||||
|
if (0 <= position && position < getItemCount()) {
|
||||||
|
notifyItemChanged(position);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void changeItem(int position, Object payload) {
|
||||||
|
if (0 <= position && position < getItemCount()) {
|
||||||
|
notifyItemChanged(position, payload);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,157 @@
|
|||||||
|
package com.xuqm.base.adapter;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.paging.PagedListAdapter;
|
||||||
|
|
||||||
|
import com.xuqm.base.adapter.callback.AdapterItemClickListener;
|
||||||
|
import com.xuqm.base.adapter.callback.AdapterItemLongClickListener;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 如果用到了{@link com.xuqm.base.ui.BaseListActivity}来展示列表页面的话,需要adapter继承这个
|
||||||
|
* <p>
|
||||||
|
* 如果item只有一种类型,可以使用{@link CommonPagedAdapter}来展示
|
||||||
|
* <p>
|
||||||
|
* 如果不用{@link CommonPagedAdapter}的话,继承后需要使用{@link #addItemViewDelegate(ItemViewDelegate)}
|
||||||
|
* 来设置展示的页面
|
||||||
|
*
|
||||||
|
* @param <T>
|
||||||
|
*/
|
||||||
|
public class BasePagedAdapter<T extends BaseItem> extends PagedListAdapter<T, ViewHolder> {
|
||||||
|
private Context context;
|
||||||
|
private AdapterItemClickListener<T> itemClickListener;//item的点击事件
|
||||||
|
private AdapterItemLongClickListener<T> itemLongClickListener;//item的长按事件
|
||||||
|
private ItemViewDelegateManager<T> mItemViewDelegateManager;//ItemViewDelegate的管理类
|
||||||
|
|
||||||
|
public BasePagedAdapter() {
|
||||||
|
super(new Diff<>());
|
||||||
|
mItemViewDelegateManager = new ItemViewDelegateManager<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getItemViewType(int position) {
|
||||||
|
if (!useItemViewDelegateManager()) return super.getItemViewType(position);
|
||||||
|
return mItemViewDelegateManager.getItemViewType(getItem(position), position);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断是否有多种ItemViewType
|
||||||
|
* 根据mItemViewDelegateManager 里面存储的数量决定
|
||||||
|
*
|
||||||
|
* @return true 有多种ItemViewType
|
||||||
|
*/
|
||||||
|
private boolean useItemViewDelegateManager() {
|
||||||
|
return mItemViewDelegateManager.getItemViewDelegateCount() > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 添加不同的item样式
|
||||||
|
*
|
||||||
|
* @param itemViewDelegate 自定义的item
|
||||||
|
* @return this
|
||||||
|
*/
|
||||||
|
public BasePagedAdapter addItemViewDelegate(ItemViewDelegate<T> itemViewDelegate) {
|
||||||
|
mItemViewDelegateManager.addDelegate(itemViewDelegate);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 添加不同的item样式
|
||||||
|
*
|
||||||
|
* @param viewType 自定义的item type 不能重复
|
||||||
|
* @param itemViewDelegate 自定义的item
|
||||||
|
* @return this
|
||||||
|
*/
|
||||||
|
public BasePagedAdapter addItemViewDelegate(int viewType, ItemViewDelegate<T> itemViewDelegate) {
|
||||||
|
mItemViewDelegateManager.addDelegate(viewType, itemViewDelegate);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||||
|
ItemViewDelegate itemViewDelegate = mItemViewDelegateManager.getItemViewDelegate(viewType);
|
||||||
|
int layoutId = itemViewDelegate.getItemViewLayoutId();//这里拿到自定义的layoutId
|
||||||
|
context = parent.getContext();//context没用传递过来,这里自己获取到
|
||||||
|
return new ViewHolder(context, parent, layoutId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
|
||||||
|
convert(holder, getItem(position));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onBindViewHolder(@NonNull ViewHolder holder, int position, @NonNull List<Object> payloads) {
|
||||||
|
if (null != itemClickListener) {
|
||||||
|
holder.itemView.setOnClickListener(v -> itemClickListener.onClick(holder.itemView, getItem(position), position));
|
||||||
|
}
|
||||||
|
if (null != itemLongClickListener) {
|
||||||
|
holder.itemView.setOnLongClickListener(v -> itemLongClickListener.onClick(holder.itemView, getItem(position), position));
|
||||||
|
}
|
||||||
|
bindViewHolder(holder, getItem(position), position, payloads);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void bindViewHolder(ViewHolder holder, T item, int position, List<Object> payloads) {
|
||||||
|
convert(holder, item);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置item点击监听
|
||||||
|
*
|
||||||
|
* @param itemClickListener item的点击事件
|
||||||
|
*/
|
||||||
|
public void setItemClickListener(AdapterItemClickListener<T> itemClickListener) {
|
||||||
|
this.itemClickListener = itemClickListener;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置item长按监听
|
||||||
|
*
|
||||||
|
* @param itemLongClickListener item的长按事件
|
||||||
|
*/
|
||||||
|
public void setItemLongClickListener(AdapterItemLongClickListener<T> itemLongClickListener) {
|
||||||
|
this.itemLongClickListener = itemLongClickListener;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 部分情况可以需要用到这个,比如item里面元素想要和item使用同一个回调处理
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
protected AdapterItemClickListener<T> getItemClickListener() {
|
||||||
|
return itemClickListener;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ui绘制的事件,分发给ItemViewDelegate自己处理
|
||||||
|
* 比如settext() setOnClickListener()这些
|
||||||
|
* @param holder holder
|
||||||
|
* @param item item
|
||||||
|
*/
|
||||||
|
public void convert(ViewHolder holder, T item) {
|
||||||
|
mItemViewDelegateManager.convert(holder, item, holder.getAdapterPosition());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 刷新知道item
|
||||||
|
* @param position position
|
||||||
|
*/
|
||||||
|
public void changeItem(int position) {
|
||||||
|
if (0 <= position && position < getItemCount()) {
|
||||||
|
notifyItemChanged(position);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void changeItem(int position, Object payload) {
|
||||||
|
if (0 <= position && position < getItemCount()) {
|
||||||
|
notifyItemChanged(position, payload);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,59 @@
|
|||||||
|
package com.xuqm.base.adapter;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 这个adapter主要是用来简化通用adapter
|
||||||
|
* 如果item只有一种样式,或者说不需要用到itemViewType,可以直接使用这个
|
||||||
|
* <p>
|
||||||
|
* 构造函数直接传入对应的layoutId,然后重写convert方法就可以了
|
||||||
|
* list不传的话,后面使用{@link #setmDatas(List)} 添加就可以了
|
||||||
|
*
|
||||||
|
* @param <T> item用到的数据类型
|
||||||
|
*/
|
||||||
|
public abstract class CommonAdapter<T> extends BaseNormalAdapter<T> {
|
||||||
|
|
||||||
|
|
||||||
|
protected CommonAdapter(final int layoutId) {
|
||||||
|
super();
|
||||||
|
addItemViewDelegate(new ItemViewDelegate<T>() {
|
||||||
|
@Override
|
||||||
|
public int getItemViewLayoutId() {
|
||||||
|
return layoutId;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isForViewType(T item, int position) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void convert(ViewHolder holder, T t, int position) {
|
||||||
|
CommonAdapter.this.convert(holder, t, position);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
protected CommonAdapter(final int layoutId, List<T> list) {
|
||||||
|
super(list);
|
||||||
|
addItemViewDelegate(new ItemViewDelegate<T>() {
|
||||||
|
@Override
|
||||||
|
public int getItemViewLayoutId() {
|
||||||
|
return layoutId;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isForViewType(T item, int position) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void convert(ViewHolder holder, T t, int position) {
|
||||||
|
CommonAdapter.this.convert(holder, t, position);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract void convert(ViewHolder holder, T item, int position);
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,36 @@
|
|||||||
|
package com.xuqm.base.adapter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 这个adapter主要是用来简化通用列表页的绘制
|
||||||
|
* 如果item只有一种样式,或者说不需要用到itemViewType,可以直接使用这个
|
||||||
|
* <p>
|
||||||
|
* 构造函数直接传入对应的layoutId,然后重写convert方法就可以了
|
||||||
|
*
|
||||||
|
* @param <T> item用到的数据类型
|
||||||
|
*/
|
||||||
|
public abstract class CommonPagedAdapter<T extends BaseItem> extends BasePagedAdapter<T> {
|
||||||
|
|
||||||
|
|
||||||
|
protected CommonPagedAdapter(final int layoutId) {
|
||||||
|
super();
|
||||||
|
addItemViewDelegate(new ItemViewDelegate<T>() {
|
||||||
|
@Override
|
||||||
|
public int getItemViewLayoutId() {
|
||||||
|
return layoutId;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isForViewType(T item, int position) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void convert(ViewHolder holder, T t, int position) {
|
||||||
|
CommonPagedAdapter.this.convert(holder, t, position);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract void convert(ViewHolder holder, T item, int position);
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,16 @@
|
|||||||
|
package com.xuqm.base.adapter;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.recyclerview.widget.DiffUtil;
|
||||||
|
|
||||||
|
public class Diff<T extends BaseItem> extends DiffUtil.ItemCallback<T> {
|
||||||
|
@Override
|
||||||
|
public boolean areItemsTheSame(@NonNull T oldItem, @NonNull T newItem) {
|
||||||
|
return oldItem.getS_id() == newItem.getS_id();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean areContentsTheSame(@NonNull T oldItem, @NonNull T newItem) {
|
||||||
|
return oldItem.getS_id() == newItem.getS_id();
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,72 @@
|
|||||||
|
package com.xuqm.base.adapter;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.util.AttributeSet;
|
||||||
|
import android.util.DisplayMetrics;
|
||||||
|
import android.view.MotionEvent;
|
||||||
|
import android.widget.HorizontalScrollView;
|
||||||
|
|
||||||
|
public class ElasticHorizontalScrollView extends HorizontalScrollView {
|
||||||
|
private float x;
|
||||||
|
private DisplayMetrics metrics;
|
||||||
|
private int threshold = 0;
|
||||||
|
|
||||||
|
public ElasticHorizontalScrollView(Context context, AttributeSet attrs) {
|
||||||
|
super(context, attrs);
|
||||||
|
|
||||||
|
metrics = getResources().getDisplayMetrics();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ElasticHorizontalScrollView(Context context) {
|
||||||
|
this(context, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setThreshold(int threshold) {
|
||||||
|
this.threshold = threshold;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onTouchEvent(MotionEvent ev) {
|
||||||
|
if (ev == null) {
|
||||||
|
return super.onTouchEvent(ev);
|
||||||
|
} else {
|
||||||
|
return commOnTouchEvent(ev);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void reset() {
|
||||||
|
scrollTo(0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean commOnTouchEvent(MotionEvent ev) {
|
||||||
|
int action = ev.getAction();
|
||||||
|
int length = threshold;
|
||||||
|
switch (action) {
|
||||||
|
case MotionEvent.ACTION_DOWN:
|
||||||
|
x = ev.getX();
|
||||||
|
break;
|
||||||
|
case MotionEvent.ACTION_UP:
|
||||||
|
//复原位置
|
||||||
|
if ((ev.getX() - x) > 0) {
|
||||||
|
if (getScrollX() > length / 2) {
|
||||||
|
smoothScrollTo(length, 0);
|
||||||
|
} else {
|
||||||
|
smoothScrollTo(0, 0);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (getScrollX() > length / 2) {
|
||||||
|
smoothScrollTo(length, 0);
|
||||||
|
} else {
|
||||||
|
smoothScrollTo(0, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
case MotionEvent.ACTION_MOVE:
|
||||||
|
return super.onTouchEvent(ev);
|
||||||
|
default:
|
||||||
|
return true;
|
||||||
|
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,30 @@
|
|||||||
|
package com.xuqm.base.adapter;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.fragment.app.Fragment;
|
||||||
|
import androidx.fragment.app.FragmentManager;
|
||||||
|
import androidx.lifecycle.Lifecycle;
|
||||||
|
import androidx.viewpager2.adapter.FragmentStateAdapter;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class FragmentAdapter extends FragmentStateAdapter {
|
||||||
|
|
||||||
|
private List<Fragment> fragments;
|
||||||
|
|
||||||
|
public FragmentAdapter(@NonNull FragmentManager fragmentManager, @NonNull Lifecycle lifecycle, List<Fragment> fragments) {
|
||||||
|
super(fragmentManager, lifecycle);
|
||||||
|
this.fragments = fragments;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public Fragment createFragment(int position) {
|
||||||
|
return fragments.get(position);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getItemCount() {
|
||||||
|
return fragments.size();
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,15 @@
|
|||||||
|
package com.xuqm.base.adapter;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by zhy on 16/6/22.
|
||||||
|
*/
|
||||||
|
public interface ItemViewDelegate<T> {
|
||||||
|
|
||||||
|
int getItemViewLayoutId();//这个 ItemViewDelegate 将要展示的页面
|
||||||
|
|
||||||
|
boolean isForViewType(T item, int position); //条件判断,用来判断什么时候展示这个ItemViewDelegate
|
||||||
|
|
||||||
|
void convert(ViewHolder holder, T item, int position);//UI绘制与事件添加
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,95 @@
|
|||||||
|
package com.xuqm.base.adapter;
|
||||||
|
|
||||||
|
|
||||||
|
import androidx.collection.SparseArrayCompat;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by zhy on 16/6/22.
|
||||||
|
*/
|
||||||
|
public class ItemViewDelegateManager<T> {
|
||||||
|
private SparseArrayCompat<ItemViewDelegate<T>> delegates = new SparseArrayCompat<>();
|
||||||
|
|
||||||
|
public int getItemViewDelegateCount() {
|
||||||
|
return delegates.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ItemViewDelegateManager<T> addDelegate(ItemViewDelegate<T> delegate) {
|
||||||
|
int viewType = delegates.size();
|
||||||
|
if (delegate != null) {
|
||||||
|
delegates.put(viewType, delegate);
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ItemViewDelegateManager<T> addDelegate(int viewType, ItemViewDelegate<T> delegate) {
|
||||||
|
if (delegates.get(viewType) != null) {
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
"An ItemViewDelegate is already registered for the viewType = "
|
||||||
|
+ viewType
|
||||||
|
+ ". Already registered ItemViewDelegate is "
|
||||||
|
+ delegates.get(viewType));
|
||||||
|
}
|
||||||
|
delegates.put(viewType, delegate);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ItemViewDelegateManager<T> removeDelegate(ItemViewDelegate<T> delegate) {
|
||||||
|
if (delegate == null) {
|
||||||
|
throw new NullPointerException("ItemViewDelegate is null");
|
||||||
|
}
|
||||||
|
int indexToRemove = delegates.indexOfValue(delegate);
|
||||||
|
|
||||||
|
if (indexToRemove >= 0) {
|
||||||
|
delegates.removeAt(indexToRemove);
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ItemViewDelegateManager<T> removeDelegate(int itemType) {
|
||||||
|
int indexToRemove = delegates.indexOfKey(itemType);
|
||||||
|
|
||||||
|
if (indexToRemove >= 0) {
|
||||||
|
delegates.removeAt(indexToRemove);
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
int getItemViewType(T item, int position) {
|
||||||
|
int delegatesCount = delegates.size();
|
||||||
|
for (int i = 0; i < delegatesCount; i++) {
|
||||||
|
ItemViewDelegate<T> delegate = delegates.valueAt(i);
|
||||||
|
if (delegate.isForViewType(item, position)) {
|
||||||
|
return delegates.keyAt(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
"No ItemViewDelegate added that matches position=" + position + " in data source");
|
||||||
|
}
|
||||||
|
|
||||||
|
void convert(ViewHolder holder, T item, int position) {
|
||||||
|
int delegatesCount = delegates.size();
|
||||||
|
for (int i = 0; i < delegatesCount; i++) {
|
||||||
|
ItemViewDelegate<T> delegate = delegates.valueAt(i);
|
||||||
|
|
||||||
|
if (delegate.isForViewType(item, position)) {
|
||||||
|
delegate.convert(holder, item, position);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
"No ItemViewDelegateManager added that matches position=" + position + " in data source");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public ItemViewDelegate getItemViewDelegate(int viewType) {
|
||||||
|
return delegates.get(viewType);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getItemViewLayoutId(int viewType) {
|
||||||
|
return getItemViewDelegate(viewType).getItemViewLayoutId();
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getItemViewType(ItemViewDelegate<T> itemViewDelegate) {
|
||||||
|
return delegates.indexOfValue(itemViewDelegate);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,178 @@
|
|||||||
|
package com.xuqm.base.adapter;
|
||||||
|
|
||||||
|
import android.util.Log;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.View.OnClickListener;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.LinearLayout;
|
||||||
|
import android.widget.LinearLayout.LayoutParams;
|
||||||
|
|
||||||
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
|
||||||
|
import com.xuqm.base.R;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author jose.han
|
||||||
|
* @date 2019/7/19 0019
|
||||||
|
* @description 包装器,再原有的adpter 基础上,分装测滑功能,基于
|
||||||
|
*/
|
||||||
|
|
||||||
|
public class SlipReAdapter extends RecyclerView.Adapter<SlipReAdapter.RViewHolder> {
|
||||||
|
|
||||||
|
private RecyclerView.Adapter mAdapter;
|
||||||
|
private ISlipClickAction mISlipClickAction;
|
||||||
|
private int mSlipViewId;
|
||||||
|
public final static int MODE_DELETE = 0;
|
||||||
|
public final static int MODE_CLICK = 0;
|
||||||
|
private int mMode = MODE_DELETE;
|
||||||
|
private int mSlipWidth = 0;
|
||||||
|
|
||||||
|
public static class Builder {
|
||||||
|
|
||||||
|
private RecyclerView.Adapter mAdapter;
|
||||||
|
private ISlipClickAction mISlipClickAction;
|
||||||
|
private int mSlipViewId;
|
||||||
|
private int mMode;
|
||||||
|
private int mSlipWidth;
|
||||||
|
|
||||||
|
public Builder setAdapter(RecyclerView.Adapter adapter) {
|
||||||
|
mAdapter = adapter;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder setISlipClickAction(
|
||||||
|
ISlipClickAction ISlipClickAction) {
|
||||||
|
mISlipClickAction = ISlipClickAction;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder setSlipViewId(int slipViewId) {
|
||||||
|
mSlipViewId = slipViewId;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder setMode(int mode) {
|
||||||
|
mMode = mode;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder setSlipWidth(float slipWidth) {
|
||||||
|
mSlipWidth = (int) slipWidth;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SlipReAdapter build() {
|
||||||
|
SlipReAdapter slipReAdapter = new SlipReAdapter();
|
||||||
|
slipReAdapter.setAdapter(mAdapter);
|
||||||
|
slipReAdapter.setISlipClickAction(mISlipClickAction);
|
||||||
|
slipReAdapter.setMode(mMode);
|
||||||
|
slipReAdapter.setSlipViewId(mSlipViewId);
|
||||||
|
slipReAdapter.setSlipWidth(mSlipWidth);
|
||||||
|
return slipReAdapter;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public SlipReAdapter() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAdapter(RecyclerView.Adapter adapter) {
|
||||||
|
mAdapter = adapter;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setISlipClickAction(
|
||||||
|
ISlipClickAction ISlipClickAction) {
|
||||||
|
mISlipClickAction = ISlipClickAction;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSlipViewId(int slipViewId) {
|
||||||
|
mSlipViewId = slipViewId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMode(int mode) {
|
||||||
|
mMode = mode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSlipWidth(int slipWidth) {
|
||||||
|
mSlipWidth = slipWidth;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public RViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
|
||||||
|
|
||||||
|
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_slip, parent, false);
|
||||||
|
LinearLayout contentLL = view.findViewById(R.id.content_ll);
|
||||||
|
LinearLayout deleteLl = view.findViewById(R.id.delete_ll);
|
||||||
|
View delete = LayoutInflater.from(parent.getContext()).inflate(mSlipViewId, null, false);
|
||||||
|
deleteLl.addView(delete);
|
||||||
|
|
||||||
|
LayoutParams layoutParams = new LayoutParams(
|
||||||
|
parent.getResources().getDisplayMetrics().widthPixels,
|
||||||
|
ViewGroup.LayoutParams.WRAP_CONTENT);
|
||||||
|
RecyclerView.ViewHolder viewHolder = mAdapter.onCreateViewHolder(parent, viewType);
|
||||||
|
viewHolder.itemView.setLayoutParams(layoutParams);
|
||||||
|
contentLL.addView(viewHolder.itemView);
|
||||||
|
|
||||||
|
return new RViewHolder(view, viewHolder, mSlipWidth);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onBindViewHolder(final RViewHolder holder, final int position) {
|
||||||
|
mAdapter.onBindViewHolder(holder.mViewHolder, position);
|
||||||
|
holder.deleteLl.setOnClickListener(new OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View v) {
|
||||||
|
mISlipClickAction.onAction(holder.getAdapterPosition());
|
||||||
|
holder.mElasticHorizontalScrollView.reset();
|
||||||
|
Log.i("SlipReAdapter", "slip action and the pos is:" + holder.getAdapterPosition());
|
||||||
|
if (mMode == MODE_DELETE) {
|
||||||
|
notifyItemRemoved(holder.getAdapterPosition());
|
||||||
|
} else if (mMode == MODE_CLICK) {
|
||||||
|
notifyItemChanged(holder.getAdapterPosition());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getItemCount() {
|
||||||
|
return mAdapter != null ? mAdapter.getItemCount() : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class RViewHolder extends RecyclerView.ViewHolder {
|
||||||
|
|
||||||
|
private View deleteLl;
|
||||||
|
private ElasticHorizontalScrollView mElasticHorizontalScrollView;
|
||||||
|
private RecyclerView.ViewHolder mViewHolder;
|
||||||
|
|
||||||
|
public RViewHolder(View itemView, RecyclerView.ViewHolder viewHolder, int threshold) {
|
||||||
|
super(itemView);
|
||||||
|
mViewHolder = viewHolder;
|
||||||
|
deleteLl = itemView.findViewById(R.id.delete_ll);
|
||||||
|
mElasticHorizontalScrollView = itemView.findViewById(R.id.ElasticHorizontalScrollView);
|
||||||
|
if (threshold != 0) {
|
||||||
|
LayoutParams layoutParams = new LayoutParams(threshold,
|
||||||
|
ViewGroup.LayoutParams.WRAP_CONTENT);
|
||||||
|
deleteLl.setLayoutParams(layoutParams);
|
||||||
|
mElasticHorizontalScrollView.setThreshold(threshold);
|
||||||
|
} else {
|
||||||
|
deleteLl.post(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
int width = deleteLl.getWidth();
|
||||||
|
mElasticHorizontalScrollView.setThreshold(width);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface ISlipClickAction {
|
||||||
|
|
||||||
|
public void onAction(int position);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,171 @@
|
|||||||
|
package com.xuqm.base.adapter;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.graphics.Bitmap;
|
||||||
|
import android.graphics.Typeface;
|
||||||
|
import android.util.SparseArray;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.ImageView;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import androidx.annotation.ColorInt;
|
||||||
|
import androidx.annotation.DrawableRes;
|
||||||
|
import androidx.annotation.IdRes;
|
||||||
|
import androidx.annotation.LayoutRes;
|
||||||
|
import androidx.annotation.StringRes;
|
||||||
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
|
||||||
|
import com.xuqm.base.adapter.callback.AdapterClickListener;
|
||||||
|
import com.xuqm.base.common.ImageHelper;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class ViewHolder extends RecyclerView.ViewHolder {
|
||||||
|
|
||||||
|
private Context context;
|
||||||
|
private ViewGroup parent;
|
||||||
|
private int layoutId;
|
||||||
|
|
||||||
|
private SparseArray<View> views = new SparseArray<>();
|
||||||
|
|
||||||
|
public ViewHolder(Context context, ViewGroup parent, @LayoutRes int layoutId) {
|
||||||
|
super(LayoutInflater.from(context).inflate(layoutId, parent, false));
|
||||||
|
this.context = context;
|
||||||
|
this.parent = parent;
|
||||||
|
this.layoutId = layoutId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public <T extends View> T getView(int viewId) {
|
||||||
|
View view = views.get(viewId);
|
||||||
|
if (null == view) {
|
||||||
|
view = itemView.findViewById(viewId);
|
||||||
|
if (null == view) throw new IllegalArgumentException("not found id");
|
||||||
|
views.put(viewId, view);
|
||||||
|
}
|
||||||
|
return (T) view;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ViewHolder setText(@IdRes int viewId, CharSequence text) {
|
||||||
|
TextView textView = getView(viewId);
|
||||||
|
textView.setText(text);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ViewHolder setTypeface(@IdRes int viewId, Typeface typeface) {
|
||||||
|
TextView textView = getView(viewId);
|
||||||
|
textView.setTypeface(typeface);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ViewHolder setEnabled(@IdRes int viewId, boolean enabled) {
|
||||||
|
View view = getView(viewId);
|
||||||
|
view.setEnabled(enabled);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ViewHolder setBackgroundResource(@IdRes int viewId, @DrawableRes int resId) {
|
||||||
|
View textView = getView(viewId);
|
||||||
|
textView.setBackgroundResource(resId);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ViewHolder setBackgroundColor(@IdRes int viewId, @ColorInt int color) {
|
||||||
|
View textView = getView(viewId);
|
||||||
|
textView.setBackgroundColor(color);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ViewHolder setTextColor(@IdRes int viewId, @ColorInt int color) {
|
||||||
|
TextView textView = getView(viewId);
|
||||||
|
textView.setTextColor(color);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ViewHolder setText(@IdRes int viewId, @StringRes int resId) {
|
||||||
|
TextView textView = getView(viewId);
|
||||||
|
textView.setText(context.getString(resId));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ViewHolder setImageResource(@IdRes int viewId, @DrawableRes int resId) {
|
||||||
|
ImageView imageView = getView(viewId);
|
||||||
|
imageView.setImageResource(resId);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ViewHolder setImage(@IdRes int viewId, String url) {
|
||||||
|
ImageView imageView = getView(viewId);
|
||||||
|
ImageHelper.load(imageView, url);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ViewHolder setImage(@IdRes int viewId, @DrawableRes int resourceId, String url) {
|
||||||
|
ImageView imageView = getView(viewId);
|
||||||
|
ImageHelper.load(imageView, resourceId, url);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ViewHolder setImage(@IdRes int viewId, @DrawableRes int placeholder, @DrawableRes int error, String url) {
|
||||||
|
ImageView imageView = getView(viewId);
|
||||||
|
ImageHelper.load(imageView, placeholder, error, url);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ViewHolder setImage(@IdRes int viewId, Bitmap bitmap) {
|
||||||
|
ImageView imageView = getView(viewId);
|
||||||
|
ImageHelper.load(imageView, bitmap);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ViewHolder gone(@IdRes int viewId) {
|
||||||
|
View view = getView(viewId);
|
||||||
|
view.setVisibility(View.GONE);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ViewHolder invisible(@IdRes int viewId) {
|
||||||
|
View view = getView(viewId);
|
||||||
|
view.setVisibility(View.INVISIBLE);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ViewHolder gone(View view) {
|
||||||
|
view.setVisibility(View.GONE);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ViewHolder visible(@IdRes int viewId) {
|
||||||
|
View view = getView(viewId);
|
||||||
|
view.setVisibility(View.VISIBLE);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ViewHolder setVisibility(@IdRes int viewId, boolean isVisible) {
|
||||||
|
View view = getView(viewId);
|
||||||
|
if (isVisible) view.setVisibility(View.VISIBLE);
|
||||||
|
else view.setVisibility(View.GONE);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ViewHolder visible(View view) {
|
||||||
|
view.setVisibility(View.VISIBLE);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ViewHolder setClickListener(@IdRes int viewId, AdapterClickListener adapterClickListener) {
|
||||||
|
View view = getView(viewId);
|
||||||
|
if (null != view) view.setOnClickListener(adapterClickListener::onClick);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ViewHolder setClickListener(List<Integer> viewIds, AdapterClickListener adapterClickListener) {
|
||||||
|
for (Integer viewId : viewIds) {
|
||||||
|
View view = getView(viewId);
|
||||||
|
if (null != view) view.setOnClickListener(adapterClickListener::onClick);
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,10 @@
|
|||||||
|
package com.xuqm.base.adapter.callback;
|
||||||
|
|
||||||
|
import android.view.View;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* adapter中,为item元素设置点击时间时候用到的监听
|
||||||
|
*/
|
||||||
|
public interface AdapterClickListener {
|
||||||
|
void onClick(View view);
|
||||||
|
}
|
||||||
@ -0,0 +1,11 @@
|
|||||||
|
package com.xuqm.base.adapter.callback;
|
||||||
|
|
||||||
|
import android.view.View;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* item设置点击事件的监听
|
||||||
|
* @param <T>
|
||||||
|
*/
|
||||||
|
public interface AdapterItemClickListener<T> {
|
||||||
|
void onClick(View view, T item, int position);
|
||||||
|
}
|
||||||
@ -0,0 +1,11 @@
|
|||||||
|
package com.xuqm.base.adapter.callback;
|
||||||
|
|
||||||
|
import android.view.View;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* item设置长按事件的监听
|
||||||
|
* @param <T>
|
||||||
|
*/
|
||||||
|
public interface AdapterItemLongClickListener<T> {
|
||||||
|
boolean onClick(View view, T item, int position);
|
||||||
|
}
|
||||||
@ -0,0 +1,143 @@
|
|||||||
|
package com.xuqm.base.common;
|
||||||
|
|
||||||
|
import android.text.TextUtils;
|
||||||
|
import android.util.Base64;
|
||||||
|
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.security.MessageDigest;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
|
import javax.crypto.Cipher;
|
||||||
|
import javax.crypto.spec.IvParameterSpec;
|
||||||
|
import javax.crypto.spec.SecretKeySpec;
|
||||||
|
|
||||||
|
public class AESCBCUtil { //密码
|
||||||
|
private static final String key = "12345678901234567890123456789012";
|
||||||
|
//iv偏移量
|
||||||
|
private static final String iv = "1234567890123456";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 加密:对字符串进行加密,并返回十六进制字符串(hex)
|
||||||
|
*
|
||||||
|
* @param encryptStr 需要加密的字符串
|
||||||
|
* @return 加密后的十六进制字符串(hex)
|
||||||
|
*/
|
||||||
|
public static String encrypt(String encryptStr, String key, String iv) {
|
||||||
|
try {
|
||||||
|
IvParameterSpec ivParameterSpec = new IvParameterSpec(iv.getBytes(StandardCharsets.UTF_8));
|
||||||
|
SecretKeySpec skeySpec = new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8), "AES");
|
||||||
|
|
||||||
|
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS7PADDING");
|
||||||
|
cipher.init(Cipher.ENCRYPT_MODE, skeySpec, ivParameterSpec);
|
||||||
|
|
||||||
|
byte[] encrypted = cipher.doFinal(encryptStr.getBytes());
|
||||||
|
|
||||||
|
|
||||||
|
byte[] encode = Base64.encode(encrypted, Base64.DEFAULT);
|
||||||
|
|
||||||
|
return new String(encode).replace("\n","");
|
||||||
|
// return byte2HexStr(encrypted);
|
||||||
|
} catch (Exception ex) {
|
||||||
|
ex.printStackTrace();
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 解密:对加密后的十六进制字符串(hex)进行解密,并返回字符串
|
||||||
|
*
|
||||||
|
* @param encryptedStr 需要解密的,加密后的十六进制字符串
|
||||||
|
* @return 解密后的字符串
|
||||||
|
*/
|
||||||
|
public static String decrypt(String encryptedStr, String key, String iv) {
|
||||||
|
try {
|
||||||
|
IvParameterSpec ivParameterSpec = new IvParameterSpec(iv.getBytes(StandardCharsets.UTF_8));
|
||||||
|
SecretKeySpec skeySpec = new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8), "AES");
|
||||||
|
|
||||||
|
|
||||||
|
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS7PADDING");
|
||||||
|
cipher.init(Cipher.DECRYPT_MODE, skeySpec, ivParameterSpec);
|
||||||
|
|
||||||
|
|
||||||
|
byte[] decode = Base64.decode(encryptedStr, Base64.DEFAULT);
|
||||||
|
|
||||||
|
// byte[] bytes = hexStr2Bytes(encryptedStr);
|
||||||
|
byte[] original = cipher.doFinal(decode);
|
||||||
|
|
||||||
|
return new String(original);
|
||||||
|
} catch (Exception ex) {
|
||||||
|
ex.printStackTrace();
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 十六进制字符串转换为byte[]
|
||||||
|
*
|
||||||
|
* @param hexStr 需要转换为byte[]的字符串
|
||||||
|
* @return 转换后的byte[]
|
||||||
|
*/
|
||||||
|
public static byte[] hexStr2Bytes(String hexStr) {
|
||||||
|
|
||||||
|
|
||||||
|
/*对输入值进行规范化整理*/
|
||||||
|
hexStr = hexStr.trim().replace(" ", "").toUpperCase(Locale.US);
|
||||||
|
//处理值初始化
|
||||||
|
int m = 0, n = 0;
|
||||||
|
int iLen = hexStr.length() / 2; //计算长度
|
||||||
|
byte[] ret = new byte[iLen]; //分配存储空间
|
||||||
|
|
||||||
|
for (int i = 0; i < iLen; i++) {
|
||||||
|
m = i * 2 + 1;
|
||||||
|
n = m + 1;
|
||||||
|
ret[i] = (byte) (Integer.decode("0x" + hexStr.substring(i * 2, m) + hexStr.substring(m, n)) & 0xFF);
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* byte[]转换为十六进制字符串
|
||||||
|
*
|
||||||
|
* @param bytes 需要转换为字符串的byte[]
|
||||||
|
* @return 转换后的十六进制字符串
|
||||||
|
*/
|
||||||
|
public static String byte2HexStr(byte[] bytes) {
|
||||||
|
String hs = "";
|
||||||
|
String stmp = "";
|
||||||
|
for (int n = 0; n < bytes.length; n++) {
|
||||||
|
stmp = (Integer.toHexString(bytes[n] & 0XFF));
|
||||||
|
if (stmp.length() == 1)
|
||||||
|
hs = hs + "0" + stmp;
|
||||||
|
else
|
||||||
|
hs = hs + stmp;
|
||||||
|
}
|
||||||
|
return hs;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String md5(String string) {
|
||||||
|
if (TextUtils.isEmpty(string)) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
MessageDigest md5 = null;
|
||||||
|
try {
|
||||||
|
md5 = MessageDigest.getInstance("MD5");
|
||||||
|
byte[] bytes = md5.digest(string.getBytes());
|
||||||
|
String result = "";
|
||||||
|
for (byte b : bytes) {
|
||||||
|
String temp = Integer.toHexString(b & 0xff);
|
||||||
|
if (temp.length() == 1) {
|
||||||
|
temp = "0" + temp;
|
||||||
|
}
|
||||||
|
result += temp;
|
||||||
|
}
|
||||||
|
return result.toUpperCase();
|
||||||
|
} catch (NoSuchAlgorithmException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,106 @@
|
|||||||
|
package com.xuqm.base.common;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
|
||||||
|
import java.util.Stack;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* activity的管理栈
|
||||||
|
*/
|
||||||
|
public class AppManager {
|
||||||
|
|
||||||
|
|
||||||
|
public static AppManager getInstance() {
|
||||||
|
return APPHolder.INSTANCE;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class APPHolder {
|
||||||
|
private static final AppManager INSTANCE = new AppManager();
|
||||||
|
}
|
||||||
|
|
||||||
|
private AppManager() {
|
||||||
|
activityStack = new Stack<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
private final Stack<Activity> activityStack;
|
||||||
|
|
||||||
|
//添加一个新的act
|
||||||
|
public void pushActivity(Activity activity) {
|
||||||
|
activityStack.add(activity);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 推出一个activity 其实toolbar的返回按钮,可以直接使用这个方法
|
||||||
|
*
|
||||||
|
* @param activity 需要退出的activity
|
||||||
|
*/
|
||||||
|
public void popActivity(Activity activity) {
|
||||||
|
if (activityStack != null && activityStack.size() > 0) {
|
||||||
|
if (activity != null) {
|
||||||
|
activity.finish();
|
||||||
|
activityStack.remove(activity);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取当前最上面的那个activity
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public Activity getActivity() {
|
||||||
|
return activityStack.lastElement();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* finish最后一个
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public void finish() {
|
||||||
|
this.popActivity(this.getActivity());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* finish最后一个之外的所有页面
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public void logout() {
|
||||||
|
if (activityStack.size() < 1)
|
||||||
|
return;
|
||||||
|
for (int i = 0; i < activityStack.size() - 1; i++) {
|
||||||
|
Activity activity = activityStack.firstElement();
|
||||||
|
if (activity == null) break;
|
||||||
|
popActivity(activity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 退出app
|
||||||
|
*/
|
||||||
|
public void exit() {
|
||||||
|
if (activityStack != null) {
|
||||||
|
while (activityStack.size() > 0) {
|
||||||
|
Activity activity = getActivity();
|
||||||
|
if (activity == null) break;
|
||||||
|
popActivity(activity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
android.os.Process.killProcess(android.os.Process.myPid());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 退出app
|
||||||
|
*/
|
||||||
|
public void exitWithOutLast() {
|
||||||
|
if (activityStack != null) {
|
||||||
|
int size = activityStack.size();
|
||||||
|
for (int i = 0; i < size - 1; i++) {
|
||||||
|
popActivity(activityStack.get(0));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,87 @@
|
|||||||
|
package com.xuqm.base.common;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.SharedPreferences;
|
||||||
|
import android.provider.Settings.Secure;
|
||||||
|
import android.telephony.TelephonyManager;
|
||||||
|
|
||||||
|
import java.io.UnsupportedEncodingException;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
public class DeviceUuidFactory {
|
||||||
|
protected static final String PREFS_FILE = "device_id.xml";
|
||||||
|
protected static final String PREFS_DEVICE_ID = "device_id";
|
||||||
|
|
||||||
|
protected static volatile UUID uuid;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public DeviceUuidFactory(Context context) {
|
||||||
|
|
||||||
|
if( uuid ==null ) {
|
||||||
|
synchronized (DeviceUuidFactory.class) {
|
||||||
|
if( uuid == null) {
|
||||||
|
final SharedPreferences prefs = context.getSharedPreferences( PREFS_FILE, 0);
|
||||||
|
final String id = prefs.getString(PREFS_DEVICE_ID, null );
|
||||||
|
|
||||||
|
if (id != null) {
|
||||||
|
// Use the ids previously computed and stored in the prefs file
|
||||||
|
uuid = UUID.fromString(id);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
final String androidId = Secure.getString(context.getContentResolver(), Secure.ANDROID_ID);
|
||||||
|
|
||||||
|
// Use the Android ID unless it's broken, in which case fallback on deviceId,
|
||||||
|
// unless it's not available, then fallback on a random number which we store
|
||||||
|
// to a prefs file
|
||||||
|
try {
|
||||||
|
if (!"9774d56d682e549c".equals(androidId)) {
|
||||||
|
uuid = UUID.nameUUIDFromBytes(androidId.getBytes("utf8"));
|
||||||
|
} else {
|
||||||
|
final String deviceId = ((TelephonyManager) context.getSystemService( Context.TELEPHONY_SERVICE )).getDeviceId();
|
||||||
|
uuid = deviceId!=null ? UUID.nameUUIDFromBytes(deviceId.getBytes("utf8")) : UUID.randomUUID();
|
||||||
|
}
|
||||||
|
} catch (UnsupportedEncodingException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write the value out to the prefs file
|
||||||
|
prefs.edit().putString(PREFS_DEVICE_ID, uuid.toString() ).commit();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a unique UUID for the current android device. As with all UUIDs, this unique ID is "very highly likely"
|
||||||
|
* to be unique across all Android devices. Much more so than ANDROID_ID is.
|
||||||
|
*
|
||||||
|
* The UUID is generated by using ANDROID_ID as the base key if appropriate, falling back on
|
||||||
|
* TelephonyManager.getDeviceID() if ANDROID_ID is known to be incorrect, and finally falling back
|
||||||
|
* on a random UUID that's persisted to SharedPreferences if getDeviceID() does not return a
|
||||||
|
* usable value.
|
||||||
|
*
|
||||||
|
* In some rare circumstances, this ID may change. In particular, if the device is factory reset a new device ID
|
||||||
|
* may be generated. In addition, if a user upgrades their phone from certain buggy implementations of Android 2.2
|
||||||
|
* to a newer, non-buggy version of Android, the device ID may change. Or, if a user uninstalls your app on
|
||||||
|
* a device that has neither a proper Android ID nor a Device ID, this ID may change on reinstallation.
|
||||||
|
*
|
||||||
|
* Note that if the code falls back on using TelephonyManager.getDeviceId(), the resulting ID will NOT
|
||||||
|
* change after a factory reset. Something to be aware of.
|
||||||
|
*
|
||||||
|
* Works around a bug in Android 2.2 for many devices when using ANDROID_ID directly.
|
||||||
|
*
|
||||||
|
* @see http://code.google.com/p/android/issues/detail?id=10603
|
||||||
|
*
|
||||||
|
* @return a UUID that may be used to uniquely identify your device for most purposes.
|
||||||
|
*/
|
||||||
|
public UUID getDeviceUuid() {
|
||||||
|
return uuid;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,10 @@
|
|||||||
|
package com.xuqm.base.common;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.util.TypedValue;
|
||||||
|
|
||||||
|
public class DimensHelper {
|
||||||
|
public static int pxToDp(Context context, int px) {
|
||||||
|
return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, px, context.getResources().getDisplayMetrics());
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,218 @@
|
|||||||
|
package com.xuqm.base.common;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.graphics.Bitmap;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.os.Build;
|
||||||
|
import android.os.Environment;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.core.content.FileProvider;
|
||||||
|
|
||||||
|
import com.xuqm.base.App;
|
||||||
|
import com.xuqm.base.BuildConfig;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
public class FileHelper {
|
||||||
|
|
||||||
|
|
||||||
|
public static String getRootFilePath() {
|
||||||
|
return App.getInstance().getExternalFilesDir(null).getPath() +
|
||||||
|
File.separator +
|
||||||
|
BuildConfig.APP_ID +
|
||||||
|
File.separator;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据路径创建文件夹
|
||||||
|
*
|
||||||
|
* @param filePath
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static boolean createDirectory(String filePath) {
|
||||||
|
if (ToolsHelper.isNull(filePath)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
File file = new File(filePath);
|
||||||
|
|
||||||
|
if (file.exists() && file.isDirectory()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return file.mkdirs();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void delete(String path) {
|
||||||
|
LogHelper.e(String.format("开始删除文件::%s", path));
|
||||||
|
File file = new File(path);
|
||||||
|
if (!file.exists()
|
||||||
|
|| !file.isFile())
|
||||||
|
return;
|
||||||
|
file.delete();
|
||||||
|
|
||||||
|
}
|
||||||
|
public static void delete(File file) {
|
||||||
|
LogHelper.e(String.format("开始删除文件::%s", file.getAbsoluteFile()));
|
||||||
|
if (!file.exists()
|
||||||
|
|| !file.isFile())
|
||||||
|
return;
|
||||||
|
file.delete();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getVoicePath() {
|
||||||
|
String path = getRootFilePath() +
|
||||||
|
"voice" +
|
||||||
|
File.separator;
|
||||||
|
|
||||||
|
return createDirectory(path) ? path : "";
|
||||||
|
}
|
||||||
|
public static String getImagePath() {
|
||||||
|
String path = getRootFilePath() +
|
||||||
|
"image" +
|
||||||
|
File.separator;
|
||||||
|
|
||||||
|
return createDirectory(path) ? path : "";
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getAppPath() {
|
||||||
|
String path = getRootFilePath() +
|
||||||
|
"apps" +
|
||||||
|
File.separator;
|
||||||
|
LogHelper.e(path);
|
||||||
|
|
||||||
|
return createDirectory(path) ? path : "";
|
||||||
|
}
|
||||||
|
public static String getDownloadPath() {
|
||||||
|
String path = getRootFilePath() +
|
||||||
|
"download" +
|
||||||
|
File.separator;
|
||||||
|
LogHelper.e(path);
|
||||||
|
|
||||||
|
return createDirectory(path) ? path : "";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 读取assets文件夹的文件
|
||||||
|
*
|
||||||
|
* @param strFileName 文件名,包含assets后面的路径
|
||||||
|
* @return 文件内容
|
||||||
|
*/
|
||||||
|
public static String readJSON(String strFileName) {
|
||||||
|
String strResult = "";
|
||||||
|
try (InputStream is = App.getInstance().getAssets().open(strFileName)) {
|
||||||
|
int size = is.available();
|
||||||
|
byte[] buffer = new byte[size];
|
||||||
|
is.read(buffer);
|
||||||
|
strResult = new String(buffer, StandardCharsets.UTF_8);
|
||||||
|
} catch (Exception ex) {
|
||||||
|
LogHelper.e("readJson", ex);
|
||||||
|
}
|
||||||
|
return strResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getBitmapFilePath(String path, String suffix) {
|
||||||
|
|
||||||
|
String pathStr = getRootFilePath() + "images" + File.separator + path + File.separator;
|
||||||
|
createDirectory(pathStr);
|
||||||
|
|
||||||
|
return pathStr + UUID.randomUUID() + "." + suffix;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 保存图片到本地
|
||||||
|
*
|
||||||
|
* @param bitmap 图片
|
||||||
|
* @param filePath 保存到的文件 png
|
||||||
|
* @return 状态
|
||||||
|
*/
|
||||||
|
public static String saveBitmap(Bitmap bitmap, String filePath) {
|
||||||
|
if (bitmap == null)
|
||||||
|
return "";
|
||||||
|
FileOutputStream fos = null;
|
||||||
|
try {
|
||||||
|
fos = new FileOutputStream(new File(filePath));
|
||||||
|
bitmap.compress(Bitmap.CompressFormat.PNG, 100, fos);
|
||||||
|
fos.flush();
|
||||||
|
return filePath;
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
} finally {
|
||||||
|
if (fos != null) {
|
||||||
|
try {
|
||||||
|
fos.close();
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
public void installAPK(String filePath) {
|
||||||
|
Intent intent = new Intent();
|
||||||
|
intent.setAction("android.intent.action.VIEW");
|
||||||
|
intent.addCategory("android.intent.category.DEFAULT");
|
||||||
|
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);// 广播里面操作需要加上这句,存在于一个独立的栈里
|
||||||
|
intent.setDataAndType(Uri.fromFile(new File(filePath)), "application/vnd.android.package-archive");
|
||||||
|
App.getInstance().startActivity(intent);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void openFile(Context activity, File file) {
|
||||||
|
Intent intent = new Intent();
|
||||||
|
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||||
|
// 设置intent的Action属性
|
||||||
|
intent.setAction(Intent.ACTION_VIEW);
|
||||||
|
// 获取文件file的MIME类型
|
||||||
|
String type = getMIMEType(file);
|
||||||
|
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||||
|
intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
|
||||||
|
}
|
||||||
|
// 设置intent的data和Type属性。
|
||||||
|
intent.setDataAndType(/* uri */FileHelper.getFileUri(file), type);
|
||||||
|
activity.startActivity(intent);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Uri getFileUri(@NonNull File file) {
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||||
|
return FileProvider.getUriForFile(AppManager.getInstance().getActivity(), BuildConfig.APP_ID + ".fileprovider", file);
|
||||||
|
} else {
|
||||||
|
return Uri.fromFile(file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String[][] MIME_MapTable = new String[][]{{".3gp", "video/3gpp"}, {".apk", "application/vnd.android.package-archive"}, {".asf", "video/x-ms-asf"}, {".avi", "video/x-msvideo"}, {".bin", "application/octet-stream"}, {".bmp", "image/bmp"}, {".c", "text/plain"}, {".class", "application/octet-stream"}, {".conf", "text/plain"}, {".cpp", "text/plain"}, {".doc", "application/msword"}, {".docx", "application/vnd.openxmlformats-officedocument.wordprocessingml.document"}, {".xls", "application/vnd.ms-excel"}, {".xlsx", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"}, {".exe", "application/octet-stream"}, {".gif", "image/gif"}, {".gtar", "application/x-gtar"}, {".gz", "application/x-gzip"}, {".h", "text/plain"}, {".htm", "text/html"}, {".html", "text/html"}, {".jar", "application/java-archive"}, {".java", "text/plain"}, {".jpeg", "image/jpeg"}, {".jpg", "image/jpeg"}, {".eucppic", "image/jpeg"}, {".js", "application/x-javascript"}, {".log", "text/plain"}, {".m3u", "audio/x-mpegurl"}, {".m4a", "audio/mp4a-latm"}, {".m4b", "audio/mp4a-latm"}, {".m4p", "audio/mp4a-latm"}, {".m4u", "video/vnd.mpegurl"}, {".m4v", "video/x-m4v"}, {".mov", "video/quicktime"}, {".mp2", "audio/x-mpeg"}, {".mp3", "audio/x-mpeg"}, {".mp4", "video/mp4"}, {".mpc", "application/vnd.mpohun.certificate"}, {".mpe", "video/mpeg"}, {".mpeg", "video/mpeg"}, {".mpg", "video/mpeg"}, {".mpg4", "video/mp4"}, {".mpga", "audio/mpeg"}, {".msg", "application/vnd.ms-outlook"}, {".ogg", "audio/ogg"}, {".pdf", "application/pdf"}, {".png", "image/png"}, {".pps", "application/vnd.ms-powerpoint"}, {".ppt", "application/vnd.ms-powerpoint"}, {".pptx", "application/vnd.openxmlformats-officedocument.presentationml.presentation"}, {".prop", "text/plain"}, {".rc", "text/plain"}, {".rmvb", "audio/x-pn-realaudio"}, {".rtf", "application/rtf"}, {".sh", "text/plain"}, {".tar", "application/x-tar"}, {".tgz", "application/x-compressed"}, {".txt", "text/plain"}, {".wav", "audio/x-wav"}, {".wma", "audio/x-ms-wma"}, {".wmv", "audio/x-ms-wmv"}, {".wps", "application/vnd.ms-works"}, {".xml", "text/plain"}, {".z", "application/x-compress"}, {".zip", "application/x-zip-compressed"}, {"", "*/*"}};
|
||||||
|
|
||||||
|
public static String getMIMEType(File file) {
|
||||||
|
String type = "*/*";
|
||||||
|
String fName = file.getName();
|
||||||
|
int dotIndex = fName.lastIndexOf(".");
|
||||||
|
if (dotIndex < 0) {
|
||||||
|
return type;
|
||||||
|
} else {
|
||||||
|
String end = fName.substring(dotIndex, fName.length()).toLowerCase();
|
||||||
|
if (end == "") {
|
||||||
|
return type;
|
||||||
|
} else {
|
||||||
|
for (int i = 0; i < MIME_MapTable.length; ++i) {
|
||||||
|
if (end.equals(MIME_MapTable[i][0])) {
|
||||||
|
type = MIME_MapTable[i][1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,112 @@
|
|||||||
|
package com.xuqm.base.common;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.widget.ImageView;
|
||||||
|
|
||||||
|
import com.bumptech.glide.Glide;
|
||||||
|
import com.bumptech.glide.load.resource.bitmap.CenterCrop;
|
||||||
|
import com.bumptech.glide.load.resource.bitmap.RoundedCorners;
|
||||||
|
import com.luck.picture.lib.engine.ImageEngine;
|
||||||
|
import com.luck.picture.lib.utils.ActivityCompatHelper;
|
||||||
|
import com.xuqm.base.R;
|
||||||
|
|
||||||
|
public class GlideEngine implements ImageEngine {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 加载图片
|
||||||
|
*
|
||||||
|
* @param context 上下文
|
||||||
|
* @param url 资源url
|
||||||
|
* @param imageView 图片承载控件
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void loadImage(Context context, String url, ImageView imageView) {
|
||||||
|
if (!ActivityCompatHelper.assertValidRequest(context)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Glide.with(context)
|
||||||
|
.load(url)
|
||||||
|
.into(imageView);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void loadImage(Context context, ImageView imageView, String url, int maxWidth, int maxHeight) {
|
||||||
|
if (!ActivityCompatHelper.assertValidRequest(context)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Glide.with(context)
|
||||||
|
.load(url)
|
||||||
|
.override(maxWidth, maxHeight)
|
||||||
|
.into(imageView);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 加载相册目录封面
|
||||||
|
*
|
||||||
|
* @param context 上下文
|
||||||
|
* @param url 图片路径
|
||||||
|
* @param imageView 承载图片ImageView
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void loadAlbumCover(Context context, String url, ImageView imageView) {
|
||||||
|
if (!ActivityCompatHelper.assertValidRequest(context)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Glide.with(context)
|
||||||
|
.asBitmap()
|
||||||
|
.load(url)
|
||||||
|
.override(180, 180)
|
||||||
|
.sizeMultiplier(0.5f)
|
||||||
|
.transform(new CenterCrop(), new RoundedCorners(8))
|
||||||
|
.placeholder(R.drawable.ps_image_placeholder)
|
||||||
|
.into(imageView);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 加载图片列表图片
|
||||||
|
*
|
||||||
|
* @param context 上下文
|
||||||
|
* @param url 图片路径
|
||||||
|
* @param imageView 承载图片ImageView
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void loadGridImage(Context context, String url, ImageView imageView) {
|
||||||
|
if (!ActivityCompatHelper.assertValidRequest(context)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Glide.with(context)
|
||||||
|
.load(url)
|
||||||
|
.override(200, 200)
|
||||||
|
.centerCrop()
|
||||||
|
.placeholder(R.drawable.ps_image_placeholder)
|
||||||
|
.into(imageView);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void pauseRequests(Context context) {
|
||||||
|
if (!ActivityCompatHelper.assertValidRequest(context)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Glide.with(context).pauseRequests();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void resumeRequests(Context context) {
|
||||||
|
if (!ActivityCompatHelper.assertValidRequest(context)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Glide.with(context).resumeRequests();
|
||||||
|
}
|
||||||
|
|
||||||
|
private GlideEngine() {
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class InstanceHolder {
|
||||||
|
static final GlideEngine instance = new GlideEngine();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static GlideEngine createGlideEngine() {
|
||||||
|
return InstanceHolder.instance;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,61 @@
|
|||||||
|
package com.xuqm.base.common;
|
||||||
|
|
||||||
|
import com.google.gson.Gson;
|
||||||
|
import com.google.gson.JsonArray;
|
||||||
|
import com.google.gson.JsonElement;
|
||||||
|
import com.google.gson.JsonParser;
|
||||||
|
|
||||||
|
import java.lang.reflect.Type;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by xuqm on 2016/6/3.
|
||||||
|
*/
|
||||||
|
public class GsonImplHelp extends Json {
|
||||||
|
private Gson gson = new Gson();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toJson(Object src) {
|
||||||
|
return gson.toJson(src);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <T> T toObject(String json, Class<T> claxx) {
|
||||||
|
return gson.fromJson(json, claxx);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <T> T toObject(String json, Type typeOfT) {
|
||||||
|
return gson.fromJson(json, typeOfT);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <T> T toObject(byte[] bytes, Class<T> claxx) {
|
||||||
|
return gson.fromJson(new String(bytes), claxx);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public <T> List<T> toList(String json, Class<T> clazz) {
|
||||||
|
JsonArray jsonArray = new JsonParser().parse(json).getAsJsonArray();
|
||||||
|
|
||||||
|
List<T> list = new ArrayList<>();
|
||||||
|
for (JsonElement jsonElement : jsonArray) {
|
||||||
|
list.add(gson.fromJson(jsonElement, clazz)); //cls
|
||||||
|
}
|
||||||
|
|
||||||
|
return list;
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T> List<T> stringToArray(String s, Class<T[]> cls) {
|
||||||
|
T[] array = new Gson().fromJson(s, cls);
|
||||||
|
return Arrays.asList(array);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,53 @@
|
|||||||
|
package com.xuqm.base.common;
|
||||||
|
|
||||||
|
import android.graphics.Bitmap;
|
||||||
|
import android.graphics.BitmapFactory;
|
||||||
|
import android.util.Base64;
|
||||||
|
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
public class ImageHelp {
|
||||||
|
/*
|
||||||
|
* bitmap转base64
|
||||||
|
* */
|
||||||
|
public static String bitmapToBase64(Bitmap bitmap) {
|
||||||
|
String result = null;
|
||||||
|
ByteArrayOutputStream baos = null;
|
||||||
|
try {
|
||||||
|
if (bitmap != null) {
|
||||||
|
baos = new ByteArrayOutputStream();
|
||||||
|
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, baos);
|
||||||
|
|
||||||
|
baos.flush();
|
||||||
|
baos.close();
|
||||||
|
|
||||||
|
byte[] bitmapBytes = baos.toByteArray();
|
||||||
|
result = Base64.encodeToString(bitmapBytes, Base64.DEFAULT);
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
} finally {
|
||||||
|
try {
|
||||||
|
if (baos != null) {
|
||||||
|
baos.flush();
|
||||||
|
baos.close();
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* base64转为bitmap
|
||||||
|
*
|
||||||
|
* @param base64Data
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static Bitmap base64ToBitmap(String base64Data) {
|
||||||
|
byte[] bytes = Base64.decode(base64Data, Base64.DEFAULT);
|
||||||
|
return BitmapFactory.decodeByteArray(bytes, 0, bytes.length);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,34 @@
|
|||||||
|
package com.xuqm.base.common;
|
||||||
|
|
||||||
|
import android.widget.ImageView;
|
||||||
|
|
||||||
|
import androidx.annotation.DrawableRes;
|
||||||
|
|
||||||
|
import com.bumptech.glide.Glide;
|
||||||
|
import com.bumptech.glide.load.engine.DiskCacheStrategy;
|
||||||
|
import com.bumptech.glide.request.RequestOptions;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 一个image相关的工具类
|
||||||
|
*
|
||||||
|
* @author xuqm
|
||||||
|
*/
|
||||||
|
public class ImageHelper {
|
||||||
|
/**
|
||||||
|
* 给imageView添加图片的方法
|
||||||
|
*
|
||||||
|
* @param imageView 需要添加图片的控件
|
||||||
|
* @param url url地址,可以是path draw等
|
||||||
|
*/
|
||||||
|
public static void load(ImageView imageView, Object url) {
|
||||||
|
Glide.with(imageView).load(url).into(imageView);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void load(ImageView imageView, @DrawableRes int resourceId, Object url) {
|
||||||
|
Glide.with(imageView).applyDefaultRequestOptions(RequestOptions.diskCacheStrategyOf(DiskCacheStrategy.AUTOMATIC)).load(url).placeholder(resourceId).error(resourceId).into(imageView);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void load(ImageView imageView, @DrawableRes int placeholder, @DrawableRes int error, Object url) {
|
||||||
|
Glide.with(imageView).applyDefaultRequestOptions(RequestOptions.diskCacheStrategyOf(DiskCacheStrategy.AUTOMATIC)).load(url).placeholder(placeholder).error(error).into(imageView);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,31 @@
|
|||||||
|
package com.xuqm.base.common;
|
||||||
|
|
||||||
|
import java.lang.reflect.Type;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by xuqm on 2016/6/3.
|
||||||
|
*/
|
||||||
|
public abstract class Json {
|
||||||
|
private static Json json;
|
||||||
|
|
||||||
|
Json() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Json get() {
|
||||||
|
if (json == null) {
|
||||||
|
json = new GsonImplHelp();
|
||||||
|
}
|
||||||
|
return json;
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract String toJson(Object src);
|
||||||
|
|
||||||
|
public abstract <T> T toObject(String json, Class<T> claxx);
|
||||||
|
public abstract <T> T toObject(String json, Type typeOfT);
|
||||||
|
|
||||||
|
public abstract <T> T toObject(byte[] bytes, Class<T> claxx);
|
||||||
|
|
||||||
|
public abstract <T> List<T> toList(String json, Class<T> claxx);
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,75 @@
|
|||||||
|
package com.xuqm.base.common;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
import com.orhanobut.logger.Logger;
|
||||||
|
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 日志库用的是 KLog
|
||||||
|
* 平常自己调试时候不想用那么多,就随便写了个类
|
||||||
|
*/
|
||||||
|
public class LogHelper {
|
||||||
|
|
||||||
|
|
||||||
|
public static void d(String tag, Object object) {
|
||||||
|
Logger.t(tag).d(object);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void d(Object object) {
|
||||||
|
StackTraceElement caller = getCallerStackTraceElement();
|
||||||
|
String tag = generateTag(caller);
|
||||||
|
Logger.t(tag).d(object);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void d(@NonNull String message, @Nullable Object... args) {
|
||||||
|
Logger.t(message).d(args);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void e(String tag, Object object) {
|
||||||
|
Logger.t(tag).e(object.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void e(Object object) {
|
||||||
|
if (null == object){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
StackTraceElement caller = getCallerStackTraceElement();
|
||||||
|
String tag = generateTag(caller);
|
||||||
|
Logger.t(tag).e("=====>" + object.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void e(String msg, Throwable tr) {
|
||||||
|
StackTraceElement caller = getCallerStackTraceElement();
|
||||||
|
String tag = generateTag(caller);
|
||||||
|
Logger.t(tag).e(tr, msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void e(String tag, String msg, Throwable tr) {
|
||||||
|
Logger.t(tag).e(tr, msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void json(String msg) {
|
||||||
|
StackTraceElement caller = getCallerStackTraceElement();
|
||||||
|
String tag = generateTag(caller);
|
||||||
|
Logger.t(tag).json(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void json(String tag, String msg) {
|
||||||
|
Logger.t(tag).json(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private static String generateTag(StackTraceElement caller) {
|
||||||
|
String tag = "%s.%s(L:%d)";
|
||||||
|
String callerClazzName = caller.getClassName();
|
||||||
|
callerClazzName = callerClazzName.substring(callerClazzName.lastIndexOf(".") + 1);
|
||||||
|
return String.format(Locale.getDefault(), tag, callerClazzName, caller.getMethodName(), caller.getLineNumber());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static StackTraceElement getCallerStackTraceElement() {
|
||||||
|
return Thread.currentThread().getStackTrace()[4];
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,8 @@
|
|||||||
|
package com.xuqm.base.common;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 下拉刷新的状态码表
|
||||||
|
*/
|
||||||
|
public enum RefreshResult {
|
||||||
|
SUCCEED, FAILED, NO_DATA, NO_MORE
|
||||||
|
}
|
||||||
@ -0,0 +1,18 @@
|
|||||||
|
package com.xuqm.base.common;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
|
||||||
|
public class ScreenUtils {
|
||||||
|
/**
|
||||||
|
* 获取屏幕高度(px)
|
||||||
|
*/
|
||||||
|
public static int getScreenHeight(Context context) {
|
||||||
|
return context.getResources().getDisplayMetrics().heightPixels;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 获取屏幕宽度(px)
|
||||||
|
*/
|
||||||
|
public static int getScreenWidth(Context context) {
|
||||||
|
return context.getResources().getDisplayMetrics().widthPixels;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,13 @@
|
|||||||
|
package com.xuqm.base.common
|
||||||
|
|
||||||
|
|
||||||
|
const val SHARE_UESR_NAME = "share_user_name"
|
||||||
|
const val SHARE_UESR_PASSWORD = "share_user_password"
|
||||||
|
const val SHARE_LOGIN_OBJ = "SHARE_LOGIN_OBJ"
|
||||||
|
const val SHARE_UESR_TOKEN = "share_user_token"
|
||||||
|
const val SHARE_UESR_ID = "share_user_id"
|
||||||
|
const val SHARE_UESR_TOKEN_TIME = "share_user_token_time"
|
||||||
|
|
||||||
|
const val MSG_COUNT = "msg_count"
|
||||||
|
|
||||||
|
const val SHARE_COUNTRY = "share_user_country"
|
||||||
@ -0,0 +1,94 @@
|
|||||||
|
package com.xuqm.base.common;
|
||||||
|
|
||||||
|
import java.text.ParseException;
|
||||||
|
import java.text.SimpleDateFormat;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.TimeZone;
|
||||||
|
|
||||||
|
public class TimeHelper {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取当前时间戳
|
||||||
|
*
|
||||||
|
* @return 时间戳
|
||||||
|
*/
|
||||||
|
public static long getTimeMillis() {
|
||||||
|
return System.currentTimeMillis();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取当前时间,指定返回样式
|
||||||
|
*
|
||||||
|
* @param formats 指定样式
|
||||||
|
* @return 时间字符串
|
||||||
|
*/
|
||||||
|
public static String getTimeString(String formats) {
|
||||||
|
return getStringFormMillis(System.currentTimeMillis(), formats);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据给定时间戳和样式,返回字符串
|
||||||
|
*
|
||||||
|
* @param millis 时间戳
|
||||||
|
* @param formats 指定字符串格式
|
||||||
|
* @return 时间字符串
|
||||||
|
*/
|
||||||
|
public static String getStringFormMillis(long millis, String formats) {
|
||||||
|
Date date = new Date(millis);
|
||||||
|
return new SimpleDateFormat(formats, Locale.CHINESE).format(date);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getStringFormMillisForGMT(long millis, String formats) {
|
||||||
|
Date date = new Date(millis);
|
||||||
|
|
||||||
|
SimpleDateFormat simpleDateFormat = new SimpleDateFormat(formats);//格式
|
||||||
|
|
||||||
|
simpleDateFormat.setTimeZone(TimeZone.getTimeZone("GMT")); // 设置时区为GMT
|
||||||
|
|
||||||
|
|
||||||
|
return simpleDateFormat.format(date);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据Date 返回指定格式的时字符串
|
||||||
|
*
|
||||||
|
* @param date 数据
|
||||||
|
* @param formats 格式
|
||||||
|
* @return 指定格式的字符串
|
||||||
|
*/
|
||||||
|
public static String getStringFromDate(Date date, String formats) {
|
||||||
|
SimpleDateFormat formatter = new SimpleDateFormat(formats, Locale.getDefault());
|
||||||
|
return formatter.format(date);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据给定字符串和格式,获取时间戳
|
||||||
|
*
|
||||||
|
* @param dateString 时间字符串
|
||||||
|
* @param formats 时间格式
|
||||||
|
* @return 时间戳
|
||||||
|
*/
|
||||||
|
public static long getTimeMillisForType(String dateString, String formats) {
|
||||||
|
SimpleDateFormat format = new SimpleDateFormat(formats, Locale.getDefault());
|
||||||
|
Date date = null;
|
||||||
|
try {
|
||||||
|
date = format.parse(dateString);
|
||||||
|
} catch (ParseException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
|
||||||
|
return date != null ? date.getTime() : 0;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取以秒为单位的时间戳
|
||||||
|
*
|
||||||
|
* @return 秒
|
||||||
|
*/
|
||||||
|
public static long getTimeFromSecond() {
|
||||||
|
return getTimeMillis() / 1000;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,274 @@
|
|||||||
|
package com.xuqm.base.common;
|
||||||
|
|
||||||
|
import android.text.Editable;
|
||||||
|
import android.text.TextUtils;
|
||||||
|
import android.text.TextWatcher;
|
||||||
|
import android.view.View;
|
||||||
|
import android.widget.EditText;
|
||||||
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
import com.google.android.material.snackbar.Snackbar;
|
||||||
|
import com.google.android.material.textfield.TextInputLayout;
|
||||||
|
import com.google.gson.internal.$Gson$Types;
|
||||||
|
|
||||||
|
import java.lang.reflect.Field;
|
||||||
|
import java.lang.reflect.ParameterizedType;
|
||||||
|
import java.lang.reflect.Type;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public class ToolsHelper {
|
||||||
|
|
||||||
|
public static boolean isNull(Object obj) {
|
||||||
|
if (null == obj) return true;
|
||||||
|
String str = obj.toString();
|
||||||
|
if (str.isEmpty()) return true;
|
||||||
|
return str.equalsIgnoreCase("null");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Long toLong(Object obj) {
|
||||||
|
if (isNull(obj)) return 0L;
|
||||||
|
try {
|
||||||
|
return Long.parseLong(obj.toString());
|
||||||
|
} catch (Exception e) {
|
||||||
|
return 0L;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int toInt(Object obj) {
|
||||||
|
if (isNull(obj)) return 0;
|
||||||
|
try {
|
||||||
|
return (int)(ToolsHelper.toDouble(obj.toString()));
|
||||||
|
} catch (Exception e) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static double toDouble(Object obj) {
|
||||||
|
if (isNull(obj)) return 0.0;
|
||||||
|
try {
|
||||||
|
return Double.parseDouble(obj.toString());
|
||||||
|
} catch (Exception e) {
|
||||||
|
return 0D;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String toString(Object obj) {
|
||||||
|
if (isNull(obj)) return "";
|
||||||
|
return obj.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 格式化json字符串
|
||||||
|
*
|
||||||
|
* @param jsonStr 需要格式化的json串
|
||||||
|
* @return 格式化后的json串
|
||||||
|
*/
|
||||||
|
public static String formatJson(String jsonStr) {
|
||||||
|
if (null == jsonStr || "".equals(jsonStr)) return "";
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
char last = '\0';
|
||||||
|
char current = '\0';
|
||||||
|
int indent = 0;
|
||||||
|
for (int i = 0; i < jsonStr.length(); i++) {
|
||||||
|
last = current;
|
||||||
|
current = jsonStr.charAt(i);
|
||||||
|
//遇到{ [换行,且下一行缩进
|
||||||
|
switch (current) {
|
||||||
|
case '{':
|
||||||
|
case '[':
|
||||||
|
sb.append(current);
|
||||||
|
sb.append('\n');
|
||||||
|
indent++;
|
||||||
|
addIndentBlank(sb, indent);
|
||||||
|
break;
|
||||||
|
//遇到} ]换行,当前行缩进
|
||||||
|
case '}':
|
||||||
|
case ']':
|
||||||
|
sb.append('\n');
|
||||||
|
indent--;
|
||||||
|
addIndentBlank(sb, indent);
|
||||||
|
sb.append(current);
|
||||||
|
break;
|
||||||
|
//遇到,换行
|
||||||
|
case ',':
|
||||||
|
sb.append(current);
|
||||||
|
if (last != '\\') {
|
||||||
|
sb.append('\n');
|
||||||
|
addIndentBlank(sb, indent);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
sb.append(current);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 添加space
|
||||||
|
*/
|
||||||
|
private static void addIndentBlank(StringBuilder sb, int indent) {
|
||||||
|
for (int i = 0; i < indent; i++) {
|
||||||
|
sb.append('\t');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* http 请求数据返回 json 中中文字符为 unicode 编码转汉字转码
|
||||||
|
*
|
||||||
|
* @param theString
|
||||||
|
* @return 转化后的结果.
|
||||||
|
*/
|
||||||
|
public static String decodeUnicode(String theString) {
|
||||||
|
char aChar;
|
||||||
|
int len = theString.length();
|
||||||
|
StringBuilder outBuffer = new StringBuilder(len);
|
||||||
|
for (int x = 0; x < len; ) {
|
||||||
|
aChar = theString.charAt(x++);
|
||||||
|
if (aChar == '\\') {
|
||||||
|
aChar = theString.charAt(x++);
|
||||||
|
if (aChar == 'u') {
|
||||||
|
int value = 0;
|
||||||
|
for (int i = 0; i < 4; i++) {
|
||||||
|
aChar = theString.charAt(x++);
|
||||||
|
switch (aChar) {
|
||||||
|
case '0':
|
||||||
|
case '1':
|
||||||
|
case '2':
|
||||||
|
case '3':
|
||||||
|
case '4':
|
||||||
|
case '5':
|
||||||
|
case '6':
|
||||||
|
case '7':
|
||||||
|
case '8':
|
||||||
|
case '9':
|
||||||
|
value = (value << 4) + aChar - '0';
|
||||||
|
break;
|
||||||
|
case 'a':
|
||||||
|
case 'b':
|
||||||
|
case 'c':
|
||||||
|
case 'd':
|
||||||
|
case 'e':
|
||||||
|
case 'f':
|
||||||
|
value = (value << 4) + 10 + aChar - 'a';
|
||||||
|
break;
|
||||||
|
case 'A':
|
||||||
|
case 'B':
|
||||||
|
case 'C':
|
||||||
|
case 'D':
|
||||||
|
case 'E':
|
||||||
|
case 'F':
|
||||||
|
value = (value << 4) + 10 + aChar - 'A';
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new IllegalArgumentException("Malformed \\uxxxx encoding.");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
outBuffer.append((char) value);
|
||||||
|
} else {
|
||||||
|
if (aChar == 't') {
|
||||||
|
aChar = '\t';
|
||||||
|
} else if (aChar != 'r') {
|
||||||
|
if (aChar == 'n') {
|
||||||
|
aChar = '\n';
|
||||||
|
} else if (aChar == 'f') {
|
||||||
|
aChar = '\f';
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
aChar = '\r';
|
||||||
|
}
|
||||||
|
outBuffer.append(aChar);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
outBuffer.append(aChar);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return outBuffer.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 弹出提示信息 感觉比Toast好看点 不过Toast不需要依赖view
|
||||||
|
*
|
||||||
|
* @param view 绑定一个view才能展示
|
||||||
|
* @param content 需要展示的内容
|
||||||
|
*/
|
||||||
|
public static void snack(View view, CharSequence content) {
|
||||||
|
Snackbar.make(view, content, Snackbar.LENGTH_SHORT).show();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void showMessage(CharSequence content) {
|
||||||
|
Toast.makeText(AppManager.getInstance().getActivity(), content, Toast.LENGTH_SHORT).show();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* EditText绑定TextInputLayout,处理一下
|
||||||
|
*
|
||||||
|
* @param editText editText
|
||||||
|
* @param textInputLayout textInputLayout
|
||||||
|
*/
|
||||||
|
public static void addTextChangedListener(EditText editText, TextInputLayout textInputLayout) {
|
||||||
|
editText.addTextChangedListener(new TextWatcher() {
|
||||||
|
@Override
|
||||||
|
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onTextChanged(CharSequence s, int start, int before, int count) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void afterTextChanged(Editable s) {
|
||||||
|
if (!TextUtils.isEmpty(textInputLayout.getError())) {//输入的时候不提示错误信息
|
||||||
|
textInputLayout.setErrorEnabled(true);
|
||||||
|
textInputLayout.setError("");
|
||||||
|
textInputLayout.setErrorEnabled(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 使用 TextInputLayout 提示错误信息
|
||||||
|
*
|
||||||
|
* @param textInputLayout TextInputLayout
|
||||||
|
* @param msg 错判的内容
|
||||||
|
*/
|
||||||
|
public static void showError(TextInputLayout textInputLayout, String msg) {
|
||||||
|
textInputLayout.setErrorEnabled(true);
|
||||||
|
textInputLayout.setError(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将Object对象里面的属性和值转化成Map对象
|
||||||
|
*
|
||||||
|
* @param obj
|
||||||
|
* @return
|
||||||
|
* @throws IllegalAccessException
|
||||||
|
*/
|
||||||
|
public static <T> Map<String, T> objectToMap(Object obj) throws IllegalAccessException {
|
||||||
|
Map<String, T> map = new HashMap<>();
|
||||||
|
Class<?> clazz = obj.getClass();
|
||||||
|
for (Field field : clazz.getDeclaredFields()) {
|
||||||
|
field.setAccessible(true);
|
||||||
|
String fieldName = field.getName();
|
||||||
|
T value = (T) field.get(obj);
|
||||||
|
if (null != value) map.put(fieldName, value);
|
||||||
|
}
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Type getSuperclassTypeParameter(Class<?> subclass) {
|
||||||
|
Type superClass = subclass.getGenericSuperclass();
|
||||||
|
// if (superClass instanceof Class) {
|
||||||
|
// System.out.println("superClass=" + superClass);
|
||||||
|
// throw new RuntimeException("Missing type parameter.");
|
||||||
|
// }
|
||||||
|
ParameterizedType parameterized = (ParameterizedType) superClass;
|
||||||
|
return $Gson$Types.canonicalize(parameterized.getActualTypeArguments()[0]);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,23 @@
|
|||||||
|
package com.xuqm.base.datasource;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.lifecycle.MutableLiveData;
|
||||||
|
import androidx.paging.DataSource;
|
||||||
|
|
||||||
|
public class DataSourceFactory<T> extends DataSource.Factory<Integer, T> {
|
||||||
|
private PagedDataLoader<T> dataLoader;
|
||||||
|
|
||||||
|
public DataSourceFactory(PagedDataLoader<T> dataLoader) {
|
||||||
|
this.dataLoader = dataLoader;
|
||||||
|
}
|
||||||
|
|
||||||
|
public MutableLiveData<PagedDataSource<T>> sourceLiveData = new MutableLiveData<>();
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public DataSource<Integer, T> create() {
|
||||||
|
PagedDataSource<T> dataSource = new PagedDataSource<>(dataLoader);
|
||||||
|
sourceLiveData.postValue(dataSource);
|
||||||
|
return dataSource;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,13 @@
|
|||||||
|
package com.xuqm.base.datasource;
|
||||||
|
|
||||||
|
import androidx.paging.PageKeyedDataSource;
|
||||||
|
|
||||||
|
public interface PagedDataLoader<T> {
|
||||||
|
void loadInitial(PageKeyedDataSource.LoadInitialParams<Integer> params, PageKeyedDataSource.LoadInitialCallback<Integer, T> callback);
|
||||||
|
|
||||||
|
void loadAfter(PageKeyedDataSource.LoadParams<Integer> params, PageKeyedDataSource.LoadCallback<Integer, T> callback);
|
||||||
|
|
||||||
|
void refresh();
|
||||||
|
|
||||||
|
void loadMore();
|
||||||
|
}
|
||||||
@ -0,0 +1,26 @@
|
|||||||
|
package com.xuqm.base.datasource;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.paging.PageKeyedDataSource;
|
||||||
|
|
||||||
|
public class PagedDataSource<T> extends PageKeyedDataSource<Integer, T> {
|
||||||
|
private PagedDataLoader<T> dataLoader;
|
||||||
|
|
||||||
|
public PagedDataSource(PagedDataLoader<T> dataLoader) {
|
||||||
|
this.dataLoader = dataLoader;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void loadInitial(@NonNull LoadInitialParams<Integer> params, @NonNull LoadInitialCallback<Integer, T> callback) {
|
||||||
|
this.dataLoader.loadInitial(params, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void loadBefore(@NonNull LoadParams<Integer> params, @NonNull LoadCallback<Integer, T> callback) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void loadAfter(@NonNull LoadParams<Integer> params, @NonNull LoadCallback<Integer, T> callback) {
|
||||||
|
this.dataLoader.loadAfter(params, callback);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,28 @@
|
|||||||
|
package com.xuqm.base.di.component;
|
||||||
|
|
||||||
|
import com.franmontiel.persistentcookiejar.PersistentCookieJar;
|
||||||
|
import com.xuqm.base.di.module.NetworkModule;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import javax.inject.Singleton;
|
||||||
|
|
||||||
|
import dagger.Component;
|
||||||
|
import okhttp3.Cookie;
|
||||||
|
import okhttp3.OkHttpClient;
|
||||||
|
import retrofit2.Retrofit;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 可以获取到Retrofit、OkHttpClient、PersistentCookieJar、cookies
|
||||||
|
*/
|
||||||
|
@Singleton
|
||||||
|
@Component(modules = NetworkModule.class)
|
||||||
|
public interface AppComponent {
|
||||||
|
Retrofit retrofit();
|
||||||
|
|
||||||
|
OkHttpClient okHttpClient();
|
||||||
|
|
||||||
|
PersistentCookieJar persistentCookieJar();
|
||||||
|
|
||||||
|
List<Cookie> cookies();
|
||||||
|
}
|
||||||
@ -0,0 +1,28 @@
|
|||||||
|
package com.xuqm.base.di.interceptor;
|
||||||
|
|
||||||
|
import com.xuqm.base.common.LogHelper;
|
||||||
|
import com.xuqm.base.common.ToolsHelper;
|
||||||
|
|
||||||
|
import okhttp3.logging.HttpLoggingInterceptor;
|
||||||
|
|
||||||
|
public class HttpLogger implements HttpLoggingInterceptor.Logger {
|
||||||
|
private StringBuilder mMessage = new StringBuilder();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void log(String message) {
|
||||||
|
// 请求或者响应开始
|
||||||
|
if (message.startsWith("--> POST")) {
|
||||||
|
mMessage.setLength(0);
|
||||||
|
}
|
||||||
|
// 以{}或者[]形式的说明是响应结果的json数据,需要进行格式化
|
||||||
|
if ((message.startsWith("{") && message.endsWith("}"))
|
||||||
|
|| (message.startsWith("[") && message.endsWith("]"))) {
|
||||||
|
message = ToolsHelper.formatJson(ToolsHelper.decodeUnicode(message));
|
||||||
|
}
|
||||||
|
mMessage.append(message.concat("\n"));
|
||||||
|
// 响应结束,打印整条日志
|
||||||
|
if (message.startsWith("<-- END HTTP")) {
|
||||||
|
LogHelper.d("___http",mMessage.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,57 @@
|
|||||||
|
package com.xuqm.base.di.interceptor;
|
||||||
|
|
||||||
|
|
||||||
|
import com.xuqm.base.common.LogHelper;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.charset.Charset;
|
||||||
|
|
||||||
|
import okhttp3.Interceptor;
|
||||||
|
import okhttp3.MediaType;
|
||||||
|
import okhttp3.Request;
|
||||||
|
import okhttp3.Response;
|
||||||
|
import okhttp3.ResponseBody;
|
||||||
|
import okio.Buffer;
|
||||||
|
|
||||||
|
import static okhttp3.internal.Util.UTF_8;
|
||||||
|
|
||||||
|
public class LoggingInterceptor implements Interceptor {
|
||||||
|
String TAG = "_____Http";
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Response intercept(Chain chain) throws IOException {
|
||||||
|
Request request = chain.request();
|
||||||
|
long startTime = System.currentTimeMillis();
|
||||||
|
Response response = chain.proceed(chain.request());
|
||||||
|
long endTime = System.currentTimeMillis();
|
||||||
|
long duration = endTime - startTime;
|
||||||
|
ResponseBody responseBody = response.body();
|
||||||
|
if (responseBody == null) {
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
okhttp3.MediaType mediaType = responseBody.contentType();
|
||||||
|
String content = response.body().string();
|
||||||
|
LogHelper.e(TAG, request.toString());
|
||||||
|
LogHelper.e(TAG, response.code() + " : " + response.message());
|
||||||
|
String method = request.method();
|
||||||
|
if ("POST".equals(method)) {
|
||||||
|
Buffer buffer = new Buffer();
|
||||||
|
try {
|
||||||
|
request.body().writeTo(buffer);
|
||||||
|
Charset charset = Charset.forName("UTF-8");
|
||||||
|
MediaType contentType = request.body().contentType();
|
||||||
|
if (contentType != null) {
|
||||||
|
charset = contentType.charset(UTF_8);
|
||||||
|
}
|
||||||
|
String params = buffer.readString(charset);
|
||||||
|
LogHelper.e(TAG, params);
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
LogHelper.json(TAG, content);
|
||||||
|
LogHelper.e(TAG, "耗时: " + duration + "毫秒");
|
||||||
|
return response.newBuilder().body(okhttp3.ResponseBody.create(mediaType, content)).build();
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,82 @@
|
|||||||
|
package com.xuqm.base.di.manager;
|
||||||
|
|
||||||
|
import com.xuqm.base.App;
|
||||||
|
import com.xuqm.base.di.component.AppComponent;
|
||||||
|
import com.xuqm.base.di.component.DaggerAppComponent;
|
||||||
|
import com.xuqm.base.di.module.NetworkModule;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import okhttp3.Interceptor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 网络访问的管理类,
|
||||||
|
*/
|
||||||
|
public class HttpManager {
|
||||||
|
|
||||||
|
private static Map<String, Object> apis = new HashMap<>();
|
||||||
|
private static Map<String, AppComponent> appComponentMap = new HashMap<>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 使用默认的appComponent和给定的service获取一个service实例
|
||||||
|
* appComponent 在{@link App #appComponent}定义
|
||||||
|
* service 可以参照retrofit的使用方法
|
||||||
|
* <p>
|
||||||
|
* appComponent {@link #getAppComponent(String)}
|
||||||
|
*
|
||||||
|
* @param service service
|
||||||
|
* @param <T> service实例class类型
|
||||||
|
* @return service实例
|
||||||
|
*/
|
||||||
|
public static <T> T getApi(final Class<T> service) {
|
||||||
|
return getApi(App.getInstance().appComponent, service);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据给定的appComponent和service获取一个service实例
|
||||||
|
* appComponent可以使用{@link #getAppComponent(String)} 方法获得
|
||||||
|
* service 可以参照retrofit的使用方法
|
||||||
|
*
|
||||||
|
* @param appComponent {@link #getAppComponent(String)}
|
||||||
|
* @param service service
|
||||||
|
* @param <T> service实例class类型
|
||||||
|
* @return service实例
|
||||||
|
*/
|
||||||
|
public static <T> T getApi(AppComponent appComponent, final Class<T> service) {
|
||||||
|
String key = appComponent.hashCode() + service.getCanonicalName();
|
||||||
|
if (!apis.containsKey(key))
|
||||||
|
synchronized (HttpManager.class) {
|
||||||
|
if (!apis.containsKey(key))
|
||||||
|
apis.put(key, appComponent.retrofit().create(service));
|
||||||
|
}
|
||||||
|
|
||||||
|
return (T) apis.get(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据指定的baseUrl 获取一个{@link AppComponent} 用来做后续事件
|
||||||
|
*
|
||||||
|
* @param baseUrl 换地地址 例如
|
||||||
|
* @return AppComponent
|
||||||
|
*/
|
||||||
|
// public static AppComponent getAppComponent(String baseUrl) {
|
||||||
|
// return getAppComponent(baseUrl, null);
|
||||||
|
// }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据指定的baseUrl 获取一个{@link AppComponent} 用来做后续事件
|
||||||
|
*
|
||||||
|
* @param baseUrl 换地地址
|
||||||
|
* @param interceptor 自定义拦截器
|
||||||
|
* @return AppComponent
|
||||||
|
*/
|
||||||
|
public static AppComponent getAppComponent(String baseUrl, Interceptor... interceptor) {
|
||||||
|
if (!appComponentMap.containsKey(baseUrl))
|
||||||
|
synchronized (HttpManager.class) {
|
||||||
|
if (!appComponentMap.containsKey(baseUrl))
|
||||||
|
appComponentMap.put(baseUrl, DaggerAppComponent.builder().networkModule(new NetworkModule(baseUrl, interceptor)).build());
|
||||||
|
}
|
||||||
|
return appComponentMap.get(baseUrl);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,97 @@
|
|||||||
|
package com.xuqm.base.di.module;
|
||||||
|
|
||||||
|
import com.franmontiel.persistentcookiejar.PersistentCookieJar;
|
||||||
|
import com.franmontiel.persistentcookiejar.cache.SetCookieCache;
|
||||||
|
import com.franmontiel.persistentcookiejar.persistence.SharedPrefsCookiePersistor;
|
||||||
|
import com.xuqm.base.App;
|
||||||
|
import com.xuqm.base.di.interceptor.HttpLogger;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
import javax.inject.Singleton;
|
||||||
|
|
||||||
|
import dagger.Module;
|
||||||
|
import dagger.Provides;
|
||||||
|
import okhttp3.Cookie;
|
||||||
|
import okhttp3.Interceptor;
|
||||||
|
import okhttp3.OkHttpClient;
|
||||||
|
import okhttp3.logging.HttpLoggingInterceptor;
|
||||||
|
import retrofit2.Retrofit;
|
||||||
|
import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory;
|
||||||
|
import retrofit2.converter.gson.GsonConverterFactory;
|
||||||
|
|
||||||
|
@Module
|
||||||
|
public class NetworkModule {
|
||||||
|
private String BaseUrl = "https://xuqinmin.com/";
|
||||||
|
private final List<Interceptor> interceptor = new ArrayList<>();
|
||||||
|
|
||||||
|
public NetworkModule() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public NetworkModule(String baseUrl, Interceptor... interceptor) {
|
||||||
|
BaseUrl = baseUrl;
|
||||||
|
this.interceptor.clear();
|
||||||
|
this.interceptor.addAll(Arrays.asList(interceptor));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
Retrofit provideRetrofit(OkHttpClient okHttpClient) {
|
||||||
|
|
||||||
|
return new Retrofit.Builder()
|
||||||
|
.baseUrl(BaseUrl)
|
||||||
|
.client(okHttpClient)
|
||||||
|
.addConverterFactory(GsonConverterFactory.create())
|
||||||
|
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
OkHttpClient provideOkHttpClient(HttpLoggingInterceptor httpLoggingInterceptor, PersistentCookieJar persistentCookieJar) {
|
||||||
|
|
||||||
|
OkHttpClient.Builder builder = new OkHttpClient.Builder()
|
||||||
|
.connectTimeout(30, TimeUnit.SECONDS)
|
||||||
|
.pingInterval(5, TimeUnit.SECONDS)
|
||||||
|
.readTimeout(60, TimeUnit.SECONDS)
|
||||||
|
.writeTimeout(60, TimeUnit.SECONDS);
|
||||||
|
// builder.addNetworkInterceptor(httpLoggingInterceptor);
|
||||||
|
if (0 != interceptor.size()) {
|
||||||
|
for (Interceptor interceptor1 : this.interceptor) {
|
||||||
|
builder.addInterceptor(interceptor1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return builder.cookieJar(persistentCookieJar)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
HttpLoggingInterceptor provideHttpLoggingInterceptor() {
|
||||||
|
return new HttpLoggingInterceptor(new HttpLogger()).setLevel(HttpLoggingInterceptor.Level.BASIC);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
PersistentCookieJar providePersistentCookieJar(SharedPrefsCookiePersistor sharedPrefsCookiePersistor) {
|
||||||
|
return new PersistentCookieJar(new SetCookieCache(), sharedPrefsCookiePersistor);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
SharedPrefsCookiePersistor provideSharedPrefsCookiePersistor() {
|
||||||
|
return new SharedPrefsCookiePersistor(App.getInstance());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
List<Cookie> provideCookies(SharedPrefsCookiePersistor sharedPrefsCookiePersistor) {
|
||||||
|
return sharedPrefsCookiePersistor.loadAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,83 @@
|
|||||||
|
package com.xuqm.base.dialog
|
||||||
|
|
||||||
|
import android.app.Dialog
|
||||||
|
import android.content.Context
|
||||||
|
import android.os.Bundle
|
||||||
|
import androidx.appcompat.app.AlertDialog
|
||||||
|
import androidx.fragment.app.DialogFragment
|
||||||
|
|
||||||
|
class AlertDialogFragment : DialogFragment() {
|
||||||
|
companion object {
|
||||||
|
fun newInstance(
|
||||||
|
title: String,
|
||||||
|
message: String,
|
||||||
|
confirm: String,
|
||||||
|
cancel: String
|
||||||
|
): AlertDialogFragment {
|
||||||
|
val args = Bundle()
|
||||||
|
args.putString("title", title)
|
||||||
|
args.putString("message", message)
|
||||||
|
args.putString("confirm", confirm)
|
||||||
|
args.putString("cancel", cancel)
|
||||||
|
val fragment = AlertDialogFragment()
|
||||||
|
fragment.arguments = args
|
||||||
|
return fragment
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private var title: String? = null
|
||||||
|
private var message: String? = null
|
||||||
|
private var confirm: String? = null
|
||||||
|
private var cancel: String? = null
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
title = arguments?.getString("title")
|
||||||
|
message = arguments?.getString("message")
|
||||||
|
confirm = arguments?.getString("confirm")
|
||||||
|
cancel = arguments?.getString("cancel")
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private lateinit var listener: NoticeDialogListener
|
||||||
|
|
||||||
|
interface NoticeDialogListener {
|
||||||
|
fun onDialogPositiveClick(dialog: DialogFragment)
|
||||||
|
fun onDialogNegativeClick(dialog: DialogFragment)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onAttach(context: Context) {
|
||||||
|
super.onAttach(context)
|
||||||
|
try {
|
||||||
|
listener = context as NoticeDialogListener
|
||||||
|
} catch (e: ClassCastException) {
|
||||||
|
throw ClassCastException(
|
||||||
|
(context.toString() +
|
||||||
|
" must implement NoticeDialogListener")
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||||
|
return activity?.let {
|
||||||
|
val builder = AlertDialog.Builder(it)
|
||||||
|
title?.let { it1 -> builder.setTitle(it1) }
|
||||||
|
message?.let { it1 -> builder.setMessage(it1) }
|
||||||
|
confirm?.let { it1 ->
|
||||||
|
builder.setPositiveButton(
|
||||||
|
it1
|
||||||
|
) { _, _ ->
|
||||||
|
listener.onDialogPositiveClick(this)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cancel?.let { it1 ->
|
||||||
|
builder.setNegativeButton(
|
||||||
|
it1
|
||||||
|
) { _, _ ->
|
||||||
|
listener.onDialogNegativeClick(this)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
builder.create()
|
||||||
|
} ?: throw IllegalStateException("Activity cannot be null")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,164 @@
|
|||||||
|
package com.xuqm.base.dialog.loading;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.res.TypedArray;
|
||||||
|
import android.graphics.Canvas;
|
||||||
|
import android.graphics.LinearGradient;
|
||||||
|
import android.graphics.Paint;
|
||||||
|
import android.graphics.RectF;
|
||||||
|
import android.graphics.Shader;
|
||||||
|
import android.util.AttributeSet;
|
||||||
|
import android.view.View;
|
||||||
|
|
||||||
|
import com.xuqm.base.R;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by qiqi on 15/11/3.
|
||||||
|
*/
|
||||||
|
public class ColorfulRingProgressView extends View {
|
||||||
|
|
||||||
|
|
||||||
|
private float mPercent = 75;
|
||||||
|
private float mStrokeWidth;
|
||||||
|
private int mBgColor = 0xffe1e1e1;
|
||||||
|
private float mStartAngle = 0;
|
||||||
|
private int mFgColorStart = 0xffffe400;
|
||||||
|
private int mFgColorEnd = 0xffff4800;
|
||||||
|
|
||||||
|
private LinearGradient mShader;
|
||||||
|
private Context mContext;
|
||||||
|
private RectF mOval;
|
||||||
|
private Paint mPaint;
|
||||||
|
|
||||||
|
|
||||||
|
public ColorfulRingProgressView(Context context, AttributeSet attrs) {
|
||||||
|
super(context, attrs);
|
||||||
|
this.mContext = context;
|
||||||
|
|
||||||
|
TypedArray a = context.getTheme().obtainStyledAttributes(attrs,
|
||||||
|
R.styleable.ColorfulRingProgressView,
|
||||||
|
0, 0);
|
||||||
|
|
||||||
|
try {
|
||||||
|
mBgColor = a.getColor(R.styleable.ColorfulRingProgressView_bgColor, 0xffe1e1e1);
|
||||||
|
mFgColorEnd = a.getColor(R.styleable.ColorfulRingProgressView_fgColorEnd, 0xffff4800);
|
||||||
|
|
||||||
|
mFgColorStart = a.getColor(R.styleable.ColorfulRingProgressView_fgColorStart, 0xffffe400);
|
||||||
|
mPercent = a.getFloat(R.styleable.ColorfulRingProgressView_percent, 75);
|
||||||
|
mStartAngle = a.getFloat(R.styleable.ColorfulRingProgressView_startAngle, 0) + 270;
|
||||||
|
mStrokeWidth = a.getDimensionPixelSize(R.styleable.ColorfulRingProgressView_strokeWidths, dp2px(21));
|
||||||
|
System.out.println("**** m" + mStrokeWidth);
|
||||||
|
} finally {
|
||||||
|
a.recycle();
|
||||||
|
}
|
||||||
|
|
||||||
|
init();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void init() {
|
||||||
|
mPaint = new Paint();
|
||||||
|
mPaint.setAntiAlias(true);
|
||||||
|
mPaint.setStyle(Paint.Style.STROKE);
|
||||||
|
mPaint.setStrokeWidth(mStrokeWidth);
|
||||||
|
mPaint.setStrokeCap(Paint.Cap.ROUND);
|
||||||
|
}
|
||||||
|
|
||||||
|
private int dp2px(float dp) {
|
||||||
|
return (int) (mContext.getResources().getDisplayMetrics().density * dp + 0.5f);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onDraw(Canvas canvas) {
|
||||||
|
super.onDraw(canvas);
|
||||||
|
|
||||||
|
mPaint.setShader(null);
|
||||||
|
mPaint.setColor(0xffFFE93C);
|
||||||
|
canvas.drawArc(mOval, 0, 360, false, mPaint);
|
||||||
|
|
||||||
|
mPaint.setShader(mShader);
|
||||||
|
canvas.drawArc(mOval, mStartAngle, mPercent * 3.6f, false, mPaint);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
|
||||||
|
super.onSizeChanged(w, h, oldw, oldh);
|
||||||
|
|
||||||
|
updateOval();
|
||||||
|
|
||||||
|
mShader = new LinearGradient(mOval.left, mOval.top,
|
||||||
|
mOval.left, mOval.bottom, mFgColorStart, mFgColorEnd, Shader.TileMode.MIRROR);
|
||||||
|
}
|
||||||
|
|
||||||
|
public float getPercent() {
|
||||||
|
return mPercent;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPercent(float mPercent) {
|
||||||
|
this.mPercent = mPercent;
|
||||||
|
refreshTheLayout();
|
||||||
|
}
|
||||||
|
|
||||||
|
public float getStrokeWidth() {
|
||||||
|
return mStrokeWidth;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setStrokeWidth(float mStrokeWidth) {
|
||||||
|
this.mStrokeWidth = mStrokeWidth;
|
||||||
|
mPaint.setStrokeWidth(mStrokeWidth);
|
||||||
|
updateOval();
|
||||||
|
refreshTheLayout();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateOval() {
|
||||||
|
int xp = getPaddingLeft() + getPaddingRight();
|
||||||
|
int yp = getPaddingBottom() + getPaddingTop();
|
||||||
|
mOval = new RectF(getPaddingLeft() + mStrokeWidth, getPaddingTop() + mStrokeWidth,
|
||||||
|
getPaddingLeft() + (getWidth() - xp) - mStrokeWidth,
|
||||||
|
getPaddingTop() + (getHeight() - yp) - mStrokeWidth);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setStrokeWidthDp(float dp) {
|
||||||
|
this.mStrokeWidth = dp2px(dp);
|
||||||
|
mPaint.setStrokeWidth(mStrokeWidth);
|
||||||
|
updateOval();
|
||||||
|
refreshTheLayout();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void refreshTheLayout() {
|
||||||
|
invalidate();
|
||||||
|
requestLayout();
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getFgColorStart() {
|
||||||
|
return mFgColorStart;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFgColorStart(int mFgColorStart) {
|
||||||
|
this.mFgColorStart = mFgColorStart;
|
||||||
|
mShader = new LinearGradient(mOval.left, mOval.top,
|
||||||
|
mOval.left, mOval.bottom, mFgColorStart, mFgColorEnd, Shader.TileMode.MIRROR);
|
||||||
|
refreshTheLayout();
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getFgColorEnd() {
|
||||||
|
return mFgColorEnd;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFgColorEnd(int mFgColorEnd) {
|
||||||
|
this.mFgColorEnd = mFgColorEnd;
|
||||||
|
mShader = new LinearGradient(mOval.left, mOval.top,
|
||||||
|
mOval.left, mOval.bottom, mFgColorStart, mFgColorEnd, Shader.TileMode.MIRROR);
|
||||||
|
refreshTheLayout();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public float getStartAngle() {
|
||||||
|
return mStartAngle;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setStartAngle(float mStartAngle) {
|
||||||
|
this.mStartAngle = mStartAngle + 270;
|
||||||
|
refreshTheLayout();
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,238 @@
|
|||||||
|
/**
|
||||||
|
* com.leadingsoft.leaderaide.activity
|
||||||
|
*
|
||||||
|
* @ClassName: LoadingDialog
|
||||||
|
* @Description: LoadingDialog等待页面
|
||||||
|
* @author: macq macq@leadingsoft.cn
|
||||||
|
* @date: 2014-06-16
|
||||||
|
*/
|
||||||
|
package com.xuqm.base.dialog.loading;
|
||||||
|
|
||||||
|
import android.app.Dialog;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.DialogInterface;
|
||||||
|
import android.content.DialogInterface.OnKeyListener;
|
||||||
|
import android.os.Handler;
|
||||||
|
import android.view.Gravity;
|
||||||
|
import android.view.KeyEvent;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.Window;
|
||||||
|
import android.view.WindowManager;
|
||||||
|
import android.widget.LinearLayout;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import com.xuqm.base.R;
|
||||||
|
import com.xuqm.base.common.AppManager;
|
||||||
|
import com.xuqm.base.common.LogHelper;
|
||||||
|
import com.xuqm.base.common.ToolsHelper;
|
||||||
|
|
||||||
|
|
||||||
|
public class LoadingDialog {
|
||||||
|
private static Dialog mDialog;
|
||||||
|
|
||||||
|
public static void showDialog(String msg) {
|
||||||
|
showDialog(AppManager.getInstance().getActivity(), msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void showDialog(Context context, String msg) {
|
||||||
|
if (isShowing()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
|
||||||
|
OnKeyListener keyListener = (dialog, keyCode, event) -> {
|
||||||
|
if (keyCode == KeyEvent.KEYCODE_BACK
|
||||||
|
&& event.getAction() == KeyEvent.ACTION_DOWN) {
|
||||||
|
mDialog.dismiss();
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
|
||||||
|
};
|
||||||
|
mDialog = new Dialog(context, R.style.dialog);
|
||||||
|
mDialog.setOnKeyListener(keyListener);
|
||||||
|
mDialog.setCancelable(false);
|
||||||
|
mDialog.show();
|
||||||
|
mDialog.setContentView(R.layout.loading_process_dialog_icon);
|
||||||
|
TextView TvLoading = (TextView) mDialog
|
||||||
|
.findViewById(R.id.loading_process_dialog_text);
|
||||||
|
TvLoading.setText(msg);
|
||||||
|
if (ToolsHelper.isNull(msg)) {
|
||||||
|
TvLoading.setVisibility(View.GONE);
|
||||||
|
}
|
||||||
|
Window window = mDialog.getWindow();
|
||||||
|
WindowManager.LayoutParams lp = window.getAttributes();
|
||||||
|
lp.gravity = Gravity.CENTER;
|
||||||
|
lp.width = WindowManager.LayoutParams.WRAP_CONTENT;//宽高可设置具体大小
|
||||||
|
lp.height = WindowManager.LayoutParams.WRAP_CONTENT;
|
||||||
|
mDialog.getWindow().setAttributes(lp);
|
||||||
|
} catch (Exception e) {
|
||||||
|
LogHelper.e(LoadingDialog.class.getSimpleName(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void showDialog(Context context, String msg, boolean isShowBackGround) {
|
||||||
|
OnKeyListener keyListener = new OnKeyListener() {
|
||||||
|
@Override
|
||||||
|
public boolean onKey(DialogInterface dialog, int keyCode,
|
||||||
|
KeyEvent event) {
|
||||||
|
if (keyCode == KeyEvent.KEYCODE_BACK
|
||||||
|
&& event.getAction() == KeyEvent.ACTION_DOWN) {
|
||||||
|
mDialog.dismiss();
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
|
||||||
|
}
|
||||||
|
};
|
||||||
|
mDialog = new Dialog(context, R.style.dialog);
|
||||||
|
mDialog.setOnKeyListener(keyListener);
|
||||||
|
mDialog.setCancelable(false);
|
||||||
|
mDialog.show();
|
||||||
|
mDialog.setContentView(R.layout.loading_process_dialog_icon);
|
||||||
|
LinearLayout ll_loading_process_dialog = (LinearLayout) mDialog.findViewById(R.id.ll_loading_process_dialog);
|
||||||
|
// 清空背景
|
||||||
|
if (!isShowBackGround) {
|
||||||
|
ll_loading_process_dialog.setBackgroundResource(0);
|
||||||
|
}
|
||||||
|
TextView TvLoading = (TextView) mDialog.findViewById(R.id.loading_process_dialog_text);
|
||||||
|
TvLoading.setText(msg);
|
||||||
|
if (ToolsHelper.isNull(msg)) {
|
||||||
|
TvLoading.setVisibility(View.GONE);
|
||||||
|
}
|
||||||
|
Window window = mDialog.getWindow();
|
||||||
|
WindowManager.LayoutParams lp = window.getAttributes();
|
||||||
|
lp.gravity = Gravity.CENTER;
|
||||||
|
lp.width = WindowManager.LayoutParams.WRAP_CONTENT;//宽高可设置具体大小
|
||||||
|
lp.height = WindowManager.LayoutParams.WRAP_CONTENT;
|
||||||
|
mDialog.getWindow().setAttributes(lp);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void updataTV(String text) {
|
||||||
|
if (null == mDialog)
|
||||||
|
return;
|
||||||
|
TextView TvLoading = (TextView) mDialog.findViewById(R.id.loading_process_dialog_text);
|
||||||
|
TvLoading.setText(text);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void dismissDialog() {
|
||||||
|
if (mDialog != null) {
|
||||||
|
if (mDialog.isShowing()) {
|
||||||
|
try {
|
||||||
|
mDialog.dismiss();
|
||||||
|
} catch (Exception e) {
|
||||||
|
// TODO: handle exception
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isShowing() {
|
||||||
|
if (mDialog != null) {
|
||||||
|
return mDialog.isShowing();
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取进度条的Dialog
|
||||||
|
*/
|
||||||
|
public static Dialog getCircleProgressDialog(Context context) {
|
||||||
|
return new Dialog(context, R.style.dialog);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取进度条的VIEW
|
||||||
|
*/
|
||||||
|
public static View getCircleProgressView(Context context) {
|
||||||
|
LayoutInflater inflater = LayoutInflater.from(context);
|
||||||
|
return inflater.inflate(R.layout.loading_circle_process_dialog_icon, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 显示带进度条的dialog
|
||||||
|
*/
|
||||||
|
public static void showCircleProgressDialog(Context context, Dialog dialog, View view) {
|
||||||
|
OnKeyListener keyListener = new OnKeyListener() {
|
||||||
|
@Override
|
||||||
|
public boolean onKey(DialogInterface dialog, int keyCode,
|
||||||
|
KeyEvent event) {
|
||||||
|
if (keyCode == KeyEvent.KEYCODE_BACK
|
||||||
|
&& event.getAction() == KeyEvent.ACTION_DOWN) {
|
||||||
|
dialog.dismiss();
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
|
||||||
|
}
|
||||||
|
};
|
||||||
|
try {
|
||||||
|
//dialog = new AlertDialog.Builder(context).create();
|
||||||
|
dialog.setOnKeyListener(keyListener);
|
||||||
|
dialog.setCancelable(false);
|
||||||
|
dialog.show();
|
||||||
|
dialog.setContentView(view);
|
||||||
|
} catch (Exception ex) {
|
||||||
|
LogHelper.e("创建圆形进度条", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 显示带进度条的dialog
|
||||||
|
*/
|
||||||
|
public static void updateCircleProgressDialog(int percent) {
|
||||||
|
if (null== mView)return;
|
||||||
|
TextView tvPercent = (TextView) mView.findViewById(R.id.tvPercent);
|
||||||
|
ColorfulRingProgressView isDownload = (ColorfulRingProgressView) mView.findViewById(R.id.crpv);
|
||||||
|
isDownload.setPercent(percent);
|
||||||
|
tvPercent.setText("" + percent);
|
||||||
|
}
|
||||||
|
|
||||||
|
/********************************* APK下载圆形进度条 ***********************************************/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取进度条的VIEW
|
||||||
|
*/
|
||||||
|
public static View getProgressViewByTextView(Context context) {
|
||||||
|
LayoutInflater inflater = LayoutInflater.from(context);
|
||||||
|
View view = inflater.inflate(R.layout.loading_circle_process_dialog_icon_by_textview, null);
|
||||||
|
return view;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static View mView;
|
||||||
|
/**
|
||||||
|
* 显示带进度条的dialog
|
||||||
|
*/
|
||||||
|
public static void showCircleProgressDialog(final Context context, final Handler handler, String msg) {
|
||||||
|
|
||||||
|
mDialog=getCircleProgressDialog(context);
|
||||||
|
mView=getProgressViewByTextView(context);
|
||||||
|
|
||||||
|
OnKeyListener keyListener = (dialog, keyCode, event) -> {
|
||||||
|
if (keyCode == KeyEvent.KEYCODE_BACK
|
||||||
|
&& event.getAction() == KeyEvent.ACTION_DOWN) {
|
||||||
|
handler.obtainMessage(9999).sendToTarget();
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
try {
|
||||||
|
//dialog = new AlertDialog.Builder(context).create();
|
||||||
|
mDialog.setOnKeyListener(keyListener);
|
||||||
|
mDialog.setCancelable(false);
|
||||||
|
mDialog.show();
|
||||||
|
|
||||||
|
TextView textView = (TextView) mView.findViewById(R.id.tip_msg);
|
||||||
|
textView.setText(msg);
|
||||||
|
TextView tvPercent = (TextView) mView.findViewById(R.id.tvPercent);
|
||||||
|
tvPercent.setText("0");
|
||||||
|
mDialog.setContentView(mView);
|
||||||
|
Window window = mDialog.getWindow();
|
||||||
|
WindowManager.LayoutParams lp = window.getAttributes();
|
||||||
|
lp.gravity = Gravity.CENTER;
|
||||||
|
lp.width = WindowManager.LayoutParams.WRAP_CONTENT;//宽高可设置具体大小
|
||||||
|
lp.height = WindowManager.LayoutParams.WRAP_CONTENT;
|
||||||
|
mDialog.getWindow().setAttributes(lp);
|
||||||
|
} catch (Exception ex) {
|
||||||
|
LogHelper.e("创建圆形进度条", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,232 @@
|
|||||||
|
package com.xuqm.base.extensions
|
||||||
|
|
||||||
|
import android.app.Activity
|
||||||
|
import android.content.Context
|
||||||
|
import android.view.inputmethod.InputMethodManager
|
||||||
|
import androidx.annotation.NonNull
|
||||||
|
import androidx.appcompat.app.AlertDialog
|
||||||
|
import androidx.fragment.app.Fragment
|
||||||
|
import com.livinglifetechway.quickpermissions_kotlin.runWithPermissions
|
||||||
|
import com.livinglifetechway.quickpermissions_kotlin.util.QuickPermissionsOptions
|
||||||
|
import com.xuqm.base.BuildConfig
|
||||||
|
import com.xuqm.base.common.LogHelper
|
||||||
|
import com.xuqm.base.common.ToolsHelper
|
||||||
|
import kotlin.experimental.and
|
||||||
|
import kotlin.properties.ReadWriteProperty
|
||||||
|
import kotlin.reflect.KProperty
|
||||||
|
|
||||||
|
|
||||||
|
private class NotNullSingleValue<T> : ReadWriteProperty<Any?, T> {
|
||||||
|
|
||||||
|
private var value: T? = null
|
||||||
|
|
||||||
|
override fun getValue(thisRef: Any?, property: KProperty<*>): T {
|
||||||
|
return value ?: throw IllegalStateException("not initialized")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
|
||||||
|
if (this.value == null) {
|
||||||
|
this.value = value
|
||||||
|
} else {
|
||||||
|
throw IllegalStateException("already initialized")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 不为空且唯一
|
||||||
|
*/
|
||||||
|
fun <T> notNullSingleValue(): ReadWriteProperty<Any?, T> = NotNullSingleValue()
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 打印log
|
||||||
|
*/
|
||||||
|
//fun Any.log(message: CharSequence) {
|
||||||
|
// KLog.d(this.javaClass.simpleName, message)
|
||||||
|
//}
|
||||||
|
|
||||||
|
fun Any.log(message: Any) {
|
||||||
|
LogHelper.d(this.javaClass.simpleName, message)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Any.loge(message: Any) {
|
||||||
|
LogHelper.e("=====>" + this.javaClass.simpleName, message)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Any.log() {
|
||||||
|
LogHelper.d("=====>" + this.javaClass.simpleName, this)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Any.loge() {
|
||||||
|
LogHelper.e("=====>" + this.javaClass.simpleName, this)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Any.showMessage(content: CharSequence) {
|
||||||
|
ToolsHelper.showMessage(content)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Any.showMessage() {
|
||||||
|
ToolsHelper.showMessage(this.toString())
|
||||||
|
}
|
||||||
|
|
||||||
|
fun ByteArray.toHexString(): String {
|
||||||
|
val sb = StringBuffer()
|
||||||
|
for (i in this.indices) {
|
||||||
|
val hex = Integer.toHexString((this[i] and 0xFF.toByte()).toInt())
|
||||||
|
if (hex.length < 2) {
|
||||||
|
sb.append(0)
|
||||||
|
}
|
||||||
|
sb.append(hex)
|
||||||
|
}
|
||||||
|
return sb.toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 弹出窗口
|
||||||
|
* @param title 标题
|
||||||
|
* @param message 提示信息
|
||||||
|
* @param confirm 点击确认按钮
|
||||||
|
* @param cancel 点击取消按钮的事件
|
||||||
|
*/
|
||||||
|
fun Activity.showDialog(title: String, message: String, confirm: () -> Unit, cancel: () -> Unit) {
|
||||||
|
AlertDialog.Builder(this).setTitle(title)
|
||||||
|
.setMessage(message)
|
||||||
|
.setPositiveButton(
|
||||||
|
"Confirm"
|
||||||
|
) { _, _ ->
|
||||||
|
confirm()
|
||||||
|
}
|
||||||
|
.setNegativeButton(
|
||||||
|
"Cancel"
|
||||||
|
) { _, _ ->
|
||||||
|
cancel()
|
||||||
|
}.create().show()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 弹出窗口---只有一个确认按钮
|
||||||
|
* @param title 标题
|
||||||
|
* @param message 提示信息
|
||||||
|
* @param confirm 点击确认按钮
|
||||||
|
*/
|
||||||
|
fun Activity.showDialog(title: String, message: String, confirm: () -> Unit) {
|
||||||
|
AlertDialog.Builder(this).setTitle(title)
|
||||||
|
.setMessage(message)
|
||||||
|
.setPositiveButton(
|
||||||
|
"Confirm"
|
||||||
|
) { _, _ ->
|
||||||
|
confirm()
|
||||||
|
}.create().show()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 权限检测--申请
|
||||||
|
* @param rationaleMethod 权限被拒绝时候的回调,提供重新申请,打开设置界面等方法
|
||||||
|
* @param permanentDeniedMethod 拒绝权限并且选中了不在提示选项的回调,可以指导用户打开设置页面授予权限
|
||||||
|
*/
|
||||||
|
fun Context?.runWithPermission(
|
||||||
|
vararg permissions: String,
|
||||||
|
callback: () -> Unit
|
||||||
|
): Any? = this.runWithPermissions(
|
||||||
|
*permissions
|
||||||
|
) { callback() }
|
||||||
|
|
||||||
|
|
||||||
|
fun Context?.runWithPermission(
|
||||||
|
vararg permissions: String,
|
||||||
|
rationaleMethod: ((com.livinglifetechway.quickpermissions_kotlin.util.QuickPermissionsRequest) -> Unit),
|
||||||
|
permanentDeniedMethod: ((com.livinglifetechway.quickpermissions_kotlin.util.QuickPermissionsRequest) -> Unit),
|
||||||
|
callback: () -> Unit
|
||||||
|
): Any? {
|
||||||
|
|
||||||
|
val quickPermissionsOption = QuickPermissionsOptions(
|
||||||
|
rationaleMethod = rationaleMethod,
|
||||||
|
permanentDeniedMethod = permanentDeniedMethod
|
||||||
|
)
|
||||||
|
|
||||||
|
return this.runWithPermissions(
|
||||||
|
*permissions, options = quickPermissionsOption
|
||||||
|
) { callback() }
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
隐藏软键盘
|
||||||
|
*/
|
||||||
|
fun Activity.hideSoftInput() {
|
||||||
|
val imm = getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
|
||||||
|
imm.hideSoftInputFromWindow(window.decorView.windowToken, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Fragment.hideSoftInput() {
|
||||||
|
activity?.hideSoftInput()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Context.putInt(key: String, value: Int) {
|
||||||
|
val sharedPref =
|
||||||
|
getSharedPreferences(BuildConfig.APP_ID, Context.MODE_PRIVATE) ?: return
|
||||||
|
with(sharedPref.edit()) {
|
||||||
|
putInt(key, value)
|
||||||
|
apply()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Context.getIntForPreferences(key: String): Int {
|
||||||
|
val sharedPref =
|
||||||
|
getSharedPreferences(BuildConfig.APP_ID, Context.MODE_PRIVATE)
|
||||||
|
return sharedPref?.getInt(key, -1) ?: -1
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Context.getIntForPreferences(key: String, @NonNull defValue: Int): Int {
|
||||||
|
val sharedPref =
|
||||||
|
getSharedPreferences(BuildConfig.APP_ID, Context.MODE_PRIVATE)
|
||||||
|
return sharedPref?.getInt(key, defValue) ?: defValue
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Context.putLong(key: String, value: Long) {
|
||||||
|
val sharedPref =
|
||||||
|
getSharedPreferences(BuildConfig.APP_ID, Context.MODE_PRIVATE) ?: return
|
||||||
|
with(sharedPref.edit()) {
|
||||||
|
putLong(key, value)
|
||||||
|
apply()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Context.getLongForPreferences(key: String): Long {
|
||||||
|
val sharedPref =
|
||||||
|
getSharedPreferences(BuildConfig.APP_ID, Context.MODE_PRIVATE)
|
||||||
|
return sharedPref?.getLong(key, -1) ?: -1
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Context.getLongForPreferences(key: String, @NonNull defValue: Long): Long {
|
||||||
|
val sharedPref =
|
||||||
|
getSharedPreferences(BuildConfig.APP_ID, Context.MODE_PRIVATE)
|
||||||
|
return sharedPref?.getLong(key, defValue) ?: defValue
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Context.putString(key: String, value: String) {
|
||||||
|
val sharedPref =
|
||||||
|
getSharedPreferences(BuildConfig.APP_ID, Context.MODE_PRIVATE) ?: return
|
||||||
|
with(sharedPref.edit()) {
|
||||||
|
putString(key, value)
|
||||||
|
apply()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Context.getStringForPreferences(key: String): String {
|
||||||
|
val sharedPref =
|
||||||
|
getSharedPreferences(BuildConfig.APP_ID, Context.MODE_PRIVATE)
|
||||||
|
return sharedPref?.getString(key, "") ?: ""
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Context.getStringForPreferences(key: String, @NonNull defValue: String): String {
|
||||||
|
val sharedPref =
|
||||||
|
getSharedPreferences(BuildConfig.APP_ID, Context.MODE_PRIVATE)
|
||||||
|
return sharedPref?.getString(key, defValue) ?: defValue
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Context.clearForPreferences() {
|
||||||
|
getSharedPreferences(BuildConfig.APP_ID, Context.MODE_PRIVATE).edit().clear().apply()
|
||||||
|
}
|
||||||
@ -0,0 +1,10 @@
|
|||||||
|
package com.xuqm.base.extensions
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.graphics.Typeface
|
||||||
|
import android.widget.TextView
|
||||||
|
|
||||||
|
|
||||||
|
fun TextView.setFont(context: Context, font: String) {
|
||||||
|
this.typeface = Typeface.createFromAsset(context.assets, "fonts/${font}.ttf")
|
||||||
|
}
|
||||||
@ -0,0 +1,25 @@
|
|||||||
|
package com.xuqm.base.extensions
|
||||||
|
|
||||||
|
class Fonts {
|
||||||
|
companion object {
|
||||||
|
const val Black = "Montserrat-Black"
|
||||||
|
const val BlackItalic = "Montserrat-BlackItalic"
|
||||||
|
const val Bold = "Montserrat-Bold"
|
||||||
|
const val BoldItalic = "Montserrat-BoldItalic"
|
||||||
|
const val ExtraBold = "Montserrat-ExtraBold"
|
||||||
|
const val ExtraBoldItalic = "Montserrat-ExtraBoldItalic"
|
||||||
|
const val ExtraLight = "Montserrat-ExtraLight"
|
||||||
|
const val ExtraLightItalic = "Montserrat-ExtraLightItalic"
|
||||||
|
const val Italic = "Montserrat-Italic"
|
||||||
|
const val Light = "Montserrat-Light"
|
||||||
|
const val LightItalic = "Montserrat-LightItalic"
|
||||||
|
const val Medium = "Montserrat-Medium"
|
||||||
|
const val MediumItalic = "Montserrat-MediumItalic"
|
||||||
|
const val Regular = "Montserrat-Regular"
|
||||||
|
const val SemiBold = "Montserrat-SemiBold"
|
||||||
|
const val SemiBoldItalic = "Montserrat-SemiBoldItalic"
|
||||||
|
const val Thin = "Montserrat-Thin"
|
||||||
|
const val ThinItalic = "Montserrat-ThinItalic"
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,22 @@
|
|||||||
|
package com.xuqm.base.file;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import okhttp3.Interceptor;
|
||||||
|
import okhttp3.Response;
|
||||||
|
|
||||||
|
public class DownloadInterceptor implements Interceptor {
|
||||||
|
|
||||||
|
private final DownloadListener downloadListener;
|
||||||
|
|
||||||
|
public DownloadInterceptor(DownloadListener downloadListener) {
|
||||||
|
this.downloadListener = downloadListener;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Response intercept(Chain chain) throws IOException {
|
||||||
|
Response response = chain.proceed(chain.request());
|
||||||
|
return response.newBuilder().body(
|
||||||
|
new DownloadResponseBody(response.body(), downloadListener)).build();
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,11 @@
|
|||||||
|
package com.xuqm.base.file;
|
||||||
|
|
||||||
|
public interface DownloadListener {
|
||||||
|
void onStartDownload();
|
||||||
|
|
||||||
|
void onProgress(int progress);
|
||||||
|
|
||||||
|
void onFinishDownload();
|
||||||
|
|
||||||
|
void onFail(String errorInfo);
|
||||||
|
}
|
||||||
@ -0,0 +1,70 @@
|
|||||||
|
package com.xuqm.base.file;
|
||||||
|
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import com.xuqm.base.common.LogHelper;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import okhttp3.MediaType;
|
||||||
|
import okhttp3.ResponseBody;
|
||||||
|
import okio.Buffer;
|
||||||
|
import okio.BufferedSource;
|
||||||
|
import okio.ForwardingSource;
|
||||||
|
import okio.Okio;
|
||||||
|
import okio.Source;
|
||||||
|
|
||||||
|
public class DownloadResponseBody extends ResponseBody {
|
||||||
|
|
||||||
|
private final ResponseBody responseBody;
|
||||||
|
|
||||||
|
private final DownloadListener downloadListener;
|
||||||
|
|
||||||
|
// BufferedSource 是okio库中的输入流,这里就当作inputStream来使用。
|
||||||
|
private BufferedSource bufferedSource;
|
||||||
|
|
||||||
|
public DownloadResponseBody(ResponseBody responseBody, DownloadListener downloadListener) {
|
||||||
|
this.responseBody = responseBody;
|
||||||
|
this.downloadListener = downloadListener;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MediaType contentType() {
|
||||||
|
return responseBody.contentType();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long contentLength() {
|
||||||
|
return responseBody.contentLength();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BufferedSource source() {
|
||||||
|
if (bufferedSource == null) {
|
||||||
|
bufferedSource = Okio.buffer(source(responseBody.source()));
|
||||||
|
}
|
||||||
|
return bufferedSource;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Source source(Source source) {
|
||||||
|
return new ForwardingSource(source) {
|
||||||
|
long totalBytesRead = 0L;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long read(Buffer sink, long byteCount) throws IOException {
|
||||||
|
long bytesRead = super.read(sink, byteCount);
|
||||||
|
// read() returns the number of bytes read, or -1 if this source is exhausted.
|
||||||
|
totalBytesRead += bytesRead != -1 ? bytesRead : 0;
|
||||||
|
Log.e("download", "read: "+ (int) (totalBytesRead * 100 / responseBody.contentLength()));
|
||||||
|
if (null != downloadListener) {
|
||||||
|
if (bytesRead != -1) {
|
||||||
|
downloadListener.onProgress((int) (totalBytesRead * 100 / responseBody.contentLength()));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
return bytesRead;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,80 @@
|
|||||||
|
package com.xuqm.base.file;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
|
||||||
|
import io.reactivex.observers.DefaultObserver;
|
||||||
|
import okhttp3.ResponseBody;
|
||||||
|
|
||||||
|
public abstract class FileDownLoadObserver<T> extends DefaultObserver<T> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onNext(T t) {
|
||||||
|
onDownLoadSuccess(t);
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public void onError(Throwable e) {
|
||||||
|
onDownLoadFail(e);
|
||||||
|
}
|
||||||
|
//可以重写,具体可由子类实现
|
||||||
|
@Override
|
||||||
|
public void onComplete() {
|
||||||
|
}
|
||||||
|
//下载成功的回调
|
||||||
|
public abstract void onDownLoadSuccess(T t);
|
||||||
|
//下载失败回调
|
||||||
|
public abstract void onDownLoadFail(Throwable throwable);
|
||||||
|
//下载进度监听
|
||||||
|
public abstract void onProgress(int progress,long total);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将文件写入本地
|
||||||
|
* @param responseBody 请求结果全体
|
||||||
|
* @param destFileDir 目标文件夹
|
||||||
|
* @param destFileName 目标文件名
|
||||||
|
* @return 写入完成的文件
|
||||||
|
* @throws IOException IO异常
|
||||||
|
*/
|
||||||
|
public File saveFile(ResponseBody responseBody, String destFileDir, String destFileName) throws IOException {
|
||||||
|
InputStream is = null;
|
||||||
|
byte[] buf = new byte[2048];
|
||||||
|
int len = 0;
|
||||||
|
FileOutputStream fos = null;
|
||||||
|
try {
|
||||||
|
is = responseBody.byteStream();
|
||||||
|
final long total = responseBody.contentLength();
|
||||||
|
long sum = 0;
|
||||||
|
|
||||||
|
File dir = new File(destFileDir);
|
||||||
|
if (!dir.exists()) {
|
||||||
|
dir.mkdirs();
|
||||||
|
}
|
||||||
|
File file = new File(dir, destFileName);
|
||||||
|
fos = new FileOutputStream(file);
|
||||||
|
while ((len = is.read(buf)) != -1) {
|
||||||
|
sum += len;
|
||||||
|
fos.write(buf, 0, len);
|
||||||
|
final long finalSum = sum;
|
||||||
|
//这里就是对进度的监听回调
|
||||||
|
onProgress((int) (finalSum * 100 / total),total);
|
||||||
|
}
|
||||||
|
fos.flush();
|
||||||
|
|
||||||
|
return file;
|
||||||
|
|
||||||
|
} finally {
|
||||||
|
try {
|
||||||
|
if (is != null) is.close();
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
if (fos != null) fos.close();
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,178 @@
|
|||||||
|
package com.xuqm.base.file;
|
||||||
|
|
||||||
|
import com.xuqm.base.common.LogHelper;
|
||||||
|
import com.xuqm.base.di.component.AppComponent;
|
||||||
|
import com.xuqm.base.di.manager.HttpManager;
|
||||||
|
import com.xuqm.base.repository.CommonService;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileNotFoundException;
|
||||||
|
import java.io.FileOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||||
|
import io.reactivex.annotations.NonNull;
|
||||||
|
import io.reactivex.disposables.CompositeDisposable;
|
||||||
|
import io.reactivex.disposables.Disposable;
|
||||||
|
import io.reactivex.schedulers.Schedulers;
|
||||||
|
import okhttp3.MediaType;
|
||||||
|
import okhttp3.RequestBody;
|
||||||
|
import okhttp3.ResponseBody;
|
||||||
|
|
||||||
|
public class FileDownloadAndUploadManager {
|
||||||
|
private static FileDownloadAndUploadManager instance;
|
||||||
|
|
||||||
|
public static FileDownloadAndUploadManager getInstance() {
|
||||||
|
if (null == instance) {
|
||||||
|
synchronized (FileDownloadAndUploadManager.class) {
|
||||||
|
if (null == instance) {
|
||||||
|
instance = new FileDownloadAndUploadManager();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
private final AppComponent appComponent;
|
||||||
|
protected CompositeDisposable compositeDisposable;
|
||||||
|
|
||||||
|
private FileDownloadAndUploadManager() {
|
||||||
|
appComponent = HttpManager.getAppComponent("https://xuqinmin.com/");
|
||||||
|
compositeDisposable = new CompositeDisposable();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void download(@NonNull String url, final String filePath) {
|
||||||
|
LogHelper.e(url);
|
||||||
|
thisListener.onStartDownload();
|
||||||
|
Disposable d = HttpManager.getApi(appComponent, CommonService.class)
|
||||||
|
.download(url)
|
||||||
|
.subscribeOn(Schedulers.io())
|
||||||
|
.unsubscribeOn(Schedulers.io())
|
||||||
|
.map(ResponseBody::byteStream)
|
||||||
|
.observeOn(Schedulers.computation())
|
||||||
|
.doOnNext(inputStream -> writeFile(inputStream, filePath))
|
||||||
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
.subscribe(body -> {
|
||||||
|
thisListener.onFinishDownload();
|
||||||
|
}, err -> {
|
||||||
|
LogHelper.e("=============>", err);
|
||||||
|
thisListener.onFail(err.getMessage());
|
||||||
|
});
|
||||||
|
compositeDisposable.add(d);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void cancel() {
|
||||||
|
compositeDisposable.dispose();
|
||||||
|
compositeDisposable.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void download(@NonNull String url, final String destDir, final String fileName, final FileDownLoadObserver<File> fileDownLoadObserver) {
|
||||||
|
HttpManager.getApi(appComponent, CommonService.class)
|
||||||
|
.download(url)
|
||||||
|
.subscribeOn(Schedulers.io())
|
||||||
|
.observeOn(Schedulers.io())
|
||||||
|
.observeOn(Schedulers.computation())
|
||||||
|
.map(responseBody -> fileDownLoadObserver.saveFile(responseBody, destDir, fileName))
|
||||||
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
.subscribe(fileDownLoadObserver);
|
||||||
|
}
|
||||||
|
|
||||||
|
// public void upload(@NonNull String url, @NonNull String filePath, final Map<String, String> formDataPart) {
|
||||||
|
// upload(url, new File(filePath), formDataPart);
|
||||||
|
// }
|
||||||
|
|
||||||
|
public void upload(@NonNull String url, @NonNull File file, final String contentType) {
|
||||||
|
|
||||||
|
MediaType mediaType = MediaType.parse(contentType);
|
||||||
|
RequestBody body =
|
||||||
|
RequestBody.create(mediaType, file);
|
||||||
|
|
||||||
|
Disposable d = HttpManager.getApi(appComponent, CommonService.class)
|
||||||
|
.upload(url, body)
|
||||||
|
.subscribeOn(Schedulers.io())
|
||||||
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
.subscribe(b -> {
|
||||||
|
LogHelper.e("=============>上传完成:::" + b);
|
||||||
|
if (null != listener)
|
||||||
|
listener.onFinishDownload();
|
||||||
|
}, err -> {
|
||||||
|
LogHelper.e("=============>", err);
|
||||||
|
if (null != listener)
|
||||||
|
listener.onFail(err.getMessage());
|
||||||
|
});
|
||||||
|
compositeDisposable.add(d);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将输入流写入文件
|
||||||
|
*
|
||||||
|
* @param inputString 文件流
|
||||||
|
* @param filePath 文件地址
|
||||||
|
*/
|
||||||
|
private void writeFile(InputStream inputString, String filePath) {
|
||||||
|
|
||||||
|
File file = new File(filePath);
|
||||||
|
if (file.exists()) {
|
||||||
|
file.delete();
|
||||||
|
}
|
||||||
|
|
||||||
|
FileOutputStream fos = null;
|
||||||
|
try {
|
||||||
|
fos = new FileOutputStream(file);
|
||||||
|
|
||||||
|
byte[] b = new byte[1024];
|
||||||
|
|
||||||
|
int len;
|
||||||
|
while ((len = inputString.read(b)) != -1) {
|
||||||
|
fos.write(b, 0, len);
|
||||||
|
}
|
||||||
|
inputString.close();
|
||||||
|
fos.close();
|
||||||
|
|
||||||
|
} catch (FileNotFoundException e) {
|
||||||
|
thisListener.onFail("FileNotFoundException");
|
||||||
|
} catch (IOException e) {
|
||||||
|
thisListener.onFail("IOException");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private DownloadListener listener;
|
||||||
|
|
||||||
|
private final DownloadListener thisListener = new DownloadListener() {
|
||||||
|
@Override
|
||||||
|
public void onStartDownload() {
|
||||||
|
if (null != listener)
|
||||||
|
listener.onStartDownload();
|
||||||
|
LogHelper.e("=========开始下载");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onProgress(int progress) {
|
||||||
|
if (null != listener)
|
||||||
|
listener.onProgress(progress);
|
||||||
|
LogHelper.e("=========下载进度" + progress);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFinishDownload() {
|
||||||
|
|
||||||
|
if (null != listener)
|
||||||
|
listener.onFinishDownload();
|
||||||
|
LogHelper.e("=========下载完成");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFail(String errorInfo) {
|
||||||
|
if (null != listener)
|
||||||
|
listener.onFail(errorInfo);
|
||||||
|
LogHelper.e("=========下载失败" + errorInfo);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
public void setListener(DownloadListener listener) {
|
||||||
|
this.listener = listener;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,48 @@
|
|||||||
|
package com.xuqm.base.model;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通用的HttpResult封装,app应该根据接口情况定制化配置
|
||||||
|
*
|
||||||
|
* @param <T>
|
||||||
|
*/
|
||||||
|
public class HttpResult<T> {
|
||||||
|
private String status;
|
||||||
|
private String maxNumber;
|
||||||
|
private T data;
|
||||||
|
|
||||||
|
public String getStatus() {
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setStatus(String status) {
|
||||||
|
this.status = status;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getMaxNumber() {
|
||||||
|
return maxNumber;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMaxNumber(String maxNumber) {
|
||||||
|
this.maxNumber = maxNumber;
|
||||||
|
}
|
||||||
|
|
||||||
|
public T getData() {
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setData(T data) {
|
||||||
|
this.data = data;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@NonNull
|
||||||
|
public String toString() {
|
||||||
|
return "HttpResult{" +
|
||||||
|
"status='" + status + '\'' +
|
||||||
|
", maxNumber='" + maxNumber + '\'' +
|
||||||
|
", data=" + data +
|
||||||
|
'}';
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,38 @@
|
|||||||
|
package com.xuqm.base.model;
|
||||||
|
|
||||||
|
public class KeyValueData<K, V> {
|
||||||
|
private K key;
|
||||||
|
private V value;
|
||||||
|
|
||||||
|
public KeyValueData() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public KeyValueData(K key, V value) {
|
||||||
|
this.key = key;
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public K getKey() {
|
||||||
|
return key;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setKey(K key) {
|
||||||
|
this.key = key;
|
||||||
|
}
|
||||||
|
|
||||||
|
public V getValue() {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setValue(V value) {
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "KeyValueData{" +
|
||||||
|
"key=" + key +
|
||||||
|
", value=" + value +
|
||||||
|
'}';
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,21 @@
|
|||||||
|
package com.xuqm.base.repository;
|
||||||
|
|
||||||
|
import io.reactivex.Observable;
|
||||||
|
import okhttp3.RequestBody;
|
||||||
|
import okhttp3.ResponseBody;
|
||||||
|
import retrofit2.http.Body;
|
||||||
|
import retrofit2.http.GET;
|
||||||
|
import retrofit2.http.POST;
|
||||||
|
import retrofit2.http.PUT;
|
||||||
|
import retrofit2.http.Streaming;
|
||||||
|
import retrofit2.http.Url;
|
||||||
|
|
||||||
|
public interface CommonService {
|
||||||
|
@Streaming
|
||||||
|
@GET
|
||||||
|
Observable<ResponseBody> download(@Url String url);
|
||||||
|
|
||||||
|
@PUT
|
||||||
|
@Streaming
|
||||||
|
Observable<ResponseBody> upload(@Url String url, @Body RequestBody body);
|
||||||
|
}
|
||||||
@ -0,0 +1,382 @@
|
|||||||
|
package com.xuqm.base.ui;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.graphics.Typeface;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.view.MotionEvent;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.WindowManager;
|
||||||
|
import android.view.inputmethod.InputMethodManager;
|
||||||
|
import android.widget.EditText;
|
||||||
|
|
||||||
|
import androidx.annotation.ColorRes;
|
||||||
|
import androidx.annotation.DrawableRes;
|
||||||
|
import androidx.annotation.LayoutRes;
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.annotation.StringRes;
|
||||||
|
import androidx.appcompat.app.AppCompatActivity;
|
||||||
|
import androidx.databinding.DataBindingUtil;
|
||||||
|
import androidx.databinding.ViewDataBinding;
|
||||||
|
|
||||||
|
import com.gyf.immersionbar.BarHide;
|
||||||
|
import com.gyf.immersionbar.ImmersionBar;
|
||||||
|
import com.xuqm.base.R;
|
||||||
|
import com.xuqm.base.common.AppManager;
|
||||||
|
import com.xuqm.base.databinding.ActivityBaseBinding;
|
||||||
|
import com.xuqm.base.ui.callback.UiCallback;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public abstract class BaseActivity<V extends ViewDataBinding> extends AppCompatActivity implements UiCallback {
|
||||||
|
|
||||||
|
protected String TAG = this.getClass().getSimpleName();
|
||||||
|
protected Activity mContext;
|
||||||
|
private V binding;
|
||||||
|
|
||||||
|
private ActivityBaseBinding baseBinding;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
AppManager.getInstance().pushActivity(this);
|
||||||
|
getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN);
|
||||||
|
mContext = this;
|
||||||
|
editText.clear();
|
||||||
|
views.clear();
|
||||||
|
if (getLayoutId() == 0) {//没有layout的时候,act自己写布局
|
||||||
|
ImmersionBar.with(this)
|
||||||
|
.init();
|
||||||
|
setContentView();
|
||||||
|
} else if (showStatus() != -1) {//没有toolbar但是有状态栏的情况
|
||||||
|
bindUi(getLayoutId());
|
||||||
|
ImmersionBar.with(this)
|
||||||
|
.titleBar(binding.getRoot()) //指定标题栏view
|
||||||
|
.statusBarColor(showStatus())
|
||||||
|
.autoStatusBarDarkModeEnable(true)
|
||||||
|
.init();
|
||||||
|
} else if (transparentStatusBar()) {//透明状态栏,但是显示状态栏的内容
|
||||||
|
bindUi(getLayoutId());
|
||||||
|
ImmersionBar.with(this).transparentBar()
|
||||||
|
.titleBar(binding.getRoot().findViewWithTag("top_status"))
|
||||||
|
.statusBarDarkFont(true)
|
||||||
|
// .hideBar(BarHide.FLAG_HIDE_NAVIGATION_BAR)
|
||||||
|
.init();
|
||||||
|
} else if (fullscreen()) {//全屏,什么都不显示
|
||||||
|
bindUi(getLayoutId());
|
||||||
|
ImmersionBar.with(this)
|
||||||
|
.fullScreen(true)
|
||||||
|
.statusBarDarkFont(true)
|
||||||
|
// .hideBar(BarHide.FLAG_HIDE_NAVIGATION_BAR)
|
||||||
|
.init();
|
||||||
|
} else {//使用base提供的toolbar
|
||||||
|
baseBinding = DataBindingUtil.setContentView(mContext, R.layout.activity_base);
|
||||||
|
ImmersionBar.with(this)
|
||||||
|
// .hideBar(BarHide.FLAG_HIDE_NAVIGATION_BAR)
|
||||||
|
.statusBarColor(R.color.white)
|
||||||
|
.statusBarDarkFont(true)
|
||||||
|
.titleBar(baseBinding.baseToolbar) //指定标题栏view
|
||||||
|
.init();
|
||||||
|
binding = DataBindingUtil.inflate(getLayoutInflater(), getLayoutId(), baseBinding.activityRootView, true);
|
||||||
|
getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR);
|
||||||
|
}
|
||||||
|
if (null != baseBinding) {
|
||||||
|
baseBinding.baseToolbar.backBtnPressed(this::backBtnPressed);
|
||||||
|
baseBinding.baseToolbar.getTitleView().setTypeface(Typeface.createFromAsset(this.getAssets(), "fonts/Montserrat-Bold.ttf"));
|
||||||
|
}
|
||||||
|
initView(savedInstanceState);
|
||||||
|
initData();
|
||||||
|
lateInitView();
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public V getBinding() {
|
||||||
|
return binding;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ActivityBaseBinding getBaseBinding() {
|
||||||
|
return baseBinding;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 导航栏展示的内容
|
||||||
|
*
|
||||||
|
* @param titleId 标题
|
||||||
|
*/
|
||||||
|
public void setTitleText(@StringRes int titleId) {
|
||||||
|
setTitleText(getText(titleId));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 导航栏展示的内容
|
||||||
|
*
|
||||||
|
* @param title 标题
|
||||||
|
*/
|
||||||
|
public void setTitleText(CharSequence title) {
|
||||||
|
if (null == baseBinding) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
baseBinding.baseToolbar.setTitle(title);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 导航栏右上角按钮
|
||||||
|
*
|
||||||
|
* @param title 标题
|
||||||
|
*/
|
||||||
|
public void setConfirmText(CharSequence title, View.OnClickListener listener) {
|
||||||
|
if (null == baseBinding) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
baseBinding.baseToolbar.getConfirmBtn().setVisibility(View.VISIBLE);
|
||||||
|
baseBinding.baseToolbar.getConfirmBtn().setText(title);
|
||||||
|
baseBinding.baseToolbar.getConfirmBtn().setOnClickListener(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置标题颜色
|
||||||
|
*
|
||||||
|
* @param color 标题颜色
|
||||||
|
*/
|
||||||
|
public void setTextColor(int color) {
|
||||||
|
if (null == baseBinding) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
baseBinding.baseToolbar.setTextColor(color);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置返回图标颜色
|
||||||
|
*
|
||||||
|
* @param iconTintColor 返回图标颜色
|
||||||
|
*/
|
||||||
|
public void setIconTintColor(int iconTintColor) {
|
||||||
|
if (null == baseBinding) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
baseBinding.baseToolbar.setIconTintColor(iconTintColor);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setIconDraw(@DrawableRes int resId) {
|
||||||
|
if (null == baseBinding) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
baseBinding.baseToolbar.getBackBtn().setImageResource(resId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否展示返回按钮
|
||||||
|
*
|
||||||
|
* @param showBack 是否展示返回按钮
|
||||||
|
*/
|
||||||
|
public void showBack(boolean showBack) {
|
||||||
|
if (null == baseBinding) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
baseBinding.baseToolbar.setShowBack(showBack);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否显示导航栏下面的线
|
||||||
|
*
|
||||||
|
* @param showLine 是否显示导航栏下面的线
|
||||||
|
*/
|
||||||
|
public void showLine(boolean showLine) {
|
||||||
|
if (null == baseBinding) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
baseBinding.baseToolbar.setShowLine(showLine);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void backBtnPressed() {
|
||||||
|
finish();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected void bindUi(@LayoutRes int layoutResID) {
|
||||||
|
binding = DataBindingUtil.setContentView(mContext, layoutResID);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 如果不提供layoutId{@link #getLayoutId()} 则可以重写这个方法自己布局
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void setContentView() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void initView(Bundle savedInstanceState) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void initData() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 这个init方法,在initData之后执行,有一些view相关的设置,需要数据信息
|
||||||
|
*/
|
||||||
|
public void lateInitView() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否需要展示statusBar,默认为false,使用默认布局展示toolbar
|
||||||
|
* false的话,可以自定义toolbar
|
||||||
|
*
|
||||||
|
* @return statusBar 的颜色
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
@ColorRes
|
||||||
|
public int showStatus() {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 如果返回true,则状态栏变成透明状态,但是状态栏的文字还显示
|
||||||
|
* 使用透明状态栏 布局必须指定一个 android:tag="top_status"
|
||||||
|
* 最好是第一个子元素指定,可以自动设置padding
|
||||||
|
*
|
||||||
|
* @return 是否需要设置状态栏为透明状态
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public boolean transparentStatusBar() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是不是黑色主题
|
||||||
|
*
|
||||||
|
* @return 全屏展示
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public boolean fullscreen() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onDestroy() {
|
||||||
|
super.onDestroy();
|
||||||
|
AppManager.getInstance().popActivity(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 点击空白处软键盘是否消失
|
||||||
|
*/
|
||||||
|
protected boolean canTouch = true;
|
||||||
|
/**
|
||||||
|
* 指定控件,点击软键盘不消失,需要addView相关控件
|
||||||
|
*/
|
||||||
|
protected boolean viewCanTouch = true;
|
||||||
|
/**
|
||||||
|
* editText注册-不在这里注册一下,软件盘消失时光标还会闪烁
|
||||||
|
*/
|
||||||
|
private final List<EditText> editText = new ArrayList<>();
|
||||||
|
/**
|
||||||
|
* 指定控件,点击软键盘不消失
|
||||||
|
*/
|
||||||
|
private final List<View> views = new ArrayList<>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* EditText与软键盘的控制
|
||||||
|
* 如果点击空白处(非EditText),关闭软键盘,隐藏光标
|
||||||
|
*
|
||||||
|
* @param editText
|
||||||
|
*/
|
||||||
|
public void addEditText(EditText... editText) {
|
||||||
|
this.editText.addAll(Arrays.asList(editText));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 部分页面存在点击按钮,软键盘不取消的需求,把对应view添加到这里就好了
|
||||||
|
*
|
||||||
|
* @param view
|
||||||
|
*/
|
||||||
|
public void addView(View view) {
|
||||||
|
this.views.add(view);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean dispatchTouchEvent(MotionEvent ev) {
|
||||||
|
|
||||||
|
for (View v : views) {
|
||||||
|
int[] leftTop = {0, 0};
|
||||||
|
//获取输入框当前的location位置
|
||||||
|
v.getLocationInWindow(leftTop);
|
||||||
|
int left = leftTop[0];
|
||||||
|
int top = leftTop[1];
|
||||||
|
int bottom = top + v.getHeight();
|
||||||
|
int right = left + v.getWidth();
|
||||||
|
if ((ev.getX() > left && ev.getX() < right
|
||||||
|
&& ev.getY() > top && ev.getY() < bottom
|
||||||
|
&& v.getVisibility() == View.VISIBLE)) {
|
||||||
|
viewCanTouch = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!canTouch || !viewCanTouch) {
|
||||||
|
viewCanTouch = true;
|
||||||
|
return super.dispatchTouchEvent(ev);
|
||||||
|
}
|
||||||
|
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
|
||||||
|
View v = getCurrentFocus();
|
||||||
|
if (isShouldHideInput(v, ev)) {
|
||||||
|
hideKeyboard(v);
|
||||||
|
}
|
||||||
|
return super.dispatchTouchEvent(ev);
|
||||||
|
}
|
||||||
|
// 必不可少,否则所有的组件都不会有TouchEvent了
|
||||||
|
if (getWindow().superDispatchTouchEvent(ev)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return onTouchEvent(ev);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 强制隐藏键盘
|
||||||
|
*
|
||||||
|
* @param v
|
||||||
|
*/
|
||||||
|
public void hideKeyboard(View v) {
|
||||||
|
InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
|
||||||
|
if (null == v || null == v.getWindowToken()) {
|
||||||
|
} else {
|
||||||
|
imm.hideSoftInputFromWindow(v.getWindowToken(), 0);
|
||||||
|
this.onHideKeyboard();
|
||||||
|
for (EditText et : editText) {
|
||||||
|
et.clearFocus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onHideKeyboard() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isShouldHideInput(View v, MotionEvent event) {
|
||||||
|
if (v != null && (v instanceof EditText)) {
|
||||||
|
int[] leftTop = {0, 0};
|
||||||
|
//获取输入框当前的location位置
|
||||||
|
v.getLocationInWindow(leftTop);
|
||||||
|
int left = leftTop[0];
|
||||||
|
int top = leftTop[1];
|
||||||
|
int bottom = top + v.getHeight();
|
||||||
|
int right = left + v.getWidth();
|
||||||
|
if ((event.getX() > left && event.getX() < right
|
||||||
|
&& event.getY() > top && event.getY() < bottom)) {
|
||||||
|
// 点击的是输入框区域,保留点击EditText的事件
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,61 @@
|
|||||||
|
package com.xuqm.base.ui;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.view.inputmethod.InputMethodManager;
|
||||||
|
|
||||||
|
import androidx.annotation.LayoutRes;
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.databinding.DataBindingUtil;
|
||||||
|
import androidx.databinding.ViewDataBinding;
|
||||||
|
import androidx.fragment.app.Fragment;
|
||||||
|
|
||||||
|
import com.xuqm.base.common.ToolsHelper;
|
||||||
|
|
||||||
|
public abstract class BaseFragment<V extends ViewDataBinding> extends Fragment {
|
||||||
|
|
||||||
|
private V binding;
|
||||||
|
protected Context mContext;
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
@Override
|
||||||
|
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
||||||
|
binding = DataBindingUtil.inflate(getLayoutInflater(), getLayoutId(), container, false);
|
||||||
|
mContext = getActivity();
|
||||||
|
return binding.getRoot();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
|
||||||
|
super.onActivityCreated(savedInstanceState);
|
||||||
|
initView();
|
||||||
|
initData();
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
protected V getBinding() {
|
||||||
|
return binding;
|
||||||
|
}
|
||||||
|
|
||||||
|
@LayoutRes
|
||||||
|
protected abstract int getLayoutId();
|
||||||
|
|
||||||
|
protected void initView() {
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void initData() {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 收起键盘
|
||||||
|
*/
|
||||||
|
protected void hideSoftInput() {
|
||||||
|
InputMethodManager imm = (InputMethodManager) getActivity().getSystemService(Context.INPUT_METHOD_SERVICE);
|
||||||
|
imm.hideSoftInputFromWindow(getActivity().getWindow().getDecorView().getWindowToken(), 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,161 @@
|
|||||||
|
package com.xuqm.base.ui;
|
||||||
|
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.view.View;
|
||||||
|
|
||||||
|
import androidx.lifecycle.ViewModelProvider;
|
||||||
|
import androidx.paging.PagedList;
|
||||||
|
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||||
|
|
||||||
|
import com.xuqm.base.R;
|
||||||
|
import com.xuqm.base.adapter.BaseItem;
|
||||||
|
import com.xuqm.base.adapter.BasePagedAdapter;
|
||||||
|
import com.xuqm.base.common.RefreshResult;
|
||||||
|
import com.xuqm.base.common.ToolsHelper;
|
||||||
|
import com.xuqm.base.databinding.ActivityBaseListBinding;
|
||||||
|
import com.xuqm.base.view.enu.Status;
|
||||||
|
import com.xuqm.base.viewmodel.BaseListViewModel;
|
||||||
|
import com.xuqm.base.viewmodel.callback.AdapterObserverCallback;
|
||||||
|
import com.xuqm.base.viewmodel.callback.DataObserverCallback;
|
||||||
|
|
||||||
|
import java.lang.reflect.ParameterizedType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 列表页面的activity继承这个,只需要指定一个adapter就可以展示数据了
|
||||||
|
* 例子参考:
|
||||||
|
* MainActivity extends BaseListActivity<User, MainViewModel>
|
||||||
|
*
|
||||||
|
* User需要继承{@link BaseItem}
|
||||||
|
* MainViewModel 需要继承{@link BaseListViewModel}
|
||||||
|
*
|
||||||
|
* @param <T>
|
||||||
|
* @param <VM>
|
||||||
|
*/
|
||||||
|
public abstract class BaseListActivity<T extends BaseItem, VM extends BaseListViewModel<T>>
|
||||||
|
extends BaseActivity<ActivityBaseListBinding> {
|
||||||
|
|
||||||
|
private ParameterizedType parameterizedType = (ParameterizedType) getClass().getGenericSuperclass();
|
||||||
|
private Class<VM> cal = (Class<VM>) parameterizedType.getActualTypeArguments()[1];
|
||||||
|
private VM viewModel;
|
||||||
|
|
||||||
|
private BasePagedAdapter<T> adapter;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getLayoutId() {
|
||||||
|
return R.layout.activity_base_list;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取到viewModel,可以做其它事情,比如item的增删改查等
|
||||||
|
*
|
||||||
|
* @return viewModel
|
||||||
|
*/
|
||||||
|
public VM getViewModel() {
|
||||||
|
return viewModel;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void initView(Bundle savedInstanceState) {
|
||||||
|
getBaseBinding().activityRootView.setBackgroundResource(R.color.bg_window);
|
||||||
|
viewModel = new ViewModelProvider(this).get(cal);
|
||||||
|
adapter = adapter();
|
||||||
|
adapter.setItemClickListener(this::itemClicked);
|
||||||
|
adapter.setItemLongClickListener(this::itemLongClicked);
|
||||||
|
getBinding().baseRecyclerView.setAdapter(adapter);
|
||||||
|
getBinding().baseRecyclerView.setLayoutManager(new LinearLayoutManager(mContext));
|
||||||
|
getBinding().baseRefreshLayout.setOnRefreshListener(() -> viewModel.invalidate());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void initData() {
|
||||||
|
|
||||||
|
/*
|
||||||
|
数据更新更新事件的观察者
|
||||||
|
*/
|
||||||
|
viewModel.observeDataObserver(this, new DataObserverCallback<T>() {
|
||||||
|
@Override
|
||||||
|
public void data(PagedList<T> data) {
|
||||||
|
adapter.submitList(data);//数据加载
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void refreshResult(RefreshResult refreshResult) {
|
||||||
|
refreshFinished(refreshResult);//刷新状态处理
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void loadMoreResult(RefreshResult refreshResult) {
|
||||||
|
loadMoreFinished(refreshResult);//加载更多的处理
|
||||||
|
}
|
||||||
|
});
|
||||||
|
//数据更新处理观察者
|
||||||
|
viewModel.observeAdapterObserver(this, new AdapterObserverCallback() {
|
||||||
|
@Override
|
||||||
|
public void notifyItem(int position, Object payload) {
|
||||||
|
adapter.notifyItemChanged(position, payload);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void removeItem(int position) {
|
||||||
|
adapter.notifyItemRemoved(position);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 如果需要对item的点击事件做处理,直接重写这个方法就可以了
|
||||||
|
*
|
||||||
|
* @param view view
|
||||||
|
* @param item item
|
||||||
|
* @param position position
|
||||||
|
*/
|
||||||
|
public void itemClicked(View view, T item, int position) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 如果需要对item的长按事件做处理,直接重写这个方法就可以了
|
||||||
|
*
|
||||||
|
* @param view view
|
||||||
|
* @param item item
|
||||||
|
* @param position position
|
||||||
|
* @return true
|
||||||
|
*/
|
||||||
|
public boolean itemLongClicked(View view, T item, int position) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void refreshFinished(RefreshResult result) {
|
||||||
|
getBinding().baseRefreshLayout.setRefreshing(false);
|
||||||
|
|
||||||
|
if (result == RefreshResult.SUCCEED)
|
||||||
|
getBinding().baseEmptyView.setStatus(Status.DISMISS);
|
||||||
|
else if (result == RefreshResult.FAILED)
|
||||||
|
getBinding().baseEmptyView.setStatus(Status.LOAD_FAILED);
|
||||||
|
else if (result == RefreshResult.NO_DATA)
|
||||||
|
getBinding().baseEmptyView.setStatus(Status.NO_DATA);
|
||||||
|
else if (result == RefreshResult.NO_MORE) {
|
||||||
|
getBinding().baseEmptyView.setStatus(Status.DISMISS);
|
||||||
|
ToolsHelper.snack(getBinding().baseEmptyView, "All loads completed");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void loadMoreFinished(RefreshResult result) {
|
||||||
|
// if (result == RefreshResult.SUCCEED) {
|
||||||
|
// } else if (result == RefreshResult.FAILED) {
|
||||||
|
// } else
|
||||||
|
if (result == RefreshResult.NO_MORE) {
|
||||||
|
ToolsHelper.snack(getBinding().baseEmptyView, "All loads completed");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 需要指定一个adapter,item只有一种类型的使用{@link com.xuqm.base.adapter.CommonPagedAdapter}
|
||||||
|
* 需要指定一个adapter,item有多种类型的使用{@link com.xuqm.base.adapter.BasePagedAdapter}
|
||||||
|
*
|
||||||
|
* @return 自定义的adapter
|
||||||
|
*/
|
||||||
|
public abstract BasePagedAdapter<T> adapter();
|
||||||
|
}
|
||||||
@ -0,0 +1,174 @@
|
|||||||
|
package com.xuqm.base.ui;
|
||||||
|
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.view.View;
|
||||||
|
|
||||||
|
import androidx.annotation.LayoutRes;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.lifecycle.ViewModelProvider;
|
||||||
|
import androidx.paging.PagedList;
|
||||||
|
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||||
|
|
||||||
|
import com.google.android.material.appbar.AppBarLayout;
|
||||||
|
import com.xuqm.base.R;
|
||||||
|
import com.xuqm.base.adapter.BaseItem;
|
||||||
|
import com.xuqm.base.adapter.BasePagedAdapter;
|
||||||
|
import com.xuqm.base.common.RefreshResult;
|
||||||
|
import com.xuqm.base.common.ToolsHelper;
|
||||||
|
import com.xuqm.base.databinding.ActivityBaseListAppBarBinding;
|
||||||
|
import com.xuqm.base.view.enu.Status;
|
||||||
|
import com.xuqm.base.viewmodel.BaseListViewModel;
|
||||||
|
import com.xuqm.base.viewmodel.callback.AdapterObserverCallback;
|
||||||
|
import com.xuqm.base.viewmodel.callback.DataObserverCallback;
|
||||||
|
|
||||||
|
import java.lang.reflect.ParameterizedType;
|
||||||
|
|
||||||
|
public abstract class BaseListAppBarFragment<T extends BaseItem, VM extends BaseListViewModel<T>> extends BaseFragment<ActivityBaseListAppBarBinding> {
|
||||||
|
|
||||||
|
private ParameterizedType parameterizedType = (ParameterizedType) getClass().getGenericSuperclass();
|
||||||
|
private Class<VM> cal = (Class<VM>) parameterizedType.getActualTypeArguments()[1];
|
||||||
|
private VM viewModel;
|
||||||
|
|
||||||
|
private BasePagedAdapter<T> adapter;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getLayoutId() {
|
||||||
|
return R.layout.activity_base_list_app_bar;
|
||||||
|
}
|
||||||
|
|
||||||
|
@LayoutRes
|
||||||
|
protected int getAppBarView() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
|
||||||
|
if (0 != getAppBarView()) {
|
||||||
|
getLayoutInflater().inflate(getAppBarView(), getBinding().appBarLayout,true);
|
||||||
|
}
|
||||||
|
super.onActivityCreated(savedInstanceState);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取到viewModel,可以做其它事情,比如item的增删改查等
|
||||||
|
*
|
||||||
|
* @return viewModel
|
||||||
|
*/
|
||||||
|
public VM getViewModel() {
|
||||||
|
return viewModel;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void initView() {
|
||||||
|
|
||||||
|
viewModel = new ViewModelProvider(this).get(cal);
|
||||||
|
adapter = adapter();
|
||||||
|
adapter.setItemClickListener(this::itemClicked);
|
||||||
|
adapter.setItemLongClickListener(this::itemLongClicked);
|
||||||
|
getBinding().baseRecyclerView.setAdapter(adapter);
|
||||||
|
getBinding().baseRecyclerView.setLayoutManager(new LinearLayoutManager(mContext));
|
||||||
|
getBinding().baseRefreshLayout.setOnRefreshListener(() -> viewModel.invalidate());
|
||||||
|
|
||||||
|
getBinding().appBarLayout.addOnOffsetChangedListener((appBarLayout, verticalOffset) -> {
|
||||||
|
if (verticalOffset >= 0) {
|
||||||
|
getBinding().baseRefreshLayout.setEnabled(true);
|
||||||
|
} else {
|
||||||
|
getBinding().baseRefreshLayout.setEnabled(false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void initData() {
|
||||||
|
|
||||||
|
/*
|
||||||
|
数据更新更新事件的观察者
|
||||||
|
*/
|
||||||
|
viewModel.observeDataObserver(this, new DataObserverCallback<T>() {
|
||||||
|
@Override
|
||||||
|
public void data(PagedList<T> data) {
|
||||||
|
adapter.submitList(data);//数据加载
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void refreshResult(RefreshResult refreshResult) {
|
||||||
|
refreshFinished(refreshResult);//刷新状态处理
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void loadMoreResult(RefreshResult refreshResult) {
|
||||||
|
loadMoreFinished(refreshResult);//加载更多的处理
|
||||||
|
}
|
||||||
|
});
|
||||||
|
//数据更新处理观察者
|
||||||
|
viewModel.observeAdapterObserver(this, new AdapterObserverCallback() {
|
||||||
|
@Override
|
||||||
|
public void notifyItem(int position, Object payload) {
|
||||||
|
adapter.notifyItemChanged(position, payload);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void removeItem(int position) {
|
||||||
|
adapter.notifyItemRemoved(position);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 如果需要对item的点击事件做处理,直接重写这个方法就可以了
|
||||||
|
*
|
||||||
|
* @param view view
|
||||||
|
* @param item item
|
||||||
|
* @param position position
|
||||||
|
*/
|
||||||
|
public void itemClicked(View view, T item, int position) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 如果需要对item的长按事件做处理,直接重写这个方法就可以了
|
||||||
|
*
|
||||||
|
* @param view view
|
||||||
|
* @param item item
|
||||||
|
* @param position position
|
||||||
|
* @return true
|
||||||
|
*/
|
||||||
|
public boolean itemLongClicked(View view, T item, int position) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void refreshFinished(RefreshResult result) {
|
||||||
|
getBinding().baseRefreshLayout.setRefreshing(false);
|
||||||
|
|
||||||
|
if (result == RefreshResult.SUCCEED)
|
||||||
|
getBinding().baseEmptyView.setStatus(Status.DISMISS);
|
||||||
|
else if (result == RefreshResult.FAILED)
|
||||||
|
getBinding().baseEmptyView.setStatus(Status.LOAD_FAILED);
|
||||||
|
else if (result == RefreshResult.NO_DATA)
|
||||||
|
getBinding().baseEmptyView.setStatus(Status.NO_DATA);
|
||||||
|
else if (result == RefreshResult.NO_MORE) {
|
||||||
|
getBinding().baseEmptyView.setStatus(Status.DISMISS);
|
||||||
|
ToolsHelper.snack(getBinding().baseEmptyView, "All loads completed");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void loadMoreFinished(RefreshResult result) {
|
||||||
|
// if (result == RefreshResult.SUCCEED) {
|
||||||
|
// } else if (result == RefreshResult.FAILED) {
|
||||||
|
// } else
|
||||||
|
if (result == RefreshResult.NO_MORE) {
|
||||||
|
ToolsHelper.snack(getBinding().baseEmptyView, "All loads completed");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 需要指定一个adapter,item只有一种类型的使用{@link com.xuqm.base.adapter.CommonPagedAdapter}
|
||||||
|
* 需要指定一个adapter,item有多种类型的使用{@link BasePagedAdapter}
|
||||||
|
*
|
||||||
|
* @return 自定义的adapter
|
||||||
|
*/
|
||||||
|
public abstract BasePagedAdapter<T> adapter();
|
||||||
|
}
|
||||||
@ -0,0 +1,174 @@
|
|||||||
|
package com.xuqm.base.ui;
|
||||||
|
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.view.View;
|
||||||
|
|
||||||
|
import androidx.databinding.ViewDataBinding;
|
||||||
|
import androidx.lifecycle.ViewModelProvider;
|
||||||
|
import androidx.paging.PagedList;
|
||||||
|
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||||
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
|
||||||
|
|
||||||
|
import com.xuqm.base.R;
|
||||||
|
import com.xuqm.base.adapter.BaseItem;
|
||||||
|
import com.xuqm.base.adapter.BasePagedAdapter;
|
||||||
|
import com.xuqm.base.common.RefreshResult;
|
||||||
|
import com.xuqm.base.common.ToolsHelper;
|
||||||
|
import com.xuqm.base.view.EmptyView;
|
||||||
|
import com.xuqm.base.view.enu.Status;
|
||||||
|
import com.xuqm.base.viewmodel.BaseListViewModel;
|
||||||
|
import com.xuqm.base.viewmodel.callback.AdapterObserverCallback;
|
||||||
|
import com.xuqm.base.viewmodel.callback.DataObserverCallback;
|
||||||
|
|
||||||
|
import java.lang.reflect.ParameterizedType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 列表页面的activity继承这个,只需要指定一个adapter就可以展示数据了
|
||||||
|
* 例子参考:
|
||||||
|
* MainActivity extends BaseListActivity<User, MainViewModel>
|
||||||
|
* <p>
|
||||||
|
* User需要继承{@link BaseItem}
|
||||||
|
* MainViewModel 需要继承{@link BaseListViewModel}
|
||||||
|
*
|
||||||
|
* @param <T>
|
||||||
|
* @param <VM>
|
||||||
|
*/
|
||||||
|
public abstract class BaseListFormLayoutActivity<T extends BaseItem, VM extends BaseListViewModel<T>, V extends ViewDataBinding>
|
||||||
|
extends BaseActivity<V> {
|
||||||
|
|
||||||
|
private ParameterizedType parameterizedType = (ParameterizedType) getClass().getGenericSuperclass();
|
||||||
|
private Class<VM> cal = (Class<VM>) parameterizedType.getActualTypeArguments()[1];
|
||||||
|
private VM viewModel;
|
||||||
|
|
||||||
|
private BasePagedAdapter<T> adapter;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getLayoutId() {
|
||||||
|
return R.layout.activity_base_list;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取到viewModel,可以做其它事情,比如item的增删改查等
|
||||||
|
*
|
||||||
|
* @return viewModel
|
||||||
|
*/
|
||||||
|
public VM getViewModel() {
|
||||||
|
return viewModel;
|
||||||
|
}
|
||||||
|
public BasePagedAdapter<T> getAdapter() {
|
||||||
|
return adapter;
|
||||||
|
}
|
||||||
|
|
||||||
|
public RecyclerView recyclerView;
|
||||||
|
private SwipeRefreshLayout swipeRefreshLayout;
|
||||||
|
private EmptyView baseEmptyView;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void initView(Bundle savedInstanceState) {
|
||||||
|
viewModel = new ViewModelProvider(this).get(cal);
|
||||||
|
adapter = adapter();
|
||||||
|
adapter.setItemClickListener(this::itemClicked);
|
||||||
|
adapter.setItemLongClickListener(this::itemLongClicked);
|
||||||
|
recyclerView = findViewById(R.id.baseRecyclerView);
|
||||||
|
swipeRefreshLayout = findViewById(R.id.baseRefreshLayout);
|
||||||
|
baseEmptyView = findViewById(R.id.baseEmptyView);
|
||||||
|
recyclerView.setAdapter(adapter);
|
||||||
|
recyclerView.setLayoutManager(new LinearLayoutManager(mContext));
|
||||||
|
swipeRefreshLayout.setOnRefreshListener(() -> viewModel.invalidate());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void initData() {
|
||||||
|
|
||||||
|
/*
|
||||||
|
数据更新更新事件的观察者
|
||||||
|
*/
|
||||||
|
viewModel.observeDataObserver(this, new DataObserverCallback<T>() {
|
||||||
|
@Override
|
||||||
|
public void data(PagedList<T> data) {
|
||||||
|
adapter.submitList(data);//数据加载
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void refreshResult(RefreshResult refreshResult) {
|
||||||
|
refreshFinished(refreshResult);//刷新状态处理
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void loadMoreResult(RefreshResult refreshResult) {
|
||||||
|
loadMoreFinished(refreshResult);//加载更多的处理
|
||||||
|
}
|
||||||
|
});
|
||||||
|
//数据更新处理观察者
|
||||||
|
viewModel.observeAdapterObserver(this, new AdapterObserverCallback() {
|
||||||
|
@Override
|
||||||
|
public void notifyItem(int position, Object payload) {
|
||||||
|
adapter.notifyItemChanged(position, payload);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void removeItem(int position) {
|
||||||
|
// adapter.notifyItemRemoved(position);
|
||||||
|
adapter.notifyItemRangeRemoved(position,adapter.getItemCount()-1);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 如果需要对item的点击事件做处理,直接重写这个方法就可以了
|
||||||
|
*
|
||||||
|
* @param view view
|
||||||
|
* @param item item
|
||||||
|
* @param position position
|
||||||
|
*/
|
||||||
|
public void itemClicked(View view, T item, int position) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 如果需要对item的长按事件做处理,直接重写这个方法就可以了
|
||||||
|
*
|
||||||
|
* @param view view
|
||||||
|
* @param item item
|
||||||
|
* @param position position
|
||||||
|
* @return true
|
||||||
|
*/
|
||||||
|
public boolean itemLongClicked(View view, T item, int position) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void refreshFinished(RefreshResult result) {
|
||||||
|
swipeRefreshLayout.setRefreshing(false);
|
||||||
|
|
||||||
|
if (result == RefreshResult.SUCCEED)
|
||||||
|
baseEmptyView.setStatus(Status.DISMISS);
|
||||||
|
else if (result == RefreshResult.FAILED)
|
||||||
|
baseEmptyView.setStatus(Status.LOAD_FAILED);
|
||||||
|
else if (result == RefreshResult.NO_DATA)
|
||||||
|
baseEmptyView.setStatus(Status.NO_DATA);
|
||||||
|
else if (result == RefreshResult.NO_MORE) {
|
||||||
|
baseEmptyView.setStatus(Status.DISMISS);
|
||||||
|
ToolsHelper.snack(baseEmptyView, "All loads completed");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void loadMoreFinished(RefreshResult result) {
|
||||||
|
// if (result == RefreshResult.SUCCEED) {
|
||||||
|
// } else if (result == RefreshResult.FAILED) {
|
||||||
|
// } else
|
||||||
|
if (result == RefreshResult.NO_MORE) {
|
||||||
|
ToolsHelper.snack(baseEmptyView, "All loads completed");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 需要指定一个adapter,item只有一种类型的使用{@link com.xuqm.base.adapter.CommonPagedAdapter}
|
||||||
|
* 需要指定一个adapter,item有多种类型的使用{@link BasePagedAdapter}
|
||||||
|
*
|
||||||
|
* @return 自定义的adapter
|
||||||
|
*/
|
||||||
|
public abstract BasePagedAdapter<T> adapter();
|
||||||
|
}
|
||||||
@ -0,0 +1,163 @@
|
|||||||
|
package com.xuqm.base.ui;
|
||||||
|
|
||||||
|
import android.view.View;
|
||||||
|
|
||||||
|
import androidx.lifecycle.ViewModelProvider;
|
||||||
|
import androidx.paging.PagedList;
|
||||||
|
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||||
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
|
||||||
|
import com.xuqm.base.R;
|
||||||
|
import com.xuqm.base.adapter.BaseItem;
|
||||||
|
import com.xuqm.base.adapter.BasePagedAdapter;
|
||||||
|
import com.xuqm.base.common.RefreshResult;
|
||||||
|
import com.xuqm.base.common.ToolsHelper;
|
||||||
|
import com.xuqm.base.databinding.ActivityBaseListBinding;
|
||||||
|
import com.xuqm.base.view.enu.Status;
|
||||||
|
import com.xuqm.base.viewmodel.BaseListViewModel;
|
||||||
|
import com.xuqm.base.viewmodel.callback.AdapterObserverCallback;
|
||||||
|
import com.xuqm.base.viewmodel.callback.DataObserverCallback;
|
||||||
|
|
||||||
|
import java.lang.reflect.ParameterizedType;
|
||||||
|
|
||||||
|
public abstract class BaseListFragment<T extends BaseItem, VM extends BaseListViewModel<T>> extends BaseFragment<ActivityBaseListBinding> {
|
||||||
|
|
||||||
|
private ParameterizedType parameterizedType = (ParameterizedType) getClass().getGenericSuperclass();
|
||||||
|
private Class<VM> cal = (Class<VM>) parameterizedType.getActualTypeArguments()[1];
|
||||||
|
protected VM viewModel;
|
||||||
|
|
||||||
|
private BasePagedAdapter<T> adapter;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getLayoutId() {
|
||||||
|
return R.layout.activity_base_list;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取到viewModel,可以做其它事情,比如item的增删改查等
|
||||||
|
*
|
||||||
|
* @return viewModel
|
||||||
|
*/
|
||||||
|
public VM getViewModel() {
|
||||||
|
return viewModel;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void initView() {
|
||||||
|
|
||||||
|
if (getFactory() == null)
|
||||||
|
viewModel = new ViewModelProvider(this).get(cal);
|
||||||
|
else
|
||||||
|
viewModel = new ViewModelProvider(this, getFactory()).get(cal);
|
||||||
|
adapter = adapter();
|
||||||
|
adapter.setItemClickListener(this::itemClicked);
|
||||||
|
adapter.setItemLongClickListener(this::itemLongClicked);
|
||||||
|
getBinding().baseRecyclerView.setAdapter(adapter);
|
||||||
|
getBinding().baseRecyclerView.setLayoutManager(layoutManager());
|
||||||
|
getBinding().baseRefreshLayout.setOnRefreshListener(() -> viewModel.invalidate());
|
||||||
|
}
|
||||||
|
|
||||||
|
protected ViewModelProvider.Factory getFactory() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void initData() {
|
||||||
|
|
||||||
|
/*
|
||||||
|
数据更新更新事件的观察者
|
||||||
|
*/
|
||||||
|
viewModel.observeDataObserver(this, new DataObserverCallback<T>() {
|
||||||
|
@Override
|
||||||
|
public void data(PagedList<T> data) {
|
||||||
|
adapter.submitList(data);//数据加载
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void refreshResult(RefreshResult refreshResult) {
|
||||||
|
refreshFinished(refreshResult);//刷新状态处理
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void loadMoreResult(RefreshResult refreshResult) {
|
||||||
|
loadMoreFinished(refreshResult);//加载更多的处理
|
||||||
|
}
|
||||||
|
});
|
||||||
|
//数据更新处理观察者
|
||||||
|
viewModel.observeAdapterObserver(this, new AdapterObserverCallback() {
|
||||||
|
@Override
|
||||||
|
public void notifyItem(int position, Object payload) {
|
||||||
|
adapter.notifyItemChanged(position, payload);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void removeItem(int position) {
|
||||||
|
adapter.notifyItemRemoved(position);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 如果需要对item的点击事件做处理,直接重写这个方法就可以了
|
||||||
|
*
|
||||||
|
* @param view view
|
||||||
|
* @param item item
|
||||||
|
* @param position position
|
||||||
|
*/
|
||||||
|
public void itemClicked(View view, T item, int position) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 如果需要对item的长按事件做处理,直接重写这个方法就可以了
|
||||||
|
*
|
||||||
|
* @param view view
|
||||||
|
* @param item item
|
||||||
|
* @param position position
|
||||||
|
* @return true
|
||||||
|
*/
|
||||||
|
public boolean itemLongClicked(View view, T item, int position) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void refreshFinished(RefreshResult result) {
|
||||||
|
getBinding().baseRefreshLayout.setRefreshing(false);
|
||||||
|
|
||||||
|
if (result == RefreshResult.SUCCEED)
|
||||||
|
getBinding().baseEmptyView.setStatus(Status.DISMISS);
|
||||||
|
else if (result == RefreshResult.FAILED)
|
||||||
|
getBinding().baseEmptyView.setStatus(Status.LOAD_FAILED);
|
||||||
|
else if (result == RefreshResult.NO_DATA)
|
||||||
|
getBinding().baseEmptyView.setStatus(Status.NO_DATA);
|
||||||
|
else if (result == RefreshResult.NO_MORE) {
|
||||||
|
getBinding().baseEmptyView.setStatus(Status.DISMISS);
|
||||||
|
// ToolsHelper.snack(getBinding().baseEmptyView, "All loads completed");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void loadMoreFinished(RefreshResult result) {
|
||||||
|
// if (result == RefreshResult.SUCCEED) {
|
||||||
|
// } else if (result == RefreshResult.FAILED) {
|
||||||
|
// } else
|
||||||
|
if (result == RefreshResult.NO_MORE) {
|
||||||
|
// ToolsHelper.snack(getBinding().baseEmptyView, "All loads completed");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 需要指定一个adapter,item只有一种类型的使用{@link com.xuqm.base.adapter.CommonPagedAdapter}
|
||||||
|
* 需要指定一个adapter,item有多种类型的使用{@link com.xuqm.base.adapter.BasePagedAdapter}
|
||||||
|
*
|
||||||
|
* @return 自定义的adapter
|
||||||
|
*/
|
||||||
|
public abstract BasePagedAdapter<T> adapter();
|
||||||
|
|
||||||
|
public RecyclerView.LayoutManager layoutManager() {
|
||||||
|
return new LinearLayoutManager(mContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
;
|
||||||
|
}
|
||||||
@ -0,0 +1,185 @@
|
|||||||
|
package com.xuqm.base.ui;
|
||||||
|
|
||||||
|
import android.view.View;
|
||||||
|
|
||||||
|
import androidx.databinding.ViewDataBinding;
|
||||||
|
import androidx.lifecycle.ViewModelProvider;
|
||||||
|
import androidx.paging.PagedList;
|
||||||
|
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||||
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
|
||||||
|
|
||||||
|
import com.xuqm.base.R;
|
||||||
|
import com.xuqm.base.adapter.BaseItem;
|
||||||
|
import com.xuqm.base.adapter.BasePagedAdapter;
|
||||||
|
import com.xuqm.base.common.RefreshResult;
|
||||||
|
import com.xuqm.base.common.ToolsHelper;
|
||||||
|
import com.xuqm.base.view.EmptyView;
|
||||||
|
import com.xuqm.base.view.enu.Status;
|
||||||
|
import com.xuqm.base.viewmodel.BaseListViewModel;
|
||||||
|
import com.xuqm.base.viewmodel.callback.AdapterObserverCallback;
|
||||||
|
import com.xuqm.base.viewmodel.callback.DataObserverCallback;
|
||||||
|
|
||||||
|
import java.lang.reflect.ParameterizedType;
|
||||||
|
|
||||||
|
public abstract class BaseListFromLayoutFragment<T extends BaseItem, VM extends BaseListViewModel<T>, V extends ViewDataBinding> extends BaseFragment<V> {
|
||||||
|
|
||||||
|
private ParameterizedType parameterizedType = (ParameterizedType) getClass().getGenericSuperclass();
|
||||||
|
private Class<VM> cal = (Class<VM>) parameterizedType.getActualTypeArguments()[1];
|
||||||
|
protected VM viewModel;
|
||||||
|
|
||||||
|
private BasePagedAdapter<T> adapter;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getLayoutId() {
|
||||||
|
return R.layout.activity_base_list;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取到viewModel,可以做其它事情,比如item的增删改查等
|
||||||
|
*
|
||||||
|
* @return viewModel
|
||||||
|
*/
|
||||||
|
public VM getViewModel() {
|
||||||
|
return viewModel;
|
||||||
|
}
|
||||||
|
|
||||||
|
public RecyclerView recyclerView;
|
||||||
|
private SwipeRefreshLayout swipeRefreshLayout;
|
||||||
|
private EmptyView baseEmptyView;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void initView() {
|
||||||
|
|
||||||
|
if (getFactory() == null)
|
||||||
|
viewModel = new ViewModelProvider(this).get(cal);
|
||||||
|
else
|
||||||
|
viewModel = new ViewModelProvider(this, getFactory()).get(cal);
|
||||||
|
adapter = adapter();
|
||||||
|
recyclerView = recyclerView();
|
||||||
|
swipeRefreshLayout = swipeRefreshLayout();
|
||||||
|
baseEmptyView = baseEmptyView();
|
||||||
|
adapter.setItemClickListener(this::itemClicked);
|
||||||
|
adapter.setItemLongClickListener(this::itemLongClicked);
|
||||||
|
recyclerView.setLayoutManager(layoutManager());
|
||||||
|
recyclerView.setAdapter(adapter);
|
||||||
|
swipeRefreshLayout.setOnRefreshListener(() -> viewModel.invalidate());
|
||||||
|
}
|
||||||
|
|
||||||
|
protected ViewModelProvider.Factory getFactory() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void initData() {
|
||||||
|
|
||||||
|
/*
|
||||||
|
数据更新更新事件的观察者
|
||||||
|
*/
|
||||||
|
viewModel.observeDataObserver(this, new DataObserverCallback<T>() {
|
||||||
|
@Override
|
||||||
|
public void data(PagedList<T> data) {
|
||||||
|
adapter.submitList(data);//数据加载
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void refreshResult(RefreshResult refreshResult) {
|
||||||
|
refreshFinished(refreshResult);//刷新状态处理
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void loadMoreResult(RefreshResult refreshResult) {
|
||||||
|
loadMoreFinished(refreshResult);//加载更多的处理
|
||||||
|
}
|
||||||
|
});
|
||||||
|
//数据更新处理观察者
|
||||||
|
viewModel.observeAdapterObserver(this, new AdapterObserverCallback() {
|
||||||
|
@Override
|
||||||
|
public void notifyItem(int position, Object payload) {
|
||||||
|
adapter.notifyItemChanged(position, payload);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void removeItem(int position) {
|
||||||
|
adapter.notifyItemRemoved(position);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 如果需要对item的点击事件做处理,直接重写这个方法就可以了
|
||||||
|
*
|
||||||
|
* @param view view
|
||||||
|
* @param item item
|
||||||
|
* @param position position
|
||||||
|
*/
|
||||||
|
public void itemClicked(View view, T item, int position) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 如果需要对item的长按事件做处理,直接重写这个方法就可以了
|
||||||
|
*
|
||||||
|
* @param view view
|
||||||
|
* @param item item
|
||||||
|
* @param position position
|
||||||
|
* @return true
|
||||||
|
*/
|
||||||
|
public boolean itemLongClicked(View view, T item, int position) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void refreshFinished(RefreshResult result) {
|
||||||
|
swipeRefreshLayout.setRefreshing(false);
|
||||||
|
|
||||||
|
if (result == RefreshResult.SUCCEED)
|
||||||
|
baseEmptyView.setStatus(Status.DISMISS);
|
||||||
|
else if (result == RefreshResult.FAILED)
|
||||||
|
baseEmptyView.setStatus(Status.LOAD_FAILED);
|
||||||
|
else if (result == RefreshResult.NO_DATA)
|
||||||
|
baseEmptyView.setStatus(Status.NO_DATA);
|
||||||
|
else if (result == RefreshResult.NO_MORE) {
|
||||||
|
baseEmptyView.setStatus(Status.DISMISS);
|
||||||
|
ToolsHelper.snack(baseEmptyView, "All loads completed");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void loadMoreFinished(RefreshResult result) {
|
||||||
|
// if (result == RefreshResult.SUCCEED) {
|
||||||
|
// } else if (result == RefreshResult.FAILED) {
|
||||||
|
// } else
|
||||||
|
if (result == RefreshResult.NO_MORE) {
|
||||||
|
// ToolsHelper.snack(getBinding().baseEmptyView, "All loads completed");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 需要指定一个adapter,item只有一种类型的使用{@link com.xuqm.base.adapter.CommonPagedAdapter}
|
||||||
|
* 需要指定一个adapter,item有多种类型的使用{@link BasePagedAdapter}
|
||||||
|
*
|
||||||
|
* @return 自定义的adapter
|
||||||
|
*/
|
||||||
|
public abstract BasePagedAdapter<T> adapter();
|
||||||
|
|
||||||
|
public RecyclerView recyclerView() {
|
||||||
|
return getActivity().findViewById(R.id.baseRecyclerView);
|
||||||
|
}
|
||||||
|
|
||||||
|
public SwipeRefreshLayout swipeRefreshLayout() {
|
||||||
|
return getActivity().findViewById(R.id.baseRefreshLayout);
|
||||||
|
}
|
||||||
|
|
||||||
|
public EmptyView baseEmptyView() {
|
||||||
|
return getActivity().findViewById(R.id.baseEmptyView);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public RecyclerView.LayoutManager layoutManager() {
|
||||||
|
return new LinearLayoutManager(mContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
;
|
||||||
|
}
|
||||||
@ -0,0 +1,44 @@
|
|||||||
|
package com.xuqm.base.ui.adapter;
|
||||||
|
|
||||||
|
import android.graphics.Bitmap;
|
||||||
|
import android.graphics.drawable.Drawable;
|
||||||
|
import android.widget.ImageView;
|
||||||
|
|
||||||
|
import androidx.databinding.BindingAdapter;
|
||||||
|
|
||||||
|
import com.bumptech.glide.Glide;
|
||||||
|
import com.xuqm.base.R;
|
||||||
|
import com.xuqm.base.common.ImageHelper;
|
||||||
|
|
||||||
|
public class ImageViewAdapter {
|
||||||
|
@BindingAdapter("android:src")
|
||||||
|
public static void setSrc(ImageView view, Bitmap bitmap) {
|
||||||
|
view.setImageBitmap(bitmap);
|
||||||
|
}
|
||||||
|
|
||||||
|
@BindingAdapter("android:src")
|
||||||
|
public static void setSrc(ImageView view, int resId) {
|
||||||
|
view.setImageResource(resId);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@BindingAdapter("imageUrl")
|
||||||
|
public static void setSrc(ImageView imageView, String url) {
|
||||||
|
Glide.with(imageView.getContext()).load(url)
|
||||||
|
.placeholder(R.drawable.ic_loading)
|
||||||
|
.into(imageView);
|
||||||
|
// ImageHelper.load(imageView,url);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@BindingAdapter({"imageUrl", "placeHolder", "error"})
|
||||||
|
public static void loadImage(ImageView imageView, String url, Drawable holderDrawable, Drawable errorDrawable) {
|
||||||
|
Glide.with(imageView.getContext())
|
||||||
|
.load(url)
|
||||||
|
.placeholder(holderDrawable)
|
||||||
|
.error(errorDrawable)
|
||||||
|
.into(imageView);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,5 @@
|
|||||||
|
package com.xuqm.base.ui.callback;
|
||||||
|
|
||||||
|
public interface ToolBarListener {
|
||||||
|
void back();
|
||||||
|
}
|
||||||
@ -0,0 +1,23 @@
|
|||||||
|
package com.xuqm.base.ui.callback;
|
||||||
|
|
||||||
|
import android.os.Bundle;
|
||||||
|
|
||||||
|
import androidx.annotation.LayoutRes;
|
||||||
|
|
||||||
|
public interface UiCallback {
|
||||||
|
@LayoutRes
|
||||||
|
int getLayoutId();
|
||||||
|
|
||||||
|
void setContentView();
|
||||||
|
|
||||||
|
void initView(Bundle savedInstanceState);
|
||||||
|
|
||||||
|
void initData();
|
||||||
|
|
||||||
|
int showStatus();
|
||||||
|
|
||||||
|
boolean transparentStatusBar();
|
||||||
|
|
||||||
|
boolean fullscreen();
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,100 @@
|
|||||||
|
package com.xuqm.base.view;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.util.AttributeSet;
|
||||||
|
import android.view.View;
|
||||||
|
import android.widget.FrameLayout;
|
||||||
|
import android.widget.LinearLayout;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
import com.xuqm.base.R;
|
||||||
|
import com.xuqm.base.view.enu.Status;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 自定义的view
|
||||||
|
* 展示不同的数据加载状态
|
||||||
|
*/
|
||||||
|
public class EmptyMsgView extends FrameLayout {
|
||||||
|
|
||||||
|
private View contentView;
|
||||||
|
private Status status = Status.DISMISS;
|
||||||
|
|
||||||
|
|
||||||
|
public EmptyMsgView(@NonNull Context context) {
|
||||||
|
super(context);
|
||||||
|
init();
|
||||||
|
}
|
||||||
|
|
||||||
|
public EmptyMsgView(@NonNull Context context, @Nullable AttributeSet attrs) {
|
||||||
|
super(context, attrs);
|
||||||
|
init();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void init() {
|
||||||
|
View.inflate(getContext(), R.layout.empty_msg_view, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onFinishInflate() {
|
||||||
|
super.onFinishInflate();
|
||||||
|
if (getChildCount() > 5) {
|
||||||
|
throw new IllegalStateException("EmptyView can only have one child view");
|
||||||
|
}
|
||||||
|
if (getChildCount() == 5) {
|
||||||
|
contentView = getChildAt(4);
|
||||||
|
}
|
||||||
|
emptyViewLoading = findViewById(R.id.emptyViewLoading);
|
||||||
|
emptyViewNoData = findViewById(R.id.emptyViewNoData);
|
||||||
|
emptyViewLoadFailed = findViewById(R.id.emptyViewLoadFailed);
|
||||||
|
emptyViewNetworkUnavailable = findViewById(R.id.emptyViewNetworkUnavailable);
|
||||||
|
|
||||||
|
setStatus(Status.DISMISS);
|
||||||
|
}
|
||||||
|
|
||||||
|
private LinearLayout emptyViewLoading;
|
||||||
|
private LinearLayout emptyViewNoData;
|
||||||
|
private LinearLayout emptyViewLoadFailed;
|
||||||
|
private LinearLayout emptyViewNetworkUnavailable;
|
||||||
|
|
||||||
|
|
||||||
|
public void setStatus(Status status) {
|
||||||
|
// LogHelper.e(contentView.getVisibility());
|
||||||
|
if (status != this.status) {
|
||||||
|
|
||||||
|
if (status == Status.DISMISS) {
|
||||||
|
if (null != contentView) contentView.setVisibility(VISIBLE);
|
||||||
|
emptyViewLoading.setVisibility(GONE);
|
||||||
|
emptyViewNoData.setVisibility(GONE);
|
||||||
|
emptyViewLoadFailed.setVisibility(GONE);
|
||||||
|
emptyViewNetworkUnavailable.setVisibility(GONE);
|
||||||
|
} else if (status == Status.LOADING) {
|
||||||
|
if (null != contentView) contentView.setVisibility(GONE);
|
||||||
|
emptyViewLoading.setVisibility(VISIBLE);
|
||||||
|
emptyViewNoData.setVisibility(GONE);
|
||||||
|
emptyViewLoadFailed.setVisibility(GONE);
|
||||||
|
emptyViewNetworkUnavailable.setVisibility(GONE);
|
||||||
|
} else if (status == Status.NO_DATA) {
|
||||||
|
if (null != contentView) contentView.setVisibility(GONE);
|
||||||
|
emptyViewLoading.setVisibility(GONE);
|
||||||
|
emptyViewNoData.setVisibility(VISIBLE);
|
||||||
|
emptyViewLoadFailed.setVisibility(GONE);
|
||||||
|
emptyViewNetworkUnavailable.setVisibility(GONE);
|
||||||
|
} else if (status == Status.LOAD_FAILED) {
|
||||||
|
if (null != contentView) contentView.setVisibility(GONE);
|
||||||
|
emptyViewLoading.setVisibility(GONE);
|
||||||
|
emptyViewNoData.setVisibility(GONE);
|
||||||
|
emptyViewLoadFailed.setVisibility(VISIBLE);
|
||||||
|
emptyViewNetworkUnavailable.setVisibility(GONE);
|
||||||
|
} else if (status == Status.NETWORK_UNAVAILABLE) {
|
||||||
|
if (null != contentView) contentView.setVisibility(GONE);
|
||||||
|
emptyViewLoading.setVisibility(GONE);
|
||||||
|
emptyViewNoData.setVisibility(GONE);
|
||||||
|
emptyViewLoadFailed.setVisibility(GONE);
|
||||||
|
emptyViewNetworkUnavailable.setVisibility(VISIBLE);
|
||||||
|
}
|
||||||
|
this.status = status;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,112 @@
|
|||||||
|
package com.xuqm.base.view;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.util.AttributeSet;
|
||||||
|
import android.view.View;
|
||||||
|
import android.widget.FrameLayout;
|
||||||
|
import android.widget.ImageView;
|
||||||
|
import android.widget.LinearLayout;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
import com.xuqm.base.R;
|
||||||
|
import com.xuqm.base.common.ImageHelper;
|
||||||
|
import com.xuqm.base.view.enu.Status;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 自定义的view
|
||||||
|
* 展示不同的数据加载状态
|
||||||
|
*/
|
||||||
|
public class EmptyView extends FrameLayout {
|
||||||
|
|
||||||
|
private View contentView;
|
||||||
|
private Status status = Status.DISMISS;
|
||||||
|
|
||||||
|
|
||||||
|
public EmptyView(@NonNull Context context) {
|
||||||
|
super(context);
|
||||||
|
init();
|
||||||
|
}
|
||||||
|
|
||||||
|
public EmptyView(@NonNull Context context, @Nullable AttributeSet attrs) {
|
||||||
|
super(context, attrs);
|
||||||
|
init();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void init() {
|
||||||
|
View.inflate(getContext(), R.layout.empty_view, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onFinishInflate() {
|
||||||
|
super.onFinishInflate();
|
||||||
|
if (getChildCount() > 5) {
|
||||||
|
throw new IllegalStateException("EmptyView can only have one child view");
|
||||||
|
}
|
||||||
|
if (getChildCount() == 5) {
|
||||||
|
contentView = getChildAt(4);
|
||||||
|
}
|
||||||
|
emptyViewLoading = findViewById(R.id.emptyViewLoading);
|
||||||
|
emptyViewNoData = findViewById(R.id.emptyViewNoData);
|
||||||
|
emptyViewLoadFailed = findViewById(R.id.emptyViewLoadFailed);
|
||||||
|
emptyViewNetworkUnavailable = findViewById(R.id.emptyViewNetworkUnavailable);
|
||||||
|
emptyIv = findViewById(R.id.empty_iv);
|
||||||
|
emptyTv = findViewById(R.id.empty_tv);
|
||||||
|
|
||||||
|
setStatus(Status.DISMISS);
|
||||||
|
}
|
||||||
|
|
||||||
|
private LinearLayout emptyViewLoading;
|
||||||
|
private LinearLayout emptyViewNoData;
|
||||||
|
private LinearLayout emptyViewLoadFailed;
|
||||||
|
private LinearLayout emptyViewNetworkUnavailable;
|
||||||
|
private ImageView emptyIv;
|
||||||
|
private TextView emptyTv;
|
||||||
|
|
||||||
|
|
||||||
|
public void setEmptyView(String content, Object url) {
|
||||||
|
emptyTv.setText(content);
|
||||||
|
ImageHelper.load(emptyIv, url);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setStatus(Status status) {
|
||||||
|
// LogHelper.e(contentView.getVisibility());
|
||||||
|
if (status != this.status) {
|
||||||
|
|
||||||
|
if (status == Status.DISMISS) {
|
||||||
|
if (null != contentView) contentView.setVisibility(VISIBLE);
|
||||||
|
emptyViewLoading.setVisibility(GONE);
|
||||||
|
emptyViewNoData.setVisibility(GONE);
|
||||||
|
emptyViewLoadFailed.setVisibility(GONE);
|
||||||
|
emptyViewNetworkUnavailable.setVisibility(GONE);
|
||||||
|
} else if (status == Status.LOADING) {
|
||||||
|
if (null != contentView) contentView.setVisibility(GONE);
|
||||||
|
emptyViewLoading.setVisibility(VISIBLE);
|
||||||
|
emptyViewNoData.setVisibility(GONE);
|
||||||
|
emptyViewLoadFailed.setVisibility(GONE);
|
||||||
|
emptyViewNetworkUnavailable.setVisibility(GONE);
|
||||||
|
} else if (status == Status.NO_DATA) {
|
||||||
|
if (null != contentView) contentView.setVisibility(GONE);
|
||||||
|
emptyViewLoading.setVisibility(GONE);
|
||||||
|
emptyViewNoData.setVisibility(VISIBLE);
|
||||||
|
emptyViewLoadFailed.setVisibility(GONE);
|
||||||
|
emptyViewNetworkUnavailable.setVisibility(GONE);
|
||||||
|
} else if (status == Status.LOAD_FAILED) {
|
||||||
|
if (null != contentView) contentView.setVisibility(GONE);
|
||||||
|
emptyViewLoading.setVisibility(GONE);
|
||||||
|
emptyViewNoData.setVisibility(GONE);
|
||||||
|
emptyViewLoadFailed.setVisibility(VISIBLE);
|
||||||
|
emptyViewNetworkUnavailable.setVisibility(GONE);
|
||||||
|
} else if (status == Status.NETWORK_UNAVAILABLE) {
|
||||||
|
if (null != contentView) contentView.setVisibility(GONE);
|
||||||
|
emptyViewLoading.setVisibility(GONE);
|
||||||
|
emptyViewNoData.setVisibility(GONE);
|
||||||
|
emptyViewLoadFailed.setVisibility(GONE);
|
||||||
|
emptyViewNetworkUnavailable.setVisibility(VISIBLE);
|
||||||
|
}
|
||||||
|
this.status = status;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,120 @@
|
|||||||
|
package com.xuqm.base.view;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.util.AttributeSet;
|
||||||
|
import android.view.View;
|
||||||
|
import android.widget.RadioGroup;
|
||||||
|
|
||||||
|
public class FlowRadioGroup extends RadioGroup {
|
||||||
|
|
||||||
|
public FlowRadioGroup(Context context) {
|
||||||
|
super(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
public FlowRadioGroup(Context context, AttributeSet attrs) {
|
||||||
|
super(context, attrs);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
||||||
|
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
|
||||||
|
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
|
||||||
|
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
|
||||||
|
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
|
||||||
|
|
||||||
|
//调用ViewGroup的方法,测量子view
|
||||||
|
measureChildren(widthMeasureSpec, heightMeasureSpec);
|
||||||
|
|
||||||
|
//最大的宽
|
||||||
|
int maxWidth = 0;
|
||||||
|
//累计的高
|
||||||
|
int totalHeight = 0;
|
||||||
|
|
||||||
|
//当前这一行的累计行宽
|
||||||
|
int lineWidth = 0;
|
||||||
|
//当前这行的最大行高
|
||||||
|
int maxLineHeight = 0;
|
||||||
|
//用于记录换行前的行宽和行高
|
||||||
|
int oldHeight;
|
||||||
|
int oldWidth;
|
||||||
|
|
||||||
|
int count = getChildCount();
|
||||||
|
//假设 widthMode和heightMode都是AT_MOST
|
||||||
|
for (int i = 0; i < count; i++) {
|
||||||
|
View child = getChildAt(i);
|
||||||
|
MarginLayoutParams params = (MarginLayoutParams) child.getLayoutParams();
|
||||||
|
oldHeight = maxLineHeight;
|
||||||
|
//当前最大宽度
|
||||||
|
oldWidth = maxWidth;
|
||||||
|
|
||||||
|
int deltaX = child.getMeasuredWidth() + params.leftMargin + params.rightMargin;
|
||||||
|
if (lineWidth + deltaX + getPaddingLeft() + getPaddingRight() > widthSize) {//如果折行,height增加
|
||||||
|
//和目前最大的宽度比较,得到最宽。不能加上当前的child的宽,所以用的是oldWidth
|
||||||
|
maxWidth = Math.max(lineWidth, oldWidth);
|
||||||
|
//重置宽度
|
||||||
|
lineWidth = deltaX;
|
||||||
|
//累加高度
|
||||||
|
totalHeight += oldHeight;
|
||||||
|
//重置行高,当前这个View,属于下一行,因此当前最大行高为这个child的高度加上margin
|
||||||
|
maxLineHeight = child.getMeasuredHeight() + params.topMargin + params.bottomMargin;
|
||||||
|
|
||||||
|
} else {
|
||||||
|
//不换行,累加宽度
|
||||||
|
lineWidth += deltaX;
|
||||||
|
//不换行,计算行最高
|
||||||
|
int deltaY = child.getMeasuredHeight() + params.topMargin + params.bottomMargin;
|
||||||
|
maxLineHeight = Math.max(maxLineHeight, deltaY);
|
||||||
|
}
|
||||||
|
if (i == count - 1) {
|
||||||
|
//前面没有加上下一行的搞,如果是最后一行,还要再叠加上最后一行的最高的值
|
||||||
|
totalHeight += maxLineHeight;
|
||||||
|
//计算最后一行和前面的最宽的一行比较
|
||||||
|
maxWidth = Math.max(lineWidth, oldWidth);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//加上当前容器的padding值
|
||||||
|
maxWidth += getPaddingLeft() + getPaddingRight();
|
||||||
|
totalHeight += getPaddingTop() + getPaddingBottom();
|
||||||
|
setMeasuredDimension(widthMode == MeasureSpec.EXACTLY ? widthSize : maxWidth,
|
||||||
|
heightMode == MeasureSpec.EXACTLY ? heightSize : totalHeight);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onLayout(boolean changed, int l, int t, int r, int b) {
|
||||||
|
int count = getChildCount();
|
||||||
|
//pre为前面所有的child的相加后的位置
|
||||||
|
int preLeft = getPaddingLeft();
|
||||||
|
int preTop = getPaddingTop();
|
||||||
|
//记录每一行的最高值
|
||||||
|
int maxHeight = 0;
|
||||||
|
for (int i = 0; i < count; i++) {
|
||||||
|
View child = getChildAt(i);
|
||||||
|
MarginLayoutParams params = (MarginLayoutParams) child.getLayoutParams();
|
||||||
|
//r-l为当前容器的宽度。如果子view的累积宽度大于容器宽度,就换行。
|
||||||
|
if (preLeft + params.leftMargin + child.getMeasuredWidth() + params.rightMargin + getPaddingRight() > (r - l)) {
|
||||||
|
//重置
|
||||||
|
preLeft = getPaddingLeft();
|
||||||
|
//要选择child的height最大的作为设置
|
||||||
|
preTop = preTop + maxHeight;
|
||||||
|
maxHeight = getChildAt(i).getMeasuredHeight() + params.topMargin + params.bottomMargin;
|
||||||
|
} else { //不换行,计算最大高度
|
||||||
|
maxHeight = Math.max(maxHeight, child.getMeasuredHeight() + params.topMargin + params.bottomMargin);
|
||||||
|
}
|
||||||
|
//left坐标
|
||||||
|
int left = preLeft + params.leftMargin;
|
||||||
|
//top坐标
|
||||||
|
int top = preTop + params.topMargin;
|
||||||
|
int right = left + child.getMeasuredWidth();
|
||||||
|
int bottom = top + child.getMeasuredHeight();
|
||||||
|
//为子view布局
|
||||||
|
child.layout(left, top, right, bottom);
|
||||||
|
//计算布局结束后,preLeft的值
|
||||||
|
preLeft += params.leftMargin + child.getMeasuredWidth() + params.rightMargin;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,35 @@
|
|||||||
|
package com.xuqm.base.view
|
||||||
|
|
||||||
|
import android.view.View
|
||||||
|
import androidx.viewpager2.widget.ViewPager2
|
||||||
|
|
||||||
|
class GalleryTransformer : ViewPager2.PageTransformer {
|
||||||
|
companion object {
|
||||||
|
private const val TARGET_ALPHA = 0.5f
|
||||||
|
private const val TARGET_SCALE = 0.9f
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun transformPage(page: View, position: Float) {
|
||||||
|
if (position < -1 || position > 1) {
|
||||||
|
//当前页面左侧以及右侧的页面效果
|
||||||
|
page.alpha = TARGET_ALPHA
|
||||||
|
page.scaleX = TARGET_SCALE
|
||||||
|
page.scaleY = TARGET_SCALE
|
||||||
|
} else {
|
||||||
|
//从不可见变为可见效果
|
||||||
|
|
||||||
|
//透明度效果
|
||||||
|
if (position <= 0) {
|
||||||
|
page.alpha =
|
||||||
|
TARGET_ALPHA + TARGET_ALPHA * (1 + position)
|
||||||
|
} else {
|
||||||
|
page.alpha =
|
||||||
|
TARGET_ALPHA + TARGET_ALPHA * (1 - position)
|
||||||
|
}
|
||||||
|
//缩放效果
|
||||||
|
val scale = Math.max(TARGET_SCALE, 1 - Math.abs(position))
|
||||||
|
page.scaleX = scale
|
||||||
|
page.scaleY = scale
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,69 @@
|
|||||||
|
package com.xuqm.base.view;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.res.TypedArray;
|
||||||
|
import android.util.AttributeSet;
|
||||||
|
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
|
||||||
|
import com.xuqm.base.R;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* max limit-able RecyclerView
|
||||||
|
*/
|
||||||
|
public class MaxLimitRecyclerView extends RecyclerView {
|
||||||
|
private int mMaxHeight;
|
||||||
|
private int mMaxWidth;
|
||||||
|
|
||||||
|
public MaxLimitRecyclerView(Context context) {
|
||||||
|
this(context, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public MaxLimitRecyclerView(Context context, @Nullable AttributeSet attrs) {
|
||||||
|
this(context, attrs, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public MaxLimitRecyclerView(Context context, @Nullable AttributeSet attrs, int defStyle) {
|
||||||
|
super(context, attrs, defStyle);
|
||||||
|
inti(attrs);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void inti(AttributeSet attrs) {
|
||||||
|
if (getContext() != null && attrs != null) {
|
||||||
|
TypedArray typedArray = null;
|
||||||
|
try {
|
||||||
|
typedArray = getContext().obtainStyledAttributes(attrs, R.styleable.MaxLimitRecyclerView);
|
||||||
|
if (typedArray.hasValue(R.styleable.MaxLimitRecyclerView_limit_maxHeight)) {
|
||||||
|
mMaxHeight = typedArray.getDimensionPixelOffset(R.styleable.MaxLimitRecyclerView_limit_maxHeight, -1);
|
||||||
|
}
|
||||||
|
if (typedArray.hasValue(R.styleable.MaxLimitRecyclerView_limit_maxWidth)) {
|
||||||
|
mMaxWidth = typedArray.getDimensionPixelOffset(R.styleable.MaxLimitRecyclerView_limit_maxWidth, -1);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
} finally {
|
||||||
|
if (typedArray != null) {
|
||||||
|
typedArray.recycle();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onMeasure(int widthSpec, int heightSpec) {
|
||||||
|
super.onMeasure(widthSpec, heightSpec);
|
||||||
|
boolean needLimit = mMaxHeight >= 0 || mMaxWidth >= 0;
|
||||||
|
if (needLimit) {
|
||||||
|
int limitHeight = getMeasuredHeight();
|
||||||
|
int limitWith = getMeasuredWidth();
|
||||||
|
if (getMeasuredHeight() > mMaxHeight) {
|
||||||
|
limitHeight = mMaxHeight;
|
||||||
|
}
|
||||||
|
// if (getMeasuredWidth() > mMaxWidth) {
|
||||||
|
// limitWith = mMaxWidth;
|
||||||
|
// }
|
||||||
|
setMeasuredDimension(limitWith, limitHeight);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,657 @@
|
|||||||
|
package com.xuqm.base.view;
|
||||||
|
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.res.ColorStateList;
|
||||||
|
import android.content.res.Resources;
|
||||||
|
import android.content.res.TypedArray;
|
||||||
|
import android.graphics.Bitmap;
|
||||||
|
import android.graphics.BitmapShader;
|
||||||
|
import android.graphics.Canvas;
|
||||||
|
import android.graphics.Color;
|
||||||
|
import android.graphics.ColorFilter;
|
||||||
|
import android.graphics.Matrix;
|
||||||
|
import android.graphics.Paint;
|
||||||
|
import android.graphics.Path;
|
||||||
|
import android.graphics.PixelFormat;
|
||||||
|
import android.graphics.Rect;
|
||||||
|
import android.graphics.RectF;
|
||||||
|
import android.graphics.Shader;
|
||||||
|
import android.graphics.drawable.BitmapDrawable;
|
||||||
|
import android.graphics.drawable.Drawable;
|
||||||
|
import android.graphics.drawable.LayerDrawable;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.util.AttributeSet;
|
||||||
|
import android.widget.ImageView;
|
||||||
|
|
||||||
|
import com.xuqm.base.R;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by JiYaRuo on 2019/10/11.
|
||||||
|
*
|
||||||
|
* @BeUsedFor 自定义ImageView 随心所欲的改变ImageView的四个圆角角度
|
||||||
|
*/
|
||||||
|
@SuppressLint("AppCompatCustomView")
|
||||||
|
public class SelectableRoundedImageView extends ImageView {
|
||||||
|
public static final String TAG = "SelectableRoundedImageView";
|
||||||
|
|
||||||
|
private int mResource = 0;
|
||||||
|
|
||||||
|
private static final ScaleType[] sScaleTypeArray = {
|
||||||
|
ScaleType.MATRIX,
|
||||||
|
ScaleType.FIT_XY,
|
||||||
|
ScaleType.FIT_START,
|
||||||
|
ScaleType.FIT_CENTER,
|
||||||
|
ScaleType.FIT_END,
|
||||||
|
ScaleType.CENTER,
|
||||||
|
ScaleType.CENTER_CROP,
|
||||||
|
ScaleType.CENTER_INSIDE
|
||||||
|
};
|
||||||
|
|
||||||
|
// Set default scale type to FIT_CENTER, which is default scale type of
|
||||||
|
// original ImageView.
|
||||||
|
private ScaleType mScaleType = ScaleType.FIT_CENTER;
|
||||||
|
|
||||||
|
private float mLeftTopCornerRadius = 0.0f;
|
||||||
|
private float mRightTopCornerRadius = 0.0f;
|
||||||
|
private float mLeftBottomCornerRadius = 0.0f;
|
||||||
|
private float mRightBottomCornerRadius = 0.0f;
|
||||||
|
|
||||||
|
private float mBorderWidth = 0.0f;
|
||||||
|
private static final int DEFAULT_BORDER_COLOR = Color.BLACK;
|
||||||
|
private ColorStateList mBorderColor = ColorStateList.valueOf(DEFAULT_BORDER_COLOR);
|
||||||
|
|
||||||
|
private boolean isOval = false;
|
||||||
|
|
||||||
|
private Drawable mDrawable;
|
||||||
|
|
||||||
|
private float[] mRadii = new float[]{0, 0, 0, 0, 0, 0, 0, 0};
|
||||||
|
|
||||||
|
public SelectableRoundedImageView(Context context) {
|
||||||
|
super(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
public SelectableRoundedImageView(Context context, AttributeSet attrs) {
|
||||||
|
this(context, attrs, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public SelectableRoundedImageView(Context context, AttributeSet attrs, int defStyle) {
|
||||||
|
super(context, attrs, defStyle);
|
||||||
|
|
||||||
|
TypedArray a = context.obtainStyledAttributes(attrs,
|
||||||
|
R.styleable.SelectableRoundedImageView, defStyle, 0);
|
||||||
|
|
||||||
|
final int index = a.getInt(R.styleable.SelectableRoundedImageView_android_scaleType, -1);
|
||||||
|
if (index >= 0) {
|
||||||
|
setScaleType(sScaleTypeArray[index]);
|
||||||
|
}
|
||||||
|
|
||||||
|
mLeftTopCornerRadius = a.getDimensionPixelSize(
|
||||||
|
R.styleable.SelectableRoundedImageView_sriv_left_top_corner_radius, 0);
|
||||||
|
mRightTopCornerRadius = a.getDimensionPixelSize(
|
||||||
|
R.styleable.SelectableRoundedImageView_sriv_right_top_corner_radius, 0);
|
||||||
|
mLeftBottomCornerRadius = a.getDimensionPixelSize(
|
||||||
|
R.styleable.SelectableRoundedImageView_sriv_left_bottom_corner_radius, 0);
|
||||||
|
mRightBottomCornerRadius = a.getDimensionPixelSize(
|
||||||
|
R.styleable.SelectableRoundedImageView_sriv_right_bottom_corner_radius, 0);
|
||||||
|
|
||||||
|
if (mLeftTopCornerRadius < 0.0f || mRightTopCornerRadius < 0.0f
|
||||||
|
|| mLeftBottomCornerRadius < 0.0f || mRightBottomCornerRadius < 0.0f) {
|
||||||
|
throw new IllegalArgumentException("radius values cannot be negative.");
|
||||||
|
}
|
||||||
|
|
||||||
|
mRadii = new float[]{
|
||||||
|
mLeftTopCornerRadius, mLeftTopCornerRadius,
|
||||||
|
mRightTopCornerRadius, mRightTopCornerRadius,
|
||||||
|
mRightBottomCornerRadius, mRightBottomCornerRadius,
|
||||||
|
mLeftBottomCornerRadius, mLeftBottomCornerRadius
|
||||||
|
};
|
||||||
|
|
||||||
|
mBorderWidth = a.getDimensionPixelSize(R.styleable.SelectableRoundedImageView_sriv_border_width, 0);
|
||||||
|
if (mBorderWidth < 0) {
|
||||||
|
throw new IllegalArgumentException("border width cannot be negative.");
|
||||||
|
}
|
||||||
|
|
||||||
|
mBorderColor = a.getColorStateList(R.styleable.SelectableRoundedImageView_sriv_border_color);
|
||||||
|
if (mBorderColor == null) {
|
||||||
|
mBorderColor = ColorStateList.valueOf(DEFAULT_BORDER_COLOR);
|
||||||
|
}
|
||||||
|
|
||||||
|
isOval = a.getBoolean(R.styleable.SelectableRoundedImageView_sriv_oval, false);
|
||||||
|
a.recycle();
|
||||||
|
|
||||||
|
updateDrawable();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void drawableStateChanged() {
|
||||||
|
super.drawableStateChanged();
|
||||||
|
invalidate();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ScaleType getScaleType() {
|
||||||
|
return mScaleType;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setScaleType(ScaleType scaleType) {
|
||||||
|
super.setScaleType(scaleType);
|
||||||
|
mScaleType = scaleType;
|
||||||
|
updateDrawable();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setImageDrawable(Drawable drawable) {
|
||||||
|
mResource = 0;
|
||||||
|
mDrawable = SelectableRoundedCornerDrawable.fromDrawable(drawable, getResources());
|
||||||
|
super.setImageDrawable(mDrawable);
|
||||||
|
updateDrawable();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setImageBitmap(Bitmap bm) {
|
||||||
|
mResource = 0;
|
||||||
|
mDrawable = SelectableRoundedCornerDrawable.fromBitmap(bm, getResources());
|
||||||
|
super.setImageDrawable(mDrawable);
|
||||||
|
updateDrawable();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setImageResource(int resId) {
|
||||||
|
if (mResource != resId) {
|
||||||
|
mResource = resId;
|
||||||
|
mDrawable = resolveResource();
|
||||||
|
super.setImageDrawable(mDrawable);
|
||||||
|
updateDrawable();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setImageURI(Uri uri) {
|
||||||
|
super.setImageURI(uri);
|
||||||
|
setImageDrawable(getDrawable());
|
||||||
|
}
|
||||||
|
|
||||||
|
private Drawable resolveResource() {
|
||||||
|
Resources rsrc = getResources();
|
||||||
|
if (rsrc == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
Drawable d = null;
|
||||||
|
|
||||||
|
if (mResource != 0) {
|
||||||
|
try {
|
||||||
|
d = rsrc.getDrawable(mResource);
|
||||||
|
} catch (Exception e) {
|
||||||
|
// Log.w(TAG, "Unable to find resource: " + mResource, e);
|
||||||
|
// Don't try again.
|
||||||
|
mResource = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return SelectableRoundedCornerDrawable.fromDrawable(d, getResources());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateDrawable() {
|
||||||
|
if (mDrawable == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
((SelectableRoundedCornerDrawable) mDrawable).setScaleType(mScaleType);
|
||||||
|
((SelectableRoundedCornerDrawable) mDrawable).setCornerRadii(mRadii);
|
||||||
|
((SelectableRoundedCornerDrawable) mDrawable).setBorderWidth(mBorderWidth);
|
||||||
|
((SelectableRoundedCornerDrawable) mDrawable).setBorderColor(mBorderColor);
|
||||||
|
((SelectableRoundedCornerDrawable) mDrawable).setOval(isOval);
|
||||||
|
}
|
||||||
|
|
||||||
|
public float getCornerRadius() {
|
||||||
|
return mLeftTopCornerRadius;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set radii for each corner.
|
||||||
|
*
|
||||||
|
* @param leftTop The desired radius for left-top corner in dip.
|
||||||
|
* @param rightTop The desired desired radius for right-top corner in dip.
|
||||||
|
* @param leftBottom The desired radius for left-bottom corner in dip.
|
||||||
|
* @param rightBottom The desired radius for right-bottom corner in dip.
|
||||||
|
*/
|
||||||
|
public void setCornerRadiiDP(float leftTop, float rightTop, float leftBottom, float rightBottom) {
|
||||||
|
final float density = getResources().getDisplayMetrics().density;
|
||||||
|
|
||||||
|
final float lt = leftTop * density;
|
||||||
|
final float rt = rightTop * density;
|
||||||
|
final float lb = leftBottom * density;
|
||||||
|
final float rb = rightBottom * density;
|
||||||
|
|
||||||
|
mRadii = new float[]{lt, lt, rt, rt, rb, rb, lb, lb};
|
||||||
|
updateDrawable();
|
||||||
|
}
|
||||||
|
|
||||||
|
public float getBorderWidth() {
|
||||||
|
return mBorderWidth;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set border width.
|
||||||
|
*
|
||||||
|
* @param width The desired width in dip.
|
||||||
|
*/
|
||||||
|
public void setBorderWidthDP(float width) {
|
||||||
|
float scaledWidth = getResources().getDisplayMetrics().density * width;
|
||||||
|
if (mBorderWidth == scaledWidth) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
mBorderWidth = scaledWidth;
|
||||||
|
updateDrawable();
|
||||||
|
invalidate();
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getBorderColor() {
|
||||||
|
return mBorderColor.getDefaultColor();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setBorderColor(int color) {
|
||||||
|
setBorderColor(ColorStateList.valueOf(color));
|
||||||
|
}
|
||||||
|
|
||||||
|
public ColorStateList getBorderColors() {
|
||||||
|
return mBorderColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setBorderColor(ColorStateList colors) {
|
||||||
|
if (mBorderColor.equals(colors)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
mBorderColor = (colors != null) ? colors : ColorStateList
|
||||||
|
.valueOf(DEFAULT_BORDER_COLOR);
|
||||||
|
updateDrawable();
|
||||||
|
if (mBorderWidth > 0) {
|
||||||
|
invalidate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isOval() {
|
||||||
|
return isOval;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setOval(boolean oval) {
|
||||||
|
isOval = oval;
|
||||||
|
updateDrawable();
|
||||||
|
invalidate();
|
||||||
|
}
|
||||||
|
|
||||||
|
static class SelectableRoundedCornerDrawable extends Drawable {
|
||||||
|
|
||||||
|
private static final String TAG = "SelectableRoundedCornerDrawable";
|
||||||
|
private static final int DEFAULT_BORDER_COLOR = Color.BLACK;
|
||||||
|
|
||||||
|
private RectF mBounds = new RectF();
|
||||||
|
private RectF mBorderBounds = new RectF();
|
||||||
|
|
||||||
|
private final RectF mBitmapRect = new RectF();
|
||||||
|
private final int mBitmapWidth;
|
||||||
|
private final int mBitmapHeight;
|
||||||
|
|
||||||
|
private final Paint mBitmapPaint;
|
||||||
|
private final Paint mBorderPaint;
|
||||||
|
|
||||||
|
private BitmapShader mBitmapShader;
|
||||||
|
|
||||||
|
private float[] mRadii = new float[]{0, 0, 0, 0, 0, 0, 0, 0};
|
||||||
|
private float[] mBorderRadii = new float[]{0, 0, 0, 0, 0, 0, 0, 0};
|
||||||
|
|
||||||
|
private boolean mOval = false;
|
||||||
|
|
||||||
|
private float mBorderWidth = 0;
|
||||||
|
private ColorStateList mBorderColor = ColorStateList.valueOf(DEFAULT_BORDER_COLOR);
|
||||||
|
// Set default scale type to FIT_CENTER, which is default scale type of
|
||||||
|
// original ImageView.
|
||||||
|
private ScaleType mScaleType = ScaleType.FIT_CENTER;
|
||||||
|
|
||||||
|
private Path mPath = new Path();
|
||||||
|
private Bitmap mBitmap;
|
||||||
|
private boolean mBoundsConfigured = false;
|
||||||
|
|
||||||
|
public SelectableRoundedCornerDrawable(Bitmap bitmap, Resources r) {
|
||||||
|
mBitmap = bitmap;
|
||||||
|
mBitmapShader = new BitmapShader(bitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
|
||||||
|
|
||||||
|
if (bitmap != null) {
|
||||||
|
mBitmapWidth = bitmap.getScaledWidth(r.getDisplayMetrics());
|
||||||
|
mBitmapHeight = bitmap.getScaledHeight(r.getDisplayMetrics());
|
||||||
|
} else {
|
||||||
|
mBitmapWidth = mBitmapHeight = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
mBitmapRect.set(0, 0, mBitmapWidth, mBitmapHeight);
|
||||||
|
|
||||||
|
mBitmapPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||||
|
mBitmapPaint.setStyle(Paint.Style.FILL);
|
||||||
|
mBitmapPaint.setShader(mBitmapShader);
|
||||||
|
|
||||||
|
mBorderPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||||
|
mBorderPaint.setStyle(Paint.Style.STROKE);
|
||||||
|
mBorderPaint.setColor(mBorderColor.getColorForState(getState(), DEFAULT_BORDER_COLOR));
|
||||||
|
mBorderPaint.setStrokeWidth(mBorderWidth);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static SelectableRoundedCornerDrawable fromBitmap(Bitmap bitmap, Resources r) {
|
||||||
|
if (bitmap != null) {
|
||||||
|
return new SelectableRoundedCornerDrawable(bitmap, r);
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Drawable fromDrawable(Drawable drawable, Resources r) {
|
||||||
|
if (drawable != null) {
|
||||||
|
if (drawable instanceof SelectableRoundedCornerDrawable) {
|
||||||
|
return drawable;
|
||||||
|
} else if (drawable instanceof LayerDrawable) {
|
||||||
|
LayerDrawable ld = (LayerDrawable) drawable;
|
||||||
|
final int num = ld.getNumberOfLayers();
|
||||||
|
for (int i = 0; i < num; i++) {
|
||||||
|
Drawable d = ld.getDrawable(i);
|
||||||
|
ld.setDrawableByLayerId(ld.getId(i), fromDrawable(d, r));
|
||||||
|
}
|
||||||
|
return ld;
|
||||||
|
}
|
||||||
|
|
||||||
|
Bitmap bm = drawableToBitmap(drawable);
|
||||||
|
if (bm != null) {
|
||||||
|
return new SelectableRoundedCornerDrawable(bm, r);
|
||||||
|
} else {
|
||||||
|
// Log.w(TAG, "Failed to create bitmap from drawable!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return drawable;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Bitmap drawableToBitmap(Drawable drawable) {
|
||||||
|
if (drawable == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (drawable instanceof BitmapDrawable) {
|
||||||
|
return ((BitmapDrawable) drawable).getBitmap();
|
||||||
|
}
|
||||||
|
|
||||||
|
Bitmap bitmap;
|
||||||
|
int width = Math.max(drawable.getIntrinsicWidth(), 2);
|
||||||
|
int height = Math.max(drawable.getIntrinsicHeight(), 2);
|
||||||
|
try {
|
||||||
|
bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
|
||||||
|
Canvas canvas = new Canvas(bitmap);
|
||||||
|
drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
|
||||||
|
drawable.draw(canvas);
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
bitmap = null;
|
||||||
|
}
|
||||||
|
return bitmap;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isStateful() {
|
||||||
|
return mBorderColor.isStateful();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean onStateChange(int[] state) {
|
||||||
|
int newColor = mBorderColor.getColorForState(state, 0);
|
||||||
|
if (mBorderPaint.getColor() != newColor) {
|
||||||
|
mBorderPaint.setColor(newColor);
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return super.onStateChange(state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void configureBounds(Canvas canvas) {
|
||||||
|
// I have discovered a truly marvelous explanation of this,
|
||||||
|
// which this comment space is too narrow to contain. :)
|
||||||
|
// If you want to understand what's going on here,
|
||||||
|
// See http://www.joooooooooonhokim.com/?p=289
|
||||||
|
Rect clipBounds = canvas.getClipBounds();
|
||||||
|
Matrix canvasMatrix = canvas.getMatrix();
|
||||||
|
|
||||||
|
if (ScaleType.CENTER == mScaleType) {
|
||||||
|
mBounds.set(clipBounds);
|
||||||
|
} else if (ScaleType.CENTER_CROP == mScaleType) {
|
||||||
|
applyScaleToRadii(canvasMatrix);
|
||||||
|
mBounds.set(clipBounds);
|
||||||
|
} else if (ScaleType.FIT_XY == mScaleType) {
|
||||||
|
Matrix m = new Matrix();
|
||||||
|
m.setRectToRect(mBitmapRect, new RectF(clipBounds), Matrix.ScaleToFit.FILL);
|
||||||
|
mBitmapShader.setLocalMatrix(m);
|
||||||
|
mBounds.set(clipBounds);
|
||||||
|
} else if (ScaleType.FIT_START == mScaleType || ScaleType.FIT_END == mScaleType
|
||||||
|
|| ScaleType.FIT_CENTER == mScaleType || ScaleType.CENTER_INSIDE == mScaleType) {
|
||||||
|
applyScaleToRadii(canvasMatrix);
|
||||||
|
mBounds.set(mBitmapRect);
|
||||||
|
} else if (ScaleType.MATRIX == mScaleType) {
|
||||||
|
applyScaleToRadii(canvasMatrix);
|
||||||
|
mBounds.set(mBitmapRect);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void applyScaleToRadii(Matrix m) {
|
||||||
|
float[] values = new float[9];
|
||||||
|
m.getValues(values);
|
||||||
|
for (int i = 0; i < mRadii.length; i++) {
|
||||||
|
mRadii[i] = mRadii[i] / values[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void adjustCanvasForBorder(Canvas canvas) {
|
||||||
|
Matrix canvasMatrix = canvas.getMatrix();
|
||||||
|
final float[] values = new float[9];
|
||||||
|
canvasMatrix.getValues(values);
|
||||||
|
|
||||||
|
final float scaleFactorX = values[0];
|
||||||
|
final float scaleFactorY = values[4];
|
||||||
|
final float translateX = values[2];
|
||||||
|
final float translateY = values[5];
|
||||||
|
|
||||||
|
final float newScaleX = mBounds.width()
|
||||||
|
/ (mBounds.width() + mBorderWidth + mBorderWidth);
|
||||||
|
final float newScaleY = mBounds.height()
|
||||||
|
/ (mBounds.height() + mBorderWidth + mBorderWidth);
|
||||||
|
|
||||||
|
canvas.scale(newScaleX, newScaleY);
|
||||||
|
if (ScaleType.FIT_START == mScaleType || ScaleType.FIT_END == mScaleType
|
||||||
|
|| ScaleType.FIT_XY == mScaleType || ScaleType.FIT_CENTER == mScaleType
|
||||||
|
|| ScaleType.CENTER_INSIDE == mScaleType || ScaleType.MATRIX == mScaleType) {
|
||||||
|
canvas.translate(mBorderWidth, mBorderWidth);
|
||||||
|
} else if (ScaleType.CENTER == mScaleType || ScaleType.CENTER_CROP == mScaleType) {
|
||||||
|
// First, make translate values to 0
|
||||||
|
canvas.translate(
|
||||||
|
-translateX / (newScaleX * scaleFactorX),
|
||||||
|
-translateY / (newScaleY * scaleFactorY));
|
||||||
|
// Then, set the final translate values.
|
||||||
|
canvas.translate(-(mBounds.left - mBorderWidth), -(mBounds.top - mBorderWidth));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void adjustBorderWidthAndBorderBounds(Canvas canvas) {
|
||||||
|
Matrix canvasMatrix = canvas.getMatrix();
|
||||||
|
final float[] values = new float[9];
|
||||||
|
canvasMatrix.getValues(values);
|
||||||
|
|
||||||
|
final float scaleFactor = values[0];
|
||||||
|
|
||||||
|
float viewWidth = mBounds.width() * scaleFactor;
|
||||||
|
mBorderWidth = (mBorderWidth * mBounds.width()) / (viewWidth - (2 * mBorderWidth));
|
||||||
|
mBorderPaint.setStrokeWidth(mBorderWidth);
|
||||||
|
|
||||||
|
mBorderBounds.set(mBounds);
|
||||||
|
mBorderBounds.inset(-mBorderWidth / 2, -mBorderWidth / 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setBorderRadii() {
|
||||||
|
for (int i = 0; i < mRadii.length; i++) {
|
||||||
|
if (mRadii[i] > 0) {
|
||||||
|
mBorderRadii[i] = mRadii[i];
|
||||||
|
mRadii[i] = mRadii[i] - mBorderWidth;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void draw(Canvas canvas) {
|
||||||
|
canvas.save();
|
||||||
|
if (!mBoundsConfigured) {
|
||||||
|
configureBounds(canvas);
|
||||||
|
if (mBorderWidth > 0) {
|
||||||
|
adjustBorderWidthAndBorderBounds(canvas);
|
||||||
|
setBorderRadii();
|
||||||
|
}
|
||||||
|
mBoundsConfigured = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mOval) {
|
||||||
|
if (mBorderWidth > 0) {
|
||||||
|
adjustCanvasForBorder(canvas);
|
||||||
|
mPath.addOval(mBounds, Path.Direction.CW);
|
||||||
|
canvas.drawPath(mPath, mBitmapPaint);
|
||||||
|
mPath.reset();
|
||||||
|
mPath.addOval(mBorderBounds, Path.Direction.CW);
|
||||||
|
canvas.drawPath(mPath, mBorderPaint);
|
||||||
|
} else {
|
||||||
|
mPath.addOval(mBounds, Path.Direction.CW);
|
||||||
|
canvas.drawPath(mPath, mBitmapPaint);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (mBorderWidth > 0) {
|
||||||
|
adjustCanvasForBorder(canvas);
|
||||||
|
mPath.addRoundRect(mBounds, mRadii, Path.Direction.CW);
|
||||||
|
canvas.drawPath(mPath, mBitmapPaint);
|
||||||
|
mPath.reset();
|
||||||
|
mPath.addRoundRect(mBorderBounds, mBorderRadii, Path.Direction.CW);
|
||||||
|
canvas.drawPath(mPath, mBorderPaint);
|
||||||
|
} else {
|
||||||
|
mPath.addRoundRect(mBounds, mRadii, Path.Direction.CW);
|
||||||
|
canvas.drawPath(mPath, mBitmapPaint);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
canvas.restore();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCornerRadii(float[] radii) {
|
||||||
|
if (radii == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (radii.length != 8) {
|
||||||
|
throw new ArrayIndexOutOfBoundsException("radii[] needs 8 values");
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < radii.length; i++) {
|
||||||
|
mRadii[i] = radii[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getOpacity() {
|
||||||
|
return (mBitmap == null || mBitmap.hasAlpha() || mBitmapPaint.getAlpha() < 255) ? PixelFormat.TRANSLUCENT
|
||||||
|
: PixelFormat.OPAQUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setAlpha(int alpha) {
|
||||||
|
mBitmapPaint.setAlpha(alpha);
|
||||||
|
invalidateSelf();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setColorFilter(ColorFilter cf) {
|
||||||
|
mBitmapPaint.setColorFilter(cf);
|
||||||
|
invalidateSelf();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setDither(boolean dither) {
|
||||||
|
mBitmapPaint.setDither(dither);
|
||||||
|
invalidateSelf();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setFilterBitmap(boolean filter) {
|
||||||
|
mBitmapPaint.setFilterBitmap(filter);
|
||||||
|
invalidateSelf();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getIntrinsicWidth() {
|
||||||
|
return mBitmapWidth;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getIntrinsicHeight() {
|
||||||
|
return mBitmapHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
public float getBorderWidth() {
|
||||||
|
return mBorderWidth;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setBorderWidth(float width) {
|
||||||
|
mBorderWidth = width;
|
||||||
|
mBorderPaint.setStrokeWidth(width);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getBorderColor() {
|
||||||
|
return mBorderColor.getDefaultColor();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setBorderColor(int color) {
|
||||||
|
setBorderColor(ColorStateList.valueOf(color));
|
||||||
|
}
|
||||||
|
|
||||||
|
public ColorStateList getBorderColors() {
|
||||||
|
return mBorderColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Controls border color of this ImageView.
|
||||||
|
*
|
||||||
|
* @param colors The desired border color. If it's null, no border will be
|
||||||
|
* drawn.
|
||||||
|
*/
|
||||||
|
public void setBorderColor(ColorStateList colors) {
|
||||||
|
if (colors == null) {
|
||||||
|
mBorderWidth = 0;
|
||||||
|
mBorderColor = ColorStateList.valueOf(Color.TRANSPARENT);
|
||||||
|
mBorderPaint.setColor(Color.TRANSPARENT);
|
||||||
|
} else {
|
||||||
|
mBorderColor = colors;
|
||||||
|
mBorderPaint.setColor(mBorderColor.getColorForState(getState(),
|
||||||
|
DEFAULT_BORDER_COLOR));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isOval() {
|
||||||
|
return mOval;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setOval(boolean oval) {
|
||||||
|
mOval = oval;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ScaleType getScaleType() {
|
||||||
|
return mScaleType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setScaleType(ScaleType scaleType) {
|
||||||
|
if (scaleType == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
mScaleType = scaleType;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,83 @@
|
|||||||
|
package com.xuqm.base.view;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.graphics.Canvas;
|
||||||
|
import android.graphics.Color;
|
||||||
|
import android.graphics.Paint;
|
||||||
|
import android.text.TextPaint;
|
||||||
|
import android.util.AttributeSet;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
public class StrokeTextView extends androidx.appcompat.widget.AppCompatTextView {
|
||||||
|
private final TextView outlineTextView;
|
||||||
|
private TextPaint strokePaint;
|
||||||
|
|
||||||
|
public StrokeTextView(Context context) {
|
||||||
|
super(context);
|
||||||
|
|
||||||
|
outlineTextView = new TextView(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
public StrokeTextView(Context context, AttributeSet attrs) {
|
||||||
|
super(context, attrs);
|
||||||
|
|
||||||
|
outlineTextView = new TextView(context, attrs);
|
||||||
|
}
|
||||||
|
|
||||||
|
public StrokeTextView(Context context, AttributeSet attrs, int defStyle) {
|
||||||
|
super(context, attrs, defStyle);
|
||||||
|
|
||||||
|
outlineTextView = new TextView(context, attrs, defStyle);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setLayoutParams(ViewGroup.LayoutParams params) {
|
||||||
|
super.setLayoutParams(params);
|
||||||
|
outlineTextView.setLayoutParams(params);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
||||||
|
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
|
||||||
|
|
||||||
|
//设置轮廓文字
|
||||||
|
CharSequence outlineText = outlineTextView.getText();
|
||||||
|
if (outlineText == null || !outlineText.equals(this.getText())) {
|
||||||
|
outlineTextView.setText(getText());
|
||||||
|
postInvalidate();
|
||||||
|
}
|
||||||
|
outlineTextView.measure(widthMeasureSpec, heightMeasureSpec);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
|
||||||
|
super.onLayout(changed, left, top, right, bottom);
|
||||||
|
outlineTextView.layout(left, top, right, bottom);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onDraw(Canvas canvas) {
|
||||||
|
|
||||||
|
if (strokePaint == null) {
|
||||||
|
strokePaint = new TextPaint();
|
||||||
|
}
|
||||||
|
//复制原来TextViewg画笔中的一些参数
|
||||||
|
TextPaint paint = getPaint();
|
||||||
|
strokePaint.setTextSize(paint.getTextSize()+3);
|
||||||
|
strokePaint.setFlags(paint.getFlags());
|
||||||
|
strokePaint.setAlpha(paint.getAlpha());
|
||||||
|
|
||||||
|
//自定义描边效果
|
||||||
|
strokePaint.setStyle(Paint.Style.STROKE);
|
||||||
|
strokePaint.setColor(Color.parseColor("#66000000"));
|
||||||
|
strokePaint.setStrokeWidth(6);
|
||||||
|
|
||||||
|
String text = getText().toString();
|
||||||
|
|
||||||
|
//在文本底层画出带描边的文本
|
||||||
|
canvas.drawText(text, (getWidth() - strokePaint.measureText(text)) / 2,
|
||||||
|
getBaseline(), strokePaint);
|
||||||
|
super.onDraw(canvas);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,98 @@
|
|||||||
|
package com.xuqm.base.view
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import android.content.Context
|
||||||
|
import android.util.AttributeSet
|
||||||
|
import android.view.Gravity
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.MotionEvent
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import android.widget.RelativeLayout
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import androidx.viewpager2.widget.ViewPager2
|
||||||
|
import com.xuqm.base.R
|
||||||
|
|
||||||
|
class SuperViewPager : RelativeLayout {
|
||||||
|
|
||||||
|
val mViewPager: ViewPager2 by lazy {
|
||||||
|
findViewById<ViewPager2>(R.id.mViewPager)
|
||||||
|
.apply {
|
||||||
|
//设置关闭过度滑动的效果
|
||||||
|
getChildAt(0).overScrollMode = View.OVER_SCROLL_NEVER
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//自己定义了一个比率,来调整画廊效果最左侧和最右侧占用的宽度
|
||||||
|
var edgeRatio = 0.25
|
||||||
|
set(value) {
|
||||||
|
field = value
|
||||||
|
refreshPageSize()
|
||||||
|
}
|
||||||
|
|
||||||
|
//为了保证画廊效果,可见的Page处理为单数
|
||||||
|
var visibleItem: Int = 1
|
||||||
|
set(value) {
|
||||||
|
field = if (value.rem(2) == 0) {
|
||||||
|
value - 1
|
||||||
|
} else {
|
||||||
|
value
|
||||||
|
}
|
||||||
|
refreshPageSize()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//刷新页面大小
|
||||||
|
private fun refreshPageSize() {
|
||||||
|
//使用post为了保证获取根布局width的时候结果不为0
|
||||||
|
mViewPager.post {
|
||||||
|
mViewPager.offscreenPageLimit = visibleItem
|
||||||
|
//根据想要显示的页面个数,动态给ViewPager2计算一个大小
|
||||||
|
val mPageWidth = if (visibleItem == 1) {
|
||||||
|
width
|
||||||
|
} else {
|
||||||
|
width.toDouble().div(visibleItem.minus(2).plus(edgeRatio)).toInt()
|
||||||
|
}
|
||||||
|
mViewPager.layoutParams = LayoutParams(
|
||||||
|
LayoutParams(
|
||||||
|
mPageWidth,
|
||||||
|
ViewGroup.LayoutParams.MATCH_PARENT
|
||||||
|
).apply { gravity = Gravity.CENTER })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将根布局的触摸事件直接传递给ViewPager
|
||||||
|
*/
|
||||||
|
@SuppressLint("ClickableViewAccessibility")
|
||||||
|
override fun onTouchEvent(event: MotionEvent?): Boolean {
|
||||||
|
return mViewPager.getChildAt(0).onTouchEvent(event)
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(context: Context?) : super(context)
|
||||||
|
constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs)
|
||||||
|
constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super(
|
||||||
|
context,
|
||||||
|
attrs,
|
||||||
|
defStyleAttr
|
||||||
|
)
|
||||||
|
|
||||||
|
init {
|
||||||
|
clipChildren = false
|
||||||
|
LayoutInflater.from(context).inflate(R.layout.super_viewpager_layout, this, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 为ViewPager2设置一个适配器,ViewPager2的适配器不再是PagerAdapter,而是RecyclerView.Adapter类型
|
||||||
|
*/
|
||||||
|
fun setAdapter(adapter: RecyclerView.Adapter<*>) {
|
||||||
|
mViewPager.adapter = adapter
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置页面切换的效果
|
||||||
|
*/
|
||||||
|
fun setPageTransformer(pageTransformer: ViewPager2.PageTransformer) {
|
||||||
|
mViewPager.setPageTransformer(pageTransformer)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,150 @@
|
|||||||
|
package com.xuqm.base.view;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.res.ColorStateList;
|
||||||
|
import android.content.res.TypedArray;
|
||||||
|
import android.graphics.drawable.Drawable;
|
||||||
|
import android.util.AttributeSet;
|
||||||
|
import android.view.View;
|
||||||
|
import android.widget.FrameLayout;
|
||||||
|
import android.widget.ImageView;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.core.content.ContextCompat;
|
||||||
|
import androidx.core.graphics.drawable.DrawableCompat;
|
||||||
|
|
||||||
|
import com.xuqm.base.R;
|
||||||
|
import com.xuqm.base.ui.callback.ToolBarListener;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 自定义的toolbar
|
||||||
|
*/
|
||||||
|
public class ToolbarLayout extends FrameLayout {
|
||||||
|
|
||||||
|
private CharSequence title = "";
|
||||||
|
private int textColor = 0xFFFFFF;
|
||||||
|
private int iconTintColor = 0xFFFFFF;
|
||||||
|
private boolean showBack = true;
|
||||||
|
private boolean showLine = false;
|
||||||
|
|
||||||
|
public void setTitle(CharSequence title) {
|
||||||
|
this.title = title;
|
||||||
|
getTitleView().setText(title);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTextColor(int textColor) {
|
||||||
|
this.textColor = textColor;
|
||||||
|
getTitleView().setTextColor(textColor);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setIconTintColor(int iconTintColor) {
|
||||||
|
this.iconTintColor = iconTintColor;
|
||||||
|
tintIcon(iconTintColor);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setShowBack(boolean showBack) {
|
||||||
|
this.showBack = showBack;
|
||||||
|
getBackBtn().setVisibility(showBack ? VISIBLE : GONE);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setShowLine(boolean showLine) {
|
||||||
|
this.showLine = showLine;
|
||||||
|
findViewById(R.id.toolbarLine).setVisibility(showLine ? VISIBLE : GONE);
|
||||||
|
}
|
||||||
|
|
||||||
|
private ToolBarListener backListener;
|
||||||
|
|
||||||
|
public void backBtnPressed(ToolBarListener listener) {
|
||||||
|
backListener = listener;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ToolbarLayout(@NonNull Context context) {
|
||||||
|
super(context);
|
||||||
|
init(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ToolbarLayout(@NonNull Context context, @Nullable AttributeSet attrs) {
|
||||||
|
super(context, attrs);
|
||||||
|
init(attrs);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void init(AttributeSet attrs) {
|
||||||
|
Context mContext = getContext();
|
||||||
|
if (null == attrs) return;
|
||||||
|
View.inflate(mContext, R.layout.toolbar, this);
|
||||||
|
TypedArray typedArray = mContext.obtainStyledAttributes(attrs, R.styleable.ToolbarLayout);
|
||||||
|
title = typedArray.getString(R.styleable.ToolbarLayout_title);
|
||||||
|
showBack = typedArray.getBoolean(R.styleable.ToolbarLayout_showBack, true);
|
||||||
|
showLine = typedArray.getBoolean(R.styleable.ToolbarLayout_showLine, false);
|
||||||
|
textColor = typedArray.getColor(R.styleable.ToolbarLayout_textColor, ContextCompat.getColor(mContext, R.color.title));
|
||||||
|
// iconTintColor = typedArray.getColor(R.styleable.ToolbarLayout_iconTint, ContextCompat.getColor(mContext, R.color.white));
|
||||||
|
typedArray.recycle();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onFinishInflate() {
|
||||||
|
super.onFinishInflate();
|
||||||
|
if (null != getBackBtn()) {
|
||||||
|
getBackBtn().setVisibility(showBack ? VISIBLE : GONE);
|
||||||
|
if (showBack) {
|
||||||
|
// tintIcon(iconTintColor);
|
||||||
|
getBackBtn().setOnClickListener(v -> backListener.back());
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (null != getTitleView()) {
|
||||||
|
getTitleView().setText(title);
|
||||||
|
getTitleView().setTextColor(textColor);
|
||||||
|
}
|
||||||
|
if (null != getConfirmBtn()) {
|
||||||
|
getTitleView().setText(title);
|
||||||
|
getConfirmBtn().setTextColor(textColor);
|
||||||
|
}
|
||||||
|
findViewById(R.id.toolbarLine).setVisibility(showLine ? VISIBLE : GONE);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void tintIcon(int colors) {
|
||||||
|
Drawable wrappedDrawable = DrawableCompat.wrap(getBackBtn().getDrawable());
|
||||||
|
DrawableCompat.setTintList(wrappedDrawable, ColorStateList.valueOf(colors));
|
||||||
|
getBackBtn().setImageDrawable(wrappedDrawable);
|
||||||
|
}
|
||||||
|
|
||||||
|
private ImageView backBtn;
|
||||||
|
|
||||||
|
public ImageView getBackBtn() {
|
||||||
|
if (null == backBtn)
|
||||||
|
backBtn = findViewById(R.id.toolbarBack);
|
||||||
|
return backBtn;
|
||||||
|
}
|
||||||
|
|
||||||
|
private TextView titleView;
|
||||||
|
|
||||||
|
public TextView getTitleView() {
|
||||||
|
if (null == titleView)
|
||||||
|
titleView = findViewById(R.id.toolbarTitle);
|
||||||
|
return titleView;
|
||||||
|
}
|
||||||
|
private TextView confirmBtn;
|
||||||
|
|
||||||
|
public TextView getConfirmBtn() {
|
||||||
|
if (null == confirmBtn)
|
||||||
|
confirmBtn = findViewById(R.id.toolbarConfirm);
|
||||||
|
return confirmBtn;
|
||||||
|
}
|
||||||
|
private ImageView closeBtn;
|
||||||
|
|
||||||
|
public ImageView getCloseBtn() {
|
||||||
|
if (null == closeBtn)
|
||||||
|
closeBtn = findViewById(R.id.toolbarClose);
|
||||||
|
return closeBtn;
|
||||||
|
}
|
||||||
|
private ImageView toolbarMenu;
|
||||||
|
|
||||||
|
public ImageView getToolbarMenu() {
|
||||||
|
if (null == toolbarMenu)
|
||||||
|
toolbarMenu = findViewById(R.id.toolbarMenu);
|
||||||
|
return toolbarMenu;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,5 @@
|
|||||||
|
package com.xuqm.base.view.enu;
|
||||||
|
|
||||||
|
public enum Status {
|
||||||
|
DISMISS, LOADING, NO_DATA, LOAD_FAILED, NETWORK_UNAVAILABLE
|
||||||
|
}
|
||||||
@ -0,0 +1,132 @@
|
|||||||
|
package com.xuqm.base.viewmodel;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.core.util.Pair;
|
||||||
|
import androidx.lifecycle.LifecycleOwner;
|
||||||
|
import androidx.lifecycle.LiveData;
|
||||||
|
import androidx.lifecycle.MutableLiveData;
|
||||||
|
import androidx.paging.LivePagedListBuilder;
|
||||||
|
import androidx.paging.PageKeyedDataSource;
|
||||||
|
import androidx.paging.PagedList;
|
||||||
|
|
||||||
|
import com.xuqm.base.common.RefreshResult;
|
||||||
|
import com.xuqm.base.datasource.DataSourceFactory;
|
||||||
|
import com.xuqm.base.datasource.PagedDataLoader;
|
||||||
|
import com.xuqm.base.viewmodel.callback.AdapterObserverCallback;
|
||||||
|
import com.xuqm.base.viewmodel.callback.DataObserverCallback;
|
||||||
|
import com.xuqm.base.viewmodel.callback.Response;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param <T>
|
||||||
|
*/
|
||||||
|
public abstract class BaseListViewModel<T> extends BaseViewModel implements PagedDataLoader<T> {
|
||||||
|
|
||||||
|
public int pageSize() {
|
||||||
|
return 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
private final ArrayList<T> data = new ArrayList<>();
|
||||||
|
|
||||||
|
private final DataSourceFactory<T> dataSourceFactory = new DataSourceFactory<>(this);
|
||||||
|
private final LiveData<PagedList<T>> loadLiveData = new LivePagedListBuilder<>(dataSourceFactory, this.pageSize()).build();
|
||||||
|
private final MutableLiveData<RefreshResult> refreshLiveData = new MutableLiveData<>();
|
||||||
|
private final MutableLiveData<RefreshResult> loadMoreLiveData = new MutableLiveData<>();
|
||||||
|
private final MutableLiveData<Pair<Integer, Object>> notifyItemLiveData = new MutableLiveData<>();
|
||||||
|
private final MutableLiveData<Integer> removeItemLiveData = new MutableLiveData<>();
|
||||||
|
|
||||||
|
public void invalidate() {
|
||||||
|
Objects.requireNonNull(dataSourceFactory.sourceLiveData.getValue()).invalidate();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void observeDataObserver(@NonNull LifecycleOwner owner, DataObserverCallback<T> dataObserverCallback) {
|
||||||
|
loadLiveData.observe(owner, dataObserverCallback::data);
|
||||||
|
refreshLiveData.observe(owner, dataObserverCallback::refreshResult);
|
||||||
|
loadMoreLiveData.observe(owner, dataObserverCallback::loadMoreResult);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void observeAdapterObserver(@NonNull LifecycleOwner owner, AdapterObserverCallback observerCallback) {
|
||||||
|
notifyItemLiveData.observe(owner, integerObjectPair -> observerCallback.notifyItem(integerObjectPair.first, integerObjectPair.second));
|
||||||
|
removeItemLiveData.observe(owner, observerCallback::removeItem);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void loadInitial(PageKeyedDataSource.LoadInitialParams<Integer> params, PageKeyedDataSource.LoadInitialCallback<Integer, T> callback) {
|
||||||
|
refresh();
|
||||||
|
data.clear();
|
||||||
|
loadData(1, onResponse -> {
|
||||||
|
if (null == onResponse) {
|
||||||
|
refreshLiveData.postValue(RefreshResult.FAILED);
|
||||||
|
} else if (onResponse.isEmpty()) {
|
||||||
|
refreshLiveData.postValue(RefreshResult.NO_DATA);
|
||||||
|
} else if (onResponse.size() < pageSize()) {
|
||||||
|
data.addAll(onResponse);
|
||||||
|
callback.onResult(onResponse, null, null);
|
||||||
|
refreshLiveData.postValue(RefreshResult.NO_MORE);
|
||||||
|
} else {
|
||||||
|
data.addAll(onResponse);
|
||||||
|
callback.onResult(onResponse, null, 2);
|
||||||
|
refreshLiveData.postValue(RefreshResult.SUCCEED);
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void loadAfter(PageKeyedDataSource.LoadParams<Integer> params, PageKeyedDataSource.LoadCallback<Integer, T> callback) {
|
||||||
|
loadMore();
|
||||||
|
loadData(params.key, onResponse -> {
|
||||||
|
if (null == onResponse) loadMoreLiveData.postValue(RefreshResult.FAILED);
|
||||||
|
else if (onResponse.size() < pageSize()) {
|
||||||
|
data.addAll(onResponse);
|
||||||
|
callback.onResult(onResponse, null);
|
||||||
|
loadMoreLiveData.postValue(RefreshResult.NO_MORE);
|
||||||
|
} else {
|
||||||
|
data.addAll(onResponse);
|
||||||
|
callback.onResult(onResponse, params.key + 1);
|
||||||
|
loadMoreLiveData.postValue(RefreshResult.SUCCEED);
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setData(List<T> data) {
|
||||||
|
this.data.clear();
|
||||||
|
this.data.addAll(data);
|
||||||
|
refreshLiveData.postValue(RefreshResult.SUCCEED);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ArrayList<T> getData() {
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void notifyItem(int position) {
|
||||||
|
notifyItemLiveData.postValue(new Pair<>(position, null));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void notifyItem(int position, Object payload) {
|
||||||
|
notifyItemLiveData.postValue(new Pair<>(position, payload));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void removeItem(int position) {
|
||||||
|
removeItemLiveData.postValue(position);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void refresh() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void loadMore() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract void loadData(int page, Response<T> onResponse);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -0,0 +1,67 @@
|
|||||||
|
package com.xuqm.base.viewmodel;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.lifecycle.DefaultLifecycleObserver;
|
||||||
|
import androidx.lifecycle.LifecycleOwner;
|
||||||
|
import androidx.lifecycle.ViewModel;
|
||||||
|
|
||||||
|
import com.xuqm.base.common.LogHelper;
|
||||||
|
|
||||||
|
import io.reactivex.disposables.CompositeDisposable;
|
||||||
|
import io.reactivex.disposables.Disposable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ViewModel 基类
|
||||||
|
*/
|
||||||
|
public abstract class BaseViewModel extends ViewModel implements DefaultLifecycleObserver {
|
||||||
|
private final String TAG = getClass().getSimpleName();
|
||||||
|
protected CompositeDisposable compositeDisposable = new CompositeDisposable();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Disposable 的管理,自动处理网络请求
|
||||||
|
*
|
||||||
|
* @param d Disposable
|
||||||
|
*/
|
||||||
|
protected void add(Disposable d) {
|
||||||
|
compositeDisposable.add(d);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate(@NonNull LifecycleOwner owner) {
|
||||||
|
LogHelper.d(TAG, "onCreate");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onStart(@NonNull LifecycleOwner owner) {
|
||||||
|
LogHelper.d(TAG, "onStart");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onResume(@NonNull LifecycleOwner owner) {
|
||||||
|
LogHelper.d(TAG, "onResume");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPause(@NonNull LifecycleOwner owner) {
|
||||||
|
LogHelper.d(TAG, "onPause");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onStop(@NonNull LifecycleOwner owner) {
|
||||||
|
LogHelper.d(TAG, "onStop");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDestroy(@NonNull LifecycleOwner owner) {
|
||||||
|
LogHelper.d(TAG, "onDestroy");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onCleared() {
|
||||||
|
super.onCleared();
|
||||||
|
LogHelper.d(TAG, "onCleared");
|
||||||
|
if (!compositeDisposable.isDisposed()) {
|
||||||
|
compositeDisposable.dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,7 @@
|
|||||||
|
package com.xuqm.base.viewmodel.callback;
|
||||||
|
|
||||||
|
public interface AdapterObserverCallback {
|
||||||
|
void notifyItem(int position, Object payload);
|
||||||
|
|
||||||
|
void removeItem(int position);
|
||||||
|
}
|
||||||
@ -0,0 +1,13 @@
|
|||||||
|
package com.xuqm.base.viewmodel.callback;
|
||||||
|
|
||||||
|
import androidx.paging.PagedList;
|
||||||
|
|
||||||
|
import com.xuqm.base.common.RefreshResult;
|
||||||
|
|
||||||
|
public interface DataObserverCallback<T> {
|
||||||
|
void data(PagedList<T> data);
|
||||||
|
|
||||||
|
void refreshResult(RefreshResult refreshResult);
|
||||||
|
|
||||||
|
void loadMoreResult(RefreshResult refreshResult);
|
||||||
|
}
|
||||||
@ -0,0 +1,7 @@
|
|||||||
|
package com.xuqm.base.viewmodel.callback;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
|
||||||
|
public interface Response<T> {
|
||||||
|
void onResponse(ArrayList<T> onResponse);
|
||||||
|
}
|
||||||
@ -0,0 +1,41 @@
|
|||||||
|
package com.xuqm.base.web
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.webkit.JavascriptInterface
|
||||||
|
import com.xuqm.base.common.AppManager
|
||||||
|
import com.xuqm.base.common.ToolsHelper
|
||||||
|
import com.xuqm.base.extensions.log
|
||||||
|
|
||||||
|
class XAndroidInterface constructor(val mContext: Context, val viewModel: XWebViewModel) {
|
||||||
|
@JavascriptInterface
|
||||||
|
fun showToast(callBackId: String, toast: String) {
|
||||||
|
ToolsHelper.showMessage(toast)
|
||||||
|
viewModel.callBackSuccess(callBackId)
|
||||||
|
}
|
||||||
|
|
||||||
|
@JavascriptInterface
|
||||||
|
fun setTitle(callBackId: String, title: String) {
|
||||||
|
viewModel.setTitle(title)
|
||||||
|
viewModel.callBackSuccess(callBackId)
|
||||||
|
}
|
||||||
|
|
||||||
|
@JavascriptInterface
|
||||||
|
fun scan(callBackId: String) {
|
||||||
|
viewModel.scan(callBackId)
|
||||||
|
}
|
||||||
|
|
||||||
|
@JavascriptInterface
|
||||||
|
fun closed() {
|
||||||
|
AppManager.getInstance().finish()
|
||||||
|
}
|
||||||
|
|
||||||
|
@JavascriptInterface
|
||||||
|
fun setTopMenu(callBackId: String, date: String) {
|
||||||
|
date.split(",").log()
|
||||||
|
viewModel.setTopMenu(callBackId,date.split(","))
|
||||||
|
}
|
||||||
|
@JavascriptInterface
|
||||||
|
fun getUserInfo(callBackId: String) {
|
||||||
|
viewModel.getUserInfo(callBackId)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,25 @@
|
|||||||
|
package com.xuqm.base.web
|
||||||
|
|
||||||
|
import android.webkit.WebView
|
||||||
|
import com.xuqm.base.App
|
||||||
|
import com.xuqm.base.common.GsonImplHelp
|
||||||
|
import com.xuqm.base.extensions.log
|
||||||
|
|
||||||
|
class XWebCallBack(val webView: WebView) {
|
||||||
|
fun error(callBackId: String, msg: String) {
|
||||||
|
sendCallBack(XWebJsCallModel(callBackId, -1, msg, null))
|
||||||
|
}
|
||||||
|
|
||||||
|
fun success(callBackId: String, data: Any?) {
|
||||||
|
sendCallBack(XWebJsCallModel(callBackId, 0, null, data))
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun sendCallBack(model: XWebJsCallModel) {
|
||||||
|
App.getInstance().runOnUiThread {
|
||||||
|
GsonImplHelp.get().toJson(model).log()
|
||||||
|
webView.loadUrl(
|
||||||
|
"javascript:__callbackMessageFromNative("+GsonImplHelp.get().toJson(model)+")"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,115 @@
|
|||||||
|
package com.xuqm.base.web
|
||||||
|
|
||||||
|
import android.Manifest
|
||||||
|
import android.net.Uri
|
||||||
|
import android.webkit.*
|
||||||
|
import com.bigkoo.alertview.AlertView
|
||||||
|
import com.xuqm.base.App
|
||||||
|
import com.xuqm.base.extensions.log
|
||||||
|
import com.xuqm.base.extensions.loge
|
||||||
|
import com.xuqm.base.extensions.runWithPermission
|
||||||
|
|
||||||
|
|
||||||
|
class XWebChromeClient(val mContext: XWebViewActivity, val viewModel: XWebViewModel) :
|
||||||
|
WebChromeClient() {
|
||||||
|
|
||||||
|
override fun onProgressChanged(view: WebView?, newProgress: Int) {
|
||||||
|
super.onProgressChanged(view, newProgress)
|
||||||
|
viewModel.setProgress(newProgress)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onJsAlert(
|
||||||
|
view: WebView,
|
||||||
|
url: String,
|
||||||
|
message: String,
|
||||||
|
result: JsResult
|
||||||
|
): Boolean {
|
||||||
|
App.getInstance().runOnUiThread {
|
||||||
|
|
||||||
|
AlertView(
|
||||||
|
"", message, null, arrayOf("Confirm"), null, mContext,
|
||||||
|
AlertView.Style.Alert
|
||||||
|
) { _, _ ->
|
||||||
|
result.confirm()
|
||||||
|
}.show()
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onJsConfirm(
|
||||||
|
view: WebView,
|
||||||
|
url: String,
|
||||||
|
message: String,
|
||||||
|
result: JsResult
|
||||||
|
): Boolean {
|
||||||
|
AlertView(
|
||||||
|
"", message, null, arrayOf("Confirm", "Cancel"), null, mContext,
|
||||||
|
AlertView.Style.Alert
|
||||||
|
) { _, position ->
|
||||||
|
when (position) {
|
||||||
|
0 -> result.confirm()
|
||||||
|
1 -> result.cancel()
|
||||||
|
}
|
||||||
|
}.show()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onShowFileChooser(
|
||||||
|
webView: WebView,
|
||||||
|
filePathCallback: ValueCallback<Array<Uri>>,
|
||||||
|
fileChooserParams: FileChooserParams
|
||||||
|
): Boolean {
|
||||||
|
var types = "*/*"
|
||||||
|
if (fileChooserParams.acceptTypes.isNotEmpty())
|
||||||
|
types = if (fileChooserParams.acceptTypes[0].contains("image"))
|
||||||
|
"image/*"
|
||||||
|
else "*/*"
|
||||||
|
mContext.selectedFile(types, filePathCallback)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onGeolocationPermissionsShowPrompt(
|
||||||
|
origin: String?,
|
||||||
|
callback: GeolocationPermissions.Callback
|
||||||
|
) {
|
||||||
|
|
||||||
|
mContext.runWithPermission(
|
||||||
|
Manifest.permission.ACCESS_COARSE_LOCATION,
|
||||||
|
Manifest.permission.ACCESS_FINE_LOCATION,
|
||||||
|
) {
|
||||||
|
|
||||||
|
AlertView(
|
||||||
|
"定位权限",
|
||||||
|
"当前页面需要获取定位权限",
|
||||||
|
null,
|
||||||
|
arrayOf("Confirm", "Cancel"),
|
||||||
|
null,
|
||||||
|
mContext,
|
||||||
|
AlertView.Style.Alert
|
||||||
|
) { _, position ->
|
||||||
|
when (position) {
|
||||||
|
0 -> callback.invoke(origin, true, true)
|
||||||
|
1 -> callback.invoke(origin, false, true)
|
||||||
|
}
|
||||||
|
super.onGeolocationPermissionsShowPrompt(origin, callback)
|
||||||
|
}.show()
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onGeolocationPermissionsHidePrompt() {
|
||||||
|
"onGeolocationPermissionsHidePrompt".loge()
|
||||||
|
super.onGeolocationPermissionsHidePrompt()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onPermissionRequest(request: PermissionRequest) {
|
||||||
|
"onPermissionRequest".loge()
|
||||||
|
request.grant(request.resources)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onPermissionRequestCanceled(request: PermissionRequest?) {
|
||||||
|
"onPermissionRequestCanceled".loge()
|
||||||
|
request?.loge()
|
||||||
|
super.onPermissionRequestCanceled(request)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,8 @@
|
|||||||
|
package com.xuqm.base.web
|
||||||
|
|
||||||
|
data class XWebJsCallModel(
|
||||||
|
val callBackId: String,
|
||||||
|
val status: Int,
|
||||||
|
val errMsg: String?,
|
||||||
|
val data: Any?
|
||||||
|
)
|
||||||
@ -0,0 +1,237 @@
|
|||||||
|
package com.xuqm.base.web
|
||||||
|
|
||||||
|
import android.Manifest
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import android.app.DownloadManager
|
||||||
|
import android.content.Intent
|
||||||
|
import android.net.Uri
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.view.View
|
||||||
|
import android.webkit.URLUtil
|
||||||
|
import android.webkit.ValueCallback
|
||||||
|
import androidx.lifecycle.ViewModelProvider
|
||||||
|
import com.bigkoo.alertview.AlertView
|
||||||
|
import com.huawei.hms.hmsscankit.ScanUtil
|
||||||
|
import com.huawei.hms.ml.scan.HmsScan
|
||||||
|
import com.luck.picture.lib.basic.PictureSelector
|
||||||
|
import com.luck.picture.lib.config.SelectMimeType
|
||||||
|
import com.luck.picture.lib.config.SelectModeConfig
|
||||||
|
import com.luck.picture.lib.entity.LocalMedia
|
||||||
|
import com.luck.picture.lib.interfaces.OnResultCallbackListener
|
||||||
|
import com.xuqm.base.R
|
||||||
|
import com.xuqm.base.common.FileHelper
|
||||||
|
import com.xuqm.base.common.GlideEngine
|
||||||
|
import com.xuqm.base.common.LogHelper
|
||||||
|
import com.xuqm.base.databinding.ActivityXWebviewBinding
|
||||||
|
import com.xuqm.base.extensions.loge
|
||||||
|
import com.xuqm.base.extensions.runWithPermission
|
||||||
|
import com.xuqm.base.extensions.showMessage
|
||||||
|
import com.xuqm.base.ui.BaseActivity
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
|
|
||||||
|
class XWebViewActivity : BaseActivity<ActivityXWebviewBinding>() {
|
||||||
|
private lateinit var title: String
|
||||||
|
private lateinit var url: String
|
||||||
|
private var hasTopBar: Boolean = true
|
||||||
|
|
||||||
|
|
||||||
|
private lateinit var viewModel: XWebViewModel
|
||||||
|
|
||||||
|
override fun getLayoutId(): Int = R.layout.activity_x_webview
|
||||||
|
|
||||||
|
@SuppressLint("SetJavaScriptEnabled")
|
||||||
|
override fun initView(savedInstanceState: Bundle?) {
|
||||||
|
super.initView(savedInstanceState)
|
||||||
|
LogHelper.e("initView")
|
||||||
|
showBack(false)
|
||||||
|
intent?.apply {
|
||||||
|
hasTopBar = getBooleanExtra("hasTopBar", true)
|
||||||
|
}
|
||||||
|
if (hasTopBar) {
|
||||||
|
showBack(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
viewModel = ViewModelProvider(this)[XWebViewModel::class.java]
|
||||||
|
|
||||||
|
viewModel.activity = this
|
||||||
|
viewModel.callBack = XWebCallBack(binding.XWebView)
|
||||||
|
|
||||||
|
binding.XWebView.webChromeClient = XWebChromeClient(this, viewModel)
|
||||||
|
binding.XWebView.webViewClient = XWebViewClient()
|
||||||
|
binding.XWebView.clearCache(true)
|
||||||
|
binding.XWebView.settings.javaScriptEnabled = true
|
||||||
|
binding.XWebView.settings.setGeolocationEnabled(true)
|
||||||
|
binding.XWebView.settings.domStorageEnabled = true
|
||||||
|
binding.XWebView.settings.mediaPlaybackRequiresUserGesture = false;
|
||||||
|
|
||||||
|
binding.XWebView.addJavascriptInterface(
|
||||||
|
XAndroidInterface(mContext, viewModel),
|
||||||
|
"ZHHBAndroid"
|
||||||
|
)
|
||||||
|
binding.XWebView.setDownloadListener { url, _, contentDisposition, mimeType, _ ->
|
||||||
|
|
||||||
|
downloadBySystem(url, contentDisposition, mimeType)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun fullscreen(): Boolean {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun initData() {
|
||||||
|
super.initData()
|
||||||
|
LogHelper.e("initData")
|
||||||
|
intent?.apply {
|
||||||
|
title = getStringExtra("title") ?: ""
|
||||||
|
url = getStringExtra("url") ?: ""
|
||||||
|
}
|
||||||
|
|
||||||
|
viewModel.title.observe(this) {
|
||||||
|
// if(ToolsHelper.isNull(it)){
|
||||||
|
// baseBinding.baseToolbar.visibility=View.GONE
|
||||||
|
// }else{
|
||||||
|
// baseBinding.baseToolbar.visibility=View.VISIBLE
|
||||||
|
setTitleText(it)
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
viewModel.progress.observe(this) {
|
||||||
|
binding.XProgressBar.progress = it
|
||||||
|
if (it > 90) {
|
||||||
|
binding.XProgressBar.visibility = View.GONE
|
||||||
|
} else {
|
||||||
|
binding.XProgressBar.visibility = View.VISIBLE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun lateInitView() {
|
||||||
|
super.lateInitView()
|
||||||
|
LogHelper.e("lateInitView")
|
||||||
|
LogHelper.e(url)
|
||||||
|
setTitleText(title)
|
||||||
|
binding.XWebView.loadUrl(url)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun backBtnPressed() {
|
||||||
|
if (binding.XWebView.canGoBack()) {
|
||||||
|
binding.XWebView.goBack()
|
||||||
|
} else finish()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onBackPressed() {
|
||||||
|
if (binding.XWebView.canGoBack()) {
|
||||||
|
binding.XWebView.goBack()
|
||||||
|
} else finish()
|
||||||
|
}
|
||||||
|
|
||||||
|
private lateinit var filePathCallback: ValueCallback<Array<Uri>>
|
||||||
|
|
||||||
|
fun selectedFile(types: String, back: ValueCallback<Array<Uri>>) {
|
||||||
|
filePathCallback = back
|
||||||
|
|
||||||
|
if (types.contains("image")) {
|
||||||
|
runWithPermission(
|
||||||
|
Manifest.permission.CAMERA,
|
||||||
|
rationaleMethod = {
|
||||||
|
"无法获取相关权限".showMessage()
|
||||||
|
back.onReceiveValue(null)
|
||||||
|
},
|
||||||
|
permanentDeniedMethod = {
|
||||||
|
"请在设置界面开放对应权限".showMessage()
|
||||||
|
back.onReceiveValue(null)
|
||||||
|
},
|
||||||
|
callback = {
|
||||||
|
PictureSelector.create(this)
|
||||||
|
.openGallery(SelectMimeType.ofImage())
|
||||||
|
.setImageEngine(GlideEngine.createGlideEngine())
|
||||||
|
.setSelectionMode(SelectModeConfig.SINGLE)
|
||||||
|
.forResult(object : OnResultCallbackListener<LocalMedia> {
|
||||||
|
override fun onResult(result: ArrayList<LocalMedia>) {
|
||||||
|
val results =
|
||||||
|
result.map { FileHelper.getFileUri(File(it.realPath)) }
|
||||||
|
back.onReceiveValue(results.toTypedArray())
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCancel() {
|
||||||
|
back.onReceiveValue(null)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT).apply {
|
||||||
|
addCategory(Intent.CATEGORY_OPENABLE)
|
||||||
|
type = types
|
||||||
|
}
|
||||||
|
mContext.startActivityForResult(intent, 10012)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||||
|
super.onActivityResult(requestCode, resultCode, data)
|
||||||
|
|
||||||
|
if (requestCode == 10012) {
|
||||||
|
if (resultCode == RESULT_OK) {
|
||||||
|
data?.data?.also { uri ->
|
||||||
|
filePathCallback.onReceiveValue(arrayOf(uri))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
filePathCallback.onReceiveValue(emptyArray())
|
||||||
|
}
|
||||||
|
} else if (requestCode == 1111) {
|
||||||
|
|
||||||
|
|
||||||
|
if (resultCode == RESULT_OK) {
|
||||||
|
val obj: HmsScan? = data?.getParcelableExtra(ScanUtil.RESULT)
|
||||||
|
if (obj != null) {
|
||||||
|
viewModel.backScan(obj.originalValue)
|
||||||
|
} else {
|
||||||
|
viewModel.backScanError()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
viewModel.backScan("Cancel")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun downloadBySystem(url: String, contentDisposition: String, mimeType: String) {
|
||||||
|
val request = DownloadManager.Request(Uri.parse(url))
|
||||||
|
|
||||||
|
val fileName: String = URLUtil.guessFileName(url, contentDisposition, mimeType)
|
||||||
|
fileName.loge()
|
||||||
|
|
||||||
|
// request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE)
|
||||||
|
// 设置通知栏的标题,如果不设置,默认使用文件名
|
||||||
|
// request.setTitle(BuildConfig.APP_Name);
|
||||||
|
// 设置通知栏的描述
|
||||||
|
request.setDescription("正在下载文件:${fileName}");
|
||||||
|
// 允许在计费流量下下载
|
||||||
|
request.setAllowedOverMetered(false)
|
||||||
|
// 允许该记录在下载管理界面可见
|
||||||
|
request.setVisibleInDownloadsUi(true)
|
||||||
|
// 允许漫游时下载
|
||||||
|
request.setAllowedOverRoaming(true)
|
||||||
|
// 允许下载的网路类型
|
||||||
|
request.setAllowedNetworkTypes(DownloadManager.Request.NETWORK_WIFI)
|
||||||
|
// 设置下载文件保存的路径和文件名
|
||||||
|
val file = File(FileHelper.getDownloadPath(), fileName)
|
||||||
|
request.setDestinationUri(Uri.fromFile(file))
|
||||||
|
val downloadManager = getSystemService(DOWNLOAD_SERVICE) as DownloadManager
|
||||||
|
// 添加一个下载任务
|
||||||
|
val downloadId = downloadManager.enqueue(request)
|
||||||
|
downloadId.loge()
|
||||||
|
|
||||||
|
AlertView(
|
||||||
|
"", "当前文件已经开始下载,请查看通知栏下载进度", null, arrayOf("Confirm"), null, mContext,
|
||||||
|
AlertView.Style.Alert
|
||||||
|
) { _, _ ->
|
||||||
|
}.show()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,19 @@
|
|||||||
|
package com.xuqm.base.web
|
||||||
|
|
||||||
|
import android.webkit.WebResourceRequest
|
||||||
|
import android.webkit.WebView
|
||||||
|
import android.webkit.WebViewClient
|
||||||
|
import com.xuqm.base.common.LogHelper
|
||||||
|
|
||||||
|
class XWebViewClient : WebViewClient() {
|
||||||
|
override fun onPageFinished(view: WebView?, url: String?) {
|
||||||
|
super.onPageFinished(view, url)
|
||||||
|
LogHelper.e(url)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun shouldOverrideUrlLoading(view: WebView?, request: WebResourceRequest?): Boolean {
|
||||||
|
LogHelper.e(request?.toString())
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,22 @@
|
|||||||
|
package com.xuqm.base.web
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
|
||||||
|
object XWebViewHelper {
|
||||||
|
fun startWebWithTopBar(context: Context, url: String, title: String?) {
|
||||||
|
val intent: Intent = Intent(context, XWebViewActivity::class.java).apply {
|
||||||
|
title?.let { putExtra("title", title) }
|
||||||
|
putExtra("url", url)
|
||||||
|
putExtra("hasTopBar", true)
|
||||||
|
}
|
||||||
|
context.startActivity(intent)
|
||||||
|
}
|
||||||
|
fun startWebNoTopBar(context: Context, url: String) {
|
||||||
|
val intent: Intent = Intent(context, XWebViewActivity::class.java).apply {
|
||||||
|
putExtra("url", url)
|
||||||
|
putExtra("hasTopBar", false)
|
||||||
|
}
|
||||||
|
context.startActivity(intent)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,128 @@
|
|||||||
|
package com.xuqm.base.web
|
||||||
|
|
||||||
|
import android.Manifest
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import android.view.View
|
||||||
|
import androidx.lifecycle.LiveData
|
||||||
|
import androidx.lifecycle.MutableLiveData
|
||||||
|
import com.bigkoo.alertview.AlertView
|
||||||
|
import com.huawei.hms.hmsscankit.ScanUtil
|
||||||
|
import com.huawei.hms.ml.scan.HmsScan
|
||||||
|
import com.huawei.hms.ml.scan.HmsScanAnalyzerOptions
|
||||||
|
import com.xuqm.base.App
|
||||||
|
import com.xuqm.base.extensions.getStringForPreferences
|
||||||
|
import com.xuqm.base.extensions.log
|
||||||
|
import com.xuqm.base.extensions.runWithPermission
|
||||||
|
import com.xuqm.base.extensions.showMessage
|
||||||
|
import com.xuqm.base.viewmodel.BaseViewModel
|
||||||
|
import com.xuqm.base.common.SHARE_UESR_NAME
|
||||||
|
|
||||||
|
class XWebViewModel : BaseViewModel() {
|
||||||
|
lateinit var callBack: XWebCallBack
|
||||||
|
|
||||||
|
@SuppressLint("StaticFieldLeak")
|
||||||
|
lateinit var activity: XWebViewActivity
|
||||||
|
|
||||||
|
private val _title = MutableLiveData<String>()
|
||||||
|
val title: LiveData<String> = _title
|
||||||
|
|
||||||
|
fun setTitle(title: String) {
|
||||||
|
_title.postValue(title)
|
||||||
|
}
|
||||||
|
|
||||||
|
private lateinit var callId: String
|
||||||
|
fun scan(callBackId: String) {
|
||||||
|
callId = callBackId
|
||||||
|
|
||||||
|
App.getInstance().runOnUiThread {
|
||||||
|
activity.runWithPermission(
|
||||||
|
Manifest.permission.CAMERA,
|
||||||
|
rationaleMethod = {
|
||||||
|
"无法获取相关权限".showMessage()
|
||||||
|
callBackError(callId, "没有相关权限")
|
||||||
|
},
|
||||||
|
permanentDeniedMethod = {
|
||||||
|
"请在设置界面开放对应权限".showMessage()
|
||||||
|
callBackError(callId, "没有相关权限")
|
||||||
|
},
|
||||||
|
callback = {
|
||||||
|
ScanUtil.startScan(
|
||||||
|
activity, 1111, HmsScanAnalyzerOptions.Creator().setHmsScanTypes(
|
||||||
|
HmsScan.QRCODE_SCAN_TYPE
|
||||||
|
).create()
|
||||||
|
);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
fun backScan(msg: String) {
|
||||||
|
callBackSuccess(callId, msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun backScanError() {
|
||||||
|
callBackError(callId, "扫码失败")
|
||||||
|
}
|
||||||
|
|
||||||
|
private val _progress = MutableLiveData<Int>().also { it.postValue(0) }
|
||||||
|
val progress: LiveData<Int> = _progress
|
||||||
|
|
||||||
|
fun setProgress(progress: Int) {
|
||||||
|
_progress.postValue(progress)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun callBackSuccess(id: String) {
|
||||||
|
if (this::callBack.isInitialized) {
|
||||||
|
callBack.success(id, null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun callBackSuccess(id: String, data: Any) {
|
||||||
|
if (this::callBack.isInitialized) {
|
||||||
|
callBack.success(id, data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun callBackError(id: String, data: String) {
|
||||||
|
if (this::callBack.isInitialized) {
|
||||||
|
callBack.error(id, data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fun getUserInfo(callBackId: String) {
|
||||||
|
if (this::callBack.isInitialized) {
|
||||||
|
callBack.success(callBackId, activity.getStringForPreferences(SHARE_UESR_NAME))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
lateinit var v: AlertView
|
||||||
|
|
||||||
|
fun setTopMenu(id: String, date: List<String>) {
|
||||||
|
"================>".log()
|
||||||
|
App.getInstance().runOnUiThread {
|
||||||
|
if (date.isNotEmpty()) {
|
||||||
|
activity.baseBinding.baseToolbar.toolbarMenu.visibility = View.VISIBLE
|
||||||
|
activity.baseBinding.baseToolbar.toolbarMenu.setOnClickListener {
|
||||||
|
v = AlertView(
|
||||||
|
"请选择需要操作的内容", "", null, date.toTypedArray(), null, activity,
|
||||||
|
AlertView.Style.ActionSheet
|
||||||
|
) { _, position ->
|
||||||
|
selected(id, position)
|
||||||
|
}
|
||||||
|
v.show()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
activity.baseBinding.baseToolbar.toolbarMenu.visibility = View.GONE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun selected(callBackId: String, data: Int) {
|
||||||
|
v.dismissImmediately()
|
||||||
|
if (this::callBack.isInitialized) {
|
||||||
|
callBack.success(callBackId, data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
某些文件未显示,因为此 diff 中更改的文件太多 显示更多
正在加载...
在新工单中引用
屏蔽一个用户