徐勤民 преди 1 месец
родител
ревизия
d780cb9660
променени са 100 файла, в които са добавени 8802 реда и са изтрити 0 реда
  1. 2 0
      base/.gitattributes
  2. 83 0
      base/.gitignore
  3. 184 0
      base/README.md
  4. 94 0
      base/build.gradle
  5. 0 0
      base/consumer-rules.pro
  6. 21 0
      base/proguard-rules.pro
  7. 26 0
      base/src/main/AndroidManifest.xml
  8. 155 0
      base/src/main/java/com/livinglifetechway/quickpermissions_kotlin/PermissionsManager.kt
  9. 237 0
      base/src/main/java/com/livinglifetechway/quickpermissions_kotlin/util/PermissionCheckerFragment.kt
  10. 54 0
      base/src/main/java/com/livinglifetechway/quickpermissions_kotlin/util/PermissionsUtil.kt
  11. 11 0
      base/src/main/java/com/livinglifetechway/quickpermissions_kotlin/util/QuickPermissionsOptions.kt
  12. 30 0
      base/src/main/java/com/livinglifetechway/quickpermissions_kotlin/util/QuickPermissionsRequest.kt
  13. 111 0
      base/src/main/java/com/xuqm/base/App.java
  14. 222 0
      base/src/main/java/com/xuqm/base/CrashHandler.java
  15. 17 0
      base/src/main/java/com/xuqm/base/adapter/BaseItem.java
  16. 186 0
      base/src/main/java/com/xuqm/base/adapter/BaseNormalAdapter.java
  17. 157 0
      base/src/main/java/com/xuqm/base/adapter/BasePagedAdapter.java
  18. 59 0
      base/src/main/java/com/xuqm/base/adapter/CommonAdapter.java
  19. 36 0
      base/src/main/java/com/xuqm/base/adapter/CommonPagedAdapter.java
  20. 16 0
      base/src/main/java/com/xuqm/base/adapter/Diff.java
  21. 72 0
      base/src/main/java/com/xuqm/base/adapter/ElasticHorizontalScrollView.java
  22. 30 0
      base/src/main/java/com/xuqm/base/adapter/FragmentAdapter.java
  23. 15 0
      base/src/main/java/com/xuqm/base/adapter/ItemViewDelegate.java
  24. 95 0
      base/src/main/java/com/xuqm/base/adapter/ItemViewDelegateManager.java
  25. 178 0
      base/src/main/java/com/xuqm/base/adapter/SlipReAdapter.java
  26. 171 0
      base/src/main/java/com/xuqm/base/adapter/ViewHolder.java
  27. 10 0
      base/src/main/java/com/xuqm/base/adapter/callback/AdapterClickListener.java
  28. 11 0
      base/src/main/java/com/xuqm/base/adapter/callback/AdapterItemClickListener.java
  29. 11 0
      base/src/main/java/com/xuqm/base/adapter/callback/AdapterItemLongClickListener.java
  30. 143 0
      base/src/main/java/com/xuqm/base/common/AESCBCUtil.java
  31. 106 0
      base/src/main/java/com/xuqm/base/common/AppManager.java
  32. 87 0
      base/src/main/java/com/xuqm/base/common/DeviceUuidFactory.java
  33. 10 0
      base/src/main/java/com/xuqm/base/common/DimensHelper.java
  34. 193 0
      base/src/main/java/com/xuqm/base/common/FileHelper.java
  35. 112 0
      base/src/main/java/com/xuqm/base/common/GlideEngine.java
  36. 61 0
      base/src/main/java/com/xuqm/base/common/GsonImplHelp.java
  37. 53 0
      base/src/main/java/com/xuqm/base/common/ImageHelp.java
  38. 34 0
      base/src/main/java/com/xuqm/base/common/ImageHelper.java
  39. 31 0
      base/src/main/java/com/xuqm/base/common/Json.java
  40. 75 0
      base/src/main/java/com/xuqm/base/common/LogHelper.java
  41. 8 0
      base/src/main/java/com/xuqm/base/common/RefreshResult.java
  42. 18 0
      base/src/main/java/com/xuqm/base/common/ScreenUtils.java
  43. 13 0
      base/src/main/java/com/xuqm/base/common/SharedPreferencesConfigs.kt
  44. 94 0
      base/src/main/java/com/xuqm/base/common/TimeHelper.java
  45. 274 0
      base/src/main/java/com/xuqm/base/common/ToolsHelper.java
  46. 23 0
      base/src/main/java/com/xuqm/base/datasource/DataSourceFactory.java
  47. 13 0
      base/src/main/java/com/xuqm/base/datasource/PagedDataLoader.java
  48. 26 0
      base/src/main/java/com/xuqm/base/datasource/PagedDataSource.java
  49. 28 0
      base/src/main/java/com/xuqm/base/di/component/AppComponent.java
  50. 28 0
      base/src/main/java/com/xuqm/base/di/interceptor/HttpLogger.java
  51. 57 0
      base/src/main/java/com/xuqm/base/di/interceptor/LoggingInterceptor.java
  52. 82 0
      base/src/main/java/com/xuqm/base/di/manager/HttpManager.java
  53. 97 0
      base/src/main/java/com/xuqm/base/di/module/NetworkModule.java
  54. 83 0
      base/src/main/java/com/xuqm/base/dialog/AlertDialogFragment.kt
  55. 164 0
      base/src/main/java/com/xuqm/base/dialog/loading/ColorfulRingProgressView.java
  56. 238 0
      base/src/main/java/com/xuqm/base/dialog/loading/LoadingDialog.java
  57. 232 0
      base/src/main/java/com/xuqm/base/extensions/CommonExt.kt
  58. 10 0
      base/src/main/java/com/xuqm/base/extensions/FontExt.kt
  59. 25 0
      base/src/main/java/com/xuqm/base/extensions/Fonts.kt
  60. 22 0
      base/src/main/java/com/xuqm/base/file/DownloadInterceptor.java
  61. 11 0
      base/src/main/java/com/xuqm/base/file/DownloadListener.java
  62. 70 0
      base/src/main/java/com/xuqm/base/file/DownloadResponseBody.java
  63. 80 0
      base/src/main/java/com/xuqm/base/file/FileDownLoadObserver.java
  64. 178 0
      base/src/main/java/com/xuqm/base/file/FileDownloadAndUploadManager.java
  65. 48 0
      base/src/main/java/com/xuqm/base/model/HttpResult.java
  66. 38 0
      base/src/main/java/com/xuqm/base/model/KeyValueData.java
  67. 21 0
      base/src/main/java/com/xuqm/base/repository/CommonService.java
  68. 382 0
      base/src/main/java/com/xuqm/base/ui/BaseActivity.java
  69. 61 0
      base/src/main/java/com/xuqm/base/ui/BaseFragment.java
  70. 161 0
      base/src/main/java/com/xuqm/base/ui/BaseListActivity.java
  71. 174 0
      base/src/main/java/com/xuqm/base/ui/BaseListAppBarFragment.java
  72. 174 0
      base/src/main/java/com/xuqm/base/ui/BaseListFormLayoutActivity.java
  73. 163 0
      base/src/main/java/com/xuqm/base/ui/BaseListFragment.java
  74. 185 0
      base/src/main/java/com/xuqm/base/ui/BaseListFromLayoutFragment.java
  75. 44 0
      base/src/main/java/com/xuqm/base/ui/adapter/ImageViewAdapter.java
  76. 5 0
      base/src/main/java/com/xuqm/base/ui/callback/ToolBarListener.java
  77. 23 0
      base/src/main/java/com/xuqm/base/ui/callback/UiCallback.java
  78. 100 0
      base/src/main/java/com/xuqm/base/view/EmptyMsgView.java
  79. 112 0
      base/src/main/java/com/xuqm/base/view/EmptyView.java
  80. 120 0
      base/src/main/java/com/xuqm/base/view/FlowRadioGroup.java
  81. 35 0
      base/src/main/java/com/xuqm/base/view/GalleryTransformer.kt
  82. 69 0
      base/src/main/java/com/xuqm/base/view/MaxLimitRecyclerView.java
  83. 657 0
      base/src/main/java/com/xuqm/base/view/SelectableRoundedImageView.java
  84. 83 0
      base/src/main/java/com/xuqm/base/view/StrokeTextView.java
  85. 98 0
      base/src/main/java/com/xuqm/base/view/SuperViewPager.kt
  86. 150 0
      base/src/main/java/com/xuqm/base/view/ToolbarLayout.java
  87. 5 0
      base/src/main/java/com/xuqm/base/view/enu/Status.java
  88. 132 0
      base/src/main/java/com/xuqm/base/viewmodel/BaseListViewModel.java
  89. 67 0
      base/src/main/java/com/xuqm/base/viewmodel/BaseViewModel.java
  90. 7 0
      base/src/main/java/com/xuqm/base/viewmodel/callback/AdapterObserverCallback.java
  91. 13 0
      base/src/main/java/com/xuqm/base/viewmodel/callback/DataObserverCallback.java
  92. 7 0
      base/src/main/java/com/xuqm/base/viewmodel/callback/Response.java
  93. 41 0
      base/src/main/java/com/xuqm/base/web/XAndroidInterface.kt
  94. 25 0
      base/src/main/java/com/xuqm/base/web/XWebCallBack.kt
  95. 115 0
      base/src/main/java/com/xuqm/base/web/XWebChromeClient.kt
  96. 8 0
      base/src/main/java/com/xuqm/base/web/XWebJsCallModel.kt
  97. 237 0
      base/src/main/java/com/xuqm/base/web/XWebViewActivity.kt
  98. 19 0
      base/src/main/java/com/xuqm/base/web/XWebViewClient.kt
  99. 22 0
      base/src/main/java/com/xuqm/base/web/XWebViewHelper.kt
  100. 128 0
      base/src/main/java/com/xuqm/base/web/XWebViewModel.kt

