6.7.0 - Alpha23 - 移除 "android.enableJetifier=true"; 模块化 Expandable Layout, Expandable RecyclerView, Recyclerview Flexible Divider

This commit is contained in:
SuperMonster003
2026-03-07 19:35:05 +08:00
parent 0cc28bdb6c
commit 528f3e59dc
47 changed files with 4673 additions and 75 deletions

View File

@@ -86,8 +86,8 @@
"نص بناء Gradle أصبح أكثر قدرة على التكيّف مع الإصدارات الجديدة _[`discussion #369`](http://discussions.autojs6.com/369)_"
],
"dependency": [
"مرفق Material Dialogs الإصدار 0.9.6.0 (محلي)",
"مرفق Material Date Time Picker الإصدار 4.2.3 (محلي)",
"مرفق Material Dialogs الإصدار 0.9.6.0 (تجزئة إلى وحدات)",
"مرفق Material Date Time Picker الإصدار 4.2.3 (تجزئة إلى وحدات)",
"مرفق libimagequant الإصدار 2.17.0 (محلي)",
"مرفق libpng الإصدار 1.6.49 (محلي)",
"مضاف ICU4J الإصدار 77.1",

View File

@@ -86,8 +86,8 @@
"Gradle build script now better adapts to newer versions _[`discussion #369`](http://discussions.autojs6.com/369)_"
],
"dependency": [
"Bundled Material Dialogs 0.9.6.0 (localized)",
"Bundled Material Date Time Picker 4.2.3 (localized)",
"Bundled Material Dialogs 0.9.6.0 (modularized)",
"Bundled Material Date Time Picker 4.2.3 (modularized)",
"Bundled libimagequant 2.17.0 (localized)",
"Bundled libpng 1.6.49 (localized)",
"Added ICU4J 77.1",

View File

@@ -86,8 +86,8 @@
"El script de compilación Gradle mejora su capacidad de adaptación de versiones _[`discussion #369`](http://discussions.autojs6.com/369)_"
],
"dependency": [
"Incluido Material Dialogs versión 0.9.6.0 (localizado)",
"Incluido Material Date Time Picker versión 4.2.3 (localizado)",
"Incluido Material Dialogs versión 0.9.6.0 (modularizado)",
"Incluido Material Date Time Picker versión 4.2.3 (modularizado)",
"Incluido libimagequant versión 2.17.0 (localizado)",
"Incluido libpng versión 1.6.49 (localizado)",
"Añadido ICU4J versión 77.1",

View File

@@ -86,8 +86,8 @@
"Le script de build Gradle améliore sa capacité d'adaptation aux nouvelles versions _[`discussion #369`](http://discussions.autojs6.com/369)_"
],
"dependency": [
"Inclus Material Dialogs version 0.9.6.0 (localisé)",
"Inclus Material Date Time Picker version 4.2.3 (localisé)",
"Inclus Material Dialogs version 0.9.6.0 (modularise)",
"Inclus Material Date Time Picker version 4.2.3 (modularise)",
"Inclus libimagequant version 2.17.0 (localisé)",
"Inclus libpng version 1.6.49 (localisé)",
"Ajouté ICU4J version 77.1",

View File

@@ -86,8 +86,8 @@
"Gradle ビルドスクリプトが新しいバージョンに柔軟に対応 _[`discussion #369`](http://discussions.autojs6.com/369)_"
],
"dependency": [
"同梱 Material Dialogs バージョン 0.9.6.0 (ローカライズ済み)",
"同梱 Material Date Time Picker バージョン 4.2.3 (ローカライズ済み)",
"同梱 Material Dialogs バージョン 0.9.6.0 (モジュール化)",
"同梱 Material Date Time Picker バージョン 4.2.3 (モジュール化)",
"同梱 libimagequant バージョン 2.17.0 (ローカライズ済み)",
"同梱 libpng バージョン 1.6.49 (ローカライズ済み)",
"追加 ICU4J バージョン 77.1",

View File

@@ -86,8 +86,8 @@
"Gradle 빌드 스크립트가 최신 버전에 더 유연하게 대응 _[`discussion #369`](http://discussions.autojs6.com/369)_"
],
"dependency": [
"내장 Material Dialogs 버전 0.9.6.0 (현지화)",
"내장 Material Date Time Picker 버전 4.2.3 (현지화)",
"내장 Material Dialogs 버전 0.9.6.0 (모듈화)",
"내장 Material Date Time Picker 버전 4.2.3 (모듈화)",
"내장 libimagequant 버전 2.17.0 (현지화)",
"내장 libpng 버전 1.6.49 (현지화)",
"추가 ICU4J 버전 77.1",

View File

@@ -86,8 +86,8 @@
"Скрипт сборки Gradle лучше адаптируется к новым версиям _[`discussion #369`](http://discussions.autojs6.com/369)_"
],
"dependency": [
"Встроено Material Dialogs версии 0.9.6.0 (локализовано)",
"Встроено Material Date Time Picker версии 4.2.3 (локализовано)",
"Встроено Material Dialogs версии 0.9.6.0 (модульный)",
"Встроено Material Date Time Picker версии 4.2.3 (модульный)",
"Встроено libimagequant версии 2.17.0 (локализовано)",
"Встроено libpng версии 1.6.49 (локализовано)",
"Добавлено ICU4J версии 77.1",

View File

@@ -177,18 +177,22 @@
"使用 [版本目录 (Version Catalogs)](https://developer.android.com/build/migrate-to-catalogs) 集中管理 Gradle 依赖和插件版本",
"模块化 Gradle 构建脚本, 将共享构建逻辑迁移至 build-logic 并抽象为约定插件",
"使用 Gradle 约定插件简化本地 AAR 库加载逻辑",
"移除 \"android.enableJetifier=true\" Gradle 配置选项以适配 Gradle 9",
"使用 [Toolchain](https://docs.gradle.org/current/userguide/toolchains.html) 替代 sourceCompatibility/targetCompatibility 以降低构建环境差异"
],
"dependency": [
"模块化 Expandable Layout 版本 1.6.0",
"模块化 Expandable RecyclerView 版本 3.0.0-RC1",
"模块化 Recyclerview Flexible Divider 版本 1.4.0",
"本地化 Root Shell 版本 1.6",
"本地化 Expandable Layout 版本 1.6.0",
"本地化 Recyclerview Flexible Divider 版本 1.4.0",
"附加 AndroidX Activity (KTX) 版本 1.12.2",
"附加 AndroidX Core (KTX) 版本 1.15.0",
"附加 AndroidX Work Runtime 版本 2.11.0",
"附加 AndroidX Interpolator 版本 1.0.0",
"附加 Room RxJava2 版本 2.8.1",
"升级 Android SDK 版本 35 -> 36",
"升级 Gradle 版本 8.14 -> 8.14.3",
"升级 Gradle 8 版本 8.14 -> 8.14.4",
"升级 Gradle 9 版本 8.14 -> 9.4.0",
"升级 Rhino 版本 1.8.1-SNAPSHOT -> 2.0.0-SNAPSHOT",
"升级 Desugar JDK Libs 版本 2.0.4 -> 2.1.5",
"升级 Android Material 版本 1.12.0 -> 1.13.0",
@@ -205,7 +209,10 @@
"升级 ARSCLib 版本 1.3.1 -> 1.3.5",
"升级 Jsoup 版本 1.19.1 -> 1.20.1",
"升级 Junit Jupiter 版本 5.10.3 -> 5.13.0",
"升级 OpenCC 版本 1.2.0 -> 1.2.2"
"升级 OpenCC 版本 1.2.0 -> 1.2.2",
"移除 Android Support Annotation v7 版本 28.0.0",
"移除 Android Support AppCompat v7 版本 28.0.0",
"移除 Android Support RecyclerView v7 版本 28.0.0"
]
},
"v6.6.4": {
@@ -294,8 +301,8 @@
"Gradle 构建脚本提升版本自适应能力 _[`discussion #369`](http://discussions.autojs6.com/369)_"
],
"dependency": [
"本地化 Material Dialogs 版本 0.9.6.0",
"本地化 Material Date Time Picker 版本 4.2.3",
"模块化 Material Dialogs 版本 0.9.6.0",
"模块化 Material Date Time Picker 版本 4.2.3",
"本地化 libimagequant 版本 2.17.0",
"本地化 libpng 版本 1.6.49",
"附加 ICU4J 版本 77.1",

View File

@@ -86,8 +86,8 @@
"Gradle 構建腳本提升版本自適應能力 _[`discussion #369`](http://discussions.autojs6.com/369)_"
],
"dependency": [
"本地化 Material Dialogs 版本 0.9.6.0",
"本地化 Material Date Time Picker 版本 4.2.3",
"模塊化 Material Dialogs 版本 0.9.6.0",
"模塊化 Material Date Time Picker 版本 4.2.3",
"本地化 libimagequant 版本 2.17.0",
"本地化 libpng 版本 1.6.49",
"附加 ICU4J 版本 77.1",

View File

@@ -86,8 +86,8 @@
"Gradle 構建指令碼提升版本自適應能力 _[`discussion #369`](http://discussions.autojs6.com/369)_"
],
"dependency": [
"本地化 Material Dialogs 版本 0.9.6.0",
"本地化 Material Date Time Picker 版本 4.2.3",
"模組化 Material Dialogs 版本 0.9.6.0",
"模組化 Material Date Time Picker 版本 4.2.3",
"本地化 libimagequant 版本 2.17.0",
"本地化 libpng 版本 1.6.49",
"附加 ICU4J 版本 77.1",

View File

@@ -284,9 +284,9 @@ dependencies /* Test */ {
dependencies /* Annotations */ {
// Android Annotations
implementation(libs.androidannotations.api)
implementation(libs.android.annotations.api)
implementation(libs.annotation)
ksp(libs.androidannotations)
ksp(libs.android.annotations)
// JCIP Annotations
implementation(libs.jcip.annotations)
@@ -332,14 +332,12 @@ dependencies /* Material Dialogs */ {
dependencies /* Layout */ {
// Expandable Layout
// implementation("com.github.aakira:expandable-layout:1.6.0")
implementation(project(":libs:expandable-layout-1_6_0"))
// implementation(project(":libs:expandable-layout-1_6_0"))
implementation(project(":modules:expandable-layout"))
// Expandable RecyclerView
implementation(libs.expandablerecyclerview)
implementation(project(":modules:expandable-recyclerview"))
// Flexible Divider
// implementation("com.yqritc:recyclerview-flexibledivider:1.4.0")
implementation(project(":libs:recyclerview-flexibledivider-1_4_0"))
implementation(project(":modules:recyclerview-flexibledivider"))
}
dependencies /* View */ {

View File

@@ -115,7 +115,7 @@ class Selector(private val scriptRuntime: ScriptRuntime) : Augmentable(scriptRun
when {
arg !is Double -> arg
paramType == Double::class.java || paramType == java.lang.Double.TYPE -> arg
paramType == Float::class.java || paramType == java.lang.Float.TYPE -> arg.toDouble()
paramType == Float::class.java || paramType == java.lang.Float.TYPE -> arg.toFloat()
paramType == Long::class.java || paramType == java.lang.Long.TYPE -> arg.roundToLong()
paramType == Int::class.java || paramType == Integer.TYPE -> arg.roundToInt()
paramType == Short::class.java || paramType == java.lang.Short.TYPE -> arg.toInt().toShort()

View File

@@ -10,7 +10,7 @@
# This option should only be used with decoupled projects. More details, visit
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
# org.gradle.parallel=true
#Thu Nov 11 16:38:18 CST 2021
# Thu Nov 11 16:38:18 CST 2021
# Configure memory settings for Gradle daemon and Kotlin daemon
# -Xms: Initial heap size
# -Xmx: Maximum heap size
@@ -29,11 +29,6 @@ org.gradle.parallel=true
# https://developer.android.com/topic/libraries/support-library/androidx-rn
# https://developer.android.com/jetpack/androidx/migrate
android.useAndroidX=true
# When this flag is set to true,
# the Android plugin automatically migrates existing third-party libraries
# to use AndroidX dependencies by rewriting their binaries.
# https://developer.android.com/jetpack/androidx/migrate
android.enableJetifier=true
# Enables namespacing of each library's R class so that its R class includes only the
# resources declared in the library itself and none from the library's dependencies,
# thereby reducing the size of the R class for that library
@@ -44,8 +39,15 @@ android.nonFinalResIds=true
# auto-download: Automatically download required Java version if not found
org.gradle.java.installations.auto-detect=true
org.gradle.java.installations.auto-download=true
# BuildConfig feature flag history:
# - Added in Gradle Build Tool 8.0.0-alpha09
# - Removed in Gradle Build Tool 8.2.0-alpha15
# - Controls generation of BuildConfig class
# ====== Archived ======
# ------ Jetifier ------
# When this flag is set to true,
# the Android plugin automatically migrates existing third-party libraries
# to use AndroidX dependencies by rewriting their binaries.
# https://developer.android.com/jetpack/androidx/migrate
# // Removed since Gradle 9.4.0
# android.enableJetifier=true
# ------ BuildConfig ------
# Controls generation of BuildConfig class
# // Removed since Gradle Build Tool 8.2.0-alpha15
# android.defaults.buildfeatures.buildconfig=true

View File

@@ -40,10 +40,9 @@ log4j = "1.2.17"
desugar = "2.1.5"
activity-ktx = "1.12.2"
android-annotations = "4.8.0"
android-device-names = "2.1.1"
android-logging-log4j = "1.0.3"
android-support-v7 = "28.0.0"
androidannotations = "4.8.0"
animated-svg-view = "1.0.6"
annotation = "1.9.1"
appcompat = "1.7.1"
@@ -60,13 +59,13 @@ commons-lang3 = "3.18.0"
constraintlayout = "2.2.1"
easywindow = "10.3"
eventbus = "3.3.1"
expandablerecyclerview = "3.0.0-RC1"
flexbox = "3.0.0"
flexmark-html2md = "0.64.8"
foojay-resolver-convention = "1.0.0"
glide = "5.0.5"
gson = "2.13.2"
icu4j = "77.1"
interpolator = "1.0.0"
jcip-annotations = "1.0"
jdeferred = "1.2.6"
joda-time = "2.14.0"
@@ -112,15 +111,14 @@ zip4j = "2.11.5"
[libraries]
activity-ktx = { module = "androidx.activity:activity-ktx", version.ref = "activity-ktx" }
android-annotations = { module = "org.androidannotations:androidannotations", version.ref = "android-annotations" }
android-annotations-api = { module = "org.androidannotations:androidannotations-api", version.ref = "android-annotations" }
android-device-names = { module = "com.jaredrummler:android-device-names", version.ref = "android-device-names" }
android-logging-log4j = { module = "de.mindpipe.android:android-logging-log4j", version.ref = "android-logging-log4j" }
androidannotations = { module = "org.androidannotations:androidannotations", version.ref = "androidannotations" }
androidannotations-api = { module = "org.androidannotations:androidannotations-api", version.ref = "androidannotations" }
animated-svg-view = { module = "com.jaredrummler:animated-svg-view", version.ref = "animated-svg-view" }
annotation = { module = "androidx.annotation:annotation", version.ref = "annotation" }
apache-commons-compress = { module = "org.apache.commons:commons-compress", version.ref = "commons-compress" }
appcompat = { module = "androidx.appcompat:appcompat", version.ref = "appcompat" }
appcompat-v7 = { module = "com.android.support:appcompat-v7", version.ref = "android-support-v7" }
arsclib = { module = "io.github.reandroid:ARSCLib", version.ref = "arsclib" }
barcode-scanning = { module = "com.google.mlkit:barcode-scanning", version.ref = "barcode-scanning" }
bcpkix-jdk15on = { module = "org.bouncycastle:bcpkix-jdk15on", version.ref = "bcpkix-jdk15on" }
@@ -136,13 +134,13 @@ core-ktx = { module = "androidx.core:core-ktx", version.ref = "core-ktx" }
desugar = { module = "com.android.tools:desugar_jdk_libs", version.ref = "desugar" }
easywindow = { module = "com.github.getActivity:EasyWindow", version.ref = "easywindow" }
eventbus = { module = "org.greenrobot:eventbus", version.ref = "eventbus" }
expandablerecyclerview = { module = "com.bignerdranch.android:expandablerecyclerview", version.ref = "expandablerecyclerview" }
flexbox = { module = "com.google.android.flexbox:flexbox", version.ref = "flexbox" }
flexmark-html2md = { module = "com.vladsch.flexmark:flexmark-html2md-converter", version.ref = "flexmark-html2md" }
glide = { module = "com.github.bumptech.glide:glide", version.ref = "glide" }
glide-ksp = { module = "com.github.bumptech.glide:ksp", version.ref = "glide" }
gson = { module = "com.google.code.gson:gson", version.ref = "gson" }
icu4j = { module = "com.ibm.icu:icu4j", version.ref = "icu4j" }
interpolator = { group = "androidx.interpolator", name = "interpolator", version.ref = "interpolator" }
jackson-databind = { module = "com.fasterxml.jackson.core:jackson-databind", version.ref = "jackson-databind" }
jcip-annotations = { module = "net.jcip:jcip-annotations", version.ref = "jcip-annotations" }
jdeferred = { module = "org.jdeferred:jdeferred-android-aar", version.ref = "jdeferred" }
@@ -170,7 +168,6 @@ preference-ktx = { module = "androidx.preference:preference-ktx", version.ref =
prov = { module = "com.madgag.spongycastle:prov", version.ref = "prov" }
r8 = { module = "com.android.tools:r8", version.ref = "r8" }
recyclerview = { module = "androidx.recyclerview:recyclerview", version.ref = "recyclerview" }
recyclerview-v7 = { module = "com.android.support:recyclerview-v7", version.ref = "android-support-v7" }
retrofit = { module = "com.squareup.retrofit2:retrofit", version.ref = "retrofit" }
retrofit-adapter-rxjava2 = { module = "com.squareup.retrofit2:adapter-rxjava2", version.ref = "retrofit" }
retrofit-converter-gson = { module = "com.squareup.retrofit2:converter-gson", version.ref = "retrofit" }
@@ -184,7 +181,6 @@ rxandroid = { module = "io.reactivex.rxjava2:rxandroid", version.ref = "rxandroi
rxjava = { module = "io.reactivex.rxjava2:rxjava", version.ref = "rxjava" }
shizuku-api = { module = "dev.rikka.shizuku:api", version.ref = "shizuku" }
shizuku-provider = { module = "dev.rikka.shizuku:provider", version.ref = "shizuku" }
support-annotations = { module = "com.android.support:support-annotations", version.ref = "android-support-v7" }
swiperefreshlayout = { module = "androidx.swiperefreshlayout:swiperefreshlayout", version.ref = "swiperefreshlayout" }
test-espresso-core = { module = "androidx.test.espresso:espresso-core", version.ref = "test-espresso-core" }
test-ext-junit = { module = "androidx.test.ext:junit", version.ref = "test-junit-version" }
@@ -200,4 +196,4 @@ zip4j = { module = "net.lingala.zip4j:zip4j", version.ref = "zip4j" }
[plugins]
foojay-resolver-convention = { id = "org.gradle.toolchains.foojay-resolver-convention", version.ref = "foojay-resolver-convention" }
foojay-resolver-convention = { id = "org.gradle.toolchains.foojay-resolver-convention", version.ref = "foojay-resolver-convention" }

View File

@@ -1,7 +0,0 @@
plugins {
id("org.autojs.build.local-arr-register-convention")
}
localAars {
files += "expandable-layout-1.6.0.aar"
}

View File

@@ -1,7 +0,0 @@
plugins {
id("org.autojs.build.local-arr-register-convention")
}
localAars {
files += "recyclerview-flexibledivider-1.4.0.aar"
}

View File

@@ -0,0 +1,22 @@
plugins {
id("org.autojs.build.versions")
id("org.autojs.build.jvm-convention")
id("com.android.library")
}
android {
namespace "com.github.aakira.expandablelayout"
compileSdkVersion versions.sdkVersionCompile
defaultConfig {
minSdkVersion versions.sdkVersionMin
targetSdkVersion versions.sdkVersionTarget
versionCode 12
versionName "1.6.0 Mod"
}
}
dependencies {
implementation libs.annotation
implementation libs.interpolator
}

View File

@@ -0,0 +1,17 @@
# Add project specific ProGuard rules here.
# By default, the flags in this file are appended to flags specified
# in /Applications/eclipse/android/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 *;
#}

View File

@@ -0,0 +1 @@
<manifest />

View File

@@ -0,0 +1,115 @@
package com.github.aakira.expandablelayout;
import android.animation.TimeInterpolator;
import androidx.annotation.IntDef;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
public interface ExpandableLayout {
/**
* Duration of expand animation
*/
int DEFAULT_DURATION = 300;
/**
* Visibility of the layout when the layout attaches
*/
boolean DEFAULT_EXPANDED = false;
/**
* Orientation of child views
*/
int HORIZONTAL = 0;
/**
* Orientation of child views
*/
int VERTICAL = 1;
/**
* Orientation of layout
*/
@Retention(RetentionPolicy.SOURCE)
@IntDef({HORIZONTAL, VERTICAL})
@interface Orientation {
}
/**
* Starts animation the state of the view to the inverse of its current state.
*/
void toggle();
/**
* Starts animation the state of the view to the inverse of its current state.
*
* @param duration
* @param interpolator use the default interpolator if the argument is null.
*/
void toggle(final long duration, @Nullable final TimeInterpolator interpolator);
/**
* Starts expand animation.
*/
void expand();
/**
* Starts expand animation.
*
* @param duration
* @param interpolator use the default interpolator if the argument is null.
*/
void expand(final long duration, @Nullable final TimeInterpolator interpolator);
/**
* Starts collapse animation.
*/
void collapse();
/**
* Starts collapse animation.
*
* @param duration
* @param interpolator use the default interpolator if the argument is null.
*/
void collapse(final long duration, @Nullable final TimeInterpolator interpolator);
/**
* Sets the expandable layout listener.
*
* @param listener ExpandableLayoutListener
*/
void setListener(@NonNull final ExpandableLayoutListener listener);
/**
* Sets the length of the animation.
* The default duration is 300 milliseconds.
*
* @param duration
*/
void setDuration(final int duration);
/**
* Sets state of expanse.
*
* @param expanded The layout is visible if expanded is true
*/
void setExpanded(final boolean expanded);
/**
* Gets state of expanse.
*
* @return true if the layout is visible
*/
boolean isExpanded();
/**
* The time interpolator used in calculating the elapsed fraction of this animation. The
* interpolator determines whether the animation runs with linear or non-linear motion,
* such as acceleration and deceleration.
* The default value is {@link android.view.animation.AccelerateDecelerateInterpolator}
*
* @param interpolator
*/
void setInterpolator(@NonNull final TimeInterpolator interpolator);
}

View File

@@ -0,0 +1,35 @@
package com.github.aakira.expandablelayout;
public interface ExpandableLayoutListener {
/**
* Notifies the start of the animation.
* Sync from android.animation.Animator.AnimatorListener.onAnimationStart(Animator animation)
*/
void onAnimationStart();
/**
* Notifies the end of the animation.
* Sync from android.animation.Animator.AnimatorListener.onAnimationEnd(Animator animation)
*/
void onAnimationEnd();
/**
* Notifies the layout is going to open.
*/
void onPreOpen();
/**
* Notifies the layout is going to equal close size.
*/
void onPreClose();
/**
* Notifies the layout opened.
*/
void onOpened();
/**
* Notifies the layout size equal closed size.
*/
void onClosed();
}

View File

@@ -0,0 +1,45 @@
package com.github.aakira.expandablelayout;
public abstract class ExpandableLayoutListenerAdapter implements ExpandableLayoutListener {
/**
* {@inheritDoc}
*/
@Override
public void onAnimationStart() {
}
/**
* {@inheritDoc}
*/
@Override
public void onAnimationEnd() {
}
/**
* {@inheritDoc}
*/
@Override
public void onPreOpen() {
}
/**
* {@inheritDoc}
*/
@Override
public void onPreClose() {
}
/**
* {@inheritDoc}
*/
@Override
public void onOpened() {
}
/**
* {@inheritDoc}
*/
@Override
public void onClosed() {
}
}

View File

@@ -0,0 +1,565 @@
package com.github.aakira.expandablelayout;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.TimeInterpolator;
import android.animation.ValueAnimator;
import android.annotation.TargetApi;
import android.content.Context;
import android.content.res.TypedArray;
import android.os.Build;
import android.os.Parcelable;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewTreeObserver;
import android.view.animation.LinearInterpolator;
import android.widget.LinearLayout;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import java.util.ArrayList;
import java.util.List;
public class ExpandableLinearLayout extends LinearLayout implements ExpandableLayout {
private int duration;
private TimeInterpolator interpolator = new LinearInterpolator();
/**
* Default state of expanse
*
* @see #defaultChildIndex
* @see #defaultPosition
*/
private boolean defaultExpanded;
/**
* You cannot define {@link #defaultExpanded}, {@link #defaultChildIndex}
* and {@link #defaultPosition} at the same time.
* {@link #defaultPosition} has priority over {@link #defaultExpanded}
* and {@link #defaultChildIndex} if you set them at the same time.
* <p/>
* <p/>
* Priority
* {@link #defaultPosition} > {@link #defaultChildIndex} > {@link #defaultExpanded}
*/
private int defaultChildIndex;
private int defaultPosition;
/**
* The close position is width from left of layout if orientation is horizontal.
* The close position is height from top of layout if orientation is vertical.
*/
private int closePosition = 0;
private ExpandableLayoutListener listener;
private ExpandableSavedState savedState;
private boolean isExpanded;
private int layoutSize = 0;
private boolean inRecyclerView = false;
private boolean isArranged = false;
private boolean isCalculatedSize = false;
private boolean isAnimating = false;
/**
* State of expanse in recycler view.
*/
private boolean recyclerExpanded = false;
/**
* view size of children
**/
private List<Integer> childSizeList = new ArrayList<>();
private ViewTreeObserver.OnGlobalLayoutListener mGlobalLayoutListener;
public ExpandableLinearLayout(final Context context) {
this(context, null);
}
public ExpandableLinearLayout(final Context context, final AttributeSet attrs) {
this(context, attrs, 0);
}
public ExpandableLinearLayout(final Context context, final AttributeSet attrs,
final int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context, attrs, defStyleAttr);
}
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public ExpandableLinearLayout(final Context context, final AttributeSet attrs,
final int defStyleAttr, final int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
init(context, attrs, defStyleAttr);
}
private void init(final Context context, final AttributeSet attrs, final int defStyleAttr) {
final TypedArray a = context.obtainStyledAttributes(
attrs, R.styleable.expandableLayout, defStyleAttr, 0);
duration = a.getInteger(R.styleable.expandableLayout_ael_duration, DEFAULT_DURATION);
defaultExpanded = a.getBoolean(R.styleable.expandableLayout_ael_expanded, DEFAULT_EXPANDED);
defaultChildIndex = a.getInteger(R.styleable.expandableLayout_ael_defaultChildIndex,
Integer.MAX_VALUE);
defaultPosition = a.getDimensionPixelSize(R.styleable.expandableLayout_ael_defaultPosition,
Integer.MIN_VALUE);
final int interpolatorType = a.getInteger(R.styleable.expandableLayout_ael_interpolator,
Utils.LINEAR_INTERPOLATOR);
a.recycle();
interpolator = Utils.createInterpolator(interpolatorType);
isExpanded = defaultExpanded;
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
if (!isCalculatedSize) {
// calculate a size of children
childSizeList.clear();
final int childCount = getChildCount();
if (childCount > 0) {
int sumSize = 0;
View view;
LayoutParams params;
for (int i = 0; i < childCount; i++) {
view = getChildAt(i);
params = (LayoutParams) view.getLayoutParams();
if (0 < i) {
sumSize = childSizeList.get(i - 1);
}
childSizeList.add(
(isVertical()
? view.getMeasuredHeight() + params.topMargin + params.bottomMargin
: view.getMeasuredWidth() + params.leftMargin + params.rightMargin
) + sumSize);
}
layoutSize = childSizeList.get(childCount - 1) +
(isVertical()
? getPaddingTop() + getPaddingBottom()
: getPaddingLeft() + getPaddingRight()
);
isCalculatedSize = true;
} else {
throw new IllegalStateException("The expandableLinearLayout must have at least one child");
}
}
if (isArranged) return;
// adjust default position if a user set a value.
if (!defaultExpanded) {
setLayoutSize(closePosition);
}
if (inRecyclerView) {
setLayoutSize(recyclerExpanded ? layoutSize : closePosition);
}
final int childNumbers = childSizeList.size();
if (childNumbers > defaultChildIndex && childNumbers > 0) {
moveChild(defaultChildIndex, 0, null);
}
if (defaultPosition > 0 && layoutSize >= defaultPosition && layoutSize > 0) {
move(defaultPosition, 0, null);
}
isArranged = true;
if (savedState == null) return;
setLayoutSize(savedState.getSize());
}
@Override
protected Parcelable onSaveInstanceState() {
final Parcelable parcelable = super.onSaveInstanceState();
final ExpandableSavedState ss = new ExpandableSavedState(parcelable);
ss.setSize(getCurrentPosition());
return ss;
}
@Override
protected void onRestoreInstanceState(final Parcelable state) {
if (!(state instanceof ExpandableSavedState)) {
super.onRestoreInstanceState(state);
return;
}
final ExpandableSavedState ss = (ExpandableSavedState) state;
super.onRestoreInstanceState(ss.getSuperState());
savedState = ss;
}
/**
* {@inheritDoc}
*/
@Override
public void setListener(@NonNull ExpandableLayoutListener listener) {
this.listener = listener;
}
/**
* {@inheritDoc}
*/
@Override
public void toggle() {
toggle(duration, interpolator);
}
/**
* {@inheritDoc}
*/
@Override
public void toggle(final long duration, final @Nullable TimeInterpolator interpolator) {
if (closePosition < getCurrentPosition()) {
collapse(duration, interpolator);
} else {
expand(duration, interpolator);
}
}
/**
* {@inheritDoc}
*/
@Override
public void expand() {
if (isAnimating) return;
createExpandAnimator(getCurrentPosition(), layoutSize, duration, interpolator).start();
}
/**
* {@inheritDoc}
*/
@Override
public void expand(final long duration, final @Nullable TimeInterpolator interpolator) {
if (isAnimating) return;
if (duration <= 0) {
move(layoutSize, duration, interpolator);
return;
}
createExpandAnimator(getCurrentPosition(), layoutSize, duration, interpolator).start();
}
/**
* {@inheritDoc}
*/
@Override
public void collapse() {
if (isAnimating) return;
createExpandAnimator(getCurrentPosition(), closePosition, duration, interpolator).start();
}
/**
* {@inheritDoc}
*/
@Override
public void collapse(final long duration, final @Nullable TimeInterpolator interpolator) {
if (isAnimating) return;
if (duration <= 0) {
move(closePosition, duration, interpolator);
return;
}
createExpandAnimator(getCurrentPosition(), closePosition, duration, interpolator).start();
}
/**
* {@inheritDoc}
*/
@Override
public void setDuration(final int duration) {
if (duration < 0) {
throw new IllegalArgumentException("Animators cannot have negative duration: " +
duration);
}
this.duration = duration;
}
/**
* {@inheritDoc}
*/
@Override
public void setExpanded(final boolean expanded) {
if (inRecyclerView) recyclerExpanded = expanded;
final int currentPosition = getCurrentPosition();
if ((expanded && (currentPosition == layoutSize))
|| (!expanded && currentPosition == closePosition)) return;
isExpanded = expanded;
setLayoutSize(expanded ? layoutSize : closePosition);
requestLayout();
}
/**
* {@inheritDoc}
*/
@Override
public boolean isExpanded() {
return isExpanded;
}
/**
* {@inheritDoc}
*/
@Override
public void setInterpolator(@NonNull final TimeInterpolator interpolator) {
this.interpolator = interpolator;
}
/**
* Initializes this layout.
*/
public void initLayout() {
closePosition = 0;
layoutSize = 0;
isArranged = false;
isCalculatedSize = false;
savedState = null;
if (isVertical()) {
measure(MeasureSpec.makeMeasureSpec(getWidth(), MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(getHeight(), MeasureSpec.UNSPECIFIED));
} else {
measure(MeasureSpec.makeMeasureSpec(getWidth(), MeasureSpec.UNSPECIFIED),
MeasureSpec.makeMeasureSpec(getHeight(), MeasureSpec.EXACTLY));
}
}
/**
* @param position
*
* @see #move(int, long, TimeInterpolator)
*/
public void move(int position) {
move(position, duration, interpolator);
}
/**
* Moves to position.
* Sets 0 to duration if you want to move immediately.
*
* @param position
* @param duration
* @param interpolator use the default interpolator if the argument is null.
*/
public void move(int position, long duration, @Nullable TimeInterpolator interpolator) {
if (isAnimating || 0 > position || layoutSize < position) return;
if (duration <= 0) {
isExpanded = position > closePosition;
setLayoutSize(position);
requestLayout();
notifyListeners();
return;
}
createExpandAnimator(getCurrentPosition(), position, duration,
interpolator == null ? this.interpolator : interpolator).start();
}
/**
* @param index child view index
*
* @see #moveChild(int, long, TimeInterpolator)
*/
public void moveChild(int index) {
moveChild(index, duration, interpolator);
}
/**
* Moves to bottom(VERTICAL) or right(HORIZONTAL) of child view
* Sets 0 to duration if you want to move immediately.
*
* @param index index child view index
* @param duration
* @param interpolator use the default interpolator if the argument is null.
*/
public void moveChild(int index, long duration, @Nullable TimeInterpolator interpolator) {
if (isAnimating) return;
final int destination = getChildPosition(index) +
(isVertical() ? getPaddingBottom() : getPaddingRight());
if (duration <= 0) {
isExpanded = destination > closePosition;
setLayoutSize(destination);
requestLayout();
notifyListeners();
return;
}
createExpandAnimator(getCurrentPosition(), destination,
duration, interpolator == null ? this.interpolator : interpolator).start();
}
/**
* Gets the width from left of layout if orientation is horizontal.
* Gets the height from top of layout if orientation is vertical.
*
* @param index index of child view
*
* @return position from top or left
*/
public int getChildPosition(final int index) {
if (0 > index || childSizeList.size() <= index) {
throw new IllegalArgumentException("There aren't the view having this index.");
}
return childSizeList.get(index);
}
/**
* Gets the width from left of layout if orientation is horizontal.
* Gets the height from top of layout if orientation is vertical.
*
* @return
*
* @see #closePosition
*/
public int getClosePosition() {
return closePosition;
}
/**
* Sets the close position directly.
*
* @param position
*
* @see #closePosition
* @see #setClosePositionIndex(int)
*/
public void setClosePosition(final int position) {
this.closePosition = position;
}
/**
* Gets the current position.
*
* @return
*/
public int getCurrentPosition() {
return isVertical() ? getMeasuredHeight() : getMeasuredWidth();
}
/**
* Sets close position using index of child view.
*
* @param childIndex
*
* @see #closePosition
* @see #setClosePosition(int)
*/
public void setClosePositionIndex(final int childIndex) {
this.closePosition = getChildPosition(childIndex);
}
/**
* Set true if expandable layout is used in recycler view.
*
* @param inRecyclerView
*/
public void setInRecyclerView(final boolean inRecyclerView) {
this.inRecyclerView = inRecyclerView;
}
private boolean isVertical() {
return getOrientation() == LinearLayout.VERTICAL;
}
private void setLayoutSize(int size) {
if (isVertical()) {
getLayoutParams().height = size;
} else {
getLayoutParams().width = size;
}
}
/**
* Creates value animator.
* Expand the layout if {@param to} is bigger than {@param from}.
* Collapse the layout if {@param from} is bigger than {@param to}.
*
* @param from
* @param to
* @param duration
* @param interpolator
*
* @return
*/
private ValueAnimator createExpandAnimator(
final int from, final int to, final long duration, final TimeInterpolator interpolator) {
final ValueAnimator valueAnimator = ValueAnimator.ofInt(from, to);
valueAnimator.setDuration(duration);
valueAnimator.setInterpolator(interpolator);
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(final ValueAnimator animator) {
if (isVertical()) {
getLayoutParams().height = (int) animator.getAnimatedValue();
} else {
getLayoutParams().width = (int) animator.getAnimatedValue();
}
requestLayout();
}
});
valueAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(Animator animator) {
isAnimating = true;
if (listener == null) return;
listener.onAnimationStart();
if (layoutSize == to) {
listener.onPreOpen();
return;
}
if (closePosition == to) {
listener.onPreClose();
}
}
@Override
public void onAnimationEnd(Animator animator) {
isAnimating = false;
isExpanded = to > closePosition;
if (listener == null) return;
listener.onAnimationEnd();
if (to == layoutSize) {
listener.onOpened();
return;
}
if (to == closePosition) {
listener.onClosed();
}
}
});
return valueAnimator;
}
/**
* Notify listeners
*/
private void notifyListeners() {
if (listener == null) return;
listener.onAnimationStart();
if (isExpanded) {
listener.onPreOpen();
} else {
listener.onPreClose();
}
mGlobalLayoutListener = new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) {
getViewTreeObserver().removeGlobalOnLayoutListener(mGlobalLayoutListener);
} else {
getViewTreeObserver().removeOnGlobalLayoutListener(mGlobalLayoutListener);
}
listener.onAnimationEnd();
if (isExpanded) {
listener.onOpened();
} else {
listener.onClosed();
}
}
};
getViewTreeObserver().addOnGlobalLayoutListener(mGlobalLayoutListener);
}
}

View File

@@ -0,0 +1,553 @@
package com.github.aakira.expandablelayout;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.TimeInterpolator;
import android.animation.ValueAnimator;
import android.annotation.TargetApi;
import android.content.Context;
import android.content.res.TypedArray;
import android.os.Build;
import android.os.Parcelable;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewTreeObserver;
import android.view.animation.LinearInterpolator;
import android.widget.RelativeLayout;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import java.util.ArrayList;
import java.util.List;
public class ExpandableRelativeLayout extends RelativeLayout implements ExpandableLayout {
private int duration;
private TimeInterpolator interpolator = new LinearInterpolator();
private int orientation;
/**
* Default state of expanse
*
* @see #defaultChildIndex
* @see #defaultPosition
*/
private boolean defaultExpanded;
/**
* You cannot define {@link #defaultExpanded}, {@link #defaultChildIndex}
* and {@link #defaultPosition} at the same time.
* {@link #defaultPosition} has priority over {@link #defaultExpanded}
* and {@link #defaultChildIndex} if you set them at the same time.
* <p>
* <p>
* Priority
* {@link #defaultPosition} > {@link #defaultChildIndex} > {@link #defaultExpanded}
*/
private int defaultChildIndex;
private int defaultPosition;
/**
* The close position is width from left of layout if orientation is horizontal.
* The close position is height from top of layout if orientation is vertical.
*/
private int closePosition = 0;
private ExpandableLayoutListener listener;
private ExpandableSavedState savedState;
private boolean isExpanded;
private int layoutSize = 0;
private boolean isArranged = false;
private boolean isCalculatedSize = false;
private boolean isAnimating = false;
/**
* view size of children
**/
private List<Integer> childSizeList = new ArrayList<>();
/**
* view position top or left of children
**/
private List<Integer> childPositionList = new ArrayList<>();
private ViewTreeObserver.OnGlobalLayoutListener mGlobalLayoutListener;
public ExpandableRelativeLayout(final Context context) {
this(context, null);
}
public ExpandableRelativeLayout(final Context context, final AttributeSet attrs) {
this(context, attrs, 0);
}
public ExpandableRelativeLayout(final Context context, final AttributeSet attrs,
final int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context, attrs, defStyleAttr);
}
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public ExpandableRelativeLayout(final Context context, final AttributeSet attrs,
final int defStyleAttr, final int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
init(context, attrs, defStyleAttr);
}
private void init(final Context context, final AttributeSet attrs, final int defStyleAttr) {
final TypedArray a = context.obtainStyledAttributes(
attrs, R.styleable.expandableLayout, defStyleAttr, 0);
duration = a.getInteger(R.styleable.expandableLayout_ael_duration, DEFAULT_DURATION);
defaultExpanded = a.getBoolean(R.styleable.expandableLayout_ael_expanded, DEFAULT_EXPANDED);
orientation = a.getInteger(R.styleable.expandableLayout_ael_orientation, VERTICAL);
defaultChildIndex = a.getInteger(R.styleable.expandableLayout_ael_defaultChildIndex,
Integer.MAX_VALUE);
defaultPosition = a.getDimensionPixelSize(R.styleable.expandableLayout_ael_defaultPosition,
Integer.MIN_VALUE);
final int interpolatorType = a.getInteger(R.styleable.expandableLayout_ael_interpolator,
Utils.LINEAR_INTERPOLATOR);
a.recycle();
interpolator = Utils.createInterpolator(interpolatorType);
isExpanded = defaultExpanded;
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
if (isCalculatedSize) return;
final int measureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
if (isVertical()) {
int measuredHeight = getMeasuredHeight();
super.onMeasure(widthMeasureSpec, measureSpec);
layoutSize = getMeasuredHeight();
setMeasuredDimension(getMeasuredWidth(), measuredHeight);
} else {
int measuredWidth = getMeasuredWidth();
super.onMeasure(measureSpec, heightMeasureSpec);
layoutSize = getMeasuredWidth();
setMeasuredDimension(measuredWidth, getMeasuredHeight());
}
// calculate a size of children
childSizeList.clear();
View view;
LayoutParams params;
for (int i = 0; i < getChildCount(); i++) {
view = getChildAt(i);
params = (LayoutParams) view.getLayoutParams();
childSizeList.add(isVertical()
? view.getMeasuredHeight() + params.topMargin + params.bottomMargin
: view.getMeasuredWidth() + params.leftMargin + params.rightMargin);
}
isCalculatedSize = true;
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
if (isArranged) return;
childPositionList.clear();
// calculate a top position of children
for (int i = 0; i < getChildCount(); i++) {
childPositionList.add((int) (isVertical() ? getChildAt(i).getY() : getChildAt(i).getX()));
}
// adjust default position if a user set a value.
if (!defaultExpanded) {
setLayoutSize(closePosition);
}
final int childNumbers = childSizeList.size();
if (childNumbers > defaultChildIndex && childNumbers > 0) {
moveChild(defaultChildIndex, 0, null);
}
if (defaultPosition > 0 && layoutSize >= defaultPosition && layoutSize > 0) {
move(defaultPosition, 0, null);
}
isArranged = true;
if (savedState == null) return;
setLayoutSize(savedState.getSize());
}
@Override
protected Parcelable onSaveInstanceState() {
final Parcelable parcelable = super.onSaveInstanceState();
final ExpandableSavedState ss = new ExpandableSavedState(parcelable);
ss.setSize(getCurrentPosition());
return ss;
}
@Override
protected void onRestoreInstanceState(final Parcelable state) {
if (!(state instanceof ExpandableSavedState)) {
super.onRestoreInstanceState(state);
return;
}
final ExpandableSavedState ss = (ExpandableSavedState) state;
super.onRestoreInstanceState(ss.getSuperState());
savedState = ss;
}
/**
* {@inheritDoc}
*/
@Override
public void setListener(@NonNull ExpandableLayoutListener listener) {
this.listener = listener;
}
/**
* {@inheritDoc}
*/
@Override
public void toggle() {
toggle(duration, interpolator);
}
/**
* {@inheritDoc}
*/
@Override
public void toggle(final long duration, final @Nullable TimeInterpolator interpolator) {
if (closePosition < getCurrentPosition()) {
collapse(duration, interpolator);
} else {
expand(duration, interpolator);
}
}
/**
* {@inheritDoc}
*/
@Override
public void expand() {
if (isAnimating) return;
createExpandAnimator(getCurrentPosition(), layoutSize, duration, interpolator).start();
}
/**
* {@inheritDoc}
*/
@Override
public void expand(final long duration, final @Nullable TimeInterpolator interpolator) {
if (isAnimating) return;
if (duration <= 0) {
move(layoutSize, duration, interpolator);
return;
}
createExpandAnimator(getCurrentPosition(), layoutSize, duration, interpolator).start();
}
/**
* {@inheritDoc}
*/
@Override
public void collapse() {
if (isAnimating) return;
createExpandAnimator(getCurrentPosition(), closePosition, duration, interpolator).start();
}
/**
* {@inheritDoc}
*/
@Override
public void collapse(final long duration, final @Nullable TimeInterpolator interpolator) {
if (isAnimating) return;
if (duration <= 0) {
move(closePosition, duration, interpolator);
return;
}
createExpandAnimator(getCurrentPosition(), closePosition, duration, interpolator).start();
}
/**
* {@inheritDoc}
*/
@Override
public void setDuration(final int duration) {
if (duration < 0) {
throw new IllegalArgumentException("Animators cannot have negative duration: " +
duration);
}
this.duration = duration;
}
/**
* {@inheritDoc}
*/
@Override
public void setExpanded(boolean expanded) {
final int currentPosition = getCurrentPosition();
if ((expanded && (currentPosition == layoutSize))
|| (!expanded && currentPosition == closePosition)) return;
isExpanded = expanded;
setLayoutSize(expanded ? layoutSize : closePosition);
requestLayout();
}
/**
* {@inheritDoc}
*/
@Override
public boolean isExpanded() {
return isExpanded;
}
/**
* {@inheritDoc}
*/
@Override
public void setInterpolator(@NonNull final TimeInterpolator interpolator) {
this.interpolator = interpolator;
}
/**
* @param position
*
* @see #move(int, long, TimeInterpolator)
*/
public void move(int position) {
move(position, duration, interpolator);
}
/**
* Moves to position.
* Sets 0 to duration if you want to move immediately.
*
* @param position
* @param duration
* @param interpolator use the default interpolator if the argument is null.
*/
public void move(int position, long duration, @Nullable TimeInterpolator interpolator) {
if (isAnimating || 0 > position || layoutSize < position) return;
if (duration <= 0) {
isExpanded = position > closePosition;
setLayoutSize(position);
requestLayout();
notifyListeners();
return;
}
createExpandAnimator(getCurrentPosition(), position, duration,
interpolator == null ? this.interpolator : interpolator).start();
}
/**
* @param index child view index
*
* @see #moveChild(int, long, TimeInterpolator)
*/
public void moveChild(int index) {
moveChild(index, duration, interpolator);
}
/**
* Moves to bottom(VERTICAL) or right(HORIZONTAL) of child view
* Sets 0 to duration if you want to move immediately.
*
* @param index index child view index
* @param duration
* @param interpolator use the default interpolator if the argument is null.
*/
public void moveChild(int index, long duration, @Nullable TimeInterpolator interpolator) {
if (isAnimating) return;
final int destination = getChildPosition(index) +
(isVertical() ? getPaddingBottom() : getPaddingRight());
if (duration <= 0) {
isExpanded = destination > closePosition;
setLayoutSize(destination);
requestLayout();
notifyListeners();
return;
}
createExpandAnimator(getCurrentPosition(), destination,
duration, interpolator == null ? this.interpolator : interpolator).start();
}
/**
* Sets orientation of expanse animation.
*
* @param orientation Set 0 if orientation is horizontal, 1 if orientation is vertical
*/
public void setOrientation(@Orientation final int orientation) {
this.orientation = orientation;
}
/**
* Gets the width from left of layout if orientation is horizontal.
* Gets the height from top of layout if orientation is vertical.
*
* @param index index of child view
*
* @return position from top or left
*/
public int getChildPosition(final int index) {
if (0 > index || childSizeList.size() <= index) {
throw new IllegalArgumentException("There aren't the view having this index.");
}
return childPositionList.get(index) + childSizeList.get(index);
}
/**
* Gets the width from left of layout if orientation is horizontal.
* Gets the height from top of layout if orientation is vertical.
*
* @return
*
* @see #closePosition
*/
public int getClosePosition() {
return closePosition;
}
/**
* Sets the close position directly.
*
* @param position
*
* @see #closePosition
* @see #setClosePositionIndex(int)
*/
public void setClosePosition(final int position) {
this.closePosition = position;
}
/**
* Gets the current position.
*
* @return
*/
public int getCurrentPosition() {
return isVertical() ? getMeasuredHeight() : getMeasuredWidth();
}
/**
* Sets close position using index of child view.
*
* @param childIndex
*
* @see #closePosition
* @see #setClosePosition(int)
*/
public void setClosePositionIndex(final int childIndex) {
this.closePosition = getChildPosition(childIndex);
}
private boolean isVertical() {
return orientation == VERTICAL;
}
private void setLayoutSize(int size) {
if (isVertical()) {
getLayoutParams().height = size;
} else {
getLayoutParams().width = size;
}
}
/**
* Creates value animator.
* Expand the layout if {@param to} is bigger than {@param from}.
* Collapse the layout if {@param from} is bigger than {@param to}.
*
* @param from
* @param to
* @param duration
* @param interpolator
*
* @return
*/
private ValueAnimator createExpandAnimator(
final int from, final int to, final long duration, final TimeInterpolator interpolator) {
final ValueAnimator valueAnimator = ValueAnimator.ofInt(from, to);
valueAnimator.setDuration(duration);
valueAnimator.setInterpolator(interpolator);
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(final ValueAnimator animator) {
if (isVertical()) {
getLayoutParams().height = (int) animator.getAnimatedValue();
} else {
getLayoutParams().width = (int) animator.getAnimatedValue();
}
requestLayout();
}
});
valueAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(Animator animator) {
isAnimating = true;
if (listener == null) return;
listener.onAnimationStart();
if (layoutSize == to) {
listener.onPreOpen();
return;
}
if (closePosition == to) {
listener.onPreClose();
}
}
@Override
public void onAnimationEnd(Animator animator) {
isAnimating = false;
isExpanded = to > closePosition;
if (listener == null) return;
listener.onAnimationEnd();
if (to == layoutSize) {
listener.onOpened();
return;
}
if (to == closePosition) {
listener.onClosed();
}
}
});
return valueAnimator;
}
/**
* Notify listeners
*/
private void notifyListeners() {
if (listener == null) return;
listener.onAnimationStart();
if (isExpanded) {
listener.onPreOpen();
} else {
listener.onPreClose();
}
mGlobalLayoutListener = new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) {
getViewTreeObserver().removeGlobalOnLayoutListener(mGlobalLayoutListener);
} else {
getViewTreeObserver().removeOnGlobalLayoutListener(mGlobalLayoutListener);
}
listener.onAnimationEnd();
if (isExpanded) {
listener.onOpened();
} else {
listener.onClosed();
}
}
};
getViewTreeObserver().addOnGlobalLayoutListener(mGlobalLayoutListener);
}
}

