├── .gitignore
├── LICENSE
├── README.md
├── app
├── .gitignore
├── build.gradle
├── proguard-rules.pro
└── src
│ ├── androidTest
│ └── java
│ │ └── me
│ │ └── shouheng
│ │ └── xdialogsample
│ │ └── ExampleInstrumentedTest.java
│ ├── main
│ ├── AndroidManifest.xml
│ ├── assets
│ │ ├── lyrics
│ │ └── province.json
│ ├── java
│ │ └── me
│ │ │ └── shouheng
│ │ │ └── xdialogsample
│ │ │ ├── APP.kt
│ │ │ ├── MainActivity.kt
│ │ │ ├── components
│ │ │ ├── AddressContent.kt
│ │ │ ├── DialogFooterEtx.kt
│ │ │ ├── DialogTitleEtx.kt
│ │ │ ├── MultipleContent.kt
│ │ │ ├── SampleFooter.kt
│ │ │ └── UpgradeContent.kt
│ │ │ ├── data
│ │ │ ├── AddressBean.kt
│ │ │ └── AddressSelectLevel.kt
│ │ │ └── utils
│ │ │ ├── AddressUtils.kt
│ │ │ ├── OnItemDebouncedClick.kt
│ │ │ ├── Utils.kt
│ │ │ └── UtilsEtx.kt
│ └── res
│ │ ├── drawable-v24
│ │ └── ic_launcher_foreground.xml
│ │ ├── drawable
│ │ ├── ic_android_black_24dp.xml
│ │ ├── ic_baseline_account_balance_24.xml
│ │ ├── ic_baseline_cancel_24.xml
│ │ └── ic_launcher_background.xml
│ │ ├── layout
│ │ ├── activity_main.xml
│ │ ├── item_tool_color.xml
│ │ ├── layout_dialog_bottom_sample.xml
│ │ ├── layout_dialog_content_multiple.xml
│ │ ├── layout_dialog_content_upgrade.xml
│ │ ├── layout_dialog_title_sample.xml
│ │ └── uix_dialog_content_address.xml
│ │ ├── mipmap-anydpi-v26
│ │ ├── ic_launcher.xml
│ │ └── ic_launcher_round.xml
│ │ ├── mipmap-hdpi
│ │ ├── ic_launcher.webp
│ │ └── ic_launcher_round.webp
│ │ ├── mipmap-mdpi
│ │ ├── ic_launcher.webp
│ │ └── ic_launcher_round.webp
│ │ ├── mipmap-xhdpi
│ │ ├── ic_launcher.webp
│ │ ├── ic_launcher_round.webp
│ │ └── update_app_top_bg.png
│ │ ├── mipmap-xxhdpi
│ │ ├── ic_launcher.webp
│ │ └── ic_launcher_round.webp
│ │ ├── mipmap-xxxhdpi
│ │ ├── ic_launcher.webp
│ │ └── ic_launcher_round.webp
│ │ ├── values-night
│ │ └── themes.xml
│ │ └── values
│ │ ├── colors.xml
│ │ ├── strings.xml
│ │ └── themes.xml
│ └── test
│ └── java
│ └── me
│ └── shouheng
│ └── xdialogsample
│ └── ExampleUnitTest.java
├── build.gradle
├── gradle.properties
├── gradlew
├── gradlew.bat
├── resources
├── Screenshot_2022-02-24-23-16-15-05_08de23611bf79886a74beffca0ad6bab.jpg
├── Screenshot_2022-02-24-23-16-19-28_08de23611bf79886a74beffca0ad6bab.jpg
├── Screenshot_2022-02-24-23-16-21-50_08de23611bf79886a74beffca0ad6bab.jpg
├── Screenshot_2022-02-24-23-16-23-42_08de23611bf79886a74beffca0ad6bab.jpg
├── Screenshot_2022-02-24-23-16-25-65_08de23611bf79886a74beffca0ad6bab.jpg
├── Screenshot_2022-02-24-23-16-29-23_08de23611bf79886a74beffca0ad6bab.jpg
├── Screenshot_2022-02-24-23-16-32-17_08de23611bf79886a74beffca0ad6bab.jpg
├── Screenshot_2022-02-24-23-16-37-24_08de23611bf79886a74beffca0ad6bab.jpg
├── Screenshot_2022-02-24-23-16-40-25_08de23611bf79886a74beffca0ad6bab.jpg
├── Screenshot_2022-02-24-23-16-45-48_08de23611bf79886a74beffca0ad6bab.jpg
├── Screenshot_2022-02-24-23-16-56-55_08de23611bf79886a74beffca0ad6bab.jpg
├── animation.gif
└── animation0.gif
├── settings.gradle
└── xdialog
├── .gitignore
├── build.gradle
├── proguard-rules.pro
└── src
└── main
├── AndroidManifest.xml
├── java
└── me
│ └── shouheng
│ └── uix
│ └── widget
│ ├── anno
│ ├── BeautyDialogDSL.kt
│ ├── BottomButtonPosition.kt
│ ├── BottomButtonStyle.kt
│ ├── DialogPosition.kt
│ ├── DialogStyle.kt
│ ├── EmptyViewState.kt
│ ├── LoadingButtonState.kt
│ └── LoadingStyle.kt
│ ├── bean
│ └── TextStyleBean.kt
│ ├── dialog
│ ├── BeautyDialog.kt
│ ├── BlurEngine.kt
│ ├── MessageDialog.kt
│ ├── content
│ │ ├── CustomList.kt
│ │ ├── IDialogContent.kt
│ │ ├── SimpleContent.kt
│ │ ├── SimpleEditor.kt
│ │ ├── SimpleGrid.kt
│ │ ├── SimpleList.kt
│ │ └── ViewBindingDialogContent.kt
│ ├── footer
│ │ ├── IDialogFooter.kt
│ │ ├── SimpleFooter.kt
│ │ └── ViewBindingDialogFooter.kt
│ └── title
│ │ ├── IDialogTitle.kt
│ │ ├── SimpleTitle.kt
│ │ └── ViewBindingDialogTitle.kt
│ ├── rv
│ ├── EmptySupportRecyclerView.kt
│ ├── EmptyView.kt
│ └── IEmptyView.kt
│ ├── text
│ ├── ClearEditText.kt
│ ├── NormalTextView.kt
│ └── RegexEditText.kt
│ ├── utils
│ ├── DialogEtx.kt
│ └── DialogUtils.java
│ └── xDialog.kt
└── res
├── anim
├── uix_dialog_alpha_enter.xml
├── uix_dialog_alpha_exit.xml
├── uix_dialog_translate_alpha_enter.xml
├── uix_dialog_translate_alpha_exit.xml
├── uix_dialog_translate_enter.xml
├── uix_dialog_translate_enter_top.xml
├── uix_dialog_translate_exit.xml
├── uix_dialog_translate_exit_top.xml
└── uix_loading.xml
├── drawable
├── ic_launcher_background.xml
├── uix_close_black_24dp.xml
└── uix_loading.png
├── layout
├── uix_dialog_content_address_item.xml
├── uix_dialog_content_edit_simple.xml
├── uix_dialog_content_list_custom.xml
├── uix_dialog_content_list_simple.xml
├── uix_dialog_content_list_simple_item.xml
├── uix_dialog_content_simple.xml
├── uix_dialog_footer_simple.xml
├── uix_dialog_layout.xml
├── uix_dialog_title_simple.xml
├── uix_layout_empty_view.xml
└── uix_message_dialog.xml
└── values
├── attrs.xml
├── bools.xml
├── colors.xml
├── configs.xml
├── strings.xml
├── styles.xml
└── themes.xml
/.gitignore:
--------------------------------------------------------------------------------
1 | # Built application files
2 | *.apk
3 | *.aar
4 | *.ap_
5 | *.aab
6 |
7 | # Files for the ART/Dalvik VM
8 | *.dex
9 |
10 | # Java class files
11 | *.class
12 |
13 | # Generated files
14 | bin/
15 | gen/
16 | out/
17 | # Uncomment the following line in case you need and you don't have the release build type files in your app
18 | # release/
19 |
20 | # Gradle files
21 | .gradle/
22 | build/
23 |
24 | # Local configuration file (sdk path, etc)
25 | local.properties
26 |
27 | # Proguard folder generated by Eclipse
28 | proguard/
29 |
30 | # Log Files
31 | *.log
32 |
33 | # Android Studio Navigation editor temp files
34 | .navigation/
35 |
36 | # Android Studio captures folder
37 | captures/
38 |
39 | # IntelliJ
40 | *.iml
41 | .idea/workspace.xml
42 | .idea/tasks.xml
43 | .idea/gradle.xml
44 | .idea/assetWizardSettings.xml
45 | .idea/dictionaries
46 | .idea/libraries
47 | # Android Studio 3 in .gitignore file.
48 | .idea/caches
49 | .idea/modules.xml
50 | # Comment next line if keeping position of elements in Navigation Editor is relevant for you
51 | .idea/navEditor.xml
52 |
53 | # Keystore files
54 | # Uncomment the following lines if you do not want to check your keystore files in.
55 | #*.jks
56 | #*.keystore
57 |
58 | # External native build folder generated in Android Studio 2.2 and later
59 | .externalNativeBuild
60 | .cxx/
61 |
62 | # Google Services (e.g. APIs or Firebase)
63 | # google-services.json
64 |
65 | # Freeline
66 | freeline.py
67 | freeline/
68 | freeline_project_description.json
69 |
70 | # fastlane
71 | fastlane/report.xml
72 | fastlane/Preview.html
73 | fastlane/screenshots
74 | fastlane/test_output
75 | fastlane/readme.md
76 |
77 | # Version control
78 | vcs.xml
79 |
80 | # lint
81 | lint/intermediates/
82 | lint/generated/
83 | lint/outputs/
84 | lint/tmp/
85 | # lint/reports/
86 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "[]"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright [yyyy] [name of copyright owner]
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ```
2 | ================================================================================================
3 | 88888888ba, 88 88
4 | 88 `"8b "" 88
5 | 88 `8b 88
6 | 8b, ,d8 88 88 88 ,adPPYYba, 88 ,adPPYba, ,adPPYb,d8
7 | `Y8, ,8P' 88 88 88 "" `Y8 88 a8" "8a a8" `Y88
8 | )888( 88 8P 88 ,adPPPPP88 88 8b d8 8b 88
9 | ,d8" "8b, 88 .a8P 88 88, ,88 88 "8a, ,a8" "8a, ,d88
10 | 8P' `Y8 88888888Y"' 88 `"8bbdP"Y8 88 `"YbbdP"' `"YbbdP"Y8
11 | aa, ,88
12 | "Y8bbdP"
13 | ================================================================================================
14 | ```
15 |
16 | # 优雅的 Android 对话框类库封装 xDialog
17 |
18 | 对 Android 开发而言,对话框是打交道比较频繁的 UI 控件之一。对话框对大部分程序员而言并不陌生。然而,当考虑到复杂的交互效果、组件复用、自定义和各种 UI 风格的时候,事情变得麻烦了起来。xDialog 就是我设计的用来整合以上多种情况的 UI 组件。相比于大部分开源库,它可自定义程度更高,能满足更多的应用场景。
19 |
20 |
21 |

22 |

23 |

24 |

25 |

26 |
27 |
28 | ## 1、整体设计
29 |
30 | xDialog 基于 Kotlin 开发,也吸取了 KotlinDSL 的特性,提供了更加方便使用的 API. 该对话框基于 DialogFragment 开发,在复用性方面,分别将对话框的头部、内容和底部进行了抽象,提取了三个接口,所以,用户只需要实现这三个接口就可以拼凑出自己的对话框,真正实现了三个部分的随意组装,
31 |
32 | ```kotlin
33 | class BeautyDialog : DialogFragment() {
34 |
35 | private var iDialogTitle: IDialogTitle? = null
36 | private var iDialogContent: IDialogContent? = null
37 | private var iDialogFooter: IDialogFooter? = null
38 |
39 | // ...
40 |
41 | override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
42 | // ...
43 | }
44 |
45 | @BeautyDialogDSL class Builder {
46 | private var dialogTitle: IDialogTitle? = null
47 | private var dialogContent: IDialogContent? = null
48 | private var dialogFooter: IDialogFooter? = null
49 |
50 | // ...
51 |
52 | /** 为对话框指定头部 */
53 | fun withTitle(iDialogTitle: IDialogTitle) {
54 | this.dialogTitle = iDialogTitle
55 | }
56 |
57 | /** 为对话框指定内容 */
58 | fun withContent(iDialogContent: IDialogContent) {
59 | this.dialogContent = iDialogContent
60 | }
61 |
62 | /** 为对话框指定底部 */
63 | fun withBottom(iDialogFooter: IDialogFooter) {
64 | this.dialogFooter = iDialogFooter
65 | }
66 |
67 | fun build(): BeautyDialog {
68 | val dialog = BeautyDialog()
69 | // ...
70 | return dialog
71 | }
72 | }
73 | }
74 |
75 | /** 提供了基于 kotlin DSL 风格的对话框构建器 */
76 | inline fun createDialog(
77 | init: BeautyDialog.Builder.() -> Unit
78 | ): BeautyDialog = BeautyDialog.Builder().apply(init).build()
79 | ```
80 |
81 | 如上代码所示,用户只需要将通过实现三个接口实现自己的各个部分,然后就可以做到指定的位置的 UI 的替换。
82 |
83 |
84 |

85 |
86 |
87 | ## 2、兼容平板、手机和横屏
88 |
89 | 基于上述设计思路,我为构建者增加了几个参数用来满足在不同分辨率上使用的场景的需求。该库内置了四种不同大小的对话框,对于用户,只需要判断上述不同的情景之后根据情况传入指定的样式即可。然后在对话框内部会使用不同的 style 作为对话框的主题,
90 |
91 | ```kotlin
92 | val theme = when(dialogStyle) {
93 | STYLE_WRAP -> R.style.BeautyDialogWrap
94 | STYLE_HALF -> R.style.BeautyDialogHalf
95 | STYLE_TWO_THIRD -> R.style.BeautyDialogTwoThird
96 | else -> R.style.BeautyDialog
97 | }
98 | val dialog = if (bottomSheet) {
99 | BottomSheetDialog(requireContext(), theme)
100 | } else {
101 | AlertDialog.Builder(requireContext(), theme).create()
102 | }
103 | ```
104 |
105 | ## 3、兼容 BottomSheet 和普通的对话框的场景
106 |
107 | 谷歌提供的 Material 库中提供了叫做 BottomSheetDialog 的对话框。相比于普通的对话框,这个类可以提供更好的交互效果。比如,比如自己的地图中就有类似的交互效果。底部弹出一部分,然后向上可以继续拖拽并展示全部内容。这种交互方式在许多情景下会非常有用,特别是,当我们需要提供的内容可能在普通的对话框装不下的情况而又不希望切换一个新的页面的时候。较少的切换页面,降低操作的层级,可以提供更好的用户交互体验。
108 |
109 |
110 |

111 |
112 |
113 | 在 xDialog 中,我增加了几个参数来支持这种应用场景,
114 |
115 | ```kotlin
116 | val dialog = if (bottomSheet) {
117 | BottomSheetDialog(requireContext(), theme).apply {
118 | halfExpandedRatio?.let { this.behavior.halfExpandedRatio = it }
119 | isFitToContents ?.let { this.behavior.isFitToContents = it }
120 | peekHeight ?.let { this.behavior.peekHeight = it }
121 | }
122 | } else {
123 | AlertDialog.Builder(requireContext(), theme).create()
124 | }
125 | ```
126 |
127 | 所以,对于使用这个库的用户而言,只需要动态调整几个参数就可以随意在两种不同的交互方式之间切换。
128 |
129 | ## 4、提供对话框背景模糊效果
130 |
131 | xDialog 为背景模糊效果提供了封装。为此 xDialog 提供了 BlurEngine 用来在 Dialog 显示的时候截取屏幕进行模糊并展示到对话框背部。用户启用的方式也非常简单,只需要传入一个参数就可以直接使用背景模糊的效果。
132 |
133 |
134 |

135 |
136 |
137 | ## 5、提供了默认的封装和多种实用 API
138 |
139 | 除了上述封装,xDialog 提供了几个默认的头部、内容和底部实现类。用户可以直接使用,也可以参照他们实现自己想要的效果,
140 |
141 | 此外,xDialog 还提供了其他的 API,比如自定义对话框显示的位置是头部、中间还是底部,自定义对话框是否可以撤销,自定义对话框的背景,监听对话框的的显示和隐藏事件等等。
142 |
143 |
144 |

145 |

146 |

147 |

148 |

149 |

150 |
151 |
152 | ## 其他
153 |
154 | 上述是对该对话框封装的分析。对话框相关的开源库还是挺多的,这里我只是觉得这个设计可以拿出来分享一下。如果你需要做相关的工作的话,可以参考一下。最后源代码地址,
155 |
156 | [源码地址:https://github.com/Shouheng88/xDialog](https://github.com/Shouheng88/xDialog)
157 |
--------------------------------------------------------------------------------
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 | apply plugin: 'kotlin-android'
3 |
4 | android {
5 | compileSdk 30
6 | defaultConfig {
7 | applicationId "me.shouheng.myapplication"
8 | minSdk 21
9 | targetSdk 30
10 | versionCode 1
11 | versionName "1.0"
12 | testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'
13 | }
14 | buildTypes {
15 | release {
16 | minifyEnabled false
17 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
18 | }
19 | }
20 | compileOptions {
21 | sourceCompatibility JavaVersion.VERSION_1_8
22 | targetCompatibility JavaVersion.VERSION_1_8
23 | }
24 | lintOptions {
25 | checkReleaseBuilds false
26 | abortOnError false
27 | }
28 | viewBinding {
29 | enabled = true
30 | }
31 | }
32 |
33 | dependencies {
34 | implementation 'androidx.appcompat:appcompat:1.3.1'
35 | implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
36 | testImplementation 'junit:junit:4.+'
37 | androidTestImplementation 'androidx.test.ext:junit:1.1.2'
38 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
39 | implementation 'androidx.multidex:multidex:2.0.1'
40 | implementation 'androidx.appcompat:appcompat:1.3.1'
41 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
42 | implementation 'com.google.android.material:material:1.4.0'
43 | implementation ("com.github.Shouheng88:vmlib:2.1") {
44 | exclude group: 'com.github.Shouheng88', module: "uix-core"
45 | exclude group: 'com.github.Shouheng88', module: "uix-widget"
46 | }
47 | implementation 'com.github.CymChad:BaseRecyclerViewAdapterHelper:2.9.49-androidx'
48 | implementation "com.github.Shouheng88:utils-ktx:2.6.0"
49 | implementation "com.github.Shouheng88:xadapter:1.1"
50 | implementation project(':xdialog')
51 | }
52 |
--------------------------------------------------------------------------------
/app/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
--------------------------------------------------------------------------------
/app/src/androidTest/java/me/shouheng/xdialogsample/ExampleInstrumentedTest.java:
--------------------------------------------------------------------------------
1 | package me.shouheng.xdialogsample;
2 |
3 | import android.content.Context;
4 | import android.support.test.InstrumentationRegistry;
5 | import android.support.test.runner.AndroidJUnit4;
6 |
7 | import org.junit.Test;
8 | import org.junit.runner.RunWith;
9 |
10 | import static org.junit.Assert.*;
11 |
12 | /**
13 | * Instrumented test, which will execute on an Android device.
14 | *
15 | * @see Testing documentation
16 | */
17 | @RunWith(AndroidJUnit4.class)
18 | public class ExampleInstrumentedTest {
19 | @Test
20 | public void useAppContext() {
21 | // Context of the app under test.
22 | Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
23 | assertEquals("me.shouheng.myapplication", appContext.getPackageName());
24 | }
25 | }
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
13 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/app/src/main/java/me/shouheng/xdialogsample/APP.kt:
--------------------------------------------------------------------------------
1 | package me.shouheng.xdialogsample
2 |
3 | import android.app.Application
4 | import me.shouheng.uix.widget.xDialog
5 | import me.shouheng.vmlib.VMLib
6 |
7 | class APP: Application() {
8 |
9 | override fun onCreate() {
10 | super.onCreate()
11 | VMLib.onCreate(this)
12 | xDialog.init(this)
13 | }
14 | }
--------------------------------------------------------------------------------
/app/src/main/java/me/shouheng/xdialogsample/components/AddressContent.kt:
--------------------------------------------------------------------------------
1 | package me.shouheng.xdialogsample.components
2 |
3 | import android.content.Context
4 | import android.view.View
5 | import androidx.annotation.RestrictTo
6 | import me.shouheng.uix.widget.anno.BeautyDialogDSL
7 | import me.shouheng.uix.widget.R
8 | import me.shouheng.uix.widget.dialog.content.ViewBindingDialogContent
9 | import me.shouheng.utils.ktx.gone
10 | import me.shouheng.utils.ktx.onDebouncedClick
11 | import me.shouheng.utils.ktx.visible
12 | import me.shouheng.xadapter.createAdapter
13 | import me.shouheng.xdialogsample.data.AddressBean
14 | import me.shouheng.xdialogsample.data.AddressSelectLevel
15 | import me.shouheng.xdialogsample.data.AddressSelectLevel.Companion.LEVEL_AREA
16 | import me.shouheng.xdialogsample.data.AddressSelectLevel.Companion.LEVEL_CITY
17 | import me.shouheng.xdialogsample.databinding.UixDialogContentAddressBinding
18 | import me.shouheng.xdialogsample.utils.AddressUtils
19 | import me.shouheng.xdialogsample.utils.onItemDebouncedClick
20 |
21 | /**
22 | * Address pick dialog content
23 | *
24 | * @author Shouheng Wang
25 | * @version 2019-10-05 10:44
26 | */
27 | class AddressContent: ViewBindingDialogContent() {
28 |
29 | private var dialog: me.shouheng.uix.widget.dialog.BeautyDialog? = null
30 | private val rvList = mutableListOf()
31 |
32 | @AddressSelectLevel
33 | private var maxLevel: Int = LEVEL_CITY
34 | private var listener: OnAddressSelectedListener? = null
35 |
36 | override fun doCreateView(ctx: Context) {
37 | val list = AddressUtils.getAddressList()
38 | val pAdapter = createAdapter {
39 | withType(AddressBean::class.java, R.layout.uix_dialog_content_address_item) {
40 | onBind { helper, item ->
41 | helper.setText(R.id.tv, item.name)
42 | }
43 | }
44 | }
45 | binding.rvProvince.adapter = pAdapter
46 | pAdapter.setNewData(list)
47 |
48 | val cityAdapter = createAdapter {
49 | withType(AddressBean.CityBean::class.java, R.layout.uix_dialog_content_address_item) {
50 | onBind { helper, item ->
51 | helper.setText(R.id.tv, item.name)
52 | }
53 | }
54 | }
55 | binding.rvCity.adapter = cityAdapter
56 |
57 | val areaAdapter = createAdapter {
58 | withType(String::class.java, R.layout.uix_dialog_content_address_item) {
59 | onBind { helper, item ->
60 | helper.setText(R.id.tv, item)
61 | }
62 | }
63 | }
64 | binding.rvArea.adapter = areaAdapter
65 |
66 | rvList.addAll(listOf(binding.rvProvince, binding.rvCity, binding.rvArea))
67 |
68 | switchToRv(binding.rvProvince)
69 |
70 | pAdapter.onItemDebouncedClick { _, _, pos ->
71 | val province = list[pos].name
72 | val cityBeans = list[pos].city
73 | cityAdapter.setNewData(cityBeans)
74 | binding.tvProvince.text = province
75 | switchToRv(binding.rvCity)
76 | cityAdapter.onItemDebouncedClick { _, _, cityPos ->
77 | val city = cityBeans!![cityPos].name
78 | val areaBeans = cityBeans[cityPos].area
79 | areaAdapter.setNewData(areaBeans)
80 | binding.tvCity.text = city
81 | switchToRv(binding.rvArea)
82 | if (maxLevel == LEVEL_CITY) {
83 | listener?.onSelected(dialog!!, province!!, city, null)
84 | }
85 | areaAdapter.onItemDebouncedClick { _, _, areaPos ->
86 | val area = areaBeans!![areaPos]
87 | binding.tvArea.text = area
88 | if (maxLevel == LEVEL_AREA) {
89 | listener?.onSelected(dialog!!, province!!, city, area)
90 | }
91 | }
92 | }
93 | }
94 |
95 | binding.tvProvince.onDebouncedClick {
96 | binding.tvCity.text = ""
97 | binding.tvArea.text = ""
98 | switchToRv(binding.rvProvince)
99 | }
100 | binding.tvCity.onDebouncedClick {
101 | binding.tvArea.text = ""
102 | switchToRv(binding.rvCity)
103 | }
104 | binding.tvArea.onDebouncedClick {
105 | switchToRv(binding.rvArea)
106 | }
107 | }
108 |
109 | private fun switchToRv(rv: View) {
110 | rv.visible()
111 | rvList.filter { it != rv }.forEach { it.gone() }
112 | }
113 |
114 | override fun setDialog(dialog: me.shouheng.uix.widget.dialog.BeautyDialog) {
115 | this.dialog = dialog
116 | }
117 |
118 | /** Get current selection. */
119 | fun getSelection(): Address {
120 | return Address(
121 | binding.tvProvince.text.toString(),
122 | binding.tvCity.text.toString(),
123 | binding.tvArea.text.toString()
124 | )
125 | }
126 |
127 | internal interface OnAddressSelectedListener {
128 | fun onSelected(dialog: me.shouheng.uix.widget.dialog.BeautyDialog, province: String, city: String?, area: String?)
129 | }
130 |
131 | data class Address(
132 | var province: String,
133 | var city: String?,
134 | var area: String?
135 | )
136 |
137 | @BeautyDialogDSL
138 | class Builder {
139 | private var maxLevel: Int = LEVEL_CITY
140 |
141 | private var onAddressSelectedListener: OnAddressSelectedListener? = null
142 |
143 | /** Specify max select level for dialog. */
144 | fun withLevel(@AddressSelectLevel maxLevel: Int) {
145 | this.maxLevel = maxLevel
146 | }
147 |
148 | /** Set address selected callback. */
149 | fun onSelected(
150 | listener: (dialog: me.shouheng.uix.widget.dialog.BeautyDialog, province: String, city: String?, area: String?) -> Unit
151 | ): Builder {
152 | this.onAddressSelectedListener = object : OnAddressSelectedListener {
153 | override fun onSelected(dialog: me.shouheng.uix.widget.dialog.BeautyDialog, province: String, city: String?, area: String?) {
154 | listener.invoke(dialog, province, city, area)
155 | }
156 | }
157 | return this
158 | }
159 |
160 | @RestrictTo(RestrictTo.Scope.LIBRARY) fun build(): AddressContent {
161 | val content = AddressContent()
162 | content.maxLevel = maxLevel
163 | content.listener = onAddressSelectedListener
164 | return content
165 | }
166 | }
167 | }
168 |
169 | /** Create address content by DSL. */
170 | inline fun addressContent(
171 | init: AddressContent.Builder.() -> Unit
172 | ): AddressContent {
173 | val builder = AddressContent.Builder()
174 | builder.init()
175 | return builder.build()
176 | }
177 |
--------------------------------------------------------------------------------
/app/src/main/java/me/shouheng/xdialogsample/components/DialogFooterEtx.kt:
--------------------------------------------------------------------------------
1 | package me.shouheng.xdialogsample.components
2 |
3 | import android.graphics.Color
4 | import android.graphics.Typeface
5 | import me.shouheng.uix.widget.bean.textStyle
6 | import me.shouheng.uix.widget.dialog.content.SimpleEditor
7 | import me.shouheng.uix.widget.dialog.footer.SimpleFooter
8 | import me.shouheng.uix.widget.dialog.footer.simpleFooter
9 | import me.shouheng.utils.ktx.toast
10 |
11 | /** Single 'Ok' styled footer. */
12 | fun singleOk(): SimpleFooter {
13 | return simpleFooter {
14 | withStyle(me.shouheng.uix.widget.anno.BottomButtonStyle.BUTTON_STYLE_SINGLE)
15 | withMiddle("OK")
16 | withMiddleStyle(textStyle {
17 | withColor(Color.RED)
18 | withTypeFace(Typeface.BOLD)
19 | })
20 | onMiddle { dlg, _, _ ->
21 | dlg.dismiss()
22 | }
23 | }
24 | }
25 |
26 | /** confirm cancel styled footer. */
27 | fun confirmCancel(): SimpleFooter {
28 | return simpleFooter {
29 | withStyle(me.shouheng.uix.widget.anno.BottomButtonStyle.BUTTON_STYLE_DOUBLE)
30 | withLeft("取消")
31 | withRight("确定")
32 | withDivider(Color.LTGRAY)
33 | withLeftStyle(textStyle {
34 | withColor(Color.GRAY)
35 | })
36 | withRightStyle(textStyle {
37 | withColor(Color.RED)
38 | })
39 | onLeft { dlg, _, _ ->
40 | dlg.dismiss()
41 | }
42 | onRight { _, _, content ->
43 | (content as? SimpleEditor)?.let { toast(it.getContent()?:"") }
44 | }
45 | }
46 | }
47 |
48 | /** Dialog footer of 'left-middle-right' */
49 | fun leftMiddleRightFooterWhite(): SimpleFooter {
50 | return simpleFooter {
51 | withStyle(me.shouheng.uix.widget.anno.BottomButtonStyle.BUTTON_STYLE_TRIPLE)
52 | withLeft("Left")
53 | withMiddle("Middle")
54 | withRight("Right")
55 | withRightStyle(textStyle {
56 | withColor(Color.RED)
57 | })
58 | onLeft { _, _, _ ->
59 | toast("Left")
60 | }
61 | onMiddle { _, _, _ ->
62 | toast("Middle")
63 | }
64 | onRight { _, _, _ ->
65 | toast("Right")
66 | }
67 | }
68 | }
69 |
70 | /** Dialog footer of 'left-middle-right' */
71 | fun leftMiddleRightFooter(): SimpleFooter {
72 | return simpleFooter {
73 | withStyle(me.shouheng.uix.widget.anno.BottomButtonStyle.BUTTON_STYLE_TRIPLE)
74 | withLeft("左")
75 | withMiddle("中")
76 | withRight("右")
77 | withLeftStyle(textStyle {
78 | withColor(Color.WHITE)
79 | withSize(14f)
80 | })
81 | withMiddleStyle(textStyle {
82 | withColor(Color.WHITE)
83 | withSize(16f)
84 | })
85 | withRightStyle(textStyle {
86 | withColor(Color.WHITE)
87 | withSize(18f)
88 | })
89 | }
90 | }
91 |
92 |
93 |
--------------------------------------------------------------------------------
/app/src/main/java/me/shouheng/xdialogsample/components/DialogTitleEtx.kt:
--------------------------------------------------------------------------------
1 | package me.shouheng.xdialogsample.components
2 |
3 | import android.graphics.Color
4 | import android.graphics.Typeface
5 | import android.view.Gravity
6 | import me.shouheng.uix.widget.bean.textStyle
7 | import me.shouheng.uix.widget.dialog.title.SimpleTitle
8 | import me.shouheng.uix.widget.dialog.title.simpleTitle
9 |
10 | /** Simple default title. */
11 | fun simpleTitle(title: String): SimpleTitle {
12 | return simpleTitle {
13 | withTitle(title)
14 | }
15 | }
16 |
17 | /** Simple title of white text color. */
18 | fun whiteSimpleTitle(title: String): SimpleTitle {
19 | return simpleTitle {
20 | withTitle(title)
21 | withStyle(textStyle {
22 | withColor(Color.WHITE)
23 | })
24 | }
25 | }
26 |
27 | /** Red title with text size 18f. */
28 | fun red18fTitle(title: String): SimpleTitle {
29 | return simpleTitle {
30 | withTitle(title)
31 | withStyle(textStyle {
32 | withSize(18f)
33 | withColor(Color.RED)
34 | })
35 | }
36 | }
37 |
38 | /** Bold left styled title. */
39 | fun boldLeftTitle(title: String): SimpleTitle {
40 | return simpleTitle {
41 | withTitle(title)
42 | withStyle(textStyle {
43 | withTypeFace(Typeface.BOLD)
44 | withGravity(Gravity.START)
45 | })
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/app/src/main/java/me/shouheng/xdialogsample/components/MultipleContent.kt:
--------------------------------------------------------------------------------
1 | package me.shouheng.xdialogsample.components
2 |
3 | import android.content.Context
4 | import android.view.View
5 | import me.shouheng.uix.widget.dialog.content.IDialogContent
6 | import me.shouheng.xdialogsample.R
7 |
8 | /**
9 | *
10 | *
11 | * @author Shouheng Wang
12 | * @version 2019-10-13 17:51
13 | */
14 | class MultipleContent: IDialogContent {
15 |
16 | override fun getView(ctx: Context): View = View.inflate(
17 | ctx, R.layout.layout_dialog_content_multiple, null
18 | )
19 |
20 | }
--------------------------------------------------------------------------------
/app/src/main/java/me/shouheng/xdialogsample/components/SampleFooter.kt:
--------------------------------------------------------------------------------
1 | package me.shouheng.xdialogsample.components
2 |
3 | import android.content.Context
4 | import android.view.View
5 | import me.shouheng.uix.widget.dialog.footer.IDialogFooter
6 | import me.shouheng.xdialogsample.R
7 |
8 | /**
9 | * @author Shouheng Wang
10 | * @version 2019-10-13 17:51
11 | */
12 | class SampleFooter : IDialogFooter {
13 |
14 | override fun getView(ctx: Context): View = View.inflate(
15 | ctx, R.layout.layout_dialog_bottom_sample, null
16 | )
17 |
18 | }
--------------------------------------------------------------------------------
/app/src/main/java/me/shouheng/xdialogsample/components/UpgradeContent.kt:
--------------------------------------------------------------------------------
1 | package me.shouheng.xdialogsample.components
2 |
3 | import android.content.Context
4 | import android.view.View
5 | import me.shouheng.uix.widget.dialog.content.IDialogContent
6 | import me.shouheng.xdialogsample.R
7 |
8 | /**
9 | * @author Shouheng Wang
10 | * @version 2019-10-13 17:51
11 | */
12 | class UpgradeContent : IDialogContent {
13 |
14 | override fun getView(ctx: Context): View = View.inflate(
15 | ctx, R.layout.layout_dialog_content_upgrade, null
16 | )
17 |
18 | }
--------------------------------------------------------------------------------
/app/src/main/java/me/shouheng/xdialogsample/data/AddressBean.kt:
--------------------------------------------------------------------------------
1 | package me.shouheng.xdialogsample.data
2 |
3 | /**
4 | * Address bean
5 | *
6 | * @author [Shouheng Wang](mailto:shouheng2020@gmail.com)
7 | * @version 2019-10-05 11:09
8 | */
9 | class AddressBean {
10 |
11 | /**
12 | * name : 北京市
13 | * city : [{"name":"北京市","area":["东城区","西城区","海淀区","朝阳区","丰台区","石景山区","门头沟区","通州区","顺义区","房山区","大兴区","昌平区","怀柔区","平谷区","密云区","延庆区"]}]
14 | */
15 |
16 | var name: String? = null
17 | var city: List? = null
18 |
19 | class CityBean {
20 | /**
21 | * name : 北京市
22 | * area : ["东城区","西城区","海淀区","朝阳区","丰台区","石景山区","门头沟区","通州区","顺义区","房山区","大兴区","昌平区","怀柔区","平谷区","密云区","延庆区"]
23 | */
24 | var name: String? = null
25 | var area: List? = null
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/app/src/main/java/me/shouheng/xdialogsample/data/AddressSelectLevel.kt:
--------------------------------------------------------------------------------
1 | package me.shouheng.xdialogsample.data
2 |
3 | import androidx.annotation.IntDef
4 | import me.shouheng.xdialogsample.data.AddressSelectLevel.Companion.LEVEL_AREA
5 | import me.shouheng.xdialogsample.data.AddressSelectLevel.Companion.LEVEL_CITY
6 |
7 | /** The max select level of province: city, area, province etc. */
8 | @IntDef(value = [LEVEL_CITY, LEVEL_AREA])
9 | @Target(allowedTargets = [AnnotationTarget.FIELD,
10 | AnnotationTarget.TYPE_PARAMETER,
11 | AnnotationTarget.VALUE_PARAMETER])
12 | annotation class AddressSelectLevel {
13 | companion object {
14 | const val LEVEL_CITY = 0
15 | const val LEVEL_AREA = 1
16 | }
17 | }
--------------------------------------------------------------------------------
/app/src/main/java/me/shouheng/xdialogsample/utils/AddressUtils.kt:
--------------------------------------------------------------------------------
1 | package me.shouheng.xdialogsample.utils
2 |
3 | import com.google.gson.Gson
4 | import com.google.gson.JsonParser
5 | import me.shouheng.utils.app.ResUtils
6 | import me.shouheng.utils.store.IOUtils
7 | import me.shouheng.xdialogsample.data.AddressBean
8 |
9 | /**
10 | * UIX Biz 工具类
11 | *
12 | * @author Shouheng Wang
13 | * @version 2019-10-13 11:57
14 | */
15 | object AddressUtils {
16 |
17 | private var addresses: List? = null
18 |
19 | /** 获取地址信息 */
20 | fun getAddressList(): List {
21 | if (addresses != null) return addresses!!
22 | val ins = ResUtils.getAssets().open("province.json")
23 | val bytes = IOUtils.is2Bytes(ins)
24 | val content = String(bytes)
25 | val list = ArrayList()
26 | val gson = Gson()
27 | try {
28 | val array = JsonParser().parse(content).asJsonArray
29 | for (jsonElement in array) {
30 | list.add(gson.fromJson(jsonElement, AddressBean::class.java))
31 | }
32 | addresses = list
33 | } catch (e: Exception) {
34 | e.printStackTrace()
35 | }
36 | return list
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/app/src/main/java/me/shouheng/xdialogsample/utils/OnItemDebouncedClick.kt:
--------------------------------------------------------------------------------
1 | package me.shouheng.xdialogsample.utils
2 |
3 | import android.view.View
4 | import com.chad.library.adapter.base.BaseQuickAdapter
5 | import com.chad.library.adapter.base.BaseViewHolder
6 | import me.shouheng.utils.ktx.NoDoubleClickListener
7 |
8 | abstract class OnItemNoDoubleClickListener: BaseQuickAdapter.OnItemClickListener {
9 |
10 | private var lastClickTime: Long = 0
11 |
12 | override fun onItemClick(adapter: BaseQuickAdapter<*, *>?, view: View?, position: Int) {
13 | val currentTime = System.currentTimeMillis()
14 | if (currentTime - lastClickTime > NoDoubleClickListener.MIN_CLICK_DELAY_TIME) {
15 | lastClickTime = currentTime
16 | onNoDoubleClick(adapter, view, position)
17 | }
18 | }
19 |
20 | protected abstract fun onNoDoubleClick(adapter: BaseQuickAdapter<*, *>?, view: View?, position: Int)
21 | }
22 |
23 | fun BaseQuickAdapter.onItemDebouncedClick(
24 | click: (adapter: BaseQuickAdapter<*, *>?, view: View?, position: Int) -> Unit
25 | ) {
26 | onItemClickListener = object : OnItemNoDoubleClickListener() {
27 | override fun onNoDoubleClick(adapter: BaseQuickAdapter<*, *>?, view: View?, position: Int) {
28 | click(adapter, view, position)
29 | }
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/app/src/main/java/me/shouheng/xdialogsample/utils/Utils.kt:
--------------------------------------------------------------------------------
1 | package me.shouheng.xdialogsample.utils
2 |
3 | import android.view.Gravity
4 | import me.shouheng.uix.widget.dialog.content.SimpleList
5 | import me.shouheng.utils.ktx.colorOf
6 | import me.shouheng.utils.ktx.drawableOf
7 | import me.shouheng.xdialogsample.R
8 |
9 | /**
10 | * @Author wangshouheng
11 | * @Time 2021/9/20
12 | */
13 | object Utils {
14 |
15 | fun getSimpleGridData(): List {
16 | return listOf(
17 | colorOf(R.color.tool_item_color_1),
18 | colorOf(R.color.tool_item_color_2),
19 | colorOf(R.color.tool_item_color_3),
20 | colorOf(R.color.tool_item_color_4),
21 | colorOf(R.color.tool_item_color_5),
22 | colorOf(R.color.tool_item_color_6),
23 | colorOf(R.color.tool_item_color_7),
24 | colorOf(R.color.tool_item_color_8),
25 | colorOf(R.color.tool_item_color_9),
26 | colorOf(R.color.tool_item_color_10),
27 | colorOf(R.color.tool_item_color_11),
28 | colorOf(R.color.tool_item_color_12)
29 | )
30 | }
31 |
32 | fun getSimpleListData(): List {
33 | return listOf(
34 | SimpleList.Item(
35 | 0,
36 | "秦时明月汉时关,万里长征人未还。\n" + "但使龙城飞将在,不教胡马度阴山。",
37 | drawableOf(R.drawable.ic_android_black_24dp)
38 | ),
39 | SimpleList.Item(
40 | 1,
41 | "春眠不觉晓,处处闻啼鸟。\n" + "夜来风雨声,花落知多少。",
42 | drawableOf(R.drawable.ic_baseline_account_balance_24)
43 | ),
44 | SimpleList.Item(
45 | 2,
46 | "君自故乡来,应知故乡事。来日绮窗前,寒梅著花未?",
47 | drawableOf(R.drawable.uix_close_black_24dp),
48 | Gravity.START),
49 | SimpleList.Item(
50 | 3,
51 | "松下问童子,言师采药去。只在此山中,云深不知处。",
52 | drawableOf(R.drawable.uix_loading),
53 | Gravity.END),
54 | SimpleList.Item(
55 | 4,
56 | "明日复明日。",
57 | drawableOf(R.drawable.uix_loading),
58 | Gravity.START)
59 | )
60 | }
61 |
62 | fun getCustomListData(): List {
63 | return listOf(
64 | SimpleList.Item(0, "第 1 项", drawableOf(R.drawable.ic_android_black_24dp)),
65 | SimpleList.Item(1, "第 2 项", drawableOf(R.drawable.ic_baseline_account_balance_24)),
66 | SimpleList.Item(2, "第 3 项", drawableOf(R.drawable.uix_close_black_24dp)),
67 | SimpleList.Item(3, "第 4 项", drawableOf(R.drawable.uix_loading)),
68 | SimpleList.Item(4, "第 5 项", drawableOf(R.drawable.ic_android_black_24dp)),
69 | SimpleList.Item(5, "第 6 项", drawableOf(R.drawable.ic_baseline_account_balance_24)),
70 | SimpleList.Item(6, "第 7 项", drawableOf(R.drawable.uix_close_black_24dp)),
71 | SimpleList.Item(7, "第 8 项", drawableOf(R.drawable.uix_loading)),
72 | SimpleList.Item(8, "第 9 项", drawableOf(R.drawable.ic_android_black_24dp)),
73 | SimpleList.Item(9, "第 10 项", drawableOf(R.drawable.ic_baseline_account_balance_24)),
74 | SimpleList.Item(10, "第 11 项", drawableOf(R.drawable.uix_close_black_24dp)),
75 | SimpleList.Item(11, "第 12 项", drawableOf(R.drawable.uix_loading)),
76 | SimpleList.Item(12, "第 13 项", drawableOf(R.drawable.ic_android_black_24dp)),
77 | SimpleList.Item(13, "第 14 项", drawableOf(R.drawable.ic_baseline_account_balance_24)),
78 | SimpleList.Item(14, "第 15 项", drawableOf(R.drawable.uix_close_black_24dp)),
79 | SimpleList.Item(15, "第 16 项", drawableOf(R.drawable.uix_loading))
80 | )
81 | }
82 | }
--------------------------------------------------------------------------------
/app/src/main/java/me/shouheng/xdialogsample/utils/UtilsEtx.kt:
--------------------------------------------------------------------------------
1 | package me.shouheng.xdialogsample.utils
2 |
3 | import android.view.View
4 | import me.shouheng.utils.ktx.onDebouncedClick
5 |
6 | inline fun onClick(v: View, crossinline onClicked: (v: View) -> Unit) {
7 | v.setOnClickListener {
8 | onClicked(it)
9 | }
10 | }
11 |
12 | inline fun onDebouncedClick(v: View, crossinline onClicked: (v: View) -> Unit) {
13 | v.onDebouncedClick {
14 | onClicked(it)
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable-v24/ic_launcher_foreground.xml:
--------------------------------------------------------------------------------
1 |
7 |
8 |
9 |
15 |
18 |
21 |
22 |
23 |
24 |
30 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_android_black_24dp.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_baseline_account_balance_24.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
13 |
16 |
19 |
22 |
23 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_baseline_cancel_24.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_launcher_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
10 |
15 |
20 |
25 |
30 |
35 |
40 |
45 |
50 |
55 |
60 |
65 |
70 |
75 |
80 |
85 |
90 |
95 |
100 |
105 |
110 |
115 |
120 |
125 |
130 |
135 |
140 |
145 |
150 |
155 |
160 |
165 |
170 |
171 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
12 |
13 |
17 |
18 |
24 |
25 |
29 |
30 |
34 |
35 |
39 |
40 |
44 |
45 |
49 |
50 |
54 |
55 |
59 |
60 |
64 |
65 |
69 |
70 |
74 |
75 |
79 |
80 |
84 |
85 |
89 |
90 |
94 |
95 |
99 |
100 |
104 |
105 |
106 |
107 |
108 |
109 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/item_tool_color.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
16 |
17 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/layout_dialog_bottom_sample.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
13 |
14 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/layout_dialog_content_multiple.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
12 |
13 |
20 |
21 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/layout_dialog_content_upgrade.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
11 |
12 |
20 |
21 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/layout_dialog_title_sample.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
13 |
14 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/uix_dialog_content_address.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
14 |
15 |
18 |
19 |
24 |
25 |
30 |
31 |
36 |
37 |
38 |
39 |
42 |
43 |
49 |
50 |
57 |
58 |
65 |
66 |
67 |
68 |
69 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Shouheng88/xDialog/774e600a1dafb86365e2cc156c5901ad7d5b6d43/app/src/main/res/mipmap-hdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Shouheng88/xDialog/774e600a1dafb86365e2cc156c5901ad7d5b6d43/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Shouheng88/xDialog/774e600a1dafb86365e2cc156c5901ad7d5b6d43/app/src/main/res/mipmap-mdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Shouheng88/xDialog/774e600a1dafb86365e2cc156c5901ad7d5b6d43/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Shouheng88/xDialog/774e600a1dafb86365e2cc156c5901ad7d5b6d43/app/src/main/res/mipmap-xhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Shouheng88/xDialog/774e600a1dafb86365e2cc156c5901ad7d5b6d43/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/update_app_top_bg.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Shouheng88/xDialog/774e600a1dafb86365e2cc156c5901ad7d5b6d43/app/src/main/res/mipmap-xhdpi/update_app_top_bg.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Shouheng88/xDialog/774e600a1dafb86365e2cc156c5901ad7d5b6d43/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Shouheng88/xDialog/774e600a1dafb86365e2cc156c5901ad7d5b6d43/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Shouheng88/xDialog/774e600a1dafb86365e2cc156c5901ad7d5b6d43/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Shouheng88/xDialog/774e600a1dafb86365e2cc156c5901ad7d5b6d43/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/values-night/themes.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #FFBB86FC
4 | #FF6200EE
5 | #FF3700B3
6 | #FF03DAC5
7 | #FF018786
8 | #FF000000
9 | #FFFFFFFF
10 |
11 | #64a994
12 | #7899c6
13 | #7d88ca
14 | #8e959f
15 | #9e908f
16 | #929992
17 | #e77e68
18 | #e67577
19 | #de7997
20 | #df8a53
21 | #d4972c
22 | #71a656
23 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | xDialog
3 |
4 |
5 | 君不見黃河之水天上來,奔流到海不復回。
6 | 君不見高堂明鏡悲白髮,朝如青絲暮成雪。
7 | 人生得意須盡歡,莫使金樽空對月。
8 | 天生我材必有用,千金散盡還復來。
9 | 烹羊宰牛且爲樂,會須一飲三百杯。
10 | 岑夫子,丹丘生。將進酒,杯莫停。
11 | 與君歌一曲,請君爲我側耳聽。
12 | 鐘鼓饌玉不足貴,但願長醉不願醒。
13 | 古來聖賢皆寂寞,惟有飲者留其名。
14 | 陳王昔時宴平樂,斗酒十千恣讙謔。
15 | 主人何為言少錢?徑須沽取對君酌。
16 | 五花馬,千金裘。呼兒將出換美酒,與爾同銷萬古愁。
17 |
18 |
19 | 加载中…
20 |
21 | Address:
22 |
--------------------------------------------------------------------------------
/app/src/main/res/values/themes.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
22 |
23 |
31 |
--------------------------------------------------------------------------------
/app/src/test/java/me/shouheng/xdialogsample/ExampleUnitTest.java:
--------------------------------------------------------------------------------
1 | package me.shouheng.xdialogsample;
2 |
3 | import org.junit.Test;
4 |
5 | import static org.junit.Assert.*;
6 |
7 | /**
8 | * Example local unit test, which will execute on the development machine (host).
9 | *
10 | * @see Testing documentation
11 | */
12 | public class ExampleUnitTest {
13 | @Test
14 | public void addition_isCorrect() {
15 | assertEquals(4, 2 + 2);
16 | }
17 | }
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | buildscript {
2 | ext.kotlin_version = '1.4.31'
3 | repositories {
4 | google()
5 | mavenCentral()
6 | }
7 | dependencies {
8 | classpath "com.android.tools.build:gradle:7.0.2"
9 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.5.20"
10 | }
11 | }
12 | allprojects {
13 | repositories {
14 | google()
15 | mavenCentral()
16 | maven { url "https://jitpack.io" }
17 | maven { url "https://dl.bintray.com/easymark/Android" }
18 | }
19 | tasks.withType(Javadoc) {
20 | options.addStringOption('Xdoclint:none', '-quiet')
21 | options.addStringOption('encoding', 'UTF-8')
22 | }
23 | }
24 | task clean(type: Delete) {
25 | delete rootProject.buildDir
26 | }
27 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | android.enableJetifier=true
2 | android.useAndroidX=true
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | #
4 | # Copyright 2015 the original author or authors.
5 | #
6 | # Licensed under the Apache License, Version 2.0 (the "License");
7 | # you may not use this file except in compliance with the License.
8 | # You may obtain a copy of the License at
9 | #
10 | # https://www.apache.org/licenses/LICENSE-2.0
11 | #
12 | # Unless required by applicable law or agreed to in writing, software
13 | # distributed under the License is distributed on an "AS IS" BASIS,
14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | # See the License for the specific language governing permissions and
16 | # limitations under the License.
17 | #
18 |
19 | ##############################################################################
20 | ##
21 | ## Gradle start up script for UN*X
22 | ##
23 | ##############################################################################
24 |
25 | # Attempt to set APP_HOME
26 | # Resolve links: $0 may be a link
27 | PRG="$0"
28 | # Need this for relative symlinks.
29 | while [ -h "$PRG" ] ; do
30 | ls=`ls -ld "$PRG"`
31 | link=`expr "$ls" : '.*-> \(.*\)$'`
32 | if expr "$link" : '/.*' > /dev/null; then
33 | PRG="$link"
34 | else
35 | PRG=`dirname "$PRG"`"/$link"
36 | fi
37 | done
38 | SAVED="`pwd`"
39 | cd "`dirname \"$PRG\"`/" >/dev/null
40 | APP_HOME="`pwd -P`"
41 | cd "$SAVED" >/dev/null
42 |
43 | APP_NAME="Gradle"
44 | APP_BASE_NAME=`basename "$0"`
45 |
46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
48 |
49 | # Use the maximum available, or set MAX_FD != -1 to use that value.
50 | MAX_FD="maximum"
51 |
52 | warn () {
53 | echo "$*"
54 | }
55 |
56 | die () {
57 | echo
58 | echo "$*"
59 | echo
60 | exit 1
61 | }
62 |
63 | # OS specific support (must be 'true' or 'false').
64 | cygwin=false
65 | msys=false
66 | darwin=false
67 | nonstop=false
68 | case "`uname`" in
69 | CYGWIN* )
70 | cygwin=true
71 | ;;
72 | Darwin* )
73 | darwin=true
74 | ;;
75 | MINGW* )
76 | msys=true
77 | ;;
78 | NONSTOP* )
79 | nonstop=true
80 | ;;
81 | esac
82 |
83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
84 |
85 |
86 | # Determine the Java command to use to start the JVM.
87 | if [ -n "$JAVA_HOME" ] ; then
88 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
89 | # IBM's JDK on AIX uses strange locations for the executables
90 | JAVACMD="$JAVA_HOME/jre/sh/java"
91 | else
92 | JAVACMD="$JAVA_HOME/bin/java"
93 | fi
94 | if [ ! -x "$JAVACMD" ] ; then
95 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
96 |
97 | Please set the JAVA_HOME variable in your environment to match the
98 | location of your Java installation."
99 | fi
100 | else
101 | JAVACMD="java"
102 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
103 |
104 | Please set the JAVA_HOME variable in your environment to match the
105 | location of your Java installation."
106 | fi
107 |
108 | # Increase the maximum file descriptors if we can.
109 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
110 | MAX_FD_LIMIT=`ulimit -H -n`
111 | if [ $? -eq 0 ] ; then
112 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
113 | MAX_FD="$MAX_FD_LIMIT"
114 | fi
115 | ulimit -n $MAX_FD
116 | if [ $? -ne 0 ] ; then
117 | warn "Could not set maximum file descriptor limit: $MAX_FD"
118 | fi
119 | else
120 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
121 | fi
122 | fi
123 |
124 | # For Darwin, add options to specify how the application appears in the dock
125 | if $darwin; then
126 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
127 | fi
128 |
129 | # For Cygwin or MSYS, switch paths to Windows format before running java
130 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
131 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
132 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
133 |
134 | JAVACMD=`cygpath --unix "$JAVACMD"`
135 |
136 | # We build the pattern for arguments to be converted via cygpath
137 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
138 | SEP=""
139 | for dir in $ROOTDIRSRAW ; do
140 | ROOTDIRS="$ROOTDIRS$SEP$dir"
141 | SEP="|"
142 | done
143 | OURCYGPATTERN="(^($ROOTDIRS))"
144 | # Add a user-defined pattern to the cygpath arguments
145 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
146 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
147 | fi
148 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
149 | i=0
150 | for arg in "$@" ; do
151 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
152 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
153 |
154 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
155 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
156 | else
157 | eval `echo args$i`="\"$arg\""
158 | fi
159 | i=`expr $i + 1`
160 | done
161 | case $i in
162 | 0) set -- ;;
163 | 1) set -- "$args0" ;;
164 | 2) set -- "$args0" "$args1" ;;
165 | 3) set -- "$args0" "$args1" "$args2" ;;
166 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;;
167 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
168 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
169 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
170 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
171 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
172 | esac
173 | fi
174 |
175 | # Escape application args
176 | save () {
177 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
178 | echo " "
179 | }
180 | APP_ARGS=`save "$@"`
181 |
182 | # Collect all arguments for the java command, following the shell quoting and substitution rules
183 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
184 |
185 | exec "$JAVACMD" "$@"
186 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @rem
2 | @rem Copyright 2015 the original author or authors.
3 | @rem
4 | @rem Licensed under the Apache License, Version 2.0 (the "License");
5 | @rem you may not use this file except in compliance with the License.
6 | @rem You may obtain a copy of the License at
7 | @rem
8 | @rem https://www.apache.org/licenses/LICENSE-2.0
9 | @rem
10 | @rem Unless required by applicable law or agreed to in writing, software
11 | @rem distributed under the License is distributed on an "AS IS" BASIS,
12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | @rem See the License for the specific language governing permissions and
14 | @rem limitations under the License.
15 | @rem
16 |
17 | @if "%DEBUG%" == "" @echo off
18 | @rem ##########################################################################
19 | @rem
20 | @rem Gradle startup script for Windows
21 | @rem
22 | @rem ##########################################################################
23 |
24 | @rem Set local scope for the variables with windows NT shell
25 | if "%OS%"=="Windows_NT" setlocal
26 |
27 | set DIRNAME=%~dp0
28 | if "%DIRNAME%" == "" set DIRNAME=.
29 | set APP_BASE_NAME=%~n0
30 | set APP_HOME=%DIRNAME%
31 |
32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter.
33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
34 |
35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
37 |
38 | @rem Find java.exe
39 | if defined JAVA_HOME goto findJavaFromJavaHome
40 |
41 | set JAVA_EXE=java.exe
42 | %JAVA_EXE% -version >NUL 2>&1
43 | if "%ERRORLEVEL%" == "0" goto execute
44 |
45 | echo.
46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
47 | echo.
48 | echo Please set the JAVA_HOME variable in your environment to match the
49 | echo location of your Java installation.
50 |
51 | goto fail
52 |
53 | :findJavaFromJavaHome
54 | set JAVA_HOME=%JAVA_HOME:"=%
55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
56 |
57 | if exist "%JAVA_EXE%" goto execute
58 |
59 | echo.
60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
61 | echo.
62 | echo Please set the JAVA_HOME variable in your environment to match the
63 | echo location of your Java installation.
64 |
65 | goto fail
66 |
67 | :execute
68 | @rem Setup the command line
69 |
70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
71 |
72 |
73 | @rem Execute Gradle
74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
75 |
76 | :end
77 | @rem End local scope for the variables with windows NT shell
78 | if "%ERRORLEVEL%"=="0" goto mainEnd
79 |
80 | :fail
81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
82 | rem the _cmd.exe /c_ return code!
83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
84 | exit /b 1
85 |
86 | :mainEnd
87 | if "%OS%"=="Windows_NT" endlocal
88 |
89 | :omega
90 |
--------------------------------------------------------------------------------
/resources/Screenshot_2022-02-24-23-16-15-05_08de23611bf79886a74beffca0ad6bab.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Shouheng88/xDialog/774e600a1dafb86365e2cc156c5901ad7d5b6d43/resources/Screenshot_2022-02-24-23-16-15-05_08de23611bf79886a74beffca0ad6bab.jpg
--------------------------------------------------------------------------------
/resources/Screenshot_2022-02-24-23-16-19-28_08de23611bf79886a74beffca0ad6bab.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Shouheng88/xDialog/774e600a1dafb86365e2cc156c5901ad7d5b6d43/resources/Screenshot_2022-02-24-23-16-19-28_08de23611bf79886a74beffca0ad6bab.jpg
--------------------------------------------------------------------------------
/resources/Screenshot_2022-02-24-23-16-21-50_08de23611bf79886a74beffca0ad6bab.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Shouheng88/xDialog/774e600a1dafb86365e2cc156c5901ad7d5b6d43/resources/Screenshot_2022-02-24-23-16-21-50_08de23611bf79886a74beffca0ad6bab.jpg
--------------------------------------------------------------------------------
/resources/Screenshot_2022-02-24-23-16-23-42_08de23611bf79886a74beffca0ad6bab.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Shouheng88/xDialog/774e600a1dafb86365e2cc156c5901ad7d5b6d43/resources/Screenshot_2022-02-24-23-16-23-42_08de23611bf79886a74beffca0ad6bab.jpg
--------------------------------------------------------------------------------
/resources/Screenshot_2022-02-24-23-16-25-65_08de23611bf79886a74beffca0ad6bab.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Shouheng88/xDialog/774e600a1dafb86365e2cc156c5901ad7d5b6d43/resources/Screenshot_2022-02-24-23-16-25-65_08de23611bf79886a74beffca0ad6bab.jpg
--------------------------------------------------------------------------------
/resources/Screenshot_2022-02-24-23-16-29-23_08de23611bf79886a74beffca0ad6bab.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Shouheng88/xDialog/774e600a1dafb86365e2cc156c5901ad7d5b6d43/resources/Screenshot_2022-02-24-23-16-29-23_08de23611bf79886a74beffca0ad6bab.jpg
--------------------------------------------------------------------------------
/resources/Screenshot_2022-02-24-23-16-32-17_08de23611bf79886a74beffca0ad6bab.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Shouheng88/xDialog/774e600a1dafb86365e2cc156c5901ad7d5b6d43/resources/Screenshot_2022-02-24-23-16-32-17_08de23611bf79886a74beffca0ad6bab.jpg
--------------------------------------------------------------------------------
/resources/Screenshot_2022-02-24-23-16-37-24_08de23611bf79886a74beffca0ad6bab.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Shouheng88/xDialog/774e600a1dafb86365e2cc156c5901ad7d5b6d43/resources/Screenshot_2022-02-24-23-16-37-24_08de23611bf79886a74beffca0ad6bab.jpg
--------------------------------------------------------------------------------
/resources/Screenshot_2022-02-24-23-16-40-25_08de23611bf79886a74beffca0ad6bab.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Shouheng88/xDialog/774e600a1dafb86365e2cc156c5901ad7d5b6d43/resources/Screenshot_2022-02-24-23-16-40-25_08de23611bf79886a74beffca0ad6bab.jpg
--------------------------------------------------------------------------------
/resources/Screenshot_2022-02-24-23-16-45-48_08de23611bf79886a74beffca0ad6bab.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Shouheng88/xDialog/774e600a1dafb86365e2cc156c5901ad7d5b6d43/resources/Screenshot_2022-02-24-23-16-45-48_08de23611bf79886a74beffca0ad6bab.jpg
--------------------------------------------------------------------------------
/resources/Screenshot_2022-02-24-23-16-56-55_08de23611bf79886a74beffca0ad6bab.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Shouheng88/xDialog/774e600a1dafb86365e2cc156c5901ad7d5b6d43/resources/Screenshot_2022-02-24-23-16-56-55_08de23611bf79886a74beffca0ad6bab.jpg
--------------------------------------------------------------------------------
/resources/animation.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Shouheng88/xDialog/774e600a1dafb86365e2cc156c5901ad7d5b6d43/resources/animation.gif
--------------------------------------------------------------------------------
/resources/animation0.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Shouheng88/xDialog/774e600a1dafb86365e2cc156c5901ad7d5b6d43/resources/animation0.gif
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app'
2 | include ':xdialog'
3 |
--------------------------------------------------------------------------------
/xdialog/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/xdialog/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.library'
2 | apply plugin: 'kotlin-android'
3 |
4 | android {
5 | compileSdk 30
6 | defaultConfig {
7 | minSdk 21
8 | targetSdk 30
9 | versionCode 1
10 | versionName "1.0"
11 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
12 | }
13 | buildTypes {
14 | release {
15 | minifyEnabled false
16 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
17 | }
18 | }
19 | compileOptions {
20 | sourceCompatibility JavaVersion.VERSION_1_8
21 | targetCompatibility JavaVersion.VERSION_1_8
22 | }
23 | kotlinOptions {
24 | jvmTarget = '1.8'
25 | }
26 | lintOptions {
27 | abortOnError false
28 | }
29 | viewBinding {
30 | enabled = true
31 | }
32 | }
33 |
34 | dependencies {
35 | implementation "androidx.appcompat:appcompat:1.3.1"
36 | compileOnly "com.google.android.material:material:1.4.0"
37 | compileOnly "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
38 | compileOnly 'com.github.bumptech.glide:glide:4.12.0'
39 | compileOnly 'com.google.code.gson:gson:2.8.6'
40 | compileOnly 'com.github.CymChad:BaseRecyclerViewAdapterHelper:2.9.49-androidx'
41 | compileOnly "com.github.Shouheng88:xadapter:1.1"
42 | }
--------------------------------------------------------------------------------
/xdialog/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
--------------------------------------------------------------------------------
/xdialog/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/xdialog/src/main/java/me/shouheng/uix/widget/anno/BeautyDialogDSL.kt:
--------------------------------------------------------------------------------
1 | package me.shouheng.uix.widget.anno
2 |
3 | @DslMarker annotation class BeautyDialogDSL
--------------------------------------------------------------------------------
/xdialog/src/main/java/me/shouheng/uix/widget/anno/BottomButtonPosition.kt:
--------------------------------------------------------------------------------
1 | package me.shouheng.uix.widget.anno
2 |
3 | import androidx.annotation.IntDef
4 | import me.shouheng.uix.widget.anno.BottomButtonPosition.Companion.BUTTON_POS_LEFT
5 | import me.shouheng.uix.widget.anno.BottomButtonPosition.Companion.BUTTON_POS_MIDDLE
6 | import me.shouheng.uix.widget.anno.BottomButtonPosition.Companion.BUTTON_POS_RIGHT
7 |
8 | /** Positions of bottom button. */
9 | @IntDef(value = [BUTTON_POS_LEFT, BUTTON_POS_MIDDLE, BUTTON_POS_RIGHT])
10 | @Target(allowedTargets = [AnnotationTarget.FIELD,
11 | AnnotationTarget.TYPE_PARAMETER,
12 | AnnotationTarget.VALUE_PARAMETER])
13 | annotation class BottomButtonPosition {
14 | companion object {
15 | const val BUTTON_POS_LEFT = 1
16 | const val BUTTON_POS_MIDDLE = 2
17 | const val BUTTON_POS_RIGHT = 3
18 |
19 | fun isLeft(position: Int): Boolean = position == BUTTON_POS_LEFT
20 | fun isMiddle(position: Int): Boolean = position == BUTTON_POS_MIDDLE
21 | fun isRight(position: Int): Boolean = position == BUTTON_POS_RIGHT
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/xdialog/src/main/java/me/shouheng/uix/widget/anno/BottomButtonStyle.kt:
--------------------------------------------------------------------------------
1 | package me.shouheng.uix.widget.anno
2 |
3 | import androidx.annotation.IntDef
4 | import me.shouheng.uix.widget.anno.BottomButtonStyle.Companion.BUTTON_STYLE_DOUBLE
5 | import me.shouheng.uix.widget.anno.BottomButtonStyle.Companion.BUTTON_STYLE_SINGLE
6 | import me.shouheng.uix.widget.anno.BottomButtonStyle.Companion.BUTTON_STYLE_TRIPLE
7 |
8 | @IntDef(value = [BUTTON_STYLE_SINGLE, BUTTON_STYLE_DOUBLE, BUTTON_STYLE_TRIPLE])
9 | @Target(allowedTargets = [AnnotationTarget.FIELD,
10 | AnnotationTarget.TYPE_PARAMETER,
11 | AnnotationTarget.VALUE_PARAMETER])
12 | annotation class BottomButtonStyle {
13 | companion object {
14 | const val BUTTON_STYLE_SINGLE = 1
15 | const val BUTTON_STYLE_DOUBLE = 2
16 | const val BUTTON_STYLE_TRIPLE = 3
17 | }
18 | }
--------------------------------------------------------------------------------
/xdialog/src/main/java/me/shouheng/uix/widget/anno/DialogPosition.kt:
--------------------------------------------------------------------------------
1 | package me.shouheng.uix.widget.anno
2 |
3 | import androidx.annotation.IntDef
4 | import me.shouheng.uix.widget.anno.DialogPosition.Companion.POS_BOTTOM
5 | import me.shouheng.uix.widget.anno.DialogPosition.Companion.POS_CENTER
6 | import me.shouheng.uix.widget.anno.DialogPosition.Companion.POS_TOP
7 |
8 | /** 对话框位置 */
9 | @IntDef(value=[POS_CENTER, POS_BOTTOM, POS_TOP])
10 | @Retention(value = AnnotationRetention.SOURCE)
11 | @Target(allowedTargets = [AnnotationTarget.FIELD,
12 | AnnotationTarget.TYPE_PARAMETER,
13 | AnnotationTarget.VALUE_PARAMETER])
14 | annotation class DialogPosition {
15 | companion object {
16 | const val POS_CENTER = 0
17 | const val POS_BOTTOM = 1
18 | const val POS_TOP = 2
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/xdialog/src/main/java/me/shouheng/uix/widget/anno/DialogStyle.kt:
--------------------------------------------------------------------------------
1 | package me.shouheng.uix.widget.anno
2 |
3 | import androidx.annotation.IntDef
4 | import me.shouheng.uix.widget.anno.DialogStyle.Companion.STYLE_MATCH
5 | import me.shouheng.uix.widget.anno.DialogStyle.Companion.STYLE_WRAP
6 |
7 | /** 对话框风格 */
8 | @IntDef(value = [STYLE_MATCH, STYLE_WRAP])
9 | @Target(allowedTargets = [AnnotationTarget.FIELD,
10 | AnnotationTarget.TYPE_PARAMETER,
11 | AnnotationTarget.VALUE_PARAMETER])
12 | annotation class DialogStyle {
13 | companion object {
14 | /** 对话框风格:按照对话框内容进行延伸 */
15 | const val STYLE_WRAP = 0
16 | /** 对话框风格:对话框宽度填充屏幕 */
17 | const val STYLE_MATCH = 1
18 | /** Half of window */
19 | const val STYLE_HALF = 2
20 | /** Two third of window */
21 | const val STYLE_TWO_THIRD = 3
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/xdialog/src/main/java/me/shouheng/uix/widget/anno/EmptyViewState.kt:
--------------------------------------------------------------------------------
1 | package me.shouheng.uix.widget.anno
2 |
3 | import androidx.annotation.IntDef
4 | import me.shouheng.uix.widget.anno.EmptyViewState.Companion.STATE_EMPTY
5 | import me.shouheng.uix.widget.anno.EmptyViewState.Companion.STATE_LOADING
6 |
7 | /** 列表为空的控件当前的状态 */
8 | @IntDef(value = [STATE_LOADING, STATE_EMPTY])
9 | @Target(allowedTargets = [AnnotationTarget.FIELD,
10 | AnnotationTarget.TYPE_PARAMETER,
11 | AnnotationTarget.VALUE_PARAMETER])
12 | annotation class EmptyViewState {
13 | companion object {
14 | /** 加载中 */
15 | const val STATE_LOADING = 0
16 | /** 列表为空 */
17 | const val STATE_EMPTY = 1
18 | }
19 | }
--------------------------------------------------------------------------------
/xdialog/src/main/java/me/shouheng/uix/widget/anno/LoadingButtonState.kt:
--------------------------------------------------------------------------------
1 | package me.shouheng.uix.widget.anno
2 |
3 | import androidx.annotation.IntDef
4 | import me.shouheng.uix.widget.anno.LoadingButtonState.Companion.LOADING_STATE_DISABLE
5 | import me.shouheng.uix.widget.anno.LoadingButtonState.Companion.LOADING_STATE_LOADING
6 | import me.shouheng.uix.widget.anno.LoadingButtonState.Companion.LOADING_STATE_NORMAL
7 |
8 | /** 带进度条的按钮的状态 */
9 | @IntDef(value = [LOADING_STATE_NORMAL, LOADING_STATE_LOADING, LOADING_STATE_DISABLE])
10 | @Target(allowedTargets = [AnnotationTarget.FIELD,
11 | AnnotationTarget.TYPE_PARAMETER,
12 | AnnotationTarget.VALUE_PARAMETER])
13 | annotation class LoadingButtonState {
14 | companion object {
15 | /** 正常状态 */
16 | const val LOADING_STATE_NORMAL = 0
17 | /** 加载状态 */
18 | const val LOADING_STATE_LOADING = 1
19 | /** 一般状态 */
20 | const val LOADING_STATE_DISABLE = 2
21 | }
22 | }
--------------------------------------------------------------------------------
/xdialog/src/main/java/me/shouheng/uix/widget/anno/LoadingStyle.kt:
--------------------------------------------------------------------------------
1 | package me.shouheng.uix.widget.anno
2 |
3 | import androidx.annotation.IntDef
4 | import me.shouheng.uix.widget.anno.LoadingStyle.Companion.STYLE_ANDROID
5 | import me.shouheng.uix.widget.anno.LoadingStyle.Companion.STYLE_IOS
6 |
7 | /** 加载小圆圈的风格 å*/
8 | @IntDef(value = [STYLE_ANDROID, STYLE_IOS])
9 | @Target(allowedTargets = [AnnotationTarget.FIELD,
10 | AnnotationTarget.TYPE_PARAMETER,
11 | AnnotationTarget.VALUE_PARAMETER])
12 | annotation class LoadingStyle {
13 | companion object {
14 | /** Android 风格 */
15 | const val STYLE_ANDROID = 0
16 | /** iOS 风格 */
17 | const val STYLE_IOS = 1
18 | }
19 | }
--------------------------------------------------------------------------------
/xdialog/src/main/java/me/shouheng/uix/widget/bean/TextStyleBean.kt:
--------------------------------------------------------------------------------
1 | package me.shouheng.uix.widget.bean
2 |
3 | import androidx.annotation.ColorInt
4 | import androidx.annotation.RestrictTo
5 | import androidx.annotation.Size
6 | import me.shouheng.uix.widget.anno.BeautyDialogDSL
7 | import java.io.Serializable
8 |
9 | /** Bean of text style. */
10 | data class TextStyleBean(
11 | @ColorInt var textColor: Int? = null,
12 | @Size var textSize: Float? = null,
13 | var typeFace: Int? = null,
14 | var gravity: Int? = null
15 | ): Serializable
16 |
17 | @BeautyDialogDSL class TextStyleBeanBuilder {
18 | @ColorInt private var textColor: Int? = null
19 | @Size private var textSize: Float? = null
20 | private var typeFace: Int? = null
21 | private var gravity: Int? = null
22 |
23 | /** Specify text color for text view. */
24 | fun withColor(textColor: Int) {
25 | this.textColor = textColor
26 | }
27 |
28 | /** Specify text size for text view. */
29 | fun withSize(textSize: Float) {
30 | this.textSize = textSize
31 | }
32 |
33 | /** Specify text typeface for text view. */
34 | fun withTypeFace(typeFace: Int) {
35 | this.typeFace = typeFace
36 | }
37 |
38 | /** Specify gravity for text view. */
39 | fun withGravity(gravity: Int) {
40 | this.gravity = gravity
41 | }
42 |
43 | @RestrictTo(RestrictTo.Scope.LIBRARY) fun build(): TextStyleBean {
44 | return TextStyleBean(textColor, textSize, typeFace, gravity)
45 | }
46 | }
47 |
48 | /** Get a text style bean. */
49 | inline fun textStyle(
50 | init: TextStyleBeanBuilder.() -> Unit
51 | ): TextStyleBean {
52 | val builder = TextStyleBeanBuilder()
53 | builder.init()
54 | return builder.build()
55 | }
56 |
--------------------------------------------------------------------------------
/xdialog/src/main/java/me/shouheng/uix/widget/dialog/MessageDialog.kt:
--------------------------------------------------------------------------------
1 | package me.shouheng.uix.widget.dialog
2 |
3 | import android.app.Dialog
4 | import android.content.Context
5 | import android.graphics.Color
6 | import android.graphics.drawable.Drawable
7 | import android.view.LayoutInflater
8 | import android.view.View
9 | import android.widget.LinearLayout
10 | import androidx.annotation.ColorInt
11 | import androidx.annotation.StringRes
12 | import me.shouheng.uix.widget.anno.LoadingStyle
13 | import me.shouheng.uix.widget.bean.TextStyleBean
14 | import me.shouheng.uix.widget.R
15 | import me.shouheng.uix.widget.databinding.UixMessageDialogBinding
16 | import me.shouheng.uix.widget.utils.*
17 |
18 | /**
19 | * Loading and message dialog.
20 | *
21 | * @author [Shouheng Wang](mailto:shouheng2020@gmail.com)
22 | * @version 2020-01-12 17:56
23 | */
24 | class MessageDialog constructor(builder: Builder) {
25 |
26 | private val message: CharSequence? = builder.message
27 | private var textStyle: TextStyleBean = GlobalConfig.textStyle
28 | @LoadingStyle private val loadingStyle: Int = builder.style
29 | private val cancelable: Boolean = builder.cancelable
30 | private val loading: Boolean = builder.loading
31 | private val icon: Drawable? = builder.icon
32 | @ColorInt private val bgColor: Int = builder.bgColor
33 | private val borderRadius: Int = builder.borderRadius
34 |
35 | private fun build(context: Context): Dialog {
36 | val binding = UixMessageDialogBinding.inflate(LayoutInflater.from(context), null, false)
37 | binding.ll.background = DialogUtils.getDrawable(bgColor, borderRadius.toFloat())
38 |
39 | if (icon != null) binding.img.setImageDrawable(icon)
40 | else binding.img.visibility = View.GONE
41 |
42 | binding.pb.gone(!loading)
43 | if (loadingStyle == LoadingStyle.STYLE_IOS)
44 | binding.pb.indeterminateDrawable = drawableOf(R.drawable.uix_loading)
45 |
46 | binding.tv.text = message
47 | binding.tv.setStyle(textStyle, GlobalConfig.textStyle)
48 |
49 | val dlg = Dialog(context, if (cancelable) R.style.Dialog_Loading_Cancelable else R.style.Dialog_Loading)
50 | dlg.setCancelable(cancelable)
51 | val params = LinearLayout.LayoutParams(
52 | LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.MATCH_PARENT)
53 | dlg.setContentView(binding.ll, params)
54 | return dlg
55 | }
56 |
57 | class Builder {
58 | internal var message: CharSequence? = null
59 | internal var textStyle: TextStyleBean = GlobalConfig.textStyle
60 | internal var style: Int = GlobalConfig.loadingStyle
61 | internal var cancelable: Boolean = GlobalConfig.cancelable
62 | internal var icon: Drawable? = null
63 | internal var bgColor: Int = GlobalConfig.bgColor
64 | internal var loading: Boolean = GlobalConfig.loading
65 | internal var borderRadius: Int = GlobalConfig.bgBorderRadius
66 |
67 | /** Specify message of dialog. */
68 | fun withMessage(message: CharSequence) {
69 | this.message = message
70 | }
71 |
72 | /** Specify message of dialog. */
73 | fun withMessage(@StringRes msgRes: Int) {
74 | this.message = stringOf(msgRes)
75 | }
76 |
77 | /** Set message text style. */
78 | fun withTextStyle(style: TextStyleBean) {
79 | this.textStyle = style
80 | }
81 |
82 | /** Set dialog loading style. */
83 | fun withStyle(@LoadingStyle style: Int) {
84 | this.style = style
85 | }
86 |
87 | /** Set if dialog cancelable. */
88 | fun cancelable(cancelable: Boolean) {
89 | this.cancelable = cancelable
90 | }
91 |
92 | /** Set dialog loading or not. */
93 | fun withLoading(loading: Boolean) {
94 | this.loading = loading
95 | }
96 |
97 | /** Set message icon. */
98 | fun withIcon(icon: Drawable?) {
99 | this.icon = icon
100 | }
101 |
102 | /** Set dialog background color. */
103 | fun withBgColor(@ColorInt bgColor: Int) {
104 | this.bgColor = bgColor
105 | }
106 |
107 | /** Set background color. */
108 | fun withRadius(borderRadius: Int) {
109 | this.borderRadius = borderRadius
110 | }
111 |
112 | fun build(context: Context): Dialog {
113 | return MessageDialog(this).build(context)
114 | }
115 | }
116 |
117 | object GlobalConfig {
118 | var textStyle = TextStyleBean()
119 | @LoadingStyle var loadingStyle: Int = LoadingStyle.STYLE_IOS
120 | var cancelable: Boolean = true
121 | var loading: Boolean = false
122 | var bgColor: Int = Color.parseColor("#C0000000")
123 | var bgBorderRadius: Int = 8f.dp()
124 | }
125 | }
126 |
127 | /** Create a message dialog by DSL. */
128 | inline fun messageDialog(
129 | context: Context,
130 | init: MessageDialog.Builder.() -> Unit
131 | ): Dialog = MessageDialog.Builder().also(init).build(context)
132 |
133 | /** Create a message dialog by DSL. */
134 | @JvmName("showMessageDialog")
135 | inline fun Context.showMessage(
136 | init: MessageDialog.Builder.() -> Unit
137 | ): Dialog = messageDialog(this, init).apply { show() }
138 |
139 | /** Simple show message. */
140 | fun Context.showMessage(
141 | msg: String,
142 | icon: Drawable? = null,
143 | cancelable: Boolean = true,
144 | textStyle: TextStyleBean = MessageDialog.GlobalConfig.textStyle
145 | ): Dialog {
146 | return showMessage {
147 | withLoading(false)
148 | withMessage(msg)
149 | withIcon(icon)
150 | cancelable(cancelable)
151 | withTextStyle(textStyle)
152 | }
153 | }
154 |
155 | /** Simple show message of string res. */
156 | fun Context.showMessage(
157 | @StringRes msgRes: Int,
158 | icon: Drawable? = null,
159 | cancelable: Boolean = true,
160 | textStyle: TextStyleBean = MessageDialog.GlobalConfig.textStyle
161 | ): Dialog {
162 | return showMessage(stringOf(msgRes), icon, cancelable, textStyle)
163 | }
164 |
165 | /** Show simple loading dialog. */
166 | fun Context.showLoading(
167 | msg: String,
168 | cancelable: Boolean = MessageDialog.GlobalConfig.cancelable,
169 | textStyle: TextStyleBean = MessageDialog.GlobalConfig.textStyle,
170 | @LoadingStyle style: Int = MessageDialog.GlobalConfig.loadingStyle
171 | ): Dialog {
172 | return showMessage {
173 | withMessage(msg)
174 | withLoading(true)
175 | withStyle(style)
176 | cancelable(cancelable)
177 | withTextStyle(textStyle)
178 | }
179 | }
180 |
181 | /** Show simple loading dialog. */
182 | fun Context.showLoading(
183 | @StringRes msgRes: Int,
184 | cancelable: Boolean = MessageDialog.GlobalConfig.cancelable,
185 | textStyle: TextStyleBean = MessageDialog.GlobalConfig.textStyle,
186 | @LoadingStyle style: Int = MessageDialog.GlobalConfig.loadingStyle
187 | ): Dialog {
188 | return showMessage {
189 | withMessage(stringOf(msgRes))
190 | withLoading(true)
191 | withStyle(style)
192 | cancelable(cancelable)
193 | withTextStyle(textStyle)
194 | }
195 | }
196 |
--------------------------------------------------------------------------------
/xdialog/src/main/java/me/shouheng/uix/widget/dialog/content/CustomList.kt:
--------------------------------------------------------------------------------
1 | package me.shouheng.uix.widget.dialog.content
2 |
3 | import android.content.Context
4 | import android.view.ViewGroup
5 | import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
6 | import androidx.annotation.RestrictTo
7 | import androidx.recyclerview.widget.LinearLayoutManager
8 | import androidx.recyclerview.widget.RecyclerView
9 | import me.shouheng.uix.widget.anno.BeautyDialogDSL
10 | import me.shouheng.uix.widget.databinding.UixDialogContentListCustomBinding
11 | import me.shouheng.uix.widget.dialog.BeautyDialog
12 | import me.shouheng.uix.widget.rv.IEmptyView
13 |
14 | /**
15 | * Custom list dialog content
16 | *
17 | * @author Shouheng Wang
18 | * @version 2019-10-21 13:59
19 | */
20 | class CustomList private constructor(): ViewBindingDialogContent() {
21 |
22 | private lateinit var dialog: BeautyDialog
23 |
24 | private var emptyView: IEmptyView? = null
25 | private var adapter: RecyclerView.Adapter<*>? = null
26 | private var layoutManager: RecyclerView.LayoutManager? = null
27 |
28 | override fun doCreateView(ctx: Context) {
29 | emptyView?.getView()?.let {
30 | binding.flContainer.addView(it, ViewGroup.LayoutParams(WRAP_CONTENT, WRAP_CONTENT))
31 | binding.rv.setEmptyView(it)
32 | }
33 | binding.rv.adapter = adapter
34 | binding.rv.layoutManager = layoutManager?:LinearLayoutManager(ctx)
35 | }
36 |
37 | override fun setDialog(dialog: BeautyDialog) {
38 | this.dialog = dialog
39 | }
40 |
41 | fun getDialog(): BeautyDialog = dialog
42 |
43 | /** Show loading view manually. */
44 | fun showLoading() {
45 | emptyView?.show()
46 | emptyView?.showLoading()
47 | }
48 |
49 | /** Show empty view manually. */
50 | fun showEmpty() {
51 | emptyView?.show()
52 | emptyView?.showEmpty()
53 | }
54 |
55 | /** Hide empty view manually. */
56 | fun hideEmptyView() {
57 | emptyView?.hide()
58 | }
59 |
60 | @BeautyDialogDSL
61 | class Builder {
62 | private var emptyView: IEmptyView? = null
63 | private var adapter: RecyclerView.Adapter<*>? = null
64 | private var layoutManager: RecyclerView.LayoutManager? = null
65 |
66 | /** Specify empty view for custom list. */
67 | fun withEmptyView(emptyView: IEmptyView) {
68 | this.emptyView = emptyView
69 | }
70 |
71 | /** Specify recyclerview adapter for custom list. */
72 | fun withAdapter(adapter: RecyclerView.Adapter<*>) {
73 | this.adapter = adapter
74 | }
75 |
76 | /** Specify a layout manager for list. */
77 | fun withLayoutManager(layoutManager: RecyclerView.LayoutManager) {
78 | this.layoutManager = layoutManager
79 | }
80 |
81 | @RestrictTo(RestrictTo.Scope.LIBRARY) fun build(): CustomList {
82 | val customList = CustomList()
83 | customList.adapter = adapter
84 | customList.emptyView = emptyView
85 | return customList
86 | }
87 | }
88 | }
89 |
90 | /** Create a custom list content by DSL. */
91 | inline fun customList(
92 | init: CustomList.Builder.() -> Unit
93 | ): CustomList {
94 | return CustomList.Builder().apply(init).build()
95 | }
96 |
--------------------------------------------------------------------------------
/xdialog/src/main/java/me/shouheng/uix/widget/dialog/content/IDialogContent.kt:
--------------------------------------------------------------------------------
1 | package me.shouheng.uix.widget.dialog.content
2 |
3 | import android.content.Context
4 | import android.view.View
5 | import me.shouheng.uix.widget.dialog.BeautyDialog
6 | import me.shouheng.uix.widget.dialog.footer.IDialogFooter
7 | import me.shouheng.uix.widget.dialog.title.IDialogTitle
8 |
9 | /**
10 | * The dialog content interface
11 | *
12 | * @author Shouheng Wang
13 | * @version 2019-10-13 16:14
14 | */
15 | interface IDialogContent {
16 |
17 | /** Override to set the dialog content view */
18 | fun getView(ctx: Context): View
19 |
20 | /** Override to get the dialog */
21 | fun setDialog(dialog: BeautyDialog) { }
22 |
23 | /** Override to get the dialog title */
24 | fun setDialogTitle(dialogTitle: IDialogTitle?) { }
25 |
26 | /** Override to get the dialog footer */
27 | fun setDialogFooter(dialogFooter: IDialogFooter?) { }
28 | }
--------------------------------------------------------------------------------
/xdialog/src/main/java/me/shouheng/uix/widget/dialog/content/SimpleContent.kt:
--------------------------------------------------------------------------------
1 | package me.shouheng.uix.widget.dialog.content
2 |
3 | import android.content.Context
4 | import android.graphics.Typeface
5 | import android.view.Gravity
6 | import androidx.annotation.RestrictTo
7 | import me.shouheng.uix.widget.anno.BeautyDialogDSL
8 | import me.shouheng.uix.widget.bean.TextStyleBean
9 | import me.shouheng.uix.widget.bean.textStyle
10 | import me.shouheng.uix.widget.databinding.UixDialogContentSimpleBinding
11 |
12 | /**
13 | * Simple dialog content for text
14 | *
15 | * @author Shouheng Wang
16 | * @version 2019-10-13 09:46
17 | */
18 | class SimpleContent private constructor(): ViewBindingDialogContent() {
19 |
20 | private var content: CharSequence? = null
21 | private var textStyle: TextStyleBean = GlobalConfig.textStyle
22 |
23 | override fun doCreateView(ctx: Context) {
24 | binding.tv.text = content
25 | binding.tv.setStyle(textStyle)
26 | }
27 |
28 | @BeautyDialogDSL
29 | class Builder {
30 | private var content: CharSequence? = null
31 | private var textStyle: TextStyleBean = GlobalConfig.textStyle
32 |
33 | /** Specify content. */
34 | fun withContent(content: CharSequence) {
35 | this.content = content
36 | }
37 |
38 | /** Specify content text style. */
39 | fun withStyle(textStyle: TextStyleBean) {
40 | this.textStyle = textStyle
41 | }
42 |
43 | @RestrictTo(RestrictTo.Scope.LIBRARY) fun build(): SimpleContent {
44 | val simpleContent = SimpleContent()
45 | simpleContent.content = content
46 | simpleContent.textStyle = textStyle
47 | return simpleContent
48 | }
49 | }
50 |
51 | /** Global and default configurations for simple content. */
52 | object GlobalConfig {
53 | var textStyle = textStyle {
54 | withSize(16f)
55 | withTypeFace(Typeface.NORMAL)
56 | withGravity(Gravity.CENTER)
57 | }
58 | }
59 | }
60 |
61 | /** Create a simple content by DSL. */
62 | inline fun simpleContent(
63 | init: SimpleContent.Builder.() -> Unit
64 | ): SimpleContent = SimpleContent.Builder().apply(init).build()
65 |
--------------------------------------------------------------------------------
/xdialog/src/main/java/me/shouheng/uix/widget/dialog/content/SimpleEditor.kt:
--------------------------------------------------------------------------------
1 | package me.shouheng.uix.widget.dialog.content
2 |
3 | import android.content.Context
4 | import android.graphics.drawable.Drawable
5 | import android.text.Editable
6 | import android.text.InputFilter
7 | import android.text.TextWatcher
8 | import android.view.inputmethod.EditorInfo
9 | import androidx.annotation.RestrictTo
10 | import me.shouheng.uix.widget.databinding.UixDialogContentEditSimpleBinding
11 | import me.shouheng.uix.widget.anno.BeautyDialogDSL
12 | import me.shouheng.uix.widget.dialog.footer.IDialogFooter
13 | import me.shouheng.uix.widget.dialog.title.IDialogTitle
14 | import me.shouheng.uix.widget.utils.dp
15 | import me.shouheng.uix.widget.utils.gone
16 | import me.shouheng.uix.widget.utils.stringOf
17 | import me.shouheng.uix.widget.utils.drawableOf
18 |
19 | /**
20 | * Simple dialog editor content
21 | *
22 | * @author Shouheng Wang
23 | * @version 2019-10-14 11:24
24 | */
25 | class SimpleEditor private constructor(): ViewBindingDialogContent(), TextWatcher {
26 |
27 | private var content: String? = null
28 | private var hint: String? = null
29 | private var textColor: Int? = null
30 | private var hintTextColor: Int? = null
31 | private var lengthTextColor: Int? = null
32 | private var bottomLineColor: Int? = null
33 | private var textSize: Float = 16f
34 | private var singleLine = false
35 | private var numeric = false
36 | private var inputRegex: String? = null
37 | private var maxLength: Int? = null
38 | private var maxLines: Int? = null
39 | private var showLength = true
40 | private var clearDrawable: Drawable? = null
41 |
42 | private var dialogTitle: IDialogTitle? = null
43 | private var dialogFooter: IDialogFooter? = null
44 |
45 | override fun doCreateView(ctx: Context) {
46 | binding.et.addTextChangedListener(this)
47 | binding.et.setText(content)
48 | binding.et.hint = hint
49 | textColor?.let { binding.et.setTextColor(it) }
50 | hintTextColor?.let { binding.et.setHintTextColor(it) }
51 | lengthTextColor?.let { binding.tv.setTextColor(it) }
52 | bottomLineColor?.let { binding.v.setBackgroundColor(it) }
53 | binding.et.textSize = textSize
54 | binding.et.setSingleLine(singleLine)
55 | if (numeric) binding.et.addInputType(EditorInfo.TYPE_CLASS_NUMBER)
56 | inputRegex?.let { binding.et.inputRegex = inputRegex }
57 | maxLength?.let { binding.et.addFilters(InputFilter.LengthFilter(it)) }
58 | maxLines?.let { binding.et.maxLines = it }
59 | if (!showLength) binding.tv.gone()
60 | clearDrawable?.let { binding.et.setClearDrawable(it) }
61 | }
62 |
63 | fun getContent(): Editable? = binding.et.text
64 |
65 | override fun afterTextChanged(s: Editable?) {
66 | if (showLength) {
67 | val text = if (maxLength != null)
68 | "${binding.et.text?.length?:0}/${maxLength}"
69 | else "${binding.et.text?.length?:0}"
70 | binding.tv.text = text
71 | }
72 | }
73 |
74 | override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) { /*noop*/ }
75 |
76 | override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) { /*noop*/ }
77 |
78 | override fun setDialogTitle(dialogTitle: IDialogTitle?) {
79 | this.dialogTitle = dialogTitle
80 | }
81 |
82 | override fun setDialogFooter(dialogFooter: IDialogFooter?) {
83 | this.dialogFooter = dialogFooter
84 | }
85 |
86 | @BeautyDialogDSL
87 | class Builder {
88 | private var content: String? = null
89 | private var hint: String? = null
90 | private var textColor: Int? = null
91 | private var hintTextColor: Int? = null
92 | private var lengthTextColor: Int? = null
93 | private var bottomLineColor: Int? = null
94 | private var textSize: Float = 16f
95 | private var singleLine = false
96 | private var numeric = false
97 | private var inputRegex: String? = null
98 | private var maxLength: Int? = null
99 | private var maxLines: Int? = null
100 | private var showLength = true
101 | private var clearDrawable: Drawable? = null
102 |
103 | /** Specify the editor content. */
104 | fun withContent(content: String) {
105 | this.content = content
106 | }
107 |
108 | /** Specify the editor hint. */
109 | fun withHint(hint: String) {
110 | this.hint = hint
111 | }
112 |
113 | /** Specify the editor text color. */
114 | fun withTextColor(textColor: Int) {
115 | this.textColor = textColor
116 | }
117 |
118 | /** Specify the editor hint text color. */
119 | fun withHintTextColor(hintTextColor: Int) {
120 | this.hintTextColor = hintTextColor
121 | }
122 |
123 | /** Specify the editor clear drawable. */
124 | fun withClearDrawable(clearDrawable: Drawable) {
125 | this.clearDrawable = clearDrawable
126 | }
127 |
128 | /** Specify the editor length text color. */
129 | fun withLengthTextColor(lengthTextColor: Int) {
130 | this.lengthTextColor = lengthTextColor
131 | }
132 |
133 | /** Specify the editor bottom line color. */
134 | fun withBottomLineColor(bottomLineColor: Int) {
135 | this.bottomLineColor = bottomLineColor
136 | }
137 |
138 | /** Specify the editor text size. */
139 | fun withTextSize(textSize: Float) {
140 | this.textSize = textSize
141 | }
142 |
143 | /** Specify the editor single line. */
144 | fun withSingleLine(singleLine: Boolean) {
145 | this.singleLine = singleLine
146 | }
147 |
148 | /** Specify the editor numeric. */
149 | fun withNumeric(numeric: Boolean) {
150 | this.numeric = numeric
151 | }
152 |
153 | /** Specify the editor input regex. */
154 | fun withInputRegex(inputRegex: String) {
155 | this.inputRegex = inputRegex
156 | }
157 |
158 | /** Specify the editor max length. */
159 | fun withMaxLength(maxLength: Int) {
160 | this.maxLength = maxLength
161 | }
162 |
163 | /** Specify the editor max lines. */
164 | fun withMaxLines(maxLines: Int) {
165 | this.maxLines = maxLines
166 | }
167 |
168 | /** Specify the editor show length or not. */
169 | fun withShowLength(showLength: Boolean) {
170 | this.showLength = showLength
171 | }
172 |
173 | @RestrictTo(RestrictTo.Scope.LIBRARY) fun build(): SimpleEditor {
174 | val editor = SimpleEditor()
175 | editor.content = content
176 | editor.hint = hint
177 | editor.textColor = textColor
178 | editor.hintTextColor = hintTextColor
179 | editor.lengthTextColor = lengthTextColor
180 | editor.bottomLineColor = bottomLineColor
181 | editor.textSize = textSize
182 | editor.singleLine = singleLine
183 | editor.numeric = numeric
184 | editor.inputRegex = inputRegex
185 | editor.maxLength = maxLength
186 | editor.maxLines = maxLines
187 | editor.showLength = showLength
188 | editor.clearDrawable = clearDrawable
189 | return editor
190 | }
191 | }
192 | }
193 |
194 | /** Create a simple editor of DSL style. */
195 | inline fun simpleEditor(
196 | init: SimpleEditor.Builder.() -> Unit
197 | ): SimpleEditor {
198 | val builder = SimpleEditor.Builder()
199 | builder.init()
200 | return builder.build()
201 | }
202 |
--------------------------------------------------------------------------------
/xdialog/src/main/java/me/shouheng/uix/widget/dialog/content/SimpleGrid.kt:
--------------------------------------------------------------------------------
1 | package me.shouheng.uix.widget.dialog.content
2 |
3 | import android.content.Context
4 | import androidx.annotation.LayoutRes
5 | import androidx.annotation.RestrictTo
6 | import androidx.recyclerview.widget.GridLayoutManager
7 | import androidx.viewbinding.ViewBinding
8 | import com.chad.library.adapter.base.BaseViewHolder
9 | import me.shouheng.uix.widget.anno.BeautyDialogDSL
10 | import me.shouheng.uix.widget.databinding.UixDialogContentListSimpleBinding
11 | import me.shouheng.uix.widget.dialog.BeautyDialog
12 | import me.shouheng.xadapter.createAdapter
13 | import me.shouheng.xadapter.viewholder.onItemClick
14 | import java.lang.reflect.ParameterizedType
15 |
16 | /** Simple grid content for dialog. */
17 | class SimpleGrid private constructor(): ViewBindingDialogContent() {
18 |
19 | private lateinit var dialog: BeautyDialog
20 |
21 | internal var itemLayout: Int? = null
22 | internal var binder: ((helper: BaseViewHolder, item: T) -> Unit)? = null
23 | internal var onItemSelected: ((dialog: BeautyDialog, item: T) -> Unit)? = null
24 | internal var list: List = emptyList()
25 | internal var spanCount = 4
26 |
27 | override fun doCreateView(ctx: Context) {
28 | val typeClass: Class = ((this.javaClass.genericSuperclass as ParameterizedType).actualTypeArguments)
29 | .firstOrNull { ViewBinding::class.java.isAssignableFrom(it as Class<*>) } as? Class
30 | ?: throw IllegalStateException("You must specify a class type for simple grid.")
31 | if (itemLayout == null) throw IllegalStateException("You must specify item layout.")
32 | val adapter = createAdapter {
33 | withType(typeClass, itemLayout!!) {
34 | onBind { helper, item ->
35 | binder?.invoke(helper, item)
36 | }
37 | this.onItemClick { adapter, view, position ->
38 | onItemSelected?.invoke(dialog, adapter.data[position] as T)
39 | }
40 | }
41 | }
42 | binding.rv.adapter = adapter
43 | binding.rv.layoutManager = GridLayoutManager(ctx, spanCount)
44 | adapter.setNewData(list)
45 | }
46 |
47 | override fun setDialog(dialog: BeautyDialog) {
48 | this.dialog = dialog
49 | }
50 |
51 | @BeautyDialogDSL
52 | class Builder {
53 | @LayoutRes
54 | private var itemLayout: Int? = null
55 | private var list: List = emptyList()
56 | private var onItemSelected: ((dialog: BeautyDialog, item: T) -> Unit)? = null
57 | private var spanCount = 4
58 | private var binder: ((helper: BaseViewHolder, item: T) -> Unit)? = null
59 |
60 | /** Specify layout for item. */
61 | fun withLayout(@LayoutRes layout: Int) {
62 | this.itemLayout = layout
63 | }
64 |
65 | /** Specify bind logic for item. */
66 | fun onBind(binder: (helper: BaseViewHolder, item: T) -> Unit) {
67 | this.binder = binder
68 | }
69 |
70 | /** Specify span count for item. */
71 | fun withSpanCount(spanCount: Int) {
72 | this.spanCount = spanCount
73 | }
74 |
75 | /** Specify data list. */
76 | fun withList(list: List) {
77 | this.list = list
78 | }
79 |
80 | /** Specify callback when item selected. */
81 | fun onItemSelected(listener: (dialog: BeautyDialog, item: T) -> Unit) {
82 | this.onItemSelected = listener
83 | }
84 |
85 | @RestrictTo(RestrictTo.Scope.LIBRARY) fun build(): SimpleGrid {
86 | val simpleList = SimpleGrid()
87 | simpleList.list = list
88 | simpleList.spanCount = spanCount
89 | simpleList.onItemSelected = onItemSelected
90 | simpleList.itemLayout = itemLayout
91 | simpleList.binder = binder
92 | return simpleList
93 | }
94 | }
95 | }
96 |
97 | /** Create a simple grid by DSL. */
98 | inline fun simpleGrid(
99 | init: SimpleGrid.Builder.() -> Unit
100 | ): SimpleGrid = SimpleGrid.Builder().apply(init).build()
101 |
--------------------------------------------------------------------------------
/xdialog/src/main/java/me/shouheng/uix/widget/dialog/content/SimpleList.kt:
--------------------------------------------------------------------------------
1 | package me.shouheng.uix.widget.dialog.content
2 |
3 | import android.content.Context
4 | import android.graphics.drawable.Drawable
5 | import androidx.annotation.LayoutRes
6 | import androidx.annotation.RestrictTo
7 | import androidx.recyclerview.widget.LinearLayoutManager
8 | import me.shouheng.uix.widget.anno.BeautyDialogDSL
9 | import me.shouheng.uix.widget.bean.TextStyleBean
10 | import me.shouheng.uix.widget.databinding.UixDialogContentListSimpleBinding
11 | import me.shouheng.uix.widget.dialog.BeautyDialog
12 | import me.shouheng.uix.widget.utils.goneIf
13 | import me.shouheng.uix.widget.text.NormalTextView
14 | import me.shouheng.xadapter.createAdapter
15 | import me.shouheng.xadapter.viewholder.onItemClick
16 | import me.shouheng.uix.widget.R
17 |
18 | /**
19 | * Simple list dialog content
20 | *
21 | * @author Shouheng Wang
22 | * @version 2019-10-15 19:06
23 | */
24 | class SimpleList private constructor(): ViewBindingDialogContent() {
25 |
26 | private lateinit var dialog: BeautyDialog
27 |
28 | private var list: List- = emptyList()
29 | private var showIcon = true
30 | private var itemClickListener: ((dialog: BeautyDialog, item: Item) -> Unit)? = null
31 | private var textStyle = GlobalConfig.textStyle
32 | private var itemLayout: Int = R.layout.uix_dialog_content_list_simple_item
33 |
34 | override fun doCreateView(ctx: Context) {
35 | val adapter = createAdapter
- {
36 | withType(Item::class.java, itemLayout) {
37 | onBind { helper, item ->
38 | val tv = helper.getView(R.id.tv)
39 | tv.text = item.content
40 | tv.setStyle(textStyle, GlobalConfig.textStyle)
41 | item.gravity?.let { tv.gravity = it }
42 | item.icon?.let { helper.setImageDrawable(R.id.iv, it) }
43 | helper.goneIf(R.id.iv, !showIcon)
44 | }
45 | this.onItemClick { adapter, _, position ->
46 | itemClickListener?.invoke(dialog, adapter.data[position] as Item)
47 | }
48 | }
49 | }
50 | binding.rv.adapter = adapter
51 | binding.rv.layoutManager = LinearLayoutManager(ctx)
52 | adapter.setNewData(list)
53 | }
54 |
55 | override fun setDialog(dialog: BeautyDialog) {
56 | this.dialog = dialog
57 | }
58 |
59 | @BeautyDialogDSL
60 | class Builder {
61 | private var list: List
- = emptyList()
62 | private var showIcon = true
63 | private var textStyle = GlobalConfig.textStyle
64 | private var onItemSelected: ((dialog: BeautyDialog, item: Item) -> Unit)? = null
65 | private var itemLayout: Int = R.layout.uix_dialog_content_list_simple_item
66 |
67 | /** Item layout id */
68 | fun withItemLayout(@LayoutRes layoutId: Int) {
69 | this.itemLayout = layoutId
70 | }
71 |
72 | /** Specify data list. */
73 | fun withList(list: List
- ) {
74 | this.list = list
75 | }
76 |
77 | /** Specify list item text style. */
78 | fun withTextStyle(textStyle: TextStyleBean) {
79 | this.textStyle = textStyle
80 | }
81 |
82 | /** Specify if list should show icon. */
83 | fun withShowIcon(showIcon: Boolean) {
84 | this.showIcon = showIcon
85 | }
86 |
87 | /** Specify callback when item selected. */
88 | fun onItemSelected(listener: (dialog: BeautyDialog, item: Item) -> Unit) {
89 | this.onItemSelected = listener
90 | }
91 |
92 | @RestrictTo(RestrictTo.Scope.LIBRARY) fun build(): SimpleList {
93 | val simpleList = SimpleList()
94 | simpleList.list = list
95 | simpleList.textStyle = textStyle
96 | simpleList.showIcon = showIcon
97 | simpleList.itemClickListener = onItemSelected
98 | simpleList.itemLayout = itemLayout
99 | return simpleList
100 | }
101 | }
102 |
103 | object GlobalConfig {
104 | /** Global text style for list item. */
105 | var textStyle = TextStyleBean()
106 | }
107 |
108 | data class Item(
109 | val id: Int,
110 | var content: CharSequence?,
111 | var icon: Drawable?,
112 | var gravity: Int? = null
113 | )
114 | }
115 |
116 | /** Create a simple list by DSL. */
117 | inline fun simpleList(
118 | init: SimpleList.Builder.() -> Unit
119 | ): SimpleList = SimpleList.Builder().apply(init).build()
120 |
--------------------------------------------------------------------------------
/xdialog/src/main/java/me/shouheng/uix/widget/dialog/content/ViewBindingDialogContent.kt:
--------------------------------------------------------------------------------
1 | package me.shouheng.uix.widget.dialog.content
2 |
3 | import android.content.Context
4 | import android.view.LayoutInflater
5 | import android.view.View
6 | import androidx.viewbinding.ViewBinding
7 | import me.shouheng.uix.widget.utils.DialogUtils
8 | import java.lang.reflect.ParameterizedType
9 |
10 | abstract class ViewBindingDialogContent : IDialogContent {
11 |
12 | protected lateinit var binding: T
13 | private set
14 |
15 | override fun getView(ctx: Context): View {
16 | val vbClass: Class = ((this.javaClass.genericSuperclass as ParameterizedType).actualTypeArguments)
17 | .firstOrNull { ViewBinding::class.java.isAssignableFrom(it as Class<*>) } as? Class
18 | ?: throw IllegalStateException("You must specify a view binding class.")
19 | val method = vbClass.getDeclaredMethod("inflate", LayoutInflater::class.java)
20 | try {
21 | binding = method.invoke(null, LayoutInflater.from(ctx)) as T
22 | doCreateView(ctx)
23 | } catch (e: Exception) {
24 | DialogUtils.e("Failed to inflate view binding.")
25 | }
26 | return binding.root
27 | }
28 |
29 | abstract fun doCreateView(ctx: Context)
30 | }
31 |
--------------------------------------------------------------------------------
/xdialog/src/main/java/me/shouheng/uix/widget/dialog/footer/IDialogFooter.kt:
--------------------------------------------------------------------------------
1 | package me.shouheng.uix.widget.dialog.footer
2 |
3 | import android.content.Context
4 | import android.view.View
5 | import me.shouheng.uix.widget.dialog.BeautyDialog
6 | import me.shouheng.uix.widget.dialog.content.IDialogContent
7 | import me.shouheng.uix.widget.dialog.title.IDialogTitle
8 |
9 | /**
10 | * Dialog footer interace
11 | *
12 | * @author Shouheng Wang
13 | * @version 2019-10-13 16:15
14 | */
15 | interface IDialogFooter {
16 |
17 | /** Get the dialog footer view */
18 | fun getView(ctx: Context): View
19 |
20 | /** Override to get the dialog */
21 | fun setDialog(dialog: BeautyDialog) { }
22 |
23 | /** Override to get the dialog title */
24 | fun setDialogTitle(dialogTitle: IDialogTitle?) { }
25 |
26 | /** Override to get the dialog content */
27 | fun setDialogContent(dialogContent: IDialogContent?) { }
28 | }
29 |
--------------------------------------------------------------------------------
/xdialog/src/main/java/me/shouheng/uix/widget/dialog/footer/SimpleFooter.kt:
--------------------------------------------------------------------------------
1 | package me.shouheng.uix.widget.dialog.footer
2 |
3 | import android.content.Context
4 | import android.graphics.Color
5 | import android.graphics.drawable.StateListDrawable
6 | import androidx.annotation.ColorInt
7 | import androidx.annotation.RestrictTo
8 | import me.shouheng.uix.widget.anno.BeautyDialogDSL
9 | import me.shouheng.uix.widget.anno.BottomButtonStyle
10 | import me.shouheng.uix.widget.anno.BottomButtonStyle.Companion.BUTTON_STYLE_DOUBLE
11 | import me.shouheng.uix.widget.anno.BottomButtonStyle.Companion.BUTTON_STYLE_SINGLE
12 | import me.shouheng.uix.widget.bean.TextStyleBean
13 | import me.shouheng.uix.widget.databinding.UixDialogFooterSimpleBinding
14 | import me.shouheng.uix.widget.dialog.BeautyDialog
15 | import me.shouheng.uix.widget.dialog.content.IDialogContent
16 | import me.shouheng.uix.widget.dialog.title.IDialogTitle
17 | import me.shouheng.uix.widget.utils.DialogUtils
18 | import me.shouheng.uix.widget.utils.gone
19 |
20 | /**
21 | * Simple dialog footer
22 | *
23 | * @author Shouheng Wang
24 | * @version 2019-10-15 11:44
25 | */
26 | class SimpleFooter private constructor(): ViewBindingDialogFooter() {
27 |
28 | private lateinit var dialog: BeautyDialog
29 | private var dialogContent: IDialogContent? = null
30 | private var dialogTitle: IDialogTitle? = null
31 |
32 | @BottomButtonStyle
33 | private var bottomStyle: Int? = BUTTON_STYLE_DOUBLE
34 |
35 | private var leftText: CharSequence? = null
36 | private var middleText: CharSequence? = null
37 | private var rightText: CharSequence? = null
38 |
39 | private var leftTextStyle = GlobalConfig.leftTextStyle
40 | private var middleTextStyle = GlobalConfig.middleTextStyle
41 | private var rightTextStyle = GlobalConfig.rightTextStyle
42 |
43 | private var dividerColor: Int? = null
44 | private var onLeft: ((dlg: BeautyDialog, title: IDialogTitle?, content: IDialogContent?) -> Unit)? = null
45 | private var onMiddle: ((dlg: BeautyDialog, title: IDialogTitle?, content: IDialogContent?) -> Unit)? = null
46 | private var onRight: ((dlg: BeautyDialog, title: IDialogTitle?, content: IDialogContent?) -> Unit)? = null
47 |
48 | override fun doCreateView(ctx: Context) {
49 | binding.tvLeft.text = leftText
50 | binding.tvMiddle.text = middleText
51 | binding.tvRight.text = rightText
52 |
53 | binding.tvLeft.setStyle( leftTextStyle, GlobalConfig.leftTextStyle)
54 | binding.tvMiddle.setStyle(middleTextStyle, GlobalConfig.middleTextStyle)
55 | binding.tvRight.setStyle( rightTextStyle, GlobalConfig.rightTextStyle)
56 |
57 | binding.tvLeft.setOnClickListener {
58 | onLeft?.invoke(dialog, dialogTitle, dialogContent)
59 | }
60 | binding.tvMiddle.setOnClickListener {
61 | onMiddle?.invoke(dialog, dialogTitle, dialogContent)
62 | }
63 | binding.tvRight.setOnClickListener {
64 | onRight?.invoke(dialog, dialogTitle, dialogContent)
65 | }
66 |
67 | val cornerRadius = dialog.dialogCornerRadius.toFloat()
68 | val normalColor = if (dialog.dialogDarkStyle) BeautyDialog.GlobalConfig.darkBGColor else BeautyDialog.GlobalConfig.lightBGColor
69 | val selectedColor = DialogUtils.computeColor(normalColor, if (dialog.dialogDarkStyle) Color.WHITE else Color.BLACK, .1f)
70 |
71 | binding.tvLeft.background = StateListDrawable().apply {
72 | val normalDrawable = DialogUtils.getDrawable(normalColor, 0f, 0f, cornerRadius, 0f)
73 | val selectedDrawable = DialogUtils.getDrawable(selectedColor, 0f, 0f, cornerRadius, 0f)
74 | addState(intArrayOf(android.R.attr.state_pressed), selectedDrawable)
75 | addState(intArrayOf(-android.R.attr.state_pressed), normalDrawable)
76 | }
77 | binding.tvMiddle.background = StateListDrawable().apply {
78 | val radius = if (bottomStyle == BUTTON_STYLE_SINGLE) cornerRadius else 0f
79 | val normalDrawable = DialogUtils.getDrawable(normalColor, 0f, 0f, radius, radius)
80 | val selectedDrawable = DialogUtils.getDrawable(selectedColor, 0f, 0f, radius, radius)
81 | addState(intArrayOf(android.R.attr.state_pressed), selectedDrawable)
82 | addState(intArrayOf(-android.R.attr.state_pressed), normalDrawable)
83 | }
84 | binding.tvRight.background = StateListDrawable().apply {
85 | val normalDrawable = DialogUtils.getDrawable(normalColor, 0f, 0f, 0f, cornerRadius)
86 | val selectedDrawable = DialogUtils.getDrawable(selectedColor, 0f, 0f, 0f, cornerRadius)
87 | addState(intArrayOf(android.R.attr.state_pressed), selectedDrawable)
88 | addState(intArrayOf(-android.R.attr.state_pressed), normalDrawable)
89 | }
90 |
91 | // 优先使用构建者模式中传入的颜色,如果没有再使用全局配置的颜色,还是没有就使用默认颜色
92 | val finalDividerColor =
93 | if (dividerColor == null) {
94 | if (GlobalConfig.dividerColor == null) selectedColor
95 | else GlobalConfig.dividerColor!!
96 | } else dividerColor!!
97 | binding.v1.setBackgroundColor(finalDividerColor)
98 | binding.v2.setBackgroundColor(finalDividerColor)
99 | binding.h.setBackgroundColor(finalDividerColor)
100 |
101 | when(bottomStyle) {
102 | BUTTON_STYLE_SINGLE -> {
103 | binding.tvLeft.gone()
104 | binding.tvRight.gone()
105 | binding.v1.gone()
106 | binding.v2.gone()
107 | }
108 | BUTTON_STYLE_DOUBLE -> {
109 | binding.tvMiddle.gone()
110 | binding.v2.gone()
111 | }
112 | else -> { /* do nothing */ }
113 | }
114 | }
115 |
116 | override fun setDialog(dialog: BeautyDialog) {
117 | this.dialog = dialog
118 | }
119 |
120 | override fun setDialogTitle(dialogTitle: IDialogTitle?) {
121 | this.dialogTitle = dialogTitle
122 | }
123 |
124 | override fun setDialogContent(dialogContent: IDialogContent?) {
125 | this.dialogContent = dialogContent
126 | }
127 |
128 | @BeautyDialogDSL
129 | class Builder {
130 |
131 | @BottomButtonStyle
132 | private var style: Int? = BUTTON_STYLE_DOUBLE
133 |
134 | private var leftText: CharSequence? = null
135 | private var middleText: CharSequence? = null
136 | private var rightText: CharSequence? = null
137 |
138 | private var leftTextStyle = GlobalConfig.leftTextStyle
139 | private var middleTextStyle = GlobalConfig.middleTextStyle
140 | private var rightTextStyle = GlobalConfig.rightTextStyle
141 |
142 | private var dividerColor: Int? = null
143 |
144 | private var onLeft: ((dlg: BeautyDialog, title: IDialogTitle?, content: IDialogContent?) -> Unit)? = null
145 | private var onMiddle: ((dlg: BeautyDialog, title: IDialogTitle?, content: IDialogContent?) -> Unit)? = null
146 | private var onRight: ((dlg: BeautyDialog, title: IDialogTitle?, content: IDialogContent?) -> Unit)? = null
147 |
148 | /** Specify footer style. */
149 | fun withStyle(@BottomButtonStyle style: Int) {
150 | this.style = style
151 | }
152 |
153 | /** Specify left button text. */
154 | fun withLeft(leftText: CharSequence) {
155 | this.leftText = leftText
156 | }
157 |
158 | /** Specify left button text tyle. */
159 | fun withLeftStyle(textStyle: TextStyleBean) {
160 | this.leftTextStyle = textStyle
161 | }
162 |
163 | /** Specify middle button text. */
164 | fun withMiddle(middleText: CharSequence) {
165 | this.middleText = middleText
166 | }
167 |
168 | /** Specify middle button text tyle. */
169 | fun withMiddleStyle(textStyle: TextStyleBean) {
170 | this.middleTextStyle = textStyle
171 | }
172 |
173 | /** Specify right button text. */
174 | fun withRight(rightText: CharSequence) {
175 | this.rightText = rightText
176 | }
177 |
178 | /** Specify right button text style. */
179 | fun withRightStyle(textStyle: TextStyleBean) {
180 | this.rightTextStyle = textStyle
181 | }
182 |
183 | /** Callback when left button is clicked. */
184 | fun onLeft(callback: (dlg: BeautyDialog, title: IDialogTitle?, content: IDialogContent?) -> Unit) {
185 | this.onLeft = callback
186 | }
187 |
188 | /** Callback when middle button is clicked. */
189 | fun onMiddle(callback: (dlg: BeautyDialog, title: IDialogTitle?, content: IDialogContent?) -> Unit) {
190 | this.onMiddle = callback
191 | }
192 |
193 | /** Callback when right button is clicked. */
194 | fun onRight(callback: (dlg: BeautyDialog, title: IDialogTitle?, content: IDialogContent?) -> Unit) {
195 | this.onRight = callback
196 | }
197 |
198 | /** Specify divider color. */
199 | fun withDivider(@ColorInt dividerColor: Int) {
200 | this.dividerColor = dividerColor
201 | }
202 |
203 | @RestrictTo(RestrictTo.Scope.LIBRARY) fun build(): SimpleFooter {
204 | val bottom = SimpleFooter()
205 | bottom.leftText = leftText
206 | bottom.leftTextStyle = leftTextStyle
207 | bottom.middleText = middleText
208 | bottom.middleTextStyle = middleTextStyle
209 | bottom.rightText = rightText
210 | bottom.rightTextStyle = rightTextStyle
211 | bottom.bottomStyle = style
212 | bottom.dividerColor = dividerColor
213 | bottom.onLeft = onLeft
214 | bottom.onMiddle = onMiddle
215 | bottom.onRight = onRight
216 | return bottom
217 | }
218 | }
219 |
220 | object GlobalConfig {
221 | var leftTextStyle = TextStyleBean()
222 | var middleTextStyle = TextStyleBean()
223 | var rightTextStyle = TextStyleBean()
224 | /** 按钮底部的分割线的颜色 */
225 | @ColorInt var dividerColor: Int? = null
226 | }
227 | }
228 |
229 | /** Create a simple footer by DSL style. */
230 | inline fun simpleFooter(
231 | init: SimpleFooter.Builder.() -> Unit
232 | ): SimpleFooter {
233 | val builder = SimpleFooter.Builder()
234 | builder.init()
235 | return builder.build()
236 | }
237 |
--------------------------------------------------------------------------------
/xdialog/src/main/java/me/shouheng/uix/widget/dialog/footer/ViewBindingDialogFooter.kt:
--------------------------------------------------------------------------------
1 | package me.shouheng.uix.widget.dialog.footer
2 |
3 | import android.content.Context
4 | import android.view.LayoutInflater
5 | import android.view.View
6 | import androidx.viewbinding.ViewBinding
7 | import me.shouheng.uix.widget.utils.DialogUtils
8 | import java.lang.reflect.ParameterizedType
9 |
10 | abstract class ViewBindingDialogFooter : IDialogFooter {
11 |
12 | protected lateinit var binding: T
13 | private set
14 |
15 | override fun getView(ctx: Context): View {
16 | val vbClass: Class = ((this.javaClass.genericSuperclass as ParameterizedType).actualTypeArguments)
17 | .firstOrNull { ViewBinding::class.java.isAssignableFrom(it as Class<*>) } as? Class
18 | ?: throw IllegalStateException("You must specify a view binding class.")
19 | val method = vbClass.getDeclaredMethod("inflate", LayoutInflater::class.java)
20 | try {
21 | binding = method.invoke(null, LayoutInflater.from(ctx)) as T
22 | doCreateView(ctx)
23 | } catch (e: Exception) {
24 | DialogUtils.e("Failed to inflate view binding.")
25 | }
26 | return binding.root
27 | }
28 |
29 | abstract fun doCreateView(ctx: Context)
30 | }
31 |
--------------------------------------------------------------------------------
/xdialog/src/main/java/me/shouheng/uix/widget/dialog/title/IDialogTitle.kt:
--------------------------------------------------------------------------------
1 | package me.shouheng.uix.widget.dialog.title
2 |
3 | import android.content.Context
4 | import android.view.View
5 | import me.shouheng.uix.widget.dialog.BeautyDialog
6 | import me.shouheng.uix.widget.dialog.content.IDialogContent
7 | import me.shouheng.uix.widget.dialog.footer.IDialogFooter
8 |
9 | /**
10 | * 对话框顶部的抽象接口
11 | *
12 | * @author Shouheng Wang
13 | * @version 2019-10-13 16:14
14 | */
15 | interface IDialogTitle {
16 |
17 | /** 获取控件 */
18 | fun getView(ctx: Context): View
19 |
20 | /** 传递 Dialog 给当前的控件,以便当前控件内部使用 */
21 | fun setDialog(dialog: BeautyDialog) { }
22 |
23 | /** 设置对话框内容 */
24 | fun setDialogContent(dialogContent: IDialogContent?) { }
25 |
26 | /** 设置对话框内容 */
27 | fun setDialogFooter(dialogFooter: IDialogFooter?) { }
28 | }
--------------------------------------------------------------------------------
/xdialog/src/main/java/me/shouheng/uix/widget/dialog/title/SimpleTitle.kt:
--------------------------------------------------------------------------------
1 | package me.shouheng.uix.widget.dialog.title
2 |
3 | import android.content.Context
4 | import android.view.Gravity
5 | import androidx.annotation.RestrictTo
6 | import me.shouheng.uix.widget.bean.TextStyleBean
7 | import me.shouheng.uix.widget.databinding.UixDialogTitleSimpleBinding
8 | import me.shouheng.uix.widget.anno.BeautyDialogDSL
9 | import me.shouheng.uix.widget.bean.textStyle
10 |
11 | /**
12 | * Simple dialog title
13 | *
14 | * @author Shouheng Wang
15 | * @version 2019-10-13 09:46
16 | */
17 | class SimpleTitle private constructor(): ViewBindingDialogTitle() {
18 |
19 | private var title: CharSequence? = null
20 | private var titleStyle: TextStyleBean = GlobalConfig.titleStyle
21 |
22 | override fun doCreateView(ctx: Context) {
23 | binding.tv.text = title
24 | binding.tv.setStyle(titleStyle, GlobalConfig.titleStyle)
25 | }
26 |
27 | @BeautyDialogDSL
28 | class Builder {
29 | private var title: CharSequence? = null
30 | private var titleStyle: TextStyleBean = GlobalConfig.titleStyle
31 |
32 | /** Specify dialog title text. */
33 | fun withTitle(title: CharSequence) {
34 | this.title = title
35 | }
36 |
37 | /** Specify dialog title style. */
38 | fun withStyle(titleStyle: TextStyleBean) {
39 | this.titleStyle = titleStyle
40 | }
41 |
42 | @RestrictTo(RestrictTo.Scope.LIBRARY) fun build(): SimpleTitle {
43 | val simpleTitle = SimpleTitle()
44 | simpleTitle.title = title
45 | simpleTitle.titleStyle = titleStyle
46 | return simpleTitle
47 | }
48 | }
49 |
50 | object GlobalConfig {
51 | var titleStyle = textStyle {
52 | withGravity(Gravity.CENTER)
53 | }
54 | }
55 | }
56 |
57 | /** Create a simple title dialog title by DSL. */
58 | inline fun simpleTitle(
59 | init: SimpleTitle.Builder.() -> Unit
60 | ): SimpleTitle {
61 | val builder = SimpleTitle.Builder()
62 | builder.init()
63 | return builder.build()
64 | }
--------------------------------------------------------------------------------
/xdialog/src/main/java/me/shouheng/uix/widget/dialog/title/ViewBindingDialogTitle.kt:
--------------------------------------------------------------------------------
1 | package me.shouheng.uix.widget.dialog.title
2 |
3 | import android.content.Context
4 | import android.view.LayoutInflater
5 | import android.view.View
6 | import androidx.viewbinding.ViewBinding
7 | import me.shouheng.uix.widget.utils.DialogUtils
8 | import java.lang.reflect.ParameterizedType
9 |
10 | abstract class ViewBindingDialogTitle : IDialogTitle {
11 |
12 | protected lateinit var binding: T
13 | private set
14 |
15 | override fun getView(ctx: Context): View {
16 | val vbClass: Class = ((this.javaClass.genericSuperclass as ParameterizedType).actualTypeArguments)
17 | .firstOrNull { ViewBinding::class.java.isAssignableFrom(it as Class<*>) } as? Class
18 | ?: throw IllegalStateException("You must specify a view binding class.")
19 | val method = vbClass.getDeclaredMethod("inflate", LayoutInflater::class.java)
20 | try {
21 | binding = method.invoke(null, LayoutInflater.from(ctx)) as T
22 | doCreateView(ctx)
23 | } catch (e: Exception) {
24 | DialogUtils.e("Failed to inflate view binding.")
25 | }
26 | return binding.root
27 | }
28 |
29 | abstract fun doCreateView(ctx: Context)
30 | }
31 |
--------------------------------------------------------------------------------
/xdialog/src/main/java/me/shouheng/uix/widget/rv/EmptySupportRecyclerView.kt:
--------------------------------------------------------------------------------
1 | package me.shouheng.uix.widget.rv
2 |
3 | import android.content.Context
4 | import androidx.recyclerview.widget.RecyclerView
5 | import android.util.AttributeSet
6 | import android.view.View
7 |
8 | /** The recyclerview support empty state. */
9 | class EmptySupportRecyclerView : RecyclerView {
10 |
11 | private var emptyView: View? = null
12 |
13 | private var emptyCount = 0
14 |
15 | private val observer = object : RecyclerView.AdapterDataObserver() {
16 | override fun onChanged() {
17 | updateEmptyState()
18 | }
19 |
20 | override fun onItemRangeInserted(positionStart: Int, itemCount: Int) {
21 | updateEmptyState()
22 | }
23 |
24 | override fun onItemRangeRemoved(positionStart: Int, itemCount: Int) {
25 | updateEmptyState()
26 | }
27 | }
28 |
29 | constructor(context: Context) : super(context)
30 |
31 | constructor(context: Context, attrs: AttributeSet) : super(context, attrs)
32 |
33 | constructor(context: Context, attrs: AttributeSet, defStyle: Int) : super(context, attrs, defStyle)
34 |
35 | private fun updateEmptyState() {
36 | val adapter = adapter
37 | if (adapter != null) {
38 | emptyView?.visibility = if (adapter.itemCount != emptyCount) View.GONE else View.VISIBLE
39 | }
40 | }
41 |
42 | override fun setAdapter(adapter: Adapter<*>?) {
43 | super.setAdapter(adapter)
44 | if (adapter != null) {
45 | adapter.registerAdapterDataObserver(observer)
46 | observer.onChanged()
47 | }
48 | }
49 |
50 | /**
51 | * 设置列表为空时展示的控件,这里的 emptyCount 用来指定,
52 | * 当 adapter 中当数据为多少当时候表示列表为空(因如果为 adapter 设置了头部或底部当情况)
53 | */
54 | fun setEmptyView(emptyView: View, emptyCount: Int = 0) {
55 | this.emptyView = emptyView
56 | this.emptyCount = emptyCount
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/xdialog/src/main/java/me/shouheng/uix/widget/rv/EmptyView.kt:
--------------------------------------------------------------------------------
1 | package me.shouheng.uix.widget.rv
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.animation.AnimationUtils
8 | import android.widget.FrameLayout
9 | import androidx.annotation.ColorInt
10 | import androidx.annotation.DrawableRes
11 | import androidx.annotation.RestrictTo
12 | import me.shouheng.uix.widget.R
13 | import me.shouheng.uix.widget.anno.EmptyViewState
14 | import me.shouheng.uix.widget.anno.LoadingStyle
15 | import me.shouheng.uix.widget.anno.BeautyDialogDSL
16 | import me.shouheng.uix.widget.databinding.UixLayoutEmptyViewBinding
17 |
18 | /**
19 | * 列表为空控件的一个实现类
20 | *
21 | * @author Shouheng Wang
22 | * @version 2019-10-22 22:21
23 | */
24 | class EmptyView : FrameLayout, IEmptyView {
25 |
26 | private lateinit var binding: UixLayoutEmptyViewBinding
27 |
28 | @LoadingStyle
29 | var loadingStyle: Int = LoadingStyle.STYLE_ANDROID
30 | set(value) {
31 | field = value
32 | isAndroidStyle = loadingStyle == LoadingStyle.STYLE_ANDROID
33 | updateStyleState()
34 | }
35 | @EmptyViewState
36 | var emptyViewState: Int = EmptyViewState.STATE_LOADING
37 | set(value) {
38 | field = value
39 | isLoading = emptyViewState == EmptyViewState.STATE_LOADING
40 | updateEmptyState()
41 | }
42 | var emptyImage: Int? = null
43 | set(value) {
44 | field = value
45 | updateEmptyImageState()
46 | }
47 | var emptyImageSize: Int? = null
48 | set(value) {
49 | field = value
50 | if (value != null) {
51 | binding.ivEmpty.layoutParams = binding.ivEmpty.layoutParams.apply {
52 | width = value
53 | height = value
54 | }
55 | }
56 | }
57 | var emptyTitle: String? = null
58 | set(value) {
59 | field = value
60 | binding.tvEmptyTitle.text = value
61 | }
62 | @ColorInt
63 | var emptyTitleColor: Int? = null
64 | set(value) {
65 | field = value
66 | value?.let { binding.tvEmptyTitle.setTextColor(it) }
67 | }
68 | var emptyDetails: String? = null
69 | set(value) {
70 | field = value
71 | binding.tvEmptyDetail.text = value
72 | }
73 | @ColorInt
74 | var emptyDetailsColor: Int? = null
75 | set(value) {
76 | field = value
77 | value?.let { binding.tvEmptyDetail.setTextColor(it) }
78 | }
79 | var loadingTips: String? = null
80 | set(value) {
81 | field = value
82 | binding.tvLoading.text = value
83 | }
84 | @ColorInt
85 | var loadingTipsColor: Int? = null
86 | set(value) {
87 | field = value
88 | value?.let { binding.tvLoading.setTextColor(it) }
89 | }
90 |
91 | private var isLoading: Boolean = false
92 | private var isAndroidStyle: Boolean = true
93 |
94 | constructor(context: Context) : this(context, null)
95 | constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, 0)
96 | constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr) {
97 | init(context, attrs)
98 | }
99 |
100 | private fun init(context: Context, attrs: AttributeSet?) {
101 | binding = UixLayoutEmptyViewBinding.inflate(LayoutInflater.from(context), this)
102 |
103 | if (attrs != null) {
104 | val typedArray = context.obtainStyledAttributes(attrs, R.styleable.EmptyView)
105 | loadingStyle = typedArray.getInt(
106 | R.styleable.EmptyView_empty_loading_style, LoadingStyle.STYLE_ANDROID)
107 | emptyViewState = typedArray.getInt(
108 | R.styleable.EmptyView_empty_state, EmptyViewState.STATE_LOADING)
109 |
110 | if (typedArray.hasValue(R.styleable.EmptyView_empty_image))
111 | emptyImage = typedArray.getResourceId(R.styleable.EmptyView_empty_image, -1)
112 | if (typedArray.hasValue(R.styleable.EmptyView_empty_image_size)) {
113 | val size = typedArray.getDimensionPixelSize(R.styleable.EmptyView_empty_image_size, 60f.dp(context))
114 | binding.ivEmpty.layoutParams = binding.ivEmpty.layoutParams.apply {
115 | width = size
116 | height = size
117 | }
118 | }
119 |
120 | emptyTitle = typedArray.getString(R.styleable.EmptyView_empty_title)
121 | emptyDetails = typedArray.getString(R.styleable.EmptyView_empty_detail)
122 | loadingTips = typedArray.getString(R.styleable.EmptyView_empty_loading_tips)
123 |
124 | if (typedArray.hasValue(R.styleable.EmptyView_empty_title_text_color))
125 | emptyTitleColor = typedArray.getColor(R.styleable.EmptyView_empty_title_text_color, 0)
126 | if (typedArray.hasValue(R.styleable.EmptyView_empty_detail_text_color))
127 | emptyDetailsColor = typedArray.getColor(R.styleable.EmptyView_empty_detail_text_color, 0)
128 | if (typedArray.hasValue(R.styleable.EmptyView_empty_loading_tips_text_color))
129 | loadingTipsColor = typedArray.getColor(R.styleable.EmptyView_empty_loading_tips_text_color, 0)
130 | typedArray.recycle()
131 | }
132 |
133 | binding.tvLoading.text = loadingTips
134 | loadingTipsColor?.let { binding.tvLoading.setTextColor(it) }
135 | binding.tvEmptyTitle.text = emptyTitle
136 | emptyTitleColor?.let { binding.tvEmptyTitle.setTextColor(it) }
137 | binding.tvEmptyDetail.text = emptyDetails
138 | emptyDetailsColor?.let { binding.tvEmptyDetail.setTextColor(it) }
139 |
140 | isAndroidStyle = loadingStyle == LoadingStyle.STYLE_ANDROID
141 | updateStyleState()
142 | val hyperspaceJumpAnimation = AnimationUtils.loadAnimation(context, R.anim.uix_loading)
143 | binding.ivLoading.startAnimation(hyperspaceJumpAnimation)
144 |
145 | isLoading = emptyViewState == EmptyViewState.STATE_LOADING
146 | updateEmptyState()
147 |
148 | updateEmptyImageState()
149 | }
150 |
151 | override fun showLoading() {
152 | isLoading = true
153 | updateEmptyState()
154 | }
155 |
156 | override fun showEmpty() {
157 | isLoading = false
158 | updateEmptyState()
159 | }
160 |
161 | private fun updateStyleState() {
162 | binding.ivLoading.gone(isAndroidStyle)
163 | binding.pb.gone(!isAndroidStyle)
164 | }
165 |
166 | private fun updateEmptyState() {
167 | binding.llLoading.gone(!isLoading)
168 | binding.llEmpty.gone(isLoading)
169 | }
170 |
171 | private fun updateEmptyImageState() {
172 | emptyImage?.let { binding.ivEmpty.setImageResource(it) }
173 | binding.ivEmpty.gone(emptyImage == null)
174 | }
175 |
176 | override fun show() {
177 | this.visibility = View.VISIBLE
178 | }
179 |
180 | override fun hide() {
181 | this.visibility = View.GONE
182 | }
183 |
184 | override fun getView(): View = this
185 |
186 | @BeautyDialogDSL
187 | class Builder {
188 | @LoadingStyle private var style: Int = LoadingStyle.STYLE_ANDROID
189 | @EmptyViewState private var state: Int = EmptyViewState.STATE_LOADING
190 | private var emptyImage: Int? = null
191 | private var emptyImageSize: Int? = null
192 | private var emptyTitle: String? = null
193 | @ColorInt private var emptyTitleColor: Int? = null
194 | private var emptyDetails: String? = null
195 | @ColorInt private var emptyDetailsColor: Int? = null
196 | private var loadingTips: String? = null
197 | @ColorInt private var loadingTipsColor: Int? = null
198 |
199 | /** Loading style empty view. */
200 | fun withStyle(@LoadingStyle loadingStyle: Int) {
201 | this.style = loadingStyle
202 | }
203 |
204 | /** Loading state empty view. */
205 | fun withState(@LoadingStyle emptyViewState: Int) {
206 | this.state = emptyViewState
207 | }
208 |
209 | /** Empty image. */
210 | fun withEmptyImage(@DrawableRes image: Int) {
211 | this.emptyImage = image
212 | }
213 |
214 | /** Empty image size. */
215 | fun withEmptyImageSize(emptyImageSize: Int) {
216 | this.emptyImageSize = emptyImageSize
217 | }
218 |
219 | /** Empty title. */
220 | fun withEmptyTitle(title: String) {
221 | this.emptyTitle = title
222 | }
223 |
224 | /** Empty title text color. */
225 | fun withEmptyTitleColor(@ColorInt textColor: Int) {
226 | this.emptyTitleColor = textColor
227 | }
228 |
229 | /** Empty detail. */
230 | fun setEmptyDetails(details: String) {
231 | this.emptyDetails = details
232 | }
233 |
234 | /** Empty detail text color. */
235 | fun withEmptyDetailColor(@ColorInt textColor: Int) {
236 | this.emptyDetailsColor = textColor
237 | }
238 |
239 | /** Loading tips. */
240 | fun withLoadingTips(tips: String) {
241 | this.loadingTips = tips
242 | }
243 |
244 | /** Loading tips text color. */
245 | fun withLoadingTipsColor(@ColorInt textColor: Int) {
246 | this.loadingTipsColor = textColor
247 | }
248 |
249 | @RestrictTo(RestrictTo.Scope.LIBRARY) fun build(context: Context): EmptyView {
250 | val emptyView = EmptyView(context)
251 | emptyView.loadingStyle = style
252 | emptyView.emptyViewState = state
253 | emptyView.emptyImage = emptyImage
254 | emptyView.emptyImageSize = emptyImageSize
255 | emptyView.emptyTitle = emptyTitle
256 | emptyView.emptyTitleColor = emptyTitleColor
257 | emptyView.emptyDetails = emptyDetails
258 | emptyView.emptyDetailsColor = emptyDetailsColor
259 | emptyView.loadingTips = loadingTips
260 | emptyView.loadingTipsColor = loadingTipsColor
261 | return emptyView
262 | }
263 | }
264 | }
265 |
266 | /** Create an empty view by DSL. */
267 | inline fun emptyView(
268 | context: Context,
269 | init: EmptyView.Builder.() -> Unit
270 | ): EmptyView {
271 | val builder = EmptyView.Builder()
272 | builder.init()
273 | return builder.build(context)
274 | }
275 |
276 | @RestrictTo(RestrictTo.Scope.LIBRARY) fun View.gone(goneIf: Boolean) {
277 | this.visibility = if (goneIf) View.GONE else View.VISIBLE
278 | }
279 |
280 | @RestrictTo(RestrictTo.Scope.LIBRARY) fun Float.dp(context: Context): Int {
281 | val scale: Float = context.getResources().getDisplayMetrics().density
282 | return (this * scale + 0.5f).toInt()
283 | }
284 |
285 |
--------------------------------------------------------------------------------
/xdialog/src/main/java/me/shouheng/uix/widget/rv/IEmptyView.kt:
--------------------------------------------------------------------------------
1 | package me.shouheng.uix.widget.rv
2 |
3 | import android.view.View
4 |
5 | /**
6 | * 列表为空的控件的接口
7 | *
8 | * @author Shouheng Wang
9 | * @version 2019-10-20 15:46
10 | */
11 | interface IEmptyView {
12 |
13 | /** 设置列表为空的控件为"加载"状态 */
14 | fun showLoading()
15 |
16 | /** 设置列表为空的控件为"列表为空"状态 */
17 | fun showEmpty()
18 |
19 | /** 显示控件 */
20 | fun show()
21 |
22 | /** 隐藏控件 */
23 | fun hide()
24 |
25 | /** 获取控件 */
26 | fun getView(): View
27 | }
--------------------------------------------------------------------------------
/xdialog/src/main/java/me/shouheng/uix/widget/text/ClearEditText.kt:
--------------------------------------------------------------------------------
1 | package me.shouheng.uix.widget.text
2 |
3 | import android.annotation.SuppressLint
4 | import android.content.Context
5 | import android.content.res.ColorStateList
6 | import android.graphics.drawable.Drawable
7 | import androidx.core.content.ContextCompat
8 | import androidx.core.graphics.drawable.DrawableCompat
9 | import android.text.Editable
10 | import android.text.TextWatcher
11 | import android.util.AttributeSet
12 | import android.view.MotionEvent
13 | import android.view.View
14 | import androidx.annotation.ColorInt
15 | import me.shouheng.uix.widget.R
16 |
17 | /**
18 | * 带有清除选项的文本编辑器
19 | *
20 | * @author [Shouheng Wang](mailto:shouheng2020@gmail.com)
21 | * @version 2019-10-03 22:55
22 | */
23 | class ClearEditText : RegexEditText, View.OnTouchListener, View.OnFocusChangeListener, TextWatcher {
24 |
25 | private var mClearDrawable: Drawable? = null
26 |
27 | private var mOnTouchListener: OnTouchListener? = null
28 |
29 | private var mOnFocusChangeListener: OnFocusChangeListener? = null
30 |
31 | constructor(context: Context) : super(context)
32 |
33 | constructor(context: Context, attrs: AttributeSet) : super(context, attrs)
34 |
35 | constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr)
36 |
37 | @SuppressLint("ClickableViewAccessibility")
38 | override fun initialize(context: Context, attrs: AttributeSet?) {
39 | super.initialize(context, attrs)
40 |
41 | val array = context.obtainStyledAttributes(attrs, R.styleable.ClearEditText)
42 | val imageTintColor = array.getColor(R.styleable.ClearEditText_clear_image_tint_color, -1)
43 | array.recycle()
44 |
45 | // Wrap the drawable so that it can be tinted pre Lollipop
46 | val clearDrawable = ContextCompat.getDrawable(context, R.drawable.uix_close_black_24dp)!!
47 | mClearDrawable = DrawableCompat.wrap(
48 | if (imageTintColor == -1) clearDrawable
49 | else tintDrawable(clearDrawable, imageTintColor)
50 | )
51 | mClearDrawable!!.setBounds(0, 0, mClearDrawable!!.intrinsicWidth, mClearDrawable!!.intrinsicHeight)
52 | setDrawableVisible(false)
53 |
54 | super.setOnTouchListener(this)
55 | super.setOnFocusChangeListener(this)
56 | super.addTextChangedListener(this)
57 | }
58 |
59 | private fun setDrawableVisible(visible: Boolean) {
60 | if (mClearDrawable!!.isVisible == visible) {
61 | return
62 | }
63 |
64 | mClearDrawable!!.setVisible(visible, false)
65 | val drawables = compoundDrawables
66 | setCompoundDrawables(drawables[0], drawables[1], if (visible) mClearDrawable else null, drawables[3])
67 | }
68 |
69 | fun setClearDrawable(drawable: Drawable) {
70 | this.mClearDrawable = drawable
71 | mClearDrawable!!.setBounds(0, 0, mClearDrawable!!.intrinsicWidth, mClearDrawable!!.intrinsicHeight)
72 | setDrawableVisible(text?.isEmpty()?:false)
73 | }
74 |
75 | override fun setOnFocusChangeListener(onFocusChangeListener: OnFocusChangeListener) {
76 | mOnFocusChangeListener = onFocusChangeListener
77 | }
78 |
79 | override fun setOnTouchListener(onTouchListener: OnTouchListener) {
80 | mOnTouchListener = onTouchListener
81 | }
82 |
83 | override fun onFocusChange(view: View, hasFocus: Boolean) {
84 | if (hasFocus && text != null) {
85 | setDrawableVisible(text!!.isNotEmpty())
86 | } else {
87 | setDrawableVisible(false)
88 | }
89 | if (mOnFocusChangeListener != null) {
90 | mOnFocusChangeListener!!.onFocusChange(view, hasFocus)
91 | }
92 | }
93 |
94 | override fun onTouch(view: View, motionEvent: MotionEvent): Boolean {
95 | val x = motionEvent.x.toInt()
96 | if (mClearDrawable!!.isVisible && x > width - paddingRight - mClearDrawable!!.intrinsicWidth) {
97 | if (motionEvent.action == MotionEvent.ACTION_UP) {
98 | setText("")
99 | }
100 | return true
101 | }
102 | return mOnTouchListener != null && mOnTouchListener!!.onTouch(view, motionEvent)
103 | }
104 |
105 | override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {
106 | if (isFocused) {
107 | setDrawableVisible(s.isNotEmpty())
108 | }
109 | }
110 |
111 | override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {}
112 |
113 | override fun afterTextChanged(s: Editable) {}
114 |
115 | private fun tintDrawable(drawable: Drawable, @ColorInt color: Int): Drawable {
116 | val wrappedDrawable = DrawableCompat.wrap(drawable.mutate())
117 | DrawableCompat.setTintList(wrappedDrawable, ColorStateList.valueOf(color))
118 | return wrappedDrawable
119 | }
120 | }
121 |
--------------------------------------------------------------------------------
/xdialog/src/main/java/me/shouheng/uix/widget/text/NormalTextView.kt:
--------------------------------------------------------------------------------
1 | package me.shouheng.uix.widget.text
2 |
3 | import android.content.Context
4 | import androidx.appcompat.widget.AppCompatTextView
5 | import android.util.AttributeSet
6 | import me.shouheng.uix.widget.bean.TextStyleBean
7 |
8 | /** 普通等文本控件,与 [AppCompatTextView] 不同的是支持通过 [TextStyleBean] 设置文字风格 */
9 | class NormalTextView : AppCompatTextView {
10 |
11 | constructor(context: Context) : super(context)
12 | constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
13 | constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)
14 |
15 | /** 设置文字风格 */
16 | fun setStyle(style: TextStyleBean) {
17 | if (style.textSize != null) this.textSize = style.textSize!!
18 | if (style.gravity != null) this.gravity = style.gravity!!
19 | if (style.textColor != null) setTextColor(style.textColor!!)
20 | if (style.typeFace != null) this.setTypeface(null, style.typeFace!!)
21 | }
22 |
23 | /**
24 | * 设置文字风格 允许设置两个文字风格,优先使用 [self] 指定的属性,当 [self] 指定的属性不存在
25 | * 的时候使用 [global] 指定的属性。这个是提供给 SDK 内部的一些控件使用的便利方法。
26 | *
27 | * @param self 文字的风格
28 | * @param global 全局的文字风格
29 | */
30 | fun setStyle(self: TextStyleBean, global: TextStyleBean) {
31 | (self.textSize?:global.textSize)?.let { textSize = it }
32 | (self.gravity?:global.gravity)?.let { gravity = it }
33 | (self.textColor?:global.textColor)?.let { setTextColor(it) }
34 | (self.typeFace?:global.typeFace)?.let { setTypeface(null, it) }
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/xdialog/src/main/java/me/shouheng/uix/widget/text/RegexEditText.kt:
--------------------------------------------------------------------------------
1 | package me.shouheng.uix.widget.text
2 |
3 | import android.content.Context
4 | import androidx.appcompat.widget.AppCompatEditText
5 | import android.text.InputFilter
6 | import android.text.Spanned
7 | import android.util.AttributeSet
8 | import me.shouheng.uix.widget.R
9 |
10 | import java.util.regex.Pattern
11 |
12 | /**
13 | * 正则表达式控制输入的编辑器
14 | *
15 | * @author [Shouheng Wang](mailto:shouheng2020@gmail.com)
16 | * @version 2019-10-03 22:48
17 | */
18 | open class RegexEditText : AppCompatEditText, InputFilter {
19 |
20 | /** 正则表达式规则 */
21 | private var mPattern: Pattern? = null
22 |
23 | /** 正则 */
24 | var inputRegex: String?
25 | get() =
26 | if (mPattern == null) { null }
27 | else mPattern!!.pattern()
28 | set(regex) {
29 | if (regex == null || "" == regex) return
30 | mPattern = Pattern.compile(regex)
31 | addFilters(this)
32 | }
33 |
34 | constructor(context: Context) : super(context) {
35 | initialize(context, null)
36 | }
37 |
38 | constructor(context: Context, attrs: AttributeSet) : super(context, attrs) {
39 | initialize(context, attrs)
40 | }
41 |
42 | constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr) {
43 | initialize(context, attrs)
44 | }
45 |
46 | /** 初始化属性 */
47 | protected open fun initialize(context: Context, attrs: AttributeSet?) {
48 | val array = context.obtainStyledAttributes(attrs, R.styleable.RegexEditText)
49 |
50 | if (array.hasValue(R.styleable.RegexEditText_inputRegex)) {
51 | inputRegex = array.getString(R.styleable.RegexEditText_inputRegex)
52 | } else {
53 | if (array.hasValue(R.styleable.RegexEditText_regexType)) {
54 | when (array.getInt(R.styleable.RegexEditText_regexType, 0)) {
55 | 0x01 -> inputRegex = REGEX_MOBILE
56 | 0x02 -> inputRegex = REGEX_CHINESE
57 | 0x03 -> inputRegex = REGEX_ENGLISH
58 | 0x04 -> inputRegex = REGEX_COUNT
59 | 0x05 -> inputRegex = REGEX_NAME
60 | 0x06 -> inputRegex = REGEX_NONNULL
61 | }
62 | }
63 | }
64 |
65 | array.recycle()
66 | }
67 |
68 | /** 是否有这个输入标记 */
69 | fun hasInputType(type: Int): Boolean {
70 | return inputType and type != 0
71 | }
72 |
73 | /** 添加一个输入标记 */
74 | fun addInputType(type: Int) {
75 | inputType = inputType or type
76 | }
77 |
78 | /** 移除一个输入标记 */
79 | fun removeInputType(type: Int) {
80 | inputType = inputType and type.inv()
81 | }
82 |
83 | /** 添加筛选规则 */
84 | fun addFilters(filter: InputFilter?) {
85 | if (filter == null) {
86 | return
87 | }
88 |
89 | val newFilters: Array
90 | val oldFilters = filters
91 | if (oldFilters != null && oldFilters.size != 0) {
92 | newFilters = arrayOfNulls(oldFilters.size + 1)
93 | // 复制旧数组的元素到新数组中
94 | System.arraycopy(oldFilters, 0, newFilters, 0, oldFilters.size)
95 | newFilters[oldFilters.size] = filter
96 | } else {
97 | newFilters = arrayOfNulls(1)
98 | newFilters[0] = filter
99 | }
100 | super.setFilters(newFilters)
101 | }
102 |
103 | /**
104 | * [InputFilter]
105 | *
106 | * @param source 新输入的字符串
107 | * @param start 新输入的字符串起始下标,一般为0
108 | * @param end 新输入的字符串终点下标,一般为source长度-1
109 | * @param dest 输入之前文本框内容
110 | * @param dstart 原内容起始坐标,一般为0
111 | * @param dend 原内容终点坐标,一般为dest长度-1
112 | * @return 返回字符串将会加入到内容中
113 | */
114 | override fun filter(source: CharSequence, start: Int, end: Int, dest: Spanned, dstart: Int, dend: Int): CharSequence {
115 | if (mPattern == null) {
116 | return source
117 | }
118 |
119 | // 拼接出最终的字符串
120 | val begin = dest.toString().substring(0, dstart)
121 | val over = dest.toString().substring(dstart + (dend - dstart), dstart + (dest.toString().length - begin.length))
122 | val result = begin + source + over
123 |
124 | // 判断是插入还是删除
125 | if (dstart > dend - 1) {
126 | if (mPattern!!.matcher(result).matches()) {
127 | // 如果匹配就允许这个文本通过
128 | return source
129 | }
130 | } else {
131 | if (!mPattern!!.matcher(result).matches()) {
132 | // 如果不匹配则不让删除(删空操作除外)
133 | if ("" != result) {
134 | return dest.toString().substring(dstart, dend)
135 | }
136 | }
137 | }
138 |
139 | // 注意这里不能返回 null,否则会和 return source 效果一致
140 | return ""
141 | }
142 |
143 | companion object {
144 | /** 手机号(只能以 1 开头) */
145 | const val REGEX_MOBILE = "[1]\\d{0,10}"
146 | /** 中文(普通的中文字符) */
147 | const val REGEX_CHINESE = "[\\u4e00-\\u9fa5]*"
148 | /** 英文(大写和小写的英文) */
149 | const val REGEX_ENGLISH = "[a-zA-Z]*"
150 | /** 计数(非 0 开头的数字) */
151 | const val REGEX_COUNT = "[1-9]\\d*"
152 | /** 用户名(中文、英文、数字)*/
153 | const val REGEX_NAME = "[$REGEX_CHINESE|$REGEX_ENGLISH|\\d*]*"
154 | /** 非空格的字符(不能输入空格)*/
155 | const val REGEX_NONNULL = "\\S+"
156 | }
157 | }
158 |
--------------------------------------------------------------------------------
/xdialog/src/main/java/me/shouheng/uix/widget/utils/DialogEtx.kt:
--------------------------------------------------------------------------------
1 | package me.shouheng.uix.widget.utils
2 |
3 | import android.app.Dialog
4 | import android.graphics.drawable.Drawable
5 | import android.view.View
6 | import androidx.annotation.*
7 | import com.chad.library.adapter.base.BaseViewHolder
8 |
9 | /** Hide dialog. */
10 | fun Dialog?.hide() {
11 | if (this?.isShowing == true) {
12 | this.dismiss()
13 | }
14 | }
15 |
16 | fun nowString(): String = DialogUtils.nowString()
17 |
18 | fun Float.dp(): Int = DialogUtils.dp2px(this)
19 |
20 | fun View.gone() { DialogUtils.setGone(this) }
21 |
22 | fun View.gone(goneIf: Boolean) {
23 | this.visibility = if (goneIf) View.GONE else View.VISIBLE
24 | }
25 |
26 | @ColorInt fun colorOf(@ColorRes id: Int): Int = DialogUtils.getColor(id)
27 |
28 | fun stringOf(@StringRes id: Int): String = DialogUtils.getString(id)
29 |
30 | fun drawableOf(@DrawableRes id: Int): Drawable = DialogUtils.getDrawable(id)
31 |
32 | /** Make given view gone if satisfy given condition defined by [goneIf]. */
33 | fun BaseViewHolder.goneIf(@IdRes id: Int, goneIf: Boolean) {
34 | this.getView(id).visibility = if (goneIf) View.GONE else View.VISIBLE
35 | }
36 |
--------------------------------------------------------------------------------
/xdialog/src/main/java/me/shouheng/uix/widget/xDialog.kt:
--------------------------------------------------------------------------------
1 | package me.shouheng.uix.widget
2 |
3 | import android.app.Application
4 | import me.shouheng.uix.widget.utils.DialogUtils
5 |
6 | object xDialog {
7 |
8 | fun init(application: Application) {
9 | DialogUtils.init(application)
10 | }
11 | }
--------------------------------------------------------------------------------
/xdialog/src/main/res/anim/uix_dialog_alpha_enter.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
8 |
--------------------------------------------------------------------------------
/xdialog/src/main/res/anim/uix_dialog_alpha_exit.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
8 |
--------------------------------------------------------------------------------
/xdialog/src/main/res/anim/uix_dialog_translate_alpha_enter.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/xdialog/src/main/res/anim/uix_dialog_translate_alpha_exit.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/xdialog/src/main/res/anim/uix_dialog_translate_enter.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
--------------------------------------------------------------------------------
/xdialog/src/main/res/anim/uix_dialog_translate_enter_top.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
--------------------------------------------------------------------------------
/xdialog/src/main/res/anim/uix_dialog_translate_exit.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
--------------------------------------------------------------------------------
/xdialog/src/main/res/anim/uix_dialog_translate_exit_top.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
--------------------------------------------------------------------------------
/xdialog/src/main/res/anim/uix_loading.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
12 |
--------------------------------------------------------------------------------
/xdialog/src/main/res/drawable/ic_launcher_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
10 |
15 |
20 |
25 |
30 |
35 |
40 |
45 |
50 |
55 |
60 |
65 |
70 |
75 |
80 |
85 |
90 |
95 |
100 |
105 |
110 |
115 |
120 |
125 |
130 |
135 |
140 |
145 |
150 |
155 |
160 |
165 |
170 |
171 |
--------------------------------------------------------------------------------
/xdialog/src/main/res/drawable/uix_close_black_24dp.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/xdialog/src/main/res/drawable/uix_loading.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Shouheng88/xDialog/774e600a1dafb86365e2cc156c5901ad7d5b6d43/xdialog/src/main/res/drawable/uix_loading.png
--------------------------------------------------------------------------------
/xdialog/src/main/res/layout/uix_dialog_content_address_item.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/xdialog/src/main/res/layout/uix_dialog_content_edit_simple.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
16 |
17 |
25 |
26 |
34 |
35 |
--------------------------------------------------------------------------------
/xdialog/src/main/res/layout/uix_dialog_content_list_custom.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
14 |
15 |
20 |
21 |
--------------------------------------------------------------------------------
/xdialog/src/main/res/layout/uix_dialog_content_list_simple.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
14 |
15 |
--------------------------------------------------------------------------------
/xdialog/src/main/res/layout/uix_dialog_content_list_simple_item.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
14 |
15 |
25 |
26 |
35 |
36 |
44 |
45 |
46 |
47 |
--------------------------------------------------------------------------------
/xdialog/src/main/res/layout/uix_dialog_content_simple.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/xdialog/src/main/res/layout/uix_dialog_footer_simple.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
12 |
13 |
17 |
18 |
25 |
26 |
30 |
31 |
38 |
39 |
43 |
44 |
51 |
52 |
53 |
54 |
--------------------------------------------------------------------------------
/xdialog/src/main/res/layout/uix_dialog_layout.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
13 |
20 |
25 |
--------------------------------------------------------------------------------
/xdialog/src/main/res/layout/uix_dialog_title_simple.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/xdialog/src/main/res/layout/uix_layout_empty_view.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
14 |
15 |
24 |
25 |
30 |
31 |
35 |
36 |
43 |
44 |
45 |
46 |
55 |
56 |
62 |
63 |
71 |
72 |
79 |
80 |
81 |
82 |
83 |
84 |
--------------------------------------------------------------------------------
/xdialog/src/main/res/layout/uix_message_dialog.xml:
--------------------------------------------------------------------------------
1 |
2 |
16 |
17 |
23 |
24 |
29 |
30 |
42 |
43 |
--------------------------------------------------------------------------------
/xdialog/src/main/res/values/attrs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
--------------------------------------------------------------------------------
/xdialog/src/main/res/values/bools.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | true
4 |
--------------------------------------------------------------------------------
/xdialog/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #181818
4 | #F0f0f0
5 |
--------------------------------------------------------------------------------
/xdialog/src/main/res/values/configs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 180
4 | 200
5 |
--------------------------------------------------------------------------------
/xdialog/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | xdialog
3 |
--------------------------------------------------------------------------------
/xdialog/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
11 |
15 |
19 |
23 |
24 |
28 |
32 |
36 |
37 |
41 |
42 |
43 |
52 |
55 |
56 |
57 |
61 |
62 |
--------------------------------------------------------------------------------
/xdialog/src/main/res/values/themes.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
16 |
--------------------------------------------------------------------------------