+ 2 - 0
base/.gitattributes

@@ -0,0 +1,2 @@
+# Auto detect text files and perform LF normalization
+* text=auto

+ 83 - 0
base/.gitignore

@@ -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 - 0
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 - 0
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 - 0
base/consumer-rules.pro


+ 21 - 0
base/proguard-rules.pro

@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+#   http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+#   public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile

+ 26 - 0
base/src/main/AndroidManifest.xml

@@ -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>

+ 155 - 0
base/src/main/java/com/livinglifetechway/quickpermissions_kotlin/PermissionsManager.kt

@@ -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
+}

+ 237 - 0
base/src/main/java/com/livinglifetechway/quickpermissions_kotlin/util/PermissionCheckerFragment.kt

@@ -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)
+        }
+    }
+}

+ 54 - 0
base/src/main/java/com/livinglifetechway/quickpermissions_kotlin/util/PermissionsUtil.kt

@@ -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
+    }
+
+}

+ 11 - 0
base/src/main/java/com/livinglifetechway/quickpermissions_kotlin/util/QuickPermissionsOptions.kt

@@ -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
+)

+ 30 - 0
base/src/main/java/com/livinglifetechway/quickpermissions_kotlin/util/QuickPermissionsRequest.kt

@@ -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()
+}

+ 111 - 0
base/src/main/java/com/xuqm/base/App.java