View File

@@ -0,0 +1,54 @@
package com.github.aakira.expandablelayout;
import android.os.Parcel;
import android.os.Parcelable;
import android.view.View;
public class ExpandableSavedState extends View.BaseSavedState {
private int size;
private float weight;
ExpandableSavedState(Parcelable superState) {
super(superState);
}
private ExpandableSavedState(Parcel in) {
super(in);
this.size = in.readInt();
this.weight = in.readFloat();
}
public int getSize() {
return this.size;
}
public void setSize(int size) {
this.size = size;
}
public float getWeight() {
return this.weight;
}
public void setWeight(float weight) {
this.weight = weight;
}
@Override
public void writeToParcel(Parcel out, int flags) {
super.writeToParcel(out, flags);
out.writeInt(this.size);
out.writeFloat(this.weight);
}
public static final Creator<ExpandableSavedState> CREATOR =
new Creator<ExpandableSavedState>() {
public ExpandableSavedState createFromParcel(Parcel in) {
return new ExpandableSavedState(in);
}
public ExpandableSavedState[] newArray(int size) {
return new ExpandableSavedState[size];
}
};
}

View File

@@ -0,0 +1,385 @@
package com.github.aakira.expandablelayout;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.TimeInterpolator;
import android.animation.ValueAnimator;
import android.annotation.TargetApi;
import android.content.Context;
import android.content.res.TypedArray;
import android.os.Build;
import android.os.Parcelable;
import android.util.AttributeSet;
import android.view.ViewTreeObserver;
import android.view.animation.LinearInterpolator;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
public class ExpandableWeightLayout extends RelativeLayout implements ExpandableLayout {
private int duration;
private TimeInterpolator interpolator = new LinearInterpolator();
private boolean defaultExpanded;
private ExpandableLayoutListener listener;
private ExpandableSavedState savedState;
private boolean isExpanded;
private float layoutWeight = 0.0f;
private boolean isArranged = false;
private boolean isCalculatedSize = false;
private boolean isAnimating = false;
private ViewTreeObserver.OnGlobalLayoutListener mGlobalLayoutListener;
public ExpandableWeightLayout(final Context context) {
this(context, null);
}
public ExpandableWeightLayout(final Context context, final AttributeSet attrs) {
this(context, attrs, 0);
}
public ExpandableWeightLayout(final Context context, final AttributeSet attrs,
final int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context, attrs, defStyleAttr);
}
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public ExpandableWeightLayout(final Context context, final AttributeSet attrs,
final int defStyleAttr, final int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
init(context, attrs, defStyleAttr);
}
private void init(final Context context, final AttributeSet attrs, final int defStyleAttr) {
final TypedArray a = context.obtainStyledAttributes(
attrs, R.styleable.expandableLayout, defStyleAttr, 0);
duration = a.getInteger(R.styleable.expandableLayout_ael_duration, DEFAULT_DURATION);
defaultExpanded = a.getBoolean(R.styleable.expandableLayout_ael_expanded, DEFAULT_EXPANDED);
final int interpolatorType = a.getInteger(R.styleable.expandableLayout_ael_interpolator,
Utils.LINEAR_INTERPOLATOR);
a.recycle();
interpolator = Utils.createInterpolator(interpolatorType);
isExpanded = defaultExpanded;
}
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
// Check this layout using the attribute of weight
if (!(getLayoutParams() instanceof LinearLayout.LayoutParams)) {
throw new AssertionError("You must arrange in LinearLayout.");
}
if (0 >= getCurrentWeight()) throw new AssertionError("You must set a weight than 0.");
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
if (!isCalculatedSize) {
layoutWeight = getCurrentWeight();
isCalculatedSize = true;
}
if (isArranged) return;
setWeight(defaultExpanded ? layoutWeight : 0);
isArranged = true;
if (savedState == null) return;
setWeight(savedState.getWeight());
}
@Override
protected Parcelable onSaveInstanceState() {
final Parcelable parcelable = super.onSaveInstanceState();
final ExpandableSavedState ss = new ExpandableSavedState(parcelable);
ss.setWeight(getCurrentWeight());
return ss;
}
@Override
protected void onRestoreInstanceState(final Parcelable state) {
if (!(state instanceof ExpandableSavedState)) {
super.onRestoreInstanceState(state);
return;
}
final ExpandableSavedState ss = (ExpandableSavedState) state;
super.onRestoreInstanceState(ss.getSuperState());
savedState = ss;
}
/**
* {@inheritDoc}
*/
@Override
public void setListener(@NonNull ExpandableLayoutListener listener) {
this.listener = listener;
}
/**
* {@inheritDoc}
*/
@Override
public void toggle() {
toggle(duration, interpolator);
}
/**
* {@inheritDoc}
*/
@Override
public void toggle(final long duration, @Nullable final TimeInterpolator interpolator) {
if (0 < getCurrentWeight()) {
collapse(duration, interpolator);
} else {
expand(duration, interpolator);
}
}
/**
* {@inheritDoc}
*/
@Override
public void expand() {
if (isAnimating) return;
createExpandAnimator(0, layoutWeight, duration, interpolator).start();
}
/**
* {@inheritDoc}
*/
@Override
public void expand(final long duration, @Nullable final TimeInterpolator interpolator) {
if (isAnimating) return;
if (duration <= 0) {
isExpanded = true;
setWeight(layoutWeight);
requestLayout();
notifyListeners();
return;
}
createExpandAnimator(getCurrentWeight(), layoutWeight, duration, interpolator).start();
}
/**
* {@inheritDoc}
*/
@Override
public void collapse() {
if (isAnimating) return;
createExpandAnimator(getCurrentWeight(), 0, duration, interpolator).start();
}
/**
* {@inheritDoc}
*/
@Override
public void collapse(final long duration, @Nullable final TimeInterpolator interpolator) {
if (isAnimating) return;
if (duration <= 0) {
isExpanded = false;
setWeight(0);
requestLayout();
notifyListeners();
return;
}
createExpandAnimator(getCurrentWeight(), 0, duration, interpolator).start();
}
/**
* {@inheritDoc}
*/
@Override
public void setDuration(@NonNull final int duration) {
if (duration < 0) {
throw new IllegalArgumentException("Animators cannot have negative duration: " +
duration);
}
this.duration = duration;
}
/**
* {@inheritDoc}
*/
@Override
public void setExpanded(boolean expanded) {
final float currentWeight = getCurrentWeight();
if ((expanded && (currentWeight == layoutWeight))
|| (!expanded && currentWeight == 0)) return;
isExpanded = expanded;
setWeight(expanded ? layoutWeight : 0);
requestLayout();
}
/**
* {@inheritDoc}
*/
@Override
public boolean isExpanded() {
return isExpanded;
}
/**
* {@inheritDoc}
*/
@Override
public void setInterpolator(@NonNull final TimeInterpolator interpolator) {
this.interpolator = interpolator;
}
/**
* Sets weight of expandable layout.
*
* @param expandWeight expand to this weight by {@link #expand()}
*/
public void setExpandWeight(final float expandWeight) {
layoutWeight = expandWeight;
}
/**
* Gets current weight of expandable layout.
*
* @return weight
*/
public float getCurrentWeight() {
return ((LinearLayout.LayoutParams) getLayoutParams()).weight;
}
/**
* @param weight
*
* @see #move(float, long, TimeInterpolator)
*/
public void move(float weight) {
move(weight, duration, interpolator);
}
/**
* Change to weight.
* Sets 0 to duration if you want to move immediately.
*
* @param weight
* @param duration
* @param interpolator use the default interpolator if the argument is null.
*/
public void move(float weight, long duration, @Nullable TimeInterpolator interpolator) {
if (isAnimating) return;
if (duration <= 0L) {
isExpanded = weight > 0;
setWeight(weight);
requestLayout();
notifyListeners();
return;
}
createExpandAnimator(getCurrentWeight(), weight, duration, interpolator).start();
}
/**
* Creates value animator.
* Expand the layout if @param.to is bigger than @param.from.
* Collapse the layout if @param.from is bigger than @param.to.
*
* @param from
* @param to
* @param duration
* @param interpolator TimeInterpolator
*
* @return
*/
private ValueAnimator createExpandAnimator(final float from, final float to, final long duration,
@Nullable final TimeInterpolator interpolator) {
final ValueAnimator valueAnimator = ValueAnimator.ofFloat(from, to);
valueAnimator.setDuration(duration);
valueAnimator.setInterpolator(interpolator == null ? this.interpolator : interpolator);
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(final ValueAnimator animation) {
setWeight((float) animation.getAnimatedValue());
requestLayout();
}
});
valueAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(Animator animation) {
isAnimating = true;
if (listener == null) return;
listener.onAnimationStart();
if (layoutWeight == to) {
listener.onPreOpen();
return;
}
if (0 == to) {
listener.onPreClose();
}
}
@Override
public void onAnimationEnd(Animator animation) {
isAnimating = false;
isExpanded = to > 0;
if (listener == null) return;
listener.onAnimationEnd();
if (to == layoutWeight) {
listener.onOpened();
return;
}
if (to == 0) {
listener.onClosed();
}
}
});
return valueAnimator;
}
private void setWeight(final float weight) {
((LinearLayout.LayoutParams) getLayoutParams()).weight = weight;
}
/**
* Notify listeners
*/
private void notifyListeners() {
if (listener == null) return;
listener.onAnimationStart();
if (isExpanded) {
listener.onPreOpen();
} else {
listener.onPreClose();
}
mGlobalLayoutListener = new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) {
getViewTreeObserver().removeGlobalOnLayoutListener(mGlobalLayoutListener);
} else {
getViewTreeObserver().removeOnGlobalLayoutListener(mGlobalLayoutListener);
}
listener.onAnimationEnd();
if (isExpanded) {
listener.onOpened();
} else {
listener.onClosed();
}
}
};
getViewTreeObserver().addOnGlobalLayoutListener(mGlobalLayoutListener);
}
}

