├── .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 | 52 | 55 | 56 | 61 | 62 | -------------------------------------------------------------------------------- /xdialog/src/main/res/values/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16 | --------------------------------------------------------------------------------