@@ -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);
+    }
+
+}

+ 222 - 0
base/src/main/java/com/xuqm/base/CrashHandler.java

@@ -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;
+
+
+    }
+}

+ 17 - 0
base/src/main/java/com/xuqm/base/adapter/BaseItem.java

@@ -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;
+    }
+}

+ 186 - 0
base/src/main/java/com/xuqm/base/adapter/BaseNormalAdapter.java

@@ -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);
+        }
+    }
+
+}

+ 157 - 0
base/src/main/java/com/xuqm/base/adapter/BasePagedAdapter.java

@@ -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);
+        }
+    }
+
+}

+ 59 - 0
base/src/main/java/com/xuqm/base/adapter/CommonAdapter.java

@@ -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);
+
+}

+ 36 - 0
base/src/main/java/com/xuqm/base/adapter/CommonPagedAdapter.java

@@ -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);
+
+}

+ 16 - 0
base/src/main/java/com/xuqm/base/adapter/Diff.java

@@ -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();
+    }
+}

+ 72 - 0
base/src/main/java/com/xuqm/base/adapter/ElasticHorizontalScrollView.java

@@ -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;
+    }
+}

+ 30 - 0
base/src/main/java/com/xuqm/base/adapter/FragmentAdapter.java

@@ -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();
+    }
+}