View File

@@ -0,0 +1,65 @@
package com.github.aakira.expandablelayout;
import android.animation.TimeInterpolator;
import android.view.animation.AccelerateDecelerateInterpolator;
import android.view.animation.AccelerateInterpolator;
import android.view.animation.AnticipateInterpolator;
import android.view.animation.AnticipateOvershootInterpolator;
import android.view.animation.BounceInterpolator;
import android.view.animation.DecelerateInterpolator;
import android.view.animation.LinearInterpolator;
import android.view.animation.OvershootInterpolator;
import androidx.annotation.IntRange;
import androidx.interpolator.view.animation.FastOutLinearInInterpolator;
import androidx.interpolator.view.animation.FastOutSlowInInterpolator;
import androidx.interpolator.view.animation.LinearOutSlowInInterpolator;
public class Utils {
public static final int ACCELERATE_DECELERATE_INTERPOLATOR = 0;
public static final int ACCELERATE_INTERPOLATOR = 1;
public static final int ANTICIPATE_INTERPOLATOR = 2;
public static final int ANTICIPATE_OVERSHOOT_INTERPOLATOR = 3;
public static final int BOUNCE_INTERPOLATOR = 4;
public static final int DECELERATE_INTERPOLATOR = 5;
public static final int FAST_OUT_LINEAR_IN_INTERPOLATOR = 6;
public static final int FAST_OUT_SLOW_IN_INTERPOLATOR = 7;
public static final int LINEAR_INTERPOLATOR = 8;
public static final int LINEAR_OUT_SLOW_IN_INTERPOLATOR = 9;
public static final int OVERSHOOT_INTERPOLATOR = 10;
/**
* Creates interpolator.
*
* @param interpolatorType
* @return
*/
public static TimeInterpolator createInterpolator(@IntRange(from = 0, to = 10) final int interpolatorType) {
switch (interpolatorType) {
case ACCELERATE_DECELERATE_INTERPOLATOR:
return new AccelerateDecelerateInterpolator();
case ACCELERATE_INTERPOLATOR:
return new AccelerateInterpolator();
case ANTICIPATE_INTERPOLATOR:
return new AnticipateInterpolator();
case ANTICIPATE_OVERSHOOT_INTERPOLATOR:
return new AnticipateOvershootInterpolator();
case BOUNCE_INTERPOLATOR:
return new BounceInterpolator();
case DECELERATE_INTERPOLATOR:
return new DecelerateInterpolator();
case FAST_OUT_LINEAR_IN_INTERPOLATOR:
return new FastOutLinearInInterpolator();
case FAST_OUT_SLOW_IN_INTERPOLATOR:
return new FastOutSlowInInterpolator();
case LINEAR_INTERPOLATOR:
return new LinearInterpolator();
case LINEAR_OUT_SLOW_IN_INTERPOLATOR:
return new LinearOutSlowInInterpolator();
case OVERSHOOT_INTERPOLATOR:
return new OvershootInterpolator();
default:
return new LinearInterpolator();
}
}
}

