Repository: DylanCaiCoding/LoadingStateView Branch: master Commit: a91fa3f3142b Files: 152 Total size: 264.8 KB Directory structure: gitextract_ifim0oyk/ ├── .gitignore ├── LICENSE ├── README.md ├── README_ZH.md ├── build.gradle ├── docs/ │ ├── .nojekyll │ ├── README.md │ ├── _sidebar.md │ ├── index.html │ └── zh/ │ ├── basic-usage.md │ ├── delegate.md │ ├── migration-guide.md │ ├── q&a.md │ └── viewbinding.md ├── gradle/ │ └── wrapper/ │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradle.properties ├── gradlew ├── gradlew.bat ├── loadingstateview/ │ ├── .gitignore │ ├── build.gradle │ ├── proguard-rules.pro │ └── src/ │ └── main/ │ ├── AndroidManifest.xml │ ├── java/ │ │ └── com/ │ │ └── dylanc/ │ │ └── loadingstateview/ │ │ └── LoadingStateView.kt │ └── res/ │ └── values/ │ └── strings.xml ├── loadingstateview-ktx/ │ ├── .gitignore │ ├── build.gradle │ ├── consumer-rules.pro │ ├── proguard-rules.pro │ └── src/ │ └── main/ │ ├── AndroidManifest.xml │ └── java/ │ └── com/ │ └── dylanc/ │ └── loadingstateview/ │ ├── BaseToolbarViewDelegate.kt │ ├── Decorative.kt │ ├── LoadingState.kt │ ├── LoadingStateDelegate.kt │ └── ToolbarConfig.kt ├── sample-java/ │ ├── .gitignore │ ├── build.gradle │ ├── proguard-rules.pro │ ├── release/ │ │ ├── app-release.apk │ │ └── output.json │ └── src/ │ ├── androidTest/ │ │ └── java/ │ │ └── com/ │ │ └── dylanc/ │ │ └── loadingstateview/ │ │ └── sample/ │ │ └── java/ │ │ └── ExampleInstrumentedTest.kt │ ├── main/ │ │ ├── AndroidManifest.xml │ │ ├── java/ │ │ │ └── com/ │ │ │ └── dylanc/ │ │ │ └── loadingstateview/ │ │ │ └── sample/ │ │ │ └── java/ │ │ │ ├── App.java │ │ │ ├── animation/ │ │ │ │ └── FadeAnimatable.java │ │ │ ├── base/ │ │ │ │ └── BaseActivity.java │ │ │ ├── delegate/ │ │ │ │ ├── BottomEditorDecorViewDelegate.java │ │ │ │ ├── CoolLoadingViewDelegate.java │ │ │ │ ├── CustomHeaderViewDelegate.java │ │ │ │ ├── EmptyViewDelegate.java │ │ │ │ ├── ErrorViewDelegate.java │ │ │ │ ├── LoadingViewDelegate.java │ │ │ │ ├── NavIconType.java │ │ │ │ ├── NothingViewDelegate.java │ │ │ │ ├── PlaceholderViewDelegate.java │ │ │ │ ├── ScrollingDecorViewDelegate.java │ │ │ │ ├── SearchHeaderViewDelegate.java │ │ │ │ ├── TimeoutViewDelegate.java │ │ │ │ └── ToolbarViewDelegate.java │ │ │ ├── ui/ │ │ │ │ ├── ActErrorActivity.java │ │ │ │ ├── BottomEditorActivity.java │ │ │ │ ├── CustomHeaderActivity.java │ │ │ │ ├── FragmentEmptyActivity.java │ │ │ │ ├── MainActivity.java │ │ │ │ ├── MultipleHeaderActivity.java │ │ │ │ ├── RecyclerViewActivity.java │ │ │ │ ├── ScrollingToolbarActivity.java │ │ │ │ ├── ViewPagerActivity.java │ │ │ │ ├── ViewPlaceholderActivity.java │ │ │ │ └── fragment/ │ │ │ │ ├── EmptyFragment.java │ │ │ │ ├── SimpleFragment.java │ │ │ │ └── TimeoutFragment.java │ │ │ ├── utils/ │ │ │ │ ├── DensityUtils.java │ │ │ │ ├── HttpUtils.java │ │ │ │ ├── KeyboardUtils.java │ │ │ │ └── ToolbarUtils.java │ │ │ └── widget/ │ │ │ ├── LoadingDrawable.java │ │ │ ├── LoadingRenderer.java │ │ │ ├── LoadingRendererFactory.java │ │ │ ├── LoadingView.java │ │ │ └── renderer/ │ │ │ └── ElectricFanLoadingRenderer.java │ │ └── res/ │ │ ├── drawable/ │ │ │ ├── bg_reload_btn.xml │ │ │ ├── bg_search.xml │ │ │ ├── ic_baseline_favorite_24.xml │ │ │ ├── ic_baseline_message_24.xml │ │ │ ├── ic_baseline_photo_camera_24.xml │ │ │ └── ic_launcher_background.xml │ │ ├── drawable-v24/ │ │ │ └── ic_launcher_foreground.xml │ │ ├── layout/ │ │ │ ├── activity_fragment.xml │ │ │ ├── activity_main.xml │ │ │ ├── activity_recycler_view.xml │ │ │ ├── activity_scrolling.xml │ │ │ ├── activity_tab_layout.xml │ │ │ ├── activity_view.xml │ │ │ ├── activity_view_pager.xml │ │ │ ├── layout_bottom_editor.xml │ │ │ ├── layout_content.xml │ │ │ ├── layout_cool_loading.xml │ │ │ ├── layout_custom_header.xml │ │ │ ├── layout_empty.xml │ │ │ ├── layout_error.xml │ │ │ ├── layout_loading.xml │ │ │ ├── layout_placeholder.xml │ │ │ ├── layout_scrolling_toolbar.xml │ │ │ ├── layout_search_header.xml │ │ │ ├── layout_timeout.xml │ │ │ ├── layout_toolbar.xml │ │ │ └── recycler_item_image.xml │ │ ├── menu/ │ │ │ └── menu_about.xml │ │ ├── mipmap-anydpi-v26/ │ │ │ ├── ic_launcher.xml │ │ │ └── ic_launcher_round.xml │ │ └── values/ │ │ ├── attrs.xml │ │ ├── colors.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test/ │ └── java/ │ └── com/ │ └── dylanc/ │ └── loadingstateview/ │ └── sample/ │ └── java/ │ └── ExampleUnitTest.kt ├── sample-kotlin/ │ ├── .gitignore │ ├── build.gradle │ ├── proguard-rules.pro │ └── src/ │ ├── androidTest/ │ │ └── java/ │ │ └── com/ │ │ └── dylanc/ │ │ └── loadingstateview/ │ │ └── sample/ │ │ └── kotlin/ │ │ └── ExampleInstrumentedTest.kt │ ├── main/ │ │ ├── AndroidManifest.xml │ │ ├── java/ │ │ │ └── com/ │ │ │ └── dylanc/ │ │ │ └── loadingstateview/ │ │ │ └── sample/ │ │ │ └── kotlin/ │ │ │ ├── App.kt │ │ │ ├── base/ │ │ │ │ ├── BaseActivity.kt │ │ │ │ ├── BaseBindingActivity.kt │ │ │ │ ├── BaseBindingFragment.kt │ │ │ │ └── BaseFragment.kt │ │ │ ├── delegate/ │ │ │ │ ├── EmptyViewDelegate.kt │ │ │ │ ├── ErrorViewDelegate.kt │ │ │ │ ├── FadeAnimatable.kt │ │ │ │ ├── LoadingViewDelegate.kt │ │ │ │ ├── ScrollingDecorViewDelegate.kt │ │ │ │ └── ToolbarViewDelegate.kt │ │ │ └── ui/ │ │ │ └── MainActivity.kt │ │ └── res/ │ │ ├── drawable/ │ │ │ ├── bg_reload_btn.xml │ │ │ ├── ic_add.xml │ │ │ ├── ic_arrow_back_ios.xml │ │ │ ├── ic_launcher_background.xml │ │ │ └── ic_refresh.xml │ │ ├── drawable-v24/ │ │ │ └── ic_launcher_foreground.xml │ │ ├── layout/ │ │ │ ├── activity_main.xml │ │ │ ├── layout_empty.xml │ │ │ ├── layout_error.xml │ │ │ ├── layout_loading.xml │ │ │ ├── layout_scrolling_toolbar.xml │ │ │ └── layout_toolbar.xml │ │ ├── mipmap-anydpi-v26/ │ │ │ ├── ic_launcher.xml │ │ │ └── ic_launcher_round.xml │ │ ├── values/ │ │ │ ├── colors.xml │ │ │ ├── strings.xml │ │ │ └── themes.xml │ │ └── values-night/ │ │ └── themes.xml │ └── test/ │ └── java/ │ └── com/ │ └── dylanc/ │ └── loadingstateview/ │ └── sample/ │ └── kotlin/ │ └── ExampleUnitTest.kt └── settings.gradle ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ *.iml .gradle .idea /local.properties /.idea/caches /.idea/libraries /.idea/modules.xml /.idea/workspace.xml /.idea/navEditor.xml /.idea/assetWizardSettings.xml .DS_Store /build /captures .externalNativeBuild ================================================ FILE: LICENSE ================================================ Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ================================================ FILE: README.md ================================================ # LoadingStateView English | [中文](README_ZH.md) [![](https://www.jitpack.io/v/DylanCaiCoding/LoadingStateView.svg)](https://www.jitpack.io/#DylanCaiCoding/LoadingLoadingStateView) [![License](https://img.shields.io/badge/License-Apache--2.0-blue.svg)](https://github.com/DylanCaiCoding/LoadingStateView/blob/master/LICENSE) `LoadingStateView` is a highly expandable Android library for showing loading status view on the low-coupling way, the core function is implemented with a [Kotlin file](https://github.com/DylanCaiCoding/LoadingStateView/blob/master/loadingstateview/src/main/java/com/dylanc/loadingstateview/LoadingStateView.kt) of over 200 lines. it not only shows different view like loading, content, error, empty and customized view when loading network data, but also manages title bar. **Major update: With the Kotlin feature, you can quickly add all functionality to the base class without affecting existing code. The overall usage is further simplified with removing the `ViewHolder`. It is recommended to upgrade!** ## Feature - No need to add view code to the layout. - Quickly add all functionality to the base class without affecting existing code. (Kotlin) - Support for use for Activity, Fragment, RecyclerView, View. - Support for show custom views. - Support for managing the title bar and add multiple headers. - Support for set reload event. - Support for update views anytime. - Support for use with most third-party libraries. ## Demo Click or scan QR code to download [![QR code](docs/img/app_download_qr_code.png)](https://www.pgyer.com/loadinghelper) | [Activity(error)](https://github.com/DylanCaiCoding/LoadingStateView/blob/master/sample-java/src/main/java/com/dylanc/loadingstateview/sample/java/ui/ActErrorActivity.java) | [View(placeholder)](https://github.com/DylanCaiCoding/LoadingStateView/blob/master/sample-java/src/main/java/com/dylanc/loadingstateview/sample/java/ui/ViewPlaceholderActivity.java) | [ViewPager(timeout)](https://github.com/DylanCaiCoding/LoadingStateView/blob/master/sample-java/src/main/java/com/dylanc/loadingstateview/sample/java/ui/ViewPagerActivity.java) | [RecyclerView(cool loading)](https://github.com/DylanCaiCoding/LoadingStateView/blob/master/sample-java/src/main/java/com/dylanc/loadingstateview/sample/java/ui/RecyclerViewActivity.java) | | :----------------------------------------------------------: | :----------------------------------------------------------: | :----------------------------------------------------------: | :----------------------------------------------------------: | | ![](docs/gif/activity_error.gif) | ![](docs/gif/view_placeholder.gif) | ![](docs/gif/viewpager_timeout.gif) | ![](docs/gif/recyclerview_loading.gif) | | [SpecialHeader(custom)](https://github.com/DylanCaiCoding/LoadingStateView/blob/master/sample-java/src/main/java/com/dylanc/loadingstateview/sample/java/ui/CustomHeaderActivity.java) | [MultipleHeader(search)](https://github.com/DylanCaiCoding/LoadingStateView/blob/master/sample-java/src/main/java/com/dylanc/loadingstateview/sample/java/ui/MultipleHeaderActivity.java) | [SpecialDecorView(scrolling)](https://github.com/DylanCaiCoding/LoadingStateView/blob/master/sample-java/src/main/java/com/dylanc/loadingstateview/sample/java/ui/ScrollingToolbarActivity.java) | [BottomDecorView(editor)](https://github.com/DylanCaiCoding/LoadingStateView/blob/master/sample-java/src/main/java/com/dylanc/loadingstateview/sample/java/ui/BottomEditorActivity.java) | | :----------------------------------------------------------: | :----------------------------------------------------------: | :----------------------------------------------------------: | :----------------------------------------------------------: | | ![](docs/gif/special_header_custom.gif) | ![](docs/gif/multiple_header_search.gif) | ![](docs/gif/special_decor_scrolling.gif) | ![](docs/gif/bottom_decor_editor.gif) | ## Usage :pencil: **[>> Usage documentation <<](https://dylancaicoding.github.io/LoadingStateView)** ## Gradle Add it in your root `build.gradle` at the end of repositories: ```groovy allprojects { repositories { // ... maven { url 'https://www.jitpack.io' } } } ``` Add dependencies in your module `build.gradle` : ```groovy dependencies { // java implementation 'com.github.DylanCaiCoding.LoadingStateView:loadingstateview:5.0.0' // kotlin implementation 'com.github.DylanCaiCoding.LoadingStateView:loadingstateview-ktx:5.0.0' } ``` ## Change log [Releases](https://github.com/DylanCaiCoding/LoadingStateView/releases) ## Author's other libraries | Library | Description | | ------------------------------------------------------------ | ------------------------------------------------------------ | | [Longan](https://github.com/DylanCaiCoding/Longan) | Probably the best Kotlin utils library for Android. | | [ViewBindingKTX](https://github.com/DylanCaiCoding/ViewBindingKTX) | The most comprehensive utils of ViewBinding. | | [MMKV-KTX](https://github.com/DylanCaiCoding/MMKV-KTX) | Use MMKV with property delegates. | | [MultiBaseUrls](https://github.com/DylanCaiCoding/MultiBaseUrls) | Use annotation to allow Retrofit to support multiple baseUrl and dynamically change baseUrl | | [Tracker](https://github.com/DylanCaiCoding/Tracker) | A lightweight tracking framework based on the tracking idea of Buzzvideo.| ## Thanks - [luckbilly/Gloading](https://github.com/luckybilly/Gloading) Optimize my library standing on the shoulders of giants. - [drakeet/MultiType](https://github.com/drakeet/MultiType) Referenced the usage of multiple adapters. - [dinuscxj/LoadingDrawable](https://github.com/dinuscxj/LoadingDrawable) The cool loading effect in the demo. ## License ``` Copyright (C) 2019. Dylan Cai Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ``` ================================================ FILE: README_ZH.md ================================================ # LoadingStateView [English](README.md) | 中文 [![](https://www.jitpack.io/v/DylanCaiCoding/LoadingStateView.svg)](https://www.jitpack.io/#DylanCaiCoding/LoadingLoadingStateView) [![License](https://img.shields.io/badge/License-Apache--2.0-blue.svg)](https://github.com/DylanCaiCoding/LoadingStateView/blob/master/LICENSE) `LoadingStateView` 是一个深度解耦缺省页和标题栏的工具,核心功能的实现代码只有一个 200 多行的 [Kotlin 文件](https://github.com/DylanCaiCoding/LoadingStateView/blob/master/loadingstateview/src/main/java/com/dylanc/loadingstateview/LoadingStateView.kt)。不仅能在请求网络数据时显示加载中、加载成功、加载失败、无数据的视图或自定义视图,还可以对标题栏进行解耦。 **重大更新:结合 Kotlin 语法特性能快速将所有功能集成到基类,不会影响已有代码。移除了 `ViewHolder`,整体用法得到进一步简化,建议升级!** - 无需在布局添加视图代码 - 支持快速集成到基类,并且不影响有代码(仅 Kotlin) - 支持 Activity、Fragment、列表或指定的 View - 支持显示自定义视图 - 支持添加多个头部控件 - 支持设置重新请求数据的事件 - 支持动态更新视图样式 - 可结合绝大部分第三方控件使用 ## 示例 点击或者扫描二维码下载 [![QR code](docs/img/app_download_qr_code.png)](https://www.pgyer.com/loadinghelper) 动态添加加载状态的布局: | [Activity(error)](https://github.com/DylanCaiCoding/LoadingStateView/blob/master/sample-java/src/main/java/com/dylanc/loadingstateview/sample/java/ui/ActErrorActivity.java) | [View(placeholder)](https://github.com/DylanCaiCoding/LoadingStateView/blob/master/sample-java/src/main/java/com/dylanc/loadingstateview/sample/java/ui/ViewPlaceholderActivity.java) | [ViewPager(timeout)](https://github.com/DylanCaiCoding/LoadingStateView/blob/master/sample-java/src/main/java/com/dylanc/loadingstateview/sample/java/ui/ViewPagerActivity.java) | [RecyclerView(cool loading)](https://github.com/DylanCaiCoding/LoadingStateView/blob/master/sample-java/src/main/java/com/dylanc/loadingstateview/sample/java/ui/RecyclerViewActivity.java) | | :----------------------------------------------------------: | :----------------------------------------------------------: | :----------------------------------------------------------: | :----------------------------------------------------------: | | ![](docs/gif/activity_error.gif) | ![](docs/gif/view_placeholder.gif) | ![](docs/gif/viewpager_timeout.gif) | ![](docs/gif/recyclerview_loading.gif) | 动态添加标题栏或装饰容器: | [SpecialHeader(custom)](https://github.com/DylanCaiCoding/LoadingStateView/blob/master/sample-java/src/main/java/com/dylanc/loadingstateview/sample/java/ui/CustomHeaderActivity.java) | [MultipleHeader(search)](https://github.com/DylanCaiCoding/LoadingStateView/blob/master/sample-java/src/main/java/com/dylanc/loadingstateview/sample/java/ui/MultipleHeaderActivity.java) | [SpecialDecorView(scrolling)](https://github.com/DylanCaiCoding/LoadingStateView/blob/master/sample-java/src/main/java/com/dylanc/loadingstateview/sample/java/ui/ScrollingToolbarActivity.java) | [BottomDecorView(editor)](https://github.com/DylanCaiCoding/LoadingStateView/blob/master/sample-java/src/main/java/com/dylanc/loadingstateview/sample/java/ui/BottomEditorActivity.java) | | :----------------------------------------------------------: | :----------------------------------------------------------: | :----------------------------------------------------------: | :----------------------------------------------------------: | | ![](docs/gif/special_header_custom.gif) | ![](docs/gif/multiple_header_search.gif) | ![](docs/gif/special_decor_scrolling.gif) | ![](docs/gif/bottom_decor_editor.gif) | ## 用法 :pencil: **[>> 使用文档 <<](https://dylancaicoding.github.io/LoadingStateView)** ## Gradle 在根目录的 `build.gradle` 添加: ```groovy allprojects { repositories { // ... maven { url 'https://www.jitpack.io' } } } ``` 在模块的 `build.gradle` 添加依赖: ```groovy dependencies { // java implementation 'com.github.DylanCaiCoding.LoadingStateView:loadingstateview:5.0.0' // kotlin implementation 'com.github.DylanCaiCoding.LoadingStateView:loadingstateview-ktx:5.0.0' } ``` ## 更新日志 [Releases](https://github.com/DylanCaiCoding/LoadingStateView/releases) ## 作者其它的库 | 库 | 简介 | | ------------------------------------------------------------ | ---------------------------------------------- | | [Longan](https://github.com/DylanCaiCoding/Longan) | 可能是最好用的 Kotlin 工具库 | | [ViewBindingKTX](https://github.com/DylanCaiCoding/ViewBindingKTX) | 最全面的 ViewBinding 工具 | | [MMKV-KTX](https://github.com/DylanCaiCoding/MMKV-KTX) | 最灵活易用的 MMKV 工具 | | [MultiBaseUrls](https://github.com/DylanCaiCoding/MultiBaseUrls) | 用注解让 Retrofit 同时支持多个 baseUrl 以及动态改变 baseUrl | | [Tracker](https://github.com/DylanCaiCoding/Tracker) | 基于西瓜视频的责任链埋点思路实现的轻量级埋点框架 | ## 感谢 - [luckbilly/Gloading](https://github.com/luckybilly/Gloading) 站在了巨人肩膀上优化了本库,非常感谢! - [drakeet/MultiType](https://github.com/drakeet/MultiType) 参考了注册配置多适配器的思想和用法 - [dinuscxj/LoadingDrawable](https://github.com/dinuscxj/LoadingDrawable) 示例中的自定义加载动画 ## License ``` Copyright (C) 2019. Dylan Cai Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ``` ================================================ FILE: build.gradle ================================================ buildscript { ext.buildConfig = [ 'versionCode' : 1, 'versionName' : "1.0.0", 'compileSdkVersion': 30, 'minSdkVersion' : 14, 'targetSdkVersion' : 30 ] ext { appCompatVersion = '1.3.1' constraintLayoutVersion = '2.1.1' coreVersion = '1.7.0-alpha01' espressoVersion = '3.4.0' glideVersion = '4.12.0' kotlinVersion = "1.5.31" lifecycleVersion = '2.4.0-alpha03' junitExtVersion = '1.1.3' junitVersion = '4.13.2' materialVersion = '1.4.0' viewBindingKTXVersion = '2.1.0' } repositories { google() jcenter() } dependencies { classpath 'com.android.tools.build:gradle:4.2.1' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion" } } allprojects { repositories { google() jcenter() maven { url 'https://www.jitpack.io' } } } task clean(type: Delete) { delete rootProject.buildDir } ================================================ FILE: docs/.nojekyll ================================================ ================================================ FILE: docs/README.md ================================================ # LoadingStateView [![](https://www.jitpack.io/v/DylanCaiCoding/LoadingStateView.svg)](https://www.jitpack.io/#DylanCaiCoding/LoadingLoadingStateView) [![License](https://img.shields.io/badge/License-Apache--2.0-blue.svg)](https://github.com/DylanCaiCoding/LoadingStateView/blob/master/LICENSE) [![GitHub Repo stars](https://img.shields.io/github/stars/DylanCaiCoding/LoadingStateView?style=social)](https://github.com/DylanCaiCoding/LoadingStateView) `LoadingStateView` 是一个深度解耦缺省页和标题栏的工具,核心功能的实现代码只有一个 200 行左右(不算注释)的 [Kotlin 文件](https://github.com/DylanCaiCoding/LoadingStateView/blob/master/loadingstateview/src/main/java/com/dylanc/loadingstateview/LoadingStateView.kt)。不仅能在请求网络数据时显示加载中、加载成功、加载失败、无数据的视图或自定义视图,还可以对标题栏进行解耦。 **重大更新:结合 Kotlin 语法特性能快速将所有功能集成到基类,不会影响已有代码。移除了 `ViewHolder`,整体用法得到进一步简化,建议升级!** - 无需在布局添加视图代码 - 支持快速集成到基类,并且不影响有代码(仅 Kotlin) - 支持 Activity、Fragment、列表或指定的 View - 支持显示自定义视图 - 支持添加多个头部控件 - 支持设置重新请求数据的事件 - 支持动态更新视图样式 - 可结合绝大部分第三方控件使用 ## 示例 点击或者扫描二维码下载 [![QR code](img/app_download_qr_code.png)](https://www.pgyer.com/loadinghelper) 动态添加加载状态的布局: | [Activity(error)](https://github.com/DylanCaiCoding/LoadingStateView/blob/master/sample-java/src/main/java/com/dylanc/loadingstateview/sample/java/ui/ActErrorActivity.java) | [View(placeholder)](https://github.com/DylanCaiCoding/LoadingStateView/blob/master/sample-java/src/main/java/com/dylanc/loadingstateview/sample/java/ui/ViewPlaceholderActivity.java) | [ViewPager(timeout)](https://github.com/DylanCaiCoding/LoadingStateView/blob/master/sample-java/src/main/java/com/dylanc/loadingstateview/sample/java/ui/ViewPagerActivity.java) | [RecyclerView(cool loading)](https://github.com/DylanCaiCoding/LoadingStateView/blob/master/sample-java/src/main/java/com/dylanc/loadingstateview/sample/java/ui/RecyclerViewActivity.java) | | :----------------------------------------------------------: | :----------------------------------------------------------: | :----------------------------------------------------------: | :----------------------------------------------------------: | | ![](gif/activity_error.gif) | ![](gif/view_placeholder.gif) | ![](gif/viewpager_timeout.gif) | ![](gif/recyclerview_loading.gif) | 动态添加标题栏或装饰容器: | [SpecialHeader(custom)](https://github.com/DylanCaiCoding/LoadingStateView/blob/master/sample-java/src/main/java/com/dylanc/loadingstateview/sample/java/ui/CustomHeaderActivity.java) | [MultipleHeader(search)](https://github.com/DylanCaiCoding/LoadingStateView/blob/master/sample-java/src/main/java/com/dylanc/loadingstateview/sample/java/ui/MultipleHeaderActivity.java) | [SpecialDecorView(scrolling)](https://github.com/DylanCaiCoding/LoadingStateView/blob/master/sample-java/src/main/java/com/dylanc/loadingstateview/sample/java/ui/ScrollingToolbarActivity.java) | [BottomDecorView(editor)](https://github.com/DylanCaiCoding/LoadingStateView/blob/master/sample-java/src/main/java/com/dylanc/loadingstateview/sample/java/ui/BottomEditorActivity.java) | | :----------------------------------------------------------: | :----------------------------------------------------------: | :----------------------------------------------------------: | :----------------------------------------------------------: | | ![](gif/special_header_custom.gif) | ![](gif/multiple_header_search.gif) | ![](gif/special_decor_scrolling.gif) | ![](gif/bottom_decor_editor.gif) | ## Gradle 在根目录的 `build.gradle` 添加: ```groovy allprojects { repositories { // ... maven { url 'https://www.jitpack.io' } } } ``` 在模块的 `build.gradle` 添加依赖: ```groovy dependencies { // java implementation 'com.github.DylanCaiCoding.LoadingStateView:loadingstateview:5.0.0' // kotlin implementation 'com.github.DylanCaiCoding.LoadingStateView:loadingstateview-ktx:5.0.0' } ``` ## 更新日志 [Releases](https://github.com/DylanCaiCoding/LoadingStateView/releases) ## 作者其它的库 | 库 | 简介 | | ------------------------------------------------------------ | ---------------------------------------------- | | [Longan](https://github.com/DylanCaiCoding/Longan) | 可能是最好用的 Kotlin 工具库 | | [ViewBindingKTX](https://github.com/DylanCaiCoding/ViewBindingKTX) | 最全面的 ViewBinding 工具 | | [MMKV-KTX](https://github.com/DylanCaiCoding/MMKV-KTX) | 用属性委托的方式使用 MMKV | | [Tracker](https://github.com/DylanCaiCoding/Tracker) | 基于西瓜视频的责任链埋点思路实现的轻量级埋点框架 | ## 感谢 - [luckbilly/Gloading](https://github.com/luckybilly/Gloading) 站在了巨人肩膀上优化了本库,非常感谢! - [drakeet/MultiType](https://github.com/drakeet/MultiType) 参考了注册配置多适配器的思想和用法 - [dinuscxj/LoadingDrawable](https://github.com/dinuscxj/LoadingDrawable) 示例中的自定义加载动画 ## License ``` Copyright (C) 2019. Dylan Cai Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ``` ================================================ FILE: docs/_sidebar.md ================================================ * [介绍](/) * [基础用法](/zh/basic-usage) * [Kotlin 委托用法](/zh/delegate) * [结合 ViewBinding 使用](/zh/viewbinding) * [老版本迁移指南](/zh/migration-guide) * [Q&A](/zh/q&a) ================================================ FILE: docs/index.html ================================================ Document
================================================ FILE: docs/zh/basic-usage.md ================================================ # 基础用法 使用 Kotlin 开发推荐用[委托用法](/zh/delegate)。 ## 显示加载中、加载失败等缺省页 第一步,使用 Activity 或 View 创建 `LoadingStateView`,不需要重新加载的话 `OnReloadListener` 可不传。 #### **Kotlin** ```kotlin val loadingStateView = LoadingStateView(this, onReloadListener) // val loadingStateView = LoadingStateView(view, onReloadListener) ``` #### **Java** ```java LoadingStateView loadingStateView = new LoadingStateView(this, onReloadListener); // LoadingStateView loadingStateView = new LoadingStateView(view, onReloadListener); ``` 第二步,创建一个类继承 `LoadingStateView.ViewDelegate`。 构造函数需要传个参数,决定视图类型,之后显示和更新会用到。默认提供了 `ViewType.LOADING`、`ViewType.ERROR`、`ViewType.EMPTY`、`ViewType.TITLE`,其它的可以用一个 String 作为类型。 #### **Kotlin** ```kotlin class LoadingViewDelegate : LoadingStateView.ViewDelegate(ViewType.LOADING) { override fun onCreateView(inflater: LayoutInflater, parent: ViewGroup): View = inflater.inflate(R.layout.layout_loading, parent, false) } ``` #### **Java** ```java public class LoadingViewDelegate extends LoadingStateView.ViewDelegate { public LoadingViewDelegate() { super(ViewType.LOADING); } @NonNull @Override public View onCreateView(@NonNull LayoutInflater inflater, @NonNull ViewGroup parent) { return inflater.inflate(R.layout.layout_loading, parent, false); } } ``` 如果需要实现点击重新请求数据,可以在点击事件调用 `onReloadListener?.onReload()` 方法。 第三步,注册 `ViewDelegate`。首先在 Application 注册全局的 `ViewDelegate`: #### **Kotlin** ```kotlin LoadingStateView.setViewDelegatePool { register(LoadingViewDelegate(), ErrorViewDelegate(), EmptyViewDelegate()) } ``` #### **Java** ```java LoadingStateView.setViewDelegatePool(pool -> pool.register(new LoadingViewDelegate(), new ErrorViewDelegate(), new EmptyViewDelegate())); ``` 如果某个页面需要不同样式,就用该页面的 `LoadingStateView` 注册对应类型的 `ViewDelegate`,就可以把全局的替换了。 #### **Kotlin** ```kotlin loadingStateView.register(CoolLoadingViewDelegate()) ``` #### **Java** ```java loadingStateView.register(new CoolLoadingViewDelegate()); ``` 第四步,显示对应类型的视图。 #### **Kotlin** ```kotlin loadingStateView.showView(viewType) loadingStateView.showLoadingView() // 显示 ViewType.LOADING 类型的视图 loadingStateView.showContentView() // 显示 ViewType.CONTENT 类型的视图 loadingStateView.showErrorView() // 显示 ViewType.ERROR 类型的视图 loadingStateView.showEmptyView() // 显示 ViewType.EMPTY 类型的视图 ``` #### **Java** ```java loadingStateView.showView(viewType); loadingStateView.showLoadingView(); // 对应视图类型 ViewType.LOADING loadingStateView.showContentView(); // 对应视图类型 ViewType.CONTENT loadingStateView.showErrorView(); // 对应视图类型 ViewType.ERROR loadingStateView.showEmptyView(); // 对应视图类型 ViewType.EMPTY ``` ## 更新视图样式 需要在 `ViewDelegate` 自行增加更新的方法,然后更新对应的 `ViewDelegate`。比如在 `ErrorViewDelegate` 增加了 `updateMsg(msg)` 方法修改请求失败的文字: #### **Kotlin** ```kotlin loadingStateView.updateViewDelegate(ViewType.ERROR) { updateMsg("服务器繁忙,请稍后重试") } ``` #### **Java** ```java loadingStateView.updateViewDelegate(ViewType.ERROR, (ErrorViewDelegate delegate) -> delegate.updateMsg("服务器繁忙,请稍后重试")); ``` ## 在内容上方添加视图 实现标题栏或搜索栏等 `ViewDelegate`,之后就可以用 `LoadingStateView` 在内容布局的上方添加视图。 #### **Kotlin** ```kotlin loadingStateView.setHeaders(ToolbarViewDelegate("消息"), SearchViewDelegate()) ``` #### **Java** ```java loadingStateView.setHeaders(new ToolbarViewDelegate("消息"), new SearchViewDelegate()); ``` ## 给内容增加装饰 可以给内容布局外层添加一层装饰,实现更复杂的样式,非简单地在内容上方增加控件,比如带联动效果的标题栏、DrawerLayout、底部输入框等布局。 接下来以滑动隐藏标题栏的效果为例,先写一个带联动效果的标题栏布局,其中有个 FragmentLayout 是用于填充内容和显示缺省页。 ```xml ``` 然后写一个类继承 `LoadingStateView.DecorViewDelegate`。 #### **Kotlin** ```kotlin class ScrollingDecorViewDelegate( private val activity: Activity, private val title: String ) : LoadingStateView.DecorViewDelegate() { override fun onCreateDecorView(context: Context, inflater: LayoutInflater): View { val view = inflater.inflate(R.layout.layout_scrolling_toolbar, null) val toolbar: Toolbar = view.findViewById(R.id.toolbar) toolbar.title = title toolbar.setNavigationOnClickListener { activity.finish() } return view } override fun getContentParent(decorView: View): ViewGroup { return decorView.findViewById(R.id.content_parent) } } ``` #### **Java** ```java public class ScrollingDecorViewDelegate extends LoadingStateView.DecorViewDelegate { private final Activity activity; private final String title; public ScrollingDecorViewDelegate(Activity activity, String title) { this.activity = activity; this.title = title; } @NotNull @Override public View onCreateDecorView(@NonNull Context context, @NotNull LayoutInflater inflater) { View view = inflater.inflate(R.layout.layout_scrolling_toolbar, null); Toolbar toolbar = view.findViewById(R.id.toolbar); toolbar.setTitle(title); toolbar.setNavigationOnClickListener(v -> activity.finish()); return view; } @NotNull @Override public ViewGroup getContentParent(@NotNull View decorView) { return decorView.findViewById(R.id.content_parent); } } ``` 与 `ViewDelegate` 不同的是,需要多实现一个 `getContentParent(decorView)` 方法来指定添加内容的容器,这里我们返回前面的 FrameLayout。 之后就可以给内容进行装饰了。 #### **Kotlin** ```kotlin loadingStateView.setDecorView(ScrollingDecorViewDelegate(this, "title")) ``` #### **Java** ```java loadingStateView.setDecorView(new ScrollingDecorViewDelegate(this, "title")); ``` ================================================ FILE: docs/zh/delegate.md ================================================ # Kotlin 委托用法 ## 准备工作 需要修改基类,只需简单的两步就可以把本库的功能集成到基类,并且不会影响到已有的代码,只是给基类扩展了新的方法。 注意添加的依赖需要是 `loadingstateview-ktx`: ```groovy dependencies { implementation 'com.github.DylanCaiCoding.LoadingStateView:loadingstateview-ktx:5.0.0' } ``` 修改步骤如下: 1. 实现 `LoadingState by LoadingStateDelegate()` 。 2. 在 Activity 的 `setContentView()` 方法后执行 `decorateContentView(this)`。在 Fragment 的 `onCreateView()` 返回 `view.decorate(this)`。 #### **Activity** ![img.png](../img/base_activity_code.png)
查看代码 ```kotlin abstract class BaseActivity(private val layoutRes: Int) : AppCompatActivity(), LoadingState by LoadingStateDelegate() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(layoutRes) decorateContentView(this) } } ```
#### **Fragment** ![img.png](../img/base_fragment_code.png)
查看代码 ```kotlin abstract class BaseFragment(private val layoutRes: Int) : Fragment(), LoadingState by LoadingStateDelegate() { override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View? { val root = inflater.inflate(layoutRes, container, false) return root.decorate(this) } } ```
这样改造基类后会得到以下的增强: - 在不影响已有代码的情况下,增加了 [LoadingState](https://github.com/DylanCaiCoding/LoadingStateView/blob/master/loadingstateview-ktx/src/main/java/com/dylanc/loadingstateview/LoadingState.kt) 接口提供的常用方法,该接口包含了 `LoadingStateView` 所有功能。 - 如果担心对基类有什么影响,在页面重写 `override val isDecorated = false` 会把一切还原,即使调用了新增的接口方法也不会生效,请放心使用。 ## 显示缺省页 先注册各类型缺省页的样式,之后才能调用对应的 `showView()` 方法。 创建类继承 `LoadingStateView.ViewDelegate`,构造函数传个视图类型参数,默认提供了 `ViewType.LOADING`、`ViewType.ERROR`、`ViewType.EMPTY`。 #### **ViewType.LOADING** ```kotlin class LoadingViewDelegate : LoadingStateView.ViewDelegate(ViewType.LOADING) { override fun onCreateView(inflater: LayoutInflater, parent: ViewGroup): View = inflater.inflate(R.layout.layout_loading, parent, false) } ``` #### **ViewType.ERROR** ```kotlin class ErrorViewDelegate : LoadingStateView.ViewDelegate(ViewType.ERROR) { override fun onCreateView(inflater: LayoutInflater, parent: ViewGroup): View = inflater.inflate(R.layout.layout_error, parent, false).apply { findViewById(R.id.btn_reload).setOnClickListener { onReloadListener?.onReload() } } } ``` #### **ViewType.EMPTY** ```kotlin class EmptyViewDelegate : LoadingStateView.ViewDelegate(ViewType.EMPTY) { override fun onCreateView(inflater: LayoutInflater, parent: ViewGroup): View = inflater.inflate(R.layout.layout_empty, parent, false) } ``` 在 Application 注册全局的 `ViewDelegate`。 ```kotlin LoadingStateView.setViewDelegatePool { register(LoadingViewDelegate(), ErrorViewDelegate(), EmptyViewDelegate()) } ``` 在实现了基类的 `Activity` 或 `Fragment` 可以调用对应的 `showView()` 方法。 ```kotlin showLoadingView() // 显示 ViewType.LOADING 类型的视图 showErrorView() // 显示 ViewType.ERROR 类型的视图 showEmptyView() // 显示 ViewType.EMPTY 类型的视图 showContentView() // 显示原来的内容视图 showView(viewType) // 显示自定义类型的视图 ``` 如果需要实现点击重新加载,就在基类重写 `onReload()` 方法。 如果某个页面需要显示不同的缺省页,可以在显示前调用一下 `registerView(viewDelegate)` 方法覆盖默认的样式。比如: ```kotlin registerView(CoolLoadingViewDelegate()) showLoadingView() ``` 如果需要动态更新某个样式,在 `ViewDelegate` 自行增加更新的方法,比如在 `ErrorViewDelegate` 增加了 `updateMsg(msg)` 方法修改请求失败的文字,然后就能更新了。 ```kotlin updateView(ViewType.ERROR) { updateMsg("服务器繁忙,请稍后重试") } ``` ## 设置标题栏 先注册标题栏样式,之后才能调用 `setToolbar()` 方法。 创建一个类继承 `BaseToolbarViewDelegate`,通常项目都有各自的标题栏封装,我们能基于已有的标题栏布局或者自定义的标题栏控件实现 `ToolbarViewDelegate`。比如: ```kotlin class ToolbarViewDelegate : BaseToolbarViewDelegate() { private lateinit var tvTitle: TextView private lateinit var ivLeft: ImageView private lateinit var ivRight: ImageView override fun onCreateToolbar(inflater: LayoutInflater, parent: ViewGroup): View { val view = inflater.inflate(R.layout.layout_toolbar, parent, false) tvTitle = view.findViewById(R.id.tv_title) ivLeft = view.findViewById(R.id.iv_left) ivRight = view.findViewById(R.id.iv_right) return view } override fun onBindToolbar(config: ToolbarConfig) { tvTitle.text = config.title if (config.navBtnType == NavBtnType.NONE) { ivLeft.visibility = View.GONE } else { ivLeft.setOnClickListener(config.onNavClickListener) ivLeft.visibility = View.VISIBLE } if (config.rightIcon != null) { ivRight.setImageResource(config.rightIcon!!) ivRight.setOnClickListener(config.onRightClickListener) ivRight.visibility = View.VISIBLE } } } ``` `ToolbarConfig` 提供了几个常用的属性。可以根据需要选择处理,比如上述例子只实现了有无返回键和右侧按钮的逻辑,项目中有功能相对完整的[示例代码](https://github.com/DylanCaiCoding/LoadingStateView/blob/master/sample-kotlin/src/main/java/com/dylanc/loadingstateview/sample/kotlin/delegate/ToolbarViewDelegate.kt)。 | 属性 | 含义 | | -------------------- | -------------------- | | title | 标题 | | navBtnType | 导航 (左侧) 按钮类型 | | navIcon | 导航 (左侧) 图标 | | navText | 导航 (左侧) 文字 | | onNavClickListener | 导航 (左侧) 按钮点击事件 | | rightIcon | 右侧图标 | | rightText | 右侧文字 | | onRightClickListener | 右侧按钮点击事件 | `onNavClickListener` 默认执行 `finish()` 操作。`navBtnType` 默认类型是 `NavBtnType.ICON`,还有 `NavBtnType.NONE`、`NavBtnType.TEXT`、`NavBtnType.ICON_TEXT`类型。其它的属性默认为空,为空的时候不用处理使用默认样式即可。 当然这点属性肯定不能满足所有的需求,所以本库支持给 `ToolbarConfig` 增加扩展属性。比如需要动态修改右侧文字颜色: ```kotlin var ToolbarConfig.rightTextColor: Int? by toolbarExtras() // 增加 rightTextColor 扩展属性 class ToolbarViewDelegate : BaseToolbarViewDelegate() { // ... override fun onBindToolbar(config: ToolbarConfig) { // ... config.rightTextColor?.let { tvRight.setTextColor(it) } // 处理扩展属性 } } ``` 在 Application 注册全局的标题栏 `ViewDelegate`。 ```kotlin LoadingStateView.setViewDelegatePool { register(ToolbarViewDelegate(), // ... ) } ``` 之后就能在实现了基类的 `Activity` 或 `Fragment` 设置标题栏了。 ```kotlin setToolbar() // 默认有返回键 setToolbar("title") // 有标题和返回键 setToolbar("title", NavBtnType.NONE) // 只有标题,无返回键 setToolbar("title") { // 以下可选 navIcon = R.drawable.account // 只修改返回键图标 navIcon { ... } // 只修改返回键的点击事件 navIcon(R.drawable.message) { ... } // 修改返回键的图标和点击事件 rightIcon(R.drawable.add) { ... } // 添加右侧图标 rightText("Delete") { ... } // 添加右侧文字 rightTextColor = Color.RED // 新增的扩展属性,修改右侧文字颜色 } ``` 这样就多了一种添加标题栏的方式,新写的代码可以用上述的方式添加标题栏,老的代码保留已有的 `` 布局或者自定义标题栏控件的用法。样式都是一样的,因为是基于已有标题栏实现的。 如果某个页面的标题栏样式变动很大,不建议写太多扩展属性来配置,这样代码阅读性也差。推荐用新布局再写一个 `BaseToolbarViewDelegate` 的实现类,在设置标题栏之前注册,这会覆盖掉默认的样式。 ```kotlin registerView(SpecialToolbarViewDelegate()) setToolbar("title") ``` 如果需要动态更新标题栏样式: ```kotlin updateToolbar { title = "Loading..." } ``` 如果有扩展属性不好配置的需求,比如开启或关闭动画,可以在 `ToolbarViewDelegate` 自行增加对应功能的函数,然后在更新的时候调用。 ```kotlin updateView(ViewType.TITLE) { startAnimation() } ``` ## 在顶部添加控件 可添加多个控件,比如添加标题栏和搜索栏,搜索栏需要另写一个类继承 `LoadingStateView.ViewDelegate`。 ```kotlin setHeaders( ToolbarViewDelegate("Search") { rightIcon(R.drawable.more) { ... } }, SearchViewDelegate(onSearchListener) ) ``` ## 给内容增加装饰 可以给内容布局再套上一层装饰,实现更复杂的样式,非简单地在顶部增加控件,比如带联动效果的标题栏、DrawerLayout、底部输入框等布局。 接下来以滑动隐藏标题栏的效果为例,先写一个带联动效果的标题栏布局,其中有个 FragmentLayout 是用于填充内容和显示缺省页。 ```xml ``` 然后写一个类继承 `LoadingStateView.DecorViewDelegate`。 ```kotlin class ScrollingDecorViewDelegate( private val activity: Activity, private val title: String ) : LoadingStateView.DecorViewDelegate() { override fun onCreateDecorView(context: Context, inflater: LayoutInflater): View { val view = inflater.inflate(R.layout.layout_scrolling_toolbar, null) val toolbar: Toolbar = view.findViewById(R.id.toolbar) toolbar.title = title toolbar.setNavigationOnClickListener { activity.finish() } return view } override fun getContentParent(decorView: View): ViewGroup { return decorView.findViewById(R.id.content_parent) } } ``` 与 `LoadingStateView.ViewDelegate` 不同的是,需要多实现一个 `getContentParent(decorView)` 方法来指定添加内容的容器,这里我们返回前面的 FrameLayout。 之后就可以给内容进行装饰了。 ```kotlin setDecorView(ScrollingDecorViewDelegate(this, "title")) ``` ================================================ FILE: docs/zh/migration-guide.md ================================================ # 老版本升级指南 `4.0.1` 版本对用法进行了优化,移除了 `ViewHolder`,使用起来更加简单,不过就意味着不能兼容之前的代码。建议根据[文档](/zh/basic-usage)改成新的用法,通常是封装在基类,要改的不多,`ViewHolder` 的代码需要移到 `onCreateView()` 中。 但是如果报红的地方特别多,个人有一个折中的方案。由于之前版本只有一个两三百行的 kt 文件,那么可以把源码拷贝出来并删除依赖,也就是说改成源码的方式集成,全局改一下相关包名。然后就可以添加新版本依赖进行使用,再慢慢将老用法改成新用法。 ================================================ FILE: docs/zh/q&a.md ================================================ # Q&A ## 为什么不分成标题栏库和缺省页库? 个人深思熟虑了非常久才决定写成一个库,有以下考虑: - 支持给内容和缺省页添加头部,所以具有管理标题栏的应用场景,感觉没什么不妥。 - 大多数情况下标题栏和缺省页关联性很强,因为缺省页往往是要在标题栏下方显示,如果分成两个库就经常需要调用两个工具类,使用起来更加麻烦。 - 分成两个库可能会多一层无意义的布局嵌套。 - 即使写在一起,核心功能的实现类才 200 多行代码,还要啥自行车。由于适配器和 View 的缓存代码能复用,在解耦缺省页后,仅加多几十行代码就能把标题栏给一起解耦了,何乐而不为。 ## 可以在布局上预览标题栏吗? 不能,这可能是本库唯一的缺点,但是本库解耦标题栏的收益远大于在布局上预览标题栏的收益。 ================================================ FILE: docs/zh/viewbinding.md ================================================ # 结合 ViewBinding 使用 ## 基础用法 如果要同时使用 `LoadingStateView` 和 `ViewBinding`,需要先得到对应的 `ViewBinding` 实例,再用其根视图去创建 `LoadingStateView`。 #### **Kotlin** ```kotlin val loadingStateView = LoadingStateView(binding.root, onReloadListener) ``` #### **Java** ```java LoadingStateView loadingStateView = new LoadingStateView(binding.getRoot(), onReloadListener); ``` ## Kotlin 委托用法 委托用法再结合上 ViewBinding 才是个人理想中的用法。会使用到个人的另一个库 [ViewBindingKTX](https://github.com/DylanCaiCoding/ViewBindingKTX),可以快速集成 ViewBinding 到基类中。 添加依赖: ```groovy implementation 'com.github.DylanCaiCoding.ViewBindingKTX:viewbinding-base:2.1.0' ``` 根据[文档](https://dylancaicoding.github.io/ViewBindingKTX/#/zh/baseclass)集成 ViewBinding,再对 ViewBinding 的根视图进行装饰。以下是在委托用法基础上修改的代码: #### **Activity** ![img.png](../img/base_binding_activity_code.png)
查看代码 ```kotlin abstract class BaseBindingActivity : AppCompatActivity(), LoadingState by LoadingStateDelegate(), ActivityBinding by ActivityBindingDelegate() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentViewWithBinding() binding.root.decorate(this) } } ```
#### **Fragment** ![img.png](../img/base_binding_fragment_code.png)
查看代码 ```kotlin abstract class BaseBindingFragment : Fragment(), LoadingState by LoadingStateDelegate(), FragmentBinding by FragmentBindingDelegate() { override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View? { return createViewWithBinding(inflater, container).decorate(this) } } ```
这样封装后不仅能在 Activity 或 Fragment 获取 `binding` 属性,还能很方便地指定显示缺省页的区域。 比如我们在已有的项目迭代开发,一些页面的布局已经写了标题栏。如果直接调用 `showLoadingView()` 函数,缺省页会把标题栏给覆盖了,通常要在标题栏下方显示缺省页,此时就可以重写 `contentView` 属性,声明在哪个控件显示缺省页,比如: ```kotlin class MainActivity : BaseBindingActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) showLoadingView() // ... } override val contentView get() = binding.container } ``` ### 已有的基类如何修改 由于要给基类增加 ViewBinding 泛型,肯定不可能直接修改基类,这会影响到已有的代码,建议继承原基类再扩展出一个支持 ViewBinding 的基类。 假设已有的基类是这种常见的封装,通过 `getLayoutId()` 函数去设置布局。 ```java public abstract class BaseActivity extends AppCompatActivity { @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(getLayoutId()); initData(); initViews(); } public abstract int getLayoutId(); public abstract void initData(); public abstract void initViews(); } ``` 目前直接继承是实现不了的,因为需要重写 `setContentView()` 的代码,所以要先将 `setContentView()` 抽到一个函数中。 ```java @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); initContentView(); initData(); initViews(); } protected void initContentView() { setContentView(getLayoutId()); } ``` 之后就可以继承基类重写该函数替换掉原来的 `setContentView()` 工作。 ```kotlin abstract class BaseBindingActivity : BaseActivity(), LoadingState by LoadingStateDelegate(), OnReloadListener, Decorative, ActivityBinding by ActivityBindingDelegate() { override fun initContentView() { setContentViewWithBinding() binding.root.decorate(this, this) } override fun getLayoutId() = -1 // 使用 ViewBinding 后,就不需要布局 id 了 } ``` Fragment 的修改也同理。 ================================================ FILE: gradle/wrapper/gradle-wrapper.properties ================================================ #Mon Nov 18 20:32:14 CST 2019 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists distributionUrl=https\://services.gradle.org/distributions/gradle-6.7.1-all.zip ================================================ FILE: gradle.properties ================================================ # Project-wide Gradle settings. # IDE (e.g. Android Studio) users: # Gradle settings configured through the IDE *will override* # any settings specified in this file. # For more details on how to configure your build environment visit # http://www.gradle.org/docs/current/userguide/build_environment.html # Specifies the JVM arguments used for the daemon process. # The setting is particularly useful for tweaking memory settings. org.gradle.jvmargs=-Xmx1536m # When configured, Gradle will run in incubating parallel mode. # This option should only be used with decoupled projects. More details, visit # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects # org.gradle.parallel=true # Kotlin code style for this project: "official" or "obsolete": kotlin.code.style=official android.useAndroidX=true android.enableJetifier=true ================================================ FILE: gradlew ================================================ #!/usr/bin/env sh ############################################################################## ## ## Gradle start up script for UN*X ## ############################################################################## # Attempt to set APP_HOME # Resolve links: $0 may be a link PRG="$0" # Need this for relative symlinks. while [ -h "$PRG" ] ; do ls=`ls -ld "$PRG"` link=`expr "$ls" : '.*-> \(.*\)$'` if expr "$link" : '/.*' > /dev/null; then PRG="$link" else PRG=`dirname "$PRG"`"/$link" fi done SAVED="`pwd`" cd "`dirname \"$PRG\"`/" >/dev/null APP_HOME="`pwd -P`" cd "$SAVED" >/dev/null APP_NAME="Gradle" APP_BASE_NAME=`basename "$0"` # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. DEFAULT_JVM_OPTS="" # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD="maximum" warn () { echo "$*" } die () { echo echo "$*" echo exit 1 } # OS specific support (must be 'true' or 'false'). cygwin=false msys=false darwin=false nonstop=false case "`uname`" in CYGWIN* ) cygwin=true ;; Darwin* ) darwin=true ;; MINGW* ) msys=true ;; NONSTOP* ) nonstop=true ;; esac CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar # Determine the Java command to use to start the JVM. if [ -n "$JAVA_HOME" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then # IBM's JDK on AIX uses strange locations for the executables JAVACMD="$JAVA_HOME/jre/sh/java" else JAVACMD="$JAVA_HOME/bin/java" fi if [ ! -x "$JAVACMD" ] ; then die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi else JAVACMD="java" which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi # Increase the maximum file descriptors if we can. if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then MAX_FD_LIMIT=`ulimit -H -n` if [ $? -eq 0 ] ; then if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then MAX_FD="$MAX_FD_LIMIT" fi ulimit -n $MAX_FD if [ $? -ne 0 ] ; then warn "Could not set maximum file descriptor limit: $MAX_FD" fi else warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" fi fi # For Darwin, add options to specify how the application appears in the dock if $darwin; then GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" fi # For Cygwin, switch paths to Windows format before running java if $cygwin ; then APP_HOME=`cygpath --path --mixed "$APP_HOME"` CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` JAVACMD=`cygpath --unix "$JAVACMD"` # We build the pattern for arguments to be converted via cygpath ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` SEP="" for dir in $ROOTDIRSRAW ; do ROOTDIRS="$ROOTDIRS$SEP$dir" SEP="|" done OURCYGPATTERN="(^($ROOTDIRS))" # Add a user-defined pattern to the cygpath arguments if [ "$GRADLE_CYGPATTERN" != "" ] ; then OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" fi # Now convert the arguments - kludge to limit ourselves to /bin/sh i=0 for arg in "$@" ; do CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` else eval `echo args$i`="\"$arg\"" fi i=$((i+1)) done case $i in (0) set -- ;; (1) set -- "$args0" ;; (2) set -- "$args0" "$args1" ;; (3) set -- "$args0" "$args1" "$args2" ;; (4) set -- "$args0" "$args1" "$args2" "$args3" ;; (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; esac fi # Escape application args save () { for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done echo " " } APP_ARGS=$(save "$@") # Collect all arguments for the java command, following the shell quoting and substitution rules eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then cd "$(dirname "$0")" fi exec "$JAVACMD" "$@" ================================================ FILE: gradlew.bat ================================================ @if "%DEBUG%" == "" @echo off @rem ########################################################################## @rem @rem Gradle startup script for Windows @rem @rem ########################################################################## @rem Set local scope for the variables with windows NT shell if "%OS%"=="Windows_NT" setlocal set DIRNAME=%~dp0 if "%DIRNAME%" == "" set DIRNAME=. set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. set DEFAULT_JVM_OPTS= @rem Find java.exe if defined JAVA_HOME goto findJavaFromJavaHome set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 if "%ERRORLEVEL%" == "0" goto init echo. echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. echo. echo Please set the JAVA_HOME variable in your environment to match the echo location of your Java installation. goto fail :findJavaFromJavaHome set JAVA_HOME=%JAVA_HOME:"=% set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto init echo. echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% echo. echo Please set the JAVA_HOME variable in your environment to match the echo location of your Java installation. goto fail :init @rem Get command-line arguments, handling Windows variants if not "%OS%" == "Windows_NT" goto win9xME_args :win9xME_args @rem Slurp the command line arguments. set CMD_LINE_ARGS= set _SKIP=2 :win9xME_args_slurp if "x%~1" == "x" goto execute set CMD_LINE_ARGS=%* :execute @rem Setup the command line set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar @rem Execute Gradle "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% :end @rem End local scope for the variables with windows NT shell if "%ERRORLEVEL%"=="0" goto mainEnd :fail rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of rem the _cmd.exe /c_ return code! if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 exit /b 1 :mainEnd if "%OS%"=="Windows_NT" endlocal :omega ================================================ FILE: loadingstateview/.gitignore ================================================ /build ================================================ FILE: loadingstateview/build.gradle ================================================ apply plugin: 'com.android.library' apply plugin: 'kotlin-android' android { compileSdkVersion buildConfig.compileSdkVersion defaultConfig { minSdkVersion buildConfig.minSdkVersion } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' } } subprojects { tasks.withType(Javadoc).all { enabled = false } } compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } kotlinOptions { jvmTarget = '1.8' freeCompilerArgs += ['-module-name', "loading_state_view"] } } dependencies { implementation "androidx.constraintlayout:constraintlayout:$constraintLayoutVersion" } ================================================ FILE: loadingstateview/proguard-rules.pro ================================================ # Add project specific ProGuard rules here. # You can control the set of applied configuration files using the # proguardFiles setting in build.gradle. # # For more details, see # http://developer.android.com/guide/developing/tools/proguard.html # If your project uses WebView with JS, uncomment the following # and specify the fully qualified class name to the JavaScript interface # class: #-keepclassmembers class fqcn.of.javascript.interface.for.webview { # public *; #} # Uncomment this to preserve the line number information for # debugging stack traces. #-keepattributes SourceFile,LineNumberTable # If you keep the line number information, uncomment this to # hide the original source file name. #-renamesourcefileattribute SourceFile ================================================ FILE: loadingstateview/src/main/AndroidManifest.xml ================================================ ================================================ FILE: loadingstateview/src/main/java/com/dylanc/loadingstateview/LoadingStateView.kt ================================================ /* * Copyright (c) 2019. Dylan Cai * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.dylanc.loadingstateview import android.app.Activity import android.content.Context import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.view.ViewGroup.LayoutParams.MATCH_PARENT import android.widget.FrameLayout import android.widget.LinearLayout import androidx.constraintlayout.widget.ConstraintLayout /** * @author Dylan Cai */ class LoadingStateView @JvmOverloads constructor( private val contentView: View, var onReloadListener: OnReloadListener? = null ) { lateinit var decorView: View private set lateinit var currentViewType: Any private set private lateinit var contentParent: ViewGroup private val parent: ViewGroup? private var currentView: View? = null private var viewDelegates: HashMap = HashMap() private val viewCaches: HashMap = HashMap() /** * Constructs a LoadingStateView with an activity and listener. */ @JvmOverloads constructor(activity: Activity, listener: OnReloadListener? = null) : this(activity.findViewById(android.R.id.content).getChildAt(0), listener) init { poolInitializer?.apply { PoolInitializer(this@LoadingStateView).invoke() } parent = contentView.parent as ViewGroup? register(ContentViewDelegate()) setDecorView(LinearDecorViewDelegate(emptyList())) } /** * Adds one or more views to decorate content in the header. * * @param delegates the view delegates of creating view */ fun setHeaders(vararg delegates: ViewDelegate) = setDecorView(LinearDecorViewDelegate(delegates)) /** * Sets an view delegate for decorating content view. * * @param decorViewDelegate the view delegate for decorating content view. */ fun setDecorView(decorViewDelegate: DecorViewDelegate) { currentView = null if (parent != null) { val index = parent.indexOfChild(contentView) if (index >= 0) { parent.removeView(contentView) } else { parent.removeView(decorView) (contentView.parent as ViewGroup).removeView(contentView) } decorView = decorViewDelegate.createDecorView() parent.addView(decorView, index) } else { decorView = decorViewDelegate.createDecorView() } contentParent = decorViewDelegate.getContentParent(decorView) showView(ViewType.CONTENT) } /** * Adds child decorative header between the content and the decorative view. * * @param delegates the view delegates of creating view */ fun addChildHeaders(vararg delegates: ViewDelegate) = addChildDecorView(LinearDecorViewDelegate(delegates)) /** * Adds child decorative view between the content and the decorative view. * * @param decorViewDelegate the view delegate for decorating content view. */ fun addChildDecorView(decorViewDelegate: DecorViewDelegate) { contentParent.removeView(currentView) currentView = null val childDecorView = decorViewDelegate.createDecorView() contentParent.addView(childDecorView) contentParent = decorViewDelegate.getContentParent(childDecorView) showView(ViewType.CONTENT) } private fun DecorViewDelegate.createDecorView() = onCreateDecorView(contentView.context, LayoutInflater.from(contentView.context)).also { decorView -> contentView.layoutParams?.let { decorView.layoutParams = if (it is ConstraintLayout.LayoutParams) ConstraintLayout.LayoutParams(it) else it (it as? ViewGroup.MarginLayoutParams)?.setMargins(0, 0, 0, 0) } } /** * Registers the view delegate of creating view before showing view. * * @param delegates the view delegate of creating view */ fun register(vararg delegates: ViewDelegate) = delegates.forEach { viewDelegates[it.viewType] = it } @JvmOverloads fun showLoadingView(animatable: Animatable? = defaultAnimatable) = showView(ViewType.LOADING, animatable) @JvmOverloads fun showContentView(animatable: Animatable? = defaultAnimatable) = showView(ViewType.CONTENT, animatable) @JvmOverloads fun showErrorView(animatable: Animatable? = defaultAnimatable) = showView(ViewType.ERROR, animatable) @JvmOverloads fun showEmptyView(animatable: Animatable? = defaultAnimatable) = showView(ViewType.EMPTY, animatable) /** * Shows the view by view type * * @param viewType the view type of view delegate */ @JvmOverloads fun showView(viewType: Any, animatable: Animatable? = defaultAnimatable) { val currentView = currentView if (currentView == null) { addView(viewType) } else { if (viewCaches[viewType] == null) addView(viewType) if (viewType != currentViewType) { val nextView = getOrCreateView(viewType) nextView.visibility = View.VISIBLE val nextViewDelegate = getViewDelegate(viewType) nextViewDelegate?.onViewAttached(nextView) getViewDelegate(currentViewType)?.onViewDetached(nextView) if (animatable != null && nextViewDelegate != null) { animatable.toggleViewsAnimation(nextView, currentView, viewType, currentViewType) } else { currentView.visibility = View.GONE } this.currentView = nextView } } currentViewType = viewType } fun updateViewDelegate(viewType: Any, callback: Callback) = callback.apply { getViewDelegate(viewType)?.invoke() } @Suppress("UNCHECKED_CAST") fun getViewDelegate(viewType: Any) = viewDelegates[viewType] as? T private fun addView(viewType: Any) { val view = getOrCreateView(viewType) (view.parent as? ViewGroup)?.removeView(view) if (parent is ConstraintLayout && viewType == ViewType.CONTENT) { val params = view.layoutParams if (view.measuredWidth == 0) params.width = MATCH_PARENT if (view.measuredHeight == 0) params.height = MATCH_PARENT view.layoutParams = params } contentParent.addView(view) currentView = view } private fun getOrCreateView(viewType: Any): View { if (viewCaches[viewType] == null) { val viewDelegate = requireNotNull(getViewDelegate(viewType)) { "Please register view delegate for $viewType type." } val view = viewDelegate.onCreateView(LayoutInflater.from(contentParent.context), contentParent) viewDelegate.onReloadListener = onReloadListener viewCaches[viewType] = view } return viewCaches[viewType]!! } abstract class ViewDelegate(val viewType: Any) { abstract fun onCreateView(inflater: LayoutInflater, parent: ViewGroup): View open fun onViewAttached(view: View) = Unit open fun onViewDetached(view: View) = Unit var onReloadListener: OnReloadListener? = null internal set } private inner class ContentViewDelegate : ViewDelegate(ViewType.CONTENT) { override fun onCreateView(inflater: LayoutInflater, parent: ViewGroup) = contentView } abstract class DecorViewDelegate { abstract fun onCreateDecorView(context: Context, inflater: LayoutInflater): View abstract fun getContentParent(decorView: View): ViewGroup } private inner class LinearDecorViewDelegate(private val views: List) : DecorViewDelegate() { private lateinit var contentParent: FrameLayout constructor(delegates: Array) : this(delegates.map { register(it) getOrCreateView(it.viewType) }) override fun onCreateDecorView(context: Context, inflater: LayoutInflater) = LinearLayout(inflater.context).apply { orientation = LinearLayout.VERTICAL contentParent = FrameLayout(context) contentParent.layoutParams = FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT) views.forEach { addView(it) } addView(contentParent) } override fun getContentParent(decorView: View) = contentParent } class PoolInitializer internal constructor(private val stateView: LoadingStateView) { fun register(vararg delegates: ViewDelegate) = stateView.register(*delegates) } fun interface Callback { fun T.invoke() } interface Animatable { fun toggleViewsAnimation(showView: View, hideView: View, showViewType: Any, hideViewType: Any) } companion object { private var poolInitializer: Callback? = null @JvmStatic var defaultAnimatable: Animatable? = null @JvmStatic fun setViewDelegatePool(poolInitializer: Callback) { this.poolInitializer = poolInitializer } } } interface OnReloadListener { fun onReload() = Unit } enum class ViewType { TITLE, LOADING, CONTENT, ERROR, EMPTY } ================================================ FILE: loadingstateview/src/main/res/values/strings.xml ================================================ Library ================================================ FILE: loadingstateview-ktx/.gitignore ================================================ /build ================================================ FILE: loadingstateview-ktx/build.gradle ================================================ plugins { id 'com.android.library' id 'org.jetbrains.kotlin.android' } android { compileSdkVersion buildConfig.compileSdkVersion defaultConfig { minSdkVersion buildConfig.minSdkVersion consumerProguardFiles "consumer-rules.pro" } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' } } compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } kotlinOptions { jvmTarget = '1.8' freeCompilerArgs += ['-module-name', "loading_state_view_ktx"] } } dependencies { api project(':loadingstateview') implementation "androidx.appcompat:appcompat:$appCompatVersion" } ================================================ FILE: loadingstateview-ktx/consumer-rules.pro ================================================ ================================================ FILE: loadingstateview-ktx/proguard-rules.pro ================================================ # Add project specific ProGuard rules here. # You can control the set of applied configuration files using the # proguardFiles setting in build.gradle. # # For more details, see # http://developer.android.com/guide/developing/tools/proguard.html # If your project uses WebView with JS, uncomment the following # and specify the fully qualified class name to the JavaScript interface # class: #-keepclassmembers class fqcn.of.javascript.interface.for.webview { # public *; #} # Uncomment this to preserve the line number information for # debugging stack traces. #-keepattributes SourceFile,LineNumberTable # If you keep the line number information, uncomment this to # hide the original source file name. #-renamesourcefileattribute SourceFile ================================================ FILE: loadingstateview-ktx/src/main/AndroidManifest.xml ================================================ ================================================ FILE: loadingstateview-ktx/src/main/java/com/dylanc/loadingstateview/BaseToolbarViewDelegate.kt ================================================ /* * Copyright (c) 2019. Dylan Cai * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.dylanc.loadingstateview import android.view.LayoutInflater import android.view.View import android.view.ViewGroup abstract class BaseToolbarViewDelegate : LoadingStateView.ViewDelegate(ViewType.TITLE) { internal lateinit var config: ToolbarConfig override fun onCreateView(inflater: LayoutInflater, parent: ViewGroup) = onCreateToolbar(inflater, parent).apply { onBindToolbar(config) } abstract fun onCreateToolbar(inflater: LayoutInflater, parent: ViewGroup): View abstract fun onBindToolbar(config: ToolbarConfig) } ================================================ FILE: loadingstateview-ktx/src/main/java/com/dylanc/loadingstateview/Decorative.kt ================================================ /* * Copyright (c) 2019. Dylan Cai * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.dylanc.loadingstateview import android.view.View interface Decorative : OnReloadListener { val isDecorated: Boolean get() = true val contentView: View? get() = null } ================================================ FILE: loadingstateview-ktx/src/main/java/com/dylanc/loadingstateview/LoadingState.kt ================================================ /* * Copyright (c) 2019. Dylan Cai * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.dylanc.loadingstateview import android.app.Activity import android.view.View import androidx.annotation.StringRes import androidx.fragment.app.Fragment interface LoadingState : Decorative, OnReloadListener { val loadingStateViewType: Any? @Deprecated("Use Activity.decorateContentView(this) instead", ReplaceWith("decorateContentView(decorative)")) fun Activity.decorateContentView(listener: OnReloadListener, decorative: Decorative) = decorateContentView(decorative) fun Activity.decorateContentView(decorative: Decorative) @Deprecated("Use View.decorate(this) instead", ReplaceWith("decorate(decorative)")) fun View.decorate(listener: OnReloadListener, decorative: Decorative): View = decorate(decorative) fun View.decorate(decorative: Decorative): View fun registerView(vararg viewDelegates: LoadingStateView.ViewDelegate) fun Activity.setToolbar(@StringRes titleId: Int, navBtnType: NavBtnType = NavBtnType.ICON, block: (ToolbarConfig.() -> Unit)? = null) fun Activity.setToolbar(title: String? = null, navBtnType: NavBtnType = NavBtnType.ICON, block: (ToolbarConfig.() -> Unit)? = null) fun Fragment.setToolbar(@StringRes titleId: Int, navBtnType: NavBtnType = NavBtnType.ICON, block: (ToolbarConfig.() -> Unit)? = null) fun Fragment.setToolbar(title: String? = null, navBtnType: NavBtnType = NavBtnType.ICON, block: (ToolbarConfig.() -> Unit)? = null) fun Activity.setHeaders(vararg delegates: LoadingStateView.ViewDelegate) fun Fragment.setHeaders(vararg delegates: LoadingStateView.ViewDelegate) fun Activity.setDecorView(delegate: LoadingStateView.DecorViewDelegate) fun Fragment.setDecorView(delegate: LoadingStateView.DecorViewDelegate) fun showLoadingView(animatable: LoadingStateView.Animatable? = LoadingStateView.defaultAnimatable) fun showContentView(animatable: LoadingStateView.Animatable? = LoadingStateView.defaultAnimatable) fun showErrorView(animatable: LoadingStateView.Animatable? = LoadingStateView.defaultAnimatable) fun showEmptyView(animatable: LoadingStateView.Animatable? = LoadingStateView.defaultAnimatable) fun showCustomView(viewType: Any, animatable: LoadingStateView.Animatable? = LoadingStateView.defaultAnimatable) fun updateToolbar(block: ToolbarConfig.() -> Unit) fun updateView(viewType: Any, block: T.() -> Unit) @Suppress("FunctionName") fun ToolbarViewDelegate( title: String? = null, navBtnType: NavBtnType = NavBtnType.ICON, block: (ToolbarConfig.() -> Unit)? = null ): BaseToolbarViewDelegate } ================================================ FILE: loadingstateview-ktx/src/main/java/com/dylanc/loadingstateview/LoadingStateDelegate.kt ================================================ /* * Copyright (c) 2019. Dylan Cai * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.dylanc.loadingstateview import android.app.Activity import android.view.View import android.view.ViewGroup import androidx.annotation.StringRes import androidx.fragment.app.Fragment class LoadingStateDelegate : LoadingState { private var loadingStateView: LoadingStateView? = null override val loadingStateViewType: Any? get() = loadingStateView?.currentViewType override fun Activity.decorateContentView(decorative: Decorative) { findViewById(android.R.id.content).getChildAt(0).decorate(decorative) } override fun View.decorate(decorative: Decorative): View = when { !decorative.isDecorated -> this decorative.contentView == null -> LoadingStateView(this, decorative).also { loadingStateView = it }.decorView else -> { loadingStateView = LoadingStateView(decorative.contentView!!, decorative) this } } override fun registerView(vararg viewDelegates: LoadingStateView.ViewDelegate) { loadingStateView?.register(*viewDelegates) } override fun Activity.setToolbar(@StringRes titleId: Int, navBtnType: NavBtnType, block: (ToolbarConfig.() -> Unit)?) { setToolbar(getString(titleId), navBtnType, block) } override fun Activity.setToolbar(title: String?, navBtnType: NavBtnType, block: (ToolbarConfig.() -> Unit)?) { loadingStateView?.setHeaders(ToolbarViewDelegate(title, navBtnType, block)) } override fun Fragment.setToolbar(@StringRes titleId: Int, navBtnType: NavBtnType, block: (ToolbarConfig.() -> Unit)?) { setToolbar(getString(titleId), navBtnType, block) } override fun Fragment.setToolbar(title: String?, navBtnType: NavBtnType, block: (ToolbarConfig.() -> Unit)?) { loadingStateView?.addChildHeaders(ToolbarViewDelegate(title, navBtnType, block)) } override fun Activity.setHeaders(vararg delegates: LoadingStateView.ViewDelegate) { loadingStateView?.setHeaders(*delegates) } override fun Fragment.setHeaders(vararg delegates: LoadingStateView.ViewDelegate) { loadingStateView?.addChildHeaders(*delegates) } override fun Activity.setDecorView(delegate: LoadingStateView.DecorViewDelegate) { loadingStateView?.setDecorView(delegate) } override fun Fragment.setDecorView(delegate: LoadingStateView.DecorViewDelegate) { loadingStateView?.addChildDecorView(delegate) } override fun showLoadingView(animatable: LoadingStateView.Animatable?) { loadingStateView?.showLoadingView(animatable) } override fun showContentView(animatable: LoadingStateView.Animatable?) { loadingStateView?.showContentView(animatable) } override fun showErrorView(animatable: LoadingStateView.Animatable?) { loadingStateView?.showErrorView(animatable) } override fun showEmptyView(animatable: LoadingStateView.Animatable?) { loadingStateView?.showEmptyView(animatable) } override fun showCustomView(viewType: Any, animatable: LoadingStateView.Animatable?) { loadingStateView?.showView(viewType, animatable) } override fun updateToolbar(block: ToolbarConfig.() -> Unit) { updateView(ViewType.TITLE) { onBindToolbar(config.apply(block)) } } override fun updateView(viewType: Any, block: T.() -> Unit) { loadingStateView?.updateViewDelegate(viewType, block) } override fun ToolbarViewDelegate(title: String?, navBtnType: NavBtnType, block: (ToolbarConfig.() -> Unit)?) = requireNotNull(loadingStateView?.getViewDelegate(ViewType.TITLE)) { "ToolbarViewDelegate must be registered before." }.apply { config = ToolbarConfig(title, navBtnType).apply { block?.invoke(this) } } } ================================================ FILE: loadingstateview-ktx/src/main/java/com/dylanc/loadingstateview/ToolbarConfig.kt ================================================ /* * Copyright (c) 2019. Dylan Cai * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ @file:Suppress("unused") package com.dylanc.loadingstateview import android.app.Activity import android.content.Context import android.content.ContextWrapper import android.view.View import androidx.annotation.DrawableRes import kotlin.properties.ReadWriteProperty import kotlin.reflect.KProperty enum class NavBtnType { ICON, TEXT, ICON_TEXT, NONE } class ToolbarConfig( var title: String? = null, var navBtnType: NavBtnType = NavBtnType.ICON, val extras: HashMap = HashMap(), ) { @DrawableRes var navIcon: Int? = null var navText: String? = null private set var onNavClickListener = View.OnClickListener { var context: Context? = it.context while (context is ContextWrapper) { if (context is Activity) { context.finish() return@OnClickListener } context = context.baseContext } } private set @DrawableRes var rightIcon: Int? = null private set var rightText: String? = null private set var onRightClickListener: View.OnClickListener? = null private set fun navIcon(@DrawableRes icon: Int? = navIcon, listener: View.OnClickListener) { navIcon = icon onNavClickListener = listener } fun navText(text: String, listener: View.OnClickListener) { navText = text onNavClickListener = listener } fun rightIcon(@DrawableRes icon: Int, listener: View.OnClickListener) { rightIcon = icon onRightClickListener = listener } fun rightText(text: String, listener: View.OnClickListener) { rightText = text onRightClickListener = listener } } fun toolbarExtras() = object : ReadWriteProperty { @Suppress("UNCHECKED_CAST") override fun getValue(thisRef: ToolbarConfig, property: KProperty<*>): T? = thisRef.extras[property.name] as? T override fun setValue(thisRef: ToolbarConfig, property: KProperty<*>, value: T?) { thisRef.extras[property.name] = value } } ================================================ FILE: sample-java/.gitignore ================================================ /build ================================================ FILE: sample-java/build.gradle ================================================ apply plugin: 'com.android.application' apply plugin: 'kotlin-android' android { compileSdkVersion buildConfig.compileSdkVersion defaultConfig { applicationId "com.dylanc.loadingstateview.sample.java" minSdkVersion buildConfig.minSdkVersion targetSdkVersion buildConfig.targetSdkVersion versionCode buildConfig.versionCode versionName buildConfig.versionName testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } buildTypes { release { minifyEnabled true proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' } } compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } viewBinding { enabled = true } } dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) implementation project(':loadingstateview') implementation "androidx.appcompat:appcompat:$appCompatVersion" implementation "androidx.constraintlayout:constraintlayout:$constraintLayoutVersion" implementation "com.google.android.material:material:$materialVersion" implementation "com.github.bumptech.glide:glide:$glideVersion" testImplementation "junit:junit:$junitVersion" androidTestImplementation "androidx.test.ext:junit:$junitExtVersion" androidTestImplementation "androidx.test.espresso:espresso-core:$espressoVersion" } ================================================ FILE: sample-java/proguard-rules.pro ================================================ # Add project specific ProGuard rules here. # You can control the set of applied configuration files using the # proguardFiles setting in build.gradle. # # For more details, see # http://developer.android.com/guide/developing/tools/proguard.html # If your project uses WebView with JS, uncomment the following # and specify the fully qualified class name to the JavaScript interface # class: #-keepclassmembers class fqcn.of.javascript.interface.for.webview { # public *; #} # Uncomment this to preserve the line number information for # debugging stack traces. #-keepattributes SourceFile,LineNumberTable # If you keep the line number information, uncomment this to # hide the original source file name. #-renamesourcefileattribute SourceFile -keep public class com.dylanc.loadingstateview.sample.java.widget.**{*;} ================================================ FILE: sample-java/release/output.json ================================================ [{"outputType":{"type":"APK"},"apkData":{"type":"MAIN","splits":[],"versionCode":2,"versionName":"1.0.1","enabled":true,"outputFile":"app-release.apk","fullName":"release","baseName":"release","dirName":""},"path":"app-release.apk","properties":{}}] ================================================ FILE: sample-java/src/androidTest/java/com/dylanc/loadingstateview/sample/java/ExampleInstrumentedTest.kt ================================================ package com.dylanc.loadingstateview.sample.java import androidx.test.platform.app.InstrumentationRegistry import androidx.test.ext.junit.runners.AndroidJUnit4 import org.junit.Test import org.junit.runner.RunWith import org.junit.Assert.* /** * Instrumented test, which will execute on an Android device. * * See [testing documentation](http://d.android.com/tools/testing). */ @RunWith(AndroidJUnit4::class) class ExampleInstrumentedTest { @Test fun useAppContext() { // Context of the app under test. val appContext = InstrumentationRegistry.getInstrumentation().targetContext assertEquals("com.dylanc.loadinghelper.sample", appContext.packageName) } } ================================================ FILE: sample-java/src/main/AndroidManifest.xml ================================================ ================================================ FILE: sample-java/src/main/java/com/dylanc/loadingstateview/sample/java/App.java ================================================ /* * Copyright (c) 2019. Dylan Cai * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.dylanc.loadingstateview.sample.java; import android.app.Application; import com.dylanc.loadingstateview.LoadingStateView; import com.dylanc.loadingstateview.sample.java.animation.FadeAnimatable; import com.dylanc.loadingstateview.sample.java.delegate.EmptyViewDelegate; import com.dylanc.loadingstateview.sample.java.delegate.ErrorViewDelegate; import com.dylanc.loadingstateview.sample.java.delegate.LoadingViewDelegate; /** * @author Dylan Cai */ public class App extends Application { @Override public void onCreate() { super.onCreate(); LoadingStateView.setViewDelegatePool(pool -> pool.register(new LoadingViewDelegate(), new ErrorViewDelegate(), new EmptyViewDelegate())); LoadingStateView.setDefaultAnimatable(new FadeAnimatable()); } } ================================================ FILE: sample-java/src/main/java/com/dylanc/loadingstateview/sample/java/animation/FadeAnimatable.java ================================================ package com.dylanc.loadingstateview.sample.java.animation; import android.view.View; import androidx.annotation.NonNull; import com.dylanc.loadingstateview.LoadingStateView; import com.dylanc.loadingstateview.ViewType; import org.jetbrains.annotations.NotNull; /** * @author Dylan Cai */ public class FadeAnimatable implements LoadingStateView.Animatable { private static final long DEFAULT_DURATION = 500; private final long duration; public FadeAnimatable() { this(DEFAULT_DURATION); } public FadeAnimatable(long duration) { this.duration = duration; } @Override public void toggleViewsAnimation(@NonNull View showView, @NonNull View hideView, @NonNull Object showViewType, @NonNull Object hideViewType) { showView.setAlpha(0); showView.setVisibility(View.VISIBLE); showView.animate().alpha(1).setDuration(duration); if (showViewType == ViewType.LOADING && hideViewType == ViewType.CONTENT) { hideView.setVisibility(View.GONE); } else { hideView.animate().alpha(0).setDuration(duration).withEndAction(() -> { hideView.setAlpha(1); hideView.setVisibility(View.GONE); }); } } } ================================================ FILE: sample-java/src/main/java/com/dylanc/loadingstateview/sample/java/base/BaseActivity.java ================================================ /* * Copyright (c) 2019. Dylan Cai * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.dylanc.loadingstateview.sample.java.base; import androidx.annotation.IdRes; import androidx.appcompat.app.AppCompatActivity; import com.dylanc.loadingstateview.LoadingStateView; import com.dylanc.loadingstateview.OnReloadListener; import com.dylanc.loadingstateview.sample.java.delegate.NavIconType; import com.dylanc.loadingstateview.sample.java.delegate.ToolbarViewDelegate; /** * 这是耦合度较低的封装方式,没有任何抽象方法,可以很方便地将基类里的代码拷贝到其它项目的基类里使用。 *

* 使用该基类时注意以下事项: * 1. 显示对应视图之前需要注册适配器,可以设置全局适配器,某个页面想修改样式时再注册个新的适配器。 * 2. 设置标题栏的方法应该根据项目需要进行编写,下面提供了参考示例。 * * @author Dylan Cai */ @SuppressWarnings("unused") public class BaseActivity extends AppCompatActivity implements OnReloadListener { private LoadingStateView loadingStateView; @Override public void setContentView(int layoutResID) { this.setContentView(layoutResID, 0); } public void setContentView(int layoutResID, @IdRes int contentViewId) { super.setContentView(layoutResID); if (contentViewId == 0) { loadingStateView = new LoadingStateView(this, this); } else { loadingStateView = new LoadingStateView(findViewById(contentViewId)); } } /** * 添加标题栏的示例方法,请根据自己的需求进行修改 */ public void setToolbar(String title) { setToolbar(title, NavIconType.BACK, 0); } public void setToolbar(String title, NavIconType type) { setToolbar(title, type, 0); } public void setToolbar(String title, NavIconType type, int menuId) { loadingStateView.setHeaders(new ToolbarViewDelegate(title, type, menuId, this::onOptionsItemSelected)); } public void showLoadingView() { loadingStateView.showLoadingView(); } public void showContentView() { loadingStateView.showContentView(); } public void showErrorView() { loadingStateView.showErrorView(); } public void showEmptyView() { loadingStateView.showEmptyView(); } public void showCustomView(Object viewType) { loadingStateView.showView(viewType); } @Override public void onReload() { } } ================================================ FILE: sample-java/src/main/java/com/dylanc/loadingstateview/sample/java/delegate/BottomEditorDecorViewDelegate.java ================================================ /* * Copyright (c) 2019. Dylan Cai * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.dylanc.loadingstateview.sample.java.delegate; import android.annotation.SuppressLint; import android.content.Context; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.EditText; import androidx.annotation.NonNull; import com.dylanc.loadingstateview.LoadingStateView; import com.dylanc.loadingstateview.sample.java.R; import com.dylanc.loadingstateview.sample.java.utils.KeyboardUtils; import org.jetbrains.annotations.NotNull; /** * @author Dylan Cai */ public class BottomEditorDecorViewDelegate extends LoadingStateView.DecorViewDelegate { private final OnSendListener onSendListener; public BottomEditorDecorViewDelegate(OnSendListener onSendListener) { this.onSendListener = onSendListener; } @NotNull @Override @SuppressLint("InflateParams") public View onCreateDecorView(@NonNull Context context, @NotNull LayoutInflater inflater) { View view = inflater.inflate(R.layout.layout_bottom_editor, null); EditText edtContent = view.findViewById(R.id.edt_content); view.findViewById(R.id.btn_send).setOnClickListener(v -> { if (onSendListener != null) { onSendListener.onSend(edtContent.getText().toString()); edtContent.setText(""); KeyboardUtils.hideKeyboard(edtContent); } }); return view; } @NotNull @Override public ViewGroup getContentParent(@NotNull View decorView) { return decorView.findViewById(R.id.content_parent); } public interface OnSendListener { void onSend(String content); } } ================================================ FILE: sample-java/src/main/java/com/dylanc/loadingstateview/sample/java/delegate/CoolLoadingViewDelegate.java ================================================ /* * Copyright (c) 2019. Dylan Cai * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.dylanc.loadingstateview.sample.java.delegate; import androidx.annotation.NonNull; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import com.dylanc.loadingstateview.LoadingStateView; import com.dylanc.loadingstateview.ViewType; import com.dylanc.loadingstateview.sample.java.R; /** * @author Dylan Cai */ public class CoolLoadingViewDelegate extends LoadingStateView.ViewDelegate { public CoolLoadingViewDelegate() { super(ViewType.LOADING); } @NonNull @Override public View onCreateView(@NonNull LayoutInflater inflater, @NonNull ViewGroup parent) { return inflater.inflate(R.layout.layout_cool_loading, parent, false); } } ================================================ FILE: sample-java/src/main/java/com/dylanc/loadingstateview/sample/java/delegate/CustomHeaderViewDelegate.java ================================================ /* * Copyright (c) 2019. Dylan Cai * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.dylanc.loadingstateview.sample.java.delegate; import android.app.Activity; import android.os.Build; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.ImageView; import androidx.annotation.NonNull; import androidx.core.content.ContextCompat; import com.dylanc.loadingstateview.LoadingStateView; import com.dylanc.loadingstateview.ViewType; import com.dylanc.loadingstateview.sample.java.R; /** * @author Dylan Cai */ public class CustomHeaderViewDelegate extends LoadingStateView.ViewDelegate { private final View.OnClickListener onMessageClickListener; private final int firstDrawableId; private final View.OnClickListener onFirstBtnClickListener; private final int secondDrawableId; private final View.OnClickListener onSecondBtnClickListener; public CustomHeaderViewDelegate(View.OnClickListener onMessageClickListener, int firstDrawableId, View.OnClickListener onFirstBtnClickListener, int secondDrawableId, View.OnClickListener onSecondBtnClickListener) { super(ViewType.TITLE); this.onMessageClickListener = onMessageClickListener; this.firstDrawableId = firstDrawableId; this.onFirstBtnClickListener = onFirstBtnClickListener; this.secondDrawableId = secondDrawableId; this.onSecondBtnClickListener = onSecondBtnClickListener; } @NonNull @Override public View onCreateView(@NonNull LayoutInflater inflater, @NonNull ViewGroup parent) { View view = inflater.inflate(R.layout.layout_custom_header, parent, false); ImageView btnFirst = view.findViewById(R.id.btn_first); ImageView btnSecond = view.findViewById(R.id.btn_second); View btnMessage = view.findViewById(R.id.btn_message); Activity activity = (Activity) view.getContext(); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { activity.getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR); } btnMessage.setOnClickListener(onMessageClickListener); btnFirst.setImageDrawable(ContextCompat.getDrawable(activity, firstDrawableId)); btnFirst.setOnClickListener(onFirstBtnClickListener); btnSecond.setImageDrawable(ContextCompat.getDrawable(activity, secondDrawableId)); btnSecond.setOnClickListener(onSecondBtnClickListener); return view; } } ================================================ FILE: sample-java/src/main/java/com/dylanc/loadingstateview/sample/java/delegate/EmptyViewDelegate.java ================================================ /* * Copyright (c) 2019. Dylan Cai * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.dylanc.loadingstateview.sample.java.delegate; import androidx.annotation.NonNull; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import com.dylanc.loadingstateview.LoadingStateView; import com.dylanc.loadingstateview.ViewType; import com.dylanc.loadingstateview.sample.java.R; /** * @author Dylan Cai */ public class EmptyViewDelegate extends LoadingStateView.ViewDelegate { public EmptyViewDelegate() { super(ViewType.EMPTY); } @NonNull @Override public View onCreateView(@NonNull LayoutInflater inflater, @NonNull ViewGroup parent) { return inflater.inflate(R.layout.layout_empty, parent, false); } } ================================================ FILE: sample-java/src/main/java/com/dylanc/loadingstateview/sample/java/delegate/ErrorViewDelegate.java ================================================ /* * Copyright (c) 2019. Dylan Cai * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.dylanc.loadingstateview.sample.java.delegate; import androidx.annotation.NonNull; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import com.dylanc.loadingstateview.LoadingStateView; import com.dylanc.loadingstateview.ViewType; import com.dylanc.loadingstateview.sample.java.R; /** * @author Dylan Cai */ public class ErrorViewDelegate extends LoadingStateView.ViewDelegate { public ErrorViewDelegate() { super(ViewType.ERROR); } @NonNull @Override public View onCreateView(@NonNull LayoutInflater inflater, @NonNull ViewGroup parent) { View view = inflater.inflate(R.layout.layout_error, parent, false); view.findViewById(R.id.btn_reload).setOnClickListener(v -> { if (getOnReloadListener() != null) { getOnReloadListener().onReload(); } }); return view; } } ================================================ FILE: sample-java/src/main/java/com/dylanc/loadingstateview/sample/java/delegate/LoadingViewDelegate.java ================================================ /* * Copyright (c) 2019. Dylan Cai * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.dylanc.loadingstateview.sample.java.delegate; import androidx.annotation.NonNull; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import com.dylanc.loadingstateview.LoadingStateView; import com.dylanc.loadingstateview.ViewType; import com.dylanc.loadingstateview.sample.java.R; /** * @author Dylan Cai */ public class LoadingViewDelegate extends LoadingStateView.ViewDelegate { public int height = ViewGroup.LayoutParams.MATCH_PARENT; public LoadingViewDelegate() { super(ViewType.LOADING); } @NonNull @Override public View onCreateView(@NonNull LayoutInflater inflater, @NonNull ViewGroup parent) { View view = inflater.inflate(R.layout.layout_loading, parent, false); ViewGroup.LayoutParams layoutParams = view.getLayoutParams(); layoutParams.height = height; view.setLayoutParams(layoutParams); return view; } } ================================================ FILE: sample-java/src/main/java/com/dylanc/loadingstateview/sample/java/delegate/NavIconType.java ================================================ /* * Copyright (c) 2019. Dylan Cai * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.dylanc.loadingstateview.sample.java.delegate; /** * @author Dylan Cai */ public enum NavIconType { BACK, NONE } ================================================ FILE: sample-java/src/main/java/com/dylanc/loadingstateview/sample/java/delegate/NothingViewDelegate.java ================================================ /* * Copyright (c) 2019. Dylan Cai * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.dylanc.loadingstateview.sample.java.delegate; import androidx.annotation.NonNull; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import com.dylanc.loadingstateview.LoadingStateView; import com.dylanc.loadingstateview.ViewType; /** * @author Dylan Cai */ public class NothingViewDelegate extends LoadingStateView.ViewDelegate { public NothingViewDelegate() { super(ViewType.EMPTY); } @NonNull @Override public View onCreateView(@NonNull LayoutInflater inflater, @NonNull ViewGroup parent) { return new View(parent.getContext()); } } ================================================ FILE: sample-java/src/main/java/com/dylanc/loadingstateview/sample/java/delegate/PlaceholderViewDelegate.java ================================================ /* * Copyright (c) 2019. Dylan Cai * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.dylanc.loadingstateview.sample.java.delegate; import androidx.annotation.NonNull; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import com.dylanc.loadingstateview.LoadingStateView; import com.dylanc.loadingstateview.ViewType; import com.dylanc.loadingstateview.sample.java.R; /** * @author Dylan Cai */ public class PlaceholderViewDelegate extends LoadingStateView.ViewDelegate { public PlaceholderViewDelegate() { super(ViewType.LOADING); } @NonNull @Override public View onCreateView(@NonNull LayoutInflater inflater, @NonNull ViewGroup parent) { return inflater.inflate(R.layout.layout_placeholder, parent, false); } } ================================================ FILE: sample-java/src/main/java/com/dylanc/loadingstateview/sample/java/delegate/ScrollingDecorViewDelegate.java ================================================ /* * Copyright (c) 2019. Dylan Cai * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.dylanc.loadingstateview.sample.java.delegate; import android.annotation.SuppressLint; import android.app.Activity; import android.content.Context; import android.os.Build; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import androidx.annotation.NonNull; import androidx.appcompat.widget.Toolbar; import com.dylanc.loadingstateview.LoadingStateView; import com.dylanc.loadingstateview.sample.java.R; import org.jetbrains.annotations.NotNull; /** * @author Dylan Cai */ public class ScrollingDecorViewDelegate extends LoadingStateView.DecorViewDelegate { private final String title; public ScrollingDecorViewDelegate(String title) { this.title = title; } @NotNull @Override @SuppressLint("InflateParams") public View onCreateDecorView(@NonNull Context context, @NotNull LayoutInflater inflater) { View view = inflater.inflate(R.layout.layout_scrolling_toolbar, null); Activity activity = (Activity) inflater.getContext(); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { activity.getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR); } Toolbar toolbar = view.findViewById(R.id.toolbar); toolbar.setTitle(title); toolbar.setNavigationOnClickListener(v -> activity.finish()); return view; } @NotNull @Override public ViewGroup getContentParent(@NotNull View decorView) { return decorView.findViewById(R.id.content_parent); } } ================================================ FILE: sample-java/src/main/java/com/dylanc/loadingstateview/sample/java/delegate/SearchHeaderViewDelegate.java ================================================ /* * Copyright (c) 2019. Dylan Cai * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.dylanc.loadingstateview.sample.java.delegate; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.view.inputmethod.EditorInfo; import android.widget.EditText; import androidx.annotation.NonNull; import com.dylanc.loadingstateview.LoadingStateView; import com.dylanc.loadingstateview.ViewType; import com.dylanc.loadingstateview.sample.java.R; import com.dylanc.loadingstateview.sample.java.utils.KeyboardUtils; /** * @author Dylan Cai */ public class SearchHeaderViewDelegate extends LoadingStateView.ViewDelegate{ public static final String VIEW_TYPE_SEARCH = "search"; private final OnSearchListener onSearchListener; public SearchHeaderViewDelegate(OnSearchListener onSearchListener) { super(VIEW_TYPE_SEARCH); this.onSearchListener = onSearchListener; } @NonNull @Override public View onCreateView(@NonNull LayoutInflater inflater, @NonNull ViewGroup parent) { View view = inflater.inflate(R.layout.layout_search_header, parent, false); EditText edtSearch = view.findViewById(R.id.edt_search); edtSearch.setOnEditorActionListener((v, actionId, event) -> { if (actionId == EditorInfo.IME_ACTION_SEARCH) { KeyboardUtils.hideKeyboard(edtSearch); if (onSearchListener != null) { onSearchListener.onSearch(edtSearch.getText().toString()); } return true; } return false; }); return view; } public interface OnSearchListener { void onSearch(String keyword); } } ================================================ FILE: sample-java/src/main/java/com/dylanc/loadingstateview/sample/java/delegate/TimeoutViewDelegate.java ================================================ /* * Copyright (c) 2019. Dylan Cai * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.dylanc.loadingstateview.sample.java.delegate; import androidx.annotation.NonNull; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import com.dylanc.loadingstateview.LoadingStateView; import com.dylanc.loadingstateview.sample.java.R; /** * @author Dylan Cai */ public class TimeoutViewDelegate extends LoadingStateView.ViewDelegate { public static final String VIEW_TYPE_TIMEOUT = "timeout"; public TimeoutViewDelegate() { super(VIEW_TYPE_TIMEOUT); } @NonNull @Override public View onCreateView(@NonNull LayoutInflater inflater, @NonNull ViewGroup parent) { View view = inflater.inflate(R.layout.layout_timeout, parent, false); view.setOnClickListener(v -> { if (getOnReloadListener() != null) { getOnReloadListener().onReload(); } }); return view; } } ================================================ FILE: sample-java/src/main/java/com/dylanc/loadingstateview/sample/java/delegate/ToolbarViewDelegate.java ================================================ /* * Copyright (c) 2019. Dylan Cai * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.dylanc.loadingstateview.sample.java.delegate; import android.app.Activity; import android.os.Build; import android.text.TextUtils; import android.view.LayoutInflater; import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; import androidx.annotation.NonNull; import androidx.appcompat.widget.Toolbar; import com.dylanc.loadingstateview.LoadingStateView; import com.dylanc.loadingstateview.ViewType; import com.dylanc.loadingstateview.sample.java.R; import org.jetbrains.annotations.NotNull; import kotlin.jvm.functions.Function1; /** * @author Dylan Cai */ public class ToolbarViewDelegate extends LoadingStateView.ViewDelegate { private final String title; private final NavIconType type; private final int menuId; private final Function1 onMenuItemClick; public ToolbarViewDelegate(String title, NavIconType type) { this(title, type, 0, null); } public ToolbarViewDelegate(String title, NavIconType type, int menuId, Function1 onMenuItemClick) { super(ViewType.TITLE); this.title = title; this.type = type; this.menuId = menuId; this.onMenuItemClick = onMenuItemClick; } @NonNull @Override public View onCreateView(@NonNull LayoutInflater inflater, @NonNull ViewGroup parent) { View view = inflater.inflate(R.layout.layout_toolbar, parent, false); Activity activity = (Activity) view.getContext(); Toolbar toolbar = view.findViewById(R.id.toolbar); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { activity.getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR); } if (!TextUtils.isEmpty(title)) { toolbar.setTitle(title); } if (type == NavIconType.BACK) { toolbar.setNavigationIcon(R.drawable.ic_arrow_back_black); toolbar.setNavigationOnClickListener(v -> activity.finish()); } else { toolbar.setNavigationIcon(null); } if (menuId > 0 && onMenuItemClick != null) { toolbar.inflateMenu(menuId); toolbar.setOnMenuItemClickListener(onMenuItemClick::invoke); } return view; } } ================================================ FILE: sample-java/src/main/java/com/dylanc/loadingstateview/sample/java/ui/ActErrorActivity.java ================================================ /* * Copyright (c) 2019. Dylan Cai * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.dylanc.loadingstateview.sample.java.ui; import android.os.Bundle; import androidx.annotation.Nullable; import androidx.appcompat.app.AppCompatActivity; import com.dylanc.loadingstateview.LoadingStateView; import com.dylanc.loadingstateview.OnReloadListener; import com.dylanc.loadingstateview.sample.java.R; import com.dylanc.loadingstateview.sample.java.delegate.NavIconType; import com.dylanc.loadingstateview.sample.java.utils.HttpUtils; import com.dylanc.loadingstateview.sample.java.utils.ToolbarUtils; /** * @author Dylan Cai */ public class ActErrorActivity extends AppCompatActivity implements OnReloadListener { private LoadingStateView loadingStateView; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.layout_content); loadingStateView = ToolbarUtils.setToolbar(this, "Activity(error)", NavIconType.BACK); loadingStateView.setOnReloadListener(this); loadData(); } private void loadData() { loadingStateView.showLoadingView(); HttpUtils.requestFailure(new HttpUtils.Callback() { @Override public void onSuccess() { loadingStateView.showContentView(); } @Override public void onFailure() { loadingStateView.showErrorView(); } }); } @Override public void onReload() { loadingStateView.showLoadingView(); HttpUtils.requestSuccess(new HttpUtils.Callback() { @Override public void onSuccess() { loadingStateView.showContentView(); } @Override public void onFailure() { loadingStateView.showErrorView(); } }); } } ================================================ FILE: sample-java/src/main/java/com/dylanc/loadingstateview/sample/java/ui/BottomEditorActivity.java ================================================ /* * Copyright (c) 2019. Dylan Cai * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.dylanc.loadingstateview.sample.java.ui; import android.os.Bundle; import androidx.annotation.Nullable; import androidx.appcompat.app.AppCompatActivity; import com.dylanc.loadingstateview.LoadingStateView; import com.dylanc.loadingstateview.ViewType; import com.dylanc.loadingstateview.sample.java.R; import com.dylanc.loadingstateview.sample.java.delegate.BottomEditorDecorViewDelegate; import com.dylanc.loadingstateview.sample.java.delegate.NothingViewDelegate; import com.dylanc.loadingstateview.sample.java.delegate.NavIconType; import com.dylanc.loadingstateview.sample.java.utils.HttpUtils; import com.dylanc.loadingstateview.sample.java.utils.ToolbarUtils; /** * @author Dylan Cai */ public class BottomEditorActivity extends AppCompatActivity implements BottomEditorDecorViewDelegate.OnSendListener { private LoadingStateView loadingStateView; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.layout_content); loadingStateView = ToolbarUtils.setToolbar(this, "BottomDecorView(editor)", NavIconType.BACK); loadingStateView.addChildDecorView(new BottomEditorDecorViewDelegate(this)); loadingStateView.register(new NothingViewDelegate()); loadingStateView.showEmptyView(); } @Override public void onSend(String content) { loadingStateView.showLoadingView(); HttpUtils.requestSuccess(new HttpUtils.Callback() { @Override public void onSuccess() { loadingStateView.showContentView(); } @Override public void onFailure() { loadingStateView.showErrorView(); } }); } } ================================================ FILE: sample-java/src/main/java/com/dylanc/loadingstateview/sample/java/ui/CustomHeaderActivity.java ================================================ /* * Copyright (c) 2019. Dylan Cai * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.dylanc.loadingstateview.sample.java.ui; import android.os.Bundle; import android.view.View; import android.widget.Toast; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.appcompat.app.AppCompatActivity; import androidx.fragment.app.Fragment; import androidx.fragment.app.FragmentManager; import androidx.fragment.app.FragmentPagerAdapter; import androidx.viewpager.widget.ViewPager; import com.dylanc.loadingstateview.sample.java.R; import com.dylanc.loadingstateview.sample.java.ui.fragment.SimpleFragment; import com.dylanc.loadingstateview.sample.java.utils.ToolbarUtils; import com.google.android.material.tabs.TabLayout; /** * @author Dylan Cai */ public class CustomHeaderActivity extends AppCompatActivity { @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_view_pager); ToolbarUtils.setCustomToolbar(this, this::onMessageClick, R.drawable.ic_baseline_photo_camera_24, this::onFirstBtnClick, R.drawable.ic_baseline_favorite_24, this::onSecondBtnClick); // This TabLayout is in the custom toolbar. TabLayout tabLayout = findViewById(R.id.tab_layout); ViewPager viewPager = findViewById(R.id.view_pager); viewPager.setAdapter(new TabPagerAdapter(getSupportFragmentManager())); tabLayout.setupWithViewPager(viewPager); } private void onMessageClick(View view) { Toast.makeText(this, "message", Toast.LENGTH_SHORT).show(); } private void onFirstBtnClick(View view) { Toast.makeText(this, "camera", Toast.LENGTH_SHORT).show(); } private void onSecondBtnClick(View view) { Toast.makeText(this, "favorite", Toast.LENGTH_SHORT).show(); } public static class TabPagerAdapter extends FragmentPagerAdapter { public TabPagerAdapter(FragmentManager fm) { super(fm, FragmentPagerAdapter.BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT); } @NonNull @Override public Fragment getItem(int i) { return new SimpleFragment(); } @Override public int getCount() { return 2; } @Nullable @Override public CharSequence getPageTitle(int position) { return "tab " + position; } } } ================================================ FILE: sample-java/src/main/java/com/dylanc/loadingstateview/sample/java/ui/FragmentEmptyActivity.java ================================================ /* * Copyright (c) 2019. Dylan Cai * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.dylanc.loadingstateview.sample.java.ui; import android.os.Bundle; import androidx.annotation.Nullable; import androidx.appcompat.app.AppCompatActivity; import androidx.fragment.app.FragmentTransaction; import com.dylanc.loadingstateview.sample.java.R; import com.dylanc.loadingstateview.sample.java.delegate.NavIconType; import com.dylanc.loadingstateview.sample.java.ui.fragment.EmptyFragment; import com.dylanc.loadingstateview.sample.java.utils.ToolbarUtils; /** * @author Dylan Cai */ public class FragmentEmptyActivity extends AppCompatActivity { @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_fragment); ToolbarUtils.setToolbar(this,"Fragment(empty)", NavIconType.BACK); final FragmentTransaction transaction = getSupportFragmentManager().beginTransaction(); transaction.add(R.id.content_view, new EmptyFragment()); transaction.commit(); } } ================================================ FILE: sample-java/src/main/java/com/dylanc/loadingstateview/sample/java/ui/MainActivity.java ================================================ /* * Copyright (c) 2019. Dylan Cai * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.dylanc.loadingstateview.sample.java.ui; import android.content.Intent; import android.net.Uri; import android.os.Bundle; import android.view.MenuItem; import android.view.View; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import com.dylanc.loadingstateview.sample.java.R; import com.dylanc.loadingstateview.sample.java.delegate.NavIconType; import com.dylanc.loadingstateview.sample.java.base.BaseActivity; /** * @author Dylan Cai */ public class MainActivity extends BaseActivity { @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); setToolbar(getString(R.string.app_name), NavIconType.NONE, R.menu.menu_about); } @Override public boolean onOptionsItemSelected(@NonNull MenuItem item) { if (item.getItemId() == R.id.about) { Uri uri = Uri.parse("https://github.com/DylanCaiCoding/LoadingHelper"); Intent intent = new Intent("android.intent.action.VIEW", uri); startActivity(intent); } return true; } public void onViewClicked(View view) { switch (view.getId()) { case R.id.btn_activity_error: startActivity(new Intent(this, ActErrorActivity.class)); break; case R.id.btn_fragment_empty: startActivity(new Intent(this, FragmentEmptyActivity.class)); break; case R.id.btn_view_placeholder: startActivity(new Intent(this, ViewPlaceholderActivity.class)); break; case R.id.btn_viewpager_timeout: startActivity(new Intent(this, ViewPagerActivity.class)); break; case R.id.btn_recyclerview_loading: startActivity(new Intent(this, RecyclerViewActivity.class)); break; case R.id.btn_custom_header: startActivity(new Intent(this, CustomHeaderActivity.class)); break; case R.id.btn_search_header: startActivity(new Intent(this, MultipleHeaderActivity.class)); break; case R.id.btn_scrolling: startActivity(new Intent(this, ScrollingToolbarActivity.class)); break; case R.id.btn_bottom_editor: startActivity(new Intent(this, BottomEditorActivity.class)); break; } } } ================================================ FILE: sample-java/src/main/java/com/dylanc/loadingstateview/sample/java/ui/MultipleHeaderActivity.java ================================================ /* * Copyright (c) 2019. Dylan Cai * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.dylanc.loadingstateview.sample.java.ui; import android.os.Bundle; import android.widget.Toast; import androidx.annotation.Nullable; import androidx.appcompat.app.AppCompatActivity; import com.dylanc.loadingstateview.LoadingStateView; import com.dylanc.loadingstateview.sample.java.R; import com.dylanc.loadingstateview.sample.java.delegate.NothingViewDelegate; import com.dylanc.loadingstateview.sample.java.delegate.SearchHeaderViewDelegate; import com.dylanc.loadingstateview.sample.java.delegate.ToolbarViewDelegate; import com.dylanc.loadingstateview.sample.java.delegate.NavIconType; import com.dylanc.loadingstateview.sample.java.utils.HttpUtils; /** * @author Dylan Cai */ public class MultipleHeaderActivity extends AppCompatActivity implements SearchHeaderViewDelegate.OnSearchListener { private LoadingStateView loadingStateView; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.layout_content); loadingStateView = new LoadingStateView(this); loadingStateView.register(new NothingViewDelegate()); loadingStateView.setHeaders( new ToolbarViewDelegate("MultipleHeader(search)", NavIconType.BACK), new SearchHeaderViewDelegate(this) ); loadingStateView.showEmptyView(); } @Override public void onSearch(String keyword) { Toast.makeText(this, "search: " + keyword, Toast.LENGTH_SHORT).show(); loadingStateView.showLoadingView(); HttpUtils.requestSuccess(new HttpUtils.Callback() { @Override public void onSuccess() { loadingStateView.showContentView(); } @Override public void onFailure() { loadingStateView.showErrorView(); } }); } } ================================================ FILE: sample-java/src/main/java/com/dylanc/loadingstateview/sample/java/ui/RecyclerViewActivity.java ================================================ /* * Copyright (c) 2019. Dylan Cai * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.dylanc.loadingstateview.sample.java.ui; import android.graphics.drawable.Drawable; import android.os.Bundle; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.ImageView; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.appcompat.app.AppCompatActivity; import androidx.recyclerview.widget.GridLayoutManager; import androidx.recyclerview.widget.RecyclerView; import com.bumptech.glide.Glide; import com.bumptech.glide.load.DataSource; import com.bumptech.glide.load.engine.GlideException; import com.bumptech.glide.request.RequestListener; import com.bumptech.glide.request.target.Target; import com.dylanc.loadingstateview.LoadingStateView; import com.dylanc.loadingstateview.ViewType; import com.dylanc.loadingstateview.sample.java.R; import com.dylanc.loadingstateview.sample.java.delegate.CoolLoadingViewDelegate; import com.dylanc.loadingstateview.sample.java.delegate.NavIconType; import com.dylanc.loadingstateview.sample.java.utils.HttpUtils; import com.dylanc.loadingstateview.sample.java.utils.ToolbarUtils; /** * @author Dylan Cai */ public class RecyclerViewActivity extends AppCompatActivity { @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_recycler_view); ToolbarUtils.setToolbar(this,"RecyclerView(cool loading)", NavIconType.BACK); RecyclerView recyclerView = findViewById(R.id.recycler_view); recyclerView.setNestedScrollingEnabled(false); recyclerView.setAdapter(new ImageAdapter()); recyclerView.setLayoutManager(new GridLayoutManager(this, 2)); } public static class ImageAdapter extends RecyclerView.Adapter { @NonNull @Override public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int i) { final View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.recycler_item_image, parent, false); return new ViewHolder(view); } @Override public void onBindViewHolder(@NonNull final ViewHolder holder, int position) { holder.showImage(HttpUtils.getRandomImageUrl()); } @Override public int getItemCount() { return 10; } static class ViewHolder extends RecyclerView.ViewHolder{ private final LoadingStateView loadingStateView; ImageView imageView; private String url; ViewHolder(@NonNull View itemView) { super(itemView); loadingStateView = new LoadingStateView(itemView.findViewById(R.id.loading_view)); loadingStateView.register(new CoolLoadingViewDelegate()); loadingStateView.setOnReloadListener(() -> showImage(url)); imageView = itemView.findViewById(R.id.image_view); } void showImage(String url) { this.url = url; loadingStateView.showLoadingView(); Glide.with(imageView.getContext()) .load(url) .listener(new RequestListener() { @Override public boolean onLoadFailed(@Nullable GlideException e, Object model, Target target, boolean isFirstResource) { loadingStateView.showErrorView(); return false; } @Override public boolean onResourceReady(Drawable resource, Object model, Target target, DataSource dataSource, boolean isFirstResource) { loadingStateView.showContentView(); return false; } }) .into(imageView); } } } } ================================================ FILE: sample-java/src/main/java/com/dylanc/loadingstateview/sample/java/ui/ScrollingToolbarActivity.java ================================================ /* * Copyright (c) 2019. Dylan Cai * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.dylanc.loadingstateview.sample.java.ui; import android.os.Bundle; import android.view.ViewGroup; import androidx.annotation.Nullable; import androidx.appcompat.app.AppCompatActivity; import com.dylanc.loadingstateview.LoadingStateView; import com.dylanc.loadingstateview.ViewType; import com.dylanc.loadingstateview.sample.java.R; import com.dylanc.loadingstateview.sample.java.delegate.LoadingViewDelegate; import com.dylanc.loadingstateview.sample.java.utils.HttpUtils; import com.dylanc.loadingstateview.sample.java.utils.ToolbarUtils; /** * @author Dylan Cai */ public class ScrollingToolbarActivity extends AppCompatActivity { private LoadingStateView loadingStateView; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_scrolling); loadingStateView = ToolbarUtils.setScrollingToolbar(this, "SpecialDecorView(scrolling)"); loadingStateView.updateViewDelegate(ViewType.LOADING, (LoadingViewDelegate delegate) -> delegate.height = ViewGroup.LayoutParams.WRAP_CONTENT); loadData(); } private void loadData() { loadingStateView.showLoadingView(); HttpUtils.requestSuccess(new HttpUtils.Callback() { @Override public void onSuccess() { loadingStateView.showContentView(); } @Override public void onFailure() { loadingStateView.showErrorView(); } }); } } ================================================ FILE: sample-java/src/main/java/com/dylanc/loadingstateview/sample/java/ui/ViewPagerActivity.java ================================================ /* * Copyright (c) 2019. Dylan Cai * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.dylanc.loadingstateview.sample.java.ui; import android.os.Bundle; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.appcompat.app.AppCompatActivity; import androidx.fragment.app.Fragment; import androidx.fragment.app.FragmentManager; import androidx.fragment.app.FragmentPagerAdapter; import androidx.viewpager.widget.ViewPager; import com.dylanc.loadingstateview.sample.java.R; import com.dylanc.loadingstateview.sample.java.delegate.NavIconType; import com.dylanc.loadingstateview.sample.java.ui.fragment.TimeoutFragment; import com.dylanc.loadingstateview.sample.java.utils.ToolbarUtils; import com.google.android.material.tabs.TabLayout; /** * @author Dylan Cai */ public class ViewPagerActivity extends AppCompatActivity { @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_tab_layout); ToolbarUtils.setToolbar(this, "ViewPager(timeout)", NavIconType.BACK); TabLayout tabLayout = findViewById(R.id.tab_layout); ViewPager viewPager = findViewById(R.id.view_pager); viewPager.setAdapter(new TabPagerAdapter(getSupportFragmentManager())); tabLayout.setupWithViewPager(viewPager); } public static class TabPagerAdapter extends FragmentPagerAdapter { public TabPagerAdapter(FragmentManager fm) { super(fm, FragmentPagerAdapter.BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT); } @NonNull @Override public Fragment getItem(int i) { return new TimeoutFragment(); } @Override public int getCount() { return 2; } @Nullable @Override public CharSequence getPageTitle(int position) { return "tab " + position; } } } ================================================ FILE: sample-java/src/main/java/com/dylanc/loadingstateview/sample/java/ui/ViewPlaceholderActivity.java ================================================ /* * Copyright (c) 2019. Dylan Cai * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.dylanc.loadingstateview.sample.java.ui; import android.os.Bundle; import android.view.View; import androidx.annotation.Nullable; import androidx.appcompat.app.AppCompatActivity; import com.dylanc.loadingstateview.LoadingStateView; import com.dylanc.loadingstateview.ViewType; import com.dylanc.loadingstateview.sample.java.R; import com.dylanc.loadingstateview.sample.java.delegate.PlaceholderViewDelegate; import com.dylanc.loadingstateview.sample.java.delegate.NavIconType; import com.dylanc.loadingstateview.sample.java.utils.HttpUtils; import com.dylanc.loadingstateview.sample.java.utils.ToolbarUtils; /** * @author Dylan Cai */ public class ViewPlaceholderActivity extends AppCompatActivity { private LoadingStateView loadingStateView; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_view); ToolbarUtils.setToolbar(this,"View(placeholder)", NavIconType.BACK); View view = findViewById(R.id.content); loadingStateView = new LoadingStateView(view); loadingStateView.register(new PlaceholderViewDelegate()); loadData(); } private void loadData() { loadingStateView.showLoadingView(); HttpUtils.requestSuccess(new HttpUtils.Callback() { @Override public void onSuccess() { loadingStateView.showContentView(); } @Override public void onFailure() { loadingStateView.showLoadingView(); } }); } } ================================================ FILE: sample-java/src/main/java/com/dylanc/loadingstateview/sample/java/ui/fragment/EmptyFragment.java ================================================ /* * Copyright (c) 2019. Dylan Cai * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.dylanc.loadingstateview.sample.java.ui.fragment; import android.os.Bundle; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.fragment.app.Fragment; import com.dylanc.loadingstateview.LoadingStateView; import com.dylanc.loadingstateview.OnReloadListener; import com.dylanc.loadingstateview.sample.java.databinding.LayoutContentBinding; import com.dylanc.loadingstateview.sample.java.utils.HttpUtils; /** * @author Dylan Cai */ @SuppressWarnings("FieldCanBeLocal") public class EmptyFragment extends Fragment implements OnReloadListener { private LayoutContentBinding binding; private LoadingStateView loadingStateView; @Nullable @Override public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { binding = LayoutContentBinding.inflate(inflater, container, false); loadingStateView = new LoadingStateView(binding.getRoot()); loadingStateView.setOnReloadListener(this); return loadingStateView.getDecorView(); } @Override public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); loadData(); } private void loadData() { loadingStateView.showLoadingView(); HttpUtils.requestSuccess(new HttpUtils.Callback() { @Override public void onSuccess() { loadingStateView.showEmptyView(); } @Override public void onFailure() { loadingStateView.showErrorView(); } }); } @Override public void onReload() { loadingStateView.showLoadingView(); HttpUtils.requestSuccess(new HttpUtils.Callback() { @Override public void onSuccess() { loadingStateView.showEmptyView(); } @Override public void onFailure() { loadingStateView.showErrorView(); } }); } } ================================================ FILE: sample-java/src/main/java/com/dylanc/loadingstateview/sample/java/ui/fragment/SimpleFragment.java ================================================ /* * Copyright (c) 2019. Dylan Cai * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.dylanc.loadingstateview.sample.java.ui.fragment; import android.os.Bundle; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.fragment.app.Fragment; import com.dylanc.loadingstateview.sample.java.R; /** * @author Dylan Cai */ public class SimpleFragment extends Fragment { @Nullable @Override public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { return inflater.inflate(R.layout.layout_content, container, false); } } ================================================ FILE: sample-java/src/main/java/com/dylanc/loadingstateview/sample/java/ui/fragment/TimeoutFragment.java ================================================ /* * Copyright (c) 2019. Dylan Cai * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.dylanc.loadingstateview.sample.java.ui.fragment; import android.os.Bundle; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.fragment.app.Fragment; import com.dylanc.loadingstateview.LoadingStateView; import com.dylanc.loadingstateview.OnReloadListener; import com.dylanc.loadingstateview.sample.java.delegate.TimeoutViewDelegate; import com.dylanc.loadingstateview.sample.java.databinding.LayoutContentBinding; import com.dylanc.loadingstateview.sample.java.utils.HttpUtils; /** * @author Dylan Cai */ @SuppressWarnings("FieldCanBeLocal") public class TimeoutFragment extends Fragment implements OnReloadListener { public static final String VIEW_TYPE_TIMEOUT = "timeout"; private LayoutContentBinding binding; private LoadingStateView loadingStateView; @Nullable @Override public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { binding = LayoutContentBinding.inflate(inflater, container, false); loadingStateView = new LoadingStateView(binding.getRoot()); loadingStateView.register(new TimeoutViewDelegate()); loadingStateView.setOnReloadListener(this); return loadingStateView.getDecorView(); } @Override public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); loadData(); } private void loadData() { loadingStateView.showLoadingView(); HttpUtils.requestFailure(new HttpUtils.Callback() { @Override public void onSuccess() { loadingStateView.showContentView(); } @Override public void onFailure() { loadingStateView.showView(VIEW_TYPE_TIMEOUT); } }); } @Override public void onReload() { loadingStateView.showLoadingView(); HttpUtils.requestSuccess(new HttpUtils.Callback() { @Override public void onSuccess() { loadingStateView.showContentView(); } @Override public void onFailure() { loadingStateView.showView(VIEW_TYPE_TIMEOUT); } }); } } ================================================ FILE: sample-java/src/main/java/com/dylanc/loadingstateview/sample/java/utils/DensityUtils.java ================================================ /* * Copyright (c) 2019. Dylan Cai * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.dylanc.loadingstateview.sample.java.utils; import android.content.Context; /** * @author Dylan Cai */ public class DensityUtils { public static float dip2px(Context context, float dpValue) { float scale = context.getResources().getDisplayMetrics().density; return dpValue * scale; } } ================================================ FILE: sample-java/src/main/java/com/dylanc/loadingstateview/sample/java/utils/HttpUtils.java ================================================ /* * Copyright (c) 2019. Dylan Cai * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.dylanc.loadingstateview.sample.java.utils; import android.os.Handler; import java.util.Random; /** * @author Dylan Cai */ public class HttpUtils { /** * 模拟请求,两秒后回调请求成功方法 */ public static void requestSuccess(final Callback callback){ new Handler().postDelayed(() -> { if (callback!=null){ callback.onSuccess(); } },2000); } /** * 模拟请求,两秒后回调请求失败方法 */ public static void requestFailure(final Callback callback){ new Handler().postDelayed(() -> { if (callback!=null){ callback.onFailure(); } },2000); } public static String getRandomImageUrl(){ int position = new Random().nextInt(100); return "https://source.unsplash.com/collection/"+position+"/1600x900"; } public interface Callback{ void onSuccess(); void onFailure(); } } ================================================ FILE: sample-java/src/main/java/com/dylanc/loadingstateview/sample/java/utils/KeyboardUtils.java ================================================ /* * Copyright (c) 2019. Dylan Cai * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.dylanc.loadingstateview.sample.java.utils; import android.content.Context; import android.view.inputmethod.InputMethodManager; import android.widget.EditText; /** * @author Dylan Cai */ public class KeyboardUtils { public static void showKeyboard( EditText editText) { InputMethodManager imm = (InputMethodManager) editText.getContext() .getSystemService(Context.INPUT_METHOD_SERVICE); if (imm != null) { imm.showSoftInput(editText, InputMethodManager.RESULT_SHOWN); imm.toggleSoftInput(InputMethodManager.SHOW_FORCED, InputMethodManager.HIDE_IMPLICIT_ONLY); } } public static void hideKeyboard(EditText editText) { InputMethodManager imm = (InputMethodManager) editText.getContext() .getSystemService(Context.INPUT_METHOD_SERVICE); if (imm != null) { imm.hideSoftInputFromWindow(editText.getWindowToken(), 0); } } } ================================================ FILE: sample-java/src/main/java/com/dylanc/loadingstateview/sample/java/utils/ToolbarUtils.java ================================================ /* * Copyright (c) 2019. Dylan Cai * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.dylanc.loadingstateview.sample.java.utils; import android.app.Activity; import android.view.MenuItem; import android.view.View; import com.dylanc.loadingstateview.LoadingStateView; import com.dylanc.loadingstateview.sample.java.delegate.CustomHeaderViewDelegate; import com.dylanc.loadingstateview.sample.java.delegate.ScrollingDecorViewDelegate; import com.dylanc.loadingstateview.sample.java.delegate.ToolbarViewDelegate; import com.dylanc.loadingstateview.sample.java.delegate.NavIconType; import kotlin.jvm.functions.Function1; /** * @author Dylan Cai */ @SuppressWarnings("UnusedReturnValue") public class ToolbarUtils { public static LoadingStateView setToolbar(Activity activity, String title, NavIconType type) { return setToolbar(activity, title, type, 0, null); } public static LoadingStateView setToolbar(Activity activity, String title, NavIconType type, int menuId, Function1 onMenuItemClick) { LoadingStateView loadingStateView = new LoadingStateView(activity); loadingStateView.setHeaders(new ToolbarViewDelegate(title, type, menuId, onMenuItemClick)); return loadingStateView; } public static LoadingStateView setCustomToolbar(Activity activity, View.OnClickListener onMessageClick, int firstDrawableId, View.OnClickListener onFirstBtnClick, int secondDrawableId, View.OnClickListener onSecondBtnClick) { LoadingStateView loadingStateView = new LoadingStateView(activity); loadingStateView.setHeaders(new CustomHeaderViewDelegate(onMessageClick, firstDrawableId, onFirstBtnClick, secondDrawableId, onSecondBtnClick)); return loadingStateView; } public static LoadingStateView setScrollingToolbar(Activity activity, String title) { LoadingStateView loadingStateView = new LoadingStateView(activity); loadingStateView.setDecorView(new ScrollingDecorViewDelegate(title)); return loadingStateView; } } ================================================ FILE: sample-java/src/main/java/com/dylanc/loadingstateview/sample/java/widget/LoadingDrawable.java ================================================ package com.dylanc.loadingstateview.sample.java.widget; import android.graphics.Canvas; import android.graphics.ColorFilter; import android.graphics.PixelFormat; import android.graphics.Rect; import android.graphics.drawable.Animatable; import android.graphics.drawable.Drawable; import androidx.annotation.NonNull; public class LoadingDrawable extends Drawable implements Animatable { private final LoadingRenderer mLoadingRender; LoadingDrawable(LoadingRenderer loadingRender) { this.mLoadingRender = loadingRender; Callback mCallback = new Callback() { @Override public void invalidateDrawable(@NonNull Drawable d) { invalidateSelf(); } @Override public void scheduleDrawable(@NonNull Drawable d, @NonNull Runnable what, long when) { scheduleSelf(what, when); } @Override public void unscheduleDrawable(@NonNull Drawable d, @NonNull Runnable what) { unscheduleSelf(what); } }; this.mLoadingRender.setCallback(mCallback); } @Override protected void onBoundsChange(Rect bounds) { super.onBoundsChange(bounds); this.mLoadingRender.setBounds(bounds); } @Override public void draw(@NonNull Canvas canvas) { if (!getBounds().isEmpty()) { this.mLoadingRender.draw(canvas); } } @Override public void setAlpha(int alpha) { this.mLoadingRender.setAlpha(alpha); } @Override public void setColorFilter(ColorFilter cf) { this.mLoadingRender.setColorFilter(cf); } @Override public int getOpacity() { return PixelFormat.TRANSLUCENT; } @Override public void start() { this.mLoadingRender.start(); } @Override public void stop() { this.mLoadingRender.stop(); } @Override public boolean isRunning() { return this.mLoadingRender.isRunning(); } @Override public int getIntrinsicHeight() { return (int) this.mLoadingRender.mHeight; } @Override public int getIntrinsicWidth() { return (int) this.mLoadingRender.mWidth; } } ================================================ FILE: sample-java/src/main/java/com/dylanc/loadingstateview/sample/java/widget/LoadingRenderer.java ================================================ package com.dylanc.loadingstateview.sample.java.widget; import android.animation.Animator; import android.animation.ValueAnimator; import android.annotation.SuppressLint; import android.content.Context; import android.graphics.Canvas; import android.graphics.ColorFilter; import android.graphics.Rect; import android.graphics.drawable.Drawable; import android.view.animation.Animation; import android.view.animation.LinearInterpolator; import com.dylanc.loadingstateview.sample.java.utils.DensityUtils; public abstract class LoadingRenderer { private static final long ANIMATION_DURATION = 1333; private static final float DEFAULT_SIZE = 56.0f; private final ValueAnimator.AnimatorUpdateListener mAnimatorUpdateListener = animation -> { computeRender((float) animation.getAnimatedValue()); invalidateSelf(); }; /** * Whenever {@link LoadingDrawable} boundary changes mBounds will be updated. * More details you can see {@link LoadingDrawable#onBoundsChange(Rect)} */ @SuppressWarnings("WeakerAccess") protected final Rect mBounds = new Rect(); private Drawable.Callback mCallback; private ValueAnimator mRenderAnimator; protected long mDuration; protected float mWidth; protected float mHeight; public LoadingRenderer(Context context) { initParams(context); setupAnimators(); } @SuppressWarnings("DeprecatedIsStillUsed") @Deprecated protected void draw(Canvas canvas, Rect bounds) { } @SuppressWarnings({"WeakerAccess", "deprecation"}) protected void draw(Canvas canvas) { draw(canvas, mBounds); } protected abstract void computeRender(float renderProgress); protected abstract void setAlpha(int alpha); protected abstract void setColorFilter(ColorFilter cf); protected abstract void reset(); protected void addRenderListener(Animator.AnimatorListener animatorListener) { mRenderAnimator.addListener(animatorListener); } void start() { reset(); mRenderAnimator.addUpdateListener(mAnimatorUpdateListener); mRenderAnimator.setRepeatCount(ValueAnimator.INFINITE); mRenderAnimator.setDuration(mDuration); mRenderAnimator.start(); } void stop() { // if I just call mRenderAnimator.end(), // it will always call the method onAnimationUpdate(ValueAnimator animation) // why ? if you know why please send email to me (dinus_developer@163.com) mRenderAnimator.removeUpdateListener(mAnimatorUpdateListener); mRenderAnimator.setRepeatCount(0); mRenderAnimator.setDuration(0); mRenderAnimator.end(); } boolean isRunning() { return mRenderAnimator.isRunning(); } void setCallback(Drawable.Callback callback) { this.mCallback = callback; } void setBounds(Rect bounds) { mBounds.set(bounds); } private void initParams(Context context) { mWidth = DensityUtils.dip2px(context, DEFAULT_SIZE); mHeight = DensityUtils.dip2px(context, DEFAULT_SIZE); mDuration = ANIMATION_DURATION; } @SuppressLint("WrongConstant") private void setupAnimators() { mRenderAnimator = ValueAnimator.ofFloat(0.0f, 1.0f); mRenderAnimator.setRepeatCount(Animation.INFINITE); mRenderAnimator.setRepeatMode(Animation.RESTART); mRenderAnimator.setDuration(mDuration); //fuck you! the default interpolator is AccelerateDecelerateInterpolator mRenderAnimator.setInterpolator(new LinearInterpolator()); mRenderAnimator.addUpdateListener(mAnimatorUpdateListener); } private void invalidateSelf() { mCallback.invalidateDrawable(null); } } ================================================ FILE: sample-java/src/main/java/com/dylanc/loadingstateview/sample/java/widget/LoadingRendererFactory.java ================================================ package com.dylanc.loadingstateview.sample.java.widget; import android.content.Context; import android.util.SparseArray; import com.dylanc.loadingstateview.sample.java.widget.renderer.ElectricFanLoadingRenderer; import java.lang.reflect.Constructor; final class LoadingRendererFactory { private static final SparseArray> LOADING_RENDERERS = new SparseArray<>(); static { LOADING_RENDERERS.put(9, ElectricFanLoadingRenderer.class); } private LoadingRendererFactory() { } static LoadingRenderer createLoadingRenderer(Context context, int loadingRendererId) throws Exception { Class loadingRendererClazz = LOADING_RENDERERS.get(loadingRendererId); Constructor[] constructors = loadingRendererClazz.getDeclaredConstructors(); for (Constructor constructor : constructors) { Class[] parameterTypes = constructor.getParameterTypes(); if (parameterTypes.length == 1 && parameterTypes[0].equals(Context.class)) { constructor.setAccessible(true); return (LoadingRenderer) constructor.newInstance(context); } } throw new InstantiationException(); } } ================================================ FILE: sample-java/src/main/java/com/dylanc/loadingstateview/sample/java/widget/LoadingView.java ================================================ package com.dylanc.loadingstateview.sample.java.widget; import android.content.Context; import android.content.res.TypedArray; import androidx.annotation.NonNull; import androidx.appcompat.widget.AppCompatImageView; import android.util.AttributeSet; import android.view.View; import com.dylanc.loadingstateview.sample.java.R; public class LoadingView extends AppCompatImageView { private LoadingDrawable mLoadingDrawable; public LoadingView(Context context) { super(context); } public LoadingView(Context context, AttributeSet attrs) { super(context, attrs); initAttrs(context, attrs); } private void initAttrs(Context context, AttributeSet attrs) { try { TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.LoadingView); int loadingRendererId = ta.getInt(R.styleable.LoadingView_loading_renderer, 0); LoadingRenderer loadingRenderer = LoadingRendererFactory.createLoadingRenderer(context, loadingRendererId); setLoadingRenderer(loadingRenderer); ta.recycle(); } catch (Exception e) { e.printStackTrace(); } } public void setLoadingRenderer(LoadingRenderer loadingRenderer) { mLoadingDrawable = new LoadingDrawable(loadingRenderer); setImageDrawable(mLoadingDrawable); } @Override protected void onAttachedToWindow() { super.onAttachedToWindow(); startAnimation(); } @Override protected void onDetachedFromWindow() { super.onDetachedFromWindow(); stopAnimation(); } @Override protected void onVisibilityChanged(@NonNull View changedView, int visibility) { super.onVisibilityChanged(changedView, visibility); final boolean visible = visibility == VISIBLE && getVisibility() == VISIBLE; if (visible) { startAnimation(); } else { stopAnimation(); } } private void startAnimation() { if (mLoadingDrawable != null) { mLoadingDrawable.start(); } } private void stopAnimation() { if (mLoadingDrawable != null) { mLoadingDrawable.stop(); } } } ================================================ FILE: sample-java/src/main/java/com/dylanc/loadingstateview/sample/java/widget/renderer/ElectricFanLoadingRenderer.java ================================================ package com.dylanc.loadingstateview.sample.java.widget.renderer; import android.animation.*; import android.content.Context; import android.graphics.*; import android.graphics.drawable.Drawable; import androidx.annotation.IntDef; import androidx.interpolator.view.animation.FastOutLinearInInterpolator; import androidx.interpolator.view.animation.FastOutSlowInInterpolator; import android.view.animation.AccelerateInterpolator; import android.view.animation.DecelerateInterpolator; import android.view.animation.Interpolator; import android.view.animation.LinearInterpolator; import com.dylanc.loadingstateview.sample.java.R; import com.dylanc.loadingstateview.sample.java.utils.DensityUtils; import com.dylanc.loadingstateview.sample.java.widget.LoadingRenderer; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.List; import java.util.Random; public class ElectricFanLoadingRenderer extends LoadingRenderer { private static final Interpolator LINEAR_INTERPOLATOR = new LinearInterpolator(); private static final Interpolator MATERIAL_INTERPOLATOR = new FastOutSlowInInterpolator(); private static final Interpolator DECELERATE_INTERPOLATOR = new DecelerateInterpolator(); private static final Interpolator ACCELERATE_INTERPOLATOR = new AccelerateInterpolator(); private static final Interpolator FASTOUTLINEARIN_INTERPOLATOR = new FastOutLinearInInterpolator(); private static final Interpolator[] INTERPOLATORS = new Interpolator[]{LINEAR_INTERPOLATOR, DECELERATE_INTERPOLATOR, ACCELERATE_INTERPOLATOR, FASTOUTLINEARIN_INTERPOLATOR, MATERIAL_INTERPOLATOR}; private static final List mLeafHolders = new ArrayList<>(); private static final Random mRandom = new Random(); static final int MODE_NORMAL = 0; static final int MODE_LEAF_COUNT = 1; @SuppressWarnings("WeakerAccess") @IntDef({MODE_NORMAL, MODE_LEAF_COUNT}) @Retention(RetentionPolicy.SOURCE) public @interface MODE { } private static final String PERCENTAGE_100 = "100%"; private static final long ANIMATION_DURATION = 7333; private static final int LEAF_COUNT = 28; private static final int DEGREE_180 = 180; private static final int DEGREE_360 = 360; private static final int FULL_GROUP_ROTATION = (int) (5.25f * DEGREE_360); private static final int DEFAULT_PROGRESS_COLOR = 0xfffca72e; private static final int DEFAULT_PROGRESS_BGCOLOR = 0xfffcd49f; private static final int DEFAULT_ELECTRIC_FAN_BGCOLOR = 0xfffccc59; private static final int DEFAULT_ELECTRIC_FAN_OUTLINE_COLOR = Color.WHITE; private static final float DEFAULT_WIDTH = 182.0f; private static final float DEFAULT_HEIGHT = 65.0f; private static final float DEFAULT_TEXT_SIZE = 11.0f; private static final float DEFAULT_STROKE_WIDTH = 2.0f; private static final float DEFAULT_STROKE_INTERVAL = .2f; private static final float DEFAULT_CENTER_RADIUS = 16.0f; private static final float DEFAULT_PROGRESS_CENTER_RADIUS = 11.0f; private static final float DEFAULT_LEAF_FLY_DURATION_FACTOR = 0.1f; private static final float LEAF_CREATE_DURATION_INTERVAL = 1.0f / LEAF_COUNT; private static final float DECELERATE_DURATION_PERCENTAGE = 0.4f; private static final float ACCELERATE_DURATION_PERCENTAGE = 0.6f; private final Paint mPaint = new Paint(); private final RectF mTempBounds = new RectF(); private final RectF mCurrentProgressBounds = new RectF(); private float mTextSize; private float mStrokeXInset; private float mStrokeYInset; private float mProgressCenterRadius; private float mScale; private float mRotation; private float mProgress; private float mNextLeafCreateThreshold; private int mProgressColor; private int mProgressBgColor; private int mElectricFanBgColor; private int mElectricFanOutlineColor; private float mStrokeWidth; private float mCenterRadius; @MODE private int mMode; private int mCurrentLeafCount; private Drawable mLeafDrawable; private Drawable mLoadingDrawable; private Drawable mElectricFanDrawable; private ElectricFanLoadingRenderer(Context context) { super(context); init(context); setupPaint(); Animator.AnimatorListener mAnimatorListener = new AnimatorListenerAdapter() { @Override public void onAnimationRepeat(Animator animator) { super.onAnimationRepeat(animator); reset(); } }; addRenderListener(mAnimatorListener); } private void init(Context context) { mMode = MODE_NORMAL; mWidth = DensityUtils.dip2px(context, DEFAULT_WIDTH); mHeight = DensityUtils.dip2px(context, DEFAULT_HEIGHT); mTextSize = DensityUtils.dip2px(context, DEFAULT_TEXT_SIZE); mStrokeWidth = DensityUtils.dip2px(context, DEFAULT_STROKE_WIDTH); mCenterRadius = DensityUtils.dip2px(context, DEFAULT_CENTER_RADIUS); mProgressCenterRadius = DensityUtils.dip2px(context, DEFAULT_PROGRESS_CENTER_RADIUS); mProgressColor = DEFAULT_PROGRESS_COLOR; mProgressBgColor = DEFAULT_PROGRESS_BGCOLOR; mElectricFanBgColor = DEFAULT_ELECTRIC_FAN_BGCOLOR; mElectricFanOutlineColor = DEFAULT_ELECTRIC_FAN_OUTLINE_COLOR; mLeafDrawable = context.getResources().getDrawable(R.drawable.ic_leaf); mLoadingDrawable = context.getResources().getDrawable(R.drawable.ic_loading); mElectricFanDrawable = context.getResources().getDrawable(R.drawable.ic_eletric_fan); mDuration = ANIMATION_DURATION; setInsets((int) mWidth, (int) mHeight); } private void setupPaint() { mPaint.setAntiAlias(true); mPaint.setStrokeWidth(mStrokeWidth); mPaint.setStyle(Paint.Style.STROKE); mPaint.setStrokeCap(Paint.Cap.ROUND); } @SuppressWarnings("deprecation") @Override protected void draw(Canvas canvas, Rect bounds) { int saveCount = canvas.save(); RectF arcBounds = mTempBounds; arcBounds.set(bounds); arcBounds.inset(mStrokeXInset, mStrokeYInset); mCurrentProgressBounds.set(arcBounds.left, arcBounds.bottom - 2 * mCenterRadius, arcBounds.right, arcBounds.bottom); //draw loading drawable mLoadingDrawable.setBounds((int) arcBounds.centerX() - mLoadingDrawable.getIntrinsicWidth() / 2, 0, (int) arcBounds.centerX() + mLoadingDrawable.getIntrinsicWidth() / 2, mLoadingDrawable.getIntrinsicHeight()); mLoadingDrawable.draw(canvas); //draw progress background float progressInset = mCenterRadius - mProgressCenterRadius; RectF progressRect = new RectF(mCurrentProgressBounds); //sub DEFAULT_STROKE_INTERVAL, otherwise will have a interval between progress background and progress outline progressRect.inset(progressInset - DEFAULT_STROKE_INTERVAL, progressInset - DEFAULT_STROKE_INTERVAL); mPaint.setColor(mProgressBgColor); mPaint.setStyle(Paint.Style.FILL); canvas.drawRoundRect(progressRect, mProgressCenterRadius, mProgressCenterRadius, mPaint); //draw progress mPaint.setColor(mProgressColor); mPaint.setStyle(Paint.Style.FILL); canvas.drawPath(createProgressPath(mProgress, mProgressCenterRadius, progressRect), mPaint); //draw leaves for (int i = 0; i < mLeafHolders.size(); i++) { int leafSaveCount = canvas.save(); LeafHolder leafHolder = mLeafHolders.get(i); Rect leafBounds = leafHolder.mLeafRect; canvas.rotate(leafHolder.mLeafRotation, leafBounds.centerX(), leafBounds.centerY()); mLeafDrawable.setBounds(leafBounds); mLeafDrawable.draw(canvas); canvas.restoreToCount(leafSaveCount); } //draw progress background outline, //after drawing the leaves and then draw the outline of the progress background can //prevent the leaves from flying to the outside RectF progressOutlineRect = new RectF(mCurrentProgressBounds); float progressOutlineStrokeInset = (mCenterRadius - mProgressCenterRadius) / 2.0f; progressOutlineRect.inset(progressOutlineStrokeInset, progressOutlineStrokeInset); mPaint.setStyle(Paint.Style.STROKE); mPaint.setColor(mProgressBgColor); mPaint.setStrokeWidth(mCenterRadius - mProgressCenterRadius); canvas.drawRoundRect(progressOutlineRect, mCenterRadius, mCenterRadius, mPaint); //draw electric fan outline float electricFanCenterX = arcBounds.right - mCenterRadius; float electricFanCenterY = arcBounds.bottom - mCenterRadius; mPaint.setColor(mElectricFanOutlineColor); mPaint.setStyle(Paint.Style.STROKE); mPaint.setStrokeWidth(mStrokeWidth); canvas.drawCircle(arcBounds.right - mCenterRadius, arcBounds.bottom - mCenterRadius, mCenterRadius - mStrokeWidth / 2.0f, mPaint); //draw electric background mPaint.setColor(mElectricFanBgColor); mPaint.setStyle(Paint.Style.FILL); canvas.drawCircle(arcBounds.right - mCenterRadius, arcBounds.bottom - mCenterRadius, mCenterRadius - mStrokeWidth + DEFAULT_STROKE_INTERVAL, mPaint); //draw electric fan int rotateSaveCount = canvas.save(); canvas.rotate(mRotation, electricFanCenterX, electricFanCenterY); mElectricFanDrawable.setBounds((int) (electricFanCenterX - mElectricFanDrawable.getIntrinsicWidth() / 2 * mScale), (int) (electricFanCenterY - mElectricFanDrawable.getIntrinsicHeight() / 2 * mScale), (int) (electricFanCenterX + mElectricFanDrawable.getIntrinsicWidth() / 2 * mScale), (int) (electricFanCenterY + mElectricFanDrawable.getIntrinsicHeight() / 2 * mScale)); mElectricFanDrawable.draw(canvas); canvas.restoreToCount(rotateSaveCount); //draw 100% text if (mScale < 1.0f) { mPaint.setTextSize(mTextSize * (1 - mScale)); mPaint.setColor(mElectricFanOutlineColor); Rect textRect = new Rect(); mPaint.getTextBounds(PERCENTAGE_100, 0, PERCENTAGE_100.length(), textRect); canvas.drawText(PERCENTAGE_100, electricFanCenterX - textRect.width() / 2.0f, electricFanCenterY + textRect.height() / 2.0f, mPaint); } canvas.restoreToCount(saveCount); } private Path createProgressPath(float progress, float circleRadius, RectF progressRect) { RectF arcProgressRect = new RectF(progressRect.left, progressRect.top, progressRect.left + circleRadius * 2, progressRect.bottom); RectF rectProgressRect = null; float progressWidth = progress * progressRect.width(); float progressModeWidth = mMode == MODE_LEAF_COUNT ? (float) mCurrentLeafCount / (float) LEAF_COUNT * progressRect.width() : progress * progressRect.width(); float swipeAngle = DEGREE_180; //the left half circle of the progressbar if (progressModeWidth < circleRadius) { swipeAngle = progressModeWidth / circleRadius * DEGREE_180; } //the center rect of the progressbar if (progressModeWidth < progressRect.width() - circleRadius && progressModeWidth >= circleRadius) { rectProgressRect = new RectF(progressRect.left + circleRadius, progressRect.top, progressRect.left + progressModeWidth, progressRect.bottom); } //the right half circle of the progressbar if (progressWidth >= progressRect.width() - circleRadius) { rectProgressRect = new RectF(progressRect.left + circleRadius, progressRect.top, progressRect.right - circleRadius, progressRect.bottom); mScale = (progressRect.width() - progressWidth) / circleRadius; } //the left of the right half circle if (progressWidth < progressRect.width() - circleRadius) { mRotation = (progressWidth / (progressRect.width() - circleRadius)) * FULL_GROUP_ROTATION % DEGREE_360; RectF leafRect = new RectF(progressRect.left + progressWidth, progressRect.top, progressRect.right - circleRadius, progressRect.bottom); addLeaf(progress, leafRect); } Path path = new Path(); path.addArc(arcProgressRect, DEGREE_180 - swipeAngle / 2, swipeAngle); if (rectProgressRect != null) { path.addRect(rectProgressRect, Path.Direction.CW); } return path; } @Override protected void computeRender(float renderProgress) { if (renderProgress < DECELERATE_DURATION_PERCENTAGE) { mProgress = DECELERATE_INTERPOLATOR.getInterpolation(renderProgress / DECELERATE_DURATION_PERCENTAGE) * DECELERATE_DURATION_PERCENTAGE; } else { mProgress = ACCELERATE_INTERPOLATOR.getInterpolation((renderProgress - DECELERATE_DURATION_PERCENTAGE) / ACCELERATE_DURATION_PERCENTAGE) * ACCELERATE_DURATION_PERCENTAGE + DECELERATE_DURATION_PERCENTAGE; } } @Override protected void setAlpha(int alpha) { mPaint.setAlpha(alpha); } @Override protected void setColorFilter(ColorFilter cf) { mPaint.setColorFilter(cf); } @Override protected void reset() { mScale = 1.0f; mCurrentLeafCount = 0; mNextLeafCreateThreshold = 0.0f; mLeafHolders.clear(); } private void setInsets(int width, int height) { final float minEdge = (float) Math.min(width, height); float insetXs; if (mCenterRadius <= 0 || minEdge < 0) { insetXs = (float) Math.ceil(mCenterRadius / 2.0f); } else { insetXs = mCenterRadius; } mStrokeYInset = (float) Math.ceil(mCenterRadius / 2.0f); mStrokeXInset = insetXs; } private void addLeaf(float progress, RectF leafFlyRect) { if (progress < mNextLeafCreateThreshold) { return; } mNextLeafCreateThreshold += LEAF_CREATE_DURATION_INTERVAL; LeafHolder leafHolder = new LeafHolder(); mLeafHolders.add(leafHolder); Animator leafAnimator = getAnimator(leafHolder, leafFlyRect, progress); leafAnimator.addListener(new AnimEndListener(leafHolder)); leafAnimator.start(); } private Animator getAnimator(LeafHolder target, RectF leafFlyRect, float progress) { ValueAnimator bezierValueAnimator = getBezierValueAnimator(target, leafFlyRect, progress); AnimatorSet finalSet = new AnimatorSet(); finalSet.playSequentially(bezierValueAnimator); finalSet.setInterpolator(INTERPOLATORS[mRandom.nextInt(INTERPOLATORS.length)]); finalSet.setTarget(target); return finalSet; } private ValueAnimator getBezierValueAnimator(LeafHolder target, RectF leafFlyRect, float progress) { BezierEvaluator evaluator = new BezierEvaluator(getPoint1(leafFlyRect), getPoint2(leafFlyRect)); int leafFlyStartY = (int) (mCurrentProgressBounds.bottom - mLeafDrawable.getIntrinsicHeight()); int leafFlyRange = (int) (mCurrentProgressBounds.height() - mLeafDrawable.getIntrinsicHeight()); int startPointY = leafFlyStartY - mRandom.nextInt(leafFlyRange); int endPointY = leafFlyStartY - mRandom.nextInt(leafFlyRange); ValueAnimator animator = ValueAnimator.ofObject(evaluator, new PointF((int) (leafFlyRect.right - mLeafDrawable.getIntrinsicWidth()), startPointY), new PointF(leafFlyRect.left, endPointY)); animator.addUpdateListener(new BezierListener(target)); animator.setTarget(target); animator.setDuration((long) ((mRandom.nextInt(300) + mDuration * DEFAULT_LEAF_FLY_DURATION_FACTOR) * (1.0f - progress))); return animator; } //get the pointF which belong to the right half side private PointF getPoint1(RectF leafFlyRect) { PointF point = new PointF(); point.x = leafFlyRect.right - mRandom.nextInt((int) (leafFlyRect.width() / 2)); point.y = (int) (leafFlyRect.bottom - mRandom.nextInt((int) leafFlyRect.height())); return point; } //get the pointF which belong to the left half side private PointF getPoint2(RectF leafFlyRect) { PointF point = new PointF(); point.x = leafFlyRect.left + mRandom.nextInt((int) (leafFlyRect.width() / 2)); point.y = (int) (leafFlyRect.bottom - mRandom.nextInt((int) leafFlyRect.height())); return point; } private static class BezierEvaluator implements TypeEvaluator { private PointF point1; private PointF point2; BezierEvaluator(PointF point1, PointF point2) { this.point1 = point1; this.point2 = point2; } //Third-order Bezier curve formula: B(t) = point0 * (1-t)^3 + 3 * point1 * t * (1-t)^2 + 3 * point2 * t^2 * (1-t) + point3 * t^3 @Override public PointF evaluate(float fraction, PointF point0, PointF point3) { float tLeft = 1.0f - fraction; float x = (float) (point0.x * Math.pow(tLeft, 3) + 3 * point1.x * fraction * Math.pow(tLeft, 2) + 3 * point2.x * Math.pow(fraction, 2) * tLeft + point3.x * Math.pow(fraction, 3)); float y = (float) (point0.y * Math.pow(tLeft, 3) + 3 * point1.y * fraction * Math.pow(tLeft, 2) + 3 * point2.y * Math.pow(fraction, 2) * tLeft + point3.y * Math.pow(fraction, 3)); return new PointF(x, y); } } private class BezierListener implements ValueAnimator.AnimatorUpdateListener { private LeafHolder target; BezierListener(LeafHolder target) { this.target = target; } @Override public void onAnimationUpdate(ValueAnimator animation) { PointF point = (PointF) animation.getAnimatedValue(); target.mLeafRect.set((int) point.x, (int) point.y, (int) (point.x + mLeafDrawable.getIntrinsicWidth()), (int) (point.y + mLeafDrawable.getIntrinsicHeight())); target.mLeafRotation = target.mMaxRotation * animation.getAnimatedFraction(); } } private class AnimEndListener extends AnimatorListenerAdapter { private LeafHolder target; AnimEndListener(LeafHolder target) { this.target = target; } @Override public void onAnimationEnd(Animator animation) { super.onAnimationEnd(animation); mLeafHolders.remove(target); mCurrentLeafCount++; } } private static class LeafHolder { Rect mLeafRect = new Rect(); float mLeafRotation = 0.0f; float mMaxRotation = mRandom.nextInt(120); } } ================================================ FILE: sample-java/src/main/res/drawable/bg_reload_btn.xml ================================================ ================================================ FILE: sample-java/src/main/res/drawable/bg_search.xml ================================================ ================================================ FILE: sample-java/src/main/res/drawable/ic_baseline_favorite_24.xml ================================================ ================================================ FILE: sample-java/src/main/res/drawable/ic_baseline_message_24.xml ================================================ ================================================ FILE: sample-java/src/main/res/drawable/ic_baseline_photo_camera_24.xml ================================================ ================================================ FILE: sample-java/src/main/res/drawable/ic_launcher_background.xml ================================================ ================================================ FILE: sample-java/src/main/res/drawable-v24/ic_launcher_foreground.xml ================================================ ================================================ FILE: sample-java/src/main/res/layout/activity_fragment.xml ================================================ ================================================ FILE: sample-java/src/main/res/layout/activity_main.xml ================================================