+ 15 - 0
base/src/main/java/com/xuqm/base/adapter/ItemViewDelegate.java

@@ -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绘制与事件添加
+
+}

+ 95 - 0
base/src/main/java/com/xuqm/base/adapter/ItemViewDelegateManager.java

@@ -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);
+    }
+}

+ 178 - 0
base/src/main/java/com/xuqm/base/adapter/SlipReAdapter.java

@@ -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);
+  }
+
+
+}

+ 171 - 0
base/src/main/java/com/xuqm/base/adapter/ViewHolder.java

@@ -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;
+    }
+
+}

+ 10 - 0
base/src/main/java/com/xuqm/base/adapter/callback/AdapterClickListener.java

@@ -0,0 +1,10 @@
+package com.xuqm.base.adapter.callback;
+
+import android.view.View;
+
+/**
+ * adapter中,为item元素设置点击时间时候用到的监听
+ */
+public interface AdapterClickListener {
+    void onClick(View view);
+}

+ 11 - 0
base/src/main/java/com/xuqm/base/adapter/callback/AdapterItemClickListener.java

@@ -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);
+}

+ 11 - 0
base/src/main/java/com/xuqm/base/adapter/callback/AdapterItemLongClickListener.java

@@ -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);
+}

+ 143 - 0
base/src/main/java/com/xuqm/base/common/AESCBCUtil.java

@@ -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 "";
+    }
+}

+ 106 - 0
base/src/main/java/com/xuqm/base/common/AppManager.java

@@ -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));
+            }
+        }
+    }
+}

+ 87 - 0
base/src/main/java/com/xuqm/base/common/DeviceUuidFactory.java

@@ -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;
+    }
+}

+ 10 - 0
base/src/main/java/com/xuqm/base/common/DimensHelper.java

@@ -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());
+    }
+}

Файловите разлики са ограничени, защото са твърде много
+ 193 - 0
base/src/main/java/com/xuqm/base/common/FileHelper.java


+ 112 - 0
base/src/main/java/com/xuqm/base/common/GlideEngine.java

@@ -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;
+    }
+}

+ 61 - 0
base/src/main/java/com/xuqm/base/common/GsonImplHelp.java

@@ -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);
+    }
+
+}

+ 53 - 0
base/src/main/java/com/xuqm/base/common/ImageHelp.java

@@ -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);
+    }
+}

+ 34 - 0
base/src/main/java/com/xuqm/base/common/ImageHelper.java

@@ -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);
+    }
+}

+ 31 - 0
base/src/main/java/com/xuqm/base/common/Json.java

@@ -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);
+
+}

+ 75 - 0
base/src/main/java/com/xuqm/base/common/LogHelper.java

@@ -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];
+    }
+}

+ 8 - 0
base/src/main/java/com/xuqm/base/common/RefreshResult.java

@@ -0,0 +1,8 @@
+package com.xuqm.base.common;
+
+/**
+ * 下拉刷新的状态码表
+ */
+public enum RefreshResult {
+    SUCCEED, FAILED, NO_DATA, NO_MORE
+}

+ 18 - 0
base/src/main/java/com/xuqm/base/common/ScreenUtils.java

@@ -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;
+    }
+}

+ 13 - 0
base/src/main/java/com/xuqm/base/common/SharedPreferencesConfigs.kt

@@ -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"

+ 94 - 0
base/src/main/java/com/xuqm/base/common/TimeHelper.java

@@ -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;
+    }
+}

+ 274 - 0
base/src/main/java/com/xuqm/base/common/ToolsHelper.java

@@ -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]);
+    }
+}

+ 23 - 0
base/src/main/java/com/xuqm/base/datasource/DataSourceFactory.java

@@ -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;
+    }
+}

+ 13 - 0
base/src/main/java/com/xuqm/base/datasource/PagedDataLoader.java

@@ -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();
+}

+ 26 - 0
base/src/main/java/com/xuqm/base/datasource/PagedDataSource.java

@@ -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);
+    }
+}

+ 28 - 0
base/src/main/java/com/xuqm/base/di/component/AppComponent.java

@@ -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();
+}