View File

@@ -0,0 +1,26 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="expandableLayout">
<attr name="ael_duration" format="integer" />
<attr name="ael_expanded" format="boolean" />
<attr name="ael_defaultChildIndex" format="integer" />
<attr name="ael_defaultPosition" format="dimension" />
<attr name="ael_orientation" format="enum">
<enum name="horizontal" value="0" />
<enum name="vertical" value="1" />
</attr>
<attr name="ael_interpolator" format="enum">
<enum name="accelerateDecelerate" value="0" />
<enum name="accelerate" value="1" />
<enum name="anticipate" value="2" />
<enum name="anticipateOvershoot" value="3" />
<enum name="bounce" value="4" />
<enum name="decelerate" value="5" />
<enum name="fastOutLinearIn" value="6" />
<enum name="fastOutSlowIn" value="7" />
<enum name="linear" value="8" />
<enum name="linearOutSlowIn" value="9" />
<enum name="overshoot" value="10" />
</attr>
</declare-styleable>
</resources>

View File

@@ -0,0 +1,21 @@
plugins {
id("org.autojs.build.versions")
id("org.autojs.build.jvm-convention")
id("com.android.library")
}
android {
namespace "com.bignerdranch.expandablerecyclerview"
compileSdkVersion versions.sdkVersionCompile
defaultConfig {
minSdkVersion versions.sdkVersionMin
targetSdkVersion versions.sdkVersionTarget
versionName "3.0.0-RC1 Mod"
}
}
dependencies {
implementation libs.annotation
implementation libs.recyclerview
}

View File

@@ -0,0 +1,17 @@
# Add project specific ProGuard rules here.
# By default, the flags in this file are appended to flags specified
# in /Applications/eclipse/android/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 *;
#}

View File

@@ -0,0 +1 @@
<manifest />

View File

@@ -0,0 +1,70 @@
package com.bignerdranch.expandablerecyclerview;
import androidx.annotation.NonNull;
import androidx.annotation.UiThread;
import androidx.recyclerview.widget.RecyclerView;
import android.view.View;
/**
* ViewHolder for a child list item.
* <p>
* The user should extend this class and implement as they wish for their
* child list item.
*/
public class ChildViewHolder<C> extends RecyclerView.ViewHolder {
C mChild;
ExpandableRecyclerAdapter mExpandableAdapter;
/**
* Default constructor.
*
* @param itemView The {@link View} being hosted in this ViewHolder
*/
public ChildViewHolder(@NonNull View itemView) {
super(itemView);
}
/**
* @return the childListItem associated with this view holder
*/
@UiThread
public C getChild() {
return mChild;
}
/**
* Returns the adapter position of the Parent associated with this ChildViewHolder
*
* @return The adapter position of the Parent if it still exists in the adapter.
* RecyclerView.NO_POSITION if item has been removed from the adapter,
* RecyclerView.Adapter.notifyDataSetChanged() has been called after the last
* layout pass or the ViewHolder has already been recycled.
*/
@UiThread
public int getParentAdapterPosition() {
int flatPosition = getAdapterPosition();
if (mExpandableAdapter == null || flatPosition == RecyclerView.NO_POSITION) {
return RecyclerView.NO_POSITION;
}
return mExpandableAdapter.getNearestParentPosition(flatPosition);
}
/**
* Returns the adapter position of the Child associated with this ChildViewHolder
*
* @return The adapter position of the Child if it still exists in the adapter.
* RecyclerView.NO_POSITION if item has been removed from the adapter,
* RecyclerView.Adapter.notifyDataSetChanged() has been called after the last
* layout pass or the ViewHolder has already been recycled.
*/
@UiThread
public int getChildAdapterPosition() {
int flatPosition = getAdapterPosition();
if (mExpandableAdapter == null || flatPosition == RecyclerView.NO_POSITION) {
return RecyclerView.NO_POSITION;
}
return mExpandableAdapter.getChildPosition(flatPosition);
}
}

