部分问题修复
这个提交包含在:
父节点
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 中更改的文件太多 显示更多
正在加载...
在新工单中引用
屏蔽一个用户