Compare commits
No commits in common. 'main' and 'androidx' have entirely different histories.
@ -1,176 +0,0 @@ |
||||
# ---> Android |
||||
# Gradle files |
||||
.gradle/ |
||||
build/ |
||||
|
||||
# Local configuration file (sdk path, etc) |
||||
local.properties |
||||
|
||||
# Log/OS Files |
||||
*.log |
||||
|
||||
# Android Studio generated files and folders |
||||
captures/ |
||||
.externalNativeBuild/ |
||||
.cxx/ |
||||
*.apk |
||||
output.json |
||||
|
||||
# IntelliJ |
||||
*.iml |
||||
.idea/ |
||||
misc.xml |
||||
deploymentTargetDropDown.xml |
||||
render.experimental.xml |
||||
|
||||
# Keystore files |
||||
*.jks |
||||
*.keystore |
||||
|
||||
# Google Services (e.g. APIs or Firebase) |
||||
google-services.json |
||||
|
||||
# Android Profiling |
||||
*.hprof |
||||
|
||||
# ---> CMake |
||||
CMakeLists.txt.user |
||||
CMakeCache.txt |
||||
CMakeFiles |
||||
CMakeScripts |
||||
Testing |
||||
Makefile |
||||
cmake_install.cmake |
||||
install_manifest.txt |
||||
compile_commands.json |
||||
CTestTestfile.cmake |
||||
_deps |
||||
|
||||
# ---> Gradle |
||||
.gradle |
||||
**/build/ |
||||
!src/**/build/ |
||||
|
||||
# Ignore Gradle GUI config |
||||
gradle-app.setting |
||||
|
||||
# Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored) |
||||
!gradle-wrapper.jar |
||||
|
||||
# Avoid ignore Gradle wrappper properties |
||||
!gradle-wrapper.properties |
||||
|
||||
# Cache of project |
||||
.gradletasknamecache |
||||
|
||||
# Eclipse Gradle plugin generated files |
||||
# Eclipse Core |
||||
.project |
||||
# JDT-specific (Eclipse Java Development Tools) |
||||
.classpath |
||||
|
||||
# ---> Java |
||||
# Compiled class file |
||||
*.class |
||||
|
||||
# Log file |
||||
*.log |
||||
|
||||
# BlueJ files |
||||
*.ctxt |
||||
|
||||
# Mobile Tools for Java (J2ME) |
||||
.mtj.tmp/ |
||||
|
||||
# Package Files # |
||||
*.jar |
||||
*.war |
||||
*.nar |
||||
*.ear |
||||
*.zip |
||||
*.tar.gz |
||||
*.rar |
||||
|
||||
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml |
||||
hs_err_pid* |
||||
replay_pid* |
||||
|
||||
# ---> JetBrains |
||||
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider |
||||
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 |
||||
|
||||
# User-specific stuff |
||||
.idea/**/workspace.xml |
||||
.idea/**/tasks.xml |
||||
.idea/**/usage.statistics.xml |
||||
.idea/**/dictionaries |
||||
.idea/**/shelf |
||||
|
||||
# AWS User-specific |
||||
.idea/**/aws.xml |
||||
|
||||
# Generated files |
||||
.idea/**/contentModel.xml |
||||
|
||||
# Sensitive or high-churn files |
||||
.idea/**/dataSources/ |
||||
.idea/**/dataSources.ids |
||||
.idea/**/dataSources.local.xml |
||||
.idea/**/sqlDataSources.xml |
||||
.idea/**/dynamic.xml |
||||
.idea/**/uiDesigner.xml |
||||
.idea/**/dbnavigator.xml |
||||
|
||||
# Gradle |
||||
.idea/**/gradle.xml |
||||
.idea/**/libraries |
||||
|
||||
# Gradle and Maven with auto-import |
||||
# When using Gradle or Maven with auto-import, you should exclude module files, |
||||
# since they will be recreated, and may cause churn. Uncomment if using |
||||
# auto-import. |
||||
# .idea/artifacts |
||||
# .idea/compiler.xml |
||||
# .idea/jarRepositories.xml |
||||
# .idea/modules.xml |
||||
# .idea/*.iml |
||||
# .idea/modules |
||||
# *.iml |
||||
# *.ipr |
||||
|
||||
# CMake |
||||
cmake-build-*/ |
||||
|
||||
# Mongo Explorer plugin |
||||
.idea/**/mongoSettings.xml |
||||
|
||||
# File-based project format |
||||
*.iws |
||||
|
||||
# IntelliJ |
||||
out/ |
||||
|
||||
# mpeltonen/sbt-idea plugin |
||||
.idea_modules/ |
||||
|
||||
# JIRA plugin |
||||
atlassian-ide-plugin.xml |
||||
|
||||
# Cursive Clojure plugin |
||||
.idea/replstate.xml |
||||
|
||||
# SonarLint plugin |
||||
.idea/sonarlint/ |
||||
|
||||
# Crashlytics plugin (for Android Studio and IntelliJ) |
||||
com_crashlytics_export_strings.xml |
||||
crashlytics.properties |
||||
crashlytics-build.properties |
||||
fabric.properties |
||||
|
||||
# Editor-based Rest Client |
||||
.idea/httpRequests |
||||
|
||||
# Android studio 3.1+ serialized cache file |
||||
.idea/caches/build_file_checksums.ser |
||||
|
@ -1,2 +1,13 @@ |
||||
# android-libs |
||||
# Android-Libs |
||||
|
||||
## modules |
||||
|
||||
- lib_base:common util classes and base classes,including BaseActivity, BaseFragment, BaseStateFragment, BaseListFragment, BaseAdapter, MVVM, MVP, MultiStateView, Dagger2, AAC, RxJava2, Kotlin, etc. |
||||
- lib_media_selector:gets photos or videos from system. |
||||
- lib_qrcode:library for scanning and generating qrcode. |
||||
- lib_network:RxJava + Retrofit + OkHttp. |
||||
- lib_cache: DiskLruCache + MMKV. |
||||
|
||||
## how to use |
||||
|
||||
refer [AndroidArchitecture](https://github.com/Ztiany/AndroidArchitecture) |
||||
|
@ -0,0 +1,22 @@ |
||||
*.iml |
||||
.gradle |
||||
/local.properties |
||||
/.idea |
||||
.DS_Store |
||||
/build |
||||
/captures |
||||
.externalNativeBuild |
||||
*.iml |
||||
.idea/ |
||||
.gradle |
||||
/local.properties |
||||
.DS_Store |
||||
/build |
||||
/captures |
||||
*.apk |
||||
*.ap_ |
||||
*.dex |
||||
*.class |
||||
bin/ |
||||
gen/ |
||||
local.properties |
@ -0,0 +1,28 @@ |
||||
# Android Development Base Library |
||||
|
||||
## 1 Third part Libraries |
||||
|
||||
- [AndroidX](https://developer.android.com/jetpack/androidx) |
||||
- [RxJava(2)](https://github.com/ReactiveX/RxJava) |
||||
- [RxAndroid(2)](https://github.com/ReactiveX/RxAndroid) |
||||
- [AutoDispose](https://github.com/uber/AutoDispose) |
||||
- [Dagger2](https://github.com/google/dagger) |
||||
- [Glide](https://github.com/bumptech/glide) |
||||
- [OkHttp](https://github.com/square/okhttp) |
||||
- [Timber](https://github.com/JakeWharton/timber) |
||||
- [WrapperAdapter](https://github.com/Ztiany/WrapperAdapter) |
||||
- [MultiTypeAdapter](https://github.com/drakeet/MultiType) |
||||
- [AndroidUtilCode](https://github.com/Blankj/AndroidUtilCode) |
||||
|
||||
## 2 Environment |
||||
|
||||
- AndroidStudio 3+ |
||||
- Java8 with desugar |
||||
|
||||
## 3 How do we make Dagger2 working with SavedStateViewModel? |
||||
|
||||
refer |
||||
|
||||
- [saving-ui-state-with-viewmodel-savedstate-and-dagger](https://proandroiddev.com/saving-ui-state-with-viewmodel-savedstate-and-dagger-f77bcaeb8b08#7f89) |
||||
- [AssistedInject](https://github.com/square/AssistedInject/issues/81) |
||||
- [github-commit-browser](https://github.com/Nimrodda/github-commit-browser) |
@ -0,0 +1,130 @@ |
||||
apply plugin: 'com.android.library' |
||||
apply plugin: 'kotlin-android' |
||||
apply plugin: 'kotlin-android-extensions' |
||||
apply plugin: 'kotlin-kapt' |
||||
|
||||
androidExtensions { |
||||
experimental = true |
||||
} |
||||
|
||||
android { |
||||
compileSdkVersion rootProject.compileSdkVersion |
||||
buildToolsVersion rootProject.buildToolsVersion |
||||
|
||||
defaultConfig { |
||||
|
||||
minSdkVersion rootProject.minSdkVersion |
||||
targetSdkVersion rootProject.targetSdkVersion |
||||
versionCode 1 |
||||
versionName "1.0" |
||||
|
||||
javaCompileOptions { |
||||
annotationProcessorOptions { |
||||
includeCompileClasspath false |
||||
} |
||||
} |
||||
} |
||||
|
||||
buildTypes { |
||||
release { |
||||
minifyEnabled false |
||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' |
||||
} |
||||
} |
||||
|
||||
lintOptions { |
||||
abortOnError false |
||||
} |
||||
|
||||
compileOptions { |
||||
sourceCompatibility JavaVersion.VERSION_1_8 |
||||
targetCompatibility JavaVersion.VERSION_1_8 |
||||
} |
||||
|
||||
kotlinOptions { |
||||
jvmTarget = "1.8" |
||||
} |
||||
|
||||
sourceSets { |
||||
main { |
||||
java.srcDirs += "src/github/java" |
||||
res.srcDirs += "src/github/res" |
||||
} |
||||
} |
||||
|
||||
} |
||||
|
||||
dependencies { |
||||
api fileTree(dir: 'libs', include: ['*.jar']) |
||||
|
||||
//测试 |
||||
testImplementation testLibraries.junit |
||||
|
||||
//AndroidSupport |
||||
api androidLibraries.supportV4 |
||||
api androidLibraries.appcompat |
||||
api androidLibraries.recyclerView |
||||
api androidLibraries.material |
||||
api androidLibraries.percentLayout |
||||
api androidLibraries.constraintLayout |
||||
api androidLibraries.viewpager2 |
||||
api androidLibraries.annotation |
||||
|
||||
//AAC |
||||
api androidLibraries.archRuntime |
||||
api androidLibraries.archCommon |
||||
api androidLibraries.fragmentKtx |
||||
api androidLibraries.lifecycleCommon |
||||
api androidLibraries.lifecycleCommonJava8 |
||||
api androidLibraries.lifecycleRuntimeKtx |
||||
api androidLibraries.lifecycleLiveDataCore |
||||
api androidLibraries.lifecycleLiveData |
||||
api androidLibraries.lifecycleLiveKtx |
||||
api androidLibraries.lifecycleViewModel |
||||
api androidLibraries.lifecycleViewModelKtx |
||||
api androidLibraries.lifecycleExtensions |
||||
api androidLibraries.lifecycleReactiveStreams |
||||
|
||||
//Kotlin |
||||
api kotlinLibraries.kotlinStdlib |
||||
api kotlinLibraries.kotlinReflect |
||||
api kotlinLibraries.kotlinCoroutines |
||||
api kotlinLibraries.kotlinAndroidCoroutines |
||||
api kotlinLibraries.kotlinxCoroutinesRx2 |
||||
|
||||
//RxJava |
||||
api thirdLibraries.rxJava |
||||
api thirdLibraries.rxAndroid |
||||
api thirdLibraries.rxBinding |
||||
api thirdLibraries.autoDispose |
||||
api thirdLibraries.autoDisposeAndroid |
||||
api thirdLibraries.autoDisposeLifecycle |
||||
api thirdLibraries.autoDisposeLifecycleArchcomponents |
||||
|
||||
/*Dagger2*/ |
||||
api thirdLibraries.dagger2 |
||||
api thirdLibraries.jsr305 |
||||
api thirdLibraries.dagger2Android |
||||
api thirdLibraries.dagger2AndroidSupport |
||||
kapt thirdLibraries.dagger2Apt |
||||
kapt thirdLibraries.dagger2AndroidApt |
||||
|
||||
//LoadMore |
||||
api uiLibraries.wrapperAdapter |
||||
|
||||
//Adapter |
||||
api uiLibraries.multiType |
||||
|
||||
//Log |
||||
api thirdLibraries.timber |
||||
|
||||
//ImageLoader |
||||
api thirdLibraries.glide |
||||
api thirdLibraries.glideOkHttp |
||||
api thirdLibraries.okHttp |
||||
|
||||
//Utils |
||||
api thirdLibraries.utilcode |
||||
api thirdLibraries.jOOR |
||||
api thirdLibraries.supportOptional |
||||
} |
@ -0,0 +1,17 @@ |
||||
# Add project specific ProGuard rules here. |
||||
# By default, the flags in this file are appended to flags specified |
||||
# in E:\DeveloperTools\sdk/tools/proguard/proguard-android.txt |
||||
# You can edit the include path and order by changing the proguardFiles |
||||
# directive in build.gradle. |
||||
# |
||||
# For more details, see |
||||
# http://developer.android.com/guide/developing/tools/proguard.html |
||||
|
||||
# Add any project specific keep options here: |
||||
|
||||
# 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 *; |
||||
#} |
@ -0,0 +1,583 @@ |
||||
/* |
||||
* Copyright (C) 2017 Jared Rummler |
||||
* |
||||
* 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 |
||||
* |
||||
* http://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. |
||||
*/ |
||||
|
||||
package com.jaredrummler.android.util; |
||||
|
||||
import android.os.Build; |
||||
import android.text.Html; |
||||
import android.text.Spanned; |
||||
import android.widget.TextView; |
||||
|
||||
import java.util.Iterator; |
||||
import java.util.LinkedList; |
||||
|
||||
import androidx.annotation.RequiresApi; |
||||
|
||||
/** |
||||
* <p>Build a valid HTML string for a {@link TextView}.</p> |
||||
* |
||||
* <p>Example usage:</p> |
||||
* |
||||
* <pre> |
||||
* <code> |
||||
* HtmlBuilder html = new HtmlBuilder() |
||||
* .p() |
||||
* .b() |
||||
* .font().color("red").face("sans-serif-condensed").text("Hello").close() |
||||
* .close() |
||||
* .close(); |
||||
* |
||||
* // html.toString():
|
||||
* // <p><b><font color="red" face="sans-serif-condensed">Hello</font></b></p>
|
||||
* |
||||
* yourEditText.setText(html.toSpan()); |
||||
* </code> |
||||
* </pre> |
||||
* |
||||
* <p>HTML Tags Supported by {@link TextView}:</p> |
||||
* |
||||
* <ul> |
||||
* <li><code><a href="..."></code></li> |
||||
* <li><code><b></code></li> |
||||
* <li><code><big></code></li> |
||||
* <li><code><blockquote></code></li> |
||||
* <li><code><br></code></li> |
||||
* <li><code><cite></code></li> |
||||
* <li><code><dfn></code></li> |
||||
* <li><code><div align="..."></code></li> |
||||
* <li><code><em></code></li> |
||||
* <li><code><font color="..." face="..."></code></li> |
||||
* <li><code><h1></code></li> |
||||
* <li><code><h2></code></li> |
||||
* <li><code><h3></code></li> |
||||
* <li><code><h4></code></li> |
||||
* <li><code><h5></code></li> |
||||
* <li><code><h6></code></li> |
||||
* <li><code><i></code></li> |
||||
* <li><code><img src="..."></code></li> |
||||
* <li><code><p></code></li> |
||||
* <li><code><small></code></li> |
||||
* <li><code><strike></code></li> |
||||
* <li><code><strong></code></li> |
||||
* <li><code><sub></code></li> |
||||
* <li><code><sup></code></li> |
||||
* <li><code><tt></code></li> |
||||
* <li><code><u></code></li> |
||||
* <li><code><ul></code> (Android 7.0+)</li> |
||||
* <li><code><li></code> (Android 7.0+)</li> |
||||
* </ul> |
||||
* |
||||
* @see <a href='https://github.com/jaredrummler/HtmlBuilder'>HtmlBuilder</a>
|
||||
*/ |
||||
public class HtmlBuilder { |
||||
|
||||
private final StringBuilder html = new StringBuilder(); |
||||
|
||||
private final LinkedList<String> tags = new LinkedList<>(); |
||||
|
||||
public HtmlBuilder open(String element, String data) { |
||||
tags.add(element); |
||||
html.append('<'); |
||||
html.append(element); |
||||
if (data != null) { |
||||
html.append(' ').append(data); |
||||
} |
||||
html.append('>'); |
||||
return this; |
||||
} |
||||
|
||||
public HtmlBuilder open(String element) { |
||||
return open(element, null); |
||||
} |
||||
|
||||
public HtmlBuilder close(String element) { |
||||
html.append("</").append(element).append('>'); |
||||
for (Iterator<String> iterator = tags.iterator(); iterator.hasNext(); ) { |
||||
if (iterator.next().equals(element)) { |
||||
iterator.remove(); |
||||
break; |
||||
} |
||||
} |
||||
return this; |
||||
} |
||||
|
||||
public HtmlBuilder close() { |
||||
if (tags.isEmpty()) { |
||||
return this; |
||||
} |
||||
html.append("</").append(tags.removeLast()).append('>'); |
||||
return this; |
||||
} |
||||
|
||||
public HtmlBuilder close(char element) { |
||||
return close(String.valueOf(element)); |
||||
} |
||||
|
||||
public HtmlBuilder append(boolean b) { |
||||
html.append(b); |
||||
return this; |
||||
} |
||||
|
||||
public HtmlBuilder append(char c) { |
||||
html.append(c); |
||||
return this; |
||||
} |
||||
|
||||
public HtmlBuilder append(int i) { |
||||
html.append(i); |
||||
return this; |
||||
} |
||||
|
||||
public HtmlBuilder append(long l) { |
||||
html.append(l); |
||||
return this; |
||||
} |
||||
|
||||
public HtmlBuilder append(float f) { |
||||
html.append(f); |
||||
return this; |
||||
} |
||||
|
||||
public HtmlBuilder append(double d) { |
||||
html.append(d); |
||||
return this; |
||||
} |
||||
|
||||
public HtmlBuilder append(Object obj) { |
||||
html.append(obj); |
||||
return this; |
||||
} |
||||
|
||||
public HtmlBuilder append(String str) { |
||||
html.append(str); |
||||
return this; |
||||
} |
||||
|
||||
public HtmlBuilder append(StringBuffer sb) { |
||||
html.append(sb); |
||||
return this; |
||||
} |
||||
|
||||
public HtmlBuilder append(char[] chars) { |
||||
html.append(chars); |
||||
return this; |
||||
} |
||||
|
||||
public HtmlBuilder append(char[] str, int offset, int len) { |
||||
html.append(str, offset, len); |
||||
return this; |
||||
} |
||||
|
||||
public HtmlBuilder append(CharSequence csq) { |
||||
html.append(csq); |
||||
return this; |
||||
} |
||||
|
||||
public HtmlBuilder append(CharSequence csq, int start, int end) { |
||||
html.append(csq, start, end); |
||||
return this; |
||||
} |
||||
|
||||
public HtmlBuilder append(Tag tag) { |
||||
html.append(tag.toString()); |
||||
return this; |
||||
} |
||||
|
||||
public HtmlBuilder a(String href, String text) { |
||||
return append(String.format("<a href=\"%s\">%s</a>", href, text)); |
||||
} |
||||
|
||||
public HtmlBuilder b() { |
||||
return open("b"); |
||||
} |
||||
|
||||
public HtmlBuilder b(String text) { |
||||
html.append("<b>").append(text).append("</b>"); |
||||
return this; |
||||
} |
||||
|
||||
public HtmlBuilder big() { |
||||
return open("big"); |
||||
} |
||||
|
||||
public HtmlBuilder big(String text) { |
||||
html.append("<big>").append(text).append("</big>"); |
||||
return this; |
||||
} |
||||
|
||||
public HtmlBuilder blockquote() { |
||||
return open("blockquote"); |
||||
} |
||||
|
||||
public HtmlBuilder blockquote(String text) { |
||||
html.append("<blockquote>").append(text).append("</blockquote>"); |
||||
return this; |
||||
} |
||||
|
||||
public HtmlBuilder br() { |
||||
html.append("<br>"); |
||||
return this; |
||||
} |
||||
|
||||
public HtmlBuilder cite() { |
||||
return open("cite"); |
||||
} |
||||
|
||||
public HtmlBuilder cite(String text) { |
||||
html.append("<cite>").append(text).append("</cite>"); |
||||
return this; |
||||
} |
||||
|
||||
public HtmlBuilder dfn() { |
||||
return open("dfn"); |
||||
} |
||||
|
||||
public HtmlBuilder dfn(String text) { |
||||
html.append("<dfn>").append(text).append("</dfn>"); |
||||
return this; |
||||
} |
||||
|
||||
public HtmlBuilder div() { |
||||
return open("div"); |
||||
} |
||||
|
||||
public HtmlBuilder div(String align) { |
||||
html.append(String.format("<div align=\"%s\">", align)); |
||||
tags.add("div"); |
||||
return this; |
||||
} |
||||
|
||||
public HtmlBuilder em() { |
||||
return open("em"); |
||||
} |
||||
|
||||
public HtmlBuilder em(String text) { |
||||
html.append("<em>").append(text).append("</em>"); |
||||
return this; |
||||
} |
||||
|
||||
public Font font() { |
||||
return new Font(this); |
||||
} |
||||
|
||||
public HtmlBuilder font(int color, String text) { |
||||
return font().color(color).text(text).close(); |
||||
} |
||||
|
||||
public HtmlBuilder font(String face, String text) { |
||||
return font().face(face).text(text).close(); |
||||
} |
||||
|
||||
public HtmlBuilder h1() { |
||||
return open("h1"); |
||||
} |
||||
|
||||
public HtmlBuilder h1(String text) { |
||||
html.append("<h1>").append(text).append("</h1>"); |
||||
return this; |
||||
} |
||||
|
||||
public HtmlBuilder h2() { |
||||
return open("h2"); |
||||
} |
||||
|
||||
public HtmlBuilder h2(String text) { |
||||
html.append("<h2>").append(text).append("</h2>"); |
||||
return this; |
||||
} |
||||
|
||||
public HtmlBuilder h3() { |
||||
return open("h3"); |
||||
} |
||||
|
||||
public HtmlBuilder h3(String text) { |
||||
html.append("<h3>").append(text).append("</h3>"); |
||||
return this; |
||||
} |
||||
|
||||
public HtmlBuilder h4() { |
||||
return open("h4"); |
||||
} |
||||
|
||||
public HtmlBuilder h4(String text) { |
||||
html.append("<h4>").append(text).append("</h4>"); |
||||
return this; |
||||
} |
||||
|
||||
public HtmlBuilder h5() { |
||||
return open("h5"); |
||||
} |
||||
|
||||
public HtmlBuilder h5(String text) { |
||||
html.append("<h5>").append(text).append("</h5>"); |
||||
return this; |
||||
} |
||||
|
||||
public HtmlBuilder h6() { |
||||
return open("h6"); |
||||
} |
||||
|
||||
public HtmlBuilder h6(String text) { |
||||
html.append("<h6>").append(text).append("</h6>"); |
||||
return this; |
||||
} |
||||
|
||||
public HtmlBuilder i() { |
||||
return open("i"); |
||||
} |
||||
|
||||
public HtmlBuilder i(String text) { |
||||
html.append("<i>").append(text).append("</i>"); |
||||
return this; |
||||
} |
||||
|
||||
public Img img() { |
||||
return new Img(this); |
||||
} |
||||
|
||||
public HtmlBuilder img(String src) { |
||||
return img().src(src).close(); |
||||
} |
||||
|
||||
public HtmlBuilder p() { |
||||
return open("p"); |
||||
} |
||||
|
||||
public HtmlBuilder p(String text) { |
||||
html.append("<p>").append(text).append("</p>"); |
||||
return this; |
||||
} |
||||
|
||||
public HtmlBuilder small() { |
||||
return open("small"); |
||||
} |
||||
|
||||
public HtmlBuilder small(String text) { |
||||
html.append("<small>").append(text).append("</small>"); |
||||
return this; |
||||
} |
||||
|
||||
public HtmlBuilder strike() { |
||||
return open("strike"); |
||||
} |
||||
|
||||
public HtmlBuilder strike(String text) { |
||||
html.append("<strike>").append(text).append("</strike>"); |
||||
return this; |
||||
} |
||||
|
||||
public HtmlBuilder strong() { |
||||
return open("strong"); |
||||
} |
||||
|
||||
public HtmlBuilder strong(String text) { |
||||
html.append("<strong>").append(text).append("</strong>"); |
||||
return this; |
||||
} |
||||
|
||||
public HtmlBuilder sub() { |
||||
return open("sub"); |
||||
} |
||||
|
||||
public HtmlBuilder sub(String text) { |
||||
html.append("<sub>").append(text).append("</sub>"); |
||||
return this; |
||||
} |
||||
|
||||
public HtmlBuilder sup() { |
||||
return open("sup"); |
||||
} |
||||
|
||||
public HtmlBuilder sup(String text) { |
||||
html.append("<sup>").append(text).append("</sup>"); |
||||
return this; |
||||
} |
||||
|
||||
public HtmlBuilder tt() { |
||||
return open("tt"); |
||||
} |
||||
|
||||
public HtmlBuilder tt(String text) { |
||||
html.append("<tt>").append(text).append("</tt>"); |
||||
return this; |
||||
} |
||||
|
||||
public HtmlBuilder u() { |
||||
return open("u"); |
||||
} |
||||
|
||||
public HtmlBuilder u(String text) { |
||||
html.append("<u>").append(text).append("</u>"); |
||||
return this; |
||||
} |
||||
|
||||
@RequiresApi(Build.VERSION_CODES.N) |
||||
public HtmlBuilder ul() { |
||||
return open("ul"); |
||||
} |
||||
|
||||
@RequiresApi(Build.VERSION_CODES.N) |
||||
public HtmlBuilder li() { |
||||
return open("li"); |
||||
} |
||||
|
||||
@RequiresApi(Build.VERSION_CODES.N) |
||||
public HtmlBuilder li(String text) { |
||||
html.append("<li>").append(text).append("</li>"); |
||||
return this; |
||||
} |
||||
|
||||
@RequiresApi(Build.VERSION_CODES.N) |
||||
public Spanned build(int flags) { |
||||
return Html.fromHtml(html.toString(), flags); |
||||
} |
||||
|
||||
public Spanned build() { |
||||
//noinspection deprecation
|
||||
return Html.fromHtml(html.toString()); |
||||
} |
||||
|
||||
@Override |
||||
public String toString() { |
||||
return html.toString(); |
||||
} |
||||
|
||||
public static class Tag { |
||||
|
||||
final HtmlBuilder builder; |
||||
final String element; |
||||
String separator = ""; |
||||
|
||||
public Tag(HtmlBuilder builder, String element) { |
||||
this.builder = builder; |
||||
this.element = element; |
||||
open(); |
||||
} |
||||
|
||||
protected void open() { |
||||
builder.append('<').append(element).append(' '); |
||||
} |
||||
|
||||
public HtmlBuilder close() { |
||||
return builder.append("</").append(element).append('>'); |
||||
} |
||||
|
||||
@Override |
||||
public String toString() { |
||||
return builder.toString(); |
||||
} |
||||
|
||||
} |
||||
|
||||
public static class Font extends Tag { |
||||
|
||||
public Font() { |
||||
this(new HtmlBuilder()); |
||||
} |
||||
|
||||
public Font(HtmlBuilder builder) { |
||||
super(builder, "font"); |
||||
} |
||||
|
||||
public Font size(int size) { |
||||
builder.append(separator).append("size=\"").append(size).append('\"'); |
||||
separator = " "; |
||||
return this; |
||||
} |
||||
|
||||
public Font size(String size) { |
||||
builder.append(separator).append("size=\"").append(size).append('\"'); |
||||
separator = " "; |
||||
return this; |
||||
} |
||||
|
||||
public Font color(int color) { |
||||
return color(String.format("#%06X", (0xFFFFFF & color))); |
||||
} |
||||
|
||||
public Font color(String color) { |
||||
builder.append(separator).append("color=\"").append(color).append('\"'); |
||||
separator = " "; |
||||
return this; |
||||
} |
||||
|
||||
public Font face(String face) { |
||||
builder.append(separator).append("face=\"").append(face).append('\"'); |
||||
separator = " "; |
||||
return this; |
||||
} |
||||
|
||||
public Font text(String text) { |
||||
builder.append('>').append(text); |
||||
return this; |
||||
} |
||||
|
||||
} |
||||
|
||||
public static class Img extends Tag { |
||||
|
||||
public Img() { |
||||
this(new HtmlBuilder()); |
||||
} |
||||
|
||||
public Img(HtmlBuilder builder) { |
||||
super(builder, "img"); |
||||
} |
||||
|
||||
public Img src(String src) { |
||||
builder.append(separator).append("src=\"").append(src).append('\"'); |
||||
separator = " "; |
||||
return this; |
||||
} |
||||
|
||||
public Img alt(String alt) { |
||||
builder.append(separator).append("alt=\"").append(alt).append('\"'); |
||||
separator = " "; |
||||
return this; |
||||
} |
||||
|
||||
public Img height(String height) { |
||||
builder.append(separator).append("height=\"").append(height).append('\"'); |
||||
separator = " "; |
||||
return this; |
||||
} |
||||
|
||||
public Img height(int height) { |
||||
builder.append(separator).append("height=\"").append(height).append('\"'); |
||||
separator = " "; |
||||
return this; |
||||
} |
||||
|
||||
public Img width(String width) { |
||||
builder.append(separator).append("width=\"").append(width).append('\"'); |
||||
separator = " "; |
||||
return this; |
||||
} |
||||
|
||||
public Img width(int width) { |
||||
builder.append(separator).append("width=\"").append(width).append('\"'); |
||||
separator = " "; |
||||
return this; |
||||
} |
||||
|
||||
@Override |
||||
public HtmlBuilder close() { |
||||
return builder.append('>'); |
||||
} |
||||
|
||||
} |
||||
|
||||
} |
@ -0,0 +1,7 @@ |
||||
<manifest package="com.android.base"> |
||||
|
||||
<application> |
||||
|
||||
</application> |
||||
|
||||
</manifest> |
@ -0,0 +1,79 @@ |
||||
package com.android.base.adapter; |
||||
|
||||
import java.util.List; |
||||
|
||||
import androidx.annotation.Nullable; |
||||
|
||||
/** |
||||
* <pre> |
||||
* 注意数据源引用的替换,只有setDataSource方法会把elements替换掉之前的数据源引用,其他方法都是基于现有数据集合做删除与添加操作。 |
||||
* </pre> |
||||
* |
||||
* @author Ztiany |
||||
* Date : 2016-09-12 11:33 |
||||
* Email: 1169654504@qq.com |
||||
*/ |
||||
public interface DataManager<T> { |
||||
|
||||
void add(T element); |
||||
|
||||
void addAt(int location, T element); |
||||
|
||||
void addItems(List<T> elements); |
||||
|
||||
/** |
||||
* 添加元素前会使用equals方法进行比较。 |
||||
* |
||||
* @param elements 元素 |
||||
*/ |
||||
void addItemsChecked(List<T> elements); |
||||
|
||||
void addItemsAt(int location, List<T> elements); |
||||
|
||||
void replace(T oldElement, T newElement); |
||||
|
||||
void replaceAt(int index, T element); |
||||
|
||||
/** |
||||
* 清除之前集合中的数据,然后把elements添加到之前的集合中,不会使用elements作为数据源 |
||||
* |
||||
* @param elements 元素 |
||||
*/ |
||||
void replaceAll(List<T> elements); |
||||
|
||||
/** |
||||
* 此方法会使用 newDataSource 替换掉之前的数据源,而不对之前的数据源做任何操作。 |
||||
* |
||||
* @param newDataSource 新的数据集 |
||||
* @param notifyDataSetChanged 是否调用adapter的notifyDataSetChanged方法 |
||||
*/ |
||||
void setDataSource(List<T> newDataSource, boolean notifyDataSetChanged); |
||||
|
||||
void remove(T element); |
||||
|
||||
void removeAt(int index); |
||||
|
||||
void removeItems(List<T> elements); |
||||
|
||||
void removeItems(List<T> elements, boolean isSuccessive); |
||||
|
||||
@Nullable |
||||
T getItem(int position); |
||||
|
||||
List<T> getItems(); |
||||
|
||||
int getDataSize(); |
||||
|
||||
boolean contains(T element); |
||||
|
||||
boolean isEmpty(); |
||||
|
||||
void clear(); |
||||
|
||||
/** |
||||
* @param t element |
||||
* @return -1 if not contains this element |
||||
*/ |
||||
int indexItem(T t); |
||||
|
||||
} |
@ -0,0 +1,73 @@ |
||||
package com.android.base.adapter; |
||||
|
||||
import android.util.SparseArray; |
||||
import android.view.View; |
||||
import android.widget.TextView; |
||||
|
||||
import androidx.annotation.IdRes; |
||||
import androidx.annotation.NonNull; |
||||
import androidx.annotation.StringRes; |
||||
|
||||
|
||||
/** |
||||
* @author Ztiany |
||||
* Email :1169654504@qq.com |
||||
* Date :015-12-29 20:47 |
||||
*/ |
||||
public class ItemHelper { |
||||
|
||||
private View mItemView; |
||||
private SparseArray<View> views; |
||||
|
||||
public ItemHelper(View itemView) { |
||||
mItemView = itemView; |
||||
views = new SparseArray<>(); |
||||
} |
||||
|
||||
@SuppressWarnings("unchecked,WeakerAccess") |
||||
public <T extends View> T getView(int viewId) { |
||||
View view = views.get(viewId); |
||||
if (view == null) { |
||||
view = mItemView.findViewById(viewId); |
||||
views.put(viewId, view); |
||||
} |
||||
return (T) view; |
||||
} |
||||
|
||||
public ItemHelper setText(CharSequence str, @IdRes int viewId) { |
||||
((TextView) getView(viewId)).setText(str == null ? "" : str); |
||||
return this; |
||||
} |
||||
|
||||
public ItemHelper setText(@StringRes int strId, @IdRes int viewId) { |
||||
((TextView) getView(viewId)).setText(strId); |
||||
return this; |
||||
} |
||||
|
||||
public ItemHelper setTag(@NonNull Object object, @IdRes int tagId, @IdRes int viewID) { |
||||
View view = getView(viewID); |
||||
view.setTag(tagId, object); |
||||
return this; |
||||
} |
||||
|
||||
public ItemHelper setTag(@NonNull Object object, @IdRes int viewID) { |
||||
View view = getView(viewID); |
||||
view.setTag(object); |
||||
return this; |
||||
} |
||||
|
||||
public <T> T getTag(@IdRes int tagId, @IdRes int viewID) { |
||||
View view = getView(viewID); |
||||
@SuppressWarnings("unchecked") |
||||
T tag = (T) view.getTag(tagId); |
||||
return tag; |
||||
} |
||||
|
||||
public <T> T getTag(@IdRes int viewID) { |
||||
View view = getView(viewID); |
||||
@SuppressWarnings("unchecked") |
||||
T tag = (T) view.getTag(); |
||||
return tag; |
||||
} |
||||
|
||||
} |
@ -0,0 +1,185 @@ |
||||
package com.android.base.adapter.list; |
||||
|
||||
import android.content.Context; |
||||
import android.view.LayoutInflater; |
||||
import android.view.View; |
||||
import android.view.ViewGroup; |
||||
import android.widget.BaseAdapter; |
||||
|
||||
import com.android.base.R; |
||||
import com.android.base.adapter.DataManager; |
||||
|
||||
import java.util.List; |
||||
|
||||
import androidx.annotation.NonNull; |
||||
import androidx.annotation.Nullable; |
||||
|
||||
/** |
||||
* absListView通用的Adapter,注意:只有setDataSource才能替换原有数据源的引用。 |
||||
* |
||||
* @param <T> 数据模型 |
||||
* @author Ztiany |
||||
*/ |
||||
@SuppressWarnings("unused") |
||||
public abstract class BaseListAdapter<T, VH extends ViewHolder> extends BaseAdapter implements DataManager<T> { |
||||
|
||||
private final Context mContext; |
||||
private final static int ITEM_ID = R.id.base_item_tag_view_id; |
||||
private DataManager<T> mDataManager; |
||||
private final LayoutInflater mLayoutInflater; |
||||
|
||||
public BaseListAdapter(@NonNull Context context) { |
||||
this(context, null); |
||||
} |
||||
|
||||
@SuppressWarnings("all") |
||||
public BaseListAdapter(Context context, List<T> data) { |
||||
this.mContext = context; |
||||
mLayoutInflater = LayoutInflater.from(context); |
||||
mDataManager = new ListDataManagerImpl<>(data, this); |
||||
} |
||||
|
||||
public Context getContext() { |
||||
return mContext; |
||||
} |
||||
|
||||
@Override |
||||
public int getCount() { |
||||
return getDataSize(); |
||||
} |
||||
|
||||
@Override |
||||
public long getItemId(int position) { |
||||
return position; |
||||
} |
||||
|
||||
@Override |
||||
@SuppressWarnings("unchecked") |
||||
public View getView(int position, View convertView, ViewGroup parent) { |
||||
VH viewHolder; |
||||
int type = getItemViewType(position); |
||||
if (convertView == null) { |
||||
viewHolder = onCreateViewHolder(mLayoutInflater, parent, type); |
||||
viewHolder.mItemView.setTag(ITEM_ID, viewHolder); |
||||
} else { |
||||
viewHolder = (VH) convertView.getTag(ITEM_ID); |
||||
} |
||||
viewHolder.setPosition(position); |
||||
viewHolder.setType(type); |
||||
T item = getItem(position); |
||||
onBindData(viewHolder, item); |
||||
return viewHolder.mItemView; |
||||
} |
||||
|
||||
protected abstract void onBindData(@NonNull VH viewHolder, T item); |
||||
|
||||
@NonNull |
||||
protected abstract VH onCreateViewHolder(@NonNull LayoutInflater layoutInflater, @NonNull ViewGroup parent, int type); |
||||
|
||||
@Override |
||||
public int getItemViewType(int position) { |
||||
return super.getItemViewType(position); |
||||
} |
||||
|
||||
@Override |
||||
public int getViewTypeCount() { |
||||
return super.getViewTypeCount(); |
||||
} |
||||
|
||||
@Override |
||||
public void add(T elem) { |
||||
mDataManager.add(elem); |
||||
} |
||||
|
||||
@Override |
||||
public void addAt(int location, T elem) { |
||||
mDataManager.addAt(location, elem); |
||||
} |
||||
|
||||
@Override |
||||
public void addItems(List<T> elements) { |
||||
mDataManager.addItems(elements); |
||||
} |
||||
|
||||
@Override |
||||
public void addItemsChecked(List<T> elements) { |
||||
mDataManager.addItemsChecked(elements); |
||||
} |
||||
|
||||
@Override |
||||
public void addItemsAt(int location, List<T> elements) { |
||||
mDataManager.addItemsAt(location, elements); |
||||
} |
||||
|
||||
@Override |
||||
public void replace(T oldElem, T newElem) { |
||||
mDataManager.replace(oldElem, newElem); |
||||
} |
||||
|
||||
@Override |
||||
public void replaceAt(int index, T elem) { |
||||
mDataManager.replaceAt(index, elem); |
||||
} |
||||
|
||||
@Override |
||||
public void replaceAll(List<T> elements) { |
||||
mDataManager.replaceAll(elements); |
||||
} |
||||
|
||||
@Override |
||||
public void remove(T elem) { |
||||
mDataManager.remove(elem); |
||||
} |
||||
|
||||
@Override |
||||
public void removeItems(List<T> elements) { |
||||
mDataManager.removeItems(elements); |
||||
} |
||||
|
||||
@Override |
||||
public void removeItems(List<T> elements, boolean isSuccessive) { |
||||
mDataManager.removeItems(elements, isSuccessive); |
||||
} |
||||
|
||||
@Override |
||||
public void removeAt(int index) { |
||||
mDataManager.removeAt(index); |
||||
} |
||||
|
||||
@Nullable |
||||
@Override |
||||
public T getItem(int position) { |
||||
return mDataManager.getItem(position); |
||||
} |
||||
|
||||
@Override |
||||
public final int getDataSize() { |
||||
return mDataManager.getDataSize(); |
||||
} |
||||
|
||||
@Override |
||||
public boolean contains(T elem) { |
||||
return mDataManager.contains(elem); |
||||
} |
||||
|
||||
@Override |
||||
public void setDataSource(List<T> elements, boolean notifyDataSetChanged) { |
||||
mDataManager.setDataSource(elements, notifyDataSetChanged); |
||||
} |
||||
|
||||
@Override |
||||
public int indexItem(T t) { |
||||
return mDataManager.indexItem(t); |
||||
} |
||||
|
||||
@Override |
||||
public void clear() { |
||||
mDataManager.clear(); |
||||
} |
||||
|
||||
@Override |
||||
public List<T> getItems() { |
||||
return mDataManager.getItems(); |
||||
} |
||||
|
||||
} |
@ -0,0 +1,191 @@ |
||||
package com.android.base.adapter.list; |
||||
|
||||
import android.widget.BaseAdapter; |
||||
|
||||
import com.android.base.adapter.DataManager; |
||||
import com.android.base.utils.common.Checker; |
||||
|
||||
import java.util.ArrayList; |
||||
import java.util.List; |
||||
|
||||
import androidx.annotation.Nullable; |
||||
|
||||
|
||||
final class ListDataManagerImpl<T> implements DataManager<T> { |
||||
|
||||
private List<T> mData; |
||||
private BaseAdapter mBaseAdapter; |
||||
|
||||
ListDataManagerImpl(List<T> tList, BaseAdapter adapter) { |
||||
mData = tList; |
||||
mBaseAdapter = adapter; |
||||
} |
||||
|
||||
private void checkData() { |
||||
if (mData == null) { |
||||
mData = new ArrayList<>(); |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
public void add(T elem) { |
||||
checkData(); |
||||
mData.add(elem); |
||||
notifyDataSetChanged(); |
||||
} |
||||
|
||||
@Override |
||||
public void addAt(int location, T elem) { |
||||
checkData(); |
||||
mData.add(location, elem); |
||||
notifyDataSetChanged(); |
||||
} |
||||
|
||||
@Override |
||||
public void addItems(List<T> elements) { |
||||
checkData(); |
||||
mData.addAll(elements); |
||||
notifyDataSetChanged(); |
||||
} |
||||
|
||||
@Override |
||||
public void addItemsChecked(List<T> elements) { |
||||
if (Checker.isEmpty(elements)) { |
||||
return; |
||||
} |
||||
if (mData == null) { |
||||
addItems(elements); |
||||
return; |
||||
} |
||||
for (T element : elements) { |
||||
if (element == null) { |
||||
continue; |
||||
} |
||||
mData.remove(element); |
||||
} |
||||
mData.addAll(elements); |
||||
notifyDataSetChanged(); |
||||
} |
||||
|
||||
@Override |
||||
public void addItemsAt(int location, List<T> elements) { |
||||
checkData(); |
||||
mData.addAll(location, elements); |
||||
notifyDataSetChanged(); |
||||
} |
||||
|
||||
@Override |
||||
public void replace(T oldElem, T newElem) { |
||||
if (mData != null && mData.contains(oldElem)) { |
||||
replaceAt(mData.indexOf(oldElem), newElem); |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
public void replaceAt(int index, T elem) { |
||||
if (mData != null && mData.size() > index) { |
||||
mData.set(index, elem); |
||||
notifyDataSetChanged(); |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
public void replaceAll(List<T> elements) { |
||||
if (mData == null) { |
||||
mData = new ArrayList<>(); |
||||
} |
||||
mData.clear(); |
||||
if (elements != null) { |
||||
mData.addAll(elements); |
||||
} |
||||
notifyDataSetChanged(); |
||||
} |
||||
|
||||
@Override |
||||
public void setDataSource(List<T> newDataSource, boolean notifyDataSetChanged) { |
||||
mData = newDataSource; |
||||
if (notifyDataSetChanged) { |
||||
notifyDataSetChanged(); |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
public void remove(T elem) { |
||||
if (mData != null && mData.contains(elem)) { |
||||
mData.remove(elem); |
||||
notifyDataSetChanged(); |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
public void removeItems(List<T> elements) { |
||||
if (mData != null && mData.containsAll(elements)) { |
||||
mData.removeAll(elements); |
||||
notifyDataSetChanged(); |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
public void removeItems(List<T> elements, boolean isSuccessive) { |
||||
removeItems(elements); |
||||
} |
||||
|
||||
@Override |
||||
public void removeAt(int index) { |
||||
if (mData != null && mData.size() > index) { |
||||
mData.remove(index); |
||||
notifyDataSetChanged(); |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
@Nullable |
||||
public T getItem(int position) { |
||||
if (mData != null && mData.size() > position) { |
||||
return mData.get(position); |
||||
} |
||||
return null; |
||||
} |
||||
|
||||
@Override |
||||
public final int getDataSize() { |
||||
return mData == null ? 0 : mData.size(); |
||||
} |
||||
|
||||
@Override |
||||
public boolean contains(T elem) { |
||||
return mData != null && mData.contains(elem); |
||||
} |
||||
|
||||
@Override |
||||
public boolean isEmpty() { |
||||
return mData == null || mData.size() == 0; |
||||
} |
||||
|
||||
@Override |
||||
public void clear() { |
||||
if (mData != null && !mData.isEmpty()) { |
||||
mData.clear(); |
||||
notifyDataSetChanged(); |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
public int indexItem(T t) { |
||||
List<T> items = getItems(); |
||||
if (items == null) { |
||||
return -1; |
||||
} |
||||
return items.indexOf(t); |
||||
} |
||||
|
||||
@Override |
||||
public List<T> getItems() { |
||||
return mData; |
||||
} |
||||
|
||||
private void notifyDataSetChanged() { |
||||
mBaseAdapter.notifyDataSetChanged(); |
||||
} |
||||
|
||||
} |
@ -0,0 +1,21 @@ |
||||
package com.android.base.adapter.list; |
||||
|
||||
import android.view.View; |
||||
|
||||
import com.android.base.adapter.ItemHelper; |
||||
|
||||
@SuppressWarnings("unused") |
||||
public class SmartViewHolder extends ViewHolder { |
||||
|
||||
private final ItemHelper mHelper; |
||||
|
||||
public SmartViewHolder(View itemView) { |
||||
super(itemView); |
||||
mHelper = new ItemHelper(itemView); |
||||
} |
||||
|
||||
public ItemHelper helper() { |
||||
return mHelper; |
||||
} |
||||
|
||||
} |
@ -0,0 +1,35 @@ |
||||
package com.android.base.adapter.list; |
||||
|
||||
import android.view.View; |
||||
|
||||
public class ViewHolder { |
||||
|
||||
protected final View mItemView; |
||||
private int mPosition; |
||||
private int mType; |
||||
|
||||
public ViewHolder(View itemView) { |
||||
mItemView = itemView; |
||||
} |
||||
|
||||
public int getPosition() { |
||||
return mPosition; |
||||
} |
||||
|
||||
void setPosition(int position) { |
||||
mPosition = position; |
||||
} |
||||
|
||||
public int getType() { |
||||
return mType; |
||||
} |
||||
|
||||
void setType(int type) { |
||||
mType = type; |
||||
} |
||||
|
||||
public View getItemView() { |
||||
return mItemView; |
||||
} |
||||
|
||||
} |
@ -0,0 +1,154 @@ |
||||
package com.android.base.adapter.pager; |
||||
|
||||
import android.os.Build; |
||||
import android.util.SparseArray; |
||||
import android.view.View; |
||||
|
||||
/** |
||||
* The RecycleBin facilitates reuse of views across layouts. The RecycleBin has two levels of |
||||
* storage: ActiveViews and ScrapViews. ActiveViews are those views which were onscreen at the |
||||
* start of a layout. By construction, they are displaying current information. At the end of |
||||
* layout, all views in ActiveViews are demoted to ScrapViews. ScrapViews are old views that |
||||
* could potentially be used by the adapter to avoid allocating views unnecessarily. |
||||
* <p> |
||||
* This class was taken from Android's implementation of {@link android.widget.AbsListView} which |
||||
* is copyrighted 2006 The Android Open Source Project. |
||||
*/ |
||||
public class RecycleBin { |
||||
|
||||
/** |
||||
* Views that were on screen at the start of layout. This array is populated at the start of |
||||
* layout, and at the end of layout all view in activeViews are moved to scrapViews. |
||||
* Views in activeViews represent a contiguous range of Views, with position of the first |
||||
* view store in mFirstActivePosition. |
||||
*/ |
||||
private View[] activeViews = new View[0]; |
||||
private int[] activeViewTypes = new int[0]; |
||||
|
||||
/** Unsorted views that can be used by the adapter as a convert view. */ |
||||
private SparseArray<View>[] scrapViews; |
||||
|
||||
private int viewTypeCount; |
||||
|
||||
private SparseArray<View> currentScrapViews; |
||||
|
||||
public void setViewTypeCount(int viewTypeCount) { |
||||
if (viewTypeCount < 1) { |
||||
throw new IllegalArgumentException("Can't have a viewTypeCount < 1"); |
||||
} |
||||
//noinspection unchecked
|
||||
SparseArray<View>[] scrapViews = new SparseArray[viewTypeCount]; |
||||
for (int i = 0; i < viewTypeCount; i++) { |
||||
scrapViews[i] = new SparseArray<View>(); |
||||
} |
||||
this.viewTypeCount = viewTypeCount; |
||||
currentScrapViews = scrapViews[0]; |
||||
this.scrapViews = scrapViews; |
||||
} |
||||
|
||||
protected boolean shouldRecycleViewType(int viewType) { |
||||
return viewType >= 0; |
||||
} |
||||
|
||||
/** @return A view from the ScrapViews collection. These are unordered. */ |
||||
View getScrapView(int position, int viewType) { |
||||
if (viewTypeCount == 1) { |
||||
return retrieveFromScrap(currentScrapViews, position); |
||||
} else if (viewType >= 0 && viewType < scrapViews.length) { |
||||
return retrieveFromScrap(scrapViews[viewType], position); |
||||
} |
||||
return null; |
||||
} |
||||
|
||||
/** |
||||
* Put a view into the ScrapViews list. These views are unordered. |
||||
* |
||||
* @param scrap The view to add |
||||
*/ |
||||
void addScrapView(View scrap, int position, int viewType) { |
||||
if (viewTypeCount == 1) { |
||||
currentScrapViews.put(position, scrap); |
||||
} else { |
||||
scrapViews[viewType].put(position, scrap); |
||||
} |
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) { |
||||
scrap.setAccessibilityDelegate(null); |
||||
} |
||||
} |
||||
|
||||
/** Move all views remaining in activeViews to scrapViews. */ |
||||
void scrapActiveViews() { |
||||
final View[] activeViews = this.activeViews; |
||||
final int[] activeViewTypes = this.activeViewTypes; |
||||
final boolean multipleScraps = viewTypeCount > 1; |
||||
|
||||
SparseArray<View> scrapViews = currentScrapViews; |
||||
final int count = activeViews.length; |
||||
for (int i = count - 1; i >= 0; i--) { |
||||
final View victim = activeViews[i]; |
||||
if (victim != null) { |
||||
int whichScrap = activeViewTypes[i]; |
||||
|
||||
activeViews[i] = null; |
||||
activeViewTypes[i] = -1; |
||||
|
||||
if (!shouldRecycleViewType(whichScrap)) { |
||||
continue; |
||||
} |
||||
|
||||
if (multipleScraps) { |
||||
scrapViews = this.scrapViews[whichScrap]; |
||||
} |
||||
scrapViews.put(i, victim); |
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) { |
||||
victim.setAccessibilityDelegate(null); |
||||
} |
||||
} |
||||
} |
||||
|
||||
pruneScrapViews(); |
||||
} |
||||
|
||||
/** |
||||
* Makes sure that the size of scrapViews does not exceed the size of activeViews. |
||||
* (This can happen if an adapter does not recycle its views). |
||||
*/ |
||||
private void pruneScrapViews() { |
||||
final int maxViews = activeViews.length; |
||||
final int viewTypeCount = this.viewTypeCount; |
||||
final SparseArray<View>[] scrapViews = this.scrapViews; |
||||
for (int i = 0; i < viewTypeCount; ++i) { |
||||
final SparseArray<View> scrapPile = scrapViews[i]; |
||||
int size = scrapPile.size(); |
||||
final int extras = size - maxViews; |
||||
size--; |
||||
for (int j = 0; j < extras; j++) { |
||||
scrapPile.remove(scrapPile.keyAt(size--)); |
||||
} |
||||
} |
||||
} |
||||
|
||||
static View retrieveFromScrap(SparseArray<View> scrapViews, int position) { |
||||
int size = scrapViews.size(); |
||||
if (size > 0) { |
||||
// See if we still have a view for this position.
|
||||
for (int i = 0; i < size; i++) { |
||||
int fromPosition = scrapViews.keyAt(i); |
||||
View view = scrapViews.get(fromPosition); |
||||
if (fromPosition == position) { |
||||
scrapViews.remove(fromPosition); |
||||
return view; |
||||
} |
||||
} |
||||
int index = size - 1; |
||||
View r = scrapViews.valueAt(index); |
||||
scrapViews.remove(scrapViews.keyAt(index)); |
||||
return r; |
||||
} else { |
||||
return null; |
||||
} |
||||
} |
||||
|
||||
} |
@ -0,0 +1,118 @@ |
||||
package com.android.base.adapter.pager; |
||||
|
||||
import android.view.View; |
||||
import android.view.ViewGroup; |
||||
import android.widget.AdapterView; |
||||
|
||||
import org.jetbrains.annotations.NotNull; |
||||
|
||||
import androidx.viewpager.widget.PagerAdapter; |
||||
|
||||
/** |
||||
* A {@link PagerAdapter} which behaves like an {@link android.widget.Adapter} with view types and view recycling. |
||||
* |
||||
* @see <a href='https://github.com/JakeWharton/salvage'>JakeWharton/salvage</a>
|
||||
*/ |
||||
public abstract class RecyclingPagerAdapter extends PagerAdapter { |
||||
|
||||
static final int IGNORE_ITEM_VIEW_TYPE = AdapterView.ITEM_VIEW_TYPE_IGNORE; |
||||
|
||||
private final RecycleBin recycleBin; |
||||
|
||||
public RecyclingPagerAdapter() { |
||||
this(new RecycleBin()); |
||||
} |
||||
|
||||
RecyclingPagerAdapter(RecycleBin recycleBin) { |
||||
this.recycleBin = recycleBin; |
||||
recycleBin.setViewTypeCount(getViewTypeCount()); |
||||
} |
||||
|
||||
@Override |
||||
public void notifyDataSetChanged() { |
||||
recycleBin.scrapActiveViews(); |
||||
super.notifyDataSetChanged(); |
||||
} |
||||
|
||||
@Override |
||||
public final Object instantiateItem(@NotNull ViewGroup container, int position) { |
||||
int viewType = getItemViewType(position); |
||||
View view = null; |
||||
if (viewType != IGNORE_ITEM_VIEW_TYPE) { |
||||
view = recycleBin.getScrapView(position, viewType); |
||||
} |
||||
view = getView(position, view, container); |
||||
container.addView(view); |
||||
return view; |
||||
} |
||||
|
||||
@Override |
||||
public final void destroyItem(ViewGroup container, int position, @NotNull Object object) { |
||||
View view = (View) object; |
||||
container.removeView(view); |
||||
int viewType = getItemViewType(position); |
||||
if (viewType != IGNORE_ITEM_VIEW_TYPE) { |
||||
recycleBin.addScrapView(view, position, viewType); |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
public final boolean isViewFromObject(@NotNull View view, @NotNull Object object) { |
||||
return view == object; |
||||
} |
||||
|
||||
/** |
||||
* <p> |
||||
* Returns the number of types of Views that will be created by |
||||
* {@link #getView}. Each type represents a set of views that can be |
||||
* converted in {@link #getView}. If the adapter always returns the same |
||||
* type of View for all items, this method should return 1. |
||||
* </p> |
||||
* <p> |
||||
* This method will only be called when when the adapter is set on the |
||||
* the {@link AdapterView}. |
||||
* </p> |
||||
* |
||||
* @return The number of types of Views that will be created by this adapter |
||||
*/ |
||||
public int getViewTypeCount() { |
||||
return 1; |
||||
} |
||||
|
||||
/** |
||||
* Get the type of View that will be created by {@link #getView} for the specified item. |
||||
* |
||||
* @param position The position of the item within the adapter's data set whose view type we |
||||
* want. |
||||
* @return An integer representing the type of View. Two views should share the same type if one |
||||
* can be converted to the other in {@link #getView}. Note: Integers must be in the |
||||
* range 0 to {@link #getViewTypeCount} - 1. {@link #IGNORE_ITEM_VIEW_TYPE} can |
||||
* also be returned. |
||||
* @see #IGNORE_ITEM_VIEW_TYPE |
||||
*/ |
||||
@SuppressWarnings("UnusedParameters") // Argument potentially used by subclasses.
|
||||
public int getItemViewType(int position) { |
||||
return 0; |
||||
} |
||||
|
||||
/** |
||||
* Get a View that displays the data at the specified position in the data set. You can either |
||||
* create a View manually or inflate it from an XML layout file. When the View is inflated, the |
||||
* parent View (GridView, ListView...) will apply default layout parameters unless you use |
||||
* {@link android.view.LayoutInflater#inflate(int, ViewGroup, boolean)} |
||||
* to specify a root view and to prevent attachment to the root. |
||||
* |
||||
* @param position The position of the item within the adapter's data set of the item whose view |
||||
* we want. |
||||
* @param convertView The old view to reuse, if possible. Note: You should check that this view |
||||
* is non-null and of an appropriate type before using. If it is not possible to convert |
||||
* this view to display the correct data, this method can create a new view. |
||||
* Heterogeneous lists can specify their number of view types, so that this View is |
||||
* always of the right type (see {@link #getViewTypeCount()} and |
||||
* {@link #getItemViewType(int)}). |
||||
* @param container The parent that this view will eventually be attached to |
||||
* @return A View corresponding to the data at the specified position. |
||||
*/ |
||||
public abstract View getView(int position, View convertView, ViewGroup container); |
||||
|
||||
} |
@ -0,0 +1,33 @@ |
||||
package com.android.base.adapter.pager |
||||
|
||||
import android.view.LayoutInflater |
||||
import android.view.View |
||||
import android.view.ViewGroup |
||||
import kotlinx.android.extensions.LayoutContainer |
||||
|
||||
/** |
||||
* 如果使用可缩放的 View 作为 pager,可能不适合使用此Adapter |
||||
* |
||||
* @param <T> 数据 |
||||
* @param <VH> View Holder类型 |
||||
</VH></T> */ |
||||
abstract class SimpleViewPagerAdapter<T>(data: List<T>) : ViewPagerAdapter<T, KtViewHolder>(data) { |
||||
|
||||
override fun createViewHolder(container: ViewGroup): KtViewHolder { |
||||
val layout = provideLayout(container) |
||||
val itemView = if (layout is Int) { |
||||
LayoutInflater.from(container.context).inflate(layout, container, false) |
||||
} else { |
||||
layout as View |
||||
} |
||||
return KtViewHolder(itemView) |
||||
} |
||||
|
||||
/**provide a layout id or a View*/ |
||||
abstract fun provideLayout(parent: ViewGroup): Any |
||||
|
||||
} |
||||
|
||||
class KtViewHolder(itemView: View) : ViewPagerAdapter.ViewHolder(itemView), LayoutContainer { |
||||
override val containerView: View = itemView |
||||
} |
@ -0,0 +1,69 @@ |
||||
package com.android.base.adapter.pager; |
||||
|
||||
import android.view.View; |
||||
import android.view.ViewGroup; |
||||
|
||||
import com.android.base.R; |
||||
import com.android.base.utils.common.Checker; |
||||
|
||||
import java.util.List; |
||||
|
||||
import androidx.annotation.NonNull; |
||||
import androidx.annotation.Nullable; |
||||
|
||||
/** |
||||
* 如果使用可缩放的 View 作为 pager,可能不适合使用此 Adapter |
||||
* |
||||
* @param <T> 数据 |
||||
* @param <VH> View Holder类型 |
||||
*/ |
||||
public abstract class ViewPagerAdapter<T, VH extends ViewPagerAdapter.ViewHolder> extends RecyclingPagerAdapter { |
||||
|
||||
private List<T> mData; |
||||
|
||||
public ViewPagerAdapter(@Nullable List<T> data) { |
||||
mData = data; |
||||
} |
||||
|
||||
@Override |
||||
@SuppressWarnings("unchecked") |
||||
public View getView(int position, View convertView, ViewGroup container) { |
||||
VH viewHolder; |
||||
if (convertView == null) { |
||||
viewHolder = createViewHolder(container); |
||||
} else { |
||||
viewHolder = (VH) convertView.getTag(R.id.base_item_tag_view_id); |
||||
} |
||||
T item = getItem(position); |
||||
onBindData(viewHolder, item); |
||||
return viewHolder.itemView; |
||||
} |
||||
|
||||
protected abstract VH createViewHolder(@NonNull ViewGroup container); |
||||
|
||||
protected abstract void onBindData(@NonNull VH viewHolder, @NonNull T item); |
||||
|
||||
@Override |
||||
public int getCount() { |
||||
return Checker.isEmpty(mData) ? 0 : mData.size(); |
||||
} |
||||
|
||||
public T getItem(int position) { |
||||
if (position < 0 || position >= mData.size()) { |
||||
return null; |
||||
} |
||||
return mData.get(position); |
||||
} |
||||
|
||||
public static class ViewHolder { |
||||
|
||||
public View itemView; |
||||
|
||||
public ViewHolder(@NonNull View itemView) { |
||||
this.itemView = itemView; |
||||
itemView.setTag(R.id.base_item_tag_view_id, this); |
||||
} |
||||
|
||||
} |
||||
|
||||
} |
@ -0,0 +1,48 @@ |
||||
package com.android.base.adapter.pager; |
||||
|
||||
import android.content.Context; |
||||
|
||||
import java.util.ArrayList; |
||||
import java.util.List; |
||||
|
||||
import androidx.fragment.app.Fragment; |
||||
import androidx.fragment.app.FragmentManager; |
||||
import androidx.fragment.app.FragmentPagerAdapter; |
||||
|
||||
@SuppressWarnings("unused") |
||||
public class ViewPagerFragmentAdapter extends FragmentPagerAdapter { |
||||
|
||||
private final List<ViewPagerInfo> mTabs; |
||||
private Context mContext; |
||||
|
||||
public ViewPagerFragmentAdapter(FragmentManager fragmentManager, Context context) { |
||||
super(fragmentManager); |
||||
mContext = context; |
||||
mTabs = new ArrayList<>(); |
||||
} |
||||
|
||||
public void setDataSource(List<ViewPagerInfo> viewPagerInfoList) { |
||||
mTabs.clear(); |
||||
mTabs.addAll(viewPagerInfoList); |
||||
} |
||||
|
||||
@Override |
||||
public int getCount() { |
||||
return mTabs.size(); |
||||
} |
||||
|
||||
@Override |
||||
public Fragment getItem(int position) { |
||||
ViewPagerInfo viewPagerInfo = mTabs.get(position); |
||||
return Fragment.instantiate(mContext, viewPagerInfo.clazz.getName(), viewPagerInfo.args); |
||||
} |
||||
|
||||
@Override |
||||
public CharSequence getPageTitle(int position) { |
||||
return mTabs.get(position).title; |
||||
} |
||||
|
||||
public List<ViewPagerInfo> getTabs() { |
||||
return mTabs; |
||||
} |
||||
} |
@ -0,0 +1,19 @@ |
||||
package com.android.base.adapter.pager; |
||||
|
||||
import android.os.Bundle; |
||||
|
||||
import androidx.fragment.app.Fragment; |
||||
|
||||
@SuppressWarnings("all") |
||||
public class ViewPagerInfo { |
||||
|
||||
public final Class<? extends Fragment> clazz; |
||||
public final Bundle args; |
||||
public final String title; |
||||
|
||||
public ViewPagerInfo(String title, Class<? extends Fragment> clazz, Bundle args) { |
||||
this.title = title; |
||||
this.clazz = clazz; |
||||
this.args = args; |
||||
} |
||||
} |
@ -0,0 +1,51 @@ |
||||
package com.android.base.adapter.pager; |
||||
|
||||
import android.content.Context; |
||||
|
||||
import org.jetbrains.annotations.NotNull; |
||||
|
||||
import java.util.ArrayList; |
||||
import java.util.List; |
||||
|
||||
import androidx.fragment.app.Fragment; |
||||
import androidx.fragment.app.FragmentManager; |
||||
import androidx.fragment.app.FragmentStatePagerAdapter; |
||||
|
||||
public class ViewPagerStateFragmentAdapter extends FragmentStatePagerAdapter { |
||||
|
||||
private final List<ViewPagerInfo> mTabs; |
||||
private Context mContext; |
||||
|
||||
public ViewPagerStateFragmentAdapter(FragmentManager fragmentManager, Context context) { |
||||
super(fragmentManager); |
||||
mContext = context; |
||||
mTabs = new ArrayList<>(); |
||||
} |
||||
|
||||
public void setDataSource(List<ViewPagerInfo> viewPagerInfoList) { |
||||
mTabs.clear(); |
||||
mTabs.addAll(viewPagerInfoList); |
||||
} |
||||
|
||||
@Override |
||||
public int getCount() { |
||||
return mTabs.size(); |
||||
} |
||||
|
||||
@NotNull |
||||
@Override |
||||
public Fragment getItem(int position) { |
||||
ViewPagerInfo viewPagerInfo = mTabs.get(position); |
||||
return Fragment.instantiate(mContext, viewPagerInfo.clazz.getName(), viewPagerInfo.args); |
||||
} |
||||
|
||||
@Override |
||||
public CharSequence getPageTitle(int position) { |
||||
return mTabs.get(position).title; |
||||
} |
||||
|
||||
protected List<ViewPagerInfo> getTabs() { |
||||
return mTabs; |
||||
} |
||||
|
||||
} |
@ -0,0 +1,50 @@ |
||||
package com.android.base.adapter.recycler; |
||||
|
||||
|
||||
import androidx.annotation.Nullable; |
||||
import androidx.recyclerview.widget.RecyclerView; |
||||
|
||||
/** |
||||
* @see <a href='http://www.cezcb.com/2018/08/24/PagingWithHeader/'>PagingWithHeader</a>
|
||||
*/ |
||||
class AdapterDataObserverProxy extends RecyclerView.AdapterDataObserver { |
||||
|
||||
private RecyclerView.AdapterDataObserver adapterDataObserver; |
||||
private int headerCount; |
||||
|
||||
AdapterDataObserverProxy(RecyclerView.AdapterDataObserver adapterDataObserver, int headerCount) { |
||||
this.adapterDataObserver = adapterDataObserver; |
||||
this.headerCount = headerCount; |
||||
} |
||||
|
||||
@Override |
||||
public void onChanged() { |
||||
adapterDataObserver.onChanged(); |
||||
} |
||||
|
||||
@Override |
||||
public void onItemRangeChanged(int positionStart, int itemCount) { |
||||
adapterDataObserver.onItemRangeChanged(positionStart + headerCount, itemCount); |
||||
} |
||||
|
||||
@Override |
||||
public void onItemRangeChanged(int positionStart, int itemCount, @Nullable Object payload) { |
||||
adapterDataObserver.onItemRangeChanged(positionStart + headerCount, itemCount, payload); |
||||
} |
||||
|
||||
@Override |
||||
public void onItemRangeInserted(int positionStart, int itemCount) { |
||||
adapterDataObserver.onItemRangeInserted(positionStart + headerCount, itemCount); |
||||
} |
||||
|
||||
@Override |
||||
public void onItemRangeRemoved(int positionStart, int itemCount) { |
||||
adapterDataObserver.onItemRangeRemoved(positionStart + headerCount, itemCount); |
||||
} |
||||
|
||||
@Override |
||||
public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) { |
||||
super.onItemRangeMoved(fromPosition + headerCount, toPosition + headerCount, itemCount); |
||||
} |
||||
|
||||
} |
@ -0,0 +1,266 @@ |
||||
package com.android.base.adapter.recycler; |
||||
|
||||
import android.content.Context; |
||||
import android.view.ViewGroup; |
||||
|
||||
import com.android.base.adapter.DataManager; |
||||
import com.android.base.utils.common.Checker; |
||||
|
||||
import java.util.ArrayList; |
||||
import java.util.List; |
||||
import java.util.concurrent.Executor; |
||||
|
||||
import androidx.annotation.NonNull; |
||||
import androidx.annotation.Nullable; |
||||
import androidx.recyclerview.widget.AdapterListUpdateCallback; |
||||
import androidx.recyclerview.widget.AsyncDifferConfig; |
||||
import androidx.recyclerview.widget.AsyncListDiffer; |
||||
import androidx.recyclerview.widget.DiffUtil; |
||||
import androidx.recyclerview.widget.RecyclerView; |
||||
|
||||
/** |
||||
* RecyclerView 的适配器,注意: 只有{@link #setDataSource(List, boolean)}才能替换原有数据源的引用。 |
||||
* |
||||
* @param <T> 当前列表使用的数据类型 |
||||
* @author Ztiany |
||||
* date : 2015-05-11 22:38 |
||||
* email: 1169654504@qq.com |
||||
*/ |
||||
@SuppressWarnings("unused") |
||||
public abstract class DiffRecyclerAdapter<T, VH extends ViewHolder> extends RecyclerView.Adapter<VH> implements DataManager<T> { |
||||
|
||||
@NonNull |
||||
private Context mContext; |
||||
|
||||
private AsyncListDiffer<T> mAsyncListDiffer; |
||||
|
||||
private final int mHeaderCount; |
||||
|
||||
@SuppressWarnings("WeakerAccess") |
||||
public DiffRecyclerAdapter(@NonNull Context context, @NonNull DiffUtil.ItemCallback<T> itemCallback, @Nullable Executor executor, int headerCount) { |
||||
mContext = context; |
||||
mHeaderCount = headerCount; |
||||
|
||||
AsyncDifferConfig.Builder<T> tBuilder = new AsyncDifferConfig.Builder<>(itemCallback); |
||||
if (executor != null) { |
||||
tBuilder.setBackgroundThreadExecutor(executor); |
||||
} |
||||
AsyncDifferConfig<T> differConfig = tBuilder.build(); |
||||
|
||||
mAsyncListDiffer = new AsyncListDiffer<>(new AdapterListUpdateCallback(this), differConfig); |
||||
} |
||||
|
||||
public DiffRecyclerAdapter(@NonNull Context context, @NonNull DiffUtil.ItemCallback<T> itemCallback, int headerCount) { |
||||
this(context, itemCallback, null, headerCount); |
||||
} |
||||
|
||||
public DiffRecyclerAdapter(@NonNull Context context, @NonNull DiffUtil.ItemCallback<T> itemCallback) { |
||||
this(context, itemCallback, null, 0); |
||||
} |
||||
|
||||
@Override |
||||
public void registerAdapterDataObserver(@NonNull RecyclerView.AdapterDataObserver observer) { |
||||
if (mHeaderCount != 0) { |
||||
super.registerAdapterDataObserver(new AdapterDataObserverProxy(observer, mHeaderCount)); |
||||
} else { |
||||
super.registerAdapterDataObserver(observer); |
||||
} |
||||
} |
||||
|
||||
@NonNull |
||||
public Context getContext() { |
||||
return mContext; |
||||
} |
||||
|
||||
@Override |
||||
public int getItemCount() { |
||||
return getDataSize(); |
||||
} |
||||
|
||||
@NonNull |
||||
@Override |
||||
public abstract VH onCreateViewHolder(@NonNull ViewGroup parent, int viewType); |
||||
|
||||
@Override |
||||
public void onBindViewHolder(@NonNull VH holder, int position, @NonNull List<Object> payloads) { |
||||
super.onBindViewHolder(holder, position, payloads); |
||||
} |
||||
|
||||
@Override |
||||
public abstract void onBindViewHolder(@NonNull VH viewHolder, int position); |
||||
|
||||
public void notifyEntryChanged(T t) { |
||||
int itemPosition = indexItem(t); |
||||
if (itemPosition != -1) { |
||||
notifyItemChanged(itemPosition); |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
public void add(T element) { |
||||
List<T> newList = newList(); |
||||
newList.add(element); |
||||
mAsyncListDiffer.submitList(newList); |
||||
} |
||||
|
||||
@Override |
||||
public void addAt(int location, T element) { |
||||
if (location > getDataSize()) { |
||||
location = getDataSize(); |
||||
} |
||||
List<T> newList = newList(); |
||||
newList.add(location, element); |
||||
mAsyncListDiffer.submitList(newList); |
||||
} |
||||
|
||||
@Override |
||||
public void addItems(List<T> elements) { |
||||
if (Checker.isEmpty(elements)) { |
||||
return; |
||||
} |
||||
List<T> newList = newList(); |
||||
newList.addAll(elements); |
||||
mAsyncListDiffer.submitList(newList); |
||||
} |
||||
|
||||
@Override |
||||
public void addItemsChecked(List<T> elements) { |
||||
if (Checker.isEmpty(elements)) { |
||||
return; |
||||
} |
||||
|
||||
List<T> newList = newList(); |
||||
|
||||
for (T element : elements) { |
||||
if (element == null) { |
||||
continue; |
||||
} |
||||
newList.remove(element); |
||||
} |
||||
|
||||
newList.addAll(elements); |
||||
mAsyncListDiffer.submitList(newList); |
||||
} |
||||
|
||||
@Override |
||||
public void addItemsAt(int location, List<T> elements) { |
||||
if (Checker.isEmpty(elements)) { |
||||
return; |
||||
} |
||||
List<T> newList = newList(); |
||||
|
||||
if (location > newList.size()) { |
||||
location = newList.size(); |
||||
} |
||||
|
||||
newList.addAll(location, elements); |
||||
mAsyncListDiffer.submitList(newList); |
||||
} |
||||
|
||||
@Override |
||||
public void replace(T oldElement, T newElement) { |
||||
if (!contains(oldElement)) { |
||||
return; |
||||
} |
||||
List<T> newList = newList(); |
||||
newList.set(newList.indexOf(oldElement), newElement); |
||||
mAsyncListDiffer.submitList(newList); |
||||
} |
||||
|
||||
@Override |
||||
public void replaceAt(int index, T element) { |
||||
if (getDataSize() > index) { |
||||
List<T> newList = newList(); |
||||
newList.set(index, element); |
||||
mAsyncListDiffer.submitList(newList); |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
public void replaceAll(List<T> elements) { |
||||
List<T> newList = new ArrayList<>(elements); |
||||
mAsyncListDiffer.submitList(newList); |
||||
} |
||||
|
||||
@Override |
||||
public void remove(T element) { |
||||
if (contains(element)) { |
||||
List<T> newList = newList(); |
||||
newList.remove(element); |
||||
mAsyncListDiffer.submitList(newList); |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
public void removeItems(List<T> elements) { |
||||
if (Checker.isEmpty(elements) || isEmpty() || !getItems().containsAll(elements)) { |
||||
return; |
||||
} |
||||
List<T> newList = newList(); |
||||
newList.removeAll(elements); |
||||
mAsyncListDiffer.submitList(newList); |
||||
} |
||||
|
||||
@Override |
||||
public void removeItems(List<T> elements, boolean isSuccessive) { |
||||
removeItems(elements); |
||||
} |
||||
|
||||
@Override |
||||
public void removeAt(int index) { |
||||
if (getDataSize() > index) { |
||||
List<T> newList = newList(); |
||||
newList.remove(index); |
||||
mAsyncListDiffer.submitList(newList); |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
public T getItem(int position) { |
||||
return getDataSize() > position ? getItems().get(position) : null; |
||||
} |
||||
|
||||
@Override |
||||
public final int getDataSize() { |
||||
return getItems() == null ? 0 : getItems().size(); |
||||
} |
||||
|
||||
@Override |
||||
public boolean contains(T element) { |
||||
return !isEmpty() && getItems().contains(element); |
||||
} |
||||
|
||||
@Override |
||||
public void clear() { |
||||
mAsyncListDiffer.submitList(null); |
||||
} |
||||
|
||||
@Override |
||||
public void setDataSource(List<T> elements, boolean notifyDataSetChanged) { |
||||
mAsyncListDiffer.submitList(elements); |
||||
} |
||||
|
||||
@Override |
||||
public List<T> getItems() { |
||||
return mAsyncListDiffer.getCurrentList(); |
||||
} |
||||
|
||||
@Override |
||||
public boolean isEmpty() { |
||||
return getDataSize() == 0; |
||||
} |
||||
|
||||
@Override |
||||
public int indexItem(T t) { |
||||
return isEmpty() ? -1 : getItems().indexOf(t); |
||||
} |
||||
|
||||
private List<T> newList() { |
||||
if (getItems() == null) { |
||||
return new ArrayList<>(); |
||||
} else { |
||||
return new ArrayList<>(getItems()); |
||||
} |
||||
} |
||||
|
||||
} |
@ -0,0 +1,5 @@ |
||||
package com.android.base.adapter.recycler; |
||||
|
||||
public interface HeaderSize { |
||||
int getHeaderSize(); |
||||
} |
@ -0,0 +1,38 @@ |
||||
package com.android.base.adapter.recycler |
||||
|
||||
import android.view.LayoutInflater |
||||
import android.view.View |
||||
import android.view.ViewGroup |
||||
import androidx.recyclerview.widget.RecyclerView |
||||
|
||||
/** |
||||
* @author Ztiany |
||||
* Email: 1169654504@qq.com |
||||
* Date : 2017-09-13 15:33 |
||||
*/ |
||||
abstract class ItemViewBinder<T, VH : RecyclerView.ViewHolder> : com.drakeet.multitype.ItemViewBinder<T, VH>() { |
||||
|
||||
protected val dataManager: MultiTypeAdapter |
||||
get() = adapter as MultiTypeAdapter |
||||
|
||||
} |
||||
|
||||
abstract class SimpleItemViewBinder<T> : ItemViewBinder<T, KtViewHolder>() { |
||||
|
||||
override fun onCreateViewHolder(inflater: LayoutInflater, parent: ViewGroup): KtViewHolder { |
||||
val layout = provideLayout(inflater, parent) |
||||
val itemView = if (layout is Int) { |
||||
inflater.inflate(layout, parent, false) |
||||
} else |
||||
layout as View |
||||
return KtViewHolder(itemView).apply { |
||||
onViewHolderCreated(this) |
||||
} |
||||
} |
||||
|
||||
protected open fun onViewHolderCreated(viewHolder: KtViewHolder) = Unit |
||||
|
||||
/**provide a layout id or a View*/ |
||||
abstract fun provideLayout(inflater: LayoutInflater, parent: ViewGroup): Any |
||||
|
||||
} |
@ -0,0 +1,14 @@ |
||||
package com.android.base.adapter.recycler |
||||
|
||||
import android.view.View |
||||
import kotlinx.android.extensions.CacheImplementation |
||||
import kotlinx.android.extensions.ContainerOptions |
||||
import kotlinx.android.extensions.LayoutContainer |
||||
|
||||
/** |
||||
*@author Ztiany |
||||
* Email: ztiany3@gmail.com |
||||
* Date : 2018-11-12 17:07 |
||||
*/ |
||||
@ContainerOptions(cache = CacheImplementation.SPARSE_ARRAY) |
||||
open class KtViewHolder(override val containerView: View) : ViewHolder(containerView), LayoutContainer |
@ -0,0 +1,169 @@ |
||||
package com.android.base.adapter.recycler; |
||||
|
||||
import android.content.Context; |
||||
|
||||
import com.android.base.adapter.DataManager; |
||||
|
||||
import java.util.ArrayList; |
||||
import java.util.List; |
||||
|
||||
import androidx.annotation.NonNull; |
||||
import androidx.annotation.Nullable; |
||||
|
||||
|
||||
/** |
||||
* @see <a href='https://github.com/drakeet/MultiType'>drakeet/MultiTypeAdapter</a>
|
||||
*/ |
||||
public class MultiTypeAdapter extends com.drakeet.multitype.MultiTypeAdapter implements DataManager<Object> { |
||||
|
||||
private final Context mContext; |
||||
|
||||
private RecyclerDataManagerImpl<Object> mRecyclerDataManager; |
||||
|
||||
public MultiTypeAdapter(Context context) { |
||||
super(); |
||||
mContext = context; |
||||
ArrayList<Object> objects = new ArrayList<>(); |
||||
mRecyclerDataManager = new RecyclerDataManagerImpl<>(objects, this); |
||||
super.setItems(objects); |
||||
} |
||||
|
||||
public MultiTypeAdapter(Context context, @NonNull List<?> items) { |
||||
super(); |
||||
mContext = context; |
||||
ArrayList<Object> objects = new ArrayList<>(items); |
||||
mRecyclerDataManager = new RecyclerDataManagerImpl<>(objects, this); |
||||
super.setItems(objects); |
||||
} |
||||
|
||||
public MultiTypeAdapter(Context context, @NonNull List<?> items, int initialCapacity) { |
||||
super(items, initialCapacity); |
||||
mContext = context; |
||||
ArrayList<Object> objects = new ArrayList<>(items); |
||||
mRecyclerDataManager = new RecyclerDataManagerImpl<>(objects, this); |
||||
super.setItems(objects); |
||||
} |
||||
|
||||
@SuppressWarnings("unused") |
||||
public void notifyEntryChanged(Object entry) { |
||||
int itemPosition = indexItem(entry); |
||||
if (itemPosition != -1) { |
||||
notifyItemChanged(itemPosition); |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
public void add(Object elem) { |
||||
mRecyclerDataManager.add(elem); |
||||
} |
||||
|
||||
@Override |
||||
public void addAt(int location, Object elem) { |
||||
mRecyclerDataManager.addAt(location, elem); |
||||
} |
||||
|
||||
@Override |
||||
public void addItems(List<Object> elements) { |
||||
mRecyclerDataManager.addItems(elements); |
||||
} |
||||
|
||||
@Override |
||||
public void addItemsChecked(List<Object> elements) { |
||||
mRecyclerDataManager.addItemsChecked(elements); |
||||
} |
||||
|
||||
@Override |
||||
public void addItemsAt(int location, List<Object> elements) { |
||||
mRecyclerDataManager.addItemsAt(location, elements); |
||||
} |
||||
|
||||
@Override |
||||
public void replace(Object oldElem, Object newElem) { |
||||
mRecyclerDataManager.replace(oldElem, newElem); |
||||
} |
||||
|
||||
@Override |
||||
public void replaceAt(int index, Object elem) { |
||||
mRecyclerDataManager.replaceAt(index, elem); |
||||
} |
||||
|
||||
@Override |
||||
public void replaceAll(List<Object> elements) { |
||||
mRecyclerDataManager.replaceAll(elements); |
||||
} |
||||
|
||||
@Override |
||||
public void setDataSource(@NonNull List<Object> newDataSource, boolean notifyDataSetChanged) { |
||||
super.setItems(newDataSource); |
||||
mRecyclerDataManager.setDataSource(newDataSource, notifyDataSetChanged); |
||||
} |
||||
|
||||
@Override |
||||
public void remove(Object elem) { |
||||
mRecyclerDataManager.remove(elem); |
||||
} |
||||
|
||||
@Override |
||||
public void removeAt(int index) { |
||||
mRecyclerDataManager.removeAt(index); |
||||
} |
||||
|
||||
@Override |
||||
public void removeItems(List<Object> elements) { |
||||
mRecyclerDataManager.removeItems(elements); |
||||
} |
||||
|
||||
@Override |
||||
public void removeItems(List<Object> elements, boolean isSuccessive) { |
||||
mRecyclerDataManager.removeItems(elements, isSuccessive); |
||||
} |
||||
|
||||
@Nullable |
||||
@Override |
||||
public Object getItem(int position) { |
||||
return mRecyclerDataManager.getItem(position); |
||||
} |
||||
|
||||
@Override |
||||
public int getDataSize() { |
||||
return mRecyclerDataManager.getDataSize(); |
||||
} |
||||
|
||||
@Override |
||||
public boolean contains(Object elem) { |
||||
return mRecyclerDataManager.contains(elem); |
||||
} |
||||
|
||||
@Override |
||||
public boolean isEmpty() { |
||||
return mRecyclerDataManager.isEmpty(); |
||||
} |
||||
|
||||
@Override |
||||
public void clear() { |
||||
mRecyclerDataManager.clear(); |
||||
} |
||||
|
||||
@NonNull |
||||
@Override |
||||
public List<Object> getItems() { |
||||
return mRecyclerDataManager.getItems(); |
||||
} |
||||
|
||||
@Override |
||||
public int indexItem(Object o) { |
||||
return mRecyclerDataManager.indexItem(o); |
||||
} |
||||
|
||||
@Override |
||||
public void setItems(@NonNull List<?> items) { |
||||
ArrayList<Object> objects = new ArrayList<>(items); |
||||
super.setItems(objects); |
||||
mRecyclerDataManager.setDataSource(objects, true); |
||||
} |
||||
|
||||
public Context getContext() { |
||||
return mContext; |
||||
} |
||||
|
||||
} |
@ -0,0 +1,177 @@ |
||||
package com.android.base.adapter.recycler; |
||||
|
||||
import android.content.Context; |
||||
import android.view.ViewGroup; |
||||
|
||||
import com.android.base.adapter.DataManager; |
||||
|
||||
import java.util.List; |
||||
|
||||
import androidx.annotation.NonNull; |
||||
import androidx.annotation.Nullable; |
||||
import androidx.recyclerview.widget.RecyclerView; |
||||
|
||||
/** |
||||
* RecyclerView 的适配器 |
||||
* 注意: 只有setDataSource才能替换原有数据源的引用。 |
||||
* |
||||
* @param <T> 当前列表使用的数据类型 |
||||
* @author Ztiany |
||||
* date : 2015-05-11 22:38 |
||||
* email: 1169654504@qq.com |
||||
*/ |
||||
public abstract class RecyclerAdapter<T, VH extends ViewHolder> extends RecyclerView.Adapter<VH> implements DataManager<T> { |
||||
|
||||
private RecyclerDataManagerImpl<T> mDataManager; |
||||
|
||||
@NonNull |
||||
private final Context mContext; |
||||
|
||||
public RecyclerAdapter(@NonNull Context context, List<T> data) { |
||||
mDataManager = new RecyclerDataManagerImpl<>(data, this); |
||||
this.mContext = context; |
||||
} |
||||
|
||||
public RecyclerAdapter(@NonNull Context context) { |
||||
this(context, null); |
||||
} |
||||
|
||||
@NonNull |
||||
public Context getContext() { |
||||
return mContext; |
||||
} |
||||
|
||||
@Override |
||||
public int getItemCount() { |
||||
return getDataSize(); |
||||
} |
||||
|
||||
@NonNull |
||||
@Override |
||||
public abstract VH onCreateViewHolder(@NonNull ViewGroup parent, int viewType); |
||||
|
||||
@Override |
||||
public void onBindViewHolder(@NonNull VH holder, int position, @NonNull List<Object> payloads) { |
||||
super.onBindViewHolder(holder, position, payloads); |
||||
} |
||||
|
||||
@Override |
||||
public abstract void onBindViewHolder(@NonNull VH viewHolder, int position); |
||||
|
||||
public void notifyEntryChanged(T t) { |
||||
int itemPosition = indexItem(t); |
||||
if (itemPosition != -1) { |
||||
notifyItemChanged(itemPosition); |
||||
} |
||||
} |
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// DataManager
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@Override |
||||
public void add(T elem) { |
||||
mDataManager.add(elem); |
||||
} |
||||
|
||||
@Override |
||||
public void addAt(int location, T elem) { |
||||
mDataManager.addAt(location, elem); |
||||
} |
||||
|
||||
@Override |
||||
public void addItems(List<T> elements) { |
||||
mDataManager.addItems(elements); |
||||
} |
||||
|
||||
@Override |
||||
public void addItemsChecked(List<T> elements) { |
||||
mDataManager.addItemsChecked(elements); |
||||
} |
||||
|
||||
@Override |
||||
public void addItemsAt(int location, List<T> elements) { |
||||
mDataManager.addItemsAt(location, elements); |
||||
} |
||||
|
||||
@Override |
||||
public void replace(T oldElem, T newElem) { |
||||
mDataManager.replace(oldElem, newElem); |
||||
} |
||||
|
||||
@Override |
||||
public void replaceAt(int index, T elem) { |
||||
mDataManager.replaceAt(index, elem); |
||||
} |
||||
|
||||
@Override |
||||
public void replaceAll(List<T> elements) { |
||||
mDataManager.replaceAll(elements); |
||||
} |
||||
|
||||
@Override |
||||
public void remove(T elem) { |
||||
mDataManager.remove(elem); |
||||
} |
||||
|
||||
@Override |
||||
public void removeItems(List<T> elements) { |
||||
mDataManager.removeItems(elements); |
||||
} |
||||
|
||||
@Override |
||||
public void removeItems(List<T> elements, boolean isSuccessive) { |
||||
mDataManager.removeItems(elements, isSuccessive); |
||||
} |
||||
|
||||
@Override |
||||
public void removeAt(int index) { |
||||
mDataManager.removeAt(index); |
||||
} |
||||
|
||||
@Nullable |
||||
@Override |
||||
public T getItem(int position) { |
||||
return mDataManager.getItem(position); |
||||
} |
||||
|
||||
@Override |
||||
public final int getDataSize() { |
||||
return mDataManager.getDataSize(); |
||||
} |
||||
|
||||
@Override |
||||
public boolean contains(T elem) { |
||||
return mDataManager.contains(elem); |
||||
} |
||||
|
||||
@Override |
||||
public void clear() { |
||||
mDataManager.clear(); |
||||
} |
||||
|
||||
@Override |
||||
public void setDataSource(List<T> elements, boolean notifyDataSetChanged) { |
||||
mDataManager.setDataSource(elements, notifyDataSetChanged); |
||||
} |
||||
|
||||
@Override |
||||
public List<T> getItems() { |
||||
return mDataManager.getItems(); |
||||
} |
||||
|
||||
protected final void setHeaderSize(HeaderSize headerSize) { |
||||
mDataManager.setHeaderSize(headerSize); |
||||
} |
||||
|
||||
@Override |
||||
public boolean isEmpty() { |
||||
return mDataManager.isEmpty(); |
||||
} |
||||
|
||||
@Override |
||||
public int indexItem(T t) { |
||||
return mDataManager.indexItem(t); |
||||
} |
||||
|
||||
} |
@ -0,0 +1,272 @@ |
||||
package com.android.base.adapter.recycler; |
||||
|
||||
|
||||
import com.android.base.adapter.DataManager; |
||||
import com.android.base.utils.common.Checker; |
||||
|
||||
import java.util.ArrayList; |
||||
import java.util.List; |
||||
|
||||
import androidx.recyclerview.widget.RecyclerView; |
||||
|
||||
|
||||
final class RecyclerDataManagerImpl<T> implements DataManager<T> { |
||||
|
||||
private List<T> mData; |
||||
private RecyclerView.Adapter mAdapter; |
||||
private HeaderSize mHeaderSize; |
||||
|
||||
RecyclerDataManagerImpl(List<T> tList, RecyclerView.Adapter adapter) { |
||||
mData = tList; |
||||
mAdapter = adapter; |
||||
} |
||||
|
||||
@Override |
||||
public boolean isEmpty() { |
||||
return getDataSize() == 0; |
||||
} |
||||
|
||||
@Override |
||||
public void add(T element) { |
||||
addAt(getDataSize(), element); |
||||
} |
||||
|
||||
@Override |
||||
public void addAt(int location, T element) { |
||||
if (mData != null) { |
||||
if (mData.isEmpty()) { |
||||
mData.add(element); |
||||
notifyItemInserted(getHeaderSize()); |
||||
} else { |
||||
int size = mData.size(); |
||||
int lastIndex = (location >= size ? size : location) + getHeaderSize(); |
||||
mData.add(location, element); |
||||
notifyItemInserted(lastIndex); |
||||
} |
||||
} else { |
||||
mData = new ArrayList<>(); |
||||
mData.add(element); |
||||
notifyDataSetChanged(); |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
public void addItems(List<T> elements) { |
||||
addItemsAt(getDataSize(), elements); |
||||
} |
||||
|
||||
@Override |
||||
public void addItemsChecked(List<T> elements) { |
||||
if (Checker.isEmpty(elements)) { |
||||
return; |
||||
} |
||||
if (mData == null) { |
||||
addItems(elements); |
||||
return; |
||||
} |
||||
boolean hasRemovedElements = false; |
||||
for (T element : elements) { |
||||
if (element == null) { |
||||
continue; |
||||
} |
||||
if (mData.contains(element)) { |
||||
mData.remove(element); |
||||
if (!hasRemovedElements) { |
||||
hasRemovedElements = true; |
||||
} |
||||
} |
||||
} |
||||
if (hasRemovedElements) { |
||||
mData.addAll(elements); |
||||
notifyDataSetChanged(); |
||||
} else { |
||||
addItems(elements); |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
public void addItemsAt(int location, List<T> elements) { |
||||
if (Checker.isEmpty(elements)) { |
||||
return; |
||||
} |
||||
if (this.mData != null) { |
||||
|
||||
if (mData.isEmpty()) { |
||||
mData.addAll(elements); |
||||
notifyItemRangeInserted(getHeaderSize(), elements.size()); |
||||
} else { |
||||
int size = mData.size(); |
||||
int lastIndex = (location >= size ? size : location) + getHeaderSize(); |
||||
this.mData.addAll(location, elements); |
||||
notifyItemRangeInserted(lastIndex, elements.size()); |
||||
} |
||||
|
||||
} else { |
||||
this.mData = new ArrayList<>(elements.size()); |
||||
mData.addAll(elements); |
||||
notifyDataSetChanged(); |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
public void replace(T oldElem, T newElem) { |
||||
if (mData != null && mData.contains(oldElem)) { |
||||
replaceAt(mData.indexOf(oldElem), newElem); |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
public void replaceAt(int index, T element) { |
||||
if (getDataSize() > index) { |
||||
mData.set(index, element); |
||||
notifyItemChanged(index + getHeaderSize()); |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
public void replaceAll(List<T> elements) { |
||||
if (mData == null) { |
||||
mData = new ArrayList<>(); |
||||
} |
||||
mData.clear(); |
||||
if (elements != null) { |
||||
mData.addAll(elements); |
||||
} |
||||
notifyDataSetChanged(); |
||||
} |
||||
|
||||
@Override |
||||
public void setDataSource(List<T> newDataSource, boolean notifyDataSetChanged) { |
||||
mData = newDataSource; |
||||
if (notifyDataSetChanged) { |
||||
notifyDataSetChanged(); |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
public void remove(T element) { |
||||
if (mData == null || mData.isEmpty()) { |
||||
return; |
||||
} |
||||
if (mData.contains(element)) { |
||||
int index = mData.indexOf(element) + getHeaderSize(); |
||||
mData.remove(element); |
||||
notifyItemRemoved(index); |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
public void removeAt(int index) { |
||||
if (getDataSize() > index) { |
||||
mData.remove(index); |
||||
notifyItemRemoved(index + getHeaderSize()); |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
public void removeItems(List<T> elements) { |
||||
if (!Checker.isEmpty(elements) && mData != null && mData.containsAll(elements)) { |
||||
mData.removeAll(elements); |
||||
notifyDataSetChanged(); |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
public void removeItems(List<T> elements, boolean isSuccessive) { |
||||
if (Checker.isEmpty(elements) || Checker.isEmpty(mData)) { |
||||
return; |
||||
} |
||||
if (!isSuccessive) { |
||||
removeItems(elements); |
||||
return; |
||||
} |
||||
T data = elements.get(0); |
||||
int index = mData.indexOf(data); |
||||
if (index == -1) { |
||||
removeItems(elements); |
||||
} else { |
||||
mData.removeAll(elements); |
||||
notifyItemRangeRemoved(index, elements.size()); |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
public T getItem(int position) { |
||||
position = position - getHeaderSize(); |
||||
if (position >= 0 && getDataSize() > position) { |
||||
return mData.get(position); |
||||
} |
||||
return null; |
||||
} |
||||
|
||||
@Override |
||||
public final int getDataSize() { |
||||
return mData == null ? 0 : mData.size(); |
||||
} |
||||
|
||||
@Override |
||||
public boolean contains(T element) { |
||||
return mData != null && mData.contains(element); |
||||
} |
||||
|
||||
@Override |
||||
public void clear() { |
||||
if (mData != null) { |
||||
mData.clear(); |
||||
notifyDataSetChanged(); |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
public int indexItem(T t) { |
||||
List<T> items = getItems(); |
||||
if (items == null) { |
||||
return -1; |
||||
} |
||||
return items.indexOf(t); |
||||
} |
||||
|
||||
@Override |
||||
public List<T> getItems() { |
||||
return mData; |
||||
} |
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// Size
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
private int getHeaderSize() { |
||||
return mHeaderSize == null ? 0 : mHeaderSize.getHeaderSize(); |
||||
} |
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// Adapter Call
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
private void notifyItemChanged(int position) { |
||||
mAdapter.notifyItemChanged(position); |
||||
} |
||||
|
||||
private void notifyDataSetChanged() { |
||||
mAdapter.notifyDataSetChanged(); |
||||
} |
||||
|
||||
private void notifyItemInserted(int position) { |
||||
mAdapter.notifyItemInserted(position); |
||||
} |
||||
|
||||
private void notifyItemRangeInserted(int position, int size) { |
||||
mAdapter.notifyItemRangeInserted(position, size); |
||||
} |
||||
|
||||
private void notifyItemRemoved(int index) { |
||||
mAdapter.notifyItemRemoved(index); |
||||
} |
||||
|
||||
private void notifyItemRangeRemoved(int index, int size) { |
||||
mAdapter.notifyItemRangeRemoved(index, size); |
||||
} |
||||
|
||||
void setHeaderSize(HeaderSize headerSize) { |
||||
mHeaderSize = headerSize; |
||||
} |
||||
|
||||
} |
@ -0,0 +1,41 @@ |
||||
package com.android.base.adapter.recycler |
||||
|
||||
import android.content.Context |
||||
import android.view.LayoutInflater |
||||
import android.view.View |
||||
import android.view.ViewGroup |
||||
|
||||
/** |
||||
* A simple way to build a simple list. If you want to build a multi type list, perhaps you need to use [MultiTypeAdapter]. |
||||
* |
||||
* @author Ztiany |
||||
* Email: ztiany3@gmail.com |
||||
* Date : 2019-01-15 11:41 |
||||
*/ |
||||
abstract class SimpleRecyclerAdapter<T>(context: Context, data: List<T>? = null) : RecyclerAdapter<T, KtViewHolder>(context, data) { |
||||
|
||||
private var layoutInflater: LayoutInflater = LayoutInflater.from(context) |
||||
|
||||
final override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): KtViewHolder { |
||||
val layout = provideLayout(parent, viewType) |
||||
val itemView = if (layout is Int) { |
||||
layoutInflater.inflate(layout, parent, false) |
||||
} else |
||||
layout as View |
||||
return KtViewHolder(itemView).apply { |
||||
onViewHolderCreated(this) |
||||
} |
||||
} |
||||
|
||||
protected open fun onViewHolderCreated(viewHolder: KtViewHolder) = Unit |
||||
|
||||
/**provide a layout id or a View*/ |
||||
abstract fun provideLayout(parent: ViewGroup, viewType: Int): Any |
||||
|
||||
override fun onBindViewHolder(viewHolder: KtViewHolder, position: Int) { |
||||
bind(viewHolder, getItem(position) ?: throw NullPointerException("SimpleRecyclerAdapter onBindViewHolder getItem return null")) |
||||
} |
||||
|
||||
protected abstract fun bind(viewHolder: KtViewHolder, item: T) |
||||
|
||||
} |
@ -0,0 +1,21 @@ |
||||
package com.android.base.adapter.recycler; |
||||
|
||||
import android.view.View; |
||||
|
||||
import com.android.base.adapter.ItemHelper; |
||||
|
||||
|
||||
public class SmartViewHolder extends ViewHolder { |
||||
|
||||
private final ItemHelper mHelper; |
||||
|
||||
public SmartViewHolder(View itemView) { |
||||
super(itemView); |
||||
mHelper = new ItemHelper(itemView); |
||||
} |
||||
|
||||
public ItemHelper helper() { |
||||
return mHelper; |
||||
} |
||||
|
||||
} |
@ -0,0 +1,29 @@ |
||||
package com.android.base.adapter.recycler; |
||||
|
||||
import android.content.Context; |
||||
import android.view.View; |
||||
|
||||
import androidx.annotation.IdRes; |
||||
import androidx.recyclerview.widget.RecyclerView; |
||||
|
||||
|
||||
public class ViewHolder extends RecyclerView.ViewHolder { |
||||
|
||||
public ViewHolder(View itemView) { |
||||
super(itemView); |
||||
} |
||||
|
||||
protected Context getContext() { |
||||
return itemView.getContext(); |
||||
} |
||||
|
||||
public <V extends View> V findView(@IdRes int viewId) { |
||||
return itemView.findViewById(viewId); |
||||
} |
||||
|
||||
public ViewHolder setItemClickListener(View.OnClickListener onClickListener) { |
||||
itemView.setOnClickListener(onClickListener); |
||||
return this; |
||||
} |
||||
|
||||
} |
@ -0,0 +1,67 @@ |
||||
package com.android.base.app |
||||
|
||||
import android.app.Activity |
||||
import android.content.Context |
||||
import android.os.Bundle |
||||
import androidx.fragment.app.Fragment |
||||
import androidx.fragment.app.FragmentActivity |
||||
import androidx.fragment.app.FragmentManager |
||||
import com.android.base.app.activity.ActivityDelegateOwner |
||||
import com.android.base.app.dagger.Injectable |
||||
import com.android.base.app.fragment.delegates.FragmentDelegateOwner |
||||
import com.android.base.interfaces.ActivityLifecycleCallbacksAdapter |
||||
import dagger.android.AndroidInjection |
||||
import dagger.android.support.AndroidSupportInjection |
||||
|
||||
internal class AndroidComponentLifecycleInjector : ActivityLifecycleCallbacksAdapter { |
||||
|
||||
private var isAutoInjectEnable = false |
||||
|
||||
var delegateInjector: DelegateInjector? = null |
||||
|
||||
fun enableAutoInject() { |
||||
isAutoInjectEnable = true |
||||
} |
||||
|
||||
override fun onActivityCreated(activity: Activity?, savedInstanceState: Bundle?) { |
||||
handleAutoInjectActivity(activity) |
||||
handleAutoInstallActivityDelegate(activity) |
||||
if (activity is FragmentActivity) { |
||||
injectFragmentLifecycle(activity) |
||||
} |
||||
} |
||||
|
||||
private fun injectFragmentLifecycle(activity: FragmentActivity) { |
||||
activity.supportFragmentManager.registerFragmentLifecycleCallbacks(object : FragmentManager.FragmentLifecycleCallbacks() { |
||||
override fun onFragmentAttached(fm: FragmentManager, fragment: Fragment, context: Context) { |
||||
handleAutoInjectFragment(fragment) |
||||
handleAutoInstallFragmentDelegate(fragment) |
||||
} |
||||
}, true) |
||||
} |
||||
|
||||
private fun handleAutoInstallFragmentDelegate(fragment: Fragment) { |
||||
if (fragment is FragmentDelegateOwner) { |
||||
delegateInjector?.injectFragmentDelegate(fragment) |
||||
} |
||||
} |
||||
|
||||
private fun handleAutoInstallActivityDelegate(activity: Activity?) { |
||||
if (activity is ActivityDelegateOwner) { |
||||
delegateInjector?.injectActivityDelegate(activity) |
||||
} |
||||
} |
||||
|
||||
private fun handleAutoInjectActivity(activity: Activity?) { |
||||
if (isAutoInjectEnable && activity is Injectable) { |
||||
AndroidInjection.inject(activity) |
||||
} |
||||
} |
||||
|
||||
private fun handleAutoInjectFragment(fragment: Fragment) { |
||||
if (isAutoInjectEnable && fragment is Injectable) { |
||||
AndroidSupportInjection.inject(fragment) |
||||
} |
||||
} |
||||
|
||||
} |
@ -0,0 +1,80 @@ |
||||
package com.android.base.app |
||||
|
||||
import android.app.Activity |
||||
import android.app.Application |
||||
import android.content.Context |
||||
import android.content.IntentFilter |
||||
import android.content.res.Configuration |
||||
import android.net.ConnectivityManager |
||||
import com.android.base.app.utils.CrashHandler |
||||
import com.android.base.receiver.NetStateReceiver |
||||
import com.android.base.utils.BaseUtils |
||||
import com.blankj.utilcode.util.AppUtils |
||||
import com.blankj.utilcode.util.Utils.OnAppStatusChangedListener |
||||
import io.reactivex.processors.BehaviorProcessor |
||||
import timber.log.Timber |
||||
import java.util.concurrent.atomic.AtomicBoolean |
||||
|
||||
/** |
||||
* @author Ztiany |
||||
* Email: ztiany3@gmail.com |
||||
* Date : 2018-10-12 18:19 |
||||
*/ |
||||
internal class ApplicationDelegate internal constructor(private val androidComponentLifecycleInjector: AndroidComponentLifecycleInjector) { |
||||
|
||||
lateinit var application: Application |
||||
|
||||
private lateinit var crashHandler: CrashHandler |
||||
|
||||
/** 获取可观察的 app 生命周期 */ |
||||
val appStatus: BehaviorProcessor<Boolean> = BehaviorProcessor.create() |
||||
|
||||
private val onCreateCalled = AtomicBoolean(false) |
||||
private val onAttachBaseCalled = AtomicBoolean(false) |
||||
|
||||
fun attachBaseContext(base: Context) { |
||||
check(onAttachBaseCalled.compareAndSet(false, true)) { "Can only be called once" } |
||||
} |
||||
|
||||
fun onCreate(application: Application) { |
||||
check(onCreateCalled.compareAndSet(false, true)) { "Can only be called once" } |
||||
this.application = application |
||||
//工具类初始化 |
||||
BaseUtils.init(application) |
||||
//异常日志记录 |
||||
crashHandler = CrashHandler.register(application) |
||||
//网络状态 |
||||
application.registerReceiver(NetStateReceiver(), IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION)) |
||||
//App前台后台 |
||||
listenActivityLifecycleCallbacks() |
||||
//声明周期回调 |
||||
application.registerActivityLifecycleCallbacks(androidComponentLifecycleInjector) |
||||
} |
||||
|
||||
fun onTerminate() {} |
||||
|
||||
fun onConfigurationChanged(newConfig: Configuration) {} |
||||
|
||||
fun onTrimMemory(level: Int) {} |
||||
|
||||
fun onLowMemory() {} |
||||
|
||||
private fun listenActivityLifecycleCallbacks() { |
||||
AppUtils.registerAppStatusChangedListener(object : OnAppStatusChangedListener { |
||||
override fun onBackground(activity: Activity?) { |
||||
Timber.d("app进入后台") |
||||
appStatus.onNext(false) |
||||
} |
||||
|
||||
override fun onForeground(activity: Activity?) { |
||||
Timber.d("app进入前台") |
||||
appStatus.onNext(true) |
||||
} |
||||
}) |
||||
} |
||||
|
||||
internal fun setCrashProcessor(crashProcessor: CrashProcessor) { |
||||
crashHandler.setCrashProcessor(crashProcessor) |
||||
} |
||||
|
||||
} |
@ -0,0 +1,55 @@ |
||||
package com.android.base.app |
||||
|
||||
import android.app.Application |
||||
import android.content.Context |
||||
import android.content.res.Configuration |
||||
import dagger.android.DispatchingAndroidInjector |
||||
import dagger.android.HasAndroidInjector |
||||
import javax.inject.Inject |
||||
|
||||
open class BaseAppContext : Application() { |
||||
|
||||
override fun attachBaseContext(base: Context) { |
||||
super.attachBaseContext(base) |
||||
Sword.applicationDelegate.attachBaseContext(base) |
||||
} |
||||
|
||||
override fun onCreate() { |
||||
super.onCreate() |
||||
Sword.applicationDelegate.onCreate(this) |
||||
} |
||||
|
||||
override fun onLowMemory() { |
||||
super.onLowMemory() |
||||
Sword.applicationDelegate.onLowMemory() |
||||
} |
||||
|
||||
override fun onTrimMemory(level: Int) { |
||||
super.onTrimMemory(level) |
||||
Sword.applicationDelegate.onTrimMemory(level) |
||||
} |
||||
|
||||
override fun onConfigurationChanged(newConfig: Configuration) { |
||||
super.onConfigurationChanged(newConfig) |
||||
Sword.applicationDelegate.onConfigurationChanged(newConfig) |
||||
} |
||||
|
||||
override fun onTerminate() { |
||||
super.onTerminate() |
||||
Sword.applicationDelegate.onTerminate() |
||||
} |
||||
|
||||
} |
||||
|
||||
/** |
||||
*@author Ztiany |
||||
* Email: ztiany3@gmail.com |
||||
* Date : 2019-10-12 10:59 |
||||
*/ |
||||
open class InjectorBaseAppContext : BaseAppContext(), HasAndroidInjector { |
||||
|
||||
@Inject lateinit var androidInjector: DispatchingAndroidInjector<Any> |
||||
|
||||
override fun androidInjector() = androidInjector |
||||
|
||||
} |
@ -0,0 +1,133 @@ |
||||
package com.android.base.app |
||||
|
||||
import android.app.Activity |
||||
import android.content.Context |
||||
import com.android.base.app.activity.ActivityDelegateOwner |
||||
import com.android.base.app.fragment.animator.FragmentAnimator |
||||
import com.android.base.app.fragment.delegates.FragmentDelegateOwner |
||||
import com.android.base.app.fragment.tools.FragmentConfig |
||||
import com.android.base.app.ui.LoadingView |
||||
import com.android.base.app.ui.PageNumber |
||||
import com.android.base.app.ui.RefreshLoadViewFactory |
||||
import com.android.base.app.ui.RefreshLoadViewFactory.Factory |
||||
import com.android.base.app.ui.RefreshViewFactory |
||||
import com.android.base.receiver.NetworkState |
||||
import com.blankj.utilcode.util.ActivityUtils |
||||
import com.blankj.utilcode.util.AppUtils |
||||
import io.reactivex.Flowable |
||||
import io.reactivex.plugins.RxJavaPlugins |
||||
import timber.log.Timber |
||||
|
||||
/** |
||||
* useful tools for android development, just like a sword. |
||||
* |
||||
* @author Ztiany |
||||
* Email: ztiany3@gmail.com |
||||
* Date : 2018-04-16 17:12 |
||||
*/ |
||||
object Sword { |
||||
|
||||
private val androidComponentLifecycleInjector = AndroidComponentLifecycleInjector() |
||||
|
||||
/** Application lifecycle delegate */ |
||||
internal val applicationDelegate = ApplicationDelegate(androidComponentLifecycleInjector) |
||||
|
||||
/** 错误类型分类器 */ |
||||
var errorClassifier: ErrorClassifier? = null |
||||
|
||||
/** dialog 最小展示时间 */ |
||||
var minimumShowingDialogMills: Long = 0 |
||||
|
||||
/** 用于创建 LoadingView*/ |
||||
var loadingViewFactory: ((Context) -> LoadingView)? = null |
||||
|
||||
/**网络状态监听器*/ |
||||
fun networkState(): Flowable<NetworkState> = NetworkState.observableState() |
||||
|
||||
fun setCrashProcessor(crashProcessor: CrashProcessor): Sword { |
||||
applicationDelegate.setCrashProcessor(crashProcessor) |
||||
return this |
||||
} |
||||
|
||||
fun setDefaultPageStart(pageStart: Int): Sword { |
||||
PageNumber.setDefaultPageStart(pageStart) |
||||
return this |
||||
} |
||||
|
||||
fun setDefaultPageSize(defaultPageSize: Int): Sword { |
||||
PageNumber.setDefaultPageSize(defaultPageSize) |
||||
return this |
||||
} |
||||
|
||||
/** 设置一个默认的布局 id,在使用 Fragments 中相关方法时,如果没有传入特定的容器 id 时,则使用设置的默认布局 id。 */ |
||||
fun setDefaultFragmentContainerId(defaultContainerId: Int): Sword { |
||||
FragmentConfig.setDefaultContainerId(defaultContainerId) |
||||
return this |
||||
} |
||||
|
||||
/**设置默认的 Fragment 转场动画*/ |
||||
fun setDefaultFragmentAnimator(animator: FragmentAnimator?): Sword { |
||||
FragmentConfig.setDefaultFragmentAnimator(animator) |
||||
return this |
||||
} |
||||
|
||||
/** RxJava2的一个重要的设计理念是:不吃掉任何一个异常。产生的问题是,当RxJava2“downStream”取消订阅后,“upStream”仍有可能抛出异常,这时由于已经取消订阅, |
||||
* “downStream”无法处理异常,此时的异常无人处理,便会导致程序崩溃,解决方案:在Application设置RxJavaPlugin的ErrorHandler。 |
||||
* refer: [RxJava2使用过程中遇到的坑](https://github.com/qqiabc521/blog/issues/3) */ |
||||
fun setupRxJavaErrorHandler(): Sword { |
||||
RxJavaPlugins.setErrorHandler { |
||||
Timber.d("RxJavaPlugins error handler receives error: $it") |
||||
} |
||||
return this |
||||
} |
||||
|
||||
fun enableAutoInject(): Sword { |
||||
androidComponentLifecycleInjector.enableAutoInject() |
||||
return this |
||||
} |
||||
|
||||
fun setDelegateInjector(delegateInjector: DelegateInjector): Sword { |
||||
androidComponentLifecycleInjector.delegateInjector = delegateInjector |
||||
return this |
||||
} |
||||
|
||||
/** 获取可观察的 app 生命周期,发射 tue 表示 app 切换到前台,发射 false 表示 app 切换到后台 */ |
||||
val appState: Flowable<Boolean> |
||||
get() = applicationDelegate.appStatus |
||||
|
||||
/** 获取当前 resume 的 Activity */ |
||||
val topActivity: Activity? |
||||
get() = ActivityUtils.getTopActivity() |
||||
|
||||
/** App是否在前台运行 */ |
||||
val isForeground: Boolean |
||||
get() = AppUtils.isAppForeground() |
||||
|
||||
fun registerRefreshLoadViewFactory(factory: Factory): Sword { |
||||
RefreshLoadViewFactory.registerFactory(factory) |
||||
return this |
||||
} |
||||
|
||||
fun registerRefreshViewFactory(factory: RefreshViewFactory.Factory): Sword { |
||||
RefreshViewFactory.registerFactory(factory) |
||||
return this |
||||
} |
||||
|
||||
} |
||||
|
||||
interface CrashProcessor { |
||||
fun uncaughtException(thread: Thread, ex: Throwable) |
||||
} |
||||
|
||||
interface ErrorClassifier { |
||||
fun isNetworkError(throwable: Throwable): Boolean |
||||
fun isServerError(throwable: Throwable): Boolean |
||||
} |
||||
|
||||
interface DelegateInjector { |
||||
|
||||
fun injectFragmentDelegate(fragment: FragmentDelegateOwner) |
||||
|
||||
fun injectActivityDelegate(activity: ActivityDelegateOwner) |
||||
|
||||
} |
@ -0,0 +1,79 @@ |
||||
/* |
||||
* Copyright (C) 2018 The Android Open Source Project |
||||
* |
||||
* 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 |
||||
* |
||||
* http://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. |
||||
*/ |
||||
|
||||
package com.android.base.app.aac |
||||
|
||||
import androidx.fragment.app.Fragment |
||||
import androidx.fragment.app.FragmentActivity |
||||
import androidx.lifecycle.DefaultLifecycleObserver |
||||
import androidx.lifecycle.Lifecycle |
||||
import androidx.lifecycle.LifecycleOwner |
||||
import kotlin.properties.ReadWriteProperty |
||||
import kotlin.reflect.KProperty |
||||
|
||||
class AutoClearedValue<T : Any>( |
||||
lifecycle: Lifecycle, |
||||
private val _event: Lifecycle.Event, |
||||
private val onCleared: (() -> Unit)? |
||||
) : ReadWriteProperty<Fragment, T> { |
||||
|
||||
private var _value: T? = null |
||||
|
||||
init { |
||||
lifecycle.addObserver(object : DefaultLifecycleObserver { |
||||
|
||||
override fun onPause(owner: LifecycleOwner) { |
||||
clearValue(Lifecycle.Event.ON_PAUSE) |
||||
} |
||||
|
||||
override fun onStop(owner: LifecycleOwner) { |
||||
clearValue(Lifecycle.Event.ON_STOP) |
||||
} |
||||
|
||||
override fun onDestroy(owner: LifecycleOwner) { |
||||
clearValue(Lifecycle.Event.ON_DESTROY) |
||||
} |
||||
|
||||
}) |
||||
} |
||||
|
||||
override fun getValue(thisRef: Fragment, property: KProperty<*>): T { |
||||
return _value |
||||
?: throw IllegalStateException("should never call auto-cleared-value get when it might not be available") |
||||
} |
||||
|
||||
override fun setValue(thisRef: Fragment, property: KProperty<*>, value: T) { |
||||
_value = value |
||||
} |
||||
|
||||
private fun clearValue(event: Lifecycle.Event) { |
||||
if (_event == event) { |
||||
_value = null |
||||
onCleared?.invoke() |
||||
} |
||||
} |
||||
|
||||
} |
||||
|
||||
fun <T : Any> Fragment.autoCleared( |
||||
event: Lifecycle.Event = Lifecycle.Event.ON_DESTROY, |
||||
onCleared: (() -> Unit)? = null |
||||
) = AutoClearedValue<T>(this.lifecycle, event, onCleared) |
||||
|
||||
fun <T : Any> FragmentActivity.autoCleared( |
||||
event: Lifecycle.Event = Lifecycle.Event.ON_DESTROY, |
||||
onCleared: (() -> Unit)? = null |
||||
) = AutoClearedValue<T>(this.lifecycle, event, onCleared) |
@ -0,0 +1,23 @@ |
||||
package com.android.base.app.aac |
||||
|
||||
import androidx.lifecycle.MutableLiveData |
||||
|
||||
fun <T> MutableLiveData<T>.init(t: T): MutableLiveData<T> { |
||||
this.postValue(t) |
||||
return this |
||||
} |
||||
|
||||
fun <T> MutableLiveData<T>.touchOff() { |
||||
this.postValue(this.value) |
||||
} |
||||
|
||||
fun <T> MutableLiveData<List<T>>.append(list: List<T>) { |
||||
val value = this.value |
||||
if (value == null) { |
||||
this.postValue(list) |
||||
} else { |
||||
val mutableList = value.toMutableList() |
||||
mutableList.addAll(list) |
||||
this.postValue(mutableList) |
||||
} |
||||
} |
@ -0,0 +1,116 @@ |
||||
package com.android.base.app.aac; |
||||
|
||||
|
||||
import java.lang.ref.WeakReference; |
||||
import java.util.ArrayList; |
||||
import java.util.List; |
||||
import java.util.ListIterator; |
||||
|
||||
import androidx.annotation.NonNull; |
||||
import androidx.annotation.Nullable; |
||||
import androidx.lifecycle.LifecycleOwner; |
||||
import androidx.lifecycle.MediatorLiveData; |
||||
import androidx.lifecycle.Observer; |
||||
import timber.log.Timber; |
||||
|
||||
public class SingleLiveData<T> extends MediatorLiveData<T> { |
||||
|
||||
private int mVersion = 0; |
||||
|
||||
private final List<WeakReference<ObserverWrapper<? super T>>> mWrapperObserverList = new ArrayList<>(); |
||||
|
||||
@Override |
||||
public void setValue(T value) { |
||||
mVersion++; |
||||
super.setValue(value); |
||||
} |
||||
|
||||
@Override |
||||
public void observe(@NonNull LifecycleOwner owner, @NonNull Observer<? super T> observer) { |
||||
super.observe(owner, getOrNewObserver(observer, mVersion)); |
||||
} |
||||
|
||||
@Override |
||||
public void observeForever(@NonNull Observer<? super T> observer) { |
||||
super.observeForever(getOrNewObserver(observer, mVersion)); |
||||
} |
||||
|
||||
@Override |
||||
@SuppressWarnings("unchecked") |
||||
public void removeObserver(@NonNull Observer<? super T> observer) { |
||||
if (observer instanceof ObserverWrapper) { |
||||
super.removeObserver(observer); |
||||
removeWrapper((ObserverWrapper) observer); |
||||
Timber.d("removeObserver() called with: observer = wrapper = [" + observer + "]"); |
||||
} else { |
||||
ObserverWrapper<? super T> wrapper = findWrapper(observer); |
||||
Timber.d("removeObserver() called with: observer = [" + observer + "], wrapper = [" + wrapper + "]"); |
||||
super.removeObserver(wrapper); |
||||
removeWrapper(wrapper); |
||||
} |
||||
} |
||||
|
||||
private void removeWrapper(ObserverWrapper observer) { |
||||
ListIterator<WeakReference<ObserverWrapper<? super T>>> iterator = mWrapperObserverList.listIterator(); |
||||
while (iterator.hasNext()) { |
||||
WeakReference<ObserverWrapper<? super T>> next = iterator.next(); |
||||
ObserverWrapper<? super T> item = next.get(); |
||||
if (item == observer) { |
||||
iterator.remove(); |
||||
break; |
||||
} |
||||
} |
||||
} |
||||
|
||||
private ObserverWrapper<? super T> findWrapper(Observer<? super T> observer) { |
||||
ListIterator<WeakReference<ObserverWrapper<? super T>>> iterator = mWrapperObserverList.listIterator(); |
||||
|
||||
ObserverWrapper<? super T> target = null; |
||||
|
||||
while (iterator.hasNext()) { |
||||
WeakReference<ObserverWrapper<? super T>> next = iterator.next(); |
||||
ObserverWrapper<? super T> item = next.get(); |
||||
if (item == null) { |
||||
iterator.remove(); |
||||
} else if (item.mOrigin == observer) { |
||||
target = item; |
||||
} |
||||
} |
||||
|
||||
return target; |
||||
} |
||||
|
||||
private Observer<? super T> getOrNewObserver(@NonNull Observer<? super T> observer, int observerVersion) { |
||||
ObserverWrapper<? super T> wrapper = findWrapper(observer); |
||||
|
||||
if (wrapper == null) { |
||||
wrapper = new ObserverWrapper<>(observerVersion, observer); |
||||
mWrapperObserverList.add(new WeakReference<>(wrapper)); |
||||
} |
||||
|
||||
Timber.d("getOrNewObserver() called with: observer = [" + observer + "], observerVersion = [" + observerVersion + "], wrapper = [" + wrapper + "]"); |
||||
|
||||
return wrapper; |
||||
} |
||||
|
||||
private class ObserverWrapper<E> implements Observer<E> { |
||||
|
||||
private final int mObserverVersion; |
||||
|
||||
private final Observer<E> mOrigin; |
||||
|
||||
private ObserverWrapper(int observerVersion, Observer<E> origin) { |
||||
mObserverVersion = observerVersion; |
||||
mOrigin = origin; |
||||
} |
||||
|
||||
@Override |
||||
public void onChanged(@Nullable E t) { |
||||
if (mObserverVersion < mVersion && mOrigin != null) { |
||||
mOrigin.onChanged(t); |
||||
} |
||||
} |
||||
|
||||
} |
||||
|
||||
} |
@ -0,0 +1,62 @@ |
||||
package com.android.base.app.aac; |
||||
|
||||
import android.util.Log; |
||||
|
||||
import java.util.concurrent.atomic.AtomicBoolean; |
||||
|
||||
import androidx.annotation.MainThread; |
||||
import androidx.annotation.NonNull; |
||||
import androidx.annotation.Nullable; |
||||
import androidx.lifecycle.LifecycleOwner; |
||||
import androidx.lifecycle.MutableLiveData; |
||||
import androidx.lifecycle.Observer; |
||||
|
||||
|
||||
/** |
||||
* A lifecycle-aware observable that sends only new updates after subscription, used for events like |
||||
* navigation and Snackbar messages. |
||||
* <p> |
||||
* This avoids a common problem with events: on configuration change (like rotation) an update |
||||
* can be emitted if the observer is active. This LiveData only calls the observable if there's an |
||||
* explicit call to setValue() or call(). |
||||
* <p> |
||||
* Note that only one observer is going to be notified of changes. |
||||
* |
||||
* @see <a href='https://github.com/googlesamples/android-architecture/blob/dev-todo-mvvm-live/todoapp/app/src/main/java/com/example/android/architecture/blueprints/todoapp/SingleLiveEvent.java'>SingleLiveEvent</a>
|
||||
*/ |
||||
public class SingleLiveEvent<T> extends MutableLiveData<T> { |
||||
|
||||
private static final String TAG = "SingleLiveEvent"; |
||||
|
||||
private final AtomicBoolean mPending = new AtomicBoolean(false); |
||||
|
||||
@MainThread |
||||
public void observe(@NonNull LifecycleOwner owner, @NonNull final Observer<? super T> observer) { |
||||
|
||||
if (hasActiveObservers()) { |
||||
Log.w(TAG, "Multiple observers registered but only one will be notified of changes."); |
||||
} |
||||
|
||||
// Observe the internal MutableLiveData
|
||||
super.observe(owner, t -> { |
||||
if (mPending.compareAndSet(true, false)) { |
||||
observer.onChanged(t); |
||||
} |
||||
}); |
||||
} |
||||
|
||||
@MainThread |
||||
public void setValue(@Nullable T t) { |
||||
mPending.set(true); |
||||
super.setValue(t); |
||||
} |
||||
|
||||
/** |
||||
* Used for cases where T is Void, to make calls cleaner. |
||||
*/ |
||||
@MainThread |
||||
public void call() { |
||||
setValue(null); |
||||
} |
||||
|
||||
} |
@ -0,0 +1,69 @@ |
||||
package com.android.base.app.activity; |
||||
|
||||
import android.app.Activity; |
||||
import android.content.Intent; |
||||
import android.os.Bundle; |
||||
|
||||
import androidx.annotation.NonNull; |
||||
import androidx.annotation.Nullable; |
||||
|
||||
|
||||
/** |
||||
* Activity生命周期代理 |
||||
* |
||||
* @author Ztiany |
||||
* Date : 2016-05-06 15:04 |
||||
* Email: 1169654504@qq.com |
||||
*/ |
||||
@SuppressWarnings("unused") |
||||
public interface ActivityDelegate<T extends Activity> { |
||||
|
||||
default void onAttachedToActivity(@NonNull T activity) { |
||||
} |
||||
|
||||
default void onDetachedFromActivity() { |
||||
} |
||||
|
||||
default void onCreateBeforeSetContentView(@Nullable Bundle savedInstanceState) { |
||||
} |
||||
|
||||
default void onCreateAfterSetContentView(@Nullable Bundle savedInstanceState) { |
||||
} |
||||
|
||||
default void onSaveInstanceState(Bundle savedInstanceState) { |
||||
} |
||||
|
||||
default void onStart() { |
||||
} |
||||
|
||||
default void onResume() { |
||||
} |
||||
|
||||
default void onPause() { |
||||
} |
||||
|
||||
default void onStop() { |
||||
} |
||||
|
||||
default void onDestroy() { |
||||
} |
||||
|
||||
default void onActivityResult(int requestCode, int resultCode, Intent data) { |
||||
} |
||||
|
||||
default void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) { |
||||
} |
||||
|
||||
default void onRestoreInstanceState(Bundle savedInstanceState) { |
||||
} |
||||
|
||||
default void onRestart() { |
||||
} |
||||
|
||||
default void onPostCreate(@Nullable Bundle savedInstanceState) { |
||||
} |
||||
|
||||
default void onResumeFragments() { |
||||
} |
||||
|
||||
} |
@ -0,0 +1,20 @@ |
||||
package com.android.base.app.activity; |
||||
|
||||
|
||||
import com.github.dmstocking.optional.java.util.function.Predicate; |
||||
|
||||
import androidx.annotation.UiThread; |
||||
|
||||
@UiThread |
||||
@SuppressWarnings("unused") |
||||
public interface ActivityDelegateOwner { |
||||
|
||||
void addDelegate(ActivityDelegate fragmentDelegate); |
||||
|
||||
boolean removeDelegate(ActivityDelegate fragmentDelegate); |
||||
|
||||
ActivityDelegate findDelegate(Predicate<ActivityDelegate> predicate); |
||||
|
||||
ActivityState getStatus(); |
||||
|
||||
} |
@ -0,0 +1,138 @@ |
||||
package com.android.base.app.activity; |
||||
|
||||
import android.content.Intent; |
||||
import android.os.Bundle; |
||||
|
||||
import com.github.dmstocking.optional.java.util.function.Predicate; |
||||
|
||||
import java.util.ArrayList; |
||||
import java.util.List; |
||||
|
||||
import androidx.annotation.Nullable; |
||||
import androidx.annotation.UiThread; |
||||
import androidx.appcompat.app.AppCompatActivity; |
||||
|
||||
/** |
||||
* @author Ztiany |
||||
* Email: 1169654504@qq.com |
||||
* Date : 2016-12-20 11:43 |
||||
*/ |
||||
@UiThread |
||||
final class ActivityDelegates { |
||||
|
||||
private final List<ActivityDelegate> mDelegates; |
||||
private AppCompatActivity mBaseActivity; |
||||
|
||||
ActivityDelegates(AppCompatActivity baseActivity) { |
||||
mDelegates = new ArrayList<>(4); |
||||
mBaseActivity = baseActivity; |
||||
} |
||||
|
||||
void callOnCreateBeforeSetContentView(@Nullable Bundle savedInstanceState) { |
||||
for (ActivityDelegate activityDelegate : mDelegates) { |
||||
activityDelegate.onCreateBeforeSetContentView(savedInstanceState); |
||||
} |
||||
} |
||||
|
||||
void callOnCreateAfterSetContentView(@Nullable Bundle savedInstanceState) { |
||||
for (ActivityDelegate activityDelegate : mDelegates) { |
||||
activityDelegate.onCreateAfterSetContentView(savedInstanceState); |
||||
} |
||||
} |
||||
|
||||
void callOnRestoreInstanceState(Bundle savedInstanceState) { |
||||
for (ActivityDelegate activityDelegate : mDelegates) { |
||||
activityDelegate.onRestoreInstanceState(savedInstanceState); |
||||
} |
||||
} |
||||
|
||||
void callOnPostCreate(@Nullable Bundle savedInstanceState) { |
||||
for (ActivityDelegate activityDelegate : mDelegates) { |
||||
activityDelegate.onPostCreate(savedInstanceState); |
||||
} |
||||
} |
||||
|
||||
void callOnSaveInstanceState(Bundle outState) { |
||||
for (ActivityDelegate activityDelegate : mDelegates) { |
||||
activityDelegate.onSaveInstanceState(outState); |
||||
} |
||||
} |
||||
|
||||
void callOnDestroy() { |
||||
for (ActivityDelegate activityDelegate : mDelegates) { |
||||
activityDelegate.onDestroy(); |
||||
} |
||||
} |
||||
|
||||
void callOnStop() { |
||||
for (ActivityDelegate activityDelegate : mDelegates) { |
||||
activityDelegate.onStop(); |
||||
} |
||||
} |
||||
|
||||
void callOnPause() { |
||||
for (ActivityDelegate activityDelegate : mDelegates) { |
||||
activityDelegate.onPause(); |
||||
} |
||||
} |
||||
|
||||
void callOnResume() { |
||||
for (ActivityDelegate activityDelegate : mDelegates) { |
||||
activityDelegate.onResume(); |
||||
} |
||||
} |
||||
|
||||
void callOnStart() { |
||||
for (ActivityDelegate activityDelegate : mDelegates) { |
||||
activityDelegate.onStart(); |
||||
} |
||||
} |
||||
|
||||
void callOnRestart() { |
||||
for (ActivityDelegate activityDelegate : mDelegates) { |
||||
activityDelegate.onRestart(); |
||||
} |
||||
} |
||||
|
||||
void callOnActivityResult(int requestCode, int resultCode, Intent data) { |
||||
for (ActivityDelegate activityDelegate : mDelegates) { |
||||
activityDelegate.onActivityResult(requestCode, resultCode, data); |
||||
} |
||||
} |
||||
|
||||
void callOnRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) { |
||||
for (ActivityDelegate activityDelegate : mDelegates) { |
||||
activityDelegate.onRequestPermissionsResult(requestCode, permissions, grantResults); |
||||
} |
||||
} |
||||
|
||||
void callOnResumeFragments() { |
||||
for (ActivityDelegate activityDelegate : mDelegates) { |
||||
activityDelegate.onResumeFragments(); |
||||
} |
||||
} |
||||
|
||||
@SuppressWarnings("unchecked") |
||||
void addActivityDelegate(ActivityDelegate activityDelegate) { |
||||
mDelegates.add(activityDelegate); |
||||
activityDelegate.onAttachedToActivity(mBaseActivity); |
||||
} |
||||
|
||||
boolean removeActivityDelegate(ActivityDelegate activityDelegate) { |
||||
boolean remove = mDelegates.remove(activityDelegate); |
||||
if (remove) { |
||||
activityDelegate.onDetachedFromActivity(); |
||||
} |
||||
return remove; |
||||
} |
||||
|
||||
ActivityDelegate findDelegate(Predicate<ActivityDelegate> predicate) { |
||||
for (ActivityDelegate delegate : mDelegates) { |
||||
if (predicate.test(delegate)) { |
||||
return delegate; |
||||
} |
||||
} |
||||
return null; |
||||
} |
||||
|
||||
} |
@ -0,0 +1,11 @@ |
||||
package com.android.base.app.activity |
||||
|
||||
enum class ActivityState { |
||||
INITIALIZED, |
||||
CREATE, |
||||
START, |
||||
RESUME, |
||||
PAUSE, |
||||
STOP, |
||||
DESTROY |
||||
} |
@ -0,0 +1,72 @@ |
||||
package com.android.base.app.activity; |
||||
|
||||
import java.util.List; |
||||
|
||||
import androidx.fragment.app.Fragment; |
||||
import androidx.fragment.app.FragmentActivity; |
||||
import androidx.fragment.app.FragmentManager; |
||||
|
||||
/** |
||||
* <pre> |
||||
* 1.Fragment需要自己处理BackPress事件,如果不处理,就交给子Fragment处理。都不处理则由Activity处理 |
||||
* 2.BackPress的传递由低层往深层传递,同一层级的中外层中的 Fragment优先处理。 |
||||
* 3.在Fragment中嵌套使用Fragment时,请使用getSupportChildFragmentManager |
||||
* </pre> |
||||
*/ |
||||
@SuppressWarnings("WeakerAccess") |
||||
public class BackHandlerHelper { |
||||
|
||||
/** |
||||
* 将back事件分发给 FragmentManager 中管理的子Fragment,如果该 FragmentManager 中的所有Fragment都 |
||||
* 没有处理back事件,则尝试 FragmentManager.popBackStack() |
||||
* |
||||
* @return 如果处理了back键则返回 <b>true</b> |
||||
* @see #handleBackPress(Fragment) |
||||
* @see #handleBackPress(FragmentActivity) |
||||
*/ |
||||
public static boolean handleBackPress(FragmentManager fragmentManager) { |
||||
List<Fragment> fragments = fragmentManager.getFragments(); |
||||
for (int i = fragments.size() - 1; i >= 0; i--) { |
||||
Fragment child = fragments.get(i); |
||||
if (isFragmentBackHandled(child)) { |
||||
return true; |
||||
} |
||||
} |
||||
return false; |
||||
} |
||||
|
||||
/** |
||||
* 将back事件分发给Fragment中的子Fragment, |
||||
* 该方法调用了 {@link #handleBackPress(FragmentManager)} |
||||
* |
||||
* @return 如果处理了back键则返回 <b>true</b> |
||||
*/ |
||||
public static boolean handleBackPress(Fragment fragment) { |
||||
return handleBackPress(fragment.getChildFragmentManager()); |
||||
} |
||||
|
||||
/** |
||||
* 将back事件分发给Activity中的子Fragment, |
||||
* 该方法调用了 {@link #handleBackPress(FragmentManager)} |
||||
* |
||||
* @return 如果处理了back键则返回 <b>true</b> |
||||
*/ |
||||
public static boolean handleBackPress(FragmentActivity fragmentActivity) { |
||||
return handleBackPress(fragmentActivity.getSupportFragmentManager()); |
||||
} |
||||
|
||||
/** |
||||
* 判断Fragment是否处理了Back键 |
||||
* |
||||
* @return 如果处理了back键则返回 <b>true</b> |
||||
*/ |
||||
@SuppressWarnings("WeakerAccess") |
||||
public static boolean isFragmentBackHandled(Fragment fragment) { |
||||
return fragment != null |
||||
&& fragment.isVisible() |
||||
&& fragment.getUserVisibleHint() //getUserVisibleHint默认情况下为true,在ViewPager中会被使用到。
|
||||
&& fragment instanceof OnBackPressListener |
||||
&& ((OnBackPressListener) fragment).onBackPressed(); |
||||
} |
||||
|
||||
} |
@ -0,0 +1,191 @@ |
||||
package com.android.base.app.activity |
||||
|
||||
import android.content.Intent |
||||
import android.os.Bundle |
||||
import android.view.View |
||||
import androidx.annotation.UiThread |
||||
import androidx.appcompat.app.AppCompatActivity |
||||
import androidx.fragment.app.Fragment |
||||
import com.android.base.rx.autodispose.AutoDisposeLifecycleOwnerEx |
||||
import com.android.base.utils.android.compat.AndroidVersion |
||||
import com.github.dmstocking.optional.java.util.function.Predicate |
||||
import timber.log.Timber |
||||
|
||||
/** |
||||
* 基础 BaseActivity 封装: |
||||
* |
||||
* 1. 封装通用流程。 |
||||
* 2. [onBackPressed] 事件分发,优先交给 [Fragment] 处理。 |
||||
* 3. 提供 RxJava 的生命周期绑定。 |
||||
* |
||||
* @author Ztiany |
||||
* Date : 2016-05-04 15:40 |
||||
* Email: 1169654504@qq.com |
||||
*/ |
||||
abstract class BaseActivity : AppCompatActivity(), ActivityDelegateOwner, AutoDisposeLifecycleOwnerEx { |
||||
|
||||
@Suppress("LeakingThis") |
||||
private val activityDelegates = ActivityDelegates(this) |
||||
|
||||
private var activityState = ActivityState.INITIALIZED |
||||
|
||||
private fun tag() = this.javaClass.simpleName |
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) { |
||||
Timber.tag(tag()).d("---->onCreate before call super") |
||||
initialize(savedInstanceState) |
||||
activityDelegates.callOnCreateBeforeSetContentView(savedInstanceState) |
||||
super.onCreate(savedInstanceState) |
||||
Timber.tag(tag()).d("---->onCreate after call super bundle = $savedInstanceState") |
||||
|
||||
when (val layout = layout()) { |
||||
is View -> setContentView(layout) |
||||
is Int -> setContentView(layout) |
||||
null -> Timber.d("layout() return null layout") |
||||
else -> throw IllegalArgumentException("layout() return type no support, layout = $layout") |
||||
} |
||||
|
||||
activityState = ActivityState.CREATE |
||||
activityDelegates.callOnCreateAfterSetContentView(savedInstanceState) |
||||
setupView(savedInstanceState) |
||||
} |
||||
|
||||
override fun onRestart() { |
||||
Timber.tag(tag()).d("---->onRestart before call super") |
||||
super.onRestart() |
||||
Timber.tag(tag()).d("---->onRestart after call super ") |
||||
activityDelegates.callOnRestart() |
||||
} |
||||
|
||||
override fun onStart() { |
||||
Timber.tag(tag()).d("---->onStart before call super") |
||||
super.onStart() |
||||
Timber.tag(tag()).d("---->onStart after call super") |
||||
activityState = ActivityState.START |
||||
activityDelegates.callOnStart() |
||||
} |
||||
|
||||
override fun onResume() { |
||||
Timber.tag(tag()).d("---->onResume before call super") |
||||
super.onResume() |
||||
Timber.tag(tag()).d("---->onResume after call super") |
||||
activityState = ActivityState.RESUME |
||||
activityDelegates.callOnResume() |
||||
} |
||||
|
||||
override fun onPause() { |
||||
Timber.tag(tag()).d("---->onPause before call super") |
||||
activityState = ActivityState.PAUSE |
||||
activityDelegates.callOnPause() |
||||
super.onPause() |
||||
Timber.tag(tag()).d("---->onPause after call super ") |
||||
} |
||||
|
||||
override fun onStop() { |
||||
Timber.tag(tag()).d("---->onStop before call super") |
||||
activityState = ActivityState.STOP |
||||
activityDelegates.callOnStop() |
||||
super.onStop() |
||||
Timber.tag(tag()).d("---->onStop after call super") |
||||
} |
||||
|
||||
override fun onDestroy() { |
||||
Timber.tag(tag()).d("---->onDestroy before call super") |
||||
activityState = ActivityState.DESTROY |
||||
activityDelegates.callOnDestroy() |
||||
super.onDestroy() |
||||
Timber.tag(tag()).d("---->onDestroy after call super") |
||||
} |
||||
|
||||
override fun onPostCreate(savedInstanceState: Bundle?) { |
||||
super.onPostCreate(savedInstanceState) |
||||
activityDelegates.callOnPostCreate(savedInstanceState) |
||||
} |
||||
|
||||
override fun onSaveInstanceState(outState: Bundle) { |
||||
super.onSaveInstanceState(outState) |
||||
activityDelegates.callOnSaveInstanceState(outState) |
||||
} |
||||
|
||||
override fun onRestoreInstanceState(savedInstanceState: Bundle) { |
||||
super.onRestoreInstanceState(savedInstanceState) |
||||
activityDelegates.callOnRestoreInstanceState(savedInstanceState) |
||||
} |
||||
|
||||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { |
||||
super.onActivityResult(requestCode, resultCode, data) |
||||
activityDelegates.callOnActivityResult(requestCode, resultCode, data) |
||||
} |
||||
|
||||
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>, grantResults: IntArray) { |
||||
super.onRequestPermissionsResult(requestCode, permissions, grantResults) |
||||
activityDelegates.callOnRequestPermissionsResult(requestCode, permissions, grantResults) |
||||
} |
||||
|
||||
override fun onResumeFragments() { |
||||
super.onResumeFragments() |
||||
activityDelegates.callOnResumeFragments() |
||||
} |
||||
|
||||
/////////////////////////////////////////////////////////////////////////// |
||||
// interface impl |
||||
/////////////////////////////////////////////////////////////////////////// |
||||
@UiThread |
||||
final override fun addDelegate(activityDelegate: ActivityDelegate<*>) { |
||||
activityDelegates.addActivityDelegate(activityDelegate) |
||||
} |
||||
|
||||
@UiThread |
||||
final override fun removeDelegate(activityDelegate: ActivityDelegate<*>): Boolean { |
||||
return activityDelegates.removeActivityDelegate(activityDelegate) |
||||
} |
||||
|
||||
@UiThread |
||||
final override fun findDelegate(predicate: Predicate<ActivityDelegate<*>?>?): ActivityDelegate<*>? { |
||||
return activityDelegates.findDelegate(predicate) |
||||
} |
||||
|
||||
override fun getStatus(): ActivityState { |
||||
return activityState |
||||
} |
||||
|
||||
/** |
||||
* Before call super.onCreate and setContentView |
||||
* |
||||
* @param savedInstanceState state |
||||
*/ |
||||
protected open fun initialize(savedInstanceState: Bundle?) {} |
||||
|
||||
/** |
||||
* provide a layoutId (int) or layout (View) |
||||
* |
||||
* @return layoutId |
||||
*/ |
||||
protected abstract fun layout(): Any? |
||||
|
||||
/** |
||||
* after setContentView |
||||
*/ |
||||
protected abstract fun setupView(savedInstanceState: Bundle?) |
||||
|
||||
override fun onBackPressed() { |
||||
if (BackHandlerHelper.handleBackPress(this)) { |
||||
Timber.d("onBackPressed() called but child fragment handle it") |
||||
} else { |
||||
superOnBackPressed() |
||||
} |
||||
} |
||||
|
||||
protected open fun superOnBackPressed() { |
||||
super.onBackPressed() |
||||
} |
||||
|
||||
override fun isDestroyed(): Boolean { |
||||
return if (AndroidVersion.atLeast(17)) { |
||||
super.isDestroyed() |
||||
} else { |
||||
status === ActivityState.DESTROY |
||||
} |
||||
} |
||||
|
||||
} |
@ -0,0 +1,26 @@ |
||||
package com.android.base.app.activity; |
||||
|
||||
import com.android.base.app.dagger.Injectable; |
||||
|
||||
import javax.inject.Inject; |
||||
|
||||
import dagger.android.AndroidInjector; |
||||
import dagger.android.DispatchingAndroidInjector; |
||||
import dagger.android.HasAndroidInjector; |
||||
|
||||
/** |
||||
* @author Ztiany |
||||
* Email: 1169654504@qq.com |
||||
* Date : 2018-11-01 |
||||
*/ |
||||
public abstract class InjectorBaseActivity extends BaseActivity implements Injectable, HasAndroidInjector { |
||||
|
||||
@Inject |
||||
DispatchingAndroidInjector<Object> androidInjector; |
||||
|
||||
@Override |
||||
public AndroidInjector<Object> androidInjector() { |
||||
return androidInjector; |
||||
} |
||||
|
||||
} |
@ -0,0 +1,13 @@ |
||||
package com.android.base.app.activity; |
||||
|
||||
/** |
||||
* Activity的返回键监听 |
||||
*/ |
||||
public interface OnBackPressListener { |
||||
|
||||
/** |
||||
* @return true 表示Fragment处理back press,false表示由Activity处理 |
||||
*/ |
||||
boolean onBackPressed(); |
||||
|
||||
} |
@ -0,0 +1,14 @@ |
||||
package com.android.base.app.dagger; |
||||
|
||||
import java.lang.annotation.Documented; |
||||
import java.lang.annotation.Retention; |
||||
import java.lang.annotation.RetentionPolicy; |
||||
|
||||
import javax.inject.Scope; |
||||
|
||||
|
||||
@Scope |
||||
@Retention(RetentionPolicy.RUNTIME) |
||||
@Documented |
||||
public @interface ActivityScope { |
||||
} |
@ -0,0 +1,22 @@ |
||||
package com.android.base.app.dagger; |
||||
|
||||
import java.lang.annotation.Documented; |
||||
import java.lang.annotation.Retention; |
||||
|
||||
import javax.inject.Qualifier; |
||||
|
||||
import static java.lang.annotation.RetentionPolicy.RUNTIME; |
||||
|
||||
|
||||
@Qualifier |
||||
@Documented |
||||
@Retention(RUNTIME) |
||||
public @interface ContextType { |
||||
|
||||
String ACTIVITY = "Activity"; |
||||
String CONTEXT = "Context"; |
||||
String APPLICATION = "Application"; |
||||
|
||||
String value() default APPLICATION; |
||||
|
||||
} |
@ -0,0 +1,14 @@ |
||||
package com.android.base.app.dagger; |
||||
|
||||
import java.lang.annotation.Documented; |
||||
import java.lang.annotation.Retention; |
||||
import java.lang.annotation.RetentionPolicy; |
||||
|
||||
import javax.inject.Scope; |
||||
|
||||
|
||||
@Scope |
||||
@Retention(RetentionPolicy.RUNTIME) |
||||
@Documented |
||||
public @interface FragmentScope { |
||||
} |
@ -0,0 +1,18 @@ |
||||
package com.android.base.app.dagger; |
||||
|
||||
import android.view.View; |
||||
|
||||
import java.util.Map; |
||||
|
||||
import javax.inject.Provider; |
||||
|
||||
/** |
||||
* @author Ztiany |
||||
* Email: ztiany3@gmail.com |
||||
* Date : 2017-06-21 14:58 |
||||
*/ |
||||
public interface HasViewInjector { |
||||
|
||||
Map<Class<? extends View>, Provider<ViewComponentBuilder>> viewInjectors(); |
||||
|
||||
} |
@ -0,0 +1,26 @@ |
||||
package com.android.base.app.dagger; |
||||
|
||||
/** |
||||
* 当 Activity 实现此接口时,如果需要为内部的 Fragment 提供注入容器,需要实现 HasSupportFragmentInjector,具体如下面代码: |
||||
* |
||||
* <pre>{@code |
||||
* |
||||
* class InjectableActivity implements Injectable, HasSupportFragmentInjector{ |
||||
* @Inject |
||||
* DispatchingAndroidInjector<Fragment> fragmentInjector; |
||||
* |
||||
* public AndroidInjector<Fragment> supportFragmentInjector() { |
||||
* return fragmentInjector; |
||||
* } |
||||
* } |
||||
* } |
||||
* |
||||
* 标记接口,用于标记此类需要被注入依赖。 |
||||
* |
||||
* @author Ztiany |
||||
* Email: ztiany3@gmail.com |
||||
* Date : 2018-12-11 12:38 |
||||
*/ |
||||
public interface Injectable { |
||||
|
||||
} |
@ -0,0 +1,14 @@ |
||||
package com.android.base.app.dagger; |
||||
|
||||
import android.view.View; |
||||
|
||||
import dagger.MembersInjector; |
||||
|
||||
/** |
||||
* how to use it? refer https://github.com/Ztiany/notes/blob/master/Android/00-Code/Dagger2AndroidInjection-v2.24/README.md.
|
||||
* |
||||
* @param <A> |
||||
*/ |
||||
@SuppressWarnings("WeakerAccess") |
||||
public interface ViewComponent<A extends View> extends MembersInjector<A> { |
||||
} |
@ -0,0 +1,15 @@ |
||||
package com.android.base.app.dagger; |
||||
|
||||
import android.view.View; |
||||
|
||||
import dagger.BindsInstance; |
||||
|
||||
|
||||
public interface ViewComponentBuilder<T extends View, C extends ViewComponent<T>> { |
||||
|
||||
@BindsInstance |
||||
ViewComponentBuilder bindInstance(T t); |
||||
|
||||
C build(); |
||||
|
||||
} |
@ -0,0 +1,60 @@ |
||||
package com.android.base.app.dagger; |
||||
|
||||
import android.app.Application; |
||||
import android.content.Context; |
||||
import android.util.Log; |
||||
import android.view.View; |
||||
|
||||
import java.util.Map; |
||||
|
||||
import javax.inject.Provider; |
||||
|
||||
/** |
||||
* @author Ztiany |
||||
* Email: ztiany3@gmail.com |
||||
* Date : 2017-06-21 14:55 |
||||
*/ |
||||
@SuppressWarnings("unused") |
||||
public class ViewInjection { |
||||
|
||||
private static final String TAG = ViewInjection.class.getSimpleName(); |
||||
|
||||
@SuppressWarnings("unchecked") |
||||
public static void inject(View view) { |
||||
|
||||
if (null == view) { |
||||
throw new NullPointerException(); |
||||
} |
||||
|
||||
HasViewInjector hasViewInjector = findHasViewInjectors(view); |
||||
|
||||
Log.d(TAG, String.format("An injector for %s was found in %s", view.getClass().getCanonicalName(), hasViewInjector.getClass().getCanonicalName())); |
||||
|
||||
Map<Class<? extends View>, Provider<ViewComponentBuilder>> viewInjectors = hasViewInjector.viewInjectors(); |
||||
Provider<ViewComponentBuilder> provider = viewInjectors.get(view.getClass()); |
||||
|
||||
if (provider != null) { |
||||
ViewComponentBuilder viewComponentBuilder = provider.get(); |
||||
viewComponentBuilder |
||||
.bindInstance(view) |
||||
.build() |
||||
.injectMembers(view); |
||||
} else { |
||||
throw new NullPointerException("ViewInjection fail "); |
||||
} |
||||
|
||||
} |
||||
|
||||
private static HasViewInjector findHasViewInjectors(View view) { |
||||
Context context = view.getContext(); |
||||
if (context instanceof HasViewInjector) { |
||||
return (HasViewInjector) context; |
||||
} |
||||
Application application = (Application) context.getApplicationContext(); |
||||
if (application instanceof HasViewInjector) { |
||||
return (HasViewInjector) application; |
||||
} |
||||
throw new IllegalArgumentException(String.format("No injector was found for %s", view.getClass().getCanonicalName())); |
||||
} |
||||
|
||||
} |
@ -0,0 +1,18 @@ |
||||
package com.android.base.app.dagger; |
||||
|
||||
import android.view.View; |
||||
|
||||
import java.lang.annotation.Target; |
||||
|
||||
import dagger.MapKey; |
||||
import dagger.internal.Beta; |
||||
|
||||
import static java.lang.annotation.ElementType.METHOD; |
||||
|
||||
@Beta |
||||
@MapKey |
||||
@Target(METHOD) |
||||
@SuppressWarnings("unused") |
||||
public @interface ViewKey { |
||||
Class<? extends View> value(); |
||||
} |
@ -0,0 +1,48 @@ |
||||
package com.android.base.app.dagger; |
||||
|
||||
|
||||
import java.util.Map; |
||||
|
||||
import javax.inject.Inject; |
||||
import javax.inject.Provider; |
||||
|
||||
import androidx.annotation.NonNull; |
||||
import androidx.lifecycle.ViewModel; |
||||
import androidx.lifecycle.ViewModelProvider; |
||||
|
||||
/** |
||||
* {@link androidx.lifecycle.AbstractSavedStateViewModelFactory} is not supported. |
||||
* |
||||
* @author Ztiany |
||||
* Email: ztiany3@gmail.com |
||||
* Date : 2018-11-05 14:31 |
||||
*/ |
||||
public class ViewModelFactory implements ViewModelProvider.Factory { |
||||
|
||||
private final Map<Class<? extends ViewModel>, Provider<ViewModel>> mCreatorMap; |
||||
|
||||
@Inject |
||||
ViewModelFactory(Map<Class<? extends ViewModel>, Provider<ViewModel>> classProviderMap) { |
||||
mCreatorMap = classProviderMap; |
||||
} |
||||
|
||||
@NonNull |
||||
@Override |
||||
@SuppressWarnings("unchecked") |
||||
public <T extends ViewModel> T create(@NonNull Class<T> modelClass) { |
||||
Provider<ViewModel> viewModelProvider = mCreatorMap.get(modelClass); |
||||
if (viewModelProvider == null) { |
||||
for (Map.Entry<Class<? extends ViewModel>, Provider<ViewModel>> entry : mCreatorMap.entrySet()) { |
||||
if (modelClass.isAssignableFrom(entry.getKey())) { |
||||
viewModelProvider = entry.getValue(); |
||||
break; |
||||
} |
||||
} |
||||
} |
||||
if (viewModelProvider != null) { |
||||
return (T) viewModelProvider.get(); |
||||
} |
||||
throw new NullPointerException("can not find provider for " + modelClass); |
||||
} |
||||
|
||||
} |
@ -0,0 +1,19 @@ |
||||
package com.android.base.app.dagger; |
||||
|
||||
|
||||
import androidx.lifecycle.ViewModel; |
||||
import dagger.MapKey; |
||||
import kotlin.annotation.AnnotationTarget; |
||||
import kotlin.annotation.MustBeDocumented; |
||||
import kotlin.annotation.Retention; |
||||
import kotlin.annotation.Target; |
||||
|
||||
@MustBeDocumented |
||||
@Target(allowedTargets = AnnotationTarget.FUNCTION) |
||||
@Retention() |
||||
@MapKey |
||||
public @interface ViewModelKey { |
||||
|
||||
Class<? extends ViewModel> value(); |
||||
|
||||
} |
@ -0,0 +1,21 @@ |
||||
package com.android.base.app.dagger; |
||||
|
||||
import androidx.lifecycle.ViewModelProvider; |
||||
import dagger.Binds; |
||||
import dagger.Module; |
||||
|
||||
/** |
||||
* {@link androidx.lifecycle.AbstractSavedStateViewModelFactory} is not supported. |
||||
* |
||||
* @author Ztiany |
||||
* Email: ztiany3@gmail.com |
||||
* Date : 2018-11-05 16:20 |
||||
*/ |
||||
@Module |
||||
public abstract class ViewModelModule { |
||||
|
||||
@Binds |
||||
@ActivityScope |
||||
abstract ViewModelProvider.Factory provideViewModelFactory(ViewModelFactory viewModelFactory); |
||||
|
||||
} |
@ -0,0 +1,19 @@ |
||||
package com.android.base.app.dagger; |
||||
|
||||
import java.lang.annotation.Documented; |
||||
import java.lang.annotation.Retention; |
||||
|
||||
import javax.inject.Scope; |
||||
|
||||
import static java.lang.annotation.RetentionPolicy.RUNTIME; |
||||
|
||||
/** |
||||
* @author Ztiany |
||||
* Email: ztiany3@gmail.com |
||||
* Date : 2017-05-23 09:59 |
||||
*/ |
||||
@Scope |
||||
@Documented |
||||
@Retention(RUNTIME) |
||||
public @interface ViewScope { |
||||
} |
@ -0,0 +1,277 @@ |
||||
package com.android.base.app.fragment |
||||
|
||||
import android.content.Context |
||||
import android.content.Intent |
||||
import android.os.Bundle |
||||
import android.view.LayoutInflater |
||||
import android.view.View |
||||
import android.view.ViewGroup |
||||
import androidx.annotation.StringRes |
||||
import androidx.annotation.UiThread |
||||
import androidx.appcompat.app.AppCompatDialogFragment |
||||
import com.android.base.app.Sword |
||||
import com.android.base.app.activity.BackHandlerHelper |
||||
import com.android.base.app.activity.OnBackPressListener |
||||
import com.android.base.app.fragment.delegates.FragmentDelegate |
||||
import com.android.base.app.fragment.delegates.FragmentDelegateOwner |
||||
import com.android.base.app.ui.LoadingView |
||||
import com.android.base.rx.autodispose.AutoDisposeLifecycleOwnerEx |
||||
import com.github.dmstocking.optional.java.util.function.Predicate |
||||
import timber.log.Timber |
||||
|
||||
/** |
||||
* @author Ztiany |
||||
* date : 2016-03-19 23:09 |
||||
* email: 1169654504@qq.com |
||||
* @see [BaseFragment] |
||||
*/ |
||||
open class BaseDialogFragment : AppCompatDialogFragment(), LoadingView, OnBackPressListener, FragmentDelegateOwner, AutoDisposeLifecycleOwnerEx { |
||||
|
||||
private var loadingView: LoadingView? = null |
||||
|
||||
private var layoutView: View? = null |
||||
|
||||
/** just for cache*/ |
||||
private var cachedView: View? = null |
||||
|
||||
@Suppress("LeakingThis") |
||||
private val fragmentDelegates = FragmentDelegates(this) |
||||
|
||||
private var recentShowingDialogTime: Long = 0 |
||||
|
||||
private var _state: Bundle? = null |
||||
|
||||
protected val state: Bundle |
||||
get() { |
||||
var state = _state |
||||
if (state == null) { |
||||
state = Bundle() |
||||
_state = state |
||||
} |
||||
return state |
||||
} |
||||
|
||||
private fun tag() = this.javaClass.simpleName |
||||
|
||||
override fun onAttach(context: Context) { |
||||
super.onAttach(context) |
||||
Timber.tag(tag()).d("onAttach() called with: context = [$context]") |
||||
fragmentDelegates.onAttach(context) |
||||
} |
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) { |
||||
super.onCreate(savedInstanceState) |
||||
_state = getInstanceState(savedInstanceState) |
||||
Timber.tag(tag()).d("-->onCreate savedInstanceState = $savedInstanceState") |
||||
fragmentDelegates.onCreate(savedInstanceState) |
||||
} |
||||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { |
||||
if (cachedView == null) { |
||||
val layout = provideLayout() ?: return null |
||||
if (layout is Int) { |
||||
return inflater.inflate(layout, container, false).also { cachedView = it } |
||||
} |
||||
if (layout is View) { |
||||
cachedView = layout |
||||
return layout |
||||
} |
||||
throw IllegalArgumentException("Here you should provide a layout id or a View") |
||||
} |
||||
|
||||
Timber.tag(tag()).d("mCachedView.parent: " + cachedView?.parent) |
||||
|
||||
cachedView?.run { |
||||
val viewParent = parent |
||||
if (viewParent != null && viewParent is ViewGroup) { |
||||
viewParent.removeView(this) |
||||
} |
||||
} |
||||
|
||||
return cachedView |
||||
} |
||||
|
||||
/** |
||||
* 使用此方法提供的布局,将只会被缓存起来,即此方法将只会被调用一次。 |
||||
* |
||||
* @return provide a layout id or a View |
||||
*/ |
||||
protected open fun provideLayout(): Any? = null |
||||
|
||||
final override fun onViewCreated(view: View, savedInstanceState: Bundle?) { |
||||
super.onViewCreated(view, savedInstanceState) |
||||
Timber.tag(tag()).d("-->onViewCreated savedInstanceState = %s", savedInstanceState) |
||||
if (layoutView !== view) { |
||||
layoutView = view |
||||
internalOnViewPrepared(view, savedInstanceState) |
||||
onViewPrepared(view, savedInstanceState) |
||||
} |
||||
fragmentDelegates.onViewCreated(view, savedInstanceState) |
||||
} |
||||
|
||||
internal open fun internalOnViewPrepared(view: View, savedInstanceState: Bundle?) {} |
||||
|
||||
/** |
||||
* View is prepared, If [androidx.fragment.app.Fragment.onCreateView] return same layout, it will be called once |
||||
* |
||||
* @param view view of fragment |
||||
*/ |
||||
protected open fun onViewPrepared(view: View, savedInstanceState: Bundle?) {} |
||||
|
||||
override fun onActivityCreated(savedInstanceState: Bundle?) { |
||||
super.onActivityCreated(savedInstanceState) |
||||
Timber.tag(tag()).d("-->onActivityCreated savedInstanceState = $savedInstanceState") |
||||
fragmentDelegates.onActivityCreated(savedInstanceState) |
||||
} |
||||
|
||||
override fun onStart() { |
||||
super.onStart() |
||||
Timber.tag(tag()).d("-->onStart") |
||||
fragmentDelegates.onStart() |
||||
} |
||||
|
||||
override fun onResume() { |
||||
super.onResume() |
||||
Timber.tag(tag()).d("-->onResume") |
||||
fragmentDelegates.onResume() |
||||
} |
||||
|
||||
override fun onPause() { |
||||
Timber.tag(tag()).d("-->onPause") |
||||
fragmentDelegates.onPause() |
||||
super.onPause() |
||||
} |
||||
|
||||
override fun onStop() { |
||||
Timber.tag(tag()).d("-->onStop") |
||||
fragmentDelegates.onStop() |
||||
super.onStop() |
||||
} |
||||
|
||||
override fun onDestroyView() { |
||||
Timber.tag(tag()).d("-->onDestroyView") |
||||
fragmentDelegates.onDestroyView() |
||||
super.onDestroyView() |
||||
} |
||||
|
||||
override fun onDestroy() { |
||||
Timber.tag(tag()).d("-->onDestroy") |
||||
fragmentDelegates.onDestroy() |
||||
super.onDestroy() |
||||
dismissLoadingDialog() |
||||
} |
||||
|
||||
override fun onDetach() { |
||||
Timber.tag(tag()).d("-->onDetach") |
||||
fragmentDelegates.onDetach() |
||||
super.onDetach() |
||||
} |
||||
|
||||
override fun onSaveInstanceState(outState: Bundle) { |
||||
fragmentDelegates.onSaveInstanceState(outState) |
||||
saveInstanceState(outState, _state) |
||||
super.onSaveInstanceState(outState) |
||||
} |
||||
|
||||
override fun setUserVisibleHint(isVisibleToUser: Boolean) { |
||||
super.setUserVisibleHint(isVisibleToUser) |
||||
Timber.tag(tag()).d("-->setUserVisibleHint ==$isVisibleToUser") |
||||
fragmentDelegates.setUserVisibleHint(isVisibleToUser) |
||||
} |
||||
|
||||
override fun onHiddenChanged(hidden: Boolean) { |
||||
super.onHiddenChanged(hidden) |
||||
Timber.tag(tag()).d("-->onHiddenChanged = $hidden") |
||||
fragmentDelegates.onHiddenChanged(hidden) |
||||
} |
||||
|
||||
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>, grantResults: IntArray) { |
||||
super.onRequestPermissionsResult(requestCode, permissions, grantResults) |
||||
fragmentDelegates.onRequestPermissionsResult(requestCode, permissions, grantResults) |
||||
} |
||||
|
||||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { |
||||
super.onActivityResult(requestCode, resultCode, data) |
||||
fragmentDelegates.onActivityResult(requestCode, resultCode, data) |
||||
} |
||||
|
||||
@UiThread |
||||
override fun addDelegate(fragmentDelegate: FragmentDelegate<*>?) { |
||||
fragmentDelegates.addDelegate(fragmentDelegate) |
||||
} |
||||
|
||||
@UiThread |
||||
override fun removeDelegate(fragmentDelegate: FragmentDelegate<*>?): Boolean { |
||||
return fragmentDelegates.removeDelegate(fragmentDelegate) |
||||
} |
||||
|
||||
override fun findDelegate(predicate: Predicate<FragmentDelegate<*>?>?): FragmentDelegate<*>? { |
||||
return fragmentDelegates.findDelegate(predicate) |
||||
} |
||||
|
||||
final override fun onBackPressed(): Boolean { |
||||
return handleBackPress() || BackHandlerHelper.handleBackPress(this) |
||||
} |
||||
|
||||
/** |
||||
* Fragment需要自己处理BackPress事件,如果不处理,就交给子Fragment处理。都不处理则由Activity处理 |
||||
*/ |
||||
protected open fun handleBackPress(): Boolean { |
||||
return false |
||||
} |
||||
|
||||
private fun loadingView(): LoadingView { |
||||
val loadingViewImpl = loadingView |
||||
return if (loadingViewImpl != null) { |
||||
loadingViewImpl |
||||
} else { |
||||
loadingView = onCreateLoadingView() ?: Sword.loadingViewFactory?.invoke(requireContext()) |
||||
loadingView ?: throw NullPointerException("you need to config LoadingViewFactory in Sword or implement onCreateLoadingView.") |
||||
} |
||||
} |
||||
|
||||
protected open fun onCreateLoadingView(): LoadingView? { |
||||
return null |
||||
} |
||||
|
||||
override fun showLoadingDialog() { |
||||
recentShowingDialogTime = System.currentTimeMillis() |
||||
loadingView().showLoadingDialog(true) |
||||
} |
||||
|
||||
override fun showLoadingDialog(cancelable: Boolean) { |
||||
recentShowingDialogTime = System.currentTimeMillis() |
||||
loadingView().showLoadingDialog(cancelable) |
||||
} |
||||
|
||||
override fun showLoadingDialog(message: CharSequence, cancelable: Boolean) { |
||||
recentShowingDialogTime = System.currentTimeMillis() |
||||
loadingView().showLoadingDialog(message, cancelable) |
||||
} |
||||
|
||||
override fun showLoadingDialog(@StringRes messageId: Int, cancelable: Boolean) { |
||||
recentShowingDialogTime = System.currentTimeMillis() |
||||
loadingView().showLoadingDialog(messageId, cancelable) |
||||
} |
||||
|
||||
override fun dismissLoadingDialog() { |
||||
loadingView().dismissLoadingDialog() |
||||
} |
||||
|
||||
override fun dismissLoadingDialog(minimumMills: Long, onDismiss: () -> Unit) { |
||||
dismissDialog(recentShowingDialogTime, minimumMills, onDismiss) |
||||
} |
||||
|
||||
override fun isLoadingDialogShowing(): Boolean { |
||||
return loadingView().isLoadingDialogShowing() |
||||
} |
||||
|
||||
override fun showMessage(message: CharSequence) { |
||||
loadingView().showMessage(message) |
||||
} |
||||
|
||||
override fun showMessage(@StringRes messageId: Int) { |
||||
loadingView().showMessage(messageId) |
||||
} |
||||
|
||||
} |
@ -0,0 +1,295 @@ |
||||
package com.android.base.app.fragment |
||||
|
||||
import android.content.Context |
||||
import android.content.Intent |
||||
import android.os.Bundle |
||||
import android.view.LayoutInflater |
||||
import android.view.View |
||||
import android.view.ViewGroup |
||||
import android.view.animation.Animation |
||||
import androidx.annotation.StringRes |
||||
import androidx.annotation.UiThread |
||||
import androidx.fragment.app.Fragment |
||||
import com.android.base.app.Sword |
||||
import com.android.base.app.activity.BackHandlerHelper |
||||
import com.android.base.app.activity.OnBackPressListener |
||||
import com.android.base.app.fragment.animator.FragmentAnimatorHelper |
||||
import com.android.base.app.fragment.delegates.FragmentDelegate |
||||
import com.android.base.app.fragment.delegates.FragmentDelegateOwner |
||||
import com.android.base.app.fragment.tools.FragmentConfig |
||||
import com.android.base.app.ui.LoadingView |
||||
import com.android.base.rx.autodispose.AutoDisposeLifecycleOwnerEx |
||||
import com.github.dmstocking.optional.java.util.function.Predicate |
||||
import timber.log.Timber |
||||
|
||||
/** |
||||
*基础 BaseFragment 封装: |
||||
* |
||||
* 1. RxJava 生命周期绑定。 |
||||
* 2. 返回键监听。 |
||||
* 3. 显示 LoadingDialog 和 Message。 |
||||
* 4. 可以添加生命周期代理。 |
||||
* |
||||
* @author Ztiany |
||||
* date : 2016-03-19 23:09 |
||||
* email: 1169654504@qq.com |
||||
*/ |
||||
open class BaseFragment : Fragment(), LoadingView, OnBackPressListener, FragmentDelegateOwner, AutoDisposeLifecycleOwnerEx { |
||||
|
||||
private var loadingView: LoadingView? = null |
||||
|
||||
private var layoutView: View? = null |
||||
|
||||
/** just for cache*/ |
||||
private var cachedView: View? = null |
||||
|
||||
private var fragmentAnimatorHelper: FragmentAnimatorHelper? = null |
||||
|
||||
@Suppress("LeakingThis") |
||||
private val fragmentDelegates = FragmentDelegates(this) |
||||
|
||||
private var recentShowingDialogTime: Long = 0 |
||||
|
||||
private var _state: Bundle? = null |
||||
|
||||
protected val state: Bundle |
||||
get() { |
||||
var state = _state |
||||
if (state == null) { |
||||
state = Bundle() |
||||
_state = state |
||||
} |
||||
return state |
||||
} |
||||
|
||||
private fun tag() = this.javaClass.simpleName |
||||
|
||||
override fun onAttach(context: Context) { |
||||
super.onAttach(context) |
||||
Timber.tag(tag()).d("onAttach() called with: context = [$context]") |
||||
fragmentDelegates.onAttach(context) |
||||
} |
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) { |
||||
super.onCreate(savedInstanceState) |
||||
_state = getInstanceState(savedInstanceState) |
||||
Timber.tag(tag()).d("-->onCreate savedInstanceState = $savedInstanceState") |
||||
fragmentDelegates.onCreate(savedInstanceState) |
||||
} |
||||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { |
||||
if (cachedView == null) { |
||||
val layout = provideLayout() ?: return null |
||||
if (layout is Int) { |
||||
return inflater.inflate(layout, container, false).also { cachedView = it } |
||||
} |
||||
if (layout is View) { |
||||
cachedView = layout |
||||
return layout |
||||
} |
||||
throw IllegalArgumentException("Here you should provide a layout id or a View") |
||||
} |
||||
|
||||
Timber.tag(tag()).d("mCachedView.parent: " + cachedView?.parent) |
||||
|
||||
cachedView?.run { |
||||
val viewParent = parent |
||||
if (viewParent != null && viewParent is ViewGroup) { |
||||
viewParent.removeView(this) |
||||
} |
||||
} |
||||
|
||||
return cachedView |
||||
} |
||||
|
||||
/** |
||||
* 使用此方法提供的布局,将只会被缓存起来,即此方法将只会被调用一次。 |
||||
* |
||||
* @return provide a layout id or a View |
||||
*/ |
||||
protected open fun provideLayout(): Any? = null |
||||
|
||||
final override fun onViewCreated(view: View, savedInstanceState: Bundle?) { |
||||
super.onViewCreated(view, savedInstanceState) |
||||
Timber.tag(tag()).d("-->onViewCreated savedInstanceState = %s", savedInstanceState) |
||||
if (layoutView !== view) { |
||||
layoutView = view |
||||
internalOnViewPrepared(view, savedInstanceState) |
||||
onViewPrepared(view, savedInstanceState) |
||||
} |
||||
fragmentDelegates.onViewCreated(view, savedInstanceState) |
||||
} |
||||
|
||||
internal open fun internalOnViewPrepared(view: View, savedInstanceState: Bundle?) {} |
||||
|
||||
/** |
||||
* View is prepared, If [androidx.fragment.app.Fragment.onCreateView] return same layout, it will be called once |
||||
* |
||||
* @param view view of fragment |
||||
*/ |
||||
protected open fun onViewPrepared(view: View, savedInstanceState: Bundle?) {} |
||||
|
||||
override fun onActivityCreated(savedInstanceState: Bundle?) { |
||||
super.onActivityCreated(savedInstanceState) |
||||
Timber.tag(tag()).d("-->onActivityCreated savedInstanceState = $savedInstanceState") |
||||
fragmentDelegates.onActivityCreated(savedInstanceState) |
||||
} |
||||
|
||||
override fun onStart() { |
||||
super.onStart() |
||||
Timber.tag(tag()).d("-->onStart") |
||||
fragmentDelegates.onStart() |
||||
} |
||||
|
||||
override fun onResume() { |
||||
super.onResume() |
||||
Timber.tag(tag()).d("-->onResume") |
||||
fragmentDelegates.onResume() |
||||
} |
||||
|
||||
override fun onPause() { |
||||
Timber.tag(tag()).d("-->onPause") |
||||
fragmentDelegates.onPause() |
||||
super.onPause() |
||||
} |
||||
|
||||
override fun onStop() { |
||||
Timber.tag(tag()).d("-->onStop") |
||||
fragmentDelegates.onStop() |
||||
super.onStop() |
||||
} |
||||
|
||||
override fun onDestroyView() { |
||||
Timber.tag(tag()).d("-->onDestroyView") |
||||
fragmentDelegates.onDestroyView() |
||||
super.onDestroyView() |
||||
} |
||||
|
||||
override fun onDestroy() { |
||||
Timber.tag(tag()).d("-->onDestroy") |
||||
fragmentDelegates.onDestroy() |
||||
super.onDestroy() |
||||
dismissLoadingDialog() |
||||
} |
||||
|
||||
override fun onDetach() { |
||||
Timber.tag(tag()).d("-->onDetach") |
||||
fragmentDelegates.onDetach() |
||||
super.onDetach() |
||||
} |
||||
|
||||
override fun onSaveInstanceState(outState: Bundle) { |
||||
fragmentDelegates.onSaveInstanceState(outState) |
||||
saveInstanceState(outState, _state) |
||||
super.onSaveInstanceState(outState) |
||||
} |
||||
|
||||
override fun setUserVisibleHint(isVisibleToUser: Boolean) { |
||||
super.setUserVisibleHint(isVisibleToUser) |
||||
Timber.tag(tag()).d("-->setUserVisibleHint ==$isVisibleToUser") |
||||
fragmentDelegates.setUserVisibleHint(isVisibleToUser) |
||||
} |
||||
|
||||
override fun onHiddenChanged(hidden: Boolean) { |
||||
super.onHiddenChanged(hidden) |
||||
Timber.tag(tag()).d("-->onHiddenChanged = $hidden") |
||||
fragmentDelegates.onHiddenChanged(hidden) |
||||
} |
||||
|
||||
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>, grantResults: IntArray) { |
||||
super.onRequestPermissionsResult(requestCode, permissions, grantResults) |
||||
fragmentDelegates.onRequestPermissionsResult(requestCode, permissions, grantResults) |
||||
} |
||||
|
||||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { |
||||
super.onActivityResult(requestCode, resultCode, data) |
||||
fragmentDelegates.onActivityResult(requestCode, resultCode, data) |
||||
} |
||||
|
||||
@UiThread |
||||
override fun addDelegate(fragmentDelegate: FragmentDelegate<*>?) { |
||||
fragmentDelegates.addDelegate(fragmentDelegate) |
||||
} |
||||
|
||||
@UiThread |
||||
override fun removeDelegate(fragmentDelegate: FragmentDelegate<*>?): Boolean { |
||||
return fragmentDelegates.removeDelegate(fragmentDelegate) |
||||
} |
||||
|
||||
override fun findDelegate(predicate: Predicate<FragmentDelegate<*>?>?): FragmentDelegate<*>? { |
||||
return fragmentDelegates.findDelegate(predicate) |
||||
} |
||||
|
||||
final override fun onBackPressed(): Boolean { |
||||
return handleBackPress() || BackHandlerHelper.handleBackPress(this) |
||||
} |
||||
|
||||
/** |
||||
* Fragment需要自己处理BackPress事件,如果不处理,就交给子Fragment处理。都不处理则由Activity处理 |
||||
*/ |
||||
protected open fun handleBackPress(): Boolean { |
||||
return false |
||||
} |
||||
|
||||
private fun loadingView(): LoadingView { |
||||
val loadingViewImpl = loadingView |
||||
return if (loadingViewImpl != null) { |
||||
loadingViewImpl |
||||
} else { |
||||
loadingView = onCreateLoadingView() ?: Sword.loadingViewFactory?.invoke(requireContext()) |
||||
loadingView ?: throw NullPointerException("you need to config LoadingViewFactory in Sword or implement onCreateLoadingView.") |
||||
} |
||||
} |
||||
|
||||
protected open fun onCreateLoadingView(): LoadingView? { |
||||
return null |
||||
} |
||||
|
||||
override fun showLoadingDialog() { |
||||
recentShowingDialogTime = System.currentTimeMillis() |
||||
loadingView().showLoadingDialog(true) |
||||
} |
||||
|
||||
override fun showLoadingDialog(cancelable: Boolean) { |
||||
recentShowingDialogTime = System.currentTimeMillis() |
||||
loadingView().showLoadingDialog(cancelable) |
||||
} |
||||
|
||||
override fun showLoadingDialog(message: CharSequence, cancelable: Boolean) { |
||||
recentShowingDialogTime = System.currentTimeMillis() |
||||
loadingView().showLoadingDialog(message, cancelable) |
||||
} |
||||
|
||||
override fun showLoadingDialog(@StringRes messageId: Int, cancelable: Boolean) { |
||||
recentShowingDialogTime = System.currentTimeMillis() |
||||
loadingView().showLoadingDialog(messageId, cancelable) |
||||
} |
||||
|
||||
override fun dismissLoadingDialog() { |
||||
loadingView().dismissLoadingDialog() |
||||
} |
||||
|
||||
override fun dismissLoadingDialog(minimumMills: Long, onDismiss: () -> Unit) { |
||||
dismissDialog(recentShowingDialogTime, minimumMills, onDismiss) |
||||
} |
||||
|
||||
override fun isLoadingDialogShowing(): Boolean { |
||||
return loadingView().isLoadingDialogShowing() |
||||
} |
||||
|
||||
override fun showMessage(message: CharSequence) { |
||||
loadingView().showMessage(message) |
||||
} |
||||
|
||||
override fun showMessage(@StringRes messageId: Int) { |
||||
loadingView().showMessage(messageId) |
||||
} |
||||
|
||||
override fun onCreateAnimation(transit: Int, enter: Boolean, nextAnim: Int): Animation? { |
||||
if (fragmentAnimatorHelper == null) { |
||||
fragmentAnimatorHelper = FragmentAnimatorHelper(requireContext(), FragmentConfig.defaultFragmentAnimator()) |
||||
} |
||||
return fragmentAnimatorHelper?.onCreateAnimation(transit, enter) |
||||
} |
||||
|
||||
} |
@ -0,0 +1,89 @@ |
||||
package com.android.base.app.fragment |
||||
|
||||
import android.os.Bundle |
||||
import androidx.recyclerview.widget.RecyclerView.Adapter |
||||
import com.android.base.adapter.DataManager |
||||
import com.android.base.app.ui.AutoPageNumber |
||||
import com.android.base.app.ui.PageNumber |
||||
import com.android.base.app.ui.RefreshListLayout |
||||
import com.ztiany.loadmore.adapter.ILoadMore |
||||
import com.ztiany.loadmore.adapter.OnLoadMoreListener |
||||
import com.ztiany.loadmore.adapter.WrapperAdapter |
||||
|
||||
/** |
||||
* @author Ztiany |
||||
* date : 2016-03-19 23:09 |
||||
* email: 1169654504@qq.com |
||||
* @see [BaseListFragment] |
||||
*/ |
||||
abstract class BaseListDialogFragment<T> : BaseStateDialogFragment(), RefreshListLayout<T> { |
||||
|
||||
/**加载更多*/ |
||||
private var loadMore: ILoadMore? = null |
||||
|
||||
/**列表数据管理*/ |
||||
private lateinit var dataManager: DataManager<T> |
||||
|
||||
/**分页页码*/ |
||||
private var pageNumber: PageNumber? = null |
||||
|
||||
override fun onActivityCreated(savedInstanceState: Bundle?) { |
||||
super.onActivityCreated(savedInstanceState) |
||||
if (!::dataManager.isInitialized) { |
||||
throw NullPointerException("you need set DataManager") |
||||
} |
||||
} |
||||
|
||||
protected fun setDataManager(dataManager: DataManager<T>) { |
||||
this.dataManager = dataManager |
||||
} |
||||
|
||||
protected fun setupLoadMore(recyclerAdapter: Adapter<*>, pageNumber: PageNumber = AutoPageNumber(this, dataManager)): Adapter<*> { |
||||
this.pageNumber = pageNumber |
||||
|
||||
return WrapperAdapter.wrap(recyclerAdapter).apply { |
||||
setOnLoadMoreListener(object : OnLoadMoreListener { |
||||
override fun onLoadMore() { |
||||
this@BaseListDialogFragment.onLoadMore() |
||||
} |
||||
|
||||
override fun canLoadMore(): Boolean { |
||||
return !isRefreshing |
||||
} |
||||
}) |
||||
loadMore = this |
||||
} |
||||
} |
||||
|
||||
/**call by [.onRefresh] or [.onLoadMore], you can get current loading type from [.isRefreshing] or [.isLoadingMore].*/ |
||||
protected open fun onStartLoad() {} |
||||
|
||||
override fun onRefresh() = onStartLoad() |
||||
|
||||
protected fun onLoadMore() = onStartLoad() |
||||
|
||||
override fun canRefresh() = !isLoadingMore |
||||
|
||||
override fun replaceData(data: List<T>) = dataManager.replaceAll(data) |
||||
|
||||
override fun addData(data: List<T>) = dataManager.addItems(data) |
||||
|
||||
override fun isEmpty(): Boolean = dataManager.isEmpty |
||||
|
||||
override fun isLoadingMore(): Boolean = loadMore != null && loadMore?.isLoadingMore ?: false |
||||
|
||||
override fun getPager(): PageNumber = pageNumber |
||||
?: throw NullPointerException("you need to call setupLoadMore to init pageNumber") |
||||
|
||||
fun loadMoreController(): ILoadMore = loadMore |
||||
?: throw NullPointerException("you need to call setupLoadMore to init loadMoreController") |
||||
|
||||
override fun loadMoreCompleted(hasMore: Boolean) { |
||||
loadMore?.loadCompleted(hasMore) |
||||
} |
||||
|
||||
override fun loadMoreFailed() { |
||||
loadMore?.loadFail() |
||||
} |
||||
|
||||
} |
@ -0,0 +1,89 @@ |
||||
package com.android.base.app.fragment |
||||
|
||||
import android.os.Bundle |
||||
import androidx.recyclerview.widget.RecyclerView.Adapter |
||||
import com.android.base.adapter.DataManager |
||||
import com.android.base.app.ui.AutoPageNumber |
||||
import com.android.base.app.ui.PageNumber |
||||
import com.android.base.app.ui.RefreshListLayout |
||||
import com.ztiany.loadmore.adapter.ILoadMore |
||||
import com.ztiany.loadmore.adapter.OnLoadMoreListener |
||||
import com.ztiany.loadmore.adapter.WrapperAdapter |
||||
|
||||
/** |
||||
* 通用的基于 RecyclerView 的列表界面,支持下拉刷新和加载更多。 |
||||
* |
||||
* @param <T> 当前列表使用的数据类型 |
||||
* @author Ztiany |
||||
* date : 2016-03-19 23:09 |
||||
* email: 1169654504@qq.com |
||||
*/ |
||||
abstract class BaseListFragment<T> : BaseStateFragment(), RefreshListLayout<T> { |
||||
|
||||
/**加载更多*/ |
||||
private var loadMore: ILoadMore? = null |
||||
|
||||
/**列表数据管理*/ |
||||
private lateinit var dataManager: DataManager<T> |
||||
|
||||
/**分页页码*/ |
||||
private var pageNumber: PageNumber? = null |
||||
|
||||
override fun onActivityCreated(savedInstanceState: Bundle?) { |
||||
super.onActivityCreated(savedInstanceState) |
||||
if (!::dataManager.isInitialized) { |
||||
throw NullPointerException("you need set DataManager") |
||||
} |
||||
} |
||||
|
||||
protected fun setDataManager(dataManager: DataManager<T>) { |
||||
this.dataManager = dataManager |
||||
} |
||||
|
||||
protected fun setupLoadMore(recyclerAdapter: Adapter<*>, pageNumber: PageNumber = AutoPageNumber(this, dataManager)): Adapter<*> { |
||||
this.pageNumber = pageNumber |
||||
|
||||
return WrapperAdapter.wrap(recyclerAdapter).apply { |
||||
setOnLoadMoreListener(object : OnLoadMoreListener { |
||||
override fun onLoadMore() { |
||||
this@BaseListFragment.onLoadMore() |
||||
} |
||||
|
||||
override fun canLoadMore(): Boolean { |
||||
return !isRefreshing |
||||
} |
||||
}) |
||||
loadMore = this |
||||
} |
||||
} |
||||
|
||||
/**call by [.onRefresh] or [.onLoadMore], you can get current loading type from [.isRefreshing] or [.isLoadingMore].*/ |
||||
protected open fun onStartLoad() {} |
||||
|
||||
override fun onRefresh() = onStartLoad() |
||||
|
||||
protected fun onLoadMore() = onStartLoad() |
||||
|
||||
override fun canRefresh() = !isLoadingMore |
||||
|
||||
override fun replaceData(data: List<T>)= dataManager.replaceAll(data) |
||||
|
||||
override fun addData(data: List<T>) = dataManager.addItems(data) |
||||
|
||||
override fun isEmpty(): Boolean = dataManager.isEmpty |
||||
|
||||
override fun isLoadingMore(): Boolean = loadMore != null && loadMore?.isLoadingMore ?: false |
||||
|
||||
override fun getPager(): PageNumber = pageNumber ?: throw NullPointerException("you need to call setupLoadMore to init pageNumber") |
||||
|
||||
fun loadMoreController(): ILoadMore = loadMore ?: throw NullPointerException("you need to call setupLoadMore to init loadMoreController") |
||||
|
||||
override fun loadMoreCompleted(hasMore: Boolean) { |
||||
loadMore?.loadCompleted(hasMore) |
||||
} |
||||
|
||||
override fun loadMoreFailed() { |
||||
loadMore?.loadFail() |
||||
} |
||||
|
||||
} |
@ -0,0 +1,119 @@ |
||||
package com.android.base.app.fragment |
||||
|
||||
import android.os.Bundle |
||||
import android.view.View |
||||
import com.android.base.adapter.DataManager |
||||
import com.android.base.app.ui.AutoPageNumber |
||||
import com.android.base.app.ui.RefreshListLayout |
||||
import com.android.base.app.ui.StateLayoutConfig |
||||
|
||||
/** |
||||
* 区别于 [BaseListFragment] 只能支持 RecyclerView。[BaseListFragment] 采用包装 [androidx.recyclerview.widget.RecyclerView.Adapter] 的方式, |
||||
* 在底部添加 load more view 的 item,来实现加载更多。[BaseListV2DialogFragment] 没有采用此种方式,所以你使用的 RefreshView 应该是支持这下来刷新和加载更多功能的。 |
||||
* |
||||
* 在调用 [BaseListV2DialogFragment] 的 [onActivityCreated] 之前,你应该设置好 [dataManager]。 |
||||
* |
||||
*@author Ztiany |
||||
* Email: ztiany3@gmail.com |
||||
* Date : 2019-03-26 15:06 |
||||
*/ |
||||
abstract class BaseListV2DialogFragment<T> : BaseFragment(), RefreshListLayout<T> { |
||||
|
||||
private lateinit var stateLayout: RefreshLoadMoreStateLayoutImpl |
||||
|
||||
protected open lateinit var dataManager: DataManager<T> |
||||
|
||||
override fun internalOnViewPrepared(view: View, savedInstanceState: Bundle?) { |
||||
stateLayout = RefreshLoadMoreStateLayoutImpl.init(view) |
||||
stateLayout.refreshView.setRefreshHandler { |
||||
onRefresh() |
||||
} |
||||
stateLayout.refreshView.setLoadMoreHandler { |
||||
onLoadMore() |
||||
} |
||||
stateLayout.setStateRetryListener(this::onRetry) |
||||
} |
||||
|
||||
protected open fun onRetry(state: Int) { |
||||
if (!isRefreshing) { |
||||
autoRefresh() |
||||
} |
||||
} |
||||
|
||||
protected open fun onRefresh() = onStartLoad() |
||||
|
||||
protected open fun onLoadMore() = onStartLoad() |
||||
|
||||
/** call by [onRefresh] or [onLoadMore], you can get current loading type from [isRefreshing] or [isLoadingMore]. */ |
||||
protected open fun onStartLoad() {} |
||||
|
||||
override fun onActivityCreated(savedInstanceState: Bundle?) { |
||||
super.onActivityCreated(savedInstanceState) |
||||
if (!::dataManager.isInitialized) { |
||||
throw NullPointerException("you need set DataManager") |
||||
} |
||||
} |
||||
|
||||
override fun onDestroyView() { |
||||
super.onDestroyView() |
||||
refreshCompleted() |
||||
} |
||||
|
||||
override fun replaceData(data: MutableList<T>?) { |
||||
dataManager.replaceAll(data) |
||||
} |
||||
|
||||
override fun addData(data: MutableList<T>?) { |
||||
dataManager.addItems(data) |
||||
} |
||||
|
||||
fun setRefreshEnable(enable: Boolean) = stateLayout.refreshView.setRefreshEnable(enable) |
||||
|
||||
fun setLoadMoreEnable(enable: Boolean) = stateLayout.refreshView.setLoadMoreEnable(enable) |
||||
|
||||
override fun getPager() = AutoPageNumber(this, dataManager) |
||||
|
||||
override fun isEmpty() = dataManager.isEmpty |
||||
|
||||
override fun loadMoreCompleted(hasMore: Boolean) = stateLayout.refreshView.loadMoreCompleted(hasMore) |
||||
|
||||
override fun loadMoreFailed() = stateLayout.refreshView.loadMoreFailed() |
||||
|
||||
override fun isRefreshing() = stateLayout.refreshView.isRefreshing |
||||
|
||||
override fun showContentLayout() = stateLayout.showContentLayout() |
||||
|
||||
override fun showLoadingLayout() = stateLayout.showLoadingLayout() |
||||
|
||||
override fun refreshCompleted() = stateLayout.refreshView.refreshCompleted() |
||||
|
||||
override fun showEmptyLayout() = stateLayout.showEmptyLayout() |
||||
|
||||
override fun showErrorLayout() = stateLayout.showErrorLayout() |
||||
|
||||
override fun showRequesting() = stateLayout.showRequesting() |
||||
|
||||
override fun showBlank() = stateLayout.showBlank() |
||||
|
||||
override fun getStateLayoutConfig() = stateLayout.stateLayoutConfig |
||||
|
||||
override fun autoRefresh() = stateLayout.refreshView.autoRefresh() |
||||
|
||||
override fun showNetErrorLayout() = stateLayout.showNetErrorLayout() |
||||
|
||||
override fun showServerErrorLayout() = stateLayout.showServerErrorLayout() |
||||
|
||||
override fun isLoadingMore() = stateLayout.refreshView.isLoadingMore |
||||
|
||||
override fun currentStatus() = stateLayout.currentStatus() |
||||
|
||||
companion object { |
||||
const val CONTENT = StateLayoutConfig.CONTENT |
||||
const val LOADING = StateLayoutConfig.LOADING |
||||
const val ERROR = StateLayoutConfig.ERROR |
||||
const val EMPTY = StateLayoutConfig.EMPTY |
||||
const val NET_ERROR = StateLayoutConfig.NET_ERROR |
||||
const val SERVER_ERROR = StateLayoutConfig.SERVER_ERROR |
||||
} |
||||
|
||||
} |
@ -0,0 +1,115 @@ |
||||
package com.android.base.app.fragment |
||||
|
||||
import android.os.Bundle |
||||
import android.view.View |
||||
import com.android.base.adapter.DataManager |
||||
import com.android.base.app.ui.AutoPageNumber |
||||
import com.android.base.app.ui.RefreshListLayout |
||||
import com.android.base.app.ui.StateLayoutConfig |
||||
|
||||
/** |
||||
*@author Ztiany |
||||
* Email: ztiany3@gmail.com |
||||
* Date : 2019-03-26 15:06 |
||||
*@see [BaseListV2Fragment] |
||||
*/ |
||||
abstract class BaseListV2Fragment<T> : BaseDialogFragment(), RefreshListLayout<T> { |
||||
|
||||
private lateinit var stateLayout: RefreshLoadMoreStateLayoutImpl |
||||
|
||||
protected open lateinit var dataManager: DataManager<T> |
||||
|
||||
override fun internalOnViewPrepared(view: View, savedInstanceState: Bundle?) { |
||||
stateLayout = RefreshLoadMoreStateLayoutImpl.init(view) |
||||
stateLayout.refreshView.setRefreshHandler { |
||||
onRefresh() |
||||
} |
||||
stateLayout.refreshView.setLoadMoreHandler { |
||||
onLoadMore() |
||||
} |
||||
stateLayout.setStateRetryListener(this::onRetry) |
||||
} |
||||
|
||||
protected open fun onRetry(state: Int) { |
||||
if (!isRefreshing) { |
||||
autoRefresh() |
||||
} |
||||
} |
||||
|
||||
protected open fun onRefresh() = onStartLoad() |
||||
|
||||
protected open fun onLoadMore() = onStartLoad() |
||||
|
||||
/** call by [onRefresh] or [onLoadMore], you can get current loading type from [isRefreshing] or [isLoadingMore]. */ |
||||
protected open fun onStartLoad() {} |
||||
|
||||
override fun onActivityCreated(savedInstanceState: Bundle?) { |
||||
super.onActivityCreated(savedInstanceState) |
||||
if (!::dataManager.isInitialized) { |
||||
throw NullPointerException("you need set DataManager") |
||||
} |
||||
} |
||||
|
||||
override fun onDestroyView() { |
||||
super.onDestroyView() |
||||
refreshCompleted() |
||||
} |
||||
|
||||
override fun replaceData(data: MutableList<T>?) { |
||||
dataManager.replaceAll(data) |
||||
} |
||||
|
||||
override fun addData(data: MutableList<T>?) { |
||||
dataManager.addItems(data) |
||||
} |
||||
|
||||
fun setRefreshEnable(enable: Boolean) = stateLayout.refreshView.setRefreshEnable(enable) |
||||
|
||||
fun setLoadMoreEnable(enable: Boolean) = stateLayout.refreshView.setLoadMoreEnable(enable) |
||||
|
||||
override fun getPager() = AutoPageNumber(this, dataManager) |
||||
|
||||
override fun isEmpty() = dataManager.isEmpty |
||||
|
||||
override fun loadMoreCompleted(hasMore: Boolean) = stateLayout.refreshView.loadMoreCompleted(hasMore) |
||||
|
||||
override fun loadMoreFailed() = stateLayout.refreshView.loadMoreFailed() |
||||
|
||||
override fun isRefreshing() = stateLayout.refreshView.isRefreshing |
||||
|
||||
override fun showContentLayout() = stateLayout.showContentLayout() |
||||
|
||||
override fun showLoadingLayout() = stateLayout.showLoadingLayout() |
||||
|
||||
override fun refreshCompleted() = stateLayout.refreshView.refreshCompleted() |
||||
|
||||
override fun showEmptyLayout() = stateLayout.showEmptyLayout() |
||||
|
||||
override fun showErrorLayout() = stateLayout.showErrorLayout() |
||||
|
||||
override fun showRequesting() = stateLayout.showRequesting() |
||||
|
||||
override fun showBlank() = stateLayout.showBlank() |
||||
|
||||
override fun getStateLayoutConfig() = stateLayout.stateLayoutConfig |
||||
|
||||
override fun autoRefresh() = stateLayout.refreshView.autoRefresh() |
||||
|
||||
override fun showNetErrorLayout() = stateLayout.showNetErrorLayout() |
||||
|
||||
override fun showServerErrorLayout() = stateLayout.showServerErrorLayout() |
||||
|
||||
override fun isLoadingMore() = stateLayout.refreshView.isLoadingMore |
||||
|
||||
override fun currentStatus() = stateLayout.currentStatus() |
||||
|
||||
companion object { |
||||
const val CONTENT = StateLayoutConfig.CONTENT |
||||
const val LOADING = StateLayoutConfig.LOADING |
||||
const val ERROR = StateLayoutConfig.ERROR |
||||
const val EMPTY = StateLayoutConfig.EMPTY |
||||
const val NET_ERROR = StateLayoutConfig.NET_ERROR |
||||
const val SERVER_ERROR = StateLayoutConfig.SERVER_ERROR |
||||
} |
||||
|
||||
} |
@ -0,0 +1,94 @@ |
||||
package com.android.base.app.fragment |
||||
|
||||
import android.os.Bundle |
||||
import android.view.View |
||||
import com.android.base.app.ui.RefreshStateLayout |
||||
import com.android.base.app.ui.RefreshView |
||||
import com.android.base.app.ui.StateLayoutConfig |
||||
import com.android.base.utils.common.ifNonNull |
||||
import com.android.base.utils.common.otherwise |
||||
|
||||
/** |
||||
* @author Ztiany |
||||
* date : 2016-03-19 23:09 |
||||
* email: 1169654504@qq.com |
||||
* @see BaseStateFragment |
||||
*/ |
||||
abstract class BaseStateDialogFragment : BaseDialogFragment(), RefreshStateLayout { |
||||
|
||||
private lateinit var stateLayout: RefreshableStateLayoutImpl |
||||
|
||||
override fun internalOnViewPrepared(view: View, savedInstanceState: Bundle?) { |
||||
stateLayout = RefreshableStateLayoutImpl.init(view) |
||||
stateLayout.setRefreshHandler(object : RefreshView.RefreshHandler() { |
||||
override fun onRefresh() { |
||||
this@BaseStateDialogFragment.onRefresh() |
||||
} |
||||
|
||||
override fun canRefresh(): Boolean { |
||||
return this@BaseStateDialogFragment.canRefresh() |
||||
} |
||||
}) |
||||
stateLayout.setStateRetryListenerUnchecked { state -> onRetry(state) } |
||||
} |
||||
|
||||
override fun onDestroyView() { |
||||
super.onDestroyView() |
||||
refreshCompleted() |
||||
} |
||||
|
||||
internal open fun canRefresh() = true |
||||
|
||||
private val refreshView: RefreshView? |
||||
get() = stateLayout.refreshView |
||||
|
||||
protected open fun onRetry(@StateLayoutConfig.RetryableState state: Int) { |
||||
refreshView.ifNonNull { |
||||
if (!this.isRefreshing) { |
||||
autoRefresh() |
||||
} |
||||
} otherwise { |
||||
onRefresh() |
||||
} |
||||
} |
||||
|
||||
protected open fun onRefresh() {} |
||||
|
||||
fun setRefreshEnable(enable: Boolean) = refreshView?.setRefreshEnable(enable) |
||||
|
||||
override fun getStateLayoutConfig(): StateLayoutConfig = stateLayout.stateLayoutConfig |
||||
|
||||
override fun isRefreshing() = stateLayout.isRefreshing |
||||
|
||||
override fun refreshCompleted() = stateLayout.refreshCompleted() |
||||
|
||||
override fun autoRefresh() = stateLayout.autoRefresh() |
||||
|
||||
override fun showContentLayout() = stateLayout.showContentLayout() |
||||
|
||||
override fun showLoadingLayout() = stateLayout.showLoadingLayout() |
||||
|
||||
override fun showEmptyLayout() = stateLayout.showEmptyLayout() |
||||
|
||||
override fun showErrorLayout() = stateLayout.showErrorLayout() |
||||
|
||||
override fun showRequesting() = stateLayout.showRequesting() |
||||
|
||||
override fun showBlank() = stateLayout.showBlank() |
||||
|
||||
override fun showNetErrorLayout() = stateLayout.showNetErrorLayout() |
||||
|
||||
override fun showServerErrorLayout() = stateLayout.showServerErrorLayout() |
||||
|
||||
override fun currentStatus() = stateLayout.currentStatus() |
||||
|
||||
companion object { |
||||
const val CONTENT = StateLayoutConfig.CONTENT |
||||
const val LOADING = StateLayoutConfig.LOADING |
||||
const val ERROR = StateLayoutConfig.ERROR |
||||
const val EMPTY = StateLayoutConfig.EMPTY |
||||
const val NET_ERROR = StateLayoutConfig.NET_ERROR |
||||
const val SERVER_ERROR = StateLayoutConfig.SERVER_ERROR |
||||
} |
||||
|
||||
} |
@ -0,0 +1,101 @@ |
||||
package com.android.base.app.fragment |
||||
|
||||
import android.os.Bundle |
||||
import android.view.View |
||||
import com.android.base.R |
||||
import com.android.base.app.ui.RefreshStateLayout |
||||
import com.android.base.app.ui.RefreshView |
||||
import com.android.base.app.ui.RefreshView.RefreshHandler |
||||
import com.android.base.app.ui.StateLayoutConfig |
||||
import com.android.base.app.ui.StateLayoutConfig.RetryableState |
||||
import com.android.base.utils.common.ifNonNull |
||||
import com.android.base.utils.common.otherwise |
||||
|
||||
/** |
||||
* 1. 支持显示{CONTENT, LOADING, ERROR, EMPTY}等状态布局、支持下拉刷新 |
||||
* 2. 使用的布局中必须有一个id = [R.id.base_state_layout] 的 Layout,确保 Layout 实现了[com.android.base.app.ui.StateLayout] |
||||
* 3. [RefreshView] (下拉刷新)的 id 必须设置为 :[R.id.base_refresh_layout],没有添加则表示不需要下拉刷新功能 |
||||
* 4. 默认所有重试和下拉刷新都会调用 [onRefresh],子类可以修改该行为 |
||||
* |
||||
* @author Ztiany |
||||
* date : 2016-03-19 23:09 |
||||
* email: 1169654504@qq.com |
||||
*/ |
||||
abstract class BaseStateFragment : BaseFragment(), RefreshStateLayout { |
||||
|
||||
private lateinit var stateLayout: RefreshableStateLayoutImpl |
||||
|
||||
override fun internalOnViewPrepared(view: View, savedInstanceState: Bundle?) { |
||||
stateLayout = RefreshableStateLayoutImpl.init(view) |
||||
stateLayout.setRefreshHandler(object : RefreshHandler() { |
||||
override fun onRefresh() { |
||||
this@BaseStateFragment.onRefresh() |
||||
} |
||||
|
||||
override fun canRefresh(): Boolean { |
||||
return this@BaseStateFragment.canRefresh() |
||||
} |
||||
}) |
||||
stateLayout.setStateRetryListenerUnchecked { state -> onRetry(state) } |
||||
} |
||||
|
||||
override fun onDestroyView() { |
||||
super.onDestroyView() |
||||
refreshCompleted() |
||||
} |
||||
|
||||
internal open fun canRefresh() = true |
||||
|
||||
private val refreshView: RefreshView? |
||||
get() = stateLayout.refreshView |
||||
|
||||
protected open fun onRetry(@RetryableState state: Int) { |
||||
refreshView.ifNonNull { |
||||
if (!this.isRefreshing) { |
||||
autoRefresh() |
||||
} |
||||
} otherwise { |
||||
onRefresh() |
||||
} |
||||
} |
||||
|
||||
protected open fun onRefresh() {} |
||||
|
||||
fun setRefreshEnable(enable: Boolean) = refreshView?.setRefreshEnable(enable) |
||||
|
||||
override fun getStateLayoutConfig(): StateLayoutConfig = stateLayout.stateLayoutConfig |
||||
|
||||
override fun isRefreshing() = stateLayout.isRefreshing |
||||
|
||||
override fun refreshCompleted() = stateLayout.refreshCompleted() |
||||
|
||||
override fun autoRefresh() = stateLayout.autoRefresh() |
||||
|
||||
override fun showContentLayout() = stateLayout.showContentLayout() |
||||
|
||||
override fun showLoadingLayout() = stateLayout.showLoadingLayout() |
||||
|
||||
override fun showEmptyLayout() = stateLayout.showEmptyLayout() |
||||
|
||||
override fun showErrorLayout() = stateLayout.showErrorLayout() |
||||
|
||||
override fun showRequesting() = stateLayout.showRequesting() |
||||
|
||||
override fun showBlank() = stateLayout.showBlank() |
||||
|
||||
override fun showNetErrorLayout() = stateLayout.showNetErrorLayout() |
||||
|
||||
override fun showServerErrorLayout() = stateLayout.showServerErrorLayout() |
||||
|
||||
override fun currentStatus() = stateLayout.currentStatus() |
||||
|
||||
companion object { |
||||
const val CONTENT = StateLayoutConfig.CONTENT |
||||
const val LOADING = StateLayoutConfig.LOADING |
||||
const val ERROR = StateLayoutConfig.ERROR |
||||
const val EMPTY = StateLayoutConfig.EMPTY |
||||
const val NET_ERROR = StateLayoutConfig.NET_ERROR |
||||
const val SERVER_ERROR = StateLayoutConfig.SERVER_ERROR |
||||
} |
||||
|
||||
} |
@ -0,0 +1,169 @@ |
||||
package com.android.base.app.fragment; |
||||
|
||||
import android.content.Context; |
||||
import android.content.Intent; |
||||
import android.os.Bundle; |
||||
import android.view.View; |
||||
|
||||
import com.android.base.app.fragment.delegates.FragmentDelegate; |
||||
import com.android.base.app.fragment.delegates.FragmentDelegateOwner; |
||||
import com.github.dmstocking.optional.java.util.function.Predicate; |
||||
|
||||
import java.util.ArrayList; |
||||
import java.util.List; |
||||
|
||||
import androidx.annotation.NonNull; |
||||
import androidx.annotation.Nullable; |
||||
import androidx.annotation.UiThread; |
||||
import androidx.fragment.app.Fragment; |
||||
|
||||
|
||||
@UiThread |
||||
final class FragmentDelegates implements FragmentDelegate<Fragment>, FragmentDelegateOwner { |
||||
|
||||
private final Fragment mDelegateOwner; |
||||
private List<FragmentDelegate> mDelegates = new ArrayList<>(4); |
||||
|
||||
<T extends Fragment & FragmentDelegateOwner> FragmentDelegates(T delegateOwner) { |
||||
mDelegateOwner = delegateOwner; |
||||
} |
||||
|
||||
@Override |
||||
public void onAttach(Context context) { |
||||
for (FragmentDelegate fragmentDelegate : mDelegates) { |
||||
fragmentDelegate.onAttach(context); |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
public void onCreate(@Nullable Bundle savedInstanceState) { |
||||
for (FragmentDelegate fragmentDelegate : mDelegates) { |
||||
fragmentDelegate.onCreate(savedInstanceState); |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { |
||||
for (FragmentDelegate fragmentDelegate : mDelegates) { |
||||
fragmentDelegate.onViewCreated(view, savedInstanceState); |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
public void onActivityCreated(@Nullable Bundle savedInstanceState) { |
||||
for (FragmentDelegate fragmentDelegate : mDelegates) { |
||||
fragmentDelegate.onActivityCreated(savedInstanceState); |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
public void onStart() { |
||||
for (FragmentDelegate fragmentDelegate : mDelegates) { |
||||
fragmentDelegate.onStart(); |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
public void onResume() { |
||||
for (FragmentDelegate fragmentDelegate : mDelegates) { |
||||
fragmentDelegate.onResume(); |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
public void onPause() { |
||||
for (FragmentDelegate fragmentDelegate : mDelegates) { |
||||
fragmentDelegate.onPause(); |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
public void onStop() { |
||||
for (FragmentDelegate fragmentDelegate : mDelegates) { |
||||
fragmentDelegate.onStop(); |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
public void onDestroy() { |
||||
for (FragmentDelegate fragmentDelegate : mDelegates) { |
||||
fragmentDelegate.onDestroy(); |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
public void onDestroyView() { |
||||
for (FragmentDelegate fragmentDelegate : mDelegates) { |
||||
fragmentDelegate.onDestroyView(); |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
public void onDetach() { |
||||
for (FragmentDelegate fragmentDelegate : mDelegates) { |
||||
fragmentDelegate.onDetach(); |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
public void setUserVisibleHint(boolean isVisibleToUser) { |
||||
for (FragmentDelegate fragmentDelegate : mDelegates) { |
||||
fragmentDelegate.setUserVisibleHint(isVisibleToUser); |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
public void onSaveInstanceState(Bundle savedInstanceState) { |
||||
for (FragmentDelegate fragmentDelegate : mDelegates) { |
||||
fragmentDelegate.onSaveInstanceState(savedInstanceState); |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
public void onHiddenChanged(boolean hidden) { |
||||
for (FragmentDelegate fragmentDelegate : mDelegates) { |
||||
fragmentDelegate.onHiddenChanged(hidden); |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
public void onActivityResult(int requestCode, int resultCode, Intent data) { |
||||
for (FragmentDelegate fragmentDelegate : mDelegates) { |
||||
fragmentDelegate.onActivityResult(requestCode, resultCode, data); |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) { |
||||
for (FragmentDelegate fragmentDelegate : mDelegates) { |
||||
fragmentDelegate.onRequestPermissionsResult(requestCode, permissions, grantResults); |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
@SuppressWarnings("unchecked") |
||||
public void addDelegate(FragmentDelegate fragmentDelegate) { |
||||
mDelegates.add(fragmentDelegate); |
||||
fragmentDelegate.onAttachToFragment(mDelegateOwner); |
||||
} |
||||
|
||||
@Override |
||||
public boolean removeDelegate(FragmentDelegate fragmentDelegate) { |
||||
boolean remove = mDelegates.remove(fragmentDelegate); |
||||
if (remove) { |
||||
fragmentDelegate.onDetachFromFragment(); |
||||
} |
||||
return remove; |
||||
} |
||||
|
||||
@Override |
||||
public FragmentDelegate findDelegate(Predicate<FragmentDelegate> predicate) { |
||||
for (FragmentDelegate delegate : mDelegates) { |
||||
if (predicate.test(delegate)) { |
||||
return delegate; |
||||
} |
||||
} |
||||
return null; |
||||
} |
||||
|
||||
} |
@ -0,0 +1,85 @@ |
||||
package com.android.base.app.fragment |
||||
|
||||
import android.graphics.drawable.Drawable |
||||
import android.view.View |
||||
import com.android.base.app.ui.* |
||||
import com.android.base.widget.StateProcessor |
||||
|
||||
internal class RefreshLoadMoreStateLayoutImpl private constructor(layout: View) : StateLayout, StateLayoutConfig { |
||||
|
||||
companion object { |
||||
fun init(view: View): RefreshLoadMoreStateLayoutImpl { |
||||
return RefreshLoadMoreStateLayoutImpl(view) |
||||
} |
||||
} |
||||
|
||||
private var _multiStateView: StateLayout? = layout.findViewById<View>(CommonId.STATE_ID) as? StateLayout |
||||
private var _refreshView: RefreshLoadMoreView |
||||
|
||||
val refreshView: RefreshLoadMoreView |
||||
get() = _refreshView |
||||
|
||||
init { |
||||
val refreshLayout = layout.findViewById<View>(CommonId.REFRESH_ID) |
||||
?: throw NullPointerException("You need to provide a refreshable layout with id R.id.base_refresh_layout in your xml.") |
||||
|
||||
_refreshView = RefreshLoadViewFactory.createRefreshView(refreshLayout) |
||||
} |
||||
|
||||
override fun showLoadingLayout() = checkMultiStateView().showLoadingLayout() |
||||
override fun showContentLayout() = checkMultiStateView().showContentLayout() |
||||
override fun showEmptyLayout() = checkMultiStateView().showEmptyLayout() |
||||
override fun showErrorLayout() = checkMultiStateView().showErrorLayout() |
||||
override fun showRequesting() = checkMultiStateView().showRequesting() |
||||
override fun showBlank() = checkMultiStateView().showBlank() |
||||
override fun showNetErrorLayout() = checkMultiStateView().showNetErrorLayout() |
||||
override fun showServerErrorLayout() = checkMultiStateView().showServerErrorLayout() |
||||
override fun currentStatus() = checkMultiStateView().currentStatus() |
||||
|
||||
override fun getStateLayoutConfig(): StateLayoutConfig = checkMultiStateView().stateLayoutConfig |
||||
|
||||
override fun setStateMessage(state: Int, message: CharSequence?): StateLayoutConfig { |
||||
checkMultiStateView().stateLayoutConfig.setStateMessage(state, message) |
||||
return checkMultiStateView().stateLayoutConfig |
||||
} |
||||
|
||||
override fun setStateIcon(state: Int, drawable: Drawable?): StateLayoutConfig { |
||||
checkMultiStateView().stateLayoutConfig.setStateIcon(state, drawable) |
||||
return checkMultiStateView().stateLayoutConfig |
||||
} |
||||
|
||||
override fun setStateIcon(state: Int, drawableId: Int): StateLayoutConfig { |
||||
checkMultiStateView().stateLayoutConfig.setStateIcon(state, drawableId) |
||||
return checkMultiStateView().stateLayoutConfig |
||||
} |
||||
|
||||
override fun setStateAction(state: Int, actionText: CharSequence?): StateLayoutConfig { |
||||
checkMultiStateView().stateLayoutConfig.setStateAction(state, actionText) |
||||
return checkMultiStateView().stateLayoutConfig |
||||
} |
||||
|
||||
override fun setStateRetryListener(retryActionListener: OnRetryActionListener?): StateLayoutConfig { |
||||
checkMultiStateView().stateLayoutConfig.setStateRetryListener(retryActionListener) |
||||
return checkMultiStateView().stateLayoutConfig |
||||
} |
||||
|
||||
override fun disableOperationWhenRequesting(disable: Boolean): StateLayoutConfig { |
||||
checkMultiStateView().stateLayoutConfig.disableOperationWhenRequesting(disable) |
||||
return checkMultiStateView().stateLayoutConfig |
||||
} |
||||
|
||||
override fun getProcessor(): StateProcessor { |
||||
return checkMultiStateView().stateLayoutConfig.processor |
||||
} |
||||
|
||||
override fun setMessageGravity(state: Int, gravity: Int): StateLayoutConfig { |
||||
checkMultiStateView().stateLayoutConfig.setMessageGravity(state, gravity) |
||||
return this |
||||
} |
||||
|
||||
private fun checkMultiStateView(): StateLayout { |
||||
return _multiStateView |
||||
?: throw IllegalStateException("Calling this function requires defining a view that implements StateLayout in the Layout") |
||||
} |
||||
|
||||
} |
@ -0,0 +1,207 @@ |
||||
package com.android.base.app.fragment; |
||||
|
||||
import android.graphics.drawable.Drawable; |
||||
import android.view.View; |
||||
|
||||
import com.android.base.app.ui.OnRetryActionListener; |
||||
import com.android.base.app.ui.RefreshStateLayout; |
||||
import com.android.base.app.ui.RefreshView; |
||||
import com.android.base.app.ui.RefreshViewFactory; |
||||
import com.android.base.app.ui.StateLayout; |
||||
import com.android.base.app.ui.StateLayoutConfig; |
||||
import com.android.base.widget.StateProcessor; |
||||
|
||||
import androidx.annotation.DrawableRes; |
||||
|
||||
import static com.android.base.app.ui.CommonId.REFRESH_ID; |
||||
import static com.android.base.app.ui.CommonId.STATE_ID; |
||||
|
||||
/** |
||||
* @author Ztiany |
||||
*/ |
||||
final class RefreshableStateLayoutImpl implements RefreshStateLayout, StateLayoutConfig { |
||||
|
||||
private StateLayout mMultiStateView; |
||||
private RefreshView mRefreshView; |
||||
private RefreshView.RefreshHandler mRefreshHandler; |
||||
|
||||
static RefreshableStateLayoutImpl init(View layoutView) { |
||||
return new RefreshableStateLayoutImpl(layoutView); |
||||
} |
||||
|
||||
private RefreshableStateLayoutImpl(View layoutView) { |
||||
setupBaseUiLogic(layoutView); |
||||
setupRefreshLogic(layoutView); |
||||
} |
||||
|
||||
RefreshView getRefreshView() { |
||||
return mRefreshView; |
||||
} |
||||
|
||||
void setRefreshHandler(RefreshView.RefreshHandler refreshHandler) { |
||||
mRefreshHandler = refreshHandler; |
||||
} |
||||
|
||||
void setStateRetryListenerUnchecked(OnRetryActionListener retryActionListener) { |
||||
if (mMultiStateView != null) { |
||||
setStateRetryListener(retryActionListener); |
||||
} |
||||
} |
||||
|
||||
@SuppressWarnings("all") |
||||
private void setupBaseUiLogic(View layoutView) { |
||||
mMultiStateView = (StateLayout) layoutView.findViewById(STATE_ID); |
||||
} |
||||
|
||||
private void setupRefreshLogic(View layoutView) { |
||||
View refreshLayout = layoutView.findViewById(REFRESH_ID); |
||||
if (refreshLayout == null) { |
||||
return; |
||||
} |
||||
mRefreshView = RefreshViewFactory.createRefreshView(refreshLayout); |
||||
mRefreshView.setRefreshHandler(new RefreshView.RefreshHandler() { |
||||
@Override |
||||
public boolean canRefresh() { |
||||
return mRefreshHandler.canRefresh(); |
||||
} |
||||
|
||||
@Override |
||||
public void onRefresh() { |
||||
mRefreshHandler.onRefresh(); |
||||
} |
||||
}); |
||||
} |
||||
|
||||
@Override |
||||
public final void autoRefresh() { |
||||
if (mRefreshView != null) { |
||||
mRefreshView.autoRefresh(); |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
public void showLoadingLayout() { |
||||
checkMultiStateView().showLoadingLayout(); |
||||
} |
||||
|
||||
@Override |
||||
public void showContentLayout() { |
||||
refreshCompleted(); |
||||
checkMultiStateView().showContentLayout(); |
||||
} |
||||
|
||||
@Override |
||||
public void showEmptyLayout() { |
||||
refreshCompleted(); |
||||
checkMultiStateView().showEmptyLayout(); |
||||
} |
||||
|
||||
@Override |
||||
public void showErrorLayout() { |
||||
refreshCompleted(); |
||||
checkMultiStateView().showErrorLayout(); |
||||
} |
||||
|
||||
@Override |
||||
public void showRequesting() { |
||||
checkMultiStateView().showRequesting(); |
||||
} |
||||
|
||||
@Override |
||||
public void showBlank() { |
||||
checkMultiStateView().showBlank(); |
||||
} |
||||
|
||||
@Override |
||||
public void showNetErrorLayout() { |
||||
refreshCompleted(); |
||||
checkMultiStateView().showNetErrorLayout(); |
||||
} |
||||
|
||||
@Override |
||||
public void showServerErrorLayout() { |
||||
refreshCompleted(); |
||||
checkMultiStateView().showServerErrorLayout(); |
||||
} |
||||
|
||||
@Override |
||||
public StateLayoutConfig getStateLayoutConfig() { |
||||
checkMultiStateView(); |
||||
return this; |
||||
} |
||||
|
||||
@Override |
||||
public int currentStatus() { |
||||
return mMultiStateView.currentStatus(); |
||||
} |
||||
|
||||
@Override |
||||
public void refreshCompleted() { |
||||
if (mRefreshView != null) { |
||||
mRefreshView.refreshCompleted(); |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
public boolean isRefreshing() { |
||||
if (mRefreshView != null) { |
||||
return mRefreshView.isRefreshing(); |
||||
} |
||||
return false; |
||||
} |
||||
|
||||
@Override |
||||
public StateLayoutConfig setStateMessage(@RetryableState int state, CharSequence message) { |
||||
checkMultiStateView().getStateLayoutConfig().setStateMessage(state, message); |
||||
return this; |
||||
} |
||||
|
||||
@Override |
||||
public StateLayoutConfig setMessageGravity(int state, int gravity) { |
||||
checkMultiStateView().getStateLayoutConfig().setMessageGravity(state, gravity); |
||||
return this; |
||||
} |
||||
|
||||
@Override |
||||
public StateLayoutConfig setStateIcon(@RetryableState int state, Drawable drawable) { |
||||
checkMultiStateView().getStateLayoutConfig().setStateIcon(state, drawable); |
||||
return this; |
||||
} |
||||
|
||||
@Override |
||||
public StateLayoutConfig setStateIcon(@RetryableState int state, @DrawableRes int drawableId) { |
||||
checkMultiStateView().getStateLayoutConfig().setStateIcon(state, drawableId); |
||||
return this; |
||||
} |
||||
|
||||
@Override |
||||
public StateLayoutConfig setStateAction(@RetryableState int state, CharSequence actionText) { |
||||
checkMultiStateView().getStateLayoutConfig().setStateAction(state, actionText); |
||||
return this; |
||||
} |
||||
|
||||
@Override |
||||
public StateLayoutConfig setStateRetryListener(OnRetryActionListener retryActionListener) { |
||||
checkMultiStateView().getStateLayoutConfig().setStateRetryListener(retryActionListener); |
||||
return this; |
||||
} |
||||
|
||||
@Override |
||||
public StateLayoutConfig disableOperationWhenRequesting(boolean disable) { |
||||
checkMultiStateView().getStateLayoutConfig().disableOperationWhenRequesting(disable); |
||||
return this; |
||||
} |
||||
|
||||
@Override |
||||
public StateProcessor getProcessor() { |
||||
return checkMultiStateView().getStateLayoutConfig().getProcessor(); |
||||
} |
||||
|
||||
private StateLayout checkMultiStateView() { |
||||
if (mMultiStateView == null) { |
||||
throw new IllegalStateException("Calling this function requires defining a view that implements StateLayout in the Layout"); |
||||
} |
||||
return mMultiStateView; |
||||
} |
||||
|
||||
} |
@ -0,0 +1,103 @@ |
||||
package com.android.base.app.fragment.animator |
||||
|
||||
import android.view.animation.* |
||||
import com.android.base.R |
||||
import com.android.base.utils.BaseUtils |
||||
|
||||
interface FragmentAnimator { |
||||
fun makeOpenEnter(): Animation? |
||||
fun makeOpenExit(): Animation? |
||||
fun makeCloseEnter(): Animation? |
||||
fun makeCloseExit(): Animation? |
||||
} |
||||
|
||||
class DefaultVerticalAnimator : FragmentAnimator { |
||||
override fun makeOpenEnter(): Animation? { |
||||
return AnimationUtils.loadAnimation(BaseUtils.getAppContext(), R.anim.v_fragment_open_enter) |
||||
} |
||||
|
||||
override fun makeOpenExit(): Animation? { |
||||
return AnimationUtils.loadAnimation(BaseUtils.getAppContext(), R.anim.v_fragment_open_exit) |
||||
} |
||||
|
||||
override fun makeCloseEnter(): Animation? { |
||||
return AnimationUtils.loadAnimation(BaseUtils.getAppContext(), R.anim.v_fragment_close_enter) |
||||
} |
||||
|
||||
override fun makeCloseExit(): Animation? { |
||||
return AnimationUtils.loadAnimation(BaseUtils.getAppContext(), R.anim.v_fragment_close_exit) |
||||
} |
||||
} |
||||
|
||||
class DefaultHorizontalAnimator : FragmentAnimator { |
||||
override fun makeOpenEnter(): Animation? { |
||||
return AnimationUtils.loadAnimation(BaseUtils.getAppContext(), R.anim.h_fragment_open_enter) |
||||
} |
||||
|
||||
override fun makeOpenExit(): Animation? { |
||||
return AnimationUtils.loadAnimation(BaseUtils.getAppContext(), R.anim.h_fragment_open_exit) |
||||
} |
||||
|
||||
override fun makeCloseEnter(): Animation? { |
||||
return AnimationUtils.loadAnimation(BaseUtils.getAppContext(), R.anim.h_fragment_close_enter) |
||||
} |
||||
|
||||
override fun makeCloseExit(): Animation? { |
||||
return AnimationUtils.loadAnimation(BaseUtils.getAppContext(), R.anim.h_fragment_close_exit) |
||||
} |
||||
} |
||||
|
||||
class DefaultNoAnimator : FragmentAnimator { |
||||
override fun makeOpenEnter(): Animation? { |
||||
return AnimationUtils.loadAnimation(BaseUtils.getAppContext(), R.anim.base_no_anim) |
||||
} |
||||
|
||||
override fun makeOpenExit(): Animation? { |
||||
return AnimationUtils.loadAnimation(BaseUtils.getAppContext(), R.anim.base_no_anim) |
||||
} |
||||
|
||||
override fun makeCloseEnter(): Animation? { |
||||
return AnimationUtils.loadAnimation(BaseUtils.getAppContext(), R.anim.base_no_anim) |
||||
} |
||||
|
||||
override fun makeCloseExit(): Animation? { |
||||
return AnimationUtils.loadAnimation(BaseUtils.getAppContext(), R.anim.base_no_anim) |
||||
} |
||||
} |
||||
|
||||
|
||||
class DefaultScaleAnimator : FragmentAnimator { |
||||
override fun makeOpenEnter(): Animation? { |
||||
return makeOpenCloseAnimation(1.125f, 1.0f, 0f, 1f) |
||||
} |
||||
|
||||
override fun makeOpenExit(): Animation? { |
||||
return makeOpenCloseAnimation(1.0f, .975f, 1f, 0f) |
||||
} |
||||
|
||||
override fun makeCloseEnter(): Animation? { |
||||
return makeOpenCloseAnimation(.975f, 1.0f, 0f, 1f) |
||||
} |
||||
|
||||
override fun makeCloseExit(): Animation? { |
||||
return makeOpenCloseAnimation(1.0f, 1.075f, 1f, 0f) |
||||
} |
||||
|
||||
companion object { |
||||
private val DECELERATE_QUINT: Interpolator = DecelerateInterpolator(2.5f) |
||||
private val DECELERATE_CUBIC: Interpolator = DecelerateInterpolator(1.5f) |
||||
private const val ANIM_DUR = 220 |
||||
private fun makeOpenCloseAnimation(startScale: Float, endScale: Float, startAlpha: Float, endAlpha: Float): Animation { |
||||
val set = AnimationSet(false) |
||||
val scale = ScaleAnimation(startScale, endScale, startScale, endScale, Animation.RELATIVE_TO_SELF, .5f, Animation.RELATIVE_TO_SELF, .5f) |
||||
scale.interpolator = DECELERATE_QUINT |
||||
scale.duration = ANIM_DUR.toLong() |
||||
set.addAnimation(scale) |
||||
val alpha = AlphaAnimation(startAlpha, endAlpha) |
||||
alpha.interpolator = DECELERATE_CUBIC |
||||
alpha.duration = ANIM_DUR.toLong() |
||||
set.addAnimation(alpha) |
||||
return set |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,103 @@ |
||||
package com.android.base.app.fragment.animator; |
||||
|
||||
import android.content.Context; |
||||
import android.view.animation.Animation; |
||||
import android.view.animation.AnimationUtils; |
||||
|
||||
import com.android.base.R; |
||||
|
||||
import androidx.fragment.app.FragmentTransaction; |
||||
|
||||
public final class FragmentAnimatorHelper { |
||||
|
||||
private Context context; |
||||
private FragmentAnimator fragmentAnimator; |
||||
|
||||
public FragmentAnimatorHelper(Context context, FragmentAnimator fragmentAnimator) { |
||||
this.context = context; |
||||
this.fragmentAnimator = fragmentAnimator; |
||||
} |
||||
|
||||
public void changeAnimation(FragmentAnimator fragmentAnimator) { |
||||
this.fragmentAnimator = fragmentAnimator; |
||||
} |
||||
|
||||
public Animation getNoneAnim() { |
||||
return AnimationUtils.loadAnimation(context, R.anim.base_no_anim); |
||||
} |
||||
|
||||
public Animation getNoneAnimFixed() { |
||||
return new Animation() { |
||||
}; |
||||
} |
||||
|
||||
private Animation initEnterAnim() { |
||||
if (fragmentAnimator == null) { |
||||
return AnimationUtils.loadAnimation(context, R.anim.base_no_anim); |
||||
} else { |
||||
Animation animation = fragmentAnimator.makeOpenEnter(); |
||||
if (animation == null) { |
||||
return AnimationUtils.loadAnimation(context, R.anim.base_no_anim); |
||||
} else { |
||||
return animation; |
||||
} |
||||
} |
||||
} |
||||
|
||||
private Animation initExitAnim() { |
||||
if (fragmentAnimator == null) { |
||||
return AnimationUtils.loadAnimation(context, R.anim.base_no_anim); |
||||
} else { |
||||
Animation animation = fragmentAnimator.makeOpenExit(); |
||||
if (animation == null) { |
||||
return AnimationUtils.loadAnimation(context, R.anim.base_no_anim); |
||||
} else { |
||||
return animation; |
||||
} |
||||
} |
||||
} |
||||
|
||||
private Animation initPopEnterAnim() { |
||||
if (fragmentAnimator == null) { |
||||
return AnimationUtils.loadAnimation(context, R.anim.base_no_anim); |
||||
} else { |
||||
Animation animation = fragmentAnimator.makeCloseEnter(); |
||||
if (animation == null) { |
||||
return AnimationUtils.loadAnimation(context, R.anim.base_no_anim); |
||||
} else { |
||||
return animation; |
||||
} |
||||
} |
||||
} |
||||
|
||||
private Animation initPopExitAnim() { |
||||
if (fragmentAnimator == null) { |
||||
return AnimationUtils.loadAnimation(context, R.anim.base_no_anim); |
||||
} else { |
||||
Animation animation = fragmentAnimator.makeCloseExit(); |
||||
if (animation == null) { |
||||
return AnimationUtils.loadAnimation(context, R.anim.base_no_anim); |
||||
} else { |
||||
return animation; |
||||
} |
||||
} |
||||
} |
||||
|
||||
public Animation onCreateAnimation(int transit, boolean enter) { |
||||
if (transit == FragmentTransaction.TRANSIT_FRAGMENT_OPEN) { |
||||
if (enter) { |
||||
return initEnterAnim(); |
||||
} else { |
||||
return initExitAnim(); |
||||
} |
||||
} else if (transit == FragmentTransaction.TRANSIT_FRAGMENT_CLOSE) { |
||||
if (enter) { |
||||
return initPopEnterAnim(); |
||||
} else { |
||||
return initPopExitAnim(); |
||||
} |
||||
} |
||||
return null; |
||||
} |
||||
|
||||
} |
@ -0,0 +1,74 @@ |
||||
package com.android.base.app.fragment.delegates; |
||||
|
||||
import android.content.Context; |
||||
import android.content.Intent; |
||||
import android.os.Bundle; |
||||
import android.view.View; |
||||
|
||||
import androidx.annotation.NonNull; |
||||
import androidx.annotation.Nullable; |
||||
import androidx.fragment.app.Fragment; |
||||
|
||||
public interface FragmentDelegate<T extends Fragment> { |
||||
|
||||
/** |
||||
* 当该Delegate被添加到Fragment中 |
||||
*/ |
||||
default void onAttachToFragment(T fragment) { |
||||
} |
||||
|
||||
/** |
||||
* 调用此方法时,清除Fragment的引用 |
||||
*/ |
||||
default void onDetachFromFragment() { |
||||
} |
||||
|
||||
default void onAttach(Context context) { |
||||
} |
||||
|
||||
default void onCreate(@Nullable Bundle savedInstanceState) { |
||||
} |
||||
|
||||
default void onActivityCreated(@Nullable Bundle savedInstanceState) { |
||||
} |
||||
|
||||
default void onSaveInstanceState(Bundle savedInstanceState) { |
||||
} |
||||
|
||||
default void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { |
||||
} |
||||
|
||||
default void onStart() { |
||||
} |
||||
|
||||
default void onResume() { |
||||
} |
||||
|
||||
default void onPause() { |
||||
} |
||||
|
||||
default void onStop() { |
||||
} |
||||
|
||||
default void onDestroy() { |
||||
} |
||||
|
||||
default void onDestroyView() { |
||||
} |
||||
|
||||
default void onDetach() { |
||||
} |
||||
|
||||
default void setUserVisibleHint(boolean isVisibleToUser) { |
||||
} |
||||
|
||||
default void onHiddenChanged(boolean hidden) { |
||||
} |
||||
|
||||
default void onActivityResult(int requestCode, int resultCode, Intent data) { |
||||
} |
||||
|
||||
default void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) { |
||||
} |
||||
|
||||
} |
@ -0,0 +1,17 @@ |
||||
package com.android.base.app.fragment.delegates; |
||||
|
||||
|
||||
import com.github.dmstocking.optional.java.util.function.Predicate; |
||||
|
||||
import androidx.annotation.UiThread; |
||||
|
||||
@UiThread |
||||
public interface FragmentDelegateOwner { |
||||
|
||||
void addDelegate(FragmentDelegate fragmentDelegate); |
||||
|
||||
boolean removeDelegate(FragmentDelegate fragmentDelegate); |
||||
|
||||
FragmentDelegate findDelegate(Predicate<FragmentDelegate> predicate); |
||||
|
||||
} |
@ -0,0 +1,96 @@ |
||||
package com.android.base.app.fragment.delegates |
||||
|
||||
import android.os.Bundle |
||||
import android.view.View |
||||
import androidx.fragment.app.Fragment |
||||
|
||||
/** |
||||
* 用于在ViewPager中实现懒加载的Fragment: |
||||
* |
||||
* - changed-1: Android Support 24 把 setUserVisibleHint 方法放到了Attach 之前调用了,所以请在在构造代码块中设置 LazyDelegate。 |
||||
* |
||||
* @author Ztiany |
||||
* Date : Date : 2016-05-06 15:02 |
||||
* Email: 1169654504@qq.com |
||||
*/ |
||||
open class LazyDelegate private constructor() : FragmentDelegate<Fragment?> { |
||||
|
||||
/** View是否准备好,如果不需要绑定view数据,只是加载网络数据,那么该字段可以去掉 */ |
||||
private var isViewPrepared = false |
||||
|
||||
/** 滑动过来后,View是否可见 */ |
||||
private var isViewVisible = false |
||||
|
||||
private var preparedListener: (() -> Unit)? = null |
||||
|
||||
/** |
||||
* 在这里实现Fragment数据的缓加载. |
||||
* |
||||
* @param isVisibleToUser true 表用户可见,false 表不可见 |
||||
*/ |
||||
override fun setUserVisibleHint(isVisibleToUser: Boolean) { |
||||
if (isVisibleToUser) { |
||||
isViewVisible = true |
||||
onVisible() |
||||
} else { |
||||
isViewVisible = false |
||||
onInvisible() |
||||
} |
||||
} |
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { |
||||
isViewPrepared = true |
||||
} |
||||
|
||||
override fun onActivityCreated(savedInstanceState: Bundle?) { |
||||
lazyLoad() |
||||
} |
||||
|
||||
/** |
||||
* 滑动过来后,界面可见时执行 |
||||
*/ |
||||
protected fun onVisible() { |
||||
lazyLoad() |
||||
} |
||||
|
||||
/** |
||||
* 滑动过来后,界面不可见时执行 |
||||
*/ |
||||
protected fun onInvisible() {} |
||||
|
||||
private fun lazyLoad() { |
||||
if (isViewPrepared && isViewVisible) { |
||||
notifyLazyLoad() |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* 懒加载数据,并在此绑定View数据 |
||||
*/ |
||||
private fun notifyLazyLoad() { |
||||
preparedListener?.invoke() |
||||
} |
||||
|
||||
companion object { |
||||
fun attach(delegateFragment: FragmentDelegateOwner, onPreparedListener: () -> Unit): LazyDelegate { |
||||
val delegate = LazyDelegate() |
||||
delegate.preparedListener = onPreparedListener |
||||
delegateFragment.addDelegate(delegate) |
||||
return delegate |
||||
} |
||||
} |
||||
|
||||
} |
||||
|
||||
class SimpleLazyLoadListener(private val onFirstLoad: () -> Unit) : (() -> Unit) { |
||||
|
||||
private var isCalled = false |
||||
|
||||
override fun invoke() { |
||||
if (!isCalled) { |
||||
onFirstLoad() |
||||
isCalled = true |
||||
} |
||||
} |
||||
|
||||
} |
@ -0,0 +1,114 @@ |
||||
package com.android.base.app.fragment.tools |
||||
|
||||
import android.os.Binder |
||||
import android.os.Bundle |
||||
import androidx.core.app.BundleCompat |
||||
import androidx.fragment.app.Fragment |
||||
import kotlin.properties.ReadWriteProperty |
||||
import kotlin.reflect.KProperty |
||||
|
||||
fun <T : Any> fragmentArgument(name: String? = null, defaultValue: T? = null): ReadWriteProperty<Fragment, T> { |
||||
return FragmentArgumentDelegate(name, defaultValue) |
||||
} |
||||
|
||||
fun <T : Any?> nullableFragmentArgument(name: String? = null): ReadWriteProperty<Fragment, T?> { |
||||
return NullableFragmentArgumentDelegate(name) |
||||
} |
||||
|
||||
internal class FragmentArgumentDelegate<T : Any>( |
||||
/**value name*/ |
||||
private val name: String? = null, |
||||
/**default cannot be null*/ |
||||
private val defaultValue: T? = null |
||||
) : ReadWriteProperty<Fragment, T> { |
||||
|
||||
private lateinit var value: T |
||||
|
||||
override operator fun getValue(thisRef: Fragment, property: KProperty<*>): T { |
||||
if (!::value.isInitialized) { |
||||
|
||||
val args = thisRef.arguments |
||||
|
||||
if (args == null) { |
||||
if (defaultValue != null) { |
||||
value = defaultValue |
||||
} else { |
||||
throw IllegalStateException("Cannot read property ${confirmPropertyName(name, property)} if no arguments have been set") |
||||
} |
||||
} else { |
||||
@Suppress("UNCHECKED_CAST") |
||||
value = args.get(confirmPropertyName(name, property)) as? T |
||||
?: throw IllegalStateException("Property ${confirmPropertyName(name, property)} could not be read") |
||||
} |
||||
|
||||
} |
||||
|
||||
return value |
||||
} |
||||
|
||||
override operator fun setValue(thisRef: Fragment, property: KProperty<*>, value: T) { |
||||
this.value = value |
||||
saveValue(thisRef, name, property, value) |
||||
} |
||||
|
||||
} |
||||
|
||||
internal class NullableFragmentArgumentDelegate<T : Any?>( |
||||
/**value name*/ |
||||
private val name: String? = null |
||||
) : ReadWriteProperty<Fragment, T?> { |
||||
|
||||
private var value: T? = null |
||||
|
||||
override operator fun getValue(thisRef: Fragment, property: KProperty<*>): T? { |
||||
if (value == null) { |
||||
@Suppress("UNCHECKED_CAST") |
||||
value = thisRef.arguments?.get(confirmPropertyName(name, property)) as? T |
||||
} |
||||
|
||||
return value |
||||
} |
||||
|
||||
override operator fun setValue(thisRef: Fragment, property: KProperty<*>, value: T?) { |
||||
this.value = value |
||||
saveValue(thisRef, name, property, value) |
||||
} |
||||
|
||||
} |
||||
|
||||
private fun confirmPropertyName(name: String?, property: KProperty<*>): String { |
||||
return name ?: property.name |
||||
} |
||||
|
||||
private fun saveValue(thisRef: Fragment, name: String?, property: KProperty<*>, value: Any?) { |
||||
var args = thisRef.arguments |
||||
val key = confirmPropertyName(name, property) |
||||
|
||||
if (value == null) { |
||||
args?.remove(key) |
||||
return |
||||
} |
||||
|
||||
if (args == null) { |
||||
args = Bundle() |
||||
thisRef.arguments = args |
||||
} |
||||
|
||||
when (value) { |
||||
is String -> args.putString(key, value) |
||||
is Int -> args.putInt(key, value) |
||||
is Short -> args.putShort(key, value) |
||||
is Long -> args.putLong(key, value) |
||||
is Byte -> args.putByte(key, value) |
||||
is ByteArray -> args.putByteArray(key, value) |
||||
is Char -> args.putChar(key, value) |
||||
is CharArray -> args.putCharArray(key, value) |
||||
is CharSequence -> args.putCharSequence(key, value) |
||||
is Float -> args.putFloat(key, value) |
||||
is Bundle -> args.putBundle(key, value) |
||||
is Binder -> BundleCompat.putBinder(args, key, value) |
||||
is android.os.Parcelable -> args.putParcelable(key, value) |
||||
is java.io.Serializable -> args.putSerializable(key, value) |
||||
else -> throw IllegalStateException("Type \"$value\" of property ${property.name} is not supported") |
||||
} |
||||
} |
@ -0,0 +1,35 @@ |
||||
package com.android.base.app.fragment.tools; |
||||
|
||||
import com.android.base.app.fragment.animator.FragmentAnimator; |
||||
|
||||
/** |
||||
* @author Ztiany |
||||
* Email: ztiany3@gmail.com |
||||
* Date : 2019-03-05 15:25 |
||||
*/ |
||||
public class FragmentConfig { |
||||
|
||||
private static final int INVALIDATE_ID = -1; |
||||
private static int sDefaultContainerId = INVALIDATE_ID; |
||||
private static FragmentAnimator sFragmentAnimator = null; |
||||
|
||||
public static void setDefaultContainerId(int defaultContainerId) { |
||||
sDefaultContainerId = defaultContainerId; |
||||
} |
||||
|
||||
public static int defaultContainerId() { |
||||
if (sDefaultContainerId == INVALIDATE_ID) { |
||||
throw new IllegalStateException("sDefaultContainerId has not set"); |
||||
} |
||||
return sDefaultContainerId; |
||||
} |
||||
|
||||
public static void setDefaultFragmentAnimator(FragmentAnimator animator) { |
||||
sFragmentAnimator = animator; |
||||
} |
||||
|
||||
public static FragmentAnimator defaultFragmentAnimator() { |
||||
return sFragmentAnimator; |
||||
} |
||||
|
||||
} |
@ -0,0 +1,133 @@ |
||||
package com.android.base.app.fragment.tools; |
||||
|
||||
import android.content.Context; |
||||
import android.os.Bundle; |
||||
|
||||
import java.lang.ref.WeakReference; |
||||
|
||||
import androidx.fragment.app.Fragment; |
||||
|
||||
@SuppressWarnings("WeakerAccess,unused") |
||||
public class FragmentInfo { |
||||
|
||||
private final int mPageId; |
||||
private final String mTag; |
||||
private final Class<? extends Fragment> mClazz; |
||||
private final int mTitleId; |
||||
private final Bundle mArguments; |
||||
private final boolean mIsToStack; |
||||
private final String mStackName; |
||||
private WeakReference<Fragment> mFragment; |
||||
|
||||
private FragmentInfo(int pageId, String tag, Class<? extends Fragment> clazz, int titleId, Bundle arguments, boolean toStack, String stackName) { |
||||
mPageId = pageId; |
||||
mTag = tag; |
||||
mClazz = clazz; |
||||
mTitleId = titleId; |
||||
mArguments = arguments; |
||||
mIsToStack = toStack; |
||||
mStackName = stackName; |
||||
} |
||||
|
||||
public Fragment getInstance() { |
||||
return mFragment == null ? null : mFragment.get(); |
||||
} |
||||
|
||||
public void setInstance(Fragment fragment) { |
||||
mFragment = new WeakReference<>(fragment); |
||||
} |
||||
|
||||
public Fragment newFragment(Context context) { |
||||
return Fragment.instantiate(context, mClazz.getName(), mArguments); |
||||
} |
||||
|
||||
public boolean isToStack() { |
||||
return mIsToStack; |
||||
} |
||||
|
||||
public String getStackName() { |
||||
return mStackName; |
||||
} |
||||
|
||||
public Bundle getArguments() { |
||||
return mArguments; |
||||
} |
||||
|
||||
public int getTitleId() { |
||||
return mTitleId; |
||||
} |
||||
|
||||
public int getPageId() { |
||||
return mPageId; |
||||
} |
||||
|
||||
public String getTag() { |
||||
return mTag; |
||||
} |
||||
|
||||
public Class<? extends Fragment> getClazz() { |
||||
return mClazz; |
||||
} |
||||
|
||||
public static PageBuilder builder() { |
||||
return new PageBuilder(); |
||||
} |
||||
|
||||
|
||||
public static class PageBuilder { |
||||
|
||||
private int mPagerId; |
||||
private String mTag; |
||||
private Class<? extends Fragment> mClazz; |
||||
private int mTitleId; |
||||
private Bundle mArguments; |
||||
private boolean mIsToStack; |
||||
private String mStackName; |
||||
|
||||
public FragmentInfo build() { |
||||
return new FragmentInfo(mPagerId, mTag, mClazz, mTitleId, mArguments, mIsToStack, mStackName); |
||||
} |
||||
|
||||
public PageBuilder pagerId(int pagerId) { |
||||
mPagerId = pagerId; |
||||
return this; |
||||
} |
||||
|
||||
public PageBuilder tag(String tag) { |
||||
this.mTag = tag; |
||||
return this; |
||||
} |
||||
|
||||
public PageBuilder clazz(Class<? extends Fragment> clazz) { |
||||
mClazz = clazz; |
||||
return this; |
||||
} |
||||
|
||||
public PageBuilder titleId(int titleId) { |
||||
mTitleId = titleId; |
||||
return this; |
||||
} |
||||
|
||||
public PageBuilder arguments(Bundle arguments) { |
||||
mArguments = arguments; |
||||
return this; |
||||
} |
||||
|
||||
public PageBuilder toStack(boolean toStack) { |
||||
this.mIsToStack = toStack; |
||||
return this; |
||||
} |
||||
|
||||
/** |
||||
* 如果需要加入到Stack,建议加上StackName。 |
||||
* |
||||
* @param stackName StackName |
||||
* @return PageBuilder |
||||
*/ |
||||
public PageBuilder stackName(String stackName) { |
||||
mStackName = stackName; |
||||
return this; |
||||
} |
||||
} |
||||
|
||||
} |
@ -0,0 +1,500 @@ |
||||
@file:JvmName("Fragments") |
||||
|
||||
package com.android.base.app.fragment.tools |
||||
|
||||
|
||||
import android.view.View |
||||
import androidx.annotation.NonNull |
||||
import androidx.fragment.app.Fragment |
||||
import androidx.fragment.app.FragmentActivity |
||||
import androidx.fragment.app.FragmentManager |
||||
import androidx.fragment.app.FragmentTransaction |
||||
import com.android.base.app.activity.ActivityDelegate |
||||
import com.android.base.app.activity.ActivityDelegateOwner |
||||
import com.android.base.app.activity.ActivityState |
||||
import com.android.base.app.fragment.delegates.FragmentDelegate |
||||
import com.android.base.app.fragment.delegates.FragmentDelegateOwner |
||||
import com.android.base.utils.common.javaClassName |
||||
import kotlin.reflect.KClass |
||||
|
||||
/**被此 annotation 标注的方法,表示需要使用 [Fragment] 的全类名作为 [FragmentTransaction] 中相关方法的 flag 参数的实参,比如 add/replace 等*/ |
||||
annotation class UsingFragmentClassNameAsFlag |
||||
|
||||
@JvmOverloads |
||||
fun Fragment.exitFragment(immediate: Boolean = false) { |
||||
activity.exitFragment(immediate) |
||||
} |
||||
|
||||
@JvmOverloads |
||||
fun FragmentActivity?.exitFragment(immediate: Boolean = false) { |
||||
if (this == null) { |
||||
return |
||||
} |
||||
val supportFragmentManager = this.supportFragmentManager |
||||
val backStackEntryCount = supportFragmentManager.backStackEntryCount |
||||
if (backStackEntryCount > 0) { |
||||
if (immediate) { |
||||
supportFragmentManager.popBackStackImmediate() |
||||
} else { |
||||
supportFragmentManager.popBackStack() |
||||
} |
||||
} else { |
||||
this.supportFinishAfterTransition() |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* @param clazz the interface container must implemented |
||||
* @param <T> Type |
||||
* @return the interface context must implemented |
||||
*/ |
||||
fun <T> Fragment.requireContainerImplement(clazz: Class<T>): T? { |
||||
if (clazz.isInstance(parentFragment)) { |
||||
return clazz.cast(parentFragment) |
||||
} |
||||
return if (clazz.isInstance(activity)) { |
||||
clazz.cast(activity) |
||||
} else { |
||||
throw RuntimeException("use this Fragment:$this, Activity or Fragment must impl interface :$clazz") |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* @param clazz the interface context must implemented |
||||
* @param <T> Type |
||||
* @return the interface context must implemented |
||||
*/ |
||||
fun <T> Fragment.requireContextImplement(clazz: Class<T>): T? { |
||||
return if (!clazz.isInstance(activity)) { |
||||
throw RuntimeException("use this Fragment:$this, Activity must impl interface :$clazz") |
||||
} else { |
||||
clazz.cast(activity) |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* @param clazz the interface parent must implemented |
||||
* @param <T> Type |
||||
* @return the interface context must implemented |
||||
*/ |
||||
fun <T> Fragment.requireParentImplement(clazz: Class<T>): T? { |
||||
return if (!clazz.isInstance(parentFragment)) { |
||||
throw RuntimeException("use this Fragment:$this, ParentFragment must impl interface :$clazz") |
||||
} else { |
||||
clazz.cast(parentFragment) |
||||
} |
||||
} |
||||
|
||||
/** 使用 [clazz] 的全限定类名作为 tag 查找 Fragment */ |
||||
fun <T : Fragment> FragmentManager.findFragmentByTag(clazz: KClass<T>): T? { |
||||
@Suppress("UNCHECKED_CAST") |
||||
return findFragmentByTag(clazz.java.name) as? T |
||||
} |
||||
|
||||
/** |
||||
* Fragment 出栈,包括 flag 对应的 Fragment,如果 flag 对应的 Fragment 不在栈中,此方法什么都不做。 |
||||
*/ |
||||
@UsingFragmentClassNameAsFlag |
||||
fun FragmentManager.popBackUntil(flag: String, immediate: Boolean = false) { |
||||
if (immediate) { |
||||
popBackStackImmediate(flag, FragmentManager.POP_BACK_STACK_INCLUSIVE) |
||||
} else { |
||||
popBackStack(flag, FragmentManager.POP_BACK_STACK_INCLUSIVE) |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Fragment 出栈,不包括 flag 对应的 Fragment,如果 flag 对应的 Fragment 不在栈中,此方法什么都不做。 |
||||
*/ |
||||
@UsingFragmentClassNameAsFlag |
||||
fun FragmentManager.popBackTo(flag: String, immediate: Boolean = false) { |
||||
if (immediate) { |
||||
popBackStackImmediate(flag, 0) |
||||
} else { |
||||
popBackStack(flag, 0) |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* 回到对应的 Fragment,如果 Fragment 在栈中,则该 Fragment 回到栈顶,如果 Fragment 不在栈中,则做一次弹栈操作。 |
||||
*/ |
||||
@UsingFragmentClassNameAsFlag |
||||
fun FragmentManager.backToFragment(flag: String, immediate: Boolean = false) { |
||||
val target = findFragmentByTag(flag) |
||||
|
||||
if (target != null) { |
||||
if (isFragmentInStack(target.javaClass)) { |
||||
if (immediate) { |
||||
popBackStackImmediate(flag, 0) |
||||
} else { |
||||
popBackStack(flag, 0) |
||||
} |
||||
} else { |
||||
popBackStack() |
||||
} |
||||
} |
||||
} |
||||
|
||||
fun FragmentManager.clearBackStack(immediate: Boolean = false) { |
||||
if (immediate) { |
||||
this.popBackStackImmediate(null, FragmentManager.POP_BACK_STACK_INCLUSIVE) |
||||
} else { |
||||
this.popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE) |
||||
} |
||||
} |
||||
|
||||
@UsingFragmentClassNameAsFlag |
||||
fun FragmentManager.isFragmentInStack(clazz: Class<out Fragment>): Boolean { |
||||
val backStackEntryCount = backStackEntryCount |
||||
if (backStackEntryCount == 0) { |
||||
return false |
||||
} |
||||
for (i in 0 until backStackEntryCount) { |
||||
if (clazz.name == getBackStackEntryAt(i).name) { |
||||
return true |
||||
} |
||||
} |
||||
return false |
||||
} |
||||
|
||||
inline fun FragmentManager.inTransaction(func: EnhanceFragmentTransaction.() -> Unit) { |
||||
val fragmentTransaction = beginTransaction() |
||||
EnhanceFragmentTransaction(this, fragmentTransaction).func() |
||||
fragmentTransaction.commit() |
||||
} |
||||
|
||||
inline fun FragmentActivity.inFragmentTransaction(func: EnhanceFragmentTransaction.() -> Unit) { |
||||
val transaction = supportFragmentManager.beginTransaction() |
||||
EnhanceFragmentTransaction(supportFragmentManager, transaction).func() |
||||
transaction.commit() |
||||
} |
||||
|
||||
fun <T> T.inSafelyFragmentTransaction( |
||||
func: EnhanceFragmentTransaction.() -> Unit |
||||
): Boolean where T : FragmentActivity, T : ActivityDelegateOwner { |
||||
|
||||
var delegate = findDelegate { |
||||
it is SafelyFragmentTransactionActivityDelegate |
||||
} as? SafelyFragmentTransactionActivityDelegate |
||||
|
||||
if (delegate == null) { |
||||
delegate = SafelyFragmentTransactionActivityDelegate() |
||||
addDelegate(delegate) |
||||
} |
||||
|
||||
val transaction = supportFragmentManager.beginTransaction() |
||||
|
||||
EnhanceFragmentTransaction(supportFragmentManager, transaction).func() |
||||
|
||||
return delegate.safeCommit(this, transaction) |
||||
} |
||||
|
||||
inline fun Fragment.inChildFragmentTransaction(func: EnhanceFragmentTransaction.() -> Unit) { |
||||
val transaction = childFragmentManager.beginTransaction() |
||||
EnhanceFragmentTransaction(childFragmentManager, transaction).func() |
||||
transaction.commit() |
||||
} |
||||
|
||||
fun <T> T.inSafelyChildFragmentTransaction( |
||||
func: EnhanceFragmentTransaction.() -> Unit |
||||
): Boolean where T : Fragment, T : FragmentDelegateOwner { |
||||
|
||||
var delegate: SafelyFragmentTransactionFragmentDelegate? = findDelegate { |
||||
it is SafelyFragmentTransactionFragmentDelegate |
||||
} as? SafelyFragmentTransactionFragmentDelegate |
||||
|
||||
if (delegate == null) { |
||||
delegate = SafelyFragmentTransactionFragmentDelegate() |
||||
addDelegate(delegate) |
||||
} |
||||
|
||||
val transaction = childFragmentManager.beginTransaction() |
||||
|
||||
EnhanceFragmentTransaction(childFragmentManager, transaction).func() |
||||
|
||||
return delegate.safeCommit(this, transaction) |
||||
} |
||||
|
||||
private class SafelyFragmentTransactionActivityDelegate : ActivityDelegate<FragmentActivity> { |
||||
|
||||
private val mPendingTransactions = mutableListOf<FragmentTransaction>() |
||||
|
||||
fun safeCommit(@NonNull activityDelegateOwner: ActivityDelegateOwner, @NonNull transaction: FragmentTransaction): Boolean { |
||||
val status = activityDelegateOwner.status |
||||
val isCommitterResumed = (status == ActivityState.CREATE || status == ActivityState.START || status == ActivityState.RESUME) |
||||
|
||||
return if (isCommitterResumed) { |
||||
transaction.commit() |
||||
false |
||||
} else { |
||||
mPendingTransactions.add(transaction) |
||||
true |
||||
} |
||||
} |
||||
|
||||
override fun onResumeFragments() { |
||||
if (mPendingTransactions.isNotEmpty()) { |
||||
mPendingTransactions.forEach { it.commit() } |
||||
mPendingTransactions.clear() |
||||
} |
||||
} |
||||
|
||||
} |
||||
|
||||
private class SafelyFragmentTransactionFragmentDelegate : FragmentDelegate<Fragment> { |
||||
|
||||
private val pendingTransactions = mutableListOf<FragmentTransaction>() |
||||
|
||||
fun safeCommit(@NonNull fragment: Fragment, @NonNull transaction: FragmentTransaction): Boolean { |
||||
return if (fragment.isResumed) { |
||||
transaction.commit() |
||||
false |
||||
} else { |
||||
pendingTransactions.add(transaction) |
||||
true |
||||
} |
||||
} |
||||
|
||||
override fun onResume() { |
||||
if (pendingTransactions.isNotEmpty()) { |
||||
pendingTransactions.forEach { it.commit() } |
||||
pendingTransactions.clear() |
||||
} |
||||
} |
||||
|
||||
} |
||||
|
||||
class EnhanceFragmentTransaction constructor( |
||||
private val fragmentManager: FragmentManager, |
||||
private val fragmentTransaction: FragmentTransaction |
||||
) : FragmentTransaction() { |
||||
|
||||
//------------------------------------------------------------------------------------------------ |
||||
// extra functions |
||||
//------------------------------------------------------------------------------------------------ |
||||
|
||||
/** |
||||
* 把 [fragment] 添加到回退栈中,并 hide 其他 fragment, |
||||
* 如果 [containerId]==0,则使用 [com.android.base.app.Sword.setDefaultFragmentContainerId] 中配置的 id, |
||||
* 如果 [tag] ==null 则使用 fragment 对应 class 的全限定类名。 |
||||
*/ |
||||
fun addWithStack(containerId: Int = 0, fragment: Fragment, tag: String? = null, transition: Boolean = true): EnhanceFragmentTransaction { |
||||
//set add to stack |
||||
val nonnullTag = (tag ?: fragment.javaClassName()) |
||||
addToBackStack(nonnullTag) |
||||
//add |
||||
fragmentTransaction.add(confirmLayoutId(containerId), fragment, nonnullTag) |
||||
//hide top |
||||
hideTopFragment() |
||||
if (transition) { |
||||
//set a transition |
||||
setOpeningTransition() |
||||
} |
||||
return this |
||||
} |
||||
|
||||
/** |
||||
* replace 方式把 [fragment] 添加到回退栈中,如果 [containerId]==0,则使用 [com.android.base.app.Sword.setDefaultFragmentContainerId] 中配置的 id, |
||||
* 如果 [tag] ==null 则使用 fragment 对应 class 的全限定类名。 |
||||
*/ |
||||
fun replaceWithStack(containerId: Int = 0, fragment: Fragment, tag: String? = null, transition: Boolean = true): EnhanceFragmentTransaction { |
||||
//set add to stack |
||||
val nonnullTag = (tag ?: fragment.javaClassName()) |
||||
addToBackStack(nonnullTag) |
||||
//add |
||||
fragmentTransaction.replace(confirmLayoutId(containerId), fragment, nonnullTag) |
||||
//set a transition |
||||
if (transition) { |
||||
setOpeningTransition() |
||||
} |
||||
return this |
||||
} |
||||
|
||||
private fun confirmLayoutId(layoutId: Int): Int { |
||||
return if (layoutId == 0) { |
||||
FragmentConfig.defaultContainerId() |
||||
} else { |
||||
layoutId |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* 添加 [fragment],默认使用 [com.android.base.app.Sword.setDefaultFragmentContainerId] 中配置的 id,如果 [tag] 为null,则使用 [fragment] 的全限定类。 |
||||
*/ |
||||
fun addFragment(fragment: Fragment, tag: String? = null): FragmentTransaction { |
||||
val nonnullTag = (tag ?: fragment.javaClassName()) |
||||
return fragmentTransaction.add(FragmentConfig.defaultContainerId(), fragment, nonnullTag) |
||||
} |
||||
|
||||
/** |
||||
* 替换为 [fragment],id 使用 [com.android.base.app.Sword.setDefaultFragmentContainerId] 中配置的 id,如果 [tag] 为null,则使用 [fragment] 的全限定类名。 |
||||
*/ |
||||
fun replaceFragment(fragment: Fragment, tag: String? = null, transition: Boolean = true): FragmentTransaction { |
||||
val nonnullTag = (tag ?: fragment.javaClassName()) |
||||
if (transition) { |
||||
setOpeningTransition() |
||||
} |
||||
return fragmentTransaction.replace(FragmentConfig.defaultContainerId(), fragment, nonnullTag) |
||||
} |
||||
|
||||
/**隐藏所有的 fragment */ |
||||
private fun hideFragments() { |
||||
for (fragment in fragmentManager.fragments) { |
||||
if (fragment != null && fragment.isVisible) { |
||||
fragmentTransaction.hide(fragment) |
||||
} |
||||
} |
||||
} |
||||
|
||||
/**隐藏第一个可见的 fragment */ |
||||
private fun hideTopFragment() { |
||||
fragmentManager.fragments.lastOrNull { it.isVisible }?.let { |
||||
fragmentTransaction.hide(it) |
||||
} |
||||
} |
||||
|
||||
fun setOpeningTransition(): FragmentTransaction { |
||||
return fragmentTransaction.setTransition(TRANSIT_FRAGMENT_OPEN) |
||||
} |
||||
|
||||
fun setClosingTransition(): FragmentTransaction { |
||||
return fragmentTransaction.setTransition(TRANSIT_FRAGMENT_CLOSE) |
||||
} |
||||
|
||||
fun setFadingTransition(): FragmentTransaction { |
||||
return fragmentTransaction.setTransition(TRANSIT_FRAGMENT_FADE) |
||||
} |
||||
|
||||
//------------------------------------------------------------------------------------------------ |
||||
// original functions |
||||
//------------------------------------------------------------------------------------------------ |
||||
override fun setBreadCrumbShortTitle(res: Int): FragmentTransaction { |
||||
return fragmentTransaction.setBreadCrumbShortTitle(res) |
||||
} |
||||
|
||||
override fun setBreadCrumbShortTitle(text: CharSequence?): FragmentTransaction { |
||||
return fragmentTransaction.setBreadCrumbShortTitle(text) |
||||
} |
||||
|
||||
override fun setPrimaryNavigationFragment(fragment: Fragment?): FragmentTransaction { |
||||
return fragmentTransaction.setPrimaryNavigationFragment(fragment) |
||||
} |
||||
|
||||
override fun runOnCommit(runnable: Runnable): FragmentTransaction { |
||||
return fragmentTransaction.runOnCommit(runnable) |
||||
} |
||||
|
||||
override fun add(fragment: Fragment, tag: String?): FragmentTransaction { |
||||
return fragmentTransaction.add(fragment, tag) |
||||
} |
||||
|
||||
override fun add(containerViewId: Int, fragment: Fragment): FragmentTransaction { |
||||
return fragmentTransaction.add(containerViewId, fragment) |
||||
} |
||||
|
||||
override fun add(containerViewId: Int, fragment: Fragment, tag: String?): FragmentTransaction { |
||||
return fragmentTransaction.add(containerViewId, fragment, tag) |
||||
} |
||||
|
||||
override fun hide(fragment: Fragment): FragmentTransaction { |
||||
return fragmentTransaction.hide(fragment) |
||||
} |
||||
|
||||
override fun replace(containerViewId: Int, fragment: Fragment): FragmentTransaction { |
||||
return fragmentTransaction.replace(containerViewId, fragment) |
||||
} |
||||
|
||||
override fun replace(containerViewId: Int, fragment: Fragment, tag: String?): FragmentTransaction { |
||||
return fragmentTransaction.replace(containerViewId, fragment, tag) |
||||
} |
||||
|
||||
override fun detach(fragment: Fragment): FragmentTransaction { |
||||
return fragmentTransaction.detach(fragment) |
||||
} |
||||
|
||||
@Deprecated("") |
||||
override fun setAllowOptimization(allowOptimization: Boolean): FragmentTransaction { |
||||
return fragmentTransaction.setAllowOptimization(allowOptimization) |
||||
} |
||||
|
||||
override fun setCustomAnimations(enter: Int, exit: Int): FragmentTransaction { |
||||
return fragmentTransaction.setCustomAnimations(enter, exit) |
||||
} |
||||
|
||||
override fun setCustomAnimations(enter: Int, exit: Int, popEnter: Int, popExit: Int): FragmentTransaction { |
||||
return fragmentTransaction.setCustomAnimations(enter, exit, popEnter, popExit) |
||||
} |
||||
|
||||
override fun addToBackStack(name: String?): FragmentTransaction { |
||||
return fragmentTransaction.addToBackStack(name) |
||||
} |
||||
|
||||
override fun disallowAddToBackStack(): FragmentTransaction { |
||||
return fragmentTransaction.disallowAddToBackStack() |
||||
} |
||||
|
||||
override fun setTransitionStyle(styleRes: Int): FragmentTransaction { |
||||
return fragmentTransaction.setTransitionStyle(styleRes) |
||||
} |
||||
|
||||
override fun setTransition(transit: Int): FragmentTransaction { |
||||
return fragmentTransaction.setTransition(transit) |
||||
} |
||||
|
||||
override fun attach(fragment: Fragment): FragmentTransaction { |
||||
return fragmentTransaction.attach(fragment) |
||||
} |
||||
|
||||
override fun show(fragment: Fragment): FragmentTransaction { |
||||
return fragmentTransaction.show(fragment) |
||||
} |
||||
|
||||
override fun isEmpty(): Boolean { |
||||
return fragmentTransaction.isEmpty |
||||
} |
||||
|
||||
override fun remove(fragment: Fragment): FragmentTransaction { |
||||
return fragmentTransaction.remove(fragment) |
||||
} |
||||
|
||||
override fun isAddToBackStackAllowed(): Boolean { |
||||
return fragmentTransaction.isAddToBackStackAllowed |
||||
} |
||||
|
||||
override fun addSharedElement(sharedElement: View, name: String): FragmentTransaction { |
||||
return fragmentTransaction.addSharedElement(sharedElement, name) |
||||
} |
||||
|
||||
override fun setBreadCrumbTitle(res: Int): FragmentTransaction { |
||||
return fragmentTransaction.setBreadCrumbTitle(res) |
||||
} |
||||
|
||||
override fun setBreadCrumbTitle(text: CharSequence?): FragmentTransaction { |
||||
return fragmentTransaction.setBreadCrumbTitle(text) |
||||
} |
||||
|
||||
override fun setReorderingAllowed(reorderingAllowed: Boolean): FragmentTransaction { |
||||
return fragmentTransaction.setReorderingAllowed(reorderingAllowed) |
||||
} |
||||
|
||||
@Deprecated("commit will be called automatically") |
||||
override fun commit(): Int { |
||||
throw UnsupportedOperationException("commit will be called automatically") |
||||
} |
||||
|
||||
@Deprecated("commitAllowingStateLoss will be called automatically") |
||||
override fun commitAllowingStateLoss(): Int { |
||||
throw UnsupportedOperationException("commitAllowingStateLoss will be called automatically") |
||||
} |
||||
|
||||
@Deprecated("commitNow will be called automatically", ReplaceWith("throw UnsupportedOperationException(\"commitNow will be called automatically\")")) |
||||
override fun commitNow() { |
||||
throw UnsupportedOperationException("commitNow will be called automatically") |
||||
} |
||||
|
||||
@Deprecated("commitNowAllowingStateLoss will be called automatically", ReplaceWith("throw UnsupportedOperationException(\"commitNowAllowingStateLoss will be called automatically\")")) |
||||
override fun commitNowAllowingStateLoss() { |
||||
throw UnsupportedOperationException("commitNowAllowingStateLoss will be called automatically") |
||||
} |
||||
|
||||
} |
@ -0,0 +1,211 @@ |
||||
package com.android.base.app.fragment.tools; |
||||
|
||||
import android.content.Context; |
||||
import android.os.Bundle; |
||||
|
||||
import java.util.ArrayList; |
||||
import java.util.Collections; |
||||
import java.util.List; |
||||
|
||||
import androidx.fragment.app.Fragment; |
||||
import androidx.fragment.app.FragmentManager; |
||||
import androidx.fragment.app.FragmentTransaction; |
||||
|
||||
public abstract class TabManager { |
||||
|
||||
@SuppressWarnings("WeakerAccess") public static final int ATTACH_DETACH = 1; |
||||
@SuppressWarnings("WeakerAccess") public static final int SHOW_HIDE = 2; |
||||
|
||||
private final FragmentManager mFragmentManager; |
||||
private final int mContainerId; |
||||
private final Tabs mMainTabs; |
||||
private final Context mContext; |
||||
|
||||
private FragmentInfo mCurrentFragmentInfo; |
||||
|
||||
private static final String CURRENT_ID_KET = "main_tab_id"; |
||||
private static final int NONE = -1; |
||||
private int mCurrentId = NONE; |
||||
private final int mOperationType; |
||||
|
||||
public TabManager(Context context, FragmentManager fragmentManager, Tabs tabs, int containerId) { |
||||
this(context, fragmentManager, tabs, containerId, ATTACH_DETACH); |
||||
} |
||||
|
||||
/** |
||||
* @param operationType {@link #ATTACH_DETACH} or {@link #SHOW_HIDE} |
||||
*/ |
||||
public TabManager(Context context, FragmentManager fragmentManager, Tabs tabs, int containerId, int operationType) { |
||||
if (operationType != ATTACH_DETACH && operationType != SHOW_HIDE) { |
||||
throw new IllegalArgumentException("the operationType must be ATTACH_DETACH or SHOW_HIDE"); |
||||
} |
||||
mMainTabs = tabs; |
||||
mContainerId = containerId; |
||||
mContext = context; |
||||
mFragmentManager = fragmentManager; |
||||
mOperationType = operationType; |
||||
} |
||||
|
||||
public final void setup(Bundle bundle) { |
||||
int pageId = mMainTabs.homePage().getPageId(); |
||||
if (bundle == null) { |
||||
switchPage(pageId); |
||||
} else { |
||||
mCurrentId = bundle.getInt(CURRENT_ID_KET, pageId); |
||||
restoreState(); |
||||
} |
||||
} |
||||
|
||||
private void restoreState() { |
||||
List<FragmentInfo> pages = mMainTabs.getPages(); |
||||
for (FragmentInfo page : pages) { |
||||
page.setInstance(mFragmentManager.findFragmentByTag(page.getTag())); |
||||
if (mCurrentId == page.getPageId()) { |
||||
mCurrentFragmentInfo = page; |
||||
} |
||||
} |
||||
if (mCurrentId == NONE) { |
||||
doChangeTab(mMainTabs.homePage().getPageId()); |
||||
} |
||||
} |
||||
|
||||
private void switchPage(int pageId) { |
||||
if (mCurrentId == pageId) { |
||||
return; |
||||
} |
||||
FragmentTransaction ft = null; |
||||
if (mCurrentFragmentInfo != null) { |
||||
Fragment fragment = mCurrentFragmentInfo.getInstance(); |
||||
if (fragment != null) { |
||||
ft = mFragmentManager.beginTransaction(); |
||||
hideOrDetach(ft, fragment); |
||||
} |
||||
} |
||||
if (ft != null) { |
||||
ft.commit(); |
||||
} |
||||
doChangeTab(pageId); |
||||
} |
||||
|
||||
private void hideOrDetach(FragmentTransaction ft, Fragment fragment) { |
||||
if (mOperationType == SHOW_HIDE) { |
||||
ft.hide(fragment); |
||||
} else { |
||||
ft.detach(fragment); |
||||
} |
||||
} |
||||
|
||||
private void showOrAttach(FragmentTransaction fragmentTransaction, Fragment fragment) { |
||||
if (mOperationType == SHOW_HIDE) { |
||||
fragmentTransaction.show(fragment); |
||||
} else { |
||||
fragmentTransaction.attach(fragment); |
||||
} |
||||
} |
||||
|
||||
private void doChangeTab(int fragmentId) { |
||||
FragmentTransaction fragmentTransaction = mFragmentManager.beginTransaction(); |
||||
FragmentInfo fragmentInfo = mMainTabs.getFragmentInfo(fragmentId); |
||||
Fragment fragment = fragmentInfo.getInstance(); |
||||
if (fragment != null) { |
||||
showOrAttach(fragmentTransaction, fragment); |
||||
} else { |
||||
Fragment newFragment = fragmentInfo.newFragment(mContext); |
||||
fragmentInfo.setInstance(newFragment); |
||||
onFragmentCreated(fragmentId, newFragment); |
||||
fragmentTransaction.add(mContainerId, newFragment, fragmentInfo.getTag()); |
||||
} |
||||
mCurrentFragmentInfo = fragmentInfo; |
||||
mCurrentId = fragmentId; |
||||
fragmentTransaction.commit(); |
||||
} |
||||
|
||||
@SuppressWarnings("WeakerAccess") |
||||
public void selectTabByPosition(int position) { |
||||
switchPage(mMainTabs.getIdByPosition(position)); |
||||
} |
||||
|
||||
public void selectTabById(int tabId) { |
||||
selectTabByPosition(mMainTabs.getPositionById(tabId)); |
||||
} |
||||
|
||||
@SuppressWarnings("unused") |
||||
public int getCurrentPosition() { |
||||
return mMainTabs.getPositionById(mCurrentId); |
||||
} |
||||
|
||||
@SuppressWarnings("WeakerAccess,unused") |
||||
protected void onFragmentCreated(int id, Fragment newFragment) { |
||||
} |
||||
|
||||
public void onSaveInstanceState(Bundle bundle) { |
||||
bundle.putInt(CURRENT_ID_KET, mCurrentId); |
||||
} |
||||
|
||||
public static abstract class Tabs { |
||||
|
||||
private final List<FragmentInfo> mPages; |
||||
|
||||
protected Tabs() { |
||||
mPages = new ArrayList<>(); |
||||
} |
||||
|
||||
protected void add(FragmentInfo page) { |
||||
mPages.add(page); |
||||
} |
||||
|
||||
FragmentInfo homePage() { |
||||
return mPages.get(defaultIndex()); |
||||
} |
||||
|
||||
protected int defaultIndex() { |
||||
return 0; |
||||
} |
||||
|
||||
public int size() { |
||||
return mPages.size(); |
||||
} |
||||
|
||||
FragmentInfo getFragmentInfo(int id) { |
||||
for (FragmentInfo page : mPages) { |
||||
if (page.getPageId() == id) { |
||||
return page; |
||||
} |
||||
} |
||||
throw new IllegalArgumentException("MainPages not has this pageId :" + id); |
||||
} |
||||
|
||||
/** |
||||
* @param clazz Fragment对应的clazz |
||||
* @return pagerId ,没有则返回-1 |
||||
*/ |
||||
@SuppressWarnings("unused") |
||||
int getIdByClazz(Class<? extends Fragment> clazz) { |
||||
for (FragmentInfo page : mPages) { |
||||
if (page.getClazz() == clazz) { |
||||
return page.getPageId(); |
||||
} |
||||
} |
||||
return -1; |
||||
} |
||||
|
||||
List<FragmentInfo> getPages() { |
||||
return Collections.unmodifiableList(mPages); |
||||
} |
||||
|
||||
private int getPositionById(int tabId) { |
||||
int size = mPages.size(); |
||||
for (int i = 0; i < size; i++) { |
||||
if (mPages.get(i).getPageId() == tabId) { |
||||
return i; |
||||
} |
||||
} |
||||
return -1; |
||||
} |
||||
|
||||
private int getIdByPosition(int position) { |
||||
return mPages.get(position).getPageId(); |
||||
} |
||||
} |
||||
|
||||
} |
@ -0,0 +1,52 @@ |
||||
package com.android.base.app.fragment |
||||
|
||||
import android.os.Bundle |
||||
import androidx.lifecycle.LifecycleOwner |
||||
import androidx.lifecycle.lifecycleScope |
||||
import com.android.base.app.ui.LoadingView |
||||
import kotlinx.coroutines.CancellationException |
||||
import kotlinx.coroutines.delay |
||||
import kotlinx.coroutines.launch |
||||
|
||||
/** |
||||
*@author Ztiany |
||||
* Email: ztiany3@gmail.com |
||||
* Date : 2019-10-16 17:28 |
||||
*/ |
||||
internal fun <T> T.dismissDialog(recentShowingDialogTime: Long, minimumMills: Long, onDismiss: () -> Unit) where T : LoadingView, T : LifecycleOwner { |
||||
|
||||
if (!isLoadingDialogShowing()) { |
||||
onDismiss() |
||||
return |
||||
} |
||||
|
||||
val dialogShowingTime = System.currentTimeMillis() - recentShowingDialogTime |
||||
|
||||
if (dialogShowingTime >= minimumMills) { |
||||
dismissLoadingDialog() |
||||
onDismiss() |
||||
} else { |
||||
lifecycleScope.launch { |
||||
try { |
||||
delay(minimumMills - dialogShowingTime) |
||||
dismissLoadingDialog() |
||||
onDismiss() |
||||
} catch (e: CancellationException) { |
||||
onDismiss() |
||||
} |
||||
} |
||||
} |
||||
|
||||
} |
||||
|
||||
private const val FRAGMENT_STATE_KEY = "fragment_state_key" |
||||
|
||||
internal fun saveInstanceState(outState: Bundle, _state: Bundle?) { |
||||
_state?.let { |
||||
outState.putBundle(FRAGMENT_STATE_KEY, it) |
||||
} |
||||
} |
||||
|
||||
internal fun getInstanceState(state: Bundle?): Bundle? { |
||||
return state?.getBundle(FRAGMENT_STATE_KEY) |
||||
} |
@ -0,0 +1,44 @@ |
||||
package com.android.base.app.mvp |
||||
|
||||
import androidx.annotation.CallSuper |
||||
import java.lang.ref.WeakReference |
||||
|
||||
abstract class AbstractPresenter<V : IBaseView> : IPresenter<V> { |
||||
|
||||
private var _view: WeakReference<V>? = null |
||||
|
||||
protected val view: V? |
||||
get() = if (_view != null) { |
||||
_view?.get() |
||||
} else null |
||||
|
||||
protected val isViewAttached: Boolean |
||||
get() = _view != null && _view?.get() != null |
||||
|
||||
final override fun bindView(view: V?) { |
||||
if (view == null) { |
||||
throw NullPointerException("Presenter bindView --> view is null") |
||||
} |
||||
if (_view != null) { |
||||
throw UnsupportedOperationException("Presenter bindView --> the view already bind") |
||||
} |
||||
_view = WeakReference(view) |
||||
} |
||||
|
||||
override fun onPostStart() {} |
||||
|
||||
override fun onPause() { |
||||
} |
||||
|
||||
override fun onResume() { |
||||
} |
||||
|
||||
@CallSuper |
||||
override fun onDestroy() { |
||||
if (_view != null) { |
||||
_view?.clear() |
||||
_view = null |
||||
} |
||||
} |
||||
|
||||
} |
@ -0,0 +1,3 @@ |
||||
package com.android.base.app.mvp |
||||
|
||||
interface IBaseView |
@ -0,0 +1,13 @@ |
||||
package com.android.base.app.mvp |
||||
|
||||
|
||||
interface IPresenter<V : IBaseView> : Lifecycle { |
||||
|
||||
/** |
||||
* bind a view |
||||
* |
||||
* @param view V |
||||
*/ |
||||
fun bindView(view: V?) |
||||
|
||||
} |
@ -0,0 +1,31 @@ |
||||
package com.android.base.app.mvp |
||||
|
||||
|
||||
/** |
||||
* @author Ztiany |
||||
* Email: 1169654504@qq.com |
||||
* Date : 2017-04-18 16:23 |
||||
*/ |
||||
interface Lifecycle { |
||||
|
||||
/** |
||||
* start the Lifecycle , initialize something, will be called only once |
||||
*/ |
||||
fun onStart() |
||||
|
||||
/** |
||||
* will be called when view is ready. |
||||
*/ |
||||
fun onPostStart() |
||||
|
||||
fun onResume() |
||||
|
||||
fun onPause() |
||||
|
||||
/** |
||||
* destroy the Lifecycle and release resource, will be called only once |
||||
*/ |
||||
fun onDestroy() |
||||
|
||||
} |
||||
|
@ -0,0 +1,66 @@ |
||||
package com.android.base.app.mvp |
||||
|
||||
import android.os.Bundle |
||||
import androidx.fragment.app.Fragment |
||||
import android.view.View |
||||
import com.android.base.app.fragment.delegates.FragmentDelegate |
||||
import com.android.base.app.fragment.delegates.FragmentDelegateOwner |
||||
|
||||
|
||||
class PresenterBinder constructor(private val lifecycle: Lifecycle) : FragmentDelegate<Fragment> { |
||||
|
||||
private var isCalled: Boolean = false |
||||
private var host: Fragment? = null |
||||
|
||||
override fun onAttachToFragment(fragment: Fragment) { |
||||
host = fragment |
||||
} |
||||
|
||||
override fun onDetachFromFragment() { |
||||
host = null |
||||
} |
||||
|
||||
override fun onActivityCreated(savedInstanceState: Bundle?) { |
||||
if (!isCalled) { |
||||
lifecycle.onStart() |
||||
val activity = host?.activity |
||||
activity?.findViewById<View>(android.R.id.content)?.post { lifecycle.onPostStart() } |
||||
isCalled = true |
||||
} |
||||
} |
||||
|
||||
override fun onResume() { |
||||
lifecycle.onResume() |
||||
} |
||||
|
||||
override fun onPause() { |
||||
lifecycle.onPause() |
||||
} |
||||
|
||||
override fun onDestroy() { |
||||
lifecycle.onDestroy() |
||||
} |
||||
|
||||
companion object { |
||||
|
||||
/** |
||||
* @param v The MVP of the V |
||||
* @param p The MVP of the P |
||||
* @param <V> The MVP of the V |
||||
</V> */ |
||||
fun <V : IBaseView> bind(fragmentDelegateOwner: FragmentDelegateOwner, v: V, p: IPresenter<V>): PresenterBinder { |
||||
p.bindView(v) |
||||
val lifecycleDelegate = PresenterBinder(p) |
||||
fragmentDelegateOwner.addDelegate(lifecycleDelegate) |
||||
return lifecycleDelegate |
||||
} |
||||
} |
||||
|
||||
} |
||||
|
||||
fun <V : IBaseView> FragmentDelegateOwner.bindPresenter(v: V, p: IPresenter<V>): PresenterBinder { |
||||
p.bindView(v) |
||||
val lifecycleDelegate = PresenterBinder(p) |
||||
addDelegate(lifecycleDelegate) |
||||
return lifecycleDelegate |
||||
} |
@ -0,0 +1,65 @@ |
||||
package com.android.base.app.mvp |
||||
|
||||
import androidx.annotation.CallSuper |
||||
import com.android.base.rx.autodispose.AutoDisposeLifecycleScopeProviderEx |
||||
import com.uber.autodispose.lifecycle.CorrespondingEventsFunction |
||||
import com.uber.autodispose.lifecycle.LifecycleEndedException |
||||
import com.uber.autodispose.lifecycle.LifecycleScopes |
||||
import io.reactivex.CompletableSource |
||||
import io.reactivex.Observable |
||||
import io.reactivex.subjects.BehaviorSubject |
||||
|
||||
|
||||
/** |
||||
* work with RxJava |
||||
* |
||||
* @author Ztiany |
||||
* Date : 2016-10-19 12:17 |
||||
* Email: 1169654504@qq.com |
||||
*/ |
||||
abstract class RxPresenter<V : IBaseView> : AbstractPresenter<V>(), AutoDisposeLifecycleScopeProviderEx<RxPresenter.LifecycleEvent> { |
||||
|
||||
private val lifecycleSubject = BehaviorSubject.create<LifecycleEvent>() |
||||
|
||||
enum class LifecycleEvent { |
||||
START, |
||||
DESTROY |
||||
} |
||||
|
||||
@CallSuper |
||||
override fun onStart() { |
||||
lifecycleSubject.onNext(LifecycleEvent.START) |
||||
} |
||||
|
||||
@CallSuper |
||||
override fun onDestroy() { |
||||
lifecycleSubject.onNext(LifecycleEvent.DESTROY) |
||||
super@RxPresenter.onDestroy() |
||||
} |
||||
|
||||
final override fun lifecycle(): Observable<LifecycleEvent> { |
||||
return lifecycleSubject |
||||
} |
||||
|
||||
final override fun correspondingEvents(): CorrespondingEventsFunction<LifecycleEvent> { |
||||
return LIFECYCLE_CORRESPONDING_EVENTS |
||||
} |
||||
|
||||
final override fun peekLifecycle(): LifecycleEvent? { |
||||
return lifecycleSubject.value |
||||
} |
||||
|
||||
final override fun requestScope(): CompletableSource { |
||||
return LifecycleScopes.resolveScopeFromLifecycle<LifecycleEvent>(this) |
||||
} |
||||
|
||||
companion object { |
||||
internal val LIFECYCLE_CORRESPONDING_EVENTS: CorrespondingEventsFunction<LifecycleEvent> = CorrespondingEventsFunction { |
||||
return@CorrespondingEventsFunction when (it) { |
||||
LifecycleEvent.START -> LifecycleEvent.DESTROY |
||||
else -> throw LifecycleEndedException("Cannot bind to LifecycleEvent when outside of it.") |
||||
} |
||||
} |
||||
} |
||||
|
||||
} |
@ -0,0 +1,61 @@ |
||||
package com.android.base.app.mvvm |
||||
|
||||
|
||||
import androidx.annotation.CallSuper |
||||
import androidx.lifecycle.ViewModel |
||||
import com.android.base.rx.autodispose.AutoDisposeLifecycleScopeProviderEx |
||||
import com.uber.autodispose.lifecycle.CorrespondingEventsFunction |
||||
import com.uber.autodispose.lifecycle.LifecycleEndedException |
||||
import com.uber.autodispose.lifecycle.LifecycleScopes |
||||
import io.reactivex.CompletableSource |
||||
import io.reactivex.Observable |
||||
import io.reactivex.subjects.BehaviorSubject |
||||
|
||||
/** |
||||
* ArchViewModel work with Rx |
||||
* |
||||
* @author Ztiany |
||||
* Email: 1169654504@qq.com |
||||
* Date : 2017-04-18 16:25 |
||||
*/ |
||||
abstract class RxViewModel : ViewModel(), AutoDisposeLifecycleScopeProviderEx<RxViewModel.ViewModelEvent> { |
||||
|
||||
private val archLifecycleSubject = BehaviorSubject.createDefault(ViewModelEvent.CREATED) |
||||
|
||||
enum class ViewModelEvent { |
||||
CREATED, CLEARED |
||||
} |
||||
|
||||
@CallSuper |
||||
override fun onCleared() { |
||||
archLifecycleSubject.onNext(ViewModelEvent.CLEARED) |
||||
super.onCleared() |
||||
} |
||||
|
||||
final override fun correspondingEvents(): CorrespondingEventsFunction<ViewModelEvent> { |
||||
return CORRESPONDING_EVENTS |
||||
} |
||||
|
||||
final override fun lifecycle(): Observable<ViewModelEvent> { |
||||
return archLifecycleSubject.hide() |
||||
} |
||||
|
||||
final override fun peekLifecycle(): ViewModelEvent? { |
||||
return archLifecycleSubject.value |
||||
} |
||||
|
||||
final override fun requestScope(): CompletableSource { |
||||
return LifecycleScopes.resolveScopeFromLifecycle(this) |
||||
} |
||||
|
||||
companion object { |
||||
private val CORRESPONDING_EVENTS = CorrespondingEventsFunction<ViewModelEvent> { event -> |
||||
when (event) { |
||||
ViewModelEvent.CREATED -> ViewModelEvent.CLEARED |
||||
else -> throw LifecycleEndedException( |
||||
"Cannot bind to ViewModel lifecycle after onCleared.") |
||||
} |
||||
} |
||||
} |
||||
|
||||
} |
@ -0,0 +1,39 @@ |
||||
package com.android.base.app.ui; |
||||
|
||||
import com.android.base.adapter.DataManager; |
||||
|
||||
/** |
||||
* @author Ztiany |
||||
* Email: ztiany3@gmail.com |
||||
* Date : 2018-04-27 14:04 |
||||
*/ |
||||
public class AutoPageNumber extends PageNumber { |
||||
|
||||
private final DataManager mDataManager; |
||||
private final RefreshListLayout mRefreshListLayout; |
||||
|
||||
public AutoPageNumber(RefreshListLayout refreshListLayout, DataManager dataManager) { |
||||
mRefreshListLayout = refreshListLayout; |
||||
mDataManager = dataManager; |
||||
} |
||||
|
||||
@Override |
||||
public int getCurrentPage() { |
||||
return calcPageNumber(mDataManager.getDataSize()); |
||||
} |
||||
|
||||
@Override |
||||
public int getLoadingPage() { |
||||
if (mRefreshListLayout.isRefreshing()) { |
||||
return getPageStart(); |
||||
} else { |
||||
return getCurrentPage() + 1; |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
public int getItemCount() { |
||||
return mDataManager.getDataSize(); |
||||
} |
||||
|
||||
} |
@ -0,0 +1,22 @@ |
||||
package com.android.base.app.ui; |
||||
|
||||
|
||||
import com.android.base.R; |
||||
|
||||
public class CommonId { |
||||
|
||||
private CommonId() { |
||||
throw new UnsupportedOperationException(); |
||||
} |
||||
|
||||
public static final int REFRESH_ID = R.id.base_refresh_layout; |
||||
public static final int STATE_ID = R.id.base_state_layout; |
||||
public static final int LIST_ID = R.id.base_list_layout; |
||||
|
||||
public static final int RETRY_TV_ID = R.id.base_retry_tv; |
||||
public static final int RETRY_IV_ID = R.id.base_retry_icon; |
||||
public static final int RETRY_BTN_ID = R.id.base_retry_btn; |
||||
|
||||
public static final int TOOLBAR_ID = R.id.common_toolbar; |
||||
|
||||
} |
@ -0,0 +1,11 @@ |
||||
package com.android.base.app.ui |
||||
|
||||
import androidx.lifecycle.LifecycleOwner |
||||
import androidx.lifecycle.LiveData |
||||
import androidx.lifecycle.Observer |
||||
import com.android.base.app.Sword |
||||
import com.android.base.data.State |
||||
import timber.log.Timber |
||||
|
||||
|
||||
|
@ -0,0 +1,25 @@ |
||||
package com.android.base.app.ui |
||||
|
||||
import androidx.annotation.StringRes |
||||
|
||||
/** |
||||
* 显示通用的 LoadingDialog 和 Message |
||||
* |
||||
* @author Ztiany |
||||
* Email: 1169654504@qq.com |
||||
* Date : 2016-12-02 15:12 |
||||
*/ |
||||
interface LoadingView { |
||||
|
||||
fun showLoadingDialog() |
||||
fun showLoadingDialog(cancelable: Boolean) |
||||
fun showLoadingDialog(message: CharSequence, cancelable: Boolean) |
||||
fun showLoadingDialog(@StringRes messageId: Int, cancelable: Boolean) |
||||
fun dismissLoadingDialog() |
||||
fun dismissLoadingDialog(minimumMills: Long, onDismiss: () -> Unit) |
||||
fun isLoadingDialogShowing(): Boolean |
||||
|
||||
fun showMessage(message: CharSequence) |
||||
fun showMessage(@StringRes messageId: Int) |
||||
|
||||
} |
@ -0,0 +1,9 @@ |
||||
package com.android.base.app.ui; |
||||
|
||||
import static com.android.base.app.ui.StateLayoutConfig.*; |
||||
|
||||
public interface OnRetryActionListener { |
||||
|
||||
void onRetry(@RetryableState int state); |
||||
|
||||
} |
@ -0,0 +1,99 @@ |
||||
package com.android.base.app.ui; |
||||
|
||||
/** |
||||
* @author Ztiany |
||||
* @version 1.0 |
||||
*/ |
||||
public abstract class PageNumber { |
||||
|
||||
private static int PAGE_START = 1; |
||||
private static int PAGE_SIZE = 20; |
||||
|
||||
private int mPageStart; |
||||
private int mPageSize; |
||||
|
||||
private Object mPageToken; |
||||
|
||||
@SuppressWarnings("WeakerAccess,unused") |
||||
public PageNumber() { |
||||
this(PAGE_START, PAGE_SIZE); |
||||
} |
||||
|
||||
@SuppressWarnings("WeakerAccess") |
||||
public PageNumber(int pageStart, int pageSize) { |
||||
mPageStart = pageStart; |
||||
mPageSize = pageSize; |
||||
} |
||||
|
||||
@SuppressWarnings("unused") |
||||
public void setPageToken(Object pageToken) { |
||||
mPageToken = pageToken; |
||||
} |
||||
|
||||
@SuppressWarnings("unchecked,unused") |
||||
public <T> T getPageToken() { |
||||
return (T) mPageToken; |
||||
} |
||||
|
||||
@SuppressWarnings("unused") |
||||
public int getPageSize() { |
||||
return mPageSize; |
||||
} |
||||
|
||||
@SuppressWarnings("WeakerAccess") |
||||
public int getPageStart() { |
||||
return mPageStart; |
||||
} |
||||
|
||||
public boolean hasMore(int size) { |
||||
return size >= mPageSize; |
||||
} |
||||
|
||||
/** |
||||
* 根据page size计算当前的页码 |
||||
*/ |
||||
int calcPageNumber(int dataSize) { |
||||
/* s=1 s=0 |
||||
19/20 = 0 1 0 |
||||
21/20 = 1 2 1 |
||||
54/20 = 2 3 2 |
||||
64/20 = 3 4 3 |
||||
*/ |
||||
int pageNumber; |
||||
int pageSize = mPageSize; |
||||
int pageStart = mPageStart; |
||||
if (pageStart == 0) { |
||||
pageNumber = (dataSize / pageSize) - 1; |
||||
pageNumber = pageNumber < 0 ? 0 : pageNumber; |
||||
} else if (pageStart == 1) { |
||||
pageNumber = (dataSize / pageSize); |
||||
pageNumber = pageNumber < 1 ? 1 : pageNumber; |
||||
} else { |
||||
throw new RuntimeException("pageStart must be 0 or 1"); |
||||
} |
||||
return pageNumber; |
||||
} |
||||
|
||||
@SuppressWarnings("unused") |
||||
public void changePageSetting(int pageStart, int pageSize) { |
||||
mPageStart = pageStart; |
||||
mPageSize = pageSize; |
||||
} |
||||
|
||||
public static void setDefaultPageStart(int pageSize) { |
||||
PAGE_START = pageSize; |
||||
} |
||||
|
||||
public static void setDefaultPageSize(int pageSize) { |
||||
PAGE_SIZE = pageSize; |
||||
} |
||||
|
||||
public abstract int getCurrentPage(); |
||||
|
||||
@SuppressWarnings("unused") |
||||
public abstract int getLoadingPage(); |
||||
|
||||
@SuppressWarnings("unused") |
||||
public abstract int getItemCount(); |
||||
|
||||
} |
@ -0,0 +1,31 @@ |
||||
package com.android.base.app.ui; |
||||
|
||||
import java.util.List; |
||||
|
||||
/** |
||||
* 列表视图行为 |
||||
* |
||||
* @author Ztiany |
||||
* Email: 1169654504@qq.com |
||||
* Date : 2017-03-29 22:16 |
||||
*/ |
||||
public interface RefreshListLayout<T> extends RefreshStateLayout { |
||||
|
||||
void replaceData(List<T> data); |
||||
|
||||
void addData(List<T> data); |
||||
|
||||
PageNumber getPager(); |
||||
|
||||
boolean isEmpty(); |
||||
|
||||
void loadMoreCompleted(boolean hasMore); |
||||
|
||||
void loadMoreFailed(); |
||||
|
||||
boolean isLoadingMore(); |
||||
|
||||
@Override |
||||
boolean isRefreshing(); |
||||
|
||||
} |
@ -0,0 +1,40 @@ |
||||
package com.android.base.app.ui; |
||||
|
||||
/** |
||||
* <br/> 对下拉刷新的抽象 |
||||
* <br/> Email: 1169654504@qq.com |
||||
* |
||||
* @author Ztiany |
||||
* @version 1.0 |
||||
*/ |
||||
public interface RefreshLoadMoreView { |
||||
|
||||
void autoRefresh(); |
||||
|
||||
void refreshCompleted(); |
||||
|
||||
void loadMoreCompleted(boolean hasMore); |
||||
|
||||
void loadMoreFailed(); |
||||
|
||||
void setRefreshHandler(RefreshHandler refreshHandler); |
||||
|
||||
void setLoadMoreHandler(LoadMoreHandler loadMoreHandler); |
||||
|
||||
boolean isRefreshing(); |
||||
|
||||
boolean isLoadingMore(); |
||||
|
||||
void setRefreshEnable(boolean enable); |
||||
|
||||
void setLoadMoreEnable(boolean enable); |
||||
|
||||
interface RefreshHandler { |
||||
void onRefresh(); |
||||
} |
||||
|
||||
interface LoadMoreHandler { |
||||
void onRefresh(); |
||||
} |
||||
|
||||
} |
@ -0,0 +1,31 @@ |
||||
package com.android.base.app.ui; |
||||
|
||||
import android.view.View; |
||||
|
||||
/** |
||||
* RefreshLoadMoreView Factory |
||||
* |
||||
* @author Ztiany |
||||
* Email: 1169654504@qq.com |
||||
* @version 1.0 |
||||
*/ |
||||
public class RefreshLoadViewFactory { |
||||
|
||||
private static Factory sFactory; |
||||
|
||||
public static RefreshLoadMoreView createRefreshView(View view) { |
||||
if (sFactory != null) { |
||||
return sFactory.createRefreshView(view); |
||||
} |
||||
throw new IllegalArgumentException("RefreshLoadViewFactory does not support create RefreshLoadMoreView . the view :" + view); |
||||
} |
||||
|
||||
public static void registerFactory(Factory factory) { |
||||
sFactory = factory; |
||||
} |
||||
|
||||
public interface Factory { |
||||
RefreshLoadMoreView createRefreshView(View view); |
||||
} |
||||
|
||||
} |
@ -0,0 +1,11 @@ |
||||
package com.android.base.app.ui; |
||||
|
||||
public interface RefreshStateLayout extends StateLayout{ |
||||
|
||||
void autoRefresh(); |
||||
|
||||
void refreshCompleted(); |
||||
|
||||
boolean isRefreshing(); |
||||
|
||||
} |
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue