.gitignore
New file @@ -0,0 +1,88 @@ # Built application files *.apk *.aar *.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 .cxx/ # 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/ # Android Profiling *.hprof .idea/.gitignore
New file @@ -0,0 +1,3 @@ # Default ignored files /shelf/ /workspace.xml .idea/compiler.xml
New file @@ -0,0 +1,6 @@ <?xml version="1.0" encoding="UTF-8"?> <project version="4"> <component name="CompilerConfiguration"> <bytecodeTargetLevel target="11" /> </component> </project> .idea/dbnavigator.xml
New file @@ -0,0 +1,420 @@ <?xml version="1.0" encoding="UTF-8"?> <project version="4"> <component name="DBNavigator.Project.DataEditorManager"> <record-view-column-sorting-type value="BY_INDEX" /> <value-preview-text-wrapping value="false" /> <value-preview-pinned value="false" /> </component> <component name="DBNavigator.Project.DatabaseBrowserManager"> <autoscroll-to-editor value="false" /> <autoscroll-from-editor value="true" /> <show-object-properties value="true" /> <loaded-nodes /> </component> <component name="DBNavigator.Project.DatabaseEditorStateManager"> <last-used-providers /> </component> <component name="DBNavigator.Project.DatabaseFileManager"> <open-files /> </component> <component name="DBNavigator.Project.Settings"> <connections /> <browser-settings> <general> <display-mode value="TABBED" /> <navigation-history-size value="100" /> <show-object-details value="false" /> </general> <filters> <object-type-filter> <object-type name="SCHEMA" enabled="true" /> <object-type name="USER" enabled="true" /> <object-type name="ROLE" enabled="true" /> <object-type name="PRIVILEGE" enabled="true" /> <object-type name="CHARSET" enabled="true" /> <object-type name="TABLE" enabled="true" /> <object-type name="VIEW" enabled="true" /> <object-type name="MATERIALIZED_VIEW" enabled="true" /> <object-type name="NESTED_TABLE" enabled="true" /> <object-type name="COLUMN" enabled="true" /> <object-type name="INDEX" enabled="true" /> <object-type name="CONSTRAINT" enabled="true" /> <object-type name="DATASET_TRIGGER" enabled="true" /> <object-type name="DATABASE_TRIGGER" enabled="true" /> <object-type name="SYNONYM" enabled="true" /> <object-type name="SEQUENCE" enabled="true" /> <object-type name="PROCEDURE" enabled="true" /> <object-type name="FUNCTION" enabled="true" /> <object-type name="PACKAGE" enabled="true" /> <object-type name="TYPE" enabled="true" /> <object-type name="TYPE_ATTRIBUTE" enabled="true" /> <object-type name="ARGUMENT" enabled="true" /> <object-type name="DIMENSION" enabled="true" /> <object-type name="CLUSTER" enabled="true" /> <object-type name="DBLINK" enabled="true" /> </object-type-filter> </filters> <sorting> <object-type name="COLUMN" sorting-type="NAME" /> <object-type name="FUNCTION" sorting-type="NAME" /> <object-type name="PROCEDURE" sorting-type="NAME" /> <object-type name="ARGUMENT" sorting-type="POSITION" /> <object-type name="TYPE ATTRIBUTE" sorting-type="POSITION" /> </sorting> <default-editors> <object-type name="VIEW" editor-type="SELECTION" /> <object-type name="PACKAGE" editor-type="SELECTION" /> <object-type name="TYPE" editor-type="SELECTION" /> </default-editors> </browser-settings> <navigation-settings> <lookup-filters> <lookup-objects> <object-type name="SCHEMA" enabled="true" /> <object-type name="USER" enabled="false" /> <object-type name="ROLE" enabled="false" /> <object-type name="PRIVILEGE" enabled="false" /> <object-type name="CHARSET" enabled="false" /> <object-type name="TABLE" enabled="true" /> <object-type name="VIEW" enabled="true" /> <object-type name="MATERIALIZED VIEW" enabled="true" /> <object-type name="INDEX" enabled="true" /> <object-type name="CONSTRAINT" enabled="true" /> <object-type name="DATASET TRIGGER" enabled="true" /> <object-type name="DATABASE TRIGGER" enabled="true" /> <object-type name="SYNONYM" enabled="false" /> <object-type name="SEQUENCE" enabled="true" /> <object-type name="PROCEDURE" enabled="true" /> <object-type name="FUNCTION" enabled="true" /> <object-type name="PACKAGE" enabled="true" /> <object-type name="TYPE" enabled="true" /> <object-type name="DIMENSION" enabled="false" /> <object-type name="CLUSTER" enabled="false" /> <object-type name="DBLINK" enabled="true" /> </lookup-objects> <force-database-load value="false" /> <prompt-connection-selection value="true" /> <prompt-schema-selection value="true" /> </lookup-filters> </navigation-settings> <dataset-grid-settings> <general> <enable-zooming value="true" /> <enable-column-tooltip value="true" /> </general> <sorting> <nulls-first value="true" /> <max-sorting-columns value="4" /> </sorting> <audit-columns> <column-names value="" /> <visible value="true" /> <editable value="false" /> </audit-columns> </dataset-grid-settings> <dataset-editor-settings> <text-editor-popup> <active value="false" /> <active-if-empty value="false" /> <data-length-threshold value="100" /> <popup-delay value="1000" /> </text-editor-popup> <values-actions-popup> <show-popup-button value="true" /> <element-count-threshold value="1000" /> <data-length-threshold value="250" /> </values-actions-popup> <general> <fetch-block-size value="100" /> <fetch-timeout value="30" /> <trim-whitespaces value="true" /> <convert-empty-strings-to-null value="true" /> <select-content-on-cell-edit value="true" /> <large-value-preview-active value="true" /> </general> <filters> <prompt-filter-dialog value="true" /> <default-filter-type value="BASIC" /> </filters> <qualified-text-editor text-length-threshold="300"> <content-types> <content-type name="Text" enabled="true" /> <content-type name="Properties" enabled="true" /> <content-type name="XML" enabled="true" /> <content-type name="DTD" enabled="true" /> <content-type name="HTML" enabled="true" /> <content-type name="XHTML" enabled="true" /> <content-type name="Java" enabled="true" /> <content-type name="SQL" enabled="true" /> <content-type name="PL/SQL" enabled="true" /> <content-type name="JSON" enabled="true" /> <content-type name="JSON5" enabled="true" /> <content-type name="Groovy" enabled="true" /> <content-type name="AIDL" enabled="true" /> <content-type name="YAML" enabled="true" /> <content-type name="Manifest" enabled="true" /> </content-types> </qualified-text-editor> <record-navigation> <navigation-target value="VIEWER" /> </record-navigation> </dataset-editor-settings> <code-editor-settings> <general> <show-object-navigation-gutter value="false" /> <show-spec-declaration-navigation-gutter value="true" /> <enable-spellchecking value="true" /> <enable-reference-spellchecking value="false" /> </general> <confirmations> <save-changes value="false" /> <revert-changes value="true" /> </confirmations> </code-editor-settings> <code-completion-settings> <filters> <basic-filter> <filter-element type="RESERVED_WORD" id="keyword" selected="true" /> <filter-element type="RESERVED_WORD" id="function" selected="true" /> <filter-element type="RESERVED_WORD" id="parameter" selected="true" /> <filter-element type="RESERVED_WORD" id="datatype" selected="true" /> <filter-element type="RESERVED_WORD" id="exception" selected="true" /> <filter-element type="OBJECT" id="schema" selected="true" /> <filter-element type="OBJECT" id="role" selected="true" /> <filter-element type="OBJECT" id="user" selected="true" /> <filter-element type="OBJECT" id="privilege" selected="true" /> <user-schema> <filter-element type="OBJECT" id="table" selected="true" /> <filter-element type="OBJECT" id="view" selected="true" /> <filter-element type="OBJECT" id="materialized view" selected="true" /> <filter-element type="OBJECT" id="index" selected="true" /> <filter-element type="OBJECT" id="constraint" selected="true" /> <filter-element type="OBJECT" id="trigger" selected="true" /> <filter-element type="OBJECT" id="synonym" selected="false" /> <filter-element type="OBJECT" id="sequence" selected="true" /> <filter-element type="OBJECT" id="procedure" selected="true" /> <filter-element type="OBJECT" id="function" selected="true" /> <filter-element type="OBJECT" id="package" selected="true" /> <filter-element type="OBJECT" id="type" selected="true" /> <filter-element type="OBJECT" id="dimension" selected="true" /> <filter-element type="OBJECT" id="cluster" selected="true" /> <filter-element type="OBJECT" id="dblink" selected="true" /> </user-schema> <public-schema> <filter-element type="OBJECT" id="table" selected="false" /> <filter-element type="OBJECT" id="view" selected="false" /> <filter-element type="OBJECT" id="materialized view" selected="false" /> <filter-element type="OBJECT" id="index" selected="false" /> <filter-element type="OBJECT" id="constraint" selected="false" /> <filter-element type="OBJECT" id="trigger" selected="false" /> <filter-element type="OBJECT" id="synonym" selected="false" /> <filter-element type="OBJECT" id="sequence" selected="false" /> <filter-element type="OBJECT" id="procedure" selected="false" /> <filter-element type="OBJECT" id="function" selected="false" /> <filter-element type="OBJECT" id="package" selected="false" /> <filter-element type="OBJECT" id="type" selected="false" /> <filter-element type="OBJECT" id="dimension" selected="false" /> <filter-element type="OBJECT" id="cluster" selected="false" /> <filter-element type="OBJECT" id="dblink" selected="false" /> </public-schema> <any-schema> <filter-element type="OBJECT" id="table" selected="true" /> <filter-element type="OBJECT" id="view" selected="true" /> <filter-element type="OBJECT" id="materialized view" selected="true" /> <filter-element type="OBJECT" id="index" selected="true" /> <filter-element type="OBJECT" id="constraint" selected="true" /> <filter-element type="OBJECT" id="trigger" selected="true" /> <filter-element type="OBJECT" id="synonym" selected="true" /> <filter-element type="OBJECT" id="sequence" selected="true" /> <filter-element type="OBJECT" id="procedure" selected="true" /> <filter-element type="OBJECT" id="function" selected="true" /> <filter-element type="OBJECT" id="package" selected="true" /> <filter-element type="OBJECT" id="type" selected="true" /> <filter-element type="OBJECT" id="dimension" selected="true" /> <filter-element type="OBJECT" id="cluster" selected="true" /> <filter-element type="OBJECT" id="dblink" selected="true" /> </any-schema> </basic-filter> <extended-filter> <filter-element type="RESERVED_WORD" id="keyword" selected="true" /> <filter-element type="RESERVED_WORD" id="function" selected="true" /> <filter-element type="RESERVED_WORD" id="parameter" selected="true" /> <filter-element type="RESERVED_WORD" id="datatype" selected="true" /> <filter-element type="RESERVED_WORD" id="exception" selected="true" /> <filter-element type="OBJECT" id="schema" selected="true" /> <filter-element type="OBJECT" id="user" selected="true" /> <filter-element type="OBJECT" id="role" selected="true" /> <filter-element type="OBJECT" id="privilege" selected="true" /> <user-schema> <filter-element type="OBJECT" id="table" selected="true" /> <filter-element type="OBJECT" id="view" selected="true" /> <filter-element type="OBJECT" id="materialized view" selected="true" /> <filter-element type="OBJECT" id="index" selected="true" /> <filter-element type="OBJECT" id="constraint" selected="true" /> <filter-element type="OBJECT" id="trigger" selected="true" /> <filter-element type="OBJECT" id="synonym" selected="true" /> <filter-element type="OBJECT" id="sequence" selected="true" /> <filter-element type="OBJECT" id="procedure" selected="true" /> <filter-element type="OBJECT" id="function" selected="true" /> <filter-element type="OBJECT" id="package" selected="true" /> <filter-element type="OBJECT" id="type" selected="true" /> <filter-element type="OBJECT" id="dimension" selected="true" /> <filter-element type="OBJECT" id="cluster" selected="true" /> <filter-element type="OBJECT" id="dblink" selected="true" /> </user-schema> <public-schema> <filter-element type="OBJECT" id="table" selected="true" /> <filter-element type="OBJECT" id="view" selected="true" /> <filter-element type="OBJECT" id="materialized view" selected="true" /> <filter-element type="OBJECT" id="index" selected="true" /> <filter-element type="OBJECT" id="constraint" selected="true" /> <filter-element type="OBJECT" id="trigger" selected="true" /> <filter-element type="OBJECT" id="synonym" selected="true" /> <filter-element type="OBJECT" id="sequence" selected="true" /> <filter-element type="OBJECT" id="procedure" selected="true" /> <filter-element type="OBJECT" id="function" selected="true" /> <filter-element type="OBJECT" id="package" selected="true" /> <filter-element type="OBJECT" id="type" selected="true" /> <filter-element type="OBJECT" id="dimension" selected="true" /> <filter-element type="OBJECT" id="cluster" selected="true" /> <filter-element type="OBJECT" id="dblink" selected="true" /> </public-schema> <any-schema> <filter-element type="OBJECT" id="table" selected="true" /> <filter-element type="OBJECT" id="view" selected="true" /> <filter-element type="OBJECT" id="materialized view" selected="true" /> <filter-element type="OBJECT" id="index" selected="true" /> <filter-element type="OBJECT" id="constraint" selected="true" /> <filter-element type="OBJECT" id="trigger" selected="true" /> <filter-element type="OBJECT" id="synonym" selected="true" /> <filter-element type="OBJECT" id="sequence" selected="true" /> <filter-element type="OBJECT" id="procedure" selected="true" /> <filter-element type="OBJECT" id="function" selected="true" /> <filter-element type="OBJECT" id="package" selected="true" /> <filter-element type="OBJECT" id="type" selected="true" /> <filter-element type="OBJECT" id="dimension" selected="true" /> <filter-element type="OBJECT" id="cluster" selected="true" /> <filter-element type="OBJECT" id="dblink" selected="true" /> </any-schema> </extended-filter> </filters> <sorting enabled="true"> <sorting-element type="RESERVED_WORD" id="keyword" /> <sorting-element type="RESERVED_WORD" id="datatype" /> <sorting-element type="OBJECT" id="column" /> <sorting-element type="OBJECT" id="table" /> <sorting-element type="OBJECT" id="view" /> <sorting-element type="OBJECT" id="materialized view" /> <sorting-element type="OBJECT" id="index" /> <sorting-element type="OBJECT" id="constraint" /> <sorting-element type="OBJECT" id="trigger" /> <sorting-element type="OBJECT" id="synonym" /> <sorting-element type="OBJECT" id="sequence" /> <sorting-element type="OBJECT" id="procedure" /> <sorting-element type="OBJECT" id="function" /> <sorting-element type="OBJECT" id="package" /> <sorting-element type="OBJECT" id="type" /> <sorting-element type="OBJECT" id="dimension" /> <sorting-element type="OBJECT" id="cluster" /> <sorting-element type="OBJECT" id="dblink" /> <sorting-element type="OBJECT" id="schema" /> <sorting-element type="OBJECT" id="role" /> <sorting-element type="OBJECT" id="user" /> <sorting-element type="RESERVED_WORD" id="function" /> <sorting-element type="RESERVED_WORD" id="parameter" /> </sorting> <format> <enforce-code-style-case value="true" /> </format> </code-completion-settings> <execution-engine-settings> <statement-execution> <fetch-block-size value="100" /> <execution-timeout value="20" /> <debug-execution-timeout value="600" /> <focus-result value="false" /> <prompt-execution value="false" /> </statement-execution> <script-execution> <command-line-interfaces /> <execution-timeout value="300" /> </script-execution> <method-execution> <execution-timeout value="30" /> <debug-execution-timeout value="600" /> <parameter-history-size value="10" /> </method-execution> </execution-engine-settings> <operation-settings> <transactions> <uncommitted-changes> <on-project-close value="ASK" /> <on-disconnect value="ASK" /> <on-autocommit-toggle value="ASK" /> </uncommitted-changes> <multiple-uncommitted-changes> <on-commit value="ASK" /> <on-rollback value="ASK" /> </multiple-uncommitted-changes> </transactions> <session-browser> <disconnect-session value="ASK" /> <kill-session value="ASK" /> <reload-on-filter-change value="false" /> </session-browser> <compiler> <compile-type value="KEEP" /> <compile-dependencies value="ASK" /> <always-show-controls value="false" /> </compiler> <debugger> <debugger-type value="ASK" /> <use-generic-runners value="true" /> </debugger> </operation-settings> <ddl-file-settings> <extensions> <mapping file-type-id="VIEW" extensions="vw" /> <mapping file-type-id="TRIGGER" extensions="trg" /> <mapping file-type-id="PROCEDURE" extensions="prc" /> <mapping file-type-id="FUNCTION" extensions="fnc" /> <mapping file-type-id="PACKAGE" extensions="pkg" /> <mapping file-type-id="PACKAGE_SPEC" extensions="pks" /> <mapping file-type-id="PACKAGE_BODY" extensions="pkb" /> <mapping file-type-id="TYPE" extensions="tpe" /> <mapping file-type-id="TYPE_SPEC" extensions="tps" /> <mapping file-type-id="TYPE_BODY" extensions="tpb" /> </extensions> <general> <lookup-ddl-files value="true" /> <create-ddl-files value="false" /> <synchronize-ddl-files value="true" /> <use-qualified-names value="false" /> <make-scripts-rerunnable value="true" /> </general> </ddl-file-settings> <general-settings> <regional-settings> <date-format value="MEDIUM" /> <number-format value="UNGROUPED" /> <locale value="SYSTEM_DEFAULT" /> <use-custom-formats value="false" /> </regional-settings> <environment> <environment-types> <environment-type id="development" name="Development" description="Development environment" color="-2430209/-12296320" readonly-code="false" readonly-data="false" /> <environment-type id="integration" name="Integration" description="Integration environment" color="-2621494/-12163514" readonly-code="true" readonly-data="false" /> <environment-type id="production" name="Production" description="Productive environment" color="-11574/-10271420" readonly-code="true" readonly-data="true" /> <environment-type id="other" name="Other" description="" color="-1576/-10724543" readonly-code="false" readonly-data="false" /> </environment-types> <visibility-settings> <connection-tabs value="true" /> <dialog-headers value="true" /> <object-editor-tabs value="true" /> <script-editor-tabs value="false" /> <execution-result-tabs value="true" /> </visibility-settings> </environment> </general-settings> </component> </project> .idea/inspectionProfiles/Project_Default.xml
New file @@ -0,0 +1,11 @@ <component name="InspectionProjectProfileManager"> <profile version="1.0"> <option name="myName" value="Project Default" /> <inspection_tool class="AutoCloseableResource" enabled="true" level="WARNING" enabled_by_default="true"> <option name="METHOD_MATCHER_CONFIG" value="java.util.Formatter,format,java.io.Writer,append,com.google.common.base.Preconditions,checkNotNull,org.hibernate.Session,close,java.io.PrintWriter,printf,java.io.PrintStream,printf,okhttp3.Interceptor.Chain,proceed" /> </inspection_tool> <inspection_tool class="JavadocDeclaration" enabled="true" level="WARNING" enabled_by_default="true"> <option name="ADDITIONAL_TAGS" value="date,desc" /> </inspection_tool> </profile> </component> .idea/misc.xml
New file @@ -0,0 +1,10 @@ <?xml version="1.0" encoding="UTF-8"?> <project version="4"> <component name="ExternalStorageConfigurationManager" enabled="true" /> <component name="ProjectRootManager" version="2" languageLevel="JDK_11" project-jdk-name="1.8" project-jdk-type="JavaSDK"> <output url="file://$PROJECT_DIR$/build/classes" /> </component> <component name="ProjectType"> <option name="id" value="Android" /> </component> </project> app/.gitignore
New file @@ -0,0 +1 @@ /build app/build.gradle
New file @@ -0,0 +1,61 @@ apply plugin: 'com.android.application' android { namespace 'com.application.zhangshi_app_android' compileSdk 33 defaultConfig { applicationId "com.application.zhangshi_app_android" minSdk 24 targetSdk 33 versionCode 1 versionName "1.0" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } // signingConfigs { // release { // storeFile file('xxx.keystore') // storePassword '123456' // keyAlias 'alias' // keyPassword '123456' // } // } buildTypes { release { debuggable true minifyEnabled true //启用Proguard shrinkResources true //是否清理无用资源,依赖于minifyEnabled zipAlignEnabled true //是否启用zipAlign压缩 // signingConfig signingConfigs.release // proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt' proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' } debug { debuggable true minifyEnabled false //不启用Proguard shrinkResources false //是否清理无用资源,依赖于minifyEnabled zipAlignEnabled false //是否启用zipAlign压缩 // signingConfig signingConfigs.release } } buildFeatures{ dataBinding = true } compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } } dependencies { implementation 'androidx.appcompat:appcompat:1.6.1' implementation 'com.google.android.material:material:1.8.0' testImplementation 'junit:junit:4.13.2' androidTestImplementation 'androidx.test.ext:junit:1.1.5' androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1' implementation project(path: ':app_base') } app/proguard-rules.pro
New file @@ -0,0 +1,21 @@ # Add project specific ProGuard rules here. # You can control the set of applied configuration files using the # proguardFiles setting in build.gradle. # # For more details, see # http://developer.android.com/guide/developing/tools/proguard.html # If your project uses WebView with JS, uncomment the following # and specify the fully qualified class name to the JavaScript interface # class: #-keepclassmembers class fqcn.of.javascript.interface.for.webview { # public *; #} # Uncomment this to preserve the line number information for # debugging stack traces. #-keepattributes SourceFile,LineNumberTable # If you keep the line number information, uncomment this to # hide the original source file name. #-renamesourcefileattribute SourceFile app/src/androidTest/java/com/application/zhangshi_app_android/ExampleInstrumentedTest.java
New file @@ -0,0 +1,26 @@ package com.application.zhangshi_app_android; import android.content.Context; import androidx.test.platform.app.InstrumentationRegistry; import androidx.test.ext.junit.runners.AndroidJUnit4; import org.junit.Test; import org.junit.runner.RunWith; import static org.junit.Assert.*; /** * Instrumented test, which will execute on an Android device. * * @see <a href="http://d.android.com/tools/testing">Testing documentation</a> */ @RunWith(AndroidJUnit4.class) public class ExampleInstrumentedTest { @Test public void useAppContext() { // Context of the app under test. Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); assertEquals("com.application.zhangshi_app_android", appContext.getPackageName()); } } app/src/main/AndroidManifest.xml
New file @@ -0,0 +1,29 @@ <?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools"> <uses-permission android:name="android.permission.INTERNET" /> <application android:name=".MyApplication" android:allowBackup="true" android:dataExtractionRules="@xml/data_extraction_rules" android:fullBackupContent="@xml/backup_rules" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:supportsRtl="true" android:theme="@style/Theme.Zhangshi_app_android" tools:targetApi="31" > <activity android:name=".view.MainActivity" android:exported="true"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> </manifest> app/src/main/java/com/application/zhangshi_app_android/MyApplication.java
New file @@ -0,0 +1,11 @@ package com.application.zhangshi_app_android; import com.android.app_base.base.BaseApplication; /** * @author Ljj * @date 2023.03.02. 17:51 * @desc */ public class MyApplication extends BaseApplication { } app/src/main/java/com/application/zhangshi_app_android/config/BaseConfig.java
New file @@ -0,0 +1,10 @@ package com.application.zhangshi_app_android.config; /** * @author Ljj * @date 2023.03.02. 15:33 * @desc */ public class BaseConfig { public final static String BASE_URL = ""; } app/src/main/java/com/application/zhangshi_app_android/model/DataRepository.java
New file @@ -0,0 +1,45 @@ package com.application.zhangshi_app_android.model; import androidx.annotation.NonNull; import androidx.annotation.VisibleForTesting; import com.android.app_base.base.model.BaseModel; import com.application.zhangshi_app_android.model.source.HttpDataSource; import com.application.zhangshi_app_android.model.source.LocalDataSource; import com.application.zhangshi_app_android.model.source.http.HttpDataSourceImpl; import com.application.zhangshi_app_android.model.source.local.LocalDataSourceImpl; /** * @author Ljj * @date 2023.03.01. 20:58 * @desc 数据仓库,包含网络数据和本地数据 */ public class DataRepository extends BaseModel implements HttpDataSource, LocalDataSource { private volatile static DataRepository INSTANCE = null; private final HttpDataSource mHttpDataSource; private final LocalDataSource mLocalDataSource; private DataRepository(@NonNull HttpDataSource httpDataSource, @NonNull LocalDataSource localDataSource) { this.mHttpDataSource = httpDataSource; this.mLocalDataSource = localDataSource; } public static DataRepository getInstance() { if (INSTANCE == null) { synchronized (DataRepository.class) { if (INSTANCE == null) { INSTANCE = new DataRepository(HttpDataSourceImpl.getInstance(), LocalDataSourceImpl.getInstance()); } } } return INSTANCE; } @Override public void onCleared() { INSTANCE = null; } } app/src/main/java/com/application/zhangshi_app_android/model/source/HttpDataSource.java
New file @@ -0,0 +1,9 @@ package com.application.zhangshi_app_android.model.source; /** * @author Ljj * @date 2023.03.01. 21:18 * @desc 网络请求数据源 接口 */ public interface HttpDataSource { } app/src/main/java/com/application/zhangshi_app_android/model/source/LocalDataSource.java
New file @@ -0,0 +1,9 @@ package com.application.zhangshi_app_android.model.source; /** * @author Ljj * @date 2023.03.01. 21:19 * @desc 本地数据源 接口 */ public interface LocalDataSource { } app/src/main/java/com/application/zhangshi_app_android/model/source/http/ApiService.java
New file @@ -0,0 +1,9 @@ package com.application.zhangshi_app_android.model.source.http; /** * @author Ljj * @date 2023.03.01. 21:13 * @desc 网络请求 service */ public interface ApiService { } app/src/main/java/com/application/zhangshi_app_android/model/source/http/HttpDataSourceImpl.java
New file @@ -0,0 +1,38 @@ package com.application.zhangshi_app_android.model.source.http; import com.android.app_base.http.RetrofitManager; import com.application.zhangshi_app_android.config.BaseConfig; import com.application.zhangshi_app_android.model.source.HttpDataSource; /** * @author Ljj * @date 2023.03.01. 21:23 * @desc 网络请求数据源 接口实现类 * 配合 Retrofit 使用 */ public class HttpDataSourceImpl implements HttpDataSource { private ApiService apiService; private volatile static HttpDataSourceImpl INSTANCE = null; private HttpDataSourceImpl() { this.apiService = RetrofitManager.getInstance().getRetrofit(BaseConfig.BASE_URL).create(ApiService.class); } public static HttpDataSourceImpl getInstance() { if (INSTANCE == null) { synchronized (HttpDataSourceImpl.class) { if (INSTANCE == null) { INSTANCE = new HttpDataSourceImpl(); } } } return INSTANCE; } public static void destroyInstance() { INSTANCE = null; } } app/src/main/java/com/application/zhangshi_app_android/model/source/local/LocalDataSourceImpl.java
New file @@ -0,0 +1,32 @@ package com.application.zhangshi_app_android.model.source.local; import com.application.zhangshi_app_android.model.source.LocalDataSource; /** * @author Ljj * @date 2023.03.01. 21:21 * @desc 本地数据源 接口实现类 * 配合 Room框架 或 SP本地存储 使用 */ public class LocalDataSourceImpl implements LocalDataSource { private volatile static LocalDataSourceImpl INSTANCE = null; public static LocalDataSourceImpl getInstance() { if (INSTANCE == null) { synchronized (LocalDataSourceImpl.class) { if (INSTANCE == null) { INSTANCE = new LocalDataSourceImpl(); } } } return INSTANCE; } public static void destroyInstance() { INSTANCE = null; } private LocalDataSourceImpl() { } } app/src/main/java/com/application/zhangshi_app_android/view/MainActivity.java
New file @@ -0,0 +1,55 @@ package com.application.zhangshi_app_android.view; import com.android.app_base.base.view.BaseActivity; import com.android.app_base.base.viewmodel.BaseViewModel; import com.android.app_base.helper.DoubleClickHelper; import com.android.app_base.manager.AppManager; import com.android.app_base.utils.ToastUtils; import com.application.zhangshi_app_android.BR; import com.application.zhangshi_app_android.R; import com.application.zhangshi_app_android.databinding.ActivityMainBinding; import com.application.zhangshi_app_android.viewmodel.MainViewModel; public class MainActivity extends BaseActivity<ActivityMainBinding, MainViewModel> { @Override public int getLayoutId() { return R.layout.activity_main; } @Override public int getVariableId() { return BR.viewModel; } @Override public void initParam() { } @Override public void initView() { } @Override public void initData() { } @Override public void initLiveDataObserve() { } @Override public void onBackPressed() { if (DoubleClickHelper.isOnDoubleClick()) { moveTaskToBack(false); // 进行内存优化,销毁掉所有的界面 AppManager.getAppManager().finishAllActivity(); } else { ToastUtils.showShort("再按一次退出"); } } } app/src/main/java/com/application/zhangshi_app_android/viewmodel/MainViewModel.java
New file @@ -0,0 +1,25 @@ package com.application.zhangshi_app_android.viewmodel; import android.app.Application; import androidx.annotation.NonNull; import com.android.app_base.base.model.BaseModel; import com.android.app_base.base.viewmodel.BaseViewModel; /** * @author Ljj * @date 2023.03.02. 23:07 * @desc */ public class MainViewModel extends BaseViewModel { public MainViewModel(@NonNull Application application) { super(application); } @Override protected BaseModel initModel() { return null; } } app/src/main/res/drawable-v24/ic_launcher_foreground.xml
New file @@ -0,0 +1,30 @@ <vector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:aapt="http://schemas.android.com/aapt" android:width="108dp" android:height="108dp" android:viewportWidth="108" android:viewportHeight="108"> <path android:pathData="M31,63.928c0,0 6.4,-11 12.1,-13.1c7.2,-2.6 26,-1.4 26,-1.4l38.1,38.1L107,108.928l-32,-1L31,63.928z"> <aapt:attr name="android:fillColor"> <gradient android:endX="85.84757" android:endY="92.4963" android:startX="42.9492" android:startY="49.59793" android:type="linear"> <item android:color="#44000000" android:offset="0.0" /> <item android:color="#00000000" android:offset="1.0" /> </gradient> </aapt:attr> </path> <path android:fillColor="#FFFFFF" android:fillType="nonZero" android:pathData="M65.3,45.828l3.8,-6.6c0.2,-0.4 0.1,-0.9 -0.3,-1.1c-0.4,-0.2 -0.9,-0.1 -1.1,0.3l-3.9,6.7c-6.3,-2.8 -13.4,-2.8 -19.7,0l-3.9,-6.7c-0.2,-0.4 -0.7,-0.5 -1.1,-0.3C38.8,38.328 38.7,38.828 38.9,39.228l3.8,6.6C36.2,49.428 31.7,56.028 31,63.928h46C76.3,56.028 71.8,49.428 65.3,45.828zM43.4,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2c-0.3,-0.7 -0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C45.3,56.528 44.5,57.328 43.4,57.328L43.4,57.328zM64.6,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2s-0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C66.5,56.528 65.6,57.328 64.6,57.328L64.6,57.328z" android:strokeWidth="1" android:strokeColor="#00000000" /> </vector> app/src/main/res/drawable/ic_launcher_background.xml
New file @@ -0,0 +1,170 @@ <?xml version="1.0" encoding="utf-8"?> <vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="108dp" android:height="108dp" android:viewportWidth="108" android:viewportHeight="108"> <path android:fillColor="#3DDC84" android:pathData="M0,0h108v108h-108z" /> <path android:fillColor="#00000000" android:pathData="M9,0L9,108" android:strokeWidth="0.8" android:strokeColor="#33FFFFFF" /> <path android:fillColor="#00000000" android:pathData="M19,0L19,108" android:strokeWidth="0.8" android:strokeColor="#33FFFFFF" /> <path android:fillColor="#00000000" android:pathData="M29,0L29,108" android:strokeWidth="0.8" android:strokeColor="#33FFFFFF" /> <path android:fillColor="#00000000" android:pathData="M39,0L39,108" android:strokeWidth="0.8" android:strokeColor="#33FFFFFF" /> <path android:fillColor="#00000000" android:pathData="M49,0L49,108" android:strokeWidth="0.8" android:strokeColor="#33FFFFFF" /> <path android:fillColor="#00000000" android:pathData="M59,0L59,108" android:strokeWidth="0.8" android:strokeColor="#33FFFFFF" /> <path android:fillColor="#00000000" android:pathData="M69,0L69,108" android:strokeWidth="0.8" android:strokeColor="#33FFFFFF" /> <path android:fillColor="#00000000" android:pathData="M79,0L79,108" android:strokeWidth="0.8" android:strokeColor="#33FFFFFF" /> <path android:fillColor="#00000000" android:pathData="M89,0L89,108" android:strokeWidth="0.8" android:strokeColor="#33FFFFFF" /> <path android:fillColor="#00000000" android:pathData="M99,0L99,108" android:strokeWidth="0.8" android:strokeColor="#33FFFFFF" /> <path android:fillColor="#00000000" android:pathData="M0,9L108,9" android:strokeWidth="0.8" android:strokeColor="#33FFFFFF" /> <path android:fillColor="#00000000" android:pathData="M0,19L108,19" android:strokeWidth="0.8" android:strokeColor="#33FFFFFF" /> <path android:fillColor="#00000000" android:pathData="M0,29L108,29" android:strokeWidth="0.8" android:strokeColor="#33FFFFFF" /> <path android:fillColor="#00000000" android:pathData="M0,39L108,39" android:strokeWidth="0.8" android:strokeColor="#33FFFFFF" /> <path android:fillColor="#00000000" android:pathData="M0,49L108,49" android:strokeWidth="0.8" android:strokeColor="#33FFFFFF" /> <path android:fillColor="#00000000" android:pathData="M0,59L108,59" android:strokeWidth="0.8" android:strokeColor="#33FFFFFF" /> <path android:fillColor="#00000000" android:pathData="M0,69L108,69" android:strokeWidth="0.8" android:strokeColor="#33FFFFFF" /> <path android:fillColor="#00000000" android:pathData="M0,79L108,79" android:strokeWidth="0.8" android:strokeColor="#33FFFFFF" /> <path android:fillColor="#00000000" android:pathData="M0,89L108,89" android:strokeWidth="0.8" android:strokeColor="#33FFFFFF" /> <path android:fillColor="#00000000" android:pathData="M0,99L108,99" android:strokeWidth="0.8" android:strokeColor="#33FFFFFF" /> <path android:fillColor="#00000000" android:pathData="M19,29L89,29" android:strokeWidth="0.8" android:strokeColor="#33FFFFFF" /> <path android:fillColor="#00000000" android:pathData="M19,39L89,39" android:strokeWidth="0.8" android:strokeColor="#33FFFFFF" /> <path android:fillColor="#00000000" android:pathData="M19,49L89,49" android:strokeWidth="0.8" android:strokeColor="#33FFFFFF" /> <path android:fillColor="#00000000" android:pathData="M19,59L89,59" android:strokeWidth="0.8" android:strokeColor="#33FFFFFF" /> <path android:fillColor="#00000000" android:pathData="M19,69L89,69" android:strokeWidth="0.8" android:strokeColor="#33FFFFFF" /> <path android:fillColor="#00000000" android:pathData="M19,79L89,79" android:strokeWidth="0.8" android:strokeColor="#33FFFFFF" /> <path android:fillColor="#00000000" android:pathData="M29,19L29,89" android:strokeWidth="0.8" android:strokeColor="#33FFFFFF" /> <path android:fillColor="#00000000" android:pathData="M39,19L39,89" android:strokeWidth="0.8" android:strokeColor="#33FFFFFF" /> <path android:fillColor="#00000000" android:pathData="M49,19L49,89" android:strokeWidth="0.8" android:strokeColor="#33FFFFFF" /> <path android:fillColor="#00000000" android:pathData="M59,19L59,89" android:strokeWidth="0.8" android:strokeColor="#33FFFFFF" /> <path android:fillColor="#00000000" android:pathData="M69,19L69,89" android:strokeWidth="0.8" android:strokeColor="#33FFFFFF" /> <path android:fillColor="#00000000" android:pathData="M79,19L79,89" android:strokeWidth="0.8" android:strokeColor="#33FFFFFF" /> </vector> app/src/main/res/layout/activity_main.xml
New file @@ -0,0 +1,36 @@ <?xml version="1.0" encoding="utf-8"?> <layout xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" xmlns:android="http://schemas.android.com/apk/res/android"> <data> <variable name="viewModel" type="com.application.zhangshi_app_android.viewmodel.MainViewModel" /> </data> <androidx.constraintlayout.widget.ConstraintLayout android:layout_width="match_parent" android:layout_height="match_parent"> <Button android:id="@+id/button" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Button" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> <TextView android:id="@+id/textView" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="TextView" app:layout_constraintBottom_toTopOf="@+id/button" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> </androidx.constraintlayout.widget.ConstraintLayout> </layout> app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
New file @@ -0,0 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> <adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android"> <background android:drawable="@drawable/ic_launcher_background" /> <foreground android:drawable="@drawable/ic_launcher_foreground" /> </adaptive-icon> app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
New file @@ -0,0 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> <adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android"> <background android:drawable="@drawable/ic_launcher_background" /> <foreground android:drawable="@drawable/ic_launcher_foreground" /> </adaptive-icon> app/src/main/res/mipmap-anydpi-v33/ic_launcher.xml
New file @@ -0,0 +1,6 @@ <?xml version="1.0" encoding="utf-8"?> <adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android"> <background android:drawable="@drawable/ic_launcher_background" /> <foreground android:drawable="@drawable/ic_launcher_foreground" /> <monochrome android:drawable="@drawable/ic_launcher_foreground" /> </adaptive-icon> app/src/main/res/mipmap-hdpi/ic_launcher.webpBinary files differ
app/src/main/res/mipmap-hdpi/ic_launcher_round.webpBinary files differ
app/src/main/res/mipmap-mdpi/ic_launcher.webpBinary files differ
app/src/main/res/mipmap-mdpi/ic_launcher_round.webpBinary files differ
app/src/main/res/mipmap-xhdpi/ic_launcher.webpBinary files differ
app/src/main/res/mipmap-xhdpi/ic_launcher_round.webpBinary files differ
app/src/main/res/mipmap-xxhdpi/ic_launcher.webpBinary files differ
app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webpBinary files differ
app/src/main/res/mipmap-xxxhdpi/ic_launcher.webpBinary files differ
app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webpBinary files differ
app/src/main/res/values-night/themes.xml
New file @@ -0,0 +1,16 @@ <resources xmlns:tools="http://schemas.android.com/tools"> <!-- Base application theme. --> <style name="Theme.Zhangshi_app_android" parent="Theme.MaterialComponents.DayNight.DarkActionBar"> <!-- Primary brand color. --> <item name="colorPrimary">@color/purple_200</item> <item name="colorPrimaryVariant">@color/purple_700</item> <item name="colorOnPrimary">@color/black</item> <!-- Secondary brand color. --> <item name="colorSecondary">@color/teal_200</item> <item name="colorSecondaryVariant">@color/teal_200</item> <item name="colorOnSecondary">@color/black</item> <!-- Status bar color. --> <item name="android:statusBarColor">?attr/colorPrimaryVariant</item> <!-- Customize your theme here. --> </style> </resources> app/src/main/res/values/colors.xml
New file @@ -0,0 +1,10 @@ <?xml version="1.0" encoding="utf-8"?> <resources> <color name="purple_200">#FFBB86FC</color> <color name="purple_500">#FF6200EE</color> <color name="purple_700">#FF3700B3</color> <color name="teal_200">#FF03DAC5</color> <color name="teal_700">#FF018786</color> <color name="black">#FF000000</color> <color name="white">#FFFFFFFF</color> </resources> app/src/main/res/values/strings.xml
New file @@ -0,0 +1,3 @@ <resources> <string name="app_name">zhangshi_app_android</string> </resources> app/src/main/res/values/themes.xml
New file @@ -0,0 +1,16 @@ <resources xmlns:tools="http://schemas.android.com/tools"> <!-- Base application theme. --> <style name="Theme.Zhangshi_app_android" parent="Theme.MaterialComponents.DayNight.DarkActionBar"> <!-- Primary brand color. --> <item name="colorPrimary">@color/purple_500</item> <item name="colorPrimaryVariant">@color/purple_700</item> <item name="colorOnPrimary">@color/white</item> <!-- Secondary brand color. --> <item name="colorSecondary">@color/teal_200</item> <item name="colorSecondaryVariant">@color/teal_700</item> <item name="colorOnSecondary">@color/black</item> <!-- Status bar color. --> <item name="android:statusBarColor">?attr/colorPrimaryVariant</item> <!-- Customize your theme here. --> </style> </resources> app/src/main/res/xml/backup_rules.xml
New file @@ -0,0 +1,13 @@ <?xml version="1.0" encoding="utf-8"?><!-- Sample backup rules file; uncomment and customize as necessary. See https://developer.android.com/guide/topics/data/autobackup for details. Note: This file is ignored for devices older that API 31 See https://developer.android.com/about/versions/12/backup-restore --> <full-backup-content> <!-- <include domain="sharedpref" path="."/> <exclude domain="sharedpref" path="device.xml"/> --> </full-backup-content> app/src/main/res/xml/data_extraction_rules.xml
New file @@ -0,0 +1,19 @@ <?xml version="1.0" encoding="utf-8"?><!-- Sample data extraction rules file; uncomment and customize as necessary. See https://developer.android.com/about/versions/12/backup-restore#xml-changes for details. --> <data-extraction-rules> <cloud-backup> <!-- TODO: Use <include> and <exclude> to control what is backed up. <include .../> <exclude .../> --> </cloud-backup> <!-- <device-transfer> <include .../> <exclude .../> </device-transfer> --> </data-extraction-rules> app/src/test/java/com/application/zhangshi_app_android/ExampleUnitTest.java
New file @@ -0,0 +1,17 @@ package com.application.zhangshi_app_android; import org.junit.Test; import static org.junit.Assert.*; /** * Example local unit test, which will execute on the development machine (host). * * @see <a href="http://d.android.com/tools/testing">Testing documentation</a> */ public class ExampleUnitTest { @Test public void addition_isCorrect() { assertEquals(4, 2 + 2); } } app_base/.gitignore
New file @@ -0,0 +1 @@ /build app_base/build.gradle
New file @@ -0,0 +1,58 @@ plugins { id 'com.android.library' } android { namespace 'com.android.app_base' compileSdk 33 defaultConfig { minSdk 24 targetSdk 33 testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" consumerProguardFiles "consumer-rules.pro" } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' } } compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } buildFeatures{ dataBinding = true } } dependencies { api fileTree(include: ['*.jar'], dir: 'libs') implementation 'androidx.appcompat:appcompat:1.6.1' implementation 'com.google.android.material:material:1.8.0' testImplementation 'junit:junit:4.13.2' androidTestImplementation 'androidx.test.ext:junit:1.1.5' androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1' //blankj工具类 api 'com.blankj:utilcodex:1.31.1' //Retrofit+Okhttp+Rxjava(Retrofit2内置了okhttp) api 'com.squareup.retrofit2:retrofit:2.9.0' api 'com.squareup.retrofit2:converter-gson:2.9.0'//gson转换和 api 'com.squareup.retrofit2:adapter-rxjava2:2.4.0'//对Rxjava适配 api 'com.squareup.retrofit2:retrofit-converters:2.4.0'//数据解析器 api 'com.squareup.retrofit2:retrofit-adapters:2.4.0'//相关适配 api 'com.squareup.okhttp3:okhttp:4.10.0' api 'com.squareup.okhttp3:logging-interceptor:4.10.0' api "io.reactivex.rxjava2:rxjava:2.2.21" api 'io.reactivex.rxjava2:rxandroid:2.1.1' //Cookie 持久化 api 'com.github.franmontiel:PersistentCookieJar:v1.0.1' } app_base/consumer-rules.pro
app_base/proguard-rules.pro
New file @@ -0,0 +1,21 @@ # Add project specific ProGuard rules here. # You can control the set of applied configuration files using the # proguardFiles setting in build.gradle. # # For more details, see # http://developer.android.com/guide/developing/tools/proguard.html # If your project uses WebView with JS, uncomment the following # and specify the fully qualified class name to the JavaScript interface # class: #-keepclassmembers class fqcn.of.javascript.interface.for.webview { # public *; #} # Uncomment this to preserve the line number information for # debugging stack traces. #-keepattributes SourceFile,LineNumberTable # If you keep the line number information, uncomment this to # hide the original source file name. #-renamesourcefileattribute SourceFile app_base/src/androidTest/java/com/android/app_base/ExampleInstrumentedTest.java
New file @@ -0,0 +1,26 @@ package com.android.app_base; import android.content.Context; import androidx.test.platform.app.InstrumentationRegistry; import androidx.test.ext.junit.runners.AndroidJUnit4; import org.junit.Test; import org.junit.runner.RunWith; import static org.junit.Assert.*; /** * Instrumented test, which will execute on an Android device. * * @see <a href="http://d.android.com/tools/testing">Testing documentation</a> */ @RunWith(AndroidJUnit4.class) public class ExampleInstrumentedTest { @Test public void useAppContext() { // Context of the app under test. Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); assertEquals("com.android.app_base.test", appContext.getPackageName()); } } app_base/src/main/AndroidManifest.xml
New file @@ -0,0 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android"> <uses-permission android:name="android.permission.INTERNET" /> </manifest> app_base/src/main/java/com/android/app_base/base/BaseApplication.java
New file @@ -0,0 +1,73 @@ package com.android.app_base.base; import android.app.Activity; import android.app.Application; import android.os.Bundle; import androidx.annotation.NonNull; import com.android.app_base.manager.AppManager; /** * Application基类 */ public class BaseApplication extends Application { private static Application sInstance; @Override public void onCreate() { super.onCreate(); setApplication(this); } /** * 当主工程没有继承BaseApplication时,可以使用setApplication方法初始化BaseApplication * @param application */ public static synchronized void setApplication(@NonNull Application application) { sInstance = application; //注册监听每个activity的生命周期,便于堆栈式管理 application.registerActivityLifecycleCallbacks(new ActivityLifecycleCallbacks() { @Override public void onActivityCreated(Activity activity, Bundle savedInstanceState) { AppManager.getAppManager().addActivity(activity); } @Override public void onActivityStarted(Activity activity) { } @Override public void onActivityResumed(Activity activity) { } @Override public void onActivityPaused(Activity activity) { } @Override public void onActivityStopped(Activity activity) { } @Override public void onActivitySaveInstanceState(Activity activity, Bundle outState) { } @Override public void onActivityDestroyed(Activity activity) { AppManager.getAppManager().removeActivity(activity); } }); } /** * 获得当前app运行的Application */ public static Application getInstance() { if (sInstance == null) { throw new NullPointerException("please inherit BaseApplication or call setApplication."); } return sInstance; } } app_base/src/main/java/com/android/app_base/base/StateViewEnum.java
New file @@ -0,0 +1,14 @@ package com.android.app_base.base; /** * 进行数据请求时,进行Dialog加载或缺省页等状态 */ public enum StateViewEnum { DIALOG_LOADING,// dialog加载中 DIALOG_DISMISS,//dialog隐藏 DATA_LOADING, DATA_ERROR,// 数据错误 DATA_NULL,// 没有数据 NET_ERROR,//网络错误 HIDE,// 隐藏 } app_base/src/main/java/com/android/app_base/base/adapter/BaseRVAdapter.java
New file @@ -0,0 +1,481 @@ package com.android.app_base.base.adapter; import android.content.Context; import android.util.SparseArray; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import androidx.annotation.IdRes; import androidx.annotation.IntRange; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.databinding.DataBindingUtil; import androidx.databinding.ViewDataBinding; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; import java.util.ArrayList; import java.util.List; /** * @author Ljj * @date 2023.03.02. 17:09 * @desc */ public abstract class BaseRVAdapter<T,VDB extends ViewDataBinding> extends RecyclerView.Adapter<BaseRVAdapter.BaseViewHolder<VDB>> { /** 上下文对象 */ private final Context mContext; /** RecyclerView 对象 */ private RecyclerView mRecyclerView; /** 条目点击监听器 */ private OnItemClickListener mItemClickListener; /** 条目长按监听器 */ private OnItemLongClickListener mItemLongClickListener; /** * 该 adapter 上绑定的滑动监听器 */ private MyOnScrollListener myOnScrollListener; /** 条目子 View 点击监听器 */ private SparseArray<OnChildClickListener> mChildClickListeners; /** 条目子 View 长按监听器 */ private SparseArray<OnChildLongClickListener> mChildLongClickListeners; /** * 列表数据 */ private List<T> mDataList; public BaseRVAdapter(Context context) { mContext = context; if (mContext == null) { throw new IllegalArgumentException("are you ok?"); } } public BaseRVAdapter(Context context, List<T> list) { mContext = context; mDataList = (list == null) ? new ArrayList<T>() : list; setData(list); } @NonNull @Override public BaseViewHolder<VDB> onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { VDB itemBind = DataBindingUtil.inflate(LayoutInflater.from(parent.getContext()),getLayoutId(),parent,false); return new BaseViewHolder<>(itemBind); } protected abstract int getLayoutId(); protected abstract void onBind(BaseViewHolder<VDB> holder, int position); @Override public void onBindViewHolder(@NonNull BaseViewHolder<VDB> holder, int position) { View itemView = holder.itemView; // 设置条目的点击和长按事件 if (mItemClickListener != null) { itemView.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { mItemClickListener.onItemClick(mRecyclerView,view, holder.getLayoutPosition()); } }); } if (mItemLongClickListener != null) { itemView.setOnLongClickListener(new View.OnLongClickListener() { @Override public boolean onLongClick(View view) { return mItemLongClickListener.onItemLongClick(mRecyclerView, view, holder.getLayoutPosition()); } }); } // 设置条目子 View 点击事件 if (mChildClickListeners != null) { for (int i = 0; i < mChildClickListeners.size(); i++) { View childView = itemView.findViewById(mChildClickListeners.keyAt(i)); if (childView != null) { childView.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { OnChildClickListener listener = mChildClickListeners.get(view.getId()); if (listener != null) { listener.onChildClick(mRecyclerView, view, holder.getLayoutPosition()); } } }); } } } // 设置条目子 View 长按事件 if (mChildLongClickListeners != null) { for (int i = 0; i < mChildLongClickListeners.size(); i++) { View childView = itemView.findViewById(mChildLongClickListeners.keyAt(i)); if (childView != null) { childView.setOnLongClickListener(new View.OnLongClickListener() { @Override public boolean onLongClick(View view) { OnChildLongClickListener listener = mChildLongClickListeners.get(view.getId()); if (listener != null) { return listener.onChildLongClick(mRecyclerView, view, holder.getLayoutPosition()); } return false; } }); } } } onBind(holder,position); } @Override public int getItemCount() { return mDataList == null ? 0 : mDataList.size(); } @Override public void onAttachedToRecyclerView(@NonNull RecyclerView recyclerView) { mRecyclerView = recyclerView; // 用户设置了滚动监听,需要给 RecyclerView 设置监听 if (myOnScrollListener != null) { // 添加滚动监听 mRecyclerView.addOnScrollListener(myOnScrollListener); } // 判断当前的布局管理器是否为空,如果为空则设置默认的布局管理器 if (mRecyclerView.getLayoutManager() == null) { RecyclerView.LayoutManager layoutManager = new LinearLayoutManager(mContext);; mRecyclerView.setLayoutManager(layoutManager); } } @Override public void onDetachedFromRecyclerView(@NonNull RecyclerView recyclerView) { // 移除滚动监听 if (myOnScrollListener != null) { mRecyclerView.removeOnScrollListener(myOnScrollListener); } mRecyclerView = null; } public RecyclerView getRecyclerView() { return mRecyclerView; } /** * 自定义ViewHolder,基类 * @param <VDB> */ public static class BaseViewHolder<VDB extends ViewDataBinding> extends RecyclerView.ViewHolder{ private VDB binding; public BaseViewHolder(@NonNull View itemView) { super(itemView); } public BaseViewHolder(VDB binding) { this(binding.getRoot()); this.binding = binding; } public VDB getBinding() { return binding; } } /** * 设置新的数据 */ public void setData(@Nullable List<T> data) { mDataList = data; notifyDataSetChanged(); } /** * 获取当前数据 */ @Nullable public List<T> getData() { return mDataList; } /** * 追加一些数据 */ public void addData(List<T> data) { if (data == null || data.size() == 0) { return; } if (mDataList == null || mDataList.size() == 0) { setData(data); } else { mDataList.addAll(data); notifyDataSetChanged(); } } /** * 从头部 追加一些数据 by hyz */ public void addDataFormHead(List<T> data) { if (data == null || data.size() == 0) { return; } if (mDataList == null || mDataList.size() == 0) { setData(data); } else { mDataList.addAll(0, data); notifyDataSetChanged(); } } /** * 清空当前数据 */ public void clearData() { if (mDataList == null || mDataList.size() == 0) { return; } mDataList.clear(); notifyDataSetChanged(); } /** * 获取某个位置上的数据 */ public T getItem(@IntRange(from = 0) int position) { return mDataList.get(position); } /** * 更新某个位置上的数据 */ public void setItem(@IntRange(from = 0) int position, @NonNull T item) { if (mDataList == null) { mDataList = new ArrayList<>(); } mDataList.set(position, item); notifyItemChanged(position); } /** * 添加单条数据 */ public void addItem(@NonNull T item) { if (mDataList == null) { mDataList = new ArrayList<>(); } addItem(mDataList.size(), item); } public void addItem(@IntRange(from = 0) int position, @NonNull T item) { if (mDataList == null) { mDataList = new ArrayList<>(); } if (position < mDataList.size()) { mDataList.add(position, item); } else { mDataList.add(item); position = mDataList.size() - 1; } notifyItemInserted(position); } /** * 删除单条数据 */ public void removeItem(@NonNull T item) { int index = mDataList.indexOf(item); if (index != -1) { removeItem(index); } } public void removeItem(@IntRange(from = 0) int position) { // 如果是在for循环删除后要记得i-- mDataList.remove(position); // 告诉适配器删除数据的位置,会有动画效果 notifyItemRemoved(position); } /** * 设置 RecyclerView 条目点击监听 */ public void setOnItemClickListener(OnItemClickListener listener) { checkRecyclerViewState(); mItemClickListener = listener; } /** * 设置 RecyclerView 条目子 View 点击监听 */ public void setOnChildClickListener(@IdRes int id, OnChildClickListener listener) { checkRecyclerViewState(); if (mChildClickListeners == null) { mChildClickListeners = new SparseArray<>(); } mChildClickListeners.put(id, listener); } /** * 设置RecyclerView条目长按监听 */ public void setOnItemLongClickListener(OnItemLongClickListener listener) { checkRecyclerViewState(); mItemLongClickListener = listener; } /** * 设置 RecyclerView 条目子 View 长按监听 */ public void setOnChildLongClickListener(@IdRes int id, OnChildLongClickListener listener) { checkRecyclerViewState(); if (mChildLongClickListeners == null) { mChildLongClickListeners = new SparseArray<>(); } mChildLongClickListeners.put(id, listener); } private void checkRecyclerViewState() { if (mRecyclerView != null) { // 必须在 RecyclerView.setAdapter() 之前设置监听 throw new IllegalStateException("are you ok?"); } } /** * 设置 RecyclerView 条目滚动监听 */ public void setOnScrollingListener(OnScrollingListener listener) { /** RecyclerView 滚动事件监听 */ //如果当前已经有设置滚动监听,再次设置需要移除原有的监听器 if (myOnScrollListener == null) { myOnScrollListener = new MyOnScrollListener(listener); } else { mRecyclerView.removeOnScrollListener(myOnScrollListener); } //用户设置了滚动监听,需要给RecyclerView设置监听 //用户设置了滚动监听,需要给RecyclerView设置监听 if (mRecyclerView != null) { //添加滚动监听 mRecyclerView.addOnScrollListener(new MyOnScrollListener(listener)); } } /** * RecyclerView 条目点击监听类 */ public interface OnItemClickListener{ /** * 当 RecyclerView 某个条目被点击时回调 * * @param recyclerView RecyclerView 对象 * @param itemView 被点击的条目对象 * @param position 被点击的条目位置 */ void onItemClick(RecyclerView recyclerView, View itemView, int position); } /** * RecyclerView 条目长按监听类 */ public interface OnItemLongClickListener { /** * 当 RecyclerView 某个条目被长按时回调 * * @param recyclerView RecyclerView 对象 * @param itemView 被点击的条目对象 * @param position 被点击的条目位置 * @return 是否拦截事件 */ boolean onItemLongClick(RecyclerView recyclerView, View itemView, int position); } /** * RecyclerView 条目子 View 点击监听类 */ public interface OnChildClickListener { /** * 当 RecyclerView 某个条目 子 View 被点击时回调 * * @param recyclerView RecyclerView 对象 * @param childView 被点击的条目子 View * @param position 被点击的条目位置 */ void onChildClick(RecyclerView recyclerView, View childView, int position); } /** * RecyclerView 条目子 View 长按监听类 */ public interface OnChildLongClickListener { /** * 当 RecyclerView 某个条目子 View 被长按时回调 * * @param recyclerView RecyclerView 对象 * @param childView 被点击的条目子 View * @param position 被点击的条目位置 */ boolean onChildLongClick(RecyclerView recyclerView, View childView, int position); } /** * RecyclerView 滚动监听接口 */ public interface OnScrollingListener { /** * 列表滚动到最顶部 * * @param recyclerView RecyclerView 对象 */ void onScrollTop(RecyclerView recyclerView); /** * 列表滚动中 * * @param recyclerView RecyclerView 对象 */ void onScrolling(RecyclerView recyclerView); /** * 列表滚动到最底部 * * @param recyclerView RecyclerView 对象 */ void onScrollDown(RecyclerView recyclerView); } /** * 自定义RecyclerView的滑动监听器 */ public static class MyOnScrollListener extends RecyclerView.OnScrollListener { private OnScrollingListener onScrollingListener; MyOnScrollListener(OnScrollingListener onScrollingListener){ this.onScrollingListener = onScrollingListener; } @Override public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) { if (onScrollingListener == null) { return; } if (newState == RecyclerView.SCROLL_STATE_IDLE) { if (!recyclerView.canScrollVertically(1)) { // 已经到底了 onScrollingListener.onScrollDown(recyclerView); } else if (!recyclerView.canScrollVertically(-1)) { // 已经到顶了 onScrollingListener.onScrollTop(recyclerView); } } else if (newState == RecyclerView.SCROLL_STATE_DRAGGING) { // 正在滚动中 onScrollingListener.onScrolling(recyclerView); } } } } app_base/src/main/java/com/android/app_base/base/model/BaseModel.java
New file @@ -0,0 +1,9 @@ package com.android.app_base.base.model; public abstract class BaseModel { public BaseModel() { } public abstract void onCleared(); } app_base/src/main/java/com/android/app_base/base/view/BaseActivity.java
New file @@ -0,0 +1,177 @@ package com.android.app_base.base.view; import android.os.Bundle; import androidx.annotation.Nullable; import androidx.appcompat.app.AppCompatActivity; import androidx.databinding.DataBindingUtil; import androidx.databinding.ViewDataBinding; import androidx.lifecycle.Observer; import androidx.lifecycle.ViewModelProvider; import androidx.lifecycle.ViewModelStoreOwner; import com.android.app_base.base.viewmodel.BaseViewModel; import com.android.app_base.base.StateViewEnum; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; /** * Activity基类,所有的 Activity 都要继承此类 */ public abstract class BaseActivity<V extends ViewDataBinding,VM extends BaseViewModel> extends AppCompatActivity { protected V binding; protected VM viewModel; private int viewModelId; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); //绑定 ViewDataBinding 和 ViewModel initViewDataBindingAndViewModel(); //初始化状态视图 initStateView(); //页面参数初始化方法 initParam(); //页面view初始化方法 initView(); //页面数据初始化方法 initData(); //页面事件监听的方法,用于ViewModel层转到View层的事件注册 initLiveDataObserve(); } @Override protected void onDestroy() { super.onDestroy(); //解除Messenger注册 // Messenger.getDefault().unregister(viewModel); if(binding != null){ binding.unbind(); } } /** * ViewDataBinding和 ViewModel的获取及相关绑定 */ private void initViewDataBindingAndViewModel() { binding = DataBindingUtil.setContentView(this, getLayoutId()); viewModelId = getVariableId(); viewModel = initViewModel(); //关联ViewModel binding.setVariable(viewModelId, viewModel); //支持LiveData绑定xml,数据改变,UI自动会更新 binding.setLifecycleOwner(this); //让ViewModel拥有View的生命周期感应 getLifecycle().addObserver(viewModel); } /** * 初始化ViewModel * @return 返回一个ViewModel */ private VM initViewModel() { Class<VM> vmClass; Type type = getClass().getGenericSuperclass(); if (type instanceof ParameterizedType){ vmClass = (Class) ((ParameterizedType) type).getActualTypeArguments()[1]; } else { //如果没有指定泛型参数,则默认使用BaseViewModel vmClass = (Class<VM>) BaseViewModel.class; } return new ViewModelProvider(this, (ViewModelProvider.Factory) ViewModelProvider.AndroidViewModelFactory.getInstance(getApplication())).get(vmClass); } /** * 对状态视图liveData进行观察监听 */ private void initStateView() { viewModel.getStateViewLiveData().observe(this, new Observer<StateViewEnum>() { @Override public void onChanged(StateViewEnum stateViewEnum) { switch (stateViewEnum) { case DIALOG_LOADING: dialogLoading(); break; case DIALOG_DISMISS: dialogDismiss(); break; case DATA_LOADING: dataLoading(); break; case DATA_ERROR: dataError(); break; case DATA_NULL: dataNull(); break; case NET_ERROR: netError(); break; case HIDE: hide(); break; default: break; } } }); } /** * 缺省页等状态视图的更新 * 有需求的,在子类选择重写 */ protected void dialogLoading() { } protected void dialogDismiss() { } protected void dataLoading() { } protected void dataError() { } protected void dataNull() { } protected void netError() { } protected void hide() { } /** * 获取根布局的id,由子类实现返回 * @return layout的id */ public abstract int getLayoutId(); /** * 获取ViewModel的id,由子类实现返回 * @return BR中viewModel的id */ public abstract int getVariableId(); /** * 初始化页面参数,例如从上一个activity传递过来的参数 */ public abstract void initParam(); /** * 初始化页面view,例如一些view的隐藏或显示,以及点击逻辑等用户交互 */ public abstract void initView(); /** * 初始化页面数据 */ public abstract void initData(); /** * 初始化LiveData的监听 * 简单的数据展示可通过DataBinding直接双向绑定到xml中 */ public abstract void initLiveDataObserve(); } app_base/src/main/java/com/android/app_base/base/view/BaseFragment.java
New file @@ -0,0 +1,191 @@ package com.android.app_base.base.view; import android.os.Bundle; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.databinding.DataBindingUtil; import androidx.databinding.ViewDataBinding; import androidx.fragment.app.Fragment; import androidx.lifecycle.Observer; import androidx.lifecycle.ViewModelProvider; import com.android.app_base.base.StateViewEnum; import com.android.app_base.base.viewmodel.BaseViewModel; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; /** * @author Ljj * @date 2023.03.02. 15:50 * @desc Fragment基类 */ public abstract class BaseFragment<V extends ViewDataBinding,VM extends BaseViewModel> extends Fragment { protected V binding; protected VM viewModel; private int viewModelId; @Override public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); //页面参数初始化方法 initParam(); } @Nullable @Override public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { binding = DataBindingUtil.inflate(inflater,getLayoutId(), container, false); return binding.getRoot(); } @Override public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); //绑定 ViewDataBinding 和 ViewModel initViewDataBindingAndViewModel(); //初始化状态视图 initStateView(); //页面view初始化方法 initView(); //页面数据初始化方法 initData(); //页面事件监听的方法,用于ViewModel层转到View层的事件注册 initLiveDataObserve(); } @Override public void onDestroyView() { super.onDestroyView(); //解除Messenger注册 // Messenger.getDefault().unregister(viewModel); if(binding != null){ binding.unbind(); } } /** * ViewDataBinding和 ViewModel的获取及相关绑定 */ private void initViewDataBindingAndViewModel() { viewModelId = getVariableId(); viewModel = initViewModel(); //关联ViewModel binding.setVariable(viewModelId, viewModel); //支持LiveData绑定xml,数据改变,UI自动会更新 binding.setLifecycleOwner(this); //让ViewModel拥有View的生命周期感应 getLifecycle().addObserver(viewModel); } /** * 初始化ViewModel * @return 返回一个ViewModel */ private VM initViewModel() { Class<VM> vmClass; Type type = getClass().getGenericSuperclass(); if (type instanceof ParameterizedType){ vmClass = (Class) ((ParameterizedType) type).getActualTypeArguments()[1]; } else { //如果没有指定泛型参数,则默认使用BaseViewModel vmClass = (Class<VM>) BaseViewModel.class; } return new ViewModelProvider(this, (ViewModelProvider.Factory) ViewModelProvider.AndroidViewModelFactory.getInstance(requireActivity().getApplication())).get(vmClass); } /** * 对状态视图liveData进行观察监听 */ private void initStateView() { viewModel.getStateViewLiveData().observe(getViewLifecycleOwner(), new Observer<StateViewEnum>() { @Override public void onChanged(StateViewEnum stateViewEnum) { switch (stateViewEnum) { case DIALOG_LOADING: dialogLoading(); break; case DIALOG_DISMISS: dialogDismiss(); break; case DATA_LOADING: dataLoading(); break; case DATA_ERROR: dataError(); break; case DATA_NULL: dataNull(); break; case NET_ERROR: netError(); break; case HIDE: hide(); break; default: break; } } }); } /** * 缺省页等状态视图的更新 * 有需求的,在子类选择重写 */ protected void dialogLoading() { } protected void dialogDismiss() { } protected void dataLoading() { } protected void dataError() { } protected void dataNull() { } protected void netError() { } protected void hide() { } /** * 获取根布局的id,由子类实现返回 * @return layout的id */ public abstract int getLayoutId(); /** * 获取ViewModel的id,由子类实现返回 * @return BR中viewModel的id */ public abstract int getVariableId(); /** * 初始化页面参数,例如从上一个activity传递过来的参数 */ abstract void initParam(); /** * 初始化页面view,例如一些view的隐藏或显示,以及点击逻辑等用户交互 */ abstract void initView(); /** * 初始化页面数据 */ abstract void initData(); /** * 初始化LiveData的监听 * 简单的数据展示可通过DataBinding直接双向绑定到xml中 */ abstract void initLiveDataObserve(); } app_base/src/main/java/com/android/app_base/base/viewmodel/BaseViewModel.java
New file @@ -0,0 +1,120 @@ package com.android.app_base.base.viewmodel; import android.app.Application; import androidx.annotation.NonNull; import androidx.lifecycle.AndroidViewModel; import androidx.lifecycle.DefaultLifecycleObserver; import androidx.lifecycle.LifecycleOwner; import androidx.lifecycle.LiveData; import androidx.lifecycle.MutableLiveData; import com.android.app_base.base.StateViewEnum; import com.android.app_base.base.model.BaseModel; import io.reactivex.disposables.CompositeDisposable; import io.reactivex.disposables.Disposable; /** * ViewModel基类 */ public abstract class BaseViewModel<M extends BaseModel> extends AndroidViewModel implements DefaultLifecycleObserver { protected Application application; /** * 数据仓库model */ protected M model; /** * 控制状态视图的LiveData */ private MutableLiveData<StateViewEnum> stateViewLiveData; //管理RxJava,主要针对RxJava异步操作造成的内存泄漏 private CompositeDisposable mCompositeDisposable; public BaseViewModel(@NonNull Application application) { super(application); this.application = application; model = initModel(); mCompositeDisposable = new CompositeDisposable(); } /** * 数据仓库初始化,由子类实现返回 */ protected abstract M initModel(); /** * Rxjava的异步操作都要调用此方法添加到 mCompositeDisposable里, * @param disposable */ protected void addSubscribe(Disposable disposable) { if (mCompositeDisposable == null) { mCompositeDisposable = new CompositeDisposable(); } mCompositeDisposable.add(disposable); } /** * 获取状态视图LiveData */ public LiveData<StateViewEnum> getStateViewLiveData() { if(stateViewLiveData == null){ stateViewLiveData = new MutableLiveData<>(); } return stateViewLiveData; } /** * 更改状态视图的状态 */ public void changeStateView(StateViewEnum state) { stateViewLiveData.postValue(state); } @Override protected void onCleared() { super.onCleared(); if (model != null) { model.onCleared(); } //ViewModel销毁时会执行,同时取消所有异步任务 if (mCompositeDisposable != null) { mCompositeDisposable.clear(); } } /** * 子类可以重写生命周期方法,便于生命周期的考虑 */ @Override public void onCreate(@NonNull LifecycleOwner owner) { DefaultLifecycleObserver.super.onCreate(owner); } @Override public void onStart(@NonNull LifecycleOwner owner) { DefaultLifecycleObserver.super.onStart(owner); } @Override public void onResume(@NonNull LifecycleOwner owner) { DefaultLifecycleObserver.super.onResume(owner); } @Override public void onPause(@NonNull LifecycleOwner owner) { DefaultLifecycleObserver.super.onPause(owner); } @Override public void onStop(@NonNull LifecycleOwner owner) { DefaultLifecycleObserver.super.onStop(owner); } @Override public void onDestroy(@NonNull LifecycleOwner owner) { DefaultLifecycleObserver.super.onDestroy(owner); } } app_base/src/main/java/com/android/app_base/helper/DoubleClickHelper.java
New file @@ -0,0 +1,29 @@ package com.android.app_base.helper; import android.os.SystemClock; /** * @desc 防双击判断工具类 */ public final class DoubleClickHelper { /** 数组的长度为2代表只记录双击操作 */ private static final long[] TIME_ARRAY = new long[2]; /** * 是否在短时间内进行了双击操作 */ public static boolean isOnDoubleClick() { // 默认间隔时长 return isOnDoubleClick(1500); } /** * 是否在短时间内进行了双击操作 */ public static boolean isOnDoubleClick(int time) { System.arraycopy(TIME_ARRAY, 1, TIME_ARRAY, 0, TIME_ARRAY.length - 1); TIME_ARRAY[TIME_ARRAY.length - 1] = SystemClock.uptimeMillis(); return TIME_ARRAY[0] >= (SystemClock.uptimeMillis() - time); } } app_base/src/main/java/com/android/app_base/http/OkHttpHelper.java
New file @@ -0,0 +1,96 @@ package com.android.app_base.http; import android.content.Context; import com.android.app_base.BuildConfig; import com.android.app_base.base.BaseApplication; import com.android.app_base.http.interceptor.CacheInterceptor; import com.blankj.utilcode.util.LogUtils; import com.franmontiel.persistentcookiejar.PersistentCookieJar; import com.franmontiel.persistentcookiejar.cache.SetCookieCache; import com.franmontiel.persistentcookiejar.persistence.SharedPrefsCookiePersistor; import java.io.File; import java.io.UnsupportedEncodingException; import java.net.URLDecoder; import java.util.concurrent.TimeUnit; import okhttp3.Cache; import okhttp3.OkHttpClient; import okhttp3.logging.HttpLoggingInterceptor; /** * @author Ljj * @date 2023.03.01. 22:36 * @desc 网络请求OkHttp配置 */ public class OkHttpHelper { //超时时间 private final static int READ_TIMEOUT = 15; private final static int CONNECT_TIMEOUT = 15; private final static int WRITE_TIMEOUT = 15; //缓存时间 private final static int CACHE_SIZE = 10 * 1024 * 1024; private static Context mContext = BaseApplication.getInstance(); private static volatile OkHttpClient okHttpClient; private OkHttpHelper() { OkHttpClient.Builder clientBuilder = new OkHttpClient.Builder(); //读取超时 clientBuilder.readTimeout(READ_TIMEOUT, TimeUnit.SECONDS); //连接超时 clientBuilder.connectTimeout(CONNECT_TIMEOUT, TimeUnit.SECONDS); //写入超时 clientBuilder.writeTimeout(WRITE_TIMEOUT, TimeUnit.SECONDS); //错误重连 clientBuilder.retryOnConnectionFailure(true); //cookie clientBuilder.cookieJar(new PersistentCookieJar(new SetCookieCache(),new SharedPrefsCookiePersistor(mContext))); //缓存拦截 Cache cache = new Cache(new File(mContext.getCacheDir(),"http_cache"),CACHE_SIZE); clientBuilder.cache(cache).addInterceptor(new CacheInterceptor()); // //增加头部信息拦截 // clientBuilder.addInterceptor(new Interceptor() { // @Override // public Response intercept(Chain chain) throws IOException { // Request build = chain.request().newBuilder() // .addHeader("Content-Type", "application/json") // .build(); // return chain.proceed(build); // } // }); //log日志拦截 if (BuildConfig.DEBUG) { clientBuilder.addInterceptor(new HttpLoggingInterceptor(new HttpLoggingInterceptor.Logger() { @Override public void log(String message) { try { // String msg = message.replaceAll("%(?![0-9a-fA-F]{2})","%25"); String text = URLDecoder.decode(message, "utf-8"); LogUtils.dTag("LOG","网络请求"+text); } catch (UnsupportedEncodingException e) { e.printStackTrace(); LogUtils.dTag("LOG","网络请求错误信息"+e.getMessage()); } } }).setLevel(HttpLoggingInterceptor.Level.BODY)); } okHttpClient = clientBuilder.build(); } public static OkHttpClient getOkHttpClient() { if (null == okHttpClient) { synchronized (OkHttpHelper.class) { if (okHttpClient == null) { new OkHttpHelper(); return okHttpClient; } } } return okHttpClient; } } app_base/src/main/java/com/android/app_base/http/ResultData.java
New file @@ -0,0 +1,45 @@ package com.android.app_base.http; /** * @author Ljj * @date 2023.03.01. 20:32 * @desc 网络请求接口数据封装 */ public class ResultData<T> { private int code; private String message; private boolean success; private T data; public T getData() { return data; } public void setData(T data) { this.data = data; } public int getCode() { return code; } public void setCode(int code) { this.code = code; } public String getMessage() { return message; } public void setMessage(String message) { this.message = message; } public boolean isSuccess() { return success; } public void setSuccess(boolean success) { this.success = success; } } app_base/src/main/java/com/android/app_base/http/RetrofitManager.java
New file @@ -0,0 +1,60 @@ package com.android.app_base.http; import androidx.collection.CircularArray; import java.util.HashMap; import java.util.Map; import okhttp3.OkHttpClient; import retrofit2.Retrofit; import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory; import retrofit2.converter.gson.GsonConverterFactory; /** * @author Ljj * @date 2023.03.02. 14:11 * @desc Retrofit管理,可以有不同的 baseUrl */ public class RetrofitManager { private static RetrofitManager instance; private final Map<String,Retrofit> retrofitMap; private OkHttpClient mClient; public static RetrofitManager getInstance(){ if (instance == null){ instance = new RetrofitManager(); } return instance; } private RetrofitManager(){ retrofitMap = new HashMap<>(); mClient = OkHttpHelper.getOkHttpClient(); } /** * 获取Retrofit对象 */ public Retrofit getRetrofit(String baseUrl) { Retrofit retrofit = retrofitMap.get(baseUrl); if (retrofit == null) { retrofit = new Retrofit.Builder() .client(mClient) .addConverterFactory(GsonConverterFactory.create()) .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) .baseUrl(baseUrl) .build(); retrofitMap.put(baseUrl, retrofit); } return retrofit; } /** * 设置 OkHttpClient */ public RetrofitManager setOkHttpClient(OkHttpClient client) { this.mClient = client; return instance; } } app_base/src/main/java/com/android/app_base/http/interceptor/CacheInterceptor.java
New file @@ -0,0 +1,56 @@ package com.android.app_base.http.interceptor; import android.content.Context; import com.blankj.utilcode.util.NetworkUtils; import java.io.IOException; import okhttp3.CacheControl; import okhttp3.Interceptor; import okhttp3.Request; import okhttp3.Response; /** * @author Ljj * @date 2023.03.02. 10:56 * @desc 网络请求缓存拦截器 */ public class CacheInterceptor implements Interceptor { private Context context; public CacheInterceptor(){ } public CacheInterceptor(Context context) { this.context = context; } @Override public Response intercept(Chain chain) throws IOException { Request request = chain.request(); if (NetworkUtils.isAvailable()) { Response response = chain.proceed(request); // read from cache for 60 s int maxAge = 60; return response.newBuilder() .removeHeader("Pragma") .removeHeader("Cache-Control") .header("Cache-Control", "public, max-age=" + maxAge) .build(); } else { //读取缓存信息 request = request.newBuilder() .cacheControl(CacheControl.FORCE_CACHE) .build(); Response response = chain.proceed(request); //set cache times is 3 days int maxStale = 60 * 60 * 24 * 3; return response.newBuilder() .removeHeader("Pragma") .removeHeader("Cache-Control") .header("Cache-Control", "public, only-if-cached, max-stale=" + maxStale) .build(); } } } app_base/src/main/java/com/android/app_base/manager/AppManager.java
New file @@ -0,0 +1,212 @@ package com.android.app_base.manager; import android.app.Activity; import android.content.Intent; import androidx.annotation.NonNull; import androidx.fragment.app.Fragment; import com.blankj.utilcode.util.ActivityUtils; import java.util.Stack; /** * @author ljj * @date 2023.3.1 * @desc APP行为管理 */ public class AppManager { private static Stack<Activity> activityStack; private static Stack<Fragment> fragmentStack; private static AppManager instance; private AppManager() { } /** * 单例模式 * * @return AppManager */ public static AppManager getAppManager() { if (instance == null) { instance = new AppManager(); } return instance; } public static Stack<Activity> getActivityStack() { return activityStack; } public static Stack<Fragment> getFragmentStack() { return fragmentStack; } /** * 添加Activity到堆栈 */ public void addActivity(Activity activity) { if (activityStack == null) { activityStack = new Stack<Activity>(); } activityStack.add(activity); } /** * 移除指定的Activity */ public void removeActivity(Activity activity) { if (activity != null) { activityStack.remove(activity); } } /** * 是否有activity */ public boolean isActivity() { if (activityStack != null) { return !activityStack.isEmpty(); } return false; } /** * 获取当前Activity(堆栈中最后一个压入的) */ public Activity currentActivity() { return activityStack.lastElement(); } /** * 开始指定类名的 Activity */ public void startActivity(Class T){ ActivityUtils.startActivity(T); } public boolean startActivity(@NonNull Intent intent) { return ActivityUtils.startActivity(intent); } /** * 结束当前Activity(堆栈中最后一个压入的) */ public void finishCurrentActivity() { Activity activity = activityStack.lastElement(); finishActivity(activity); } /** * 结束指定的Activity */ public void finishActivity(Activity activity) { if (activity != null) { if (!activity.isFinishing()) { activity.finish(); } } } /** * 结束指定类名的Activity */ public void finishActivity(Class<?> cls) { for (Activity activity : activityStack) { if (activity.getClass().equals(cls)) { finishActivity(activity); break; } } } /** * 结束所有Activity */ public void finishAllActivity() { for (int i = 0, size = activityStack.size(); i < size; i++) { if (null != activityStack.get(i)) { finishActivity(activityStack.get(i)); } } activityStack.clear(); } /** * 获取指定的Activity */ public Activity getActivity(Class<?> cls) { if (activityStack != null) for (Activity activity : activityStack) { if (activity.getClass().equals(cls)) { return activity; } } return null; } /** * 添加Fragment到堆栈 */ public void addFragment(Fragment fragment) { if (fragmentStack == null) { fragmentStack = new Stack<Fragment>(); } fragmentStack.add(fragment); } /** * 移除指定的Fragment */ public void removeFragment(Fragment fragment) { if (fragment != null) { fragmentStack.remove(fragment); } } /** * 是否有Fragment */ public boolean isFragment() { if (fragmentStack != null) { return !fragmentStack.isEmpty(); } return false; } /** * 获取当前Activity(堆栈中最后一个压入的) */ public Fragment currentFragment() { if (fragmentStack != null) { Fragment fragment = fragmentStack.lastElement(); return fragment; } return null; } /** * 退出应用程序 */ public void AppExit() { try { finishAllActivity(); // 杀死该应用进程 // android.os.Process.killProcess(android.os.Process.myPid()); // 调用 System.exit(n) 实际上等效于调用: // Runtime.getRuntime().exit(n) // finish()是Activity的类方法,仅仅针对Activity,当调用finish()时,只是将活动推向后台,并没有立即释放内存,活动的资源并没有被清理;当调用System.exit(0)时,退出当前Activity并释放资源(内存),但是该方法不可以结束整个App如有多个Activty或者有其他组件service等不会结束。 // 其实android的机制决定了用户无法完全退出应用,当你的application最长时间没有被用过的时候,android自身会决定将application关闭了。 //System.exit(0); } catch (Exception e) { activityStack.clear(); e.printStackTrace(); } } } app_base/src/main/java/com/android/app_base/utils/SPUtils.java
New file @@ -0,0 +1,282 @@ package com.android.app_base.utils; import android.content.Context; import android.content.SharedPreferences; import androidx.annotation.NonNull; import com.android.app_base.base.BaseApplication; import java.util.Collections; import java.util.HashMap; import java.util.Map; import java.util.Set; /** * @desc SharedPreferences工具类 */ public final class SPUtils { private static Map<String, SPUtils> sSPMap = new HashMap<>(); private SharedPreferences sp; /** * 获取SP实例 * * @return {@link SPUtils} */ public static SPUtils getInstance() { return getInstance(""); } /** * 获取SP实例 * * @param spName sp名 * @return {@link SPUtils} */ public static SPUtils getInstance(String spName) { if (isSpace(spName)) spName = "spUtils"; SPUtils sp = sSPMap.get(spName); if (sp == null) { sp = new SPUtils(spName); sSPMap.put(spName, sp); } return sp; } private SPUtils(final String spName) { sp = BaseApplication.getInstance().getSharedPreferences(spName, Context.MODE_PRIVATE); } /** * SP中写入String * * @param key 键 * @param value 值 */ public void put(@NonNull final String key, @NonNull final String value) { sp.edit().putString(key, value).apply(); } /** * SP中读取String * * @param key 键 * @return 存在返回对应值,不存在返回默认值{@code ""} */ public String getString(@NonNull final String key) { return getString(key, ""); } /** * SP中读取String * * @param key 键 * @param defaultValue 默认值 * @return 存在返回对应值,不存在返回默认值{@code defaultValue} */ public String getString(@NonNull final String key, @NonNull final String defaultValue) { return sp.getString(key, defaultValue); } /** * SP中写入int * * @param key 键 * @param value 值 */ public void put(@NonNull final String key, final int value) { sp.edit().putInt(key, value).apply(); } /** * SP中读取int * * @param key 键 * @return 存在返回对应值,不存在返回默认值-1 */ public int getInt(@NonNull final String key) { return getInt(key, -1); } /** * SP中读取int * * @param key 键 * @param defaultValue 默认值 * @return 存在返回对应值,不存在返回默认值{@code defaultValue} */ public int getInt(@NonNull final String key, final int defaultValue) { return sp.getInt(key, defaultValue); } /** * SP中写入long * * @param key 键 * @param value 值 */ public void put(@NonNull final String key, final long value) { sp.edit().putLong(key, value).apply(); } /** * SP中读取long * * @param key 键 * @return 存在返回对应值,不存在返回默认值-1 */ public long getLong(@NonNull final String key) { return getLong(key, -1L); } /** * SP中读取long * * @param key 键 * @param defaultValue 默认值 * @return 存在返回对应值,不存在返回默认值{@code defaultValue} */ public long getLong(@NonNull final String key, final long defaultValue) { return sp.getLong(key, defaultValue); } /** * SP中写入float * * @param key 键 * @param value 值 */ public void put(@NonNull final String key, final float value) { sp.edit().putFloat(key, value).apply(); } /** * SP中读取float * * @param key 键 * @return 存在返回对应值,不存在返回默认值-1 */ public float getFloat(@NonNull final String key) { return getFloat(key, -1f); } /** * SP中读取float * * @param key 键 * @param defaultValue 默认值 * @return 存在返回对应值,不存在返回默认值{@code defaultValue} */ public float getFloat(@NonNull final String key, final float defaultValue) { return sp.getFloat(key, defaultValue); } /** * SP中写入boolean * * @param key 键 * @param value 值 */ public void put(@NonNull final String key, final boolean value) { sp.edit().putBoolean(key, value).apply(); } /** * SP中读取boolean * * @param key 键 * @return 存在返回对应值,不存在返回默认值{@code false} */ public boolean getBoolean(@NonNull final String key) { return getBoolean(key, false); } /** * SP中读取boolean * * @param key 键 * @param defaultValue 默认值 * @return 存在返回对应值,不存在返回默认值{@code defaultValue} */ public boolean getBoolean(@NonNull final String key, final boolean defaultValue) { return sp.getBoolean(key, defaultValue); } /** * SP中写入String集合 * * @param key 键 * @param values 值 */ public void put(@NonNull final String key, @NonNull final Set<String> values) { sp.edit().putStringSet(key, values).apply(); } /** * SP中读取StringSet * * @param key 键 * @return 存在返回对应值,不存在返回默认值{@code Collections.<String>emptySet()} */ public Set<String> getStringSet(@NonNull final String key) { return getStringSet(key, Collections.<String>emptySet()); } /** * SP中读取StringSet * * @param key 键 * @param defaultValue 默认值 * @return 存在返回对应值,不存在返回默认值{@code defaultValue} */ public Set<String> getStringSet(@NonNull final String key, @NonNull final Set<String> defaultValue) { return sp.getStringSet(key, defaultValue); } /** * SP中获取所有键值对 * * @return Map对象 */ public Map<String, ?> getAll() { return sp.getAll(); } /** * SP中是否存在该key * * @param key 键 * @return {@code true}: 存在<br>{@code false}: 不存在 */ public boolean contains(@NonNull final String key) { return sp.contains(key); } /** * SP中移除该key * * @param key 键 */ public void remove(@NonNull final String key) { sp.edit().remove(key).apply(); } /** * SP中清除所有数据 */ public void clear() { sp.edit().clear().apply(); } private static boolean isSpace(final String s) { if (s == null) return true; for (int i = 0, len = s.length(); i < len; ++i) { if (!Character.isWhitespace(s.charAt(i))) { return false; } } return true; } } app_base/src/main/java/com/android/app_base/utils/ToastUtils.java
New file @@ -0,0 +1,429 @@ package com.android.app_base.utils; import android.content.Context; import android.os.Handler; import android.os.Looper; import android.text.SpannableString; import android.text.Spanned; import android.text.style.ForegroundColorSpan; import android.view.Gravity; import android.view.LayoutInflater; import android.view.View; import android.widget.Toast; import androidx.annotation.ColorInt; import androidx.annotation.DrawableRes; import androidx.annotation.LayoutRes; import androidx.annotation.Nullable; import androidx.annotation.StringRes; import com.android.app_base.base.BaseApplication; import com.blankj.utilcode.util.Utils; import java.lang.ref.WeakReference; /** * 吐司工具类 */ public final class ToastUtils { private static final int DEFAULT_COLOR = 0x12000000; private static Toast sToast; private static int gravity = Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM; private static int xOffset = 0; private static int yOffset = (int) (64 * BaseApplication.getInstance().getResources().getDisplayMetrics().density + 0.5); private static int backgroundColor = DEFAULT_COLOR; private static int bgResource = -1; private static int messageColor = DEFAULT_COLOR; private static WeakReference<View> sViewWeakReference; private static Handler sHandler = new Handler(Looper.getMainLooper()); private ToastUtils() { throw new UnsupportedOperationException("u can't instantiate me..."); } /** * 设置吐司位置 * * @param gravity 位置 * @param xOffset x偏移 * @param yOffset y偏移 */ public static void setGravity(int gravity, int xOffset, int yOffset) { ToastUtils.gravity = gravity; ToastUtils.xOffset = xOffset; ToastUtils.yOffset = yOffset; } /** * 设置吐司view * * @param layoutId 视图 */ public static void setView(@LayoutRes int layoutId) { LayoutInflater inflate = (LayoutInflater) BaseApplication.getInstance().getSystemService(Context.LAYOUT_INFLATER_SERVICE); sViewWeakReference = new WeakReference<>(inflate.inflate(layoutId, null)); } /** * 设置吐司view * * @param view 视图 */ public static void setView(@Nullable View view) { sViewWeakReference = view == null ? null : new WeakReference<>(view); } /** * 获取吐司view * * @return view */ public static View getView() { if (sViewWeakReference != null) { final View view = sViewWeakReference.get(); if (view != null) { return view; } } if (sToast != null) return sToast.getView(); return null; } /** * 设置背景颜色 * * @param backgroundColor 背景色 */ public static void setBackgroundColor(@ColorInt int backgroundColor) { ToastUtils.backgroundColor = backgroundColor; } /** * 设置背景资源 * * @param bgResource 背景资源 */ public static void setBgResource(@DrawableRes int bgResource) { ToastUtils.bgResource = bgResource; } /** * 设置消息颜色 * * @param messageColor 颜色 */ public static void setMessageColor(@ColorInt int messageColor) { ToastUtils.messageColor = messageColor; } /** * 安全地显示短时吐司 * * @param text 文本 */ public static void showShortSafe(final CharSequence text) { sHandler.post(new Runnable() { @Override public void run() { show(text, Toast.LENGTH_SHORT); } }); } /** * 安全地显示短时吐司 * * @param resId 资源Id */ public static void showShortSafe(final @StringRes int resId) { sHandler.post(new Runnable() { @Override public void run() { show(resId, Toast.LENGTH_SHORT); } }); } /** * 安全地显示短时吐司 * * @param resId 资源Id * @param args 参数 */ public static void showShortSafe(final @StringRes int resId, final Object... args) { sHandler.post(new Runnable() { @Override public void run() { show(resId, Toast.LENGTH_SHORT, args); } }); } /** * 安全地显示短时吐司 * * @param format 格式 * @param args 参数 */ public static void showShortSafe(final String format, final Object... args) { sHandler.post(new Runnable() { @Override public void run() { show(format, Toast.LENGTH_SHORT, args); } }); } /** * 安全地显示长时吐司 * * @param text 文本 */ public static void showLongSafe(final CharSequence text) { sHandler.post(new Runnable() { @Override public void run() { show(text, Toast.LENGTH_LONG); } }); } /** * 安全地显示长时吐司 * * @param resId 资源Id */ public static void showLongSafe(final @StringRes int resId) { sHandler.post(new Runnable() { @Override public void run() { show(resId, Toast.LENGTH_LONG); } }); } /** * 安全地显示长时吐司 * * @param resId 资源Id * @param args 参数 */ public static void showLongSafe(final @StringRes int resId, final Object... args) { sHandler.post(new Runnable() { @Override public void run() { show(resId, Toast.LENGTH_LONG, args); } }); } /** * 安全地显示长时吐司 * * @param format 格式 * @param args 参数 */ public static void showLongSafe(final String format, final Object... args) { sHandler.post(new Runnable() { @Override public void run() { show(format, Toast.LENGTH_LONG, args); } }); } /** * 显示短时吐司 * * @param text 文本 */ public static void showShort(CharSequence text) { show(text, Toast.LENGTH_SHORT); } /** * 显示短时吐司 * * @param resId 资源Id */ public static void showShort(@StringRes int resId) { show(resId, Toast.LENGTH_SHORT); } /** * 显示短时吐司 * * @param resId 资源Id * @param args 参数 */ public static void showShort(@StringRes int resId, Object... args) { show(resId, Toast.LENGTH_SHORT, args); } /** * 显示短时吐司 * * @param format 格式 * @param args 参数 */ public static void showShort(String format, Object... args) { show(format, Toast.LENGTH_SHORT, args); } /** * 显示长时吐司 * * @param text 文本 */ public static void showLong(CharSequence text) { show(text, Toast.LENGTH_LONG); } /** * 显示长时吐司 * * @param resId 资源Id */ public static void showLong(@StringRes int resId) { show(resId, Toast.LENGTH_LONG); } /** * 显示长时吐司 * * @param resId 资源Id * @param args 参数 */ public static void showLong(@StringRes int resId, Object... args) { show(resId, Toast.LENGTH_LONG, args); } /** * 显示长时吐司 * * @param format 格式 * @param args 参数 */ public static void showLong(String format, Object... args) { show(format, Toast.LENGTH_LONG, args); } /** * 安全地显示短时自定义吐司 */ public static void showCustomShortSafe() { sHandler.post(new Runnable() { @Override public void run() { show("", Toast.LENGTH_SHORT); } }); } /** * 安全地显示长时自定义吐司 */ public static void showCustomLongSafe() { sHandler.post(new Runnable() { @Override public void run() { show("", Toast.LENGTH_LONG); } }); } /** * 显示短时自定义吐司 */ public static void showCustomShort() { show("", Toast.LENGTH_SHORT); } /** * 显示长时自定义吐司 */ public static void showCustomLong() { show("", Toast.LENGTH_LONG); } /** * 显示吐司 * * @param resId 资源Id * @param duration 显示时长 */ private static void show(@StringRes int resId, int duration) { show(BaseApplication.getInstance().getResources().getText(resId).toString(), duration); } /** * 显示吐司 * * @param resId 资源Id * @param duration 显示时长 * @param args 参数 */ private static void show(@StringRes int resId, int duration, Object... args) { show(String.format(BaseApplication.getInstance().getResources().getString(resId), args), duration); } /** * 显示吐司 * * @param format 格式 * @param duration 显示时长 * @param args 参数 */ private static void show(String format, int duration, Object... args) { show(String.format(format, args), duration); } /** * 显示吐司 * * @param text 文本 * @param duration 显示时长 */ private static void show(CharSequence text, int duration) { cancel(); boolean isCustom = false; if (sViewWeakReference != null) { final View view = sViewWeakReference.get(); if (view != null) { sToast = new Toast(BaseApplication.getInstance()); sToast.setView(view); sToast.setDuration(duration); isCustom = true; } } if (!isCustom) { if (messageColor != DEFAULT_COLOR) { SpannableString spannableString = new SpannableString(text); ForegroundColorSpan colorSpan = new ForegroundColorSpan(messageColor); spannableString.setSpan(colorSpan, 0, spannableString.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); sToast = Toast.makeText(BaseApplication.getInstance(), spannableString, duration); } else { sToast = Toast.makeText(BaseApplication.getInstance(), text, duration); } } View view = sToast.getView(); if (bgResource != -1) { view.setBackgroundResource(bgResource); } else if (backgroundColor != DEFAULT_COLOR) { view.setBackgroundColor(backgroundColor); } sToast.setGravity(gravity, xOffset, yOffset); sToast.show(); } /** * 取消吐司显示 */ public static void cancel() { if (sToast != null) { sToast.cancel(); sToast = null; } } } app_base/src/test/java/com/android/app_base/ExampleUnitTest.java
New file @@ -0,0 +1,17 @@ package com.android.app_base; import org.junit.Test; import static org.junit.Assert.*; /** * Example local unit test, which will execute on the development machine (host). * * @see <a href="http://d.android.com/tools/testing">Testing documentation</a> */ public class ExampleUnitTest { @Test public void addition_isCorrect() { assertEquals(4, 2 + 2); } } build.gradle
New file @@ -0,0 +1,26 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. buildscript { repositories { google() mavenCentral() maven { url 'https://jitpack.io' } maven { allowInsecureProtocol = true url 'http://maven.aliyun.com/nexus/content/groups/public/' } jcenter() } dependencies { classpath 'com.android.tools.build:gradle:7.1.3' classpath 'com.blankj:bus-gradle-plugin:latest.release' // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files } } task clean(type: Delete) { delete rootProject.buildDir } gradle.properties
New file @@ -0,0 +1,23 @@ # Project-wide Gradle settings. # IDE (e.g. Android Studio) users: # Gradle settings configured through the IDE *will override* # any settings specified in this file. # For more details on how to configure your build environment visit # http://www.gradle.org/docs/current/userguide/build_environment.html # Specifies the JVM arguments used for the daemon process. # The setting is particularly useful for tweaking memory settings. org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 # When configured, Gradle will run in incubating parallel mode. # This option should only be used with decoupled projects. More details, visit # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects # org.gradle.parallel=true # AndroidX package structure to make it clearer which packages are bundled with the # Android operating system, and which are packaged with your app's APK # https://developer.android.com/topic/libraries/support-library/androidx-rn android.useAndroidX=true # Enables namespacing of each library's R class so that its R class includes only the # resources declared in the library itself and none from the library's dependencies, # thereby reducing the size of the R class for that library android.nonTransitiveRClass=true android.injected.testOnly=false gradle/wrapper/gradle-wrapper.jarBinary files differ
gradle/wrapper/gradle-wrapper.properties
New file @@ -0,0 +1,6 @@ #Wed Mar 01 13:44:42 CST 2023 distributionBase=GRADLE_USER_HOME distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-bin.zip distributionPath=wrapper/dists zipStorePath=wrapper/dists zipStoreBase=GRADLE_USER_HOME gradlew
New file @@ -0,0 +1,185 @@ #!/usr/bin/env sh # # Copyright 2015 the original author or authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # https://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # ############################################################################## ## ## Gradle start up script for UN*X ## ############################################################################## # Attempt to set APP_HOME # Resolve links: $0 may be a link PRG="$0" # Need this for relative symlinks. while [ -h "$PRG" ] ; do ls=`ls -ld "$PRG"` link=`expr "$ls" : '.*-> \(.*\)$'` if expr "$link" : '/.*' > /dev/null; then PRG="$link" else PRG=`dirname "$PRG"`"/$link" fi done SAVED="`pwd`" cd "`dirname \"$PRG\"`/" >/dev/null APP_HOME="`pwd -P`" cd "$SAVED" >/dev/null APP_NAME="Gradle" APP_BASE_NAME=`basename "$0"` # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD="maximum" warn () { echo "$*" } die () { echo echo "$*" echo exit 1 } # OS specific support (must be 'true' or 'false'). cygwin=false msys=false darwin=false nonstop=false case "`uname`" in CYGWIN* ) cygwin=true ;; Darwin* ) darwin=true ;; MINGW* ) msys=true ;; NONSTOP* ) nonstop=true ;; esac CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar # Determine the Java command to use to start the JVM. if [ -n "$JAVA_HOME" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then # IBM's JDK on AIX uses strange locations for the executables JAVACMD="$JAVA_HOME/jre/sh/java" else JAVACMD="$JAVA_HOME/bin/java" fi if [ ! -x "$JAVACMD" ] ; then die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi else JAVACMD="java" which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi # Increase the maximum file descriptors if we can. if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then MAX_FD_LIMIT=`ulimit -H -n` if [ $? -eq 0 ] ; then if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then MAX_FD="$MAX_FD_LIMIT" fi ulimit -n $MAX_FD if [ $? -ne 0 ] ; then warn "Could not set maximum file descriptor limit: $MAX_FD" fi else warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" fi fi # For Darwin, add options to specify how the application appears in the dock if $darwin; then GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" fi # For Cygwin or MSYS, switch paths to Windows format before running java if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then APP_HOME=`cygpath --path --mixed "$APP_HOME"` CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` JAVACMD=`cygpath --unix "$JAVACMD"` # We build the pattern for arguments to be converted via cygpath ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` SEP="" for dir in $ROOTDIRSRAW ; do ROOTDIRS="$ROOTDIRS$SEP$dir" SEP="|" done OURCYGPATTERN="(^($ROOTDIRS))" # Add a user-defined pattern to the cygpath arguments if [ "$GRADLE_CYGPATTERN" != "" ] ; then OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" fi # Now convert the arguments - kludge to limit ourselves to /bin/sh i=0 for arg in "$@" ; do CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` else eval `echo args$i`="\"$arg\"" fi i=`expr $i + 1` done case $i in 0) set -- ;; 1) set -- "$args0" ;; 2) set -- "$args0" "$args1" ;; 3) set -- "$args0" "$args1" "$args2" ;; 4) set -- "$args0" "$args1" "$args2" "$args3" ;; 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; esac fi # Escape application args save () { for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done echo " " } APP_ARGS=`save "$@"` # Collect all arguments for the java command, following the shell quoting and substitution rules eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" exec "$JAVACMD" "$@" gradlew.bat
New file @@ -0,0 +1,89 @@ @rem @rem Copyright 2015 the original author or authors. @rem @rem Licensed under the Apache License, Version 2.0 (the "License"); @rem you may not use this file except in compliance with the License. @rem You may obtain a copy of the License at @rem @rem https://www.apache.org/licenses/LICENSE-2.0 @rem @rem Unless required by applicable law or agreed to in writing, software @rem distributed under the License is distributed on an "AS IS" BASIS, @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @rem See the License for the specific language governing permissions and @rem limitations under the License. @rem @if "%DEBUG%" == "" @echo off @rem ########################################################################## @rem @rem Gradle startup script for Windows @rem @rem ########################################################################## @rem Set local scope for the variables with windows NT shell if "%OS%"=="Windows_NT" setlocal set DIRNAME=%~dp0 if "%DIRNAME%" == "" set DIRNAME=. set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% @rem Resolve any "." and ".." in APP_HOME to make it shorter. for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" @rem Find java.exe if defined JAVA_HOME goto findJavaFromJavaHome set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 if "%ERRORLEVEL%" == "0" goto execute echo. echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. echo. echo Please set the JAVA_HOME variable in your environment to match the echo location of your Java installation. goto fail :findJavaFromJavaHome set JAVA_HOME=%JAVA_HOME:"=% set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto execute echo. echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% echo. echo Please set the JAVA_HOME variable in your environment to match the echo location of your Java installation. goto fail :execute @rem Setup the command line set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar @rem Execute Gradle "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* :end @rem End local scope for the variables with windows NT shell if "%ERRORLEVEL%"=="0" goto mainEnd :fail rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of rem the _cmd.exe /c_ return code! if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 exit /b 1 :mainEnd if "%OS%"=="Windows_NT" endlocal :omega settings.gradle
New file @@ -0,0 +1,19 @@ pluginManagement { repositories { gradlePluginPortal() google() mavenCentral() } } dependencyResolutionManagement { repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) repositories { google() mavenCentral() jcenter() maven { url 'https://jitpack.io' } } } rootProject.name = "zhangshi_app_android" include ':app' include ':app_base'