View File

@@ -0,0 +1,201 @@
package com.bignerdranch.expandablerecyclerview;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.UiThread;
import androidx.recyclerview.widget.RecyclerView;
import android.view.View;
import com.bignerdranch.expandablerecyclerview.model.Parent;
/**
* ViewHolder for a {@link Parent}
* Keeps track of expanded state and holds callbacks which can be used to
* trigger expansion-based events.
*
* @author Ryan Brooks
* @version 1.0
* @since 5/27/2015
*/
public class ParentViewHolder<P extends Parent<C>, C> extends RecyclerView.ViewHolder implements View.OnClickListener {
@Nullable
private ParentViewHolderExpandCollapseListener mParentViewHolderExpandCollapseListener;
private boolean mExpanded;
P mParent;
ExpandableRecyclerAdapter mExpandableAdapter;
/**
* Empowers {@link com.bignerdranch.expandablerecyclerview.ExpandableRecyclerAdapter}
* implementations to be notified of expand/collapse state change events.
*/
interface ParentViewHolderExpandCollapseListener {
/**
* Called when a parent is expanded.
*
* @param flatParentPosition The index of the parent in the list being expanded
*/
@UiThread
void onParentExpanded(int flatParentPosition);
/**
* Called when a parent is collapsed.
*
* @param flatParentPosition The index of the parent in the list being collapsed
*/
@UiThread
void onParentCollapsed(int flatParentPosition);
}
/**
* Default constructor.
*
* @param itemView The {@link View} being hosted in this ViewHolder
*/
@UiThread
public ParentViewHolder(@NonNull View itemView) {
super(itemView);
mExpanded = false;
}
/**
* @return the Parent associated with this ViewHolder
*/
@UiThread
public P getParent() {
return mParent;
}
/**
* Returns the adapter position of the Parent associated with this ParentViewHolder
*
* @return The adapter position of the Parent if it still exists in the adapter.
* RecyclerView.NO_POSITION if item has been removed from the adapter,
* RecyclerView.Adapter.notifyDataSetChanged() has been called after the last
* layout pass or the ViewHolder has already been recycled.
*/
@UiThread
public int getParentAdapterPosition() {
int flatPosition = getAdapterPosition();
if (flatPosition == RecyclerView.NO_POSITION) {
return flatPosition;
}
return mExpandableAdapter.getNearestParentPosition(flatPosition);
}
/**
* Sets a {@link android.view.View.OnClickListener} on the entire parent
* view to trigger expansion.
*/
@UiThread
public void setMainItemClickToExpand() {
itemView.setOnClickListener(this);
}
/**
* Returns expanded state for the {@link Parent}
* corresponding to this {@link ParentViewHolder}.
*
* @return true if expanded, false if not
*/
@UiThread
public boolean isExpanded() {
return mExpanded;
}
/**
* Setter method for expanded state, used for initialization of expanded state.
* changes to the state are given in {@link #onExpansionToggled(boolean)}
*
* @param expanded true if expanded, false if not
*/
@UiThread
public void setExpanded(boolean expanded) {
mExpanded = expanded;
}
/**
* Callback triggered when expansion state is changed, but not during
* initialization.
* <p>
* Useful for implementing animations on expansion.
*
* @param expanded true if view is expanded before expansion is toggled,
* false if not
*/
@UiThread
public void onExpansionToggled(boolean expanded) {
}
/**
* Setter for the {@link ParentViewHolderExpandCollapseListener} implemented in
* {@link com.bignerdranch.expandablerecyclerview.ExpandableRecyclerAdapter}.
*
* @param parentViewHolderExpandCollapseListener The {@link ParentViewHolderExpandCollapseListener} to set on the {@link ParentViewHolder}
*/
@UiThread
void setParentViewHolderExpandCollapseListener(ParentViewHolderExpandCollapseListener parentViewHolderExpandCollapseListener) {
mParentViewHolderExpandCollapseListener = parentViewHolderExpandCollapseListener;
}
/**
* {@link android.view.View.OnClickListener} to listen for click events on
* the entire parent {@link View}.
* <p>
* Only registered if {@link #shouldItemViewClickToggleExpansion()} is true.
*
* @param v The {@link View} that is the trigger for expansion
*/
@Override
@UiThread
public void onClick(View v) {
if (mExpanded) {
collapseView();
} else {
expandView();
}
}
/**
* Used to determine whether a click in the entire parent {@link View}
* should trigger row expansion.
* <p>
* If you return false, you can call {@link #expandView()} to trigger an
* expansion in response to a another event or {@link #collapseView()} to
* trigger a collapse.
*
* @return true to set an {@link android.view.View.OnClickListener} on the item view
*/
@UiThread
public boolean shouldItemViewClickToggleExpansion() {
return true;
}
/**
* Triggers expansion of the parent.
*/
@UiThread
protected void expandView() {
setExpanded(true);
onExpansionToggled(false);
if (mParentViewHolderExpandCollapseListener != null) {
mParentViewHolderExpandCollapseListener.onParentExpanded(getAdapterPosition());
}
}
/**
* Triggers collapse of the parent.
*/
@UiThread
protected void collapseView() {
setExpanded(false);
onExpansionToggled(true);
if (mParentViewHolderExpandCollapseListener != null) {
mParentViewHolderExpandCollapseListener.onParentCollapsed(getAdapterPosition());
}
}
}

View File

@@ -0,0 +1,125 @@
package com.bignerdranch.expandablerecyclerview.model;
import androidx.annotation.NonNull;
import java.util.ArrayList;
import java.util.List;
/**
* Wrapper used to link metadata with a list item.
*
* @param <P> Parent list item
* @param <C> Child list item
*/
public class ExpandableWrapper<P extends Parent<C>, C> {
private P mParent;
private C mChild;
private boolean mWrappedParent;
private boolean mExpanded;
private List<ExpandableWrapper<P, C>> mWrappedChildList;
/**
* Constructor to wrap a parent object of type {@link P}.
*
* @param parent The parent object to wrap
*/
public ExpandableWrapper(@NonNull P parent) {
mParent = parent;
mWrappedParent = true;
mExpanded = false;
mWrappedChildList = generateChildItemList(parent);
}
/**
* Constructor to wrap a child object of type {@link C}
*
* @param child The child object to wrap
*/
public ExpandableWrapper(@NonNull C child) {
mChild = child;
mWrappedParent = false;
mExpanded = false;
}
public P getParent() {
return mParent;
}
public void setParent(@NonNull P parent) {
mParent = parent;
mWrappedChildList = generateChildItemList(parent);
}
public C getChild() {
return mChild;
}
public boolean isExpanded() {
return mExpanded;
}
public void setExpanded(boolean expanded) {
mExpanded = expanded;
}
public boolean isParent() {
return mWrappedParent;
}
/**
* @return The initial expanded state of a parent
* @throws IllegalStateException If a parent isn't being wrapped
*/
public boolean isParentInitiallyExpanded() {
if (!mWrappedParent) {
throw new IllegalStateException("Parent not wrapped");
}
return mParent.isInitiallyExpanded();
}
/**
* @return The list of children of a parent
* @throws IllegalStateException If a parent isn't being wrapped
*/
public List<ExpandableWrapper<P, C>> getWrappedChildList() {
if (!mWrappedParent) {
throw new IllegalStateException("Parent not wrapped");
}
return mWrappedChildList;
}
private List<ExpandableWrapper<P, C>> generateChildItemList(P parentListItem) {
List<ExpandableWrapper<P, C>> childItemList = new ArrayList<>();
for (C child : parentListItem.getChildList()) {
childItemList.add(new ExpandableWrapper<P, C>(child));
}
return childItemList;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
final ExpandableWrapper<?, ?> that = (ExpandableWrapper<?, ?>) o;
if (mParent != null ? !mParent.equals(that.mParent) : that.mParent != null)
return false;
return mChild != null ? mChild.equals(that.mChild) : that.mChild == null;
}
@Override
public int hashCode() {
int result = mParent != null ? mParent.hashCode() : 0;
result = 31 * result + (mChild != null ? mChild.hashCode() : 0);
return result;
}
}

View File

