300 | * `0 ~ 1` 表示从透明到不透明(纯黑)
301 | */
302 | fun setDimAmount(amount: Float): DialogManager {
303 | mDimAmount = if (amount < 0 || amount > 1) 0.5F else amount
304 | isDimmedBehind = true
305 | return this
306 | }
307 |
308 | fun setDimmedBehind(dimmedBehind: Boolean): DialogManager {
309 | isDimmedBehind = if (mDimAmount != -1F) true else dimmedBehind
310 | return this
311 | }
312 |
313 | fun applyDimAmount(window: Window? = null) {
314 | (window ?: currentDialog()?.window ?: return).apply {
315 | if (isDimmedBehind && mDimAmount != -1F) this.setDimAmount(mDimAmount)
316 | }
317 | }
318 |
319 | fun applySize(window: Window? = null) {
320 | if (isContextIllegal(dialog) || !isShowing()) return
321 |
322 | (window ?: currentDialog()?.window ?: return).apply {
323 | if (mWidth != -3 && mHeight != -3) {
324 | setLayout(mWidth, mHeight)
325 | }
326 | }
327 | }
328 |
329 | fun setOnShowListener(listener: (Window) -> Unit): DialogManager {
330 | currentDialog()?.apply {
331 | setOnShowListener {
332 | this.window?.apply {
333 | listener.invoke(this)
334 | }
335 | }
336 | }
337 | return this
338 | }
339 |
340 | fun setOnDismissListener(listener: DialogInterface.OnDismissListener): DialogManager {
341 | if (isDialogType) {
342 | currentDialog()?.setOnDismissListener(listener)
343 | } else {
344 | dialogFragment?.setOnDismissListener(listener)
345 | }
346 | return this
347 | }
348 |
349 | fun setOnCancelListener(listener: DialogInterface.OnCancelListener): DialogManager {
350 | if (isDialogType) {
351 | currentDialog()?.setOnCancelListener(listener)
352 | } else {
353 | dialogFragment?.setOnCancelListener(listener)
354 | }
355 | return this
356 | }
357 |
358 | fun setOnKeyListener(listener: DialogInterface.OnKeyListener): DialogManager {
359 | currentDialog()?.setOnKeyListener(listener)
360 | return this
361 | }
362 |
363 | fun create(): DialogManager {
364 | if (isContextIllegal(dialog) || isShowing()) return this
365 | if (isDialogType) {
366 | currentDialog()?.create()
367 | } else {
368 | show()
369 | }
370 | return this
371 | }
372 |
373 | fun show(): DialogManager {
374 | if (isContextIllegal(dialog) || isShowing()) return this
375 |
376 | if (isDialogType) {
377 | currentDialog()?.show()
378 | } else {
379 | (mContext as? FragmentActivity?)?.run {
380 | dialogFragment?.show(this)
381 | }
382 | }
383 |
384 | //注: 对 Dialog.Window 的设置需要在显示后才有效果 ╮(╯▽╰)╭
385 | //Note: The setting of Dialog.Window needs to be effective after display .
386 | currentDialog()?.apply {
387 | window?.apply {
388 | setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN)
389 |
390 | if (isDimmedBehind) addFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND)
391 | else clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND)
392 |
393 | if (mAnimResId > 0) setWindowAnimations(mAnimResId)
394 |
395 | applyDimAmount(this)
396 | applySize(this)
397 | }
398 | }
399 | return this
400 | }
401 |
402 | }
--------------------------------------------------------------------------------
/dialog_usage/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/dialog_usage/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | id 'com.android.library'
3 | id 'kotlin-android'
4 | id 'org.jetbrains.kotlin.android'
5 | }
6 |
7 | android {
8 | compileSdkVersion rootProject.ext.compileSdkVersion
9 | buildToolsVersion rootProject.ext.buildToolsVersion
10 | defaultConfig {
11 | minSdkVersion rootProject.ext.minSdkVersion
12 | targetSdkVersion rootProject.ext.targetSdkVersion
13 |
14 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
15 | consumerProguardFiles 'consumer-rules.pro'
16 | }
17 |
18 | buildTypes {
19 | release {
20 | minifyEnabled false
21 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
22 | }
23 | }
24 | lintOptions {
25 | abortOnError false
26 | }
27 | namespace 'ando.dialog.usage'
28 | }
29 |
30 | dependencies {
31 | implementation 'androidx.appcompat:appcompat:1.6.1'
32 |
33 | //implementation 'com.github.javakam:dialog.core:6.0.0@aar'
34 | implementation project(':dialog_core')
35 | }
36 |
37 | ext {
38 | PUBLISH_ARTIFACT_ID = 'dialog.usage'
39 | PUBLISH_VERSION = rootProject.ext.versionUsage
40 | }
41 | apply from: "${rootProject.projectDir}/publish.gradle"
--------------------------------------------------------------------------------
/dialog_usage/consumer-rules.pro:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/javakam/DialogManager/4738866ca1e4262595c1df845be5c7e8bb18e347/dialog_usage/consumer-rules.pro
--------------------------------------------------------------------------------
/dialog_usage/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
--------------------------------------------------------------------------------
/dialog_usage/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/dialog_usage/src/main/java/ando/dialog/usage/BaseDialog.kt:
--------------------------------------------------------------------------------
1 | package ando.dialog.usage
2 |
3 | import android.app.Dialog
4 | import android.content.Context
5 | import android.content.DialogInterface
6 | import android.os.Bundle
7 | import android.view.View
8 | import android.view.Window
9 |
10 | /**
11 | * Title: BaseDialog
12 | *
(R.id.progressbar_ando_dialog_loading).visibility = View.VISIBLE
24 | }
25 | .setDimmedBehind(true)
26 | .setCancelable(true)
27 | .setCanceledOnTouchOutside(true)
28 |
29 | fun loadingCircle(context: Context): DialogManager =
30 | DialogManager.with(context, R.style.AndoLoadingDialog)
31 | .setContentView(R.layout.layout_ando_dialog_loading) { v ->
32 | val image: ImageView = v.findViewById(R.id.iv_ando_dialog_loading)
33 | image.visibility = View.VISIBLE
34 | val anim = AnimationUtils.loadAnimation(context, R.anim.anim_ando_dialog_loading)
35 | image.startAnimation(anim)
36 | }
37 | .setDimmedBehind(true)
38 | .setCancelable(true)
39 | .setCanceledOnTouchOutside(true)
40 |
--------------------------------------------------------------------------------
/dialog_usage/src/main/res/anim/anim_ando_dialog_bottom_in.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/dialog_usage/src/main/res/anim/anim_ando_dialog_bottom_out.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/dialog_usage/src/main/res/anim/anim_ando_dialog_loading.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
13 |
--------------------------------------------------------------------------------
/dialog_usage/src/main/res/drawable-xhdpi/ic_ando_dialog_loading_line.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/javakam/DialogManager/4738866ca1e4262595c1df845be5c7e8bb18e347/dialog_usage/src/main/res/drawable-xhdpi/ic_ando_dialog_loading_line.png
--------------------------------------------------------------------------------
/dialog_usage/src/main/res/drawable-xhdpi/ic_ando_dialog_loading_point.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/javakam/DialogManager/4738866ca1e4262595c1df845be5c7e8bb18e347/dialog_usage/src/main/res/drawable-xhdpi/ic_ando_dialog_loading_point.png
--------------------------------------------------------------------------------
/dialog_usage/src/main/res/drawable/rectangle_ando_dialog_bottom.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/dialog_usage/src/main/res/drawable/rectangle_ando_dialog_datetime.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/dialog_usage/src/main/res/drawable/rectangle_ando_dialog_loading.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/dialog_usage/src/main/res/drawable/rotate_ando_dialog_loading.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/dialog_usage/src/main/res/layout/layout_ando_dialog_datetime.xml:
--------------------------------------------------------------------------------
1 |
2 |
12 |
13 |
19 |
20 |
26 |
27 |
33 |
34 |
35 |
36 |
42 |
43 |
55 |
56 |
60 |
61 |
72 |
73 |
74 |
75 |
--------------------------------------------------------------------------------
/dialog_usage/src/main/res/layout/layout_ando_dialog_loading.xml:
--------------------------------------------------------------------------------
1 |
2 |
11 |
12 |
18 |
19 |
25 |
26 |
33 |
34 |
--------------------------------------------------------------------------------
/dialog_usage/src/main/res/values-en/values.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | Loading…
4 |
--------------------------------------------------------------------------------
/dialog_usage/src/main/res/values/values.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
26 |
27 |
31 |
32 |
36 |
37 |
38 | 加载中…
39 | 120dp
40 | 108dp
41 | 5dp
42 | #80000000
43 | @drawable/ic_ando_dialog_loading_line
44 |
45 | 1300
46 |
47 |
58 |
59 |
66 |
67 |
74 |
75 |
76 |
77 | @android:color/white
78 | 20dp
79 | 300
80 |
81 |
85 |
86 |
87 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 | # IDE (e.g. Android Studio) users:
3 | # Gradle settings configured through the IDE *will override*
4 | # any settings specified in this file.
5 | # For more details on how to configure your build environment visit
6 | # http://www.gradle.org/docs/current/userguide/build_environment.html
7 | # Specifies the JVM arguments used for the daemon process.
8 | # The setting is particularly useful for tweaking memory settings.
9 | org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
10 | # When configured, Gradle will run in incubating parallel mode.
11 | # This option should only be used with decoupled projects. More details, visit
12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
13 | # org.gradle.parallel=true
14 | # AndroidX package structure to make it clearer which packages are bundled with the
15 | # Android operating system, and which are packaged with your app"s APK
16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn
17 | android.useAndroidX=true
18 | # Automatically convert third-party libraries to use AndroidX
19 | android.enableJetifier=true
20 | # Kotlin code style for this project: "official" or "obsolete":
21 | kotlin.code.style=official
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/javakam/DialogManager/4738866ca1e4262595c1df845be5c7e8bb18e347/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Mon Mar 08 11:14:47 CST 2021
2 | distributionBase=GRADLE_USER_HOME
3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-bin.zip
4 | distributionPath=wrapper/dists
5 | zipStorePath=wrapper/dists
6 | zipStoreBase=GRADLE_USER_HOME
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Attempt to set APP_HOME
10 | # Resolve links: $0 may be a link
11 | PRG="$0"
12 | # Need this for relative symlinks.
13 | while [ -h "$PRG" ] ; do
14 | ls=`ls -ld "$PRG"`
15 | link=`expr "$ls" : '.*-> \(.*\)$'`
16 | if expr "$link" : '/.*' > /dev/null; then
17 | PRG="$link"
18 | else
19 | PRG=`dirname "$PRG"`"/$link"
20 | fi
21 | done
22 | SAVED="`pwd`"
23 | cd "`dirname \"$PRG\"`/" >/dev/null
24 | APP_HOME="`pwd -P`"
25 | cd "$SAVED" >/dev/null
26 |
27 | APP_NAME="Gradle"
28 | APP_BASE_NAME=`basename "$0"`
29 |
30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
31 | DEFAULT_JVM_OPTS=""
32 |
33 | # Use the maximum available, or set MAX_FD != -1 to use that value.
34 | MAX_FD="maximum"
35 |
36 | warn () {
37 | echo "$*"
38 | }
39 |
40 | die () {
41 | echo
42 | echo "$*"
43 | echo
44 | exit 1
45 | }
46 |
47 | # OS specific support (must be 'true' or 'false').
48 | cygwin=false
49 | msys=false
50 | darwin=false
51 | nonstop=false
52 | case "`uname`" in
53 | CYGWIN* )
54 | cygwin=true
55 | ;;
56 | Darwin* )
57 | darwin=true
58 | ;;
59 | MINGW* )
60 | msys=true
61 | ;;
62 | NONSTOP* )
63 | nonstop=true
64 | ;;
65 | esac
66 |
67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
68 |
69 | # Determine the Java command to use to start the JVM.
70 | if [ -n "$JAVA_HOME" ] ; then
71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
72 | # IBM's JDK on AIX uses strange locations for the executables
73 | JAVACMD="$JAVA_HOME/jre/sh/java"
74 | else
75 | JAVACMD="$JAVA_HOME/bin/java"
76 | fi
77 | if [ ! -x "$JAVACMD" ] ; then
78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
79 |
80 | Please set the JAVA_HOME variable in your environment to match the
81 | location of your Java installation."
82 | fi
83 | else
84 | JAVACMD="java"
85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
86 |
87 | Please set the JAVA_HOME variable in your environment to match the
88 | location of your Java installation."
89 | fi
90 |
91 | # Increase the maximum file descriptors if we can.
92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
93 | MAX_FD_LIMIT=`ulimit -H -n`
94 | if [ $? -eq 0 ] ; then
95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
96 | MAX_FD="$MAX_FD_LIMIT"
97 | fi
98 | ulimit -n $MAX_FD
99 | if [ $? -ne 0 ] ; then
100 | warn "Could not set maximum file descriptor limit: $MAX_FD"
101 | fi
102 | else
103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
104 | fi
105 | fi
106 |
107 | # For Darwin, add options to specify how the application appears in the dock
108 | if $darwin; then
109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
110 | fi
111 |
112 | # For Cygwin, switch paths to Windows format before running java
113 | if $cygwin ; then
114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
116 | JAVACMD=`cygpath --unix "$JAVACMD"`
117 |
118 | # We build the pattern for arguments to be converted via cygpath
119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
120 | SEP=""
121 | for dir in $ROOTDIRSRAW ; do
122 | ROOTDIRS="$ROOTDIRS$SEP$dir"
123 | SEP="|"
124 | done
125 | OURCYGPATTERN="(^($ROOTDIRS))"
126 | # Add a user-defined pattern to the cygpath arguments
127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
129 | fi
130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
131 | i=0
132 | for arg in "$@" ; do
133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
135 |
136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
138 | else
139 | eval `echo args$i`="\"$arg\""
140 | fi
141 | i=$((i+1))
142 | done
143 | case $i in
144 | (0) set -- ;;
145 | (1) set -- "$args0" ;;
146 | (2) set -- "$args0" "$args1" ;;
147 | (3) set -- "$args0" "$args1" "$args2" ;;
148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
154 | esac
155 | fi
156 |
157 | # Escape application args
158 | save () {
159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
160 | echo " "
161 | }
162 | APP_ARGS=$(save "$@")
163 |
164 | # Collect all arguments for the java command, following the shell quoting and substitution rules
165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
166 |
167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
169 | cd "$(dirname "$0")"
170 | fi
171 |
172 | exec "$JAVACMD" "$@"
173 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | set DIRNAME=%~dp0
12 | if "%DIRNAME%" == "" set DIRNAME=.
13 | set APP_BASE_NAME=%~n0
14 | set APP_HOME=%DIRNAME%
15 |
16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
17 | set DEFAULT_JVM_OPTS=
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windows variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 |
53 | :win9xME_args
54 | @rem Slurp the command line arguments.
55 | set CMD_LINE_ARGS=
56 | set _SKIP=2
57 |
58 | :win9xME_args_slurp
59 | if "x%~1" == "x" goto execute
60 |
61 | set CMD_LINE_ARGS=%*
62 |
63 | :execute
64 | @rem Setup the command line
65 |
66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
67 |
68 | @rem Execute Gradle
69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
70 |
71 | :end
72 | @rem End local scope for the variables with windows NT shell
73 | if "%ERRORLEVEL%"=="0" goto mainEnd
74 |
75 | :fail
76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
77 | rem the _cmd.exe /c_ return code!
78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
79 | exit /b 1
80 |
81 | :mainEnd
82 | if "%OS%"=="Windows_NT" endlocal
83 |
84 | :omega
85 |
--------------------------------------------------------------------------------
/publish.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'maven-publish'
2 | apply plugin: 'signing'
3 |
4 | task androidSourcesJar(type: Jar) {
5 | archiveClassifier.set("sources")
6 | from android.sourceSets.main.java.source
7 |
8 | exclude "**/R.class"
9 | exclude "**/BuildConfig.class"
10 | }
11 |
12 | //查看Sonatype信息 https://issues.sonatype.org/issues/?filter=-2
13 | ext {
14 | PUBLISH_GROUP_ID = "com.github.javakam"
15 | //PUBLISH_VERSION = rootProject.ext.versionName
16 | }
17 | ext["signing.keyId"] = ''
18 | ext["signing.password"] = ''
19 | ext["signing.secretKeyRingFile"] = ''
20 | ext["ossrhUsername"] = ''
21 | ext["ossrhPassword"] = ''
22 | File secretPropsFile = project.rootProject.file('local.properties')
23 | if (secretPropsFile.exists()) {
24 | println "Found secret props file, loading props"
25 | Properties p = new Properties()
26 | p.load(new FileInputStream(secretPropsFile))
27 | p.each { name, value ->
28 | ext[name] = value
29 | }
30 | } else {
31 | println "No props file, loading env vars"
32 | }
33 |
34 | publishing {
35 | publications {
36 | release(MavenPublication) {
37 | // The coordinates of the library, being set from variables that
38 | // we'll set up in a moment
39 | groupId PUBLISH_GROUP_ID
40 | artifactId PUBLISH_ARTIFACT_ID
41 | version PUBLISH_VERSION
42 |
43 | // Two artifacts, the `aar` and the sources
44 | artifact("$buildDir/outputs/aar/${project.getName()}-release.aar")
45 | artifact androidSourcesJar
46 |
47 | // Self-explanatory metadata for the most part
48 | pom {
49 | name = PUBLISH_ARTIFACT_ID
50 | description = 'Ando Dialog Manager.'
51 | // If your project has a dedicated site, use its URL here
52 | url = "https://github.com/javakam/${rootProject.name}"
53 | licenses {
54 | license {
55 | //协议类型,一般默认Apache License2.0的话不用改
56 | name = 'The Apache License, Version 2.0'
57 | url = 'http://www.apache.org/licenses/LICENSE-2.0.txt'
58 | }
59 | }
60 | developers {
61 | developer {
62 | id = 'javakam'
63 | name = 'javakam'
64 | email = 'jooybao@foxmail.com'
65 | }
66 | }
67 | // 版本控制信息,如果您使用的是GitHub,请遵循此处显示的格式
68 | scm {
69 | //修改成你的Git地址
70 | connection = "scm:git:github.com/javakam/${rootProject.name}.git"
71 | developerConnection = "scm:git:ssh://github.com:javakam/${rootProject.name}.git"
72 | //分支地址
73 | url = "https://github.com/javakam/${rootProject.name}/tree/master"
74 | }
75 | // A slightly hacky fix so that your POM will include any transitive dependencies
76 | // that your library builds upon
77 | withXml {
78 | def dependenciesNode = asNode().appendNode('dependencies')
79 |
80 | project.configurations.implementation.allDependencies.each {
81 | def dependencyNode = dependenciesNode.appendNode('dependency')
82 | dependencyNode.appendNode('groupId', it.group)
83 | dependencyNode.appendNode('artifactId', it.name)
84 | dependencyNode.appendNode('version', it.version)
85 | }
86 | }
87 | }
88 | }
89 | }
90 | repositories {
91 | // The repository to publish to, Sonatype/MavenCentral
92 | maven {
93 | //Failed to publish publication 'release' to repository 'maven'
94 | //https://blog.csdn.net/chuyouyinghe/article/details/122424202
95 | allowInsecureProtocol = true
96 | // This is an arbitrary name, you may also use "mavencentral" or
97 | // any other name that's descriptive for you
98 | name = "maven"
99 | def releasesRepoUrl = "https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/"
100 | def snapshotsRepoUrl = "https://s01.oss.sonatype.org/content/repositories/snapshots/"
101 | // You only need this if you want to publish snapshots, otherwise just set the URL
102 | // to the release repo directly
103 | url = version.endsWith('SNAPSHOT') ? snapshotsRepoUrl : releasesRepoUrl
104 |
105 | // The username and password we've fetched earlier
106 | credentials {
107 | username ossrhUsername
108 | password ossrhPassword
109 | }
110 | }
111 | }
112 | }
113 |
114 | signing {
115 | //useGpgCmd()
116 | sign publishing.publications
117 | }
--------------------------------------------------------------------------------
/screenshot/func.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/javakam/DialogManager/4738866ca1e4262595c1df845be5c7e8bb18e347/screenshot/func.gif
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | rootProject.name = "DialogManager"
2 | include ':dialog_bottom_sheet'
3 | include ':dialog_usage'
4 | include ':dialog_core'
5 | include ':app'
6 | include ':widget_option_list'
7 |
--------------------------------------------------------------------------------
/widget_option_list/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/widget_option_list/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | id 'com.android.library'
3 | id 'kotlin-android'
4 | id 'org.jetbrains.kotlin.android'
5 | }
6 |
7 | android {
8 | compileSdkVersion rootProject.ext.compileSdkVersion
9 | buildToolsVersion rootProject.ext.buildToolsVersion
10 | defaultConfig {
11 | minSdkVersion rootProject.ext.minSdkVersion
12 | targetSdkVersion rootProject.ext.targetSdkVersion
13 |
14 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
15 | consumerProguardFiles 'consumer-rules.pro'
16 | }
17 |
18 | buildTypes {
19 | release {
20 | minifyEnabled false
21 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
22 | }
23 | }
24 | lintOptions {
25 | abortOnError false
26 | }
27 | namespace 'ando.widget.option.list'
28 | }
29 |
30 | dependencies {
31 | implementation 'androidx.appcompat:appcompat:1.6.1'
32 | implementation 'com.google.android.material:material:1.8.0'
33 | }
34 |
35 | ext {
36 | PUBLISH_ARTIFACT_ID = 'widget.optionview'
37 | PUBLISH_VERSION = rootProject.ext.versionOptionList
38 | }
39 | apply from: "${rootProject.projectDir}/publish.gradle"
--------------------------------------------------------------------------------
/widget_option_list/consumer-rules.pro:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/javakam/DialogManager/4738866ca1e4262595c1df845be5c7e8bb18e347/widget_option_list/consumer-rules.pro
--------------------------------------------------------------------------------
/widget_option_list/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
--------------------------------------------------------------------------------
/widget_option_list/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/widget_option_list/src/main/java/ando/widget/option/list/EasyAdapter.java:
--------------------------------------------------------------------------------
1 | package ando.widget.option.list;
2 |
3 | import android.view.View;
4 |
5 | import androidx.annotation.NonNull;
6 | import androidx.recyclerview.widget.RecyclerView;
7 |
8 | import java.util.ArrayList;
9 | import java.util.List;
10 |
11 | /**
12 | * 仿照原生RecyclerView.Adapter的实现,在原生适配器的基础上 支持监听item单击事件以及支持单选模式、多选模式
13 | *
14 | * https://github.com/ZhouHuang23/EasyAdapter
15 | */
16 | public abstract class EasyAdapter extends RecyclerView.Adapter implements View.OnClickListener {
17 | private OnItemClickListener onItemClickListener;
18 | private OnItemSingleSelectListener onItemSingleSelectListener;
19 | private OnItemMultiSelectListener onItemMultiSelectListener;
20 | private SelectMode selectMode;
21 |
22 | public void setOnItemClickListener(OnItemClickListener onItemClickListener) {
23 | this.onItemClickListener = onItemClickListener;
24 | }
25 |
26 | public void setOnItemSingleSelectListener(OnItemSingleSelectListener onItemSingleSelectListener) {
27 | this.onItemSingleSelectListener = onItemSingleSelectListener;
28 | }
29 |
30 | public void setOnItemMultiSelectListener(OnItemMultiSelectListener onItemMultiSelectListener) {
31 | this.onItemMultiSelectListener = onItemMultiSelectListener;
32 | }
33 |
34 | public abstract void whenBindViewHolder(VH holder, int position);
35 |
36 | private int singleSelected = 0; // 默认为第一个被选中
37 | private final List multiSelected = new ArrayList<>();
38 | private int maxSelectedCount = -1;
39 |
40 | @Override
41 | public void onBindViewHolder(@NonNull VH holder, int position) {
42 | whenBindViewHolder(holder, position);
43 |
44 | holder.itemView.setTag(position);// itemView 绑定位置
45 | holder.itemView.setOnClickListener(this);
46 |
47 | if (selectMode == SelectMode.CLICK) { //点击
48 | holder.itemView.setSelected(false);
49 | } else if (selectMode == SelectMode.SINGLE_SELECT) { //单选
50 | holder.itemView.setSelected(singleSelected == position);
51 | } else if (selectMode == SelectMode.MULTI_SELECT) {//多选
52 | holder.itemView.setSelected(multiSelected.contains(position));
53 | }
54 | }
55 |
56 | @Override
57 | public void onClick(View v) {
58 | final int itemPosition = (int) v.getTag();
59 | if (selectMode == SelectMode.CLICK) {//点击模式
60 | if (onItemClickListener != null) {
61 | onItemClickListener.onClicked(itemPosition);
62 | }
63 | } else if (selectMode == SelectMode.SINGLE_SELECT) { //单选模式
64 | if (onItemSingleSelectListener != null) {
65 | if (singleSelected == itemPosition) {
66 | onItemSingleSelectListener.onSelected(itemPosition, false);
67 | } else {
68 | singleSelected = itemPosition;
69 | onItemSingleSelectListener.onSelected(itemPosition, true);
70 | }
71 | }
72 | notifyDataSetChanged();//通知刷新
73 | } else if (selectMode == SelectMode.MULTI_SELECT) {//多选模式
74 | if (maxSelectedCount <= 0 //选择不受限制
75 | || multiSelected.size() < maxSelectedCount) { // 选择个数需要小于最大可选数
76 | if (multiSelected.contains(itemPosition)) {
77 | multiSelected.remove((Integer) itemPosition);
78 | if (onItemMultiSelectListener != null) {
79 | onItemMultiSelectListener.onSelected(Operation.ORDINARY, itemPosition, false);
80 | }
81 | } else {
82 | multiSelected.add(itemPosition);
83 | if (onItemMultiSelectListener != null) {
84 | onItemMultiSelectListener.onSelected(Operation.ORDINARY, itemPosition, true);
85 | }
86 | }
87 |
88 | } else if (multiSelected.size() == maxSelectedCount && multiSelected.contains(itemPosition)) { //当等于最大数量并且点击的item包含在已选中 可清除
89 | multiSelected.remove((Integer) itemPosition);
90 | if (onItemMultiSelectListener != null) {
91 | onItemMultiSelectListener.onSelected(Operation.ORDINARY, itemPosition, false);
92 | }
93 | }
94 | notifyDataSetChanged();
95 | }
96 | }
97 |
98 | //=========API=========
99 |
100 | /**
101 | * 设置选择模式
102 | */
103 | public void setSelectMode(SelectMode selectMode) {
104 | this.selectMode = selectMode;
105 | notifyDataSetChanged();
106 | }
107 |
108 | /**
109 | * 获取选择模式
110 | */
111 | public SelectMode getSelectMode() {
112 | return selectMode;
113 | }
114 |
115 | /**
116 | * 是否为单选
117 | */
118 | public boolean isSelectSingleMode() {
119 | return (selectMode == SelectMode.SINGLE_SELECT);
120 | }
121 |
122 | /**
123 | * 是否为多选
124 | */
125 | public boolean isSelectMultiMode() {
126 | return (selectMode == SelectMode.MULTI_SELECT);
127 | }
128 |
129 | /**
130 | * 设置默认选中项,一个或多个
131 | */
132 | public void setSelected(int... itemPositions) {
133 | multiSelected.clear();
134 | if (selectMode == SelectMode.SINGLE_SELECT) {
135 | singleSelected = itemPositions[0];
136 | if (onItemSingleSelectListener != null) {
137 | onItemSingleSelectListener.onSelected(singleSelected, true);
138 | }
139 | } else {
140 | for (int itemPosition : itemPositions) {
141 | multiSelected.add(itemPosition);
142 | if (onItemMultiSelectListener != null) {
143 | onItemMultiSelectListener.onSelected(Operation.ORDINARY, itemPosition, true);
144 | }
145 | }
146 | }
147 | notifyDataSetChanged();
148 | }
149 |
150 | /**
151 | * 获取单选模式选中Item位置
152 | */
153 | public int getSingleSelected() {
154 | return singleSelected;
155 | }
156 |
157 | /**
158 | * 清除选择项,只有在MULT_SELECT模式下有效
159 | */
160 | public void clearSelected() {
161 | if (selectMode == SelectMode.MULTI_SELECT) {
162 | multiSelected.clear();
163 | if (onItemMultiSelectListener != null) {
164 | onItemMultiSelectListener.onSelected(Operation.ALL_CANCEL, -1, false);
165 | }
166 | notifyDataSetChanged();
167 | }
168 | }
169 |
170 | /**
171 | * 获取单选项位置
172 | */
173 | public int getSingleSelectedPosition() {
174 | return singleSelected;
175 | }
176 |
177 | /**
178 | * 获取多选项位置,元素顺序按照选择顺序排列
179 | */
180 | public List getMultiSelectedPosition() {
181 | return multiSelected;
182 | }
183 |
184 | /**
185 | * 设置最大可选数量
186 | *
187 | * @param maxSelectedCount maxSelectedCount <= 0 表示不限制选择数
188 | */
189 | public void setMaxSelectedCount(int maxSelectedCount) {
190 | if (maxSelectedCount < multiSelected.size()) {
191 | multiSelected.clear();
192 | }
193 | this.maxSelectedCount = maxSelectedCount;
194 | if (onItemMultiSelectListener != null) {
195 | onItemMultiSelectListener.onSelected(Operation.SET_MAX_COUNT, -1, false);
196 | }
197 | notifyDataSetChanged();
198 | }
199 |
200 | /**
201 | * 获取最大可选数目
202 | */
203 | public int getMaxSelectedCount() {
204 | return maxSelectedCount;
205 | }
206 |
207 | /**
208 | * 选择全部,仅在maxSelectedCount <= 0 不限制选择数时有效
209 | */
210 | public void selectAll() {
211 | if (maxSelectedCount <= 0) {
212 | multiSelected.clear();
213 | for (int i = 0; i < getItemCount(); i++) {
214 | multiSelected.add(i);
215 | }
216 | if (onItemMultiSelectListener != null) {
217 | onItemMultiSelectListener.onSelected(Operation.ALL_SELECTED, -1, false);
218 | }
219 | notifyDataSetChanged();
220 | }
221 | }
222 |
223 | /**
224 | * 反选全部,仅在maxSelectedCount <= 0 不限制选择数时有效
225 | */
226 | public void reverseSelected() {
227 | if (maxSelectedCount <= 0) {
228 | if (onItemMultiSelectListener != null) {
229 | onItemMultiSelectListener.onSelected(Operation.REVERSE_SELECTED, -1, false);
230 | }
231 | for (int i = 0; i < getItemCount(); i++) {
232 | if (multiSelected.contains(i)) {
233 | multiSelected.remove((Integer) i);
234 | } else {
235 | multiSelected.add(i);
236 | }
237 | }
238 | notifyDataSetChanged();
239 | }
240 | }
241 |
242 | /**
243 | * 判断某个item位置是否被选中
244 | */
245 | public boolean isSelected(int position) {
246 | if (selectMode == SelectMode.SINGLE_SELECT) {
247 | return position == singleSelected;
248 | } else if (selectMode == SelectMode.MULTI_SELECT) {
249 | return multiSelected.contains(position);
250 | }
251 | return false;
252 | }
253 |
254 | /**
255 | * 点选模式监听接口
256 | */
257 | public interface OnItemClickListener {
258 | /**
259 | * 点选模式下,点击item时回调
260 | *
261 | * @param itemPosition 点击的item位置
262 | */
263 | void onClicked(int itemPosition);
264 | }
265 |
266 | /**
267 | * 单选模式监听接口
268 | */
269 | public interface OnItemSingleSelectListener {
270 | /**
271 | * 单选模式下,点击Item选中时回调
272 | *
273 | * @param itemPosition 点击的item位置
274 | * @param isSelected 是否选中
275 | */
276 | void onSelected(int itemPosition, boolean isSelected);
277 | }
278 |
279 | /**
280 | * 多选模式监听接口
281 | */
282 | public interface OnItemMultiSelectListener {
283 | /**
284 | * 多选模式下,点击Item选中时回调
285 | *
286 | * @param operation 操作类型,分为普通,全选 反选 取消全部等。
287 | * @param itemPosition 点击的item位置 仅在操作类型为普通时生效
288 | * @param isSelected 是否选中 仅在操作类型为普通时生效
289 | */
290 | void onSelected(Operation operation, int itemPosition, boolean isSelected);
291 | }
292 |
293 | /**
294 | * 选择模式,分为点击,单选,多选。
295 | */
296 | public enum SelectMode {
297 | CLICK, SINGLE_SELECT, MULTI_SELECT
298 | }
299 |
300 | /**
301 | * 操作类型,分为普通,全选 反选 取消全部等。
302 | */
303 | public enum Operation {
304 | ORDINARY, ALL_SELECTED, REVERSE_SELECTED, ALL_CANCEL, SET_MAX_COUNT
305 | }
306 | }
--------------------------------------------------------------------------------
/widget_option_list/src/main/java/ando/widget/option/list/OptionGlobal.kt:
--------------------------------------------------------------------------------
1 | package ando.widget.option.list
2 |
3 | import androidx.annotation.IntDef
4 |
5 | /**
6 | * @author javakam
7 | * @date 2021-06-01 10:31
8 | */
9 |
10 | //不显示选项框/单选/多选 注:单选和多选仅支持横向ItemView布局
11 | const val MODE_CHECK_NONE = 1
12 | const val MODE_CHECK_SINGLE = 2
13 | const val MODE_CHECK_MULTI = 3
14 |
15 | @Retention(AnnotationRetention.SOURCE)
16 | @IntDef(value = [MODE_CHECK_NONE, MODE_CHECK_SINGLE, MODE_CHECK_MULTI])
17 | annotation class CheckMode
18 |
19 | data class OptConfig(
20 | var title: String? = null,
21 | var titleLayoutResource: Int = OptionView.LAYOUT_TITLE,
22 | var itemLayoutResource: Int = OptionView.LAYOUT_ITEM_HORIZONTAL,
23 | var columns: Int = 1,
24 | var setting: OptSetting = OptSetting(MODE_CHECK_NONE, isItemViewHorizontal = true, isCheckTriggerByItemView = false),
25 | )
26 |
27 | data class OptSetting(
28 | @CheckMode var checkMode: Int = MODE_CHECK_NONE, //不显示选项框/单选/多选
29 | var isItemViewHorizontal: Boolean = true, //是否为横向显示的 ItemView
30 | var isCheckTriggerByItemView: Boolean = false, //用于控制点击时由 Adapter.ItemView 还是 CheckBox 切换勾选状态, 默认为 CheckBox
31 | )
32 |
33 | fun OptSetting.isCheckShow(): Boolean = (checkMode != MODE_CHECK_NONE)
34 | fun OptSetting.isCheckSingle(): Boolean = (checkMode == MODE_CHECK_SINGLE)
35 |
--------------------------------------------------------------------------------
/widget_option_list/src/main/java/ando/widget/option/list/OptionItem.java:
--------------------------------------------------------------------------------
1 | package ando.widget.option.list;
2 |
3 | import android.graphics.drawable.Drawable;
4 | import android.os.Build;
5 | import android.os.Parcel;
6 | import android.os.Parcelable;
7 |
8 | import androidx.annotation.Nullable;
9 |
10 | import java.util.Objects;
11 |
12 | /**
13 | * @author javakam
14 | * @date 2021/4/20 9:36
15 | */
16 | public class OptionItem implements Parcelable {
17 |
18 | private Integer id;
19 | private String title;
20 | private Drawable icon;
21 | private boolean isChecked = false;
22 |
23 | public OptionItem(Integer id, String title, @Nullable Drawable icon) {
24 | this.id = id;
25 | this.title = title;
26 | this.icon = icon;
27 | }
28 |
29 | public OptionItem(Integer id, String title, @Nullable Drawable icon, boolean isChecked) {
30 | this.id = id;
31 | this.title = title;
32 | this.icon = icon;
33 | this.isChecked = isChecked;
34 | }
35 |
36 | @Override
37 | public String toString() {
38 | return "OptionItem{" +
39 | "id=" + id +
40 | ", title='" + title + '\'' +
41 | ", isChecked=" + isChecked +
42 | '}';
43 | }
44 |
45 | @Override
46 | public boolean equals(Object o) {
47 | if (this == o) {
48 | return true;
49 | }
50 | if (!(o instanceof OptionItem)) {
51 | return false;
52 | }
53 | OptionItem that = (OptionItem) o;
54 | return Objects.equals(id, that.id) && Objects.equals(title, that.title);
55 | }
56 |
57 | @Override
58 | public int hashCode() {
59 | return Objects.hash(id, title);
60 | }
61 |
62 | public Integer getId() {
63 | return id;
64 | }
65 |
66 | public void setId(Integer id) {
67 | this.id = id;
68 | }
69 |
70 | public String getTitle() {
71 | return title;
72 | }
73 |
74 | public void setTitle(String title) {
75 | this.title = title;
76 | }
77 |
78 | @Nullable
79 | public Drawable getIcon() {
80 | return icon;
81 | }
82 |
83 | public void setIcon(@Nullable Drawable icon) {
84 | this.icon = icon;
85 | }
86 |
87 | public boolean isChecked() {
88 | return isChecked;
89 | }
90 |
91 | public void setChecked(boolean checked) {
92 | isChecked = checked;
93 | }
94 |
95 | @Override
96 | public int describeContents() {
97 | return 0;
98 | }
99 |
100 | @Override
101 | public void writeToParcel(Parcel dest, int flags) {
102 | dest.writeValue(this.id);
103 | dest.writeString(this.title);
104 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
105 | dest.writeBoolean(this.isChecked);
106 | }
107 | }
108 |
109 | protected OptionItem(Parcel in) {
110 | this.id = (Integer) in.readValue(Integer.class.getClassLoader());
111 | this.title = in.readString();
112 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
113 | this.isChecked = in.readBoolean();
114 | }
115 | }
116 |
117 | public static final Creator CREATOR = new Creator() {
118 | @Override
119 | public OptionItem createFromParcel(Parcel source) {
120 | return new OptionItem(source);
121 | }
122 |
123 | @Override
124 | public OptionItem[] newArray(int size) {
125 | return new OptionItem[size];
126 | }
127 | };
128 |
129 | }
--------------------------------------------------------------------------------
/widget_option_list/src/main/java/ando/widget/option/list/OptionView.kt:
--------------------------------------------------------------------------------
1 | package ando.widget.option.list
2 |
3 | import android.content.Context
4 | import android.util.AttributeSet
5 | import android.view.LayoutInflater
6 | import android.view.View
7 | import android.view.ViewGroup
8 | import android.widget.ImageView
9 | import android.widget.TextView
10 | import androidx.recyclerview.widget.GridLayoutManager
11 | import androidx.recyclerview.widget.LinearLayoutManager
12 | import androidx.recyclerview.widget.RecyclerView
13 | import kotlin.math.abs
14 |
15 | /**
16 | * ### 列表支持单选或者多选功能, 由`RecyclerView`实现
17 | *
18 | * @author javakam
19 | * @date 2021-05-13 9:38
20 | */
21 | class OptionView @JvmOverloads constructor(
22 | context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
23 | ) : RecyclerView(context, attrs, defStyleAttr) {
24 |
25 | companion object {
26 | val LAYOUT_TITLE: Int by lazy { R.layout.option_item_layout_header }
27 | val LAYOUT_ITEM_VERTICAL: Int by lazy { R.layout.option_item_layout_vertical }
28 | val LAYOUT_ITEM_HORIZONTAL: Int by lazy { R.layout.option_item_layout_horizontal }
29 |
30 | const val VIEW_TYPE_TITLE = 0
31 | const val VIEW_TYPE_ITEM = 1
32 | }
33 |
34 | private lateinit var mConfig: OptConfig
35 | private lateinit var mAdapter: CheckAdapter
36 | private var isCheckShow: Boolean = false
37 | private var onItemViewCallBack: OnItemViewCallBack? = null
38 | private var onItemClickListener: OnItemClickListener? = null
39 |
40 | override fun onDetachedFromWindow() {
41 | onItemViewCallBack = null
42 | onItemClickListener = null
43 | super.onDetachedFromWindow()
44 | }
45 |
46 | fun obtain(
47 | isItemVertical: Boolean,
48 | setting: OptSetting,
49 | data: List? = null,
50 | onItemViewCallBack: OnItemViewCallBack?,
51 | onItemClickListener: OnItemClickListener?,
52 | ): OptionView {
53 | if (isItemVertical) {
54 | obtain(
55 | OptConfig(null, LAYOUT_TITLE, LAYOUT_ITEM_VERTICAL, 1, setting),
56 | data, onItemViewCallBack, onItemClickListener
57 | )
58 | } else {
59 | obtain(
60 | OptConfig(null, LAYOUT_TITLE, LAYOUT_ITEM_HORIZONTAL, 1, setting),
61 | data, onItemViewCallBack, onItemClickListener
62 | )
63 | }
64 | return this
65 | }
66 |
67 | fun obtain(
68 | config: OptConfig?, data: List? = null,
69 | onItemViewCallBack: OnItemViewCallBack?, onItemClickListener: OnItemClickListener?
70 | ): OptionView {
71 | this.mConfig = config ?: OptConfig(
72 | null, LAYOUT_TITLE, LAYOUT_ITEM_HORIZONTAL, 1,
73 | OptSetting(MODE_CHECK_NONE, isItemViewHorizontal = true, isCheckTriggerByItemView = false)
74 | )
75 | this.onItemViewCallBack = onItemViewCallBack
76 | this.onItemClickListener = onItemClickListener
77 | this.isCheckShow = (mConfig.setting.isCheckShow())
78 |
79 | val columns = mConfig.columns
80 | layoutManager = if (columns > 1) {
81 | GridLayoutManager(context, columns).apply {
82 | spanSizeLookup = object : GridLayoutManager.SpanSizeLookup() {
83 | override fun getSpanSize(position: Int): Int {
84 | return if (mConfig.title != null && position == 0) columns else 1
85 | }
86 | }
87 | }
88 | } else LinearLayoutManager(context)
89 |
90 | if (!this::mAdapter.isInitialized) {
91 | this.mAdapter = CheckAdapter()
92 |
93 | this.mAdapter.selectMode = if (mConfig.setting.isCheckSingle()) EasyAdapter.SelectMode.SINGLE_SELECT
94 | else EasyAdapter.SelectMode.MULTI_SELECT
95 |
96 | this.mAdapter.setOnItemSingleSelectListener { itemPosition, _ ->
97 | if (itemPosition >= 0) {
98 | this.onItemClickListener?.onItemSelected(mAdapter.getItems()[itemPosition])
99 | }
100 | }
101 | }
102 | //没有数据时候, 会有 Adapter.Title
103 | if (data.isNullOrEmpty()) visibility = View.GONE else {
104 | visibility = View.VISIBLE
105 | setData(data)
106 | }
107 | adapter = mAdapter
108 | return this
109 | }
110 |
111 | fun setData(data: List?): OptionView {
112 | if (data.isNullOrEmpty()) return this
113 | //data.forEach { Log.e("123", "${it.title} ${it.isChecked}") }
114 | if (mConfig.setting.isCheckSingle()) {
115 | val index = data.indexOfFirst { it.isChecked }
116 | if (index != -1) {
117 | data.forEachIndexed { i, it -> if (index != i) it.isChecked = false }
118 | }
119 | }
120 | mAdapter.applyConfig(data = data)
121 | visibility = if (mAdapter.getItems().isEmpty()) View.GONE else View.VISIBLE
122 | mAdapter.notifyDataSetChanged()
123 | return this
124 | }
125 |
126 | fun getData(): List {
127 | if (this::mAdapter.isInitialized) {
128 | return mAdapter.getItems()
129 | }
130 | return emptyList()
131 | }
132 |
133 | private inner class CheckAdapter : EasyAdapter() {
134 | private var title: String? = null
135 | private val items = ArrayList()
136 | fun applyConfig(data: List?) {
137 | this.title = mConfig.title
138 | if (data.isNullOrEmpty()) return
139 | this.items.clear()
140 | this.items.addAll(data)
141 | //设置预选位置
142 | if (items.isNotEmpty()) {
143 | if (mConfig.setting.isCheckSingle()) {//单选
144 | val preSelectIndex: Int = items.indexOfFirst { it.isChecked }
145 | if (preSelectIndex >= 0) {
146 | items[preSelectIndex].isChecked = true
147 | }
148 | setSelected(preSelectIndex)
149 | } else {//多选
150 | var preSelectIndexList = intArrayOf()
151 | items.forEachIndexed { i, item ->
152 | if (item.isChecked) preSelectIndexList = preSelectIndexList.plus(i)
153 | }
154 | if (preSelectIndexList.isNotEmpty()) setSelected(*preSelectIndexList)
155 | }
156 | }
157 | }
158 |
159 | fun getItems(): List = items
160 |
161 | override fun getItemCount(): Int {
162 | return if (title == null) items.size else items.size + 1
163 | }
164 |
165 | override fun getItemViewType(position: Int): Int {
166 | if (title != null) {
167 | if (position == 0) return VIEW_TYPE_TITLE
168 | }
169 | return VIEW_TYPE_ITEM
170 | }
171 |
172 | private fun getRealPosition(holder: ViewHolder): Int {
173 | return if (title != null) holder.adapterPosition - 1 else holder.adapterPosition
174 | }
175 |
176 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
177 | if (viewType == VIEW_TYPE_TITLE) {
178 | val header = LayoutInflater.from(parent.context).inflate(mConfig.titleLayoutResource, parent, false)
179 | onItemViewCallBack?.onHeaderCreated(header)
180 | return TitleViewHolder(header)
181 | }
182 |
183 | if (viewType == VIEW_TYPE_ITEM) {
184 | val view = LayoutInflater.from(parent.context).inflate(mConfig.itemLayoutResource, parent, false)
185 | onItemViewCallBack?.onItemCreated(view)
186 | val holder = ItemViewHolder(view)
187 | val isHorizontal = (mConfig.setting.isItemViewHorizontal)
188 | if (isCheckShow && isHorizontal) {
189 | holder.setIsHorizontal(isHorizontal)
190 | holder.setShowCheckBox(isCheckShow)
191 | }
192 | return holder
193 | }
194 | throw IllegalStateException("Can't recognize this type")
195 | }
196 |
197 | override fun onBindViewHolder(holder: ViewHolder, position: Int) {
198 | /////////////////////////// EasyAdapter.onBindViewHolder 是没有 Title, 需要调整下 position ///////////////////////////
199 | //super.onBindViewHolder(holder, position)
200 | val correctPosition = if (title == null) position else position - 1
201 | holder.itemView.tag = correctPosition
202 | holder.itemView.setOnClickListener(this)
203 | when (selectMode) {
204 | SelectMode.CLICK -> { //点击
205 | holder.itemView.isSelected = false
206 | }
207 |
208 | SelectMode.SINGLE_SELECT -> { //单选
209 | holder.itemView.isSelected = (singleSelected == correctPosition)
210 | }
211 |
212 | SelectMode.MULTI_SELECT -> { //多选
213 | holder.itemView.isSelected = multiSelectedPosition.contains(correctPosition)
214 | }
215 |
216 | else -> {}
217 | }
218 | /////////////////////////// END ///////////////////////////
219 |
220 | if (holder is ItemViewHolder) {
221 | holder.bind(items[correctPosition])
222 |
223 | val isSelected = isSelected(correctPosition)
224 | holder.ivCheckBox?.isSelected = isSelected
225 | items[correctPosition].isChecked = isSelected //修改 Bean 的 Check 状态
226 | //Log.w("123", "vh = $correctPosition -> $isSelected -> haveTitle=${(title == null)}")
227 | } else if (holder is TitleViewHolder) {
228 | holder.bind(title)
229 | }
230 | }
231 |
232 | override fun whenBindViewHolder(holder: ViewHolder?, position: Int) {
233 | }
234 | }
235 |
236 | internal class ItemViewHolder(itemView: View) : ViewHolder(itemView) {
237 | var isHorizontal: Boolean = false
238 | var isCheckShow: Boolean = false
239 | var tvTitle: TextView? = null
240 | var ivIcon: ImageView? = null
241 | var ivCheckBox: ImageView? = null
242 |
243 | fun setIsHorizontal(isHorizontal: Boolean) {
244 | this.isHorizontal = isHorizontal
245 | }
246 |
247 | fun setShowCheckBox(isCheckShow: Boolean) {
248 | this.isCheckShow = isCheckShow
249 | }
250 |
251 | fun bind(item: OptionItem) {
252 | if (item.title.isNullOrBlank()) {
253 | tvTitle?.visibility = View.GONE
254 | } else {
255 | tvTitle?.visibility = View.VISIBLE
256 | tvTitle?.text = item.title
257 | }
258 |
259 | if (item.icon == null) {
260 | ivIcon?.visibility = View.GONE
261 | } else {
262 | ivIcon?.visibility = View.VISIBLE
263 | ivIcon?.setImageDrawable(item.icon)
264 | }
265 |
266 | if (isCheckShow && isHorizontal) {
267 | if (ivCheckBox?.visibility == View.INVISIBLE) ivCheckBox?.visibility = View.VISIBLE
268 | ivCheckBox?.isSelected = item.isChecked
269 | } else ivCheckBox?.visibility = View.INVISIBLE
270 | }
271 |
272 | init {
273 | tvTitle = itemView.findViewById(R.id.id_ando_bottom_sheet_item_title)
274 | ivIcon = itemView.findViewById(R.id.id_ando_bottom_sheet_item_icon)
275 | ivCheckBox = itemView.findViewById(R.id.id_ando_bottom_sheet_item_check_box)
276 | check(!(tvTitle == null && ivIcon == null)) {
277 | "At least define a TextView with id 'id_ando_bottom_sheet_item_title' or an ImageView with id 'id_ando_bottom_sheet_item_icon' in the item resource"
278 | }
279 | }
280 | }
281 |
282 | internal class TitleViewHolder(itemView: View) : ViewHolder(itemView) {
283 | var text: TextView? = null
284 | fun bind(header: String?) {
285 | text?.text = header
286 | }
287 |
288 | init {
289 | text = itemView.findViewById(R.id.id_ando_bottom_sheet_item_head_title)
290 | checkNotNull(text) { "TextView in the Alternative header resource must have the id 'header'" }
291 | }
292 | }
293 |
294 | private abstract class NoShakeListener(private val duration: Long = 500) : OnClickListener {
295 | private var lastClickTime: Long = 0
296 |
297 | private val isFastDoubleClick: Boolean
298 | get() {
299 | val nowTime = System.currentTimeMillis()
300 | return if (abs(nowTime - lastClickTime) < duration) {
301 | true // 快速点击事件
302 | } else {
303 | lastClickTime = nowTime
304 | false // 单次点击事件
305 | }
306 | }
307 |
308 | override fun onClick(v: View) {
309 | if (!isFastDoubleClick) onSingleClick(v)
310 | }
311 |
312 | protected abstract fun onSingleClick(v: View)
313 | }
314 |
315 | interface OnItemViewCallBack {
316 | fun onHeaderCreated(v: View) {}
317 | fun onItemCreated(v: View) {}
318 | }
319 |
320 | interface OnItemClickListener {
321 | fun onItemSelected(item: OptionItem) {}
322 | }
323 |
324 | }
--------------------------------------------------------------------------------
/widget_option_list/src/main/res/drawable/ic_ando_option_check_checked.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
11 |
--------------------------------------------------------------------------------
/widget_option_list/src/main/res/drawable/ic_ando_option_check_normal.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
10 |
--------------------------------------------------------------------------------
/widget_option_list/src/main/res/drawable/sel_ando_option_checkbox.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/widget_option_list/src/main/res/layout/option_item_layout_header.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/widget_option_list/src/main/res/layout/option_item_layout_horizontal.xml:
--------------------------------------------------------------------------------
1 |
2 |
15 |
16 |
26 |
27 |
35 |
36 |
40 |
41 |
51 |
52 |
--------------------------------------------------------------------------------
/widget_option_list/src/main/res/layout/option_item_layout_vertical.xml:
--------------------------------------------------------------------------------
1 |
2 |
12 |
13 |
23 |
24 |
32 |
33 |
--------------------------------------------------------------------------------
/widget_option_list/src/main/res/values/values_option_view.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | #666666
9 | #2E2E2E
10 |
11 | #3368AE
12 | #DDDDDD
13 |
14 | 12dp
15 | 16sp
16 | 32dp
17 | 12dp
18 | 15dp
19 | 15dp
20 | 15sp
21 | 22dp
22 |
23 |
--------------------------------------------------------------------------------