+ 28 - 0
base/src/main/java/com/xuqm/base/di/interceptor/HttpLogger.java

@@ -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());
+        }
+    }
+}

+ 57 - 0
base/src/main/java/com/xuqm/base/di/interceptor/LoggingInterceptor.java

@@ -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();
+
+    }
+}

+ 82 - 0
base/src/main/java/com/xuqm/base/di/manager/HttpManager.java

@@ -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);
+    }
+}

+ 97 - 0
base/src/main/java/com/xuqm/base/di/module/NetworkModule.java

@@ -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();
+    }
+
+}

+ 83 - 0
base/src/main/java/com/xuqm/base/dialog/AlertDialogFragment.kt

@@ -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")
+    }
+
+}

+ 164 - 0
base/src/main/java/com/xuqm/base/dialog/loading/ColorfulRingProgressView.java

@@ -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();
+    }
+}

+ 238 - 0
base/src/main/java/com/xuqm/base/dialog/loading/LoadingDialog.java

@@ -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);
+        }
+    }
+}

+ 232 - 0
base/src/main/java/com/xuqm/base/extensions/CommonExt.kt

@@ -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()
+}

+ 10 - 0
base/src/main/java/com/xuqm/base/extensions/FontExt.kt

@@ -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")
+}

+ 25 - 0
base/src/main/java/com/xuqm/base/extensions/Fonts.kt

@@ -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"
+    }
+
+}

+ 22 - 0
base/src/main/java/com/xuqm/base/file/DownloadInterceptor.java

@@ -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();
+    }
+}

+ 11 - 0
base/src/main/java/com/xuqm/base/file/DownloadListener.java

@@ -0,0 +1,11 @@
+package com.xuqm.base.file;
+
+public interface DownloadListener {
+    void onStartDownload();
+
+    void onProgress(int progress);
+
+    void onFinishDownload();
+
+    void onFail(String errorInfo);
+}

+ 70 - 0
base/src/main/java/com/xuqm/base/file/DownloadResponseBody.java

@@ -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;
+            }
+        };
+
+    }
+}

+ 80 - 0
base/src/main/java/com/xuqm/base/file/FileDownLoadObserver.java

@@ -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();
+            }
+        }
+    }
+}

+ 178 - 0
base/src/main/java/com/xuqm/base/file/FileDownloadAndUploadManager.java

@@ -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;
+    }
+}

+ 48 - 0
base/src/main/java/com/xuqm/base/model/HttpResult.java

@@ -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 +
+                '}';
+    }
+}

+ 38 - 0
base/src/main/java/com/xuqm/base/model/KeyValueData.java

@@ -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 +
+                '}';
+    }
+}

+ 21 - 0
base/src/main/java/com/xuqm/base/repository/CommonService.java

@@ -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);
+}

+ 382 - 0
base/src/main/java/com/xuqm/base/ui/BaseActivity.java

@@ -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;
+    }
+
+}

+ 61 - 0
base/src/main/java/com/xuqm/base/ui/BaseFragment.java

@@ -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);
+    }
+}

+ 161 - 0
base/src/main/java/com/xuqm/base/ui/BaseListActivity.java

@@ -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();
+}

+ 174 - 0
base/src/main/java/com/xuqm/base/ui/BaseListAppBarFragment.java

@@ -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();
+}

+ 174 - 0
base/src/main/java/com/xuqm/base/ui/BaseListFormLayoutActivity.java

@@ -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();
+}

+ 163 - 0
base/src/main/java/com/xuqm/base/ui/BaseListFragment.java

@@ -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);
+    }
+
+    ;
+}

+ 185 - 0
base/src/main/java/com/xuqm/base/ui/BaseListFromLayoutFragment.java

@@ -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);
+    }
+
+    ;
+}

+ 44 - 0
base/src/main/java/com/xuqm/base/ui/adapter/ImageViewAdapter.java

@@ -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);
+    }
+
+
+}

+ 5 - 0
base/src/main/java/com/xuqm/base/ui/callback/ToolBarListener.java

@@ -0,0 +1,5 @@
+package com.xuqm.base.ui.callback;
+
+public interface ToolBarListener {
+    void back();
+}

+ 23 - 0
base/src/main/java/com/xuqm/base/ui/callback/UiCallback.java

@@ -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();
+
+}

+ 100 - 0
base/src/main/java/com/xuqm/base/view/EmptyMsgView.java

@@ -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;
+        }
+    }
+}

+ 112 - 0
base/src/main/java/com/xuqm/base/view/EmptyView.java

@@ -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;
+        }
+    }
+}

+ 120 - 0
base/src/main/java/com/xuqm/base/view/FlowRadioGroup.java

@@ -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;
+
+        }
+
+    }
+
+}

+ 35 - 0
base/src/main/java/com/xuqm/base/view/GalleryTransformer.kt

@@ -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
+        }
+    }
+}

+ 69 - 0
base/src/main/java/com/xuqm/base/view/MaxLimitRecyclerView.java

@@ -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);
+        }
+    }
+}

+ 657 - 0
base/src/main/java/com/xuqm/base/view/SelectableRoundedImageView.java

@@ -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;
+        }
+    }
+
+}

+ 83 - 0
base/src/main/java/com/xuqm/base/view/StrokeTextView.java

@@ -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);
+    }
+}

+ 98 - 0
base/src/main/java/com/xuqm/base/view/SuperViewPager.kt

@@ -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)
+    }
+}

+ 150 - 0
base/src/main/java/com/xuqm/base/view/ToolbarLayout.java

@@ -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;
+    }
+}

+ 5 - 0
base/src/main/java/com/xuqm/base/view/enu/Status.java

@@ -0,0 +1,5 @@
+package com.xuqm.base.view.enu;
+
+public enum Status {
+    DISMISS, LOADING, NO_DATA, LOAD_FAILED, NETWORK_UNAVAILABLE
+}

+ 132 - 0
base/src/main/java/com/xuqm/base/viewmodel/BaseListViewModel.java

@@ -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);
+
+}
+
+

+ 67 - 0
base/src/main/java/com/xuqm/base/viewmodel/BaseViewModel.java

@@ -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();
+        }
+    }
+}

+ 7 - 0
base/src/main/java/com/xuqm/base/viewmodel/callback/AdapterObserverCallback.java

@@ -0,0 +1,7 @@
+package com.xuqm.base.viewmodel.callback;
+
+public interface AdapterObserverCallback {
+    void notifyItem(int position, Object payload);
+
+    void removeItem(int position);
+}

+ 13 - 0
base/src/main/java/com/xuqm/base/viewmodel/callback/DataObserverCallback.java

@@ -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);
+}

+ 7 - 0
base/src/main/java/com/xuqm/base/viewmodel/callback/Response.java

@@ -0,0 +1,7 @@
+package com.xuqm.base.viewmodel.callback;
+
+import java.util.ArrayList;
+
+public interface Response<T> {
+    void onResponse(ArrayList<T> onResponse);
+}

+ 41 - 0
base/src/main/java/com/xuqm/base/web/XAndroidInterface.kt

@@ -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)
+    }
+}

+ 25 - 0
base/src/main/java/com/xuqm/base/web/XWebCallBack.kt

@@ -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)+")"
+            )
+        }
+    }
+}

+ 115 - 0
base/src/main/java/com/xuqm/base/web/XWebChromeClient.kt

@@ -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)
+    }
+}

+ 8 - 0
base/src/main/java/com/xuqm/base/web/XWebJsCallModel.kt

@@ -0,0 +1,8 @@
+package com.xuqm.base.web
+
+data class XWebJsCallModel(
+    val callBackId: String,
+    val status: Int,
+    val errMsg: String?,
+    val data: Any?
+)

+ 237 - 0
base/src/main/java/com/xuqm/base/web/XWebViewActivity.kt

@@ -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()
+    }
+
+}

+ 19 - 0
base/src/main/java/com/xuqm/base/web/XWebViewClient.kt

@@ -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
+    }
+
+}

+ 22 - 0
base/src/main/java/com/xuqm/base/web/XWebViewHelper.kt

@@ -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)
+    }
+}

+ 128 - 0
base/src/main/java/com/xuqm/base/web/XWebViewModel.kt

@@ -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)
+        }
+    }
+
+}

Някои файлове не бяха показани, защото твърде много файлове са промени