@@ -0,0 +1,26 @@
package com.bignerdranch.expandablerecyclerview.model;
import java.util.List;
/**
* Interface for implementing required methods in a parent.
*/
public interface Parent<C> {
/**
* Getter for the list of this parent's child items.
* <p>
* If list is empty, the parent has no children.
*
* @return A {@link List} of the children of this {@link Parent}
*/
List<C> getChildList();
/**
* Getter used to determine if this {@link Parent}'s
* {@link android.view.View} should show up initially as expanded.
*
* @return true if expanded, false if not
*/
boolean isInitiallyExpanded();
}

View File

@@ -0,0 +1,32 @@
package com.bignerdranch.expandablerecyclerview.model;
import java.util.List;
/**
* Simple implementation of the ParentListItem interface,
* by default all items are not initially expanded.
*
* @param <C> Type of the Child Items held by the Parent.
*/
public class SimpleParent<C> implements Parent<C> {
private List<C> mChildList;
protected SimpleParent(List<C> childItemList) {
mChildList = childItemList;
}
@Override
public boolean isInitiallyExpanded() {
return false;
}
@Override
public List<C> getChildList() {
return mChildList;
}
public void setChildList(List<C> childList) {
mChildList = childList;
}
}

View File

@@ -6,7 +6,7 @@ plugins {
android {
namespace = "com.wdullaer.materialdatetimepicker"
version = "4.2.3 (54)"
version = "4.2.3 (54) Mod"
compileSdk = versions.sdkVersionCompile
defaultConfig {

View File

@@ -6,7 +6,7 @@ plugins {
android {
namespace = "com.afollestad.materialdialogs"
version = "0.9.6.0 (179)"
version = "0.9.6.0 (179) Mod"
compileSdk = versions.sdkVersionCompile
@@ -27,8 +27,8 @@ repositories {
}
dependencies {
implementation(libs.appcompat.v7)
implementation(libs.support.annotations)
implementation(libs.recyclerview.v7)
implementation(libs.appcompat)
implementation(libs.annotation)
implementation(libs.recyclerview)
implementation(libs.materialprogressbar)
}

View File

@@ -0,0 +1,20 @@
plugins {
id("org.autojs.build.versions")
id("org.autojs.build.jvm-convention")
id("com.android.library")
}
android {
namespace "com.yqritc.recyclerviewflexibledivider"
compileSdkVersion versions.sdkVersionCompile
defaultConfig {
minSdkVersion versions.sdkVersionMin
targetSdkVersion versions.sdkVersionTarget
versionCode 15
versionName "1.4.0 Mod"
}
}
dependencies {
implementation libs.recyclerview
}

View File

@@ -0,0 +1 @@
<manifest package="com.yqritc.recyclerviewflexibledivider"></manifest>

View File

@@ -0,0 +1,439 @@
package com.yqritc.recyclerviewflexibledivider;
import android.content.Context;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.view.View;
import androidx.annotation.ColorRes;
import androidx.annotation.DimenRes;
import androidx.annotation.DrawableRes;
import androidx.core.content.ContextCompat;
import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
/**
* Created by yqritc on 2015/01/08.
*/
public abstract class FlexibleDividerDecoration extends RecyclerView.ItemDecoration {
private static final int DEFAULT_SIZE = 2;
private static final int[] ATTRS = new int[]{
android.R.attr.listDivider
};
protected enum DividerType {
DRAWABLE, PAINT, COLOR
}
protected DividerType mDividerType = DividerType.DRAWABLE;
protected VisibilityProvider mVisibilityProvider;
protected PaintProvider mPaintProvider;
protected ColorProvider mColorProvider;
protected DrawableProvider mDrawableProvider;
protected SizeProvider mSizeProvider;
protected boolean mShowLastDivider;
protected boolean mPositionInsideItem;
private Paint mPaint;
protected FlexibleDividerDecoration(Builder builder) {
if (builder.mPaintProvider != null) {
mDividerType = DividerType.PAINT;
mPaintProvider = builder.mPaintProvider;
} else if (builder.mColorProvider != null) {
mDividerType = DividerType.COLOR;
mColorProvider = builder.mColorProvider;
mPaint = new Paint();
setSizeProvider(builder);
} else {
mDividerType = DividerType.DRAWABLE;
if (builder.mDrawableProvider == null) {
TypedArray a = builder.mContext.obtainStyledAttributes(ATTRS);
final Drawable divider = a.getDrawable(0);
a.recycle();
mDrawableProvider = new DrawableProvider() {
@Override
public Drawable drawableProvider(int position, RecyclerView parent) {
return divider;
}
};
} else {
mDrawableProvider = builder.mDrawableProvider;
}
mSizeProvider = builder.mSizeProvider;
}
mVisibilityProvider = builder.mVisibilityProvider;
mShowLastDivider = builder.mShowLastDivider;
mPositionInsideItem = builder.mPositionInsideItem;
}
private void setSizeProvider(Builder builder) {
mSizeProvider = builder.mSizeProvider;
if (mSizeProvider == null) {
mSizeProvider = new SizeProvider() {
@Override
public int dividerSize(int position, RecyclerView parent) {
return DEFAULT_SIZE;
}
};
}
}
@Override
public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
RecyclerView.Adapter adapter = parent.getAdapter();
if (adapter == null) {
return;
}
int itemCount = adapter.getItemCount();
int lastDividerOffset = getLastDividerOffset(parent);
int validChildCount = parent.getChildCount();
int lastChildPosition = -1;
for (int i = 0; i < validChildCount; i++) {
View child = parent.getChildAt(i);
int childPosition = parent.getChildAdapterPosition(child);
if (childPosition < lastChildPosition) {
// Avoid remaining divider when animation starts
continue;
}
lastChildPosition = childPosition;
if (!mShowLastDivider && childPosition >= itemCount - lastDividerOffset) {
// Don't draw divider for last line if mShowLastDivider = false
continue;
}
if (wasDividerAlreadyDrawn(childPosition, parent)) {
// No need to draw divider again as it was drawn already by previous column
continue;
}
int groupIndex = getGroupIndex(childPosition, parent);
if (mVisibilityProvider.shouldHideDivider(groupIndex, parent)) {
continue;
}
Rect bounds = getDividerBound(groupIndex, parent, child);
switch (mDividerType) {
case DRAWABLE:
Drawable drawable = mDrawableProvider.drawableProvider(groupIndex, parent);
drawable.setBounds(bounds);
drawable.draw(c);
break;
case PAINT:
mPaint = mPaintProvider.dividerPaint(groupIndex, parent);
c.drawLine(bounds.left, bounds.top, bounds.right, bounds.bottom, mPaint);
break;
case COLOR:
mPaint.setColor(mColorProvider.dividerColor(groupIndex, parent));
mPaint.setStrokeWidth(mSizeProvider.dividerSize(groupIndex, parent));
c.drawLine(bounds.left, bounds.top, bounds.right, bounds.bottom, mPaint);
break;
}
}
}
@Override
public void getItemOffsets(Rect rect, View v, RecyclerView parent, RecyclerView.State state) {
int position = parent.getChildAdapterPosition(v);
int itemCount = parent.getAdapter().getItemCount();
int lastDividerOffset = getLastDividerOffset(parent);
if (!mShowLastDivider && position >= itemCount - lastDividerOffset) {
// Don't set item offset for last line if mShowLastDivider = false
return;
}
int groupIndex = getGroupIndex(position, parent);
if (mVisibilityProvider.shouldHideDivider(groupIndex, parent)) {
return;
}
setItemOffsets(rect, groupIndex, parent);
}
/**
* Check if recyclerview is reverse layout
*
* @param parent RecyclerView
* @return true if recyclerview is reverse layout
*/
protected boolean isReverseLayout(RecyclerView parent) {
RecyclerView.LayoutManager layoutManager = parent.getLayoutManager();
if (layoutManager instanceof LinearLayoutManager) {
return ((LinearLayoutManager) layoutManager).getReverseLayout();
} else {
return false;
}
}
/**
* In the case mShowLastDivider = false,
* Returns offset for how many views we don't have to draw a divider for,
* for LinearLayoutManager it is as simple as not drawing the last child divider,
* but for a GridLayoutManager it needs to take the span count for the last items into account
* until we use the span count configured for the grid.
*
* @param parent RecyclerView
* @return offset for how many views we don't have to draw a divider or 1 if its a
* LinearLayoutManager
*/
private int getLastDividerOffset(RecyclerView parent) {
if (parent.getLayoutManager() instanceof GridLayoutManager) {
GridLayoutManager layoutManager = (GridLayoutManager) parent.getLayoutManager();
GridLayoutManager.SpanSizeLookup spanSizeLookup = layoutManager.getSpanSizeLookup();
int spanCount = layoutManager.getSpanCount();
int itemCount = parent.getAdapter().getItemCount();
for (int i = itemCount - 1; i >= 0; i--) {
if (spanSizeLookup.getSpanIndex(i, spanCount) == 0) {
return itemCount - i;
}
}
}
return 1;
}
/**
* Determines whether divider was already drawn for the row the item is in,
* effectively only makes sense for a grid
*
* @param position current view position to draw divider
* @param parent RecyclerView
* @return true if the divider can be skipped as it is in the same row as the previous one.
*/
private boolean wasDividerAlreadyDrawn(int position, RecyclerView parent) {
if (parent.getLayoutManager() instanceof GridLayoutManager) {
GridLayoutManager layoutManager = (GridLayoutManager) parent.getLayoutManager();
GridLayoutManager.SpanSizeLookup spanSizeLookup = layoutManager.getSpanSizeLookup();
int spanCount = layoutManager.getSpanCount();
return spanSizeLookup.getSpanIndex(position, spanCount) > 0;
}
return false;
}
/**
* Returns a group index for GridLayoutManager.
* for LinearLayoutManager, always returns position.
*
* @param position current view position to draw divider
* @param parent RecyclerView
* @return group index of items
*/
private int getGroupIndex(int position, RecyclerView parent) {
if (parent.getLayoutManager() instanceof GridLayoutManager) {
GridLayoutManager layoutManager = (GridLayoutManager) parent.getLayoutManager();
GridLayoutManager.SpanSizeLookup spanSizeLookup = layoutManager.getSpanSizeLookup();
int spanCount = layoutManager.getSpanCount();
return spanSizeLookup.getSpanGroupIndex(position, spanCount);
}
return position;
}
protected abstract Rect getDividerBound(int position, RecyclerView parent, View child);
protected abstract void setItemOffsets(Rect outRect, int position, RecyclerView parent);
/**
* Interface for controlling divider visibility
*/
public interface VisibilityProvider {
/**
* Returns true if divider should be hidden.
*
* @param position Divider position (or group index for GridLayoutManager)
* @param parent RecyclerView
* @return True if the divider at position should be hidden
*/
boolean shouldHideDivider(int position, RecyclerView parent);
}
/**
* Interface for controlling paint instance for divider drawing
*/
public interface PaintProvider {
/**
* Returns {@link android.graphics.Paint} for divider
*
* @param position Divider position (or group index for GridLayoutManager)
* @param parent RecyclerView
* @return Paint instance
*/
Paint dividerPaint(int position, RecyclerView parent);
}
/**
* Interface for controlling divider color
*/
public interface ColorProvider {
/**
* Returns {@link android.graphics.Color} value of divider
*
* @param position Divider position (or group index for GridLayoutManager)
* @param parent RecyclerView
* @return Color value
*/
int dividerColor(int position, RecyclerView parent);
}
/**
* Interface for controlling drawable object for divider drawing
*/
public interface DrawableProvider {
/**
* Returns drawable instance for divider
*
* @param position Divider position (or group index for GridLayoutManager)
* @param parent RecyclerView
* @return Drawable instance
*/
Drawable drawableProvider(int position, RecyclerView parent);
}
/**
* Interface for controlling divider size
*/
public interface SizeProvider {
/**
* Returns size value of divider.
* Height for horizontal divider, width for vertical divider
*
* @param position Divider position (or group index for GridLayoutManager)
* @param parent RecyclerView
* @return Size of divider
*/
int dividerSize(int position, RecyclerView parent);
}
public static class Builder<T extends Builder> {
private Context mContext;
protected Resources mResources;
private PaintProvider mPaintProvider;
private ColorProvider mColorProvider;
private DrawableProvider mDrawableProvider;
private SizeProvider mSizeProvider;
private VisibilityProvider mVisibilityProvider = new VisibilityProvider() {
@Override
public boolean shouldHideDivider(int position, RecyclerView parent) {
return false;
}
};
private boolean mShowLastDivider = false;
private boolean mPositionInsideItem = false;
public Builder(Context context) {
mContext = context;
mResources = context.getResources();
}
public T paint(final Paint paint) {
return paintProvider(new PaintProvider() {
@Override
public Paint dividerPaint(int position, RecyclerView parent) {
return paint;
}
});
}
public T paintProvider(PaintProvider provider) {
mPaintProvider = provider;
return (T) this;
}
public T color(final int color) {
return colorProvider(new ColorProvider() {
@Override
public int dividerColor(int position, RecyclerView parent) {
return color;
}
});
}
public T colorResId(@ColorRes int colorId) {
return color(ContextCompat.getColor(mContext, colorId));
}
public T colorProvider(ColorProvider provider) {
mColorProvider = provider;
return (T) this;
}
public T drawable(@DrawableRes int id) {
return drawable(ContextCompat.getDrawable(mContext, id));
}
public T drawable(final Drawable drawable) {
return drawableProvider(new DrawableProvider() {
@Override
public Drawable drawableProvider(int position, RecyclerView parent) {
return drawable;
}
});
}
public T drawableProvider(DrawableProvider provider) {
mDrawableProvider = provider;
return (T) this;
}
public T size(final int size) {
return sizeProvider(new SizeProvider() {
@Override
public int dividerSize(int position, RecyclerView parent) {
return size;
}
});
}
public T sizeResId(@DimenRes int sizeId) {
return size(mResources.getDimensionPixelSize(sizeId));
}
public T sizeProvider(SizeProvider provider) {
mSizeProvider = provider;
return (T) this;
}
public T visibilityProvider(VisibilityProvider provider) {
mVisibilityProvider = provider;
return (T) this;
}
public T showLastDivider() {
mShowLastDivider = true;
return (T) this;
}
public T positionInsideItem(boolean positionInsideItem) {
mPositionInsideItem = positionInsideItem;
return (T) this;
}
protected void checkBuilderParams() {
if (mPaintProvider != null) {
if (mColorProvider != null) {
throw new IllegalArgumentException(
"Use setColor method of Paint class to specify line color. Do not provider ColorProvider if you set PaintProvider.");
}
if (mSizeProvider != null) {
throw new IllegalArgumentException(
"Use setStrokeWidth method of Paint class to specify line size. Do not provider SizeProvider if you set PaintProvider.");
}
}
}
}
}

View File

@@ -0,0 +1,174 @@
package com.yqritc.recyclerviewflexibledivider;
import android.content.Context;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.view.View;
import androidx.annotation.DimenRes;
import androidx.core.view.ViewCompat;
import androidx.recyclerview.widget.RecyclerView;
/**
* Created by yqritc on 2015/01/15.
*/
public class HorizontalDividerItemDecoration extends FlexibleDividerDecoration {
private MarginProvider mMarginProvider;
protected HorizontalDividerItemDecoration(Builder builder) {
super(builder);
mMarginProvider = builder.mMarginProvider;
}
@Override
protected Rect getDividerBound(int position, RecyclerView parent, View child) {
Rect bounds = new Rect(0, 0, 0, 0);
int transitionX = (int) ViewCompat.getTranslationX(child);
int transitionY = (int) ViewCompat.getTranslationY(child);
RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child.getLayoutParams();
bounds.left = parent.getPaddingLeft() +
mMarginProvider.dividerLeftMargin(position, parent) + transitionX;
bounds.right = parent.getWidth() - parent.getPaddingRight() -
mMarginProvider.dividerRightMargin(position, parent) + transitionX;
int dividerSize = getDividerSize(position, parent);
boolean isReverseLayout = isReverseLayout(parent);
if (mDividerType == DividerType.DRAWABLE) {
// set top and bottom position of divider
if (isReverseLayout) {
bounds.bottom = child.getTop() - params.topMargin + transitionY;
bounds.top = bounds.bottom - dividerSize;
} else {
bounds.top = child.getBottom() + params.bottomMargin + transitionY;
bounds.bottom = bounds.top + dividerSize;
}
} else {
// set center point of divider
int halfSize = dividerSize / 2;
if (isReverseLayout) {
bounds.top = child.getTop() - params.topMargin - halfSize + transitionY;
} else {
bounds.top = child.getBottom() + params.bottomMargin + halfSize + transitionY;
}
bounds.bottom = bounds.top;
}
if (mPositionInsideItem) {
if (isReverseLayout) {
bounds.top += dividerSize;
bounds.bottom += dividerSize;
} else {
bounds.top -= dividerSize;
bounds.bottom -= dividerSize;
}
}
return bounds;
}
@Override
protected void setItemOffsets(Rect outRect, int position, RecyclerView parent) {
if (mPositionInsideItem) {
outRect.set(0, 0, 0, 0);
return;
}
if (isReverseLayout(parent)) {
outRect.set(0, getDividerSize(position, parent), 0, 0);
} else {
outRect.set(0, 0, 0, getDividerSize(position, parent));
}
}
private int getDividerSize(int position, RecyclerView parent) {
if (mPaintProvider != null) {
return (int) mPaintProvider.dividerPaint(position, parent).getStrokeWidth();
} else if (mSizeProvider != null) {
return mSizeProvider.dividerSize(position, parent);
} else if (mDrawableProvider != null) {
Drawable drawable = mDrawableProvider.drawableProvider(position, parent);
return drawable.getIntrinsicHeight();
}
throw new RuntimeException("failed to get size");
}
/**
* Interface for controlling divider margin
*/
public interface MarginProvider {
/**
* Returns left margin of divider.
*
* @param position Divider position (or group index for GridLayoutManager)
* @param parent RecyclerView
* @return left margin
*/
int dividerLeftMargin(int position, RecyclerView parent);
/**
* Returns right margin of divider.
*
* @param position Divider position (or group index for GridLayoutManager)
* @param parent RecyclerView
* @return right margin
*/
int dividerRightMargin(int position, RecyclerView parent);
}
public static class Builder extends FlexibleDividerDecoration.Builder<Builder> {
private MarginProvider mMarginProvider = new MarginProvider() {
@Override
public int dividerLeftMargin(int position, RecyclerView parent) {
return 0;
}
@Override
public int dividerRightMargin(int position, RecyclerView parent) {
return 0;
}
};
public Builder(Context context) {
super(context);
}
public Builder margin(final int leftMargin, final int rightMargin) {
return marginProvider(new MarginProvider() {
@Override
public int dividerLeftMargin(int position, RecyclerView parent) {
return leftMargin;
}
@Override
public int dividerRightMargin(int position, RecyclerView parent) {
return rightMargin;
}
});
}
public Builder margin(int horizontalMargin) {
return margin(horizontalMargin, horizontalMargin);
}
public Builder marginResId(@DimenRes int leftMarginId, @DimenRes int rightMarginId) {
return margin(mResources.getDimensionPixelSize(leftMarginId),
mResources.getDimensionPixelSize(rightMarginId));
}
public Builder marginResId(@DimenRes int horizontalMarginId) {
return marginResId(horizontalMarginId, horizontalMarginId);
}
public Builder marginProvider(MarginProvider provider) {
mMarginProvider = provider;
return this;
}
public HorizontalDividerItemDecoration build() {
checkBuilderParams();
return new HorizontalDividerItemDecoration(this);
}
}
}

View File

@@ -0,0 +1,174 @@
package com.yqritc.recyclerviewflexibledivider;
import android.content.Context;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.view.View;
import androidx.annotation.DimenRes;
import androidx.core.view.ViewCompat;
import androidx.recyclerview.widget.RecyclerView;
/**
* Created by yqritc on 2015/01/15.
*/
public class VerticalDividerItemDecoration extends FlexibleDividerDecoration {
private MarginProvider mMarginProvider;
protected VerticalDividerItemDecoration(Builder builder) {
super(builder);
mMarginProvider = builder.mMarginProvider;
}
@Override
protected Rect getDividerBound(int position, RecyclerView parent, View child) {
Rect bounds = new Rect(0, 0, 0, 0);
int transitionX = (int) ViewCompat.getTranslationX(child);
int transitionY = (int) ViewCompat.getTranslationY(child);
RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child.getLayoutParams();
bounds.top = parent.getPaddingTop() +
mMarginProvider.dividerTopMargin(position, parent) + transitionY;
bounds.bottom = parent.getHeight() - parent.getPaddingBottom() -
mMarginProvider.dividerBottomMargin(position, parent) + transitionY;
int dividerSize = getDividerSize(position, parent);
boolean isReverseLayout = isReverseLayout(parent);
if (mDividerType == DividerType.DRAWABLE) {
// set left and right position of divider
if (isReverseLayout) {
bounds.right = child.getLeft() - params.leftMargin + transitionX;
bounds.left = bounds.right - dividerSize;
} else {
bounds.left = child.getRight() + params.rightMargin + transitionX;
bounds.right = bounds.left + dividerSize;
}
} else {
// set center point of divider
int halfSize = dividerSize / 2;
if (isReverseLayout) {
bounds.left = child.getLeft() - params.leftMargin - halfSize + transitionX;
} else {
bounds.left = child.getRight() + params.rightMargin + halfSize + transitionX;
}
bounds.right = bounds.left;
}
if (mPositionInsideItem) {
if (isReverseLayout) {
bounds.left += dividerSize;
bounds.right += dividerSize;
} else {
bounds.left -= dividerSize;
bounds.right -= dividerSize;
}
}
return bounds;
}
@Override
protected void setItemOffsets(Rect outRect, int position, RecyclerView parent) {
if (mPositionInsideItem) {
outRect.set(0, 0, 0, 0);
return;
}
if (isReverseLayout(parent)) {
outRect.set(getDividerSize(position, parent), 0, 0, 0);
} else {
outRect.set(0, 0, getDividerSize(position, parent), 0);
}
}
private int getDividerSize(int position, RecyclerView parent) {
if (mPaintProvider != null) {
return (int) mPaintProvider.dividerPaint(position, parent).getStrokeWidth();
} else if (mSizeProvider != null) {
return mSizeProvider.dividerSize(position, parent);
} else if (mDrawableProvider != null) {
Drawable drawable = mDrawableProvider.drawableProvider(position, parent);
return drawable.getIntrinsicWidth();
}
throw new RuntimeException("failed to get size");
}
/**
* Interface for controlling divider margin
*/
public interface MarginProvider {
/**
* Returns top margin of divider.
*
* @param position Divider position (or group index for GridLayoutManager)
* @param parent RecyclerView
* @return top margin
*/
int dividerTopMargin(int position, RecyclerView parent);
/**
* Returns bottom margin of divider.
*
* @param position Divider position (or group index for GridLayoutManager)
* @param parent RecyclerView
* @return bottom margin
*/
int dividerBottomMargin(int position, RecyclerView parent);
}
public static class Builder extends FlexibleDividerDecoration.Builder<Builder> {
private MarginProvider mMarginProvider = new MarginProvider() {
@Override
public int dividerTopMargin(int position, RecyclerView parent) {
return 0;
}
@Override
public int dividerBottomMargin(int position, RecyclerView parent) {
return 0;
}
};
public Builder(Context context) {
super(context);
}
public Builder margin(final int topMargin, final int bottomMargin) {
return marginProvider(new MarginProvider() {
@Override
public int dividerTopMargin(int position, RecyclerView parent) {
return topMargin;
}
@Override
public int dividerBottomMargin(int position, RecyclerView parent) {
return bottomMargin;
}
});
}
public Builder margin(int verticalMargin) {
return margin(verticalMargin, verticalMargin);
}
public Builder marginResId(@DimenRes int topMarginId, @DimenRes int bottomMarginId) {
return margin(mResources.getDimensionPixelSize(topMarginId),
mResources.getDimensionPixelSize(bottomMarginId));
}
public Builder marginResId(@DimenRes int verticalMarginId) {
return marginResId(verticalMarginId, verticalMarginId);
}
public Builder marginProvider(MarginProvider provider) {
mMarginProvider = provider;
return this;
}
public VerticalDividerItemDecoration build() {
checkBuilderParams();
return new VerticalDividerItemDecoration(this);
}
}
}

View File

@@ -1,5 +1,5 @@
#Sat Mar 07 12:33:49 CST 2026
BUILD_TIME=1772858029128
#Sat Mar 07 19:14:09 CST 2026
BUILD_TIME=1772882049270
COMPILE_SDK_VERSION=36
IMAGE_QUANT_CMAKE_VERSION=3.22.1
IMAGE_QUANT_NDK_VERSION=26.1.10909125
@@ -27,6 +27,6 @@ RAPID_OCR_OPENCV_MOBILE_LABEL_VERSION=13
RAPID_OCR_OPENCV_MOBILE_VERSION=4.5.3
TARGET_SDK_VERSION=36
TARGET_SDK_VERSION_INRT=29
VERSION_BUILD=3778
VERSION_BUILD=3780
VERSION_NAME=6.7.0 Alpha23
VSCODE_EXT_REQUIRED_VERSION=1.0.13