Repository: wolfking0608/SmartRefreshLayout Branch: master Commit: 1af1907b88dc Files: 233 Total size: 11.3 MB Directory structure: gitextract_vxfl1tfg/ ├── .gitignore ├── LICENSE ├── README.md ├── README_EN.md ├── app/ │ ├── .gitignore │ ├── build.gradle │ ├── debug.keystore │ ├── proguard-rules.pro │ └── src/ │ ├── androidTest/ │ │ └── java/ │ │ └── com/ │ │ └── scwang/ │ │ └── refreshlayout/ │ │ └── ExampleInstrumentedTest.java │ ├── main/ │ │ ├── AndroidManifest.xml │ │ ├── java/ │ │ │ └── com/ │ │ │ └── scwang/ │ │ │ └── refreshlayout/ │ │ │ ├── App.java │ │ │ ├── activity/ │ │ │ │ ├── ExperimentActivity.java │ │ │ │ ├── IndexMainActivity.java │ │ │ │ ├── practice/ │ │ │ │ │ ├── BannerPracticeActivity.java │ │ │ │ │ ├── FeedlistPracticeActivity.java │ │ │ │ │ ├── ProfilePracticeActivity.java │ │ │ │ │ ├── QQBrowserPracticeActivity.java │ │ │ │ │ ├── RepastPracticeActivity.java │ │ │ │ │ ├── WebviewPracticeActivity.java │ │ │ │ │ └── WeiboPracticeActivity.java │ │ │ │ ├── style/ │ │ │ │ │ ├── BezierStyleActivity.java │ │ │ │ │ ├── CircleStyleActivity.java │ │ │ │ │ ├── ClassicsStyleActivity.java │ │ │ │ │ ├── DeliveryStyleActivity.java │ │ │ │ │ ├── DropboxStyleActivity.java │ │ │ │ │ ├── FlyRefreshStyleActivity.java │ │ │ │ │ ├── FunGameBattleCityStyleActivity.java │ │ │ │ │ ├── FunGameHitBlockStyleActivity.java │ │ │ │ │ ├── MaterialStyleActivity.java │ │ │ │ │ ├── PhoenixStyleActivity.java │ │ │ │ │ ├── StoreHouseStyleActivity.java │ │ │ │ │ ├── TaurusStyleActivity.java │ │ │ │ │ ├── WaterDropStyleActivity.java │ │ │ │ │ └── WaveSwipStyleActivity.java │ │ │ │ └── using/ │ │ │ │ ├── AssignCodeUsingActivity.java │ │ │ │ ├── AssignDefaultUsingActivity.java │ │ │ │ ├── AssignXmlUsingActivity.java │ │ │ │ ├── BasicUsingActivity.java │ │ │ │ ├── CustomUsingActivity.java │ │ │ │ ├── ListenerUsingActivity.java │ │ │ │ ├── NestLayoutUsingActivity.java │ │ │ │ ├── OverScrollUsingActivity.java │ │ │ │ └── SnapHelperUsingActivity.java │ │ │ ├── adapter/ │ │ │ │ ├── BaseRecyclerAdapter.java │ │ │ │ └── SmartViewHolder.java │ │ │ ├── fragment/ │ │ │ │ ├── RefreshPractiveFragment.java │ │ │ │ ├── RefreshStylesFragment.java │ │ │ │ └── RefreshUsingFragment.java │ │ │ ├── util/ │ │ │ │ ├── DynamicTimeFormat.java │ │ │ │ └── StatusBarUtil.java │ │ │ └── widget/ │ │ │ └── RefreshLayout.java │ │ └── res/ │ │ ├── drawable/ │ │ │ ├── bc_background_panel.xml │ │ │ ├── ic_arrow_back_dark_24dp.xml │ │ │ ├── ic_arrow_back_gray_24dp.xml │ │ │ ├── ic_arrow_back_white_24dp.xml │ │ │ ├── ic_chevron_right.xml │ │ │ ├── ic_chevron_right_gray.xml │ │ │ ├── ic_feed_list_favorite.xml │ │ │ ├── ic_feed_list_mail.xml │ │ │ ├── ic_feed_list_photo.xml │ │ │ ├── ic_fly_refresh_folder.xml │ │ │ ├── ic_fly_refresh_info.xml │ │ │ ├── ic_fly_refresh_poll.xml │ │ │ ├── ic_fly_refresh_send.xml │ │ │ ├── ic_fly_refresh_smartphone.xml │ │ │ ├── ic_index_dashboard.xml │ │ │ ├── ic_index_home.xml │ │ │ ├── ic_index_notifications.xml │ │ │ ├── ic_list_divider.xml │ │ │ └── ic_progress_hojder.xml │ │ ├── layout/ │ │ │ ├── activity_experiment.xml │ │ │ ├── activity_fly_refresh.xml │ │ │ ├── activity_fly_refresh_item.xml │ │ │ ├── activity_index_main.xml │ │ │ ├── activity_practice_banner.xml │ │ │ ├── activity_practice_feedlist.xml │ │ │ ├── activity_practice_profile.xml │ │ │ ├── activity_practice_qqbrowser.xml │ │ │ ├── activity_practice_repast.xml │ │ │ ├── activity_practice_webview.xml │ │ │ ├── activity_practice_weibo.xml │ │ │ ├── activity_style_bezier.xml │ │ │ ├── activity_style_circle.xml │ │ │ ├── activity_style_classics.xml │ │ │ ├── activity_style_delivery.xml │ │ │ ├── activity_style_dropbox.xml │ │ │ ├── activity_style_fungame_battlecity.xml │ │ │ ├── activity_style_fungame_hitblock.xml │ │ │ ├── activity_style_material.xml │ │ │ ├── activity_style_phoenix.xml │ │ │ ├── activity_style_storehouse.xml │ │ │ ├── activity_style_taurus.xml │ │ │ ├── activity_style_water_drop.xml │ │ │ ├── activity_style_wave_swip.xml │ │ │ ├── activity_using_assign_code.xml │ │ │ ├── activity_using_assign_default.xml │ │ │ ├── activity_using_assign_xml.xml │ │ │ ├── activity_using_basic.xml │ │ │ ├── activity_using_custom.xml │ │ │ ├── activity_using_listener.xml │ │ │ ├── activity_using_overscroll.xml │ │ │ ├── activity_using_region.xml │ │ │ ├── activity_using_snaphelper.xml │ │ │ ├── fragment_refresh_practive.xml │ │ │ ├── fragment_refresh_styles.xml │ │ │ ├── fragment_refresh_using.xml │ │ │ ├── listitem_movie_banner.xml │ │ │ ├── listitem_movie_header.xml │ │ │ ├── listitem_movie_item.xml │ │ │ ├── listitem_practive_repast.xml │ │ │ ├── listitem_style_delivery.xml │ │ │ └── listitem_using_snaphelper.xml │ │ ├── menu/ │ │ │ └── navigation.xml │ │ ├── values/ │ │ │ ├── colors.xml │ │ │ ├── dimens.xml │ │ │ ├── strings.xml │ │ │ └── styles.xml │ │ ├── values-v19/ │ │ │ └── styles.xml │ │ └── values-v21/ │ │ └── styles.xml │ └── test/ │ └── java/ │ └── com/ │ └── scwang/ │ └── refreshlayout/ │ └── ExampleUnitTest.java ├── art/ │ ├── UMLRefreshLayout.classdiagram │ ├── app-debug.apk │ ├── md_custom.md │ ├── md_donationlist.md │ ├── md_multitouch.md │ ├── md_property.md │ ├── md_smart.md │ └── md_update.md ├── bintrayUpload.bat ├── build.gradle ├── refresh-footer/ │ ├── .gitignore │ ├── build.gradle │ ├── proguard-rules.pro │ └── src/ │ ├── androidTest/ │ │ └── java/ │ │ └── com/ │ │ └── scwang/ │ │ └── smartrefresh/ │ │ └── footer/ │ │ └── ExampleInstrumentedTest.java │ ├── main/ │ │ ├── AndroidManifest.xml │ │ └── res/ │ │ └── values/ │ │ ├── attrs.xml │ │ └── strings.xml │ └── test/ │ └── java/ │ └── com/ │ └── scwang/ │ └── smartrefresh/ │ └── footer/ │ └── ExampleUnitTest.java ├── refresh-header/ │ ├── .gitignore │ ├── build.gradle │ ├── proguard-rules.pro │ └── src/ │ ├── androidTest/ │ │ └── java/ │ │ └── com/ │ │ └── scwang/ │ │ └── smartrefresh/ │ │ └── header/ │ │ └── ExampleInstrumentedTest.java │ ├── main/ │ │ ├── AndroidManifest.xml │ │ ├── java/ │ │ │ └── com/ │ │ │ └── scwang/ │ │ │ └── smartrefresh/ │ │ │ └── header/ │ │ │ ├── CircleHeader.java │ │ │ ├── DeliveryHeader.java │ │ │ ├── DropboxHeader.java │ │ │ ├── FlyRefreshHeader.java │ │ │ ├── FunGameBattleCityHeader.java │ │ │ ├── FunGameHitBlockHeader.java │ │ │ ├── MaterialHeader.java │ │ │ ├── PhoenixHeader.java │ │ │ ├── StoreHouseHeader.java │ │ │ ├── TaurusHeader.java │ │ │ ├── WaterDropHeader.java │ │ │ ├── WaveSwipeHeader.java │ │ │ ├── flyrefresh/ │ │ │ │ ├── FlyView.java │ │ │ │ ├── MountanScenceView.java │ │ │ │ ├── PathInterpolatorCompat.java │ │ │ │ ├── PathInterpolatorCompatApi21.java │ │ │ │ ├── PathInterpolatorCompatBase.java │ │ │ │ └── PathInterpolatorGingerbread.java │ │ │ ├── fungame/ │ │ │ │ ├── FunGameBase.java │ │ │ │ ├── FunGameHeader.java │ │ │ │ └── FunGameView.java │ │ │ ├── internal/ │ │ │ │ ├── FastOutSlowInInterpolator.java │ │ │ │ ├── LookupTableInterpolator.java │ │ │ │ └── MaterialProgressDrawable.java │ │ │ ├── material/ │ │ │ │ └── CircleImageView.java │ │ │ ├── storehouse/ │ │ │ │ ├── StoreHouseBarItem.java │ │ │ │ └── StoreHousePath.java │ │ │ ├── waterdrop/ │ │ │ │ ├── Circle.java │ │ │ │ └── WaterDropView.java │ │ │ └── waveswipe/ │ │ │ ├── AnimationImageView.java │ │ │ ├── DisplayUtil.java │ │ │ ├── DropBounceInterpolator.java │ │ │ └── WaveView.java │ │ └── res/ │ │ └── values/ │ │ ├── attrs.xml │ │ └── strings.xml │ └── test/ │ └── java/ │ └── com/ │ └── scwang/ │ └── smartrefresh/ │ └── header/ │ └── ExampleUnitTest.java ├── refresh-layout/ │ ├── .gitignore │ ├── build.gradle │ ├── proguard-rules.pro │ └── src/ │ ├── androidTest/ │ │ └── java/ │ │ └── com/ │ │ └── scwang/ │ │ └── smartrefresh/ │ │ └── layout/ │ │ └── ExampleInstrumentedTest.java │ ├── main/ │ │ ├── AndroidManifest.xml │ │ ├── java/ │ │ │ ├── android/ │ │ │ │ └── support/ │ │ │ │ └── v4/ │ │ │ │ └── view/ │ │ │ │ └── PagerAdapterWrapper.java │ │ │ └── com/ │ │ │ └── scwang/ │ │ │ └── smartrefresh/ │ │ │ └── layout/ │ │ │ ├── SmartRefreshLayout.java │ │ │ ├── api/ │ │ │ │ ├── DefaultRefreshFooterCreater.java │ │ │ │ ├── DefaultRefreshHeaderCreater.java │ │ │ │ ├── RefreshContent.java │ │ │ │ ├── RefreshFooter.java │ │ │ │ ├── RefreshHeader.java │ │ │ │ ├── RefreshInternal.java │ │ │ │ ├── RefreshKernel.java │ │ │ │ ├── RefreshLayout.java │ │ │ │ └── ScrollBoundaryDecider.java │ │ │ ├── constant/ │ │ │ │ ├── DimensionStatus.java │ │ │ │ ├── RefreshState.java │ │ │ │ └── SpinnerStyle.java │ │ │ ├── footer/ │ │ │ │ ├── BallPulseFooter.java │ │ │ │ ├── ClassicsFooter.java │ │ │ │ ├── FalsifyFooter.java │ │ │ │ └── ballpulse/ │ │ │ │ └── BallPulseView.java │ │ │ ├── header/ │ │ │ │ ├── BezierRadarHeader.java │ │ │ │ ├── ClassicsHeader.java │ │ │ │ ├── FalsifyHeader.java │ │ │ │ └── bezierradar/ │ │ │ │ ├── RippleView.java │ │ │ │ ├── RoundDotView.java │ │ │ │ ├── RoundProgressView.java │ │ │ │ └── WaveView.java │ │ │ ├── impl/ │ │ │ │ ├── RefreshContentWrapper.java │ │ │ │ ├── RefreshFooterWrapper.java │ │ │ │ ├── RefreshHeaderWrapper.java │ │ │ │ └── ScrollBoundaryDeciderAdapter.java │ │ │ ├── internal/ │ │ │ │ ├── ProgressDrawable.java │ │ │ │ └── pathview/ │ │ │ │ ├── PathParser.java │ │ │ │ ├── PathsDrawable.java │ │ │ │ └── PathsView.java │ │ │ ├── listener/ │ │ │ │ ├── AnimationEndListener.java │ │ │ │ ├── OnLoadmoreListener.java │ │ │ │ ├── OnMultiPurposeListener.java │ │ │ │ ├── OnRefreshListener.java │ │ │ │ ├── OnRefreshLoadmoreListener.java │ │ │ │ ├── OnStateChangedListener.java │ │ │ │ └── SimpleMultiPurposeListener.java │ │ │ └── util/ │ │ │ ├── ColorUtils.java │ │ │ ├── DelayedRunable.java │ │ │ ├── DensityUtil.java │ │ │ ├── ScrollBoundaryUtil.java │ │ │ └── ViscousFluidInterpolator.java │ │ └── res/ │ │ └── values/ │ │ ├── attrs.xml │ │ └── strings.xml │ └── test/ │ └── java/ │ └── com/ │ └── scwang/ │ └── smartrefresh/ │ └── layout/ │ └── ExampleUnitTest.java └── settings.gradle ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ *.iml .gradle gradle /local.properties /.idea /build /gradlew /gradlew.bat ================================================ 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 ================================================ # Android智能下拉刷新框架-SmartRefreshLayout [![License](https://img.shields.io/badge/License%20-Apache%202-337ab7.svg)](https://www.apache.org/licenses/LICENSE-2.0) [![Arsenal](https://img.shields.io/badge/%20Arsenal%20-%20SmartRefresh%20-4cae4c.svg?style=flat)](https://android-arsenal.com/details/1/6001) [![Jcenter](https://img.shields.io/badge/%20Jcenter%20-1.0.3-5bc0de.svg) ](https://bintray.com/scwang90/maven/SmartRefreshLayout/_latestVersion) [![MinSdk](https://img.shields.io/badge/%20MinSdk%20-%2012%2B%20-f0ad4e.svg?style=flat)](https://android-arsenal.com/api?level=12) [![Methods](https://img.shields.io/badge/%20Methods%20%7C%20Size%20-%201251%20%7C%20129%20KB-d9534f.svg)](http://www.methodscount.com/?lib=com.scwang.smartrefresh%3ASmartRefreshLayout%3A1.0.2) ## [English](README_EN.md) | 中文 正如名字所说,SmartRefreshLayout是一个“聪明”或者说“智能”的下拉刷新布局,由于它的“智能”,它不只是如其它的刷新布局所说的支持所有的View,还支持多层嵌套的视图结构。 除了“聪明”之外,SmartRefreshLayout还具备了很多的特点。 它继承自ViewGroup 而不是其它的FrameLayout或者LinearLayout,提高了性能。 它也吸取了现在流行的各种刷新布局的优点,包括谷歌官方的 SwipeRefreshLayout,现在非常流行的 [TwinklingRefreshLayout](https://github.com/lcodecorex/TwinklingRefreshLayout) 、[Ultra-Pull-To-Refresh](https://github.com/liaohuqiu/android-Ultra-Pull-To-Refresh)。还集成了各种炫酷的 Header 和 Footer。 SmartRefreshLayout的目标是打造一个强大,稳定,成熟的下拉刷新框架,并集成各种的炫酷、多样、实用、美观的Header和Footer。 ## 特点功能: - 支持所有的 View(AbsListView、RecyclerView、WebView....View) 和多层嵌套的视图结构 - 支持自定义并且已经集成了很多炫酷的 Header 和 Footer (图). - 支持和ListView的同步滚动 和 RecyclerView、AppBarLayout、CoordinatorLayout 的嵌套滚动 NestedScrolling. - 支持在Android Studio Xml 编辑器中预览 效果(图) - 支持分别在 Default(默认)、Xml、JavaCode 三个中设置 Header 和 Footer. - 支持自动刷新、自动上拉加载(自动检测列表惯性滚动到底部,而不用手动上拉). - 支持通用的刷新监听器 OnRefreshListener 和更详细的滚动监听 OnMultiPurposeListener. - 支持自定义回弹动画的插值器,实现各种炫酷的动画效果. - 支持设置主题来适配任何场景的App,不会出现炫酷但很尴尬的情况. - 支持设置多种滑动方式来适配各种效果的Header和Footer:平移、拉伸、背后固定、顶层固定、全屏 - 支持内容尺寸自适应 Content-wrap_content - 支持继承重写和扩展功能,内部实现没有 private 方法和字段,继承之后都可以重写覆盖 - 支持越界回弹(Listview、RecyclerView、ScrollView、WebView...View) - 支持多点触摸,下拉、上拉各种手势冲突 ## 传送门 - [属性方法](art/md_property.md) - [智能之处](art/md_smart.md) - [常见问题](https://github.com/scwang90/SmartRefreshLayout/issues/71) - [更新日志](art/md_update.md) - [博客文章](https://segmentfault.com/a/1190000010066071) - [源码下载](https://github.com/scwang90/SmartRefreshLayout/releases) - [多点触摸](art/md_multitouch.md) - [自定义Header](art/md_custom.md) ## Demo [下载 APK-Demo](art/app-debug.apk) ![](art/png_apk_rqcode.png) #### 项目演示 ![](art/gif_practive_weibo.gif) ![](art/gif_practive_feedlist.gif) ![](art/gif_practive_repast.gif) ![](art/gif_practive_profile.gif) #### 风格演示 ![](art/gif_Delivery.gif) ![](art/gif_Dropbox.gif) 上面这两个是我自己实现的Header,设计来自:[Refresh-your-delivery](https://dribbble.com/shots/2753803-Refresh-your-delivery),[Dropbox-Refresh](https://dribbble.com/shots/3470499-Dropbox-Refresh) 下面的Header是我把github上其它优秀的Header进行的整理和集合还有优化: ![](art/gif_BezierRadar.gif) ![](art/gif_Circle.gif) 整理来自:[TwinklingRefreshLayout](https://github.com/lcodecorex/TwinklingRefreshLayout/blob/master/art/gif_recyclerview2.gif),[Pull Down To Refresh](https://dribbble.com/shots/1797373-Pull-Down-To-Refresh) ![](art/gif_FlyRefresh.gif) ![](art/gif_Classics.gif) 整理来自:[FlyRefresh](https://github.com/race604/FlyRefresh),[ClassicsHeader](#1) ![](art/gif_Phoenix.gif) ![](art/gif_Taurus.gif) 整理来自:[Yalantis/Phoenix](https://github.com/Yalantis/Phoenix),[Yalantis/Taurus](https://github.com/Yalantis/Taurus) ![](art/gif_BattleCity.gif) ![](art/gif_HitBlock.gif) 整理来自:[FunGame/BattleCity](https://github.com/Hitomis/FunGameRefresh),[FunGame/HitBlock](https://github.com/Hitomis/FunGameRefresh) ![](art/gif_WaveSwipe.gif) ![](art/gif_Material.gif) 整理来自:[WaveSwipeRefreshLayout](https://github.com/recruit-lifestyle/WaveSwipeRefreshLayout),[MaterialHeader](https://developer.android.com/reference/android/support/v4/widget/SwipeRefreshLayout.html) ![](art/gif_StoreHouse.gif) ![](art/gif_WaterDrop.gif) 整理来自:[Ultra-Pull-To-Refresh](https://github.com/liaohuqiu/android-Ultra-Pull-To-Refresh),[WaterDrop](https://github.com/THEONE10211024/WaterDropListView) 看到这么多炫酷的Header,是不是觉得很棒?这时你或许会担心这么多的Header集成在一起,但是平时只会用到一个,是不是要引入很多无用的代码和资源? 请放心,我已经把刷新布局分成三个包啦,用到的时候自行引用就可以啦! - SmartRefreshLayout 刷新布局核心实现,自带ClassicsHeader(经典)、BezierRadarHeader(贝塞尔雷达)两个 Header. - SmartRefreshHeader 各种Header的集成,除了Layout自带的Header,其它都在这个包中. - SmartRefreshFooter 各种Footer的集成,除了Layout自带的Footer,其它都在这个包中. ## 简单用例 #### 1.在 buld.gradle 中添加依赖 ``` compile 'com.android.support:appcompat-v7:25.3.1'//版本随意 compile 'com.scwang.smartrefresh:SmartRefreshLayout:1.0.3' compile 'com.scwang.smartrefresh:SmartRefreshHeader:1.0.3'//没有使用特殊Header,可以不加这行 ``` #### 2.在XML布局文件中添加 SmartRefreshLayout ```xml ``` #### 3.在 Activity 或者 Fragment 中添加代码 ```java RefreshLayout refreshLayout = (RefreshLayout)findViewById(R.id.refreshLayout); refreshLayout.setOnRefreshListener(new OnRefreshListener() { @Override public void onRefresh(RefreshLayout refreshlayout) { refreshlayout.finishRefresh(2000); } }); refreshLayout.setOnLoadmoreListener(new OnLoadmoreListener() { @Override public void onLoadmore(RefreshLayout refreshlayout) { refreshlayout.finishLoadmore(2000); } }); ``` ## 使用指定的 Header 和 Footer #### 1.方法一 全局设置 ```java public class App extends Application { //static 代码段可以防止内存泄露 static { //设置全局的Header构建器 SmartRefreshLayout.setDefaultRefreshHeaderCreater(new DefaultRefreshHeaderCreater() { @Override public RefreshHeader createRefreshHeader(Context context, RefreshLayout layout) { layout.setPrimaryColorsId(R.color.colorPrimary, android.R.color.white);//全局设置主题颜色 return new ClassicsHeader(context).setSpinnerStyle(SpinnerStyle.Translate);//指定为经典Header,默认是 贝塞尔雷达Header } }); //设置全局的Footer构建器 SmartRefreshLayout.setDefaultRefreshFooterCreater(new DefaultRefreshFooterCreater() { @Override public RefreshFooter createRefreshFooter(Context context, RefreshLayout layout) { //指定为经典Footer,默认是 BallPulseFooter return new ClassicsFooter(context).setSpinnerStyle(SpinnerStyle.Translate); } }); } } ``` 注意:方法一 设置的Header和Footer的优先级是最低的,如果同时还使用了方法二、三,将会被其它方法取代 #### 2.方法二 XML布局文件指定 ```xml ``` 注意:方法二 XML设置的Header和Footer的优先级是中等的,会被方法三覆盖。而且使用本方法的时候,Android Studio 会有预览效果,如下图: ![](art/jpg_preview_xml_define.jpg) 不过不用担心,只是预览效果,运行的时候只有下拉才会出现~ #### 3.方法三 Java代码设置 ```java final RefreshLayout refreshLayout = (RefreshLayout) findViewById(R.id.refreshLayout); //设置 Header 为 Material风格 refreshLayout.setRefreshHeader(new MaterialHeader(this).setShowBezierWave(true)); //设置 Footer 为 球脉冲 refreshLayout.setRefreshFooter(new BallPulseFooter(this).setSpinnerStyle(SpinnerStyle.Scale)); ``` ## 混淆 SmartRefreshLayout 没有使用到:序列化、反序列化、JNI、反射,所以并不需要添加混淆过滤代码,并且已经混淆测试通过,如果你在项目的使用中混淆之后出现问题,请及时通知我。 ## 赞赏 如果你喜欢 SmartRefreshLayout 的设计,感觉 SmartRefreshLayout 帮助到了你,可以点右上角 "Star" 支持一下 谢谢! ^_^ 你也还可以扫描下面的二维码~ 请作者喝一杯咖啡。 ![](art/pay_alipay.jpg) ![](art/pay_wxpay.jpg) ![](art/pay_tencent.jpg) 如果在捐赠留言中备注名称,将会被记录到列表中~ 如果你也是github开源作者,捐赠时可以留下github项目地址或者个人主页地址,链接将会被添加到列表中起到互相推广的作用 [捐赠列表](art/md_donationlist.md) ## 讨论 ### QQ解决群 - 602537182 (付费) #### 进群须知 自开群以来,还是有很多的朋友提出了很多问题,我也解决了很多问题,其中有大半问题是本库的Bug导致,也有些是使用者项目本 身的环境问题,这花费了我大量的时间,经过我的观察和测试,到目前为止,本库的bug已经越来越少,当然不能说完全没有,但是 已经能满足很大部分项目的需求。所以从现在起,我做出一个决定:把之前的讨论群改成解决群,并开启付费入群功能,专为解决大 家在使用本库时遇到的问题,不管是本库bug还是,特殊的项目环境导致(包含项目本身的bug)。 我也有自己的工作和娱乐时间,只有大家理解和支持我,我才能专心的为大家解决问题。不过用担心,我已经建立了另一个可以免费 进入的QQ讨论群。 ### QQ讨论群 - 477963933 #### 进群须知 这个群,免费进入,大家可以相互讨论本库的相关使用和出现的问题,群主也会在里面解决问题,如果提出的问题,群成员不 能帮助解决,需要群主解决,但是要花费群主五分钟以上的时间(本库Bug除外),群主将不会解决这个问题,如果项目紧急,请付 费进入解决群解决(不过注意,付费群中群主会很认真很努力的解决问题,但也不能保证已经能完美解决)或者转换使用其他的刷新 库。 #### 温馨提示 加入群的答案在本文档中可以找到~ ## 感谢 [SwipeRefreshLayout](https://developer.android.com/reference/android/support/v4/widget/SwipeRefreshLayout.html) [TwinklingRefreshLayout](https://github.com/lcodecorex/TwinklingRefreshLayout) [Ultra-Pull-To-Refresh](https://github.com/liaohuqiu/android-Ultra-Pull-To-Refresh) License ------- Copyright 2017 scwang90 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_EN.md ================================================ # Android Smart Refresh Layout Framework [![License](https://img.shields.io/badge/License%20-Apache%202-337ab7.svg)](https://www.apache.org/licenses/LICENSE-2.0) [![Arsenal](https://img.shields.io/badge/%20Arsenal%20-%20SmartRefresh%20-4cae4c.svg?style=flat)](https://android-arsenal.com/details/1/6001) [![Jcenter](https://img.shields.io/badge/%20Jcenter%20-1.0.3-5bc0de.svg) ](https://bintray.com/scwang90/maven/SmartRefreshLayout/_latestVersion) [![MinSdk](https://img.shields.io/badge/%20MinSdk%20-%2012%2B%20-f0ad4e.svg?style=flat)](https://android-arsenal.com/api?level=12) [![Methods](https://img.shields.io/badge/%20Methods%20%7C%20Size%20-%201251%20%7C%20129%20KB-d9534f.svg)](http://www.methodscount.com/?lib=com.scwang.smartrefresh%3ASmartRefreshLayout%3A1.0.2) ## English | [中文](README.md) As the name says, SmartRefreshLayout is a "smart" refresh layout,Because of its "smart", it does not just support all the View as other refresh layouts said, but also support multi-layered nested view structures. In addition to "smart", SmartRefreshLayout also has a lot of features. It extends from ViewGroup rather than the other FrameLayout or LinearLayout, improving performance. It absorbs the advantages of various refresh layout in fashion now,Including Google official [SwipeRefreshLayout](https://developer.android.com/reference/android/support/v4/widget/SwipeRefreshLayout.html)、[TwinklingRefreshLayout](https://github.com/lcodecorex/TwinklingRefreshLayout) 、[Ultra-Pull-To-Refresh](https://github.com/liaohuqiu/android-Ultra-Pull-To-Refresh)。Also integrates various cool Header and Footer. SmartRefreshLayout's goal is to build a strong, stable and mature refresh layout framework, and integrate all kinds of cool and diverse, practical, beautiful Header and Footer. ## Features - Support all of the View (AbsListView RecyclerView WebView... View) and multi-layered nested view structures. - Support custom Header and Footer, and has integrated a lot of cool Header and Footer. - Support synchronous scrolling with ListView and NestedScrolling width RecyclerView、AppBarLayout、CoordinatorLayout. - Support preview in the Android Studio xml editor. - Support three ways (default、xml、java) to set the Header and Footer. - Support automatic refresh, automatic loading (automatic detection list, scroll to the bottom without having to manually pull). - Support generic refresh listener (OnRefreshListener) and more detailed scrolling listener (OnMultiPurposeListener). - Support custom rebound animation interpolator, to achieve a variety of cool animation effects. - Support set the theme to fit any scene of App, won't appear cool but very awkward situation. - Support for setting a variety of transformations (Translation, stretching, behind the fixed, top fixed, full screen) for Header and Footer. - Support content size adaptation (wrap_content). - Support rewrite and extension, internal implementation without private methods and fields. - Support cross-border rebound (Listview、RecyclerView、ScrollView、WebView...View). ## Gateway - [Smart place](art/md_smart.md) - [Update log](art/md_update.md) - [Attribute method](art/md_property.md) - [Blog posts](https://segmentfault.com/a/1190000010066071) - [Download the source code](https://github.com/scwang90/SmartRefreshLayout/releases) ## Demo [Download APK-Demo](art/app-debug.apk) ![](art/png_apk_rqcode.png) #### Practical ![](art/gif_practive_weibo.gif) ![](art/gif_practive_feedlist.gif) ![](art/gif_practive_repast.gif) ![](art/gif_practive_profile.gif) #### Style ![](art/gif_Delivery.gif) ![](art/gif_Dropbox.gif) The two above is my own implementation of the Header, the design comes from: [Refresh-your-delivery](https://dribbble.com/shots/2753803-Refresh-your-delivery),[Dropbox-Refresh](https://dribbble.com/shots/3470499-Dropbox-Refresh) ![](art/gif_BezierRadar.gif) ![](art/gif_Circle.gif) [TwinklingRefreshLayout](https://github.com/lcodecorex/TwinklingRefreshLayout/blob/master/art/gif_recyclerview2.gif),[Pull Down To Refresh](https://dribbble.com/shots/1797373-Pull-Down-To-Refresh) ![](art/gif_FlyRefresh.gif) ![](art/gif_Classics.gif) [FlyRefresh](https://github.com/race604/FlyRefresh),[ClassicsHeader](#1) ![](art/gif_Phoenix.gif) ![](art/gif_Taurus.gif) [Yalantis/Phoenix](https://github.com/Yalantis/Phoenix),[Yalantis/Taurus](https://github.com/Yalantis/Taurus) ![](art/gif_BattleCity.gif) ![](art/gif_HitBlock.gif) [FunGame/BattleCity](https://github.com/Hitomis/FunGameRefresh),[FunGame/HitBlock](https://github.com/Hitomis/FunGameRefresh) ![](art/gif_WaveSwipe.gif) ![](art/gif_Material.gif) [WaveSwipeRefreshLayout](https://github.com/recruit-lifestyle/WaveSwipeRefreshLayout),[MaterialHeader](https://developer.android.com/reference/android/support/v4/widget/SwipeRefreshLayout.html) ![](art/gif_StoreHouse.gif) ![](art/gif_WaterDrop.gif) [Ultra-Pull-To-Refresh](https://github.com/liaohuqiu/android-Ultra-Pull-To-Refresh),[WaterDrop](https://github.com/THEONE10211024/WaterDropListView) See so many cool headers, is not it feel great? At this point you may be worried that so many headers together, but usually only use one, is not to introduce a lot of useless code and resources? Please rest assured that I have divided it into three packages, when used to reference their own it! - **SmartRefreshLayout:** The core to realize,Bring ClassicsHeader and BezierRadarHeader. - **SmartRefreshHeader:** Integration of various kinds of the Header. - **SmartRefreshFooter:** Integration of various kinds of the Footer. ## Usage #### 1.Add a gradle dependency. ``` compile 'com.android.support:appcompat-v7:25.3.1' compile 'com.scwang.smartrefresh:SmartRefreshLayout:1.0.3' compile 'com.scwang.smartrefresh:SmartRefreshHeader:1.0.3'//If you use the special Header ``` #### 2.Add SmartRefreshLayout in the layout xml. ```xml ``` #### 3.Coding in the Activity or Fragment. ```java RefreshLayout refreshLayout = (RefreshLayout)findViewById(R.id.refreshLayout); refreshLayout.setOnRefreshListener(new OnRefreshListener() { @Override public void onRefresh(RefreshLayout refreshlayout) { refreshlayout.finishRefresh(2000); } }); refreshLayout.setOnLoadmoreListener(new OnLoadmoreListener() { @Override public void onLoadmore(RefreshLayout refreshlayout) { refreshlayout.finishLoadmore(2000); } }); ``` ## Use the specified Header and Footer #### 1.Global settings ```java public class App extends Application { public void onCreate() { super.onCreate(); SmartRefreshLayout.setDefaultRefreshHeaderCreater(new DefaultRefreshHeaderCreater() { @Override public RefreshHeader createRefreshHeader(Context context, RefreshLayout layout) { return new ClassicsHeader(context).setSpinnerStyle(SpinnerStyle.Translate); } }); SmartRefreshLayout.setDefaultRefreshFooterCreater(new DefaultRefreshFooterCreater() { @Override public RefreshFooter createRefreshFooter(Context context, RefreshLayout layout) { return new ClassicsFooter(context).setSpinnerStyle(SpinnerStyle.Translate); } }); } } ``` Note: this method is the lowest priority. #### 2.Specified in the XML layout file ```xml ``` Note: this method of priority is medium。When using this method, the Android Studio will have preview effect, the following figure: ![](art/jpg_preview_xml_define.jpg) But don't worry, just a preview effect, run only the drop-down will appear. #### 3.Specified in the java code ```java final RefreshLayout refreshLayout = (RefreshLayout) findViewById(R.id.refreshLayout); refreshLayout.setRefreshHeader(new MaterialHeader(this).setShowBezierWave(true)); refreshLayout.setRefreshFooter(new BallPulseFooter(this).setSpinnerStyle(SpinnerStyle.Scale)); ``` ## ProGuard This library does't use serialization and deserialization, JNI, reflection, so there is no need to add confusing filtering code, and it has been confusing tests pass, if you after the confusion in the use of the project appear problem, please inform me. ## Donate If you like this library's design, feel it help to you, you can point the upper right corner "Star" support Thank you! ^ _ ^ You can also scan the qr code below to ask the author to drink a cup of coffee. ![](art/pay_alipay.jpg) ![](art/pay_wxpay.jpg) ![](art/pay_tencent.jpg) If in the donation message note name, will be record to the list [Donation list](art/md_donationlist.md) ## Discuss Contact me: scwang90@hotmail.com ## Thanks [SwipeRefreshLayout](https://developer.android.com/reference/android/support/v4/widget/SwipeRefreshLayout.html) [TwinklingRefreshLayout](https://github.com/lcodecorex/TwinklingRefreshLayout) [android-Ultra-Pull-To-Refresh](https://github.com/liaohuqiu/android-Ultra-Pull-To-Refresh) License ------- Copyright 2017 scwang90 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: app/.gitignore ================================================ /build ================================================ FILE: app/build.gradle ================================================ apply plugin: 'com.android.application' android { compileSdkVersion 26 buildToolsVersion "26.0.1" defaultConfig { applicationId "com.scwang.refreshlayout" minSdkVersion 15 targetSdkVersion 26 versionCode 1 versionName "1.0" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" vectorDrawables.useSupportLibrary = true renderscriptTargetApi 25 renderscriptSupportModeEnabled true // Enable RS support } signingConfigs { debug { storeFile file('debug.keystore') storePassword "android" keyAlias "androiddebugkey" keyPassword "android" } } buildTypes { release { minifyEnabled true proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' signingConfig = signingConfigs.debug } } lintOptions { abortOnError false } } repositories { maven { url "https://jitpack.io" } } dependencies { compile fileTree(include: ['*.jar'], dir: 'libs') androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', { exclude group: 'com.android.support', module: 'support-annotations' }) debugCompile 'com.squareup.leakcanary:leakcanary-android:1.5.1' releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.5.1' testCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.5.1' compile project(':refresh-footer') compile project(':refresh-header') compile project(':refresh-layout') compile 'com.android.support:cardview-v7:26.0.0-alpha1' compile 'com.android.support.constraint:constraint-layout:1.0.2' compile 'com.android.support:design:26.0.0-alpha1' compile 'com.flyco.roundview:FlycoRoundView_Lib:1.1.4@aar' compile 'com.github.CymChad:BaseRecyclerViewAdapterHelper:2.9.22' compile 'com.github.mmin18:realtimeblurview:1.0.6' compile 'com.google.code.gson:gson:2.7' compile 'com.youth.banner:banner:1.4.9' //最新版本 compile 'de.hdodenhof:circleimageview:2.1.0' compile 'jp.wasabeef:recyclerview-animators:2.2.6' compile 'pl.droidsonroids.gif:android-gif-drawable:1.2.3' // compile 'com.scwang.smartrefresh:SmartRefreshLayout:1.0.3' // compile 'com.scwang.smartrefresh:SmartRefreshHeader:1.0.3' testCompile 'junit:junit:4.12' compile 'com.github.bumptech.glide:glide:4.0.0-RC1' annotationProcessor 'com.github.bumptech.glide:compiler:4.0.0-RC1' } ================================================ FILE: app/proguard-rules.pro ================================================ # Add project specific ProGuard rules here. # By default, the flags in this file are appended to flags specified # in E:\Android\android-sdk/tools/proguard/proguard-android.txt # You can edit the include path and order by changing the proguardFiles # directive in build.gradle. # # For more details, see # http://developer.android.com/guide/developing/tools/proguard.html # Add any project specific keep options here: # If your project uses WebView with JS, uncomment the following # and specify the fully qualified class name to the JavaScript interface # class: #-keepclassmembers class fqcn.of.javascript.interface.for.webview { # public *; #} # 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 #app混淆标记 -keep class com.github.mmin18.** {*;} -dontwarn android.support.v8.renderscript.* -keepclassmembers class android.support.v8.renderscript.RenderScript { native *** rsn*(...); native *** n*(...); } -keep enum com.scwang.smartrefresh.layout.impl.** {*;} ================================================ FILE: app/src/androidTest/java/com/scwang/refreshlayout/ExampleInstrumentedTest.java ================================================ package com.scwang.refreshlayout; import android.content.Context; import android.support.test.InstrumentationRegistry; import android.support.test.runner.AndroidJUnit4; import org.junit.Test; import org.junit.runner.RunWith; import static org.junit.Assert.*; /** * Instrumentation test, which will execute on an Android device. * * @see Testing documentation */ @RunWith(AndroidJUnit4.class) public class ExampleInstrumentedTest { @Test public void useAppContext() throws Exception { // Context of the app under test. Context appContext = InstrumentationRegistry.getTargetContext(); assertEquals("com.scwang.refreshlayout", appContext.getPackageName()); } } ================================================ FILE: app/src/main/AndroidManifest.xml ================================================ ================================================ FILE: app/src/main/java/com/scwang/refreshlayout/App.java ================================================ package com.scwang.refreshlayout; import android.app.Application; import android.content.Context; import android.support.annotation.NonNull; import android.support.v7.app.AppCompatDelegate; import com.scwang.smartrefresh.layout.SmartRefreshLayout; import com.scwang.smartrefresh.layout.api.DefaultRefreshHeaderCreater; import com.scwang.smartrefresh.layout.api.RefreshHeader; import com.scwang.smartrefresh.layout.api.RefreshLayout; import com.scwang.smartrefresh.layout.constant.SpinnerStyle; import com.scwang.smartrefresh.layout.header.ClassicsHeader; import com.squareup.leakcanary.LeakCanary; /** * * Created by SCWANG on 2017/6/11. */ public class App extends Application { static { AppCompatDelegate.setCompatVectorFromResourcesEnabled(true); SmartRefreshLayout.setDefaultRefreshHeaderCreater(new DefaultRefreshHeaderCreater() { @NonNull @Override public RefreshHeader createRefreshHeader(Context context, RefreshLayout layout) { layout.setPrimaryColorsId(R.color.colorPrimary, android.R.color.white);//全局设置主题颜色 return new ClassicsHeader(context).setSpinnerStyle(SpinnerStyle.Translate); } }); } @Override public void onCreate() { super.onCreate(); if (LeakCanary.isInAnalyzerProcess(this)) { // This process is dedicated to LeakCanary for heap analysis. // You should not init your app in this process. return; } LeakCanary.install(this); } } ================================================ FILE: app/src/main/java/com/scwang/refreshlayout/activity/ExperimentActivity.java ================================================ package com.scwang.refreshlayout.activity; import android.os.Bundle; import android.support.v7.app.AppCompatActivity; import android.support.v7.widget.DividerItemDecoration; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; import com.scwang.refreshlayout.R; import com.scwang.refreshlayout.adapter.BaseRecyclerAdapter; import com.scwang.refreshlayout.adapter.SmartViewHolder; import java.util.Arrays; import java.util.Collection; import java.util.Locale; import static android.R.layout.simple_list_item_2; import static android.support.v7.widget.DividerItemDecoration.VERTICAL; public class ExperimentActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_experiment); final RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recyclerView); recyclerView.addItemDecoration(new DividerItemDecoration(this, VERTICAL)); recyclerView.setLayoutManager(new LinearLayoutManager(this)); recyclerView.setAdapter(new BaseRecyclerAdapter(buildData(),simple_list_item_2) { @Override protected void onBindViewHolder(SmartViewHolder holder, Void model, int position) { holder.text(android.R.id.text1, String.format(Locale.CHINA, "第%02d条数据", position)); holder.text(android.R.id.text2, String.format(Locale.CHINA, "这是测试的第%02d条数据", position)); holder.textColorId(android.R.id.text2, R.color.colorTextAssistant); } }); } protected Collection buildData() { return Arrays.asList(null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null); } } ================================================ FILE: app/src/main/java/com/scwang/refreshlayout/activity/IndexMainActivity.java ================================================ package com.scwang.refreshlayout.activity; import android.os.Bundle; import android.support.annotation.IdRes; import android.support.annotation.NonNull; import android.support.design.widget.BottomNavigationView; import android.support.design.widget.BottomNavigationView.OnNavigationItemSelectedListener; import android.support.v4.app.Fragment; import android.support.v4.app.FragmentTransaction; import android.support.v7.app.AppCompatActivity; import android.view.MenuItem; import com.scwang.refreshlayout.R; import com.scwang.refreshlayout.fragment.RefreshPractiveFragment; import com.scwang.refreshlayout.fragment.RefreshStylesFragment; import com.scwang.refreshlayout.fragment.RefreshUsingFragment; import com.scwang.refreshlayout.util.StatusBarUtil; public class IndexMainActivity extends AppCompatActivity implements OnNavigationItemSelectedListener { private enum TabFragment { practice(R.id.navigation_practice,RefreshPractiveFragment.class), styles(R.id.navigation_style,RefreshStylesFragment.class), using(R.id.navigation_using,RefreshUsingFragment.class) ; private Fragment fragment; private final int menuId; private final Class clazz; TabFragment(@IdRes int menuId, Class clazz) { this.menuId = menuId; this.clazz = clazz; } @NonNull public Fragment fragment() { if (fragment == null) { try { fragment = clazz.newInstance(); } catch (Exception e) { e.printStackTrace(); fragment = new Fragment(); } } return fragment; } public static TabFragment from(int itemId) { for (TabFragment fragment : values()) { if (fragment.menuId == itemId) { return fragment; } } return styles; } public static void onDestroy() { for (TabFragment fragment : values()) { fragment.fragment = null; } } } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_index_main); final BottomNavigationView navigation = (BottomNavigationView) findViewById(R.id.navigation); navigation.setOnNavigationItemSelectedListener(this); navigation.setSelectedItemId(R.id.navigation_style); //状态栏透明和间距处理 StatusBarUtil.immersive(this, 0xff000000, 0.1f); } @Override protected void onDestroy() { super.onDestroy(); TabFragment.onDestroy(); } @Override public boolean onNavigationItemSelected(@NonNull MenuItem item) { getSupportFragmentManager() .beginTransaction() .setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN) .replace(R.id.content,TabFragment.from(item.getItemId()).fragment()) .commit(); return true; } } ================================================ FILE: app/src/main/java/com/scwang/refreshlayout/activity/practice/BannerPracticeActivity.java ================================================ package com.scwang.refreshlayout.activity.practice; import android.content.Context; import android.os.Bundle; import android.support.v7.app.AppCompatActivity; import android.support.v7.widget.DividerItemDecoration; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; import android.support.v7.widget.Toolbar; import android.view.LayoutInflater; import android.view.View; import android.widget.ImageView; import com.bumptech.glide.Glide; import com.chad.library.adapter.base.BaseQuickAdapter; import com.chad.library.adapter.base.BaseViewHolder; import com.google.gson.Gson; import com.google.gson.reflect.TypeToken; import com.scwang.refreshlayout.R; import com.scwang.refreshlayout.util.StatusBarUtil; import com.scwang.smartrefresh.layout.api.RefreshLayout; import com.scwang.smartrefresh.layout.listener.OnRefreshListener; import com.youth.banner.Banner; import com.youth.banner.loader.ImageLoader; import java.util.ArrayList; import java.util.List; import static android.support.v7.widget.DividerItemDecoration.VERTICAL; /** * 广告轮播-Banner */ @SuppressWarnings("ALL") public class BannerPracticeActivity extends AppCompatActivity { private QuickAdapter mAdapter; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_practice_banner); final Toolbar toolbar = (Toolbar)findViewById(R.id.toolbar); toolbar.setNavigationOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { finish(); } }); final RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recyclerView); final RefreshLayout refreshLayout = (RefreshLayout) findViewById(R.id.refreshLayout); mAdapter = new QuickAdapter(); recyclerView.addItemDecoration(new DividerItemDecoration(this, VERTICAL)); recyclerView.setLayoutManager(new LinearLayoutManager(this)); recyclerView.setAdapter(mAdapter); refreshLayout.autoRefresh(); refreshLayout.setOnRefreshListener(new OnRefreshListener() { @Override public void onRefresh(final RefreshLayout refreshlayout) { refreshlayout.getLayout().postDelayed(new Runnable() { @Override public void run() { if (mAdapter.getItemCount() < 2) { List movies = new Gson().fromJson(JSON_MOVIES, new TypeToken>() {}.getType()); mAdapter.replaceData(movies); } refreshlayout.finishRefresh(); } },2000); } }); //添加Header View header = LayoutInflater.from(this).inflate(R.layout.listitem_movie_header, recyclerView, false); Banner banner = (Banner) header; banner.setImageLoader(new GlideImageLoader()); banner.setImages(BANNER_ITEMS); banner.start(); mAdapter.addHeaderView(banner); //状态栏透明和间距处理 StatusBarUtil.immersive(this); StatusBarUtil.setPaddingSmart(this, toolbar); StatusBarUtil.setPaddingSmart(this, recyclerView); StatusBarUtil.setMargin(this, findViewById(R.id.header)); StatusBarUtil.setPaddingSmart(this, findViewById(R.id.blurview)); } public class QuickAdapter extends BaseQuickAdapter { public QuickAdapter() { super(R.layout.listitem_movie_item); } @Override protected void convert(BaseViewHolder viewHolder, Movie item) { viewHolder.setText(R.id.lmi_title, item.filmName) .setText(R.id.lmi_actor, item.actors) .setText(R.id.lmi_grade, item.grade) .setText(R.id.lmi_describe, item.shortinfo); Glide.with(mContext).load(item.picaddr).into((ImageView) viewHolder.getView(R.id.lmi_avatar)); } } public class GlideImageLoader extends ImageLoader { @Override public void displayImage(Context context, Object path, ImageView imageView) { imageView.setImageResource(((BannerItem) path).pic); } } public static class Movie { public String actors; public String filmName; public String grade; public String info; public String picaddr; public String shortinfo; } public static class BannerItem { public int pic; public String title; public BannerItem() { } public BannerItem(String title, int pic) { this.pic = pic; this.title = title; } } public static List BANNER_ITEMS = new ArrayList(){{ add(new BannerItem("最后的骑士", R.mipmap.image_movie_header_48621499931969370)); add(new BannerItem("三生三世十里桃花", R.mipmap.image_movie_header_12981501221820220)); add(new BannerItem("豆福传", R.mipmap.image_movie_header_12231501221682438)); }}; public static String JSON_MOVIES = "[" + "{\"actors\":\"丹尼斯·威缇可宁|Emma|Nikki|Jiayao|Wang|Maggie|Mao|Gang-yun|Sa\",\"filmName\":\"神灵寨\",\"grade\":\"5.0\",\"picaddr\":\"http://app.infunpw.com/commons/images/cinema/cinema_films/3823.jpg\",\"releasedate\":\"2017-07-31\",\"shortinfo\":\"父亲忽病危 新娘真够黑\",\"type\":\"剧情|喜剧\"}," + "{\"actors\":\"刘亦菲|杨洋|彭子苏|严屹宽|罗晋\",\"filmName\":\"三生三世十里桃花\",\"grade\":\"9.2\",\"picaddr\":\"http://app.infunpw.com/commons/images/cinema/cinema_films/3566.jpg\",\"releasedate\":\"2017-08-03\",\"shortinfo\":\"虐心姐弟恋 颜值要逆天\",\"type\":\"剧情|爱情|奇幻\"}," + "{\"actors\":\"尹航|代旭|李晨浩|衣云鹤|张念骅\",\"filmName\":\"谁是球王\",\"grade\":\"10.0\",\"picaddr\":\"http://app.infunpw.com/commons/images/cinema/cinema_films/3750.jpg\",\"releasedate\":\"2017-08-03\",\"shortinfo\":\"足球变人生 再战可辉煌\",\"type\":\"剧情|喜剧\"}," + "{\"actors\":null,\"filmName\":\"大象林旺之一夜成名\",\"grade\":\"10.0\",\"picaddr\":\"http://app.infunpw.com/commons/images/cinema/cinema_films/3757.jpg\",\"releasedate\":\"2017-08-04\",\"shortinfo\":\"大象参二战 一生好伙伴\",\"type\":\"动作|动画|战争|冒险\"}," + "{\"actors\":\"薛凯琪|陈意涵|张钧甯|迈克·泰森\",\"filmName\":\"闺蜜2:无二不作\",\"grade\":\"8.3\",\"picaddr\":\"http://app.infunpw.com/commons/images/cinema/cinema_films/3776.jpg\",\"releasedate\":\"2017-08-04\",\"shortinfo\":\"闺蜜团出战 会一会新娘\",\"type\":\"喜剧|爱情\"}," + "{\"actors\":\"彭禺厶|王萌|周凯文|曹琦|孟子叶\",\"filmName\":\"诡井\",\"grade\":\"5.0\",\"picaddr\":\"http://app.infunpw.com/commons/images/cinema/cinema_films/3824.jpg\",\"releasedate\":\"2017-08-04\",\"shortinfo\":\"午夜深井中 怨魂欲现形\",\"type\":\"恐怖|惊悚\"}," + "{\"actors\":\"旺卓措|刘承宙|高欣生|段楠|来钰\",\"filmName\":\"荒野加油站\",\"grade\":\"5.0\",\"picaddr\":\"http://app.infunpw.com/commons/images/cinema/cinema_films/3821.jpg\",\"releasedate\":\"2017-08-04\",\"shortinfo\":\"夜半拉乘客 结果遇不测\",\"type\":\"惊悚|悬疑\"}," + "{\"actors\":\"刘佩琦|曹云金|罗昱焜\",\"filmName\":\"龙之战\",\"grade\":\"5.0\",\"picaddr\":\"http://app.infunpw.com/commons/images/cinema/cinema_films/3778.jpg\",\"releasedate\":\"2017-08-04\",\"shortinfo\":\"持倭刀屹立 抗外敌救国\",\"type\":\"动作|战争|历史\"}," + "{\"actors\":\"金巴|曲尼次仁|夏诺.扎西敦珠|索朗尼玛|益西旦增\",\"filmName\":\"皮绳上的魂\",\"grade\":\"5.0\",\"picaddr\":\"http://app.infunpw.com/commons/images/cinema/cinema_films/3801.jpg\",\"releasedate\":\"2017-08-04\",\"shortinfo\":\"走完朝圣路 又上降魔旅\",\"type\":\"剧情\"}," + "{\"actors\":\"严丽祯|李晔|王衡|李传缨|李心仪\",\"filmName\":\"玩偶奇兵\",\"grade\":\"10.0\",\"picaddr\":\"http://app.infunpw.com/commons/images/cinema/cinema_films/3779.jpg\",\"releasedate\":\"2017-08-04\",\"shortinfo\":\"玩偶战数码 一头两个大\",\"type\":\"动画|冒险|奇幻\"}," + "{\"actors\":\"斯蒂芬·马布里|吴尊|何冰|郑秀妍|王庆祥\",\"filmName\":\"我是马布里\",\"grade\":\"0.0\",\"picaddr\":\"http://app.infunpw.com/commons/images/cinema/cinema_films/3810.jpg\",\"releasedate\":\"2017-08-04\",\"shortinfo\":\"吴尊助冠军 热血灌篮魂\",\"type\":\"剧情|运动\"}," + "{\"actors\":\"周鹏雨|穆建荣|陈泽帆|鹿露|宋星成\",\"filmName\":\"原罪的羔羊\",\"grade\":\"5.0\",\"picaddr\":\"http://app.infunpw.com/commons/images/cinema/cinema_films/3802.jpg\",\"releasedate\":\"2017-08-04\",\"shortinfo\":\"古镇来戏班 往事不一般\",\"type\":\"悬疑\"}," + "{\"actors\":\"王大陆|张天爱|任达华|盛冠森|王迅\",\"filmName\":\"鲛珠传\",\"grade\":\"7.1\",\"picaddr\":\"http://app.infunpw.com/commons/images/cinema/cinema_films/3777.jpg\",\"releasedate\":\"2017-08-04\",\"shortinfo\":\"改编热IP 杠杠号召力\",\"type\":\"喜剧|动作|奇幻\"}," + "{\"actors\":\"成龙|罗伯特·雷德福\",\"filmName\":\"地球:神奇的一天\",\"grade\":\"10.0\",\"picaddr\":\"http://app.infunpw.com/commons/images/cinema/cinema_films/3803.jpg\",\"releasedate\":\"2017-08-11\",\"shortinfo\":\"史诗纪录片 十年再相见\",\"type\":\"纪录片\"}," + "{\"actors\":\"刘德华|舒淇|杨祐宁|张静初|让·雷诺|曾志伟|沙溢\",\"filmName\":\"侠盗联盟\",\"grade\":\"10.0\",\"picaddr\":\"http://app.infunpw.com/commons/images/cinema/cinema_films/3592.jpg\",\"releasedate\":\"2017-08-11\",\"shortinfo\":\"侠盗三剑客 越洋逃恐吓\",\"type\":\"动作|冒险\"}," + "{\"actors\":\"廖凡|李易峰|万茜|李纯|张国柱\",\"filmName\":\"心理罪\",\"grade\":\"10.0\",\"picaddr\":\"http://app.infunpw.com/commons/images/cinema/cinema_films/3795.jpg\",\"releasedate\":\"2017-08-11\",\"shortinfo\":\"侦探两搭档 真相背后藏\",\"type\":\"悬疑|犯罪\"}," + "{\"actors\":\"徐瑞阳|赵倩|姜启杨|徐万学|韩靓|韦安\",\"filmName\":\"隐隐惊马槽之绝战女僵尸\",\"grade\":\"5.0\",\"picaddr\":\"http://app.infunpw.com/commons/images/cinema/cinema_films/3825.jpg\",\"releasedate\":\"2017-08-11\",\"shortinfo\":\"阴兵来借道 尸占惊马槽\",\"type\":\"惊悚|动作|冒险|悬疑\"}," + "{\"actors\":\"宋睿|王良|张佳浩|叶常清\",\"filmName\":\"左眼阴阳\",\"grade\":\"10.0\",\"picaddr\":\"http://app.infunpw.com/commons/images/cinema/cinema_films/3804.jpg\",\"releasedate\":\"2017-08-11\",\"shortinfo\":\"左眼见到鬼 是诡还是魅\",\"type\":\"恐怖|惊悚|悬疑\"}," + "{\"actors\":null,\"filmName\":\"二十二\",\"grade\":\"10.0\",\"picaddr\":\"http://app.infunpw.com/commons/images/cinema/cinema_films/3811.jpg\",\"releasedate\":\"2017-08-14\",\"shortinfo\":\"二战女俘虏 讲述心中苦\",\"type\":\"纪录片\"}," + "{\"actors\":\"郭富城|王千源|刘涛|余皑磊|冯嘉怡\",\"filmName\":\"破·局\",\"grade\":\"5.0\",\"picaddr\":\"http://app.infunpw.com/commons/images/cinema/cinema_films/3812.jpg\",\"releasedate\":\"2017-08-18\",\"shortinfo\":\"影帝硬碰硬 迷局谁怕谁\",\"type\":\"动作|犯罪\"}" + "]"; } ================================================ FILE: app/src/main/java/com/scwang/refreshlayout/activity/practice/FeedlistPracticeActivity.java ================================================ package com.scwang.refreshlayout.activity.practice; import android.os.Bundle; import android.support.v7.app.AppCompatActivity; import android.support.v7.widget.Toolbar; import android.view.View; import com.scwang.refreshlayout.R; import com.scwang.smartrefresh.layout.api.RefreshLayout; /** * 微博列表 */ public class FeedlistPracticeActivity extends AppCompatActivity { private static boolean isFirstEnter = true; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_practice_feedlist); final Toolbar toolbar = (Toolbar)findViewById(R.id.toolbar); toolbar.setNavigationOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { finish(); } }); final RefreshLayout refreshLayout = (RefreshLayout) findViewById(R.id.refreshLayout); if (isFirstEnter) { isFirstEnter = false; refreshLayout.autoRefresh(); } } } ================================================ FILE: app/src/main/java/com/scwang/refreshlayout/activity/practice/ProfilePracticeActivity.java ================================================ package com.scwang.refreshlayout.activity.practice; import android.os.Bundle; import android.support.v7.app.AppCompatActivity; import android.support.v7.widget.Toolbar; import android.view.View; import com.scwang.refreshlayout.R; import com.scwang.refreshlayout.util.StatusBarUtil; /** * 个人中心 */ public class ProfilePracticeActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_practice_profile); final Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); toolbar.setNavigationOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { finish(); } }); //状态栏透明和间距处理 StatusBarUtil.immersive(this); StatusBarUtil.setPaddingSmart(this, toolbar); StatusBarUtil.setPaddingSmart(this, findViewById(R.id.profile)); StatusBarUtil.setPaddingSmart(this, findViewById(R.id.blurview)); } } ================================================ FILE: app/src/main/java/com/scwang/refreshlayout/activity/practice/QQBrowserPracticeActivity.java ================================================ package com.scwang.refreshlayout.activity.practice; import android.annotation.SuppressLint; import android.os.Bundle; import android.support.v7.app.AppCompatActivity; import android.support.v7.widget.Toolbar; import android.view.View; import android.webkit.WebView; import android.webkit.WebViewClient; import com.scwang.refreshlayout.R; import com.scwang.refreshlayout.util.StatusBarUtil; /** * QQ浏览器-Github */ public class QQBrowserPracticeActivity extends AppCompatActivity { @SuppressLint("SetJavaScriptEnabled") @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_practice_qqbrowser); final Toolbar toolbar = (Toolbar)findViewById(R.id.toolbar); toolbar.setNavigationOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { finish(); } }); final WebView webView = (WebView) findViewById(R.id.webView); webView.loadUrl("https://github.com/scwang90/SmartRefreshLayout"); webView.getSettings().setJavaScriptEnabled(true); webView.setWebViewClient(new WebViewClient(){ @Override public boolean shouldOverrideUrlLoading(WebView view, String url) { view.loadUrl(url); return true; } }); //状态栏透明和间距处理 StatusBarUtil.immersive(this); StatusBarUtil.setPaddingSmart(this, toolbar); } } ================================================ FILE: app/src/main/java/com/scwang/refreshlayout/activity/practice/RepastPracticeActivity.java ================================================ package com.scwang.refreshlayout.activity.practice; import android.os.Bundle; import android.support.v7.app.AppCompatActivity; import android.support.v7.widget.DefaultItemAnimator; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; import android.support.v7.widget.Toolbar; import android.view.View; import android.widget.Toast; import com.scwang.refreshlayout.R; import com.scwang.refreshlayout.adapter.BaseRecyclerAdapter; import com.scwang.refreshlayout.adapter.SmartViewHolder; import com.scwang.refreshlayout.util.StatusBarUtil; import com.scwang.smartrefresh.layout.api.RefreshLayout; import com.scwang.smartrefresh.layout.listener.OnRefreshLoadmoreListener; import java.util.Arrays; import java.util.Collection; /** * 餐饮美食 */ public class RepastPracticeActivity extends AppCompatActivity { private class Model { int imageId; int avatarId; String name; String nickname; } private static boolean isFirstEnter = true; private BaseRecyclerAdapter mAdapter; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_practice_repast); final Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); toolbar.setNavigationOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { finish(); } }); final RefreshLayout refreshLayout = (RefreshLayout) findViewById(R.id.refreshLayout); //第一次进入演示刷新 if (isFirstEnter) { isFirstEnter = false; refreshLayout.autoRefresh(); } //初始化列表和监听 View view = findViewById(R.id.recyclerView); if (view instanceof RecyclerView) { RecyclerView recyclerView = (RecyclerView) view; recyclerView.setLayoutManager(new LinearLayoutManager(this)); recyclerView.setItemAnimator(new DefaultItemAnimator()); recyclerView.setAdapter(mAdapter = new BaseRecyclerAdapter(loadModels(), R.layout.listitem_practive_repast) { @Override protected void onBindViewHolder(SmartViewHolder holder, Model model, int position) { holder.text(R.id.name, model.name); holder.text(R.id.nickname, model.nickname); holder.image(R.id.image, model.imageId); holder.image(R.id.avatar, model.avatarId); } }); refreshLayout.setOnRefreshLoadmoreListener(new OnRefreshLoadmoreListener() { @Override public void onRefresh(final RefreshLayout refreshlayout) { refreshLayout.getLayout().postDelayed(new Runnable() { @Override public void run() { refreshlayout.finishRefresh(); refreshlayout.setLoadmoreFinished(false);//恢复上拉状态 } }, 2000); } @Override public void onLoadmore(final RefreshLayout refreshlayout) { refreshLayout.getLayout().postDelayed(new Runnable() { @Override public void run() { mAdapter.loadmore(loadModels()); refreshlayout.finishLoadmore(); if (mAdapter.getCount() > 12) { Toast.makeText(getBaseContext(), "数据全部加载完毕", Toast.LENGTH_SHORT).show(); refreshlayout.setLoadmoreFinished(true);//设置之后,将不会再触发加载事件 } } }, 1000); } }); } //状态栏透明和间距处理 StatusBarUtil.darkMode(this); StatusBarUtil.setPaddingSmart(this, view); StatusBarUtil.setPaddingSmart(this, toolbar); StatusBarUtil.setPaddingSmart(this, findViewById(R.id.blurview)); StatusBarUtil.setMargin(this, findViewById(R.id.gifview)); } /** * 模拟数据 */ private Collection loadModels() { return Arrays.asList( new Model() {{ this.name = "但家香酥鸭"; this.nickname = "爱过那张脸"; this.imageId = R.mipmap.image_practice_repast_1; this.avatarId = R.mipmap.image_avatar_1; }}, new Model() {{ this.name = "香菇蒸鸟蛋"; this.nickname = "淑女算个鸟"; this.imageId = R.mipmap.image_practice_repast_2; this.avatarId = R.mipmap.image_avatar_2; }}, new Model() {{ this.name = "花溪牛肉粉"; this.nickname = "性感妩媚"; this.imageId = R.mipmap.image_practice_repast_3; this.avatarId = R.mipmap.image_avatar_3; }}, new Model() {{ this.name = "破酥包"; this.nickname = "一丝丝纯真"; this.imageId = R.mipmap.image_practice_repast_4; this.avatarId = R.mipmap.image_avatar_4; }}, new Model() {{ this.name = "盐菜饭"; this.nickname = "等着你回来"; this.imageId = R.mipmap.image_practice_repast_5; this.avatarId = R.mipmap.image_avatar_5; }}, new Model() {{ this.name = "米豆腐"; this.nickname = "宝宝树人"; this.imageId = R.mipmap.image_practice_repast_6; this.avatarId = R.mipmap.image_avatar_6; }}); } } ================================================ FILE: app/src/main/java/com/scwang/refreshlayout/activity/practice/WebviewPracticeActivity.java ================================================ package com.scwang.refreshlayout.activity.practice; import android.annotation.SuppressLint; import android.os.Bundle; import android.support.v7.app.AppCompatActivity; import android.support.v7.widget.Toolbar; import android.view.View; import android.webkit.WebView; import android.webkit.WebViewClient; import com.scwang.refreshlayout.R; import com.scwang.refreshlayout.util.StatusBarUtil; import com.scwang.smartrefresh.layout.api.RefreshLayout; import com.scwang.smartrefresh.layout.listener.OnRefreshListener; import com.scwang.smartrefresh.layout.util.DensityUtil; import java.util.Locale; /** * 网页-Github */ public class WebviewPracticeActivity extends AppCompatActivity { @SuppressLint("SetJavaScriptEnabled") @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_practice_webview); final Toolbar toolbar = (Toolbar)findViewById(R.id.toolbar); toolbar.setNavigationOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { finish(); } }); final WebView webView = (WebView) findViewById(R.id.webView); final RefreshLayout refreshLayout = (RefreshLayout) findViewById(R.id.refreshLayout); refreshLayout.setOnRefreshListener(new OnRefreshListener() { @Override public void onRefresh(RefreshLayout refreshlayout) { webView.loadUrl("https://github.com/scwang90/SmartRefreshLayout"); } }); refreshLayout.autoRefresh(); webView.getSettings().setJavaScriptEnabled(true); webView.setWebViewClient(new WebViewClient(){ @Override public boolean shouldOverrideUrlLoading(WebView view, String url) { view.loadUrl(url); return true; } @Override public void onPageFinished(WebView view, String url) { super.onPageFinished(view, url); refreshLayout.finishRefresh(); view.loadUrl(String.format(Locale.CHINA, "javascript:document.body.style.paddingTop='%fpx'; void 0", DensityUtil.px2dp(webView.getPaddingTop()))); } }); //状态栏透明和间距处理 StatusBarUtil.immersive(this); StatusBarUtil.setPaddingSmart(this, webView); StatusBarUtil.setPaddingSmart(this, toolbar); StatusBarUtil.setMargin(this, findViewById(R.id.header)); StatusBarUtil.setPaddingSmart(this, findViewById(R.id.blurview)); } } ================================================ FILE: app/src/main/java/com/scwang/refreshlayout/activity/practice/WeiboPracticeActivity.java ================================================ package com.scwang.refreshlayout.activity.practice; import android.os.Bundle; import android.support.v4.content.ContextCompat; import android.support.v4.widget.NestedScrollView; import android.support.v7.app.AppCompatActivity; import android.support.v7.widget.Toolbar; import android.view.View; import android.widget.Toast; import com.scwang.refreshlayout.R; import com.scwang.refreshlayout.util.StatusBarUtil; import com.scwang.smartrefresh.layout.api.RefreshHeader; import com.scwang.smartrefresh.layout.api.RefreshLayout; import com.scwang.smartrefresh.layout.listener.SimpleMultiPurposeListener; import com.scwang.smartrefresh.layout.util.DensityUtil; /** * 微博列表 */ public class WeiboPracticeActivity extends AppCompatActivity { private int mOffset = 0; private int mScrollY = 0; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_practice_weibo); final Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); toolbar.setNavigationOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { finish(); } }); //状态栏透明和间距处理 StatusBarUtil.immersive(this); StatusBarUtil.setPaddingSmart(this, toolbar); final View parallax = findViewById(R.id.parallax); final View buttonBar = findViewById(R.id.buttonBarLayout); final NestedScrollView scrollView = (NestedScrollView)findViewById(R.id.scrollView); final RefreshLayout refreshLayout = (RefreshLayout)findViewById(R.id.refreshLayout); findViewById(R.id.attention).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { Toast.makeText(view.getContext(),"点击了关注",Toast.LENGTH_SHORT).show(); } }); findViewById(R.id.leaveword).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { Toast.makeText(view.getContext(),"点击了留言",Toast.LENGTH_SHORT).show(); } }); refreshLayout.setOnMultiPurposeListener(new SimpleMultiPurposeListener() { @Override public void onHeaderPulling(RefreshHeader header, float percent, int offset, int bottomHeight, int extendHeight) { mOffset = offset / 2; parallax.setTranslationY(mOffset - mScrollY); toolbar.setAlpha(1 - Math.min(percent, 1)); } @Override public void onHeaderReleasing(RefreshHeader header, float percent, int offset, int bottomHeight, int extendHeight) { mOffset = offset / 2; parallax.setTranslationY(mOffset - mScrollY); toolbar.setAlpha(1 - Math.min(percent, 1)); } }); scrollView.setOnScrollChangeListener(new NestedScrollView.OnScrollChangeListener() { private int lastScrollY = 0; private int h = DensityUtil.dp2px(170); private int color = ContextCompat.getColor(getApplicationContext(), R.color.colorPrimary)&0x00ffffff; @Override public void onScrollChange(NestedScrollView v, int scrollX, int scrollY, int oldScrollX, int oldScrollY) { if (lastScrollY < h) { scrollY = Math.min(h, scrollY); mScrollY = scrollY > h ? h : scrollY; buttonBar.setAlpha(1f * mScrollY / h); toolbar.setBackgroundColor(((255 * mScrollY / h) << 24) | color); parallax.setTranslationY(mOffset - mScrollY); } lastScrollY = scrollY; } }); buttonBar.setAlpha(0); toolbar.setBackgroundColor(0); } } ================================================ FILE: app/src/main/java/com/scwang/refreshlayout/activity/style/BezierStyleActivity.java ================================================ package com.scwang.refreshlayout.activity.style; import android.os.Build; import android.os.Bundle; import android.support.v4.content.ContextCompat; import android.support.v7.app.AppCompatActivity; import android.support.v7.widget.DefaultItemAnimator; import android.support.v7.widget.DividerItemDecoration; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; import android.support.v7.widget.Toolbar; import android.view.View; import android.widget.AdapterView; import com.scwang.refreshlayout.R; import com.scwang.refreshlayout.adapter.BaseRecyclerAdapter; import com.scwang.refreshlayout.adapter.SmartViewHolder; import com.scwang.smartrefresh.layout.api.RefreshLayout; import com.scwang.smartrefresh.layout.header.BezierRadarHeader; import java.util.Arrays; import static android.R.layout.simple_list_item_2; import static android.support.v7.widget.DividerItemDecoration.VERTICAL; public class BezierStyleActivity extends AppCompatActivity implements AdapterView.OnItemClickListener { private enum Item { 内容不偏移("下拉的时候列表内容停留在原位不动"), 内容跟随偏移("下拉的时候列表内容跟随向下偏移"), 橙色主题("更改为橙色主题颜色"), 红色主题("更改为红色主题颜色"), 绿色主题("更改为绿色主题颜色"), 蓝色主题("更改为蓝色主题颜色"), 打开左右拖动("打开左右拖动效果"), 关闭左右拖动("关闭左右拖动效果"), ; public String name; Item(String name) { this.name = name; } } private Toolbar mToolbar; private RefreshLayout mRefreshLayout; private BezierRadarHeader mRefreshHeader; private static boolean isFirstEnter = true; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_style_bezier); mToolbar = (Toolbar)findViewById(R.id.toolbar); mToolbar.setNavigationOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { finish(); } }); mRefreshLayout = (RefreshLayout)findViewById(R.id.refreshLayout); mRefreshHeader = (BezierRadarHeader) findViewById(R.id.header); if (isFirstEnter) { isFirstEnter = false; mRefreshLayout.autoRefresh();//第一次进入触发自动刷新,演示效果 } View view = findViewById(R.id.recyclerView); if (view instanceof RecyclerView) { RecyclerView recyclerView = (RecyclerView) view; recyclerView.setLayoutManager(new LinearLayoutManager(this)); recyclerView.addItemDecoration(new DividerItemDecoration(this, VERTICAL)); recyclerView.setItemAnimator(new DefaultItemAnimator()); recyclerView.setAdapter(new BaseRecyclerAdapter(Arrays.asList(Item.values()), simple_list_item_2,this) { @Override protected void onBindViewHolder(SmartViewHolder holder, Item model, int position) { holder.text(android.R.id.text1, model.name()); holder.text(android.R.id.text2, model.name); holder.textColorId(android.R.id.text2, R.color.colorTextAssistant); } }); } } @Override public void onItemClick(AdapterView parent, View view, int position, long id) { switch (Item.values()[position]) { case 内容不偏移: mRefreshLayout.setEnableHeaderTranslationContent(false); break; case 内容跟随偏移: mRefreshLayout.setEnableHeaderTranslationContent(true); break; case 蓝色主题: setThemeColor(R.color.colorPrimary, R.color.colorPrimaryDark); break; case 绿色主题: setThemeColor(android.R.color.holo_green_light, android.R.color.holo_green_dark); break; case 红色主题: setThemeColor(android.R.color.holo_red_light, android.R.color.holo_red_dark); break; case 橙色主题: setThemeColor(android.R.color.holo_orange_light, android.R.color.holo_orange_dark); break; case 打开左右拖动: mRefreshHeader.setEnableHorizontalDrag(true); break; case 关闭左右拖动: mRefreshHeader.setEnableHorizontalDrag(false); break; } mRefreshLayout.autoRefresh(); } private void setThemeColor(int colorPrimary, int colorPrimaryDark) { mToolbar.setBackgroundResource(colorPrimary); mRefreshLayout.setPrimaryColorsId(colorPrimary, android.R.color.white); if (Build.VERSION.SDK_INT >= 21) { getWindow().setStatusBarColor(ContextCompat.getColor(this, colorPrimaryDark)); } } } ================================================ FILE: app/src/main/java/com/scwang/refreshlayout/activity/style/CircleStyleActivity.java ================================================ package com.scwang.refreshlayout.activity.style; import android.os.Build; import android.os.Bundle; import android.support.v4.content.ContextCompat; import android.support.v7.app.AppCompatActivity; import android.support.v7.widget.DefaultItemAnimator; import android.support.v7.widget.DividerItemDecoration; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; import android.support.v7.widget.Toolbar; import android.view.View; import android.widget.AdapterView; import com.scwang.refreshlayout.R; import com.scwang.refreshlayout.adapter.BaseRecyclerAdapter; import com.scwang.refreshlayout.adapter.SmartViewHolder; import com.scwang.smartrefresh.layout.api.RefreshLayout; import java.util.Arrays; import static android.R.layout.simple_list_item_2; import static android.support.v7.widget.DividerItemDecoration.VERTICAL; public class CircleStyleActivity extends AppCompatActivity implements AdapterView.OnItemClickListener { private enum Item { 内容不偏移("下拉的时候列表内容停留在原位不动"), 内容跟随偏移("下拉的时候列表内容跟随向下偏移"), 橙色主题("更改为橙色主题颜色"), 红色主题("更改为红色主题颜色"), 绿色主题("更改为绿色主题颜色"), 蓝色主题("更改为蓝色主题颜色"), ; public String name; Item(String name) { this.name = name; } } private Toolbar mToolbar; private RefreshLayout mRefreshLayout; private static boolean isFirstEnter = true; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_style_circle); mToolbar = (Toolbar)findViewById(R.id.toolbar); mToolbar.setNavigationOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { finish(); } }); mRefreshLayout = (RefreshLayout)findViewById(R.id.refreshLayout); if (isFirstEnter) { isFirstEnter = false; mRefreshLayout.autoRefresh();//第一次进入触发自动刷新,演示效果 } View view = findViewById(R.id.recyclerView); if (view instanceof RecyclerView) { RecyclerView recyclerView = (RecyclerView) view; recyclerView.setLayoutManager(new LinearLayoutManager(this)); recyclerView.addItemDecoration(new DividerItemDecoration(this, VERTICAL)); recyclerView.setItemAnimator(new DefaultItemAnimator()); recyclerView.setAdapter(new BaseRecyclerAdapter(Arrays.asList(Item.values()), simple_list_item_2,this) { @Override protected void onBindViewHolder(SmartViewHolder holder, Item model, int position) { holder.text(android.R.id.text1, model.name()); holder.text(android.R.id.text2, model.name); holder.textColorId(android.R.id.text2, R.color.colorTextAssistant); } }); } } @Override public void onItemClick(AdapterView parent, View view, int position, long id) { switch (Item.values()[position]) { case 内容不偏移: mRefreshLayout.setEnableHeaderTranslationContent(false); break; case 内容跟随偏移: mRefreshLayout.setEnableHeaderTranslationContent(true); break; case 蓝色主题: setThemeColor(R.color.colorPrimary, R.color.colorPrimaryDark); break; case 绿色主题: setThemeColor(android.R.color.holo_green_light, android.R.color.holo_green_dark); break; case 红色主题: setThemeColor(android.R.color.holo_red_light, android.R.color.holo_red_dark); break; case 橙色主题: setThemeColor(android.R.color.holo_orange_light, android.R.color.holo_orange_dark); break; } mRefreshLayout.autoRefresh(); } private void setThemeColor(int colorPrimary, int colorPrimaryDark) { mToolbar.setBackgroundResource(colorPrimary); mRefreshLayout.setPrimaryColorsId(colorPrimary, android.R.color.white); if (Build.VERSION.SDK_INT >= 21) { getWindow().setStatusBarColor(ContextCompat.getColor(this, colorPrimaryDark)); } } } ================================================ FILE: app/src/main/java/com/scwang/refreshlayout/activity/style/ClassicsStyleActivity.java ================================================ package com.scwang.refreshlayout.activity.style; import android.graphics.drawable.Drawable; import android.graphics.drawable.LayerDrawable; import android.os.Build; import android.os.Bundle; import android.support.graphics.drawable.VectorDrawableCompat; import android.support.v4.content.ContextCompat; import android.support.v7.app.AppCompatActivity; import android.support.v7.widget.DefaultItemAnimator; import android.support.v7.widget.DividerItemDecoration; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; import android.support.v7.widget.Toolbar; import android.view.View; import android.widget.AdapterView; import com.scwang.refreshlayout.R; import com.scwang.refreshlayout.adapter.BaseRecyclerAdapter; import com.scwang.refreshlayout.adapter.SmartViewHolder; import com.scwang.refreshlayout.util.DynamicTimeFormat; import com.scwang.smartrefresh.layout.api.RefreshLayout; import com.scwang.smartrefresh.layout.constant.SpinnerStyle; import com.scwang.smartrefresh.layout.header.ClassicsHeader; import java.text.SimpleDateFormat; import java.util.Arrays; import java.util.Date; import java.util.Locale; import java.util.Random; import static android.R.layout.simple_list_item_2; import static android.support.v7.widget.DividerItemDecoration.VERTICAL; public class ClassicsStyleActivity extends AppCompatActivity implements AdapterView.OnItemClickListener { private BaseRecyclerAdapter mAdpater; private enum Item { 尺寸拉伸("下拉的时候Header的高度跟随变大"), 位置平移("下拉的时候Header的位置向下偏移"), 背后固定("下拉的时候Header固定在背后"), 显示时间("开启显示上次更新功能"), 隐藏时间("关闭显示上次更新功能"), 默认主题("更改为默认主题颜色"), 橙色主题("更改为橙色主题颜色"), 红色主题("更改为红色主题颜色"), 绿色主题("更改为绿色主题颜色"), 蓝色主题("更改为蓝色主题颜色"), 加载更多("上啦加载更多"), ; public String name; Item(String name) { this.name = name; } } private Toolbar mToolbar; private RecyclerView mRecyclerView; private RefreshLayout mRefreshLayout; private ClassicsHeader mClassicsHeader; private Drawable mDrawableProgress; private static boolean isFirstEnter = true; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_style_classics); mToolbar = (Toolbar)findViewById(R.id.toolbar); mToolbar.setNavigationOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { finish(); } }); mRefreshLayout = (RefreshLayout)findViewById(R.id.refreshLayout); int deta = new Random().nextInt(7 * 24 * 60 * 60 * 1000); mClassicsHeader = (ClassicsHeader)mRefreshLayout.getRefreshHeader(); mClassicsHeader.setLastUpdateTime(new Date(System.currentTimeMillis()-deta)); mClassicsHeader.setTimeFormat(new SimpleDateFormat("更新于 MM-dd HH:mm", Locale.CHINA)); mClassicsHeader.setTimeFormat(new DynamicTimeFormat("更新于 %s")); mDrawableProgress = mClassicsHeader.getProgressView().getDrawable(); if (mDrawableProgress instanceof LayerDrawable) { mDrawableProgress = ((LayerDrawable) mDrawableProgress).getDrawable(0); } View view = findViewById(R.id.recyclerView); if (view instanceof RecyclerView) { RecyclerView recyclerView = (RecyclerView) view; recyclerView.setLayoutManager(new LinearLayoutManager(this)); recyclerView.addItemDecoration(new DividerItemDecoration(this, VERTICAL)); recyclerView.setItemAnimator(new DefaultItemAnimator()); recyclerView.setAdapter(mAdpater = new BaseRecyclerAdapter(Arrays.asList(Item.values()), simple_list_item_2,this) { @Override protected void onBindViewHolder(SmartViewHolder holder, Item model, int position) { holder.text(android.R.id.text1, model.name()); holder.text(android.R.id.text2, model.name); holder.textColorId(android.R.id.text2, R.color.colorTextAssistant); } }); mRecyclerView = recyclerView; } if (isFirstEnter) { isFirstEnter = false; //触发自动刷新 mRefreshLayout.autoRefresh(); } } @Override public void onItemClick(AdapterView parent, View view, int position, long id) { switch (Item.values()[position]) { case 背后固定: mClassicsHeader.setSpinnerStyle(SpinnerStyle.FixedBehind); mRefreshLayout.setPrimaryColors(0xff444444, 0xffffffff); if (Build.VERSION.SDK_INT >= 21) { mDrawableProgress.setTint(0xffffffff); } else if (mDrawableProgress instanceof VectorDrawableCompat) { ((VectorDrawableCompat) mDrawableProgress).setTint(0xffffffff); } /* * 由于是后面才设置,需要手动更改视图的位置 * 如果在 onCreate 或者 xml 中设置好[SpinnerStyle] 就不用手动调整位置了 */ mRefreshLayout.getLayout().bringChildToFront(mRecyclerView); break; case 尺寸拉伸: mClassicsHeader.setSpinnerStyle(SpinnerStyle.Scale); break; case 位置平移: mClassicsHeader.setSpinnerStyle(SpinnerStyle.Translate); break; case 显示时间: mClassicsHeader.setEnableLastTime(true); break; case 隐藏时间: mClassicsHeader.setEnableLastTime(false); break; case 默认主题: setThemeColor(R.color.colorPrimary, R.color.colorPrimaryDark); mRefreshLayout.getLayout().setBackgroundResource(android.R.color.transparent); mRefreshLayout.setPrimaryColors(0, 0xff666666); if (Build.VERSION.SDK_INT >= 21) { mDrawableProgress.setTint(0xff666666); } else if (mDrawableProgress instanceof VectorDrawableCompat) { ((VectorDrawableCompat) mDrawableProgress).setTint(0xff666666); } break; case 蓝色主题: setThemeColor(R.color.colorPrimary, R.color.colorPrimaryDark); break; case 绿色主题: setThemeColor(android.R.color.holo_green_light, android.R.color.holo_green_dark); break; case 红色主题: setThemeColor(android.R.color.holo_red_light, android.R.color.holo_red_dark); break; case 橙色主题: setThemeColor(android.R.color.holo_orange_light, android.R.color.holo_orange_dark); break; case 加载更多: mRefreshLayout.autoLoadmore(); return; } mRefreshLayout.autoRefresh(); } private void setThemeColor(int colorPrimary, int colorPrimaryDark) { mToolbar.setBackgroundResource(colorPrimary); mRefreshLayout.setPrimaryColorsId(colorPrimary, android.R.color.white); if (Build.VERSION.SDK_INT >= 21) { getWindow().setStatusBarColor(ContextCompat.getColor(this, colorPrimaryDark)); mDrawableProgress.setTint(0xffffffff); } else if (mDrawableProgress instanceof VectorDrawableCompat) { ((VectorDrawableCompat) mDrawableProgress).setTint(0xffffffff); } } } ================================================ FILE: app/src/main/java/com/scwang/refreshlayout/activity/style/DeliveryStyleActivity.java ================================================ package com.scwang.refreshlayout.activity.style; import android.os.Build; import android.os.Bundle; import android.support.v4.content.ContextCompat; import android.support.v7.app.AppCompatActivity; import android.support.v7.widget.DefaultItemAnimator; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; import android.support.v7.widget.Toolbar; import android.view.View; import android.view.Window; import android.widget.AdapterView; import com.scwang.refreshlayout.R; import com.scwang.refreshlayout.adapter.BaseRecyclerAdapter; import com.scwang.refreshlayout.adapter.SmartViewHolder; import com.scwang.smartrefresh.layout.api.RefreshLayout; import java.util.Arrays; import static com.scwang.refreshlayout.R.layout.listitem_style_delivery; public class DeliveryStyleActivity extends AppCompatActivity implements AdapterView.OnItemClickListener { private enum Item { 默认主题("更改为默认主题颜色"), 橙色主题("更改为橙色主题颜色"), 红色主题("更改为红色主题颜色"), 绿色主题("更改为绿色主题颜色"), 蓝色主题("更改为蓝色主题颜色"), ; public String name; Item(String name) { this.name = name; } } private Toolbar mToolbar; private RefreshLayout mRefreshLayout; private static boolean isFirstEnter = true; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_style_delivery); if (Build.VERSION.SDK_INT >= 21) { getWindow().setStatusBarColor(0xfff0f0f0); } if (Build.VERSION.SDK_INT >= 23) { Window window = getWindow(); int systemUiVisibility = window.getDecorView().getSystemUiVisibility(); systemUiVisibility |= View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR; window.getDecorView().setSystemUiVisibility(systemUiVisibility); } mToolbar = (Toolbar)findViewById(R.id.toolbar); mToolbar.setNavigationOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { finish(); } }); mRefreshLayout = (RefreshLayout)findViewById(R.id.refreshLayout); if (isFirstEnter) { isFirstEnter = false; mRefreshLayout.autoRefresh();//第一次进入触发自动刷新,演示效果 } View view = findViewById(R.id.recyclerView); if (view instanceof RecyclerView) { RecyclerView recyclerView = (RecyclerView) view; recyclerView.setLayoutManager(new LinearLayoutManager(this)); recyclerView.setItemAnimator(new DefaultItemAnimator()); recyclerView.setAdapter(new BaseRecyclerAdapter(Arrays.asList(Item.values()), listitem_style_delivery,this) { @Override protected void onBindViewHolder(SmartViewHolder holder, Item model, int position) { } }); } } @Override public void onItemClick(AdapterView parent, View view, int position, long id) { switch (Item.values()[position]) { case 默认主题: mToolbar.setBackgroundResource(android.R.color.white); mToolbar.setTitleTextColor(0xffbbbbbb); mToolbar.setNavigationIcon(R.drawable.ic_arrow_back_gray_24dp); mRefreshLayout.setPrimaryColors(0xfff0f0f0, 0xffffffff); if (Build.VERSION.SDK_INT >= 21) { getWindow().setStatusBarColor(0xfff0f0f0); } if (Build.VERSION.SDK_INT >= 23) { Window window = getWindow(); int systemUiVisibility = window.getDecorView().getSystemUiVisibility(); systemUiVisibility |= View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR; window.getDecorView().setSystemUiVisibility(systemUiVisibility); } break; case 蓝色主题: setThemeColor(R.color.colorPrimary, R.color.colorPrimaryDark); break; case 绿色主题: setThemeColor(android.R.color.holo_green_light, android.R.color.holo_green_dark); break; case 红色主题: setThemeColor(android.R.color.holo_red_light, android.R.color.holo_red_dark); break; case 橙色主题: setThemeColor(android.R.color.holo_orange_light, android.R.color.holo_orange_dark); break; } mRefreshLayout.autoRefresh(); } private void setThemeColor(int colorPrimary, int colorPrimaryDark) { mToolbar.setBackgroundResource(colorPrimary); mToolbar.setTitleTextColor(ContextCompat.getColor(this, android.R.color.white)); mToolbar.setNavigationIcon(R.drawable.ic_arrow_back_white_24dp); mRefreshLayout.setPrimaryColorsId(colorPrimary, android.R.color.white); if (Build.VERSION.SDK_INT >= 21) { getWindow().setStatusBarColor(ContextCompat.getColor(this, colorPrimaryDark)); } if (Build.VERSION.SDK_INT >= 23) { Window window = getWindow(); int systemUiVisibility = window.getDecorView().getSystemUiVisibility(); systemUiVisibility &= ~View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR; window.getDecorView().setSystemUiVisibility(systemUiVisibility); } } } ================================================ FILE: app/src/main/java/com/scwang/refreshlayout/activity/style/DropboxStyleActivity.java ================================================ package com.scwang.refreshlayout.activity.style; import android.os.Build; import android.os.Bundle; import android.support.v4.content.ContextCompat; import android.support.v7.app.AppCompatActivity; import android.support.v7.widget.DefaultItemAnimator; import android.support.v7.widget.DividerItemDecoration; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; import android.support.v7.widget.Toolbar; import android.view.View; import android.widget.AdapterView; import com.scwang.refreshlayout.R; import com.scwang.refreshlayout.adapter.BaseRecyclerAdapter; import com.scwang.refreshlayout.adapter.SmartViewHolder; import com.scwang.smartrefresh.layout.api.RefreshLayout; import java.util.Arrays; import static android.R.layout.simple_list_item_2; import static android.support.v7.widget.DividerItemDecoration.VERTICAL; public class DropboxStyleActivity extends AppCompatActivity implements AdapterView.OnItemClickListener { private enum Item { 默认主题("更改为默认主题颜色"), 橙色主题("更改为橙色主题颜色"), 红色主题("更改为红色主题颜色"), 绿色主题("更改为绿色主题颜色"), 蓝色主题("更改为蓝色主题颜色"), ; public String name; Item(String name) { this.name = name; } } private Toolbar mToolbar; private RefreshLayout mRefreshLayout; private static boolean isFirstEnter = true; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_style_dropbox); mToolbar = (Toolbar)findViewById(R.id.toolbar); mToolbar.setNavigationOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { finish(); } }); mRefreshLayout = (RefreshLayout)findViewById(R.id.refreshLayout); if (isFirstEnter) { isFirstEnter = false; mRefreshLayout.autoRefresh();//第一次进入触发自动刷新,演示效果 } View view = findViewById(R.id.recyclerView); if (view instanceof RecyclerView) { RecyclerView recyclerView = (RecyclerView) view; recyclerView.setLayoutManager(new LinearLayoutManager(this)); recyclerView.addItemDecoration(new DividerItemDecoration(this, VERTICAL)); recyclerView.setItemAnimator(new DefaultItemAnimator()); recyclerView.setAdapter(new BaseRecyclerAdapter(Arrays.asList(Item.values()), simple_list_item_2,this) { @Override protected void onBindViewHolder(SmartViewHolder holder, Item model, int position) { holder.text(android.R.id.text1, model.name()); holder.text(android.R.id.text2, model.name); holder.textColorId(android.R.id.text2, R.color.colorTextAssistant); } }); } } @Override public void onItemClick(AdapterView parent, View view, int position, long id) { switch (Item.values()[position]) { case 默认主题: setThemeColor(R.color.colorPrimary, R.color.colorPrimaryDark); mRefreshLayout.setPrimaryColors(0xff283645, 0xff6ea9ff); break; case 蓝色主题: setThemeColor(R.color.colorPrimary, R.color.colorPrimaryDark); break; case 绿色主题: setThemeColor(android.R.color.holo_green_light, android.R.color.holo_green_dark); break; case 红色主题: setThemeColor(android.R.color.holo_red_light, android.R.color.holo_red_dark); break; case 橙色主题: setThemeColor(android.R.color.holo_orange_light, android.R.color.holo_orange_dark); break; } mRefreshLayout.autoRefresh(); } private void setThemeColor(int colorPrimary, int colorPrimaryDark) { mToolbar.setBackgroundResource(colorPrimary); mRefreshLayout.setPrimaryColorsId(colorPrimary, android.R.color.white); if (Build.VERSION.SDK_INT >= 21) { getWindow().setStatusBarColor(ContextCompat.getColor(this, colorPrimaryDark)); } } } ================================================ FILE: app/src/main/java/com/scwang/refreshlayout/activity/style/FlyRefreshStyleActivity.java ================================================ package com.scwang.refreshlayout.activity.style; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.AnimatorSet; import android.animation.ObjectAnimator; import android.animation.ValueAnimator; import android.content.Context; import android.content.res.ColorStateList; import android.graphics.Color; import android.graphics.drawable.ShapeDrawable; import android.graphics.drawable.shapes.OvalShape; import android.os.Bundle; import android.support.design.widget.AppBarLayout; import android.support.design.widget.CollapsingToolbarLayout; import android.support.design.widget.FloatingActionButton; import android.support.v4.content.ContextCompat; import android.support.v7.app.AppCompatActivity; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; import android.support.v7.widget.Toolbar; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.view.animation.AccelerateInterpolator; import android.view.animation.DecelerateInterpolator; import android.view.animation.Interpolator; import android.view.animation.OvershootInterpolator; import android.widget.ImageView; import android.widget.TextView; import com.scwang.refreshlayout.R; import com.scwang.refreshlayout.util.StatusBarUtil; import com.scwang.smartrefresh.header.FlyRefreshHeader; import com.scwang.smartrefresh.header.flyrefresh.FlyView; import com.scwang.smartrefresh.header.flyrefresh.MountanScenceView; import com.scwang.smartrefresh.layout.api.RefreshHeader; import com.scwang.smartrefresh.layout.api.RefreshLayout; import com.scwang.smartrefresh.layout.listener.OnRefreshListener; import com.scwang.smartrefresh.layout.listener.SimpleMultiPurposeListener; import com.scwang.smartrefresh.layout.util.DensityUtil; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Date; import java.util.Locale; import jp.wasabeef.recyclerview.animators.BaseItemAnimator; public class FlyRefreshStyleActivity extends AppCompatActivity { private RecyclerView mListView; private RefreshLayout mRefreshlayout; private ItemAdapter mAdapter; private FlyView mFlyView; private ArrayList mDataSet = new ArrayList<>(); private LinearLayoutManager mLayoutManager; private MountanScenceView mScenceView; private FlyRefreshHeader mFlyRefreshHeader; private CollapsingToolbarLayout mToolbarLayout; private FloatingActionButton mActionButton; private View.OnClickListener mThemeListener; private static boolean isFirstEnter = true; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_fly_refresh); final Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); setSupportActionBar(toolbar); toolbar.setNavigationOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { finish(); } }); /************************************************************ * 关键代码-开始 ************************************************************/ mFlyView = (FlyView) findViewById(R.id.flyview); mScenceView = (MountanScenceView) findViewById(R.id.mountan); mFlyRefreshHeader = (FlyRefreshHeader)findViewById(R.id.flyrefresh); mFlyRefreshHeader.setUp(mScenceView, mFlyView);//绑定场景和纸飞机 mRefreshlayout = (RefreshLayout) findViewById(R.id.refreshLayout); mRefreshlayout.setReboundInterpolator(new ElasticOutInterpolator());//设置回弹插值器,会带有弹簧震动效果 mRefreshlayout.setReboundDuration(800);//设置回弹动画时长 mRefreshlayout.setOnRefreshListener(new OnRefreshListener() { @Override public void onRefresh(RefreshLayout refreshlayout) { View child = mListView.getChildAt(0); if (child != null) { //开始刷新的时候个第一个item设置动画效果 bounceAnimateView(child.findViewById(R.id.icon)); } updateTheme();//改变主题颜色 mRefreshlayout.getLayout().postDelayed(new Runnable() { @Override public void run() { //通知刷新完成,这里改为通知Header,让纸飞机飞回来 mFlyRefreshHeader.finishRefresh(new AnimatorListenerAdapter() { public void onAnimationEnd(Animator animation) { addItemData();//在纸飞机回到原位之后添加数据效果更真实 } }); } }, 2000);//模拟两秒的后台数据加载 } }); //设置 让 AppBarLayout 和 RefreshLayout 的滚动同步 并不保持 toolbar 位置不变 final AppBarLayout appBar = (AppBarLayout) findViewById(R.id.app_bar); mRefreshlayout.setOnMultiPurposeListener(new SimpleMultiPurposeListener() { @Override public void onHeaderPulling(RefreshHeader header, float percent, int offset, int footerHeight, int extendHeight) { appBar.setTranslationY(offset); toolbar.setTranslationY(-offset); } @Override public void onHeaderReleasing(RefreshHeader header, float percent, int offset, int footerHeight, int extendHeight) { appBar.setTranslationY(offset); toolbar.setTranslationY(-offset); } }); /************************************************************ * 关键代码-结束 ************************************************************/ if (isFirstEnter) { isFirstEnter = false; mRefreshlayout.autoRefresh();//第一次进入触发自动刷新,演示效果 } /** * 初始化列表数据 */ initDataSet(); mAdapter = new ItemAdapter(this); mLayoutManager = new LinearLayoutManager(this); mListView = (RecyclerView) findViewById(R.id.recyclerView); mListView.setLayoutManager(mLayoutManager); mListView.setAdapter(mAdapter); mListView.setItemAnimator(new SampleItemAnimator()); mToolbarLayout = (CollapsingToolbarLayout) findViewById(R.id.toolbar_layout); mActionButton = (FloatingActionButton) findViewById(R.id.fab); /** * 设置点击 ActionButton 时候触发自动刷新 并改变主题颜色 */ mActionButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { updateTheme(); mRefreshlayout.autoRefresh(); } }); /** * 监听 AppBarLayout 的关闭和开启 给 FlyView(纸飞机) 和 ActionButton 设置关闭隐藏动画 */ appBar.addOnOffsetChangedListener(new AppBarLayout.OnOffsetChangedListener() { boolean misAppbarExpand = true; @Override public void onOffsetChanged(AppBarLayout appBarLayout, int verticalOffset) { int scrollRange = appBarLayout.getTotalScrollRange(); float fraction = 1f * (scrollRange + verticalOffset) / scrollRange; if (fraction < 0.1 && misAppbarExpand) { misAppbarExpand = false; mActionButton.animate().scaleX(0).scaleY(0); mFlyView.animate().scaleX(0).scaleY(0); ValueAnimator animator = ValueAnimator.ofInt(mListView.getPaddingTop(), 0); animator.setDuration(300); animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { mListView.setPadding(0, (int) animation.getAnimatedValue(), 0, 0); } }); animator.start(); } if (fraction > 0.8 && !misAppbarExpand) { misAppbarExpand = true; mActionButton.animate().scaleX(1).scaleY(1); mFlyView.animate().scaleX(1).scaleY(1); ValueAnimator animator = ValueAnimator.ofInt(mListView.getPaddingTop(), DensityUtil.dp2px(25)); animator.setDuration(300); animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { mListView.setPadding(0, (int) animation.getAnimatedValue(), 0, 0); } }); animator.start(); } } }); //状态栏透明和间距处理 StatusBarUtil.immersive(this); StatusBarUtil.setPaddingSmart(this, toolbar); // StatusBarUtil.setPaddingSmart(this, findViewById(R.id.profile)); // StatusBarUtil.setPaddingSmart(this, findViewById(R.id.blurview)); } private void updateTheme() { if (mThemeListener == null) { mThemeListener = new View.OnClickListener() { int index = 0; int[] ids = new int[]{ R.color.colorPrimary, android.R.color.holo_green_light, android.R.color.holo_red_light, android.R.color.holo_orange_light, android.R.color.holo_blue_bright, }; @Override public void onClick(View v) { int color = ContextCompat.getColor(getApplication(), ids[index % ids.length]); mRefreshlayout.setPrimaryColors(color); mActionButton.setBackgroundColor(color); mActionButton.setBackgroundTintList(ColorStateList.valueOf(color)); mToolbarLayout.setContentScrimColor(color); index++; } }; } mThemeListener.onClick(null); } private void initDataSet() { mDataSet.add(new ItemData(Color.parseColor("#76A9FC"), R.drawable.ic_fly_refresh_poll, "Meeting Minutes", new Date(2014 - 1900, 2, 9))); mDataSet.add(new ItemData(Color.GRAY, R.drawable.ic_fly_refresh_folder, "Favorites Photos", new Date(2014 - 1900, 1, 3))); mDataSet.add(new ItemData(Color.GRAY, R.drawable.ic_fly_refresh_folder, "Photos", new Date(2014 - 1900, 0, 9))); } private void addItemData() { ItemData itemData = new ItemData(Color.parseColor("#FFC970"), R.drawable.ic_fly_refresh_smartphone, "Magic Cube Show", new Date()); mDataSet.add(0, itemData); mAdapter.notifyItemInserted(0); mLayoutManager.scrollToPosition(0); } private void bounceAnimateView(final View view) { if (view == null) { return; } ValueAnimator swing = ValueAnimator.ofFloat(0, 60, -40, 0); swing.setDuration(400); swing.setInterpolator(new AccelerateInterpolator()); swing.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { view.setRotationX((float)animation.getAnimatedValue()); } }); swing.start(); } private class ItemAdapter extends RecyclerView.Adapter { private LayoutInflater mInflater; private DateFormat dateFormat; public ItemAdapter(Context context) { mInflater = LayoutInflater.from(context); dateFormat = SimpleDateFormat.getDateInstance(DateFormat.DEFAULT, Locale.ENGLISH); } @Override public ItemViewHolder onCreateViewHolder(ViewGroup viewGroup, int i) { View view = mInflater.inflate(R.layout.activity_fly_refresh_item, viewGroup, false); return new ItemViewHolder(view); } @Override public void onBindViewHolder(ItemViewHolder itemViewHolder, int i) { final ItemData data = mDataSet.get(i); ShapeDrawable drawable = new ShapeDrawable(new OvalShape()); drawable.getPaint().setColor(data.color); itemViewHolder.icon.setBackgroundDrawable(drawable); itemViewHolder.icon.setImageResource(data.icon); itemViewHolder.title.setText(data.title); itemViewHolder.subTitle.setText(dateFormat.format(data.time)); } @Override public int getItemCount() { return mDataSet.size(); } } private static class ItemViewHolder extends RecyclerView.ViewHolder { ImageView icon; TextView title; TextView subTitle; public ItemViewHolder(View itemView) { super(itemView); icon = (ImageView) itemView.findViewById(R.id.icon); title = (TextView) itemView.findViewById(R.id.title); subTitle = (TextView) itemView.findViewById(R.id.subtitle); } } public class ItemData { int color; public int icon; public String title; public Date time; public ItemData(int color, int icon, String title, Date time) { this.color = color; this.icon = icon; this.title = title; this.time = time; } public ItemData(int icon, String title) { this(Color.DKGRAY, icon, title, new Date()); } } public class SampleItemAnimator extends BaseItemAnimator { @Override protected void preAnimateAddImpl(RecyclerView.ViewHolder holder) { View icon = holder.itemView.findViewById(R.id.icon); icon.setRotationX(30); View right = holder.itemView.findViewById(R.id.right); right.setPivotX(0); right.setPivotY(0); right.setRotationY(90); } @Override protected void animateRemoveImpl(RecyclerView.ViewHolder viewHolder) { } @Override protected void animateAddImpl(final RecyclerView.ViewHolder holder) { View target = holder.itemView; View icon = target.findViewById(R.id.icon); Animator swing = ObjectAnimator.ofFloat(icon, "rotationX", 45, 0); swing.setInterpolator(new OvershootInterpolator(5)); View right = holder.itemView.findViewById(R.id.right); Animator rotateIn = ObjectAnimator.ofFloat(right, "rotationY", 90, 0); rotateIn.setInterpolator(new DecelerateInterpolator()); AnimatorSet animator = new AnimatorSet(); animator.setDuration(getAddDuration()); animator.playTogether(swing, rotateIn); animator.start(); } } public class ElasticOutInterpolator implements Interpolator { @Override public float getInterpolation(float t) { if (t == 0) return 0; if (t >= 1) return 1; float p=.3f; float s=p/4; return ((float)Math.pow(2,-10*t) * (float)Math.sin( (t-s)*(2*(float)Math.PI)/p) + 1); } } } ================================================ FILE: app/src/main/java/com/scwang/refreshlayout/activity/style/FunGameBattleCityStyleActivity.java ================================================ package com.scwang.refreshlayout.activity.style; import android.os.Build; import android.os.Bundle; import android.support.v4.content.ContextCompat; import android.support.v7.app.AppCompatActivity; import android.support.v7.widget.DefaultItemAnimator; import android.support.v7.widget.DividerItemDecoration; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; import android.support.v7.widget.Toolbar; import android.view.View; import android.widget.AdapterView; import com.scwang.refreshlayout.R; import com.scwang.refreshlayout.adapter.BaseRecyclerAdapter; import com.scwang.refreshlayout.adapter.SmartViewHolder; import com.scwang.smartrefresh.layout.api.RefreshLayout; import java.util.Arrays; import static android.R.layout.simple_list_item_2; import static android.support.v7.widget.DividerItemDecoration.VERTICAL; public class FunGameBattleCityStyleActivity extends AppCompatActivity implements AdapterView.OnItemClickListener { private enum Item { 默认主题("更改为橙色默认颜色"), 橙色主题("更改为橙色主题颜色"), 红色主题("更改为红色主题颜色"), 绿色主题("更改为绿色主题颜色"), 蓝色主题("更改为蓝色主题颜色"), ; public String name; Item(String name) { this.name = name; } } private Toolbar mToolbar; private RefreshLayout mRefreshLayout; private static boolean isFirstEnter = true; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_style_fungame_battlecity); mToolbar = (Toolbar)findViewById(R.id.toolbar); mToolbar.setNavigationOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { finish(); } }); mRefreshLayout = (RefreshLayout)findViewById(R.id.refreshLayout); if (isFirstEnter) { isFirstEnter = false; mRefreshLayout.autoRefresh();//第一次进入触发自动刷新,演示效果 } View view = findViewById(R.id.recyclerView); if (view instanceof RecyclerView) { RecyclerView recyclerView = (RecyclerView) view; recyclerView.setLayoutManager(new LinearLayoutManager(this)); recyclerView.addItemDecoration(new DividerItemDecoration(this, VERTICAL)); recyclerView.setItemAnimator(new DefaultItemAnimator()); recyclerView.setAdapter(new BaseRecyclerAdapter(Arrays.asList(Item.values()), simple_list_item_2,this) { @Override protected void onBindViewHolder(SmartViewHolder holder, Item model, int position) { holder.text(android.R.id.text1, model.name()); holder.text(android.R.id.text2, model.name); holder.textColorId(android.R.id.text2, R.color.colorTextAssistant); } }); } } @Override public void onItemClick(AdapterView parent, View view, int position, long id) { switch (Item.values()[position]) { case 默认主题: mRefreshLayout.setPrimaryColorsId(android.R.color.white, android.R.color.black); break; case 蓝色主题: setThemeColor(R.color.colorPrimary, R.color.colorPrimaryDark); break; case 绿色主题: setThemeColor(android.R.color.holo_green_light, android.R.color.holo_green_dark); break; case 红色主题: setThemeColor(android.R.color.holo_red_light, android.R.color.holo_red_dark); break; case 橙色主题: setThemeColor(android.R.color.holo_orange_light, android.R.color.holo_orange_dark); break; } mRefreshLayout.autoRefresh(); } private void setThemeColor(int colorPrimary, int colorPrimaryDark) { mToolbar.setBackgroundResource(colorPrimary); mRefreshLayout.setPrimaryColorsId(colorPrimary, android.R.color.white); if (Build.VERSION.SDK_INT >= 21) { getWindow().setStatusBarColor(ContextCompat.getColor(this, colorPrimaryDark)); } } } ================================================ FILE: app/src/main/java/com/scwang/refreshlayout/activity/style/FunGameHitBlockStyleActivity.java ================================================ package com.scwang.refreshlayout.activity.style; import android.os.Build; import android.os.Bundle; import android.support.v4.content.ContextCompat; import android.support.v7.app.AppCompatActivity; import android.support.v7.widget.DefaultItemAnimator; import android.support.v7.widget.DividerItemDecoration; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; import android.support.v7.widget.Toolbar; import android.view.View; import android.widget.AdapterView; import com.scwang.refreshlayout.R; import com.scwang.refreshlayout.adapter.BaseRecyclerAdapter; import com.scwang.refreshlayout.adapter.SmartViewHolder; import com.scwang.smartrefresh.layout.api.RefreshLayout; import java.util.Arrays; import static android.R.layout.simple_list_item_2; import static android.support.v7.widget.DividerItemDecoration.VERTICAL; public class FunGameHitBlockStyleActivity extends AppCompatActivity implements AdapterView.OnItemClickListener { private enum Item { 默认主题("更改为橙色默认颜色"), 橙色主题("更改为橙色主题颜色"), 红色主题("更改为红色主题颜色"), 绿色主题("更改为绿色主题颜色"), 蓝色主题("更改为蓝色主题颜色"), ; public String name; Item(String name) { this.name = name; } } private Toolbar mToolbar; private RefreshLayout mRefreshLayout; private static boolean isFirstEnter = true; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_style_fungame_hitblock); mToolbar = (Toolbar)findViewById(R.id.toolbar); mToolbar.setNavigationOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { finish(); } }); mRefreshLayout = (RefreshLayout)findViewById(R.id.refreshLayout); if (isFirstEnter) { isFirstEnter = false; mRefreshLayout.autoRefresh();//第一次进入触发自动刷新,演示效果 } View view = findViewById(R.id.recyclerView); if (view instanceof RecyclerView) { RecyclerView recyclerView = (RecyclerView) view; recyclerView.setLayoutManager(new LinearLayoutManager(this)); recyclerView.addItemDecoration(new DividerItemDecoration(this, VERTICAL)); recyclerView.setItemAnimator(new DefaultItemAnimator()); recyclerView.setAdapter(new BaseRecyclerAdapter(Arrays.asList(Item.values()), simple_list_item_2,this) { @Override protected void onBindViewHolder(SmartViewHolder holder, Item model, int position) { holder.text(android.R.id.text1, model.name()); holder.text(android.R.id.text2, model.name); holder.textColorId(android.R.id.text2, R.color.colorTextAssistant); } }); } } @Override public void onItemClick(AdapterView parent, View view, int position, long id) { switch (Item.values()[position]) { case 默认主题: mRefreshLayout.setPrimaryColorsId(android.R.color.white, android.R.color.black); break; case 蓝色主题: setThemeColor(R.color.colorPrimary, R.color.colorPrimaryDark); break; case 绿色主题: setThemeColor(android.R.color.holo_green_light, android.R.color.holo_green_dark); break; case 红色主题: setThemeColor(android.R.color.holo_red_light, android.R.color.holo_red_dark); break; case 橙色主题: setThemeColor(android.R.color.holo_orange_light, android.R.color.holo_orange_dark); break; } mRefreshLayout.autoRefresh(); } private void setThemeColor(int colorPrimary, int colorPrimaryDark) { mToolbar.setBackgroundResource(colorPrimary); mRefreshLayout.setPrimaryColorsId(colorPrimary, android.R.color.white); if (Build.VERSION.SDK_INT >= 21) { getWindow().setStatusBarColor(ContextCompat.getColor(this, colorPrimaryDark)); } } } ================================================ FILE: app/src/main/java/com/scwang/refreshlayout/activity/style/MaterialStyleActivity.java ================================================ package com.scwang.refreshlayout.activity.style; import android.os.Build; import android.os.Bundle; import android.support.v4.content.ContextCompat; import android.support.v7.app.AppCompatActivity; import android.support.v7.widget.DefaultItemAnimator; import android.support.v7.widget.DividerItemDecoration; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; import android.support.v7.widget.Toolbar; import android.view.View; import android.widget.AdapterView; import com.scwang.refreshlayout.R; import com.scwang.refreshlayout.adapter.BaseRecyclerAdapter; import com.scwang.refreshlayout.adapter.SmartViewHolder; import com.scwang.smartrefresh.header.MaterialHeader; import com.scwang.smartrefresh.layout.api.RefreshLayout; import java.util.Arrays; import static android.R.layout.simple_list_item_2; import static android.support.v7.widget.DividerItemDecoration.VERTICAL; public class MaterialStyleActivity extends AppCompatActivity implements AdapterView.OnItemClickListener { private enum Item { 内容不偏移("下拉的时候列表内容停留在原位不动"), 内容跟随偏移("下拉的时候列表内容跟随向下偏移"), 打开背景("下拉的时候绘制贝塞尔曲线背景"), 关闭背景("下拉的时候不绘制贝塞尔曲线背景"), 橙色主题("更改为橙色主题颜色"), 红色主题("更改为红色主题颜色"), 绿色主题("更改为绿色主题颜色"), 蓝色主题("更改为蓝色主题颜色"), ; public String name; Item(String name) { this.name = name; } } private Toolbar mToolbar; private RefreshLayout mRefreshLayout; private MaterialHeader mMaterialHeader; private static boolean isFirstEnter = true; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_style_material); mToolbar = (Toolbar)findViewById(R.id.toolbar); mToolbar.setNavigationOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { finish(); } }); mRefreshLayout = (RefreshLayout)findViewById(R.id.refreshLayout); if (isFirstEnter) { isFirstEnter = false; mRefreshLayout.autoRefresh();//第一次进入触发自动刷新,演示效果 } mMaterialHeader = (MaterialHeader)mRefreshLayout.getRefreshHeader(); View view = findViewById(R.id.recyclerView); if (view instanceof RecyclerView) { RecyclerView recyclerView = (RecyclerView) view; recyclerView.setLayoutManager(new LinearLayoutManager(this)); recyclerView.addItemDecoration(new DividerItemDecoration(this, VERTICAL)); recyclerView.setItemAnimator(new DefaultItemAnimator()); recyclerView.setAdapter(new BaseRecyclerAdapter(Arrays.asList(Item.values()), simple_list_item_2,this) { @Override protected void onBindViewHolder(SmartViewHolder holder, Item model, int position) { holder.text(android.R.id.text1, model.name()); holder.text(android.R.id.text2, model.name); holder.textColorId(android.R.id.text2, R.color.colorTextAssistant); } }); } } @Override public void onItemClick(AdapterView parent, View view, int position, long id) { switch (Item.values()[position]) { case 内容不偏移: mRefreshLayout.setEnableHeaderTranslationContent(false); break; case 内容跟随偏移: mRefreshLayout.setEnableHeaderTranslationContent(true); break; case 打开背景: mMaterialHeader.setShowBezierWave(true); break; case 关闭背景: mMaterialHeader.setShowBezierWave(false); break; case 蓝色主题: setThemeColor(R.color.colorPrimary, R.color.colorPrimaryDark); break; case 绿色主题: setThemeColor(android.R.color.holo_green_light, android.R.color.holo_green_dark); break; case 红色主题: setThemeColor(android.R.color.holo_red_light, android.R.color.holo_red_dark); break; case 橙色主题: setThemeColor(android.R.color.holo_orange_light, android.R.color.holo_orange_dark); break; } mRefreshLayout.autoRefresh(); } private void setThemeColor(int colorPrimary, int colorPrimaryDark) { mToolbar.setBackgroundResource(colorPrimary); mRefreshLayout.setPrimaryColorsId(colorPrimary, android.R.color.white); if (Build.VERSION.SDK_INT >= 21) { getWindow().setStatusBarColor(ContextCompat.getColor(this, colorPrimaryDark)); } } } ================================================ FILE: app/src/main/java/com/scwang/refreshlayout/activity/style/PhoenixStyleActivity.java ================================================ package com.scwang.refreshlayout.activity.style; import android.content.res.ColorStateList; import android.os.Build; import android.os.Bundle; import android.support.design.widget.AppBarLayout; import android.support.design.widget.CollapsingToolbarLayout; import android.support.design.widget.FloatingActionButton; import android.support.v4.content.ContextCompat; import android.support.v7.app.AppCompatActivity; import android.support.v7.widget.DefaultItemAnimator; import android.support.v7.widget.DividerItemDecoration; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; import android.support.v7.widget.Toolbar; import android.view.View; import android.widget.AdapterView; import com.scwang.refreshlayout.R; import com.scwang.refreshlayout.adapter.BaseRecyclerAdapter; import com.scwang.refreshlayout.adapter.SmartViewHolder; import com.scwang.smartrefresh.layout.api.RefreshLayout; import java.util.Arrays; import static android.R.layout.simple_list_item_2; import static android.support.v7.widget.DividerItemDecoration.VERTICAL; public class PhoenixStyleActivity extends AppCompatActivity implements AdapterView.OnItemClickListener { private enum Item { 折叠("折叠AppBarLayout,变成正常的列表页面"), 展开("展开AppBarLayout,变成可伸展头部的页面"), 橙色主题("更改为橙色主题颜色"), 红色主题("更改为红色主题颜色"), 绿色主题("更改为绿色主题颜色"), 蓝色主题("更改为蓝色主题颜色"), ; public String name; Item(String name) { this.name = name; } } private Toolbar mToolbar; private RecyclerView mRecyclerView; private AppBarLayout mAppBarLayout; private RefreshLayout mRefreshLayout; private FloatingActionButton mActionButton; private CollapsingToolbarLayout mToolbarLayout; private static boolean isFirstEnter = true; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_style_phoenix); mToolbar = (Toolbar)findViewById(R.id.toolbar); mToolbar.setNavigationOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { finish(); } }); mRefreshLayout = (RefreshLayout)findViewById(R.id.refreshLayout); if (isFirstEnter) { isFirstEnter = false; mRefreshLayout.autoRefresh();//第一次进入触发自动刷新,演示效果 } mAppBarLayout = (AppBarLayout) findViewById(R.id.app_bar); mToolbarLayout = (CollapsingToolbarLayout) findViewById(R.id.toolbar_layout); View view = findViewById(R.id.recyclerView); if (view instanceof RecyclerView) { RecyclerView recyclerView = (RecyclerView) view; recyclerView.setLayoutManager(new LinearLayoutManager(this)); recyclerView.addItemDecoration(new DividerItemDecoration(this, VERTICAL)); recyclerView.setItemAnimator(new DefaultItemAnimator()); recyclerView.setAdapter(new BaseRecyclerAdapter(Arrays.asList(Item.values()), simple_list_item_2,this) { @Override protected void onBindViewHolder(SmartViewHolder holder, Item model, int position) { holder.text(android.R.id.text1, model.name()); holder.text(android.R.id.text2, model.name); holder.textColorId(android.R.id.text2, R.color.colorTextAssistant); } }); mRecyclerView = recyclerView; } /** * 监听 AppBarLayout 的关闭和开启 给 FlyView(纸飞机) 和 ActionButton 设置关闭隐藏动画 */ mActionButton = (FloatingActionButton) findViewById(R.id.fab); mAppBarLayout.addOnOffsetChangedListener(new AppBarLayout.OnOffsetChangedListener() { boolean misAppbarExpand = true; View fab = findViewById(R.id.fab); @Override public void onOffsetChanged(AppBarLayout appBarLayout, int verticalOffset) { int scrollRange = appBarLayout.getTotalScrollRange(); float fraction = 1f * (scrollRange + verticalOffset) / scrollRange; if (fraction < 0.1 && misAppbarExpand) { misAppbarExpand = false; fab.animate().scaleX(0).scaleY(0); } if (fraction > 0.8 && !misAppbarExpand) { misAppbarExpand = true; fab.animate().scaleX(1).scaleY(1); } } }); } @Override public void onItemClick(AdapterView parent, View view, int position, long id) { switch (Item.values()[position]) { case 折叠: mAppBarLayout.setExpanded(false, true); mAppBarLayout.setEnabled(false); mRecyclerView.setNestedScrollingEnabled(false); break; case 展开: mAppBarLayout.setEnabled(true); mAppBarLayout.setExpanded(true, true); mRecyclerView.setNestedScrollingEnabled(true); break; case 蓝色主题: setThemeColor(R.color.colorPrimary, R.color.colorPrimaryDark); break; case 绿色主题: setThemeColor(android.R.color.holo_green_light, android.R.color.holo_green_dark); break; case 红色主题: setThemeColor(android.R.color.holo_red_light, android.R.color.holo_red_dark); break; case 橙色主题: setThemeColor(android.R.color.holo_orange_light, android.R.color.holo_orange_dark); break; } mRefreshLayout.autoRefresh(); } private void setThemeColor(int colorPrimary, int colorPrimaryDark) { mToolbar.setBackgroundResource(colorPrimary); mAppBarLayout.setBackgroundResource(colorPrimary); mToolbarLayout.setContentScrimResource(colorPrimary); mRefreshLayout.setPrimaryColorsId(colorPrimary, android.R.color.white); mActionButton.setBackgroundColor(ContextCompat.getColor(this, colorPrimaryDark)); mActionButton.setBackgroundTintList(ColorStateList.valueOf(ContextCompat.getColor(this, colorPrimaryDark))); if (Build.VERSION.SDK_INT >= 21) { getWindow().setStatusBarColor(ContextCompat.getColor(this, colorPrimaryDark)); } } } ================================================ FILE: app/src/main/java/com/scwang/refreshlayout/activity/style/StoreHouseStyleActivity.java ================================================ package com.scwang.refreshlayout.activity.style; import android.os.Build; import android.os.Bundle; import android.support.v4.content.ContextCompat; import android.support.v7.app.AppCompatActivity; import android.support.v7.widget.DefaultItemAnimator; import android.support.v7.widget.DividerItemDecoration; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; import android.support.v7.widget.Toolbar; import android.view.View; import android.widget.AdapterView; import com.scwang.refreshlayout.R; import com.scwang.refreshlayout.adapter.BaseRecyclerAdapter; import com.scwang.refreshlayout.adapter.SmartViewHolder; import com.scwang.smartrefresh.layout.api.RefreshLayout; import java.util.Arrays; import static android.R.layout.simple_list_item_2; import static android.support.v7.widget.DividerItemDecoration.VERTICAL; public class StoreHouseStyleActivity extends AppCompatActivity implements AdapterView.OnItemClickListener { private enum Item { 橙色主题("更改为橙色主题颜色"), 红色主题("更改为红色主题颜色"), 绿色主题("更改为绿色主题颜色"), 蓝色主题("更改为蓝色主题颜色"), ; public String name; Item(String name) { this.name = name; } } private Toolbar mToolbar; private RefreshLayout mRefreshLayout; private static boolean isFirstEnter = true; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_style_storehouse); mToolbar = (Toolbar)findViewById(R.id.toolbar); mToolbar.setNavigationOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { finish(); } }); mRefreshLayout = (RefreshLayout)findViewById(R.id.refreshLayout); if (isFirstEnter) { isFirstEnter = false; mRefreshLayout.autoRefresh();//第一次进入触发自动刷新,演示效果 } View view = findViewById(R.id.recyclerView); if (view instanceof RecyclerView) { RecyclerView recyclerView = (RecyclerView) view; recyclerView.addItemDecoration(new DividerItemDecoration(this, VERTICAL)); recyclerView.setLayoutManager(new LinearLayoutManager(this)); recyclerView.setItemAnimator(new DefaultItemAnimator()); recyclerView.setAdapter(new BaseRecyclerAdapter(Arrays.asList(Item.values()), simple_list_item_2,this) { @Override protected void onBindViewHolder(SmartViewHolder holder, Item model, int position) { holder.text(android.R.id.text1, model.name()); holder.text(android.R.id.text2, model.name); holder.textColorId(android.R.id.text2, R.color.colorTextAssistant); } }); } } @Override public void onItemClick(AdapterView parent, View view, int position, long id) { switch (Item.values()[position]) { case 蓝色主题: setThemeColor(R.color.colorPrimary, R.color.colorPrimaryDark); break; case 绿色主题: setThemeColor(android.R.color.holo_green_light, android.R.color.holo_green_dark); break; case 红色主题: setThemeColor(android.R.color.holo_red_light, android.R.color.holo_red_dark); break; case 橙色主题: setThemeColor(android.R.color.holo_orange_light, android.R.color.holo_orange_dark); break; } mRefreshLayout.autoRefresh(); } private void setThemeColor(int colorPrimary, int colorPrimaryDark) { mToolbar.setBackgroundResource(colorPrimary); mRefreshLayout.setPrimaryColorsId(colorPrimary, android.R.color.white); if (Build.VERSION.SDK_INT >= 21) { getWindow().setStatusBarColor(ContextCompat.getColor(this, colorPrimaryDark)); } } } ================================================ FILE: app/src/main/java/com/scwang/refreshlayout/activity/style/TaurusStyleActivity.java ================================================ package com.scwang.refreshlayout.activity.style; import android.content.res.ColorStateList; import android.os.Build; import android.os.Bundle; import android.support.design.widget.AppBarLayout; import android.support.design.widget.CollapsingToolbarLayout; import android.support.design.widget.FloatingActionButton; import android.support.v4.content.ContextCompat; import android.support.v7.app.AppCompatActivity; import android.support.v7.widget.DefaultItemAnimator; import android.support.v7.widget.DividerItemDecoration; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; import android.support.v7.widget.Toolbar; import android.view.View; import android.widget.AdapterView; import com.scwang.refreshlayout.R; import com.scwang.refreshlayout.adapter.BaseRecyclerAdapter; import com.scwang.refreshlayout.adapter.SmartViewHolder; import com.scwang.smartrefresh.layout.api.RefreshLayout; import java.util.Arrays; import static android.R.layout.simple_list_item_2; import static android.support.v7.widget.DividerItemDecoration.VERTICAL; public class TaurusStyleActivity extends AppCompatActivity implements AdapterView.OnItemClickListener { private enum Item { 折叠("折叠AppBarLayout,变成正常的列表页面"), 展开("展开AppBarLayout,变成可伸展头部的页面"), 橙色主题("更改为橙色主题颜色"), 红色主题("更改为红色主题颜色"), 绿色主题("更改为绿色主题颜色"), 蓝色主题("更改为蓝色主题颜色"), ; public String name; Item(String name) { this.name = name; } } private Toolbar mToolbar; private RecyclerView mRecyclerView; private AppBarLayout mAppBarLayout; private RefreshLayout mRefreshLayout; private FloatingActionButton mActionButton; private CollapsingToolbarLayout mToolbarLayout; private static boolean isFirstEnter = true; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_style_taurus); mToolbar = (Toolbar)findViewById(R.id.toolbar); mToolbar.setNavigationOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { finish(); } }); mRefreshLayout = (RefreshLayout)findViewById(R.id.refreshLayout); if (isFirstEnter) { isFirstEnter = false; mRefreshLayout.autoRefresh();//第一次进入触发自动刷新,演示效果 } mAppBarLayout = (AppBarLayout) findViewById(R.id.app_bar); mToolbarLayout = (CollapsingToolbarLayout) findViewById(R.id.toolbar_layout); View view = findViewById(R.id.recyclerView); if (view instanceof RecyclerView) { RecyclerView recyclerView = (RecyclerView) view; recyclerView.setLayoutManager(new LinearLayoutManager(this)); recyclerView.addItemDecoration(new DividerItemDecoration(this, VERTICAL)); recyclerView.setItemAnimator(new DefaultItemAnimator()); recyclerView.setAdapter(new BaseRecyclerAdapter(Arrays.asList(Item.values()), simple_list_item_2,this) { @Override protected void onBindViewHolder(SmartViewHolder holder, Item model, int position) { holder.text(android.R.id.text1, model.name()); holder.text(android.R.id.text2, model.name); holder.textColorId(android.R.id.text2, R.color.colorTextAssistant); } }); mRecyclerView = recyclerView; } /** * 监听 AppBarLayout 的关闭和开启 给 FlyView(纸飞机) 和 ActionButton 设置关闭隐藏动画 */ mActionButton = (FloatingActionButton) findViewById(R.id.fab); mAppBarLayout.addOnOffsetChangedListener(new AppBarLayout.OnOffsetChangedListener() { boolean misAppbarExpand = true; View fab = findViewById(R.id.fab); @Override public void onOffsetChanged(AppBarLayout appBarLayout, int verticalOffset) { int scrollRange = appBarLayout.getTotalScrollRange(); float fraction = 1f * (scrollRange + verticalOffset) / scrollRange; if (fraction < 0.1 && misAppbarExpand) { misAppbarExpand = false; fab.animate().scaleX(0).scaleY(0); } if (fraction > 0.8 && !misAppbarExpand) { misAppbarExpand = true; fab.animate().scaleX(1).scaleY(1); } } }); } @Override public void onItemClick(AdapterView parent, View view, int position, long id) { switch (Item.values()[position]) { case 折叠: mAppBarLayout.setExpanded(false, true); mAppBarLayout.setEnabled(false); mRecyclerView.setNestedScrollingEnabled(false); break; case 展开: mAppBarLayout.setEnabled(true); mAppBarLayout.setExpanded(true, true); mRecyclerView.setNestedScrollingEnabled(true); break; case 蓝色主题: setThemeColor(R.color.colorPrimary, R.color.colorPrimaryDark); break; case 绿色主题: setThemeColor(android.R.color.holo_green_light, android.R.color.holo_green_dark); break; case 红色主题: setThemeColor(android.R.color.holo_red_light, android.R.color.holo_red_dark); break; case 橙色主题: setThemeColor(android.R.color.holo_orange_light, android.R.color.holo_orange_dark); break; } mRefreshLayout.autoRefresh(); } private void setThemeColor(int colorPrimary, int colorPrimaryDark) { mToolbar.setBackgroundResource(colorPrimary); mAppBarLayout.setBackgroundResource(colorPrimary); mToolbarLayout.setContentScrimResource(colorPrimary); mRefreshLayout.setPrimaryColorsId(colorPrimary, android.R.color.white); mActionButton.setBackgroundColor(ContextCompat.getColor(this, colorPrimaryDark)); mActionButton.setBackgroundTintList(ColorStateList.valueOf(ContextCompat.getColor(this, colorPrimaryDark))); if (Build.VERSION.SDK_INT >= 21) { getWindow().setStatusBarColor(ContextCompat.getColor(this, colorPrimaryDark)); } } } ================================================ FILE: app/src/main/java/com/scwang/refreshlayout/activity/style/WaterDropStyleActivity.java ================================================ package com.scwang.refreshlayout.activity.style; import android.os.Build; import android.os.Bundle; import android.support.v4.content.ContextCompat; import android.support.v7.app.AppCompatActivity; import android.support.v7.widget.DefaultItemAnimator; import android.support.v7.widget.DividerItemDecoration; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; import android.support.v7.widget.Toolbar; import android.view.View; import android.widget.AdapterView; import com.scwang.refreshlayout.R; import com.scwang.refreshlayout.adapter.BaseRecyclerAdapter; import com.scwang.refreshlayout.adapter.SmartViewHolder; import com.scwang.smartrefresh.layout.api.RefreshLayout; import java.util.Arrays; import static android.R.layout.simple_list_item_2; import static android.support.v7.widget.DividerItemDecoration.VERTICAL; public class WaterDropStyleActivity extends AppCompatActivity implements AdapterView.OnItemClickListener { private enum Item { 内容不偏移("下拉的时候列表内容停留在原位不动"), 内容跟随偏移("下拉的时候列表内容跟随向下偏移"), 默认主题("更改为默认主题颜色"), 橙色主题("更改为橙色主题颜色"), 红色主题("更改为红色主题颜色"), 绿色主题("更改为绿色主题颜色"), 蓝色主题("更改为蓝色主题颜色"), ; public String name; Item(String name) { this.name = name; } } private Toolbar mToolbar; private RefreshLayout mRefreshLayout; private static boolean isFirstEnter = true; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_style_water_drop); mToolbar = (Toolbar)findViewById(R.id.toolbar); mToolbar.setNavigationOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { finish(); } }); mRefreshLayout = (RefreshLayout)findViewById(R.id.refreshLayout); if (isFirstEnter) { isFirstEnter = false; mRefreshLayout.autoRefresh();//第一次进入触发自动刷新,演示效果 } View view = findViewById(R.id.recyclerView); if (view instanceof RecyclerView) { RecyclerView recyclerView = (RecyclerView) view; recyclerView.setLayoutManager(new LinearLayoutManager(this)); recyclerView.addItemDecoration(new DividerItemDecoration(this, VERTICAL)); recyclerView.setItemAnimator(new DefaultItemAnimator()); recyclerView.setAdapter(new BaseRecyclerAdapter(Arrays.asList(Item.values()), simple_list_item_2,this) { @Override protected void onBindViewHolder(SmartViewHolder holder, Item model, int position) { holder.text(android.R.id.text1, model.name()); holder.text(android.R.id.text2, model.name); holder.textColorId(android.R.id.text2, R.color.colorTextAssistant); } }); } } @Override public void onItemClick(AdapterView parent, View view, int position, long id) { switch (Item.values()[position]) { case 内容不偏移: mRefreshLayout.setEnableHeaderTranslationContent(false); break; case 内容跟随偏移: mRefreshLayout.setEnableHeaderTranslationContent(true); break; case 默认主题: setThemeColor(R.color.colorPrimary, R.color.colorPrimaryDark); mRefreshLayout.setPrimaryColorsId(android.R.color.darker_gray); break; case 蓝色主题: setThemeColor(R.color.colorPrimary, R.color.colorPrimaryDark); break; case 绿色主题: setThemeColor(android.R.color.holo_green_light, android.R.color.holo_green_dark); break; case 红色主题: setThemeColor(android.R.color.holo_red_light, android.R.color.holo_red_dark); break; case 橙色主题: setThemeColor(android.R.color.holo_orange_light, android.R.color.holo_orange_dark); break; } mRefreshLayout.autoRefresh(); } private void setThemeColor(int colorPrimary, int colorPrimaryDark) { mToolbar.setBackgroundResource(colorPrimary); mRefreshLayout.setPrimaryColorsId(colorPrimary, android.R.color.white); if (Build.VERSION.SDK_INT >= 21) { getWindow().setStatusBarColor(ContextCompat.getColor(this, colorPrimaryDark)); } } } ================================================ FILE: app/src/main/java/com/scwang/refreshlayout/activity/style/WaveSwipStyleActivity.java ================================================ package com.scwang.refreshlayout.activity.style; import android.os.Build; import android.os.Bundle; import android.support.v4.content.ContextCompat; import android.support.v7.app.AppCompatActivity; import android.support.v7.widget.DefaultItemAnimator; import android.support.v7.widget.DividerItemDecoration; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; import android.support.v7.widget.Toolbar; import android.view.View; import android.widget.AdapterView; import com.scwang.refreshlayout.R; import com.scwang.refreshlayout.adapter.BaseRecyclerAdapter; import com.scwang.refreshlayout.adapter.SmartViewHolder; import com.scwang.smartrefresh.layout.api.RefreshLayout; import java.util.Arrays; import static android.R.layout.simple_list_item_2; import static android.support.v7.widget.DividerItemDecoration.VERTICAL; public class WaveSwipStyleActivity extends AppCompatActivity implements AdapterView.OnItemClickListener { private enum Item { 内容不偏移("下拉的时候列表内容停留在原位不动"), 内容跟随偏移("下拉的时候列表内容跟随向下偏移"), 橙色主题("更改为橙色主题颜色"), 红色主题("更改为红色主题颜色"), 绿色主题("更改为绿色主题颜色"), 蓝色主题("更改为蓝色主题颜色"), ; public String name; Item(String name) { this.name = name; } } private Toolbar mToolbar; private RefreshLayout mRefreshLayout; private static boolean isFirstEnter = true; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_style_wave_swip); mToolbar = (Toolbar)findViewById(R.id.toolbar); mToolbar.setNavigationOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { finish(); } }); mRefreshLayout = (RefreshLayout)findViewById(R.id.refreshLayout); if (isFirstEnter) { isFirstEnter = false; mRefreshLayout.autoRefresh();//第一次进入触发自动刷新,演示效果 } View view = findViewById(R.id.recyclerView); if (view instanceof RecyclerView) { RecyclerView recyclerView = (RecyclerView) view; recyclerView.setLayoutManager(new LinearLayoutManager(this)); recyclerView.addItemDecoration(new DividerItemDecoration(this, VERTICAL)); recyclerView.setItemAnimator(new DefaultItemAnimator()); recyclerView.setAdapter(new BaseRecyclerAdapter(Arrays.asList(Item.values()), simple_list_item_2,this) { @Override protected void onBindViewHolder(SmartViewHolder holder, Item model, int position) { holder.text(android.R.id.text1, model.name()); holder.text(android.R.id.text2, model.name); holder.textColorId(android.R.id.text2, R.color.colorTextAssistant); } }); } } @Override public void onItemClick(AdapterView parent, View view, int position, long id) { switch (Item.values()[position]) { case 内容不偏移: mRefreshLayout.setEnableHeaderTranslationContent(false); break; case 内容跟随偏移: mRefreshLayout.setEnableHeaderTranslationContent(true); break; case 蓝色主题: setThemeColor(R.color.colorPrimary, R.color.colorPrimaryDark); break; case 绿色主题: setThemeColor(android.R.color.holo_green_light, android.R.color.holo_green_dark); break; case 红色主题: setThemeColor(android.R.color.holo_red_light, android.R.color.holo_red_dark); break; case 橙色主题: setThemeColor(android.R.color.holo_orange_light, android.R.color.holo_orange_dark); break; } mRefreshLayout.autoRefresh(); } private void setThemeColor(int colorPrimary, int colorPrimaryDark) { mToolbar.setBackgroundResource(colorPrimary); mRefreshLayout.setPrimaryColorsId(colorPrimary, android.R.color.white); if (Build.VERSION.SDK_INT >= 21) { getWindow().setStatusBarColor(ContextCompat.getColor(this, colorPrimaryDark)); } } } ================================================ FILE: app/src/main/java/com/scwang/refreshlayout/activity/using/AssignCodeUsingActivity.java ================================================ package com.scwang.refreshlayout.activity.using; import android.os.Bundle; import android.support.v7.app.AppCompatActivity; import android.support.v7.widget.Toolbar; import android.view.View; import com.scwang.refreshlayout.R; import com.scwang.smartrefresh.header.MaterialHeader; import com.scwang.smartrefresh.layout.api.RefreshLayout; import com.scwang.smartrefresh.layout.constant.RefreshState; import com.scwang.smartrefresh.layout.constant.SpinnerStyle; import com.scwang.smartrefresh.layout.footer.BallPulseFooter; import com.scwang.smartrefresh.layout.listener.SimpleMultiPurposeListener; /** * 在Java代码中指定Header和Footer */ public class AssignCodeUsingActivity extends AppCompatActivity { private static boolean isFirstEnter = true; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_using_assign_code); final Toolbar toolbar = (Toolbar)findViewById(R.id.toolbar); toolbar.setNavigationOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { finish(); } }); final RefreshLayout refreshLayout = (RefreshLayout) findViewById(R.id.refreshLayout); //设置 Header 为 Material风格 refreshLayout.setRefreshHeader(new MaterialHeader(this).setShowBezierWave(true)); //设置 Footer 为 球脉冲 refreshLayout.setRefreshFooter(new BallPulseFooter(this).setSpinnerStyle(SpinnerStyle.Scale)); /** * 以下代码仅仅为了演示效果而已,不是必须的 */ //设置主题颜色 refreshLayout.setPrimaryColorsId(R.color.colorPrimary, android.R.color.white); if (isFirstEnter) { isFirstEnter = false; //触发上啦加载 refreshLayout.autoLoadmore(); //通过多功能监听接口实现 在第一次加载完成之后 自动刷新 refreshLayout.setOnMultiPurposeListener(new SimpleMultiPurposeListener(){ @Override public void onStateChanged(RefreshLayout refreshLayout, RefreshState oldState, RefreshState newState) { if (oldState == RefreshState.LoadFinish && newState == RefreshState.None) { refreshLayout.autoRefresh(); refreshLayout.setOnMultiPurposeListener(null); } } }); } } } ================================================ FILE: app/src/main/java/com/scwang/refreshlayout/activity/using/AssignDefaultUsingActivity.java ================================================ package com.scwang.refreshlayout.activity.using; import android.content.Context; import android.os.Bundle; import android.support.annotation.NonNull; import android.support.v4.content.ContextCompat; import android.support.v7.app.AppCompatActivity; import android.support.v7.widget.Toolbar; import android.view.View; import com.scwang.refreshlayout.R; import com.scwang.smartrefresh.layout.SmartRefreshLayout; import com.scwang.smartrefresh.layout.api.DefaultRefreshFooterCreater; import com.scwang.smartrefresh.layout.api.DefaultRefreshHeaderCreater; import com.scwang.smartrefresh.layout.api.RefreshFooter; import com.scwang.smartrefresh.layout.api.RefreshHeader; import com.scwang.smartrefresh.layout.api.RefreshLayout; import com.scwang.smartrefresh.layout.constant.RefreshState; import com.scwang.smartrefresh.layout.constant.SpinnerStyle; import com.scwang.smartrefresh.layout.footer.ClassicsFooter; import com.scwang.smartrefresh.layout.header.ClassicsHeader; import com.scwang.smartrefresh.layout.listener.SimpleMultiPurposeListener; /** * 全局指定默认的Header和Footer */ public class AssignDefaultUsingActivity extends AppCompatActivity { private static boolean isFirstEnter = true; /* * 关键代码,需要在布局生成之前设置,建议代码放在 Application 中 */ static { //设置全局的Header构建器 SmartRefreshLayout.setDefaultRefreshHeaderCreater(new DefaultRefreshHeaderCreater() { @NonNull @Override public RefreshHeader createRefreshHeader(Context context, RefreshLayout layout) { ClassicsHeader header = new ClassicsHeader(context).setSpinnerStyle(SpinnerStyle.FixedBehind); header.setPrimaryColors(ContextCompat.getColor(context, R.color.colorPrimary), ContextCompat.getColor(context, android.R.color.white)); return header;//指定为经典Header,默认是 贝塞尔雷达Header } }); //设置全局的Footer构建器 SmartRefreshLayout.setDefaultRefreshFooterCreater(new DefaultRefreshFooterCreater() { @NonNull @Override public RefreshFooter createRefreshFooter(Context context, RefreshLayout layout) { layout.setEnableLoadmoreWhenContentNotFull(true); ClassicsFooter footer = new ClassicsFooter(context); footer.setBackgroundResource(android.R.color.white); footer.setSpinnerStyle(SpinnerStyle.Scale);//设置为拉伸模式 return footer;//指定为经典Footer,默认是 BallPulseFooter } }); } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_using_assign_default); final Toolbar toolbar = (Toolbar)findViewById(R.id.toolbar); toolbar.setNavigationOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { finish(); } }); /** * 以下代码仅仅为了演示效果而已,不是必须的 * 关键代码在构造函数中 */ final RefreshLayout refreshLayout = (RefreshLayout) findViewById(R.id.refreshLayout); if (isFirstEnter) { isFirstEnter = false; //触发上啦加载 refreshLayout.autoLoadmore(); //通过多功能监听接口实现 在第一次加载完成之后 自动刷新 refreshLayout.setOnMultiPurposeListener(new SimpleMultiPurposeListener(){ @Override public void onStateChanged(RefreshLayout refreshLayout, RefreshState oldState, RefreshState newState) { if (oldState == RefreshState.LoadFinish && newState == RefreshState.None) { refreshLayout.autoRefresh(); refreshLayout.setOnMultiPurposeListener(null); } } }); } } } ================================================ FILE: app/src/main/java/com/scwang/refreshlayout/activity/using/AssignXmlUsingActivity.java ================================================ package com.scwang.refreshlayout.activity.using; import android.os.Bundle; import android.support.v7.app.AppCompatActivity; import android.support.v7.widget.Toolbar; import android.view.View; import com.scwang.refreshlayout.R; import com.scwang.smartrefresh.layout.api.RefreshLayout; import com.scwang.smartrefresh.layout.constant.RefreshState; import com.scwang.smartrefresh.layout.listener.SimpleMultiPurposeListener; /** * 在XML中指定Header和Footer */ public class AssignXmlUsingActivity extends AppCompatActivity { private static boolean isFirstEnter = true; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_using_assign_xml); final Toolbar toolbar = (Toolbar)findViewById(R.id.toolbar); toolbar.setNavigationOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { finish(); } }); /** * 以下代码仅仅为了演示效果而已,不是必须的 * 关键代码在 activity_using_assign_xml 中 */ final RefreshLayout refreshLayout = (RefreshLayout) findViewById(R.id.refreshLayout); if (isFirstEnter) { isFirstEnter = false; //触发上啦加载 refreshLayout.autoLoadmore(); //通过多功能监听接口实现 在第一次加载完成之后 自动刷新 refreshLayout.setOnMultiPurposeListener(new SimpleMultiPurposeListener(){ @Override public void onStateChanged(RefreshLayout refreshLayout, RefreshState oldState, RefreshState newState) { if (oldState == RefreshState.LoadFinish && newState == RefreshState.None) { refreshLayout.autoRefresh(); refreshLayout.setOnMultiPurposeListener(null); } } }); } } } ================================================ FILE: app/src/main/java/com/scwang/refreshlayout/activity/using/BasicUsingActivity.java ================================================ package com.scwang.refreshlayout.activity.using; import android.os.Bundle; import android.support.v7.app.AppCompatActivity; import android.support.v7.widget.Toolbar; import android.view.View; import android.widget.ListView; import android.widget.Toast; import com.scwang.refreshlayout.R; import com.scwang.refreshlayout.adapter.BaseRecyclerAdapter; import com.scwang.refreshlayout.adapter.SmartViewHolder; import com.scwang.smartrefresh.layout.api.RefreshLayout; import com.scwang.smartrefresh.layout.listener.OnLoadmoreListener; import com.scwang.smartrefresh.layout.listener.OnRefreshListener; import java.util.Arrays; import java.util.Collection; import java.util.Locale; import static android.R.layout.simple_list_item_2; /** * 基本的功能使用 */ public class BasicUsingActivity extends AppCompatActivity { private BaseRecyclerAdapter mAdapter; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_using_basic); final Toolbar toolbar = (Toolbar)findViewById(R.id.toolbar); toolbar.setNavigationOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { finish(); } }); ListView listView = (ListView) findViewById(R.id.listview); listView.setAdapter(mAdapter = new BaseRecyclerAdapter(simple_list_item_2) { @Override protected void onBindViewHolder(SmartViewHolder holder, Void model, int position) { holder.text(android.R.id.text1, String.format(Locale.CHINA, "第%02d条数据", position)); holder.text(android.R.id.text2, String.format(Locale.CHINA, "这是测试的第%02d条数据", position)); holder.textColorId(android.R.id.text2, R.color.colorTextAssistant); } }); final RefreshLayout refreshLayout = (RefreshLayout) findViewById(R.id.refreshLayout); refreshLayout.setEnableAutoLoadmore(true);//开启自动加载功能(非必须) refreshLayout.setOnRefreshListener(new OnRefreshListener() { @Override public void onRefresh(final RefreshLayout refreshlayout) { refreshlayout.getLayout().postDelayed(new Runnable() { @Override public void run() { mAdapter.refresh(initData()); refreshlayout.finishRefresh(); refreshlayout.setLoadmoreFinished(false); } }, 2000); } }); refreshLayout.setOnLoadmoreListener(new OnLoadmoreListener() { @Override public void onLoadmore(final RefreshLayout refreshlayout) { refreshlayout.getLayout().postDelayed(new Runnable() { @Override public void run() { mAdapter.loadmore(initData()); refreshlayout.finishLoadmore(); if (mAdapter.getItemCount() > 60) { Toast.makeText(getApplication(), "数据全部加载完毕", Toast.LENGTH_SHORT).show(); refreshlayout.setLoadmoreFinished(true);//将不会再次触发加载更多事件 } } }, 2000); } }); //触发自动刷新 refreshLayout.autoRefresh(); } private Collection initData() { return Arrays.asList(null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null); } } ================================================ FILE: app/src/main/java/com/scwang/refreshlayout/activity/using/CustomUsingActivity.java ================================================ package com.scwang.refreshlayout.activity.using; import android.content.Context; import android.os.Bundle; import android.support.annotation.NonNull; import android.support.v7.app.AppCompatActivity; import android.support.v7.widget.Toolbar; import android.util.AttributeSet; import android.view.Gravity; import android.view.View; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.ListView; import android.widget.TextView; import com.scwang.refreshlayout.R; import com.scwang.refreshlayout.adapter.BaseRecyclerAdapter; import com.scwang.refreshlayout.adapter.SmartViewHolder; import com.scwang.smartrefresh.layout.api.RefreshHeader; import com.scwang.smartrefresh.layout.api.RefreshKernel; import com.scwang.smartrefresh.layout.api.RefreshLayout; import com.scwang.smartrefresh.layout.constant.RefreshState; import com.scwang.smartrefresh.layout.constant.SpinnerStyle; import com.scwang.smartrefresh.layout.internal.ProgressDrawable; import com.scwang.smartrefresh.layout.internal.pathview.PathsView; import com.scwang.smartrefresh.layout.listener.OnRefreshListener; import com.scwang.smartrefresh.layout.util.DensityUtil; import java.util.Arrays; import java.util.Collection; import java.util.Locale; import static android.R.layout.simple_list_item_2; /** * 自定义Header功能使用 */ public class CustomUsingActivity extends AppCompatActivity { private BaseRecyclerAdapter mAdapter; private static boolean isFirstEnter = true; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_using_custom); final Toolbar toolbar = (Toolbar)findViewById(R.id.toolbar); toolbar.setNavigationOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { finish(); } }); ListView listView = (ListView) findViewById(R.id.listview); listView.setAdapter(mAdapter = new BaseRecyclerAdapter(simple_list_item_2) { @Override protected void onBindViewHolder(SmartViewHolder holder, Void model, int position) { holder.text(android.R.id.text1, String.format(Locale.CHINA, "第%02d条数据", position)); holder.text(android.R.id.text2, String.format(Locale.CHINA, "这是测试的第%02d条数据", position)); holder.textColorId(android.R.id.text2, R.color.colorTextAssistant); } }); final RefreshLayout refreshLayout = (RefreshLayout) findViewById(R.id.refreshLayout); refreshLayout.setOnRefreshListener(new OnRefreshListener() { @Override public void onRefresh(final RefreshLayout refreshlayout) { refreshlayout.getLayout().postDelayed(new Runnable() { @Override public void run() { mAdapter.refresh(initData()); refreshlayout.finishRefresh(); } }, 2000); } }); refreshLayout.setRefreshHeader(new ClassicsHeader(this)); refreshLayout.setHeaderHeight(60); //触发自动刷新 if (isFirstEnter) { isFirstEnter = false; refreshLayout.autoRefresh(); } else { mAdapter.refresh(initData()); } } private Collection initData() { return Arrays.asList(null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null); } public static class ClassicsHeader extends LinearLayout implements RefreshHeader { private TextView mHeaderText;//标题文本 private PathsView mArrowView;//下拉箭头 private ImageView mProgressView;//刷新动画视图 private ProgressDrawable mProgressDrawable;//刷新动画 public ClassicsHeader(Context context) { super(context); initView(context); } public ClassicsHeader(Context context, AttributeSet attrs) { super(context, attrs); this.initView(context); } public ClassicsHeader(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); this.initView(context); } private void initView(Context context) { setGravity(Gravity.CENTER); mHeaderText = new TextView(context); mProgressDrawable = new ProgressDrawable(); mArrowView = new PathsView(context); mProgressView = new ImageView(context); mProgressView.setImageDrawable(mProgressDrawable); mArrowView.parserPaths("M20,12l-1.41,-1.41L13,16.17V4h-2v12.17l-5.58,-5.59L4,12l8,8 8,-8z"); addView(mProgressView, DensityUtil.dp2px(20), DensityUtil.dp2px(20)); addView(mArrowView, DensityUtil.dp2px(20), DensityUtil.dp2px(20)); addView(new View(context), DensityUtil.dp2px(20), DensityUtil.dp2px(20)); addView(mHeaderText, LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT); setMinimumHeight(DensityUtil.dp2px(60)); } @NonNull public View getView() { return this;//真实的视图就是自己,不能返回null } @Override public SpinnerStyle getSpinnerStyle() { return SpinnerStyle.Translate;//指定为平移,不能null } @Override public void onStartAnimator(RefreshLayout layout, int headHeight, int extendHeight) { mProgressDrawable.start();//开始动画 } @Override public int onFinish(RefreshLayout layout, boolean success) { mProgressDrawable.stop();//停止动画 if (success){ mHeaderText.setText("刷新完成"); } else { mHeaderText.setText("刷新失败"); } return 500;//延迟500毫秒之后再弹回 } @Override public void onStateChanged(RefreshLayout refreshLayout, RefreshState oldState, RefreshState newState) { switch (newState) { case None: case PullDownToRefresh: mHeaderText.setText("下拉开始刷新"); mArrowView.setVisibility(VISIBLE);//显示下拉箭头 mProgressView.setVisibility(GONE);//隐藏动画 mArrowView.animate().rotation(0);//还原箭头方向 break; case Refreshing: mHeaderText.setText("正在刷新"); mProgressView.setVisibility(VISIBLE);//显示加载动画 mArrowView.setVisibility(GONE);//隐藏箭头 break; case ReleaseToRefresh: mHeaderText.setText("释放立即刷新"); mArrowView.animate().rotation(180);//显示箭头改为朝上 break; } } @Override public boolean isSupportHorizontalDrag() { return false; } @Override public void onInitialized(RefreshKernel kernel, int height, int extendHeight) { } @Override public void onHorizontalDrag(float percentX, int offsetX, int offsetMax) { } @Override public void onPullingDown(float percent, int offset, int headHeight, int extendHeight) { } @Override public void onReleasing(float percent, int offset, int headHeight, int extendHeight) { } @Override public void setPrimaryColors(int... colors){ } } } ================================================ FILE: app/src/main/java/com/scwang/refreshlayout/activity/using/ListenerUsingActivity.java ================================================ package com.scwang.refreshlayout.activity.using; import android.os.Bundle; import android.support.v7.app.AppCompatActivity; import android.support.v7.widget.Toolbar; import android.text.method.ScrollingMovementMethod; import android.view.View; import android.widget.TextView; import com.scwang.refreshlayout.R; import com.scwang.smartrefresh.layout.api.RefreshFooter; import com.scwang.smartrefresh.layout.api.RefreshHeader; import com.scwang.smartrefresh.layout.api.RefreshLayout; import com.scwang.smartrefresh.layout.constant.RefreshState; import com.scwang.smartrefresh.layout.listener.SimpleMultiPurposeListener; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.Date; import java.util.Locale; /** * 多功能监听器的使用 */ public class ListenerUsingActivity extends AppCompatActivity { private static DateFormat FORMAT = new SimpleDateFormat("HH:mm sss", Locale.CHINA); private TextView mTvContent; private String mHeaderPulling; private String mHeaderReleasing; private String mFooterPulling; private String mFooterReleasing; private String mFooterStartAnimator; private String mHeaderStartAnimator; private String mFooterFinish; private String mHeaderFinish; private String mRefresh; private String mLoadmore; private String mStateChanged; private static boolean isFirstEnter = true; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_using_listener); final Toolbar toolbar = (Toolbar)findViewById(R.id.toolbar); toolbar.setNavigationOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { finish(); } }); mTvContent = (TextView) findViewById(R.id.content); mTvContent.setMovementMethod(ScrollingMovementMethod.getInstance()); final RefreshLayout refreshLayout = (RefreshLayout) findViewById(R.id.refreshLayout); refreshLayout.setOnMultiPurposeListener(new SimpleMultiPurposeListener() { @Override public void onHeaderPulling(RefreshHeader header, float percent, int offset, int headerHeight, int extendHeight) { mHeaderPulling = String.format(Locale.CHINA, "%s\npercent=%.02f offset=%03d\nheight=%03d extend=%03d", FORMAT.format(new Date()), percent,offset,headerHeight,extendHeight); updateContent(); } @Override public void onHeaderReleasing(RefreshHeader header, float percent, int offset, int headerHeight, int extendHeight) { mHeaderReleasing = String.format(Locale.CHINA, "%s\npercent=%.02f offset=%03d\nheight=%03d extend=%03d", FORMAT.format(new Date()), percent,offset,headerHeight,extendHeight); updateContent(); } @Override public void onHeaderStartAnimator(RefreshHeader header, int headerHeight, int extendHeight) { mHeaderStartAnimator = String.format(Locale.CHINA, "%s\nheight=%03d extend=%03d", FORMAT.format(new Date()), headerHeight,extendHeight); updateContent(); } @Override public void onHeaderFinish(RefreshHeader header, boolean success) { mHeaderFinish = String.format(Locale.CHINA, "%s - " + success,FORMAT.format(new Date())); updateContent(); } @Override public void onFooterPulling(RefreshFooter footer, float percent, int offset, int footerHeight, int extendHeight) { mFooterPulling = String.format(Locale.CHINA, "%s\npercent=%.02f\noffset=%03d height=%03d\nextend=%03d", FORMAT.format(new Date()), percent,offset,footerHeight,extendHeight); updateContent(); } @Override public void onFooterReleasing(RefreshFooter footer, float percent, int offset, int footerHeight, int extendHeight) { mFooterReleasing = String.format(Locale.CHINA, "%s\npercent=%.02f\noffset=%03d height=%03d\nextend=%03d", FORMAT.format(new Date()), percent,offset,footerHeight,extendHeight); updateContent(); } @Override public void onFooterStartAnimator(RefreshFooter footer, int footerHeight, int extendHeight) { mFooterStartAnimator = String.format(Locale.CHINA, "%s\nheight=%03d extend=%03d", FORMAT.format(new Date()), footerHeight,extendHeight); updateContent(); } @Override public void onFooterFinish(RefreshFooter footer, boolean success) { mFooterFinish = String.format(Locale.CHINA, "%s - " + success,FORMAT.format(new Date())); updateContent(); } @Override public void onRefresh(RefreshLayout refreshlayout) { mRefresh = String.format(Locale.CHINA, "%s",FORMAT.format(new Date())); updateContent(); } @Override public void onLoadmore(RefreshLayout refreshlayout) { mLoadmore = String.format(Locale.CHINA, "%s",FORMAT.format(new Date())); updateContent(); } @Override public void onStateChanged(RefreshLayout refreshLayout, RefreshState oldState, RefreshState newState) { mStateChanged = String.format(Locale.CHINA, "%s\nnew=%s\nold=%s", FORMAT.format(new Date()), newState.name(), oldState.name()); updateContent(); } }); if (isFirstEnter) { isFirstEnter = false; //触发自动刷新 refreshLayout.autoRefresh(); } else { updateContent(); } } private void updateContent() { mTvContent.setText(String.format(Locale.CHINA, "onStateChanged:%s\n\n" + "onHeaderPulling:%s\n\n" + "onHeaderReleasing:%s\n\n" + "onHeaderStartAnimator:%s\n\n" + "onHeaderFinish:%s\n\n" + "onFooterPulling:%s\n\n" + "onFooterReleasing:%s\n\n" + "onFooterStartAnimator:%s\n\n" + "onFooterFinish:%s\n\n" + "onRefresh:%s\n\n" + "onLoadmore:%s\n\n", mStateChanged, mHeaderPulling, mHeaderReleasing, mHeaderStartAnimator, mHeaderFinish, mFooterPulling, mFooterReleasing, mFooterStartAnimator, mFooterFinish, mRefresh, mLoadmore)); } } ================================================ FILE: app/src/main/java/com/scwang/refreshlayout/activity/using/NestLayoutUsingActivity.java ================================================ package com.scwang.refreshlayout.activity.using; import android.os.Bundle; import android.support.v7.app.AppCompatActivity; import android.support.v7.widget.DefaultItemAnimator; import android.support.v7.widget.DividerItemDecoration; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; import android.support.v7.widget.Toolbar; import android.view.View; import android.widget.AdapterView; import android.widget.TextView; import com.scwang.refreshlayout.R; import com.scwang.refreshlayout.adapter.BaseRecyclerAdapter; import com.scwang.refreshlayout.adapter.SmartViewHolder; import com.scwang.smartrefresh.layout.api.RefreshLayout; import java.util.Arrays; public class NestLayoutUsingActivity extends AppCompatActivity implements AdapterView.OnItemClickListener { private static String[] provinces = new String[]{ "北京","天津","上海","重庆", "黑龙江","吉林","辽宁","河北","河南","山东","江苏","山西","陕西","甘肃","四川","青海","湖南","湖北","江西","安徽","浙江","福建","广东","广西","贵州","云南","海南", "内蒙古","新疆维吾尔族自治区","宁夏回族自治区","西藏","宁夏回族自治区", "香港","澳门" }; private static boolean isFirstEnter = true; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_using_region); final Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); if (toolbar != null) { toolbar.setNavigationOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { finish(); } }); } View view = findViewById(R.id.recyclerView); if (view instanceof RecyclerView) { RecyclerView recyclerView = (RecyclerView) view; recyclerView.setLayoutManager(new LinearLayoutManager(this)); recyclerView.setItemAnimator(new DefaultItemAnimator()); recyclerView.addItemDecoration(new DividerItemDecoration(this,DividerItemDecoration.VERTICAL)); recyclerView.setAdapter(new BaseRecyclerAdapter(Arrays.asList(provinces),android.R.layout.simple_list_item_1) { @Override protected void onBindViewHolder(SmartViewHolder holder, String model, int position) { holder.text(android.R.id.text1, model); } }.setOnItemClickListener(this)); } /** * 以下代码仅仅为了演示效果而已,不是必须的 * 关键代码在 activity_using_assign_xml 中 */ final RefreshLayout refreshLayout = (RefreshLayout) findViewById(R.id.refreshLayout); if (isFirstEnter && refreshLayout != null) { isFirstEnter = false; //触发上啦加载 refreshLayout.autoRefresh(); } } @Override public void onItemClick(AdapterView parent, View view, int position, long id) { String province = provinces[position]; view = findViewById(R.id.region); if (view instanceof TextView) { ((TextView) view).setText(province); } } } ================================================ FILE: app/src/main/java/com/scwang/refreshlayout/activity/using/OverScrollUsingActivity.java ================================================ package com.scwang.refreshlayout.activity.using; import android.os.Bundle; import android.support.v7.app.AppCompatActivity; import android.support.v7.widget.Toolbar; import android.view.View; import android.webkit.WebView; import android.webkit.WebViewClient; import com.scwang.refreshlayout.R; import com.scwang.smartrefresh.layout.api.RefreshLayout; import com.scwang.smartrefresh.layout.listener.OnRefreshListener; /** * 越界回弹使用演示 */ public class OverScrollUsingActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_using_overscroll); final Toolbar toolbar = (Toolbar)findViewById(R.id.toolbar); toolbar.setNavigationOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { finish(); } }); final WebView webView = (WebView) findViewById(R.id.webView); final RefreshLayout refreshLayout = (RefreshLayout) findViewById(R.id.refreshLayout); refreshLayout.setOnRefreshListener(new OnRefreshListener() { @Override public void onRefresh(RefreshLayout refreshlayout) { webView.loadUrl("http://github.com"); } }); refreshLayout.autoRefresh(); webView.setWebViewClient(new WebViewClient(){ @Override public boolean shouldOverrideUrlLoading(WebView view, String url) { view.loadUrl(url); return true; } @Override public void onPageCommitVisible(WebView view, String url) { refreshLayout.finishRefresh(); } }); // TextView textView = (TextView) findViewById(R.id.textView); // textView.setMovementMethod(new ScrollingMovementMethod()); } } ================================================ FILE: app/src/main/java/com/scwang/refreshlayout/activity/using/SnapHelperUsingActivity.java ================================================ package com.scwang.refreshlayout.activity.using; import android.os.Bundle; import android.support.v7.app.AppCompatActivity; import android.support.v7.widget.DefaultItemAnimator; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.PagerSnapHelper; import android.support.v7.widget.RecyclerView; import android.support.v7.widget.SnapHelper; import android.support.v7.widget.Toolbar; import android.view.View; import com.scwang.refreshlayout.R; import com.scwang.refreshlayout.adapter.BaseRecyclerAdapter; import com.scwang.refreshlayout.adapter.SmartViewHolder; import java.util.Arrays; import java.util.Collection; /** * 结合 SnapHelper 使用 * Created by SCWANG on 2017/8/4. */ public class SnapHelperUsingActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_using_snaphelper); final Toolbar toolbar = (Toolbar)findViewById(R.id.toolbar); toolbar.setNavigationOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { finish(); } }); //初始化列表和监听 View view = findViewById(R.id.recyclerView); if (view instanceof RecyclerView) { RecyclerView recyclerView = (RecyclerView) view; recyclerView.setLayoutManager(new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false)); recyclerView.setItemAnimator(new DefaultItemAnimator()); recyclerView.setAdapter(new BaseRecyclerAdapter(loadModels(), R.layout.listitem_using_snaphelper) { @Override protected void onBindViewHolder(SmartViewHolder holder, Integer model, int position) { holder.image(R.id.imageView, model); } }); SnapHelper snapHelper = new PagerSnapHelper(); snapHelper.attachToRecyclerView(recyclerView); } } private Collection loadModels() { return Arrays.asList( R.mipmap.image_weibo_home_1, R.mipmap.image_weibo_home_2, R.mipmap.image_weibo_home_1, R.mipmap.image_weibo_home_2, R.mipmap.image_weibo_home_1, R.mipmap.image_weibo_home_2); } } ================================================ FILE: app/src/main/java/com/scwang/refreshlayout/adapter/BaseRecyclerAdapter.java ================================================ package com.scwang.refreshlayout.adapter; import android.database.DataSetObservable; import android.database.DataSetObserver; import android.support.annotation.LayoutRes; import android.support.v7.widget.RecyclerView; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.AdapterView; import android.widget.ListAdapter; import java.util.ArrayList; import java.util.Collection; import java.util.List; /** * * Created by SCWANG on 2017/6/11. */ public abstract class BaseRecyclerAdapter extends RecyclerView.Adapter implements ListAdapter { // private final int mLayoutId; private final List mList; private AdapterView.OnItemClickListener mListener; public BaseRecyclerAdapter(@LayoutRes int layoutId) { setHasStableIds(false); this.mList = new ArrayList<>(); this.mLayoutId = layoutId; } public BaseRecyclerAdapter(Collection collection, @LayoutRes int layoutId) { setHasStableIds(false); this.mList = new ArrayList<>(collection); this.mLayoutId = layoutId; } public BaseRecyclerAdapter(Collection collection, @LayoutRes int layoutId, AdapterView.OnItemClickListener listener) { setHasStableIds(false); setOnItemClickListener(listener); this.mList = new ArrayList<>(collection); this.mLayoutId = layoutId; } // // @Override public SmartViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { return new SmartViewHolder(LayoutInflater.from(parent.getContext()).inflate(mLayoutId, parent, false),mListener); } @Override public void onBindViewHolder(SmartViewHolder holder, int position) { onBindViewHolder(holder, position < mList.size() ? mList.get(position) : null, position); } protected abstract void onBindViewHolder(SmartViewHolder holder, T model, int position); @Override public int getItemCount() { return mList.size(); } // // public BaseRecyclerAdapter setOnItemClickListener(AdapterView.OnItemClickListener listener) { mListener = listener; return this; } public BaseRecyclerAdapter refresh(Collection collection) { mList.clear(); mList.addAll(collection); notifyDataSetChanged(); notifyListDataSetChanged(); return this; } public BaseRecyclerAdapter loadmore(Collection collection) { mList.addAll(collection); notifyDataSetChanged(); notifyListDataSetChanged(); return this; } // // private final DataSetObservable mDataSetObservable = new DataSetObservable(); // public boolean hasStableIds() { // return false; // } public void registerDataSetObserver(DataSetObserver observer) { mDataSetObservable.registerObserver(observer); } public void unregisterDataSetObserver(DataSetObserver observer) { mDataSetObservable.unregisterObserver(observer); } /** * Notifies the attached observers that the underlying data has been changed * and any View reflecting the data set should refresh itself. */ public void notifyListDataSetChanged() { mDataSetObservable.notifyChanged(); } /** * Notifies the attached observers that the underlying data is no longer valid * or available. Once invoked this adapter is no longer valid and should * not report further data set changes. */ public void notifyDataSetInvalidated() { mDataSetObservable.notifyInvalidated(); } public boolean areAllItemsEnabled() { return true; } public boolean isEnabled(int position) { return true; } @Override public View getView(int position, View convertView, ViewGroup parent) { SmartViewHolder holder; if (convertView != null) { holder = (SmartViewHolder) convertView.getTag(); } else { holder = onCreateViewHolder(parent, getItemViewType(position)); convertView = holder.itemView; convertView.setTag(holder); } onBindViewHolder(holder, position); return convertView; } public int getItemViewType(int position) { return 0; } public int getViewTypeCount() { return 1; } public boolean isEmpty() { return getCount() == 0; } @Override public Object getItem(int position) { return mList.get(position); } @Override public int getCount() { return mList.size(); } // } ================================================ FILE: app/src/main/java/com/scwang/refreshlayout/adapter/SmartViewHolder.java ================================================ package com.scwang.refreshlayout.adapter; import android.content.res.Resources; import android.support.annotation.StringRes; import android.support.v4.content.ContextCompat; import android.support.v7.widget.RecyclerView; import android.util.TypedValue; import android.view.View; import android.widget.AdapterView; import android.widget.ImageView; import android.widget.TextView; public class SmartViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener { private final AdapterView.OnItemClickListener mListener; public SmartViewHolder(View itemView, AdapterView.OnItemClickListener mListener) { super(itemView); this.mListener = mListener; itemView.setOnClickListener(this); /** * 设置水波纹背景 */ if (itemView.getBackground() == null) { TypedValue typedValue = new TypedValue(); Resources.Theme theme = itemView.getContext().getTheme(); int top = itemView.getPaddingTop(); int bottom = itemView.getPaddingBottom(); int left = itemView.getPaddingLeft(); int right = itemView.getPaddingRight(); if (theme.resolveAttribute(android.R.attr.selectableItemBackground, typedValue, true)) { itemView.setBackgroundResource(typedValue.resourceId); } itemView.setPadding(left, top, right, bottom); } } @Override public void onClick(View v) { if (mListener != null) { int position = getAdapterPosition(); if(position >= 0){ mListener.onItemClick(null, v, position, getItemId()); } } } private View findViewById(int id) { return id == 0 ? itemView : itemView.findViewById(id); } public SmartViewHolder text(int id, CharSequence sequence) { View view = findViewById(id); if (view instanceof TextView) { ((TextView) view).setText(sequence); } return this; } public SmartViewHolder text(int id,@StringRes int stringRes) { View view = findViewById(id); if (view instanceof TextView) { ((TextView) view).setText(stringRes); } return this; } public SmartViewHolder textColorId(int id, int colorId) { View view = findViewById(id); if (view instanceof TextView) { ((TextView) view).setTextColor(ContextCompat.getColor(view.getContext(), colorId)); } return this; } public SmartViewHolder image(int id, int imageId) { View view = findViewById(id); if (view instanceof ImageView) { ((ImageView) view).setImageResource(imageId); } return this; } } ================================================ FILE: app/src/main/java/com/scwang/refreshlayout/fragment/RefreshPractiveFragment.java ================================================ package com.scwang.refreshlayout.fragment; import android.content.Intent; import android.os.Bundle; import android.support.annotation.Nullable; import android.support.v4.app.Fragment; import android.support.v7.widget.DefaultItemAnimator; import android.support.v7.widget.DividerItemDecoration; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.AdapterView; import com.scwang.refreshlayout.R; import com.scwang.refreshlayout.activity.practice.BannerPracticeActivity; import com.scwang.refreshlayout.activity.practice.FeedlistPracticeActivity; import com.scwang.refreshlayout.activity.practice.ProfilePracticeActivity; import com.scwang.refreshlayout.activity.practice.QQBrowserPracticeActivity; import com.scwang.refreshlayout.activity.practice.RepastPracticeActivity; import com.scwang.refreshlayout.activity.practice.WebviewPracticeActivity; import com.scwang.refreshlayout.activity.practice.WeiboPracticeActivity; import com.scwang.refreshlayout.adapter.BaseRecyclerAdapter; import com.scwang.refreshlayout.adapter.SmartViewHolder; import com.scwang.refreshlayout.util.StatusBarUtil; import java.util.Arrays; import static android.R.layout.simple_list_item_2; import static android.support.v7.widget.DividerItemDecoration.VERTICAL; /** * 实战演示 * A simple {@link Fragment} subclass. */ public class RefreshPractiveFragment extends Fragment implements AdapterView.OnItemClickListener { private enum Item { Repast("餐饮美食-简单自定义Header-外边距magin", RepastPracticeActivity.class), Profile("个人中心-PureScrollMode-纯滚动模式", ProfilePracticeActivity.class), Webview("网页引用-WebView", WebviewPracticeActivity.class), FeedList("微博列表-智能识别", FeedlistPracticeActivity.class), Weibo("微博主页-CoordinatorLayout", WeiboPracticeActivity.class), Banner("滚动广告-Banner", BannerPracticeActivity.class), QQBrowser("QQ浏览器-模拟QQ浏览器内核提示", QQBrowserPracticeActivity.class), ; public String name; public Class clazz; Item(String name, Class clazz) { this.name = name; this.clazz = clazz; } } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { return inflater.inflate(R.layout.fragment_refresh_practive, container, false); } @Override public void onViewCreated(View root, @Nullable Bundle savedInstanceState) { super.onViewCreated(root, savedInstanceState); StatusBarUtil.setPaddingSmart(getContext(), root.findViewById(R.id.toolbar)); View view = root.findViewById(R.id.recyclerView); if (view instanceof RecyclerView) { RecyclerView recyclerView = (RecyclerView) view; recyclerView.setItemAnimator(new DefaultItemAnimator()); recyclerView.setLayoutManager(new LinearLayoutManager(getContext())); recyclerView.addItemDecoration(new DividerItemDecoration(getContext(), VERTICAL)); recyclerView.setAdapter(new BaseRecyclerAdapter(Arrays.asList(Item.values()), simple_list_item_2,this) { @Override protected void onBindViewHolder(SmartViewHolder holder, Item model, int position) { holder.text(android.R.id.text1, model.name()); holder.text(android.R.id.text2, model.name); holder.textColorId(android.R.id.text2, R.color.colorTextAssistant); } }); } } @Override public void onItemClick(AdapterView parent, View view, int position, long id) { startActivity(new Intent(getContext(), Item.values()[position].clazz)); } } ================================================ FILE: app/src/main/java/com/scwang/refreshlayout/fragment/RefreshStylesFragment.java ================================================ package com.scwang.refreshlayout.fragment; import android.content.Intent; import android.os.Bundle; import android.support.annotation.Nullable; import android.support.annotation.StringRes; import android.support.v4.app.Fragment; import android.support.v7.widget.DefaultItemAnimator; import android.support.v7.widget.DividerItemDecoration; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.AdapterView; import com.scwang.refreshlayout.R; import com.scwang.refreshlayout.activity.ExperimentActivity; import com.scwang.refreshlayout.activity.style.BezierStyleActivity; import com.scwang.refreshlayout.activity.style.CircleStyleActivity; import com.scwang.refreshlayout.activity.style.ClassicsStyleActivity; import com.scwang.refreshlayout.activity.style.DeliveryStyleActivity; import com.scwang.refreshlayout.activity.style.DropboxStyleActivity; import com.scwang.refreshlayout.activity.style.FlyRefreshStyleActivity; import com.scwang.refreshlayout.activity.style.FunGameBattleCityStyleActivity; import com.scwang.refreshlayout.activity.style.FunGameHitBlockStyleActivity; import com.scwang.refreshlayout.activity.style.MaterialStyleActivity; import com.scwang.refreshlayout.activity.style.PhoenixStyleActivity; import com.scwang.refreshlayout.activity.style.StoreHouseStyleActivity; import com.scwang.refreshlayout.activity.style.TaurusStyleActivity; import com.scwang.refreshlayout.activity.style.WaterDropStyleActivity; import com.scwang.refreshlayout.activity.style.WaveSwipStyleActivity; import com.scwang.refreshlayout.adapter.BaseRecyclerAdapter; import com.scwang.refreshlayout.adapter.SmartViewHolder; import com.scwang.refreshlayout.util.StatusBarUtil; import java.util.Arrays; import static android.R.layout.simple_list_item_2; import static android.support.v7.widget.DividerItemDecoration.VERTICAL; import static com.scwang.refreshlayout.R.id.recyclerView; /** * 风格展示 * A simple {@link Fragment} subclass. */ public class RefreshStylesFragment extends Fragment implements AdapterView.OnItemClickListener { private enum Item { Delivery(R.string.title_activity_style_delivery,DeliveryStyleActivity.class), Dropbox(R.string.title_activity_style_dropbox, DropboxStyleActivity.class), FlyRefresh(R.string.title_activity_style_flyrefresh, FlyRefreshStyleActivity.class), WaveSwipe(R.string.title_activity_style_wave_swip, WaveSwipStyleActivity.class), WaterDrop(R.string.title_activity_style_water_drop, WaterDropStyleActivity.class), Material(R.string.title_activity_style_material, MaterialStyleActivity.class), Phoenix(R.string.title_activity_style_phoenix, PhoenixStyleActivity.class), Taurus(R.string.title_activity_style_taurus, TaurusStyleActivity.class), Bezier(R.string.title_activity_style_bezier, BezierStyleActivity.class), Circle(R.string.title_activity_style_circle, CircleStyleActivity.class), FunGameHitBlock(R.string.title_activity_style_fungame_hitblock, FunGameHitBlockStyleActivity.class), FunGameBattleCity(R.string.title_activity_style_fungame_battlecity, FunGameBattleCityStyleActivity.class), StoreHouse(R.string.title_activity_style_storehouse, StoreHouseStyleActivity.class), Classics(R.string.title_activity_style_classics, ClassicsStyleActivity.class), ; public int nameId; public Class clazz; Item(@StringRes int nameId, Class clazz) { this.nameId = nameId; this.clazz = clazz; } } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { return inflater.inflate(R.layout.fragment_refresh_styles, container, false); } @Override public void onViewCreated(View root, @Nullable Bundle savedInstanceState) { super.onViewCreated(root, savedInstanceState); StatusBarUtil.setPaddingSmart(getContext(), root.findViewById(R.id.toolbar)); View view = root.findViewById(recyclerView); if (view instanceof RecyclerView) { RecyclerView recyclerView = (RecyclerView) view; recyclerView.setItemAnimator(new DefaultItemAnimator()); recyclerView.setLayoutManager(new LinearLayoutManager(getContext())); recyclerView.addItemDecoration(new DividerItemDecoration(getContext(), VERTICAL)); recyclerView.setAdapter(new BaseRecyclerAdapter(Arrays.asList(Item.values()), simple_list_item_2,this) { @Override protected void onBindViewHolder(SmartViewHolder holder, Item model, int position) { holder.text(android.R.id.text1, model.name()); holder.text(android.R.id.text2, model.nameId); holder.textColorId(android.R.id.text2, R.color.colorTextAssistant); } }); } root.findViewById(R.id.toolbar).setOnLongClickListener(new View.OnLongClickListener() { @Override public boolean onLongClick(View v) { startActivity(new Intent(getContext(), ExperimentActivity.class)); return false; } }); } @Override public void onItemClick(AdapterView parent, View view, int position, long id) { startActivity(new Intent(getContext(), Item.values()[position].clazz)); } } ================================================ FILE: app/src/main/java/com/scwang/refreshlayout/fragment/RefreshUsingFragment.java ================================================ package com.scwang.refreshlayout.fragment; import android.content.Intent; import android.os.Bundle; import android.support.annotation.Nullable; import android.support.v4.app.Fragment; import android.support.v7.widget.DefaultItemAnimator; import android.support.v7.widget.DividerItemDecoration; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.AdapterView; import com.scwang.refreshlayout.R; import com.scwang.refreshlayout.activity.using.AssignCodeUsingActivity; import com.scwang.refreshlayout.activity.using.AssignDefaultUsingActivity; import com.scwang.refreshlayout.activity.using.AssignXmlUsingActivity; import com.scwang.refreshlayout.activity.using.BasicUsingActivity; import com.scwang.refreshlayout.activity.using.CustomUsingActivity; import com.scwang.refreshlayout.activity.using.ListenerUsingActivity; import com.scwang.refreshlayout.activity.using.NestLayoutUsingActivity; import com.scwang.refreshlayout.activity.using.SnapHelperUsingActivity; import com.scwang.refreshlayout.adapter.BaseRecyclerAdapter; import com.scwang.refreshlayout.adapter.SmartViewHolder; import com.scwang.refreshlayout.util.StatusBarUtil; import java.util.Arrays; import static android.R.layout.simple_list_item_2; import static android.support.v7.widget.DividerItemDecoration.VERTICAL; /** * 使用示例 * A simple {@link Fragment} subclass. */ public class RefreshUsingFragment extends Fragment implements AdapterView.OnItemClickListener { private enum Item { Basic("基本的使用", BasicUsingActivity.class), DefaultCreater("设置全局默认的Header和Footer", AssignDefaultUsingActivity.class), XmlDefine("在XML中定义Header和Footer", AssignXmlUsingActivity.class), CodeDefine("在代码中指定Header和Footer", AssignCodeUsingActivity.class), Listener("多功能监听器", ListenerUsingActivity.class), NestLayout("嵌套Layout作为内容", NestLayoutUsingActivity.class), Custom("自定义Header", CustomUsingActivity.class), SnapHelper("结合 SnapHelper 使用", SnapHelperUsingActivity.class), ; public String name; public Class clazz; Item(String name, Class clazz) { this.name = name; this.clazz = clazz; } } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { return inflater.inflate(R.layout.fragment_refresh_using, container, false); } @Override public void onViewCreated(View root, @Nullable Bundle savedInstanceState) { super.onViewCreated(root, savedInstanceState); StatusBarUtil.setPaddingSmart(getContext(), root.findViewById(R.id.toolbar)); View view = root.findViewById(R.id.recyclerView); if (view instanceof RecyclerView) { RecyclerView recyclerView = (RecyclerView) view; recyclerView.setItemAnimator(new DefaultItemAnimator()); recyclerView.setLayoutManager(new LinearLayoutManager(getContext())); recyclerView.addItemDecoration(new DividerItemDecoration(getContext(), VERTICAL)); recyclerView.setAdapter(new BaseRecyclerAdapter(Arrays.asList(Item.values()), simple_list_item_2,this) { @Override protected void onBindViewHolder(SmartViewHolder holder, Item model, int position) { holder.text(android.R.id.text1, model.name()); holder.text(android.R.id.text2, model.name); holder.textColorId(android.R.id.text2, R.color.colorTextAssistant); } }); } } @Override public void onItemClick(AdapterView parent, View view, int position, long id) { Item item = Item.values()[position]; startActivity(new Intent(getContext(), item.clazz)); } } ================================================ FILE: app/src/main/java/com/scwang/refreshlayout/util/DynamicTimeFormat.java ================================================ package com.scwang.refreshlayout.util; import android.support.annotation.NonNull; import java.text.FieldPosition; import java.text.SimpleDateFormat; import java.util.Calendar; import java.util.Date; import java.util.Locale; /** * 动态时间格式化 * Created by SCWANG on 2017/6/17. */ public class DynamicTimeFormat extends SimpleDateFormat { private static Locale locale = Locale.CHINA; private static String weeks[] = {"周日", "周一", "周二", "周三", "周四", "周五", "周六"}; private static String moments[] = {"中午", "凌晨", "早上", "下午", "晚上"}; private String mFormat = "%s"; public DynamicTimeFormat() { this("%s", "yyyy年", "M月d日", "HH:mm"); } public DynamicTimeFormat(String format) { this(); this.mFormat = format; } public DynamicTimeFormat(String yearFormat, String dateFormat, String timeFormat) { super(String.format(locale, "%s %s %s", yearFormat, dateFormat, timeFormat), locale); } public DynamicTimeFormat(String format, String yearFormat, String dateFormat, String timeFormat) { this(yearFormat, dateFormat, timeFormat); this.mFormat = format; } @Override public StringBuffer format(@NonNull Date date, @NonNull StringBuffer toAppendTo, @NonNull FieldPosition pos) { toAppendTo = super.format(date, toAppendTo, pos); Calendar otherCalendar = calendar; Calendar todayCalendar = Calendar.getInstance(); int hour = otherCalendar.get(Calendar.HOUR_OF_DAY); String[] times = toAppendTo.toString().split(" "); String moment = hour == 12 ? moments[0] : moments[hour / 6 + 1]; String timeFormat = moment + " " + times[2]; String dateFormat = times[1] + " " + timeFormat; String yearFormat = times[0] + dateFormat; toAppendTo.delete(0, toAppendTo.length()); boolean yearTemp = todayCalendar.get(Calendar.YEAR) == otherCalendar.get(Calendar.YEAR); if (yearTemp) { int todayMonth = todayCalendar.get(Calendar.MONTH); int otherMonth = otherCalendar.get(Calendar.MONTH); if (todayMonth == otherMonth) {//表示是同一个月 int temp = todayCalendar.get(Calendar.DATE) - otherCalendar.get(Calendar.DATE); switch (temp) { case 0: toAppendTo.append(timeFormat); break; case 1: toAppendTo.append("昨天 "); toAppendTo.append(timeFormat); break; case 2: toAppendTo.append("前天 "); toAppendTo.append(timeFormat); break; case 3: case 4: case 5: case 6: int dayOfMonth = otherCalendar.get(Calendar.WEEK_OF_MONTH); int todayOfMonth = todayCalendar.get(Calendar.WEEK_OF_MONTH); if (dayOfMonth == todayOfMonth) {//表示是同一周 int dayOfWeek = otherCalendar.get(Calendar.DAY_OF_WEEK); if (dayOfWeek != 1) {//判断当前是不是星期日 如想显示为:周日 12:09 可去掉此判断 toAppendTo.append(weeks[otherCalendar.get(Calendar.DAY_OF_WEEK) - 1]); toAppendTo.append(' '); toAppendTo.append(timeFormat); } else { toAppendTo.append(dateFormat); } } else { toAppendTo.append(dateFormat); } break; default: toAppendTo.append(dateFormat); break; } } else { toAppendTo.append(dateFormat); } } else { toAppendTo.append(yearFormat); } int length = toAppendTo.length(); toAppendTo.append(String.format(locale, mFormat, toAppendTo.toString())); toAppendTo.delete(0, length); return toAppendTo; } } ================================================ FILE: app/src/main/java/com/scwang/refreshlayout/util/StatusBarUtil.java ================================================ package com.scwang.refreshlayout.util; import android.support.annotation.RequiresApi; import android.app.Activity; import android.content.Context; import android.content.res.Resources; import android.os.Build; import android.support.annotation.FloatRange; import android.util.Log; import android.util.TypedValue; import android.view.View; import android.view.ViewGroup; import android.view.Window; import android.view.WindowManager; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.util.regex.Pattern; /** * 状态栏透明 * Created by SCWANG on 2016/10/26. */ @SuppressWarnings("unused") public class StatusBarUtil { public static int DEFAULT_COLOR = 0; public static float DEFAULT_ALPHA = 0;//Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP ? 0.2f : 0.3f; public static final int MIN_API = 19; // public static void immersive(Activity activity) { immersive(activity, DEFAULT_COLOR, DEFAULT_ALPHA); } public static void immersive(Activity activity, int color, @FloatRange(from = 0.0, to = 1.0) float alpha) { immersive(activity.getWindow(), color, alpha); } public static void immersive(Activity activity, int color) { immersive(activity.getWindow(), color, 1f); } public static void immersive(Window window) { immersive(window, DEFAULT_COLOR, DEFAULT_ALPHA); } public static void immersive(Window window, int color) { immersive(window, color, 1f); } public static void immersive(Window window, int color, @FloatRange(from = 0.0, to = 1.0) float alpha) { if (Build.VERSION.SDK_INT >= 21) { window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS); window.setStatusBarColor(mixtureColor(color, alpha)); int systemUiVisibility = window.getDecorView().getSystemUiVisibility(); systemUiVisibility |= View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN; systemUiVisibility |= View.SYSTEM_UI_FLAG_LAYOUT_STABLE; window.getDecorView().setSystemUiVisibility(systemUiVisibility); } else if (Build.VERSION.SDK_INT >= 19) { window.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); setTranslucentView((ViewGroup) window.getDecorView(), color, alpha); } else if (Build.VERSION.SDK_INT >= MIN_API && Build.VERSION.SDK_INT > 16) { int systemUiVisibility = window.getDecorView().getSystemUiVisibility(); systemUiVisibility |= View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN; systemUiVisibility |= View.SYSTEM_UI_FLAG_LAYOUT_STABLE; window.getDecorView().setSystemUiVisibility(systemUiVisibility); } } // // public static void darkMode(Activity activity, boolean dark) { if (isFlyme4Later()) { darkModeForFlyme4(activity.getWindow(), dark); } else if (isMIUI6Later()) { darkModeForMIUI6(activity.getWindow(), dark); } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { darkModeForM(activity.getWindow(), dark); } } /** 设置状态栏darkMode,字体颜色及icon变黑(目前支持MIUI6以上,Flyme4以上,Android M以上) */ public static void darkMode(Activity activity) { darkMode(activity.getWindow(), DEFAULT_COLOR, DEFAULT_ALPHA); } public static void darkMode(Activity activity, int color, @FloatRange(from = 0.0, to = 1.0) float alpha) { darkMode(activity.getWindow(), color, alpha); } /** 设置状态栏darkMode,字体颜色及icon变黑(目前支持MIUI6以上,Flyme4以上,Android M以上) */ public static void darkMode(Window window, int color, @FloatRange(from = 0.0, to = 1.0) float alpha) { if (isFlyme4Later()) { darkModeForFlyme4(window, true); immersive(window,color,alpha); } else if (isMIUI6Later()) { darkModeForMIUI6(window, true); immersive(window,color,alpha); } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { darkModeForM(window, true); immersive(window, color, alpha); } else if (Build.VERSION.SDK_INT >= 19) { window.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); setTranslucentView((ViewGroup) window.getDecorView(), color, alpha); } else { immersive(window, color, alpha); } // if (Build.VERSION.SDK_INT >= 21) { // window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); // window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS); // window.setStatusBarColor(Color.TRANSPARENT); // } else if (Build.VERSION.SDK_INT >= 19) { // window.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); // } // setTranslucentView((ViewGroup) window.getDecorView(), color, alpha); } //-------------------------> /** android 6.0设置字体颜色 */ @RequiresApi(Build.VERSION_CODES.M) private static void darkModeForM(Window window, boolean dark) { // window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); // window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS); // window.setStatusBarColor(Color.TRANSPARENT); int systemUiVisibility = window.getDecorView().getSystemUiVisibility(); if (dark) { systemUiVisibility |= View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR; } else { systemUiVisibility &= ~View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR; } window.getDecorView().setSystemUiVisibility(systemUiVisibility); } /** * 设置Flyme4+的darkMode,darkMode时候字体颜色及icon变黑 * http://open-wiki.flyme.cn/index.php?title=Flyme%E7%B3%BB%E7%BB%9FAPI */ public static boolean darkModeForFlyme4(Window window, boolean dark) { boolean result = false; if (window != null) { try { WindowManager.LayoutParams e = window.getAttributes(); Field darkFlag = WindowManager.LayoutParams.class.getDeclaredField("MEIZU_FLAG_DARK_STATUS_BAR_ICON"); Field meizuFlags = WindowManager.LayoutParams.class.getDeclaredField("meizuFlags"); darkFlag.setAccessible(true); meizuFlags.setAccessible(true); int bit = darkFlag.getInt(null); int value = meizuFlags.getInt(e); if (dark) { value |= bit; } else { value &= ~bit; } meizuFlags.setInt(e, value); window.setAttributes(e); result = true; } catch (Exception var8) { Log.e("StatusBar", "darkIcon: failed"); } } return result; } /** * 设置MIUI6+的状态栏是否为darkMode,darkMode时候字体颜色及icon变黑 * http://dev.xiaomi.com/doc/p=4769/ */ public static boolean darkModeForMIUI6(Window window, boolean darkmode) { Class clazz = window.getClass(); try { int darkModeFlag = 0; Class layoutParams = Class.forName("android.view.MiuiWindowManager$LayoutParams"); Field field = layoutParams.getField("EXTRA_FLAG_STATUS_BAR_DARK_MODE"); darkModeFlag = field.getInt(layoutParams); Method extraFlagField = clazz.getMethod("setExtraFlags", int.class, int.class); extraFlagField.invoke(window, darkmode ? darkModeFlag : 0, darkModeFlag); return true; } catch (Exception e) { e.printStackTrace(); return false; } } /** 判断是否Flyme4以上 */ public static boolean isFlyme4Later() { return Build.FINGERPRINT.contains("Flyme_OS_4") || Build.VERSION.INCREMENTAL.contains("Flyme_OS_4") || Pattern.compile("Flyme OS [4|5]", Pattern.CASE_INSENSITIVE).matcher(Build.DISPLAY).find(); } /** 判断是否为MIUI6以上 */ public static boolean isMIUI6Later() { try { Class clz = Class.forName("android.os.SystemProperties"); Method mtd = clz.getMethod("get", String.class); String val = (String) mtd.invoke(null, "ro.miui.ui.version.name"); val = val.replaceAll("[vV]", ""); int version = Integer.parseInt(val); return version >= 6; } catch (Exception e) { return false; } } // /** 增加View的paddingTop,增加的值为状态栏高度 */ public static void setPadding(Context context, View view) { if (Build.VERSION.SDK_INT >= MIN_API) { view.setPadding(view.getPaddingLeft(), view.getPaddingTop() + getStatusBarHeight(context), view.getPaddingRight(), view.getPaddingBottom()); } } /** 增加View的paddingTop,增加的值为状态栏高度 (智能判断,并设置高度)*/ public static void setPaddingSmart(Context context, View view) { if (Build.VERSION.SDK_INT >= MIN_API) { ViewGroup.LayoutParams lp = view.getLayoutParams(); if (lp != null && lp.height > 0) { lp.height += getStatusBarHeight(context);//增高 } view.setPadding(view.getPaddingLeft(), view.getPaddingTop() + getStatusBarHeight(context), view.getPaddingRight(), view.getPaddingBottom()); } } /** 增加View的高度以及paddingTop,增加的值为状态栏高度.一般是在沉浸式全屏给ToolBar用的 */ public static void setHeightAndPadding(Context context, View view) { if (Build.VERSION.SDK_INT >= MIN_API) { ViewGroup.LayoutParams lp = view.getLayoutParams(); lp.height += getStatusBarHeight(context);//增高 view.setPadding(view.getPaddingLeft(), view.getPaddingTop() + getStatusBarHeight(context), view.getPaddingRight(), view.getPaddingBottom()); } } /** 增加View上边距(MarginTop)一般是给高度为 WARP_CONTENT 的小控件用的*/ public static void setMargin(Context context, View view) { if (Build.VERSION.SDK_INT >= MIN_API) { ViewGroup.LayoutParams lp = view.getLayoutParams(); if (lp instanceof ViewGroup.MarginLayoutParams) { ((ViewGroup.MarginLayoutParams) lp).topMargin += getStatusBarHeight(context);//增高 } view.setLayoutParams(lp); } } /** * 创建假的透明栏 */ public static void setTranslucentView(ViewGroup container, int color, @FloatRange(from = 0.0, to = 1.0) float alpha) { if (Build.VERSION.SDK_INT >= 19) { int mixtureColor = mixtureColor(color, alpha); View translucentView = container.findViewById(android.R.id.custom); if (translucentView == null && mixtureColor != 0) { translucentView = new View(container.getContext()); translucentView.setId(android.R.id.custom); ViewGroup.LayoutParams lp = new ViewGroup.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, getStatusBarHeight(container.getContext())); container.addView(translucentView, lp); } if (translucentView != null) { translucentView.setBackgroundColor(mixtureColor); } } } public static int mixtureColor(int color, @FloatRange(from = 0.0, to = 1.0) float alpha) { int a = (color & 0xff000000) == 0 ? 0xff : color >>> 24; return (color & 0x00ffffff) | (((int) (a * alpha)) << 24); } /** 获取状态栏高度 */ public static int getStatusBarHeight(Context context) { int result = 24; int resId = context.getResources().getIdentifier("status_bar_height", "dimen", "android"); if (resId > 0) { result = context.getResources().getDimensionPixelSize(resId); } else { result = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, result, Resources.getSystem().getDisplayMetrics()); } return result; } } ================================================ FILE: app/src/main/java/com/scwang/refreshlayout/widget/RefreshLayout.java ================================================ package com.scwang.refreshlayout.widget; import android.annotation.TargetApi; import android.content.Context; import android.os.Build; import android.support.annotation.Px; import android.support.v4.view.GestureDetectorCompat; import android.util.AttributeSet; import android.view.GestureDetector; import android.view.MotionEvent; import android.view.View; import android.view.ViewConfiguration; import android.view.ViewGroup; import android.widget.OverScroller; import com.scwang.smartrefresh.layout.api.RefreshContent; import com.scwang.smartrefresh.layout.api.RefreshFooter; import com.scwang.smartrefresh.layout.api.RefreshHeader; import com.scwang.smartrefresh.layout.impl.RefreshContentWrapper; import com.scwang.smartrefresh.layout.impl.RefreshFooterWrapper; import com.scwang.smartrefresh.layout.impl.RefreshHeaderWrapper; import static android.view.View.MeasureSpec.AT_MOST; import static android.view.View.MeasureSpec.EXACTLY; import static android.view.View.MeasureSpec.getSize; import static android.view.View.MeasureSpec.makeMeasureSpec; import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT; /** * ScrollView * Created by SCWANG on 2017/7/10. */ public class RefreshLayout extends ViewGroup implements GestureDetector.OnGestureListener { private OverScroller mScroller; private RefreshHeader mRefreshHeader; private RefreshFooter mRefreshFooter; private RefreshContent mRefreshContent; private GestureDetectorCompat mGesture; private int mTouchSlop; /** * 头部高度 */ protected int mHeaderHeight; /** * 底部高度 */ protected int mFooterHeight; // public RefreshLayout(Context context) { super(context); initView(context); } public RefreshLayout(Context context, AttributeSet attrs) { super(context, attrs); initView(context); } public RefreshLayout(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); initView(context); } @TargetApi(Build.VERSION_CODES.LOLLIPOP) public RefreshLayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); initView(context); } private void initView(Context context) { mScroller = new OverScroller(context); mGesture = new GestureDetectorCompat(context, this); mGesture.setIsLongpressEnabled(false); mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop(); } // // @Override protected void onFinishInflate() { super.onFinishInflate(); final int childCount = getChildCount(); if (childCount == 1) { mRefreshContent = new RefreshContentWrapper(getChildAt(0)); } else if (childCount > 1) { mRefreshHeader = new RefreshHeaderWrapper(getChildAt(0)); mRefreshContent = new RefreshContentWrapper(getChildAt(1)); if (childCount > 2) { mRefreshFooter = new RefreshFooterWrapper(getChildAt(2)); } } } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); if (mRefreshContent != null) { mRefreshContent.measure(widthMeasureSpec, heightMeasureSpec); } if (mRefreshHeader != null) { final View headerView = mRefreshHeader.getView(); final LayoutParams lp = (LayoutParams) headerView.getLayoutParams(); final int widthSpec = getChildMeasureSpec(widthMeasureSpec, lp.leftMargin + lp.rightMargin, lp.width); int heightSpec = heightMeasureSpec; if (lp.height > 0) { heightSpec = makeMeasureSpec(lp.height, EXACTLY); headerView.measure(widthSpec, heightSpec); } else if (lp.height == WRAP_CONTENT) { heightSpec = makeMeasureSpec(Math.max(getSize(heightMeasureSpec)/* - lp.topMargin*/ - lp.bottomMargin, 0), AT_MOST); headerView.measure(widthSpec, heightSpec); } else { headerView.measure(widthSpec, heightSpec); } mHeaderHeight = headerView.getMeasuredHeight(); } if (mRefreshFooter != null) { final View footerView = mRefreshFooter.getView(); final LayoutParams lp = (LayoutParams) footerView.getLayoutParams(); final int widthSpec = getChildMeasureSpec(widthMeasureSpec, lp.leftMargin + lp.rightMargin, lp.width); int heightSpec = heightMeasureSpec; if (lp.height > 0) { heightSpec = makeMeasureSpec(lp.height, EXACTLY); footerView.measure(widthSpec, heightSpec); } else if (lp.height == WRAP_CONTENT) { heightSpec = makeMeasureSpec(Math.max(getSize(heightMeasureSpec)/* - lp.topMargin*/ - lp.bottomMargin, 0), AT_MOST); footerView.measure(widthSpec, heightSpec); } else { footerView.measure(widthSpec, heightSpec); } mFooterHeight = footerView.getMeasuredHeight(); } } @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { if (mRefreshContent != null) { mRefreshContent.layout(0, 0, getMeasuredWidth(), getMeasuredHeight()); } if (mRefreshHeader != null) { final View headerView = mRefreshHeader.getView(); int right = headerView.getMeasuredWidth(); int top = -headerView.getMeasuredHeight(); headerView.layout(0, top, right, 0); } if (mRefreshFooter != null) { final View headerView = mRefreshFooter.getView(); int right = headerView.getMeasuredWidth(); int top = getMeasuredHeight(); int bottom = top + headerView.getMeasuredHeight(); headerView.layout(0, top, right, bottom); } } // // @Override protected LayoutParams generateDefaultLayoutParams() { return new LayoutParams(MATCH_PARENT, MATCH_PARENT); } @Override protected LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) { return new LayoutParams(p); } @Override public LayoutParams generateLayoutParams(AttributeSet attrs) { return new LayoutParams(getContext(), attrs); } public static class LayoutParams extends MarginLayoutParams { public LayoutParams(Context c, AttributeSet attrs) { super(c, attrs); } public LayoutParams(int width, int height) { super(width, height); } public LayoutParams(MarginLayoutParams source) { super(source); } public LayoutParams(ViewGroup.LayoutParams source) { super(source); } } // // // @Override // public boolean dispatchTouchEvent(MotionEvent ev) { // switch (MotionEventCompat.getActionMasked(ev)) { // case MotionEvent.ACTION_DOWN: // mIsBeingDragged = false; // mInitialDownY = ev.getY(); // mGesture.onTouchEvent(ev); // break; // // } // return mIsBeingDragged;//super.dispatchTouchEvent(ev); // } @Override public boolean dispatchTouchEvent(MotionEvent ev) { mGesture.onTouchEvent(ev); return super.dispatchTouchEvent(ev); } // @Override // public boolean onInterceptTouchEvent(MotionEvent ev) { // return false;//super.onInterceptTouchEvent(ev); // } // @Override // public boolean onTouchEvent(MotionEvent event) { // return mGesture.onTouchEvent(event); // } // // private int mlastScrollY; boolean isFling = false; int scrollY; int currllY; @Override public void computeScroll() { if (mScroller.computeScrollOffset()) { int currY = mScroller.getCurrY(); int dy = currY - mlastScrollY; // if (isFling) { // scrollTo(0, getScrollY() + dy); // } else { // if (dy < 0 && mRefreshContent.canRefresh()) { // isFling = true; // scrollY = getScrollY(); // currllY = mScroller.getCurrY(); // } else if (dy > 0 && mRefreshContent.canLoadmore()) { // isFling = true; // scrollY = getScrollY(); // currllY = mScroller.getCurrY(); // } // } // mlastScrollY = currY; postInvalidate(); } else { isFling = false; } } @Override public void scrollTo(@Px int x, @Px int y) { super.scrollTo(x, y); } // // @Override public boolean onDown(MotionEvent e) { return true; } @Override public void onShowPress(MotionEvent e) { } @Override public boolean onSingleTapUp(MotionEvent e) { return false; } @Override public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { // scrollBy(0, (int)distanceY); return true; } @Override public void onLongPress(MotionEvent e) { } @Override public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { isFling = false; mScroller.fling(0, getScrollY(), 0, -(int)velocityY, 0, 0, -mHeaderHeight, mFooterHeight); mlastScrollY = mScroller.getCurrY(); postInvalidate(); return true; } // } ================================================ FILE: app/src/main/res/drawable/bc_background_panel.xml ================================================ ================================================ FILE: app/src/main/res/drawable/ic_arrow_back_dark_24dp.xml ================================================ ================================================ FILE: app/src/main/res/drawable/ic_arrow_back_gray_24dp.xml ================================================ ================================================ FILE: app/src/main/res/drawable/ic_arrow_back_white_24dp.xml ================================================ ================================================ FILE: app/src/main/res/drawable/ic_chevron_right.xml ================================================ ================================================ FILE: app/src/main/res/drawable/ic_chevron_right_gray.xml ================================================ ================================================ FILE: app/src/main/res/drawable/ic_feed_list_favorite.xml ================================================ ================================================ FILE: app/src/main/res/drawable/ic_feed_list_mail.xml ================================================ ================================================ FILE: app/src/main/res/drawable/ic_feed_list_photo.xml ================================================ ================================================ FILE: app/src/main/res/drawable/ic_fly_refresh_folder.xml ================================================ ================================================ FILE: app/src/main/res/drawable/ic_fly_refresh_info.xml ================================================ ================================================ FILE: app/src/main/res/drawable/ic_fly_refresh_poll.xml ================================================ ================================================ FILE: app/src/main/res/drawable/ic_fly_refresh_send.xml ================================================ ================================================ FILE: app/src/main/res/drawable/ic_fly_refresh_smartphone.xml ================================================ ================================================ FILE: app/src/main/res/drawable/ic_index_dashboard.xml ================================================ ================================================ FILE: app/src/main/res/drawable/ic_index_home.xml ================================================ ================================================ FILE: app/src/main/res/drawable/ic_index_notifications.xml ================================================ ================================================ FILE: app/src/main/res/drawable/ic_list_divider.xml ================================================ ================================================ FILE: app/src/main/res/drawable/ic_progress_hojder.xml ================================================ ================================================ FILE: app/src/main/res/layout/activity_experiment.xml ================================================ ================================================ FILE: app/src/main/res/layout/activity_fly_refresh.xml ================================================ ================================================ FILE: app/src/main/res/layout/activity_fly_refresh_item.xml ================================================ ================================================ FILE: app/src/main/res/layout/activity_index_main.xml ================================================ ================================================ FILE: app/src/main/res/layout/activity_practice_banner.xml ================================================ ================================================ FILE: app/src/main/res/layout/activity_practice_feedlist.xml ================================================ ================================================ FILE: app/src/main/res/layout/activity_practice_profile.xml ================================================ ================================================ FILE: app/src/main/res/layout/activity_practice_qqbrowser.xml ================================================ ================================================ FILE: app/src/main/res/layout/activity_practice_repast.xml ================================================ ================================================ FILE: app/src/main/res/layout/activity_practice_webview.xml ================================================ ================================================ FILE: app/src/main/res/layout/activity_practice_weibo.xml ================================================ ================================================ FILE: app/src/main/res/layout/activity_style_bezier.xml ================================================ ================================================ FILE: app/src/main/res/layout/activity_style_circle.xml ================================================ ================================================ FILE: app/src/main/res/layout/activity_style_classics.xml ================================================ ================================================ FILE: app/src/main/res/layout/activity_style_delivery.xml ================================================ ================================================ FILE: app/src/main/res/layout/activity_style_dropbox.xml ================================================ ================================================ FILE: app/src/main/res/layout/activity_style_fungame_battlecity.xml ================================================ ================================================ FILE: app/src/main/res/layout/activity_style_fungame_hitblock.xml ================================================ ================================================ FILE: app/src/main/res/layout/activity_style_material.xml ================================================ ================================================ FILE: app/src/main/res/layout/activity_style_phoenix.xml ================================================ ================================================ FILE: app/src/main/res/layout/activity_style_storehouse.xml ================================================ ================================================ FILE: app/src/main/res/layout/activity_style_taurus.xml ================================================ ================================================ FILE: app/src/main/res/layout/activity_style_water_drop.xml ================================================ ================================================ FILE: app/src/main/res/layout/activity_style_wave_swip.xml ================================================ ================================================ FILE: app/src/main/res/layout/activity_using_assign_code.xml ================================================ ================================================ FILE: app/src/main/res/layout/activity_using_assign_default.xml ================================================ ================================================ FILE: app/src/main/res/layout/activity_using_assign_xml.xml ================================================ ================================================ FILE: app/src/main/res/layout/activity_using_basic.xml ================================================ ================================================ FILE: app/src/main/res/layout/activity_using_custom.xml ================================================ ================================================ FILE: app/src/main/res/layout/activity_using_listener.xml ================================================ ================================================ FILE: app/src/main/res/layout/activity_using_overscroll.xml ================================================ ================================================ FILE: app/src/main/res/layout/activity_using_region.xml ================================================ ================================================ FILE: app/src/main/res/layout/activity_using_snaphelper.xml ================================================ ================================================ FILE: app/src/main/res/layout/fragment_refresh_practive.xml ================================================ ================================================ FILE: app/src/main/res/layout/fragment_refresh_styles.xml ================================================ ================================================ FILE: app/src/main/res/layout/fragment_refresh_using.xml ================================================ ================================================ FILE: app/src/main/res/layout/listitem_movie_banner.xml ================================================ ================================================ FILE: app/src/main/res/layout/listitem_movie_header.xml ================================================ ================================================ FILE: app/src/main/res/layout/listitem_movie_item.xml ================================================ ================================================ FILE: app/src/main/res/layout/listitem_practive_repast.xml ================================================ ================================================ FILE: app/src/main/res/layout/listitem_style_delivery.xml ================================================ ================================================ FILE: app/src/main/res/layout/listitem_using_snaphelper.xml ================================================ ================================================ FILE: app/src/main/res/menu/navigation.xml ================================================ ================================================ FILE: app/src/main/res/values/colors.xml ================================================ #33aaff #2299ee #cc0094ff #FF4081 #333333 #444444 #888888 #FFFFFF #f2f2f2 #33aaaaaa ================================================ FILE: app/src/main/res/values/dimens.xml ================================================ 240dp 16dp 16dp 16dp 16dp 0.5dp 10dp 15dp 10dp 5dp 12sp 14sp 16sp 17sp 19sp 20sp ================================================ FILE: app/src/main/res/values/strings.xml ================================================ SmartRefreshLayout Settings Settings FlyRefresh Refresh 风格 使用 实战 未选择 已选地区 风格展示 使用示例 实战 全屏水波 官方主题 苹果水滴 金色校园 冲上云霄 贝塞尔雷达 经典风格 弹出圆圈 战争城市 打砖块 StoreHouse 基本使用 全局默认 XML配置 java代码设置 这是使用定制Header和Footer的第一种方法-全局默认\n\n 本方法是实际项目中最常用的方法,其特点有:\n\n 1.设置一次全部生效,不用多次写重复的代码\n 2.优先级最低,在特别页面可以轻松的覆盖替换\n\n 使用方法: \n //设置全局的Header构建器 \n SmartRefreshLayout.setDefaultRefreshHeaderCreater(new DefaultRefreshHeaderCreater() {\n public RefreshHeader createRefreshHeader(Context context, RefreshLayout layout) {\n return new ClassicsHeader(context);//指定为经典Header,默认是 贝塞尔雷达Header\n }\n });\n //设置全局的Footer构建器\n SmartRefreshLayout.setDefaultRefreshFooterCreater(new DefaultRefreshFooterCreater() {\n public RefreshFooter createRefreshFooter(Context context, RefreshLayout layout) {\n return new ClassicsFooter(context);//指定为经典Footer,默认是 BallPulseFooter\n }\n });\n\n 关键代码,需要在布局生成之前设置,建议代码放在 Application.onCreate 中 \n \t\n \t\n \t\n \n ]]> 这是使用定制Header和Footer的第三种方法-代码设置\n\n 本方法是实际项目中比较少用的方法,其特点有:\n\n 1.设置一次生效一次,需要多处设置,建议特别的页面使用\n 2.优先级最高,即使使用了会覆盖方法一和方法二的设置\n\n 使用方法: \n final RefreshLayout refreshLayout = (RefreshLayout) findViewById(R.id.refreshLayout);\n //设置 Header 为 Material风格\n refreshLayout.setRefreshHeader(new MaterialHeader(this));\n //设置 Footer 为 三球动画\n refreshLayout.setRefreshFooter(new BallPulseFooter(this));\n\n 掉落盒子 气球快递 纸飞机 越界回弹 多功能监听器 自定义Header 餐饮美食 个人中心 Github 微博列表 滚动广告 微博主页 QQ浏览器 网页由 github.com 提供\n已启用QQ浏览器X5内核 SnapHelper ================================================ FILE: app/src/main/res/values/styles.xml ================================================ ================================================ FILE: app/src/main/res/values-v19/styles.xml ================================================ ================================================ FILE: app/src/main/res/values-v21/styles.xml ================================================ ================================================ FILE: app/src/test/java/com/scwang/refreshlayout/ExampleUnitTest.java ================================================ package com.scwang.refreshlayout; import org.junit.Test; import static org.junit.Assert.*; /** * Example local unit test, which will execute on the development machine (host). * * @see Testing documentation */ public class ExampleUnitTest { @Test public void addition_isCorrect() throws Exception { assertEquals(4, 2 + 2); } } ================================================ FILE: art/UMLRefreshLayout.classdiagram ================================================  AbsListView RecyclerView ... View Layout ================================================ FILE: art/app-debug.apk ================================================ [File too large to display: 10.1 MB] ================================================ FILE: art/md_custom.md ================================================ # 自定义Header和Footer ## 把情感化设计的APP卡通形象植入APP界面当中 稍微大一点的互联网公司都有属于自己的企业或app的卡通吉祥物。这些品牌元素都是我们 app设计师发挥创意设计的好素材。 也是需要我们巧妙植入到我们APPui界面设计当中去的 最重要的元素。 比如美团、饿了吗、天猫。。。 ![](png_ptr_meituan.png) ![](png_ptr_elema.png) ![](png_ptr_tianmao.png) ## 安卓下拉刷新框架 SmartRefreshLayout被设计为一个刷新框架,具有非常高的自定性和可扩展性,可以应付 项目中的各种情况和场景。 通过SmartRefreshLayout框架,你可以在一个稳定强大的下拉布局中实现自己项目需求的 Header ,不用去关心滑动事件处理,不用关心子控件的回弹和滚动边界,只需关注自己真 正的项目需求Header的样子和动画。 ### 体系结构 在学习使用框架的自定义功能之前,我们还是有必要来了解一下框架的体系和结构: - **RefreshLayout** 下拉的基本功能,包括布局测量、滑动事件处理、参数设定等等 - **RefreshHeader** 下拉头部的事件处理和显示接口 - **RefreshFooter** 上拉底部的事件处理和显示接口 - **RefreshContent** 对不同内容的统一封装,包括判断是否可滚动、回弹判断、智能识别 下面是UML关系类图 ![](jpg_uml.jpg) ### 优势特点 网上其他的开源下拉控件一样的可以自定义 Header 和 Footer ,SmartRefreshLayout 和它们 比起来有什么优势? #### 变换方式 - **Translate 平行移动** 特点: 最常见,HeaderView高度不会改变, - **Scale 拉伸形变** 特点:在下拉和上弹(HeaderView高度改变)时候,会自动触发OnDraw事件 - **FixedFront 固定在前面** 特点:不会上下移动,HeaderView高度不会改变 - **FixedBehind 固定在后面** 特点:不会上下移动,HeaderView高度不会改变(类似微信浏览器效果) - **Screen 全屏幕** 特点:固定在前面,尺寸充满整个布局 SmartRefreshLayout 的Header和Footer都有多种变换方式,适应不同风格的 Header 和 Footer,下面是不同变换方式Header的Demo **FixedBehind 固定在后面** 和 **Scale 拉伸形变** ![](gif_practive_feedlist.gif) ![](gif_Circle.gif) **Screen 全屏幕** 和 **Translate 平行移动** ![](gif_WaveSwipe.gif) ![](gif_practive_weibo.gif) #### 独立事件 Header和Footer 可以独立的处理手指滑动事件来为动画提供操作指令,也可以使用RefreshLayout的核心接口来完成一些不寻常的操作指令。 下面的打砖块 Header中 ,Header可以独立的使用滑动事件来为游戏挡板提供指令,并同时可以调用核心接口来通知RefreshLayout上下滚动列表 ![](gif_HitBlock.gif) ![](gif_BattleCity.gif) ### RefreshHeader接口 实现自定义Header的第一步就是实现RefreshHeader接口,我们来看看RefreshHeader接口的定义和详细说明 ~~~java public interface RefreshHeader { /** * 获取真实视图(必须返回,不能为null) */ @NonNull View getView(); /** * 获取变换方式(必须指定一个:平移、拉伸、固定、全屏) */ SpinnerStyle getSpinnerStyle(); /** * 设置主题颜色 (如果自定义的Header没有注意颜色,本方法可以什么都不处理) * @param colors 对应Xml中配置的 srlPrimaryColor srlAccentColor */ void setPrimaryColors(int... colors); /** * 尺寸定义初始化完成 (如果高度不改变(代码修改:setHeader),只调用一次, 在RefreshLayout#onMeasure中调用) * @param kernel RefreshKernel 核心接口(用于完成高级Header功能) * @param height HeaderHeight or FooterHeight * @param extendHeight extendHeaderHeight or extendFooterHeight */ void onInitialized(RefreshKernel kernel, int height, int extendHeight); /** * 开始动画(开始刷新或者开始加载动画) * @param layout RefreshLayout * @param height HeaderHeight or FooterHeight * @param extendHeight extendHeaderHeight or extendFooterHeight */ void onStartAnimator(RefreshLayout layout, int height, int extendHeight); /** * 动画结束 * @param layout RefreshLayout * @param success 数据是否成功刷新或加载 * @return 完成动画所需时间 如果返回 Integer.MAX_VALUE 将取消本次完成事件,继续保持原有状态 */ int onFinish(RefreshLayout layout, boolean success); /** * 手指拖动下拉(会连续多次调用,用于实时控制动画关键帧) * @param percent 下拉的百分比 值 = offset/headerHeight (0 - percent - (headerHeight+extendHeight) / headerHeight ) * @param offset 下拉的像素偏移量 0 - offset - (headerHeight+extendHeight) * @param headerHeight Header的高度 * @param extendHeight Header的扩展高度 */ void onPullingDown(float percent, int offset, int headerHeight, int extendHeight); /** * 手指释放之后的持续动画(会连续多次调用,用于实时控制动画关键帧) * @param percent 下拉的百分比 值 = offset/headerHeight (0 - percent - (headerHeight+extendHeight) / headerHeight ) * @param offset 下拉的像素偏移量 0 - offset - (headerHeight+extendHeight) * @param headerHeight Header的高度 * @param extendHeight Header的扩展高度 */ void onReleasing(float percent, int offset, int headerHeight, int extendHeight); } ~~~ ### 实现 RefreshHeader 接下来我们通过实现一个最简单的经典Header(没有更新时间),来慢慢了解自定义Header的流程。 #### 需求分析 我们的经典Header需要一个标题文本、刷新动画、下拉箭头。由此我们可以选定一个继承LinearLayout并列出成员变量 ~~~java public class ClassicsHeader extends LinearLayout implements RefreshHeader { private TextView mHeaderText;//标题文本 private PathsView mArrowView;//下拉箭头 private ImageView mProgressView;//刷新动画视图 private ProgressDrawable mProgressDrawable;//刷新动画 public ClassicsHeader(Context context) { super(context); setGravity(Gravity.CENTER_HORIZONTAL); mHeaderText = new TextView(context); mProgressDrawable = new ProgressDrawable(); mArrowView = new PathsView(context); mProgressView = new ImageView(context); mProgressView.setImageDrawable(mProgressDrawable); addView(mProgressView); addView(mArrowView, lpProgress); addView(mHeaderText, lpHeaderText); } } ~~~ #### 指定样式 根据我们的常识,经典Header在下拉的时候是贴着列表平移向下冒出,所以我们实现样式直接指定为:平移 ~~~java public class ClassicsHeader extends LinearLayout implements RefreshHeader { @NonNull public View getView() { return this;//真实的视图就是自己,不能返回null } @Override public SpinnerStyle getSpinnerStyle() { return SpinnerStyle.Translate;//指定为平移,不能null } } ~~~ #### 动画开关 接下来我们需要在关键地方对动画进行控制和开启 ~~~java public class ClassicsHeader extends LinearLayout implements RefreshHeader { @Override public void onStartAnimator(RefreshLayout layout, int headHeight, int extendHeight) { mProgressDrawable.start();//开始动画 } @Override public int onFinish(RefreshLayout layout, boolean success) { mProgressDrawable.stop();//停止动画 if (success){ mHeaderText.setText("刷新完成"); } else { mHeaderText.setText("刷新失败"); } return 500;//延迟500毫秒之后再弹回 } } ~~~ #### 状态控制 我们还要在不同的状态控制内部控件的显示和旋转 ~~~java public class ClassicsHeader extends LinearLayout implements RefreshHeader { @Override public void onStateChanged(RefreshLayout refreshLayout, RefreshState oldState, RefreshState newState) { switch (newState) { case None: case PullDownToRefresh: mHeaderText.setText("下拉开始刷新"); mArrowView.setVisibility(VISIBLE);//显示下拉箭头 mProgressView.setVisibility(GONE);//隐藏动画 mArrowView.animate().rotation(0);//还原箭头方向 break; case Refreshing: mHeaderText.setText("正在刷新"); mProgressView.setVisibility(VISIBLE);//显示加载动画 mArrowView.setVisibility(GONE);//隐藏箭头 break; case ReleaseToRefresh: mHeaderText.setText("释放立即刷新"); mArrowView.animate().rotation(180);//显示箭头改为朝上 break; } } } ~~~ #### 最后整合 虽然接口的其他方法我们不用特意去实现,但是方法体还是要声明一下,整合之后完整的代码如下: ~~~java public class ClassicsHeader extends LinearLayout implements RefreshHeader { private TextView mHeaderText;//标题文本 private PathsView mArrowView;//下拉箭头 private ImageView mProgressView;//刷新动画视图 private ProgressDrawable mProgressDrawable;//刷新动画 public ClassicsHeader(Context context) { super(context); initView(context); } public ClassicsHeader(Context context, AttributeSet attrs) { super(context, attrs); this.initView(context); } public ClassicsHeader(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); this.initView(context); } private void initView(Context context) { setGravity(Gravity.CENTER); mHeaderText = new TextView(context); mProgressDrawable = new ProgressDrawable(); mArrowView = new PathsView(context); mProgressView = new ImageView(context); mProgressView.setImageDrawable(mProgressDrawable); mArrowView.parserPaths("M20,12l-1.41,-1.41L13,16.17V4h-2v12.17l-5.58,-5.59L4,12l8,8 8,-8z"); addView(mProgressView, DensityUtil.dp2px(20), DensityUtil.dp2px(20)); addView(mArrowView, DensityUtil.dp2px(20), DensityUtil.dp2px(20)); addView(new View(context), DensityUtil.dp2px(20), DensityUtil.dp2px(20)); addView(mHeaderText, LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT); setMinimumHeight(DensityUtil.dp2px(60)); } @NonNull public View getView() { return this;//真实的视图就是自己,不能返回null } @Override public SpinnerStyle getSpinnerStyle() { return SpinnerStyle.Translate;//指定为平移,不能null } @Override public void onStartAnimator(RefreshLayout layout, int headHeight, int extendHeight) { mProgressDrawable.start();//开始动画 } @Override public int onFinish(RefreshLayout layout, boolean success) { mProgressDrawable.stop();//停止动画 if (success){ mHeaderText.setText("刷新完成"); } else { mHeaderText.setText("刷新失败"); } return 500;//延迟500毫秒之后再弹回 } @Override public boolean isSupportHorizontalDrag() { return false; } @Override public void onStateChanged(RefreshLayout refreshLayout, RefreshState oldState, RefreshState newState) { switch (newState) { case None: case PullDownToRefresh: mHeaderText.setText("下拉开始刷新"); mArrowView.setVisibility(VISIBLE);//显示下拉箭头 mProgressView.setVisibility(GONE);//隐藏动画 mArrowView.animate().rotation(0);//还原箭头方向 break; case Refreshing: mHeaderText.setText("正在刷新"); mProgressView.setVisibility(VISIBLE);//显示加载动画 mArrowView.setVisibility(GONE);//隐藏箭头 break; case ReleaseToRefresh: mHeaderText.setText("释放立即刷新"); mArrowView.animate().rotation(180);//显示箭头改为朝上 break; } } @Override public void onInitialized(RefreshKernel kernel, int height, int extendHeight) { } @Override public void onHorizontalDrag(float percentX, int offsetX, int offsetMax) { } @Override public void onPullingDown(float percent, int offset, int headHeight, int extendHeight) { } @Override public void onReleasing(float percent, int offset, int headHeight, int extendHeight) { } @Override public void setPrimaryColors(int... colors){ } } ~~~ ### 实现 RefreshFooter 具体方法和 RefreshHeader 非常相似,这里就不再演示了 ================================================ FILE: art/md_donationlist.md ================================================ # 捐赠列表 非常感谢列表中的朋友,你们的肯定和支持是我前进的最大动力! |昵称|日期|方式|留言|友情连接| |:---:|:---:|:---:|:---:|:---:| |[pay_join]|2017-9-6|腾讯|👍|| |[pay_join]|2017-9-6|腾讯|👍|| |[未留名]|2017-9-5|微信|支持呢!好厉害大神!| | |[pay_join]|2017-8-29|腾讯|👍|| |[未留名]|2017-8-28|微信|好棒| | |[pay_join]|2017-8-27|腾讯|👍|| |[未留名]|2017-8-24|微信|重要的是支持你| | |[pay_join]|2017-8-24|腾讯|👍|| |[pay_join]|2017-8-23|腾讯|👍|| |[pay_join]|2017-8-20|腾讯|👍|| |[未留名]|2017-8-16|微信|不错,加油| | |[pay_join]|2017-8-15|腾讯|👍|| |[未留名]|2017-8-14|微信|大神,请手下我的膝盖| | |[未留名]|2017-8-13|微信|| | |[未留名]|2017-8-13|微信|| | |[pay_join]|2017-8-11|腾讯|👍|| |[pay_join]|2017-8-10|腾讯|👍|| |[pay_join]|2017-8-10|腾讯|👍|| |[pay_join]|2017-8-9|腾讯|👍|| |[pay_join]|2017-8-4|腾讯||| |[pay_join]|2017-8-3|腾讯||| |[pay_join]|2017-8-2|腾讯||| |[pay_join]|2017-8-2|腾讯||| |[未留名]|2017-8-2|微信|给大神端茶-github|| |[pay_join]|2017-8-1|腾讯||| |[pay_join]|2017-8-1|腾讯||| |[pay_join]|2017-7-30|腾讯||| |[pay_join]|2017-7-29|腾讯||| |[pay_join]|2017-7-27|腾讯||| |[pay_join]|2017-7-27|腾讯||| |□□|2017-7-27|微信|👍👍👍👍👍|| |XiaoJiaqing|2017-7-26|微信|👍|[XiaoJianqing](https://github.com/XiaoJiaqing)| |[未留名]|2017-7-26|阿里|👍| | |[未留名]|2017-7-26|腾讯|大神,请你喝水|| |Bter|2017-7-23|微信|不错的smart|| |[pay_join]|2017-7-23|腾讯||| |[未留名]|2017-7-21|微信|| | |[未留名]|2017-7-19|阿里|| | |[未留名]|2017-7-18|阿里|| | |[未留名]|2017-7-17|微信|| | |[未留名]|2017-7-17|微信|| | |[未留名]|2017-7-15|微信|👍| | |[未留名]|2017-7-15|微信|git小助手给你点赞|| |[未留名]|2017-7-14|微信|👍| | |[未留名]|2017-7-14|微信|| | |[未留名]|2017-7-14|微信|👍| | |[未留名]|2017-7-13|微信|👍| | |剑弑九幽|2017-7-13|阿里|👍|[jianshijiuyou](https://github.com/jianshijiuyou)| |zxy198717|2017-7-13|微信|👍|[zxy198717](https://github.com/zxy198717)| |[未留名]|2017-7-12|阿里|| | |matt|2017-7-12|微信|👍|[addappcn](https://github.com/addappcn)| |[未留名]|2017-7-12|微信|谢谢分享| | |RainliFu|2017-7-12|微信|👍|[RainliFu](https://github.com/RainliFu)| |[未留名]|2017-7-12|微信|Six!Six!Six| | |[未留名]|2017-7-11|微信|👍| | |[未留名]|2017-7-11|微信|感谢你的开源项目| | |sugarya|2017-7-11|微信|👍|[sugarya](https://github.com/sugarya)| |stormzhang|2017-7-11|微信|👍|[stormzhang](https://github.com/stormzhang) |[未留名]|2017-7-10|微信| | | ================================================ FILE: art/md_multitouch.md ================================================ # 多点触摸 多点触摸就是多个手指一起滑动,直接看效果吧~ ![](https://github.com/scwang90/SmartRefreshLayout/raw/master/art/gif_demo_multitouch_1.gif) ![](https://github.com/scwang90/SmartRefreshLayout/raw/master/art/gif_demo_multitouch_2.gif) ================================================ FILE: art/md_property.md ================================================ # 代码示例 ## SmartRefreshLayout java代码设置 ~~~java public class RefreshActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { //下面示例中的值等于默认值 RefreshLayout refreshLayout = (RefreshLayout)findViewById(R.id.refreshLayout); refreshLayout.setPrimaryColorsId(R.color.colorPrimary, android.R.color.white); refreshLayout.setDragRate(0.5f);//显示下拉高度/手指真实下拉高度=阻尼效果 refreshLayout.setReboundDuration(300);//回弹动画时长(毫秒) refreshLayout.setHeaderMaxDragRate(2);//最大显示下拉高度/Header标准高度 refreshLayout.setFooterMaxDragRate(2);//最大显示下拉高度/Footer标准高度 refreshLayout.setHeaderHeight(100);//Header标准高度(显示下拉高度>=标准高度 触发刷新) refreshLayout.setHeaderHeightPx(100);//同上-像素为单位 refreshLayout.setFooterHeight(100);//Footer标准高度(显示上拉高度>=标准高度 触发加载) refreshLayout.setFooterHeightPx(100);//同上-像素为单位 refreshLayout.setEnableRefresh(true);//是否启用下拉刷新功能 refreshLayout.setEnableLoadmore(true);//是否启用上拉加载功能 refreshLayout.setEnableAutoLoadmore(true);//是否启用列表惯性滑动到底部时自动加载更多 refreshLayout.setEnablePureScrollMode(false);//是否启用纯滚动模式 refreshLayout.setEnableNestedScroll(false);//是否启用嵌套滚动 refreshLayout.setEnableOverScrollBounce(true);//是否启用越界回弹 refreshLayout.setEnableScrollContentWhenLoaded(true);//是否在加载完成时滚动列表显示新的内容 refreshLayout.setEnableHeaderTranslationContent(true);//是否下拉Header的时候向下平移列表或者内容 refreshLayout.setEnableFooterTranslationContent(true);//是否上啦Footer的时候向上平移列表或者内容 refreshLayout.setEnableLoadmoreWhenContentNotFull(true);//是否在列表不满一页时候开启上拉加载功能 refreshLayout.setDisableContentWhenRefresh(false);//是否在刷新的时候禁止列表的操作 refreshLayout.setDisableContentWhenLoading(false);//是否在加载的时候禁止列表的操作 refreshLayout.setOnMultiPurposeListener(new OnMultiPurposeListener());//设置多功能监听器 refreshLayout.setScrollBoundaryDecider(new ScrollBoundaryDecider());//设置滚动边界判断 refreshLayout.setRefreshHeader(new ClassicsHeader(this));//设置Header refreshLayout.setRefreshFooter(new ClassicsFooter(this));//设置Footer refreshLayout.autoRefresh();//自动刷新 refreshLayout.autoLoadmore();//自动加载 refreshLayout.autoRefresh(400);//延迟400毫秒后自动刷新 refreshLayout.autoLoadmore(400);//延迟400毫秒后自动加载 refreshlayout.finishRefresh();//结束刷新 refreshlayout.finishLoadmore();//结束加载 refreshlayout.finishRefresh(3000);//延迟3000毫秒后结束刷新 refreshlayout.finishLoadmore(3000);//延迟3000毫秒后结束加载 refreshlayout.finishRefresh(false);//结束刷新(刷新失败) refreshlayout.finishLoadmore(false);//结束加载(加载失败) } } ~~~ xml代码设置 ~~~xml ~~~ ## ClassicsHeader java代码设置 ~~~java public class RefreshActivity extends Activity { static { ClassicsHeader.REFRESH_HEADER_PULLDOWN = "下拉可以刷新"; ClassicsHeader.REFRESH_HEADER_REFRESHING = "正在刷新..."; ClassicsHeader.REFRESH_HEADER_LOADING = "正在加载..."; ClassicsHeader.REFRESH_HEADER_RELEASE = "释放立即刷新"; ClassicsHeader.REFRESH_HEADER_FINISH = "刷新完成"; ClassicsHeader.REFRESH_HEADER_FAILED = "刷新失败"; ClassicsHeader.REFRESH_HEADER_LASTTIME = "上次更新 M-d HH:mm"; } @Override protected void onCreate(Bundle savedInstanceState) { //下面示例中的值等于默认值 ClassicsHeader header = (ClassicsHeader)findViewById(R.id.header); header.setAccentColor(android.R.color.white);//设置强调颜色 header.setPrimaryColor(R.color.colorPrimary);//设置主题颜色 header.setTextSizeTitle(16);//设置标题文字大小(sp单位) header.setTextSizeTitle(16, TypedValue.COMPLEX_UNIT_SP);//同上 header.setTextSizeTime(10);//设置时间文字大小(sp单位) header.setTextSizeTime(10, TypedValue.COMPLEX_UNIT_SP);//同上 header.setTextTimeMarginTop(10);//设置时间文字的上边距(dp单位) header.setTextTimeMarginTopPx(10);//同上-像素单位 header.setEnableLastTime(true);//是否显示时间 header.setFinishDuration(500);//设置刷新完成显示的停留时间 header.setDrawableSize(20);//同时设置箭头和图片的大小(dp单位) header.setDrawableArrowSize(20);//设置箭头的大小(dp单位) header.setDrawableProgressSize(20);//设置图片的大小(dp单位) header.setDrawableMarginRight(20);//设置图片和箭头和文字的间距(dp单位) header.setDrawableSizePx(20);//同上-像素单位 header.setDrawableArrowSizePx(20);//同上-像素单位 header.setDrawableProgressSizePx(20);//同上-像素单位 header.setDrawableMarginRightPx(20);//同上-像素单位 header.setArrowBitmap(bitmap);//设置箭头位图 header.setArrowDrawable(drawable);//设置箭头图片 header.setArrowResource(R.drawable.ic_arrow);//设置箭头资源 header.setProgressBitmap(bitmap);//设置图片位图 header.setProgressDrawable(drawable);//设置图片 header.setProgressResource(R.drawable.ic_progress);//设置图片资源 header.setTimeFormat(new DynamicTimeFormat("上次更新 %s"));//设置时间格式化 header.setSpinnerStyle(SpinnerStyle.Translate);//设置状态(不支持:MatchLayout) } } ~~~ xml代码设置 ~~~xml ~~~ ## ClassicsFooter java代码设置 ~~~java public class RefreshActivity extends Activity { static { ClassicsFooter.REFRESH_FOOTER_PULLUP = "上拉加载更多"; ClassicsFooter.REFRESH_FOOTER_RELEASE = "释放立即加载"; ClassicsFooter.REFRESH_FOOTER_REFRESHING = "正在刷新..."; ClassicsFooter.REFRESH_FOOTER_LOADING = "正在加载..."; ClassicsFooter.REFRESH_FOOTER_FINISH = "加载完成"; ClassicsFooter.REFRESH_FOOTER_FAILED = "加载失败"; ClassicsFooter.REFRESH_FOOTER_ALLLOADED = "全部加载完成"; } @Override protected void onCreate(Bundle savedInstanceState) { //下面示例中的值等于默认值 ClassicsFooter footer = (ClassicsFooter)findViewById(R.id.footer); footer.setAccentColor(android.R.color.white);//设置强调颜色 footer.setPrimaryColor(R.color.colorPrimary);//设置主题颜色 footer.setTextSizeTitle(16);//设置标题文字大小(sp单位) footer.setTextSizeTitle(16, TypedValue.COMPLEX_UNIT_SP);//同上 footer.setFinishDuration(500);//设置刷新完成显示的停留时间 footer.setDrawableSize(20);//同时设置箭头和图片的大小(dp单位) footer.setDrawableArrowSize(20);//设置箭头的大小(dp单位) footer.setDrawableProgressSize(20);//设置图片的大小(dp单位) footer.setDrawableMarginRight(20);//设置图片和箭头和文字的间距(dp单位) footer.setDrawableSizePx(20);//同上-像素单位 footer.setDrawableArrowSizePx(20);//同上-像素单位 footer.setDrawableProgressSizePx(20);//同上-像素单位 footer.setDrawableMarginRightPx(20);//同上-像素单位 footer.setArrowBitmap(bitmap);//设置箭头位图 footer.setArrowDrawable(drawable);//设置箭头图片 footer.setArrowResource(R.drawable.ic_arrow);//设置箭头资源 footer.setProgressBitmap(bitmap);//设置图片位图 footer.setProgressDrawable(drawable);//设置图片 footer.setProgressResource(R.drawable.ic_progress);//设置图片资源 footer.setSpinnerStyle(SpinnerStyle.Translate);//设置状态(不支持:MatchLayout) } } ~~~ xml代码设置 ~~~xml ~~~ # 属性表格 ## Attributes |name|format|description| |:---:|:---:|:---:| |srlPrimaryColor|color|主题颜色| |srlAccentColor|color|强调颜色| |srlReboundDuration|integer|释放后回弹动画时长(默认250毫秒)| |srlHeaderHeight|dimension|Header的标准高度(dp)| |srlFooterHeight|dimension|Footer的标准高度(dp)| |srlDragRate|float|显示拖动高度/真实拖动高度(默认0.5,阻尼效果)| |srlHeaderMaxDragRate|float|Header最大拖动高度/Header标准高度(默认2,要求>=1)| |srlFooterMaxDragRate|float|Footer最大拖动高度/Footer标准高度(默认2,要求>=1)| |srlEnableRefresh|boolean|是否开启下拉刷新功能(默认true)| |srlEnableLoadmore|boolean|是否开启加上拉加载功能(默认false-智能开启)| |srlEnableAutoLoadmore|boolean|是否监听列表惯性滚动到底部时触发加载事件(默认true)| |srlEnableHeaderTranslationContent|boolean|拖动Header的时候是否同时拖动内容(默认true)| |srlEnableFooterTranslationContent|boolean|拖动Footer的时候是否同时拖动内容(默认true)| |srlEnablePreviewInEditMode|boolean|是否在编辑模式时显示预览效果(默认true)| |srlEnablePureScrollMode|boolean|是否开启纯滚动模式(默认false-开启时只支持一个子视图)| |srlEnableOverScrollBounce|boolean|设置是否开启越界回弹功能(默认true)| |srlEnableNestedScrolling|boolean|是否开启嵌套滚动NestedScrolling(默认false-智能开启)| |srlEnableScrollContentWhenLoaded|boolean|是否在加载完成之后滚动内容显示新数据(默认-true)| |srlEnableLoadmoreWhenContentNotFull|boolean|在内容不满一页的时候,是否可以上拉加载更多(默认-false)| |srlDisableContentWhenRefresh|boolean|是否在刷新的时候禁止内容的一切手势操作(默认false)| |srlDisableContentWhenLoading|boolean|是否在加载的时候禁止内容的一切手势操作(默认false)| |srlFixedHeaderViewId|id|指定固定的视图Id| |srlFixedFooterViewId|id|指定固定的视图Id| ## Method |name|format|description| |:---:|:---:|:---:| |setPrimaryColors|colors|主题\强调颜色| |setPrimaryColorsId|colors|主题\强调颜色资源Id| |setReboundDuration|integer|释放后回弹动画时长(默认250毫秒)| |setHeaderHeight|dimension|Header的标准高度(px/dp 两个版本)| |setFooterHeight|dimension|Footer的标准高度(px/dp 两个版本)| |setDragRate|float|显示拖动高度/真实拖动高度(默认0.5,阻尼效果)| |setHeaderMaxDragRate|float|Header最大拖动高度/Header标准高度(默认2,要求>=1)| |setFooterMaxDragRate|float|Footer最大拖动高度/Footer标准高度(默认2,要求>=1)| |setEnableRefresh|boolean|是否开启下拉刷新功能(默认true)| |setEnableLoadmore|boolean|是否开启加上拉加载功能(默认false-智能开启)| |setEnableHeaderTranslationContent|boolean|拖动Header的时候是否同时拖动内容(默认true)| |setEnableFooterTranslationContent|boolean|拖动Footer的时候是否同时拖动内容(默认true)| |setEnableAutoLoadmore|boolean|是否监听列表惯性滚动到底部时触发加载事件(默认true)| |setEnablePureScrollMode|boolean|是否开启纯滚动模式(默认false-开启时只支持一个子视图)| |setEnableOverScrollBounce|boolean|设置是否开启越界回弹功能(默认true)| |setEnableNestedScrolling|boolean|是否开启嵌套滚动NestedScrolling(默认false-智能开启)| |setEnableScrollContentWhenLoaded|boolean|是否在加载完成之后滚动内容显示新数据(默认-true)| |setEnableLoadmoreWhenContentNotFull|boolean|在内容不满一页的时候,是否可以上拉加载更多(默认-false)| |setDisableContentWhenRefresh|boolean|是否在刷新的时候禁止内容的一切手势操作(默认false)| |setDisableContentWhenLoading|boolean|是否在加载的时候禁止内容的一切手势操作(默认false)| |setReboundInterpolator|Interpolator|设置回弹动画的插值器(默认减速)| |setRefreshHeader|RefreshHeader|设置指定的Header(默认贝塞尔雷达)| |setRefreshFooter|RefreshFooter|设置指定的Footer(默认球脉冲)| |setOnRefreshListener|OnRefreshListener|设置刷新监听器(默认3秒后关刷新)| |setOnLoadmoreListener|OnLoadmoreListener|设置加载监听器(默认3秒后关加载)| |setOnRefreshLoadmoreListener|OnRefreshLoadmoreListener|同时设置上面两个监听器| |setOnMultiPurposeListener|OnMultiPurposeListener|设置多功能监听器| |setLoadmoreFinished|boolean|设置全部数据加载完成,之后不会触发加载事件| |setScrollBoundaryDecider|boundary|设置滚动边界判断| |finishRefresh|(int delayed)|完成刷新,结束刷新动画| |finishLoadmore|(int delayed)|完成加载,结束加载动画| |finishRefresh|(boolean success)|完成刷新,并设置是否成功| |finishLoadmore|(boolean success)|完成加载,并设置是否成功| |getRefreshHeader|RefreshHeader|获取Header| |getRefreshFooter|RefreshFooter|获取Footer| |getState|RefreshState|获取当前状态| |isRefreshing|boolean|是否正在刷新| |isLoading|boolean|是否正在加载| |autoRefresh|(int delayed)|触发自动刷新| |autoLoadmore|(int delayed)|触发自动加载| ## Header-Attributes |name|format|description| |:---:|:---:|:---:| |srlPrimaryColor|color|主题颜色| |srlAccentColor|color|强调颜色| |srlDrawableArrow|drawable|箭头图片| |srlDrawableProgress|drawable|转动图片| |srlClassicsSpinnerStyle|enum|变换样式:Translate(平行移动)、Scale(拉伸形变)、FixedBehind(固定在背后)| |srlSpinnerStyle|enum|变换样式:srlClassicsSpinnerStyle的全部、FixedFront(固定在前面或全屏)| |srlFinishDuration|int|动画结束时,显示完成状态停留的时间(毫秒)| |srlEnableLastTime|boolean|是否显示上次更新时间(默认true)| |srlDrawableMarginRight|dimension|图片相对右边文字的距离(默认20dp)| |srlTextTimeMarginTop|dimension|更新时间相对上面标题的距离(默认2dp)| |srlTextSizeTitle|dimension|标题文字大小(默认16sp)| |srlTextSizeTime|dimension|时间文字大小(默认12sp)| ## Header-Method |name|format|description| |:---:|:---:|:---:| |setPrimaryColor|color|主题颜色| |setAccentColor|color|强调颜色| |setArrowDrawable|drawable|设置箭头图片| |setProgressDrawable|drawable|设置转动图片| |setArrowBitmap|bitmap|设置箭头图片| |setProgressBitmap|bitmap|设置转动图片| |setArrowResource|int|设置箭头图片| |setProgressResource|int|设置转动图片| |setSpinnerStyle|enum|变换样式:参考属性srlSpinnerStyle| |setClassicsSpinnerStyle|enum|变换样式:参考属性srlClassicsSpinnerStyle| |setFinishDuration|int|设置动画结束时,显示完成状态停留的时间(毫秒)| |setEnableLastTime|boolean|是否显示上次更新时间(默认true)| |setTextSizeTitle|dimension|标题文字大小(默认16sp)| |setTextSizeTime|dimension|时间文字大小(默认12sp)| ================================================ FILE: art/md_smart.md ================================================ # 智能之处 智能是什么玩意?有什么用? >智能主要体现 SmartRefreshLayout 对未知布局的自动识别上,这样可以让我们更高效的实现我们所需的功能,也可以实现一些非寻常的功能。 >下面通过**自定义Header** 和 **嵌套Layout作为内容** 来了解 SmartRefreshLayout 的智能之处。 ## 自定义Header 我们来看这一下这个伪代码例子: ```xml ``` 在Android Studio 中的预览效果图 ![](https://github.com/scwang90/SmartRefreshLayout/raw/master/art/jpg_preview_xml_define.jpg) 对比代码和我们预想的一样,那我们来对代码做一些改动,ClassicsHeader换成一个简单的TextView,看看会发生什么? ```xml ``` 在Android Studio 中的预览效果图 和 运行效果图 ![](https://github.com/scwang90/SmartRefreshLayout/raw/master/art/jpg_preview_textheader.jpg) ![](https://github.com/scwang90/SmartRefreshLayout/raw/master/art/gif_preview_textheader.gif) 这时发现我们我们替换的 TextView 自动就变成了Header,只是它还不会动。要动起来?那么太简单啦,网上随便一搜索就一大堆的 gif 。如这里:[拖拖拖 ~~垃机C4D](http://www.ui.cn/detail/255143.html),类似的我们还可以找到很多,又如:[环游东京30天:GIF版旅行指南](http://www.xueui.cn/appreciate/motion-design/gif-version-of-tokyo-travel-guide.html) 那我们就选择 [环游东京30天:GIF版旅行指南](http://www.xueui.cn/appreciate/motion-design/gif-version-of-tokyo-travel-guide.html) 中的这张: ![](http://78rbeb.com1.z0.glb.clouddn.com/wp-content/uploads/2017/05/201705031493854833.gif) 接着我们来改代码: ``` compile 'pl.droidsonroids.gif:android-gif-drawable:1.2.3'//一个开源gif控件 ``` ```xml ``` 在 Android Studio 中的预览效果图 和 运行效果图 ![](https://github.com/scwang90/SmartRefreshLayout/raw/master/art/jpg_preview_gifheader.jpg) ![](https://github.com/scwang90/SmartRefreshLayout/raw/master/art/gif_practive_repast.gif) 哈哈!一行Java代码都不用写,就完成了一个自定义的Header ## 嵌套Layout作为内容 如果boos要求在列表的前面**固定**一个广告条怎么办?这好办呀,一般我们会开开心心的下下这样的代码: ```xml ``` 但是在运行下拉刷新的时候,我们发现 Header是在广告条之下的,看着会别扭~,其实我们可以试试另一种方式,把广告条写到 RefreshLayout内部,看看会发生什么? ```xml ``` 由于伪代码过于简单,而且运行效果过于丑陋,这里还是贴出在实际项目中的实际情况吧~ ![](https://github.com/scwang90/SmartRefreshLayout/raw/master/art/gif_practive_feedlist.gif) ![](https://github.com/scwang90/SmartRefreshLayout/raw/master/art/gif_practive_smart.gif) 我们注意看右边的图,仔细观察手指触摸的位置和下拉效果。可以看到在**列表已经滚动到中部时,轻微下拉列表是不会触发刷新的,但是如果是触摸固定的布局,则可以触发下拉**。从这里可以看出 SmartRefreshLayout 对滚动边界的判断是动态的,智能的!当然如果 SmartRefreshLayout 的智能还是不能满足你,可以通过 setListener 自己实现滚动边界的判断,更为准确! ================================================ FILE: art/md_update.md ================================================ # 更新日志 ## V 1.0.4 (开发中) >添加:类似淘宝二楼的二级下拉刷新 >添加:srlEnableBounceShowHeaderAndFooter 属性和对应的set方法,控制越界回弹时候是否显示Header和Footer ## V 1.0.3 >添加:下拉和上拉时,支持多点触摸,手势不冲突 >添加:当 内容视图不满一页时,默认不能上拉加载更多,不过必要时,通过设置还是可以上拉的 >添加:为 Heaer和Footer添加拖动时水平方向坐标 x,实现左右拖动Header的效果 >添加:为 Refreshlayout 添加多点触摸支持,在多个手指触摸式不会发生冲突,并且随意拖动 >添加:为 Refreshlayout 添加 EnableLoadmoreWhenContentNotFull 功能 >添加:为 Refreshlayout 添加 srlEnabledNestedScroll 属性 和 srlEnabledNestedScroll 方法 >添加:为 RefreshHeader 添加 srlEnableHorizontalDrag 属性 和 setEnableHorizontalDrag 方法 >添加:为 ClassicsHeader 添加 srlEnableLastTime 属性 和 setEnableLastTime 方法 控制时间显示 >添加:为 ClassicsHeader 添加 srlDrawableArrow 属性 和 setArrowResource 方法 改变箭头图片 >添加:为 ClassicsHeader 添加 srlProgressDrawable 属性 和 setProgressResource 改变转动图片 >添加:为 ClassicsHeader 添加 srlTextSizeTime 属性 和 setTextSizeTime 方法 设置字体大小 >添加:为 ClassicsHeader 添加 srlTextTimeMarginTop 属性 时间文字的上间距 >添加:为 ClassicsFooter 添加 srlDrawableArrow 属性 和 setArrowResource 方法 改变箭头图片 >添加:为 ClassicsFooter 添加 srlDrawableProgress 属性 和 setProgressResource 改变转动图片 >添加:为 ClassicsHeader 和 ClassicsFooter 添加 srlFinishDuration 属性 和 setFinishDuration 方法 >添加:为 ClassicsHeader 和 ClassicsFooter 添加 srlDrawableMarginRight 属性 设置图片间距 >添加:为 ClassicsHeader 和 ClassicsFooter 添加 srlTextSizeTitle 属性 和 setTextSizeTitle 方法 >添加:为 ClassicsHeader 和 ClassicsFooter 添加 srlDrawableSize 属性 和 setDrawableSize 方法 >添加:为 ClassicsHeader 和 ClassicsFooter 添加 srlDrawableArrowSize 属性 和 setDrawableArrowSize 方法 >添加:为 ClassicsHeader 和 ClassicsFooter 添加 srlDrawableProgressSize 属性 和 setDrawableProgressSize 方法 >修改:改 EnableLoadMore 由默认true->变为false,并增加智能开启功能 >修改:改 EnableNestedScrolling 由默认true->变为false,并增加智能开启功能 >修复:在 ClassicsFooter 加载失败时,显示成了加载完成的错误 >修复:正在刷新时,向上拖动导致的栈溢出崩溃 >修复:autoRefresh(0) 的 延时为没有延时 >修复:StaggeredGridLayoutManager 导致的 autoLoadmore 无效 >修复:列表监听滚动无效的问题 >修复:内存泄漏问题 >修复:隐式去除对 support-design 支持包的依赖 ## V 1.0.2 >添加:AbsListView 和 RecyclerView 的越界回弹 >添加:srlFixedHeaderViewId 属性,指定固定的视图Id >添加:srlFixedFooterViewId 属性,指定固定的视图Id >添加:srlEnablePureScrollMode 属性,是否开启纯滚动模式 >添加:srlEnableNestedScrolling 属性,是否开启嵌套滚动NestedScrolling >添加:srlEnableScrollContentWhenLoaded 属性,是否在加载完成之后滚动内容显示新数据 >添加:setScrollBoundaryDecider 方法,设置滚动边界判断 >添加:finishRefresh(boolean success);方法,完成刷新,并设置是否成功 >添加:finishLoadmore(boolean success);方法,完成加载,并设置是否成功 >修复:DeliveryHeader,DropboxHeader 在API-17以下显示不全的问题 ## V 1.0.1 >添加:srlEnableAutoLoadmore 属性 >修改:srlExtendHeaderRate 属性为:srlHeaderMaxDragRate >修改:srlExtendFooterRate 属性为:srlFooterMaxDragRate >修复:DeliveryHeader,DropboxHeader 在API-21以下崩溃的问题 ## V 1.0.0 >添加:DeliveryHeader,DropboxHeader ================================================ FILE: bintrayUpload.bat ================================================ gradlew clean build bintrayUpload -PbintrayUser=scwang90 -PbintrayKey=%1 -PdryRun=false ================================================ FILE: build.gradle ================================================ // Top-level build file where you can add configuration options common to all sub-projects/modules. buildscript { repositories { jcenter() } dependencies { classpath 'com.android.tools.build:gradle:2.3.2' // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files classpath 'com.novoda:bintray-release:0.4.0' } } allprojects { repositories { jcenter() } tasks.withType(Javadoc) { options{ encoding "UTF-8" charSet 'UTF-8' links "http://docs.oracle.com/javase/7/docs/api" } } } task clean(type: Delete) { delete rootProject.buildDir } ================================================ FILE: refresh-footer/.gitignore ================================================ /build ================================================ FILE: refresh-footer/build.gradle ================================================ apply plugin: 'com.android.library' //apply plugin: 'com.novoda.bintray-release' //apply plugin: 'me.tatarka.retrolambda' android { compileSdkVersion 26 buildToolsVersion "26.0.1" defaultConfig { minSdkVersion 12 targetSdkVersion 26 versionCode 1 versionName "1.0" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } lintOptions { abortOnError false } } dependencies { compile fileTree(include: ['*.jar'], dir: 'libs') androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', { exclude group: 'com.android.support', module: 'support-annotations' }) testCompile 'junit:junit:4.12' provided 'com.android.support:support-compat:26.0.0-alpha1' provided project(':refresh-layout') } //publish { // userOrg = 'scwang90' // groupId = 'com.scwang.smartrefresh' // artifactId = 'SmartRefreshFooter' // version = '1.0.0-alpha-1' // description = 'Some delicate footers of SmartRefreshLayout' // website = "https://github.com/scwang90/${rootProject.name}" //} ================================================ FILE: refresh-footer/proguard-rules.pro ================================================ # Add project specific ProGuard rules here. # By default, the flags in this file are appended to flags specified # in E:\Android\android-sdk/tools/proguard/proguard-android.txt # You can edit the include path and order by changing the proguardFiles # directive in build.gradle. # # For more details, see # http://developer.android.com/guide/developing/tools/proguard.html # Add any project specific keep options here: # If your project uses WebView with JS, uncomment the following # and specify the fully qualified class name to the JavaScript interface # class: #-keepclassmembers class fqcn.of.javascript.interface.for.webview { # public *; #} # 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: refresh-footer/src/androidTest/java/com/scwang/smartrefresh/footer/ExampleInstrumentedTest.java ================================================ package com.scwang.smartrefreshfooter; import android.content.Context; import android.support.test.InstrumentationRegistry; import android.support.test.runner.AndroidJUnit4; import org.junit.Test; import org.junit.runner.RunWith; import static org.junit.Assert.*; /** * Instrumentation test, which will execute on an Android device. * * @see Testing documentation */ @RunWith(AndroidJUnit4.class) public class ExampleInstrumentedTest { @Test public void useAppContext() throws Exception { // Context of the app under test. Context appContext = InstrumentationRegistry.getTargetContext(); assertEquals("com.scwang.smartrefreshfooter.test", appContext.getPackageName()); } } ================================================ FILE: refresh-footer/src/main/AndroidManifest.xml ================================================ ================================================ FILE: refresh-footer/src/main/res/values/attrs.xml ================================================ ================================================ FILE: refresh-footer/src/main/res/values/strings.xml ================================================ SmartRefreshFooter ================================================ FILE: refresh-footer/src/test/java/com/scwang/smartrefresh/footer/ExampleUnitTest.java ================================================ package com.scwang.smartrefreshfooter; import org.junit.Test; import static org.junit.Assert.*; /** * Example local unit test, which will execute on the development machine (host). * * @see Testing documentation */ public class ExampleUnitTest { @Test public void addition_isCorrect() throws Exception { assertEquals(4, 2 + 2); } } ================================================ FILE: refresh-header/.gitignore ================================================ /build ================================================ FILE: refresh-header/build.gradle ================================================ apply plugin: 'com.android.library' apply plugin: 'com.novoda.bintray-release' android { compileSdkVersion 26 buildToolsVersion "26.0.1" defaultConfig { minSdkVersion 12 targetSdkVersion 26 versionCode 1 versionName "1.0" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } lintOptions { abortOnError false } } dependencies { compile fileTree(include: ['*.jar'], dir: 'libs') androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', { exclude group: 'com.android.support', module: 'support-annotations' }) testCompile 'junit:junit:4.12' provided 'com.android.support:support-compat:26.0.0-alpha1' provided project(':refresh-layout') } publish { userOrg = 'scwang90' groupId = 'com.scwang.smartrefresh' artifactId = 'SmartRefreshHeader' version = '1.0.4-alpha-0' description = 'Some delicate headers of SmartRefreshLayout' website = "https://github.com/scwang90/${rootProject.name}" } ================================================ FILE: refresh-header/proguard-rules.pro ================================================ # Add project specific ProGuard rules here. # By default, the flags in this file are appended to flags specified # in E:\Android\android-sdk/tools/proguard/proguard-android.txt # You can edit the include path and order by changing the proguardFiles # directive in build.gradle. # # For more details, see # http://developer.android.com/guide/developing/tools/proguard.html # Add any project specific keep options here: # If your project uses WebView with JS, uncomment the following # and specify the fully qualified class name to the JavaScript interface # class: #-keepclassmembers class fqcn.of.javascript.interface.for.webview { # public *; #} # 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: refresh-header/src/androidTest/java/com/scwang/smartrefresh/header/ExampleInstrumentedTest.java ================================================ package com.scwang.smartrefresh.header; import android.content.Context; import android.support.test.InstrumentationRegistry; import android.support.test.runner.AndroidJUnit4; import org.junit.Test; import org.junit.runner.RunWith; import static org.junit.Assert.*; /** * Instrumentation test, which will execute on an Android device. * * @see Testing documentation */ @RunWith(AndroidJUnit4.class) public class ExampleInstrumentedTest { @Test public void useAppContext() throws Exception { // Context of the app under test. Context appContext = InstrumentationRegistry.getTargetContext(); assertEquals("com.scwang.smartrefresh.header.test", appContext.getPackageName()); } } ================================================ FILE: refresh-header/src/main/AndroidManifest.xml ================================================ ================================================ FILE: refresh-header/src/main/java/com/scwang/smartrefresh/header/CircleHeader.java ================================================ package com.scwang.smartrefresh.header; import android.animation.ValueAnimator; import android.content.Context; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.Path; import android.graphics.RectF; import android.os.Build; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.annotation.RequiresApi; import android.util.AttributeSet; import android.view.View; import android.view.animation.AccelerateInterpolator; import android.view.animation.DecelerateInterpolator; import com.scwang.smartrefresh.layout.api.RefreshHeader; import com.scwang.smartrefresh.layout.api.RefreshKernel; import com.scwang.smartrefresh.layout.api.RefreshLayout; import com.scwang.smartrefresh.layout.constant.RefreshState; import com.scwang.smartrefresh.layout.constant.SpinnerStyle; import com.scwang.smartrefresh.layout.util.DensityUtil; /** * CircleRefresh * https://github.com/tuesda/CircleRefreshLayout * Created by zhanglei on 15/7/18. */ public class CircleHeader extends View implements RefreshHeader { // private static final int DURATION_FINISH = 800; //动画时长 private Path mPath; private Paint mBackPaint; private Paint mFrontPaint; private Paint mOuterPaint; private float mWaveHeight; private float mHeadHeight; private float mSpringRatio; private float mFinishRatio; private RefreshState mState; private float mBollY;//弹出球体的Y坐标 private boolean mShowBoll;//是否显示中心球体 private boolean mShowBollTail;//是否显示球体拖拽的尾巴 private boolean mShowOuter; private float mBollRadius;//球体半径 private int mRefreshStop = 90; private int mRefreshStart = 90; private boolean mOuterIsStart = true; private static final int TARGET_DEGREE = 270; // // public CircleHeader(Context context) { super(context, null, 0); initView(context, null); } public CircleHeader(Context context, AttributeSet attrs) { super(context, attrs, 0); initView(context, attrs); } public CircleHeader(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); initView(context, attrs); } @RequiresApi(Build.VERSION_CODES.LOLLIPOP) public CircleHeader(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); initView(context, attrs); } private void initView(Context context, AttributeSet attrs) { setMinimumHeight(DensityUtil.dp2px(100)); mBackPaint = new Paint(); mBackPaint.setColor(0xff11bbff); mBackPaint.setAntiAlias(true); mFrontPaint = new Paint(); mFrontPaint.setColor(0xffffffff); mFrontPaint.setAntiAlias(true); mOuterPaint = new Paint(); mOuterPaint.setAntiAlias(true); mOuterPaint.setColor(0xffffffff); mOuterPaint.setStyle(Paint.Style.STROKE); mOuterPaint.setStrokeWidth(DensityUtil.dp2px(2f)); mPath = new Path(); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { setMeasuredDimension(resolveSize(getSuggestedMinimumWidth(), widthMeasureSpec), resolveSize(getSuggestedMinimumHeight(), heightMeasureSpec)); } // // @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); if (isInEditMode()) { mShowBoll = true; mShowOuter = true; mHeadHeight = getHeight(); mRefreshStop = 270; mBollY = mHeadHeight / 2; mBollRadius = mHeadHeight / 6; } int viewWidth = getWidth(); int viewHeight = getHeight(); drawWave(canvas, viewWidth, viewHeight); drawSpringUp(canvas, viewWidth); drawBoll(canvas, viewWidth); drawOuter(canvas, viewWidth); drawFinish(canvas, viewWidth); } private void drawWave(Canvas canvas, int viewWidth, int viewHeight) { float baseHeight = Math.min(mHeadHeight, viewHeight); if (mWaveHeight != 0) { mPath.reset(); mPath.lineTo(viewWidth, 0); mPath.lineTo(viewWidth, baseHeight); mPath.quadTo(viewWidth / 2, baseHeight + mWaveHeight * 2, 0, baseHeight); mPath.close(); canvas.drawPath(mPath, mBackPaint); } else { canvas.drawRect(0, 0, viewWidth, baseHeight, mBackPaint); } } private void drawSpringUp(Canvas canvas, int viewWidth) { if (mSpringRatio > 0) { float leftX = (viewWidth / 2 - 4 * mBollRadius + mSpringRatio * 3 * mBollRadius); if (mSpringRatio < 0.9) { mPath.reset(); mPath.moveTo(leftX, mBollY); mPath.quadTo(viewWidth / 2, mBollY - mBollRadius * mSpringRatio * 2, viewWidth - leftX, mBollY); canvas.drawPath(mPath, mFrontPaint); } else { canvas.drawCircle(viewWidth / 2, mBollY, mBollRadius, mFrontPaint); } } } private void drawBoll(Canvas canvas, int viewWidth) { if (mShowBoll) { canvas.drawCircle(viewWidth / 2, mBollY, mBollRadius, mFrontPaint); drawBollTail(canvas, viewWidth, (mHeadHeight + mWaveHeight) / mHeadHeight); } } private void drawBollTail(Canvas canvas, int viewWidth, float fraction) { if (mShowBollTail) { final float bottom = mHeadHeight + mWaveHeight; final float starty = mBollY + mBollRadius * fraction / 2; final float startx = viewWidth / 2 + (float) Math.sqrt(mBollRadius * mBollRadius * (1 - fraction * fraction / 4)); final float bezier1x = (viewWidth / 2 + (mBollRadius * 3 / 4) * (1 - fraction)); final float bezier2x = bezier1x + mBollRadius; mPath.reset(); mPath.moveTo(startx, starty); mPath.quadTo(bezier1x, bottom, bezier2x, bottom); mPath.lineTo(viewWidth - bezier2x, bottom); mPath.quadTo(viewWidth - bezier1x, bottom, viewWidth - startx, starty); canvas.drawPath(mPath, mFrontPaint); } } private void drawOuter(Canvas canvas, int viewWidth) { if (mShowOuter) { float outerR = mBollRadius + mOuterPaint.getStrokeWidth() * 2; mRefreshStart += mOuterIsStart ? 3 : 10; mRefreshStop += mOuterIsStart ? 10 : 3; mRefreshStart = mRefreshStart % 360; mRefreshStop = mRefreshStop % 360; int swipe = mRefreshStop - mRefreshStart; swipe = swipe < 0 ? swipe + 360 : swipe; canvas.drawArc(new RectF(viewWidth / 2 - outerR, mBollY - outerR, viewWidth / 2 + outerR, mBollY + outerR), mRefreshStart, swipe, false, mOuterPaint); if (swipe >= TARGET_DEGREE) { mOuterIsStart = false; } else if (swipe <= 10) { mOuterIsStart = true; } invalidate(); } } private void drawFinish(Canvas canvas, int viewWidth) { if (mFinishRatio > 0) { int beforeColor = mOuterPaint.getColor(); if (mFinishRatio < 0.3) { canvas.drawCircle(viewWidth / 2, mBollY, mBollRadius, mFrontPaint); int outerR = (int) (mBollRadius + mOuterPaint.getStrokeWidth() * 2 * (1+mFinishRatio / 0.3f)); int afterColor = Color.argb((int) (0xff * (1 - mFinishRatio / 0.3f)), Color.red(beforeColor), Color.green(beforeColor), Color.blue(beforeColor)); mOuterPaint.setColor(afterColor); canvas.drawArc(new RectF(viewWidth / 2 - outerR, mBollY - outerR, viewWidth / 2 + outerR, mBollY + outerR), 0, 360, false, mOuterPaint); } mOuterPaint.setColor(beforeColor); if (mFinishRatio >= 0.3 && mFinishRatio < 0.7) { float fraction = (mFinishRatio - 0.3f) / 0.4f; mBollY = (int) (mHeadHeight / 2 + (mHeadHeight - mHeadHeight / 2) * fraction); canvas.drawCircle(viewWidth / 2, mBollY, mBollRadius, mFrontPaint); if (mBollY >= mHeadHeight - mBollRadius * 2) { mShowBollTail = true; drawBollTail(canvas, viewWidth, fraction); } mShowBollTail = false; } if (mFinishRatio >= 0.7 && mFinishRatio <= 1) { float fraction = (mFinishRatio - 0.7f) / 0.3f; int leftX = (int) (viewWidth / 2 - mBollRadius - 2 * mBollRadius * fraction); mPath.reset(); mPath.moveTo(leftX, mHeadHeight); mPath.quadTo(viewWidth / 2, mHeadHeight - (mBollRadius * (1 - fraction)), viewWidth - leftX, mHeadHeight); canvas.drawPath(mPath, mFrontPaint); } } } // // @Override public void onInitialized(RefreshKernel kernel, int height, int extendHeight) { } @Override public boolean isSupportHorizontalDrag() { return false; } @Override public void onHorizontalDrag(float percentX, int offsetX, int offsetMax) { } @Override public void onPullingDown(float percent, int offset, int headHeight, int extendHeight) { mHeadHeight = headHeight; mWaveHeight = Math.max(offset - headHeight, 0) * .8f; } @Override public void onReleasing(float percent, int offset, int headHeight, int extendHeight) { if (mState != RefreshState.Refreshing) { onPullingDown(percent, offset, headHeight, extendHeight); } } @Override public void onStateChanged(RefreshLayout refreshLayout, RefreshState oldState, RefreshState newState) { mState = newState; } @Override public void onStartAnimator(RefreshLayout layout, int headHeight, int extendHeight) { mHeadHeight = headHeight; mBollRadius = headHeight / 6; DecelerateInterpolator interpolator = new DecelerateInterpolator(); final float reboundHeight = Math.min(mWaveHeight * 0.8f, mHeadHeight / 2); ValueAnimator waveAnimator = ValueAnimator.ofFloat( mWaveHeight, 0, -(reboundHeight*1.0f),0, -(reboundHeight*0.4f),0 ); waveAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { float speed = 0; float springBollY; float springRatio = 0; int springstatus = 0;//0 还没开始弹起 1 向上弹起 2 在弹起的最高点停住 @Override public void onAnimationUpdate(ValueAnimator animation) { float curValue = (float) animation.getAnimatedValue(); if (springstatus == 0 && curValue <= 0) { springstatus = 1; speed = Math.abs(curValue - mWaveHeight); } if (springstatus == 1) { springRatio = -curValue / reboundHeight; if (springRatio >= mSpringRatio) { mSpringRatio = springRatio; mBollY = mHeadHeight + curValue; speed = Math.abs(curValue - mWaveHeight); } else { springstatus = 2; mSpringRatio = 0; mShowBoll = true; mShowBollTail = true; springBollY = mBollY; } } if (springstatus == 2) { if (mBollY > mHeadHeight / 2) { mBollY = Math.max(mHeadHeight / 2, mBollY - speed); float bolly = animation.getAnimatedFraction() * (mHeadHeight / 2 - springBollY) + springBollY; if (mBollY > bolly) { mBollY = bolly; } } } if (mShowBollTail && curValue < mWaveHeight) { mShowOuter = true; mShowBollTail = false; mOuterIsStart = true; mRefreshStart = 90; mRefreshStop = 90; } mWaveHeight = curValue; CircleHeader.this.invalidate(); } }); waveAnimator.setInterpolator(interpolator); waveAnimator.setDuration(1000); waveAnimator.start(); } @Override public int onFinish(RefreshLayout layout, boolean success) { mShowOuter = false; mShowBoll = false; ValueAnimator animator = ValueAnimator.ofFloat(0, 1); animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { mFinishRatio = (float) animation.getAnimatedValue(); CircleHeader.this.invalidate(); } }); animator.setInterpolator(new AccelerateInterpolator()); animator.setDuration(DURATION_FINISH); animator.start(); return DURATION_FINISH; } @Override@Deprecated public void setPrimaryColors(int... colors) { if (colors.length > 0) { mBackPaint.setColor(colors[0]); if (colors.length > 1) { mFrontPaint.setColor(colors[1]); mOuterPaint.setColor(colors[1]); } } } @NonNull @Override public View getView() { return this; } @Override public SpinnerStyle getSpinnerStyle() { return SpinnerStyle.Scale; } // } ================================================ FILE: refresh-header/src/main/java/com/scwang/smartrefresh/header/DeliveryHeader.java ================================================ package com.scwang.smartrefresh.header; import android.support.annotation.RequiresApi; import android.content.Context; import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.Rect; import android.os.Build; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.util.AttributeSet; import android.view.View; import com.scwang.smartrefresh.layout.api.RefreshHeader; import com.scwang.smartrefresh.layout.api.RefreshKernel; import com.scwang.smartrefresh.layout.api.RefreshLayout; import com.scwang.smartrefresh.layout.constant.RefreshState; import com.scwang.smartrefresh.layout.constant.SpinnerStyle; import com.scwang.smartrefresh.layout.internal.pathview.PathsDrawable; import com.scwang.smartrefresh.layout.util.DensityUtil; /** * Refresh your delivery! * https://dribbble.com/shots/2753803-Refresh-your-delivery * Created by SCWANG on 2017/6/25. */ public class DeliveryHeader extends View implements RefreshHeader { // private Paint mPaint; private int mCloudX1; private int mCloudX2; private int mCloudX3; private int mHeaderHeight; private float mAppreciation; private RefreshState mState; private PathsDrawable mCloudDrawable; private PathsDrawable mUmbrellaDrawable; private PathsDrawable mBoxDrawable; // // public DeliveryHeader(Context context) { super(context); this.initView(context, null); } public DeliveryHeader(Context context, @Nullable AttributeSet attrs) { super(context, attrs); this.initView(context, attrs); } public DeliveryHeader(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); this.initView(context, attrs); } @SuppressWarnings("unused") @RequiresApi(Build.VERSION_CODES.LOLLIPOP) public DeliveryHeader(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); this.initView(context, attrs); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { setMeasuredDimension(resolveSize(getSuggestedMinimumWidth(), widthMeasureSpec), resolveSize(getSuggestedMinimumHeight(), heightMeasureSpec)); } private void initView(Context context, AttributeSet attrs) { mPaint = new Paint(); mPaint.setAntiAlias(true); // setBackgroundColor(0xff283645); setMinimumHeight(DensityUtil.dp2px(150)); mCloudDrawable = new PathsDrawable(); mCloudDrawable.parserPaths("M63,0.1A22.6,22.4 0,0 0,42.1 14.2,17.3 17.3,0 0,0 30.9,10.2 17.3,17.3 0,0 0,13.7 25.8,8.8 8.8,0 0,0 8.7,24.2 8.8,8.8 0,0 0,0 32h99a7.9,7.9 0,0 0,0 -0.6,7.9 7.9,0 0,0 -7.9,-7.9 7.9,7.9 0,0 0,-5.8 2.6,22.6 22.4,0 0,0 0.3,-3.6A22.6,22.4 0,0 0,63 0.1Z"); mCloudDrawable.parserColors(0xffffffff); mCloudDrawable.setGeometricHeight(DensityUtil.dp2px(20)); mUmbrellaDrawable = new PathsDrawable(); mUmbrellaDrawable.parserPaths( "M113.91,328.86 L119.02,331.02 134.86,359.02 133.99,359.02ZM2.18,144.52c-3.67,-76.84 49.96,-122.23 96.3,-134.98 6.03,0.21 7.57,0.59 13.23,3.9 0.19,1.7 0.25,2.17 -0.41,3.98 -47.88,37.64 -55.13,79.65 -68.07,137.22C37.56,194.8 16.18,191.4 2.18,144.52Z", "m133.99,359.02 l-0.71,-26.66 2.6,0.26 -1.02,26.4zM119.02,331.02c-3.39,-0.99 -8.53,-3.03 -8.72,-6.61 0,-0.81 -2.02,-3.63 -4.49,-6.27C88.05,299.71 7.29,218.46 2.18,144.52c17.67,43.57 33.35,45.33 41.05,10.12 0.13,-70.78 33.78,-125.48 68.07,-137.22 2.34,3.33 4.11,4.81 8.14,7.8 -22.02,65.69 -23.25,84.11 -24.14,150.23 -8.68,29.57 -37.44,32.81 -52.07,-20.81 14.12,64.06 31.66,101.57 60.64,147.13 6.2,8.38 14.74,18.4 15.15,29.25zM98.48,9.54c4.59,-1.5 17.8,-4.6 33.87,-5.07 0.95,0.95 1.38,1.91 1.14,2.91 -8.81,1.34 -16.36,3.1 -21.78,6.06 -2.53,-1.27 -7.82,-3.26 -13.23,-3.9z", "m119.02,331.02c-1.29,-7.57 -4.22,-12.31 -6.54,-15.79 -36.86,-54.89 -63.48,-98.79 -69.25,-160.59 19.89,45.9 41.27,48.65 52.07,20.81 -1.95,-52.55 -8.04,-91.2 24.14,-150.23 10.47,-0.28 16.85,0.17 30.66,-0.34 40.19,60.54 24.92,135.95 22.16,149.57 -13.9,53.18 -66.91,34.12 -76.96,1 11.54,50.55 20.28,89.27 30,135.97 4.12,10.03 5.37,10.37 5.06,21.35 -2.82,-0.22 -8.22,-1.01 -11.35,-1.75z", "m172.27,174.45c4.91,-51.6 -1.8,-105.99 -22.16,-149.57 2.51,-3.42 3.25,-4.36 6.59,-6.04 47.91,22.5 77.5,62.66 68.9,139.94 -16.53,49.7 -45.39,52.78 -53.33,15.68zM154.36,13.39c-6.65,-2.73 -11.65,-4.27 -20.87,-6.01 -0.25,-1.02 -0.71,-2.21 -1.14,-2.91 16.31,-0.22 27.58,2.29 37.27,4.82 -5.49,0.42 -11.39,1.87 -15.26,4.11z", "m133.99,359.02 l14.98,-28.13 2.24,-0.75 -16.34,28.88zM141.15,332.65c0.01,-11.71 2.3,-14.29 4.13,-21.52 11.82,-46.68 16.09,-77.45 26.98,-136.68 12.18,38.55 37.7,23.31 53.33,-15.68 -4.01,53.72 -43.68,121.28 -68.8,155.25 -6.17,9.5 -8.27,16.22 -5.59,16.12 -3.69,1.47 -6.24,2.05 -10.05,2.51z", "m225.59,158.77c1.61,-52.44 -22.26,-117.1 -68.9,-139.94 -1.48,-2.24 -1.63,-2.16 -2.34,-5.44 3.7,-3.42 9.42,-4.82 15.26,-4.11 48.59,9.81 103.07,66.75 96.62,132.26 -9.7,45.68 -35.45,51.78 -40.64,17.24z", "m156.48,313.99c32.9,-59.38 53.82,-87.19 69.12,-155.22 12.23,38.4 28.73,22.32 40.64,-17.24 -2.11,50.59 -43.12,112.84 -99.62,171.38 -4.57,4.73 -8.31,9.42 -8.31,10.4 -0,2.28 -3.52,5.43 -7.1,6.82 -4.65,0.73 2.08,-12.86 5.27,-16.15z", "M130.37,332.77C129.51,321.51 128.56,320.77 125.3,311.42 113.97,281.37 101.34,222.24 95.3,175.45c16.48,38.98 60.02,33.39 76.96,-1 -5.91,58.92 -10.85,88.45 -27.42,138.74 -1.67,6.75 -2.67,11.63 -3.7,19.46 -2.94,0.45 -6.48,0.45 -10.78,0.12zM119.44,25.22c-3.52,-1.25 -6.98,-3.72 -8.14,-7.8 -0.44,-1.53 -0.24,-2.79 0.41,-3.98 2.48,-4.55 14.53,-6.26 21.78,-6.06 5.29,0.15 14.87,0.72 20.87,6.01 1.82,1.61 2.74,3.95 2.34,5.44 -0.76,2.83 -4.21,5.19 -6.59,6.04 -7.49,2.68 -22.62,3.2 -30.66,0.34z" ); mUmbrellaDrawable.parserColors( 0xff92dfeb, 0xff6dd0e9, 0xff4fc3e7, 0xff2fb6e6, 0xff25a9de, 0xff11abe4, 0xff0e9bd8, 0xff40b7e1 ); mUmbrellaDrawable.setGeometricWidth(DensityUtil.dp2px(200)); mBoxDrawable = new PathsDrawable(); mBoxDrawable.parserPaths( "M0,17.5 L3.1,29.8 2.9,76.4 47.5,93 92.8,76.2V29.9L94.9,18.1 47.4,0.5Z", "M3.1,29.8 L47.8,46.4 47.5,93 2.9,76.4ZM0,17.5 L47.9,35.4 47.8,46.4 0.2,28.8Z", "m56.5,17.8c0,2.1 -3.8,3.8 -8.5,3.8 -4.7,0 -8.5,-1.7 -8.5,-3.8 0,-2.1 3.8,-3.8 8.5,-3.8 4.7,0 8.5,1.7 8.5,3.8zM3.1,29.8 L3.1,34.7l44.7,16.9 0,-5.1z", "M47.9,35.4 L47.5,93 92.8,76.2V29.9l2.2,-0.8 0,-10.9z", "M82.6,80 L92.8,62.4 92.8,76.2ZM47.6,79.8 L59.8,88.4 47.5,93ZM47.8,46.4 L92.8,29.9 92.8,34.2 47.8,51.6Z" ); mBoxDrawable.parserColors( 0xfff8b147, 0xfff2973c, 0xffed8030, 0xfffec051, 0xfff7ad49 ); mBoxDrawable.setGeometricWidth(DensityUtil.dp2px(50)); // TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.DeliveryHeader); // ta.recycle(); if (isInEditMode()) { mState = RefreshState.Refreshing; mAppreciation = 100; mCloudX1 = (int)(mCloudDrawable.width()*3.5f); mCloudX2 = (int)(mCloudDrawable.width()*0.5f); mCloudX3 = (int)(mCloudDrawable.width()*2.0f); } } @Override protected void onDraw(Canvas canvas) { final int width = getWidth(); final int height = getHeight(); final int saveCount = canvas.getSaveCount(); canvas.save(); int shake = (int) (mHeaderHeight / 13 * Math.sin(mAppreciation)); if (mState == RefreshState.Refreshing || mState == RefreshState.RefreshFinish) { mCloudDrawable.getBounds().offsetTo(mCloudX1, mHeaderHeight / 3); mCloudDrawable.draw(canvas); mCloudDrawable.getBounds().offsetTo(mCloudX2, mHeaderHeight / 2); mCloudDrawable.draw(canvas); mCloudDrawable.getBounds().offsetTo(mCloudX3, mHeaderHeight * 2 / 3); mCloudDrawable.draw(canvas); canvas.rotate(5 * (float) Math.sin(mAppreciation / 2), width / 2 , mHeaderHeight / 2 - mUmbrellaDrawable.height()); calculateFrame(width); } final int centerY = height - mHeaderHeight / 2 + shake; final int centerYBox = centerY + (mHeaderHeight / 2 - mBoxDrawable.height()) - Math.min(mHeaderHeight / 2 - mBoxDrawable.height(), DensityUtil.dp2px(mAppreciation * 100)); mBoxDrawable.getBounds().offsetTo(width / 2 - mBoxDrawable.width() / 2, centerYBox - mBoxDrawable.height() / 4); mBoxDrawable.draw(canvas); if (mState == RefreshState.Refreshing || mState == RefreshState.RefreshFinish) { Rect bounds = mUmbrellaDrawable.getBounds(); final int centerYUmbrella = centerY - mHeaderHeight + Math.min(mHeaderHeight, DensityUtil.dp2px(mAppreciation * 100)); mUmbrellaDrawable.getBounds().offsetTo(width / 2 - bounds.width() / 2, centerYUmbrella - bounds.height()); mUmbrellaDrawable.draw(canvas); } canvas.restoreToCount(saveCount); } private void calculateFrame(int width) { mCloudX1 += DensityUtil.dp2px(9); mCloudX2 += DensityUtil.dp2px(5); mCloudX3 += DensityUtil.dp2px(12); int cloudWidth = mCloudDrawable.getBounds().width(); if (mCloudX1 > width + cloudWidth) { mCloudX1 = -cloudWidth; } if (mCloudX2 > width + cloudWidth) { mCloudX2 = -cloudWidth; } if (mCloudX3 > width + cloudWidth) { mCloudX3 = -cloudWidth; } mAppreciation += 0.1f; invalidate(); } // // @Override public boolean isSupportHorizontalDrag() { return false; } @Override public void onHorizontalDrag(float percentX, int offsetX, int offsetMax) { } @Override public void onPullingDown(float percent, int offset, int headerHeight, int extendHeight) { if (mState != RefreshState.Refreshing) { mBoxDrawable.getPaint().setAlpha((int) (255 * (1f - Math.max(0, percent - 1)))); } } @Override public void onReleasing(float percent, int offset, int headerHeight, int extendHeight) { if (mState != RefreshState.Refreshing) { mBoxDrawable.getPaint().setAlpha((int) (255 * (1f - Math.max(0, percent - 1)))); } } @Override public void onStateChanged(RefreshLayout refreshLayout, RefreshState oldState, RefreshState newState) { mState = newState; if (newState == RefreshState.None) { mAppreciation = 0; mCloudX1 = mCloudX2 = mCloudX3 = -mCloudDrawable.getBounds().width(); } } @NonNull @Override public View getView() { return this; } @Override public SpinnerStyle getSpinnerStyle() { return SpinnerStyle.Scale; } @Override@Deprecated public void setPrimaryColors(int... colors) { if (colors.length > 0) { setBackgroundColor(colors[0]); if (colors.length > 1) { mCloudDrawable.parserColors(colors[1]); } } } @Override public void onInitialized(RefreshKernel kernel, int height, int extendHeight) { mHeaderHeight = height; } @Override public void onStartAnimator(RefreshLayout layout, int height, int extendHeight) { mState = RefreshState.Refreshing; invalidate(); } @Override public int onFinish(RefreshLayout layout, boolean success) { return 0; } // } ================================================ FILE: refresh-header/src/main/java/com/scwang/smartrefresh/header/DropboxHeader.java ================================================ package com.scwang.smartrefresh.header; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.ValueAnimator; import android.content.Context; import android.content.res.TypedArray; import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.Path; import android.graphics.Rect; import android.graphics.drawable.Drawable; import android.os.Build; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.annotation.RequiresApi; import android.util.AttributeSet; import android.view.View; import android.view.animation.AccelerateInterpolator; import com.scwang.smartrefresh.layout.api.RefreshHeader; import com.scwang.smartrefresh.layout.api.RefreshKernel; import com.scwang.smartrefresh.layout.api.RefreshLayout; import com.scwang.smartrefresh.layout.constant.RefreshState; import com.scwang.smartrefresh.layout.constant.SpinnerStyle; import com.scwang.smartrefresh.layout.internal.pathview.PathsDrawable; import com.scwang.smartrefresh.layout.util.ColorUtils; import com.scwang.smartrefresh.layout.util.DensityUtil; /** * DropboxRefresh * https://dribbble.com/shots/3470499-Dropbox-Refresh * Created by SCWANG on 2017/6/24. */ public class DropboxHeader extends View implements RefreshHeader { // private Path mPath; private Paint mPaint; private BoxBody mBoxBody; private int mAccentColor; private int mHeaderHeight; private boolean mDropOutOverFlow; private Drawable mDrawable1; private Drawable mDrawable2; private Drawable mDrawable3; private float mDropOutPercent; private float mReboundPercent; private ValueAnimator mReboundAnimator; private ValueAnimator mDropOutAnimator; private RefreshState mState; // // public DropboxHeader(Context context) { super(context); this.initView(context, null); } public DropboxHeader(Context context, @Nullable AttributeSet attrs) { super(context, attrs); this.initView(context, attrs); } public DropboxHeader(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); this.initView(context, attrs); } @RequiresApi(Build.VERSION_CODES.LOLLIPOP) public DropboxHeader(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); this.initView(context, attrs); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { setMeasuredDimension(resolveSize(getSuggestedMinimumWidth(), widthMeasureSpec), resolveSize(getSuggestedMinimumHeight(), heightMeasureSpec)); } private void initView(Context context, AttributeSet attrs) { mPath = new Path(); mPaint = new Paint(); mBoxBody = new BoxBody(); mPaint.setAntiAlias(true); mAccentColor = 0xff6ea9ff; setBackgroundColor(0xff283645); setMinimumHeight(DensityUtil.dp2px(150)); TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.DropboxHeader); if (ta.hasValue(R.styleable.DropboxHeader_dhDrawable1)) { mDrawable1 = ta.getDrawable(R.styleable.DropboxHeader_dhDrawable1); } else { PathsDrawable drawable1 = new PathsDrawable(); drawable1.parserPaths( "M3 2h18v20h-18z", "m4,1c-1.105,0 -2,0.895 -2,2v3,11 3,1c0,1.105 0.895,2 2,2h2,12 2c1.105,0 2,-0.895 2,-2v-1,-3 -11,-3c0,-1.105 -0.895,-2 -2,-2h-2,-12 -2zM3.5,3h1c0.276,0 0.5,0.224 0.5,0.5v1c0,0.276 -0.224,0.5 -0.5,0.5h-1c-0.276,0 -0.5,-0.224 -0.5,-0.5v-1c0,-0.276 0.224,-0.5 0.5,-0.5zM19.5,3h1c0.276,0 0.5,0.224 0.5,0.5v1c0,0.276 -0.224,0.5 -0.5,0.5h-1c-0.276,0 -0.5,-0.224 -0.5,-0.5v-1c0,-0.276 0.224,-0.5 0.5,-0.5zM3.5,6h1c0.276,0 0.5,0.224 0.5,0.5v1c0,0.276 -0.224,0.5 -0.5,0.5h-1c-0.276,0 -0.5,-0.224 -0.5,-0.5v-1c0,-0.276 0.224,-0.5 0.5,-0.5zM19.5,6h1c0.276,0 0.5,0.224 0.5,0.5v1c0,0.276 -0.224,0.5 -0.5,0.5h-1c-0.276,0 -0.5,-0.224 -0.5,-0.5v-1c0,-0.276 0.224,-0.5 0.5,-0.5zM3.5,9h1c0.276,0 0.5,0.224 0.5,0.5v1c0,0.276 -0.224,0.5 -0.5,0.5h-1c-0.276,0 -0.5,-0.224 -0.5,-0.5v-1c0,-0.276 0.224,-0.5 0.5,-0.5zM19.5,9h1c0.276,0 0.5,0.224 0.5,0.5v1c0,0.276 -0.224,0.5 -0.5,0.5h-1c-0.276,0 -0.5,-0.224 -0.5,-0.5v-1c0,-0.276 0.224,-0.5 0.5,-0.5zM3.5,12h1c0.276,0 0.5,0.224 0.5,0.5v1c0,0.276 -0.224,0.5 -0.5,0.5h-1c-0.276,0 -0.5,-0.224 -0.5,-0.5v-1c0,-0.276 0.224,-0.5 0.5,-0.5zM19.5,12h1c0.276,0 0.5,0.224 0.5,0.5v1c0,0.276 -0.224,0.5 -0.5,0.5h-1c-0.276,0 -0.5,-0.224 -0.5,-0.5v-1c0,-0.276 0.224,-0.5 0.5,-0.5zM3.5,15h1c0.276,0 0.5,0.224 0.5,0.5v1c0,0.276 -0.224,0.5 -0.5,0.5h-1c-0.276,0 -0.5,-0.224 -0.5,-0.5v-1c0,-0.276 0.224,-0.5 0.5,-0.5zM19.5,15h1c0.276,0 0.5,0.224 0.5,0.5v1c0,0.276 -0.224,0.5 -0.5,0.5h-1c-0.276,0 -0.5,-0.224 -0.5,-0.5v-1c0,-0.276 0.224,-0.5 0.5,-0.5zM3.5,18h1c0.276,0 0.5,0.224 0.5,0.5v1c0,0.276 -0.224,0.5 -0.5,0.5h-1c-0.276,0 -0.5,-0.224 -0.5,-0.5v-1c0,-0.276 0.224,-0.5 0.5,-0.5zM19.5,18h1c0.276,0 0.5,0.224 0.5,0.5v1c0,0.276 -0.224,0.5 -0.5,0.5h-1c-0.276,0 -0.5,-0.224 -0.5,-0.5v-1c0,-0.276 0.224,-0.5 0.5,-0.5z" ); drawable1.parserColors( 0xffecf0f1, 0xfffc4108 ); mDrawable1 = drawable1; } if (ta.hasValue(R.styleable.DropboxHeader_dhDrawable2)) { mDrawable2 = ta.getDrawable(R.styleable.DropboxHeader_dhDrawable2); } else { PathsDrawable drawable2 = new PathsDrawable(); drawable2.parserPaths( "M49,16.5l-14,-14l-27,0l0,53l41,0z", "m16,23.5h25c0.55,0 1,-0.45 1,-1 0,-0.55 -0.45,-1 -1,-1L16,21.5c-0.55,0 -1,0.45 -1,1 0,0.55 0.45,1 1,1z", "m16,15.5h10c0.55,0 1,-0.45 1,-1 0,-0.55 -0.45,-1 -1,-1L16,13.5c-0.55,0 -1,0.45 -1,1 0,0.55 0.45,1 1,1z", "M41,29.5L16,29.5c-0.55,0 -1,0.45 -1,1 0,0.55 0.45,1 1,1h25c0.55,0 1,-0.45 1,-1 0,-0.55 -0.45,-1 -1,-1z", "M41,37.5L16,37.5c-0.55,0 -1,0.45 -1,1 0,0.55 0.45,1 1,1h25c0.55,0 1,-0.45 1,-1 0,-0.55 -0.45,-1 -1,-1z", "M41,45.5L16,45.5c-0.55,0 -1,0.45 -1,1 0,0.55 0.45,1 1,1h25c0.55,0 1,-0.45 1,-1 0,-0.55 -0.45,-1 -1,-1z", "M49,16.5l-14,-14l0,14z" ); drawable2.parserColors( 0xfffed469, 0xffd5ae57 ); mDrawable2 = drawable2; } if (ta.hasValue(R.styleable.DropboxHeader_dhDrawable3)) { mDrawable3 = ta.getDrawable(R.styleable.DropboxHeader_dhDrawable3); } else { PathsDrawable drawable3 = new PathsDrawable(); drawable3.parserPaths("M6.021,2.188L6.021,11.362C5.46,11.327 4.843,11.414 4.229,11.663C2.624,12.312 1.696,13.729 2.155,14.825C2.62,15.924 4.294,16.284 5.898,15.634C7.131,15.134 7.856,14.184 7.965,13.272L7.958,4.387L15.02,3.028L15.02,9.406C14.422,9.343 13.746,9.432 13.076,9.703C11.471,10.353 10.544,11.77 11.004,12.866C11.467,13.964 13.141,14.325 14.746,13.675C15.979,13.174 16.836,12.224 16.947,11.313L16.958,0.002L6.021,2.188L6.021,2.188Z"); drawable3.parserColors(0xff98d761); mDrawable3 = drawable3; } ta.recycle(); } @Override protected void onAttachedToWindow() { super.onAttachedToWindow(); initAnimator(); } @Override protected void onDetachedFromWindow() { super.onDetachedFromWindow(); if (mReboundAnimator != null) { mReboundAnimator.removeAllUpdateListeners(); mReboundAnimator.removeAllListeners(); mReboundAnimator = null; } if (mDropOutAnimator != null) { mDropOutAnimator.removeAllUpdateListeners(); mDropOutAnimator.removeAllListeners(); mDropOutAnimator = null; } } private void initAnimator() { AccelerateInterpolator interpolator = new AccelerateInterpolator(); mReboundAnimator = ValueAnimator.ofFloat(0, 1, 0); mReboundAnimator.setInterpolator(interpolator); mReboundAnimator.setDuration(300); mReboundAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { mReboundPercent = (float) animation.getAnimatedValue(); DropboxHeader.this.invalidate(); } }); mReboundAnimator.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { if (mState == RefreshState.Refreshing) { if (mDropOutAnimator != null) { mDropOutAnimator.start(); } } } }); mDropOutAnimator = ValueAnimator.ofFloat(0, 1); mDropOutAnimator.setInterpolator(interpolator); mDropOutAnimator.setDuration(300); mDropOutAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { if (mDropOutPercent < 1 || mDropOutPercent >= 3) { mDropOutPercent = (float) animation.getAnimatedValue(); } else if (mDropOutPercent < 2) { mDropOutPercent = 1 + (float) animation.getAnimatedValue(); } else if (mDropOutPercent < 3) { mDropOutPercent = 2 + (float) animation.getAnimatedValue(); if (mDropOutPercent == 3) { mDropOutOverFlow = true; } } DropboxHeader.this.invalidate(); } }); mDropOutAnimator.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { if (mReboundAnimator != null) { mReboundAnimator.start(); } } }); } @Override protected void onDraw(Canvas canvas) { final int width = getWidth(); final int height = getHeight(); final int sideLength = generateSideLength(); BoxBody body = generateBoxBody(width, height, sideLength); mPaint.setColor(ColorUtils.setAlphaComponent(mAccentColor, 150)); canvas.drawPath(generateBoxBodyPath(body), mPaint); mPaint.setColor(mAccentColor); canvas.drawPath(generateBoxCoverPath(body), mPaint); if (isInEditMode()) { mDropOutPercent = 2.5f; } if (mDropOutPercent > 0) { canvas.clipPath(generateClipPath(body, width)); final float percent1 = Math.min(mDropOutPercent, 1); Rect bounds1 = mDrawable1.getBounds(); bounds1.offsetTo(width / 2 - bounds1.width() / 2, (int)((body.boxCenterY - bounds1.height() / 2 + bounds1.height()) * percent1) - bounds1.height()); mDrawable1.draw(canvas); final float percent2 = Math.min(Math.max(mDropOutPercent - 1, 0), 1); Rect bounds2 = mDrawable2.getBounds(); bounds2.offsetTo(width / 2 - bounds2.width() / 2, (int)((body.boxCenterY - bounds2.height() / 2 + bounds2.height()) * percent2) - bounds2.height()); mDrawable2.draw(canvas); final float percent3 = Math.min(Math.max(mDropOutPercent - 2, 0), 1); Rect bounds3 = mDrawable3.getBounds(); bounds3.offsetTo(width / 2 - bounds3.width() / 2, (int) ((body.boxCenterY - bounds3.height() / 2 + bounds3.height()) * percent3) - bounds3.height()); mDrawable3.draw(canvas); if (mDropOutOverFlow) { bounds1.offsetTo(width / 2 - bounds1.width() / 2, ((body.boxCenterY - bounds1.height() / 2))); mDrawable1.draw(canvas); bounds2.offsetTo(width / 2 - bounds2.width() / 2, ((body.boxCenterY - bounds2.height() / 2))); mDrawable2.draw(canvas); bounds3.offsetTo(width / 2 - bounds3.width() / 2, ((body.boxCenterY - bounds3.height() / 2))); mDrawable3.draw(canvas); } } } // // private int generateSideLength() { return mHeaderHeight / 5; } @NonNull private Path generateClipPath(BoxBody body, int width) { mPath.reset(); mPath.lineTo(0, body.boxCenterTop); mPath.lineTo(body.boxLeft, body.boxCenterTop); mPath.lineTo(body.boxCenterX, body.boxCenterY); mPath.lineTo(body.boxRight, body.boxCenterTop); mPath.lineTo(width, body.boxCenterTop); mPath.lineTo(width, 0); mPath.close(); return mPath; } @NonNull private BoxBody generateBoxBody(int width, int height, int sideLength) { final int margin = sideLength / 2; return mBoxBody.measure(width, height, sideLength, margin); } @NonNull private Path generateBoxCoverPath(BoxBody body) { mPath.reset(); final int sideLength = (body.boxCenterX - body.boxLeft) * 4 / 5; final double offsetAngle = mReboundPercent * (Math.PI * 2 / 5); /** * 开始画左上的盖子 */ final float offsetLeftTopX = sideLength * (float) Math.sin(Math.PI / 3 - offsetAngle / 2); final float offsetLeftTopY = sideLength * (float) Math.cos(Math.PI / 3 - offsetAngle / 2); mPath.moveTo(body.boxLeft, body.boxCenterTop); mPath.lineTo(body.boxCenterX, body.boxTop); mPath.lineTo(body.boxCenterX - offsetLeftTopX, body.boxTop - offsetLeftTopY); mPath.lineTo(body.boxLeft - offsetLeftTopX, body.boxCenterTop - offsetLeftTopY); mPath.close(); /** * 开始画左下的盖子 */ final float offsetLeftBottomX = sideLength * (float) Math.sin(Math.PI / 3 + offsetAngle); final float offsetLeftBottomY = sideLength * (float) Math.cos(Math.PI / 3 + offsetAngle); mPath.moveTo(body.boxLeft, body.boxCenterTop); mPath.lineTo(body.boxCenterX, (body.boxBottom + body.boxTop) / 2); mPath.lineTo(body.boxCenterX - offsetLeftBottomX, (body.boxBottom + body.boxTop) / 2 + offsetLeftBottomY); mPath.lineTo(body.boxLeft - offsetLeftBottomX, body.boxCenterTop + offsetLeftBottomY); mPath.close(); /** * 开始画右上的盖子 */ final float offsetRightTopX = sideLength * (float) Math.sin(Math.PI / 3 - offsetAngle / 2); final float offsetRightTopY = sideLength * (float) Math.cos(Math.PI / 3 - offsetAngle / 2); mPath.moveTo(body.boxRight, body.boxCenterTop); mPath.lineTo(body.boxCenterX, body.boxTop); mPath.lineTo(body.boxCenterX + offsetRightTopX, body.boxTop - offsetRightTopY); mPath.lineTo(body.boxRight + offsetRightTopX, body.boxCenterTop - offsetRightTopY); mPath.close(); /** * 开始画右下的盖子 */ final float offsetRightBottomX = sideLength * (float) Math.sin(Math.PI / 3 + offsetAngle); final float offsetRightBottomY = sideLength * (float) Math.cos(Math.PI / 3 + offsetAngle); mPath.moveTo(body.boxRight, body.boxCenterTop); mPath.lineTo(body.boxCenterX, (body.boxBottom + body.boxTop) / 2); mPath.lineTo(body.boxCenterX + offsetRightBottomX, (body.boxBottom + body.boxTop) / 2 + offsetRightBottomY); mPath.lineTo(body.boxRight + offsetRightBottomX, body.boxCenterTop + offsetRightBottomY); mPath.close(); return mPath; } @NonNull private Path generateBoxBodyPath(BoxBody body) { mPath.reset(); mPath.moveTo(body.boxLeft, body.boxCenterBottom); mPath.lineTo(body.boxCenterX, body.boxBottom); mPath.lineTo(body.boxRight, body.boxCenterBottom); mPath.quadTo(body.boxRight + body.boxSideLength / 2 * mReboundPercent, body.boxCenterY, body.boxRight, body.boxCenterTop); mPath.lineTo(body.boxCenterX, body.boxTop); mPath.lineTo(body.boxLeft, body.boxCenterTop); mPath.quadTo(body.boxLeft - body.boxSideLength / 2 * mReboundPercent, body.boxCenterY, body.boxLeft, body.boxCenterBottom); mPath.close(); return mPath; } // // @Override public boolean isSupportHorizontalDrag() { return false; } @Override public void onHorizontalDrag(float percentX, int offsetX, int offsetMax) { } @Override public void onPullingDown(float percent, int offset, int headerHeight, int extendHeight) { if (mState != RefreshState.Refreshing) { mReboundPercent = 1f * Math.max(0, offset - headerHeight) / extendHeight; } } @Override public void onReleasing(float percent, int offset, int headerHeight, int extendHeight) { mReboundPercent = 1f * Math.max(0, offset - headerHeight) / extendHeight; } @Override public void onStateChanged(RefreshLayout refreshLayout, RefreshState oldState, RefreshState newState) { mState = newState; if (newState == RefreshState.None) { mDropOutOverFlow = false; } } @NonNull @Override public View getView() { return this; } @Override public SpinnerStyle getSpinnerStyle() { return SpinnerStyle.Scale; } @Override@Deprecated public void setPrimaryColors(int... colors) { if (colors.length > 0) { setBackgroundColor(colors[0]); if (colors.length > 1) { mAccentColor = colors[1]; } } } @Override public void onInitialized(RefreshKernel kernel, int height, int extendHeight) { mHeaderHeight = height; final int sideLength = generateSideLength(); mDrawable1.setBounds(0, 0, sideLength, sideLength); mDrawable2.setBounds(0, 0, sideLength, sideLength); mDrawable3.setBounds(0, 0, sideLength, sideLength); } @Override public void onStartAnimator(RefreshLayout layout, int height, int extendHeight) { if (mDropOutAnimator != null) { mDropOutAnimator.start(); } } @Override public int onFinish(RefreshLayout layout, boolean success) { mDropOutPercent = 0; return 0; } // private static class BoxBody { private int boxCenterX; private int boxCenterY; private int boxBottom; private int boxTop; private int boxLeft; private int boxCenterTop; private int boxCenterBottom; private int boxRight; private int boxSideLength; BoxBody measure(int width, int height, int sideLength, int margin) { boxSideLength = sideLength; boxCenterX = width / 2; boxBottom = height - margin; boxTop = boxBottom - 2 * sideLength; boxLeft = boxCenterX - (int) (sideLength * Math.sin(Math.PI / 3)); boxCenterTop = boxTop + sideLength / 2; boxCenterBottom = boxBottom - sideLength / 2; boxRight = width - boxLeft; boxCenterY = boxBottom - sideLength; return this; } } } ================================================ FILE: refresh-header/src/main/java/com/scwang/smartrefresh/header/FlyRefreshHeader.java ================================================ package com.scwang.smartrefresh.header; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.AnimatorSet; import android.animation.ObjectAnimator; import android.animation.ValueAnimator; import android.content.Context; import android.os.Build; import android.support.annotation.Nullable; import android.support.annotation.RequiresApi; import android.util.AttributeSet; import android.view.View; import android.view.animation.AccelerateInterpolator; import android.view.animation.DecelerateInterpolator; import com.scwang.smartrefresh.header.flyrefresh.FlyView; import com.scwang.smartrefresh.header.flyrefresh.MountanScenceView; import com.scwang.smartrefresh.header.flyrefresh.PathInterpolatorCompat; import com.scwang.smartrefresh.layout.api.RefreshHeader; import com.scwang.smartrefresh.layout.api.RefreshKernel; import com.scwang.smartrefresh.layout.api.RefreshLayout; import com.scwang.smartrefresh.layout.header.FalsifyHeader; import com.scwang.smartrefresh.layout.util.DensityUtil; /** * 纸飞机和山丘 * Created by SCWANG on 2017/6/6. */ public class FlyRefreshHeader extends FalsifyHeader implements RefreshHeader { private FlyView mFlyView; private AnimatorSet mFlyAnimator; private MountanScenceView mScenceView; private RefreshLayout mRefreshLayout; private RefreshKernel mRefreshKernel; private float mCurrentPercent; private boolean mIsRefreshing = false; private int mOffset = 0; // public FlyRefreshHeader(Context context) { super(context); } public FlyRefreshHeader(Context context, AttributeSet attrs) { super(context, attrs); } public FlyRefreshHeader(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } @RequiresApi(Build.VERSION_CODES.LOLLIPOP) public FlyRefreshHeader(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); } @Override protected void onDetachedFromWindow() { super.onDetachedFromWindow(); mFlyView = null; mRefreshLayout = null; mRefreshKernel = null; } // // @Override public boolean isSupportHorizontalDrag() { return false; } @Override public void onHorizontalDrag(float percentX, int offsetX, int offsetMax) { } @Override public void onPullingDown(float percent, int offset, int headHeight, int extendHeight) { if (offset < 0) { if (mOffset > 0) { offset = 0; percent = 0; } else { return; } } mOffset = offset; mCurrentPercent = percent; if (mScenceView != null) { mScenceView.updatePercent(percent); mScenceView.postInvalidate(); } if (mFlyView != null) { if (headHeight + extendHeight > 0) { mFlyView.setRotation((-45f) * offset / (headHeight + extendHeight)); } else { mFlyView.setRotation((-45f) * percent); } } } @Override public void onReleasing(float percent, int offset, int headHeight, int extendHeight) { if (!mIsRefreshing) { onPullingDown(percent, offset, headHeight, extendHeight); } } @Override public void onStartAnimator(RefreshLayout layout, int headHeight, int extendHeight) { /** * 提前关闭 下拉视图偏移 */ mRefreshKernel.animSpinner(0); if (mCurrentPercent > 0) { ValueAnimator valueAnimator = ValueAnimator.ofFloat(mCurrentPercent, 0); valueAnimator.setDuration(300); valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { FlyRefreshHeader.this.onPullingDown((float) animation.getAnimatedValue(), 0, 0, 0); } }); valueAnimator.start(); mCurrentPercent = 0; } if (mFlyView != null && !mIsRefreshing) { if (mFlyAnimator != null) { mFlyAnimator.end(); mFlyView.clearAnimation(); } mIsRefreshing = true; layout.setEnableRefresh(false); final int offDistX = ((View) mRefreshLayout).getWidth()-mFlyView.getLeft(); final int offDistY = -(mFlyView.getTop() - mOffset) * 2 / 3; ObjectAnimator transX = ObjectAnimator.ofFloat(mFlyView, "translationX", 0, offDistX); ObjectAnimator transY = ObjectAnimator.ofFloat(mFlyView, "translationY", 0, offDistY); transY.setInterpolator(PathInterpolatorCompat.create(0.7f, 1f)); ObjectAnimator rotation = ObjectAnimator.ofFloat(mFlyView, "rotation", mFlyView.getRotation(), 0); rotation.setInterpolator(new DecelerateInterpolator()); ObjectAnimator rotationX = ObjectAnimator.ofFloat(mFlyView, "rotationX", mFlyView.getRotationX(), 50); rotationX.setInterpolator(new DecelerateInterpolator()); AnimatorSet flyUpAnim = new AnimatorSet(); flyUpAnim.setDuration(800); flyUpAnim.playTogether(transX ,transY ,rotation ,rotationX ,ObjectAnimator.ofFloat(mFlyView, "scaleX", mFlyView.getScaleX(), 0.5f) ,ObjectAnimator.ofFloat(mFlyView, "scaleY", mFlyView.getScaleY(), 0.5f) ); mFlyAnimator = flyUpAnim; mFlyAnimator.start(); } } @Override@Deprecated public void setPrimaryColors(int... colors) { if (colors.length > 0) { if (mScenceView != null) { mScenceView.setPrimaryColor(colors[0]); } } } @Override public void onInitialized(RefreshKernel kernel, int height, int extendHeight) { mRefreshKernel = kernel; mRefreshLayout = kernel.getRefreshLayout(); } @Override public int onFinish(RefreshLayout layout, boolean success) { if (mIsRefreshing) { finishRefresh(); } return super.onFinish(layout, success); } // // public void setUpMountanScenceView(MountanScenceView scenceView){ mScenceView = scenceView; } public void setUpFlyView(FlyView flyView) { mFlyView = flyView; } public void setUp(MountanScenceView scenceView, FlyView flyView) { setUpFlyView(flyView); setUpMountanScenceView(scenceView); } public void finishRefresh() { finishRefresh(null); } public void finishRefresh(final AnimatorListenerAdapter listenerAdapter) { if (mFlyView == null || !mIsRefreshing || mRefreshLayout == null) { return; } if (mFlyAnimator != null) { mFlyAnimator.end(); mFlyView.clearAnimation(); } mIsRefreshing = false; mRefreshLayout.finishRefresh(0); final int offDistX = -mFlyView.getRight(); final int offDistY = -DensityUtil.dp2px(10); AnimatorSet flyDownAnim = new AnimatorSet(); flyDownAnim.setDuration(800); ObjectAnimator transX1 = ObjectAnimator.ofFloat(mFlyView, "translationX", mFlyView.getTranslationX(), offDistX); ObjectAnimator transY1 = ObjectAnimator.ofFloat(mFlyView, "translationY", mFlyView.getTranslationY(), offDistY); transY1.setInterpolator(PathInterpolatorCompat.create(0.1f, 1f)); ObjectAnimator rotation1 = ObjectAnimator.ofFloat(mFlyView, "rotation", mFlyView.getRotation(), 0); ObjectAnimator rotationX1 = ObjectAnimator.ofFloat(mFlyView, "rotationX", mFlyView.getRotationX(), 30); rotation1.setInterpolator(new AccelerateInterpolator()); flyDownAnim.playTogether(transX1, transY1 , rotation1 , rotationX1 , ObjectAnimator.ofFloat(mFlyView, "scaleX", mFlyView.getScaleX(), 0.9f) , ObjectAnimator.ofFloat(mFlyView, "scaleY", mFlyView.getScaleY(), 0.9f) ); flyDownAnim.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationStart(Animator animation) { if (mFlyView != null) { mFlyView.setRotationY(180); } } }); AnimatorSet flyInAnim = new AnimatorSet(); flyInAnim.setDuration(800); flyInAnim.setInterpolator(new DecelerateInterpolator()); ObjectAnimator tranX2 = ObjectAnimator.ofFloat(mFlyView, "translationX", offDistX, 0); ObjectAnimator tranY2 = ObjectAnimator.ofFloat(mFlyView, "translationY", offDistY, 0); ObjectAnimator rotationX2 = ObjectAnimator.ofFloat(mFlyView, "rotationX", 30, 0); flyInAnim.playTogether(tranX2, tranY2 , rotationX2 , ObjectAnimator.ofFloat(mFlyView, "scaleX", 0.9f, 1f) , ObjectAnimator.ofFloat(mFlyView, "scaleY", 0.9f, 1f) ); flyInAnim.setStartDelay(100); flyInAnim.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationStart(Animator animation) { if (mFlyView != null) { mFlyView.setRotationY(0); } } @Override public void onAnimationEnd(Animator animation) { if (mRefreshLayout != null) { mRefreshLayout.setEnableRefresh(true); } if (listenerAdapter != null) { listenerAdapter.onAnimationEnd(animation); } } }); mFlyAnimator = new AnimatorSet(); mFlyAnimator.playSequentially(flyDownAnim, flyInAnim); mFlyAnimator.start(); } // } ================================================ FILE: refresh-header/src/main/java/com/scwang/smartrefresh/header/FunGameBattleCityHeader.java ================================================ package com.scwang.smartrefresh.header; import android.support.annotation.RequiresApi; import android.content.Context; import android.graphics.Canvas; import android.graphics.Point; import android.graphics.RectF; import android.os.Build; import android.support.annotation.Nullable; import android.util.AttributeSet; import android.util.SparseArray; import com.scwang.smartrefresh.header.fungame.FunGameView; import com.scwang.smartrefresh.layout.util.DensityUtil; import java.util.LinkedList; import java.util.Queue; import java.util.Random; /** * Created by Hitomis on 2016/3/09. * email:196425254@qq.com */ public class FunGameBattleCityHeader extends FunGameView { /** * 轨道数量 */ private static int TANK_ROW_NUM = 3; /** * 炮管尺寸所在tank尺寸的比率 */ private static final float TANK_BARREL_RATIO = 1/3.f; /** * 默认子弹之间空隙间距 */ private static final int DEFAULT_BULLET_NUM_SPACING = 360; /** * 默认敌方坦克之间间距 */ private static final int DEFAULT_ENEMY_TANK_NUM_SPACING = 60; /** * 表示运行漏掉的敌方坦克总数量 和 升级后消灭坦克总数量的增量 */ private static final int DEFAULT_TANK_MAGIC_TOTAL_NUM = 8; /** * 所有轨道上敌方坦克矩阵集合 */ private SparseArray> eTankSparseArray; /** * 屏幕上所有子弹坐标点集合 */ private Queue mBulletList; /** * 击中敌方坦克的子弹坐标点 */ private Point usedBullet; /** * 用于随机定位一个轨道下标值 */ private Random random; /** * 子弹半径 */ private float bulletRadius; /** * 敌方坦克间距、子弹间距 */ private int enemyTankSpace, bulletSpace; /** * 炮筒尺寸 */ private int barrelSize; /** * 敌方坦克速度、子弹速度 */ private int enemySpeed = 1, bulletSpeed = 4; /** * 当前前一辆敌方坦克和后一辆已经存在的间距值 * 用于确定是否要派出新的一辆敌方坦克 */ private int offsetETankX; /** * 当前前一颗子弹和后一颗子弹的间距值 * 用于确定是否要发射新的一颗子弹 */ private int offsetMBulletX; /** * 当前漏掉的坦克数量 */ private int overstepNum; /** * 当前难度等级需要消灭坦克数量 */ private int levelNum; /** * 当前难度等级内消灭的敌方坦克数量 */ private int wipeOutNum; /** * 表示第一次标示值,用于添加第一辆敌方坦克逻辑 */ private boolean once = true; public FunGameBattleCityHeader(Context context) { super(context); } public FunGameBattleCityHeader(Context context, AttributeSet attrs) { super(context, attrs); } public FunGameBattleCityHeader(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); } @RequiresApi(Build.VERSION_CODES.LOLLIPOP) public FunGameBattleCityHeader(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); } @Override protected void initConcreteView() { random = new Random(); controllerSize = mHeaderHeight/TANK_ROW_NUM; barrelSize = (int) Math.floor(controllerSize * TANK_BARREL_RATIO + .5f); bulletRadius = (barrelSize - 2 * DIVIDING_LINE_SIZE) * .5f; resetConfigParams(); } @Override protected void drawGame(Canvas canvas, int width, int height) { drawSelfTank(canvas,width); if (status == STATUS_GAME_PLAY || status == STATUS_GAME_FINISHED || status == STATUS_GAME_FAIL) { drawEnemyTank(canvas,width); makeBulletPath(canvas,width); } if (isInEditMode()) { drawTank(canvas, new RectF(controllerSize, 0, controllerSize * 2, controllerSize)); drawTank(canvas, new RectF(0, controllerSize, controllerSize, controllerSize*2)); drawTank(canvas, new RectF(controllerSize * 3, controllerSize * 2, controllerSize * 4, controllerSize*3)); } } @Override protected void resetConfigParams() { status = FunGameView.STATUS_GAME_PREPAR; controllerPosition = DIVIDING_LINE_SIZE; enemySpeed = DensityUtil.dp2px(1); bulletSpeed = DensityUtil.dp2px(4); levelNum = DEFAULT_TANK_MAGIC_TOTAL_NUM; wipeOutNum = 0; once = true; enemyTankSpace = controllerSize + barrelSize + DEFAULT_ENEMY_TANK_NUM_SPACING; bulletSpace = DEFAULT_BULLET_NUM_SPACING; eTankSparseArray = new SparseArray<>(); for (int i = 0; i < TANK_ROW_NUM; i++) { Queue rectFQueue = new LinkedList<>(); eTankSparseArray.put(i, rectFQueue); } mBulletList = new LinkedList<>(); } /** * 由index轨道下标从左边起始位置生成一个用于绘制敌方坦克的Rect * @param index 轨道下标 * @return 敌方坦克矩阵 */ private RectF generateEnemyTank(int index) { float left = - (controllerSize + barrelSize); float top = index * (controllerSize) + DIVIDING_LINE_SIZE; return new RectF(left, top, left + barrelSize * 2.5f, top + controllerSize); } /** * 绘制子弹路径 * @param canvas 默认画布 */ private void makeBulletPath(Canvas canvas, int width) { mPaint.setColor(mModelColor); offsetMBulletX += bulletSpeed; if (offsetMBulletX / bulletSpace == 1) { offsetMBulletX = 0; } if (offsetMBulletX == 0) { Point bulletPoint = new Point(); bulletPoint.x = width - controllerSize - barrelSize; bulletPoint.y = (int) (controllerPosition + controllerSize * .5f); mBulletList.offer(bulletPoint); } boolean isOversetp = false; for (Point point : mBulletList) { if (checkWipeOutETank(point)) { usedBullet = point; continue; } if (point.x + bulletRadius <= 0) { isOversetp = true; } drawBullet(canvas, point); } if (isOversetp) { mBulletList.poll(); } mBulletList.remove(usedBullet); usedBullet = null; } /** * 由Y坐标获取该坐标所在轨道的下标 * @param y 坐标Y值 * @return 轨道下标 */ private int getTrackIndex(int y) { int index = y / (mHeaderHeight / TANK_ROW_NUM); index = index >= TANK_ROW_NUM ? TANK_ROW_NUM - 1 : index; index = index < 0 ? 0 : index; return index; } /** * 判断是否消灭敌方坦克 * @param point 单签子弹坐标点 * @return 消灭:true, 反之:false */ private boolean checkWipeOutETank(Point point) { boolean beHit = false; int trackIndex = getTrackIndex(point.y); RectF rectF = eTankSparseArray.get(trackIndex).peek(); if (rectF != null && rectF.contains(point.x, point.y)) { // 击中 if (++wipeOutNum == levelNum) { upLevel(); } eTankSparseArray.get(trackIndex).poll(); beHit = true; } return beHit; } /** * 难度升级 */ private void upLevel() { levelNum += DEFAULT_TANK_MAGIC_TOTAL_NUM; enemySpeed += DensityUtil.dp2px(1); bulletSpeed += DensityUtil.dp2px(1); wipeOutNum = 0; if (enemyTankSpace > 12) enemyTankSpace -= 12; if (bulletSpace > 30) bulletSpace -= 30; } /** * 绘制子弹 * @param canvas 默认画布 * @param point 子弹圆心坐标点 */ private void drawBullet(Canvas canvas, Point point) { point.x -= bulletSpeed; canvas.drawCircle(point.x, point.y, bulletRadius, mPaint); } /** * 判断我方坦克是否与敌方坦克相撞 * @param index 轨道下标 * @param selfX 我方坦克所在坐标X值 * @param selfY 我方坦克矩阵的top 或者 bottom 值 * @return true:相撞,反之:false */ private boolean checkTankCrash(int index, float selfX, float selfY) { boolean isCrash = false; RectF rectF = eTankSparseArray.get(index).peek(); if (rectF != null && rectF.contains(selfX, selfY)) { isCrash = true; } return isCrash; } /** * 绘制我方坦克 * @param canvas 默认画布 */ private void drawSelfTank(Canvas canvas, int width) { mPaint.setColor(rModelColor); boolean isAboveCrash = checkTankCrash(getTrackIndex((int) controllerPosition), width - controllerSize, controllerPosition); boolean isBelowCrash = checkTankCrash(getTrackIndex((int) (controllerPosition + controllerSize)), width - controllerSize, controllerPosition + controllerSize); if (isAboveCrash || isBelowCrash) { status = STATUS_GAME_OVER; } canvas.drawRect(width - controllerSize, controllerPosition + DIVIDING_LINE_SIZE, width, controllerPosition + controllerSize + DIVIDING_LINE_SIZE, mPaint); canvas.drawRect(width - controllerSize - barrelSize, controllerPosition + (controllerSize - barrelSize) * .5f, width - controllerSize, controllerPosition + (controllerSize - barrelSize) * .5f + barrelSize, mPaint); } /** * 绘制三条轨道上的敌方坦克 * @param canvas 默认画布 */ private void drawEnemyTank(Canvas canvas, int width) { mPaint.setColor(lModelColor); offsetETankX += enemySpeed; if (offsetETankX / enemyTankSpace == 1 || once) { offsetETankX = 0; once = false; } boolean isOverstep = false; int option = apperanceOption(); for (int i = 0; i < TANK_ROW_NUM; i++) { Queue rectFQueue = eTankSparseArray.get(i); if (offsetETankX == 0 && i == option) { rectFQueue.offer(generateEnemyTank(i)); } for (RectF rectF : rectFQueue) { if (rectF.left >= width) { isOverstep = true; if (++overstepNum >= DEFAULT_TANK_MAGIC_TOTAL_NUM) { status = STATUS_GAME_OVER; break; } continue; } drawTank(canvas, rectF); } if (status == STATUS_GAME_OVER) break; if (isOverstep) { rectFQueue.poll(); isOverstep = false; } } invalidate(); } /** * 绘制一辆敌方坦克 * @param canvas 默认画布 * @param rectF 坦克矩阵 */ private void drawTank(Canvas canvas, RectF rectF) { rectF.set(rectF.left + enemySpeed, rectF.top, rectF.right + enemySpeed, rectF.bottom); canvas.drawRect(rectF, mPaint); float barrelTop = rectF.top + (controllerSize - barrelSize) * .5f; canvas.drawRect(rectF.right, barrelTop, rectF.right + barrelSize, barrelTop + barrelSize, mPaint); } /** * 随机定位一个轨道下标值 * @return 轨道下标 */ private int apperanceOption() { return random.nextInt(TANK_ROW_NUM); } } ================================================ FILE: refresh-header/src/main/java/com/scwang/smartrefresh/header/FunGameHitBlockHeader.java ================================================ package com.scwang.smartrefresh.header; import android.content.Context; import android.content.res.TypedArray; import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.Point; import android.os.Build; import android.support.annotation.Nullable; import android.support.annotation.RequiresApi; import android.util.AttributeSet; import com.scwang.smartrefresh.header.fungame.FunGameView; import com.scwang.smartrefresh.layout.util.ColorUtils; import com.scwang.smartrefresh.layout.util.DensityUtil; import java.util.ArrayList; import java.util.List; /** * Created by Hitomis on 2016/2/29. * email:196425254@qq.com */ public class FunGameHitBlockHeader extends FunGameView { /** * 默认矩形块竖向排列的数目 */ private static final int BLOCK_VERTICAL_NUM = 5; /** * 默认矩形块横向排列的数目 */ private static final int BLOCK_HORIZONTAL_NUM = 3; /** * 矩形块的宽度占屏幕宽度比率 */ private static final float BLOCK_WIDTH_RATIO = .01806f; /** * 挡板所在位置占屏幕宽度的比率 */ private static final float RACKET_POSITION_RATIO = .8f; /** * 矩形块所在位置占屏幕宽度的比率 */ private static final float BLOCK_POSITION_RATIO = .08f; /** * 小球默认其实弹射角度 */ private static final int DEFAULT_ANGLE = 30; /** * 分割线默认宽度大小 */ static final float DIVIDING_LINE_SIZE = 1.f; /** * 小球移动速度 */ private static final int SPEED = 3; /** * 矩形砖块的高度、宽度 */ private float blockHeight, blockWidth; /** * 小球半径 */ private float BALL_RADIUS; private Paint blockPaint; private float blockLeft, racketLeft; private float cx, cy; private List pointList; private boolean isleft; private int angle; private int blockHorizontalNum; private int speed; public FunGameHitBlockHeader(Context context) { super(context); initView(context, null); } public FunGameHitBlockHeader(Context context, AttributeSet attrs) { super(context, attrs); initView(context, attrs); } public FunGameHitBlockHeader(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); initView(context, attrs); } @RequiresApi(Build.VERSION_CODES.LOLLIPOP) public FunGameHitBlockHeader(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); initView(context, attrs); } private void initView(Context context, AttributeSet attrs) { TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.FunGameHitBlockHeader); blockHorizontalNum = typedArray.getInt(R.styleable.FunGameHitBlockHeader_fgvBlockHorizontalNum, BLOCK_HORIZONTAL_NUM); speed = typedArray.getInt(R.styleable.FunGameHitBlockHeader_fgvBallSpeed, DensityUtil.dp2px(SPEED)); typedArray.recycle(); blockPaint = new Paint(Paint.ANTI_ALIAS_FLAG); blockPaint.setStyle(Paint.Style.FILL); BALL_RADIUS = DensityUtil.dp2px(4); } @Override protected void initConcreteView() { final int measuredWidth = getMeasuredWidth(); controllerSize = (int) (blockHeight * 1.6f); blockHeight = mHeaderHeight / BLOCK_VERTICAL_NUM - DIVIDING_LINE_SIZE; blockWidth = measuredWidth * BLOCK_WIDTH_RATIO; blockLeft = measuredWidth * BLOCK_POSITION_RATIO; racketLeft = measuredWidth * RACKET_POSITION_RATIO; controllerSize = (int) (blockHeight * 1.6f); } @Override protected void drawGame(Canvas canvas, int width, int height) { drawColorBlock(canvas); drawRacket(canvas); if (status == STATUS_GAME_PLAY || status == STATUS_GAME_FINISHED || status == STATUS_GAME_FAIL || isInEditMode()) { makeBallPath(canvas, width); } } @Override protected void resetConfigParams() { cx = racketLeft - 3 * BALL_RADIUS; cy = (int) (mHeaderHeight * .5f); controllerPosition = DIVIDING_LINE_SIZE; angle = DEFAULT_ANGLE; isleft = true; if (pointList == null) { pointList = new ArrayList<>(); } else { pointList.clear(); } } /** * 绘制挡板 * @param canvas 默认画布 */ private void drawRacket(Canvas canvas) { mPaint.setColor(rModelColor); canvas.drawRect(racketLeft, controllerPosition, racketLeft + blockWidth, controllerPosition + controllerSize, mPaint); } /** * 绘制并处理小球运动的轨迹 * @param canvas 默认画布 * @param width 视图宽度 */ private void makeBallPath(Canvas canvas, int width) { mPaint.setColor(mModelColor); if (cx <= blockLeft + blockHorizontalNum * blockWidth + (blockHorizontalNum - 1) * DIVIDING_LINE_SIZE + BALL_RADIUS) { // 小球进入到色块区域 if (checkTouchBlock(cx, cy)) { // 反弹回来 isleft = false; } } if (cx <= blockLeft + BALL_RADIUS ) { // 小球穿过色块区域 isleft = false; } if (cx + BALL_RADIUS >= racketLeft && cx - BALL_RADIUS < racketLeft + blockWidth) { //小球当前坐标X值在挡板X值区域范围内 if (checkTouchRacket(cy)) { // 小球与挡板接触 if (pointList.size() == blockHorizontalNum * BLOCK_VERTICAL_NUM) { // 矩形块全部被消灭,游戏结束 status = STATUS_GAME_OVER; return; } isleft = true; } } else if (cx > width) { // 小球超出挡板区域 status = STATUS_GAME_OVER; } if (cy <= BALL_RADIUS + DIVIDING_LINE_SIZE) { // 小球撞到上边界 angle = 180 - DEFAULT_ANGLE; } else if (cy >= mHeaderHeight - BALL_RADIUS - DIVIDING_LINE_SIZE) { // 小球撞到下边界 angle = 180 + DEFAULT_ANGLE; } if (isleft) { cx -= speed; } else { cx += speed; } cy -= (float) Math.tan(Math.toRadians(angle)) * speed; canvas.drawCircle(cx, cy, BALL_RADIUS, mPaint); invalidate(); } /** * 检查小球是否撞击到挡板 * @param y 小球当前坐标Y值 * @return 小球位于挡板Y值区域范围内:true,反之:false */ private boolean checkTouchRacket(float y) { boolean flag = false; float diffVal = y - controllerPosition; if (diffVal >= 0 && diffVal <= controllerSize) { // 小球位于挡板Y值区域范围内 flag = true; } return flag; } /** * 检查小球是否撞击到矩形块 * @param x 小球坐标X值 * @param y 小球坐标Y值 * @return 撞击到:true,反之:false */ private boolean checkTouchBlock(float x, float y) { int columnX = (int) ((x - blockLeft - BALL_RADIUS - speed ) / blockWidth); columnX = columnX == blockHorizontalNum ? columnX - 1 : columnX; int rowY = (int) (y / blockHeight); rowY = rowY == BLOCK_VERTICAL_NUM ? rowY - 1 : rowY; Point p = new Point(); p.set(columnX, rowY); boolean flag = false; for (Point point : pointList) { if (point.equals(p.x, p.y)) { flag = true; break; } } if (!flag) { pointList.add(p); } return !flag; } /** * 绘制矩形色块 * @param canvas 默认画布 */ private void drawColorBlock(Canvas canvas) { float left, top; int column, row; for (int i = 0; i < blockHorizontalNum * BLOCK_VERTICAL_NUM; i++) { row = i / blockHorizontalNum; column = i % blockHorizontalNum; boolean flag = false; for (Point point : pointList) { if (point.equals(column, row)) { flag = true; break; } } if (flag) { continue; } blockPaint.setColor(ColorUtils.setAlphaComponent(lModelColor, 255 / (column + 1))); left = blockLeft + column * (blockWidth + DIVIDING_LINE_SIZE); top = DIVIDING_LINE_SIZE + row * (blockHeight + DIVIDING_LINE_SIZE); canvas.drawRect(left, top, left + blockWidth, top + blockHeight, blockPaint); } } } ================================================ FILE: refresh-header/src/main/java/com/scwang/smartrefresh/header/MaterialHeader.java ================================================ package com.scwang.smartrefresh.header; import android.support.annotation.RequiresApi; import android.content.Context; import android.content.res.TypedArray; import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.Path; import android.os.Build; import android.support.annotation.NonNull; import android.support.annotation.VisibleForTesting; import android.util.AttributeSet; import android.util.DisplayMetrics; import android.view.View; import android.view.ViewGroup; import com.scwang.smartrefresh.header.internal.MaterialProgressDrawable; import com.scwang.smartrefresh.header.material.CircleImageView; import com.scwang.smartrefresh.layout.api.RefreshHeader; import com.scwang.smartrefresh.layout.api.RefreshKernel; import com.scwang.smartrefresh.layout.api.RefreshLayout; import com.scwang.smartrefresh.layout.constant.RefreshState; import com.scwang.smartrefresh.layout.constant.SpinnerStyle; import com.scwang.smartrefresh.layout.util.DensityUtil; import static android.view.View.MeasureSpec.getSize; /** * Material 主题下拉头 * Created by SCWANG on 2017/6/2. */ @SuppressWarnings("unused") public class MaterialHeader extends ViewGroup implements RefreshHeader { // Maps to ProgressBar.Large style public static final int SIZE_LARGE = 0; // Maps to ProgressBar default style public static final int SIZE_DEFAULT = 1; private static final int CIRCLE_BG_LIGHT = 0xFFFAFAFA; private static final float MAX_PROGRESS_ANGLE = .8f; @VisibleForTesting private static final int CIRCLE_DIAMETER = 40; @VisibleForTesting private static final int CIRCLE_DIAMETER_LARGE = 56; private boolean mFinished; private int mCircleDiameter; private CircleImageView mCircleView; private MaterialProgressDrawable mProgress; /** * 贝塞尔背景 */ private int mWaveHeight; private int mHeadHeight; private Path mBezierPath; private Paint mBezierPaint; private boolean mShowBezierWave = false; private RefreshState mState; // public MaterialHeader(Context context) { super(context); initView(context, null); } public MaterialHeader(Context context, AttributeSet attrs) { super(context, attrs); initView(context, attrs); } public MaterialHeader(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); initView(context, attrs); } @RequiresApi(Build.VERSION_CODES.LOLLIPOP) public MaterialHeader(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); initView(context, attrs); } private void initView(Context context, AttributeSet attrs) { setMinimumHeight(DensityUtil.dp2px(100)); mProgress = new MaterialProgressDrawable(context, this); mProgress.setBackgroundColor(CIRCLE_BG_LIGHT); mProgress.setAlpha(255); mProgress.setColorSchemeColors(0xff0099cc,0xffff4444,0xff669900,0xffaa66cc,0xffff8800); mCircleView = new CircleImageView(context,CIRCLE_BG_LIGHT); mCircleView.setImageDrawable(mProgress); mCircleView.setVisibility(View.GONE); addView(mCircleView); final DisplayMetrics metrics = getResources().getDisplayMetrics(); mCircleDiameter = (int) (CIRCLE_DIAMETER * metrics.density); mBezierPath = new Path(); mBezierPaint = new Paint(); mBezierPaint.setAntiAlias(true); mBezierPaint.setStyle(Paint.Style.FILL); TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.MaterialHeader); mShowBezierWave = ta.getBoolean(R.styleable.MaterialHeader_mhShowBezierWave, mShowBezierWave); mBezierPaint.setColor(ta.getColor(R.styleable.MaterialHeader_mhPrimaryColor, 0xff11bbff)); if (ta.hasValue(R.styleable.MaterialHeader_mhShadowRadius)) { int radius = ta.getDimensionPixelOffset(R.styleable.MaterialHeader_mhShadowRadius, 0); int color = ta.getColor(R.styleable.MaterialHeader_mhShadowColor, 0xff000000); mBezierPaint.setShadowLayer(radius, 0, 0, color); setLayerType(LAYER_TYPE_SOFTWARE, null); } ta.recycle(); } @Override public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { setMeasuredDimension(getSize(widthMeasureSpec), getSize(heightMeasureSpec)); mCircleView.measure(MeasureSpec.makeMeasureSpec(mCircleDiameter, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(mCircleDiameter, MeasureSpec.EXACTLY)); // setMeasuredDimension(resolveSize(getSuggestedMinimumWidth(), widthMeasureSpec), // resolveSize(getSuggestedMinimumHeight(), heightMeasureSpec)); } @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { if (getChildCount() == 0) { return; } final int width = getMeasuredWidth(); int circleWidth = mCircleView.getMeasuredWidth(); int circleHeight = mCircleView.getMeasuredHeight(); if (isInEditMode() && mHeadHeight > 0) { int circleTop = mHeadHeight - circleHeight / 2; mCircleView.layout((width / 2 - circleWidth / 2), circleTop, (width / 2 + circleWidth / 2), circleTop + circleHeight); mProgress.showArrow(true); mProgress.setStartEndTrim(0f, MAX_PROGRESS_ANGLE); mProgress.setArrowScale(1); mCircleView.setAlpha(1f); mCircleView.setVisibility(VISIBLE); } else { mCircleView.layout((width / 2 - circleWidth / 2), -mCircleDiameter, (width / 2 + circleWidth / 2), circleHeight - mCircleDiameter); } } @Override protected void dispatchDraw(Canvas canvas) { if (mShowBezierWave) { //重置画笔 mBezierPath.reset(); mBezierPath.lineTo(0, mHeadHeight); //绘制贝塞尔曲线 mBezierPath.quadTo(getMeasuredWidth() / 2, mHeadHeight + mWaveHeight * 1.9f, getMeasuredWidth(), mHeadHeight); mBezierPath.lineTo(getMeasuredWidth(), 0); canvas.drawPath(mBezierPath, mBezierPaint); } super.dispatchDraw(canvas); } // // /** * One of DEFAULT, or LARGE. */ public MaterialHeader setSize(int size) { if (size != SIZE_LARGE && size != SIZE_DEFAULT) { return this; } DisplayMetrics metrics = getResources().getDisplayMetrics(); if (size == SIZE_LARGE) { mCircleDiameter = (int) (CIRCLE_DIAMETER_LARGE * metrics.density); } else { mCircleDiameter = (int) (CIRCLE_DIAMETER * metrics.density); } // force the bounds of the progress circle inside the circle view to // update by setting it to null before updating its size and then // re-setting it mCircleView.setImageDrawable(null); mProgress.updateSizes(size); mCircleView.setImageDrawable(mProgress); return this; } public MaterialHeader setShowBezierWave(boolean show) { this.mShowBezierWave = show; return this; } // // @Override public void onInitialized(RefreshKernel kernel, int height, int extendHeight) { if (isInEditMode()) { mWaveHeight = mHeadHeight = height / 2; } } @Override public boolean isSupportHorizontalDrag() { return false; } @Override public void onHorizontalDrag(float percentX, int offsetX, int offsetMax) { } @Override public void onPullingDown(float percent, int offset, int headHeight, int extendHeight) { if (mShowBezierWave) { mHeadHeight = Math.min(offset, headHeight); mWaveHeight = Math.max(0, offset - headHeight); postInvalidate(); } if (mState != RefreshState.Refreshing) { float originalDragPercent = 1f * offset / headHeight; float dragPercent = Math.min(1f, Math.abs(originalDragPercent)); float adjustedPercent = (float) Math.max(dragPercent - .4, 0) * 5 / 3; float extraOS = Math.abs(offset) - headHeight; float tensionSlingshotPercent = Math.max(0, Math.min(extraOS, (float) headHeight * 2) / (float) headHeight); float tensionPercent = (float) ((tensionSlingshotPercent / 4) - Math.pow( (tensionSlingshotPercent / 4), 2)) * 2f; float strokeStart = adjustedPercent * .8f; mProgress.showArrow(true); mProgress.setStartEndTrim(0f, Math.min(MAX_PROGRESS_ANGLE, strokeStart)); mProgress.setArrowScale(Math.min(1f, adjustedPercent)); float rotation = (-0.25f + .4f * adjustedPercent + tensionPercent * 2) * .5f; mProgress.setProgressRotation(rotation); mCircleView.setAlpha(Math.min(1f, originalDragPercent*2)); } float targetY = offset / 2 + mCircleDiameter / 2; mCircleView.setTranslationY(Math.min(offset, targetY));//setTargetOffsetTopAndBottom(targetY - mCurrentTargetOffsetTop, true /* requires update */); } @Override public void onReleasing(float percent, int offset, int headHeight, int extendHeight) { if (!mProgress.isRunning() && !mFinished) { onPullingDown(percent, offset, headHeight, extendHeight); } else { if (mShowBezierWave) { mHeadHeight = Math.min(offset, headHeight); mWaveHeight = Math.max(0, offset - headHeight); postInvalidate(); } } } @Override public void onStartAnimator(RefreshLayout layout, int headHeight, int extendHeight) { mProgress.start(); if ((int) mCircleView.getTranslationY() != headHeight / 2 + mCircleDiameter / 2) { mCircleView.animate().translationY(headHeight / 2 + mCircleDiameter / 2); } } @Override public void onStateChanged(RefreshLayout refreshLayout, RefreshState oldState, RefreshState newState) { mState = newState; switch (newState) { case None: break; case PullDownToRefresh: mFinished = false; mCircleView.setVisibility(VISIBLE); mCircleView.setScaleX(1); mCircleView.setScaleY(1); break; case ReleaseToRefresh: break; case Refreshing: break; } } @Override public int onFinish(RefreshLayout layout, boolean success) { mProgress.stop(); mCircleView.animate().scaleX(0).scaleY(0); mFinished = true; return 0; } @Override@Deprecated public void setPrimaryColors(int... colors) { if (colors.length > 0) { mBezierPaint.setColor(colors[0]); } } @NonNull @Override public View getView() { return this; } @Override public SpinnerStyle getSpinnerStyle() { return SpinnerStyle.MatchLayout; } // // public MaterialHeader setColorSchemeColors(int... colors) { mProgress.setColorSchemeColors(colors); return this; } // } ================================================ FILE: refresh-header/src/main/java/com/scwang/smartrefresh/header/PhoenixHeader.java ================================================ package com.scwang.smartrefresh.header; import android.support.annotation.RequiresApi; import android.content.Context; import android.content.res.Resources; import android.content.res.TypedArray; import android.graphics.Canvas; import android.graphics.Matrix; import android.graphics.Rect; import android.os.Build; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.util.AttributeSet; import android.view.View; import android.view.animation.Animation; import android.view.animation.Interpolator; import android.view.animation.LinearInterpolator; import android.view.animation.Transformation; import com.scwang.smartrefresh.layout.api.RefreshHeader; import com.scwang.smartrefresh.layout.api.RefreshKernel; import com.scwang.smartrefresh.layout.api.RefreshLayout; import com.scwang.smartrefresh.layout.constant.RefreshState; import com.scwang.smartrefresh.layout.constant.SpinnerStyle; import com.scwang.smartrefresh.layout.internal.pathview.PathsDrawable; import com.scwang.smartrefresh.layout.util.DensityUtil; /** * Phoenix * Created by SCWANG on 2017/5/31. */ public class PhoenixHeader extends View implements RefreshHeader/*, SizeDefinition*/ { // private static final int ANIMATION_DURATION = 1000; private static final float SUN_INITIAL_ROTATE_GROWTH = 1.2f; private static final Interpolator LINEAR_INTERPOLATOR = new LinearInterpolator(); private static String[] townPaths = new String[]{ "m 527,396 v -8 h 39.5 39.5 v 8 8 H 566.5 527 Z m 84,-6 v -14 h 7.5 7.5 v 14 14 h -7.5 -7.5 z m 20,6 v -8 h 65.5 65.5 v 8 8 H 696.5 631 Z m 143,-54 v -62 h 52 52 v 62 62 h -52 -52 z m 44,34 c 0,-3.33333 -0.33333,-4 -2,-4 -1.91667,0 -2,-0.66667 -2,-16 v -16 h -10 -10 v 16 c 0,15.33333 -0.0833,16 -2,16 -1.66667,0 -2,0.66667 -2,4 v 4 h 14 14 z m -20,-12 v -8 h 6 6 v 8 8 h -6 -6 z m 0,-16 v -4 h 6 6 v 4 4 h -6 -6 z m 64,28 c 0,-3.33333 -0.33333,-4 -2,-4 -1.91667,0 -2,-0.66667 -2,-16 v -16 h -10 -10 v 16 c 0,15.33333 -0.0833,16 -2,16 -1.66667,0 -2,0.66667 -2,4 v 4 h 14 14 z m -20,-12 v -8 h 6 6 v 8 8 h -6 -6 z m 0,-16 v -4 h 6 6 v 4 4 h -6 -6 z m -24,-24 c 0,-3.33333 -0.33333,-4 -2,-4 -1.91667,0 -2,-0.66667 -2,-16 v -16 h -10 -10 v 16 c 0,15.33333 -0.0833,16 -2,16 -1.66667,0 -2,0.66667 -2,4 v 4 h 14 14 z m -20,-12 v -8 h 6 6 v 8 8 h -6 -6 z m 0,-16 v -4 h 6 6 v 4 4 h -6 -6 z m 64,28 c 0,-3.33333 -0.33333,-4 -2,-4 -1.91667,0 -2,-0.66667 -2,-16 v -16 h -10 -10 v 16 c 0,15.33333 -0.0833,16 -2,16 -1.66667,0 -2,0.66667 -2,4 v 4 h 14 14 z m -20,-12 v -8 h 6 6 v 8 8 h -6 -6 z m 0,-16 v -4 h 6 6 v 4 4 h -6 -6 z m 448,46 v -62 h 51.5 51.5 v 62 62 h -29.5 -29.5 v -26 -26 h -14.5 -14.5 v 26 26 h -7.5 -7.5 z m 88,42 c 0,-3.33333 -0.3333,-4 -2,-4 -1.9048,0 -2,-0.66667 -2,-14 v -14 h -10.5 -10.5 v 14 c 0,13.33333 -0.095,14 -2,14 -1.6667,0 -2,0.66667 -2,4 v 4 h 14.5 14.5 z m -21,-10 v -6 h 6.5 6.5 v 6 6 h -6.5 -6.5 z m 0,-14 v -4 h 6.5 6.5 v 4 4 h -6.5 -6.5 z m -23,-24 c 0,-3.33333 -0.3333,-4 -2,-4 -1.9048,0 -2,-0.66667 -2,-14 v -14 h -10.5 -10.5 v 14 c 0,13.33333 -0.095,14 -2,14 -1.6667,0 -2,0.66667 -2,4 v 4 h 14.5 14.5 z m -21,-10 v -6 h 6.5 6.5 v 6 6 h -6.5 -6.5 z m 0,-14 v -4 h 6.5 6.5 v 4 4 h -6.5 -6.5 z m 65,24 c 0,-3.33333 -0.3333,-4 -2,-4 -1.9048,0 -2,-0.66667 -2,-14 v -14 h -10.5 -10.5 v 14 c 0,13.33333 -0.095,14 -2,14 -1.6667,0 -2,0.66667 -2,4 v 4 h 14.5 14.5 z m -21,-10 v -6 h 6.5 6.5 v 6 6 h -6.5 -6.5 z m 0,-14 v -4 h 6.5 6.5 v 4 4 h -6.5 -6.5 z M 29.8125,370.31689 c 0.721875,-0.28887 1.584375,-0.25335 1.916667,0.0789 0.332291,0.33229 -0.258334,0.56864 -1.3125,0.52522 -1.164943,-0.048 -1.4019,-0.28494 -0.604167,-0.60416 z m 1811,0 c 0.7219,-0.28887 1.5844,-0.25335 1.9167,0.0789 0.3323,0.33229 -0.2584,0.56864 -1.3125,0.52522 -1.165,-0.048 -1.4019,-0.28494 -0.6042,-0.60416 z M 527,324 v -24 h 31.5 31.5 v 24 24 H 558.5 527 Z m 68,0 v -24 h 83.5 83.5 v 24 24 H 678.5 595 Z m 69,10 c 0,-1.95471 -0.66667,-2 -29.44098,-2 -28.44028,0 -31.71136,0.32647 -30.05902,3 0.38938,0.63003 11.51038,1 30.05902,1 C 663.33333,336 664,335.95471 664,334 Z m 24,0 c 0,-1.83333 -0.66667,-2 -8,-2 -7.33333,0 -8,0.16667 -8,2 0,1.83333 0.66667,2 8,2 7.33333,0 8,-0.16667 8,-2 z m 64,0 c 0,-1.94872 -0.66667,-2 -26,-2 -25.33333,0 -26,0.0513 -26,2 0,1.94872 0.66667,2 26,2 25.33333,0 26,-0.0513 26,-2 z M 363.5,317 c 0.99549,-1.1 2.03498,-2 2.30998,-2 0.275,0 -0.31449,0.9 -1.30998,2 -0.99549,1.1 -2.03498,2 -2.30998,2 -0.275,0 0.31449,-0.9 1.30998,-2 z m 1143.905,-0.25 -1.905,-2.25 2.25,1.90499 c 2.1144,1.79022 2.7052,2.59501 1.905,2.59501 -0.1898,0 -1.2023,-1.0125 -2.25,-2.25 z m -1095.00001,-1 -1.90499,-2.25 2.25,1.90499 c 2.11444,1.79022 2.70524,2.59501 1.90499,2.59501 -0.18976,0 -1.20226,-1.0125 -2.25,-2.25 z M 1458.5,316 c 0.9955,-1.1 2.035,-2 2.31,-2 0.275,0 -0.3145,0.9 -1.31,2 -0.9955,1.1 -2.035,2 -2.31,2 -0.275,0 0.3145,-0.9 1.31,-2 z M 161.92431,313.25 159.5,310.5 l 2.75,2.42431 c 2.57029,2.26589 3.20859,3.07569 2.42431,3.07569 -0.17912,0 -1.41662,-1.2375 -2.75,-2.75 z M 1709,313.5 c 1.2917,-1.375 2.5736,-2.5 2.8486,-2.5 0.275,0 -0.5569,1.125 -1.8486,2.5 -1.2917,1.375 -2.5736,2.5 -2.8486,2.5 -0.275,0 0.5569,-1.125 1.8486,-2.5 z m -1608,1.12244 c 0,-0.20765 0.7875,-0.99515 1.75,-1.75 1.58606,-1.24387 1.62143,-1.2085 0.37756,0.37756 C 101.82119,314.91575 101,315.44548 101,314.62244 Z M 1769,313.5 c -0.6847,-0.825 -1.0199,-1.5 -0.7449,-1.5 0.275,0 1.0602,0.675 1.7449,1.5 0.6847,0.825 1.0199,1.5 0.7449,1.5 -0.275,0 -1.0602,-0.675 -1.7449,-1.5 z M 527,282 v -6 h 7.5 7.5 v 6 6 h -7.5 -7.5 z m 20,0 v -6 h 5.5 5.5 v 6 6 h -5.5 -5.5 z m 16,0 v -6 h 5.5 5.5 v 6 6 h -5.5 -5.5 z m 16,0 v -6 h 5.5 5.5 v 6 6 h -5.5 -5.5 z m 16,0 v -6 h 5.5 5.5 v 6 6 h -5.5 -5.5 z m 16,0 v -6 h 5.5 5.5 v 6 6 h -5.5 -5.5 z m 16,0 v -6 h 5.5 5.5 v 6 6 h -5.5 -5.5 z m 16,0 v -6 h 5.5 5.5 v 6 6 h -5.5 -5.5 z m 16,0 v -6 h 5.5 5.5 v 6 6 h -5.5 -5.5 z m 15.2001,0.25 0.2999,-5.75 5.75,-0.2999 5.75,-0.29991 V 281.9501 288 h -6.0499 -6.04991 z m 16,0 0.2999,-5.75 5.75,-0.2999 5.75,-0.29991 V 281.9501 288 h -6.0499 -6.04991 z m 16,0 0.2999,-5.75 5.75,-0.2999 5.75,-0.29991 V 281.9501 288 h -6.0499 -6.04991 z m 16,0 0.2999,-5.75 5.75,-0.2999 5.75,-0.29991 V 281.9501 288 h -6.0499 -6.04991 z m 16,0 0.2999,-5.75 5.75,-0.2999 5.75,-0.29991 V 281.9501 288 h -6.0499 -6.04991 z m 16,0 0.2999,-5.75 3.75,-0.31037 3.75,-0.31036 V 281.93963 288 h -4.0499 -4.04991 z M 774,274 c 0,-1.91667 0.66667,-2 16,-2 15.33333,0 16,0.0833 16,2 0,1.91667 -0.66667,2 -16,2 -15.33333,0 -16,-0.0833 -16,-2 z m 36,0 c 0,-1.91667 0.66667,-2 16,-2 15.33333,0 16,0.0833 16,2 0,1.91667 -0.66667,2 -16,2 -15.33333,0 -16,-0.0833 -16,-2 z m 36,0 c 0,-1.91667 0.66667,-2 16,-2 15.33333,0 16,0.0833 16,2 0,1.91667 -0.66667,2 -16,2 -15.33333,0 -16,-0.0833 -16,-2 z m 444,0 c 0,-1.97411 0.6667,-2 51.5,-2 50.8333,0 51.5,0.0259 51.5,2 0,1.97411 -0.6667,2 -51.5,2 -50.8333,0 -51.5,-0.0259 -51.5,-2 z M 774,258 v -10 h 4 4 v 10 10 h -4 -4 z m 12,0 v -10 h 4 4 v 10 10 h -4 -4 z m 12,0 v -10 h 4 4 v 10 10 h -4 -4 z m 12,0 v -10 h 4 4 v 10 10 h -4 -4 z m 12,0 v -10 h 4 4 v 10 10 h -4 -4 z m 12,0 v -10 h 4 4 v 10 10 h -4 -4 z m 12,0 v -10 h 4 4 v 10 10 h -4 -4 z m 12,0 v -10 h 4 4 v 10 10 h -4 -4 z m 12,0 v -10 h 4 4 v 10 10 h -4 -4 z m 420,0 v -10 h 51.5 51.5 v 10 10 h -51.5 -51.5 z m 23.6418,-26.34524 c 2.672,-2.38988 10.2499,-9.1272 16.8397,-14.97183 l 11.9814,-10.6266 3.0186,2.80156 c 1.6602,1.54085 8.981,8.09794 16.2685,14.57131 7.2875,6.47336 13.25,11.94998 13.25,12.17027 0,0.22029 -14.8987,0.40053 -33.1082,0.40053 h -33.1081 z M 1394,228.5 c -0.6847,-0.825 -1.0199,-1.5 -0.7449,-1.5 0.275,0 1.0602,0.675 1.7449,1.5 0.6847,0.825 1.0199,1.5 0.7449,1.5 -0.275,0 -1.0602,-0.675 -1.7449,-1.5 z m -103,-0.87756 c 0,-0.20765 0.7875,-0.99515 1.75,-1.75 1.5861,-1.24387 1.6214,-1.2085 0.3776,0.37756 -1.3064,1.66575 -2.1276,2.19548 -2.1276,1.37244 z m 93.405,-7.87244 -1.905,-2.25 2.25,1.90499 c 2.1144,1.79022 2.7052,2.59501 1.905,2.59501 -0.1898,0 -1.2023,-1.0125 -2.25,-2.25 z M 1301,218.62244 c 0,-0.20765 0.7875,-0.99515 1.75,-1.75 1.5861,-1.24387 1.6214,-1.2085 0.3776,0.37756 -1.3064,1.66575 -2.1276,2.19548 -2.1276,1.37244 z M 1375,211.5 c -0.6847,-0.825 -1.0199,-1.5 -0.7449,-1.5 0.275,0 1.0602,0.675 1.7449,1.5 0.6847,0.825 1.0199,1.5 0.7449,1.5 -0.275,0 -1.0602,-0.675 -1.7449,-1.5 z m -65,-0.87756 c 0,-0.20765 0.7875,-0.99515 1.75,-1.75 1.5861,-1.24387 1.6214,-1.2085 0.3776,0.37756 -1.3064,1.66575 -2.1276,2.19548 -2.1276,1.37244 z M 1366,203.5 c -0.6847,-0.825 -1.0199,-1.5 -0.7449,-1.5 0.275,0 1.0602,0.675 1.7449,1.5 0.6847,0.825 1.0199,1.5 0.7449,1.5 -0.275,0 -1.0602,-0.675 -1.7449,-1.5 z M 1141.4469,172 c 0,-17.875 0.1356,-25.1875 0.3013,-16.25 0.1657,8.9375 0.1657,23.5625 0,32.5 -0.1657,8.9375 -0.3013,1.625 -0.3013,-16.25 z M 1319,202.62244 c 0,-0.20765 0.7875,-0.99515 1.75,-1.75 1.5861,-1.24387 1.6214,-1.2085 0.3776,0.37756 -1.3064,1.66575 -2.1276,2.19548 -2.1276,1.37244 z M 1357,195.5 c -0.6847,-0.825 -1.0199,-1.5 -0.7449,-1.5 0.275,0 1.0602,0.675 1.7449,1.5 0.6847,0.825 1.0199,1.5 0.7449,1.5 -0.275,0 -1.0602,-0.675 -1.7449,-1.5 z m -29,-0.87756 c 0,-0.20765 0.7875,-0.99515 1.75,-1.75 1.5861,-1.24387 1.6214,-1.2085 0.3776,0.37756 -1.3064,1.66575 -2.1276,2.19548 -2.1276,1.37244 z M 1348,187.5 c -0.6847,-0.825 -1.0199,-1.5 -0.7449,-1.5 0.275,0 1.0602,0.675 1.7449,1.5 0.6847,0.825 1.0199,1.5 0.7449,1.5 -0.275,0 -1.0602,-0.675 -1.7449,-1.5 z m -11,-0.87756 c 0,-0.20765 0.7875,-0.99515 1.75,-1.75 1.5861,-1.24387 1.6214,-1.2085 0.3776,0.37756 -1.3064,1.66575 -2.1276,2.19548 -2.1276,1.37244 z" , "m 918,382 c 0,-1.83333 0.66667,-2 8,-2 7.33333,0 8,0.16667 8,2 0,1.83333 -0.66667,2 -8,2 -7.33333,0 -8,-0.16667 -8,-2 z m 40,0 c 0,-1.83333 0.66667,-2 8,-2 7.33333,0 8,0.16667 8,2 0,1.83333 -0.66667,2 -8,2 -7.33333,0 -8,-0.16667 -8,-2 z m 40,0 c 0,-1.83333 0.66667,-2 8,-2 7.3333,0 8,0.16667 8,2 0,1.83333 -0.6667,2 -8,2 -7.33333,0 -8,-0.16667 -8,-2 z m -80,-10 v -4 h 8 8 v 4 4 h -8 -8 z m 40,0 v -4 h 8 8 v 4 4 h -8 -8 z m 40,0 v -4 h 8 8 v 4 4 h -8 -8 z M 32.75,370.33772 c 0.6875,-0.27741 1.8125,-0.27741 2.5,0 0.6875,0.27741 0.125,0.50439 -1.25,0.50439 -1.375,0 -1.9375,-0.22698 -1.25,-0.50439 z m 1804,0 c 0.6875,-0.27741 1.8125,-0.27741 2.5,0 0.6875,0.27741 0.125,0.50439 -1.25,0.50439 -1.375,0 -1.9375,-0.22698 -1.25,-0.50439 z M 918,346 c 0,-1.83333 0.66667,-2 8,-2 7.33333,0 8,0.16667 8,2 0,1.83333 -0.66667,2 -8,2 -7.33333,0 -8,-0.16667 -8,-2 z m 40,0 c 0,-1.83333 0.66667,-2 8,-2 7.33333,0 8,0.16667 8,2 0,1.83333 -0.66667,2 -8,2 -7.33333,0 -8,-0.16667 -8,-2 z m 40,0 c 0,-1.83333 0.66667,-2 8,-2 7.3333,0 8,0.16667 8,2 0,1.83333 -0.6667,2 -8,2 -7.33333,0 -8,-0.16667 -8,-2 z m -80,-10 v -4 h 8 8 v 4 4 h -8 -8 z m 40,0 v -4 h 8 8 v 4 4 h -8 -8 z m 40,0 v -4 h 8 8 v 4 4 h -8 -8 z m -80,-26 c 0,-1.83333 0.66667,-2 8,-2 7.33333,0 8,0.16667 8,2 0,1.83333 -0.66667,2 -8,2 -7.33333,0 -8,-0.16667 -8,-2 z m 40,0 c 0,-1.83333 0.66667,-2 8,-2 7.33333,0 8,0.16667 8,2 0,1.83333 -0.66667,2 -8,2 -7.33333,0 -8,-0.16667 -8,-2 z m 40,0 c 0,-1.83333 0.66667,-2 8,-2 7.3333,0 8,0.16667 8,2 0,1.83333 -0.6667,2 -8,2 -7.33333,0 -8,-0.16667 -8,-2 z m -564,-2.37756 c 0,-0.20765 0.7875,-0.99515 1.75,-1.75 1.58606,-1.24387 1.62143,-1.2085 0.37756,0.37756 C 434.82119,307.91575 434,308.44548 434,307.62244 Z M 485.40499,305.75 483.5,303.5 l 2.25,1.90499 c 2.11444,1.79022 2.70524,2.59501 1.90499,2.59501 -0.18976,0 -1.20226,-1.0125 -2.25,-2.25 z M 1436,306.5 c -0.6847,-0.825 -1.0199,-1.5 -0.7449,-1.5 0.275,0 1.0602,0.675 1.7449,1.5 0.6847,0.825 1.0199,1.5 0.7449,1.5 -0.275,0 -1.0602,-0.675 -1.7449,-1.5 z M 387.75,306.33772 c 0.6875,-0.27741 1.8125,-0.27741 2.5,0 0.6875,0.27741 0.125,0.50439 -1.25,0.50439 -1.375,0 -1.9375,-0.22698 -1.25,-0.50439 z m 1094,0 c 0.6875,-0.27741 1.8125,-0.27741 2.5,0 0.6875,0.27741 0.125,0.50439 -1.25,0.50439 -1.375,0 -1.9375,-0.22698 -1.25,-0.50439 z M 918,300 v -4 h 8 8 v 4 4 h -8 -8 z m 40,0 v -4 h 8 8 v 4 4 h -8 -8 z m 40,0 v -4 h 8 8 v 4 4 h -8 -8 z m -80,-26 c 0,-1.83333 0.66667,-2 8,-2 7.33333,0 8,0.16667 8,2 0,1.83333 -0.66667,2 -8,2 -7.33333,0 -8,-0.16667 -8,-2 z m 40,0 c 0,-1.83333 0.66667,-2 8,-2 7.33333,0 8,0.16667 8,2 0,1.83333 -0.66667,2 -8,2 -7.33333,0 -8,-0.16667 -8,-2 z m 40,0 c 0,-1.83333 0.66667,-2 8,-2 7.3333,0 8,0.16667 8,2 0,1.83333 -0.6667,2 -8,2 -7.33333,0 -8,-0.16667 -8,-2 z m -80,-10 v -4 h 8 8 v 4 4 h -8 -8 z m 40,0 v -4 h 8 8 v 4 4 h -8 -8 z m 40,0 v -4 h 8 8 v 4 4 h -8 -8 z m -80,-26 c 0,-1.83333 0.66667,-2 8,-2 7.33333,0 8,0.16667 8,2 0,1.83333 -0.66667,2 -8,2 -7.33333,0 -8,-0.16667 -8,-2 z m 40,0 c 0,-1.83333 0.66667,-2 8,-2 7.33333,0 8,0.16667 8,2 0,1.83333 -0.66667,2 -8,2 -7.33333,0 -8,-0.16667 -8,-2 z m 40,0 c 0,-1.83333 0.66667,-2 8,-2 7.3333,0 8,0.16667 8,2 0,1.83333 -0.6667,2 -8,2 -7.33333,0 -8,-0.16667 -8,-2 z m -80,-10 v -4 h 8 8 v 4 4 h -8 -8 z m 40,0 v -4 h 8 8 v 4 4 h -8 -8 z m 40,0 v -4 h 8 8 v 4 4 h -8 -8 z m -80,-26 c 0,-1.83333 0.66667,-2 8,-2 7.33333,0 8,0.16667 8,2 0,1.83333 -0.66667,2 -8,2 -7.33333,0 -8,-0.16667 -8,-2 z m 40,0 c 0,-1.83333 0.66667,-2 8,-2 7.33333,0 8,0.16667 8,2 0,1.83333 -0.66667,2 -8,2 -7.33333,0 -8,-0.16667 -8,-2 z m 40,0 c 0,-1.83333 0.66667,-2 8,-2 7.3333,0 8,0.16667 8,2 0,1.83333 -0.6667,2 -8,2 -7.33333,0 -8,-0.16667 -8,-2 z m -80,-10 v -4 h 8 8 v 4 4 h -8 -8 z m 40,0 v -4 h 8 8 v 4 4 h -8 -8 z m 40,0 v -4 h 8 8 v 4 4 h -8 -8 z m -80,-26 c 0,-1.83333 0.66667,-2 8,-2 7.33333,0 8,0.16667 8,2 0,1.83333 -0.66667,2 -8,2 -7.33333,0 -8,-0.16667 -8,-2 z m 40,0 c 0,-1.83333 0.66667,-2 8,-2 7.33333,0 8,0.16667 8,2 0,1.83333 -0.66667,2 -8,2 -7.33333,0 -8,-0.16667 -8,-2 z m 40,0 c 0,-1.83333 0.66667,-2 8,-2 7.3333,0 8,0.16667 8,2 0,1.83333 -0.6667,2 -8,2 -7.33333,0 -8,-0.16667 -8,-2 z m -80,-10 v -4 h 8 8 v 4 4 h -8 -8 z m 40,0 v -4 h 8 8 v 4 4 h -8 -8 z m 40,0 v -4 h 8 8 v 4 4 h -8 -8 z m -80,-26 c 0,-1.83333 0.66667,-2 8,-2 7.33333,0 8,0.16667 8,2 0,1.83333 -0.66667,2 -8,2 -7.33333,0 -8,-0.16667 -8,-2 z m 40,0 c 0,-1.83333 0.66667,-2 8,-2 7.33333,0 8,0.16667 8,2 0,1.83333 -0.66667,2 -8,2 -7.33333,0 -8,-0.16667 -8,-2 z m 40,0 c 0,-1.83333 0.66667,-2 8,-2 7.3333,0 8,0.16667 8,2 0,1.83333 -0.6667,2 -8,2 -7.33333,0 -8,-0.16667 -8,-2 z m -80,-10 v -4 h 8 8 v 4 4 h -8 -8 z m 40,0 v -4 h 8 8 v 4 4 h -8 -8 z m 40,0 v -4 h 8 8 v 4 4 h -8 -8 z M 918,94 c 0,-1.833333 0.66667,-2 8,-2 7.33333,0 8,0.166667 8,2 0,1.833333 -0.66667,2 -8,2 -7.33333,0 -8,-0.166667 -8,-2 z m 40,0 c 0,-1.833333 0.66667,-2 8,-2 7.33333,0 8,0.166667 8,2 0,1.833333 -0.66667,2 -8,2 -7.33333,0 -8,-0.166667 -8,-2 z m 40,0 c 0,-1.833333 0.66667,-2 8,-2 7.3333,0 8,0.166667 8,2 0,1.833333 -0.6667,2 -8,2 -7.33333,0 -8,-0.166667 -8,-2 z M 918,84 v -4 h 8 8 v 4 4 h -8 -8 z m 40,0 v -4 h 8 8 v 4 4 h -8 -8 z m 40,0 v -4 h 8 8 v 4 4 h -8 -8 z" , "M 4.5739313,406 C 6.1329087,390.62752 17.222484,379.98335 32.5,379.19556 c 6.349167,-0.3274 7.623849,-0.0454 13.709769,3.03352 l 6.709769,3.39448 1.998297,-6.56178 c 5.136604,-16.86699 17.015502,-29.38079 34.72114,-36.5769 5.16164,-2.09785 5.86426,-2.74348 6.936094,-6.37356 5.154111,-17.45591 23.527961,-28.88239 41.762181,-25.97142 8.34594,1.33237 14.35275,4.46572 20.73472,10.81589 l 5.57197,5.54421 3.32907,-2.25213 c 6.54497,-4.42769 14.65489,-5.09439 22.18665,-1.8239 1.18896,0.51628 1.85615,0.10158 2.35247,-1.46219 1.05757,-3.33211 7.39525,-9.87388 11.68701,-12.06337 7.23167,-3.68932 14.39827,-4.13543 23.30086,-1.45042 1.67658,0.50565 2.39169,-0.30815 4.42215,-5.03249 3.27759,-7.62603 10.43307,-14.74295 18.80259,-18.70124 6.07019,-2.87084 7.71185,-3.20318 15.77526,-3.19355 7.71529,0.009 9.8465,0.40492 14.93013,2.77216 7.35743,3.42605 13.31091,8.25668 16.54026,13.42071 l 2.47038,3.95037 9.02962,-0.0438 c 7.41544,-0.036 9.85838,0.36168 13.66573,2.22462 6.52502,3.1927 12.10496,8.24858 14.88295,13.48514 2.17316,4.09644 2.64122,4.43724 5.46495,3.97901 5.13645,-0.83353 11.65968,1.52475 15.56677,5.62771 l 3.55078,3.72879 3.79778,-4.36579 c 5.1783,-5.95279 12.42156,-9.47438 20.71688,-10.07229 9.02781,-0.65072 15.4615,1.59319 22.11856,7.71438 l 5.17953,4.76259 4.44007,-1.96406 c 3.00957,-1.33128 5.68986,-1.79642 8.31925,-1.44375 3.65414,0.49013 3.9889,0.30808 5.77025,-3.13807 2.68961,-5.20323 9.71564,-11.67993 15.55611,-14.33986 6.95882,-3.16927 17.97335,-3.16151 25.09189,0.0177 6.89689,3.08019 13.92869,10.43489 16.85081,17.62461 l 2.36103,5.80918 9.59878,-0.19361 c 5.27933,-0.10648 10.07704,0.10197 10.66157,0.46324 0.61329,0.37903 -0.5943,1.87548 -2.85512,3.53811 -3.07459,2.26107 -5.41383,3.0203 -10.86758,3.52717 l -6.94967,0.6459 -1.30921,-4.36976 c -4.02335,-13.42874 -18.85899,-23.02547 -33.26717,-21.51952 -10.13485,1.0593 -20.27562,7.78121 -24.67467,16.35585 -1.81846,3.54454 -2.06491,3.66981 -6.76394,3.438 -3.21294,-0.1585 -6.11836,0.40028 -8.51608,1.63783 l -3.63936,1.8784 -4.25534,-4.31809 C 404.74267,326.05322 397.13098,323 389.14063,323 c -8.81293,0 -14.67534,2.46351 -21.21261,8.91399 l -5.66117,5.58601 -2.32695,-2.89183 c -3.28645,-4.08428 -10.22613,-6.94873 -15.41711,-6.36363 -4.07398,0.4592 -4.23458,0.3627 -6.49832,-3.90448 -2.88192,-5.43248 -8.83317,-10.90017 -14.90207,-13.69122 -3.56971,-1.6417 -6.63508,-2.13634 -13.46018,-2.17197 l -8.83777,-0.0462 -3.41786,-4.70973 C 290.60953,294.35478 278.11638,288 266.5,288 c -7.13081,0 -16.96947,3.24874 -22.74437,7.51023 -5.62304,4.14943 -11.28119,11.49825 -12.92054,16.78125 -0.87243,2.81152 -1.58413,3.63506 -2.75357,3.18631 -13.86254,-5.31956 -28.24371,0.0421 -36.08698,13.45405 -0.69326,1.18547 -1.51606,1.22849 -4.78196,0.25 -7.07459,-2.1196 -14.51841,-0.90988 -19.65288,3.19386 l -2.65786,2.1243 -6.24982,-6.10291 c -8.11829,-7.92746 -14.86653,-10.68447 -26.15202,-10.68447 -5.8209,0 -9.5382,0.59299 -13.64709,2.17703 -9.85759,3.80025 -18.83585,13.36668 -22.22847,23.68467 -1.598023,4.86007 -1.896764,5.14628 -7.075558,6.77875 -15.31023,4.82612 -30.615041,21.10102 -35.129657,37.35632 l -1.724546,6.20939 -2.781269,-1.98044 c -6.42973,-4.57837 -16.855946,-6.19291 -25.138154,-3.89273 -7.540377,2.09415 -17.1209318,11.94164 -19.76831,20.3191 C 4.1852701,410.96485 4.1113608,410.56123 4.5739313,406 Z M 1867,409.46016 c 0,-2.36037 -3.6559,-8.99868 -7.1433,-12.97058 -9.1835,-10.45943 -25.0776,-12.59415 -36.9649,-4.96469 l -3.6082,2.3158 -1.7137,-6.17034 c -4.5035,-16.2155 -19.8253,-32.49643 -35.1188,-37.31728 -5.1788,-1.63247 -5.4775,-1.91868 -7.0755,-6.77875 -3.3927,-10.31799 -12.3709,-19.88442 -22.2285,-23.68467 -4.1089,-1.58404 -7.8262,-2.17703 -13.6471,-2.17703 -11.2855,0 -18.0337,2.75701 -26.152,10.68447 l -6.2498,6.10291 -2.6579,-2.1243 c -5.1345,-4.10374 -12.5783,-5.31346 -19.6529,-3.19386 -3.2659,0.97849 -4.0887,0.93547 -4.7819,-0.25 -6.5917,-11.27181 -18.7592,-17.43715 -29.7577,-15.0784 -2.6138,0.56055 -5.4619,1.2915 -6.3293,1.62435 -1.1695,0.44875 -1.8812,-0.37479 -2.7536,-3.18631 -2.9015,-9.35059 -13.1479,-18.8575 -24.0202,-22.28688 -15.7846,-4.97878 -33.0149,0.57538 -42.5513,13.71639 l -3.4178,4.70973 -8.8378,0.0462 c -6.8251,0.0356 -9.8905,0.53027 -13.4602,2.17197 -6.0689,2.79105 -12.0201,8.25874 -14.9021,13.69122 -2.2637,4.26718 -2.4243,4.36368 -6.4983,3.90448 -5.191,-0.5851 -12.1306,2.27935 -15.4171,6.36363 l -2.3269,2.89183 -5.6612,-5.58601 C 1497.5347,325.46351 1491.6723,323 1482.8594,323 c -7.9904,0 -15.6021,3.05322 -20.8253,8.3535 l -4.2554,4.31809 -3.6393,-1.8784 c -2.3978,-1.23755 -5.3032,-1.79633 -8.5161,-1.63783 -4.6991,0.23181 -4.9455,0.10654 -6.764,-3.438 -4.5554,-8.87949 -14.4301,-15.23729 -25.5782,-16.46847 L 1406.5,311.5 l -0.3189,-3.89244 -0.3188,-3.89244 6.38,0.51214 c 3.5091,0.28167 8.6026,1.49229 11.3189,2.69028 5.7461,2.53417 12.79,9.00802 15.4949,14.24085 1.7814,3.44615 2.1161,3.6282 5.7703,3.13807 2.6293,-0.35267 5.3096,0.11247 8.3192,1.44375 l 4.4401,1.96406 5.1795,-4.76259 c 6.6571,-6.12119 13.0908,-8.3651 22.1186,-7.71438 8.2953,0.59791 15.5385,4.1195 20.7168,10.07229 l 3.7978,4.36579 3.5508,-3.72879 c 3.9071,-4.10296 10.4303,-6.46124 15.5668,-5.62771 2.8237,0.45823 3.2918,0.11743 5.4649,-3.97901 2.778,-5.23656 8.358,-10.29244 14.883,-13.48514 3.8073,-1.86294 6.2503,-2.26063 13.6657,-2.22462 l 9.0296,0.0438 2.4704,-3.95037 c 3.2294,-5.16403 9.1828,-9.99466 16.5403,-13.42071 5.0836,-2.36724 7.2148,-2.76295 14.9301,-2.77216 8.0634,-0.01 9.7051,0.32271 15.7753,3.19355 8.3695,3.95829 15.525,11.07521 18.8025,18.70124 2.0305,4.72434 2.7456,5.53814 4.4222,5.03249 8.9026,-2.68501 16.0692,-2.2389 23.3009,1.45042 4.2917,2.18949 10.6294,8.73126 11.687,12.06337 0.4963,1.56377 1.1635,1.97847 2.3524,1.46219 7.5318,-3.27049 15.6417,-2.60379 22.1867,1.8239 l 3.3291,2.25213 5.5719,-5.54421 c 6.382,-6.35017 12.3888,-9.48352 20.7348,-10.81589 18.2342,-2.91097 36.608,8.51551 41.7621,25.97142 1.0719,3.63008 1.7745,4.27571 6.9361,6.37356 17.7057,7.19611 29.5846,19.70991 34.7212,36.5769 l 1.9983,6.56178 6.7097,-3.39448 c 6.086,-3.07888 7.3606,-3.36092 13.7098,-3.03352 15.1064,0.77897 26.0186,11.10459 28.063,26.55444 0.3821,2.8875 0.4117,5.25 0.066,5.25 -0.3458,0 -0.6288,-0.69293 -0.6288,-1.53984 z M 501.76166,321.29274 c 1.24391,-0.23919 3.04391,-0.23011 4,0.0202 0.95609,0.25029 -0.0617,0.446 -2.26166,0.4349 -2.2,-0.0111 -2.98225,-0.21589 -1.73834,-0.45508 z M 128.8125,301.31689 c 0.72187,-0.28887 1.58437,-0.25335 1.91667,0.0789 0.33229,0.33229 -0.25834,0.56864 -1.3125,0.52522 -1.16495,-0.048 -1.4019,-0.28494 -0.60417,-0.60416 z m 6,0 c 0.72187,-0.28887 1.58437,-0.25335 1.91667,0.0789 0.33229,0.33229 -0.25834,0.56864 -1.3125,0.52522 -1.16495,-0.048 -1.4019,-0.28494 -0.60417,-0.60416 z m 1601,0 c 0.7219,-0.28887 1.5844,-0.25335 1.9167,0.0789 0.3323,0.33229 -0.2584,0.56864 -1.3125,0.52522 -1.165,-0.048 -1.4019,-0.28494 -0.6042,-0.60416 z m 6,0 c 0.7219,-0.28887 1.5844,-0.25335 1.9167,0.0789 0.3323,0.33229 -0.2584,0.56864 -1.3125,0.52522 -1.165,-0.048 -1.4019,-0.28494 -0.6042,-0.60416 z m -1528,-4 c 0.72187,-0.28887 1.58437,-0.25335 1.91667,0.0789 0.33229,0.33229 -0.25834,0.56864 -1.3125,0.52522 -1.16495,-0.048 -1.4019,-0.28494 -0.60417,-0.60416 z m 1443,0 c 0.7219,-0.28887 1.5844,-0.25335 1.9167,0.0789 0.3323,0.33229 -0.2584,0.56864 -1.3125,0.52522 -1.165,-0.048 -1.4019,-0.28494 -0.6042,-0.60416 z M 236,284.5 c 1.29175,-1.375 2.57363,-2.5 2.84863,-2.5 0.275,0 -0.55688,1.125 -1.84863,2.5 -1.29175,1.375 -2.57363,2.5 -2.84863,2.5 -0.275,0 0.55688,-1.125 1.84863,-2.5 z m 1398.9243,-0.25 -2.4243,-2.75 2.75,2.42431 c 2.5703,2.26589 3.2086,3.07569 2.4243,3.07569 -0.1791,0 -1.4166,-1.2375 -2.75,-2.75 z M 930.22178,26.25 930.5,12.5 933.75,12.186642 937,11.873283 V 25.936642 40 h -3.52822 -3.52822 z M 942,26 V 12 h 3.5 3.5 v 14 14 h -3.5 -3.5 z m 12,0 V 12 h 3.5 3.5 v 14 14 h -3.5 -3.5 z m 12,0 V 12 h 3.5 3.5 v 14 14 h -3.5 -3.5 z m 12,0 V 12 h 3.5 3.5 v 14 14 h -3.5 -3.5 z m 12,-0.0499 V 11.900196 l 5.75,0.299904 5.75,0.299904 0.2782,13.75 0.2782,13.75 h -6.02818 L 990,40 V 25.950096 Z" , "m 1310,410 c 0,-1.85965 0.6667,-2 9.5,-2 8.8333,0 9.5,0.14035 9.5,2 0,1.85965 -0.6667,2 -9.5,2 -8.8333,0 -9.5,-0.14035 -9.5,-2 z M 526.34941,396 c 0,-4.675 0.17226,-6.5875 0.3828,-4.25 0.21053,2.3375 0.21053,6.1625 0,8.5 -0.21054,2.3375 -0.3828,0.425 -0.3828,-4.25 z m 84.04798,-6 c 0,-7.975 0.1553,-11.2375 0.34512,-7.25 0.18981,3.9875 0.18981,10.5125 0,14.5 -0.18982,3.9875 -0.34512,0.725 -0.34512,-7.25 z m 19.95202,6 c 0,-4.675 0.17226,-6.5875 0.3828,-4.25 0.21053,2.3375 0.21053,6.1625 0,8.5 -0.21054,2.3375 -0.3828,0.425 -0.3828,-4.25 z m 675.03909,-4.5 c 0,-7.15 0.1611,-9.94379 0.354,-6.20841 0.1929,3.73537 0.1915,9.58537 0,13 -0.1947,3.41462 -0.3525,0.35841 -0.3508,-6.79159 z M 1310,402 c 0,-1.85965 0.6667,-2 9.5,-2 8.8333,0 9.5,0.14035 9.5,2 0,1.85965 -0.6667,2 -9.5,2 -8.8333,0 -9.5,-0.14035 -9.5,-2 z m 83.4705,-60 c 0,-34.375 0.1255,-48.4375 0.279,-31.25 0.1534,17.1875 0.1534,45.3125 0,62.5 -0.1535,17.1875 -0.279,3.125 -0.279,-31.25 z M 1310,394 c 0,-1.85965 0.6667,-2 9.5,-2 8.8333,0 9.5,0.14035 9.5,2 0,1.85965 -0.6667,2 -9.5,2 -8.8333,0 -9.5,-0.14035 -9.5,-2 z m 0,-10.91667 c 0,-2.95555 0.4986,-5.14934 1.25,-5.5 1,-0.46666 1,-0.7 0,-1.16666 -0.8768,-0.40915 -1.25,-3.54445 -1.25,-10.5 V 356 h 9.5 9.5 v 16 16 h -9.5 -9.5 z M 1318,374 c 0,-1.46667 -0.6667,-2 -2.5,-2 -1.8333,0 -2.5,0.53333 -2.5,2 0,1.46667 0.6667,2 2.5,2 1.8333,0 2.5,-0.53333 2.5,-2 z m -791,6 v -4 h 39.5 39.5 v 4 4 H 566.5 527 Z m 104,0 v -4 h 65.5 65.5 v 4 4 H 696.5 631 Z m 674.3854,-16 c 0,-6.875 0.1597,-9.6875 0.355,-6.25 0.1953,3.4375 0.1953,9.0625 0,12.5 -0.1953,3.4375 -0.355,0.625 -0.355,-6.25 z M 526.43244,324 c 0,-13.475 0.14155,-18.9875 0.31456,-12.25 0.17301,6.7375 0.17301,17.7625 0,24.5 -0.17301,6.7375 -0.31456,1.225 -0.31456,-12.25 z m 68,0 c 0,-13.475 0.14155,-18.9875 0.31456,-12.25 0.17301,6.7375 0.17301,17.7625 0,24.5 -0.17301,6.7375 -0.31456,1.225 -0.31456,-12.25 z m -68.11237,-42 c 0,-3.575 0.1815,-5.0375 0.40333,-3.25 0.22182,1.7875 0.22182,4.7125 0,6.5 -0.22183,1.7875 -0.40333,0.325 -0.40333,-3.25 z m 20,0 c 0,-3.575 0.1815,-5.0375 0.40333,-3.25 0.22182,1.7875 0.22182,4.7125 0,6.5 -0.22183,1.7875 -0.40333,0.325 -0.40333,-3.25 z m 16,0 c 0,-3.575 0.1815,-5.0375 0.40333,-3.25 0.22182,1.7875 0.22182,4.7125 0,6.5 -0.22183,1.7875 -0.40333,0.325 -0.40333,-3.25 z m 16,0 c 0,-3.575 0.1815,-5.0375 0.40333,-3.25 0.22182,1.7875 0.22182,4.7125 0,6.5 -0.22183,1.7875 -0.40333,0.325 -0.40333,-3.25 z m 16,0 c 0,-3.575 0.1815,-5.0375 0.40333,-3.25 0.22182,1.7875 0.22182,4.7125 0,6.5 -0.22183,1.7875 -0.40333,0.325 -0.40333,-3.25 z m 16,0 c 0,-3.575 0.1815,-5.0375 0.40333,-3.25 0.22182,1.7875 0.22182,4.7125 0,6.5 -0.22183,1.7875 -0.40333,0.325 -0.40333,-3.25 z m 16,0 c 0,-3.575 0.1815,-5.0375 0.40333,-3.25 0.22182,1.7875 0.22182,4.7125 0,6.5 -0.22183,1.7875 -0.40333,0.325 -0.40333,-3.25 z m 16,0 c 0,-3.575 0.1815,-5.0375 0.40333,-3.25 0.22182,1.7875 0.22182,4.7125 0,6.5 -0.22183,1.7875 -0.40333,0.325 -0.40333,-3.25 z m 16,0 c 0,-3.575 0.1815,-5.0375 0.40333,-3.25 0.22182,1.7875 0.22182,4.7125 0,6.5 -0.22183,1.7875 -0.40333,0.325 -0.40333,-3.25 z m 734.83783,-8 c 0,-1.375 0.227,-1.9375 0.5044,-1.25 0.2774,0.6875 0.2774,1.8125 0,2.5 -0.2774,0.6875 -0.5044,0.125 -0.5044,-1.25 z m 0.2121,-16 c 0,-5.775 0.1652,-8.1375 0.3672,-5.25 0.202,2.8875 0.202,7.6125 0,10.5 -0.202,2.8875 -0.3672,0.525 -0.3672,-5.25 z m -91.3343,-23.38472 c 0.8054,-0.76159 10.156,-9.09636 20.7792,-18.52171 l 19.3148,-17.13699 2.8298,2.27171 c 1.5563,1.24944 10.918,9.47171 20.8038,18.27171 l 17.9741,16 -3.8856,0.30832 c -3.7026,0.29381 -4.5325,-0.26794 -17.6187,-11.92668 -7.5532,-6.72926 -15.0903,-13.49175 -16.7492,-15.02776 l -3.016,-2.79275 -16.8694,14.96944 C 1309.7217,235.11925 1308.4891,236 1304.6503,236 c -3.5131,0 -3.8757,-0.19209 -2.6146,-1.38472 z M 1397,231.5 c -0.6847,-0.825 -1.0199,-1.5 -0.7449,-1.5 0.275,0 1.0602,0.675 1.7449,1.5 0.6847,0.825 1.0199,1.5 0.7449,1.5 -0.275,0 -1.0602,-0.675 -1.7449,-1.5 z m -9,-8 c -0.6847,-0.825 -1.0199,-1.5 -0.7449,-1.5 0.275,0 1.0602,0.675 1.7449,1.5 0.6847,0.825 1.0199,1.5 0.7449,1.5 -0.275,0 -1.0602,-0.675 -1.7449,-1.5 z m -89.5,-2.5 c 0.9955,-1.1 2.035,-2 2.31,-2 0.275,0 -0.3145,0.9 -1.31,2 -0.9955,1.1 -2.035,2 -2.31,2 -0.275,0 0.3145,-0.9 1.31,-2 z" , "M 890,227.99371 V 51.987428 l 75.75,0.256286 75.75,0.256286 0.2528,175.75 0.2528,175.75 H 966.00279 890 Z M 906,226 c 0,-161.333333 -0.008,-162 -2,-162 -1.99177,0 -2,0.666667 -2,162 0,161.33333 0.008,162 2,162 1.99177,0 2,-0.66667 2,-162 z m 32,150 v -12 h -12 -12 v 12 12 h 12 12 z m 40,0 v -12 h -12 -12 v 12 12 h 12 12 z m 40,0 v -12 h -12 -12 v 12 12 h 12 12 z m 12,-150 V 64 h -2.5 -2.5 v 162 162 h 2.5 2.5 z m -92,114 v -12 h -12 -12 v 12 12 h 12 12 z m 40,0 v -12 h -12 -12 v 12 12 h 12 12 z m 40,0 v -12 h -12 -12 v 12 12 h 12 12 z m -80,-36 v -12 h -12 -12 v 12 12 h 12 12 z m 40,0 v -12 h -12 -12 v 12 12 h 12 12 z m 40,0 v -12 h -12 -12 v 12 12 h 12 12 z m -80,-36 v -12 h -12 -12 v 12 12 h 12 12 z m 40,0 v -12 h -12 -12 v 12 12 h 12 12 z m 40,0 v -12 h -12 -12 v 12 12 h 12 12 z m -80,-36 v -12 h -12 -12 v 12 12 h 12 12 z m 40,0 v -12 h -12 -12 v 12 12 h 12 12 z m 40,0 v -12 h -12 -12 v 12 12 h 12 12 z m -80,-36 v -12 h -12 -12 v 12 12 h 12 12 z m 40,0 v -12 h -12 -12 v 12 12 h 12 12 z m 40,0 v -12 h -12 -12 v 12 12 h 12 12 z m -80,-36 v -12 h -12 -12 v 12 12 h 12 12 z m 40,0 v -12 h -12 -12 v 12 12 h 12 12 z m 40,0 v -12 h -12 -12 v 12 12 h 12 12 z m -80,-36 v -12 h -12 -12 v 12 12 h 12 12 z m 40,0 v -12 h -12 -12 v 12 12 h 12 12 z m 40,0 v -12 h -12 -12 v 12 12 h 12 12 z M 938,88 V 76 h -12 -12 v 12 12 h 12 12 z m 40,0 V 76 h -12 -12 v 12 12 h 12 12 z m 40,0 V 76 h -12 -12 v 12 12 h 12 12 z M 9.5,381 c 0.995486,-1.1 2.034975,-2 2.309975,-2 0.275,0 -0.314489,0.9 -1.309975,2 -0.9954863,1.1 -2.0349751,2 -2.3099751,2 -0.275,0 0.3144888,-0.9 1.3099751,-2 z m 1851.905,-0.25 -1.905,-2.25 2.25,1.90499 c 2.1144,1.79022 2.7052,2.59501 1.905,2.59501 -0.1898,0 -1.2023,-1.0125 -2.25,-2.25 z M 62,352.62244 c 0,-0.20765 0.7875,-0.99515 1.75,-1.75 1.586056,-1.24387 1.621426,-1.2085 0.377555,0.37756 C 62.821186,352.91575 62,353.44548 62,352.62244 Z M 1808,351.5 c -0.6847,-0.825 -1.0199,-1.5 -0.7449,-1.5 0.275,0 1.0602,0.675 1.7449,1.5 0.6847,0.825 1.0199,1.5 0.7449,1.5 -0.275,0 -1.0602,-0.675 -1.7449,-1.5 z M 67,347.62244 c 0,-0.20765 0.7875,-0.99515 1.75,-1.75 1.586056,-1.24387 1.621426,-1.2085 0.377555,0.37756 C 67.821186,347.91575 67,348.44548 67,347.62244 Z M 1803,346.5 c -0.6847,-0.825 -1.0199,-1.5 -0.7449,-1.5 0.275,0 1.0602,0.675 1.7449,1.5 0.6847,0.825 1.0199,1.5 0.7449,1.5 -0.275,0 -1.0602,-0.675 -1.7449,-1.5 z M 501.73097,337.34406 c 0.67703,-0.27392 2.02703,-0.29059 3,-0.0371 0.97297,0.25354 0.41903,0.47766 -1.23097,0.49804 -1.65,0.0204 -2.44606,-0.18706 -1.76903,-0.46099 z m -140.12796,-8.17739 c -0.28327,-0.45834 0.12038,-0.83334 0.89699,-0.83334 0.77661,0 1.18026,0.375 0.89699,0.83334 C 363.11373,329.625 362.71008,330 362.5,330 c -0.21008,0 -0.61373,-0.375 -0.89699,-0.83333 z m 139.66074,0.11575 c 1.52006,-0.22986 3.77006,-0.22371 5,0.0136 1.22994,0.23736 -0.0138,0.42542 -2.76375,0.41792 -2.75,-0.008 -3.75631,-0.20172 -2.23625,-0.43157 z M 1508.603,329.16667 c -0.2833,-0.45834 0.1204,-0.83334 0.897,-0.83334 0.7766,0 1.1803,0.375 0.897,0.83334 -0.2833,0.45833 -0.6869,0.83333 -0.897,0.83333 -0.2101,0 -0.6137,-0.375 -0.897,-0.83333 z M 410.40499,323.75 408.5,321.5 l 2.25,1.90499 c 1.2375,1.04774 2.25,2.06024 2.25,2.25 0,0.80025 -0.80479,0.20945 -2.59501,-1.90499 z M 1460.5,324 c 0.9955,-1.1 2.035,-2 2.31,-2 0.275,0 -0.3145,0.9 -1.31,2 -0.9955,1.1 -2.035,2 -2.31,2 -0.275,0 0.3145,-0.9 1.31,-2 z M 366,324.62244 c 0,-0.20765 0.7875,-0.99515 1.75,-1.75 1.58606,-1.24387 1.62143,-1.2085 0.37756,0.37756 C 366.82119,324.91575 366,325.44548 366,324.62244 Z M 1504,323.5 c -0.6847,-0.825 -1.0199,-1.5 -0.7449,-1.5 0.275,0 1.0602,0.675 1.7449,1.5 0.6847,0.825 1.0199,1.5 0.7449,1.5 -0.275,0 -1.0602,-0.675 -1.7449,-1.5 z M 437,322.62244 c 0,-0.20765 0.7875,-0.99515 1.75,-1.75 1.58606,-1.24387 1.62143,-1.2085 0.37756,0.37756 C 437.82119,322.91575 437,323.44548 437,322.62244 Z M 483,321.5 c -0.68469,-0.825 -1.01989,-1.5 -0.74489,-1.5 0.275,0 1.0602,0.675 1.74489,1.5 0.68469,0.825 1.01989,1.5 0.74489,1.5 -0.275,0 -1.0602,-0.675 -1.74489,-1.5 z m 950,0 c -0.6847,-0.825 -1.0199,-1.5 -0.7449,-1.5 0.275,0 1.0602,0.675 1.7449,1.5 0.6847,0.825 1.0199,1.5 0.7449,1.5 -0.275,0 -1.0602,-0.675 -1.7449,-1.5 z M 130.76166,317.29274 c 1.24391,-0.23919 3.04391,-0.23011 4,0.0202 0.95609,0.25029 -0.0617,0.446 -2.26166,0.4349 -2.2,-0.0111 -2.98225,-0.21589 -1.73834,-0.45508 z m 1607.00004,0 c 1.2439,-0.23919 3.0439,-0.23011 4,0.0202 0.956,0.25029 -0.062,0.446 -2.2617,0.4349 -2.2,-0.0111 -2.9823,-0.21589 -1.7383,-0.45508 z M 438,313.5 c 1.29175,-1.375 2.57363,-2.5 2.84863,-2.5 0.275,0 -0.55688,1.125 -1.84863,2.5 -1.29175,1.375 -2.57363,2.5 -2.84863,2.5 -0.275,0 0.55688,-1.125 1.84863,-2.5 z m 44.92431,-0.25 -2.42431,-2.75 2.75,2.42431 c 2.57029,2.26589 3.20859,3.07569 2.42431,3.07569 -0.17912,0 -1.41662,-1.2375 -2.75,-2.75 z m 949.99999,0 -2.4243,-2.75 2.75,2.42431 c 1.5125,1.33338 2.75,2.57088 2.75,2.75 0,0.78428 -0.8098,0.14598 -3.0757,-2.42431 z M 196,314.62244 c 0,-0.20765 0.7875,-0.99515 1.75,-1.75 1.58606,-1.24387 1.62143,-1.2085 0.37756,0.37756 C 196.82119,314.91575 196,315.44548 196,314.62244 Z M 1674,313.5 c -0.6847,-0.825 -1.0199,-1.5 -0.7449,-1.5 0.275,0 1.0602,0.675 1.7449,1.5 0.6847,0.825 1.0199,1.5 0.7449,1.5 -0.275,0 -1.0602,-0.675 -1.7449,-1.5 z M 215.25,313.31067 c 0.9625,-0.25152 2.5375,-0.25152 3.5,0 0.9625,0.25153 0.175,0.45733 -1.75,0.45733 -1.925,0 -2.7125,-0.2058 -1.75,-0.45733 z m 1438,0 c 0.9625,-0.25152 2.5375,-0.25152 3.5,0 0.9625,0.25153 0.175,0.45733 -1.75,0.45733 -1.925,0 -2.7125,-0.2058 -1.75,-0.45733 z M 128.8125,309.31689 c 0.72187,-0.28887 1.58437,-0.25335 1.91667,0.0789 0.33229,0.33229 -0.25834,0.56864 -1.3125,0.52522 -1.16495,-0.048 -1.4019,-0.28494 -0.60417,-0.60416 z m 6,0 c 0.72187,-0.28887 1.58437,-0.25335 1.91667,0.0789 0.33229,0.33229 -0.25834,0.56864 -1.3125,0.52522 -1.16495,-0.048 -1.4019,-0.28494 -0.60417,-0.60416 z m 1601,0 c 0.7219,-0.28887 1.5844,-0.25335 1.9167,0.0789 0.3323,0.33229 -0.2584,0.56864 -1.3125,0.52522 -1.165,-0.048 -1.4019,-0.28494 -0.6042,-0.60416 z m 6,0 c 0.7219,-0.28887 1.5844,-0.25335 1.9167,0.0789 0.3323,0.33229 -0.2584,0.56864 -1.3125,0.52522 -1.165,-0.048 -1.4019,-0.28494 -0.6042,-0.60416 z m -1528,-4 c 0.72187,-0.28887 1.58437,-0.25335 1.91667,0.0789 0.33229,0.33229 -0.25834,0.56864 -1.3125,0.52522 -1.16495,-0.048 -1.4019,-0.28494 -0.60417,-0.60416 z m 1443,0 c 0.7219,-0.28887 1.5844,-0.25335 1.9167,0.0789 0.3323,0.33229 -0.2584,0.56864 -1.3125,0.52522 -1.165,-0.048 -1.4019,-0.28494 -0.6042,-0.60416 z M 335.40499,302.75 333.5,300.5 l 2.25,1.90499 c 1.2375,1.04774 2.25,2.06024 2.25,2.25 0,0.80025 -0.80479,0.20945 -2.59501,-1.90499 z M 1535.5,303 c 0.9955,-1.1 2.035,-2 2.31,-2 0.275,0 -0.3145,0.9 -1.31,2 -0.9955,1.1 -2.035,2 -2.31,2 -0.275,0 0.3145,-0.9 1.31,-2 z M 131.8125,301.31689 c 0.72187,-0.28887 1.58437,-0.25335 1.91667,0.0789 0.33229,0.33229 -0.25834,0.56864 -1.3125,0.52522 -1.16495,-0.048 -1.4019,-0.28494 -0.60417,-0.60416 z m 1607,0 c 0.7219,-0.28887 1.5844,-0.25335 1.9167,0.0789 0.3323,0.33229 -0.2584,0.56864 -1.3125,0.52522 -1.165,-0.048 -1.4019,-0.28494 -0.6042,-0.60416 z m -1522,-4 c 0.72187,-0.28887 1.58437,-0.25335 1.91667,0.0789 0.33229,0.33229 -0.25834,0.56864 -1.3125,0.52522 -1.16495,-0.048 -1.4019,-0.28494 -0.60417,-0.60416 z m 1437,0 c 0.7219,-0.28887 1.5844,-0.25335 1.9167,0.0789 0.3323,0.33229 -0.2584,0.56864 -1.3125,0.52522 -1.165,-0.048 -1.4019,-0.28494 -0.6042,-0.60416 z M 937.39739,26 c 0,-7.975 0.1553,-11.2375 0.34512,-7.25 0.18981,3.9875 0.18981,10.5125 0,14.5 -0.18982,3.9875 -0.34512,0.725 -0.34512,-7.25 z m 12,0 c 0,-7.975 0.1553,-11.2375 0.34512,-7.25 0.18981,3.9875 0.18981,10.5125 0,14.5 -0.18982,3.9875 -0.34512,0.725 -0.34512,-7.25 z m 12,0 c 0,-7.975 0.1553,-11.2375 0.34512,-7.25 0.18981,3.9875 0.18981,10.5125 0,14.5 -0.18982,3.9875 -0.34512,0.725 -0.34512,-7.25 z m 12,0 c 0,-7.975 0.1553,-11.2375 0.34512,-7.25 0.18981,3.9875 0.18981,10.5125 0,14.5 -0.18982,3.9875 -0.34512,0.725 -0.34512,-7.25 z m 12,0 c 0,-7.975 0.1553,-11.2375 0.34512,-7.25 0.18981,3.9875 0.18981,10.5125 0,14.5 -0.18982,3.9875 -0.34512,0.725 -0.34512,-7.25 z" , "m 1054,396 v -8 h 111.5 111.5 v 8 8 H 1165.5 1054 Z m 275.1579,6 c 0,-1.375 0.227,-1.9375 0.5044,-1.25 0.2774,0.6875 0.2774,1.8125 0,2.5 -0.2774,0.6875 -0.5044,0.125 -0.5044,-1.25 z m 0,-8 c 0,-1.375 0.227,-1.9375 0.5044,-1.25 0.2774,0.6875 0.2774,1.8125 0,2.5 -0.2774,0.6875 -0.5044,0.125 -0.5044,-1.25 z m 0.1281,-10.5 c 0.01,-2.75 0.2017,-3.75631 0.4316,-2.23625 0.2298,1.52006 0.2237,3.77006 -0.014,5 -0.2373,1.22994 -0.4254,-0.0138 -0.4179,-2.76375 z M 526.27236,380 c 0,-2.475 0.19502,-3.4875 0.43337,-2.25 0.23836,1.2375 0.23836,3.2625 0,4.5 -0.23835,1.2375 -0.43337,0.225 -0.43337,-2.25 z m 104,0 c 0,-2.475 0.19502,-3.4875 0.43337,-2.25 0.23836,1.2375 0.23836,3.2625 0,4.5 -0.23835,1.2375 -0.43337,0.225 -0.43337,-2.25 z M 1054,366 v -10 h 10 10 v 10 10 h -10 -10 z m 24,0 v -10 h 10 10 v 10 10 h -10 -10 z m 24,0 v -10 h 8 8 v 10 10 h -8 -8 z m 20,0 v -10 h 10 10 v 10 10 h -10 -10 z m 24,0 v -10 h 10 10 v 10 10 h -10 -10 z m 24,0 v -10 h 10 10 v 10 10 h -10 -10 z m 24,0 v -10 h 8 8 v 10 10 h -8 -8 z m 20,0 v -10 h 10 10 v 10 10 h -10 -10 z m 24,0 v -10 h 10 10 v 10 10 h -10 -10 z m 24,0 v -10 h 8 8 v 10 10 h -8 -8 z m 51.1579,8 c 0,-1.375 0.227,-1.9375 0.5044,-1.25 0.2774,0.6875 0.2774,1.8125 0,2.5 -0.2774,0.6875 -0.5044,0.125 -0.5044,-1.25 z m 16.2121,-8 c 0,-5.775 0.1652,-8.1375 0.3672,-5.25 0.202,2.8875 0.202,7.6125 0,10.5 -0.202,2.8875 -0.3672,0.525 -0.3672,-5.25 z M 1054,342 v -10 h 10 10 v 10 10 h -10 -10 z m 24,0 v -10 h 10 10 v 10 10 h -10 -10 z m 24,0 v -10 h 8 8 v 10 10 h -8 -8 z m 20,0 v -10 h 10 10 v 10 10 h -10 -10 z m 24,0 v -10 h 10 10 v 10 10 h -10 -10 z m 24,0 v -10 h 10 10 v 10 10 h -10 -10 z m 24,0 v -10 h 8 8 v 10 10 h -8 -8 z m 20,0 v -10 h 10 10 v 10 10 h -10 -10 z m 24,0 v -10 h 10 10 v 10 10 h -10 -10 z m 24,0 v -10 h 8 8 v 10 10 h -8 -8 z m -208,-24 v -10 h 10 10 v 10 10 h -10 -10 z m 24,0 v -10 h 10 10 v 10 10 h -10 -10 z m 24,0 v -10 h 8 8 v 10 10 h -8 -8 z m 20,0 v -10 h 10 10 v 10 10 h -10 -10 z m 24,0 v -10 h 10 10 v 10 10 h -10 -10 z m 24,0 v -10 h 10 10 v 10 10 h -10 -10 z m 24,0 v -10 h 8 8 v 10 10 h -8 -8 z m 20,0 v -10 h 10 10 v 10 10 h -10 -10 z m 24,0 v -10 h 10 10 v 10 10 h -10 -10 z m 24,0 v -10 h 8 8 v 10 10 h -8 -8 z M 514.44225,293 c 0,-16.225 0.13752,-22.8625 0.30561,-14.75 0.16808,8.1125 0.16808,21.3875 0,29.5 -0.16809,8.1125 -0.30561,1.475 -0.30561,-14.75 z M 1054,296 v -8 h 10 10 v 8 8 h -10 -10 z m 24,0 v -8 h 10 10 v 8 8 h -10 -10 z m 24,0 v -8 h 8 8 v 8 8 h -8 -8 z m 20,0 v -8 h 10 10 v 8 8 h -10 -10 z m 24,0 v -8 h 10 10 v 8 8 h -10 -10 z m 24,0 v -8 h 10 10 v 8 8 h -10 -10 z m 24,0 v -8 h 8 8 v 8 8 h -8 -8 z m 20,0 v -8 h 10 10 v 8 8 h -10 -10 z m 24,0 v -8 h 10 10 v 8 8 h -10 -10 z m 24,0 v -8 h 8 8 v 8 8 h -8 -8 z m 143.4439,-30 c 0,-16.775 0.1368,-23.6375 0.3041,-15.25 0.1672,8.3875 0.1672,22.1125 0,30.5 -0.1673,8.3875 -0.3041,1.525 -0.3041,-15.25 z m -863.13403,16.5 c 0.006,-3.3 0.1923,-4.52944 0.41486,-2.73209 0.22256,1.79735 0.21797,4.49735 -0.0102,6 -0.22818,1.50265 -0.41027,0.0321 -0.40466,-3.26791 z m 16,0 c 0.006,-3.3 0.1923,-4.52944 0.41486,-2.73209 0.22256,1.79735 0.21797,4.49735 -0.0102,6 -0.22818,1.50265 -0.41027,0.0321 -0.40466,-3.26791 z m 16,0 c 0.006,-3.3 0.1923,-4.52944 0.41486,-2.73209 0.22256,1.79735 0.21797,4.49735 -0.0102,6 -0.22818,1.50265 -0.41027,0.0321 -0.40466,-3.26791 z M 1054,274 v -10 h 10 10 v 10 10 h -10 -10 z m 24,0 v -10 h 10 10 v 10 10 h -10 -10 z m 24,0 v -10 h 8 8 v 10 10 h -8 -8 z m 20,0 v -10 h 10 10 v 10 10 h -10 -10 z m 24,0 v -10 h 10 10 v 10 10 h -10 -10 z m 24,0 v -10 h 10 10 v 10 10 h -10 -10 z m 24,0 v -10 h 8 8 v 10 10 h -8 -8 z m 20,0 v -10 h 10 10 v 10 10 h -10 -10 z m 24,0 v -10 h 10 10 v 10 10 h -10 -10 z m 24,0 v -10 h 8 8 v 10 10 h -8 -8 z m -208,-24 v -10 h 10 10 v 10 10 h -10 -10 z m 24,0 v -10 h 10 10 v 10 10 h -10 -10 z m 24,0 v -10 h 8 8 v 10 10 h -8 -8 z m 20,0 v -10 h 10 10 v 10 10 h -10 -10 z m 24,0 v -10 h 10 10 v 10 10 h -10 -10 z m 24,0 v -10 h 10 10 v 10 10 h -10 -10 z m 24,0 v -10 h 8 8 v 10 10 h -8 -8 z m 20,0 v -10 h 10 10 v 10 10 h -10 -10 z m 24,0 v -10 h 10 10 v 10 10 h -10 -10 z m 24,0 v -10 h 8 8 v 10 10 h -8 -8 z m 118,-17.5 c -0.6847,-0.825 -1.0199,-1.5 -0.7449,-1.5 0.275,0 1.0602,0.675 1.7449,1.5 0.6847,0.825 1.0199,1.5 0.7449,1.5 -0.275,0 -1.0602,-0.675 -1.7449,-1.5 z M 1054,224 v -8 h 111.5 111.5 v 8 8 H 1165.5 1054 Z m 16,2 c 0,-1.7037 -0.6667,-2 -4.5,-2 -3.8333,0 -4.5,0.2963 -4.5,2 0,1.7037 0.6667,2 4.5,2 3.8333,0 4.5,-0.2963 4.5,-2 z m 24,0 c 0,-1.7037 -0.6667,-2 -4.5,-2 -3.8333,0 -4.5,0.2963 -4.5,2 0,1.7037 0.6667,2 4.5,2 3.8333,0 4.5,-0.2963 4.5,-2 z m 24,0 c 0,-1.7037 -0.6667,-2 -4.5,-2 -3.8333,0 -4.5,0.2963 -4.5,2 0,1.7037 0.6667,2 4.5,2 3.8333,0 4.5,-0.2963 4.5,-2 z m 24,0 c 0,-1.7037 -0.6667,-2 -4.5,-2 -3.8333,0 -4.5,0.2963 -4.5,2 0,1.7037 0.6667,2 4.5,2 3.8333,0 4.5,-0.2963 4.5,-2 z m 24,0 c 0,-1.7037 -0.6667,-2 -4.5,-2 -3.8333,0 -4.5,0.2963 -4.5,2 0,1.7037 0.6667,2 4.5,2 3.8333,0 4.5,-0.2963 4.5,-2 z m 24,0 c 0,-1.7037 -0.6667,-2 -4.5,-2 -3.8333,0 -4.5,0.2963 -4.5,2 0,1.7037 0.6667,2 4.5,2 3.8333,0 4.5,-0.2963 4.5,-2 z m 24,0 c 0,-1.7037 -0.6667,-2 -4.5,-2 -3.8333,0 -4.5,0.2963 -4.5,2 0,1.7037 0.6667,2 4.5,2 3.8333,0 4.5,-0.2963 4.5,-2 z m 24,0 c 0,-1.7037 -0.6667,-2 -4.5,-2 -3.8333,0 -4.5,0.2963 -4.5,2 0,1.7037 0.6667,2 4.5,2 3.8333,0 4.5,-0.2963 4.5,-2 z m 24,0 c 0,-1.7037 -0.6667,-2 -4.5,-2 -3.8333,0 -4.5,0.2963 -4.5,2 0,1.7037 0.6667,2 4.5,2 3.8333,0 4.5,-0.2963 4.5,-2 z m -108,-40 v -18 h 49.5 49.5 v 18 18 h -11.5 -11.5 v -5.82186 c 0,-7.71571 -2.324,-14.17532 -6.7335,-18.71557 -5.4053,-5.56566 -10.3424,-7.41865 -19.7665,-7.41865 -9.2559,0 -14.214,1.82937 -19.1993,7.08397 -4.5799,4.82728 -6.3007,9.69951 -6.3007,17.83999 V 204 h -12 -12 z m 0,-28 v -6 h 49.5 49.5 v 6 6 h -49.5 -49.5 z" , "M 7.7680134,405.56677 C 7.083138,401.98408 13.304524,394.37112 20,390.59878 24.878249,387.8503 26.404365,387.5 33.5,387.5 c 7.16991,0 8.590459,0.33463 13.690548,3.225 l 5.690548,3.22499 0.694233,-2.22499 c 0.381829,-1.22375 1.033665,-2.45 1.448525,-2.725 0.41486,-0.275 0.972713,-2.1353 1.239674,-4.134 1.364591,-10.21654 14.093734,-24.91755 27.297652,-31.52632 4.371314,-2.18791 8.958314,-4.24227 10.193332,-4.56523 1.297109,-0.3392 2.249614,-1.36586 2.255257,-2.43083 0.01703,-3.21322 4.568601,-11.67328 8.907731,-16.55687 2.6122,-2.93997 6.70168,-5.94984 10.86955,-8 6.26895,-3.08368 7.33147,-3.28675 17.19736,-3.28675 10.07635,0 10.77927,0.14297 16.82818,3.42269 3.47192,1.88247 8.19692,5.51389 10.5,8.06982 l 4.18741,4.64715 4.38634,-2.779 c 6.69501,-4.24169 13.27928,-4.68133 21.22074,-1.41697 1.15901,0.47642 1.95666,-0.18336 2.86091,-2.36642 1.76645,-4.26458 8.81031,-10.44528 14.28817,-12.5373 4.55501,-1.73957 14.91819,-1.80104 20.24384,-0.12006 1.68476,0.53177 2.38177,-0.25701 4.42215,-5.00441 6.54297,-15.22367 24.73576,-24.71039 41.18232,-21.47473 3.48088,0.68482 8.52897,2.36 11.21797,3.72262 5.21218,2.64122 12.86478,9.86419 15.16632,14.31487 1.36433,2.63833 1.51091,2.67532 6.5281,1.64756 9.97066,-2.04246 23.54057,3.63916 30.00483,12.56282 1.59937,2.20787 3.18351,4.88252 3.5203,5.94367 0.49871,1.57129 1.21805,1.81573 3.87596,1.31711 5.0735,-0.9518 11.24379,1.21232 15.45043,5.41896 l 3.76288,3.76288 3.78293,-4.34873 c 2.08062,-2.3918 6.10108,-5.56502 8.93437,-7.0516 4.42389,-2.32114 6.35192,-2.70287 13.65144,-2.70287 7.26618,0 9.22578,0.38418 13.5,2.64668 2.75,1.45567 6.50712,4.21682 8.34915,6.13588 l 3.34916,3.4892 4.54807,-2.01183 c 3.07496,-1.3602 5.7842,-1.84603 8.36445,-1.49995 3.62576,0.48632 3.9403,0.30043 6.29744,-3.72172 C 439.41655,318.31395 449.2924,312.5987 461,312.5987 c 8.9917,0 14.68922,2.2301 21.32542,8.34714 4.22137,3.89113 9.67458,13.02316 9.67458,16.20122 0,1.1624 1.33034,1.36441 6.75,1.02501 l 6.75,-0.42272 -45.92196,35.12532 L 413.65608,408 H 210.94462 8.2331543 Z M 1455.3458,359.86462 l -48.1541,-48.1295 5.9973,0.62684 c 10.7353,1.12207 20.0437,7.05988 25.4027,16.20436 2.3572,4.02215 2.6717,4.20804 6.2975,3.72172 2.5802,-0.34608 5.2895,0.13975 8.3644,1.49995 l 4.5481,2.01183 3.3491,-3.4892 c 1.8421,-1.91906 5.5992,-4.68021 8.3492,-6.13588 4.2742,-2.2625 6.2338,-2.64668 13.5,-2.64668 7.2995,0 9.2276,0.38173 13.6514,2.70287 2.8333,1.48658 6.8538,4.6598 8.9344,7.0516 l 3.7829,4.34873 3.7629,-3.76288 c 4.2067,-4.20664 10.377,-6.37076 15.4505,-5.41896 2.6579,0.49862 3.3772,0.25418 3.8759,-1.31711 0.3368,-1.06115 1.9209,-3.7358 3.5203,-5.94367 6.4643,-8.92366 20.0342,-14.60528 30.0048,-12.56282 5.0172,1.02776 5.1638,0.99077 6.5281,-1.64756 2.3155,-4.47766 9.9665,-11.67988 15.2538,-14.35919 2.7371,-1.387 7.9092,-3.06218 11.4936,-3.72262 16.4597,-3.03282 34.312,6.37855 40.8192,21.51905 2.0404,4.7474 2.7374,5.53618 4.4222,5.00441 5.3256,-1.68098 15.6888,-1.61951 20.2438,0.12006 5.4779,2.09202 12.5218,8.27272 14.2882,12.5373 0.9043,2.18306 1.7019,2.84284 2.8609,2.36642 7.9415,-3.26436 14.5257,-2.82472 21.2208,1.41697 l 4.3863,2.779 4.1874,-4.64715 c 2.3031,-2.55593 7.0281,-6.18735 10.5,-8.06982 6.0489,-3.27972 6.7518,-3.42269 16.8282,-3.42269 9.8659,0 10.9284,0.20307 17.1973,3.28675 4.1679,2.05016 8.2574,5.06003 10.8696,8 4.3391,4.88359 8.8907,13.34365 8.9077,16.55687 0.01,1.06497 0.9582,2.09163 2.2553,2.43083 1.235,0.32296 5.822,2.37732 10.1933,4.56523 13.2039,6.60877 25.9331,21.30978 27.2977,31.52632 0.2669,1.9987 0.8248,3.859 1.2396,4.134 0.4149,0.275 1.0667,1.50125 1.4486,2.725 l 0.6942,2.22499 5.6906,-3.22499 c 5.1,-2.89037 6.5206,-3.225 13.6905,-3.225 7.0956,0 8.6218,0.3503 13.5,3.09878 6.6955,3.77234 12.9169,11.3853 12.232,14.96799 L 1863.7668,408 1683.6334,407.997 1503.5,407.994 1455.3458,359.86451 Z M 31.25,378.31067 c 0.9625,-0.25152 2.5375,-0.25152 3.5,0 0.9625,0.25153 0.175,0.45733 -1.75,0.45733 -1.925,0 -2.7125,-0.2058 -1.75,-0.45733 z m 1806,0 c 0.9625,-0.25152 2.5375,-0.25152 3.5,0 0.9625,0.25153 0.175,0.45733 -1.75,0.45733 -1.925,0 -2.7125,-0.2058 -1.75,-0.45733 z M 68,355.5 c 1.291745,-1.375 2.573628,-2.5 2.848628,-2.5 0.275,0 -0.556883,1.125 -1.848628,2.5 -1.291745,1.375 -2.573628,2.5 -2.848628,2.5 -0.275,0 0.556883,-1.125 1.848628,-2.5 z m 1734.9243,-0.25 -2.4243,-2.75 2.75,2.42431 c 2.5703,2.26589 3.2086,3.07569 2.4243,3.07569 -0.1791,0 -1.4166,-1.2375 -2.75,-2.75 z M 508.61514,335.82092 c 1.58667,-1.1985 3.44736,-2.6036 4.13486,-3.12245 0.93453,-0.70528 1.25,-0.15533 1.25,2.17909 0,2.99315 -0.17121,3.12244 -4.13486,3.12244 h -4.13486 z M 105,320.5 c 1.29175,-1.375 2.57363,-2.5 2.84863,-2.5 0.275,0 -0.55688,1.125 -1.84863,2.5 -1.29175,1.375 -2.57363,2.5 -2.84863,2.5 -0.275,0 0.55688,-1.125 1.84863,-2.5 z m 1660.9243,-0.25 -2.4243,-2.75 2.75,2.42431 c 1.5125,1.33338 2.75,2.57088 2.75,2.75 0,0.78428 -0.8098,0.14598 -3.0757,-2.42431 z M 387.25,314.31067 c 0.9625,-0.25152 2.5375,-0.25152 3.5,0 0.9625,0.25153 0.175,0.45733 -1.75,0.45733 -1.925,0 -2.7125,-0.2058 -1.75,-0.45733 z m 1094,0 c 0.9625,-0.25152 2.5375,-0.25152 3.5,0 0.9625,0.25153 0.175,0.45733 -1.75,0.45733 -1.925,0 -2.7125,-0.2058 -1.75,-0.45733 z" , "M 0.66153808,410.03587 C -1.9965072,384.60507 20.718842,365.10358 43.86672,372.94355 c 3.501696,1.18599 6.47456,2.02137 6.606365,1.8564 0.131804,-0.16497 1.063588,-2.49812 2.07063,-5.18478 4.627072,-12.34443 16.971773,-25.73303 29.510333,-32.00582 C 86.022114,335.62421 89.848355,334 90.556805,334 c 0.70845,0 1.833681,-1.6875 2.500514,-3.75 3.283087,-10.15452 11.651141,-19.53147 21.911611,-24.5534 6.14376,-3.00703 7.18464,-3.1966 17.5521,-3.1966 10.38406,0 11.39773,0.18545 17.53893,3.20875 3.58485,1.76482 8.4689,5.00991 10.85345,7.21131 4.19134,3.86942 4.42954,3.95396 7.1611,2.54141 4.94053,-2.55485 12.16337,-3.64278 16.98787,-2.55879 3.62038,0.81345 4.56898,0.72053 5.26056,-0.51525 2.08599,-3.72746 6.96855,-8.23238 11.85821,-10.94103 4.83008,-2.67564 6.22874,-2.95564 15.22045,-3.04692 l 9.90159,-0.10053 1.77482,-3.45948 c 0.97615,-1.90271 3.78353,-5.68247 6.23861,-8.39947 16.90815,-18.71196 45.69915,-18.64368 62.42243,0.14804 5.54081,6.22612 5.90252,6.45015 9.4658,5.86246 4.50488,-0.74299 13.93121,1.08822 19.09021,3.70855 4.94802,2.51318 11.38452,8.37312 14.34085,13.05624 1.5767,2.49766 2.85502,3.53355 3.87669,3.1415 2.41134,-0.92532 10.67447,1.55523 14.24755,4.27704 l 3.26015,2.48344 4.31951,-3.53952 c 7.41646,-6.07723 12.98711,-8.05821 22.66019,-8.05821 9.61518,0 15.24574,1.98263 22.4909,7.91949 2.8917,2.36954 4.58108,3.13452 5.57105,2.52268 0.78146,-0.48296 3.78175,-1.17309 6.66733,-1.53362 4.83411,-0.60399 5.51693,-1.03193 8.68707,-5.44454 15.45346,-21.51008 49.65021,-18.24018 60.67988,5.80224 l 2.36041,5.1452 9.02168,0.28451 9.02168,0.2845 0.29557,3.5 0.29558,3.5 -10.68327,-0.0703 -10.68327,-0.0703 -1.89245,-5.07313 c -2.45433,-6.57939 -8.26006,-13.09962 -14.97675,-16.81992 -4.88414,-2.70527 -6.19139,-2.96606 -14.85541,-2.96354 -7.93686,0.002 -10.19472,0.37354 -13.7222,2.25614 -5.94114,3.17078 -10.92713,7.7866 -13.91104,12.87825 -2.47274,4.2194 -2.71971,4.36285 -7.51144,4.36285 -2.72505,0 -6.33391,0.71325 -8.01968,1.58499 -2.98114,1.54161 -3.15086,1.5049 -6.20034,-1.34133 -7.69078,-7.17817 -17.85251,-10.99717 -25.95605,-9.75484 -6.6508,1.01961 -16.30942,6.16825 -19.86019,10.58671 l -3.17024,3.94495 -3.07441,-3.15801 C 355.58634,321.76595 351.24231,320 345.15344,320 c -4.48683,0 -5.01551,-0.25279 -6.12365,-2.92808 -1.76567,-4.26269 -9.34403,-11.55542 -14.80068,-14.24283 -3.83029,-1.88642 -6.49168,-2.35432 -14.00285,-2.46183 l -9.27374,-0.13273 -2.80466,-3.86727 c -4.05576,-5.59238 -9.75849,-10.29069 -16.10223,-13.26615 -4.81732,-2.2595 -6.8617,-2.60111 -15.56666,-2.60111 -9.30005,0 -10.49951,0.23556 -16.67169,3.27411 -7.78741,3.83373 -15.61756,11.78702 -18.43723,18.72719 -1.84415,4.53908 -1.91853,4.59173 -5.37005,3.80077 -6.69993,-1.53538 -16.19741,-0.842 -20.7222,1.51284 -5.80041,3.01873 -10.7828,7.57583 -12.30763,11.2571 -1.02204,2.46742 -1.69399,2.95364 -3.37322,2.44081 -8.03003,-2.45231 -15.51988,-1.73296 -21.05313,2.02201 l -3.65734,2.48193 -4.91798,-5.04909 c -3.00714,-3.08733 -7.55043,-6.29604 -11.69324,-8.25839 C 142.2104,309.8365 140.55633,309.5 132.5,309.5 c -7.7157,0 -9.83278,0.39014 -14.83589,2.734 -10.94345,5.12679 -17.6198,12.40999 -21.313723,23.25107 -1.096971,3.21943 -2.313153,4.95394 -3.734092,5.32552 -1.158999,0.30309 -5.602683,2.30057 -9.874853,4.43886 -13.521017,6.76749 -25.272928,21.05796 -28.783038,35.00055 -0.657709,2.6125 -1.410446,4.75 -1.672748,4.75 -0.262303,0 -1.73709,-0.89732 -3.277304,-1.99405 C 45.065326,380.19827 34.554813,377.6785 30.129513,378.47998 15.450888,381.13846 4.9081998,392.77312 4.2862209,407 l -0.2841707,6.5 1.6906729,-5 c 1.2989246,-3.84144 1.7781524,-4.47921 2.0682089,-2.75242 l 0.377536,2.24758 202.595292,0.25242 202.5953,0.25242 -4.33023,3 -4.33023,3 38.9157,0.51766 c 21.40363,0.28472 -69.35769,0.62222 -201.69184,0.75 L 1.2849128,416 Z M 1462.5,414.95719 c 25.3,-0.35268 46.1967,-0.71232 46.4371,-0.79921 0.2404,-0.0869 -0.8563,-1.50798 -2.4371,-3.15798 l -2.8742,-3 h 180.1176 180.1175 l 0.3779,-2.25 c 0.2906,-1.73047 0.7682,-1.09549 2.0685,2.75 l 1.6906,5 -0.2841,-6.5 c -0.622,-14.22688 -11.1647,-25.86154 -25.8433,-28.52002 -4.4253,-0.80148 -14.9358,1.71829 -18.8789,4.52597 -1.5402,1.09673 -3.015,1.99405 -3.2773,1.99405 -0.2623,0 -1.015,-2.1375 -1.6727,-4.75 -3.5101,-13.94259 -15.262,-28.23306 -28.783,-35.00055 -4.2722,-2.13829 -8.7159,-4.13577 -9.8749,-4.43886 -1.4209,-0.37158 -2.6371,-2.10609 -3.7341,-5.32552 -3.6939,-10.84108 -10.3703,-18.12428 -21.3137,-23.25107 -5.0031,-2.34386 -7.1202,-2.734 -14.8359,-2.734 -8.0563,0 -9.7104,0.3365 -15.7753,3.20928 -4.1428,1.96235 -8.6861,5.17106 -11.6932,8.25839 l -4.918,5.04909 -3.6573,-2.48193 c -5.4933,-3.72783 -12.6947,-4.44688 -20.9562,-2.09241 -1.5744,0.44869 -2.5765,-0.38807 -4.7091,-3.93229 -2.9874,-4.96494 -9.9239,-9.92784 -16.2755,-11.64487 -3.6813,-0.99514 -10.233,-0.72306 -16.5154,0.68583 -2.217,0.49719 -2.7117,0.0392 -4.3701,-4.04599 -2.8181,-6.94218 -10.6472,-14.89598 -18.4372,-18.73099 -6.1722,-3.03855 -7.3716,-3.27411 -16.6717,-3.27411 -8.7049,0 -10.7493,0.34161 -15.5666,2.60111 -6.3438,2.97546 -12.0465,7.67377 -16.1023,13.26615 l -2.8046,3.86727 -9.2738,0.13273 c -7.5111,0.10751 -10.1725,0.57541 -14.0028,2.46183 -5.4567,2.68741 -13.035,9.98014 -14.8007,14.24283 -1.1081,2.67529 -1.6368,2.92808 -6.1236,2.92808 -6.0889,0 -10.4329,1.76595 -14.421,5.86247 l -3.0744,3.15801 -3.1703,-3.94495 c -3.5507,-4.41846 -13.2094,-9.5671 -19.8602,-10.58671 -8.1035,-1.24233 -18.2652,2.57667 -25.956,9.75484 -3.0495,2.84623 -3.2192,2.88294 -6.2003,1.34133 -1.6858,-0.87174 -5.2947,-1.58499 -8.0197,-1.58499 -4.7917,0 -5.0387,-0.14345 -7.5115,-4.36285 -5.4297,-9.26513 -14.3865,-14.70867 -25.3832,-15.42674 l -7.25,-0.47342 v -3.30111 c 0,-4.01655 1.9241,-4.86594 8.7369,-3.85688 10.6413,1.57611 19.1917,6.53308 24.8467,14.40455 3.1702,4.41261 3.853,4.84055 8.6871,5.44454 2.8856,0.36053 5.8859,1.05066 6.6673,1.53362 0.99,0.61184 2.6794,-0.15314 5.5711,-2.52268 7.2452,-5.93686 12.8757,-7.91949 22.4909,-7.91949 9.6731,0 15.2437,1.98098 22.6602,8.05821 l 4.3195,3.53952 3.2602,-2.48344 c 3.573,-2.72181 11.8362,-5.20236 14.2475,-4.27704 1.0217,0.39205 2.3,-0.64384 3.8767,-3.1415 2.9563,-4.68312 9.3928,-10.54306 14.3408,-13.05624 5.4151,-2.7504 14.4729,-4.40489 19.7682,-3.61082 2.8857,0.43274 4.5269,0.30436 4.5269,-0.3541 0,-0.56812 2.2852,-3.53337 5.0783,-6.58943 16.2559,-17.78659 45.1321,-17.39506 61.6051,0.8353 2.4551,2.717 5.2624,6.49676 6.2386,8.39947 l 1.7748,3.45948 9.9016,0.10053 c 8.9917,0.0913 10.3904,0.37128 15.2205,3.04692 4.8896,2.70865 9.7722,7.21357 11.8582,10.94103 0.6915,1.23578 1.6401,1.3287 5.2605,0.51525 4.8245,-1.08399 12.0474,0.004 16.9879,2.55879 2.7316,1.41255 2.9698,1.32801 7.1611,-2.54141 2.3845,-2.2014 7.2686,-5.44649 10.8534,-7.21131 6.1412,-3.0233 7.1549,-3.20875 17.539,-3.20875 10.3674,0 11.4083,0.18957 17.5521,3.1966 10.2604,5.02193 18.6285,14.39888 21.9116,24.5534 0.6668,2.0625 1.792,3.75 2.5005,3.75 0.7084,0 4.5347,1.62421 8.5028,3.60935 12.5385,6.27279 24.8832,19.66139 29.5103,32.00582 1.007,2.68666 1.9388,5.01981 2.0706,5.18478 0.1318,0.16497 3.1047,-0.67041 6.6064,-1.8564 23.1479,-7.83997 45.8632,11.66152 43.2052,37.09232 L 1870.7151,416 1643.6075,415.79921 C 1518.6833,415.68876 1437.1969,415.3099 1462.5,414.95719 Z M 54.710251,387 c 0.287656,-1.1 0.695528,-2 0.90638,-2 C 55.827484,385 56,385.9 56,387 c 0,1.1 -0.407871,2 -0.90638,2 -0.49851,0 -0.671025,-0.9 -0.383369,-2 z M 1816,387 c 0,-1.1 0.1725,-2 0.3834,-2 0.2108,0 0.6187,0.9 0.9063,2 0.2877,1.1 0.1152,2 -0.3833,2 -0.4985,0 -0.9064,-0.9 -0.9064,-2 z M 1025.4881,226 c 0,-89.375 0.1178,-125.9375 0.2618,-81.25 0.144,44.6875 0.144,117.8125 0,162.5 -0.144,44.6875 -0.2618,8.125 -0.2618,-81.25 z m 383.9169,88.75 -1.905,-2.25 2.25,1.90499 c 2.1144,1.79022 2.7052,2.59501 1.905,2.59501 -0.1898,0 -1.2023,-1.0125 -2.25,-2.25 z" , "m 1305.3201,410 c 0,-3.575 0.1815,-5.0375 0.4033,-3.25 0.2218,1.7875 0.2218,4.7125 0,6.5 -0.2218,1.7875 -0.4033,0.325 -0.4033,-3.25 z m 23.8378,0 c 0,-1.375 0.227,-1.9375 0.5044,-1.25 0.2774,0.6875 0.2774,1.8125 0,2.5 -0.2774,0.6875 -0.5044,0.125 -0.5044,-1.25 z M 762.34941,396 c 0,-4.675 0.17226,-6.5875 0.3828,-4.25 0.21053,2.3375 0.21053,6.1625 0,8.5 -0.21054,2.3375 -0.3828,0.425 -0.3828,-4.25 z m 514.99999,0 c 0,-4.675 0.1723,-6.5875 0.3828,-4.25 0.2105,2.3375 0.2105,6.1625 0,8.5 -0.2105,2.3375 -0.3828,0.425 -0.3828,-4.25 z m 56.0873,-18 c 0,-14.575 0.1398,-20.5375 0.3107,-13.25 0.1709,7.2875 0.1709,19.2125 0,26.5 -0.1709,7.2875 -0.3107,1.325 -0.3107,-13.25 z M 590.43244,324 c 0,-13.475 0.14155,-18.9875 0.31456,-12.25 0.17301,6.7375 0.17301,17.7625 0,24.5 -0.17301,6.7375 -0.31456,1.225 -0.31456,-12.25 z m -76.14643,10.5 c 0.008,-2.75 0.20172,-3.75631 0.43157,-2.23625 0.22986,1.52006 0.22371,3.77006 -0.0136,5 -0.23736,1.22994 -0.42542,-0.0138 -0.41792,-2.76375 z m 891.06899,-30 c 0,-4.95 0.1746,-6.84892 0.3813,-4.21983 0.2066,2.6291 0.2042,6.6791 -0.01,9 -0.2097,2.32091 -0.3788,0.16983 -0.3757,-4.78017 z M 590.32007,282 c 0,-3.575 0.1815,-5.0375 0.40333,-3.25 0.22182,1.7875 0.22182,4.7125 0,6.5 -0.22183,1.7875 -0.40333,0.325 -0.40333,-3.25 z m 16,0 c 0,-3.575 0.1815,-5.0375 0.40333,-3.25 0.22182,1.7875 0.22182,4.7125 0,6.5 -0.22183,1.7875 -0.40333,0.325 -0.40333,-3.25 z m 16,0 c 0,-3.575 0.1815,-5.0375 0.40333,-3.25 0.22182,1.7875 0.22182,4.7125 0,6.5 -0.22183,1.7875 -0.40333,0.325 -0.40333,-3.25 z m 16,0 c 0,-3.575 0.1815,-5.0375 0.40333,-3.25 0.22182,1.7875 0.22182,4.7125 0,6.5 -0.22183,1.7875 -0.40333,0.325 -0.40333,-3.25 z m 16,0 c 0,-3.575 0.1815,-5.0375 0.40333,-3.25 0.22182,1.7875 0.22182,4.7125 0,6.5 -0.22183,1.7875 -0.40333,0.325 -0.40333,-3.25 z m 16,0 c 0,-3.575 0.1815,-5.0375 0.40333,-3.25 0.22182,1.7875 0.22182,4.7125 0,6.5 -0.22183,1.7875 -0.40333,0.325 -0.40333,-3.25 z m 16,0 c 0,-3.575 0.1815,-5.0375 0.40333,-3.25 0.22182,1.7875 0.22182,4.7125 0,6.5 -0.22183,1.7875 -0.40333,0.325 -0.40333,-3.25 z m 16,0 c 0,-3.575 0.1815,-5.0375 0.40333,-3.25 0.22182,1.7875 0.22182,4.7125 0,6.5 -0.22183,1.7875 -0.40333,0.325 -0.40333,-3.25 z m 16,0 c 0,-3.575 0.1815,-5.0375 0.40333,-3.25 0.22182,1.7875 0.22182,4.7125 0,6.5 -0.22183,1.7875 -0.40333,0.325 -0.40333,-3.25 z m 16,0 c 0,-3.575 0.1815,-5.0375 0.40333,-3.25 0.22182,1.7875 0.22182,4.7125 0,6.5 -0.22183,1.7875 -0.40333,0.325 -0.40333,-3.25 z m 16,0 c 0,-3.575 0.1815,-5.0375 0.40333,-3.25 0.22182,1.7875 0.22182,4.7125 0,6.5 -0.22183,1.7875 -0.40333,0.325 -0.40333,-3.25 z m 23.83782,-4 c 0,-1.375 0.22698,-1.9375 0.50439,-1.25 0.27741,0.6875 0.27741,1.8125 0,2.5 -0.27741,0.6875 -0.50439,0.125 -0.50439,-1.25 z m 32,-4 c 0,-1.375 0.22698,-1.9375 0.50439,-1.25 0.27741,0.6875 0.27741,1.8125 0,2.5 -0.27741,0.6875 -0.50439,0.125 -0.50439,-1.25 z m 36,0 c 0,-1.375 0.22698,-1.9375 0.50439,-1.25 0.27741,0.6875 0.27741,1.8125 0,2.5 -0.27741,0.6875 -0.50439,0.125 -0.50439,-1.25 z m -68,-4 c 0,-1.375 0.22698,-1.9375 0.50439,-1.25 0.27741,0.6875 0.27741,1.8125 0,2.5 -0.27741,0.6875 -0.50439,0.125 -0.50439,-1.25 z m 8.21207,-12 c 0,-5.775 0.16528,-8.1375 0.36728,-5.25 0.202,2.8875 0.202,7.6125 0,10.5 -0.202,2.8875 -0.36728,0.525 -0.36728,-5.25 z m 12,0 c 0,-5.775 0.16528,-8.1375 0.36728,-5.25 0.202,2.8875 0.202,7.6125 0,10.5 -0.202,2.8875 -0.36728,0.525 -0.36728,-5.25 z m 12,0 c 0,-5.775 0.16528,-8.1375 0.36728,-5.25 0.202,2.8875 0.202,7.6125 0,10.5 -0.202,2.8875 -0.36728,0.525 -0.36728,-5.25 z m 12,0 c 0,-5.775 0.16528,-8.1375 0.36728,-5.25 0.202,2.8875 0.202,7.6125 0,10.5 -0.202,2.8875 -0.36728,0.525 -0.36728,-5.25 z m 12,0 c 0,-5.775 0.16528,-8.1375 0.36728,-5.25 0.202,2.8875 0.202,7.6125 0,10.5 -0.202,2.8875 -0.36728,0.525 -0.36728,-5.25 z m 12,0 c 0,-5.775 0.16528,-8.1375 0.36728,-5.25 0.202,2.8875 0.202,7.6125 0,10.5 -0.202,2.8875 -0.36728,0.525 -0.36728,-5.25 z m -79.97257,-8 c 0,-7.975 0.1553,-11.2375 0.34512,-7.25 0.18981,3.9875 0.18981,10.5125 0,14.5 -0.18982,3.9875 -0.34512,0.725 -0.34512,-7.25 z m 514.95201,-26 c 0,-4.675 0.1723,-6.5875 0.3828,-4.25 0.2105,2.3375 0.2105,6.1625 0,8.5 -0.2105,2.3375 -0.3828,0.425 -0.3828,-4.25 z M 1377,229.5 c -0.6847,-0.825 -1.0199,-1.5 -0.7449,-1.5 0.275,0 1.0602,0.675 1.7449,1.5 0.6847,0.825 1.0199,1.5 0.7449,1.5 -0.275,0 -1.0602,-0.675 -1.7449,-1.5 z m -68.5,-1.5 c 0.9955,-1.1 2.035,-2 2.31,-2 0.275,0 -0.3145,0.9 -1.31,2 -0.9955,1.1 -2.035,2 -2.31,2 -0.275,0 0.3145,-0.9 1.31,-2 z m 84.5,0.5 c -0.6847,-0.825 -1.0199,-1.5 -0.7449,-1.5 0.275,0 1.0602,0.675 1.7449,1.5 0.6847,0.825 1.0199,1.5 0.7449,1.5 -0.275,0 -1.0602,-0.675 -1.7449,-1.5 z M 1061.1579,226 c 0,-1.375 0.227,-1.9375 0.5044,-1.25 0.2774,0.6875 0.2774,1.8125 0,2.5 -0.2774,0.6875 -0.5044,0.125 -0.5044,-1.25 z m 24,0 c 0,-1.375 0.227,-1.9375 0.5044,-1.25 0.2774,0.6875 0.2774,1.8125 0,2.5 -0.2774,0.6875 -0.5044,0.125 -0.5044,-1.25 z m 24,0 c 0,-1.375 0.227,-1.9375 0.5044,-1.25 0.2774,0.6875 0.2774,1.8125 0,2.5 -0.2774,0.6875 -0.5044,0.125 -0.5044,-1.25 z m 24,0 c 0,-1.375 0.227,-1.9375 0.5044,-1.25 0.2774,0.6875 0.2774,1.8125 0,2.5 -0.2774,0.6875 -0.5044,0.125 -0.5044,-1.25 z m 24,0 c 0,-1.375 0.227,-1.9375 0.5044,-1.25 0.2774,0.6875 0.2774,1.8125 0,2.5 -0.2774,0.6875 -0.5044,0.125 -0.5044,-1.25 z m 24,0 c 0,-1.375 0.227,-1.9375 0.5044,-1.25 0.2774,0.6875 0.2774,1.8125 0,2.5 -0.2774,0.6875 -0.5044,0.125 -0.5044,-1.25 z M 1292,227.62244 c 0,-0.20765 0.7875,-0.99515 1.75,-1.75 1.5861,-1.24387 1.6214,-1.2085 0.3776,0.37756 -1.3064,1.66575 -2.1276,2.19548 -2.1276,1.37244 z m 76.405,-5.87244 -1.905,-2.25 2.25,1.90499 c 2.1144,1.79022 2.7052,2.59501 1.905,2.59501 -0.1898,0 -1.2023,-1.0125 -2.25,-2.25 z M 1317.5,220 c 0.9955,-1.1 2.035,-2 2.31,-2 0.275,0 -0.3145,0.9 -1.31,2 -0.9955,1.1 -2.035,2 -2.31,2 -0.275,0 0.3145,-0.9 1.31,-2 z m 63.5,-2.5 c -0.6847,-0.825 -1.0199,-1.5 -0.7449,-1.5 0.275,0 1.0602,0.675 1.7449,1.5 0.6847,0.825 1.0199,1.5 0.7449,1.5 -0.275,0 -1.0602,-0.675 -1.7449,-1.5 z m -77,-0.87756 c 0,-0.20765 0.7875,-0.99515 1.75,-1.75 1.5861,-1.24387 1.6214,-1.2085 0.3776,0.37756 -1.3064,1.66575 -2.1276,2.19548 -2.1276,1.37244 z m 55.405,-2.87244 -1.905,-2.25 2.25,1.90499 c 1.2375,1.04774 2.25,2.06024 2.25,2.25 0,0.80025 -0.8048,0.20945 -2.595,-1.90499 z M 1326.5,212 c 0.9955,-1.1 2.035,-2 2.31,-2 0.275,0 -0.3145,0.9 -1.31,2 -0.9955,1.1 -2.035,2 -2.31,2 -0.275,0 0.3145,-0.9 1.31,-2 z m 45.5,-2.5 c -0.6847,-0.825 -1.0199,-1.5 -0.7449,-1.5 0.275,0 1.0602,0.675 1.7449,1.5 0.6847,0.825 1.0199,1.5 0.7449,1.5 -0.275,0 -1.0602,-0.675 -1.7449,-1.5 z m -59,-0.87756 c 0,-0.20765 0.7875,-0.99515 1.75,-1.75 1.5861,-1.24387 1.6214,-1.2085 0.3776,0.37756 -1.3064,1.66575 -2.1276,2.19548 -2.1276,1.37244 z m 37.405,-2.87244 -1.905,-2.25 2.25,1.90499 c 2.1144,1.79022 2.7052,2.59501 1.905,2.59501 -0.1898,0 -1.2023,-1.0125 -2.25,-2.25 z M 1335.5,204 c 0.9955,-1.1 2.035,-2 2.31,-2 0.275,0 -0.3145,0.9 -1.31,2 -0.9955,1.1 -2.035,2 -2.31,2 -0.275,0 0.3145,-0.9 1.31,-2 z m -82.0849,-18 c 0,-10.175 0.1485,-14.3375 0.33,-9.25 0.1815,5.0875 0.1815,13.4125 0,18.5 -0.1815,5.0875 -0.33,0.925 -0.33,-9.25 z m 109.9899,15.75 -1.905,-2.25 2.25,1.90499 c 2.1144,1.79022 2.7052,2.59501 1.905,2.59501 -0.1898,0 -1.2023,-1.0125 -2.25,-2.25 z M 1321,201.62244 c 0,-0.20765 0.7875,-0.99515 1.75,-1.75 1.5861,-1.24387 1.6214,-1.2085 0.3776,0.37756 -1.3064,1.66575 -2.1276,2.19548 -2.1276,1.37244 z M 1355,194.5 c -0.6847,-0.825 -1.0199,-1.5 -0.7449,-1.5 0.275,0 1.0602,0.675 1.7449,1.5 0.6847,0.825 1.0199,1.5 0.7449,1.5 -0.275,0 -1.0602,-0.675 -1.7449,-1.5 z m -25,-0.87756 c 0,-0.20765 0.7875,-0.99515 1.75,-1.75 1.5861,-1.24387 1.6214,-1.2085 0.3776,0.37756 -1.3064,1.66575 -2.1276,2.19548 -2.1276,1.37244 z M 1346,186.5 c -0.6847,-0.825 -1.0199,-1.5 -0.7449,-1.5 0.275,0 1.0602,0.675 1.7449,1.5 0.6847,0.825 1.0199,1.5 0.7449,1.5 -0.275,0 -1.0602,-0.675 -1.7449,-1.5 z m -7,-0.87756 c 0,-0.20765 0.7875,-0.99515 1.75,-1.75 1.5861,-1.24387 1.6214,-1.2085 0.3776,0.37756 -1.3064,1.66575 -2.1276,2.19548 -2.1276,1.37244 z M 1253.3201,158 c 0,-3.575 0.1815,-5.0375 0.4033,-3.25 0.2218,1.7875 0.2218,4.7125 0,6.5 -0.2218,1.7875 -0.4033,0.325 -0.4033,-3.25 z" , "m 486.75,415.26276 c 2.8875,-0.202 7.6125,-0.202 10.5,0 2.8875,0.202 0.525,0.36728 -5.25,0.36728 -5.775,0 -8.1375,-0.16528 -5.25,-0.36728 z m 921.4821,0.0227 c 1.5026,-0.22817 4.2026,-0.23276 6,-0.0102 1.7973,0.22256 0.5679,0.40925 -2.7321,0.41486 -3.3,0.006 -4.7706,-0.17648 -3.2679,-0.40466 z m 79.9915,-1.01236 c 2.0479,-0.21488 5.6479,-0.21785 8,-0.007 2.352,0.21125 0.6764,0.38706 -3.7236,0.39069 -4.4,0.004 -6.3244,-0.16921 -4.2764,-0.38408 z M 416.03571,406.57853 c 0.80536,-0.7818 21.26429,-16.54901 45.46429,-35.03822 42.45428,-32.43581 44.14052,-33.60664 48,-33.32854 3.99775,0.28807 4.00017,0.29083 4.29928,4.90337 0.29158,4.49632 0.21498,4.63201 -2.97515,5.27003 -8.51822,1.70365 -15.59215,10.96496 -16.56433,21.68629 L 493.72222,376 H 503.86111 514 v 16 16 h -49.71429 c -42.98985,0 -49.51622,-0.19227 -48.25,-1.42147 z M 1406,360.5 c 0,-35.56406 0.2936,-47.5 1.1684,-47.5 0.6426,0 22.5801,21.36935 48.75,47.48745 l 47.5816,47.48746 -48.75,0.0125 L 1406,408 Z" , "m 1290,410 v -6 h 7.5 7.5 v 6 6 h -7.5 -7.5 z m 44,0 v -6 h 30.0293 30.0293 l -0.1187,-78 -0.1188,-78 h -51.7596 -51.7595 l -0.5884,-13.22929 c -0.3236,-7.27611 -0.4197,-13.39795 -0.2136,-13.60409 0.2062,-0.20613 0.5155,1.58042 0.6874,3.97011 l 0.3126,4.34489 10.9117,-9.99081 c 6.0015,-5.49494 17.6698,-15.9403 25.9297,-23.21189 l 15.018,-13.22109 6.0703,5.37797 c 3.3387,2.95789 12.8528,11.3572 21.1424,18.66514 8.2897,7.30794 17.2407,15.33734 19.8911,17.84311 8.5868,8.11835 12.2502,11.05595 13.7874,11.05595 1.3244,0 1.5902,9.38207 2.1893,77.25 0.375,42.4875 0.6545,82.9875 0.621,90 L 1406,416 h -36 -36 z m 50,-174.41182 c 0,-0.2265 -7.9875,-7.49933 -17.75,-16.16185 -9.7625,-8.66252 -19.1093,-17.01542 -20.7706,-18.562 l -3.0206,-2.81197 -16.4794,14.62744 c -9.0637,8.0451 -18.6656,16.58331 -21.3376,18.97382 L 1299.7837,236 h 42.1081 C 1365.0513,236 1384,235.81468 1384,235.58818 Z M 1309.4405,384 c 0,-15.675 0.1383,-22.0875 0.3072,-14.25 0.169,7.8375 0.169,20.6625 0,28.5 -0.1689,7.8375 -0.3072,1.425 -0.3072,-14.25 z m -703.09109,12 c 0,-4.675 0.17226,-6.5875 0.3828,-4.25 0.21053,2.3375 0.21053,6.1625 0,8.5 -0.21054,2.3375 -0.3828,0.425 -0.3828,-4.25 z m 20.04798,-6 c 0,-7.975 0.1553,-11.2375 0.34512,-7.25 0.18981,3.9875 0.18981,10.5125 0,14.5 -0.18982,3.9875 -0.34512,0.725 -0.34512,-7.25 z m 136.25629,1.843 C 762.29415,385.15665 762,361.75665 762,339.843 V 300 h -84 -84 v 24 c 0,20.66667 -0.20833,24 -1.5,24 -1.29167,0 -1.5,-3.33333 -1.5,-24 V 300 H 558.5 526 v 24 24 h -5.87742 -5.87742 l 0.2545,-12.75 c 0.13998,-7.0125 0.30982,-25.9125 0.37742,-42 L 515,264 h 124 124 v -14 -14 h 57.5 57.5 v 6 6 h -4 -4 v 10 10 h 4 c 3.33333,0 4,0.33333 4,2 0,1.91667 -0.66667,2 -16,2 -15.33333,0 -16,0.0833 -16,2 0,1.91667 0.66667,2 16,2 15.33333,0 16,0.0833 16,2 0,1.97411 -0.66667,2 -51.5,2 -50.83333,0 -51.5,-0.0259 -51.5,-2 0,-1.91667 0.66667,-2 16,-2 15.33333,0 16,-0.0833 16,-2 0,-1.91667 -0.66667,-2 -16,-2 -15.33333,0 -16,-0.0833 -16,-2 0,-1.66667 0.66667,-2 4,-2 h 4 v -10 -10 h -4.5 -4.5 v 78 78 h -5.34632 -5.34633 z M 543,282 v -6 h -8.5 -8.5 v 6 6 h 8.5 8.5 z m 16,0 v -6 h -6.5 -6.5 v 6 6 h 6.5 6.5 z m 16,0 v -6 h -6.5 -6.5 v 6 6 h 6.5 6.5 z m 16,0 v -6 h -6.5 -6.5 v 6 6 h 6.5 6.5 z m 16,0 v -6 h -6.5 -6.5 v 6 6 h 6.5 6.5 z m 16,0 v -6 h -6.5 -6.5 v 6 6 h 6.5 6.5 z m 16,0 v -6 h -6.5 -6.5 v 6 6 h 6.5 6.5 z m 16,0 v -6 h -6.5 -6.5 v 6 6 h 6.5 6.5 z m 16,0 v -6 h -6.5 -6.5 v 6 6 h 6.5 6.5 z m 16,0 v -6 h -6.5 -6.5 v 6 6 h 6.5 6.5 z m 16,0 v -6 h -6.5 -6.5 v 6 6 h 6.5 6.5 z m 16,0 v -6 h -6.5 -6.5 v 6 6 h 6.5 6.5 z m 16,0 v -6 h -6.5 -6.5 v 6 6 h 6.5 6.5 z m 16,0 v -6 h -6.5 -6.5 v 6 6 h 6.5 6.5 z m 11,0 v -6 h -4 -4 v 6 6 h 4 4 z m 81,-8 c 0,-1.91919 -0.66667,-2 -16.5,-2 -15.83333,0 -16.5,0.0808 -16.5,2 0,1.91919 0.66667,2 16.5,2 15.83333,0 16.5,-0.0808 16.5,-2 z m -48,-16 v -10 h -4.5 -4.5 v 10 10 h 4.5 4.5 z m 12,0 v -10 h -4.5 -4.5 v 10 10 h 4.5 4.5 z m 12,0 v -10 h -4.5 -4.5 v 10 10 h 4.5 4.5 z m 12,0 v -10 h -4.5 -4.5 v 10 10 h 4.5 4.5 z m 12,0 v -10 h -4.5 -4.5 v 10 10 h 4.5 4.5 z m 11,0 v -10 h -4 -4 v 10 10 h 4 4 z m 12,0 v -10 h -4 -4 v 10 10 h 4 4 z m 463.1579,140 c 0,-1.375 0.227,-1.9375 0.5044,-1.25 0.2774,0.6875 0.2774,1.8125 0,2.5 -0.2774,0.6875 -0.5044,0.125 -0.5044,-1.25 z m 0,-8 c 0,-1.375 0.227,-1.9375 0.5044,-1.25 0.2774,0.6875 0.2774,1.8125 0,2.5 -0.2774,0.6875 -0.5044,0.125 -0.5044,-1.25 z m -803.00001,-4 c 0,-1.375 0.22698,-1.9375 0.50439,-1.25 0.27741,0.6875 0.27741,1.8125 0,2.5 -0.27741,0.6875 -0.50439,0.125 -0.50439,-1.25 z m 80.11447,-6 c 0,-2.475 0.19502,-3.4875 0.43337,-2.25 0.23836,1.2375 0.23836,3.2625 0,4.5 -0.23835,1.2375 -0.43337,0.225 -0.43337,-2.25 z m 710.88554,-6 c 0,-1.375 0.227,-1.9375 0.5044,-1.25 0.2774,0.6875 0.2774,1.8125 0,2.5 -0.2774,0.6875 -0.5044,0.125 -0.5044,-1.25 z m -112,-148 c 0,-1.375 0.227,-1.9375 0.5044,-1.25 0.2774,0.6875 0.2774,1.8125 0,2.5 -0.2774,0.6875 -0.5044,0.125 -0.5044,-1.25 z m 24,0 c 0,-1.375 0.227,-1.9375 0.5044,-1.25 0.2774,0.6875 0.2774,1.8125 0,2.5 -0.2774,0.6875 -0.5044,0.125 -0.5044,-1.25 z m 24,0 c 0,-1.375 0.227,-1.9375 0.5044,-1.25 0.2774,0.6875 0.2774,1.8125 0,2.5 -0.2774,0.6875 -0.5044,0.125 -0.5044,-1.25 z m 36.1145,-18 c 0,-2.475 0.195,-3.4875 0.4333,-2.25 0.2384,1.2375 0.2384,3.2625 0,4.5 -0.2383,1.2375 -0.4333,0.225 -0.4333,-2.25 z M 1190,199.5651 c 0,-5.27981 1.3658,-9.24583 4.2409,-12.3151 1.7928,-1.91386 3.1424,-2.25 9.0334,-2.25 6.543,0 7.1136,0.1879 10.3257,3.4 3.1235,3.1235 3.4,3.89608 3.4,9.5 v 6.1 h -13.5 -13.5 z" , "m 503.00374,367.25 c 0.01,-1.96313 4.32215,-7.47813 7.23712,-9.2554 3.20584,-1.95463 5.29104,-1.98719 127.50914,-1.99086 L 762,356 v 6 6 H 632.5 c -71.225,0 -129.49832,-0.3375 -129.49626,-0.75 z" , "M 454.03976,415.01443 405.57952,414.5 l 4.10415,-3.25 4.10414,-3.25 H 463.8939 514 v 4 4 l -5.75,-0.23557 c -3.1625,-0.12956 -27.55711,-0.46706 -54.21024,-0.75 z M 1406,411.5 V 408 h 48.7308 48.7307 l 2.8397,2.83964 2.8396,2.83964 -18.2307,0.66036 C 1480.8832,414.70284 1457.6765,415 1439.3397,415 H 1406 Z" , "m 514,396 v -20 h 6 6 v 14 14 h 40.5 40.5 v -8 -8 h -40 c -39.33333,0 -40,-0.0333 -40,-2 0,-1.96667 0.66667,-2 40,-2 h 40 v -4 c 0,-2.88889 0.41667,-4 1.5,-4 1.2619,0 1.5,2.22222 1.5,14 v 14 h 8.5 8.5 v -14 c 0,-11.77778 0.2381,-14 1.5,-14 1.08333,0 1.5,1.11111 1.5,4 v 4 h 66 c 65.33333,0 66,0.0202 66,2 0,1.9798 -0.66667,2 -66,2 h -66 v 8 8 h 124 124 v 6 6 H 696 514 Z m 775.4168,1.5 c -9e-4,-10.45 0.1465,-14.86087 0.3276,-9.80194 0.1811,5.05893 0.1819,13.60893 0,19 -0.1801,5.39107 -0.3283,1.25194 -0.3293,-9.19806 z m 16.5832,0 c 0,-11.66667 0.3694,-18.5 1,-18.5 0.55,0 1,-0.675 1,-1.5 0,-0.825 -0.45,-1.5 -1,-1.5 -0.6111,0 -1,-4.66667 -1,-12 v -12 h 13.5 13.5 v 22.84638 c 0,12.5655 0.2924,26.9655 0.6497,32 L 1334.2995,416 H 1320.1497 1306 Z m 24,12.5 c 0,-1.86667 -0.6667,-2 -10,-2 -9.3333,0 -10,-0.13333 -10,-2 0,-1.86667 0.6667,-2 10,-2 h 10 v -12.5 c 0,-7.66667 0.3867,-12.5 1,-12.5 0.55,0 1,-0.675 1,-1.5 0,-0.825 -0.45,-1.5 -1,-1.5 -0.6,0 -1,-4 -1,-10 v -10 h -10.5 -10.5 v 28 28 h 10.5 c 9.8333,0 10.5,-0.12698 10.5,-2 z m -20,-12 c 0,-1.85965 0.6667,-2 9.5,-2 8.8333,0 9.5,0.14035 9.5,2 0,1.85965 -0.6667,2 -9.5,2 -8.8333,0 -9.5,-0.14035 -9.5,-2 z m 0,-8 c 0,-1.85965 0.6667,-2 9.5,-2 8.8333,0 9.5,0.14035 9.5,2 0,1.85965 -0.6667,2 -9.5,2 -8.8333,0 -9.5,-0.14035 -9.5,-2 z m 4,-16 c 0,-1.11111 0.6667,-2 1.5,-2 0.8333,0 1.5,0.88889 1.5,2 0,1.11111 -0.6667,2 -1.5,2 -0.8333,0 -1.5,-0.88889 -1.5,-2 z m 35,10 c 0,-3.33333 0.3333,-4 2,-4 1.9048,0 2,-0.66667 2,-14 v -14 h 10.5 10.5 v 14 c 0,13.33333 0.095,14 2,14 1.6667,0 2,0.66667 2,4 v 4 h -14.5 -14.5 z m 21,-10 v -6 h -6.5 -6.5 v 6 6 h 6.5 6.5 z m 0,-14 v -4 h -6.5 -6.5 v 4 4 h 6.5 6.5 z m -580,16 c 0,-3.33333 0.33333,-4 2,-4 1.91667,0 2,-0.66667 2,-16 v -16 h 10 10 v 16 c 0,15.33333 0.0833,16 2,16 1.66667,0 2,0.66667 2,4 v 4 h -14 -14 z m 20,-12 v -8 h -6 -6 v 8 8 h 6 6 z m 0,-16 v -4 h -6 -6 v 4 4 h 6 6 z m 24,28 c 0,-3.33333 0.33333,-4 2,-4 1.91667,0 2,-0.66667 2,-16 v -16 h 10 10 v 16 c 0,15.33333 0.0833,16 2,16 1.66667,0 2,0.66667 2,4 v 4 h -14 -14 z m 20,-12 v -8 h -6 -6 v 8 8 h 6 6 z m 0,-16 v -4 h -6 -6 v 4 4 h 6 6 z m 435.4679,-45 c -0.01,-40.975 0.1194,-65.6125 0.2809,-54.75 l 0.2935,19.75 h 51.4788 c 50.8122,0 51.4789,0.0259 51.4789,2 0,1.97411 -0.6667,2 -51.5,2 -50.8333,0 -51.5,0.0259 -51.5,2 0,1.97411 0.6667,2 51.5,2 50.8333,0 51.5,0.0259 51.5,2 0,1.97411 -0.6667,2 -51.4905,2 h -51.4905 l -0.2692,48.75 c -0.1481,26.8125 -0.275,15.225 -0.2819,-25.75 z m -76.0979,63 c 0,-5.775 0.1652,-8.1375 0.3672,-5.25 0.202,2.8875 0.202,7.6125 0,10.5 -0.202,2.8875 -0.3672,0.525 -0.3672,-5.25 z m 24,0 c 0,-5.775 0.1652,-8.1375 0.3672,-5.25 0.202,2.8875 0.202,7.6125 0,10.5 -0.202,2.8875 -0.3672,0.525 -0.3672,-5.25 z m 24,0 c 0,-5.775 0.1652,-8.1375 0.3672,-5.25 0.202,2.8875 0.202,7.6125 0,10.5 -0.202,2.8875 -0.3672,0.525 -0.3672,-5.25 z m -48,-24 c 0,-5.775 0.1652,-8.1375 0.3672,-5.25 0.202,2.8875 0.202,7.6125 0,10.5 -0.202,2.8875 -0.3672,0.525 -0.3672,-5.25 z m 24,0 c 0,-5.775 0.1652,-8.1375 0.3672,-5.25 0.202,2.8875 0.202,7.6125 0,10.5 -0.202,2.8875 -0.3672,0.525 -0.3672,-5.25 z m 24,0 c 0,-5.775 0.1652,-8.1375 0.3672,-5.25 0.202,2.8875 0.202,7.6125 0,10.5 -0.202,2.8875 -0.3672,0.525 -0.3672,-5.25 z m 43.63,-6 c 0,-3.33333 0.3333,-4 2,-4 1.9048,0 2,-0.66667 2,-14 v -14 h 10.5 10.5 v 14 c 0,13.33333 0.095,14 2,14 1.6667,0 2,0.66667 2,4 v 4 h -14.5 -14.5 z m 21,-10 v -6 h -6.5 -6.5 v 6 6 h 6.5 6.5 z m 0,-14 v -4 h -6.5 -6.5 v 4 4 h 6.5 6.5 z m 23,24 c 0,-3.33333 0.3333,-4 2,-4 1.9048,0 2,-0.66667 2,-14 v -14 h 10.5 10.5 v 14 c 0,13.33333 0.095,14 2,14 1.6667,0 2,0.66667 2,4 v 4 h -14.5 -14.5 z m 21,-10 v -6 h -6.5 -6.5 v 6 6 h 6.5 6.5 z m 0,-14 v -4 h -6.5 -6.5 v 4 4 h 6.5 6.5 z m -764.54444,22.92809 c -0.37471,-0.60629 -0.26336,-1.52029 0.24746,-2.03111 0.54087,-0.54087 12.80375,-0.81771 29.36287,-0.66287 C 659.55582,332.46312 663.5,332.70807 663.5,334 c 0,1.2923 -3.97139,1.53672 -28.68157,1.76522 -20.08197,0.1857 -28.88585,-0.0653 -29.36287,-0.83713 z m 68.01762,0.0285 c -1.31929,-2.13466 1.23818,-3.05935 7.62839,-2.75817 4.8868,0.23032 6.39843,0.65595 6.39843,1.80157 0,1.15389 -1.53765,1.56778 -6.66396,1.79373 -4.17939,0.18421 -6.92453,-0.1279 -7.36286,-0.83713 z m 27.98367,-0.0264 c -1.58708,-2.56795 2.10043,-2.95646 25.61134,-2.69838 20.9313,0.22976 24.43181,0.48311 24.43181,1.76819 0,1.28559 -3.52773,1.53821 -24.68028,1.76732 -17.19739,0.18627 -24.88724,-0.0675 -25.36287,-0.83713 z M 790,324 c 0,-3.33333 0.33333,-4 2,-4 1.91667,0 2,-0.66667 2,-16 v -16 h 10 10 v 16 c 0,15.33333 0.0833,16 2,16 1.66667,0 2,0.66667 2,4 v 4 h -14 -14 z m 20,-12 v -8 h -6 -6 v 8 8 h 6 6 z m 0,-16 v -4 h -6 -6 v 4 4 h 6 6 z m 24,28 c 0,-3.33333 0.33333,-4 2,-4 1.91667,0 2,-0.66667 2,-16 v -16 h 10 10 v 16 c 0,15.33333 0.0833,16 2,16 1.66667,0 2,0.66667 2,4 v 4 h -14 -14 z m 20,-12 v -8 h -6 -6 v 8 8 h 6 6 z m 0,-16 v -4 h -6 -6 v 4 4 h 6 6 z m 359.37,22 c 0,-5.775 0.1652,-8.1375 0.3672,-5.25 0.202,2.8875 0.202,7.6125 0,10.5 -0.202,2.8875 -0.3672,0.525 -0.3672,-5.25 z m 24,0 c 0,-5.775 0.1652,-8.1375 0.3672,-5.25 0.202,2.8875 0.202,7.6125 0,10.5 -0.202,2.8875 -0.3672,0.525 -0.3672,-5.25 z m 24,0 c 0,-5.775 0.1652,-8.1375 0.3672,-5.25 0.202,2.8875 0.202,7.6125 0,10.5 -0.202,2.8875 -0.3672,0.525 -0.3672,-5.25 z m -48.0206,-22 c 0,-4.675 0.1723,-6.5875 0.3828,-4.25 0.2105,2.3375 0.2105,6.1625 0,8.5 -0.2105,2.3375 -0.3828,0.425 -0.3828,-4.25 z m 24,0 c 0,-4.675 0.1723,-6.5875 0.3828,-4.25 0.2105,2.3375 0.2105,6.1625 0,8.5 -0.2105,2.3375 -0.3828,0.425 -0.3828,-4.25 z m 24,0 c 0,-4.675 0.1723,-6.5875 0.3828,-4.25 0.2105,2.3375 0.2105,6.1625 0,8.5 -0.2105,2.3375 -0.3828,0.425 -0.3828,-4.25 z M 1213.37,274 c 0,-5.775 0.1652,-8.1375 0.3672,-5.25 0.202,2.8875 0.202,7.6125 0,10.5 -0.202,2.8875 -0.3672,0.525 -0.3672,-5.25 z m 24,0 c 0,-5.775 0.1652,-8.1375 0.3672,-5.25 0.202,2.8875 0.202,7.6125 0,10.5 -0.202,2.8875 -0.3672,0.525 -0.3672,-5.25 z m 24,0 c 0,-5.775 0.1652,-8.1375 0.3672,-5.25 0.202,2.8875 0.202,7.6125 0,10.5 -0.202,2.8875 -0.3672,0.525 -0.3672,-5.25 z m -48,-24 c 0,-5.775 0.1652,-8.1375 0.3672,-5.25 0.202,2.8875 0.202,7.6125 0,10.5 -0.202,2.8875 -0.3672,0.525 -0.3672,-5.25 z m 24,0 c 0,-5.775 0.1652,-8.1375 0.3672,-5.25 0.202,2.8875 0.202,7.6125 0,10.5 -0.202,2.8875 -0.3672,0.525 -0.3672,-5.25 z m 24,0 c 0,-5.775 0.1652,-8.1375 0.3672,-5.25 0.202,2.8875 0.202,7.6125 0,10.5 -0.202,2.8875 -0.3672,0.525 -0.3672,-5.25 z m -44.0601,-51.5 c 0.01,-3.3 0.1923,-4.52944 0.4148,-2.73209 0.2226,1.79735 0.218,4.49735 -0.01,6 -0.2281,1.50265 -0.4102,0.0321 -0.4046,-3.26791 z m 12,0 c 0.01,-3.3 0.1923,-4.52944 0.4148,-2.73209 0.2226,1.79735 0.218,4.49735 -0.01,6 -0.2281,1.50265 -0.4102,0.0321 -0.4046,-3.26791 z m 36.137,-26.5 c 0,-17.875 0.1356,-25.1875 0.3013,-16.25 0.1657,8.9375 0.1657,23.5625 0,32.5 -0.1657,8.9375 -0.3013,1.625 -0.3013,-16.25 z M 1191,190.34098 c 0,-0.54579 1.2375,-2.08329 2.75,-3.41667 l 2.75,-2.42431 -2.5,3 c -1.375,1.65 -2.6125,3.1875 -2.75,3.41667 -0.1375,0.22916 -0.25,-0.0299 -0.25,-0.57569 z m 21.6942,-2.87258 c -1.5431,-1.35762 -2.4091,-2.4684 -1.9243,-2.4684 0.4848,0 1.9384,1.125 3.2301,2.5 2.9886,3.18124 2.328,3.16525 -1.3058,-0.0316 z m -12.9442,-3.1918 c 1.7875,-0.22182 4.7125,-0.22182 6.5,0 1.7875,0.22183 0.325,0.40333 -3.25,0.40333 -3.575,0 -5.0375,-0.1815 -3.25,-0.40333 z" , "m 1054,410 v -6 h 112 112 v -8 -8 h -112 -112 v -6 -6 h 10 10 v -10 -10 h -10 c -9.3333,0 -10,-0.13333 -10,-2 0,-1.86667 0.6667,-2 10,-2 h 10 v -10 -10 h -10 c -9.3333,0 -10,-0.13333 -10,-2 0,-1.86667 0.6667,-2 10,-2 h 10 v -10 -10 h -10 c -9.3333,0 -10,-0.13333 -10,-2 0,-1.86667 0.6667,-2 10,-2 h 10 v -8 -8 h -10 c -9.3333,0 -10,-0.13333 -10,-2 0,-1.86667 0.6667,-2 10,-2 h 10 v -10 -10 h -10 c -9.3333,0 -10,-0.13333 -10,-2 0,-1.86667 0.6667,-2 10,-2 h 10 v -10 -10 h -10 -10 v -4 -4 h 112 112 v -8 -8 h -112 -112 v -6 -6 h 44 44 v -32 -32 h 61.5 61.5 v 32 32 h 12 12 l 0.046,4.25 c 0.025,2.3375 0.022,41.6 -0.01,87.25 -0.029,45.65 -0.05,91.4375 -0.046,101.75 L 1289,416 H 1171.5 1054 Z m 44,-44 v -10 h -10 -10 v 10 10 h 10 10 z m 20,0 v -10 h -8 -8 v 10 10 h 8 8 z m 24,0 v -10 h -10 -10 v 10 10 h 10 10 z m 24,0 v -10 h -10 -10 v 10 10 h 10 10 z m 24,0 v -10 h -10 -10 v 10 10 h 10 10 z m 20,0 v -10 h -8 -8 v 10 10 h 8 8 z m 24,0 v -10 h -10.5 -10.5 v 10 10 h 10.5 10.5 z m 24,0 v -10 h -10.5 -10.5 v 10 10 h 10.5 10.5 z m 20,0 v -10 h -8.5 -8.5 v 10 10 h 8.5 8.5 z m -180,-24 v -10 h -10 -10 v 10 10 h 10 10 z m 20,0 v -10 h -8 -8 v 10 10 h 8 8 z m 24,0 v -10 h -10 -10 v 10 10 h 10 10 z m 24,0 v -10 h -10 -10 v 10 10 h 10 10 z m 24,0 v -10 h -10 -10 v 10 10 h 10 10 z m 20,0 v -10 h -8 -8 v 10 10 h 8 8 z m 24,0 v -10 h -10.5 -10.5 v 10 10 h 10.5 10.5 z m 24,0 v -10 h -10.5 -10.5 v 10 10 h 10.5 10.5 z m 20,0 v -10 h -8.5 -8.5 v 10 10 h 8.5 8.5 z m -180,-24 v -10 h -10 -10 v 10 10 h 10 10 z m 20,0 v -10 h -8 -8 v 10 10 h 8 8 z m 24,0 v -10 h -10 -10 v 10 10 h 10 10 z m 24,0 v -10 h -10 -10 v 10 10 h 10 10 z m 24,0 v -10 h -10 -10 v 10 10 h 10 10 z m 20,0 v -10 h -8 -8 v 10 10 h 8 8 z m 24,0 v -10 h -10.5 -10.5 v 10 10 h 10.5 10.5 z m 24,0 v -10 h -10.5 -10.5 v 10 10 h 10.5 10.5 z m 20,0 v -10 h -8.5 -8.5 v 10 10 h 8.5 8.5 z m -180,-22 v -8 h -10 -10 v 8 8 h 10 10 z m 20,0 v -8 h -8 -8 v 8 8 h 8 8 z m 24,0 v -8 h -10 -10 v 8 8 h 10 10 z m 24,0 v -8 h -10 -10 v 8 8 h 10 10 z m 24,0 v -8 h -10 -10 v 8 8 h 10 10 z m 20,0 v -8 h -8 -8 v 8 8 h 8 8 z m 24,0 v -8 h -10.5 -10.5 v 8 8 h 10.5 10.5 z m 24,0 v -8 h -10.5 -10.5 v 8 8 h 10.5 10.5 z m 20,0 v -8 h -8.5 -8.5 v 8 8 h 8.5 8.5 z m -180,-22 v -10 h -10 -10 v 10 10 h 10 10 z m 20,0 v -10 h -8 -8 v 10 10 h 8 8 z m 24,0 v -10 h -10 -10 v 10 10 h 10 10 z m 24,0 v -10 h -10 -10 v 10 10 h 10 10 z m 24,0 v -10 h -10 -10 v 10 10 h 10 10 z m 20,0 v -10 h -8 -8 v 10 10 h 8 8 z m 24,0 v -10 h -10.5 -10.5 v 10 10 h 10.5 10.5 z m 24,0 v -10 h -10.5 -10.5 v 10 10 h 10.5 10.5 z m 20,0 v -10 h -8.5 -8.5 v 10 10 h 8.5 8.5 z m -180,-24 v -10 h -10 -10 v 10 10 h 10 10 z m 20,0 v -10 h -8 -8 v 10 10 h 8 8 z m 24,0 v -10 h -10 -10 v 10 10 h 10 10 z m 24,0 v -10 h -10 -10 v 10 10 h 10 10 z m 24,0 v -10 h -10 -10 v 10 10 h 10 10 z m 20,0 v -10 h -8 -8 v 10 10 h 8 8 z m 24,0 v -10 h -10.5 -10.5 v 10 10 h 10.5 10.5 z m 24,0 v -10 h -10.5 -10.5 v 10 10 h 10.5 10.5 z m 20,0 v -10 h -8.5 -8.5 v 10 10 h 8.5 8.5 z m -99.7896,-53.25 c 0.7939,-12.1573 6.5349,-20.39719 16.2211,-23.28151 18.7342,-5.57861 34.5685,5.2901 34.5685,23.72785 V 204 h 12.5 12.5 v -18 -18 h -50 -50 v 18 18 h 11.8685 11.8685 z M 1218,198.64788 c 0,-3.91528 -0.6121,-6.2604 -2.28,-8.73543 -3.0319,-4.49909 -6.0651,-5.88 -12.9542,-5.89771 -4.9181,-0.0126 -6.1935,0.39559 -8.9607,2.86811 -2.9575,2.64251 -3.26,3.4762 -3.6286,10 L 1189.7743,204 H 1203.8872 1218 Z M 1254,158 v -6 h -50 -50 v 6 6 h 50 50 z m 51,219.5 c 0,-0.825 0.675,-1.5 1.5,-1.5 0.825,0 1.5,0.675 1.5,1.5 0,0.825 -0.675,1.5 -1.5,1.5 -0.825,0 -1.5,-0.675 -1.5,-1.5 z m 24,0 c 0,-0.825 0.675,-1.5 1.5,-1.5 0.825,0 1.5,0.675 1.5,1.5 0,0.825 -0.675,1.5 -1.5,1.5 -0.825,0 -1.5,-0.675 -1.5,-1.5 z m -19,-0.5 c 0,-0.55 0.45,-1 1,-1 0.55,0 1,0.45 1,1 0,0.55 -0.45,1 -1,1 -0.55,0 -1,-0.45 -1,-1 z m -815.72764,-5 c 0,-2.475 0.19502,-3.4875 0.43337,-2.25 0.23836,1.2375 0.23836,3.2625 0,4.5 -0.23835,1.2375 -0.43337,0.225 -0.43337,-2.25 z" , "M 878,228 V 40 h 20 20 V 20 0 h 48 48 v 20 20 h 20 20 v 188 188 h -88 -88 z m 164,0 V 52 h -76 -76 v 176 176 h 76 76 z M 938,26 V 12 h -4 -4 v 14 14 h 4 4 z m 12,0 V 12 h -4 -4 v 14 14 h 4 4 z m 12,0 V 12 h -4 -4 v 14 14 h 4 4 z m 12,0 V 12 h -4 -4 v 14 14 h 4 4 z m 12,0 V 12 h -4 -4 v 14 14 h 4 4 z m 16,0 V 12 h -6 -6 v 14 14 h 6 6 z" , "m 495.02196,370.75 c 0.0359,-8.57055 4.64104,-16.34192 12.09064,-20.40327 3.23526,-1.76378 9.03545,-1.85864 129.1374,-2.11185 L 762,347.96977 V 351.98488 356 H 637.36803 C 514.15656,356 512.69889,356.023 509.4745,358.01576 506.75822,359.69451 503,365.14239 503,367.40113 503,367.73051 561.275,368 632.5,368 H 762 v 4 4 H 628.5 495 l 0.022,-5.25 z M 1213.1579,226 c 0,-1.375 0.227,-1.9375 0.5044,-1.25 0.2774,0.6875 0.2774,1.8125 0,2.5 -0.2774,0.6875 -0.5044,0.125 -0.5044,-1.25 z m 24,0 c 0,-1.375 0.227,-1.9375 0.5044,-1.25 0.2774,0.6875 0.2774,1.8125 0,2.5 -0.2774,0.6875 -0.5044,0.125 -0.5044,-1.25 z m 24,0 c 0,-1.375 0.227,-1.9375 0.5044,-1.25 0.2774,0.6875 0.2774,1.8125 0,2.5 -0.2774,0.6875 -0.5044,0.125 -0.5044,-1.25 z" , "m 902,226 c 0,-161.333333 0.008,-162 2,-162 1.99177,0 2,0.666667 2,162 0,161.33333 -0.008,162 -2,162 -1.99177,0 -2,-0.66667 -2,-162 z m 12,150 v -12 h 12 12 v 12 12 h -12 -12 z m 20,6 c 0,-1.83333 -0.66667,-2 -8,-2 -7.33333,0 -8,0.16667 -8,2 0,1.83333 0.66667,2 8,2 7.33333,0 8,-0.16667 8,-2 z m 0,-10 v -4 h -8 -8 v 4 4 h 8 8 z m 20,4 v -12 h 12 12 v 12 12 h -12 -12 z m 20,6 c 0,-1.83333 -0.66667,-2 -8,-2 -7.33333,0 -8,0.16667 -8,2 0,1.83333 0.66667,2 8,2 7.33333,0 8,-0.16667 8,-2 z m 0,-10 v -4 h -8 -8 v 4 4 h 8 8 z m 20,4 v -12 h 12 12 v 12 12 h -12 -12 z m 20,6 c 0,-1.83333 -0.6667,-2 -8,-2 -7.33333,0 -8,0.16667 -8,2 0,1.83333 0.66667,2 8,2 7.3333,0 8,-0.16667 8,-2 z m 0,-10 v -4 h -8 -8 v 4 4 h 8 8 z m 12,-146 c 0,-161.333333 0.01,-162 2,-162 1.9918,0 2,0.666667 2,162 0,161.33333 -0.01,162 -2,162 -1.9918,0 -2,-0.66667 -2,-162 z M 914,340 v -12 h 12 12 v 12 12 h -12 -12 z m 20,6 c 0,-1.83333 -0.66667,-2 -8,-2 -7.33333,0 -8,0.16667 -8,2 0,1.83333 0.66667,2 8,2 7.33333,0 8,-0.16667 8,-2 z m 0,-10 v -4 h -8 -8 v 4 4 h 8 8 z m 20,4 v -12 h 12 12 v 12 12 h -12 -12 z m 20,6 c 0,-1.83333 -0.66667,-2 -8,-2 -7.33333,0 -8,0.16667 -8,2 0,1.83333 0.66667,2 8,2 7.33333,0 8,-0.16667 8,-2 z m 0,-10 v -4 h -8 -8 v 4 4 h 8 8 z m 20,4 v -12 h 12 12 v 12 12 h -12 -12 z m 20,6 c 0,-1.83333 -0.6667,-2 -8,-2 -7.33333,0 -8,0.16667 -8,2 0,1.83333 0.66667,2 8,2 7.3333,0 8,-0.16667 8,-2 z m 0,-10 v -4 h -8 -8 v 4 4 h 8 8 z M 914,304 v -12 h 12 12 v 12 12 h -12 -12 z m 20,6 c 0,-1.83333 -0.66667,-2 -8,-2 -7.33333,0 -8,0.16667 -8,2 0,1.83333 0.66667,2 8,2 7.33333,0 8,-0.16667 8,-2 z m 0,-10 v -4 h -8 -8 v 4 4 h 8 8 z m 20,4 v -12 h 12 12 v 12 12 h -12 -12 z m 20,6 c 0,-1.83333 -0.66667,-2 -8,-2 -7.33333,0 -8,0.16667 -8,2 0,1.83333 0.66667,2 8,2 7.33333,0 8,-0.16667 8,-2 z m 0,-10 v -4 h -8 -8 v 4 4 h 8 8 z m 20,4 v -12 h 12 12 v 12 12 h -12 -12 z m 20,6 c 0,-1.83333 -0.6667,-2 -8,-2 -7.33333,0 -8,0.16667 -8,2 0,1.83333 0.66667,2 8,2 7.3333,0 8,-0.16667 8,-2 z m 0,-10 v -4 h -8 -8 v 4 4 h 8 8 z M 914,268 v -12 h 12 12 v 12 12 h -12 -12 z m 20,6 c 0,-1.83333 -0.66667,-2 -8,-2 -7.33333,0 -8,0.16667 -8,2 0,1.83333 0.66667,2 8,2 7.33333,0 8,-0.16667 8,-2 z m 0,-10 v -4 h -8 -8 v 4 4 h 8 8 z m 20,4 v -12 h 12 12 v 12 12 h -12 -12 z m 20,6 c 0,-1.83333 -0.66667,-2 -8,-2 -7.33333,0 -8,0.16667 -8,2 0,1.83333 0.66667,2 8,2 7.33333,0 8,-0.16667 8,-2 z m 0,-10 v -4 h -8 -8 v 4 4 h 8 8 z m 20,4 v -12 h 12 12 v 12 12 h -12 -12 z m 20,6 c 0,-1.83333 -0.6667,-2 -8,-2 -7.33333,0 -8,0.16667 -8,2 0,1.83333 0.66667,2 8,2 7.3333,0 8,-0.16667 8,-2 z m 0,-10 v -4 h -8 -8 v 4 4 h 8 8 z M 914,232 v -12 h 12 12 v 12 12 h -12 -12 z m 20,6 c 0,-1.83333 -0.66667,-2 -8,-2 -7.33333,0 -8,0.16667 -8,2 0,1.83333 0.66667,2 8,2 7.33333,0 8,-0.16667 8,-2 z m 0,-10 v -4 h -8 -8 v 4 4 h 8 8 z m 20,4 v -12 h 12 12 v 12 12 h -12 -12 z m 20,6 c 0,-1.83333 -0.66667,-2 -8,-2 -7.33333,0 -8,0.16667 -8,2 0,1.83333 0.66667,2 8,2 7.33333,0 8,-0.16667 8,-2 z m 0,-10 v -4 h -8 -8 v 4 4 h 8 8 z m 20,4 v -12 h 12 12 v 12 12 h -12 -12 z m 20,6 c 0,-1.83333 -0.6667,-2 -8,-2 -7.33333,0 -8,0.16667 -8,2 0,1.83333 0.66667,2 8,2 7.3333,0 8,-0.16667 8,-2 z m 0,-10 v -4 h -8 -8 v 4 4 h 8 8 z m 48,-2 c 0,-1.66667 0.6667,-2 4,-2 3.3333,0 4,0.33333 4,2 0,1.66667 -0.6667,2 -4,2 -3.3333,0 -4,-0.33333 -4,-2 z m 24,0 c 0,-1.66667 0.6667,-2 4,-2 3.3333,0 4,0.33333 4,2 0,1.66667 -0.6667,2 -4,2 -3.3333,0 -4,-0.33333 -4,-2 z m 24,0 c 0,-1.66667 0.6667,-2 4,-2 3.3333,0 4,0.33333 4,2 0,1.66667 -0.6667,2 -4,2 -3.3333,0 -4,-0.33333 -4,-2 z m 24,0 c 0,-1.66667 0.6667,-2 4,-2 3.3333,0 4,0.33333 4,2 0,1.66667 -0.6667,2 -4,2 -3.3333,0 -4,-0.33333 -4,-2 z m 24,0 c 0,-1.66667 0.6667,-2 4,-2 3.3333,0 4,0.33333 4,2 0,1.66667 -0.6667,2 -4,2 -3.3333,0 -4,-0.33333 -4,-2 z m 24,0 c 0,-1.66667 0.6667,-2 4,-2 3.3333,0 4,0.33333 4,2 0,1.66667 -0.6667,2 -4,2 -3.3333,0 -4,-0.33333 -4,-2 z m 24,0 c 0,-1.61905 0.6667,-2 3.5,-2 2.8333,0 3.5,0.38095 3.5,2 0,1.61905 -0.6667,2 -3.5,2 -2.8333,0 -3.5,-0.38095 -3.5,-2 z m 24,0 c 0,-1.61905 0.6667,-2 3.5,-2 2.8333,0 3.5,0.38095 3.5,2 0,1.61905 -0.6667,2 -3.5,2 -2.8333,0 -3.5,-0.38095 -3.5,-2 z m 24,0 c 0,-1.61905 0.6667,-2 3.5,-2 2.8333,0 3.5,0.38095 3.5,2 0,1.61905 -0.6667,2 -3.5,2 -2.8333,0 -3.5,-0.38095 -3.5,-2 z M 914,196 v -12 h 12 12 v 12 12 h -12 -12 z m 20,6 c 0,-1.83333 -0.66667,-2 -8,-2 -7.33333,0 -8,0.16667 -8,2 0,1.83333 0.66667,2 8,2 7.33333,0 8,-0.16667 8,-2 z m 0,-10 v -4 h -8 -8 v 4 4 h 8 8 z m 20,4 v -12 h 12 12 v 12 12 h -12 -12 z m 20,6 c 0,-1.83333 -0.66667,-2 -8,-2 -7.33333,0 -8,0.16667 -8,2 0,1.83333 0.66667,2 8,2 7.33333,0 8,-0.16667 8,-2 z m 0,-10 v -4 h -8 -8 v 4 4 h 8 8 z m 20,4 v -12 h 12 12 v 12 12 h -12 -12 z m 20,6 c 0,-1.83333 -0.6667,-2 -8,-2 -7.33333,0 -8,0.16667 -8,2 0,1.83333 0.66667,2 8,2 7.3333,0 8,-0.16667 8,-2 z m 0,-10 v -4 h -8 -8 v 4 4 h 8 8 z M 914,160 v -12 h 12 12 v 12 12 h -12 -12 z m 20,6 c 0,-1.83333 -0.66667,-2 -8,-2 -7.33333,0 -8,0.16667 -8,2 0,1.83333 0.66667,2 8,2 7.33333,0 8,-0.16667 8,-2 z m 0,-10 v -4 h -8 -8 v 4 4 h 8 8 z m 20,4 v -12 h 12 12 v 12 12 h -12 -12 z m 20,6 c 0,-1.83333 -0.66667,-2 -8,-2 -7.33333,0 -8,0.16667 -8,2 0,1.83333 0.66667,2 8,2 7.33333,0 8,-0.16667 8,-2 z m 0,-10 v -4 h -8 -8 v 4 4 h 8 8 z m 20,4 v -12 h 12 12 v 12 12 h -12 -12 z m 20,6 c 0,-1.83333 -0.6667,-2 -8,-2 -7.33333,0 -8,0.16667 -8,2 0,1.83333 0.66667,2 8,2 7.3333,0 8,-0.16667 8,-2 z m 0,-10 v -4 h -8 -8 v 4 4 h 8 8 z M 914,124 v -12 h 12 12 v 12 12 h -12 -12 z m 20,6 c 0,-1.83333 -0.66667,-2 -8,-2 -7.33333,0 -8,0.16667 -8,2 0,1.83333 0.66667,2 8,2 7.33333,0 8,-0.16667 8,-2 z m 0,-10 v -4 h -8 -8 v 4 4 h 8 8 z m 20,4 v -12 h 12 12 v 12 12 h -12 -12 z m 20,6 c 0,-1.83333 -0.66667,-2 -8,-2 -7.33333,0 -8,0.16667 -8,2 0,1.83333 0.66667,2 8,2 7.33333,0 8,-0.16667 8,-2 z m 0,-10 v -4 h -8 -8 v 4 4 h 8 8 z m 20,4 v -12 h 12 12 v 12 12 h -12 -12 z m 20,6 c 0,-1.83333 -0.6667,-2 -8,-2 -7.33333,0 -8,0.16667 -8,2 0,1.83333 0.66667,2 8,2 7.3333,0 8,-0.16667 8,-2 z m 0,-10 v -4 h -8 -8 v 4 4 h 8 8 z M 914,88 V 76 h 12 12 v 12 12 h -12 -12 z m 20,6 c 0,-1.833333 -0.66667,-2 -8,-2 -7.33333,0 -8,0.166667 -8,2 0,1.833333 0.66667,2 8,2 7.33333,0 8,-0.166667 8,-2 z m 0,-10 v -4 h -8 -8 v 4 4 h 8 8 z m 20,4 V 76 h 12 12 v 12 12 h -12 -12 z m 20,6 c 0,-1.833333 -0.66667,-2 -8,-2 -7.33333,0 -8,0.166667 -8,2 0,1.833333 0.66667,2 8,2 7.33333,0 8,-0.166667 8,-2 z m 0,-10 v -4 h -8 -8 v 4 4 h 8 8 z m 20,4 V 76 h 12 12 v 12 12 h -12 -12 z m 20,6 c 0,-1.833333 -0.6667,-2 -8,-2 -7.33333,0 -8,0.166667 -8,2 0,1.833333 0.66667,2 8,2 7.3333,0 8,-0.166667 8,-2 z m 0,-10 v -4 h -8 -8 v 4 4 h 8 8 z" , }; private static int[] townColors = new int[]{ 0xfff2ebe0, 0xffb3ddeb, 0xff98d2d0, 0xffc0b8b1, 0xff85acbe, 0xffafa68b, 0xff69b3af, 0xff679893, 0xff8c7982, 0xff549691, 0xff847079, 0xffb65649, 0xff52837e, 0xff66525d, 0xff434343, 0xff2b4247, 0xff360000, 0xff000000, }; private static int[] skyColors = new int[]{ 0xff38adc1, 0x88ffffff, }; private static String[] skyPaths = new String[]{ "M0,0L1600,0L1600,1040L0,1040z", "m412,660v-4h76,76v4,4h-76,-76zM1200,654c0,-2 0.7,-2 76,-2 75.3,0 76,0 76,2 0,2 -0.7,2 -76,2 -75.3,0 -76,-0 -76,-2zM904,638c0,-2 0.7,-2 56,-2 55.3,0 56,0 56,2 0,2 -0.7,2 -56,2 -55.3,0 -56,-0 -56,-2zM184,610c0,-2 0.7,-2 72,-2 71.3,0 72,0 72,2 0,2 -0.7,2 -72,2 -71.3,0 -72,-0 -72,-2zM852,578c0,-2 0.7,-2 67.9,-2h67.9l1.2,-7.4c2.1,-13.2 11,-27.4 20,-32.1 2.3,-1.2 2.8,-2.3 3.4,-6.6 2.2,-18.8 21.7,-31.8 39.8,-26.6 3,0.9 6.7,2.6 8.2,3.7 1.5,1.2 2.8,2 2.9,1.8 6.9,-11.6 16.3,-20.9 26.7,-26.3 29.2,-15.4 64.5,-6.4 82.9,21 5.1,7.6 10.1,20.7 10.1,26.5 0,1.6 0.8,3.6 1.8,4.4 14.6,12.2 19,18.9 22,33.3L1208.5,576h195.8c195.1,0 195.8,0 195.8,2 0,2 -0.7,2 -197.9,2h-197.9l-0.6,-6.7c-1.2,-12.7 -9.2,-25.4 -20.5,-32.3 -4.8,-3 -4.8,-3 -5.4,-9.3 -1.6,-16.9 -13.5,-34.7 -29,-43.5 -16.9,-9.6 -38.9,-9.9 -56.2,-0.9 -8,4.2 -20.1,16.4 -24.4,24.5l-3.3,6.3 -3.9,-3.5c-5.3,-4.8 -10.3,-6.7 -17.5,-6.8 -14.6,-0.1 -26.2,10.6 -26.8,24.7 -0.2,4.9 -0.3,5.1 -5.8,8.6 -9.8,6.4 -17.2,19.5 -18.6,33L991.7,580L921.9,580C852.7,580 852,580 852,578ZM0,522c0,-2 0.7,-2 127.3,-2l127.3,-0 1.6,-8.2c2.2,-11.5 6.1,-19.1 13.3,-26.3C279,475.9 289.3,472 305.1,472c7.8,0 8,-0.1 9,-2.8 6.4,-17.3 20.6,-30.7 36.8,-34.7 3.7,-0.9 7.3,-2 8,-2.3 0.7,-0.4 2.6,-4 4.3,-8 7.9,-19.7 22,-34.6 41.4,-43.7 12.9,-6.1 21.5,-7.9 36,-7.8 9.9,0.1 13.6,0.6 21,2.8 21.3,6.3 38.3,19.5 49.5,38.5 4.5,7.5 6.5,12.7 8.9,22.5l1.7,7 5.9,0.7c21.7,2.4 40.5,19.7 46.1,42.3 1.4,5.6 2.1,6.8 5.5,9 6.3,4.2 11.3,12.3 13.3,21.8l0.6,2.8h71.5C735.3,520 736,520 736,522c0,2 -0.7,2 -73.4,2h-73.4l-1,-2.8c-0.6,-1.5 -1.6,-4.6 -2.3,-6.8 -0.7,-2.2 -2.3,-5.7 -3.7,-7.7C579.9,503.2 573.4,498 571.3,498c-0.6,0 -1.5,-2.6 -2.1,-5.8 -4.2,-23.5 -21.6,-40.7 -43.7,-43.2 -7.9,-0.9 -9.5,-1.8 -9.5,-5.6 -0,-4.3 -4.3,-17.3 -8.3,-24.8 -7,-13.5 -22.8,-28.3 -36.4,-34.2 -9.9,-4.3 -16.1,-5.6 -28.4,-6.2 -10.7,-0.5 -13.8,-0.2 -21,1.6 -28.5,7.2 -48.2,26.5 -57.4,56.4 -0.4,1.2 -1.6,1.8 -4.1,1.8 -5,0 -14.8,3.5 -20.9,7.4 -9.9,6.4 -18.3,17.6 -21,28.4l-1.1,4.3 -11.5,0.3c-7.7,0.2 -13.3,0.9 -17,2.1 -16.2,5.3 -26.1,18.4 -28.4,37.8L259.9,524L129.9,524C0.7,524 0,524 0,522ZM1208,396v-4h16,16v4,4h-16,-16zM1280,396v-4h74,74v4,4h-74,-74z", }; private static int[] sunColors = new int[]{ 0xfffef9e8, 0xfff3e59c, }; private static String[] sunPaths = new String[]{ "m109.1,226.62c-0.79,-0.95 -1.06,-6.89 -0.87,-19.14 0.24,-15.65 0.48,-17.89 2.02,-18.86 1.28,-0.8 2.22,-0.8 3.5,0 1.54,0.97 1.78,3.22 2.02,18.86C116.06,225.82 115.66,228 112,228c-0.97,0 -2.27,-0.62 -2.9,-1.38zM56.2,211.8c-0.66,-0.66 -1.2,-1.75 -1.2,-2.42 0,-0.67 3.91,-8 8.68,-16.3C71.62,179.3 72.61,178 75.16,178c5.4,0 4.68,3.34 -3.95,18.26 -4.35,7.52 -8.48,14.36 -9.18,15.2C60.5,213.3 57.85,213.45 56.2,211.8ZM165.97,211.47c-0.7,-0.84 -4.83,-7.68 -9.18,-15.2C148.16,181.34 147.43,178 152.84,178c2.56,0 3.55,1.3 11.48,15.08 4.78,8.3 8.68,15.63 8.68,16.3 0,3.33 -4.81,4.76 -7.03,2.08zM16.2,171.8c-1.65,-1.65 -1.5,-4.3 0.33,-5.83 0.84,-0.7 7.68,-4.83 15.2,-9.18C46.66,148.16 50,147.43 50,152.84c0,2.56 -1.3,3.55 -15.08,11.48C26.62,169.09 19.29,173 18.62,173 17.95,173 16.86,172.46 16.2,171.8ZM192.75,164.26C179.38,156.4 178,155.33 178,152.81c0,-5.44 3.35,-4.67 18.95,4.39 7.89,4.58 14.83,9.1 15.44,10.06 1.38,2.19 -0.25,5.15 -2.99,5.45 -1.04,0.12 -8.54,-3.69 -16.65,-8.46zM105.06,164.91C90.72,162.1 75.91,150.34 69.37,136.56 48.31,92.21 94.21,46.31 138.56,67.37c10.34,4.91 19.16,13.73 24.07,24.07 18.52,38.99 -15.04,81.81 -57.58,73.47zM125.2,156.89c12.02,-2.51 23.55,-11.32 29.53,-22.54 13.91,-26.1 -2.19,-58.49 -31.58,-63.53 -29.3,-5.02 -55.36,21.03 -50.33,50.33 4.23,24.68 27.93,40.85 52.38,35.74zM1.22,118.82C0.55,118.15 0,116.85 0,115.92c0,-3.57 2.26,-3.98 20.52,-3.7 15.65,0.24 17.89,0.48 18.86,2.02 0.8,1.28 0.8,2.22 -0,3.5 -0.97,1.54 -3.22,1.78 -19.02,2.02 -13.09,0.2 -18.25,-0.06 -19.14,-0.95zM189.22,118.82C188.55,118.15 188,116.85 188,115.92c0,-3.57 2.26,-3.98 20.52,-3.7 15.65,0.24 17.89,0.48 18.86,2.02 0.8,1.28 0.8,2.22 -0,3.5 -0.97,1.54 -3.22,1.78 -19.02,2.02 -13.09,0.2 -18.25,-0.06 -19.14,-0.95zM30.61,70.66c-7.64,-4.49 -14.39,-8.95 -14.99,-9.91 -1.28,-2.02 0.14,-5.06 2.59,-5.54 0.82,-0.16 8.31,3.63 16.64,8.43C48.51,71.51 50,72.63 50,75.08c0,2.57 -1.65,4.04 -4.33,3.84 -0.64,-0.05 -7.42,-3.76 -15.06,-8.25zM179.25,78.34C178.56,78.06 178,76.6 178,75.1c0,-2.48 1.44,-3.56 15.16,-11.46 8.34,-4.8 15.83,-8.59 16.64,-8.43 2.44,0.48 3.86,3.52 2.59,5.53 -1.22,1.93 -28.89,18.3 -30.73,18.18 -0.64,-0.04 -1.72,-0.31 -2.41,-0.58zM63.65,34.92C58.83,26.62 55.02,19.13 55.19,18.27c0.49,-2.51 3.51,-3.95 5.55,-2.66 0.96,0.6 5.48,7.55 10.06,15.44C79.85,46.65 80.63,50 75.19,50 72.66,50 71.62,48.63 63.65,34.92ZM149.46,47.69c-0.43,-1.71 1.57,-6.01 7.74,-16.65 4.58,-7.89 9.1,-14.83 10.06,-15.44 2.02,-1.27 5.05,0.15 5.53,2.59 0.16,0.82 -3.63,8.31 -8.43,16.64C156.39,48.7 155.39,50 152.84,50c-2.09,0 -2.95,-0.59 -3.38,-2.31zM109.1,38.62c-0.79,-0.95 -1.06,-6.89 -0.87,-19.14 0.24,-15.65 0.48,-17.89 2.02,-18.86 1.28,-0.8 2.22,-0.8 3.5,0 1.54,0.97 1.78,3.22 2.02,18.86C116.06,37.82 115.66,40 112,40c-0.97,0 -2.27,-0.62 -2.9,-1.38z", "M105.5,156.61C83.66,151.3 69.02,129.01 72.82,106.85 76.91,83 99.57,66.78 123.15,70.82c23.85,4.09 40.07,26.75 36.03,50.33 -4.31,25.12 -29.03,41.45 -53.68,35.46z" }; // private PathsDrawable mDrawableSun; private PathsDrawable mDrawableSky; private PathsDrawable mDrawableTown; private Matrix mMatrix; private float mPercent; private float mRotate; private int mHeaderHeight; private int mSunSize; private boolean isRefreshing; private Animation mAnimation; // public PhoenixHeader(Context context) { super(context); initView(context, null); } public PhoenixHeader(Context context, AttributeSet attrs) { super(context, attrs); initView(context, attrs); } public PhoenixHeader(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); initView(context, attrs); } @RequiresApi(Build.VERSION_CODES.LOLLIPOP) public PhoenixHeader(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); initView(context, attrs); } private void initView(Context context, AttributeSet attrs) { mMatrix = new Matrix(); DensityUtil density = new DensityUtil(); mSunSize = density.dip2px(40); setupAnimation(); setupPathsDrawable(); setMinimumHeight(density.dip2px(100)); TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.PhoenixHeader); int primaryColor = ta.getColor(R.styleable.PhoenixHeader_phPrimaryColor, 0); int accentColor = ta.getColor(R.styleable.PhoenixHeader_phAccentColor, 0); if (primaryColor != 0) { setBackgroundColor(primaryColor); if (accentColor != 0) { mDrawableSky.parserColors(primaryColor, accentColor); } else { mDrawableSky.parserColors(primaryColor); } } ta.recycle(); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { setMeasuredDimension(resolveSize(getSuggestedMinimumWidth(), widthMeasureSpec), resolveSize(getSuggestedMinimumHeight(), heightMeasureSpec)); } private void setupAnimation() { mAnimation = new Animation() { @Override public void applyTransformation(float interpolatedTime, Transformation t) { mRotate = (interpolatedTime); invalidate(); } }; mAnimation.setRepeatCount(Animation.INFINITE); mAnimation.setRepeatMode(Animation.RESTART); mAnimation.setInterpolator(LINEAR_INTERPOLATOR); mAnimation.setDuration(ANIMATION_DURATION); } private void setupPathsDrawable() { int widthPixels = Resources.getSystem().getDisplayMetrics().widthPixels; mDrawableTown = new PathsDrawable(); mDrawableTown.parserPaths(townPaths); mDrawableTown.parserColors(townColors); Rect bounds = mDrawableTown.getBounds(); mDrawableTown.setBounds(0, 0, widthPixels, widthPixels * bounds.height() / bounds.width()); mDrawableSky = new PathsDrawable(); mDrawableSky.parserPaths(skyPaths); mDrawableSky.parserColors(skyColors); bounds = mDrawableSky.getBounds(); mDrawableSky.setBounds(0, 0, widthPixels, widthPixels * bounds.height() / bounds.width()); mDrawableSun = new PathsDrawable(); mDrawableSun.parserPaths(sunPaths); mDrawableSun.parserColors(sunColors); mDrawableSun.setBounds(0, 0, mSunSize, mSunSize); } // // @Override public void onInitialized(RefreshKernel kernel, int height, int extendHeight) { } @Override public boolean isSupportHorizontalDrag() { return false; } @Override public void onHorizontalDrag(float percentX, int offsetX, int offsetMax) { } @Override public void onPullingDown(float percent, int offset, int headHeight, int extendHeight) { mRotate = mPercent = 1f * offset / headHeight; mHeaderHeight = headHeight; } @Override public void onReleasing(float percent, int offset, int headHeight, int extendHeight) { mRotate = mPercent = 1f * offset / headHeight; mHeaderHeight = headHeight; } @Override public void onStartAnimator(RefreshLayout layout, int headHeight, int extendHeight) { isRefreshing = true; startAnimation(mAnimation); } @Override public void onStateChanged(RefreshLayout refreshLayout, RefreshState oldState, RefreshState newState) { } @Override public int onFinish(RefreshLayout layout, boolean success) { isRefreshing = false; clearAnimation(); return 0; } @Override@Deprecated public void setPrimaryColors(int... colors) { if (mDrawableSky != null) { if (colors.length > 1) { setBackgroundColor(colors[0]); mDrawableSky.parserColors(colors); } else if (colors.length > 0) { setBackgroundColor(colors[0]); mDrawableSky.parserColors(colors[0], skyColors[1]); } } } @NonNull @Override public View getView() { return this; } @Override public SpinnerStyle getSpinnerStyle() { return SpinnerStyle.Scale; } // // @Override // public int defineHeight() { // return (int)(Resources.getSystem().getDisplayMetrics().widthPixels * 0.27); // } // // @Override // public int defineExtendHeight() { // return (int) (defineHeight() * 0.3f); // } // @Override public void onDraw(Canvas canvas) { int width = getWidth(); int height = getHeight(); drawSky(canvas, width, height); drawSun(canvas, width, height); drawTown(canvas, width, height); } private void drawSky(Canvas canvas, int width, int height) { Matrix matrix = mMatrix; matrix.reset(); int bWidth = mDrawableSky.width();//mSky.getWidth(); int bHeight = mDrawableSky.height();//mSky.getHeight(); float townScale = 1f * width / bWidth; float offsetx = 0; float offsety = height / 2 - bHeight / 2; // matrix.postScale(townScale, townScale); // matrix.postTranslate(offsetx, offsety); // // canvas.drawBitmap(mSky, matrix, null); final int saveCount = canvas.getSaveCount(); canvas.save(); canvas.translate(offsetx, offsety); matrix.postScale(townScale, townScale); mDrawableSky.draw(canvas); canvas.restoreToCount(saveCount); } private void drawTown(Canvas canvas, int width, int height) { Matrix matrix = mMatrix; matrix.reset(); int bWidth = mDrawableTown.width();//mTown.getWidth(); int bHeight = mDrawableTown.height();//mTown.getHeight(); float townScale = 1f * width / bWidth; float amplification = (0.3f * Math.max(mPercent - 1, 0) + 1); float offsetx = width / 2 - (int) (width * amplification) / 2; float offsety = mHeaderHeight * 0.1f * mPercent; townScale = amplification * townScale; if (offsety + bHeight * townScale < height) { offsety = height - bHeight * townScale; } // matrix.postScale(townScale, townScale, mDrawableTown.width() / 2, mDrawableTown.height() / 2); // matrix.postTranslate(offsetx, offsety); // canvas.drawBitmap(mTown, matrix, null); final int saveCount = canvas.getSaveCount(); canvas.save(); canvas.translate(offsetx, offsety); canvas.scale(townScale, townScale); mDrawableTown.draw(canvas); canvas.restoreToCount(saveCount); } private void drawSun(Canvas canvas, int width, int height) { Matrix matrix = mMatrix; matrix.reset(); float mSunLeftOffset = 0.3f * (float) width; float mSunTopOffset = (mHeaderHeight * 0.1f); float sunRadius = (float) mSunSize / 2.0f; float offsetX = mSunLeftOffset + sunRadius; float offsetY = mSunTopOffset + (mHeaderHeight / 2) * (1.0f - Math.min(mPercent, 1)); // Move the sun up int bWidth = mDrawableSun.width(); float sunScale = 1f * mSunSize / bWidth; if (mPercent > 1) { sunScale = sunScale * (1.0f - 0.5f * (mPercent - 1)); sunRadius = sunRadius * (1.0f - 0.5f * (mPercent - 1)); } matrix.preScale(sunScale, sunScale); matrix.postRotate((isRefreshing ? -360 : 360) * mRotate * (isRefreshing ? 1 : SUN_INITIAL_ROTATE_GROWTH), sunRadius, sunRadius); // canvas.save(); // canvas.translate(offsetX, offsetY); // canvas.drawBitmap(mSun, matrix, null); // canvas.restore(); final int saveCount = canvas.getSaveCount(); canvas.save(); canvas.translate(offsetX, offsetY); canvas.concat(matrix); mDrawableSun.draw(canvas); canvas.restoreToCount(saveCount); } // } ================================================ FILE: refresh-header/src/main/java/com/scwang/smartrefresh/header/StoreHouseHeader.java ================================================ package com.scwang.smartrefresh.header; import android.support.annotation.RequiresApi; import android.content.Context; import android.content.res.Resources; import android.content.res.TypedArray; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Matrix; import android.graphics.PointF; import android.os.Build; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.util.AttributeSet; import android.view.View; import android.view.animation.Transformation; import com.scwang.smartrefresh.header.storehouse.StoreHouseBarItem; import com.scwang.smartrefresh.header.storehouse.StoreHousePath; import com.scwang.smartrefresh.layout.api.RefreshHeader; import com.scwang.smartrefresh.layout.api.RefreshKernel; import com.scwang.smartrefresh.layout.api.RefreshLayout; import com.scwang.smartrefresh.layout.constant.RefreshState; import com.scwang.smartrefresh.layout.constant.SpinnerStyle; import com.scwang.smartrefresh.layout.util.DensityUtil; import java.util.ArrayList; public class StoreHouseHeader extends View implements RefreshHeader { // public ArrayList mItemList = new ArrayList(); private int mLineWidth = -1; private float mScale = 1; private int mDropHeight = -1; private static final float mInternalAnimationFactor = 0.7f; private int mHorizontalRandomness = -1; private float mProgress = 0; private int mDrawZoneWidth = 0; private int mDrawZoneHeight = 0; private int mOffsetX = 0; private int mOffsetY = 0; private static final float mBarDarkAlpha = 0.4f; private static final float mFromAlpha = 1.0f; private static final float mToAlpha = 0.4f; private int mLoadingAniDuration = 1000; private int mLoadingAniSegDuration = 1000; private static final int mLoadingAniItemDuration = 400; private Transformation mTransformation = new Transformation(); private boolean mIsInLoading = false; private AniController mAniController = new AniController(); private int mTextColor = Color.WHITE; private int mBackgroundColor = 0; private Matrix mMatrix = new Matrix(); private RefreshKernel mRefreshKernel; // // public StoreHouseHeader(Context context) { super(context); this.initView(context, null); } public StoreHouseHeader(Context context, AttributeSet attrs) { super(context, attrs); this.initView(context, attrs); } public StoreHouseHeader(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); this.initView(context, attrs); } @RequiresApi(Build.VERSION_CODES.LOLLIPOP) public StoreHouseHeader(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); this.initView(context, attrs); } private void initView(Context context, AttributeSet attrs) { DensityUtil density = new DensityUtil(); mLineWidth = density.dip2px(1); mDropHeight = density.dip2px(40); mHorizontalRandomness = Resources.getSystem().getDisplayMetrics().widthPixels / 2; // setBackgroundColor(0xff333333); mBackgroundColor = 0xff333333; setTextColor(0xffcccccc); TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.StoreHouseHeader); mLineWidth = ta.getDimensionPixelOffset(R.styleable.StoreHouseHeader_shhLineWidth, mLineWidth); mDropHeight = ta.getDimensionPixelOffset(R.styleable.StoreHouseHeader_shhDropHeight, mDropHeight); if (ta.hasValue(R.styleable.StoreHouseHeader_shhText)) { initWithString(ta.getString(R.styleable.StoreHouseHeader_shhText)); } else { initWithString("StoreHouse"); } ta.recycle(); setMinimumHeight(mDrawZoneHeight + DensityUtil.dp2px(40)); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { // int height = getTopOffset() + mDrawZoneHeight + getBottomOffset(); // heightMeasureSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY); setMeasuredDimension(resolveSize(getSuggestedMinimumWidth(), widthMeasureSpec), resolveSize(getSuggestedMinimumHeight(), heightMeasureSpec)); mOffsetX = (getMeasuredWidth() - mDrawZoneWidth) / 2; mOffsetY = (getMeasuredHeight() - mDrawZoneHeight) / 2;//getTopOffset(); mDropHeight = getMeasuredHeight() / 2;//getTopOffset(); } @Override public void onDraw(Canvas canvas) { super.onDraw(canvas); float progress = mProgress; int c1 = canvas.save(); int len = mItemList.size(); if (isInEditMode()) { progress = 1; } for (int i = 0; i < len; i++) { canvas.save(); StoreHouseBarItem storeHouseBarItem = mItemList.get(i); float offsetX = mOffsetX + storeHouseBarItem.midPoint.x; float offsetY = mOffsetY + storeHouseBarItem.midPoint.y; if (mIsInLoading) { storeHouseBarItem.getTransformation(getDrawingTime(), mTransformation); canvas.translate(offsetX, offsetY); } else { if (progress == 0) { storeHouseBarItem.resetPosition(mHorizontalRandomness); continue; } float startPadding = (1 - mInternalAnimationFactor) * i / len; float endPadding = 1 - mInternalAnimationFactor - startPadding; // done if (progress == 1 || progress >= 1 - endPadding) { canvas.translate(offsetX, offsetY); storeHouseBarItem.setAlpha(mBarDarkAlpha); } else { float realProgress; if (progress <= startPadding) { realProgress = 0; } else { realProgress = Math.min(1, (progress - startPadding) / mInternalAnimationFactor); } offsetX += storeHouseBarItem.translationX * (1 - realProgress); offsetY += -mDropHeight * (1 - realProgress); mMatrix.reset(); mMatrix.postRotate(360 * realProgress); mMatrix.postScale(realProgress, realProgress); mMatrix.postTranslate(offsetX, offsetY); storeHouseBarItem.setAlpha(mBarDarkAlpha * realProgress); canvas.concat(mMatrix); } } storeHouseBarItem.draw(canvas); canvas.restore(); } if (mIsInLoading) { invalidate(); } canvas.restoreToCount(c1); } @Override protected void onDetachedFromWindow() { super.onDetachedFromWindow(); mRefreshKernel = null; } // // public int getLoadingAniDuration() { return mLoadingAniDuration; } public StoreHouseHeader setLoadingAniDuration(int duration) { mLoadingAniDuration = duration; mLoadingAniSegDuration = duration; return this; } public StoreHouseHeader setLineWidth(int width) { mLineWidth = width; for (int i = 0; i < mItemList.size(); i++) { mItemList.get(i).setLineWidth(width); } return this; } public StoreHouseHeader setTextColor(int color) { mTextColor = color; for (int i = 0; i < mItemList.size(); i++) { mItemList.get(i).setColor(color); } return this; } public StoreHouseHeader setDropHeight(int height) { mDropHeight = height; return this; } public StoreHouseHeader initWithString(String str) { initWithString(str, 25); return this; } public StoreHouseHeader initWithString(String str, int fontSize) { ArrayList pointList = StoreHousePath.getPath(str, fontSize * 0.01f, 14); initWithPointList(pointList); return this; } public StoreHouseHeader initWithStringArray(int id) { String[] points = getResources().getStringArray(id); ArrayList pointList = new ArrayList(); for (String point : points) { String[] x = point.split(","); float[] f = new float[4]; for (int j = 0; j < 4; j++) { f[j] = Float.parseFloat(x[j]); } pointList.add(f); } initWithPointList(pointList); return this; } public float getScale() { return mScale; } public StoreHouseHeader setScale(float scale) { mScale = scale; return this; } public StoreHouseHeader initWithPointList(ArrayList pointList) { float drawWidth = 0; float drawHeight = 0; boolean shouldLayout = mItemList.size() > 0; mItemList.clear(); DensityUtil density = new DensityUtil(); for (int i = 0; i < pointList.size(); i++) { float[] line = pointList.get(i); PointF startPoint = new PointF(density.dip2px(line[0]) * mScale, density.dip2px(line[1]) * mScale); PointF endPoint = new PointF(density.dip2px(line[2]) * mScale, density.dip2px(line[3]) * mScale); drawWidth = Math.max(drawWidth, startPoint.x); drawWidth = Math.max(drawWidth, endPoint.x); drawHeight = Math.max(drawHeight, startPoint.y); drawHeight = Math.max(drawHeight, endPoint.y); StoreHouseBarItem item = new StoreHouseBarItem(i, startPoint, endPoint, mTextColor, mLineWidth); item.resetPosition(mHorizontalRandomness); mItemList.add(item); } mDrawZoneWidth = (int) Math.ceil(drawWidth); mDrawZoneHeight = (int) Math.ceil(drawHeight); if (shouldLayout) { requestLayout(); } return this; } // // private Runnable restoreRunable; private void restoreRefreshLayoutBackground() { if (restoreRunable != null) { restoreRunable.run(); restoreRunable = null; } } private void replaceRefreshLayoutBackground(RefreshLayout refreshLayout) { // if (restoreRunable == null) { // restoreRunable = new Runnable() { // Drawable drawable = refreshLayout.getLayout().getBackground(); // @Override // public void run() { // refreshLayout.getLayout().setBackgroundDrawable(drawable); // } // }; // refreshLayout.getLayout().setBackgroundDrawable(getBackground()); // } } // // private void setProgress(float progress) { mProgress = progress; } private int getTopOffset() { return getPaddingTop() + DensityUtil.dp2px(10); } private int getBottomOffset() { return getPaddingBottom() + DensityUtil.dp2px(10); } private void beginLoading() { mIsInLoading = true; mAniController.start(); invalidate(); } private void loadFinish() { mIsInLoading = false; mAniController.stop(); } // // @Override public void onInitialized(RefreshKernel kernel, int height, int extendHeight) { if (mBackgroundColor != 0) { kernel.requestDrawBackgoundForHeader(mBackgroundColor); } mRefreshKernel = kernel; } @Override public boolean isSupportHorizontalDrag() { return false; } @Override public void onHorizontalDrag(float percentX, int offsetX, int offsetMax) { } @Override public void onPullingDown(float percent, int offset, int headHeight, int extendHeight) { setProgress(percent * .8f); invalidate(); } @Override public void onReleasing(float percent, int offset, int headHeight, int extendHeight) { setProgress(percent * .8f); invalidate(); } @Override public void onStartAnimator(RefreshLayout layout, int headHeight, int extendHeight) { beginLoading(); } @Override public void onStateChanged(RefreshLayout refreshLayout, RefreshState oldState, RefreshState newState) { if (newState == RefreshState.ReleaseToRefresh) { replaceRefreshLayoutBackground(refreshLayout); } else if (newState == RefreshState.None) { restoreRefreshLayoutBackground(); } } @Override public int onFinish(RefreshLayout layout, boolean success) { loadFinish(); for (int i = 0; i < mItemList.size(); i++) { mItemList.get(i).resetPosition(mHorizontalRandomness); } return 0; } @Override@Deprecated public void setPrimaryColors(int... colors) { if (colors.length > 0) { mBackgroundColor = colors[0]; if (mRefreshKernel != null) { mRefreshKernel.requestDrawBackgoundForHeader(colors[0]); } if (colors.length > 1) { setTextColor(colors[1]); } } } @NonNull @Override public View getView() { return this; } @Override public SpinnerStyle getSpinnerStyle() { return SpinnerStyle.Translate; } // private class AniController implements Runnable { private int mTick = 0; private int mCountPerSeg = 0; private int mSegCount = 0; private int mInterval = 0; private boolean mRunning = true; private void start() { mRunning = true; mTick = 0; mInterval = mLoadingAniDuration / mItemList.size(); mCountPerSeg = mLoadingAniSegDuration / mInterval; mSegCount = mItemList.size() / mCountPerSeg + 1; run(); } @Override public void run() { int pos = mTick % mCountPerSeg; for (int i = 0; i < mSegCount; i++) { int index = i * mCountPerSeg + pos; if (index > mTick) { continue; } index = index % mItemList.size(); StoreHouseBarItem item = mItemList.get(index); item.setFillAfter(false); item.setFillEnabled(true); item.setFillBefore(false); item.setDuration(mLoadingAniItemDuration); item.start(mFromAlpha, mToAlpha); } mTick++; if (mRunning && mRefreshKernel != null) { mRefreshKernel.getRefreshLayout().getLayout().postDelayed(this, mInterval); } } private void stop() { mRunning = false; removeCallbacks(this); } } } ================================================ FILE: refresh-header/src/main/java/com/scwang/smartrefresh/header/TaurusHeader.java ================================================ package com.scwang.smartrefresh.header; import android.support.annotation.RequiresApi; import android.content.Context; import android.content.res.TypedArray; import android.graphics.Canvas; import android.graphics.Matrix; import android.graphics.Paint; import android.os.Build; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.util.AttributeSet; import android.view.View; import android.view.animation.AccelerateDecelerateInterpolator; import android.view.animation.Animation; import android.view.animation.Interpolator; import android.view.animation.Transformation; import com.scwang.smartrefresh.layout.api.RefreshHeader; import com.scwang.smartrefresh.layout.api.RefreshKernel; import com.scwang.smartrefresh.layout.api.RefreshLayout; import com.scwang.smartrefresh.layout.constant.RefreshState; import com.scwang.smartrefresh.layout.constant.SpinnerStyle; import com.scwang.smartrefresh.layout.internal.pathview.PathsDrawable; import com.scwang.smartrefresh.layout.util.DensityUtil; import java.util.HashMap; import java.util.Map; import java.util.Random; /** * Taurus * Created by SCWANG on 2017/5/31. */ public class TaurusHeader extends View implements RefreshHeader/*, SizeDefinition*/ { // private static final float SCALE_START_PERCENT = 0.5f; private static final int ANIMATION_DURATION = 1000; private static final float SIDE_CLOUDS_INITIAL_SCALE = 0.6f;//1.05f; private static final float SIDE_CLOUDS_FINAL_SCALE = 1f;//1.55f; private static final float CENTER_CLOUDS_INITIAL_SCALE = 0.8f;//0.8f; private static final float CENTER_CLOUDS_FINAL_SCALE = 1f;//1.30f; private static final Interpolator ACCELERATE_DECELERATE_INTERPOLATOR = new AccelerateDecelerateInterpolator(); // Multiply with this animation interpolator time private static final int LOADING_ANIMATION_COEFFICIENT = 80; private static final int SLOW_DOWN_ANIMATION_COEFFICIENT = 6; // Amount of lines when is going lading animation private static final int WIND_SET_AMOUNT = 10; private static final int Y_SIDE_CLOUDS_SLOW_DOWN_COF = 4; private static final int X_SIDE_CLOUDS_SLOW_DOWN_COF = 2; private static final int MIN_WIND_LINE_WIDTH = 50; private static final int MAX_WIND_LINE_WIDTH = 300; private static final int MIN_WIND_X_OFFSET = 1000; private static final int MAX_WIND_X_OFFSET = 2000; private static final int RANDOM_Y_COEFFICIENT = 5; private PathsDrawable mAirplane; private PathsDrawable mCloudCenter; private PathsDrawable mCloudLeft; private PathsDrawable mCloudRight; private Matrix mMatrix; private float mPercent; private int mHeaderHeight; private Animation mAnimation; private boolean isRefreshing = false; private float mLoadingAnimationTime; private float mLastAnimationTime; private Random mRandom; private boolean mEndOfRefreshing; //KEY: Y position, Value: X offset of wind private Map mWinds; private Paint mWindPaint; private float mWindLineWidth; private boolean mNewWindSet; private boolean mInverseDirection; private enum AnimationPart { FIRST, SECOND, THIRD, FOURTH } // // public TaurusHeader(Context context) { super(context); initView(context, null); } public TaurusHeader(Context context, AttributeSet attrs) { super(context, attrs); initView(context, attrs); } public TaurusHeader(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); initView(context, attrs); } @RequiresApi(Build.VERSION_CODES.LOLLIPOP) public TaurusHeader(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); initView(context, attrs); } private void initView(Context context, AttributeSet attrs) { mMatrix = new Matrix(); mWinds = new HashMap<>(); mRandom = new Random(); mWindPaint = new Paint(); mWindPaint.setColor(0xffffffff); mWindPaint.setStrokeWidth(DensityUtil.dp2px(3)); mWindPaint.setAlpha(50); setupAnimations(); setupPathDrawable(); TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.TaurusHeader); int primaryColor = ta.getColor(R.styleable.TaurusHeader_thPrimaryColor, 0); if (primaryColor != 0) { setBackgroundColor(primaryColor); } else { setBackgroundColor(0xff11bbff); } ta.recycle(); } // // @Override public void onInitialized(RefreshKernel kernel, int height, int extendHeight) { } @Override public boolean isSupportHorizontalDrag() { return false; } @Override public void onHorizontalDrag(float percentX, int offsetX, int offsetMax) { } @Override public void onPullingDown(float percent, int offset, int headHeight, int extendHeight) { mEndOfRefreshing = false; mPercent = 1f * offset / headHeight; mHeaderHeight = headHeight; } @Override public void onReleasing(float percent, int offset, int headHeight, int extendHeight) { mPercent = 1f * offset / headHeight; mHeaderHeight = headHeight; } @Override public void onStartAnimator(RefreshLayout layout, int headHeight, int extendHeight) { isRefreshing = true; startAnimation(mAnimation); } @Override public void onStateChanged(RefreshLayout refreshLayout, RefreshState oldState, RefreshState newState) { } @Override public int onFinish(RefreshLayout layout, boolean success) { isRefreshing = false; mEndOfRefreshing = true; clearAnimation(); return 0; } @Override@Deprecated public void setPrimaryColors(int... colors) { setBackgroundColor(colors[0]); } @NonNull @Override public View getView() { return this; } @Override public SpinnerStyle getSpinnerStyle() { return SpinnerStyle.Scale; } // // @Override public void onDraw(Canvas canvas) { int width = getWidth(); int height = getHeight(); if (isRefreshing) { // Set up new set of wind while (mWinds.size() < WIND_SET_AMOUNT) { float y = (float) (mHeaderHeight / (Math.random() * RANDOM_Y_COEFFICIENT)); float x = random(MIN_WIND_X_OFFSET, MAX_WIND_X_OFFSET); // Magic with checking interval between winds if (mWinds.size() > 1) { y = 0; while (y == 0) { float tmp = (float) (mHeaderHeight / (Math.random() * RANDOM_Y_COEFFICIENT)); for (Map.Entry wind : mWinds.entrySet()) { // We want that interval will be greater than fifth part of draggable distance if (Math.abs(wind.getKey() - tmp) > mHeaderHeight / RANDOM_Y_COEFFICIENT) { y = tmp; } else { y = 0; break; } } } } mWinds.put(y, x); drawWind(canvas, y, x, width); } // Draw current set of wind if (mWinds.size() >= WIND_SET_AMOUNT) { for (Map.Entry wind : mWinds.entrySet()) { drawWind(canvas, wind.getKey(), wind.getValue(), width); } } // We should to create new set of winds if (mInverseDirection && mNewWindSet) { mWinds.clear(); mNewWindSet = false; mWindLineWidth = random(MIN_WIND_LINE_WIDTH, MAX_WIND_LINE_WIDTH); } // needed for checking direction mLastAnimationTime = mLoadingAnimationTime; } drawAirplane(canvas, width, height); drawSideClouds(canvas, width, height); drawCenterClouds(canvas, width, height); } /** * Draw wind on loading animation * * @param canvas - area where we will draw * @param y - y position fot one of lines * @param xOffset - x offset for on of lines */ private void drawWind(Canvas canvas, float y, float xOffset, int width) { /* We should multiply current animation time with this coefficient for taking all screen width in time Removing slowing of animation with dividing on {@LINK #SLOW_DOWN_ANIMATION_COEFFICIENT} And we should don't forget about distance that should "fly" line that depend on screen of device and x offset */ float cof = (width + xOffset) / (LOADING_ANIMATION_COEFFICIENT / SLOW_DOWN_ANIMATION_COEFFICIENT); float time = mLoadingAnimationTime; // HORRIBLE HACK FOR REVERS ANIMATION THAT SHOULD WORK LIKE RESTART ANIMATION if (mLastAnimationTime - mLoadingAnimationTime > 0) { mInverseDirection = true; // take time from 0 to end of animation time time = (LOADING_ANIMATION_COEFFICIENT / SLOW_DOWN_ANIMATION_COEFFICIENT) - mLoadingAnimationTime; } else { mNewWindSet = true; mInverseDirection = false; } // Taking current x position of drawing wind // For fully disappearing of line we should subtract wind line width float x = (width - (time * cof)) + xOffset - mWindLineWidth; float xEnd = x + mWindLineWidth; canvas.drawLine(x, y, xEnd, y, mWindPaint); } private void drawSideClouds(Canvas canvas, int width, int height) { Matrix matrix = mMatrix; matrix.reset(); mCloudLeft = mCloudCenter; mCloudRight = mCloudCenter; // Drag percent will newer get more then 1 here float dragPercent = Math.min(1f, Math.abs(mPercent)); if (isInEditMode()) { dragPercent = 1; mHeaderHeight = height; } float scale; float scalePercentDelta = dragPercent - SCALE_START_PERCENT; if (scalePercentDelta > 0) { float scalePercent = scalePercentDelta / (1.0f - SCALE_START_PERCENT); scale = SIDE_CLOUDS_INITIAL_SCALE + (SIDE_CLOUDS_FINAL_SCALE - SIDE_CLOUDS_INITIAL_SCALE) * scalePercent; } else { scale = SIDE_CLOUDS_INITIAL_SCALE; } // Current y position of clouds float dragYOffset = mHeaderHeight * (1.0f - dragPercent); // Position where clouds fully visible on screen and we should drag them with content of listView // int cloudsVisiblePosition = mHeaderHeight / 2 - mCloudCenter.height() / 2; // boolean needMoveCloudsWithContent = false; // if (dragYOffset < cloudsVisiblePosition) { // needMoveCloudsWithContent = true; // } float offsetLeftX = 0 - mCloudLeft.width() / 2; float offsetLeftY = (//needMoveCloudsWithContent //? mHeaderHeight * dragPercent - mCloudLeft.height() : dragYOffset); float offsetRightX = width - mCloudRight.width() / 2; float offsetRightY = (//needMoveCloudsWithContent //? mHeaderHeight * dragPercent - mCloudRight.height() : dragYOffset); // Magic with animation on loading process if (isRefreshing) { if (checkCurrentAnimationPart(AnimationPart.FIRST)) { offsetLeftX -= 2*getAnimationPartValue(AnimationPart.FIRST) / Y_SIDE_CLOUDS_SLOW_DOWN_COF; offsetRightX += getAnimationPartValue(AnimationPart.FIRST) / X_SIDE_CLOUDS_SLOW_DOWN_COF; } else if (checkCurrentAnimationPart(AnimationPart.SECOND)) { offsetLeftX -= 2*getAnimationPartValue(AnimationPart.SECOND) / Y_SIDE_CLOUDS_SLOW_DOWN_COF; offsetRightX += getAnimationPartValue(AnimationPart.SECOND) / X_SIDE_CLOUDS_SLOW_DOWN_COF; } else if (checkCurrentAnimationPart(AnimationPart.THIRD)) { offsetLeftX -= getAnimationPartValue(AnimationPart.THIRD) / Y_SIDE_CLOUDS_SLOW_DOWN_COF; offsetRightX += 2*getAnimationPartValue(AnimationPart.THIRD) / X_SIDE_CLOUDS_SLOW_DOWN_COF; } else if (checkCurrentAnimationPart(AnimationPart.FOURTH)) { offsetLeftX -= getAnimationPartValue(AnimationPart.FOURTH) / X_SIDE_CLOUDS_SLOW_DOWN_COF; offsetRightX += 2*getAnimationPartValue(AnimationPart.FOURTH) / Y_SIDE_CLOUDS_SLOW_DOWN_COF; } } if (offsetLeftY + scale * mCloudLeft.height() < height + 2) { offsetLeftY = height + 2 - scale * mCloudLeft.height(); } if (offsetRightY + scale * mCloudRight.height() < height + 2) { offsetRightY = height + 2 - scale * mCloudRight.height(); } final int saveCount = canvas.getSaveCount(); canvas.save(); canvas.translate(offsetLeftX, offsetLeftY); matrix.postScale(scale, scale, mCloudLeft.width() * 3 / 4, mCloudLeft.height()); canvas.concat(matrix); mCloudLeft.setAlpha(100); mCloudLeft.draw(canvas); mCloudLeft.setAlpha(255); canvas.restoreToCount(saveCount); canvas.save(); canvas.translate(offsetRightX, offsetRightY); matrix.postScale(scale, scale, 0, mCloudRight.height()); canvas.concat(matrix); mCloudRight.setAlpha(100); mCloudRight.draw(canvas); mCloudRight.setAlpha(255); canvas.restoreToCount(saveCount); } private void drawCenterClouds(Canvas canvas, int width, int height) { Matrix matrix = mMatrix; matrix.reset(); float dragPercent = Math.min(1f, Math.abs(mPercent)); if (isInEditMode()) { dragPercent = 1; mHeaderHeight = height; } float scale; float overdragPercent = 0; boolean overdrag = false; if (mPercent > 1.0f) { overdrag = true; // Here we want know about how mach percent of over drag we done overdragPercent = Math.abs(1.0f - mPercent); } float scalePercentDelta = dragPercent - SCALE_START_PERCENT; if (scalePercentDelta > 0) { float scalePercent = scalePercentDelta / (1.0f - SCALE_START_PERCENT); scale = CENTER_CLOUDS_INITIAL_SCALE + (CENTER_CLOUDS_FINAL_SCALE - CENTER_CLOUDS_INITIAL_SCALE) * scalePercent; } else { scale = CENTER_CLOUDS_INITIAL_SCALE; } float parallaxPercent = 0; boolean parallax = false; // Current y position of clouds float dragYOffset = mHeaderHeight * dragPercent; // Position when should start parallax scrolling int startParallaxHeight = mHeaderHeight - mCloudCenter.height()/2; if (dragYOffset > startParallaxHeight) { parallax = true; parallaxPercent = dragYOffset - startParallaxHeight; } float offsetX = (width / 2) - mCloudCenter.width() / 2; float offsetY = dragYOffset - (parallax ? mCloudCenter.height()/2 + parallaxPercent : mCloudCenter.height()/2); float sx = overdrag ? scale + overdragPercent / 4 : scale; float sy = overdrag ? scale + overdragPercent / 2 : scale; if (isRefreshing && !overdrag) { if (checkCurrentAnimationPart(AnimationPart.FIRST)) { sx = scale - (getAnimationPartValue(AnimationPart.FIRST) / LOADING_ANIMATION_COEFFICIENT) / 8; } else if (checkCurrentAnimationPart(AnimationPart.SECOND)) { sx = scale - (getAnimationPartValue(AnimationPart.SECOND) / LOADING_ANIMATION_COEFFICIENT) / 8; } else if (checkCurrentAnimationPart(AnimationPart.THIRD)) { sx = scale + (getAnimationPartValue(AnimationPart.THIRD) / LOADING_ANIMATION_COEFFICIENT) / 6; } else if (checkCurrentAnimationPart(AnimationPart.FOURTH)) { sx = scale + (getAnimationPartValue(AnimationPart.FOURTH) / LOADING_ANIMATION_COEFFICIENT) / 6; } sy = sx; } matrix.postScale(sx, sy, mCloudCenter.width() / 2, 0); if (offsetY + sy * mCloudCenter.height() < height + 2) { offsetY = height + 2 - sy * mCloudCenter.height(); } final int saveCount = canvas.getSaveCount(); canvas.save(); canvas.translate(offsetX, offsetY); canvas.concat(matrix); mCloudCenter.draw(canvas); canvas.restoreToCount(saveCount); } private void drawAirplane(Canvas canvas, int width, int height) { Matrix matrix = mMatrix; matrix.reset(); float dragPercent = mPercent; float rotateAngle = 0; if (isInEditMode()) { dragPercent = 1; mHeaderHeight = height; } // Check overdrag if (dragPercent > 1.0f /*&& !mEndOfRefreshing*/) { rotateAngle = (dragPercent % 1) * 20; dragPercent = 1.0f; } float offsetX = ((width * dragPercent) / 2) - mAirplane.width() / 2; float offsetY = mHeaderHeight * (1 - dragPercent/2) - mAirplane.height() / 2; if (mEndOfRefreshing) { offsetX = width/2 + width * (1-dragPercent) / 2 - mAirplane.width() / 2; offsetY = (dragPercent) * (mHeaderHeight / 2 + mAirplane.height() * 3 / 2) - 2 * mAirplane.height(); } if (isRefreshing) { if (checkCurrentAnimationPart(AnimationPart.FIRST)) { offsetY -= getAnimationPartValue(AnimationPart.FIRST); } else if (checkCurrentAnimationPart(AnimationPart.SECOND)) { offsetY -= getAnimationPartValue(AnimationPart.SECOND); } else if (checkCurrentAnimationPart(AnimationPart.THIRD)) { offsetY += getAnimationPartValue(AnimationPart.THIRD); } else if (checkCurrentAnimationPart(AnimationPart.FOURTH)) { offsetY += getAnimationPartValue(AnimationPart.FOURTH); } } if (rotateAngle > 0) { matrix.postRotate(rotateAngle, mAirplane.width() / 2, mAirplane.height() / 2); } final int saveCount = canvas.getSaveCount(); canvas.save(); canvas.translate(offsetX, offsetY); canvas.concat(matrix); mAirplane.draw(canvas); canvas.restoreToCount(saveCount); } // // @Override // public int defineHeight() { // return (int)(Resources.getSystem().getDisplayMetrics().widthPixels * 0.3); // } // // @Override // public int defineExtendHeight() { // return (int) (defineHeight() * 0.3f); // } // private void setupPathDrawable() { DensityUtil density = new DensityUtil(); mAirplane = new PathsDrawable(); //mAirplane.parserPaths("M60.68,16.15l-0.13,-0.11c-1.09,-0.76 -2.63,-1.16 -4.47,-1.16c-2.92,0 -5.95,0.99 -7.32,1.92l-10.76,7.35l-20.97,4.45c-0.18,0.04 -0.35,0.11 -0.51,0.22c-0.41,0.28 -0.64,0.76 -0.62,1.25c0.04,0.71 0.58,1.27 1.28,1.34l8.87,0.89l-8.65,5.9c-2.57,-1.18 -5.02,-2.33 -7.27,-3.4c-3.48,-1.67 -5.76,-1.96 -6.83,-0.89c-1.11,1.11 -0.39,3.02 0.01,3.6l8.33,10.8c0.28,0.41 0.6,0.64 0.99,0.71c0.64,0.11 1.2,-0.27 1.78,-0.68l2.11,-1.45l11.72,-5.69l-1.71,6.12c-0.19,0.68 0.14,1.38 0.78,1.68c0.18,0.08 0.39,0.13 0.59,0.13c0.29,0 0.57,-0.09 0.81,-0.25c0.16,-0.1 0.28,-0.23 0.38,-0.39l6.7,-10.19l4.1,-4.8L58.08,21.08c0.28,-0.19 0.55,-0.36 0.82,-0.54c0.63,-0.4 1.22,-0.78 1.65,-1.21C61.47,18.41 61.52,17.39 60.68,16.15z"); mAirplane.parserPaths("m23.01,81.48c-0.21,-0.3 -0.38,-0.83 -0.38,-1.19 0,-0.55 0.24,-0.78 1.5,-1.48 1.78,-0.97 2.62,-1.94 2.24,-2.57 -0.57,-0.93 -1.97,-1.24 -11.64,-2.59 -5.35,-0.74 -10.21,-1.44 -10.82,-1.54l-1.09,-0.18 1.19,-0.91c0.99,-0.76 1.38,-0.91 2.35,-0.91 0.64,0 6.39,0.33 12.79,0.74 6.39,0.41 12.09,0.71 12.65,0.67l1.03,-0.07 -1.24,-2.19C30.18,66.77 15.91,42 15.13,40.68l-0.51,-0.87 4.19,-1.26c2.3,-0.69 4.27,-1.26 4.37,-1.26 0.1,0 5.95,3.85 13,8.55 14.69,9.81 17.1,11.31 19.7,12.31 4.63,1.78 6.45,1.69 12.94,-0.64 13.18,-4.73 25.22,-9.13 25.75,-9.4 0.69,-0.36 3.6,1.33 -24.38,-14.22L50.73,23.07 46.74,16.42 42.75,9.77 43.63,8.89c0.83,-0.83 0.91,-0.86 1.46,-0.52 0.32,0.2 3.72,3.09 7.55,6.44 3.83,3.34 7.21,6.16 7.5,6.27 0.29,0.11 13.6,2.82 29.58,6.03 15.98,3.21 31.86,6.4 35.3,7.1l6.26,1.26 3.22,-1.13c41.63,-14.63 67.88,-23.23 85.38,-28 14.83,-4.04 23.75,-4.75 32.07,-2.57 7.04,1.84 9.87,4.88 7.71,8.27 -1.6,2.5 -4.6,4.63 -10.61,7.54 -5.94,2.88 -10.22,4.46 -25.4,9.41 -8.15,2.66 -16.66,5.72 -39.01,14.02 -66.79,24.82 -88.49,31.25 -121.66,36.07 -14.56,2.11 -24.17,2.95 -34.08,2.95 -5.43,0 -5.52,-0.01 -5.89,-0.54z"); mAirplane.setBounds(0, 0, density.dip2px(65), density.dip2px(20)); mAirplane.parserColors(0xffffffff); mCloudCenter = new PathsDrawable(); mCloudCenter.parserPaths( "M551.81,1.01A65.42,65.42 0,0 0,504.38 21.5A50.65,50.65 0,0 0,492.4 20A50.65,50.65 0,0 0,441.75 70.65A50.65,50.65 0,0 0,492.4 121.3A50.65,50.65 0,0 0,511.22 117.64A65.42,65.42 0,0 0,517.45 122L586.25,122A65.42,65.42 0,0 0,599.79 110.78A59.79,59.79 0,0 0,607.81 122L696.34,122A59.79,59.79 0,0 0,711.87 81.9A59.79,59.79 0,0 0,652.07 22.11A59.79,59.79 0,0 0,610.93 38.57A65.42,65.42 0,0 0,551.81 1.01zM246.2,1.71A54.87,54.87 0,0 0,195.14 36.64A46.78,46.78 0,0 0,167.77 27.74A46.78,46.78 0,0 0,120.99 74.52A46.78,46.78 0,0 0,167.77 121.3A46.78,46.78 0,0 0,208.92 96.74A54.87,54.87 0,0 0,246.2 111.45A54.87,54.87 0,0 0,268.71 106.54A39.04,39.04 0,0 0,281.09 122L327.6,122A39.04,39.04 0,0 0,343.38 90.7A39.04,39.04 0,0 0,304.34 51.66A39.04,39.04 0,0 0,300.82 51.85A54.87,54.87 0,0 0,246.2 1.71z", "m506.71,31.37a53.11,53.11 0,0 0,-53.11 53.11,53.11 53.11,0 0,0 15.55,37.5h75.12a53.11,53.11 0,0 0,1.88 -2.01,28.49 28.49,0 0,0 0.81,2.01h212.96a96.72,96.72 0,0 0,-87.09 -54.85,96.72 96.72,0 0,0 -73.14,33.52 28.49,28.49 0,0 0,-26.74 -18.74,28.49 28.49,0 0,0 -13.16,3.23 53.11,53.11 0,0 0,0.03 -0.66,53.11 53.11,0 0,0 -53.11,-53.11zM206.23,31.81a53.81,53.81 0,0 0,-49.99 34.03,74.91 74.91,0 0,0 -47.45,-17 74.91,74.91 0,0 0,-73.54 60.82,31.3 31.3,0 0,0 -10.17,-1.73 31.3,31.3 0,0 0,-26.09 14.05L300.86,121.98a37.63,37.63 0,0 0,0.2 -3.85,37.63 37.63,0 0,0 -37.63,-37.63 37.63,37.63 0,0 0,-3.65 0.21,53.81 53.81,0 0,0 -53.54,-48.9z", "m424.05,36.88a53.46,53.46 0,0 0,-40.89 19.02,53.46 53.46,0 0,0 -1.34,1.76 62.6,62.6 0,0 0,-5.39 -0.27,62.6 62.6,0 0,0 -61.36,50.17 62.6,62.6 0,0 0,-0.53 3.51,15.83 15.83,0 0,0 -10.33,-3.84 15.83,15.83 0,0 0,-8.06 2.23,21.1 21.1,0 0,0 -18.31,-10.67 21.1,21.1 0,0 0,-19.47 12.97,21.81 21.81,0 0,0 -6.56,-1.01 21.81,21.81 0,0 0,-19.09 11.32L522.84,122.07a43.61,43.61 0,0 0,-43.11 -37.35,43.61 43.61,0 0,0 -2.57,0.09 53.46,53.46 0,0 0,-53.11 -47.93zM129.08,38.4a50.29,50.29 0,0 0,-50.29 50.29,50.29 50.29,0 0,0 2.37,15.06 15.48,15.83 0,0 0,-5.87 1.68,15.48 15.83,0 0,0 -0.98,0.58 16.53,16.18 0,0 0,-0.19 -0.21,16.53 16.18,0 0,0 -11.86,-4.91 16.53,16.18 0,0 0,-16.38 14.13,20.05 16.18,0 0,0 -14.97,7.04L223.95,122.07a42.56,42.56 0,0 0,1.14 -9.56,42.56 42.56,0 0,0 -42.56,-42.56 42.56,42.56 0,0 0,-6.58 0.54,50.29 50.29,0 0,0 -0,-0.01 50.29,50.29 0,0 0,-46.88 -32.07zM631.67,82.61a64.01,64.01 0,0 0,-44.9 18.42,26.73 26.73,0 0,0 -10.67,-2.24 26.73,26.73 0,0 0,-22.72 12.71,16.88 16.88,0 0,0 -0.25,-0.12 16.88,16.88 0,0 0,-6.57 -1.33,16.88 16.88,0 0,0 -16.15,12.03h160.36a64.01,64.01 0,0 0,-59.1 -39.46z" ); // mCloudCenter.parserColors(0xfffdfdfd,0xffe8f3fd,0xffc7dcf1); mCloudCenter.parserColors(0xaac7dcf1,0xdde8f3fd,0xfffdfdfd); mCloudCenter.setBounds(0, 0, density.dip2px(260), density.dip2px(45)); } private float random(int min, int max) { // nextInt is normally exclusive of the top value, // so add 1 to make it inclusive return mRandom.nextInt((max - min) + 1) + min; } /** * We need a special value for different part of animation * * @param part - needed part * @return - value for needed part */ private float getAnimationPartValue(AnimationPart part) { switch (part) { case FIRST: { return mLoadingAnimationTime; } case SECOND: { return getAnimationTimePart(AnimationPart.FOURTH) - (mLoadingAnimationTime - getAnimationTimePart(AnimationPart.FOURTH)); } case THIRD: { return mLoadingAnimationTime - getAnimationTimePart(AnimationPart.SECOND); } case FOURTH: { return getAnimationTimePart(AnimationPart.THIRD) - (mLoadingAnimationTime - getAnimationTimePart(AnimationPart.FOURTH)); } default: return 0; } } /** * On drawing we should check current part of animation * * @param part - needed part of animation * @return - return true if current part */ private boolean checkCurrentAnimationPart(AnimationPart part) { switch (part) { case FIRST: { return mLoadingAnimationTime < getAnimationTimePart(AnimationPart.FOURTH); } case SECOND: case THIRD: { return mLoadingAnimationTime < getAnimationTimePart(part); } case FOURTH: { return mLoadingAnimationTime > getAnimationTimePart(AnimationPart.THIRD); } default: return false; } } /** * Get part of animation duration * * @param part - needed part of time * @return - interval of time */ private int getAnimationTimePart(AnimationPart part) { switch (part) { case SECOND: { return LOADING_ANIMATION_COEFFICIENT / 2; } case THIRD: { return getAnimationTimePart(AnimationPart.FOURTH) * 3; } case FOURTH: { return LOADING_ANIMATION_COEFFICIENT / 4; } default: return 0; } } private void setupAnimations() { mAnimation = new Animation() { @Override public void applyTransformation(float interpolatedTime, @NonNull Transformation t) { setLoadingAnimationTime(interpolatedTime); } }; mAnimation.setRepeatCount(Animation.INFINITE); mAnimation.setRepeatMode(Animation.REVERSE); mAnimation.setInterpolator(ACCELERATE_DECELERATE_INTERPOLATOR); mAnimation.setDuration(ANIMATION_DURATION); } private void setLoadingAnimationTime(float loadingAnimationTime) { /**SLOW DOWN ANIMATION IN {@link #SLOW_DOWN_ANIMATION_COEFFICIENT} time */ mLoadingAnimationTime = LOADING_ANIMATION_COEFFICIENT * (loadingAnimationTime / SLOW_DOWN_ANIMATION_COEFFICIENT); invalidate(); } // } ================================================ FILE: refresh-header/src/main/java/com/scwang/smartrefresh/header/WaterDropHeader.java ================================================ /** * @file XListViewHeader.java * @create Apr 18, 2012 5:22:27 PM * @author Maxwin * @description XListView's header */ package com.scwang.smartrefresh.header; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.support.annotation.RequiresApi; import android.content.Context; import android.graphics.Canvas; import android.os.Build; import android.support.annotation.NonNull; import android.util.AttributeSet; import android.view.View; import android.view.ViewGroup; import android.widget.ImageView; import com.scwang.smartrefresh.header.internal.MaterialProgressDrawable; import com.scwang.smartrefresh.header.waterdrop.WaterDropView; import com.scwang.smartrefresh.layout.api.RefreshHeader; import com.scwang.smartrefresh.layout.api.RefreshKernel; import com.scwang.smartrefresh.layout.api.RefreshLayout; import com.scwang.smartrefresh.layout.constant.RefreshState; import com.scwang.smartrefresh.layout.constant.SpinnerStyle; import com.scwang.smartrefresh.layout.internal.ProgressDrawable; import com.scwang.smartrefresh.layout.util.DensityUtil; import static android.view.View.MeasureSpec.AT_MOST; import static android.view.View.MeasureSpec.EXACTLY; import static android.view.View.MeasureSpec.getSize; import static android.view.View.MeasureSpec.makeMeasureSpec; import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; public class WaterDropHeader extends ViewGroup implements RefreshHeader { // private static final float MAX_PROGRESS_ANGLE = 0.8f; private RefreshState mState; private ImageView mImageView; private WaterDropView mWaterDropView; private ProgressDrawable mProgressDrawable; private MaterialProgressDrawable mProgress; private int mProgressDegree = 0; // // public WaterDropHeader(Context context) { super(context); this.initView(context); } public WaterDropHeader(Context context, AttributeSet attrs) { super(context, attrs); this.initView(context); } public WaterDropHeader(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); this.initView(context); } @RequiresApi(Build.VERSION_CODES.LOLLIPOP) public WaterDropHeader(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); this.initView(context); } private void initView(Context context) { DensityUtil density = new DensityUtil(); mWaterDropView = new WaterDropView(context); addView(mWaterDropView, MATCH_PARENT, MATCH_PARENT); mWaterDropView.updateComleteState(0); mProgressDrawable = new ProgressDrawable(); mProgressDrawable.setBounds(0,0, density.dip2px(20), density.dip2px(20)); mImageView = new ImageView(context); mProgress = new MaterialProgressDrawable(context, mImageView); mProgress.setBackgroundColor(0xffffffff); mProgress.setAlpha(255); mProgress.setColorSchemeColors(0xffffffff,0xff0099cc,0xffff4444,0xff669900,0xffaa66cc,0xffff8800); mImageView.setImageDrawable(mProgress); addView(mImageView, density.dip2px(30), density.dip2px(30)); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); LayoutParams lpImage = mImageView.getLayoutParams(); mImageView.measure( makeMeasureSpec(lpImage.width, EXACTLY), makeMeasureSpec(lpImage.height, EXACTLY) ); mWaterDropView.measure( makeMeasureSpec(getSize(widthMeasureSpec), AT_MOST), heightMeasureSpec ); int maxWidth = Math.max(mImageView.getMeasuredWidth(), mWaterDropView.getMeasuredHeight()); int maxHeight = Math.max(mImageView.getMeasuredHeight(), mWaterDropView.getMeasuredHeight()); setMeasuredDimension(resolveSize(maxWidth, widthMeasureSpec), resolveSize(maxHeight, heightMeasureSpec)); } @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { final int measuredWidth = getMeasuredWidth(); final int widthWaterDrop = mWaterDropView.getMeasuredWidth(); final int heightWaterDrop = mWaterDropView.getMeasuredHeight(); final int leftWaterDrop = measuredWidth / 2 - widthWaterDrop / 2; final int topWaterDrop = 0; mWaterDropView.layout(leftWaterDrop, topWaterDrop, leftWaterDrop + widthWaterDrop, topWaterDrop + heightWaterDrop); final int widthImage = mImageView.getMeasuredWidth(); final int heightImage = mImageView.getMeasuredHeight(); final int leftImage = measuredWidth / 2 - widthImage / 2; int topImage = widthWaterDrop / 2 - widthImage / 2; if (topImage + heightImage > mWaterDropView.getBottom() - (widthWaterDrop - widthImage) / 2) { topImage = mWaterDropView.getBottom() - (widthWaterDrop - widthImage) / 2 - heightImage; } mImageView.layout(leftImage, topImage, leftImage + widthImage, topImage + heightImage); } // // @Override protected void dispatchDraw(Canvas canvas) { super.dispatchDraw(canvas); if (mState == RefreshState.Refreshing) { canvas.save(); canvas.translate( getWidth()/2-mProgressDrawable.width()/2, mWaterDropView.getMaxCircleRadius() +mWaterDropView.getPaddingTop() -mProgressDrawable.height()/2 ); canvas.rotate(mProgressDegree, mProgressDrawable.width() / 2, mProgressDrawable.height() / 2); mProgressDrawable.draw(canvas); canvas.restore(); } } // // @Override public void onInitialized(RefreshKernel kernel, int height, int extendHeight) { } @Override public boolean isSupportHorizontalDrag() { return false; } @Override public void onHorizontalDrag(float percentX, int offsetX, int offsetMax) { } @Override public void onPullingDown(float percent, int offset, int headHeight, int extendHeight) { mWaterDropView.updateComleteState((offset), headHeight + extendHeight); mWaterDropView.postInvalidate(); float originalDragPercent = 1f * offset / headHeight; float dragPercent = Math.min(1f, Math.abs(originalDragPercent)); float adjustedPercent = (float) Math.max(dragPercent - .4, 0) * 5 / 3; float extraOS = Math.abs(offset) - headHeight; float tensionSlingshotPercent = Math.max(0, Math.min(extraOS, (float) headHeight * 2) / (float) headHeight); float tensionPercent = (float) ((tensionSlingshotPercent / 4) - Math.pow( (tensionSlingshotPercent / 4), 2)) * 2f; float strokeStart = adjustedPercent * .8f; float rotation = (-0.25f + .4f * adjustedPercent + tensionPercent * 2) * .5f; mProgress.showArrow(true); mProgress.setStartEndTrim(0f, Math.min(MAX_PROGRESS_ANGLE, strokeStart)); mProgress.setArrowScale(Math.min(1f, adjustedPercent)); mProgress.setProgressRotation(rotation); } @Override public void onReleasing(float percent, int offset, int headHeight, int extendHeight) { if (mState != RefreshState.Refreshing) { mWaterDropView.updateComleteState(Math.max(offset, 0), headHeight + extendHeight); mWaterDropView.postInvalidate(); } } @Override public void onStateChanged(RefreshLayout refreshLayout, RefreshState oldState, RefreshState newState) { mState = newState; switch (newState) { case None: mWaterDropView.setVisibility(View.VISIBLE); break; case PullDownToRefresh: mWaterDropView.setVisibility(View.VISIBLE); break; case PullDownCanceled: break; case ReleaseToRefresh: mWaterDropView.setVisibility(View.VISIBLE); break; case Refreshing: break; case RefreshFinish: mWaterDropView.setVisibility(View.GONE); break; } } @Override public void onStartAnimator(final RefreshLayout layout, int headHeight, int extendHeight) { Animator animator = mWaterDropView.createAnimator(); animator.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { mWaterDropView.animate().alpha(0).setListener(new AnimatorListenerAdapter() { public void onAnimationEnd(Animator animation) { mWaterDropView.setVisibility(GONE); mWaterDropView.setAlpha(1); } }); } }); animator.start();//开始回弹 layout.getLayout().postDelayed(new Runnable() { @Override public void run() { mProgressDegree = (mProgressDegree + 30) % 360; invalidate(); if (mState == RefreshState.Refreshing) { layout.getLayout().postDelayed(this, 100); } } },100); } @Override public int onFinish(RefreshLayout layout, boolean success) { return 0; } @Override@Deprecated public void setPrimaryColors(int... colors) { if (colors.length > 0) { mWaterDropView.setIndicatorColor(colors[0]); } } @NonNull @Override public View getView() { return this; } @Override public SpinnerStyle getSpinnerStyle() { return SpinnerStyle.Scale; } // } ================================================ FILE: refresh-header/src/main/java/com/scwang/smartrefresh/header/WaveSwipeHeader.java ================================================ package com.scwang.smartrefresh.header; import android.animation.ValueAnimator; import android.content.Context; import android.content.res.Resources; import android.content.res.TypedArray; import android.graphics.Color; import android.os.Build; import android.support.annotation.IdRes; import android.support.annotation.NonNull; import android.support.annotation.RequiresApi; import android.util.AttributeSet; import android.view.View; import android.view.ViewGroup; import android.view.animation.AccelerateDecelerateInterpolator; import android.view.animation.Animation; import android.view.animation.Transformation; import com.scwang.smartrefresh.header.internal.MaterialProgressDrawable; import com.scwang.smartrefresh.header.waveswipe.AnimationImageView; import com.scwang.smartrefresh.header.waveswipe.DisplayUtil; import com.scwang.smartrefresh.header.waveswipe.WaveView; import com.scwang.smartrefresh.layout.api.RefreshHeader; import com.scwang.smartrefresh.layout.api.RefreshKernel; import com.scwang.smartrefresh.layout.api.RefreshLayout; import com.scwang.smartrefresh.layout.constant.RefreshState; import com.scwang.smartrefresh.layout.constant.SpinnerStyle; import com.scwang.smartrefresh.layout.util.DensityUtil; import static android.view.View.MeasureSpec.EXACTLY; import static android.view.View.MeasureSpec.getSize; import static android.view.View.MeasureSpec.makeMeasureSpec; /** * 水滴下拉头 * Created by SCWANG on 2017/6/4. */ public class WaveSwipeHeader extends ViewGroup implements RefreshHeader { /** * 落ちる前の回転の最大のAngle値 */ private static final float MAX_PROGRESS_ROTATION_RATE = 0.8f; private enum VERTICAL_DRAG_THRESHOLD { FIRST(0.1f), SECOND(0.16f + FIRST.val), THIRD(0.5f + FIRST.val); // FIRST(0.2f), SECOND(0.26f + FIRST.val), THIRD(0.7f + FIRST.val); final float val; VERTICAL_DRAG_THRESHOLD(float val) { this.val = val; } } // private WaveView mWaveView; private RefreshState mState; private ProgressAnimationImageView mCircleView; private float mLastFirstBounds; public WaveSwipeHeader(Context context) { super(context); this.initView(context, null); } public WaveSwipeHeader(Context context, AttributeSet attrs) { super(context, attrs); this.initView(context, attrs); } public WaveSwipeHeader(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); this.initView(context, attrs); } @RequiresApi(Build.VERSION_CODES.LOLLIPOP) public WaveSwipeHeader(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); this.initView(context, attrs); } private void initView(Context context, AttributeSet attrs) { addView(mWaveView = new WaveView(context)); addView(mCircleView = new ProgressAnimationImageView(getContext())); TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.WaveSwipeHeader); int primaryColor = ta.getColor(R.styleable.WaveSwipeHeader_wshPrimaryColor, 0); int accentColor = ta.getColor(R.styleable.WaveSwipeHeader_wshAccentColor, 0); if (primaryColor != 0) { mWaveView.setWaveColor(primaryColor); } if (accentColor != 0) { mCircleView.setProgressColorSchemeColors(accentColor); } if (ta.hasValue(R.styleable.WaveSwipeHeader_wshShadowRadius)) { int radius = ta.getDimensionPixelOffset(R.styleable.WaveSwipeHeader_wshShadowRadius, 0); int color = ta.getColor(R.styleable.WaveSwipeHeader_wshShadowColor, 0xff000000); mWaveView.setShadow(radius, color); } ta.recycle(); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { setMeasuredDimension(getSize(widthMeasureSpec), getSize(heightMeasureSpec)); mCircleView.measure(); mWaveView.measure(makeMeasureSpec(getSize(widthMeasureSpec), EXACTLY),makeMeasureSpec(getSize(heightMeasureSpec), EXACTLY)); } @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { mWaveView.layout(0, 0, getMeasuredWidth(), getMeasuredHeight()); final int thisWidth = getMeasuredWidth(); final int circleWidth = mCircleView.getMeasuredWidth(); final int circleHeight = mCircleView.getMeasuredHeight(); mCircleView.layout((thisWidth - circleWidth) / 2, -circleHeight , (thisWidth + circleWidth) / 2, 0); if (isInEditMode()) { onPullingDown(0.99f, DensityUtil.dp2px(99), DensityUtil.dp2px(100), DensityUtil.dp2px(100)); } } // // /** * @param colors セットするColor達 */ public void setColorSchemeColors(int... colors) { mCircleView.setProgressColorSchemeColors(colors); } // // @Override public void onInitialized(RefreshKernel kernel, int height, int extendHeight) { } @Override public boolean isSupportHorizontalDrag() { return false; } @Override public void onHorizontalDrag(float percentX, int offsetX, int offsetMax) { } @Override public void onPullingDown(float percent, int offset, int headHeight, int extendHeight) { if (mState == RefreshState.Refreshing) { return; } float dragPercent = Math.min(1f, percent); float adjustedPercent = (float) Math.max(dragPercent - .4, 0) * 5 / 3; // 0f...2f float tensionSlingshotPercent = (percent > 3f) ? 2f : (percent > 1f) ? percent - 1f : 0; float tensionPercent = (4f - tensionSlingshotPercent) * tensionSlingshotPercent / 8f; if (percent < 1f) { float strokeStart = adjustedPercent * .8f; mCircleView.setProgressStartEndTrim(0f, Math.min(MAX_PROGRESS_ROTATION_RATE, strokeStart)); mCircleView.setArrowScale(Math.min(1f, adjustedPercent)); } float rotation = (-0.25f + .4f * adjustedPercent + tensionPercent * 2) * .5f; mCircleView.setProgressRotation(rotation); mCircleView.setTranslationY(mWaveView.getCurrentCircleCenterY()); float seed = 1f * offset / Math.min(getMeasuredWidth(), getMeasuredHeight()); float firstBounds = seed * (5f - 2 * seed) / 3.5f; float secondBounds = firstBounds - VERTICAL_DRAG_THRESHOLD.FIRST.val; float finalBounds = (firstBounds - VERTICAL_DRAG_THRESHOLD.SECOND.val) / 5; mLastFirstBounds = firstBounds; if (firstBounds < VERTICAL_DRAG_THRESHOLD.FIRST.val) { // draw a wave and not draw a circle mWaveView.beginPhase(firstBounds); } else if (firstBounds < VERTICAL_DRAG_THRESHOLD.SECOND.val) { // draw a circle with a wave mWaveView.appearPhase(firstBounds, secondBounds); } else /*if (firstBounds < VERTICAL_DRAG_THRESHOLD.THIRD.val)*/ { // draw a circle with expanding a wave mWaveView.expandPhase(firstBounds, secondBounds, finalBounds); // } else { // // stop to draw a wave and drop a circle // onDropPhase(); } } @Override public void onReleasing(float percent, int offset, int headHeight, int extendHeight) { } @Override public void onStartAnimator(RefreshLayout layout, int headHeight, int extendHeight) { mLastFirstBounds = 0; mWaveView.animationDropCircle(); mCircleView.makeProgressTransparent(); mCircleView.startProgress(); ValueAnimator animator = ValueAnimator.ofFloat(0, 0); animator.setDuration(500); animator.setInterpolator(new AccelerateDecelerateInterpolator()); animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator valueAnimator) { mCircleView.setTranslationY( mWaveView.getCurrentCircleCenterY() + mCircleView.getHeight() / 2.f); } }); animator.start(); } @Override public void onStateChanged(RefreshLayout refreshLayout, RefreshState oldState, RefreshState newState) { mState = newState; switch (newState) { case None: break; case PullDownToRefresh: mCircleView.showArrow(true); mCircleView.scaleWithKeepingAspectRatio(1f); mCircleView.makeProgressTransparent(); break; case PullDownCanceled: mCircleView.showArrow(false); mCircleView.setProgressRotation(0); mCircleView.setProgressStartEndTrim(0f, 0f); mWaveView.startWaveAnimation(mLastFirstBounds); mLastFirstBounds = 0; break; case ReleaseToRefresh: break; case Refreshing: break; } } @Override public int onFinish(RefreshLayout layout, boolean success) { Animation scaleDownAnimation = new Animation() { @Override public void applyTransformation(float interpolatedTime, Transformation t) { mCircleView.scaleWithKeepingAspectRatio(1 - interpolatedTime); } }; scaleDownAnimation.setDuration(200); mCircleView.setAnimationListener(new Animation.AnimationListener() { public void onAnimationStart(Animation animation) {} public void onAnimationRepeat(Animation animation) {} public void onAnimationEnd(Animation animation) { mCircleView.stopProgress(); mCircleView.makeProgressTransparent(); mWaveView.startDisappearCircleAnimation(); } }); mCircleView.clearAnimation(); mCircleView.startAnimation(scaleDownAnimation); return 0; } @Override@Deprecated public void setPrimaryColors(int... colors) { if (colors.length > 0) { mWaveView.setWaveColor(colors[0]); if (colors.length > 1) { mCircleView.setProgressColorSchemeColors(colors[1]); } } } @NonNull @Override public View getView() { return this; } @Override public SpinnerStyle getSpinnerStyle() { return SpinnerStyle.MatchLayout; } // // /** * Custom view has progress drawable. * Some features of MaterialProgressDrawable are decorated. * * @author jmatsu */ private class ProgressAnimationImageView extends AnimationImageView { private final MaterialProgressDrawable mProgress; /** * Constructor * {@inheritDoc} */ public ProgressAnimationImageView(Context context) { super(context); mProgress = new MaterialProgressDrawable(context, WaveSwipeHeader.this); mProgress.setBackgroundColor(Color.TRANSPARENT); if (DisplayUtil.isOver600dp(getContext())) { // Make the progress be big mProgress.updateSizes(MaterialProgressDrawable.LARGE); } setImageDrawable(mProgress); } public void measure() { final int circleDiameter = mProgress.getIntrinsicWidth(); measure(makeMeasureSpecExactly(circleDiameter), makeMeasureSpecExactly(circleDiameter)); } private int makeMeasureSpecExactly(int length) { return MeasureSpec.makeMeasureSpec(length, MeasureSpec.EXACTLY); } public void makeProgressTransparent() { mProgress.setAlpha(0xff); } public void showArrow(boolean show) { mProgress.showArrow(show); } public void setArrowScale(float scale) { mProgress.setArrowScale(scale); } public void setProgressAlpha(int alpha) { mProgress.setAlpha(alpha); } public void setProgressStartEndTrim(float startAngle, float endAngle) { mProgress.setStartEndTrim(startAngle, endAngle); } public void setProgressRotation(float rotation) { mProgress.setProgressRotation(rotation); } public void startProgress() { mProgress.start(); } public void stopProgress() { mProgress.stop(); } public void setProgressColorSchemeColors(@NonNull int... colors) { mProgress.setColorSchemeColors(colors); } public void setProgressColorSchemeColorsFromResource(@IdRes int... resources) { final Resources res = getResources(); final int[] colorRes = new int[resources.length]; for (int i = 0; i < resources.length; i++) { colorRes[i] = res.getColor(resources[i]); } setColorSchemeColors(colorRes); } public void scaleWithKeepingAspectRatio(float scale) { this.setScaleX(scale); this.setScaleY(scale); } } // } ================================================ FILE: refresh-header/src/main/java/com/scwang/smartrefresh/header/flyrefresh/FlyView.java ================================================ package com.scwang.smartrefresh.header.flyrefresh; import android.content.Context; import android.util.AttributeSet; import com.scwang.smartrefresh.layout.internal.pathview.PathsView; import com.scwang.smartrefresh.layout.util.DensityUtil; /** * 纸飞机视图 * Created by SCWANG on 2017/6/6. */ public class FlyView extends PathsView { public FlyView(Context context) { super(context); this.initView(context, null, 0); } public FlyView(Context context, AttributeSet attrs) { super(context, attrs); this.initView(context, attrs, 0); } public FlyView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); this.initView(context, attrs, defStyleAttr); } private void initView(Context context, AttributeSet attrs, int defStyleAttr) { parserColors(0xffffffff); parserPaths("M2.01,21L23,12 2.01,3 2,10l15,2 -15,2z"); setMinimumWidth(DensityUtil.dp2px(25)); setMinimumHeight(DensityUtil.dp2px(25)); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { setMeasuredDimension(resolveSize(getSuggestedMinimumWidth(), widthMeasureSpec), resolveSize(getSuggestedMinimumHeight(), heightMeasureSpec)); } } ================================================ FILE: refresh-header/src/main/java/com/scwang/smartrefresh/header/flyrefresh/MountanScenceView.java ================================================ package com.scwang.smartrefresh.header.flyrefresh; import android.content.Context; import android.content.res.TypedArray; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Matrix; import android.graphics.Paint; import android.graphics.Path; import android.graphics.RectF; import android.os.Build; import android.support.annotation.RequiresApi; import android.util.AttributeSet; import android.view.View; import android.view.animation.Interpolator; import com.scwang.smartrefresh.header.R; import com.scwang.smartrefresh.layout.util.ColorUtils; /** * 山丘树木场景视图 * Created by jing on 15-5-28. */ public class MountanScenceView extends View { private int COLOR_BACKGROUND = Color.parseColor("#7ECEC9"); private int COLOR_MOUNTAIN_1 = Color.parseColor("#86DAD7"); private int COLOR_MOUNTAIN_2 = Color.parseColor("#3C929C"); private int COLOR_MOUNTAIN_3 = Color.parseColor("#3E5F73"); private int COLOR_TREE_1_BRANCH = Color.parseColor("#1F7177"); private int COLOR_TREE_1_BTRUNK = Color.parseColor("#0C3E48"); private int COLOR_TREE_2_BRANCH = Color.parseColor("#34888F"); private int COLOR_TREE_2_BTRUNK = Color.parseColor("#1B6169"); private int COLOR_TREE_3_BRANCH = Color.parseColor("#57B1AE"); private int COLOR_TREE_3_BTRUNK = Color.parseColor("#62A4AD"); private static final int WIDTH = 240; private static final int HEIGHT = 180; private static final int TREE_WIDTH = 100; private static final int TREE_HEIGHT = 200; private Paint mMountPaint = new Paint(); private Paint mTrunkPaint = new Paint(); private Paint mBranchPaint = new Paint(); private Paint mBoarderPaint = new Paint(); private Path mMount1 = new Path(); private Path mMount2 = new Path(); private Path mMount3 = new Path(); private Path mTrunk = new Path(); private Path mBranch = new Path(); private Matrix mTransMatrix = new Matrix(); private float mScaleX = 5f; private float mScaleY = 5f; private float mMoveFactor = 0; private float mBounceMax = 1; private float mTreeBendFactor = Float.MAX_VALUE; private int mViewportHeight = 0; // public MountanScenceView(Context context) { super(context); initView(context, null, 0); } public MountanScenceView(Context context, AttributeSet attrs) { super(context, attrs); initView(context, attrs, 0); } public MountanScenceView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); initView(context, attrs, defStyleAttr); } @RequiresApi(Build.VERSION_CODES.LOLLIPOP) public MountanScenceView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); initView(context, attrs, defStyleAttr); } private void initView(Context context, AttributeSet attrs, int defStyleAttr) { mMountPaint.setAntiAlias(true); mMountPaint.setStyle(Paint.Style.FILL); mTrunkPaint.setAntiAlias(true); mBranchPaint.setAntiAlias(true); mBoarderPaint.setAntiAlias(true); mBoarderPaint.setStyle(Paint.Style.STROKE); mBoarderPaint.setStrokeWidth(2); mBoarderPaint.setStrokeJoin(Paint.Join.ROUND); TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.MountanScenceView); if (ta.hasValue(R.styleable.MountanScenceView_msvPrimaryColor)) { setPrimaryColor(ta.getColor(R.styleable.MountanScenceView_msvPrimaryColor, 0xff000000)); } mViewportHeight = ta.getDimensionPixelOffset(R.styleable.MountanScenceView_msvViewportHeight, 0); ta.recycle(); updateMountainPath(mMoveFactor, HEIGHT); updateTreePath(mMoveFactor, true); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); final int width = getMeasuredWidth(); final int height = getMeasuredHeight(); mScaleX = 1f * width / WIDTH; mScaleY = 1f * (mViewportHeight > 0 ? mViewportHeight : height) / HEIGHT; updateMountainPath(mMoveFactor, height); updateTreePath(mMoveFactor, true); } private void updateMountainPath(float factor,int height) { mTransMatrix.reset(); mTransMatrix.setScale(mScaleX, mScaleY); int offset1 = (int) (10 * factor); mMount1.reset(); mMount1.moveTo(0, 95 + offset1); mMount1.lineTo(55, 74 + offset1); mMount1.lineTo(146, 104 + offset1); mMount1.lineTo(227, 72 + offset1); mMount1.lineTo(WIDTH, 80 + offset1); mMount1.lineTo(WIDTH, HEIGHT); mMount1.lineTo(0, HEIGHT); mMount1.close(); mMount1.transform(mTransMatrix); int offset2 = (int) (20 * factor); mMount2.reset(); mMount2.moveTo(0, 103 + offset2); mMount2.lineTo(67, 90 + offset2); mMount2.lineTo(165, 115 + offset2); mMount2.lineTo(221, 87 + offset2); mMount2.lineTo(WIDTH, 100 + offset2); mMount2.lineTo(WIDTH, HEIGHT); mMount2.lineTo(0, HEIGHT); mMount2.close(); mMount2.transform(mTransMatrix); int offset3 = (int) (30 * factor); mMount3.reset(); mMount3.moveTo(0, 114 + offset3); mMount3.cubicTo(30, 106 + offset3, 196, 97 + offset3, WIDTH, 104 + offset3); mMount3.lineTo(WIDTH, height / mScaleY); mMount3.lineTo(0, height / mScaleY); mMount3.close(); mMount3.transform(mTransMatrix); } private void updateTreePath(float factor, boolean force) { if (factor == mTreeBendFactor && !force) { return; } final Interpolator interpolator = PathInterpolatorCompat.create(0.8f, -0.5f * factor); final float width = TREE_WIDTH; final float height = TREE_HEIGHT; final float maxMove = width * 0.3f * factor; final float trunkSize = width * 0.05f; final float branchSize = width * 0.2f; final float x0 = width / 2; final float y0 = height; final int N = 25; final float dp = 1f / N; final float dy = -dp * height; float y = y0; float p = 0; float[] xx = new float[N + 1]; float[] yy = new float[N + 1]; for (int i = 0; i <= N; i++) { xx[i] = interpolator.getInterpolation(p) * maxMove + x0; yy[i] = y; y += dy; p += dp; } mTrunk.reset(); mTrunk.moveTo(x0 - trunkSize, y0); int max = (int) (N * 0.7f); int max1 = (int) (max * 0.5f); float diff = max - max1; for (int i = 0; i < max; i++) { if (i < max1) { mTrunk.lineTo(xx[i] - trunkSize, yy[i]); } else { mTrunk.lineTo(xx[i] - trunkSize * (max - i) / diff, yy[i]); } } for (int i = max - 1; i >= 0; i--) { if (i < max1) { mTrunk.lineTo(xx[i] + trunkSize, yy[i]); } else { mTrunk.lineTo(xx[i] + trunkSize * (max - i) / diff, yy[i]); } } mTrunk.close(); mBranch.reset(); int min = (int) (N * 0.4f); diff = N - min; mBranch.moveTo(xx[min] - branchSize, yy[min]); mBranch.addArc(new RectF(xx[min] - branchSize, yy[min] - branchSize, xx[min] + branchSize, yy[min] + branchSize), 0f, 180f); for (int i = min; i <= N; i++) { float f = (i - min) / diff; mBranch.lineTo(xx[i] - branchSize + f * f * branchSize, yy[i]); } for (int i = N; i >= min; i--) { float f = (i - min) / diff; mBranch.lineTo(xx[i] + branchSize - f * f * branchSize, yy[i]); } } private void drawTree(Canvas canvas, float scale, float baseX, float baseY, int colorTrunk, int colorBranch) { canvas.save(); final float dx = baseX - TREE_WIDTH * scale / 2; final float dy = baseY - TREE_HEIGHT * scale; canvas.translate(dx, dy); canvas.scale(scale, scale); mBranchPaint.setColor(colorBranch); canvas.drawPath(mBranch, mBranchPaint); mTrunkPaint.setColor(colorTrunk); canvas.drawPath(mTrunk, mTrunkPaint); mBoarderPaint.setColor(colorTrunk); canvas.drawPath(mBranch, mBoarderPaint); canvas.restore(); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); canvas.drawColor(COLOR_BACKGROUND); mMountPaint.setColor(COLOR_MOUNTAIN_1); canvas.drawPath(mMount1, mMountPaint); canvas.save(); canvas.scale(-1, 1, getWidth() / 2, 0); drawTree(canvas, 0.12f * mScaleX, 180 * mScaleX, (93 + 20 * mMoveFactor) * mScaleY, COLOR_TREE_3_BTRUNK, COLOR_TREE_3_BRANCH); drawTree(canvas, 0.1f * mScaleX, 200 * mScaleX, (96 + 20 * mMoveFactor) * mScaleY, COLOR_TREE_3_BTRUNK, COLOR_TREE_3_BRANCH); canvas.restore(); mMountPaint.setColor(COLOR_MOUNTAIN_2); canvas.drawPath(mMount2, mMountPaint); drawTree(canvas, 0.2f * mScaleX, 160 * mScaleX, (105 + 30 * mMoveFactor) * mScaleY, COLOR_TREE_1_BTRUNK, COLOR_TREE_1_BRANCH); drawTree(canvas, 0.14f * mScaleX, 180 * mScaleX, (105 + 30 * mMoveFactor) * mScaleY, COLOR_TREE_2_BTRUNK, COLOR_TREE_2_BRANCH); drawTree(canvas, 0.16f * mScaleX, 140 * mScaleX, (105 + 30 * mMoveFactor) * mScaleY, COLOR_TREE_2_BTRUNK, COLOR_TREE_2_BRANCH); mMountPaint.setColor(COLOR_MOUNTAIN_3); canvas.drawPath(mMount3, mMountPaint); } // public void setPrimaryColor(int color) { // private int COLOR_BACKGROUND = Color.parseColor("#7ECEC9"); // private int COLOR_MOUNTAIN_1 = Color.parseColor("#86DAD7"); // private int COLOR_MOUNTAIN_2 = Color.parseColor("#3C929C"); // private int COLOR_MOUNTAIN_3 = Color.parseColor("#3E5F73"); // private int COLOR_TREE_1_BRANCH = Color.parseColor("#1F7177"); // private int COLOR_TREE_1_BTRUNK = Color.parseColor("#0C3E48"); // private int COLOR_TREE_2_BRANCH = Color.parseColor("#34888F"); // private int COLOR_TREE_2_BTRUNK = Color.parseColor("#1B6169"); // private int COLOR_TREE_3_BRANCH = Color.parseColor("#57B1AE"); // private int COLOR_TREE_3_BTRUNK = Color.parseColor("#62A4AD"); COLOR_BACKGROUND = color; COLOR_MOUNTAIN_1 = ColorUtils.compositeColors(0x99ffffff,color); COLOR_MOUNTAIN_2 = ColorUtils.compositeColors(0x993C929C,color); COLOR_MOUNTAIN_3 = ColorUtils.compositeColors(0xCC3E5F73,color); COLOR_TREE_1_BRANCH = ColorUtils.compositeColors(0x551F7177,color); COLOR_TREE_1_BTRUNK = ColorUtils.compositeColors(0xCC0C3E48,color); COLOR_TREE_2_BRANCH = ColorUtils.compositeColors(0x5534888F,color); COLOR_TREE_2_BTRUNK = ColorUtils.compositeColors(0xCC1B6169,color); COLOR_TREE_3_BRANCH = ColorUtils.compositeColors(0x5557B1AE,color); COLOR_TREE_3_BTRUNK = ColorUtils.compositeColors(0xCC62A4AD,color); } public void updatePercent(float percent) { mBounceMax = percent; float bendFactor = Math.max(0, percent); mMoveFactor = Math.max(0, mBounceMax); int height = getMeasuredHeight(); updateMountainPath(mMoveFactor, height > 0 ? height : HEIGHT); updateTreePath(bendFactor, false); } } ================================================ FILE: refresh-header/src/main/java/com/scwang/smartrefresh/header/flyrefresh/PathInterpolatorCompat.java ================================================ /* * Copyright (C) 2015 The Android Open Source Project * * 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.scwang.smartrefresh.header.flyrefresh; import android.graphics.Path; import android.os.Build; import android.view.animation.Interpolator; /** * Helper for creating path-based {@link Interpolator} instances. On API 21 or newer, the * platform implementation will be used and on older platforms a compatible alternative * implementation will be used. */ public final class PathInterpolatorCompat { private PathInterpolatorCompat() { // prevent instantiation } /** * Create an {@link Interpolator} for an arbitrary {@link Path}. The {@link Path} * must begin at {@code (0, 0)} and end at {@code (1, 1)}. The x-coordinate along the * {@link Path} is the input value and the output is the y coordinate of the line at that * point. This means that the Path must conform to a function {@code y = f(x)}. * * The {@link Path} must not have gaps in the x direction and must not * loop back on itself such that there can be two points sharing the same x coordinate. * * @param path the {@link Path} to use to make the line representing the {@link Interpolator} * @return the {@link Interpolator} representing the {@link Path} */ public static Interpolator create(Path path) { if (Build.VERSION.SDK_INT >= 21) { return PathInterpolatorCompatApi21.create(path); } return PathInterpolatorCompatBase.create(path); } /** * Create an {@link Interpolator} for a quadratic Bezier curve. The end points * {@code (0, 0)} and {@code (1, 1)} are assumed. * * @param controlX the x coordinate of the quadratic Bezier control point * @param controlY the y coordinate of the quadratic Bezier control point * @return the {@link Interpolator} representing the quadratic Bezier curve */ public static Interpolator create(float controlX, float controlY) { if (Build.VERSION.SDK_INT >= 21) { return PathInterpolatorCompatApi21.create(controlX, controlY); } return PathInterpolatorCompatBase.create(controlX, controlY); } /** * Create an {@link Interpolator} for a cubic Bezier curve. The end points * {@code (0, 0)} and {@code (1, 1)} are assumed. * * @param controlX1 the x coordinate of the first control point of the cubic Bezier * @param controlY1 the y coordinate of the first control point of the cubic Bezier * @param controlX2 the x coordinate of the second control point of the cubic Bezier * @param controlY2 the y coordinate of the second control point of the cubic Bezier * @return the {@link Interpolator} representing the cubic Bezier curve */ public static Interpolator create(float controlX1, float controlY1, float controlX2, float controlY2) { if (Build.VERSION.SDK_INT >= 21) { return PathInterpolatorCompatApi21.create(controlX1, controlY1, controlX2, controlY2); } return PathInterpolatorCompatBase.create(controlX1, controlY1, controlX2, controlY2); } } ================================================ FILE: refresh-header/src/main/java/com/scwang/smartrefresh/header/flyrefresh/PathInterpolatorCompatApi21.java ================================================ /* * Copyright (C) 2015 The Android Open Source Project * * 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.scwang.smartrefresh.header.flyrefresh; import android.graphics.Path; import android.support.annotation.RequiresApi; import android.annotation.TargetApi; import android.view.animation.Interpolator; import android.view.animation.PathInterpolator; /** * API 21+ implementation for path interpolator compatibility. */ @RequiresApi(21) @TargetApi(21) class PathInterpolatorCompatApi21 { private PathInterpolatorCompatApi21() { // prevent instantiation } public static Interpolator create(Path path) { return new PathInterpolator(path); } public static Interpolator create(float controlX, float controlY) { return new PathInterpolator(controlX, controlY); } public static Interpolator create(float controlX1, float controlY1, float controlX2, float controlY2) { return new PathInterpolator(controlX1, controlY1, controlX2, controlY2); } } ================================================ FILE: refresh-header/src/main/java/com/scwang/smartrefresh/header/flyrefresh/PathInterpolatorCompatBase.java ================================================ /* * Copyright (C) 2015 The Android Open Source Project * * 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.scwang.smartrefresh.header.flyrefresh; import android.graphics.Path; import android.support.annotation.RequiresApi; import android.annotation.TargetApi; import android.view.animation.Interpolator; /** * Base implementation for path interpolator compatibility. */ @RequiresApi(9) @TargetApi(9) class PathInterpolatorCompatBase { private PathInterpolatorCompatBase() { // prevent instantiation } public static Interpolator create(Path path) { return new PathInterpolatorGingerbread(path); } public static Interpolator create(float controlX, float controlY) { return new PathInterpolatorGingerbread(controlX, controlY); } public static Interpolator create(float controlX1, float controlY1, float controlX2, float controlY2) { return new PathInterpolatorGingerbread(controlX1, controlY1, controlX2, controlY2); } } ================================================ FILE: refresh-header/src/main/java/com/scwang/smartrefresh/header/flyrefresh/PathInterpolatorGingerbread.java ================================================ /* * Copyright (C) 2015 The Android Open Source Project * * 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.scwang.smartrefresh.header.flyrefresh; import android.graphics.Path; import android.graphics.PathMeasure; import android.support.annotation.RequiresApi; import android.annotation.TargetApi; import android.view.animation.Interpolator; /** * A path interpolator implementation compatible with API 9+. */ @RequiresApi(9) @TargetApi(9) class PathInterpolatorGingerbread implements Interpolator { /** * Governs the accuracy of the approximation of the {@link Path}. */ private static final float PRECISION = 0.002f; private final float[] mX; private final float[] mY; public PathInterpolatorGingerbread(Path path) { final PathMeasure pathMeasure = new PathMeasure(path, false /* forceClosed */); final float pathLength = pathMeasure.getLength(); final int numPoints = (int) (pathLength / PRECISION) + 1; mX = new float[numPoints]; mY = new float[numPoints]; final float[] position = new float[2]; for (int i = 0; i < numPoints; ++i) { final float distance = (i * pathLength) / (numPoints - 1); pathMeasure.getPosTan(distance, position, null /* tangent */); mX[i] = position[0]; mY[i] = position[1]; } } public PathInterpolatorGingerbread(float controlX, float controlY) { this(createQuad(controlX, controlY)); } public PathInterpolatorGingerbread(float controlX1, float controlY1, float controlX2, float controlY2) { this(createCubic(controlX1, controlY1, controlX2, controlY2)); } @Override public float getInterpolation(float t) { if (t <= 0.0f) { return 0.0f; } else if (t >= 1.0f) { return 1.0f; } // Do a binary search for the correct x to interpolate between. int startIndex = 0; int endIndex = mX.length - 1; while (endIndex - startIndex > 1) { int midIndex = (startIndex + endIndex) / 2; if (t < mX[midIndex]) { endIndex = midIndex; } else { startIndex = midIndex; } } final float xRange = mX[endIndex] - mX[startIndex]; if (xRange == 0) { return mY[startIndex]; } final float tInRange = t - mX[startIndex]; final float fraction = tInRange / xRange; final float startY = mY[startIndex]; final float endY = mY[endIndex]; return startY + (fraction * (endY - startY)); } private static Path createQuad(float controlX, float controlY) { final Path path = new Path(); path.moveTo(0.0f, 0.0f); path.quadTo(controlX, controlY, 1.0f, 1.0f); return path; } private static Path createCubic(float controlX1, float controlY1, float controlX2, float controlY2) { final Path path = new Path(); path.moveTo(0.0f, 0.0f); path.cubicTo(controlX1, controlY1, controlX2, controlY2, 1.0f, 1.0f); return path; } } ================================================ FILE: refresh-header/src/main/java/com/scwang/smartrefresh/header/fungame/FunGameBase.java ================================================ package com.scwang.smartrefresh.header.fungame; import android.content.Context; import android.os.Build; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.annotation.RequiresApi; import android.util.AttributeSet; import android.view.MotionEvent; import android.view.View; import android.widget.FrameLayout; import com.scwang.smartrefresh.layout.api.RefreshContent; import com.scwang.smartrefresh.layout.api.RefreshHeader; import com.scwang.smartrefresh.layout.api.RefreshKernel; import com.scwang.smartrefresh.layout.api.RefreshLayout; import com.scwang.smartrefresh.layout.constant.RefreshState; import com.scwang.smartrefresh.layout.constant.SpinnerStyle; import static android.view.MotionEvent.ACTION_MASK; /** * 游戏 header * Created by SCWANG on 2017/6/17. */ public class FunGameBase extends FrameLayout implements RefreshHeader { // protected int mOffset; protected int mHeaderHeight; protected int mScreenHeightPixels; protected float mTouchY; protected boolean mIsFinish; protected boolean mLastFinish; protected boolean mManualOperation; protected RefreshState mState; protected RefreshKernel mRefreshKernel; protected RefreshContent mRefreshContent; // // public FunGameBase(Context context) { super(context); initView(context); } public FunGameBase(Context context, @Nullable AttributeSet attrs) { super(context, attrs); initView(context); } public FunGameBase(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); initView(context); } @RequiresApi(Build.VERSION_CODES.LOLLIPOP) public FunGameBase(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); initView(context); } private void initView(Context context) { mScreenHeightPixels = context.getResources().getDisplayMetrics().heightPixels; } @Override public void setTranslationY(float translationY) { if (!isInEditMode()) { super.setTranslationY(translationY); } } @Override public boolean onInterceptTouchEvent(MotionEvent ev) { return mState == RefreshState.Refreshing || super.onInterceptTouchEvent(ev); } @Override public boolean onTouchEvent(MotionEvent event) { if (mState == RefreshState.Refreshing || mState == RefreshState.RefreshFinish) { if (!mManualOperation) { onManualOperationStart(); } switch (event.getAction() & ACTION_MASK) { case MotionEvent.ACTION_DOWN: mTouchY = event.getRawY(); mRefreshKernel.moveSpinner(0, true); break; case MotionEvent.ACTION_MOVE: float dy = event.getRawY() - mTouchY; if (dy >= 0) { final double M = mHeaderHeight * 2; final double H = mScreenHeightPixels * 2 / 3; final double x = Math.max(0, dy * 0.5); final double y = Math.min(M * (1 - Math.pow(100, -x / H)), x);// 公式 y = M(1-40^(-x/H)) mRefreshKernel.moveSpinner((int) y, false); } else { final double M = mHeaderHeight * 2; final double H = mScreenHeightPixels * 2 / 3; final double x = -Math.min(0, dy * 0.5); final double y = -Math.min(M * (1 - Math.pow(100, -x / H)), x);// 公式 y = M(1-40^(-x/H)) mRefreshKernel.moveSpinner((int) y, false); } break; case MotionEvent.ACTION_UP: case MotionEvent.ACTION_CANCEL: onManualOperationRelease(); mTouchY = -1; if (mIsFinish) { mRefreshKernel.moveSpinner(mHeaderHeight, true); } break; } return true; } return super.onTouchEvent(event); } @Override protected void onDetachedFromWindow() { super.onDetachedFromWindow(); mRefreshKernel = null; mRefreshContent = null; } // // boolean enableLoadmore; protected void onManualOperationStart() { if (!mManualOperation) { mManualOperation = true; mRefreshContent = mRefreshKernel.getRefreshContent(); enableLoadmore = mRefreshKernel.getRefreshLayout().isEnableLoadmore(); mRefreshKernel.getRefreshLayout().setEnableLoadmore(false); View contentView = mRefreshContent.getView(); MarginLayoutParams params = (MarginLayoutParams)contentView.getLayoutParams(); params.topMargin += mHeaderHeight; contentView.setLayoutParams(params); } } protected void onManualOperationMove(float percent, int offset, int headHeight, int extendHeight) { } protected void onManualOperationRelease() { if (mIsFinish) { mManualOperation = false; mRefreshKernel.getRefreshLayout().setEnableLoadmore(enableLoadmore); if (mTouchY != -1) {//还没松手 onFinish(mRefreshKernel.getRefreshLayout(), mLastFinish); mRefreshKernel.setStateRefresingFinish(); mRefreshKernel.animSpinner(0); // mRefreshKernel.getRefreshLayout().finishRefresh(0); } else { mRefreshKernel.moveSpinner(mHeaderHeight, true); } View contentView = mRefreshContent.getView(); MarginLayoutParams params = (MarginLayoutParams)contentView.getLayoutParams(); params.topMargin -= mHeaderHeight; contentView.setLayoutParams(params); } else { mRefreshKernel.moveSpinner(0, true); } } // // @Override public boolean isSupportHorizontalDrag() { return false; } @Override public void onHorizontalDrag(float percentX, int offsetX, int offsetMax) { } @Override public void onPullingDown(float percent, int offset, int headHeight, int extendHeight) { if (mManualOperation) onManualOperationMove(percent, offset, headHeight, extendHeight); else { mOffset = offset; setTranslationY(mOffset - mHeaderHeight); } } @Override public void onReleasing(float percent, int offset, int headHeight, int extendHeight) { onPullingDown(percent, offset, headHeight, extendHeight); } @Override public void onStartAnimator(RefreshLayout layout, int headHeight, int extendHeight) { mIsFinish = false; } @Override public void onStateChanged(RefreshLayout refreshLayout, RefreshState oldState, RefreshState newState) { mState = newState; } @Override public void onInitialized(RefreshKernel kernel, int height, int extendHeight) { mRefreshKernel = kernel; mHeaderHeight = height; setTranslationY(mOffset - mHeaderHeight); kernel.requestHeaderNeedTouchEventWhenRefreshing(true); } @Override public int onFinish(RefreshLayout layout, boolean success) { mLastFinish = success; if (!mIsFinish) { mIsFinish = true; if (mManualOperation) { if (mTouchY == -1) {//已经放手 onManualOperationRelease(); onFinish(layout, success); return 0; } return Integer.MAX_VALUE; } } return 0; } @Override@Deprecated public void setPrimaryColors(int... colors) { } @NonNull @Override public View getView() { return this; } @Override public SpinnerStyle getSpinnerStyle() { return SpinnerStyle.MatchLayout; } // } ================================================ FILE: refresh-header/src/main/java/com/scwang/smartrefresh/header/fungame/FunGameHeader.java ================================================ package com.scwang.smartrefresh.header.fungame; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.AnimatorSet; import android.animation.ObjectAnimator; import android.content.Context; import android.content.res.TypedArray; import android.graphics.Color; import android.os.Build; import android.support.annotation.Nullable; import android.support.annotation.RequiresApi; import android.util.AttributeSet; import android.util.TypedValue; import android.view.Gravity; import android.view.View; import android.widget.RelativeLayout; import android.widget.TextView; import com.scwang.smartrefresh.header.R; import com.scwang.smartrefresh.layout.api.RefreshHeader; import com.scwang.smartrefresh.layout.api.RefreshKernel; import com.scwang.smartrefresh.layout.api.RefreshLayout; import com.scwang.smartrefresh.layout.util.ColorUtils; import com.scwang.smartrefresh.layout.util.DensityUtil; import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; /** * 游戏 header * Created by SCWANG on 2017/6/17. */ public class FunGameHeader extends FunGameBase implements RefreshHeader { // /** * 分割线默认宽度大小 */ protected float DIVIDING_LINE_SIZE = 1.f; private RelativeLayout curtainReLayout, maskReLayout; private TextView topMaskView, bottomMaskView; private int halfHitBlockHeight; private boolean isStart = false; private String topMaskViewText = "下拉即将展开";//"Pull To Break Out!"; private String bottomMaskViewText = "拖动控制游戏";//"Scrooll to move handle"; private int topMaskTextSize; private int bottomMaskTextSize; // // public FunGameHeader(Context context) { super(context); this.initView(context, null); } public FunGameHeader(Context context, @Nullable AttributeSet attrs) { super(context, attrs); this.initView(context, attrs); } public FunGameHeader(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); this.initView(context, attrs); } @RequiresApi(Build.VERSION_CODES.LOLLIPOP) public FunGameHeader(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); this.initView(context, attrs); } private void initView(Context context, AttributeSet attrs) { TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.FunGameHeader); if (ta.hasValue(R.styleable.FunGameHeader_fgvMaskTopText)) { topMaskViewText = ta.getString(R.styleable.FunGameHeader_fgvMaskTopText); } if (ta.hasValue(R.styleable.FunGameHeader_fgvMaskBottomText)) { bottomMaskViewText = ta.getString(R.styleable.FunGameHeader_fgvMaskBottomText); } topMaskTextSize = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 16, getResources().getDisplayMetrics()); bottomMaskTextSize = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 16, getResources().getDisplayMetrics()); topMaskTextSize = ta.getDimensionPixelSize(R.styleable.FunGameHeader_fgvBottomTextSize, topMaskTextSize); bottomMaskTextSize = ta.getDimensionPixelSize(R.styleable.FunGameHeader_fgvBottomTextSize, bottomMaskTextSize); ta.recycle(); curtainReLayout = new RelativeLayout(context); maskReLayout = new RelativeLayout(context); maskReLayout.setBackgroundColor(Color.parseColor("#3A3A3A")); topMaskView = createMaskTextView(context,topMaskViewText, topMaskTextSize, Gravity.BOTTOM); bottomMaskView = createMaskTextView(context,bottomMaskViewText, bottomMaskTextSize, Gravity.TOP); DIVIDING_LINE_SIZE = Math.max(1, DensityUtil.dp2px(0.5f)); } private TextView createMaskTextView(Context context, String text, int textSize, int gravity) { TextView maskTextView = new TextView(context); maskTextView.setTextColor(Color.BLACK); maskTextView.setBackgroundColor(Color.WHITE); maskTextView.setGravity(gravity | Gravity.CENTER_HORIZONTAL); maskTextView.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize); maskTextView.setText(text); return maskTextView; } private void coverMaskView() { if (getChildCount() < 2 && !isInEditMode()) { LayoutParams maskLp = new LayoutParams(MATCH_PARENT,mHeaderHeight); // maskLp.topMargin = (int) FunGameView.DIVIDING_LINE_SIZE; // maskLp.bottomMargin = (int) FunGameView.DIVIDING_LINE_SIZE; addView(maskReLayout, maskLp); addView(curtainReLayout, maskLp); halfHitBlockHeight = (int) ((mHeaderHeight/* - 2 * DIVIDING_LINE_SIZE*/) * .5f); RelativeLayout.LayoutParams topRelayLayoutParams = new RelativeLayout.LayoutParams(MATCH_PARENT, halfHitBlockHeight); RelativeLayout.LayoutParams bottomRelayLayoutParams = new RelativeLayout.LayoutParams(MATCH_PARENT, halfHitBlockHeight); bottomRelayLayoutParams.topMargin = mHeaderHeight - halfHitBlockHeight; curtainReLayout.addView(topMaskView, topRelayLayoutParams); curtainReLayout.addView(bottomMaskView, bottomRelayLayoutParams); } } private void doStart(long delay) { ObjectAnimator topMaskAnimator = ObjectAnimator.ofFloat(topMaskView, "translationY", topMaskView.getTranslationY(), -halfHitBlockHeight); ObjectAnimator bottomMaskAnimator = ObjectAnimator.ofFloat(bottomMaskView, "translationY", bottomMaskView.getTranslationY(), halfHitBlockHeight); ObjectAnimator maskShadowAnimator = ObjectAnimator.ofFloat(maskReLayout, "alpha", maskReLayout.getAlpha(), 0); AnimatorSet animatorSet = new AnimatorSet(); animatorSet.play(topMaskAnimator).with(bottomMaskAnimator).with(maskShadowAnimator); animatorSet.setDuration(800); animatorSet.setStartDelay(delay); animatorSet.start(); animatorSet.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { topMaskView.setVisibility(View.GONE); bottomMaskView.setVisibility(View.GONE); maskReLayout.setVisibility(View.GONE); onGameStart(); } }); } protected void onGameStart() { } public void postStart() { if (!isStart) { doStart(200); isStart = true; } } public void postEnd() { isStart = false; topMaskView.setTranslationY(topMaskView.getTranslationY() + halfHitBlockHeight); bottomMaskView.setTranslationY(bottomMaskView.getTranslationY() - halfHitBlockHeight); maskReLayout.setAlpha(1.f); topMaskView.setVisibility(View.VISIBLE); bottomMaskView.setVisibility(View.VISIBLE); maskReLayout.setVisibility(View.VISIBLE); } public void setTopMaskViewText(String topMaskViewText) { this.topMaskViewText = topMaskViewText; topMaskView.setText(topMaskViewText); } public void setBottomMaskViewText(String bottomMaskViewText) { this.bottomMaskViewText = bottomMaskViewText; bottomMaskView.setText(bottomMaskViewText); } // @Override@Deprecated public void setPrimaryColors(int... colors) { super.setPrimaryColors(colors); if (colors.length > 0) { topMaskView.setTextColor(colors[0]); bottomMaskView.setTextColor(colors[0]); if (colors.length > 1) { maskReLayout.setBackgroundColor(ColorUtils.setAlphaComponent(colors[1],200)); topMaskView.setBackgroundColor(ColorUtils.setAlphaComponent(colors[1],200)); bottomMaskView.setBackgroundColor(ColorUtils.setAlphaComponent(colors[1],200)); } } } @Override public void onInitialized(RefreshKernel kernel, int height, int extendHeight) { super.onInitialized(kernel, height, extendHeight); coverMaskView(); } @Override public void onStartAnimator(RefreshLayout layout, int headHeight, int extendHeight) { super.onStartAnimator(layout, headHeight, extendHeight); postStart(); } @Override public int onFinish(RefreshLayout layout, boolean success) { if (!mManualOperation) { postEnd(); } return super.onFinish(layout, success); } // } ================================================ FILE: refresh-header/src/main/java/com/scwang/smartrefresh/header/fungame/FunGameView.java ================================================ package com.scwang.smartrefresh.header.fungame; import android.content.Context; import android.content.res.TypedArray; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.os.Build; import android.support.annotation.Nullable; import android.support.annotation.RequiresApi; import android.text.TextPaint; import android.util.AttributeSet; import android.util.DisplayMetrics; import android.view.WindowManager; import com.scwang.smartrefresh.header.R; import com.scwang.smartrefresh.layout.api.RefreshKernel; import com.scwang.smartrefresh.layout.api.RefreshLayout; import com.scwang.smartrefresh.layout.util.ColorUtils; import com.scwang.smartrefresh.layout.util.DensityUtil; /** * Created by Hitomis on 2016/3/9. * email:196425254@qq.com */ public abstract class FunGameView extends FunGameHeader { // protected static final int STATUS_GAME_PREPAR = 0; protected static final int STATUS_GAME_PLAY = 1; protected static final int STATUS_GAME_OVER = 2; protected static final int STATUS_GAME_FINISHED = 3; protected static final int STATUS_GAME_FAIL = 4; /** * 控件高度占屏幕高度比率 */ protected static final float VIEW_HEIGHT_RATIO = .161f; public static String textGameOver = "游戏结束"; public static String textLoading = "玩个游戏解解闷"; public static String textLoadingFinished = "刷新完成"; public static String textLoadingFail = "刷新失败"; // private String loadingText = "Loading..."; // private String loadingFinishedText = "Loading Finished"; // private String gameOverText = "Game Over"; protected Paint mPaint; protected TextPaint textPaint; protected float controllerPosition; protected int controllerSize; protected int status = STATUS_GAME_PREPAR; protected int lModelColor, rModelColor, mModelColor; protected int mBackColor, mBoundaryColor = 0xff606060; // public FunGameView(Context context) { super(context); this.initView(context, null); } public FunGameView(Context context, @Nullable AttributeSet attrs) { super(context, attrs); this.initView(context, attrs); } public FunGameView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); this.initView(context, attrs); } @RequiresApi(Build.VERSION_CODES.LOLLIPOP) public FunGameView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); this.initView(context, attrs); } private void initView(Context context, AttributeSet attrs) { TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.FunGameView); mBackColor = ta.getColor(R.styleable.FunGameView_fgvBackColor, 0); lModelColor = ta.getColor(R.styleable.FunGameView_fgvLeftColor, Color.rgb(0, 0, 0)); mModelColor = ta.getColor(R.styleable.FunGameView_fgvMiddleColor, Color.BLACK); rModelColor = ta.getColor(R.styleable.FunGameView_fgvRightColor, Color.parseColor("#A5A5A5")); if (ta.hasValue(R.styleable.FunGameView_fgvTextGameOver)) { textGameOver = ta.getString(R.styleable.FunGameView_fgvTextGameOver); } if (ta.hasValue(R.styleable.FunGameView_fgvTextGameOver)) { textLoading = ta.getString(R.styleable.FunGameView_fgvTextLoading); } if (ta.hasValue(R.styleable.FunGameView_fgvTextGameOver)) { textLoadingFinished = ta.getString(R.styleable.FunGameView_fgvTextLoadingFinished); } ta.recycle(); initBaseTools(); initBaseConfigParams(); initConcreteView(); } protected void initBaseTools() { textPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG); textPaint.setColor(Color.parseColor("#C1C2C2")); mPaint = new Paint(Paint.ANTI_ALIAS_FLAG); mPaint.setStrokeWidth(DIVIDING_LINE_SIZE); } protected void initBaseConfigParams() { controllerPosition = DIVIDING_LINE_SIZE; } protected abstract void initConcreteView(); protected abstract void drawGame(Canvas canvas, int width, int height); protected abstract void resetConfigParams(); /** * 绘制分割线 * @param canvas 默认画布 */ private void drawBoundary(Canvas canvas,int width,int height) { mPaint.setColor(mBackColor); canvas.drawRect(0, 0, width, height, mPaint); mPaint.setColor(mBoundaryColor); canvas.drawLine(0, 0, width, 0, mPaint); canvas.drawLine(0, height - DIVIDING_LINE_SIZE, width, height - DIVIDING_LINE_SIZE, mPaint); } @Override protected void dispatchDraw(Canvas canvas) { final int width = getWidth(); final int height = mHeaderHeight; drawBoundary(canvas, width, height); drawText(canvas, width, height); drawGame(canvas, width, height); super.dispatchDraw(canvas); } /** * 绘制文字内容 * @param canvas 默认画布 */ private void drawText(Canvas canvas, int width, int height) { switch (status) { case STATUS_GAME_PREPAR: case STATUS_GAME_PLAY: textPaint.setTextSize(DensityUtil.dp2px(25)); promptText(canvas, textLoading, width, height); break; case STATUS_GAME_FINISHED: textPaint.setTextSize(DensityUtil.dp2px(20)); promptText(canvas, textLoadingFinished, width, height); break; case STATUS_GAME_FAIL: textPaint.setTextSize(DensityUtil.dp2px(20)); promptText(canvas, textLoadingFail, width, height); break; case STATUS_GAME_OVER: textPaint.setTextSize(DensityUtil.dp2px(25)); promptText(canvas, textGameOver, width, height); break; } } /** * 提示文字信息 * @param canvas 默认画布 * @param text 相关文字字符串 */ private void promptText(Canvas canvas, String text, int width, int height) { float textX = (width - textPaint.measureText(text)) * .5f; float textY = height * .5f - (textPaint.ascent() + textPaint.descent()) * .5f; canvas.drawText(text, textX, textY, textPaint); } /** * 获取当前控件状态 */ public int getCurrStatus() { return status; } public String getTextGameOver() { return textGameOver; } public void setTextGameOver(String textGameOver) { this.textGameOver = textGameOver; } public String getTextLoading() { return textLoading; } public void setTextLoading(String textLoading) { this.textLoading = textLoading; } public String getTextLoadingFinished() { return textLoadingFinished; } public void setTextLoadingFinished(String textLoadingFinished) { this.textLoadingFinished = textLoadingFinished; } /** * 获取屏幕尺寸 * * @param context context * @return 手机屏幕尺寸 */ private DisplayMetrics getScreenMetrics(Context context) { WindowManager manager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); DisplayMetrics dm = new DisplayMetrics(); manager.getDefaultDisplay().getMetrics(dm); return dm; } // @Override protected void onGameStart() { postStatus(FunGameView.STATUS_GAME_PLAY); } @Override protected void onManualOperationMove(float percent, int offset, int headHeight, int extendHeight) { moveController(Math.max(offset, 0)); } /** * 移动控制器(控制器对象为具体控件中的右边图像模型) * @param distance 移动的距离 */ public void moveController(float distance) { float maxDistance = (mHeaderHeight - 2 * DIVIDING_LINE_SIZE - controllerSize); if (distance > maxDistance) { distance = maxDistance; } controllerPosition = distance; postInvalidate(); } /** * 更新当前控件状态 * @param status 状态码 */ public void postStatus(int status) { this.status = status; if (status == STATUS_GAME_PREPAR) { resetConfigParams(); } postInvalidate(); } // // @Override public void onInitialized(RefreshKernel kernel, int height, int extendHeight) { super.onInitialized(kernel, height, extendHeight); initConcreteView(); postStatus(STATUS_GAME_PREPAR); } @Override public int onFinish(RefreshLayout layout, boolean success) { if (mManualOperation) { postStatus(success ? FunGameView.STATUS_GAME_FINISHED : FunGameView.STATUS_GAME_FAIL); } else { postStatus(FunGameView.STATUS_GAME_PREPAR); } return super.onFinish(layout, success); } @Override@Deprecated public void setPrimaryColors(int... colors) { super.setPrimaryColors(colors); if (colors.length > 0) { mBoundaryColor = mBackColor = colors[0]; if (mBackColor == 0 || mBackColor == 0xffffffff) { mBoundaryColor = 0xff606060; } if (colors.length > 1) { mModelColor = colors[1]; lModelColor = ColorUtils.setAlphaComponent(colors[1], 225); rModelColor = ColorUtils.setAlphaComponent(colors[1], 200); textPaint.setColor(ColorUtils.setAlphaComponent(colors[1], 150)); } } } // } ================================================ FILE: refresh-header/src/main/java/com/scwang/smartrefresh/header/internal/FastOutSlowInInterpolator.java ================================================ /* * Copyright (C) 2015 The Android Open Source Project * * 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.scwang.smartrefresh.header.internal; /** * Interpolator corresponding to {@link android.R.interpolator#fast_out_slow_in}. * * Uses a lookup table for the Bezier curve from (0,0) to (1,1) with control points: * P0 (0, 0) * P1 (0.4, 0) * P2 (0.2, 1.0) * P3 (1.0, 1.0) */ public class FastOutSlowInInterpolator extends LookupTableInterpolator { /** * Lookup table values sampled with x at regular intervals between 0 and 1 for a total of * 201 points. */ private static final float[] VALUES = new float[] { 0.0000f, 0.0001f, 0.0002f, 0.0005f, 0.0009f, 0.0014f, 0.0020f, 0.0027f, 0.0036f, 0.0046f, 0.0058f, 0.0071f, 0.0085f, 0.0101f, 0.0118f, 0.0137f, 0.0158f, 0.0180f, 0.0205f, 0.0231f, 0.0259f, 0.0289f, 0.0321f, 0.0355f, 0.0391f, 0.0430f, 0.0471f, 0.0514f, 0.0560f, 0.0608f, 0.0660f, 0.0714f, 0.0771f, 0.0830f, 0.0893f, 0.0959f, 0.1029f, 0.1101f, 0.1177f, 0.1257f, 0.1339f, 0.1426f, 0.1516f, 0.1610f, 0.1707f, 0.1808f, 0.1913f, 0.2021f, 0.2133f, 0.2248f, 0.2366f, 0.2487f, 0.2611f, 0.2738f, 0.2867f, 0.2998f, 0.3131f, 0.3265f, 0.3400f, 0.3536f, 0.3673f, 0.3810f, 0.3946f, 0.4082f, 0.4217f, 0.4352f, 0.4485f, 0.4616f, 0.4746f, 0.4874f, 0.5000f, 0.5124f, 0.5246f, 0.5365f, 0.5482f, 0.5597f, 0.5710f, 0.5820f, 0.5928f, 0.6033f, 0.6136f, 0.6237f, 0.6335f, 0.6431f, 0.6525f, 0.6616f, 0.6706f, 0.6793f, 0.6878f, 0.6961f, 0.7043f, 0.7122f, 0.7199f, 0.7275f, 0.7349f, 0.7421f, 0.7491f, 0.7559f, 0.7626f, 0.7692f, 0.7756f, 0.7818f, 0.7879f, 0.7938f, 0.7996f, 0.8053f, 0.8108f, 0.8162f, 0.8215f, 0.8266f, 0.8317f, 0.8366f, 0.8414f, 0.8461f, 0.8507f, 0.8551f, 0.8595f, 0.8638f, 0.8679f, 0.8720f, 0.8760f, 0.8798f, 0.8836f, 0.8873f, 0.8909f, 0.8945f, 0.8979f, 0.9013f, 0.9046f, 0.9078f, 0.9109f, 0.9139f, 0.9169f, 0.9198f, 0.9227f, 0.9254f, 0.9281f, 0.9307f, 0.9333f, 0.9358f, 0.9382f, 0.9406f, 0.9429f, 0.9452f, 0.9474f, 0.9495f, 0.9516f, 0.9536f, 0.9556f, 0.9575f, 0.9594f, 0.9612f, 0.9629f, 0.9646f, 0.9663f, 0.9679f, 0.9695f, 0.9710f, 0.9725f, 0.9739f, 0.9753f, 0.9766f, 0.9779f, 0.9791f, 0.9803f, 0.9815f, 0.9826f, 0.9837f, 0.9848f, 0.9858f, 0.9867f, 0.9877f, 0.9885f, 0.9894f, 0.9902f, 0.9910f, 0.9917f, 0.9924f, 0.9931f, 0.9937f, 0.9944f, 0.9949f, 0.9955f, 0.9960f, 0.9964f, 0.9969f, 0.9973f, 0.9977f, 0.9980f, 0.9984f, 0.9986f, 0.9989f, 0.9991f, 0.9993f, 0.9995f, 0.9997f, 0.9998f, 0.9999f, 0.9999f, 1.0000f, 1.0000f }; public FastOutSlowInInterpolator() { super(VALUES); } } ================================================ FILE: refresh-header/src/main/java/com/scwang/smartrefresh/header/internal/LookupTableInterpolator.java ================================================ /* * Copyright (C) 2015 The Android Open Source Project * * 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.scwang.smartrefresh.header.internal; import android.view.animation.Interpolator; /** * An {@link Interpolator} that uses a lookup table to compute an interpolation based on a * given input. */ abstract class LookupTableInterpolator implements Interpolator { private final float[] mValues; private final float mStepSize; public LookupTableInterpolator(float[] values) { mValues = values; mStepSize = 1f / (mValues.length - 1); } @Override public float getInterpolation(float input) { if (input >= 1.0f) { return 1.0f; } if (input <= 0f) { return 0f; } // Calculate index - We use min with length - 2 to avoid IndexOutOfBoundsException when // we lerp (linearly interpolate) in the return statement int position = Math.min((int) (input * (mValues.length - 1)), mValues.length - 2); // Calculate values to account for small offsets as the lookup table has discrete values float quantized = position * mStepSize; float diff = input - quantized; float weight = diff / mStepSize; // Linearly interpolate between the table values return mValues[position] + weight * (mValues[position + 1] - mValues[position]); } } ================================================ FILE: refresh-header/src/main/java/com/scwang/smartrefresh/header/internal/MaterialProgressDrawable.java ================================================ /* * Copyright (C) 2014 The Android Open Source Project * * 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.scwang.smartrefresh.header.internal; import android.content.Context; import android.content.res.Resources; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.ColorFilter; import android.graphics.Paint; import android.graphics.Paint.Style; import android.graphics.Path; import android.graphics.PixelFormat; import android.graphics.Rect; import android.graphics.RectF; import android.graphics.drawable.Animatable; import android.graphics.drawable.Drawable; import android.support.annotation.IntDef; import android.support.annotation.NonNull; import android.util.DisplayMetrics; import android.view.View; import android.view.animation.Animation; import android.view.animation.Interpolator; import android.view.animation.LinearInterpolator; import android.view.animation.Transformation; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; /** * Fancy progress indicator for Material theme. */ public class MaterialProgressDrawable extends Drawable implements Animatable { private static final Interpolator LINEAR_INTERPOLATOR = new LinearInterpolator(); static final Interpolator MATERIAL_INTERPOLATOR = new FastOutSlowInInterpolator(); private static final float FULL_ROTATION = 1080.0f; @Retention(RetentionPolicy.SOURCE) @IntDef({LARGE, DEFAULT}) public @interface ProgressDrawableSize {} // Maps to ProgressBar.Large style public static final int LARGE = 0; // Maps to ProgressBar default style public static final int DEFAULT = 1; // Maps to ProgressBar default style private static final int CIRCLE_DIAMETER = 40; private static final float CENTER_RADIUS = 8.75f; //should add up to 10 when + stroke_width private static final float STROKE_WIDTH = 2.5f; // Maps to ProgressBar.Large style private static final int CIRCLE_DIAMETER_LARGE = 56; private static final float CENTER_RADIUS_LARGE = 12.5f; private static final float STROKE_WIDTH_LARGE = 3f; private static final int[] COLORS = new int[] { Color.BLACK }; /** * The value in the linear interpolator for animating the drawable at which * the color transition should start */ private static final float COLOR_START_DELAY_OFFSET = 0.75f; private static final float END_TRIM_START_DELAY_OFFSET = 0.5f; private static final float START_TRIM_DURATION_OFFSET = 0.5f; /** The duration of a single progress spin in milliseconds. */ private static final int ANIMATION_DURATION = 1332; /** The number of points in the progress "star". */ private static final float NUM_POINTS = 5f; /** The list of animators operating on this drawable. */ private final ArrayList mAnimators = new ArrayList(); /** The indicator ring, used to manage animation state. */ private final Ring mRing; /** Canvas rotation in degrees. */ private float mRotation; /** Layout info for the arrowhead in dp */ private static final int ARROW_WIDTH = 10; private static final int ARROW_HEIGHT = 5; private static final float ARROW_OFFSET_ANGLE = 5; /** Layout info for the arrowhead for the large spinner in dp */ private static final int ARROW_WIDTH_LARGE = 12; private static final int ARROW_HEIGHT_LARGE = 6; private static final float MAX_PROGRESS_ARC = .8f; private Resources mResources; private View mParent; private Animation mAnimation; float mRotationCount; private double mWidth; private double mHeight; boolean mFinishing; public MaterialProgressDrawable(Context context, View parent) { mParent = parent; mResources = context.getResources(); mRing = new Ring(mCallback); mRing.setColors(COLORS); updateSizes(DEFAULT); setupAnimators(); } private void setSizeParameters(double progressCircleWidth, double progressCircleHeight, double centerRadius, double strokeWidth, float arrowWidth, float arrowHeight) { final Ring ring = mRing; final DisplayMetrics metrics = mResources.getDisplayMetrics(); final float screenDensity = metrics.density; mWidth = progressCircleWidth * screenDensity; mHeight = progressCircleHeight * screenDensity; ring.setStrokeWidth((float) strokeWidth * screenDensity); ring.setCenterRadius(centerRadius * screenDensity); ring.setColorIndex(0); ring.setArrowDimensions(arrowWidth * screenDensity, arrowHeight * screenDensity); ring.setInsets((int) mWidth, (int) mHeight); } /** * Set the overall size for the progress spinner. This updates the radius * and stroke width of the ring. */ public void updateSizes(@ProgressDrawableSize int size) { if (size == LARGE) { setSizeParameters(CIRCLE_DIAMETER_LARGE, CIRCLE_DIAMETER_LARGE, CENTER_RADIUS_LARGE, STROKE_WIDTH_LARGE, ARROW_WIDTH_LARGE, ARROW_HEIGHT_LARGE); } else { setSizeParameters(CIRCLE_DIAMETER, CIRCLE_DIAMETER, CENTER_RADIUS, STROKE_WIDTH, ARROW_WIDTH, ARROW_HEIGHT); } } /** * @param show Set to true to display the arrowhead on the progress spinner. */ public void showArrow(boolean show) { mRing.setShowArrow(show); } /** * @param scale Set the scale of the arrowhead for the spinner. */ public void setArrowScale(float scale) { mRing.setArrowScale(scale); } /** * Set the start and end trim for the progress spinner arc. * * @param startAngle start angle * @param endAngle end angle */ public void setStartEndTrim(float startAngle, float endAngle) { mRing.setStartTrim(startAngle); mRing.setEndTrim(endAngle); } /** * Set the amount of rotation to apply to the progress spinner. * * @param rotation Rotation is from [0..1] */ public void setProgressRotation(float rotation) { mRing.setRotation(rotation); } /** * Update the background color of the circle image view. */ public void setBackgroundColor(int color) { mRing.setBackgroundColor(color); } /** * Set the colors used in the progress animation from color resources. * The first color will also be the color of the bar that grows in response * to a user swipe gesture. * * @param colors */ public void setColorSchemeColors(int... colors) { mRing.setColors(colors); mRing.setColorIndex(0); } @Override public int getIntrinsicHeight() { return (int) mHeight; } @Override public int getIntrinsicWidth() { return (int) mWidth; } @Override public void draw(Canvas c) { final Rect bounds = getBounds(); final int saveCount = c.save(); c.rotate(mRotation, bounds.exactCenterX(), bounds.exactCenterY()); mRing.draw(c, bounds); c.restoreToCount(saveCount); } @Override public void setAlpha(int alpha) { mRing.setAlpha(alpha); } public int getAlpha() { return mRing.getAlpha(); } @Override public void setColorFilter(ColorFilter colorFilter) { mRing.setColorFilter(colorFilter); } @SuppressWarnings("unused") void setRotation(float rotation) { mRotation = rotation; invalidateSelf(); } @SuppressWarnings("unused") private float getRotation() { return mRotation; } @Override public int getOpacity() { return PixelFormat.TRANSLUCENT; } @Override public boolean isRunning() { final ArrayList animators = mAnimators; final int N = animators.size(); for (int i = 0; i < N; i++) { final Animation animator = animators.get(i); if (animator.hasStarted() && !animator.hasEnded()) { return true; } } return false; } @Override public void start() { mAnimation.reset(); mRing.storeOriginals(); // Already showing some part of the ring if (mRing.getEndTrim() != mRing.getStartTrim()) { mFinishing = true; mAnimation.setDuration(ANIMATION_DURATION / 2); mParent.startAnimation(mAnimation); } else { mRing.setColorIndex(0); mRing.resetOriginals(); mAnimation.setDuration(ANIMATION_DURATION); mParent.startAnimation(mAnimation); } } @Override public void stop() { mParent.clearAnimation(); setRotation(0); mRing.setShowArrow(false); mRing.setColorIndex(0); mRing.resetOriginals(); } float getMinProgressArc(Ring ring) { return (float) Math.toRadians( ring.getStrokeWidth() / (2 * Math.PI * ring.getCenterRadius())); } // Adapted from ArgbEvaluator.java private int evaluateColorChange(float fraction, int startValue, int endValue) { int startInt = (Integer) startValue; int startA = (startInt >> 24) & 0xff; int startR = (startInt >> 16) & 0xff; int startG = (startInt >> 8) & 0xff; int startB = startInt & 0xff; int endInt = (Integer) endValue; int endA = (endInt >> 24) & 0xff; int endR = (endInt >> 16) & 0xff; int endG = (endInt >> 8) & 0xff; int endB = endInt & 0xff; return (int) ((startA + (int) (fraction * (endA - startA))) << 24) | (int) ((startR + (int) (fraction * (endR - startR))) << 16) | (int) ((startG + (int) (fraction * (endG - startG))) << 8) | (int) ((startB + (int) (fraction * (endB - startB)))); } /** * Update the ring color if this is within the last 25% of the animation. * The new ring color will be a translation from the starting ring color to * the next color. */ void updateRingColor(float interpolatedTime, Ring ring) { if (interpolatedTime > COLOR_START_DELAY_OFFSET) { // scale the interpolatedTime so that the full // transformation from 0 - 1 takes place in the // remaining time ring.setColor(evaluateColorChange((interpolatedTime - COLOR_START_DELAY_OFFSET) / (1.0f - COLOR_START_DELAY_OFFSET), ring.getStartingColor(), ring.getNextColor())); } } void applyFinishTranslation(float interpolatedTime, Ring ring) { // shrink back down and complete a full rotation before // starting other circles // Rotation goes between [0..1]. updateRingColor(interpolatedTime, ring); float targetRotation = (float) (Math.floor(ring.getStartingRotation() / MAX_PROGRESS_ARC) + 1f); final float minProgressArc = getMinProgressArc(ring); final float startTrim = ring.getStartingStartTrim() + (ring.getStartingEndTrim() - minProgressArc - ring.getStartingStartTrim()) * interpolatedTime; ring.setStartTrim(startTrim); ring.setEndTrim(ring.getStartingEndTrim()); final float rotation = ring.getStartingRotation() + ((targetRotation - ring.getStartingRotation()) * interpolatedTime); ring.setRotation(rotation); } private void setupAnimators() { final Ring ring = mRing; final Animation animation = new Animation() { @Override public void applyTransformation(float interpolatedTime, Transformation t) { if (mFinishing) { applyFinishTranslation(interpolatedTime, ring); } else { // The minProgressArc is calculated from 0 to create an // angle that matches the stroke width. final float minProgressArc = getMinProgressArc(ring); final float startingEndTrim = ring.getStartingEndTrim(); final float startingTrim = ring.getStartingStartTrim(); final float startingRotation = ring.getStartingRotation(); updateRingColor(interpolatedTime, ring); // Moving the start trim only occurs in the first 50% of a // single ring animation if (interpolatedTime <= START_TRIM_DURATION_OFFSET) { // scale the interpolatedTime so that the full // transformation from 0 - 1 takes place in the // remaining time final float scaledTime = (interpolatedTime) / (1.0f - START_TRIM_DURATION_OFFSET); final float startTrim = startingTrim + ((MAX_PROGRESS_ARC - minProgressArc) * MATERIAL_INTERPOLATOR .getInterpolation(scaledTime)); ring.setStartTrim(startTrim); } // Moving the end trim starts after 50% of a single ring // animation completes if (interpolatedTime > END_TRIM_START_DELAY_OFFSET) { // scale the interpolatedTime so that the full // transformation from 0 - 1 takes place in the // remaining time final float minArc = MAX_PROGRESS_ARC - minProgressArc; float scaledTime = (interpolatedTime - START_TRIM_DURATION_OFFSET) / (1.0f - START_TRIM_DURATION_OFFSET); final float endTrim = startingEndTrim + (minArc * MATERIAL_INTERPOLATOR.getInterpolation(scaledTime)); ring.setEndTrim(endTrim); } final float rotation = startingRotation + (0.25f * interpolatedTime); ring.setRotation(rotation); float groupRotation = ((FULL_ROTATION / NUM_POINTS) * interpolatedTime) + (FULL_ROTATION * (mRotationCount / NUM_POINTS)); setRotation(groupRotation); } } }; animation.setRepeatCount(Animation.INFINITE); animation.setRepeatMode(Animation.RESTART); animation.setInterpolator(LINEAR_INTERPOLATOR); animation.setAnimationListener(new Animation.AnimationListener() { @Override public void onAnimationStart(Animation animation) { mRotationCount = 0; } @Override public void onAnimationEnd(Animation animation) { // do nothing } @Override public void onAnimationRepeat(Animation animation) { ring.storeOriginals(); ring.goToNextColor(); ring.setStartTrim(ring.getEndTrim()); if (mFinishing) { // finished closing the last ring from the swipe gesture; go // into progress mode mFinishing = false; animation.setDuration(ANIMATION_DURATION); ring.setShowArrow(false); } else { mRotationCount = (mRotationCount + 1) % (NUM_POINTS); } } }); mAnimation = animation; } private final Callback mCallback = new Callback() { @Override public void invalidateDrawable(Drawable d) { invalidateSelf(); } @Override public void scheduleDrawable(Drawable d, Runnable what, long when) { scheduleSelf(what, when); } @Override public void unscheduleDrawable(Drawable d, Runnable what) { unscheduleSelf(what); } }; private static class Ring { private final RectF mTempBounds = new RectF(); private final Paint mPaint = new Paint(); private final Paint mArrowPaint = new Paint(); private final Callback mCallback; private float mStartTrim = 0.0f; private float mEndTrim = 0.0f; private float mRotation = 0.0f; private float mStrokeWidth = 5.0f; private float mStrokeInset = 2.5f; private int[] mColors; // mColorIndex represents the offset into the available mColors that the // progress circle should currently display. As the progress circle is // animating, the mColorIndex moves by one to the next available color. private int mColorIndex; private float mStartingStartTrim; private float mStartingEndTrim; private float mStartingRotation; private boolean mShowArrow; private Path mArrow; private float mArrowScale; private double mRingCenterRadius; private int mArrowWidth; private int mArrowHeight; private int mAlpha; private final Paint mCirclePaint = new Paint(Paint.ANTI_ALIAS_FLAG); private int mBackgroundColor; private int mCurrentColor; Ring(Callback callback) { mCallback = callback; mPaint.setStrokeCap(Paint.Cap.SQUARE); mPaint.setAntiAlias(true); mPaint.setStyle(Style.STROKE); mArrowPaint.setStyle(Style.FILL); mArrowPaint.setAntiAlias(true); } public void setBackgroundColor(int color) { mBackgroundColor = color; } /** * Set the dimensions of the arrowhead. * * @param width Width of the hypotenuse of the arrow head * @param height Height of the arrow point */ public void setArrowDimensions(float width, float height) { mArrowWidth = (int) width; mArrowHeight = (int) height; } /** * Draw the progress spinner */ public void draw(Canvas c, Rect bounds) { final RectF arcBounds = mTempBounds; arcBounds.set(bounds); arcBounds.inset(mStrokeInset, mStrokeInset); final float startAngle = (mStartTrim + mRotation) * 360; final float endAngle = (mEndTrim + mRotation) * 360; float sweepAngle = endAngle - startAngle; if (sweepAngle != 0) { mPaint.setColor(mCurrentColor); c.drawArc(arcBounds, startAngle, sweepAngle, false, mPaint); } drawTriangle(c, startAngle, sweepAngle, bounds); if (mAlpha < 255) { mCirclePaint.setColor(mBackgroundColor); mCirclePaint.setAlpha(255 - mAlpha); c.drawCircle(bounds.exactCenterX(), bounds.exactCenterY(), bounds.width() / 2, mCirclePaint); } } private void drawTriangle(Canvas c, float startAngle, float sweepAngle, Rect bounds) { if (mShowArrow) { if (mArrow == null) { mArrow = new Path(); mArrow.setFillType(Path.FillType.EVEN_ODD); } else { mArrow.reset(); } // Adjust the position of the triangle so that it is inset as // much as the arc, but also centered on the arc. float inset = (int) mStrokeInset / 2 * mArrowScale; float x = (float) (mRingCenterRadius * Math.cos(0) + bounds.exactCenterX()); float y = (float) (mRingCenterRadius * Math.sin(0) + bounds.exactCenterY()); // Update the path each time. This works around an issue in SKIA // where concatenating a rotation matrix to a scale matrix // ignored a starting negative rotation. This appears to have // been fixed as of API 21. mArrow.moveTo(0, 0); mArrow.lineTo(mArrowWidth * mArrowScale, 0); mArrow.lineTo((mArrowWidth * mArrowScale / 2), (mArrowHeight * mArrowScale)); mArrow.offset(x - inset, y); mArrow.close(); // draw a triangle mArrowPaint.setColor(mCurrentColor); c.rotate(startAngle + sweepAngle - ARROW_OFFSET_ANGLE, bounds.exactCenterX(), bounds.exactCenterY()); c.drawPath(mArrow, mArrowPaint); } } /** * Set the colors the progress spinner alternates between. * * @param colors Array of integers describing the colors. Must be non-null. */ public void setColors(@NonNull int[] colors) { mColors = colors; // if colors are reset, make sure to reset the color index as well setColorIndex(0); } /** * Set the absolute color of the progress spinner. This is should only * be used when animating between current and next color when the * spinner is rotating. * * @param color int describing the color. */ public void setColor(int color) { mCurrentColor = color; } /** * @param index Index into the color array of the color to display in * the progress spinner. */ public void setColorIndex(int index) { mColorIndex = index; mCurrentColor = mColors[mColorIndex]; } /** * @return int describing the next color the progress spinner should use when drawing. */ public int getNextColor() { return mColors[getNextColorIndex()]; } private int getNextColorIndex() { return (mColorIndex + 1) % (mColors.length); } /** * Proceed to the next available ring color. This will automatically * wrap back to the beginning of colors. */ public void goToNextColor() { setColorIndex(getNextColorIndex()); } public void setColorFilter(ColorFilter filter) { mPaint.setColorFilter(filter); invalidateSelf(); } /** * @param alpha Set the alpha of the progress spinner and associated arrowhead. */ public void setAlpha(int alpha) { mAlpha = alpha; } /** * @return Current alpha of the progress spinner and arrowhead. */ public int getAlpha() { return mAlpha; } /** * @param strokeWidth Set the stroke width of the progress spinner in pixels. */ public void setStrokeWidth(float strokeWidth) { mStrokeWidth = strokeWidth; mPaint.setStrokeWidth(strokeWidth); invalidateSelf(); } @SuppressWarnings("unused") public float getStrokeWidth() { return mStrokeWidth; } @SuppressWarnings("unused") public void setStartTrim(float startTrim) { mStartTrim = startTrim; invalidateSelf(); } @SuppressWarnings("unused") public float getStartTrim() { return mStartTrim; } public float getStartingStartTrim() { return mStartingStartTrim; } public float getStartingEndTrim() { return mStartingEndTrim; } public int getStartingColor() { return mColors[mColorIndex]; } @SuppressWarnings("unused") public void setEndTrim(float endTrim) { mEndTrim = endTrim; invalidateSelf(); } @SuppressWarnings("unused") public float getEndTrim() { return mEndTrim; } @SuppressWarnings("unused") public void setRotation(float rotation) { mRotation = rotation; invalidateSelf(); } @SuppressWarnings("unused") public float getRotation() { return mRotation; } public void setInsets(int width, int height) { final float minEdge = (float) Math.min(width, height); float insets; if (mRingCenterRadius <= 0 || minEdge < 0) { insets = (float) Math.ceil(mStrokeWidth / 2.0f); } else { insets = (float) (minEdge / 2.0f - mRingCenterRadius); } mStrokeInset = insets; } @SuppressWarnings("unused") public float getInsets() { return mStrokeInset; } /** * @param centerRadius Inner radius in px of the circle the progress * spinner arc traces. */ public void setCenterRadius(double centerRadius) { mRingCenterRadius = centerRadius; } public double getCenterRadius() { return mRingCenterRadius; } /** * @param show Set to true to show the arrow head on the progress spinner. */ public void setShowArrow(boolean show) { if (mShowArrow != show) { mShowArrow = show; invalidateSelf(); } } /** * @param scale Set the scale of the arrowhead for the spinner. */ public void setArrowScale(float scale) { if (scale != mArrowScale) { mArrowScale = scale; invalidateSelf(); } } /** * @return The amount the progress spinner is currently rotated, between [0..1]. */ public float getStartingRotation() { return mStartingRotation; } /** * If the start / end trim are offset to begin with, store them so that * animation starts from that offset. */ public void storeOriginals() { mStartingStartTrim = mStartTrim; mStartingEndTrim = mEndTrim; mStartingRotation = mRotation; } /** * Reset the progress spinner to default rotation, start and end angles. */ public void resetOriginals() { mStartingStartTrim = 0; mStartingEndTrim = 0; mStartingRotation = 0; setStartTrim(0); setEndTrim(0); setRotation(0); } private void invalidateSelf() { mCallback.invalidateDrawable(null); } } } ================================================ FILE: refresh-header/src/main/java/com/scwang/smartrefresh/header/material/CircleImageView.java ================================================ /* * Copyright (C) 2014 The Android Open Source Project * * 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.scwang.smartrefresh.header.material; import android.content.Context; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.RadialGradient; import android.graphics.Shader; import android.graphics.drawable.ShapeDrawable; import android.graphics.drawable.shapes.OvalShape; import android.os.Build; import android.view.animation.Animation; import android.widget.ImageView; /** * Private class created to work around issues with AnimationListeners being * called before the animation is actually complete and support shadows on older * platforms. */ public class CircleImageView extends ImageView { private static final int KEY_SHADOW_COLOR = 0x1E000000; private static final int FILL_SHADOW_COLOR = 0x3D000000; // PX private static final float X_OFFSET = 0f; private static final float Y_OFFSET = 1.75f; private static final float SHADOW_RADIUS = 3.5f; private static final int SHADOW_ELEVATION = 4; private Animation.AnimationListener mListener; int mShadowRadius; public CircleImageView(Context context, int color) { super(context); final float density = getContext().getResources().getDisplayMetrics().density; final int shadowYOffset = (int) (density * Y_OFFSET); final int shadowXOffset = (int) (density * X_OFFSET); mShadowRadius = (int) (density * SHADOW_RADIUS); ShapeDrawable circle; if (elevationSupported()) { circle = new ShapeDrawable(new OvalShape()); setElevation(SHADOW_ELEVATION * density); } else { OvalShape oval = new OvalShadow(mShadowRadius); circle = new ShapeDrawable(oval); this.setLayerType(LAYER_TYPE_SOFTWARE, circle.getPaint()); circle.getPaint().setShadowLayer(mShadowRadius, shadowXOffset, shadowYOffset, KEY_SHADOW_COLOR); final int padding = mShadowRadius; // set padding so the inner image sits correctly within the shadow. setPadding(padding, padding, padding, padding); } circle.getPaint().setColor(color); if (Build.VERSION.SDK_INT >= 16) { this.setBackground(circle); } else { //noinspection deprecation this.setBackgroundDrawable(circle); } } private boolean elevationSupported() { return android.os.Build.VERSION.SDK_INT >= 21; } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); if (!elevationSupported()) { setMeasuredDimension(getMeasuredWidth() + mShadowRadius * 2, getMeasuredHeight() + mShadowRadius * 2); } } public void setAnimationListener(Animation.AnimationListener listener) { mListener = listener; } @Override public void onAnimationStart() { super.onAnimationStart(); if (mListener != null) { mListener.onAnimationStart(getAnimation()); } } @Override public void onAnimationEnd() { super.onAnimationEnd(); if (mListener != null) { mListener.onAnimationEnd(getAnimation()); } } /** * Update the background color of the circle image view. * * @param colorRes Id of a color resource. */ public void setBackgroundColorRes(int colorRes) { Context context = getContext(); if (Build.VERSION.SDK_INT >= 23) { setBackgroundColor(context.getResources().getColor(colorRes, context.getTheme())); } else { //noinspection deprecation setBackgroundColor(context.getResources().getColor(colorRes)); } } @Override public void setBackgroundColor(int color) { if (getBackground() instanceof ShapeDrawable) { ((ShapeDrawable) getBackground()).getPaint().setColor(color); } } private class OvalShadow extends OvalShape { private RadialGradient mRadialGradient; private Paint mShadowPaint; OvalShadow(int shadowRadius) { super(); mShadowPaint = new Paint(); mShadowRadius = shadowRadius; updateRadialGradient((int) rect().width()); } @Override protected void onResize(float width, float height) { super.onResize(width, height); updateRadialGradient((int) width); } @Override public void draw(Canvas canvas, Paint paint) { final int viewWidth = CircleImageView.this.getWidth(); final int viewHeight = CircleImageView.this.getHeight(); canvas.drawCircle(viewWidth / 2, viewHeight / 2, viewWidth / 2, mShadowPaint); canvas.drawCircle(viewWidth / 2, viewHeight / 2, viewWidth / 2 - mShadowRadius, paint); } private void updateRadialGradient(int diameter) { mRadialGradient = new RadialGradient(diameter / 2, diameter / 2, mShadowRadius, new int[] { FILL_SHADOW_COLOR, Color.TRANSPARENT }, null, Shader.TileMode.CLAMP); mShadowPaint.setShader(mRadialGradient); } } } ================================================ FILE: refresh-header/src/main/java/com/scwang/smartrefresh/header/storehouse/StoreHouseBarItem.java ================================================ package com.scwang.smartrefresh.header.storehouse; import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.PointF; import android.view.animation.Animation; import android.view.animation.Transformation; import java.util.Random; /** * * Created by srain on 11/6/14. */ public class StoreHouseBarItem extends Animation { public PointF midPoint; public float translationX; public int index; private final Paint mPaint = new Paint(); private float mFromAlpha = 1.0f; private float mToAlpha = 0.4f; private PointF mCStartPoint; private PointF mCEndPoint; public StoreHouseBarItem(int index, PointF start, PointF end, int color, int lineWidth) { this.index = index; midPoint = new PointF((start.x + end.x) / 2, (start.y + end.y) / 2); mCStartPoint = new PointF(start.x - midPoint.x, start.y - midPoint.y); mCEndPoint = new PointF(end.x - midPoint.x, end.y - midPoint.y); setColor(color); setLineWidth(lineWidth); mPaint.setAntiAlias(true); mPaint.setStyle(Paint.Style.STROKE); } public void setLineWidth(int width) { mPaint.setStrokeWidth(width); } public void setColor(int color) { mPaint.setColor(color); } public void resetPosition(int horizontalRandomness) { Random random = new Random(); int randomNumber = -random.nextInt(horizontalRandomness) + horizontalRandomness; translationX = randomNumber; } @Override protected void applyTransformation(float interpolatedTime, Transformation t) { float alpha = mFromAlpha; alpha = alpha + ((mToAlpha - alpha) * interpolatedTime); setAlpha(alpha); } public void start(float fromAlpha, float toAlpha) { mFromAlpha = fromAlpha; mToAlpha = toAlpha; super.start(); } public void setAlpha(float alpha) { mPaint.setAlpha((int) (alpha * 255)); } public void draw(Canvas canvas) { canvas.drawLine(mCStartPoint.x, mCStartPoint.y, mCEndPoint.x, mCEndPoint.y, mPaint); } } ================================================ FILE: refresh-header/src/main/java/com/scwang/smartrefresh/header/storehouse/StoreHousePath.java ================================================ package com.scwang.smartrefresh.header.storehouse; import android.util.SparseArray; import java.util.ArrayList; /** * * Created by srain on 11/7/14. */ public class StoreHousePath { private static final SparseArray sPointList; static { sPointList = new SparseArray(); float[][] LETTERS = new float[][]{ new float[]{ // A 24, 0, 1, 22, 1, 22, 1, 72, 24, 0, 47, 22, 47, 22, 47, 72, 1, 48, 47, 48 }, new float[]{ // B 0, 0, 0, 72, 0, 0, 37, 0, 37, 0, 47, 11, 47, 11, 47, 26, 47, 26, 38, 36, 38, 36, 0, 36, 38, 36, 47, 46, 47, 46, 47, 61, 47, 61, 38, 71, 37, 72, 0, 72, }, new float[]{ // C 47, 0, 0, 0, 0, 0, 0, 72, 0, 72, 47, 72, }, new float[]{ // D 0, 0, 0, 72, 0, 0, 24, 0, 24, 0, 47, 22, 47, 22, 47, 48, 47, 48, 23, 72, 23, 72, 0, 72, }, new float[]{ // E 0, 0, 0, 72, 0, 0, 47, 0, 0, 36, 37, 36, 0, 72, 47, 72, }, new float[]{ // F 0, 0, 0, 72, 0, 0, 47, 0, 0, 36, 37, 36, }, new float[]{ // G 47, 23, 47, 0, 47, 0, 0, 0, 0, 0, 0, 72, 0, 72, 47, 72, 47, 72, 47, 48, 47, 48, 24, 48, }, new float[]{ // H 0, 0, 0, 72, 0, 36, 47, 36, 47, 0, 47, 72, }, new float[]{ // I 0, 0, 47, 0, 24, 0, 24, 72, 0, 72, 47, 72, }, new float[]{ // J 47, 0, 47, 72, 47, 72, 24, 72, 24, 72, 0, 48, }, new float[]{ // K 0, 0, 0, 72, 47, 0, 3, 33, 3, 38, 47, 72, }, new float[]{ // L 0, 0, 0, 72, 0, 72, 47, 72, }, new float[]{ // M 0, 0, 0, 72, 0, 0, 24, 23, 24, 23, 47, 0, 47, 0, 47, 72, }, new float[]{ // N 0, 0, 0, 72, 0, 0, 47, 72, 47, 72, 47, 0, }, new float[]{ // O 0, 0, 0, 72, 0, 72, 47, 72, 47, 72, 47, 0, 47, 0, 0, 0, }, new float[]{ // P 0, 0, 0, 72, 0, 0, 47, 0, 47, 0, 47, 36, 47, 36, 0, 36, }, new float[]{ // Q 0, 0, 0, 72, 0, 72, 23, 72, 23, 72, 47, 48, 47, 48, 47, 0, 47, 0, 0, 0, 24, 28, 47, 71, }, new float[]{ // R 0, 0, 0, 72, 0, 0, 47, 0, 47, 0, 47, 36, 47, 36, 0, 36, 0, 37, 47, 72, }, new float[]{ // S 47, 0, 0, 0, 0, 0, 0, 36, 0, 36, 47, 36, 47, 36, 47, 72, 47, 72, 0, 72, }, new float[]{ // T 0, 0, 47, 0, 24, 0, 24, 72, }, new float[]{ // U 0, 0, 0, 72, 0, 72, 47, 72, 47, 72, 47, 0, }, new float[]{ // V 0, 0, 24, 72, 24, 72, 47, 0, }, new float[]{ // W 0, 0, 0, 72, 0, 72, 24, 49, 24, 49, 47, 72, 47, 72, 47, 0 }, new float[]{ // X 0, 0, 47, 72, 47, 0, 0, 72 }, new float[]{ // Y 0, 0, 24, 23, 47, 0, 24, 23, 24, 23, 24, 72 }, new float[]{ // Z 0, 0, 47, 0, 47, 0, 0, 72, 0, 72, 47, 72 }, }; final float[][] NUMBERS = new float[][]{ new float[]{ // 0 0, 0, 0, 72, 0, 72, 47, 72, 47, 72, 47, 0, 47, 0, 0, 0, }, new float[]{ // 1 24, 0, 24, 72, }, new float[]{ // 2 0, 0, 47, 0, 47, 0, 47, 36, 47, 36, 0, 36, 0, 36, 0, 72, 0, 72, 47, 72 }, new float[]{ // 3 0, 0, 47, 0, 47, 0, 47, 36, 47, 36, 0, 36, 47, 36, 47, 72, 47, 72, 0, 72, }, new float[]{ // 4 0, 0, 0, 36, 0, 36, 47, 36, 47, 0, 47, 72, }, new float[]{ // 5 0, 0, 0, 36, 0, 36, 47, 36, 47, 36, 47, 72, 47, 72, 0, 72, 0, 0, 47, 0 }, new float[]{ // 6 0, 0, 0, 72, 0, 72, 47, 72, 47, 72, 47, 36, 47, 36, 0, 36 }, new float[]{ // 7 0, 0, 47, 0, 47, 0, 47, 72 }, new float[]{ // 8 0, 0, 0, 72, 0, 72, 47, 72, 47, 72, 47, 0, 47, 0, 0, 0, 0, 36, 47, 36 }, new float[]{ // 9 47, 0, 0, 0, 0, 0, 0, 36, 0, 36, 47, 36, 47, 0, 47, 72, } }; // A - Z for (int i = 0; i < LETTERS.length; i++) { sPointList.append(i + 65, LETTERS[i]); } // a - z for (int i = 0; i < LETTERS.length; i++) { sPointList.append(i + 65 + 32, LETTERS[i]); } // 0 - 9 for (int i = 0; i < NUMBERS.length; i++) { sPointList.append(i + 48, NUMBERS[i]); } // blank addChar(' ', new float[]{}); // - addChar('-', new float[]{ 0, 36, 47, 36 }); // . addChar('.', new float[]{ 24, 60, 24, 72 }); } public static void addChar(char c, float[] points) { sPointList.append(c, points); } public static ArrayList getPath(String str) { return getPath(str, 1, 14); } /** * @param str * @param scale * @param gapBetweenLetter * @return ArrayList of float[] {x1, y1, x2, y2} */ public static ArrayList getPath(String str, float scale, int gapBetweenLetter) { ArrayList list = new ArrayList(); float offsetForWidth = 0; for (int i = 0; i < str.length(); i++) { int pos = str.charAt(i); int key = sPointList.indexOfKey(pos); if (key == -1) { continue; } float[] points = sPointList.get(pos); int pointCount = points.length / 4; for (int j = 0; j < pointCount; j++) { float[] line = new float[4]; for (int k = 0; k < 4; k++) { float l = points[j * 4 + k]; // x if (k % 2 == 0) { line[k] = (l + offsetForWidth) * scale; } // y else { line[k] = l * scale; } } list.add(line); } offsetForWidth += 57 + gapBetweenLetter; } return list; } } ================================================ FILE: refresh-header/src/main/java/com/scwang/smartrefresh/header/waterdrop/Circle.java ================================================ package com.scwang.smartrefresh.header.waterdrop; /** * Created by xiayong on 2015/6/25. * 实心圆 */ public class Circle { public float x;//圆x坐标 public float y;//圆y坐标 public float radius;//圆半径 public int color;//圆的颜色 } ================================================ FILE: refresh-header/src/main/java/com/scwang/smartrefresh/header/waterdrop/WaterDropView.java ================================================ package com.scwang.smartrefresh.header.waterdrop; import android.animation.Animator; import android.animation.ValueAnimator; import android.content.Context; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.Path; import android.util.AttributeSet; import android.view.View; import android.view.animation.DecelerateInterpolator; import com.scwang.smartrefresh.layout.util.DensityUtil; /** * 下拉头中间的“水滴” * Created by xiayong on 2015/6/23. */ public class WaterDropView extends View { private Circle topCircle; private Circle bottomCircle; private Path mPath; private Paint mPaint; private int mMaxCircleRadius;//圆半径最大值 private int mMinCircleRaidus;//圆半径最小值 private static int STROKE_WIDTH = 2;//边线宽度 private final static int BACK_ANIM_DURATION = 180; public WaterDropView(Context context) { super(context); initView(context, null); } public WaterDropView(Context context, AttributeSet attrs) { super(context, attrs); initView(context, attrs); } public WaterDropView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); initView(context, attrs); } private void initView(Context context, AttributeSet attrs) { // setBackgroundColor(0xffbbff11); topCircle = new Circle(); bottomCircle = new Circle(); mPath = new Path(); mPaint = new Paint(); mPaint.setColor(Color.GRAY); mPaint.setAntiAlias(true); mPaint.setStyle(Paint.Style.FILL_AND_STROKE); mPaint.setStrokeWidth(STROKE_WIDTH = DensityUtil.dp2px(0.5f)); mPaint.setShadowLayer(STROKE_WIDTH, 0, 0, 0xCC000000); setLayerType(LAYER_TYPE_SOFTWARE, null); int padding = 4 * STROKE_WIDTH; setPadding(padding, padding, padding, padding); mPaint.setColor(Color.GRAY); mMaxCircleRadius = DensityUtil.dp2px(20); mMinCircleRaidus = mMaxCircleRadius / 5; topCircle.radius = (mMaxCircleRadius); bottomCircle.radius = (mMaxCircleRadius); topCircle.x = (STROKE_WIDTH + mMaxCircleRadius); topCircle.y = (STROKE_WIDTH + mMaxCircleRadius); bottomCircle.x = (STROKE_WIDTH + mMaxCircleRadius); bottomCircle.y = (STROKE_WIDTH + mMaxCircleRadius); } public int getMaxCircleRadius() { return mMaxCircleRadius; } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { //宽度:上圆和下圆的最大直径 int width = (int) ((mMaxCircleRadius + STROKE_WIDTH) * 2); //高度:上圆半径 + 圆心距 + 下圆半径 int height = (int) Math.ceil(bottomCircle.y+bottomCircle.radius + STROKE_WIDTH * 2); setMeasuredDimension(width + getPaddingLeft() + getPaddingRight(), resolveSize(height + getPaddingTop() + getPaddingBottom(), heightMeasureSpec)); } @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { super.onLayout(changed, left, top, right, bottom); updateComleteState(getHeight()); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); final int paddingTop = getPaddingTop(); final int paddingLeft = getPaddingLeft(); final int paddingBottom = getPaddingBottom(); final int height = getHeight(); canvas.save(); if (height <= topCircle.radius * 2 + paddingTop + paddingBottom) { canvas.translate(paddingLeft, height - topCircle.radius * 2 - paddingBottom); canvas.drawCircle(topCircle.x, topCircle.y, topCircle.radius, mPaint); } else { canvas.translate(paddingLeft, paddingTop); makeBezierPath(); canvas.drawPath(mPath, mPaint); // canvas.drawCircle(topCircle.x, topCircle.y, topCircle.radius, mPaint); // canvas.drawCircle(bottomCircle.x, bottomCircle.y, bottomCircle.radius, mPaint); } canvas.restore(); } private void makeBezierPath() { mPath.reset(); mPath.addCircle(topCircle.x, topCircle.y, topCircle.radius, Path.Direction.CCW); if (bottomCircle.y > topCircle.y + DensityUtil.dp2px(1)) { mPath.addCircle(bottomCircle.x, bottomCircle.y, bottomCircle.radius, Path.Direction.CCW); //获取两圆的两个切线形成的四个切点 double angle = getAngle(); float top_x1 = (float) (topCircle.x - topCircle.radius * Math.cos(angle)); float top_y1 = (float) (topCircle.y + topCircle.radius * Math.sin(angle)); float top_x2 = (float) (topCircle.x + topCircle.radius * Math.cos(angle)); float top_y2 = top_y1; float bottom_x1 = (float) (bottomCircle.x - bottomCircle.radius * Math.cos(angle)); float bottom_y1 = (float) (bottomCircle.y + bottomCircle.radius * Math.sin(angle)); float bottom_x2 = (float) (bottomCircle.x + bottomCircle.radius * Math.cos(angle)); float bottom_y2 = bottom_y1; mPath.moveTo(topCircle.x, topCircle.y); mPath.lineTo(top_x1, top_y1); mPath.quadTo((bottomCircle.x - bottomCircle.radius), (bottomCircle.y + topCircle.y) / 2, bottom_x1, bottom_y1); mPath.lineTo(bottom_x2, bottom_y2); mPath.quadTo((bottomCircle.x + bottomCircle.radius), (bottomCircle.y + top_y2) / 2, top_x2, top_y2); } mPath.close(); } /** * 获得两个圆切线与圆心连线的夹角 */ private double getAngle() { if (bottomCircle.radius > topCircle.radius) { throw new IllegalStateException("bottomCircle's radius must be less than the topCircle's"); } return Math.asin((topCircle.radius - bottomCircle.radius) / (bottomCircle.y - topCircle.y)); } /** * 创建回弹动画 * 上圆半径减速恢复至最大半径 * 下圆半径减速恢复至最大半径 * 圆心距减速从最大值减到0(下圆Y从当前位置移动到上圆Y)。 */ public Animator createAnimator() { ValueAnimator valueAnimator = ValueAnimator.ofFloat(1, 0.001f).setDuration(BACK_ANIM_DURATION); valueAnimator.setInterpolator(new DecelerateInterpolator()); valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator valueAnimator1) { WaterDropView.this.updateComleteState((float) valueAnimator1.getAnimatedValue()); WaterDropView.this.postInvalidate(); } }); return valueAnimator; } /** * 完成的百分比 */ public void updateComleteState(int offset, int maxHeight) { // float space = mMaxCircleRadius * 2 + getPaddingTop() + getPaddingBottom(); // updateComleteState(Math.max(0, 1f * (offset - space) / (maxHeight - space))); } /** * 完成的百分比 */ public void updateComleteState(float percent) { float top_r = (float) (mMaxCircleRadius - 0.25 * percent * mMaxCircleRadius); float bottom_r = (mMinCircleRaidus - mMaxCircleRadius) * percent + mMaxCircleRadius; float bottomCricleOffset = 4 * percent * mMaxCircleRadius; topCircle.radius = (top_r); bottomCircle.radius = (bottom_r); bottomCircle.y = (topCircle.y + bottomCricleOffset); } /** * 完成的百分比 */ public void updateComleteState(int height) { final int paddingTop = getPaddingTop(); final int paddingBottom = getPaddingBottom(); float space = mMaxCircleRadius * 2 + paddingTop + paddingBottom; if (height < space) { topCircle.radius = mMaxCircleRadius; bottomCircle.radius = mMaxCircleRadius; bottomCircle.y = topCircle.y; } else { float limit = mMaxCircleRadius - mMinCircleRaidus; float x = Math.max(0, height - space); float y = (float) (limit * (1 - Math.pow(100, -x / DensityUtil.dp2px(200)))); topCircle.radius = mMaxCircleRadius - y / 4; bottomCircle.radius = mMaxCircleRadius - y; int validHeight = height - paddingTop - paddingBottom; bottomCircle.y = validHeight - bottomCircle.radius; } } public Circle getTopCircle() { return topCircle; } public Circle getBottomCircle() { return bottomCircle; } public void setIndicatorColor(int color) { mPaint.setColor(color); } public int getIndicatorColor() { return mPaint.getColor(); } } ================================================ FILE: refresh-header/src/main/java/com/scwang/smartrefresh/header/waveswipe/AnimationImageView.java ================================================ /* * Copyright (C) 2015 RECRUIT LIFESTYLE CO., LTD. * * 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.scwang.smartrefresh.header.waveswipe; import android.content.Context; import android.view.animation.Animation; import android.widget.ImageView; /** * @author amyu */ public class AnimationImageView extends ImageView { /** * AnimationのStartとEnd時にListenerにアレする */ private Animation.AnimationListener mListener; /** * コンストラクタ * {@inheritDoc} */ public AnimationImageView(Context context) { super(context); } /** * {@link AnimationImageView#mListener} のセット * * @param listener {@link android.view.animation.Animation.AnimationListener} */ public void setAnimationListener(Animation.AnimationListener listener) { mListener = listener; } /** * ViewのAnimationのStart時にセットされたListenerの {@link android.view.animation.Animation.AnimationListener#onAnimationStart(Animation)} * を呼ぶ */ @Override public void onAnimationStart() { super.onAnimationStart(); if (mListener != null) { mListener.onAnimationStart(getAnimation()); } } /** * ViewのAnimationのEnd時にセットされたListenerの {@link android.view.animation.Animation.AnimationListener#onAnimationEnd(Animation)} * (Animation)} を呼ぶ */ @Override public void onAnimationEnd() { super.onAnimationEnd(); if (mListener != null) { mListener.onAnimationEnd(getAnimation()); } } } ================================================ FILE: refresh-header/src/main/java/com/scwang/smartrefresh/header/waveswipe/DisplayUtil.java ================================================ /* * Copyright (C) RECRUIT LIFESTYLE CO., LTD. * * 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.scwang.smartrefresh.header.waveswipe; import android.content.Context; import android.content.res.Resources; import android.util.DisplayMetrics; /** * @author amyu */ public class DisplayUtil { private DisplayUtil(){} /** * 現在の向きが600dpを超えているかどうか * * @param context {@link Context} * @return 600dpを超えているかどうか */ public static boolean isOver600dp(Context context) { Resources resources = context.getResources(); DisplayMetrics displayMetrics = resources.getDisplayMetrics(); return displayMetrics.widthPixels / displayMetrics.density >= 600; } } ================================================ FILE: refresh-header/src/main/java/com/scwang/smartrefresh/header/waveswipe/DropBounceInterpolator.java ================================================ package com.scwang.smartrefresh.header.waveswipe; /* * Copyright (C) 2015 RECRUIT LIFESTYLE CO., LTD. * * 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. */ import android.content.Context; import android.util.AttributeSet; import android.view.animation.Interpolator; /** * @author amyu * * {@link WaveView#mDropBounceHorizontalAnimator} と {@link WaveView#mDropVertexAnimator} にセットするInterpolator * WavePullToRefresh/DropBounceInterpolator.gcxにグラフの詳細 */ public class DropBounceInterpolator implements Interpolator { public DropBounceInterpolator() { } @SuppressWarnings({"UnusedDeclaration"}) public DropBounceInterpolator(Context context, AttributeSet attrs) { } /** * {@inheritDoc} * * @param v * @return */ @Override public float getInterpolation(float v) { //y = -19 * (x - 0.125)^2 + 1.3 (0 <= x < 0.25) //y = -6.5 * (x - 0.625)^2 + 1.1 (0.5 <= x < 0.75) //y = 0 (0.25 <= x < 0.5 && 0.75 <= x <=1) if (v < 0.25f) { return -38.4f * (float) Math.pow(v - 0.125, 2) + 0.6f; } else if (v >= 0.5 && v < 0.75) { return -19.2f * (float) Math.pow(v - 0.625, 2) + 0.3f; } else { return 0; } } } ================================================ FILE: refresh-header/src/main/java/com/scwang/smartrefresh/header/waveswipe/WaveView.java ================================================ /* * Copyright (C) 2015 RECRUIT LIFESTYLE CO., LTD. * * 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.scwang.smartrefresh.header.waveswipe; import android.animation.Animator; import android.animation.ValueAnimator; import android.content.Context; import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.Path; import android.graphics.RectF; import android.os.Build; import android.util.Log; import android.view.View; import android.view.ViewTreeObserver; import android.view.animation.AccelerateDecelerateInterpolator; import android.view.animation.BounceInterpolator; /** * @author amyu *

* 波と落ちる円を描画するView */ public class WaveView extends View implements ViewTreeObserver.OnPreDrawListener { /** * {@link WaveView#mDropCircleAnimator} のDuration */ private static final long DROP_CIRCLE_ANIMATOR_DURATION = 500; /** * {@link WaveView#mDropBounceVerticalAnimator} のDuration */ private static final long DROP_VERTEX_ANIMATION_DURATION = 500; /** * {@link WaveView#mDropBounceVerticalAnimator} と {@link WaveView#mDropBounceHorizontalAnimator} * のDuration */ private static final long DROP_BOUNCE_ANIMATOR_DURATION = 500; /** * {@link WaveView#mDisappearCircleAnimator} のDuration */ private static final int DROP_REMOVE_ANIMATOR_DURATION = 200; /** * 波がくねくねしているDuration */ private static final int WAVE_ANIMATOR_DURATION = 1000; /** * 波の最大の高さ */ private static final float MAX_WAVE_HEIGHT = 0.2f; /** * 影の色 */ private static final int SHADOW_COLOR = 0x99000000; /** * 円のRadius */ private float mDropCircleRadius = 100; /** * すべてを描画するPaint */ private Paint mPaint; /** * 画面の波を描画するためのPath */ private Path mWavePath; /** * 落ちる円の接線を描画するためのPath */ private Path mDropTangentPath; /** * 落ちる円を描画するためのPath */ private Path mDropCirclePath; /** * 影のPaint */ // private Paint mShadowPaint; /** * 影のPath */ private Path mShadowPath; /** * 落ちる円の座標を入れているRectF */ private RectF mDropRect; /** * Viewの横幅 */ private int mWidth; /** * {@link WaveView#mDropCircleAnimator} でアニメーションしてる時の円の中心のY座標 */ private float mCurrentCircleCenterY; /** * 円が落ちる最大の高さ */ private int mMaxDropHeight; private boolean mIsManualRefreshing = false; /** * 落ちる円の高さが更新されたかどうか */ private boolean mDropHeightUpdated = false; /** * {@link WaveView#mMaxDropHeight} を更新するための一時的な値の置き場 */ private int mUpdateMaxDropHeight; /** * 落ちてくる円についてくる三角形の一番上の頂点のAnimator */ private ValueAnimator mDropVertexAnimator; /** * 落ちた円が横に伸びるときのAnimator */ private ValueAnimator mDropBounceVerticalAnimator; /** * 落ちた縁が縦に伸びるときのAnimator */ private ValueAnimator mDropBounceHorizontalAnimator; /** * 落ちる円の中心座標のAnimator */ private ValueAnimator mDropCircleAnimator; /** * 落ちた円を消すためのAnimator */ private ValueAnimator mDisappearCircleAnimator; /** * 帰ってくる波ののAnimator */ private ValueAnimator mWaveReverseAnimator; /** * ベジェ曲線を引く際の座標 * 左側の2つのアンカーポイントでいい感じに右側にも */ private static final float[][] BEGIN_PHASE_POINTS = { //1 {0.1655f, 0}, //ハンドル {0.4188f, -0.0109f}, //ハンドル {0.4606f, -0.0049f}, //アンカーポイント //2 {0.4893f, 0.f}, //ハンドル {0.4893f, 0.f}, //ハンドル {0.5f, 0.f} //アンカーポイント }; private static final float[][] APPEAR_PHASE_POINTS = { //1 {0.1655f, 0.f}, //ハンドル {0.5237f, 0.0553f}, //ハンドル {0.4557f, 0.0936f}, //アンカーポイント //2 {0.3908f, 0.1302f}, //ハンドル {0.4303f, 0.2173f}, //ハンドル {0.5f, 0.2173f} //アンカーポイント }; private static final float[][] EXPAND_PHASE_POINTS = { //1 {0.1655f, 0.f}, //ハンドル {0.5909f, 0.0000f}, //ハンドル {0.4557f, 0.1642f}, //アンカーポイント //2 {0.3941f, 0.2061f}, //ハンドル {0.4303f, 0.2889f}, //ハンドル {0.5f, 0.2889f} //アンカーポイント }; /** * 各AnimatorのAnimatorUpdateListener */ private ValueAnimator.AnimatorUpdateListener mAnimatorUpdateListener = new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator valueAnimator) { WaveView.this.postInvalidate(); } }; /** * Constructor * {@inheritDoc} */ public WaveView(Context context) { super(context); getViewTreeObserver().addOnPreDrawListener(this); initView(); } /** * Viewのサイズが決まったら {@link WaveView#mWidth} に横幅 * {@inheritDoc} */ @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { mWidth = w; mDropCircleRadius = w / 14.4f; updateMaxDropHeight((int) Math.min(Math.min(w, h), getHeight() - mDropCircleRadius)); super.onSizeChanged(w, h, oldw, oldh); } /** * 描画されてから {@link WaveView#mMaxDropHeight} を更新する * {@inheritDoc} */ @Override public boolean onPreDraw() { getViewTreeObserver().removeOnPreDrawListener(this); if (mDropHeightUpdated) { updateMaxDropHeight(mUpdateMaxDropHeight); } return false; } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); //引っ張ってる最中の波と終わったあとの波 // canvas.drawPath(mWavePath, mShadowPaint); canvas.drawPath(mWavePath, mPaint); if (!isInEditMode()) { mWavePath.rewind(); //円が落ちる部分の描画 mDropTangentPath.rewind(); mDropCirclePath.rewind(); } float circleCenterY = (Float) mDropCircleAnimator.getAnimatedValue(); float circleCenterX = mWidth / 2.f; mDropRect.setEmpty(); //円の座標をRectFに保存 float scale = (Float) mDisappearCircleAnimator.getAnimatedValue(); float vertical = (Float) mDropBounceVerticalAnimator.getAnimatedValue(); float horizontal = (Float) mDropBounceHorizontalAnimator.getAnimatedValue(); mDropRect.set(circleCenterX - mDropCircleRadius * (1 + vertical) * scale + mDropCircleRadius * horizontal / 2, circleCenterY + mDropCircleRadius * (1 + horizontal) * scale - mDropCircleRadius * vertical / 2, circleCenterX + mDropCircleRadius * (1 + vertical) * scale - mDropCircleRadius * horizontal / 2, circleCenterY - mDropCircleRadius * (1 + horizontal) * scale + mDropCircleRadius * vertical / 2); float vertex = (Float) mDropVertexAnimator.getAnimatedValue(); mDropTangentPath.moveTo(circleCenterX, vertex); //円の接点(p1,q),(p2,q) double q = (Math.pow(mDropCircleRadius, 2) + circleCenterY * vertex - Math.pow(circleCenterY, 2)) / ( vertex - circleCenterY); //2次方程式解くための解の公式 double b = -2.0 * mWidth / 2; double c = Math.pow(q - circleCenterY, 2) + Math.pow(circleCenterX, 2) - Math.pow(mDropCircleRadius, 2); double p1 = (-b + Math.sqrt(b * b - 4 * c)) / 2; double p2 = (-b - Math.sqrt(b * b - 4 * c)) / 2; mDropTangentPath.lineTo((float) p1, (float) q); mDropTangentPath.lineTo((float) p2, (float) q); mDropTangentPath.close(); mShadowPath.set(mDropTangentPath); mShadowPath.addOval(mDropRect, Path.Direction.CCW); mDropCirclePath.addOval(mDropRect, Path.Direction.CCW); if (mDropVertexAnimator.isRunning()) { // canvas.drawPath(mShadowPath, mShadowPaint); } else { // canvas.drawPath(mDropCirclePath, mShadowPaint); } canvas.drawPath(mDropTangentPath, mPaint); canvas.drawPath(mDropCirclePath, mPaint); } @Override protected void onDetachedFromWindow() { if (mDisappearCircleAnimator != null) { mDisappearCircleAnimator.end(); mDisappearCircleAnimator.removeAllUpdateListeners(); } if (mDropCircleAnimator != null) { mDropCircleAnimator.end(); mDropCircleAnimator.removeAllUpdateListeners(); } if (mDropVertexAnimator != null) { mDropVertexAnimator.end(); mDropVertexAnimator.removeAllUpdateListeners(); } if (mWaveReverseAnimator != null) { mWaveReverseAnimator.end(); mWaveReverseAnimator.removeAllUpdateListeners(); } if (mDropBounceHorizontalAnimator != null) { mDropBounceHorizontalAnimator.end(); mDropBounceHorizontalAnimator.removeAllUpdateListeners(); } if (mDropBounceVerticalAnimator != null) { mDropBounceVerticalAnimator.end(); mDropBounceVerticalAnimator.removeAllUpdateListeners(); } super.onDetachedFromWindow(); } private void initView() { setUpPaint(); setUpPath(); resetAnimator(); mDropRect = new RectF(); setLayerType(View.LAYER_TYPE_SOFTWARE, null); } private void setUpPaint() { float density = getResources().getDisplayMetrics().density; mPaint = new Paint(); mPaint.setColor(0xff2196F3); mPaint.setAntiAlias(true); mPaint.setStyle(Paint.Style.FILL); mPaint.setShadowLayer((int) (0.5f + 2.0f * density), 0f, 0f, SHADOW_COLOR); // float density = getResources().getDisplayMetrics().density; // mShadowPaint = new Paint(); // mShadowPaint.setAntiAlias(true); // mShadowPaint.setShadowLayer((int) (0.5f + 2.0f * density), 0f, 0f, SHADOW_COLOR); } private void setUpPath() { mWavePath = new Path(); mDropTangentPath = new Path(); mDropCirclePath = new Path(); mShadowPath = new Path(); } private void resetAnimator() { mDropVertexAnimator = ValueAnimator.ofFloat(0.f, 0.f); mDropBounceVerticalAnimator = ValueAnimator.ofFloat(0.f, 0.f); mDropBounceHorizontalAnimator = ValueAnimator.ofFloat(0.f, 0.f); mDropCircleAnimator = ValueAnimator.ofFloat(-1000.f, -1000.f); mDropCircleAnimator.start(); mDisappearCircleAnimator = ValueAnimator.ofFloat(1.f, 1.f); mDisappearCircleAnimator.setDuration(1); // immediately finish animation cycle mDisappearCircleAnimator.start(); } private void onPreDragWave() { if (mWaveReverseAnimator != null) { if (mWaveReverseAnimator.isRunning()) { mWaveReverseAnimator.cancel(); } } } public void manualRefresh() { if (mIsManualRefreshing) { return; } mIsManualRefreshing = true; mDropCircleAnimator = ValueAnimator.ofFloat(mMaxDropHeight, mMaxDropHeight); mDropCircleAnimator.start(); mDropVertexAnimator = ValueAnimator.ofFloat(mMaxDropHeight - mDropCircleRadius, mMaxDropHeight - mDropCircleRadius); mDropVertexAnimator.start(); mCurrentCircleCenterY = mMaxDropHeight; postInvalidate(); } public void beginPhase(float move1) { onPreDragWave(); //円を描画し始める前の引っ張ったら膨れる波の部分の描画 mWavePath.moveTo(0, 0); //左半分の描画 mWavePath.cubicTo(mWidth * BEGIN_PHASE_POINTS[0][0], BEGIN_PHASE_POINTS[0][1], mWidth * BEGIN_PHASE_POINTS[1][0], mWidth * (BEGIN_PHASE_POINTS[1][1] + move1), mWidth * BEGIN_PHASE_POINTS[2][0], mWidth * (BEGIN_PHASE_POINTS[2][1] + move1)); mWavePath.cubicTo(mWidth * BEGIN_PHASE_POINTS[3][0], mWidth * (BEGIN_PHASE_POINTS[3][1] + move1), mWidth * BEGIN_PHASE_POINTS[4][0], mWidth * (BEGIN_PHASE_POINTS[4][1] + move1), mWidth * BEGIN_PHASE_POINTS[5][0], mWidth * (BEGIN_PHASE_POINTS[5][1] + move1)); //右半分の描画 mWavePath.cubicTo(mWidth - mWidth * BEGIN_PHASE_POINTS[4][0], mWidth * (BEGIN_PHASE_POINTS[4][1] + move1), mWidth - mWidth * BEGIN_PHASE_POINTS[3][0], mWidth * (BEGIN_PHASE_POINTS[3][1] + move1), mWidth - mWidth * BEGIN_PHASE_POINTS[2][0], mWidth * (BEGIN_PHASE_POINTS[2][1] + move1)); mWavePath.cubicTo(mWidth - mWidth * BEGIN_PHASE_POINTS[1][0], mWidth * (BEGIN_PHASE_POINTS[1][1] + move1), mWidth - mWidth * BEGIN_PHASE_POINTS[0][0], BEGIN_PHASE_POINTS[0][1], mWidth, 0); postInvalidateOnAnimation(); } public void postInvalidateOnAnimation() { if (Build.VERSION.SDK_INT >= 16) { super.postInvalidateOnAnimation(); } else { super.invalidate(); } } public void appearPhase(float move1, float move2) { onPreDragWave(); mWavePath.moveTo(0, 0); //左半分の描画 mWavePath.cubicTo(mWidth * APPEAR_PHASE_POINTS[0][0], mWidth * APPEAR_PHASE_POINTS[0][1], mWidth * Math.min(BEGIN_PHASE_POINTS[1][0] + move2, APPEAR_PHASE_POINTS[1][0]), mWidth * Math.max(BEGIN_PHASE_POINTS[1][1] + move1 - move2, APPEAR_PHASE_POINTS[1][1]), mWidth * Math.max(BEGIN_PHASE_POINTS[2][0] - move2, APPEAR_PHASE_POINTS[2][0]), mWidth * Math.max(BEGIN_PHASE_POINTS[2][1] + move1 - move2, APPEAR_PHASE_POINTS[2][1])); mWavePath.cubicTo( mWidth * Math.max(BEGIN_PHASE_POINTS[3][0] - move2, APPEAR_PHASE_POINTS[3][0]), mWidth * Math.min(BEGIN_PHASE_POINTS[3][1] + move1 + move2, APPEAR_PHASE_POINTS[3][1]), mWidth * Math.max(BEGIN_PHASE_POINTS[4][0] - move2, APPEAR_PHASE_POINTS[4][0]), mWidth * Math.min(BEGIN_PHASE_POINTS[4][1] + move1 + move2, APPEAR_PHASE_POINTS[4][1]), mWidth * APPEAR_PHASE_POINTS[5][0], mWidth * Math.min(BEGIN_PHASE_POINTS[0][1] + move1 + move2, APPEAR_PHASE_POINTS[5][1])); //右半分の描画 mWavePath.cubicTo( mWidth - mWidth * Math.max(BEGIN_PHASE_POINTS[4][0] - move2, APPEAR_PHASE_POINTS[4][0]), mWidth * Math.min(BEGIN_PHASE_POINTS[4][1] + move1 + move2, APPEAR_PHASE_POINTS[4][1]), mWidth - mWidth * Math.max(BEGIN_PHASE_POINTS[3][0] - move2, APPEAR_PHASE_POINTS[3][0]), mWidth * Math.min(BEGIN_PHASE_POINTS[3][1] + move1 + move2, APPEAR_PHASE_POINTS[3][1]), mWidth - mWidth * Math.max(BEGIN_PHASE_POINTS[2][0] - move2, APPEAR_PHASE_POINTS[2][0]), mWidth * Math.max(BEGIN_PHASE_POINTS[2][1] + move1 - move2, APPEAR_PHASE_POINTS[2][1])); mWavePath.cubicTo( mWidth - mWidth * Math.min(BEGIN_PHASE_POINTS[1][0] + move2, APPEAR_PHASE_POINTS[1][0]), mWidth * Math.max(BEGIN_PHASE_POINTS[1][1] + move1 - move2, APPEAR_PHASE_POINTS[1][1]), mWidth - mWidth * APPEAR_PHASE_POINTS[0][0], mWidth * APPEAR_PHASE_POINTS[0][1], mWidth, 0); mCurrentCircleCenterY = mWidth * Math.min(BEGIN_PHASE_POINTS[3][1] + move1 + move2, APPEAR_PHASE_POINTS[3][1]) + mDropCircleRadius; postInvalidateOnAnimation(); } public void expandPhase(float move1, float move2, float move3) { onPreDragWave(); mWavePath.moveTo(0, 0); //左半分の描画 mWavePath.cubicTo(mWidth * EXPAND_PHASE_POINTS[0][0], mWidth * EXPAND_PHASE_POINTS[0][1], mWidth * Math.min( Math.min(BEGIN_PHASE_POINTS[1][0] + move2, APPEAR_PHASE_POINTS[1][0]) + move3, EXPAND_PHASE_POINTS[1][0]), mWidth * Math.max( Math.max(BEGIN_PHASE_POINTS[1][1] + move1 - move2, APPEAR_PHASE_POINTS[1][1]) - move3, EXPAND_PHASE_POINTS[1][1]), mWidth * Math.max(BEGIN_PHASE_POINTS[2][0] - move2, EXPAND_PHASE_POINTS[2][0]), mWidth * Math.min( Math.max(BEGIN_PHASE_POINTS[2][1] + move1 - move2, APPEAR_PHASE_POINTS[2][1]) + move3, EXPAND_PHASE_POINTS[2][1])); mWavePath.cubicTo(mWidth * Math.min( Math.max(BEGIN_PHASE_POINTS[3][0] - move2, APPEAR_PHASE_POINTS[3][0]) + move3, EXPAND_PHASE_POINTS[3][0]), mWidth * Math.min( Math.min(BEGIN_PHASE_POINTS[3][1] + move1 + move2, APPEAR_PHASE_POINTS[3][1]) + move3, EXPAND_PHASE_POINTS[3][1]), mWidth * Math.max(BEGIN_PHASE_POINTS[4][0] - move2, EXPAND_PHASE_POINTS[4][0]), mWidth * Math.min( Math.min(BEGIN_PHASE_POINTS[4][1] + move1 + move2, APPEAR_PHASE_POINTS[4][1]) + move3, EXPAND_PHASE_POINTS[4][1]), mWidth * EXPAND_PHASE_POINTS[5][0], mWidth * Math.min( Math.min(BEGIN_PHASE_POINTS[0][1] + move1 + move2, APPEAR_PHASE_POINTS[5][1]) + move3, EXPAND_PHASE_POINTS[5][1])); //右半分の描画 mWavePath.cubicTo( mWidth - mWidth * Math.max(BEGIN_PHASE_POINTS[4][0] - move2, EXPAND_PHASE_POINTS[4][0]), mWidth * Math.min( Math.min(BEGIN_PHASE_POINTS[4][1] + move1 + move2, APPEAR_PHASE_POINTS[4][1]) + move3, EXPAND_PHASE_POINTS[4][1]), mWidth - mWidth * Math.min( Math.max(BEGIN_PHASE_POINTS[3][0] - move2, APPEAR_PHASE_POINTS[3][0]) + move3, EXPAND_PHASE_POINTS[3][0]), mWidth * Math.min( Math.min(BEGIN_PHASE_POINTS[3][1] + move1 + move2, APPEAR_PHASE_POINTS[3][1]) + move3, EXPAND_PHASE_POINTS[3][1]), mWidth - mWidth * Math.max(BEGIN_PHASE_POINTS[2][0] - move2, EXPAND_PHASE_POINTS[2][0]), mWidth * Math.min( Math.max(BEGIN_PHASE_POINTS[2][1] + move1 - move2, APPEAR_PHASE_POINTS[2][1]) + move3, EXPAND_PHASE_POINTS[2][1])); mWavePath.cubicTo(mWidth - mWidth * Math.min( Math.min(BEGIN_PHASE_POINTS[1][0] + move2, APPEAR_PHASE_POINTS[1][0]) + move3, EXPAND_PHASE_POINTS[1][0]), mWidth * Math.max( Math.max(BEGIN_PHASE_POINTS[1][1] + move1 - move2, APPEAR_PHASE_POINTS[1][1]) - move3, EXPAND_PHASE_POINTS[1][1]), mWidth - mWidth * EXPAND_PHASE_POINTS[0][0], mWidth * EXPAND_PHASE_POINTS[0][1], mWidth, 0); mCurrentCircleCenterY = mWidth * Math.min( Math.min(BEGIN_PHASE_POINTS[3][1] + move1 + move2, APPEAR_PHASE_POINTS[3][1]) + move3, EXPAND_PHASE_POINTS[3][1]) + mDropCircleRadius; postInvalidateOnAnimation(); } /** * @param height 高さ */ private void updateMaxDropHeight(int height) { if (500 * (mWidth / 1440.f) > height) { Log.w("WaveView", "DropHeight is more than " + 500 * (mWidth / 1440.f)); return; } mMaxDropHeight = (int) Math.min(height, getHeight() - mDropCircleRadius); if (mIsManualRefreshing) { mIsManualRefreshing = false; manualRefresh(); } } public void startDropAnimation() { // show dropBubble again mDisappearCircleAnimator = ValueAnimator.ofFloat(1.f, 1.f); mDisappearCircleAnimator.setDuration(1); mDisappearCircleAnimator.start(); mDropCircleAnimator = ValueAnimator.ofFloat(500 * (mWidth / 1440.f), mMaxDropHeight); mDropCircleAnimator.setDuration(DROP_CIRCLE_ANIMATOR_DURATION); mDropCircleAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { mCurrentCircleCenterY = (float) animation.getAnimatedValue(); postInvalidateOnAnimation(); } }); mDropCircleAnimator.setInterpolator(new AccelerateDecelerateInterpolator()); mDropCircleAnimator.start(); mDropVertexAnimator = ValueAnimator.ofFloat(0.f, mMaxDropHeight - mDropCircleRadius); mDropVertexAnimator.setDuration(DROP_VERTEX_ANIMATION_DURATION); mDropVertexAnimator.addUpdateListener(mAnimatorUpdateListener); mDropVertexAnimator.start(); mDropBounceVerticalAnimator = ValueAnimator.ofFloat(0.f, 1.f); mDropBounceVerticalAnimator.setDuration(DROP_BOUNCE_ANIMATOR_DURATION); mDropBounceVerticalAnimator.addUpdateListener(mAnimatorUpdateListener); mDropBounceVerticalAnimator.setInterpolator(new DropBounceInterpolator()); mDropBounceVerticalAnimator.setStartDelay(DROP_VERTEX_ANIMATION_DURATION); mDropBounceVerticalAnimator.start(); mDropBounceHorizontalAnimator = ValueAnimator.ofFloat(0.f, 1.f); mDropBounceHorizontalAnimator.setDuration(DROP_BOUNCE_ANIMATOR_DURATION); mDropBounceHorizontalAnimator.addUpdateListener(mAnimatorUpdateListener); mDropBounceHorizontalAnimator.setInterpolator(new DropBounceInterpolator()); mDropBounceHorizontalAnimator.setStartDelay( (long) (DROP_VERTEX_ANIMATION_DURATION + DROP_BOUNCE_ANIMATOR_DURATION * 0.25)); mDropBounceHorizontalAnimator.start(); } public void startDisappearCircleAnimation() { mDisappearCircleAnimator = ValueAnimator.ofFloat(1.f, 0.f); mDisappearCircleAnimator.addUpdateListener(mAnimatorUpdateListener); mDisappearCircleAnimator.setDuration(DROP_REMOVE_ANIMATOR_DURATION); mDisappearCircleAnimator.addListener(new Animator.AnimatorListener() { @Override public void onAnimationStart(Animator animator) { } @Override public void onAnimationEnd(Animator animator) { //アニメーション修旅時にAnimatorをリセットすることにより落ちてくる円の初期位置を-100.fにする resetAnimator(); mIsManualRefreshing = false; } @Override public void onAnimationCancel(Animator animator) { } @Override public void onAnimationRepeat(Animator animator) { } }); mDisappearCircleAnimator.start(); } /** * @param h 波が始まる高さ */ public void startWaveAnimation(float h) { h = Math.min(h, MAX_WAVE_HEIGHT) * mWidth; mWaveReverseAnimator = ValueAnimator.ofFloat(h, 0.f); mWaveReverseAnimator.setDuration(WAVE_ANIMATOR_DURATION); mWaveReverseAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator valueAnimator) { float h = (Float) valueAnimator.getAnimatedValue(); mWavePath.moveTo(0, 0); mWavePath.quadTo(0.25f * mWidth, 0, 0.333f * mWidth, h * 0.5f); mWavePath.quadTo(mWidth * 0.5f, h * 1.4f, 0.666f * mWidth, h * 0.5f); mWavePath.quadTo(0.75f * mWidth, 0, mWidth, 0); postInvalidate(); } }); mWaveReverseAnimator.setInterpolator(new BounceInterpolator()); mWaveReverseAnimator.start(); } public void animationDropCircle() { if (mDisappearCircleAnimator.isRunning()) { return; } startDropAnimation(); startWaveAnimation(0.1f); } public float getCurrentCircleCenterY() { return mCurrentCircleCenterY; } /** * @param maxDropHeight ある程度の高さ */ public void setMaxDropHeight(int maxDropHeight) { if (mDropHeightUpdated) { updateMaxDropHeight(maxDropHeight); } else { mUpdateMaxDropHeight = maxDropHeight; mDropHeightUpdated = true; if (getViewTreeObserver().isAlive()) { getViewTreeObserver().removeOnPreDrawListener(this); getViewTreeObserver().addOnPreDrawListener(this); } } } public boolean isDisappearCircleAnimatorRunning() { return mDisappearCircleAnimator.isRunning(); } /** * @param radius 影の深さ */ public void setShadowRadius(int radius) { // mShadowPaint.setShadowLayer(radius, 0.0f, 2.0f, SHADOW_COLOR); mPaint.setShadowLayer(radius, 0f, 0f, SHADOW_COLOR); } /** * @param radius 影の深さ */ public void setShadow(int radius, int color) { mPaint.setShadowLayer(radius, 0f, 0f, color); } /** * WaveView is colored by given color (including alpha) * * @param color ARGB color. WaveView will be colored by Black if rgb color is provided. * @see Paint#setColor(int) */ public void setWaveColor(int color) { mPaint.setColor(color); invalidate(); } public void setWaveARGBColor(int a, int r, int g, int b) { mPaint.setARGB(a, r, g, b); invalidate(); } } ================================================ FILE: refresh-header/src/main/res/values/attrs.xml ================================================ ================================================ FILE: refresh-header/src/main/res/values/strings.xml ================================================ SmartRefreshHeader ================================================ FILE: refresh-header/src/test/java/com/scwang/smartrefresh/header/ExampleUnitTest.java ================================================ package com.scwang.smartrefresh.header; import org.junit.Test; import static org.junit.Assert.*; /** * Example local unit test, which will execute on the development machine (host). * * @see Testing documentation */ public class ExampleUnitTest { @Test public void addition_isCorrect() throws Exception { assertEquals(4, 2 + 2); } } ================================================ FILE: refresh-layout/.gitignore ================================================ /build ================================================ FILE: refresh-layout/build.gradle ================================================ apply plugin: 'com.android.library' apply plugin: 'com.novoda.bintray-release' android { compileSdkVersion 26 buildToolsVersion "26.0.1" defaultConfig { minSdkVersion 12 targetSdkVersion 26 versionCode 1 versionName "1.0" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } lintOptions { abortOnError false } } dependencies { compile fileTree(dir: 'libs', include: ['*.jar']) androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', { exclude group: 'com.android.support', module: 'support-annotations' }) testCompile 'junit:junit:4.12' provided 'com.android.support:design:26.0.0-alpha1' } publish { userOrg = 'scwang90' groupId = 'com.scwang.smartrefresh' artifactId = 'SmartRefreshLayout' version = '1.0.3' description = 'An intelligent refresh layout' website = "https://github.com/scwang90/${rootProject.name}" } ================================================ FILE: refresh-layout/proguard-rules.pro ================================================ # Add project specific ProGuard rules here. # By default, the flags in this file are appended to flags specified # in E:\Android\android-sdk/tools/proguard/proguard-android.txt # You can edit the include path and order by changing the proguardFiles # directive in build.gradle. # # For more details, see # http://developer.android.com/guide/developing/tools/proguard.html # Add any project specific keep options here: # If your project uses WebView with JS, uncomment the following # and specify the fully qualified class name to the JavaScript interface # class: #-keepclassmembers class fqcn.of.javascript.interface.for.webview { # public *; #} # 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: refresh-layout/src/androidTest/java/com/scwang/smartrefresh/layout/ExampleInstrumentedTest.java ================================================ package com.scwang.smartrefresh.layout; import android.content.Context; import android.support.test.InstrumentationRegistry; import android.support.test.runner.AndroidJUnit4; import org.junit.Test; import org.junit.runner.RunWith; import static org.junit.Assert.*; /** * Instrumentation test, which will execute on an Android device. * * @see Testing documentation */ @RunWith(AndroidJUnit4.class) public class ExampleInstrumentedTest { @Test public void useAppContext() throws Exception { // Context of the app under test. Context appContext = InstrumentationRegistry.getTargetContext(); assertEquals("com.scwang.smartrefresh.layout.test", appContext.getPackageName()); } } ================================================ FILE: refresh-layout/src/main/AndroidManifest.xml ================================================ ================================================ FILE: refresh-layout/src/main/java/android/support/v4/view/PagerAdapterWrapper.java ================================================ package android.support.v4.view; import android.database.DataSetObserver; import android.os.Parcelable; import android.view.View; import android.view.ViewGroup; import java.lang.reflect.Field; @SuppressWarnings("deprecation") public class PagerAdapterWrapper extends PagerAdapter { protected PagerAdapter wrapped = null; public PagerAdapterWrapper(PagerAdapter wrapped) { this.wrapped = wrapped; } public void attachViewPager(ViewPager viewPager) { //viewPager.mAdapter = this; try { Field[] fields = ViewPager.class.getDeclaredFields(); if (fields != null && fields.length > 0) { for (Field field : fields) { if (PagerAdapter.class.equals(field.getType())) { field.setAccessible(true); field.set(viewPager, this); break; } } } } catch (IllegalAccessException e) { e.printStackTrace(); } } @Override protected void setViewPagerObserver(DataSetObserver observer) { super.setViewPagerObserver(observer); } @Override public int getCount() { return wrapped.getCount(); } @Override public void startUpdate(ViewGroup container) { wrapped.startUpdate(container); } @Override public Object instantiateItem(ViewGroup container, int position) { return wrapped.instantiateItem(container, position); } @Override public void destroyItem(ViewGroup container, int position, Object object) { wrapped.destroyItem(container, position, object); } @Override public void setPrimaryItem(ViewGroup container, int position, Object object) { wrapped.setPrimaryItem(container, position, object); } @Override public void finishUpdate(ViewGroup container) { wrapped.finishUpdate(container); } @Override @Deprecated public void startUpdate(View container) { wrapped.startUpdate(container); } @Override @Deprecated public Object instantiateItem(View container, int position) { return wrapped.instantiateItem(container, position); } @Override @Deprecated public void destroyItem(View container, int position, Object object) { wrapped.destroyItem(container, position, object); } @Override @Deprecated public void setPrimaryItem(View container, int position, Object object) { wrapped.setPrimaryItem(container, position, object); } @Override @Deprecated public void finishUpdate(View container) { wrapped.finishUpdate(container); } @Override public boolean isViewFromObject(View view, Object object) { return wrapped.isViewFromObject(view, object); } @Override public Parcelable saveState() { return wrapped.saveState(); } @Override public void restoreState(Parcelable state, ClassLoader loader) { wrapped.restoreState(state, loader); } @Override public int getItemPosition(Object object) { return wrapped.getItemPosition(object); } @Override public void notifyDataSetChanged() { wrapped.notifyDataSetChanged(); } @Override public void registerDataSetObserver(DataSetObserver observer) { wrapped.registerDataSetObserver(observer); } @Override public void unregisterDataSetObserver(DataSetObserver observer) { wrapped.unregisterDataSetObserver(observer); } @Override public CharSequence getPageTitle(int position) { return wrapped.getPageTitle(position); } @Override public float getPageWidth(int position) { return wrapped.getPageWidth(position); } } ================================================ FILE: refresh-layout/src/main/java/com/scwang/smartrefresh/layout/SmartRefreshLayout.java ================================================ package com.scwang.smartrefresh.layout; import android.animation.Animator; import android.animation.Animator.AnimatorListener; import android.animation.AnimatorListenerAdapter; import android.animation.ValueAnimator; import android.animation.ValueAnimator.AnimatorUpdateListener; import android.content.Context; import android.content.res.TypedArray; import android.graphics.Canvas; import android.graphics.Paint; import android.os.Build; import android.os.Handler; import android.support.annotation.ColorRes; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.annotation.RequiresApi; import android.support.design.widget.CoordinatorLayout; import android.support.v4.content.ContextCompat; import android.support.v4.view.MotionEventCompat; import android.support.v4.view.NestedScrollingChild; import android.support.v4.view.NestedScrollingChildHelper; import android.support.v4.view.NestedScrollingParent; import android.support.v4.view.NestedScrollingParentHelper; import android.support.v4.view.ScrollingView; import android.support.v4.view.ViewCompat; import android.support.v4.view.ViewPager; import android.util.AttributeSet; import android.view.MotionEvent; import android.view.View; import android.view.ViewConfiguration; import android.view.ViewGroup; import android.view.ViewParent; import android.view.animation.DecelerateInterpolator; import android.view.animation.Interpolator; import android.webkit.WebView; import android.widget.AbsListView; import android.widget.ScrollView; import com.scwang.smartrefresh.layout.api.DefaultRefreshFooterCreater; import com.scwang.smartrefresh.layout.api.DefaultRefreshHeaderCreater; import com.scwang.smartrefresh.layout.api.RefreshContent; import com.scwang.smartrefresh.layout.api.RefreshFooter; import com.scwang.smartrefresh.layout.api.RefreshHeader; import com.scwang.smartrefresh.layout.api.RefreshKernel; import com.scwang.smartrefresh.layout.api.RefreshLayout; import com.scwang.smartrefresh.layout.api.ScrollBoundaryDecider; import com.scwang.smartrefresh.layout.constant.DimensionStatus; import com.scwang.smartrefresh.layout.constant.RefreshState; import com.scwang.smartrefresh.layout.constant.SpinnerStyle; import com.scwang.smartrefresh.layout.footer.BallPulseFooter; import com.scwang.smartrefresh.layout.header.BezierRadarHeader; import com.scwang.smartrefresh.layout.header.FalsifyHeader; import com.scwang.smartrefresh.layout.impl.RefreshContentWrapper; import com.scwang.smartrefresh.layout.impl.RefreshFooterWrapper; import com.scwang.smartrefresh.layout.impl.RefreshHeaderWrapper; import com.scwang.smartrefresh.layout.listener.OnLoadmoreListener; import com.scwang.smartrefresh.layout.listener.OnMultiPurposeListener; import com.scwang.smartrefresh.layout.listener.OnRefreshListener; import com.scwang.smartrefresh.layout.listener.OnRefreshLoadmoreListener; import com.scwang.smartrefresh.layout.util.DelayedRunable; import com.scwang.smartrefresh.layout.util.DensityUtil; import com.scwang.smartrefresh.layout.util.ViscousFluidInterpolator; import java.util.ArrayList; import java.util.List; import static android.view.View.MeasureSpec.AT_MOST; import static android.view.View.MeasureSpec.EXACTLY; import static android.view.View.MeasureSpec.getSize; import static android.view.View.MeasureSpec.makeMeasureSpec; import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT; import static com.scwang.smartrefresh.layout.util.DensityUtil.dp2px; import static java.lang.System.currentTimeMillis; /** * 智能刷新布局 * Intelligent Refreshlayout * Created by SCWANG on 2017/5/26. */ @SuppressWarnings({"unused", "WeakerAccess"}) public class SmartRefreshLayout extends ViewGroup implements RefreshLayout, NestedScrollingParent, NestedScrollingChild { // // protected int mTouchSlop; protected int mSpinner;//当前的 Spinner protected int mLastSpinner;//最后的,的Spinner protected int mTouchSpinner;//触摸时候,的Spinner protected int mReboundDuration = 250; protected int mScreenHeightPixels; protected float mTouchX; protected float mTouchY; protected float mLastTouchX;//用于实现Header的左右拖动效果 protected float mLastTouchY;//用于实现多点触摸 protected float mDragRate = .5f; protected boolean mIsBeingDragged; protected Interpolator mReboundInterpolator; protected int mFixedHeaderViewId;//固定在头部的视图Id protected int mFixedFooterViewId;//固定在 头部的视图Id // // protected int[] mPrimaryColors; protected boolean mEnableRefresh = true; protected boolean mEnableLoadmore = false; protected boolean mEnableHeaderTranslationContent = true;//是否启用内容视图拖动效果 protected boolean mEnableFooterTranslationContent = true;//是否启用内容视图拖动效果 protected boolean mEnablePreviewInEditMode = true;//是否在编辑模式下开启预览功能 protected boolean mEnableOverScrollBounce = true;//是否启用越界回弹 protected boolean mEnableAutoLoadmore = true;//是否在列表滚动到底部时自动加载更多 protected boolean mEnablePureScrollMode = false;//是否开启纯滚动模式 protected boolean mEnableScrollContentWhenLoaded = true;//是否在加载更多完成之后滚动内容显示新数据 protected boolean mEnableLoadmoreWhenContentNotFull = false;//在内容不满一页的时候,是否可以上拉加载更多 protected boolean mDisableContentWhenRefresh = false;//是否开启在刷新时候禁止操作内容视图 protected boolean mDisableContentWhenLoading = false;//是否开启在刷新时候禁止操作内容视图 protected boolean mLoadmoreFinished = false;//数据是否全部加载完成,如果完成就不能在触发加载事件 protected boolean mManualLoadmore = false;//是否手动设置过Loadmore,用于智能开启 protected boolean mManualNestedScrolling = false;//是否手动设置过 NestedScrolling,用于智能开启 // // protected OnRefreshListener mRefreshListener; protected OnLoadmoreListener mLoadmoreListener; protected OnMultiPurposeListener mOnMultiPurposeListener; protected ScrollBoundaryDecider mScrollBoundaryDecider; // // protected int[] mParentScrollConsumed = new int[2]; protected int[] mParentOffsetInWindow = new int[2]; protected int mTotalUnconsumed; protected boolean mNestedScrollInProgress; protected NestedScrollingChildHelper mNestedScrollingChildHelper; protected NestedScrollingParentHelper mNestedScrollingParentHelper; // // /** * 头部高度 */ protected int mHeaderHeight; protected DimensionStatus mHeaderHeightStatus = DimensionStatus.DefaultUnNotify; /** * 底部高度 */ protected int mFooterHeight; protected DimensionStatus mFooterHeightStatus = DimensionStatus.DefaultUnNotify; /** * 扩展高度 */ protected int mHeaderExtendHeight; /** * 扩展高度 */ protected int mFooterExtendHeight; /** * 最大拖动比率(最大高度/Header高度) */ protected float mHeaderMaxDragRate = 2.0f; /** * 最大拖动比率(最大高度/Footer高度) */ protected float mFooterMaxDragRate = 2.0f; /** * 下拉头部视图 */ protected RefreshHeader mRefreshHeader; /** * 显示内容视图 */ protected RefreshContent mRefreshContent; /** * 上拉底部视图 */ protected RefreshFooter mRefreshFooter; // protected Paint mPaint; protected Handler handler; protected RefreshKernel mKernel; protected List mDelayedRunables; protected RefreshState mState = RefreshState.None; //主状态 protected RefreshState mViceState = RefreshState.None; //副状态(主状态刷新时候的滚动状态) protected long mLastLoadingTime = 0; protected long mLastRefreshingTime = 0; protected int mHeaderBackgroundColor = 0; //为Header绘制纯色背景 protected int mFooterBackgroundColor = 0; protected boolean mHeaderNeedTouchEventWhenRefreshing; //为游戏Header提供独立事件 protected boolean mFooterNeedTouchEventWhenRefreshing; protected static boolean sManualFooterCreater = false; protected static DefaultRefreshFooterCreater sFooterCreater = new DefaultRefreshFooterCreater() { @NonNull @Override public RefreshFooter createRefreshFooter(Context context, RefreshLayout layout) { return new BallPulseFooter(context); } }; protected static DefaultRefreshHeaderCreater sHeaderCreater = new DefaultRefreshHeaderCreater() { @NonNull @Override public RefreshHeader createRefreshHeader(Context context, RefreshLayout layout) { return new BezierRadarHeader(context); } }; // // public SmartRefreshLayout(Context context) { super(context); this.initView(context, null); } public SmartRefreshLayout(Context context, AttributeSet attrs) { super(context, attrs); this.initView(context, attrs); } public SmartRefreshLayout(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); this.initView(context, attrs); } @RequiresApi(Build.VERSION_CODES.LOLLIPOP) public SmartRefreshLayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); this.initView(context, attrs); } private void initView(Context context, AttributeSet attrs) { setClipToPadding(false); mScreenHeightPixels = context.getResources().getDisplayMetrics().heightPixels; mReboundInterpolator = new ViscousFluidInterpolator(); mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop(); mNestedScrollingParentHelper = new NestedScrollingParentHelper(this); mNestedScrollingChildHelper = new NestedScrollingChildHelper(this); DensityUtil density = new DensityUtil(); TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.SmartRefreshLayout); ViewCompat.setNestedScrollingEnabled(this, ta.getBoolean(R.styleable.SmartRefreshLayout_srlEnableNestedScrolling, false)); mDragRate = ta.getFloat(R.styleable.SmartRefreshLayout_srlDragRate, mDragRate); mHeaderMaxDragRate = ta.getFloat(R.styleable.SmartRefreshLayout_srlHeaderMaxDragRate, mHeaderMaxDragRate); mFooterMaxDragRate = ta.getFloat(R.styleable.SmartRefreshLayout_srlFooterMaxDragRate, mFooterMaxDragRate); mEnableRefresh = ta.getBoolean(R.styleable.SmartRefreshLayout_srlEnableRefresh, mEnableRefresh); mReboundDuration = ta.getInt(R.styleable.SmartRefreshLayout_srlReboundDuration, mReboundDuration); mEnableLoadmore = ta.getBoolean(R.styleable.SmartRefreshLayout_srlEnableLoadmore, mEnableLoadmore); mHeaderHeight = ta.getDimensionPixelOffset(R.styleable.SmartRefreshLayout_srlHeaderHeight, density.dip2px(100)); mFooterHeight = ta.getDimensionPixelOffset(R.styleable.SmartRefreshLayout_srlFooterHeight, density.dip2px(60)); mDisableContentWhenRefresh = ta.getBoolean(R.styleable.SmartRefreshLayout_srlDisableContentWhenRefresh, mDisableContentWhenRefresh); mDisableContentWhenLoading = ta.getBoolean(R.styleable.SmartRefreshLayout_srlDisableContentWhenLoading, mDisableContentWhenLoading); mEnableHeaderTranslationContent = ta.getBoolean(R.styleable.SmartRefreshLayout_srlEnableHeaderTranslationContent, mEnableHeaderTranslationContent); mEnableFooterTranslationContent = ta.getBoolean(R.styleable.SmartRefreshLayout_srlEnableFooterTranslationContent, mEnableFooterTranslationContent); mEnablePreviewInEditMode = ta.getBoolean(R.styleable.SmartRefreshLayout_srlEnablePreviewInEditMode, mEnablePreviewInEditMode); mEnableAutoLoadmore = ta.getBoolean(R.styleable.SmartRefreshLayout_srlEnableAutoLoadmore, mEnableAutoLoadmore); mEnableOverScrollBounce = ta.getBoolean(R.styleable.SmartRefreshLayout_srlEnableOverScrollBounce, mEnableOverScrollBounce); mEnablePureScrollMode = ta.getBoolean(R.styleable.SmartRefreshLayout_srlEnablePureScrollMode, mEnablePureScrollMode); mEnableScrollContentWhenLoaded = ta.getBoolean(R.styleable.SmartRefreshLayout_srlEnableScrollContentWhenLoaded, mEnableScrollContentWhenLoaded); mEnableLoadmoreWhenContentNotFull = ta.getBoolean(R.styleable.SmartRefreshLayout_srlEnableLoadmoreWhenContentNotFull, mEnableLoadmoreWhenContentNotFull); mFixedHeaderViewId = ta.getResourceId(R.styleable.SmartRefreshLayout_srlFixedHeaderViewId, View.NO_ID); mFixedFooterViewId = ta.getResourceId(R.styleable.SmartRefreshLayout_srlFixedFooterViewId, View.NO_ID); mManualLoadmore = ta.hasValue(R.styleable.SmartRefreshLayout_srlEnableLoadmore); mManualNestedScrolling = ta.hasValue(R.styleable.SmartRefreshLayout_srlEnableNestedScrolling); mHeaderHeightStatus = ta.hasValue(R.styleable.SmartRefreshLayout_srlHeaderHeight) ? DimensionStatus.XmlLayoutUnNotify : mHeaderHeightStatus; mFooterHeightStatus = ta.hasValue(R.styleable.SmartRefreshLayout_srlFooterHeight) ? DimensionStatus.XmlLayoutUnNotify : mFooterHeightStatus; mHeaderExtendHeight = (int) Math.max((mHeaderHeight * (mHeaderMaxDragRate - 1)), 0); mFooterExtendHeight = (int) Math.max((mFooterHeight * (mFooterMaxDragRate - 1)), 0); int accentColor = ta.getColor(R.styleable.SmartRefreshLayout_srlAccentColor, 0); int primaryColor = ta.getColor(R.styleable.SmartRefreshLayout_srlPrimaryColor, 0); if (primaryColor != 0) { if (accentColor != 0) { mPrimaryColors = new int[]{primaryColor, accentColor}; } else { mPrimaryColors = new int[]{primaryColor}; } } ta.recycle(); } // // @Override protected void onFinishInflate() { super.onFinishInflate(); final int count = getChildCount(); if (count > 3) { throw new RuntimeException("最多只支持3个子View,Most only support three sub view"); } else if (mEnablePureScrollMode && count > 1) { throw new RuntimeException("PureScrollMode模式只支持一个子View,Most only support one sub view in PureScrollMode"); } //定义为确认的子View索引 boolean[] uncertains = new boolean[count]; //第一次查找确认的 子View for (int i = 0; i < count; i++) { View view = getChildAt(i); if (view instanceof RefreshHeader && mRefreshHeader == null) { mRefreshHeader = ((RefreshHeader) view); } else if (view instanceof RefreshFooter && mRefreshFooter == null) { mEnableLoadmore = mEnableLoadmore || !mManualLoadmore; mRefreshFooter = ((RefreshFooter) view); } else if (mRefreshContent == null && (view instanceof AbsListView || view instanceof WebView || view instanceof ScrollView || view instanceof ScrollingView || view instanceof NestedScrollingChild || view instanceof NestedScrollingParent || view instanceof ViewPager)) { mRefreshContent = new RefreshContentWrapper(view); } else if (RefreshHeaderWrapper.isTagedHeader(view) && mRefreshHeader == null) { mRefreshHeader = new RefreshHeaderWrapper(view); } else if (RefreshFooterWrapper.isTagedFooter(view) && mRefreshFooter == null) { mRefreshFooter = new RefreshFooterWrapper(view); } else if (RefreshContentWrapper.isTagedContent(view) && mRefreshContent == null) { mRefreshContent = new RefreshContentWrapper(view); } else { uncertains[i] = true;//标记未确认 } } //如果有 未确认(uncertains)的子View 通过智能算法计算 for (int i = 0; i < count; i++) { if (uncertains[i]) { View view = getChildAt(i); if (count == 1 && mRefreshContent == null) { mRefreshContent = new RefreshContentWrapper(view); } else if (i == 0 && mRefreshHeader == null) { mRefreshHeader = new RefreshHeaderWrapper(view); } else if (count == 2 && mRefreshContent == null) { mRefreshContent = new RefreshContentWrapper(view); } else if (i == 2 && mRefreshFooter == null) { mEnableLoadmore = mEnableLoadmore || !mManualLoadmore; mRefreshFooter = new RefreshFooterWrapper(view); } else if (mRefreshContent == null) { mRefreshContent = new RefreshContentWrapper(view); } } } if (isInEditMode()) { if (mPrimaryColors != null) { if (mRefreshHeader != null) { mRefreshHeader.setPrimaryColors(mPrimaryColors); } if (mRefreshFooter != null) { mRefreshFooter.setPrimaryColors(mPrimaryColors); } } //重新排序 if (mRefreshContent != null) { bringChildToFront(mRefreshContent.getView()); } if (mRefreshHeader != null && mRefreshHeader.getSpinnerStyle() != SpinnerStyle.FixedBehind) { bringChildToFront(mRefreshHeader.getView()); } if (mRefreshFooter != null && mRefreshFooter.getSpinnerStyle() != SpinnerStyle.FixedBehind) { bringChildToFront(mRefreshFooter.getView()); } if (mKernel == null) { mKernel = new RefreshKernelImpl(); } } } @Override protected void onAttachedToWindow() { super.onAttachedToWindow(); if (isInEditMode()) return; if (mKernel == null) { mKernel = new RefreshKernelImpl(); } if (handler == null) { handler = new Handler(); } if (mDelayedRunables != null) { for (DelayedRunable runable : mDelayedRunables) { handler.postDelayed(runable, runable.delayMillis); } mDelayedRunables.clear(); mDelayedRunables = null; } if (mRefreshContent == null && mRefreshHeader == null && mRefreshFooter == null) { onFinishInflate(); } if (mRefreshHeader == null) { if (mEnablePureScrollMode) { mRefreshHeader = new FalsifyHeader(getContext()); } else { mRefreshHeader = sHeaderCreater.createRefreshHeader(getContext(), this); } if (!(mRefreshHeader.getView().getLayoutParams() instanceof MarginLayoutParams)) { if (mRefreshHeader.getSpinnerStyle() == SpinnerStyle.Scale) { addView(mRefreshHeader.getView(), MATCH_PARENT, MATCH_PARENT); } else { addView(mRefreshHeader.getView(), MATCH_PARENT, WRAP_CONTENT); } } } if (mRefreshFooter == null) { if (mEnablePureScrollMode) { mRefreshFooter = new RefreshFooterWrapper(new FalsifyHeader(getContext())); mEnableLoadmore = mEnableLoadmore || !mManualLoadmore; } else { mRefreshFooter = sFooterCreater.createRefreshFooter(getContext(), this); mEnableLoadmore = mEnableLoadmore || (!mManualLoadmore && sManualFooterCreater); } if (!(mRefreshFooter.getView().getLayoutParams() instanceof MarginLayoutParams)) { if (mRefreshFooter.getSpinnerStyle() == SpinnerStyle.Scale) { addView(mRefreshFooter.getView(), MATCH_PARENT, MATCH_PARENT); } else { addView(mRefreshFooter.getView(), MATCH_PARENT, WRAP_CONTENT); } } } if (mRefreshContent == null) { for (int i = 0, len = getChildCount(); i < len; i++) { View view = getChildAt(i); if ((mRefreshHeader == null || view != mRefreshHeader.getView()) && (mRefreshFooter == null || view != mRefreshFooter.getView())) { mRefreshContent = new RefreshContentWrapper(view); } } if (mRefreshContent == null) { mRefreshContent = new RefreshContentWrapper(getContext()); mRefreshContent.getView().setLayoutParams(new LayoutParams(MATCH_PARENT, MATCH_PARENT)); } } View fixedHeaderView = mFixedHeaderViewId > 0 ? findViewById(mFixedHeaderViewId) : null; View fixedFooterView = mFixedFooterViewId > 0 ? findViewById(mFixedFooterViewId) : null; mRefreshContent.setScrollBoundaryDecider(mScrollBoundaryDecider); mRefreshContent.setEnableLoadmoreWhenContentNotFull(mEnableLoadmoreWhenContentNotFull || mEnablePureScrollMode); mRefreshContent.setupComponent(mKernel, fixedHeaderView, fixedFooterView); if (mSpinner != 0) { notifyStateChanged(RefreshState.None); mRefreshContent.moveSpinner(mSpinner = 0); } //重新排序 bringChildToFront(mRefreshContent.getView()); if (mRefreshHeader.getSpinnerStyle() != SpinnerStyle.FixedBehind) { bringChildToFront(mRefreshHeader.getView()); } if (mRefreshFooter.getSpinnerStyle() != SpinnerStyle.FixedBehind) { bringChildToFront(mRefreshFooter.getView()); } if (mRefreshListener == null) { mRefreshListener = new OnRefreshListener() { @Override public void onRefresh(RefreshLayout refreshlayout) { refreshlayout.finishRefresh(3000); } }; } if (mLoadmoreListener == null) { mLoadmoreListener = new OnLoadmoreListener() { @Override public void onLoadmore(RefreshLayout refreshlayout) { refreshlayout.finishLoadmore(2000); } }; } if (mPrimaryColors != null) { mRefreshHeader.setPrimaryColors(mPrimaryColors); mRefreshFooter.setPrimaryColors(mPrimaryColors); } try { if (!mManualNestedScrolling && !isNestedScrollingEnabled()) { for (ViewParent parent = this ; parent != null ; parent = parent.getParent()) { if (parent instanceof CoordinatorLayout) { setNestedScrollingEnabled(true); mManualNestedScrolling = false; break; } } } } catch (Throwable e) {//try 不能删除,否则会出现兼容性问题 } } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int minimumHeight = 0; final boolean isInEditMode = isInEditMode() && mEnablePreviewInEditMode; if (mRefreshHeader != null) { final View headerView = mRefreshHeader.getView(); final LayoutParams lp = (LayoutParams) headerView.getLayoutParams(); final int widthSpec = getChildMeasureSpec(widthMeasureSpec, lp.leftMargin + lp.rightMargin, lp.width); int heightSpec = heightMeasureSpec; if (mHeaderHeightStatus.gteReplaceWith(DimensionStatus.XmlLayoutUnNotify)) { heightSpec = makeMeasureSpec(Math.max(mHeaderHeight - lp.bottomMargin, 0), EXACTLY); headerView.measure(widthSpec, heightSpec); } else if (mRefreshHeader.getSpinnerStyle() == SpinnerStyle.MatchLayout) { headerView.measure(widthSpec, heightSpec); } else if (lp.height > 0) { if (mHeaderHeightStatus.canReplaceWith(DimensionStatus.XmlExact)) { mHeaderHeightStatus = DimensionStatus.XmlExact; mHeaderHeight = lp.height + lp.bottomMargin; mHeaderExtendHeight = (int) Math.max((mHeaderHeight * (mHeaderMaxDragRate - 1)), 0); mRefreshHeader.onInitialized(mKernel, mHeaderHeight, mHeaderExtendHeight); } heightSpec = makeMeasureSpec(lp.height, EXACTLY); headerView.measure(widthSpec, heightSpec); } else if (lp.height == WRAP_CONTENT) { heightSpec = makeMeasureSpec(Math.max(getSize(heightMeasureSpec) - lp.bottomMargin, 0), AT_MOST); headerView.measure(widthSpec, heightSpec); int measuredHeight = headerView.getMeasuredHeight(); if (measuredHeight > 0 && mHeaderHeightStatus.canReplaceWith(DimensionStatus.XmlWrap)) { mHeaderHeightStatus = DimensionStatus.XmlWrap; mHeaderHeight = headerView.getMeasuredHeight() + lp.bottomMargin; mHeaderExtendHeight = (int) Math.max((mHeaderHeight * (mHeaderMaxDragRate - 1)), 0); mRefreshHeader.onInitialized(mKernel, mHeaderHeight, mHeaderExtendHeight); } else if (measuredHeight <= 0) { heightSpec = makeMeasureSpec(Math.max(mHeaderHeight - lp.bottomMargin, 0), EXACTLY); headerView.measure(widthSpec, heightSpec); } } else if (lp.height == MATCH_PARENT) { heightSpec = makeMeasureSpec(Math.max(mHeaderHeight - lp.bottomMargin, 0), EXACTLY); headerView.measure(widthSpec, heightSpec); } else { headerView.measure(widthSpec, heightSpec); } if (mRefreshHeader.getSpinnerStyle() == SpinnerStyle.Scale && !isInEditMode) { final int height = Math.max(0, mSpinner); heightSpec = makeMeasureSpec(Math.max(height - lp.bottomMargin, 0), EXACTLY); headerView.measure(widthSpec, heightSpec); } if (!mHeaderHeightStatus.notifyed) { mHeaderHeightStatus = mHeaderHeightStatus.notifyed(); mRefreshHeader.onInitialized(mKernel, mHeaderHeight, mHeaderExtendHeight); } if (isInEditMode) { minimumHeight += headerView.getMeasuredHeight(); } } if (mRefreshFooter != null) { final View footerView = mRefreshFooter.getView(); final LayoutParams lp = (LayoutParams) footerView.getLayoutParams(); final int widthSpec = getChildMeasureSpec(widthMeasureSpec, lp.leftMargin + lp.rightMargin, lp.width); int heightSpec = heightMeasureSpec; if (mFooterHeightStatus.gteReplaceWith(DimensionStatus.XmlLayoutUnNotify)) { heightSpec = makeMeasureSpec(Math.max(mFooterHeight - lp.topMargin, 0), EXACTLY); footerView.measure(widthSpec, heightSpec); } else if (mRefreshFooter.getSpinnerStyle() == SpinnerStyle.MatchLayout) { footerView.measure(widthSpec, heightSpec); } else if (lp.height > 0) { if (mFooterHeightStatus.canReplaceWith(DimensionStatus.XmlExact)) { mFooterHeightStatus = DimensionStatus.XmlExact; mFooterHeight = lp.height + lp.topMargin; mFooterExtendHeight = (int) Math.max((mFooterHeight * (mFooterMaxDragRate - 1)), 0); mRefreshFooter.onInitialized(mKernel, mFooterHeight, mFooterExtendHeight); } heightSpec = makeMeasureSpec(lp.height - lp.topMargin, EXACTLY); footerView.measure(widthSpec, heightSpec); } else if (lp.height == WRAP_CONTENT) { heightSpec = makeMeasureSpec(Math.max(getSize(heightMeasureSpec) - lp.topMargin, 0), AT_MOST); footerView.measure(widthSpec, heightSpec); int measuredHeight = footerView.getMeasuredHeight(); if (measuredHeight > 0 && mFooterHeightStatus.canReplaceWith(DimensionStatus.XmlWrap)) { mFooterHeightStatus = DimensionStatus.XmlWrap; mFooterHeight = footerView.getMeasuredHeight() + lp.topMargin; mFooterExtendHeight = (int) Math.max((mFooterHeight * (mFooterMaxDragRate - 1)), 0); mRefreshFooter.onInitialized(mKernel, mFooterHeight, mFooterExtendHeight); } else if (measuredHeight <= 0) { heightSpec = makeMeasureSpec(Math.max(mFooterHeight - lp.topMargin, 0), EXACTLY); footerView.measure(widthSpec, heightSpec); } } else if (lp.height == MATCH_PARENT) { heightSpec = makeMeasureSpec(Math.max(mFooterHeight - lp.topMargin, 0), EXACTLY); footerView.measure(widthSpec, heightSpec); } else { footerView.measure(widthSpec, heightSpec); } if (mRefreshFooter.getSpinnerStyle() == SpinnerStyle.Scale && !isInEditMode) { final int height = Math.max(0, -mSpinner); heightSpec = makeMeasureSpec(Math.max(height - lp.topMargin, 0), EXACTLY); footerView.measure(widthSpec, heightSpec); } if (!mFooterHeightStatus.notifyed) { mFooterHeightStatus = mFooterHeightStatus.notifyed(); mRefreshFooter.onInitialized(mKernel, mFooterHeight, mFooterExtendHeight); } if (isInEditMode) { minimumHeight += footerView.getMeasuredHeight(); } } if (mRefreshContent != null) { final LayoutParams lp = (LayoutParams) mRefreshContent.getLayoutParams(); final int widthSpec = getChildMeasureSpec(widthMeasureSpec, getPaddingLeft() + getPaddingRight() + lp.leftMargin + lp.rightMargin, lp.width); final int heightSpec = getChildMeasureSpec(heightMeasureSpec, getPaddingTop() + getPaddingBottom() + lp.topMargin + lp.bottomMargin + ((isInEditMode && mRefreshHeader != null && (mEnableHeaderTranslationContent || mRefreshHeader.getSpinnerStyle() == SpinnerStyle.FixedBehind)) ? mHeaderHeight : 0) + ((isInEditMode && mRefreshFooter != null && (mEnableFooterTranslationContent || mRefreshFooter.getSpinnerStyle() == SpinnerStyle.FixedBehind)) ? mFooterHeight : 0), lp.height); mRefreshContent.measure(widthSpec, heightSpec); mRefreshContent.onInitialHeaderAndFooter(mHeaderHeight, mFooterHeight); minimumHeight += mRefreshContent.getMeasuredHeight(); } setMeasuredDimension(resolveSize(getSuggestedMinimumWidth(), widthMeasureSpec), resolveSize(minimumHeight, heightMeasureSpec)); mLastTouchX = getMeasuredWidth() / 2; } @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { final int paddingLeft = getPaddingLeft(); final int paddingTop = getPaddingTop(); final int paddingBottom = getPaddingBottom(); final boolean isInEditMode = isInEditMode() && mEnablePreviewInEditMode; if (mRefreshContent != null) { final LayoutParams lp = (LayoutParams) mRefreshContent.getLayoutParams(); int left = paddingLeft + lp.leftMargin; int top = paddingTop + lp.topMargin; int right = left + mRefreshContent.getMeasuredWidth(); int bottom = top + mRefreshContent.getMeasuredHeight(); if (isInEditMode && mRefreshHeader != null && (mEnableHeaderTranslationContent || mRefreshHeader.getSpinnerStyle() == SpinnerStyle.FixedBehind)) { top = top + mHeaderHeight; bottom = bottom + mHeaderHeight; } mRefreshContent.layout(left, top, right, bottom); } if (mRefreshHeader != null) { final View headerView = mRefreshHeader.getView(); final LayoutParams lp = (LayoutParams) headerView.getLayoutParams(); int left = lp.leftMargin; int top = lp.topMargin; int right = left + headerView.getMeasuredWidth(); int bottom = top + headerView.getMeasuredHeight(); if (!isInEditMode) { if (mRefreshHeader.getSpinnerStyle() == SpinnerStyle.Translate) { top = top - mHeaderHeight + Math.max(0, mSpinner); bottom = top + headerView.getMeasuredHeight(); } else if (mRefreshHeader.getSpinnerStyle() == SpinnerStyle.Scale) { bottom = top + Math.max(Math.max(0, mSpinner) - lp.bottomMargin, 0); } } headerView.layout(left, top, right, bottom); } if (mRefreshFooter != null) { final View footerView = mRefreshFooter.getView(); final LayoutParams lp = (LayoutParams) footerView.getLayoutParams(); final SpinnerStyle style = mRefreshFooter.getSpinnerStyle(); int left = lp.leftMargin; int top = lp.topMargin + getMeasuredHeight(); if (isInEditMode || style == SpinnerStyle.FixedFront || style == SpinnerStyle.FixedBehind) { top = top - mFooterHeight; } else if (style == SpinnerStyle.Scale || style == SpinnerStyle.Translate) { top = top - Math.max(Math.max(-mSpinner, 0) - lp.topMargin, 0); } int right = left + footerView.getMeasuredWidth(); int bottom = top + footerView.getMeasuredHeight(); footerView.layout(left, top, right, bottom); } } @Override protected void dispatchDraw(Canvas canvas) { boolean isInEditMode = mEnablePreviewInEditMode && isInEditMode(); if (mHeaderBackgroundColor != 0 && (mSpinner > 0 || isInEditMode)) { mPaint.setColor(mHeaderBackgroundColor); canvas.drawRect(0, 0, getWidth(), (isInEditMode) ? mHeaderHeight : mSpinner, mPaint); } else if (mFooterBackgroundColor != 0 && (mSpinner < 0 || isInEditMode)) { final int height = getHeight(); mPaint.setColor(mFooterBackgroundColor); canvas.drawRect(0, height - (isInEditMode ? (mFooterHeight) : -mSpinner), getWidth(), height, mPaint); } super.dispatchDraw(canvas); } @Override protected void onDetachedFromWindow() { super.onDetachedFromWindow(); mSpinner = 0; mRefreshContent.moveSpinner(0); notifyStateChanged(RefreshState.None); handler.removeCallbacksAndMessages(null); handler = null; mKernel = null; // mRefreshHeader = null; // mRefreshFooter = null; // mRefreshContent = null; mManualLoadmore = true; mManualNestedScrolling = true; } // // MotionEvent mFalsifyEvent = null; @Override public boolean dispatchTouchEvent(MotionEvent e) { // //--------------------------------------------------------------------------- //多点触摸计算代码 //--------------------------------------------------------------------------- final int action = MotionEventCompat.getActionMasked(e); final boolean pointerUp = action == MotionEvent.ACTION_POINTER_UP; final int skipIndex = pointerUp ? e.getActionIndex() : -1; // Determine focal point float sumX = 0, sumY = 0; final int count = e.getPointerCount(); for (int i = 0; i < count; i++) { if (skipIndex == i) continue; sumX += e.getX(i); sumY += e.getY(i); } final int div = pointerUp ? count - 1 : count; final float touchX = sumX / div; final float touchY = sumY / div; if ((action == MotionEvent.ACTION_POINTER_UP || action == MotionEvent.ACTION_POINTER_DOWN) && mIsBeingDragged) { mTouchY += touchY - mLastTouchY; } mLastTouchX = touchX; mLastTouchY = touchY; //--------------------------------------------------------------------------- // if (mRefreshContent != null) { //为 RefreshContent 传递当前触摸事件的坐标,用于智能判断对应坐标位置View的滚动边界和相关信息 switch (action) { case MotionEvent.ACTION_DOWN: mRefreshContent.onActionDown(e); break; case MotionEvent.ACTION_UP: case MotionEvent.ACTION_CANCEL: mRefreshContent.onActionUpOrCancel(); } } if ((reboundAnimator != null && !interceptAnimator(action)) || (mState == RefreshState.Loading && mDisableContentWhenLoading) || (mState == RefreshState.Refreshing && mDisableContentWhenRefresh)) { return false; } if (mNestedScrollInProgress) {//嵌套滚动时,补充竖直方向不滚动,但是水平方向滚动,需要通知 onHorizontalDrag int totalUnconsumed = this.mTotalUnconsumed; boolean ret = super.dispatchTouchEvent(e); if (action == MotionEvent.ACTION_MOVE && totalUnconsumed == mTotalUnconsumed) { final int offsetX = (int) mLastTouchX; final int offsetMax = getWidth(); final float percentX = mLastTouchX / offsetMax; if (mSpinner > 0 && mRefreshHeader != null && mRefreshHeader.isSupportHorizontalDrag()) { mRefreshHeader.onHorizontalDrag(percentX, offsetX, offsetMax); } else if (mSpinner < 0 && mRefreshFooter != null && mRefreshFooter.isSupportHorizontalDrag()) { mRefreshFooter.onHorizontalDrag(percentX, offsetX, offsetMax); } } return ret; } else if (!isEnabled() || (!mEnableRefresh && !mEnableLoadmore) || (mHeaderNeedTouchEventWhenRefreshing && (mState == RefreshState.Refreshing || mState == RefreshState.RefreshFinish)) || (mFooterNeedTouchEventWhenRefreshing && (mState == RefreshState.Loading || mState == RefreshState.LoadFinish)) ) { return super.dispatchTouchEvent(e); } switch (action) { case MotionEvent.ACTION_DOWN: mTouchX = touchX; mTouchY = touchY; mLastTouchY = touchY; mLastSpinner = 0; mTouchSpinner = mSpinner; mIsBeingDragged = false; super.dispatchTouchEvent(e); return true; case MotionEvent.ACTION_MOVE: float dx = touchX - mTouchX; float dy = touchY - mTouchY; mLastTouchY = touchY; if (!mIsBeingDragged) { if (Math.abs(dy) >= mTouchSlop && Math.abs(dx) < Math.abs(dy)) {//滑动允许最大角度为45度 if (dy > 0 && (mSpinner < 0 || (mEnableRefresh && mRefreshContent.canRefresh()))) { if (mSpinner < 0) { setStatePullUpToLoad(); } else { setStatePullDownToRefresh(); } mIsBeingDragged = true; mTouchY = touchY - mTouchSlop; dy = touchY - mTouchY; e.setAction(MotionEvent.ACTION_CANCEL); super.dispatchTouchEvent(e); } else if (dy < 0 && (mSpinner > 0 || (mEnableLoadmore && mRefreshContent.canLoadmore()))) { if (mSpinner > 0) { setStatePullDownToRefresh(); } else { setStatePullUpToLoad(); } mIsBeingDragged = true; mTouchY = touchY + mTouchSlop; dy = touchY - mTouchY; e.setAction(MotionEvent.ACTION_CANCEL); super.dispatchTouchEvent(e); } else { return super.dispatchTouchEvent(e); } } else { return super.dispatchTouchEvent(e); } } if (mIsBeingDragged) { final float spinner = dy + mTouchSpinner; if ((mRefreshContent != null) && (getViceState().isHeader() && (spinner < 0 || mLastSpinner < 0)) || (getViceState().isFooter() && (spinner > 0 || mLastSpinner > 0))) { long time = e.getEventTime(); if (mFalsifyEvent == null) { mFalsifyEvent = MotionEvent.obtain(time, time, MotionEvent.ACTION_DOWN, mTouchX + dx, mTouchY, 0); super.dispatchTouchEvent(mFalsifyEvent); } MotionEvent em = MotionEvent.obtain(time, time, MotionEvent.ACTION_MOVE, mTouchX + dx, mTouchY + spinner, 0); super.dispatchTouchEvent(em); if ((getViceState().isHeader() && spinner < 0) || (getViceState().isFooter() && spinner > 0)) { mLastSpinner = (int) spinner; if (mSpinner != 0) { moveSpinnerInfinitely(0); } return true; } mLastSpinner = (int) spinner; mFalsifyEvent = null; MotionEvent ec = MotionEvent.obtain(time, time, MotionEvent.ACTION_CANCEL, mTouchX, mTouchY + spinner, 0); super.dispatchTouchEvent(ec); } if (getViceState().isDraging()) { moveSpinnerInfinitely(spinner); return true; } } break; case MotionEvent.ACTION_UP: case MotionEvent.ACTION_CANCEL: mIsBeingDragged = false; if (mFalsifyEvent != null) { mFalsifyEvent = null; long time = e.getEventTime(); MotionEvent ec = MotionEvent.obtain(time, time, mSpinner == 0 ? MotionEvent.ACTION_UP : MotionEvent.ACTION_CANCEL, mTouchX, touchY, 0); super.dispatchTouchEvent(ec); } if (overSpinner()) { return true; } break; } return super.dispatchTouchEvent(e); } /** * 在动画执行时,触摸屏幕,打断动画,转为拖动状态 */ protected boolean interceptAnimator(int action) { if (reboundAnimator != null && action == MotionEvent.ACTION_DOWN) { if (mState == RefreshState.LoadFinish || mState == RefreshState.RefreshFinish) { return false; } if (mState == RefreshState.PullDownCanceled) { setStatePullDownToRefresh(); } else if (mState == RefreshState.PullUpCanceled) { setStatePullUpToLoad(); } reboundAnimator.cancel(); reboundAnimator = null; return true; } return false; } @Override public void requestDisallowInterceptTouchEvent(boolean b) { // if this is a List < L or another view that doesn't support nested // scrolling, ignore this request so that the vertical scroll event // isn't stolen View target = mRefreshContent.getScrollableView(); if ((android.os.Build.VERSION.SDK_INT >= 21 || !(target instanceof AbsListView)) && (target == null || ViewCompat.isNestedScrollingEnabled(target))) { super.requestDisallowInterceptTouchEvent(b); //} else { // Nope. } } // // protected void notifyStateChanged(RefreshState state) { final RefreshState oldState = mState; if (oldState != state) { mState = state; mViceState = state; if (mRefreshFooter != null) { mRefreshFooter.onStateChanged(this, oldState, state); } if (mRefreshHeader != null) { mRefreshHeader.onStateChanged(this, oldState, state); } if (mOnMultiPurposeListener != null) { mOnMultiPurposeListener.onStateChanged(this, oldState, state); } } } protected void setStatePullUpToLoad() { if (mState != RefreshState.Refreshing && mState != RefreshState.Loading) { notifyStateChanged(RefreshState.PullToUpLoad); } else { setViceState(RefreshState.PullToUpLoad); } } protected void setStateReleaseToLoad() { if (mState != RefreshState.Refreshing && mState != RefreshState.Loading) { notifyStateChanged(RefreshState.ReleaseToLoad); } else { setViceState(RefreshState.ReleaseToLoad); } } protected void setStateReleaseToRefresh() { if (mState != RefreshState.Refreshing && mState != RefreshState.Loading) { notifyStateChanged(RefreshState.ReleaseToRefresh); } else { setViceState(RefreshState.ReleaseToRefresh); } } protected void setStatePullDownToRefresh() { if (mState != RefreshState.Refreshing && mState != RefreshState.Loading) { notifyStateChanged(RefreshState.PullDownToRefresh); } else { setViceState(RefreshState.PullDownToRefresh); } } protected void setStatePullDownCanceled() { if (mState != RefreshState.Refreshing && mState != RefreshState.Loading) { notifyStateChanged(RefreshState.PullDownCanceled); resetStatus(); } else { setViceState(RefreshState.PullDownCanceled); } } protected void setStatePullUpCanceled() { if (mState != RefreshState.Refreshing && mState != RefreshState.Loading) { notifyStateChanged(RefreshState.PullUpCanceled); resetStatus(); } else { setViceState(RefreshState.PullUpCanceled); } } protected void setStateLodingFinish() { notifyStateChanged(RefreshState.LoadFinish); } protected void setStateRefresingFinish() { notifyStateChanged(RefreshState.RefreshFinish); } protected void setStateLoding() { mLastLoadingTime = currentTimeMillis(); notifyStateChanged(RefreshState.Loading); animSpinner(-mFooterHeight); if (mLoadmoreListener != null) { mLoadmoreListener.onLoadmore(this); } if (mRefreshFooter != null) { mRefreshFooter.onStartAnimator(this, mFooterHeight, mFooterExtendHeight); } if (mOnMultiPurposeListener != null) { mOnMultiPurposeListener.onLoadmore(this); mOnMultiPurposeListener.onFooterStartAnimator(mRefreshFooter, mFooterHeight, mFooterExtendHeight); } } protected void setStateRefresing() { mLastRefreshingTime = currentTimeMillis(); notifyStateChanged(RefreshState.Refreshing); animSpinner(mHeaderHeight); if (mRefreshListener != null) { mRefreshListener.onRefresh(this); } if (mRefreshHeader != null) { mRefreshHeader.onStartAnimator(this, mHeaderHeight, mHeaderExtendHeight); } if (mOnMultiPurposeListener != null) { mOnMultiPurposeListener.onRefresh(this); mOnMultiPurposeListener.onHeaderStartAnimator(mRefreshHeader, mHeaderHeight, mHeaderExtendHeight); } } /** * 重置状态 */ protected void resetStatus() { if (mState != RefreshState.None) { if (mSpinner == 0) { notifyStateChanged(RefreshState.None); } } if (mSpinner != 0) { animSpinner(0); } } protected RefreshState getViceState() { return (mState == RefreshState.Refreshing || mState == RefreshState.Loading) ? mViceState : mState; } protected void setViceState(RefreshState state) { if (mState == RefreshState.Refreshing || mState == RefreshState.Loading) { if (mViceState != state) { mViceState = state; } } } // // // protected ValueAnimator reboundAnimator; protected AnimatorListener reboundAnimatorEndListener = new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { reboundAnimator = null; if ((int) ((ValueAnimator) animation).getAnimatedValue() == 0) { if (mState != RefreshState.None && mState != RefreshState.Refreshing && mState != RefreshState.Loading) { notifyStateChanged(RefreshState.None); } } } }; protected AnimatorUpdateListener reboundUpdateListener = new AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { moveSpinner((int) animation.getAnimatedValue(), true); } }; // protected ValueAnimator animSpinner(int endSpinner) { return animSpinner(endSpinner, 0); } protected ValueAnimator animSpinner(int endSpinner, int startDelay) { return animSpinner(endSpinner, startDelay, mReboundInterpolator); } /** * 执行回弹动画 */ protected ValueAnimator animSpinner(int endSpinner, int startDelay, Interpolator interpolator) { if (mSpinner != endSpinner) { if (reboundAnimator != null) { reboundAnimator.cancel(); } reboundAnimator = ValueAnimator.ofInt(mSpinner, endSpinner); reboundAnimator.setDuration(mReboundDuration); reboundAnimator.setInterpolator(interpolator); reboundAnimator.addUpdateListener(reboundUpdateListener); reboundAnimator.addListener(reboundAnimatorEndListener); reboundAnimator.setStartDelay(startDelay); reboundAnimator.start(); } return reboundAnimator; } /** * 越界回弹动画 */ protected ValueAnimator animSpinnerBounce(int bounceSpinner) { if (reboundAnimator == null) { mLastTouchX = getMeasuredWidth() / 2; if (mState == RefreshState.Refreshing && bounceSpinner > 0) { reboundAnimator = ValueAnimator.ofInt(mSpinner, Math.min(2 * bounceSpinner, mHeaderHeight)); reboundAnimator.addListener(reboundAnimatorEndListener); } else if (mState == RefreshState.Loading && bounceSpinner < 0) { reboundAnimator = ValueAnimator.ofInt(mSpinner, Math.max(2 * bounceSpinner, -mFooterHeight)); reboundAnimator.addListener(reboundAnimatorEndListener); } else if (mSpinner == 0 && mEnableOverScrollBounce) { if (bounceSpinner > 0) { if (mState != RefreshState.Loading) { setStatePullDownToRefresh(); } reboundAnimator = ValueAnimator.ofInt(0, Math.min(bounceSpinner, mHeaderHeight + mHeaderExtendHeight)); } else { if (mState != RefreshState.Refreshing) { setStatePullUpToLoad(); } reboundAnimator = ValueAnimator.ofInt(0, Math.max(bounceSpinner, -mFooterHeight - mFooterExtendHeight)); } reboundAnimator.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationStart(Animator animation) { } @Override public void onAnimationEnd(Animator animation) { reboundAnimator = ValueAnimator.ofInt(mSpinner, 0); reboundAnimator.setDuration(mReboundDuration * 2 / 3); reboundAnimator.setInterpolator(new DecelerateInterpolator()); reboundAnimator.addUpdateListener(reboundUpdateListener); reboundAnimator.addListener(reboundAnimatorEndListener); reboundAnimator.start(); } }); } if (reboundAnimator != null) { reboundAnimator.setDuration(mReboundDuration * 2 / 3); reboundAnimator.setInterpolator(new DecelerateInterpolator()); reboundAnimator.addUpdateListener(reboundUpdateListener); reboundAnimator.start(); } } return reboundAnimator; } /** * 手势拖动结束 * 开始执行回弹动画 */ protected boolean overSpinner() { if (mState == RefreshState.Loading) { if (mSpinner < -mFooterHeight) { mTotalUnconsumed = -mFooterHeight; animSpinner(-mFooterHeight); } else if (mSpinner > 0) { mTotalUnconsumed = 0; animSpinner(0); } else { return false; } } else if (mState == RefreshState.Refreshing) { if (mSpinner > mHeaderHeight) { mTotalUnconsumed = mHeaderHeight; animSpinner(mHeaderHeight); } else if (mSpinner < 0) { mTotalUnconsumed = 0; animSpinner(0); } else { return false; } } else if (mState == RefreshState.PullDownToRefresh || (mEnablePureScrollMode && mState == RefreshState.ReleaseToRefresh)) { setStatePullDownCanceled(); } else if (mState == RefreshState.PullToUpLoad || (mEnablePureScrollMode && mState == RefreshState.ReleaseToLoad)) { setStatePullUpCanceled(); } else if (mState == RefreshState.ReleaseToRefresh) { setStateRefresing(); } else if (mState == RefreshState.ReleaseToLoad) { setStateLoding(); } else if (mSpinner != 0) { animSpinner(0); } else { return false; } return true; } protected void moveSpinnerInfinitely(float dy) { if (mState == RefreshState.Refreshing && dy >= 0) { if (dy < mHeaderHeight) { moveSpinner((int) dy, false); } else { final double M = mHeaderExtendHeight; final double H = Math.max(mScreenHeightPixels * 4 / 3, getHeight()) - mHeaderHeight; final double x = Math.max(0, (dy - mHeaderHeight) * mDragRate); final double y = Math.min(M * (1 - Math.pow(100, -x / H)), x);// 公式 y = M(1-40^(-x/H)) moveSpinner((int) y + mHeaderHeight, false); } } else if (mState == RefreshState.Loading && dy < 0) { if (dy > -mFooterHeight) { moveSpinner((int) dy, false); } else { final double M = mFooterExtendHeight; final double H = Math.max(mScreenHeightPixels * 4 / 3, getHeight()) - mFooterHeight; final double x = -Math.min(0, (dy + mHeaderHeight) * mDragRate); final double y = -Math.min(M * (1 - Math.pow(100, -x / H)), x);// 公式 y = M(1-40^(-x/H)) moveSpinner((int) y - mFooterHeight, false); } } else if (dy >= 0) { final double M = mHeaderExtendHeight + mHeaderHeight; final double H = Math.max(mScreenHeightPixels / 2, getHeight()); final double x = Math.max(0, dy * mDragRate); final double y = Math.min(M * (1 - Math.pow(100, -x / H)), x);// 公式 y = M(1-40^(-x/H)) moveSpinner((int) y, false); } else { final double M = mFooterExtendHeight + mFooterHeight; final double H = Math.max(mScreenHeightPixels / 2, getHeight()); final double x = -Math.min(0, dy * mDragRate); final double y = -Math.min(M * (1 - Math.pow(100, -x / H)), x);// 公式 y = M(1-40^(-x/H)) moveSpinner((int) y, false); } } /** * 移动滚动 Scroll * moveSpinner 的取名来自 谷歌官方的 @{@link android.support.v4.widget.SwipeRefreshLayout#moveSpinner(float)} */ protected void moveSpinner(int spinner, boolean isAnimator) { if (mSpinner == spinner && (mRefreshHeader == null || !mRefreshHeader.isSupportHorizontalDrag()) && (mRefreshFooter == null || !mRefreshFooter.isSupportHorizontalDrag())) { return; } final int oldSpinner = mSpinner; this.mSpinner = spinner; if (!isAnimator && getViceState().isDraging()) { if (mSpinner > mHeaderHeight) { setStateReleaseToRefresh(); } else if (-mSpinner > mFooterHeight && !mLoadmoreFinished) { setStateReleaseToLoad(); } else if (mSpinner < 0 && !mLoadmoreFinished) { setStatePullUpToLoad(); } else if (mSpinner > 0) { setStatePullDownToRefresh(); } } if (mRefreshContent != null) { if (spinner > 0) { if (mEnableHeaderTranslationContent || mRefreshHeader == null || mRefreshHeader.getSpinnerStyle() == SpinnerStyle.FixedBehind) { mRefreshContent.moveSpinner(spinner); if (mHeaderBackgroundColor != 0) { invalidate(); } } } else { if (mEnableFooterTranslationContent || mRefreshFooter == null || mRefreshFooter.getSpinnerStyle() == SpinnerStyle.FixedBehind) { mRefreshContent.moveSpinner(spinner); if (mHeaderBackgroundColor != 0) { invalidate(); } } } } if ((spinner > 0 || oldSpinner > 0) && mRefreshHeader != null) { spinner = Math.max(spinner, 0); if (mEnableRefresh || (mState == RefreshState.RefreshFinish && isAnimator)) { if (oldSpinner != mSpinner && (mRefreshHeader.getSpinnerStyle() == SpinnerStyle.Scale || mRefreshHeader.getSpinnerStyle() == SpinnerStyle.Translate)) { mRefreshHeader.getView().requestLayout(); } } final int offset = spinner; final int headerHeight = mHeaderHeight; final int extendHeight = mHeaderExtendHeight; final float percent = 1f * spinner / mHeaderHeight; if (isAnimator) { mRefreshHeader.onReleasing(percent, offset, headerHeight, extendHeight); if (mOnMultiPurposeListener != null) { mOnMultiPurposeListener.onHeaderReleasing(mRefreshHeader, percent, offset, headerHeight, extendHeight); } } else { if (mRefreshHeader.isSupportHorizontalDrag()) { final int offsetX = (int) mLastTouchX; final int offsetMax = getWidth(); final float percentX = mLastTouchX / offsetMax; mRefreshHeader.onHorizontalDrag(percentX, offsetX, offsetMax); } mRefreshHeader.onPullingDown(percent, offset, headerHeight, extendHeight); if (mOnMultiPurposeListener != null) { mOnMultiPurposeListener.onHeaderPulling(mRefreshHeader, percent, offset, headerHeight, extendHeight); } } } if ((spinner < 0 || oldSpinner < 0) && mRefreshFooter != null) { spinner = Math.min(spinner, 0); if (mEnableLoadmore || (mState == RefreshState.LoadFinish && isAnimator)) { if (oldSpinner != mSpinner && (mRefreshFooter.getSpinnerStyle() == SpinnerStyle.Scale || mRefreshFooter.getSpinnerStyle() == SpinnerStyle.Translate)) { mRefreshFooter.getView().requestLayout(); } } final int offset = -spinner; final int footerHeight = mFooterHeight; final int extendHeight = mFooterExtendHeight; final float percent = -spinner * 1f / mFooterHeight; if (isAnimator) { mRefreshFooter.onPullReleasing(percent, offset, footerHeight, extendHeight); if (mOnMultiPurposeListener != null) { mOnMultiPurposeListener.onFooterReleasing(mRefreshFooter, percent, offset, footerHeight, extendHeight); } } else { if (mRefreshFooter.isSupportHorizontalDrag()) { final int offsetX = (int) mLastTouchX; final int offsetMax = getWidth(); final float percentX = mLastTouchX / offsetMax; mRefreshFooter.onHorizontalDrag(percentX, offsetX, offsetMax); } mRefreshFooter.onPullingUp(percent, offset, footerHeight, extendHeight); if (mOnMultiPurposeListener != null) { mOnMultiPurposeListener.onFooterPulling(mRefreshFooter, percent, offset, footerHeight, extendHeight); } } } } // // @Override protected boolean checkLayoutParams(ViewGroup.LayoutParams p) { return p instanceof LayoutParams; } @Override protected LayoutParams generateDefaultLayoutParams() { return new LayoutParams(MATCH_PARENT, MATCH_PARENT); } @Override protected LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) { return new LayoutParams(p); } @Override public LayoutParams generateLayoutParams(AttributeSet attrs) { return new LayoutParams(getContext(), attrs); } public static class LayoutParams extends MarginLayoutParams { public LayoutParams(Context context, AttributeSet attrs) { super(context, attrs); TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.SmartRefreshLayout_Layout); backgroundColor = ta.getColor(R.styleable.SmartRefreshLayout_Layout_layout_srlBackgroundColor, backgroundColor); if (ta.hasValue(R.styleable.SmartRefreshLayout_Layout_layout_srlSpinnerStyle)) { spinnerStyle = SpinnerStyle.values()[ta.getInt(R.styleable.SmartRefreshLayout_Layout_layout_srlSpinnerStyle, SpinnerStyle.Translate.ordinal())]; } ta.recycle(); } public LayoutParams(int width, int height) { super(width, height); } public LayoutParams(MarginLayoutParams source) { super(source); } public LayoutParams(ViewGroup.LayoutParams source) { super(source); } public int backgroundColor = 0; public SpinnerStyle spinnerStyle = null; } // // // @Override public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes) { boolean accepted = isEnabled() && isNestedScrollingEnabled() && (nestedScrollAxes & ViewCompat.SCROLL_AXIS_VERTICAL) != 0; accepted = accepted && (mEnableRefresh || mEnableLoadmore); return accepted; } @Override public void onNestedScrollAccepted(View child, View target, int axes) { // Reset the counter of how much leftover scroll needs to be consumed. mNestedScrollingParentHelper.onNestedScrollAccepted(child, target, axes); // Dispatch up to the nested parent startNestedScroll(axes & ViewCompat.SCROLL_AXIS_VERTICAL); mTotalUnconsumed = 0; mTouchSpinner = mSpinner; mNestedScrollInProgress = true; } @Override public void onNestedPreScroll(View target, int dx, int dy, int[] consumed) { // If we are in the middle of consuming, a scroll, then we want to move the spinner back up // before allowing the list to scroll if (mState == RefreshState.Refreshing || mState == RefreshState.Loading) { final int[] parentConsumed = mParentScrollConsumed; if (dispatchNestedPreScroll(dx, dy, parentConsumed, null)) { dy -= parentConsumed[1]; } //判断 mTotalUnconsumed和dy 同为负数或者正数 if (mState == RefreshState.Refreshing && (dy * mTotalUnconsumed > 0 || mTouchSpinner > 0)) { consumed[1] = 0; if (Math.abs(dy) > Math.abs(mTotalUnconsumed)) { consumed[1] += mTotalUnconsumed; mTotalUnconsumed = 0; dy -= mTotalUnconsumed; if (mTouchSpinner <= 0) { moveSpinnerInfinitely(0); } } else { mTotalUnconsumed -= dy; consumed[1] += dy; dy = 0; moveSpinnerInfinitely(mTotalUnconsumed + mTouchSpinner); } if (dy > 0 && mTouchSpinner > 0) { if (dy > mTouchSpinner) { consumed[1] += mTouchSpinner; mTouchSpinner = 0; } else { mTouchSpinner -= dy; consumed[1] += dy; } moveSpinnerInfinitely(mTouchSpinner); } } else { if (mState == RefreshState.Loading && (dy * mTotalUnconsumed > 0 || mTouchSpinner < 0)) { consumed[1] = 0; if (Math.abs(dy) > Math.abs(mTotalUnconsumed)) { consumed[1] += mTotalUnconsumed; mTotalUnconsumed = 0; dy -= mTotalUnconsumed; if (mTouchSpinner >= 0) { moveSpinnerInfinitely(0); } } else { mTotalUnconsumed -= dy; consumed[1] += dy; dy = 0; moveSpinnerInfinitely(mTotalUnconsumed + mTouchSpinner); } if (dy < 0 && mTouchSpinner < 0) { if (dy < mTouchSpinner) { consumed[1] += mTouchSpinner; mTouchSpinner = 0; } else { mTouchSpinner -= dy; consumed[1] += dy; } moveSpinnerInfinitely(mTouchSpinner); } } } } else { if (mEnableRefresh && dy > 0 && mTotalUnconsumed > 0) { if (dy > mTotalUnconsumed) { consumed[1] = dy - mTotalUnconsumed; mTotalUnconsumed = 0; } else { mTotalUnconsumed -= dy; consumed[1] = dy; } moveSpinnerInfinitely(mTotalUnconsumed); } else if (mEnableLoadmore && dy < 0 && mTotalUnconsumed < 0) { if (dy < mTotalUnconsumed) { consumed[1] = dy - mTotalUnconsumed; mTotalUnconsumed = 0; } else { mTotalUnconsumed -= dy; consumed[1] = dy; } moveSpinnerInfinitely(mTotalUnconsumed); } // If a client layout is using a custom start position for the circle // view, they mean to hide it again before scrolling the child view // If we get back to mTotalUnconsumed == 0 and there is more to go, hide // the circle so it isn't exposed if its blocking content is moved // if (mUsingCustomStart && dy > 0 && mTotalUnconsumed == 0 // && Math.abs(dy - consumed[1]) > 0) { // mCircleView.setVisibility(View.GONE); // } // Now let our nested parent consume the leftovers final int[] parentConsumed = mParentScrollConsumed; if (dispatchNestedPreScroll(dx - consumed[0], dy - consumed[1], parentConsumed, null)) { consumed[0] += parentConsumed[0]; consumed[1] += parentConsumed[1]; } } } @Override public int getNestedScrollAxes() { return mNestedScrollingParentHelper.getNestedScrollAxes(); } @Override public void onStopNestedScroll(View target) { mNestedScrollingParentHelper.onStopNestedScroll(target); mNestedScrollInProgress = false; // Finish the spinner for nested scrolling if we ever consumed any // unconsumed nested scroll // if (mState != RefreshState.Refreshing && mState != RefreshState.Loading) { // } mTotalUnconsumed = 0; overSpinner(); // Dispatch up our nested parent stopNestedScroll(); } @Override public void onNestedScroll(final View target, final int dxConsumed, final int dyConsumed, final int dxUnconsumed, final int dyUnconsumed) { // Dispatch up to the nested parent first dispatchNestedScroll(dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, mParentOffsetInWindow); // This is a bit of a hack. Nested scrolling works from the bottom up, and as we are // sometimes between two nested scrolling views, we need a way to be able to know when any // nested scrolling parent has stopped handling events. We do that by using the // 'offset in window 'functionality to see if we have been moved from the event. // This is a decent indication of whether we should take over the event stream or not. final int dy = dyUnconsumed + mParentOffsetInWindow[1]; if (mState == RefreshState.Refreshing || mState == RefreshState.Loading) { if (mEnableRefresh && dy < 0 && (mRefreshContent == null || mRefreshContent.canRefresh())) { mTotalUnconsumed += Math.abs(dy); moveSpinnerInfinitely(mTotalUnconsumed + mTouchSpinner); } else if (mEnableLoadmore && dy > 0 && (mRefreshContent == null || mRefreshContent.canLoadmore())) { mTotalUnconsumed -= Math.abs(dy); moveSpinnerInfinitely(mTotalUnconsumed + mTouchSpinner); } } else { if (mEnableRefresh && dy < 0 && (mRefreshContent == null || mRefreshContent.canRefresh())) { if (mState == RefreshState.None) { setStatePullDownToRefresh(); } mTotalUnconsumed += Math.abs(dy); moveSpinnerInfinitely(mTotalUnconsumed); } else if (mEnableLoadmore && dy > 0 && (mRefreshContent == null || mRefreshContent.canLoadmore())) { if (mState == RefreshState.None && !mLoadmoreFinished) { setStatePullUpToLoad(); } mTotalUnconsumed -= Math.abs(dy); moveSpinnerInfinitely(mTotalUnconsumed); } } } @Override public boolean onNestedPreFling(View target, float velocityX, float velocityY) { return reboundAnimator != null || mState == RefreshState.ReleaseToRefresh || mState == RefreshState.ReleaseToLoad || (mState == RefreshState.PullDownToRefresh && mSpinner > 0) || (mState == RefreshState.PullToUpLoad && mSpinner > 0) || (mState == RefreshState.Refreshing && mSpinner != 0) || (mState == RefreshState.Loading && mSpinner != 0) || dispatchNestedPreFling(velocityX, velocityY); } @Override public boolean onNestedFling(View target, float velocityX, float velocityY, boolean consumed) { return dispatchNestedFling(velocityX, velocityY, consumed); } // // @Override public void setNestedScrollingEnabled(boolean enabled) { mManualNestedScrolling = true; mNestedScrollingChildHelper.setNestedScrollingEnabled(enabled); } @Override public boolean isNestedScrollingEnabled() { return mNestedScrollingChildHelper.isNestedScrollingEnabled(); } @Override public boolean startNestedScroll(int axes) { return mNestedScrollingChildHelper.startNestedScroll(axes); } @Override public void stopNestedScroll() { mNestedScrollingChildHelper.stopNestedScroll(); } @Override public boolean hasNestedScrollingParent() { return mNestedScrollingChildHelper.hasNestedScrollingParent(); } @Override public boolean dispatchNestedScroll(int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed, int[] offsetInWindow) { return mNestedScrollingChildHelper.dispatchNestedScroll(dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, offsetInWindow); } @Override public boolean dispatchNestedPreScroll(int dx, int dy, int[] consumed, int[] offsetInWindow) { return mNestedScrollingChildHelper.dispatchNestedPreScroll( dx, dy, consumed, offsetInWindow); } @Override public boolean dispatchNestedFling(float velocityX, float velocityY, boolean consumed) { return mNestedScrollingChildHelper.dispatchNestedFling(velocityX, velocityY, consumed); } @Override public boolean dispatchNestedPreFling(float velocityX, float velocityY) { return mNestedScrollingChildHelper.dispatchNestedPreFling(velocityX, velocityY); } // // // @Override public SmartRefreshLayout setFooterHeight(float heightDp) { return setFooterHeightPx(dp2px(heightDp)); } @Override public SmartRefreshLayout setFooterHeightPx(int heightPx) { if (mFooterHeightStatus.canReplaceWith(DimensionStatus.CodeExact)) { mFooterHeight = heightPx; mFooterExtendHeight = (int) Math.max((heightPx * (mFooterMaxDragRate - 1)), 0); mFooterHeightStatus = DimensionStatus.CodeExactUnNotify; if (mRefreshFooter != null) { mRefreshFooter.getView().requestLayout(); } } return this; } @Override public SmartRefreshLayout setHeaderHeight(float heightDp) { return setHeaderHeightPx(dp2px(heightDp)); } @Override public SmartRefreshLayout setHeaderHeightPx(int heightPx) { if (mHeaderHeightStatus.canReplaceWith(DimensionStatus.CodeExact)) { mHeaderHeight = heightPx; mHeaderExtendHeight = (int) Math.max((heightPx * (mHeaderMaxDragRate - 1)), 0); mHeaderHeightStatus = DimensionStatus.CodeExactUnNotify; if (mRefreshHeader != null) { mRefreshHeader.getView().requestLayout(); } } return this; } @Override public SmartRefreshLayout setDragRate(float rate) { this.mDragRate = rate; return this; } /** * 设置下拉最大高度和Header高度的比率(将会影响可以下拉的最大高度) */ @Override public SmartRefreshLayout setHeaderMaxDragRate(float rate) { this.mHeaderMaxDragRate = rate; this.mHeaderExtendHeight = (int) Math.max((mHeaderHeight * (mHeaderMaxDragRate - 1)), 0); if (mRefreshHeader != null && mKernel != null) { mRefreshHeader.onInitialized(mKernel, mHeaderHeight, mHeaderExtendHeight); } else { mHeaderHeightStatus = mHeaderHeightStatus.unNotify(); } return this; } /** * 设置上啦最大高度和Footer高度的比率(将会影响可以上啦的最大高度) */ @Override public SmartRefreshLayout setFooterMaxDragRate(float rate) { this.mFooterMaxDragRate = rate; this.mFooterExtendHeight = (int) Math.max((mFooterHeight * (mFooterMaxDragRate - 1)), 0); if (mRefreshFooter != null && mKernel != null) { mRefreshFooter.onInitialized(mKernel, mFooterHeight, mFooterExtendHeight); } else { mFooterHeightStatus = mFooterHeightStatus.unNotify(); } return this; } /** * 设置回弹显示插值器 */ @Override public SmartRefreshLayout setReboundInterpolator(Interpolator interpolator) { this.mReboundInterpolator = interpolator; return this; } /** * 设置回弹动画时长 */ @Override public SmartRefreshLayout setReboundDuration(int duration) { this.mReboundDuration = duration; return this; } /** * 设置是否启用上啦加载更多(默认启用) */ @Override public SmartRefreshLayout setEnableLoadmore(boolean enable) { this.mManualLoadmore = true; this.mEnableLoadmore = enable; return this; } /** * 是否启用下拉刷新(默认启用) */ @Override public SmartRefreshLayout setEnableRefresh(boolean enable) { this.mEnableRefresh = enable; return this; } /** * 设置是否启用内容视图拖动效果 */ @Override public SmartRefreshLayout setEnableHeaderTranslationContent(boolean enable) { this.mEnableHeaderTranslationContent = enable; return this; } /** * 设置是否启用内容视图拖动效果 */ @Override public SmartRefreshLayout setEnableFooterTranslationContent(boolean enable) { this.mEnableFooterTranslationContent = enable; return this; } /** * 设置是否开启在刷新时候禁止操作内容视图 */ @Override public SmartRefreshLayout setDisableContentWhenRefresh(boolean disable) { this.mDisableContentWhenRefresh = disable; return this; } /** * 设置是否开启在加载时候禁止操作内容视图 */ @Override public SmartRefreshLayout setDisableContentWhenLoading(boolean disable) { this.mDisableContentWhenLoading = disable; return this; } /** * 设置是否监听列表在滚动到底部时触发加载事件 */ @Override public SmartRefreshLayout setEnableAutoLoadmore(boolean enable) { this.mEnableAutoLoadmore = enable; return this; } /** * 设置是否启用越界回弹 */ @Override public SmartRefreshLayout setEnableOverScrollBounce(boolean enable) { this.mEnableOverScrollBounce = enable; return this; } /** * 设置是否开启纯滚动模式 */ @Override public SmartRefreshLayout setEnablePureScrollMode(boolean enable) { this.mEnablePureScrollMode = enable; if (mRefreshContent != null) { mRefreshContent.setEnableLoadmoreWhenContentNotFull(enable || mEnableLoadmoreWhenContentNotFull); } return this; } /** * 设置是否在加载更多完成之后滚动内容显示新数据 */ @Override public SmartRefreshLayout setEnableScrollContentWhenLoaded(boolean enable) { this.mEnableScrollContentWhenLoaded = enable; return this; } /** * 设置在内容不满一页的时候,是否可以上拉加载更多 */ @Override public SmartRefreshLayout setEnableLoadmoreWhenContentNotFull(boolean enable) { this.mEnableLoadmoreWhenContentNotFull = enable; if (mRefreshContent != null) { mRefreshContent.setEnableLoadmoreWhenContentNotFull(enable || mEnablePureScrollMode); } return this; } /** * 设置是会否启用嵌套滚动功能(默认关闭+智能开启) */ @Override public RefreshLayout setEnableNestedScroll(boolean enabled) { setNestedScrollingEnabled(enabled); return this; } /** * 设置指定的Header */ @Override public SmartRefreshLayout setRefreshHeader(RefreshHeader header) { if (header != null) { if (mRefreshHeader != null) { removeView(mRefreshHeader.getView()); } this.mRefreshHeader = header; this.mHeaderHeightStatus = mHeaderHeightStatus.unNotify(); if (header.getSpinnerStyle() == SpinnerStyle.FixedBehind) { this.addView(mRefreshHeader.getView(), 0, new LayoutParams(MATCH_PARENT, WRAP_CONTENT)); } else { this.addView(mRefreshHeader.getView(), MATCH_PARENT, WRAP_CONTENT); } } return this; } /** * 设置指定的Header */ @Override public SmartRefreshLayout setRefreshHeader(RefreshHeader header, int width, int height) { if (header != null) { if (mRefreshHeader != null) { removeView(mRefreshHeader.getView()); } this.mRefreshHeader = header; this.mHeaderHeightStatus = mHeaderHeightStatus.unNotify(); if (header.getSpinnerStyle() == SpinnerStyle.FixedBehind) { this.addView(mRefreshHeader.getView(), 0, new LayoutParams(width, height)); } else { this.addView(mRefreshHeader.getView(), width, height); } } return this; } /** * 设置指定的Footer */ @Override public SmartRefreshLayout setRefreshFooter(RefreshFooter footer) { if (footer != null) { if (mRefreshFooter != null) { removeView(mRefreshFooter.getView()); } this.mRefreshFooter = footer; this.mFooterHeightStatus = mFooterHeightStatus.unNotify(); this.mEnableLoadmore = !mManualLoadmore || mEnableLoadmore; if (mRefreshFooter.getSpinnerStyle() == SpinnerStyle.FixedBehind) { this.addView(mRefreshFooter.getView(), 0, new LayoutParams(MATCH_PARENT, WRAP_CONTENT)); } else { this.addView(mRefreshFooter.getView(), MATCH_PARENT, WRAP_CONTENT); } } return this; } /** * 设置指定的Footer */ @Override public SmartRefreshLayout setRefreshFooter(RefreshFooter footer, int width, int height) { if (footer != null) { if (mRefreshFooter != null) { removeView(mRefreshFooter.getView()); } this.mRefreshFooter = footer; this.mFooterHeightStatus = mFooterHeightStatus.unNotify(); this.mEnableLoadmore = !mManualLoadmore || mEnableLoadmore; if (mRefreshFooter.getSpinnerStyle() == SpinnerStyle.FixedBehind) { this.addView(mRefreshFooter.getView(), 0, new LayoutParams(width, height)); } else { this.addView(mRefreshFooter.getView(), width, height); } } return this; } /** * 获取底部上啦组件的实现 */ @Nullable @Override public RefreshFooter getRefreshFooter() { return mRefreshFooter; } /** * 获取顶部下拉组件的实现 */ @Nullable @Override public RefreshHeader getRefreshHeader() { return mRefreshHeader; } /** * 获取状态 */ @Override public RefreshState getState() { return mState; } /** * 获取实体布局视图 */ @Override public SmartRefreshLayout getLayout() { return this; } /** * 单独设置刷新监听器 */ @Override public SmartRefreshLayout setOnRefreshListener(OnRefreshListener listener) { this.mRefreshListener = listener; return this; } /** * 单独设置加载监听器 */ @Override public SmartRefreshLayout setOnLoadmoreListener(OnLoadmoreListener listener) { this.mLoadmoreListener = listener; this.mEnableLoadmore = mEnableLoadmore || (!mManualLoadmore && listener != null); return this; } /** * 同时设置刷新和加载监听器 */ @Override public SmartRefreshLayout setOnRefreshLoadmoreListener(OnRefreshLoadmoreListener listener) { this.mRefreshListener = listener; this.mLoadmoreListener = listener; this.mEnableLoadmore = mEnableLoadmore || (!mManualLoadmore && listener != null); return this; } /** * 设置多功能监听器 */ @Override public SmartRefreshLayout setOnMultiPurposeListener(OnMultiPurposeListener listener) { this.mOnMultiPurposeListener = listener; return this; } /** * 设置主题颜色 */ @Override public SmartRefreshLayout setPrimaryColorsId(@ColorRes int... primaryColorId) { int[] colors = new int[primaryColorId.length]; for (int i = 0; i < primaryColorId.length; i++) { colors[i] = ContextCompat.getColor(getContext(), primaryColorId[i]); } setPrimaryColors(colors); return this; } /** * 设置主题颜色 */ @Override public SmartRefreshLayout setPrimaryColors(int... colors) { if (mRefreshHeader != null) { mRefreshHeader.setPrimaryColors(colors); } if (mRefreshFooter != null) { mRefreshFooter.setPrimaryColors(colors); } mPrimaryColors = colors; return this; } /** * 设置滚动边界 */ @Override public RefreshLayout setScrollBoundaryDecider(ScrollBoundaryDecider boundary) { mScrollBoundaryDecider = boundary; if (mRefreshContent != null) { mRefreshContent.setScrollBoundaryDecider(boundary); } return this; } /** * 设置数据全部加载完成,将不能再次触发加载功能 */ @Override public SmartRefreshLayout setLoadmoreFinished(boolean finished) { mLoadmoreFinished = finished; if (mRefreshFooter != null) { mRefreshFooter.setLoadmoreFinished(finished); } return this; } /** * 完成刷新 */ @Override public SmartRefreshLayout finishRefresh() { long passTime = System.currentTimeMillis() - mLastRefreshingTime; return finishRefresh(Math.max(0, 1000 - (int) passTime));//保证刷新动画有1000毫秒的时间 } /** * 完成加载 */ @Override public SmartRefreshLayout finishLoadmore() { long passTime = System.currentTimeMillis() - mLastLoadingTime; return finishLoadmore(Math.max(0, 1000 - (int) passTime));//保证加载动画有1000毫秒的时间 } /** * 完成刷新 */ @Override public SmartRefreshLayout finishRefresh(int delayed) { return finishRefresh(delayed, true); } /** * 完成刷新 */ @Override public SmartRefreshLayout finishRefresh(boolean success) { long passTime = System.currentTimeMillis() - mLastRefreshingTime; return finishRefresh(Math.max(0, 1000 - (int) passTime), success); } /** * 完成刷新 */ @Override public SmartRefreshLayout finishRefresh(int delayed, final boolean success) { postDelayed(new Runnable() { @Override public void run() { if (mState == RefreshState.Refreshing) { if (mRefreshHeader != null) { int startDelay = mRefreshHeader.onFinish(SmartRefreshLayout.this, success); notifyStateChanged(RefreshState.RefreshFinish); if (mOnMultiPurposeListener != null) { mOnMultiPurposeListener.onHeaderFinish(mRefreshHeader, success); } if (startDelay < Integer.MAX_VALUE) { if (mSpinner == 0) { resetStatus(); } else { animSpinner(0, startDelay); } } } else { resetStatus(); } } } }, delayed); return this; } /** * 完成加载 */ @Override public SmartRefreshLayout finishLoadmore(int delayed) { return finishLoadmore(delayed, true); } /** * 完成加载 */ @Override public SmartRefreshLayout finishLoadmore(boolean success) { long passTime = System.currentTimeMillis() - mLastLoadingTime; return finishLoadmore(Math.max(0, 1000 - (int) passTime), success); } /** * 完成加载 */ @Override public SmartRefreshLayout finishLoadmore(int delayed, final boolean success) { postDelayed(new Runnable() { @Override public void run() { if (mState == RefreshState.Loading) { if (mRefreshFooter != null && mKernel != null && mRefreshContent != null) { int startDelay = mRefreshFooter.onFinish(SmartRefreshLayout.this, success); if (startDelay == Integer.MAX_VALUE) { return; } notifyStateChanged(RefreshState.LoadFinish); AnimatorUpdateListener updateListener = mRefreshContent.onLoadingFinish(mKernel, mFooterHeight, startDelay, mReboundDuration); if (mOnMultiPurposeListener != null) { mOnMultiPurposeListener.onFooterFinish(mRefreshFooter, success); } if (mSpinner == 0) { resetStatus(); } else { ValueAnimator valueAnimator = animSpinner(0, startDelay); if (updateListener != null && valueAnimator != null) { valueAnimator.addUpdateListener(updateListener); } } } else { resetStatus(); } } } }, delayed); return this; } /** * 是否正在刷新 */ @Override public boolean isRefreshing() { return mState == RefreshState.Refreshing; } /** * 是否正在加载 */ @Override public boolean isLoading() { return mState == RefreshState.Loading; } /** * 自动刷新 */ @Override public boolean autoRefresh() { return autoRefresh(400); } /** * 自动刷新 */ @Override public boolean autoRefresh(int delayed) { return autoRefresh(delayed, 1f * (mHeaderHeight + mHeaderExtendHeight / 2) / mHeaderHeight); } /** * 自动刷新 */ @Override public boolean autoRefresh(int delayed, final float dragrate) { if (mState == RefreshState.None && mEnableRefresh) { if (reboundAnimator != null) { reboundAnimator.cancel(); } Runnable runnable = new Runnable() { @Override public void run() { reboundAnimator = ValueAnimator.ofInt(mSpinner, (int) (mHeaderHeight * dragrate)); reboundAnimator.setDuration(mReboundDuration); reboundAnimator.setInterpolator(new DecelerateInterpolator()); reboundAnimator.addUpdateListener(new AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { moveSpinner((int) animation.getAnimatedValue(), false); } }); reboundAnimator.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationStart(Animator animation) { mLastTouchX = getMeasuredWidth() / 2; setStatePullDownToRefresh(); } @Override public void onAnimationEnd(Animator animation) { reboundAnimator = null; if (mState != RefreshState.ReleaseToRefresh) { setStateReleaseToRefresh(); } overSpinner(); } }); reboundAnimator.start(); } }; if (delayed > 0) { reboundAnimator = new ValueAnimator(); postDelayed(runnable, delayed); } else { runnable.run(); } return true; } else { return false; } } /** * 自动加载 */ @Override public boolean autoLoadmore() { return autoLoadmore(0); } /** * 自动加载 */ @Override public boolean autoLoadmore(int delayed) { return autoLoadmore(delayed, 1f * (mFooterHeight + mFooterExtendHeight / 2) / mFooterHeight); } /** * 自动加载 */ @Override public boolean autoLoadmore(int delayed, final float dragrate) { if (mState == RefreshState.None && (mEnableLoadmore && !mLoadmoreFinished)) { if (reboundAnimator != null) { reboundAnimator.cancel(); } Runnable runnable = new Runnable() { @Override public void run() { reboundAnimator = ValueAnimator.ofInt(mSpinner, -(int) (mFooterHeight * dragrate)); reboundAnimator.setDuration(mReboundDuration); reboundAnimator.setInterpolator(new DecelerateInterpolator()); reboundAnimator.addUpdateListener(new AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { moveSpinner((int) animation.getAnimatedValue(), false); } }); reboundAnimator.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationStart(Animator animation) { mLastTouchX = getMeasuredWidth() / 2; setStatePullUpToLoad(); } @Override public void onAnimationEnd(Animator animation) { reboundAnimator = null; if (mState != RefreshState.ReleaseToLoad) { setStateReleaseToLoad(); } overSpinner(); } }); reboundAnimator.start(); } }; if (delayed > 0) { reboundAnimator = new ValueAnimator(); postDelayed(runnable, delayed); } else { runnable.run(); } return true; } else { return false; } } @Override public boolean isEnableLoadmore() { return mEnableLoadmore; } @Override public boolean isLoadmoreFinished() { return mLoadmoreFinished; } @Override public boolean isEnableAutoLoadmore() { return mEnableAutoLoadmore; } @Override public boolean isEnableRefresh() { return mEnableRefresh; } @Override public boolean isEnableOverScrollBounce() { return mEnableOverScrollBounce; } @Override public boolean isEnablePureScrollMode() { return mEnablePureScrollMode; } @Override public boolean isEnableScrollContentWhenLoaded() { return mEnableScrollContentWhenLoaded; } /** * 设置默认Header构建器 */ public static void setDefaultRefreshHeaderCreater(@NonNull DefaultRefreshHeaderCreater creater) { sHeaderCreater = creater; } /** * 设置默认Footer构建器 */ public static void setDefaultRefreshFooterCreater(@NonNull DefaultRefreshFooterCreater creater) { sFooterCreater = creater; sManualFooterCreater = true; } // // protected class RefreshKernelImpl implements RefreshKernel { @NonNull @Override public RefreshLayout getRefreshLayout() { return SmartRefreshLayout.this; } @NonNull @Override public RefreshContent getRefreshContent() { return SmartRefreshLayout.this.mRefreshContent; } // public RefreshKernel setStatePullUpToLoad() { SmartRefreshLayout.this.setStatePullUpToLoad(); return this; } public RefreshKernel setStateReleaseToLoad() { SmartRefreshLayout.this.setStateReleaseToLoad(); return this; } public RefreshKernel setStateReleaseToRefresh() { SmartRefreshLayout.this.setStateReleaseToRefresh(); return this; } public RefreshKernel setStatePullDownToRefresh() { SmartRefreshLayout.this.setStatePullDownToRefresh(); return this; } public RefreshKernel setStatePullDownCanceled() { SmartRefreshLayout.this.setStatePullDownCanceled(); return this; } public RefreshKernel setStatePullUpCanceled() { SmartRefreshLayout.this.setStatePullUpCanceled(); return this; } public RefreshKernel setStateLoding() { SmartRefreshLayout.this.setStateLoding(); return this; } public RefreshKernel setStateRefresing() { SmartRefreshLayout.this.setStateRefresing(); return this; } @Override public RefreshKernel setStateLodingFinish() { SmartRefreshLayout.this.setStateLodingFinish(); return this; } @Override public RefreshKernel setStateRefresingFinish() { SmartRefreshLayout.this.setStateRefresingFinish(); return this; } public RefreshKernel resetStatus() { SmartRefreshLayout.this.resetStatus(); return this; } // // public RefreshKernel overSpinner() { SmartRefreshLayout.this.overSpinner(); return this; } public RefreshKernel moveSpinnerInfinitely(float dy) { SmartRefreshLayout.this.moveSpinnerInfinitely(dy); return this; } public RefreshKernel moveSpinner(int spinner, boolean isAnimator) { SmartRefreshLayout.this.moveSpinner(spinner, isAnimator); return this; } public RefreshKernel animSpinner(int endSpinner) { SmartRefreshLayout.this.animSpinner(endSpinner); return this; } @Override public RefreshKernel animSpinnerBounce(int bounceSpinner) { SmartRefreshLayout.this.animSpinnerBounce(bounceSpinner); return this; } @Override public int getSpinner() { return mSpinner; } // // @Override public RefreshKernel requestDrawBackgoundForHeader(int backgroundColor) { if (mPaint == null && backgroundColor != 0) { mPaint = new Paint(); } mHeaderBackgroundColor = backgroundColor; return this; } @Override public RefreshKernel requestDrawBackgoundForFooter(int backgroundColor) { if (mPaint == null && backgroundColor != 0) { mPaint = new Paint(); } mFooterBackgroundColor = backgroundColor; return this; } @Override public RefreshKernel requestHeaderNeedTouchEventWhenRefreshing(boolean request) { mHeaderNeedTouchEventWhenRefreshing = request; return this; } @Override public RefreshKernel requestFooterNeedTouchEventWhenLoading(boolean request) { mFooterNeedTouchEventWhenRefreshing = request; return this; } @Override public RefreshKernel requestRemeasureHeightForHeader() { if (mHeaderHeightStatus.notifyed) { mHeaderHeightStatus = mHeaderHeightStatus.unNotify(); } return this; } @Override public RefreshKernel requestRemeasureHeightForFooter() { if (mFooterHeightStatus.notifyed) { mFooterHeightStatus = mFooterHeightStatus.unNotify(); } return this; } // } // // @Override public boolean post(Runnable action) { if (handler == null) { mDelayedRunables = mDelayedRunables == null ? new ArrayList() : mDelayedRunables; mDelayedRunables.add(new DelayedRunable(action)); return false; } return handler.post(new DelayedRunable(action)); } @Override public boolean postDelayed(Runnable action, long delayMillis) { if (handler == null) { mDelayedRunables = mDelayedRunables == null ? new ArrayList() : mDelayedRunables; mDelayedRunables.add(new DelayedRunable(action, delayMillis)); return false; } return handler.postDelayed(new DelayedRunable(action), delayMillis); } // } ================================================ FILE: refresh-layout/src/main/java/com/scwang/smartrefresh/layout/api/DefaultRefreshFooterCreater.java ================================================ package com.scwang.smartrefresh.layout.api; import android.content.Context; import android.support.annotation.NonNull; /** * 默认Footer创建器 * Created by SCWANG on 2017/5/26. */ public interface DefaultRefreshFooterCreater { @NonNull RefreshFooter createRefreshFooter(Context context, RefreshLayout layout); } ================================================ FILE: refresh-layout/src/main/java/com/scwang/smartrefresh/layout/api/DefaultRefreshHeaderCreater.java ================================================ package com.scwang.smartrefresh.layout.api; import android.content.Context; import android.support.annotation.NonNull; /** * 默认Header创建器 * Created by SCWANG on 2017/5/26. */ public interface DefaultRefreshHeaderCreater { @NonNull RefreshHeader createRefreshHeader(Context context, RefreshLayout layout); } ================================================ FILE: refresh-layout/src/main/java/com/scwang/smartrefresh/layout/api/RefreshContent.java ================================================ package com.scwang.smartrefresh.layout.api; import android.animation.ValueAnimator.AnimatorUpdateListener; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; /** * 刷新内容组件 * Created by SCWANG on 2017/5/26. */ public interface RefreshContent { void moveSpinner(int spinner); boolean canRefresh(); boolean canLoadmore(); int getMeasuredWidth(); int getMeasuredHeight(); void measure(int widthSpec, int heightSpec); void layout(int left, int top, int right, int bottom); View getView(); View getScrollableView(); ViewGroup.LayoutParams getLayoutParams(); void onActionDown(MotionEvent e); void onActionUpOrCancel(); void setupComponent(RefreshKernel kernel, View fixedHeader, View fixedFooter); void onInitialHeaderAndFooter(int headerHeight, int footerHeight); void setScrollBoundaryDecider(ScrollBoundaryDecider boundary); void setEnableLoadmoreWhenContentNotFull(boolean enable); AnimatorUpdateListener onLoadingFinish(RefreshKernel kernel, int footerHeight, int startDelay, int reboundDuration); } ================================================ FILE: refresh-layout/src/main/java/com/scwang/smartrefresh/layout/api/RefreshFooter.java ================================================ package com.scwang.smartrefresh.layout.api; /** * 刷新底部 * Created by SCWANG on 2017/5/26. */ public interface RefreshFooter extends RefreshInternal { /** * 手指拖动下拉(会连续多次调用) * @param percent 下拉的百分比 值 = offset/footerHeight (0 - percent - (footerHeight+extendHeight) / footerHeight ) * @param offset 下拉的像素偏移量 0 - offset - (footerHeight+extendHeight) * @param footerHeight Footer的高度 * @param extendHeight Footer的扩展高度 */ void onPullingUp(float percent, int offset, int footerHeight, int extendHeight); /** * 手指释放之后的持续动画(会连续多次调用) * @param percent 下拉的百分比 值 = offset/footerHeight (0 - percent - (footerHeight+extendHeight) / footerHeight ) * @param offset 下拉的像素偏移量 0 - offset - (footerHeight+extendHeight) * @param footerHeight Footer的高度 * @param extendHeight Footer的扩展高度 */ void onPullReleasing(float percent, int offset, int footerHeight, int extendHeight); /** * 设置数据全部加载完成,将不能再次触发加载功能 * @return true 支持全部加载完成的状态显示 false 不支持 */ boolean setLoadmoreFinished(boolean finished); } ================================================ FILE: refresh-layout/src/main/java/com/scwang/smartrefresh/layout/api/RefreshHeader.java ================================================ package com.scwang.smartrefresh.layout.api; /** * 刷新头部 * Created by SCWANG on 2017/5/26. */ public interface RefreshHeader extends RefreshInternal { /** * 手指拖动下拉(会连续多次调用) * @param percent 下拉的百分比 值 = offset/headerHeight (0 - percent - (headerHeight+extendHeight) / headerHeight ) * @param offset 下拉的像素偏移量 0 - offset - (headerHeight+extendHeight) * @param headerHeight Header的高度 * @param extendHeight Header的扩展高度 */ void onPullingDown(float percent, int offset, int headerHeight, int extendHeight); /** * 手指释放之后的持续动画 * @param percent 下拉的百分比 值 = offset/headerHeight (0 - percent - (headerHeight+extendHeight) / headerHeight ) * @param offset 下拉的像素偏移量 0 - offset - (headerHeight+extendHeight) * @param headerHeight Header的高度 * @param extendHeight Header的扩展高度 */ void onReleasing(float percent, int offset, int headerHeight, int extendHeight); } ================================================ FILE: refresh-layout/src/main/java/com/scwang/smartrefresh/layout/api/RefreshInternal.java ================================================ package com.scwang.smartrefresh.layout.api; import android.support.annotation.NonNull; import android.view.View; import com.scwang.smartrefresh.layout.constant.SpinnerStyle; import com.scwang.smartrefresh.layout.listener.OnStateChangedListener; /** * 刷新内部组件 * Created by SCWANG on 2017/5/26. */ public interface RefreshInternal extends OnStateChangedListener { /** * 获取实体视图 */ @NonNull View getView(); /** * 获取变换方式 {@link SpinnerStyle} */ SpinnerStyle getSpinnerStyle(); /** * 设置主题颜色 * @param colors 对应Xml中配置的 srlPrimaryColor srlAccentColor */ void setPrimaryColors(int... colors); /** * 尺寸定义完成 (如果高度不改变(代码修改:setHeader),只调用一次, 在RefreshLayout#onMeasure中调用) * @param kernel RefreshKernel * @param height HeaderHeight or FooterHeight * @param extendHeight extendHeaderHeight or extendFooterHeight */ void onInitialized(RefreshKernel kernel, int height, int extendHeight); /** * 水平方向的拖动 * @param percentX 下拉时,手指水平坐标对屏幕的占比(0 - percentX - 1) * @param offsetX 下拉时,手指水平坐标对屏幕的偏移(0 - offsetX - LayoutWidth) */ void onHorizontalDrag(float percentX, int offsetX, int offsetMax); /** * 开始动画 * @param layout RefreshLayout * @param height HeaderHeight or FooterHeight * @param extendHeight extendHeaderHeight or extendFooterHeight */ void onStartAnimator(RefreshLayout layout, int height, int extendHeight); /** * 动画结束 * @param layout RefreshLayout * @param success 数据是否成功刷新或加载 * @return 完成动画所需时间 如果返回 Integer.MAX_VALUE 将取消本次完成事件,继续保持原有状态 */ int onFinish(RefreshLayout layout, boolean success); /** * 是否支持水平方向的拖动(将会影响到onHorizontalDrag的调用) * @return 水平拖动需要消耗更多的时间和资源,所以如果不支持请返回false */ boolean isSupportHorizontalDrag(); } ================================================ FILE: refresh-layout/src/main/java/com/scwang/smartrefresh/layout/api/RefreshKernel.java ================================================ package com.scwang.smartrefresh.layout.api; import android.support.annotation.NonNull; /** * 刷新布局核心功能接口 * 为功能复杂的 Header 或者 Footer 开放的接口 * Created by SCWANG on 2017/5/26. */ public interface RefreshKernel { @NonNull RefreshLayout getRefreshLayout(); @NonNull RefreshContent getRefreshContent(); // RefreshKernel setStatePullUpToLoad() ; RefreshKernel setStateReleaseToLoad() ; RefreshKernel setStateReleaseToRefresh() ; RefreshKernel setStatePullDownToRefresh() ; RefreshKernel setStatePullDownCanceled() ; RefreshKernel setStatePullUpCanceled() ; RefreshKernel setStateLoding() ; RefreshKernel setStateRefresing() ; RefreshKernel setStateLodingFinish() ; RefreshKernel setStateRefresingFinish() ; RefreshKernel resetStatus() ; // // /** * 结束视图位移(调用之后,如果没有在初始位移状态,会执行动画回到初始位置) * moveSpinner 的取名来自 谷歌官方的 @{@link android.support.v4.widget.SwipeRefreshLayout#moveSpinner(float)} */ RefreshKernel overSpinner() ; /** * 移动视图到预设距离(dy 会被内部函数计算,将会出现无限接近最大值(height+extendHeader)的阻尼效果) * moveSpinner 的取名来自 谷歌官方的 @{@link android.support.v4.widget.SwipeRefreshLayout#moveSpinner(float)} * @param dy 距离 (px) 大于0表示下拉 小于0表示上啦 */ RefreshKernel moveSpinnerInfinitely(float dy); /** * 移动视图到指定位置 * moveSpinner 的取名来自 谷歌官方的 @{@link android.support.v4.widget.SwipeRefreshLayout#moveSpinner(float)} * @param spinner 位置 (px) * @param isAnimator 标记是否是动画执行 */ RefreshKernel moveSpinner(int spinner, boolean isAnimator); /** * 执行动画使视图位移到指定的 位置 * moveSpinner 的取名来自 谷歌官方的 @{@link android.support.v4.widget.SwipeRefreshLayout#moveSpinner(float)} * @param endSpinner 指定的结束位置 (px) */ RefreshKernel animSpinner(int endSpinner); /** * 回弹动画 * @param bounceSpinner 回弹的最大位置 (px) */ RefreshKernel animSpinnerBounce(int bounceSpinner); /** * 获取 Spinner */ int getSpinner(); // // /** * 指定在下拉时候为 Header 绘制背景 * @param backgroundColor 背景颜色 */ RefreshKernel requestDrawBackgoundForHeader(int backgroundColor); /** * 指定在下拉时候为 Footer 绘制背景 * @param backgroundColor 背景颜色 */ RefreshKernel requestDrawBackgoundForFooter(int backgroundColor); /** * 请求事件 */ RefreshKernel requestHeaderNeedTouchEventWhenRefreshing(boolean request); /** * 请求事件 */ RefreshKernel requestFooterNeedTouchEventWhenLoading(boolean request); /** * 请求重新测量 */ RefreshKernel requestRemeasureHeightForHeader(); /** * 请求重新测量 */ RefreshKernel requestRemeasureHeightForFooter(); // } ================================================ FILE: refresh-layout/src/main/java/com/scwang/smartrefresh/layout/api/RefreshLayout.java ================================================ package com.scwang.smartrefresh.layout.api; import android.support.annotation.ColorRes; import android.support.annotation.Nullable; import android.view.ViewGroup; import android.view.animation.Interpolator; import com.scwang.smartrefresh.layout.constant.RefreshState; import com.scwang.smartrefresh.layout.listener.OnLoadmoreListener; import com.scwang.smartrefresh.layout.listener.OnMultiPurposeListener; import com.scwang.smartrefresh.layout.listener.OnRefreshListener; import com.scwang.smartrefresh.layout.listener.OnRefreshLoadmoreListener; /** * 刷新布局 * Created by SCWANG on 2017/5/26. */ public interface RefreshLayout { RefreshLayout setFooterHeight(float dp); RefreshLayout setFooterHeightPx(int px); RefreshLayout setHeaderHeight(float dp); RefreshLayout setHeaderHeightPx(int px); /** * 显示拖动高度/真实拖动高度(默认0.5,阻尼效果) */ RefreshLayout setDragRate(float rate); /** * 设置下拉最大高度和Header高度的比率(将会影响可以下拉的最大高度) */ RefreshLayout setHeaderMaxDragRate(float rate); /** * 设置上啦最大高度和Footer高度的比率(将会影响可以上啦的最大高度) */ RefreshLayout setFooterMaxDragRate(float rate); /** * 设置回弹显示插值器 */ RefreshLayout setReboundInterpolator(Interpolator interpolator); /** * 设置回弹动画时长 */ RefreshLayout setReboundDuration(int duration); /** * 设置是否启用上啦加载更多(默认启用) */ RefreshLayout setEnableLoadmore(boolean enable); /** * 是否启用下拉刷新(默认启用) */ RefreshLayout setEnableRefresh(boolean enable); /** * 设置是否启在下拉Header的同时下拉内容 */ RefreshLayout setEnableHeaderTranslationContent(boolean enable); /** * 设置是否启在上拉Footer的同时上拉内容 */ RefreshLayout setEnableFooterTranslationContent(boolean enable); /** * 设置是否开启在刷新时候禁止操作内容视图 */ RefreshLayout setDisableContentWhenRefresh(boolean disable); /** * 设置是否开启在加载时候禁止操作内容视图 */ RefreshLayout setDisableContentWhenLoading(boolean disable); /** * 设置是否监听列表在滚动到底部时触发加载事件(默认true) */ RefreshLayout setEnableAutoLoadmore(boolean enable); /** * 设置数据全部加载完成,将不能再次触发加载功能 */ RefreshLayout setLoadmoreFinished(boolean finished); /** * 设置指定的Footer */ RefreshLayout setRefreshFooter(RefreshFooter footer); /** * 设置指定的Footer */ RefreshLayout setRefreshFooter(RefreshFooter footer, int width, int height); /** * 设置指定的Header */ RefreshLayout setRefreshHeader(RefreshHeader header); /** * 设置指定的Header */ RefreshLayout setRefreshHeader(RefreshHeader header, int width, int height); /** * 设置是否启用越界回弹 */ RefreshLayout setEnableOverScrollBounce(boolean enable); /** * 设置是否开启纯滚动模式 */ RefreshLayout setEnablePureScrollMode(boolean enable); /** * 设置是否在加载更多完成之后滚动内容显示新数据 */ RefreshLayout setEnableScrollContentWhenLoaded(boolean enable); /** * 设置在内容不满一页的时候,是否可以上拉加载更多 */ RefreshLayout setEnableLoadmoreWhenContentNotFull(boolean enable); /** * 设置是会否启用嵌套滚动功能(默认关闭+智能开启) */ RefreshLayout setEnableNestedScroll(boolean enabled); /** * 单独设置刷新监听器 */ RefreshLayout setOnRefreshListener(OnRefreshListener listener); /** * 单独设置加载监听器 */ RefreshLayout setOnLoadmoreListener(OnLoadmoreListener listener); /** * 同时设置刷新和加载监听器 */ RefreshLayout setOnRefreshLoadmoreListener(OnRefreshLoadmoreListener listener); /** * 设置多功能监听器 */ RefreshLayout setOnMultiPurposeListener(OnMultiPurposeListener listener); /** * 设置主题颜色 */ RefreshLayout setPrimaryColorsId(@ColorRes int... primaryColorId); /** * 设置主题颜色 */ RefreshLayout setPrimaryColors(int... colors); /** * 设置滚动边界判断器 */ RefreshLayout setScrollBoundaryDecider(ScrollBoundaryDecider boundary); /** * 完成刷新 */ RefreshLayout finishRefresh(); /** * 完成加载 */ RefreshLayout finishLoadmore(); /** * 完成刷新 */ RefreshLayout finishRefresh(int delayed); /** * 完成加载 * @param success 数据是否成功刷新 (会影响到上次更新时间的改变) */ RefreshLayout finishRefresh(boolean success); /** * 完成刷新 */ RefreshLayout finishRefresh(int delayed, boolean success); /** * 完成加载 */ RefreshLayout finishLoadmore(int delayed); /** * 完成加载 */ RefreshLayout finishLoadmore(boolean success); /** * 完成加载 */ RefreshLayout finishLoadmore(int delayed, boolean success); /** * 获取当前 Header */ @Nullable RefreshHeader getRefreshHeader(); /** * 获取当前 Footer */ @Nullable RefreshFooter getRefreshFooter(); /** * 获取当前状态 */ RefreshState getState(); /** * 获取实体布局视图 */ ViewGroup getLayout(); /** * 是否正在刷新 */ boolean isRefreshing(); /** * 是否正在加载 */ boolean isLoading(); /** * 自动刷新 */ boolean autoRefresh(); /** * 自动刷新 */ boolean autoRefresh(int delayed); /** * 自动刷新 */ boolean autoRefresh(int delayed, float dragrate); /** * 自动加载 */ boolean autoLoadmore(); /** * 自动加载 */ boolean autoLoadmore(int delayed); /** * 自动加载 */ boolean autoLoadmore(int delayed, float dragrate); boolean isEnableRefresh(); boolean isEnableLoadmore(); boolean isLoadmoreFinished(); boolean isEnableAutoLoadmore(); boolean isEnableOverScrollBounce(); boolean isEnablePureScrollMode(); boolean isEnableScrollContentWhenLoaded(); } ================================================ FILE: refresh-layout/src/main/java/com/scwang/smartrefresh/layout/api/ScrollBoundaryDecider.java ================================================ package com.scwang.smartrefresh.layout.api; import android.view.View; /** * 滚动边界 * Created by SCWANG on 2017/7/8. */ public interface ScrollBoundaryDecider { /** * 根据内容视图状态判断是否可以开始下拉刷新 * @param content 内容视图 * @return true 将会触发下拉刷新 */ boolean canRefresh(View content); /** * 根据内容视图状态判断是否可以开始上拉加载 * @param content 内容视图 * @return true 将会触发加载更多 */ boolean canLoadmore(View content); } ================================================ FILE: refresh-layout/src/main/java/com/scwang/smartrefresh/layout/constant/DimensionStatus.java ================================================ package com.scwang.smartrefresh.layout.constant; /** * 尺寸值的定义状态,用于在值覆盖的时候决定优先级 * 越往下优先级越高 */ public enum DimensionStatus { DefaultUnNotify(false),//默认值,但是还没通知确认 Default(true),//默认值 XmlWrap(true),//Xml计算 XmlExact(true),//Xml 的view 指定 XmlLayoutUnNotify(false),//Xml 的layout 中指定,但是还没通知确认 XmlLayout(true),//Xml 的layout 中指定 CodeExactUnNotify(false),//代码指定,但是还没通知确认 CodeExact(true),//代码指定 DeadLockUnNotify(false),//锁死,但是还没通知确认 DeadLock(true);//锁死 public final boolean notifyed; DimensionStatus(boolean notifyed) { this.notifyed = notifyed; } /** * 转换为未通知状态 */ public DimensionStatus unNotify() { if (notifyed) { DimensionStatus prev = values()[ordinal() - 1]; if (!prev.notifyed) { return prev; } return DefaultUnNotify; } return this; } /** * 转换为通知状态 */ public DimensionStatus notifyed() { if (!notifyed) { return values()[ordinal() + 1]; } return this; } /** * 小于等于 */ public boolean canReplaceWith(DimensionStatus status) { return ordinal() < status.ordinal() || ((!notifyed || CodeExact == this) && ordinal() == status.ordinal()); } /** * 大于等于 */ public boolean gteReplaceWith(DimensionStatus status) { return ordinal() >= status.ordinal(); } } ================================================ FILE: refresh-layout/src/main/java/com/scwang/smartrefresh/layout/constant/RefreshState.java ================================================ package com.scwang.smartrefresh.layout.constant; public enum RefreshState { None, PullDownToRefresh, PullToUpLoad, PullDownCanceled, PullUpCanceled, ReleaseToRefresh, ReleaseToLoad, Refreshing, Loading, RefreshFinish, LoadFinish,; public boolean isAnimating() { return this == Refreshing || this == Loading; } public boolean isDraging() { return ordinal() >= PullDownToRefresh.ordinal() && ordinal() <= ReleaseToLoad.ordinal() && this != PullDownCanceled && this != PullUpCanceled; } public boolean isDragingHeader() { return this == PullDownToRefresh || this == ReleaseToRefresh; } public boolean isDragingFooter() { return this == PullToUpLoad || this == ReleaseToLoad; } public boolean isHeader() { return (ordinal() & 1) == 1; } public boolean isFooter() { return (ordinal() & 1) == 0 && ordinal() > 0; } } ================================================ FILE: refresh-layout/src/main/java/com/scwang/smartrefresh/layout/constant/SpinnerStyle.java ================================================ package com.scwang.smartrefresh.layout.constant; /** * 顶部和底部的组件在拖动时候的变换方式 * Created by SCWANG on 2017/5/26. */ public enum SpinnerStyle { Translate,//平行移动 特点: HeaderView高度不会改变, Scale,//拉伸形变 特点:在下拉和上弹(HeaderView高度改变)时候,会自动触发OnDraw事件 FixedBehind,//固定在背后 特点:HeaderView高度不会改变, FixedFront,//固定在前面 特点:HeaderView高度不会改变, MatchLayout//填满布局 特点:HeaderView高度不会改变,尺寸充满 RefreshLayout } ================================================ FILE: refresh-layout/src/main/java/com/scwang/smartrefresh/layout/footer/BallPulseFooter.java ================================================ package com.scwang.smartrefresh.layout.footer; import android.content.Context; import android.content.res.TypedArray; import android.support.annotation.AttrRes; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.v4.graphics.ColorUtils; import android.util.AttributeSet; import android.view.View; import android.view.ViewGroup; import com.scwang.smartrefresh.layout.R; import com.scwang.smartrefresh.layout.api.RefreshFooter; import com.scwang.smartrefresh.layout.api.RefreshKernel; import com.scwang.smartrefresh.layout.api.RefreshLayout; import com.scwang.smartrefresh.layout.constant.RefreshState; import com.scwang.smartrefresh.layout.constant.SpinnerStyle; import com.scwang.smartrefresh.layout.footer.ballpulse.BallPulseView; import com.scwang.smartrefresh.layout.util.DensityUtil; import static android.view.View.MeasureSpec.AT_MOST; import static android.view.View.MeasureSpec.getSize; import static android.view.View.MeasureSpec.makeMeasureSpec; import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT; /** * 球脉冲底部加载组件 * Created by SCWANG on 2017/5/30. */ public class BallPulseFooter extends ViewGroup implements RefreshFooter { private BallPulseView mBallPulseView; private SpinnerStyle mSpinnerStyle = SpinnerStyle.Translate; // public BallPulseFooter(@NonNull Context context) { super(context); initView(context, null, 0); } public BallPulseFooter(@NonNull Context context, @Nullable AttributeSet attrs) { super(context, attrs); initView(context, attrs, 0); } public BallPulseFooter(@NonNull Context context, @Nullable AttributeSet attrs, @AttrRes int defStyleAttr) { super(context, attrs, defStyleAttr); initView(context, attrs, defStyleAttr); } private void initView(Context context, AttributeSet attrs, int defStyleAttr) { mBallPulseView = new BallPulseView(context); addView(mBallPulseView, WRAP_CONTENT, WRAP_CONTENT); setMinimumHeight(DensityUtil.dp2px(60)); TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.BallPulseFooter); int primaryColor = ta.getColor(R.styleable.BallPulseFooter_srlPrimaryColor, 0); int accentColor = ta.getColor(R.styleable.BallPulseFooter_srlAccentColor, 0); if (primaryColor != 0) { mBallPulseView.setNormalColor(primaryColor); } if (accentColor != 0) { mBallPulseView.setAnimatingColor(accentColor); } mSpinnerStyle = SpinnerStyle.values()[ta.getInt(R.styleable.BallPulseFooter_srlClassicsSpinnerStyle, mSpinnerStyle.ordinal())]; ta.recycle(); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int widthSpec = makeMeasureSpec(getSize(widthMeasureSpec), AT_MOST); int heightSpec = makeMeasureSpec(getSize(heightMeasureSpec), AT_MOST); mBallPulseView.measure(widthSpec, heightSpec); setMeasuredDimension( resolveSize(mBallPulseView.getMeasuredWidth(), widthMeasureSpec), resolveSize(mBallPulseView.getMeasuredHeight(), heightMeasureSpec) ); } @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { int pwidth = getMeasuredWidth(); int pheight = getMeasuredHeight(); int cwidth = mBallPulseView.getMeasuredWidth(); int cheight = mBallPulseView.getMeasuredHeight(); int left = pwidth / 2 - cwidth / 2; int top = pheight / 2 - cheight / 2; mBallPulseView.layout(left, top, left + cwidth, top + cheight); } // // @Override public void onInitialized(RefreshKernel kernel, int height, int extendHeight) { } @Override public boolean isSupportHorizontalDrag() { return false; } @Override public void onHorizontalDrag(float percentX, int offsetX, int offsetMax) { } @Override public void onPullingUp(float percent, int offset, int footerHeight, int extendHeight) { } @Override public void onPullReleasing(float percent, int offset, int footerHeight, int extendHeight) { } @Override public void onStartAnimator(RefreshLayout layout, int footerHeight, int extendHeight) { mBallPulseView.startAnim(); } @Override public void onStateChanged(RefreshLayout refreshLayout, RefreshState oldState, RefreshState newState) { } @Override public int onFinish(RefreshLayout layout, boolean success) { mBallPulseView.stopAnim(); return 0; } @Override public boolean setLoadmoreFinished(boolean finished) { return false; } @Override@Deprecated public void setPrimaryColors(int... colors) { if (colors.length > 1) { mBallPulseView.setNormalColor(colors[1]); mBallPulseView.setAnimatingColor(colors[0]); } else if (colors.length > 0) { mBallPulseView.setNormalColor(ColorUtils.compositeColors(0x99ffffff,colors[0])); mBallPulseView.setAnimatingColor(colors[0]); } } @NonNull @Override public View getView() { return this; } @Override public SpinnerStyle getSpinnerStyle() { return mSpinnerStyle; } // // public BallPulseFooter setSpinnerStyle(SpinnerStyle mSpinnerStyle) { this.mSpinnerStyle = mSpinnerStyle; return this; } // } ================================================ FILE: refresh-layout/src/main/java/com/scwang/smartrefresh/layout/footer/ClassicsFooter.java ================================================ package com.scwang.smartrefresh.layout.footer; import android.content.Context; import android.content.res.TypedArray; import android.graphics.Bitmap; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; import android.support.annotation.DrawableRes; import android.support.annotation.NonNull; import android.util.AttributeSet; import android.util.TypedValue; import android.view.View; import android.view.ViewGroup; import android.view.animation.LinearInterpolator; import android.widget.ImageView; import android.widget.RelativeLayout; import android.widget.TextView; import com.scwang.smartrefresh.layout.R; import com.scwang.smartrefresh.layout.api.RefreshFooter; import com.scwang.smartrefresh.layout.api.RefreshKernel; import com.scwang.smartrefresh.layout.api.RefreshLayout; import com.scwang.smartrefresh.layout.constant.RefreshState; import com.scwang.smartrefresh.layout.constant.SpinnerStyle; import com.scwang.smartrefresh.layout.internal.ProgressDrawable; import com.scwang.smartrefresh.layout.internal.pathview.PathsDrawable; import com.scwang.smartrefresh.layout.util.DensityUtil; import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT; /** * 经典上拉底部组件 * Created by SCWANG on 2017/5/28. */ @SuppressWarnings("unused") public class ClassicsFooter extends RelativeLayout implements RefreshFooter { public static String REFRESH_FOOTER_PULLUP = "上拉加载更多"; public static String REFRESH_FOOTER_RELEASE = "释放立即加载"; public static String REFRESH_FOOTER_LOADING = "正在加载..."; public static String REFRESH_FOOTER_REFRESHING = "正在刷新..."; public static String REFRESH_FOOTER_FINISH = "加载完成"; public static String REFRESH_FOOTER_FAILED = "加载失败"; public static String REFRESH_FOOTER_ALLLOADED = "全部加载完成"; protected TextView mTitleText; protected ImageView mArrowView; protected ImageView mProgressView; protected PathsDrawable mArrowDrawable; protected ProgressDrawable mProgressDrawable; protected SpinnerStyle mSpinnerStyle = SpinnerStyle.Translate; protected RefreshKernel mRefreshKernel; protected int mFinishDuration = 500; protected int mBackgroundColor = 0; protected boolean mLoadmoreFinished = false; protected int mPaddingTop = 20; protected int mPaddingBottom = 20; // public ClassicsFooter(Context context) { super(context); this.initView(context, null, 0); } public ClassicsFooter(Context context, AttributeSet attrs) { super(context, attrs); this.initView(context, attrs, 0); } public ClassicsFooter(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); this.initView(context, attrs, defStyleAttr); } private void initView(Context context, AttributeSet attrs, int defStyleAttr) { DensityUtil density = new DensityUtil(); mTitleText = new TextView(context); mTitleText.setId(android.R.id.widget_frame); mTitleText.setTextColor(0xff666666); mTitleText.setText(REFRESH_FOOTER_PULLUP); LayoutParams lpBottomText = new LayoutParams(WRAP_CONTENT, WRAP_CONTENT); lpBottomText.addRule(CENTER_IN_PARENT); addView(mTitleText, lpBottomText); LayoutParams lpArrow = new LayoutParams(density.dip2px(20), density.dip2px(20)); lpArrow.addRule(CENTER_VERTICAL); lpArrow.addRule(LEFT_OF, android.R.id.widget_frame); mArrowView = new ImageView(context); addView(mArrowView, lpArrow); LayoutParams lpProgress = new LayoutParams((ViewGroup.LayoutParams)lpArrow); lpProgress.addRule(CENTER_VERTICAL); lpProgress.addRule(LEFT_OF, android.R.id.widget_frame); mProgressView = new ImageView(context); mProgressView.animate().setInterpolator(new LinearInterpolator()); addView(mProgressView, lpProgress); if (!isInEditMode()) { mProgressView.setVisibility(GONE); } else { mArrowView.setVisibility(GONE); } TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.ClassicsFooter); lpProgress.rightMargin = ta.getDimensionPixelSize(R.styleable.ClassicsFooter_srlDrawableMarginRight, density.dip2px(20)); lpArrow.rightMargin = lpProgress.rightMargin; lpArrow.width = ta.getLayoutDimension(R.styleable.ClassicsHeader_srlDrawableArrowSize, lpArrow.width); lpArrow.height = ta.getLayoutDimension(R.styleable.ClassicsHeader_srlDrawableArrowSize, lpArrow.height); lpProgress.width = ta.getLayoutDimension(R.styleable.ClassicsHeader_srlDrawableProgressSize, lpProgress.width); lpProgress.height = ta.getLayoutDimension(R.styleable.ClassicsHeader_srlDrawableProgressSize, lpProgress.height); lpArrow.width = ta.getLayoutDimension(R.styleable.ClassicsHeader_srlDrawableSize, lpArrow.width); lpArrow.height = ta.getLayoutDimension(R.styleable.ClassicsHeader_srlDrawableSize, lpArrow.height); lpProgress.width = ta.getLayoutDimension(R.styleable.ClassicsHeader_srlDrawableSize, lpProgress.width); lpProgress.height = ta.getLayoutDimension(R.styleable.ClassicsHeader_srlDrawableSize, lpProgress.height); mFinishDuration = ta.getInt(R.styleable.ClassicsFooter_srlFinishDuration, mFinishDuration); mSpinnerStyle = SpinnerStyle.values()[ta.getInt(R.styleable.ClassicsFooter_srlClassicsSpinnerStyle, mSpinnerStyle.ordinal())]; if (ta.hasValue(R.styleable.ClassicsFooter_srlDrawableArrow)) { mArrowView.setImageDrawable(ta.getDrawable(R.styleable.ClassicsFooter_srlDrawableArrow)); } else { mArrowDrawable = new PathsDrawable(); mArrowDrawable.parserColors(0xff666666); mArrowDrawable.parserPaths("M20,12l-1.41,-1.41L13,16.17V4h-2v12.17l-5.58,-5.59L4,12l8,8 8,-8z"); mArrowView.setImageDrawable(mArrowDrawable); } if (ta.hasValue(R.styleable.ClassicsFooter_srlDrawableProgress)) { mProgressView.setImageDrawable(ta.getDrawable(R.styleable.ClassicsFooter_srlDrawableProgress)); } else { mProgressDrawable = new ProgressDrawable(); mProgressDrawable.setColor(0xff666666); mProgressView.setImageDrawable(mProgressDrawable); } if (ta.hasValue(R.styleable.ClassicsFooter_srlTextSizeTitle)) { mTitleText.setTextSize(TypedValue.COMPLEX_UNIT_PX, ta.getDimensionPixelSize(R.styleable.ClassicsFooter_srlTextSizeTitle, DensityUtil.dp2px(16))); } else { mTitleText.setTextSize(16); } if (ta.hasValue(R.styleable.ClassicsFooter_srlPrimaryColor)) { setPrimaryColor(ta.getColor(R.styleable.ClassicsFooter_srlPrimaryColor, 0)); } if (ta.hasValue(R.styleable.ClassicsFooter_srlAccentColor)) { setAccentColor(ta.getColor(R.styleable.ClassicsFooter_srlAccentColor, 0)); } ta.recycle(); if (getPaddingTop() == 0) { if (getPaddingBottom() == 0) { setPadding(getPaddingLeft(), mPaddingTop = density.dip2px(20), getPaddingRight(), mPaddingBottom = density.dip2px(20)); } else { setPadding(getPaddingLeft(), mPaddingTop = density.dip2px(20), getPaddingRight(), mPaddingBottom = getPaddingBottom()); } } else { if (getPaddingBottom() == 0) { setPadding(getPaddingLeft(), mPaddingTop = getPaddingTop(), getPaddingRight(), mPaddingBottom = density.dip2px(20)); } else { mPaddingTop = getPaddingTop(); mPaddingBottom = getPaddingBottom(); } } } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { if (MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.EXACTLY) { setPadding(getPaddingLeft(), 0, getPaddingRight(), 0); } else { setPadding(getPaddingLeft(), mPaddingTop, getPaddingRight(), mPaddingBottom); } super.onMeasure(widthMeasureSpec, heightMeasureSpec); } // // @Override public void onInitialized(RefreshKernel kernel, int height, int extendHeight) { mRefreshKernel = kernel; mRefreshKernel.requestDrawBackgoundForFooter(mBackgroundColor); } @Override public boolean isSupportHorizontalDrag() { return false; } @Override public void onHorizontalDrag(float percentX, int offsetX, int offsetMax) { } @Override public void onPullingUp(float percent, int offset, int footerHeight, int extendHeight) { } @Override public void onPullReleasing(float percent, int offset, int headHeight, int extendHeight) { } @Override public void onStartAnimator(RefreshLayout layout, int headHeight, int extendHeight) { if (!mLoadmoreFinished) { mProgressView.setVisibility(VISIBLE); if (mProgressDrawable != null) { mProgressDrawable.start(); } else { mProgressView.animate().rotation(36000).setDuration(100000); } } } @Override public int onFinish(RefreshLayout layout, boolean success) { if (!mLoadmoreFinished) { if (mProgressDrawable != null) { mProgressDrawable.stop(); } else { mProgressView.animate().rotation(0).setDuration(300); } mProgressView.setVisibility(GONE); if (success) { mTitleText.setText(REFRESH_FOOTER_FINISH); } else { mTitleText.setText(REFRESH_FOOTER_FAILED); } return mFinishDuration; } return 0; } /** * ClassicsFooter 在(SpinnerStyle.FixedBehind)时才有主题色 */ @Override@Deprecated public void setPrimaryColors(int... colors) { if (mSpinnerStyle == SpinnerStyle.FixedBehind) { if (colors.length > 0) { if (!(getBackground() instanceof BitmapDrawable)) { setPrimaryColor(colors[0]); } if (colors.length > 1) { setAccentColor(colors[1]); } else { setAccentColor(colors[0] == 0xffffffff ? 0xff666666 : 0xffffffff); } } } } /** * 设置数据全部加载完成,将不能再次触发加载功能 */ @Override public boolean setLoadmoreFinished(boolean finished) { if (mLoadmoreFinished != finished) { mLoadmoreFinished = finished; if (finished) { mTitleText.setText(REFRESH_FOOTER_ALLLOADED); } else { mTitleText.setText(REFRESH_FOOTER_PULLUP); } if (mProgressDrawable != null) { mProgressDrawable.stop(); } else { mProgressView.animate().rotation(0).setDuration(300); } mProgressView.setVisibility(GONE); mArrowView.setVisibility(GONE); } return true; } @NonNull public View getView() { return this; } @Override public SpinnerStyle getSpinnerStyle() { return mSpinnerStyle; } @Override public void onStateChanged(RefreshLayout refreshLayout, RefreshState oldState, RefreshState newState) { if (!mLoadmoreFinished) { switch (newState) { case None: // restoreRefreshLayoutBackground(); mArrowView.setVisibility(VISIBLE); case PullToUpLoad: mTitleText.setText(REFRESH_FOOTER_PULLUP); mArrowView.animate().rotation(180); break; case Loading: mArrowView.setVisibility(GONE); mTitleText.setText(REFRESH_FOOTER_LOADING); break; case ReleaseToLoad: mTitleText.setText(REFRESH_FOOTER_RELEASE); mArrowView.animate().rotation(0); // replaceRefreshLayoutBackground(refreshLayout); break; case Refreshing: mTitleText.setText(REFRESH_FOOTER_REFRESHING); mProgressView.setVisibility(GONE); mArrowView.setVisibility(GONE); break; } } } // // // private Runnable restoreRunable; // private void restoreRefreshLayoutBackground() { // if (restoreRunable != null) { // restoreRunable.run(); // restoreRunable = null; // } // } // // private void replaceRefreshLayoutBackground(final RefreshLayout refreshLayout) { // if (restoreRunable == null && mSpinnerStyle == SpinnerStyle.FixedBehind) { // restoreRunable = new Runnable() { // Drawable drawable = refreshLayout.getLayout().getBackground(); // @Override // public void run() { // refreshLayout.getLayout().setBackgroundDrawable(drawable); // } // }; // refreshLayout.getLayout().setBackgroundDrawable(getBackground()); // } // } // // public ClassicsFooter setProgressBitmap(Bitmap bitmap) { mProgressDrawable = null; mProgressView.setImageBitmap(bitmap); return this; } public ClassicsFooter setProgressDrawable(Drawable drawable) { mProgressDrawable = null; mProgressView.setImageDrawable(drawable); return this; } public ClassicsFooter setProgressResource(@DrawableRes int resId) { mProgressDrawable = null; mProgressView.setImageResource(resId); return this; } public ClassicsFooter setArrowBitmap(Bitmap bitmap) { mArrowDrawable = null; mArrowView.setImageBitmap(bitmap); return this; } public ClassicsFooter setArrowDrawable(Drawable drawable) { mArrowDrawable = null; mArrowView.setImageDrawable(drawable); return this; } public ClassicsFooter setArrowResource(@DrawableRes int resId) { mArrowDrawable = null; mArrowView.setImageResource(resId); return this; } public ClassicsFooter setSpinnerStyle(SpinnerStyle style) { this.mSpinnerStyle = style; return this; } public ClassicsFooter setAccentColor(int accentColor) { mTitleText.setTextColor(accentColor); if (mProgressDrawable != null) { mProgressDrawable.setColor(accentColor); } if (mArrowDrawable != null) { mArrowDrawable.parserColors(accentColor); } return this; } public ClassicsFooter setPrimaryColor(int primaryColor) { setBackgroundColor(mBackgroundColor = primaryColor); if (mRefreshKernel != null) { mRefreshKernel.requestDrawBackgoundForFooter(mBackgroundColor); } return this; } public ClassicsFooter setFinishDuration(int delay) { mFinishDuration = delay; return this; } public ClassicsFooter setTextSizeTitle(float size) { mTitleText.setTextSize(size); if (mRefreshKernel != null) { mRefreshKernel.requestRemeasureHeightForFooter(); } return this; } public ClassicsFooter setTextSizeTitle(int unit, float size) { mTitleText.setTextSize(unit, size); if (mRefreshKernel != null) { mRefreshKernel.requestRemeasureHeightForFooter(); } return this; } public ClassicsFooter setDrawableMarginRight(float dp) { return setDrawableMarginRightPx(DensityUtil.dp2px(dp)); } public ClassicsFooter setDrawableMarginRightPx(int px) { MarginLayoutParams lpArrow = (MarginLayoutParams)mArrowView.getLayoutParams(); MarginLayoutParams lpProgress = (MarginLayoutParams)mProgressView.getLayoutParams(); lpArrow.rightMargin = lpProgress.rightMargin = px; mArrowView.setLayoutParams(lpArrow); mProgressView.setLayoutParams(lpProgress); return this; } public ClassicsFooter setDrawableSize(float dp) { return setDrawableSizePx(DensityUtil.dp2px(dp)); } public ClassicsFooter setDrawableSizePx(int px) { ViewGroup.LayoutParams lpArrow = mArrowView.getLayoutParams(); ViewGroup.LayoutParams lpProgress = mProgressView.getLayoutParams(); lpArrow.width = lpProgress.width = px; lpArrow.height = lpProgress.height = px; mArrowView.setLayoutParams(lpArrow); mProgressView.setLayoutParams(lpProgress); return this; } public ClassicsFooter setDrawableArrowSize(float dp) { return setDrawableArrowSizePx(DensityUtil.dp2px(dp)); } public ClassicsFooter setDrawableArrowSizePx(int px) { ViewGroup.LayoutParams lpArrow = mArrowView.getLayoutParams(); lpArrow.width = px; lpArrow.height = px; mArrowView.setLayoutParams(lpArrow); return this; } public ClassicsFooter setDrawableProgressSize(float dp) { return setDrawableProgressSizePx(DensityUtil.dp2px(dp)); } public ClassicsFooter setDrawableProgressSizePx(int px) { ViewGroup.LayoutParams lpProgress = mProgressView.getLayoutParams(); lpProgress.width = px; lpProgress.height = px; mProgressView.setLayoutParams(lpProgress); return this; } public TextView getTitleText() { return mTitleText; } public ImageView getProgressView() { return mProgressView; } public ImageView getArrowView() { return mArrowView; } // } ================================================ FILE: refresh-layout/src/main/java/com/scwang/smartrefresh/layout/footer/FalsifyFooter.java ================================================ package com.scwang.smartrefresh.layout.footer; import android.annotation.SuppressLint; import android.content.Context; import android.graphics.Canvas; import android.graphics.DashPathEffect; import android.graphics.Paint; import android.os.Build; import android.support.annotation.Nullable; import android.support.annotation.RequiresApi; import android.util.AttributeSet; import android.view.Gravity; import android.widget.TextView; import com.scwang.smartrefresh.layout.api.RefreshFooter; import com.scwang.smartrefresh.layout.header.FalsifyHeader; import com.scwang.smartrefresh.layout.util.DensityUtil; import static android.view.View.MeasureSpec.EXACTLY; import static android.view.View.MeasureSpec.makeMeasureSpec; /** * 虚假的 Footer * 用于 正真的 Footer 在 RefreshLayout 外部时, * Created by SCWANG on 2017/6/14. */ public class FalsifyFooter extends FalsifyHeader implements RefreshFooter { // public FalsifyFooter(Context context) { super(context); } public FalsifyFooter(Context context, AttributeSet attrs) { super(context, attrs); } public FalsifyFooter(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } @RequiresApi(Build.VERSION_CODES.LOLLIPOP) public FalsifyFooter(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); } @Override@SuppressLint("DrawAllocation") protected void onDraw(Canvas canvas) { super.onDraw(canvas); if (isInEditMode()) {//这段代码在运行时不会执行,只会在Studio编辑预览时运行,不用在意性能问题 int d = DensityUtil.dp2px(5); Paint paint = new Paint(); paint.setStyle(Paint.Style.STROKE); paint.setColor(0x44ffffff); paint.setStrokeWidth(DensityUtil.dp2px(1)); paint.setPathEffect(new DashPathEffect(new float[]{d, d, d, d}, 1)); canvas.drawRect(d, d, getWidth() - d, getBottom() - d, paint); TextView textView = new TextView(getContext()); textView.setText(getClass().getSimpleName()+" 虚假区域\n运行时代表上拉Footer的高度【" + DensityUtil.px2dp(getHeight()) + "dp】\n而不会显示任何东西"); textView.setTextColor(0x44ffffff); textView.setGravity(Gravity.CENTER); textView.measure(makeMeasureSpec(getWidth(), EXACTLY), makeMeasureSpec(getHeight(), EXACTLY)); textView.layout(0, 0, getWidth(), getHeight()); textView.draw(canvas); } } // // @Override public void onPullingUp(float percent, int offset, int footerHeight, int extendHeight) { } @Override public void onPullReleasing(float percent, int offset, int footerHeight, int extendHeight) { } @Override public boolean setLoadmoreFinished(boolean finished) { return false; } // } ================================================ FILE: refresh-layout/src/main/java/com/scwang/smartrefresh/layout/footer/ballpulse/BallPulseView.java ================================================ package com.scwang.smartrefresh.layout.footer.ballpulse; import android.animation.ValueAnimator; import android.content.Context; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.support.annotation.ColorInt; import android.util.AttributeSet; import android.view.View; import com.scwang.smartrefresh.layout.util.DensityUtil; import java.util.ArrayList; import java.util.HashMap; import java.util.Map; public class BallPulseView extends View { public static final int DEFAULT_SIZE = 50; //dp private Paint mPaint; private int normalColor = 0xffeeeeee; private int animatingColor = 0xffe75946; private float circleSpacing; private float[] scaleFloats = new float[]{1f, 1f, 1f}; private boolean mIsStarted = false; private ArrayList mAnimators; private Map mUpdateListeners = new HashMap<>();; // public BallPulseView(Context context) { this(context, null); } public BallPulseView(Context context, AttributeSet attrs) { this(context, attrs, 0); } public BallPulseView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); circleSpacing = DensityUtil.dp2px(4); mPaint = new Paint(); mPaint.setColor(Color.WHITE); mPaint.setStyle(Paint.Style.FILL); mPaint.setAntiAlias(true); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int default_size = DensityUtil.dp2px(DEFAULT_SIZE); setMeasuredDimension(resolveSize(default_size, widthMeasureSpec), resolveSize(default_size, heightMeasureSpec)); } @Override protected void onDetachedFromWindow() { super.onDetachedFromWindow(); if (mAnimators != null) for (int i = 0; i < mAnimators.size(); i++) { mAnimators.get(i).cancel(); } } @Override protected void onDraw(Canvas canvas) { float radius = (Math.min(getWidth(), getHeight()) - circleSpacing * 2) / 6; float x = getWidth() / 2 - (radius * 2 + circleSpacing); float y = getHeight() / 2; for (int i = 0; i < 3; i++) { canvas.save(); float translateX = x + (radius * 2) * i + circleSpacing * i; canvas.translate(translateX, y); canvas.scale(scaleFloats[i], scaleFloats[i]); canvas.drawCircle(0, 0, radius, mPaint); canvas.restore(); } } // // private boolean isStarted() { // for (ValueAnimator animator : mAnimators) { // return animator.isStarted(); // } return mIsStarted; } private void createAnimators() { mAnimators = new ArrayList<>(); int[] delays = new int[]{120, 240, 360}; for (int i = 0; i < 3; i++) { final int index = i; ValueAnimator scaleAnim = ValueAnimator.ofFloat(1, 0.3f, 1); scaleAnim.setDuration(750); scaleAnim.setRepeatCount(ValueAnimator.INFINITE); scaleAnim.setStartDelay(delays[i]); mUpdateListeners.put(scaleAnim, new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { scaleFloats[index] = (float) animation.getAnimatedValue(); postInvalidate(); } }); mAnimators.add(scaleAnim); } } // // public void setIndicatorColor(int color) { mPaint.setColor(color); } public void setNormalColor(@ColorInt int color) { normalColor = color; } public void setAnimatingColor(@ColorInt int color) { animatingColor = color; } public void startAnim() { if (mAnimators == null) createAnimators(); if (mAnimators == null) return; if (isStarted()) return; for (int i = 0; i < mAnimators.size(); i++) { ValueAnimator animator = mAnimators.get(i); //when the animator restart , add the updateListener again because they was removed by animator stop . ValueAnimator.AnimatorUpdateListener updateListener = mUpdateListeners.get(animator); if (updateListener != null) { animator.addUpdateListener(updateListener); } animator.start(); } mIsStarted = true; setIndicatorColor(animatingColor); } public void stopAnim() { if (mAnimators != null && mIsStarted) { mIsStarted = false; for (ValueAnimator animator : mAnimators) { if (animator != null /*&& animator.isStarted()*/) { animator.removeAllUpdateListeners(); animator.end(); } } scaleFloats = new float[]{1f, 1f, 1f}; } setIndicatorColor(normalColor); } // } ================================================ FILE: refresh-layout/src/main/java/com/scwang/smartrefresh/layout/header/BezierRadarHeader.java ================================================ package com.scwang.smartrefresh.layout.header; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.ValueAnimator; import android.content.Context; import android.content.res.TypedArray; import android.support.annotation.NonNull; import android.support.v4.content.ContextCompat; import android.util.AttributeSet; import android.view.View; import android.view.animation.DecelerateInterpolator; import android.widget.FrameLayout; import com.scwang.smartrefresh.layout.R; import com.scwang.smartrefresh.layout.api.RefreshHeader; import com.scwang.smartrefresh.layout.api.RefreshKernel; import com.scwang.smartrefresh.layout.api.RefreshLayout; import com.scwang.smartrefresh.layout.constant.RefreshState; import com.scwang.smartrefresh.layout.constant.SpinnerStyle; import com.scwang.smartrefresh.layout.header.bezierradar.RippleView; import com.scwang.smartrefresh.layout.header.bezierradar.RoundDotView; import com.scwang.smartrefresh.layout.header.bezierradar.RoundProgressView; import com.scwang.smartrefresh.layout.header.bezierradar.WaveView; import com.scwang.smartrefresh.layout.util.DensityUtil; import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; /** * 贝塞尔曲线类雷达风格刷新组件 * Created by lcodecore on 2016/10/2. */ public class BezierRadarHeader extends FrameLayout implements RefreshHeader { private WaveView mWaveView; private RippleView mRippleView; private RoundDotView mDotView; private RoundProgressView mProgressView; private boolean mEnableHorizontalDrag = false; // public BezierRadarHeader(Context context) { this(context,null); } public BezierRadarHeader(Context context, AttributeSet attrs) { this(context, attrs,0); } public BezierRadarHeader(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); initView(context, attrs, defStyleAttr); } private void initView(Context context, AttributeSet attrs, int defStyleAttr) { setMinimumHeight(DensityUtil.dp2px(100)); /** * 初始化headView */ mWaveView = new WaveView(getContext()); mRippleView = new RippleView(getContext()); mDotView = new RoundDotView(getContext()); mProgressView = new RoundProgressView(getContext()); if (isInEditMode()) { this.addView(mWaveView, MATCH_PARENT, MATCH_PARENT); this.addView(mProgressView, MATCH_PARENT, MATCH_PARENT); mWaveView.setHeadHeight(1000); } else { this.addView(mWaveView, MATCH_PARENT, MATCH_PARENT); this.addView(mDotView, MATCH_PARENT, MATCH_PARENT); this.addView(mProgressView, MATCH_PARENT, MATCH_PARENT); this.addView(mRippleView, MATCH_PARENT, MATCH_PARENT); mProgressView.setScaleX(0); mProgressView.setScaleY(0); } TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.BezierRadarHeader); mEnableHorizontalDrag = ta.getBoolean(R.styleable.BezierRadarHeader_srlEnableHorizontalDrag, mEnableHorizontalDrag); int primaryColor = ta.getColor(R.styleable.BezierRadarHeader_srlPrimaryColor, 0); int accentColor = ta.getColor(R.styleable.BezierRadarHeader_srlAccentColor, 0); if (primaryColor != 0) { setPrimaryColor(primaryColor); } if (accentColor != 0) { setAccentColor(primaryColor); } ta.recycle(); } // // public BezierRadarHeader setPrimaryColor(int color) { mWaveView.setWaveColor(color); mProgressView.setBackColor(color); return this; } public BezierRadarHeader setAccentColor(int color) { mDotView.setDotColor(color); mRippleView.setFrontColor(color); mProgressView.setFrontColor(color); return this; } public BezierRadarHeader setPrimaryColorId(int colorId) { setPrimaryColor(ContextCompat.getColor(getContext(), colorId)); return this; } public BezierRadarHeader setAccentColorId(int colorId) { setAccentColor(ContextCompat.getColor(getContext(), colorId)); return this; } public BezierRadarHeader setEnableHorizontalDrag(boolean enable) { this.mEnableHorizontalDrag = enable; if (!enable) { mWaveView.setWaveOffsetX(-1); } return this; } // // @Override@Deprecated public void setPrimaryColors(int... colors) { if (colors.length > 0) { setPrimaryColor(colors[0]); } if (colors.length > 1) { setAccentColor(colors[1]); } } @NonNull public View getView() { return this; } @Override public SpinnerStyle getSpinnerStyle() { return SpinnerStyle.Scale; } @Override public void onInitialized(RefreshKernel kernel, int height, int extendHeight) { } @Override public boolean isSupportHorizontalDrag() { return mEnableHorizontalDrag; } @Override public void onHorizontalDrag(float percentX, int offsetX, int offsetMax) { mWaveView.setWaveOffsetX(offsetX); mWaveView.invalidate(); } @Override public void onPullingDown(float percent, int offset, int headHeight, int extendHeight) { mWaveView.setHeadHeight(Math.min(headHeight, offset)); mWaveView.setWaveHeight((int)(1.9f*Math.max(0, offset - headHeight))); mDotView.setFraction(percent); } @Override public void onReleasing(float percent, int offset, int headHeight, int extendHeight) { onPullingDown(percent, offset, headHeight, extendHeight); } @Override public void onStartAnimator(final RefreshLayout layout, int headHeight, int extendHeight) { mWaveView.setHeadHeight(headHeight); ValueAnimator animator = ValueAnimator.ofInt( mWaveView.getWaveHeight(), 0, -(int)(mWaveView.getWaveHeight()*0.8),0, -(int)(mWaveView.getWaveHeight()*0.4f),0); animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { mWaveView.setWaveHeight((int) animation.getAnimatedValue()/2); mWaveView.invalidate(); } }); animator.setInterpolator(new DecelerateInterpolator()); animator.setDuration(800); animator.start(); /*处理圈圈进度条**/ ValueAnimator valueAnimator = ValueAnimator.ofFloat(1, 0); valueAnimator.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { super.onAnimationEnd(animation); mDotView.setVisibility(INVISIBLE); mProgressView.animate().scaleX((float) 1.0); mProgressView.animate().scaleY((float) 1.0); layout.getLayout().postDelayed(new Runnable() { @Override public void run() { mProgressView.startAnim(); } }, 200); } }); valueAnimator.setInterpolator(new DecelerateInterpolator()); valueAnimator.setDuration(300); valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { mDotView.setAlpha((Float) animation.getAnimatedValue()); } }); valueAnimator.start(); } @Override public int onFinish(RefreshLayout layout, boolean success) { mProgressView.stopAnim(); mProgressView.animate().scaleX(0f); mProgressView.animate().scaleY(0f); mRippleView.setVisibility(VISIBLE); mRippleView.startReveal(); return 400; } @Override public void onStateChanged(RefreshLayout refreshLayout, RefreshState oldState, RefreshState newState) { switch (newState) { case None: mRippleView.setVisibility(GONE); mDotView.setAlpha(1); mDotView.setVisibility(VISIBLE); break; case PullDownToRefresh: mProgressView.setScaleX(0); mProgressView.setScaleY(0); break; case PullToUpLoad: break; case Refreshing: break; case Loading: break; } } // } ================================================ FILE: refresh-layout/src/main/java/com/scwang/smartrefresh/layout/header/ClassicsHeader.java ================================================ package com.scwang.smartrefresh.layout.header; import android.content.Context; import android.content.SharedPreferences; import android.content.res.TypedArray; import android.graphics.Bitmap; import android.graphics.drawable.Animatable; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; import android.os.Build; import android.support.annotation.DrawableRes; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.annotation.RequiresApi; import android.support.v4.app.Fragment; import android.support.v4.app.FragmentActivity; import android.support.v4.app.FragmentManager; import android.util.AttributeSet; import android.util.TypedValue; import android.view.Gravity; import android.view.View; import android.view.ViewGroup; import android.view.animation.LinearInterpolator; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.RelativeLayout; import android.widget.TextView; import com.scwang.smartrefresh.layout.R; import com.scwang.smartrefresh.layout.api.RefreshHeader; import com.scwang.smartrefresh.layout.api.RefreshKernel; import com.scwang.smartrefresh.layout.api.RefreshLayout; import com.scwang.smartrefresh.layout.constant.RefreshState; import com.scwang.smartrefresh.layout.constant.SpinnerStyle; import com.scwang.smartrefresh.layout.internal.ProgressDrawable; import com.scwang.smartrefresh.layout.internal.pathview.PathsDrawable; import com.scwang.smartrefresh.layout.util.DensityUtil; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.Date; import java.util.List; import java.util.Locale; import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT; /** * 经典下拉头部 * Created by SCWANG on 2017/5/28. */ @SuppressWarnings("unused") public class ClassicsHeader extends RelativeLayout implements RefreshHeader { public static String REFRESH_HEADER_PULLDOWN = "下拉可以刷新"; public static String REFRESH_HEADER_REFRESHING = "正在刷新..."; public static String REFRESH_HEADER_LOADING = "正在加载..."; public static String REFRESH_HEADER_RELEASE = "释放立即刷新"; public static String REFRESH_HEADER_FINISH = "刷新完成"; public static String REFRESH_HEADER_FAILED = "刷新失败"; public static String REFRESH_HEADER_LASTTIME = "上次更新 M-d HH:mm"; protected String KEY_LAST_UPDATE_TIME = "LAST_UPDATE_TIME"; protected Date mLastTime; protected TextView mTitleText; protected TextView mLastUpdateText; protected ImageView mArrowView; protected ImageView mProgressView; protected SharedPreferences mShared; protected RefreshKernel mRefreshKernel; protected PathsDrawable mArrowDrawable; protected ProgressDrawable mProgressDrawable; protected SpinnerStyle mSpinnerStyle = SpinnerStyle.Translate; protected DateFormat mFormat = new SimpleDateFormat(REFRESH_HEADER_LASTTIME, Locale.CHINA); protected int mFinishDuration = 500; protected int mBackgroundColor; protected int mPaddingTop = 20; protected int mPaddingBottom = 20; protected boolean mEnableLastTime = true; // public ClassicsHeader(Context context) { super(context); this.initView(context, null); } public ClassicsHeader(Context context, AttributeSet attrs) { super(context, attrs); this.initView(context, attrs); } public ClassicsHeader(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); this.initView(context, attrs); } @RequiresApi(Build.VERSION_CODES.LOLLIPOP) public ClassicsHeader(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); this.initView(context, attrs); } private void initView(Context context, AttributeSet attrs) { DensityUtil density = new DensityUtil(); LinearLayout layout = new LinearLayout(context); layout.setId(android.R.id.widget_frame); layout.setGravity(Gravity.CENTER_HORIZONTAL); layout.setOrientation(LinearLayout.VERTICAL); mTitleText = new TextView(context); mTitleText.setText(REFRESH_HEADER_PULLDOWN); mTitleText.setTextColor(0xff666666); mLastUpdateText = new TextView(context); mLastUpdateText.setTextColor(0xff7c7c7c); LinearLayout.LayoutParams lpHeaderText = new LinearLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT); layout.addView(mTitleText, lpHeaderText); LinearLayout.LayoutParams lpUpdateText = new LinearLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT); layout.addView(mLastUpdateText, lpUpdateText); LayoutParams lpHeaderLayout = new LayoutParams(WRAP_CONTENT, WRAP_CONTENT); lpHeaderLayout.addRule(CENTER_IN_PARENT); addView(layout,lpHeaderLayout); LayoutParams lpArrow = new LayoutParams(density.dip2px(20), density.dip2px(20)); lpArrow.addRule(CENTER_VERTICAL); lpArrow.addRule(LEFT_OF, android.R.id.widget_frame); mArrowView = new ImageView(context); addView(mArrowView, lpArrow); LayoutParams lpProgress = new LayoutParams((ViewGroup.LayoutParams)lpArrow); lpProgress.addRule(CENTER_VERTICAL); lpProgress.addRule(LEFT_OF, android.R.id.widget_frame); mProgressView = new ImageView(context); mProgressView.animate().setInterpolator(new LinearInterpolator()); addView(mProgressView, lpProgress); if (isInEditMode()) { mArrowView.setVisibility(GONE); mTitleText.setText(REFRESH_HEADER_REFRESHING); } else { mProgressView.setVisibility(GONE); } TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.ClassicsHeader); lpUpdateText.topMargin = ta.getDimensionPixelSize(R.styleable.ClassicsHeader_srlTextTimeMarginTop, density.dip2px(0)); lpProgress.rightMargin = ta.getDimensionPixelSize(R.styleable.ClassicsFooter_srlDrawableMarginRight, density.dip2px(20)); lpArrow.rightMargin = lpProgress.rightMargin; lpArrow.width = ta.getLayoutDimension(R.styleable.ClassicsHeader_srlDrawableArrowSize, lpArrow.width); lpArrow.height = ta.getLayoutDimension(R.styleable.ClassicsHeader_srlDrawableArrowSize, lpArrow.height); lpProgress.width = ta.getLayoutDimension(R.styleable.ClassicsHeader_srlDrawableProgressSize, lpProgress.width); lpProgress.height = ta.getLayoutDimension(R.styleable.ClassicsHeader_srlDrawableProgressSize, lpProgress.height); lpArrow.width = ta.getLayoutDimension(R.styleable.ClassicsHeader_srlDrawableSize, lpArrow.width); lpArrow.height = ta.getLayoutDimension(R.styleable.ClassicsHeader_srlDrawableSize, lpArrow.height); lpProgress.width = ta.getLayoutDimension(R.styleable.ClassicsHeader_srlDrawableSize, lpProgress.width); lpProgress.height = ta.getLayoutDimension(R.styleable.ClassicsHeader_srlDrawableSize, lpProgress.height); mFinishDuration = ta.getInt(R.styleable.ClassicsHeader_srlFinishDuration, mFinishDuration); mEnableLastTime = ta.getBoolean(R.styleable.ClassicsHeader_srlEnableLastTime, mEnableLastTime); mSpinnerStyle = SpinnerStyle.values()[ta.getInt(R.styleable.ClassicsHeader_srlClassicsSpinnerStyle,mSpinnerStyle.ordinal())]; mLastUpdateText.setVisibility(mEnableLastTime ? VISIBLE : GONE); if (ta.hasValue(R.styleable.ClassicsHeader_srlDrawableArrow)) { mArrowView.setImageDrawable(ta.getDrawable(R.styleable.ClassicsHeader_srlDrawableArrow)); } else { mArrowDrawable = new PathsDrawable(); mArrowDrawable.parserColors(0xff666666); mArrowDrawable.parserPaths("M20,12l-1.41,-1.41L13,16.17V4h-2v12.17l-5.58,-5.59L4,12l8,8 8,-8z"); mArrowView.setImageDrawable(mArrowDrawable); } if (ta.hasValue(R.styleable.ClassicsHeader_srlDrawableProgress)) { mProgressView.setImageDrawable(ta.getDrawable(R.styleable.ClassicsHeader_srlDrawableProgress)); } else { mProgressDrawable = new ProgressDrawable(); mProgressDrawable.setColor(0xff666666); mProgressView.setImageDrawable(mProgressDrawable); } if (ta.hasValue(R.styleable.ClassicsHeader_srlTextSizeTitle)) { mTitleText.setTextSize(TypedValue.COMPLEX_UNIT_PX, ta.getDimensionPixelSize(R.styleable.ClassicsHeader_srlTextSizeTitle, DensityUtil.dp2px(16))); } else { mTitleText.setTextSize(16); } if (ta.hasValue(R.styleable.ClassicsHeader_srlTextSizeTime)) { mLastUpdateText.setTextSize(TypedValue.COMPLEX_UNIT_PX, ta.getDimensionPixelSize(R.styleable.ClassicsHeader_srlTextSizeTime, DensityUtil.dp2px(12))); } else { mLastUpdateText.setTextSize(12); } int primaryColor = ta.getColor(R.styleable.ClassicsHeader_srlPrimaryColor, 0); int accentColor = ta.getColor(R.styleable.ClassicsHeader_srlAccentColor, 0); if (primaryColor != 0) { if (accentColor != 0) { setPrimaryColors(primaryColor, accentColor); } else { setPrimaryColors(primaryColor); } } else if (accentColor != 0) { setPrimaryColors(0, accentColor); } ta.recycle(); if (getPaddingTop() == 0) { if (getPaddingBottom() == 0) { setPadding(getPaddingLeft(), mPaddingTop = density.dip2px(20), getPaddingRight(), mPaddingBottom = density.dip2px(20)); } else { setPadding(getPaddingLeft(), mPaddingTop = density.dip2px(20), getPaddingRight(), mPaddingBottom = getPaddingBottom()); } } else { if (getPaddingBottom() == 0) { setPadding(getPaddingLeft(), mPaddingTop = getPaddingTop(), getPaddingRight(), mPaddingBottom = density.dip2px(20)); } else { mPaddingTop = getPaddingTop(); mPaddingBottom = getPaddingBottom(); } } try {//try 不能删除-否则会出现兼容性问题 if (context instanceof FragmentActivity) { FragmentManager manager = ((FragmentActivity) context).getSupportFragmentManager(); if (manager != null) { List fragments = manager.getFragments(); if (fragments != null && fragments.size() > 0) { setLastUpdateTime(new Date()); return; } } } } catch (Throwable e) { e.printStackTrace(); } KEY_LAST_UPDATE_TIME += context.getClass().getName(); mShared = context.getSharedPreferences("ClassicsHeader", Context.MODE_PRIVATE); setLastUpdateTime(new Date(mShared.getLong(KEY_LAST_UPDATE_TIME, System.currentTimeMillis()))); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { if (MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.EXACTLY) { setPadding(getPaddingLeft(), 0, getPaddingRight(), 0); } else { setPadding(getPaddingLeft(), mPaddingTop, getPaddingRight(), mPaddingBottom); } super.onMeasure(widthMeasureSpec, heightMeasureSpec); } // // @Override public void onInitialized(RefreshKernel kernel, int height, int extendHeight) { mRefreshKernel = kernel; mRefreshKernel.requestDrawBackgoundForHeader(mBackgroundColor); } @Override public boolean isSupportHorizontalDrag() { return false; } @Override public void onHorizontalDrag(float percentX, int offsetX, int offsetMax) { } @Override public void onPullingDown(float percent, int offset, int headHeight, int extendHeight) { } @Override public void onReleasing(float percent, int offset, int headHeight, int extendHeight) { } @Override public void onStartAnimator(RefreshLayout layout, int headHeight, int extendHeight) { if (mProgressDrawable != null) { mProgressDrawable.start(); } else { Drawable drawable = mProgressView.getDrawable(); if (drawable instanceof Animatable) { ((Animatable) drawable).start(); } else { mProgressView.animate().rotation(36000).setDuration(100000); } } } @Override public int onFinish(RefreshLayout layout, boolean success) { if (mProgressDrawable != null) { mProgressDrawable.stop(); } else { Drawable drawable = mProgressView.getDrawable(); if (drawable instanceof Animatable) { ((Animatable) drawable).stop(); } else { mProgressView.animate().rotation(0).setDuration(300); } } mProgressView.setVisibility(GONE); if (success) { mTitleText.setText(REFRESH_HEADER_FINISH); setLastUpdateTime(new Date()); } else { mTitleText.setText(REFRESH_HEADER_FAILED); } return mFinishDuration;//延迟500毫秒之后再弹回 } @Override@Deprecated public void setPrimaryColors(int... colors) { if (colors.length > 0) { if (!(getBackground() instanceof BitmapDrawable)) { setPrimaryColor(colors[0]); } if (colors.length > 1) { setAccentColor(colors[1]); } else { setAccentColor(colors[0] == 0xffffffff ? 0xff666666 : 0xffffffff); } } } @NonNull public View getView() { return this; } @Override public SpinnerStyle getSpinnerStyle() { return mSpinnerStyle; } @Override public void onStateChanged(RefreshLayout refreshLayout, RefreshState oldState, RefreshState newState) { switch (newState) { case None: // restoreRefreshLayoutBackground(); mLastUpdateText.setVisibility(mEnableLastTime ? VISIBLE : GONE); case PullDownToRefresh: mTitleText.setText(REFRESH_HEADER_PULLDOWN); mArrowView.setVisibility(VISIBLE); mProgressView.setVisibility(GONE); mArrowView.animate().rotation(0); break; case Refreshing: mTitleText.setText(REFRESH_HEADER_REFRESHING); mProgressView.setVisibility(VISIBLE); mArrowView.setVisibility(GONE); break; case ReleaseToRefresh: mTitleText.setText(REFRESH_HEADER_RELEASE); mArrowView.animate().rotation(180); // replaceRefreshLayoutBackground(refreshLayout); break; case Loading: mArrowView.setVisibility(GONE); mProgressView.setVisibility(GONE); mLastUpdateText.setVisibility(GONE); mTitleText.setText(REFRESH_HEADER_LOADING); break; } } // // // private Runnable restoreRunable; // private void restoreRefreshLayoutBackground() { // if (restoreRunable != null) { // restoreRunable.run(); // restoreRunable = null; // } // } // // private void replaceRefreshLayoutBackground(final RefreshLayout refreshLayout) { // if (restoreRunable == null && mSpinnerStyle == SpinnerStyle.FixedBehind) { // restoreRunable = new Runnable() { // Drawable drawable = refreshLayout.getLayout().getBackground(); // @Override // public void run() { // refreshLayout.getLayout().setBackgroundDrawable(drawable); // } // }; // refreshLayout.getLayout().setBackgroundDrawable(getBackground()); // } // } // // public ClassicsHeader setProgressBitmap(Bitmap bitmap) { mProgressDrawable = null; mProgressView.setImageBitmap(bitmap); return this; } public ClassicsHeader setProgressDrawable(Drawable drawable) { mProgressDrawable = null; mProgressView.setImageDrawable(drawable); return this; } public ClassicsHeader setProgressResource(@DrawableRes int resId) { mProgressDrawable = null; mProgressView.setImageResource(resId); return this; } public ClassicsHeader setArrowBitmap(Bitmap bitmap) { mArrowDrawable = null; mArrowView.setImageBitmap(bitmap); return this; } public ClassicsHeader setArrowDrawable(Drawable drawable) { mArrowDrawable = null; mArrowView.setImageDrawable(drawable); return this; } public ClassicsHeader setArrowResource(@DrawableRes int resId) { mArrowDrawable = null; mArrowView.setImageResource(resId); return this; } public ClassicsHeader setLastUpdateTime(Date time) { mLastTime = time; mLastUpdateText.setText(mFormat.format(time)); if (mShared != null && !isInEditMode()) { mShared.edit().putLong(KEY_LAST_UPDATE_TIME, time.getTime()).apply(); } return this; } public ClassicsHeader setTimeFormat(DateFormat format) { mFormat = format; mLastUpdateText.setText(mFormat.format(mLastTime)); return this; } public ClassicsHeader setSpinnerStyle(SpinnerStyle style) { this.mSpinnerStyle = style; return this; } public ClassicsHeader setPrimaryColor(int primaryColor) { setBackgroundColor(mBackgroundColor = primaryColor); if (mRefreshKernel != null) { mRefreshKernel.requestDrawBackgoundForHeader(mBackgroundColor); } return this; } public ClassicsHeader setAccentColor(int accentColor) { if (mArrowDrawable != null) { mArrowDrawable.parserColors(accentColor); } if (mProgressDrawable != null) { mProgressDrawable.setColor(accentColor); } mTitleText.setTextColor(accentColor); mLastUpdateText.setTextColor(accentColor&0x00ffffff|0xcc000000); return this; } public ClassicsHeader setFinishDuration(int delay) { mFinishDuration = delay; return this; } public ClassicsHeader setEnableLastTime(boolean enable) { mEnableLastTime = enable; mLastUpdateText.setVisibility(enable ? VISIBLE : GONE); if (mRefreshKernel != null) { mRefreshKernel.requestRemeasureHeightForHeader(); } return this; } public ClassicsHeader setTextSizeTitle(float size) { mTitleText.setTextSize(size); if (mRefreshKernel != null) { mRefreshKernel.requestRemeasureHeightForHeader(); } return this; } public ClassicsHeader setTextSizeTitle(int unit, float size) { mTitleText.setTextSize(unit, size); if (mRefreshKernel != null) { mRefreshKernel.requestRemeasureHeightForHeader(); } return this; } public ClassicsHeader setTextSizeTime(float size) { mLastUpdateText.setTextSize(size); if (mRefreshKernel != null) { mRefreshKernel.requestRemeasureHeightForHeader(); } return this; } public ClassicsHeader setTextSizeTime(int unit, float size) { mLastUpdateText.setTextSize(unit, size); if (mRefreshKernel != null) { mRefreshKernel.requestRemeasureHeightForHeader(); } return this; } public ClassicsHeader setTextTimeMarginTop(float dp) { return setTextTimeMarginTopPx(DensityUtil.dp2px(dp)); } public ClassicsHeader setTextTimeMarginTopPx(int px) { MarginLayoutParams lp = (MarginLayoutParams)mLastUpdateText.getLayoutParams(); lp.topMargin = px; mLastUpdateText.setLayoutParams(lp); return this; } public ClassicsHeader setDrawableMarginRight(float dp) { return setDrawableMarginRightPx(DensityUtil.dp2px(dp)); } public ClassicsHeader setDrawableMarginRightPx(int px) { MarginLayoutParams lpArrow = (MarginLayoutParams)mArrowView.getLayoutParams(); MarginLayoutParams lpProgress = (MarginLayoutParams)mProgressView.getLayoutParams(); lpArrow.rightMargin = lpProgress.rightMargin = px; mArrowView.setLayoutParams(lpArrow); mProgressView.setLayoutParams(lpProgress); return this; } public ClassicsHeader setDrawableSize(float dp) { return setDrawableSizePx(DensityUtil.dp2px(dp)); } public ClassicsHeader setDrawableSizePx(int px) { ViewGroup.LayoutParams lpArrow = mArrowView.getLayoutParams(); ViewGroup.LayoutParams lpProgress = mProgressView.getLayoutParams(); lpArrow.width = lpProgress.width = px; lpArrow.height = lpProgress.height = px; mArrowView.setLayoutParams(lpArrow); mProgressView.setLayoutParams(lpProgress); return this; } public ClassicsHeader setDrawableArrowSize(float dp) { return setDrawableArrowSizePx(DensityUtil.dp2px(dp)); } public ClassicsHeader setDrawableArrowSizePx(int px) { ViewGroup.LayoutParams lpArrow = mArrowView.getLayoutParams(); lpArrow.width = px; lpArrow.height = px; mArrowView.setLayoutParams(lpArrow); return this; } public ClassicsHeader setDrawableProgressSize(float dp) { return setDrawableProgressSizePx(DensityUtil.dp2px(dp)); } public ClassicsHeader setDrawableProgressSizePx(int px) { ViewGroup.LayoutParams lpProgress = mProgressView.getLayoutParams(); lpProgress.width = px; lpProgress.height = px; mProgressView.setLayoutParams(lpProgress); return this; } public ImageView getArrowView() { return mArrowView; } public ImageView getProgressView() { return mProgressView; } public TextView getTitleText() { return mTitleText; } public TextView getLastUpdateText() { return mLastUpdateText; } // } ================================================ FILE: refresh-layout/src/main/java/com/scwang/smartrefresh/layout/header/FalsifyHeader.java ================================================ package com.scwang.smartrefresh.layout.header; import android.annotation.SuppressLint; import android.support.annotation.RequiresApi; import android.content.Context; import android.graphics.Canvas; import android.graphics.DashPathEffect; import android.graphics.Paint; import android.os.Build; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.util.AttributeSet; import android.view.Gravity; import android.view.View; import android.widget.TextView; import com.scwang.smartrefresh.layout.api.RefreshHeader; import com.scwang.smartrefresh.layout.api.RefreshKernel; import com.scwang.smartrefresh.layout.api.RefreshLayout; import com.scwang.smartrefresh.layout.constant.RefreshState; import com.scwang.smartrefresh.layout.constant.SpinnerStyle; import com.scwang.smartrefresh.layout.util.DensityUtil; import static android.view.View.MeasureSpec.EXACTLY; import static android.view.View.MeasureSpec.makeMeasureSpec; /** * 虚假的 Header * 用于 正真的 Header 在 RefreshLayout 外部时, * 使用本虚假的 FalsifyHeader 填充在 RefreshLayout 内部 * 具体使用方法 参考 QQ空间风格(QzoneHeader) 和 纸飞机(FlyRefreshHeader) * Created by SCWANG on 2017/6/14. */ public class FalsifyHeader extends View implements RefreshHeader { protected RefreshKernel mRefreshKernel; // public FalsifyHeader(Context context) { super(context); } public FalsifyHeader(Context context, AttributeSet attrs) { super(context, attrs); } public FalsifyHeader(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } @RequiresApi(Build.VERSION_CODES.LOLLIPOP) public FalsifyHeader(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { setMeasuredDimension(resolveSize(getSuggestedMinimumWidth(), widthMeasureSpec), resolveSize(getSuggestedMinimumHeight(), heightMeasureSpec)); } @Override@SuppressLint("DrawAllocation") protected void onDraw(Canvas canvas) { super.onDraw(canvas); if (isInEditMode()) {//这段代码在运行时不会执行,只会在Studio编辑预览时运行,不用在意性能问题 int d = DensityUtil.dp2px(5); Paint paint = new Paint(); paint.setStyle(Paint.Style.STROKE); paint.setColor(0x44ffffff); paint.setStrokeWidth(DensityUtil.dp2px(1)); paint.setPathEffect(new DashPathEffect(new float[]{d, d, d, d}, 1)); canvas.drawRect(d, d, getWidth() - d, getBottom() - d, paint); TextView textView = new TextView(getContext()); textView.setText(getClass().getSimpleName()+" 虚假区域\n运行时代表下拉Header的高度【" + DensityUtil.px2dp(getHeight()) + "dp】\n而不会显示任何东西"); textView.setTextColor(0x44ffffff); textView.setGravity(Gravity.CENTER); textView.measure(makeMeasureSpec(getWidth(), EXACTLY), makeMeasureSpec(getHeight(), EXACTLY)); textView.layout(0, 0, getWidth(), getHeight()); textView.draw(canvas); } } // // @Override public void onInitialized(RefreshKernel kernel, int height, int extendHeight) { mRefreshKernel = kernel; } @Override public boolean isSupportHorizontalDrag() { return false; } @Override public void onHorizontalDrag(float percentX, int offsetX, int offsetMax) { } @Override public void onPullingDown(float percent, int offset, int headHeight, int extendHeight) { } @Override public void onReleasing(float percent, int offset, int headHeight, int extendHeight) { } @Override public void onStartAnimator(RefreshLayout layout, int headHeight, int extendHeight) { if (mRefreshKernel != null) { mRefreshKernel.resetStatus(); } } @Override public void onStateChanged(RefreshLayout refreshLayout, RefreshState oldState, RefreshState newState) { } @Override public int onFinish(RefreshLayout layout, boolean success) { return 0; } @Override@Deprecated public void setPrimaryColors(int... colors) { } @NonNull @Override public View getView() { return this; } @Override public SpinnerStyle getSpinnerStyle() { return SpinnerStyle.Scale; } // } ================================================ FILE: refresh-layout/src/main/java/com/scwang/smartrefresh/layout/header/bezierradar/RippleView.java ================================================ package com.scwang.smartrefresh.layout.header.bezierradar; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.ValueAnimator; import android.content.Context; import android.graphics.Canvas; import android.graphics.Paint; import android.view.View; /** * cjj */ public class RippleView extends View { private int mRadius; private Paint mPaint; private ValueAnimator mAnimator; public RippleView(Context context) { super(context); mPaint = new Paint(); mPaint.setAntiAlias(true); mPaint.setColor(0xffffffff); mPaint.setStyle(Paint.Style.FILL); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { setMeasuredDimension(resolveSize(getSuggestedMinimumWidth(), widthMeasureSpec), resolveSize(getSuggestedMinimumHeight(), heightMeasureSpec)); } public void setFrontColor(int color) { mPaint.setColor(color); } public void startReveal() { if (mAnimator == null) { int bigRadius = (int) (Math.sqrt(Math.pow(getHeight(), 2) + Math.pow(getWidth(), 2))); mAnimator = ValueAnimator.ofInt(0, bigRadius); mAnimator.setDuration(400); mAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { mRadius = (int) animation.getAnimatedValue(); invalidate(); } }); mAnimator.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { } }); } mAnimator.start(); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); canvas.drawCircle(getWidth() / 2, getHeight() / 2, mRadius, mPaint); } } ================================================ FILE: refresh-layout/src/main/java/com/scwang/smartrefresh/layout/header/bezierradar/RoundDotView.java ================================================ package com.scwang.smartrefresh.layout.header.bezierradar; import android.content.Context; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.view.View; import com.scwang.smartrefresh.layout.util.DensityUtil; /** * * Created by cjj on 2015/8/27. */ public class RoundDotView extends View { private int num = 7; private Paint mPath; private float mRadius; private float fraction; public RoundDotView(Context context) { super(context); mPath = new Paint(); mPath.setAntiAlias(true); mPath.setColor(Color.WHITE); mRadius = DensityUtil.dp2px(7); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { setMeasuredDimension(resolveSize(getSuggestedMinimumWidth(), widthMeasureSpec), resolveSize(getSuggestedMinimumHeight(), heightMeasureSpec)); } public void setDotColor(int color) { mPath.setColor(color); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); int width = getWidth(); int height = getHeight(); float wide = (width / num) * fraction-((fraction>1)?((fraction-1)*(width / num)/fraction):0);//y1 = t*(w/n)-(t>1)*((t-1)*(w/n)/t) float high = height - ((fraction>1)?((fraction-1)*height/2/fraction):0);//y2 = x - (t>1)*((t-1)*x/t); for (int i = 0 ; i < num; i++) { float index = 1f + i - (1f + num) / 2;//y3 = (x + 1) - (n + 1)/2; 居中 index 变量:0 1 2 3 4 结果: -2 -1 0 1 2 float alpha = 255 * (1 - (2 * (Math.abs(index) / num)));//y4 = m * ( 1 - 2 * abs(y3) / n); 横向 alpha 差 float x = DensityUtil.px2dp(height); mPath.setAlpha((int) (alpha * (1d - 1d / Math.pow((x / 800d + 1d), 15))));//y5 = y4 * (1-1/((x/800+1)^15));竖直 alpha 差 float radius = mRadius * (1-1/((x/10+1)));//y6 = mRadius*(1-1/(x/10+1));半径 canvas.drawCircle(width / 2- radius/2 + wide * index , high / 2, radius, mPath); } } public void setFraction(float fraction) { this.fraction = fraction; } } ================================================ FILE: refresh-layout/src/main/java/com/scwang/smartrefresh/layout/header/bezierradar/RoundProgressView.java ================================================ package com.scwang.smartrefresh.layout.header.bezierradar; import android.animation.ValueAnimator; import android.content.Context; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.RectF; import android.view.View; import android.view.animation.AccelerateDecelerateInterpolator; import com.scwang.smartrefresh.layout.util.DensityUtil; /** * 中心圆形加载进度视图 * Created by Administrator on 2015/8/27. */ public class RoundProgressView extends View { private Paint mPath; private Paint mPantR; private ValueAnimator mAnimator; private int endAngle = 0; private int stratAngle = 270; private int mRadius = 0; private int mOutsideCircle = 0; private RectF mRect = new RectF(0,0,0,0); public RoundProgressView(Context context) { super(context); initView(); } private void initView() { mPath = new Paint(); mPantR = new Paint(); mPath.setAntiAlias(true); mPantR.setAntiAlias(true); mPath.setColor(Color.WHITE); mPantR.setColor(0x55000000); DensityUtil density = new DensityUtil(); mRadius = density.dip2px(20); mOutsideCircle = density.dip2px(7); mPath.setStrokeWidth(density.dip2px(3)); mPantR.setStrokeWidth(density.dip2px(3)); mAnimator = ValueAnimator.ofInt(0,360); mAnimator.setDuration(720); mAnimator.setRepeatCount(ValueAnimator.INFINITE); mAnimator.setInterpolator(new AccelerateDecelerateInterpolator()); } @Override protected void onAttachedToWindow() { super.onAttachedToWindow(); mAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { endAngle = (int) animation.getAnimatedValue(); postInvalidate(); } }); } @Override protected void onDetachedFromWindow() { super.onDetachedFromWindow(); mAnimator.removeAllUpdateListeners(); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { setMeasuredDimension(resolveSize(getSuggestedMinimumWidth(), widthMeasureSpec), resolveSize(getSuggestedMinimumHeight(), heightMeasureSpec)); } public void setBackColor(int backColor) { mPantR.setColor(backColor&0x00ffffff|0x55000000); } public void setFrontColor(int color) { mPath.setColor(color); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); int width = getWidth(); int height = getHeight(); if (isInEditMode()) { stratAngle = 0; endAngle = 270; } mPath.setStyle(Paint.Style.FILL); canvas.drawCircle(width / 2, height / 2, mRadius, mPath); mPath.setStyle(Paint.Style.STROKE);//设置为空心 canvas.drawCircle(width / 2, height / 2, mRadius + mOutsideCircle, mPath); mPantR.setStyle(Paint.Style.FILL); mRect.set(width/2- mRadius, height/2- mRadius, width/2+ mRadius, height/2+ mRadius); canvas.drawArc(mRect, stratAngle, endAngle, true, mPantR); mRadius += mOutsideCircle; mPantR.setStyle(Paint.Style.STROKE); mRect.set(width/2- mRadius, height/2- mRadius, width/2+ mRadius, height/2+ mRadius); canvas.drawArc(mRect, stratAngle, endAngle, false, mPantR); mRadius -= mOutsideCircle; } public void startAnim(){ if (mAnimator !=null) mAnimator.start(); } public void stopAnim(){ if (mAnimator !=null && mAnimator.isRunning()) mAnimator.cancel(); } } ================================================ FILE: refresh-layout/src/main/java/com/scwang/smartrefresh/layout/header/bezierradar/WaveView.java ================================================ package com.scwang.smartrefresh.layout.header.bezierradar; import android.content.Context; import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.Path; import android.util.AttributeSet; import android.view.View; /** * Created by cjj on 2015/8/5. * 绘制贝塞尔来绘制波浪形 */ public class WaveView extends View { private int waveHeight; private int headHeight; private Path path; private Paint paint; private int mOffsetX = -1; public WaveView(Context context) { this(context, null, 0); } public WaveView(Context context, AttributeSet attrs) { this(context, attrs, 0); } public WaveView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); initView(); } private void initView() { path = new Path(); paint = new Paint(); paint.setColor(0xff1F2426); paint.setAntiAlias(true); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { setMeasuredDimension(resolveSize(getSuggestedMinimumWidth(), widthMeasureSpec), resolveSize(getSuggestedMinimumHeight(), heightMeasureSpec)); } public void setWaveColor(int color) { paint.setColor(color); } public int getHeadHeight() { return headHeight; } public void setHeadHeight(int headHeight) { this.headHeight = headHeight; } public int getWaveHeight() { return waveHeight; } public void setWaveHeight(int waveHeight) { this.waveHeight = waveHeight; } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); final int width = getWidth(); //重置画笔 path.reset(); //绘制贝塞尔曲线 path.lineTo(0, headHeight); path.quadTo(mOffsetX >= 0 ? (mOffsetX) : width / 2, headHeight + waveHeight, width, headHeight); path.lineTo(width, 0); canvas.drawPath(path, paint); } public void setWaveOffsetX(int offset) { mOffsetX = offset; } } ================================================ FILE: refresh-layout/src/main/java/com/scwang/smartrefresh/layout/impl/RefreshContentWrapper.java ================================================ package com.scwang.smartrefresh.layout.impl; import android.animation.ValueAnimator; import android.animation.ValueAnimator.AnimatorUpdateListener; import android.content.Context; import android.database.DataSetObserver; import android.os.Build; import android.support.annotation.NonNull; import android.support.annotation.RequiresApi; import android.support.design.widget.AppBarLayout; import android.support.design.widget.CoordinatorLayout; import android.support.v4.app.Fragment; import android.support.v4.view.NestedScrollingChild; import android.support.v4.view.NestedScrollingParent; import android.support.v4.view.PagerAdapter; import android.support.v4.view.PagerAdapterWrapper; import android.support.v4.view.ScrollingView; import android.support.v4.view.ViewPager; import android.support.v4.widget.ListViewCompat; import android.support.v4.widget.NestedScrollView; import android.support.v4.widget.Space; import android.support.v7.widget.RecyclerView; import android.util.SparseArray; import android.view.Gravity; import android.view.MotionEvent; import android.view.View; import android.view.View.MeasureSpec; import android.view.ViewGroup; import android.webkit.WebView; import android.widget.AbsListView; import android.widget.FrameLayout; import android.widget.ListView; import android.widget.ScrollView; import com.scwang.smartrefresh.layout.api.RefreshContent; import com.scwang.smartrefresh.layout.api.RefreshKernel; import com.scwang.smartrefresh.layout.api.RefreshLayout; import com.scwang.smartrefresh.layout.api.ScrollBoundaryDecider; import com.scwang.smartrefresh.layout.constant.RefreshState; import java.lang.reflect.Field; import java.util.Collections; import java.util.Queue; import java.util.concurrent.LinkedBlockingQueue; import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT; import static com.scwang.smartrefresh.layout.util.ScrollBoundaryUtil.canScrollDown; import static com.scwang.smartrefresh.layout.util.ScrollBoundaryUtil.canScrollUp; /** * 刷新内容包装 * Created by SCWANG on 2017/5/26. */ @SuppressWarnings("WeakerAccess") public class RefreshContentWrapper implements RefreshContent { protected static final String TAG_REFRESH_CONTENT_WRAPPER = "TAG_REFRESH_CONTENT_WRAPPER"; protected int mHeaderHeight = Integer.MAX_VALUE; protected int mFooterHeight = mHeaderHeight - 1; protected View mContentView;//直接内容视图 protected View mRealContentView;//被包裹的原真实视图 protected View mScrollableView; protected View mFixedHeader; protected View mFixedFooter; protected boolean mEnableRefresh = true; protected boolean mEnableLoadmore = true; protected MotionEvent mMotionEvent; protected ScrollBoundaryDeciderAdapter mBoundaryAdapter = new ScrollBoundaryDeciderAdapter(); public RefreshContentWrapper(View view) { this.mContentView = mRealContentView = view; this.mContentView.setTag(TAG_REFRESH_CONTENT_WRAPPER.hashCode(), TAG_REFRESH_CONTENT_WRAPPER); } public RefreshContentWrapper(Context context) { this.mContentView = mRealContentView = new View(context); this.mContentView.setTag(TAG_REFRESH_CONTENT_WRAPPER.hashCode(), TAG_REFRESH_CONTENT_WRAPPER); } public static boolean isTagedContent(View view) { return TAG_REFRESH_CONTENT_WRAPPER.equals(view.getTag(TAG_REFRESH_CONTENT_WRAPPER.hashCode())); } // protected void findScrollableView(View content, RefreshKernel kernel) { mScrollableView = findScrollableViewInternal(content, true); try {//try 不能删除,不然会出现兼容性问题 if (mScrollableView instanceof CoordinatorLayout) { kernel.getRefreshLayout().setEnableNestedScroll(false); wrapperCoordinatorLayout(((CoordinatorLayout) mScrollableView), kernel.getRefreshLayout()); } } catch (Throwable ignored) { } try {//try 不能删除,不然会出现兼容性问题 if (mScrollableView instanceof ViewPager) { wrapperViewPager((ViewPager) this.mScrollableView); } } catch (Throwable ignored) { } if (mScrollableView instanceof NestedScrollingParent && !(mScrollableView instanceof NestedScrollingChild)) { mScrollableView = findScrollableViewInternal(mScrollableView, false); } if (mScrollableView == null) { mScrollableView = content; } } protected void wrapperCoordinatorLayout(CoordinatorLayout layout, final RefreshLayout refreshLayout) { for (int i = layout.getChildCount() - 1; i >= 0; i--) { View view = layout.getChildAt(i); if (view instanceof AppBarLayout) { ((AppBarLayout) view).addOnOffsetChangedListener(new AppBarLayout.OnOffsetChangedListener() { @Override public void onOffsetChanged(AppBarLayout appBarLayout, int verticalOffset) { mEnableRefresh = verticalOffset >= 0; mEnableLoadmore = refreshLayout.isEnableLoadmore() && (appBarLayout.getTotalScrollRange() + verticalOffset) <= 0; } }); } } } protected void wrapperViewPager(final ViewPager viewPager) { wrapperViewPager(viewPager, null); } protected void wrapperViewPager(final ViewPager viewPager, final PagerPrimaryAdapter primaryAdapter) { viewPager.post(new Runnable() { int count = 0; PagerPrimaryAdapter mAdapter = primaryAdapter; @Override public void run() { count++; PagerAdapter adapter = viewPager.getAdapter(); if (adapter != null) { if (adapter instanceof PagerPrimaryAdapter) { if (adapter == primaryAdapter && count < 10) { viewPager.postDelayed(this, 500); } } else { if (mAdapter == null) { mAdapter = new PagerPrimaryAdapter(adapter); } else { mAdapter.wrapper(adapter); } mAdapter.attachViewPager(viewPager); } } else if (count < 10) { viewPager.postDelayed(this, 500); } } }); } protected View findScrollableViewInternal(View content, boolean selfable) { View scrollableView = null; Queue views = new LinkedBlockingQueue<>(Collections.singletonList(content)); while (!views.isEmpty() && scrollableView == null) { View view = views.poll(); if (view != null) { if ((selfable || view != content) && (view instanceof AbsListView || view instanceof ScrollView || view instanceof ScrollingView || view instanceof NestedScrollingChild || view instanceof NestedScrollingParent || view instanceof WebView || view instanceof ViewPager)) { scrollableView = view; } else if (view instanceof ViewGroup) { ViewGroup group = (ViewGroup) view; for (int j = 0; j < group.getChildCount(); j++) { views.add(group.getChildAt(j)); } } } } return scrollableView; } // // @NonNull public View getView() { return mContentView; } @Override public void moveSpinner(int spinner) { mRealContentView.setTranslationY(spinner); if (mFixedHeader != null) { mFixedHeader.setTranslationY(Math.max(0, spinner)); } if (mFixedFooter != null) { mFixedFooter.setTranslationY(Math.min(0, spinner)); } } @Override public boolean canRefresh() { return mEnableRefresh && mBoundaryAdapter.canRefresh(mContentView); } @Override public boolean canLoadmore() { return mEnableLoadmore && mBoundaryAdapter.canLoadmore(mContentView); } @Override public void measure(int widthSpec, int heightSpec) { mContentView.measure(widthSpec, heightSpec); } @Override public ViewGroup.LayoutParams getLayoutParams() { return mContentView.getLayoutParams(); } @Override public int getMeasuredWidth() { return mContentView.getMeasuredWidth(); } @Override public int getMeasuredHeight() { return mContentView.getMeasuredHeight(); } @Override public void layout(int left, int top, int right, int bottom) { mContentView.layout(left, top, right, bottom); } @Override public View getScrollableView() { return mScrollableView; } @Override public void onActionDown(MotionEvent e) { mMotionEvent = MotionEvent.obtain(e); mMotionEvent.offsetLocation(-mContentView.getLeft(), -mContentView.getTop()); mBoundaryAdapter.setActionEvent(mMotionEvent); } @Override public void onActionUpOrCancel() { mMotionEvent = null; mBoundaryAdapter.setActionEvent(null); } @Override public void setupComponent(RefreshKernel kernel, View fixedHeader, View fixedFooter) { this.findScrollableView(mContentView, kernel); try {//try 不能删除,不然会出现兼容性问题 if (mScrollableView instanceof RecyclerView) { RecyclerViewScrollComponent component = new RecyclerViewScrollComponent(kernel); component.attach((RecyclerView) mScrollableView); } } catch (Throwable ignored) { } try {//try 不能删除,不然会出现兼容性问题 if (mScrollableView instanceof NestedScrollView) { NestedScrollViewScrollComponent component = new NestedScrollViewScrollComponent(kernel); component.attach((NestedScrollView) mScrollableView); } } catch (Throwable ignored) { } if (mScrollableView instanceof AbsListView) { AbsListViewScrollComponent component = new AbsListViewScrollComponent(kernel); component.attach(((AbsListView) mScrollableView)); } else if (Build.VERSION.SDK_INT >= 23 && mScrollableView != null) { Api23ViewScrollComponent component = new Api23ViewScrollComponent(kernel); component.attach(mScrollableView); } if (fixedHeader != null || fixedFooter != null) { mFixedHeader = fixedHeader; mFixedFooter = fixedFooter; FrameLayout frameLayout = new FrameLayout(mContentView.getContext()); kernel.getRefreshLayout().getLayout().removeView(mContentView); ViewGroup.LayoutParams layoutParams = mContentView.getLayoutParams(); frameLayout.addView(mContentView, MATCH_PARENT, MATCH_PARENT); kernel.getRefreshLayout().getLayout().addView(frameLayout, layoutParams); mContentView = frameLayout; if (fixedHeader != null) { fixedHeader.setClickable(true); ViewGroup.LayoutParams lp = fixedHeader.getLayoutParams(); ViewGroup parent = (ViewGroup) fixedHeader.getParent(); int index = parent.indexOfChild(fixedHeader); parent.removeView(fixedHeader); lp.height = measureViewHeight(fixedHeader); parent.addView(new Space(mContentView.getContext()), index, lp); frameLayout.addView(fixedHeader); } if (fixedFooter != null) { fixedFooter.setClickable(true); ViewGroup.LayoutParams lp = fixedFooter.getLayoutParams(); ViewGroup parent = (ViewGroup) fixedFooter.getParent(); int index = parent.indexOfChild(fixedFooter); parent.removeView(fixedFooter); FrameLayout.LayoutParams flp = new FrameLayout.LayoutParams(lp); lp.height = measureViewHeight(fixedFooter); parent.addView(new Space(mContentView.getContext()), index, lp); flp.gravity = Gravity.BOTTOM; frameLayout.addView(fixedFooter, flp); } } } @Override public void onInitialHeaderAndFooter(int headerHeight, int footerHeight) { mHeaderHeight = headerHeight; mFooterHeight = footerHeight; } @Override public void setScrollBoundaryDecider(ScrollBoundaryDecider boundary) { if (boundary instanceof ScrollBoundaryDeciderAdapter) { mBoundaryAdapter = ((ScrollBoundaryDeciderAdapter) boundary); } else { mBoundaryAdapter.setScrollBoundaryDecider(boundary); } } @Override public void setEnableLoadmoreWhenContentNotFull(boolean enable) { mBoundaryAdapter.setEnableLoadmoreWhenContentNotFull(enable); } @Override public AnimatorUpdateListener onLoadingFinish(final RefreshKernel kernel, final int footerHeight, int startDelay, final int duration) { if (mScrollableView != null && kernel.getRefreshLayout().isEnableScrollContentWhenLoaded()) { if (!canScrollDown(mScrollableView)) { return null; } if (mScrollableView instanceof AbsListView && !(mScrollableView instanceof ListView) && Build.VERSION.SDK_INT < 19) { if (startDelay > 0) { kernel.getRefreshLayout().getLayout().postDelayed(new Runnable() { @Override public void run() { ((AbsListView) mScrollableView).smoothScrollBy(footerHeight, duration); } }, startDelay); } else { ((AbsListView) mScrollableView).smoothScrollBy(footerHeight, duration); } return null; } return new AnimatorUpdateListener() { int lastValue = kernel.getSpinner(); @Override public void onAnimationUpdate(ValueAnimator animation) { int value = (int) animation.getAnimatedValue(); if (mScrollableView instanceof ListView) { ListViewCompat.scrollListBy((ListView) mScrollableView, value - lastValue); } else { mScrollableView.scrollBy(0, value - lastValue); } lastValue = value; } }; } return null; } // // @RequiresApi(api = Build.VERSION_CODES.M) protected class Api23ViewScrollComponent implements View.OnScrollChangeListener { long lastTime = 0; long lastTimeOld = 0; int lastScrollY = 0; int lastOldScrollY = 0; RefreshKernel kernel; View.OnScrollChangeListener mScrollListener; Api23ViewScrollComponent(RefreshKernel kernel) { this.kernel = kernel; } @Override public void onScrollChange(View v, int scrollX, int scrollY, int oldScrollX, int oldScrollY) { if (mScrollListener != null) { mScrollListener.onScrollChange(v, scrollX, scrollY, oldScrollX, oldScrollY); } if (lastScrollY == scrollY && lastOldScrollY == oldScrollY) { return; } RefreshLayout layout = kernel.getRefreshLayout(); boolean overScroll = layout.isEnableOverScrollBounce() || layout.isRefreshing() || layout.isLoading(); if (scrollY <= 0 && oldScrollY > 0 && mMotionEvent == null && lastTime - lastTimeOld > 1000 && overScroll && layout.isEnableRefresh()) { //time:16000000 value:160 final int velocity = (lastOldScrollY - oldScrollY) * 16000 / (int)((lastTime - lastTimeOld)/1000f); kernel.animSpinnerBounce(Math.min(velocity, mHeaderHeight)); } else if (oldScrollY < scrollY && mMotionEvent == null && layout.isEnableLoadmore()) { if (!layout.isLoadmoreFinished() && layout.isEnableAutoLoadmore() && !layout.isEnablePureScrollMode() && layout.getState() == RefreshState.None && !canScrollDown(v)) { kernel.getRefreshLayout().autoLoadmore(0, 1); } else if (overScroll && lastTime - lastTimeOld > 1000 && !canScrollDown(v)) { final int velocity = (lastOldScrollY - oldScrollY) * 16000 / (int)((lastTime - lastTimeOld)/1000f); kernel.animSpinnerBounce(Math.max(velocity, -mFooterHeight)); } } lastScrollY = scrollY; lastOldScrollY = oldScrollY; lastTimeOld = lastTime; lastTime = System.nanoTime(); } void attach(View view) { Field[] declaredFields = View.class.getDeclaredFields(); if (declaredFields != null) { for (Field field : declaredFields) { if (View.OnScrollChangeListener.class.equals(field.getType())) { try { field.setAccessible(true); Object listener = field.get(view); if (listener != null && !view.equals(listener)) { mScrollListener = (View.OnScrollChangeListener) listener; } } catch (IllegalAccessException e) { e.printStackTrace(); } } } } view.setOnScrollChangeListener(new Api23ViewScrollComponent(kernel)); } } protected class NestedScrollViewScrollComponent implements NestedScrollView.OnScrollChangeListener { long lastTime = 0; long lastTimeOld = 0; int lastScrollY = 0; int lastOldScrollY = 0; RefreshKernel kernel; NestedScrollView.OnScrollChangeListener mScrollChangeListener; NestedScrollViewScrollComponent(RefreshKernel kernel) { this.kernel = kernel; } @Override public void onScrollChange(NestedScrollView scrollView, int scrollX, int scrollY, int oldScrollX, int oldScrollY) { if (mScrollChangeListener != null) { mScrollChangeListener.onScrollChange(scrollView, scrollX, scrollY, oldScrollX, oldScrollY); } if (lastScrollY == scrollY && lastOldScrollY == oldScrollY) { return; } RefreshLayout layout = kernel.getRefreshLayout(); boolean overScroll = layout.isEnableOverScrollBounce() || layout.isRefreshing() || layout.isLoading(); if (scrollY <= 0 && oldScrollY > 0 && mMotionEvent == null && lastTime - lastTimeOld > 1000 && overScroll && layout.isEnableRefresh()) { final int velocity = (lastOldScrollY - oldScrollY) * 16000 / (int)((lastTime - lastTimeOld)/1000f); kernel.animSpinnerBounce(Math.min(velocity, mHeaderHeight)); } else if (oldScrollY < scrollY && mMotionEvent == null && layout.isEnableLoadmore()) { if (!layout.isLoadmoreFinished() && layout.isEnableAutoLoadmore() && !layout.isEnablePureScrollMode() && layout.getState() == RefreshState.None && !canScrollDown(scrollView)) { kernel.getRefreshLayout().autoLoadmore(0, 1); } else if (overScroll && lastTime - lastTimeOld > 1000 && !canScrollDown(mScrollableView)) { final int velocity = (lastOldScrollY - oldScrollY) * 16000 / (int)((lastTime - lastTimeOld)/1000f); kernel.animSpinnerBounce(Math.max(velocity, -mFooterHeight)); } } lastScrollY = scrollY; lastOldScrollY = oldScrollY; lastTimeOld = lastTime; lastTime = System.nanoTime(); } void attach(NestedScrollView scrollView) { //获得原始监听器,用作转发 Field[] declaredFields = NestedScrollView.class.getDeclaredFields(); if (declaredFields != null) { for (Field field : declaredFields) { if (NestedScrollView.OnScrollChangeListener.class.equals(field.getType())) { try { field.setAccessible(true); Object listener = field.get(scrollView); if (listener != null && !scrollView.equals(listener)) { mScrollChangeListener = (NestedScrollView.OnScrollChangeListener) listener; } } catch (IllegalAccessException e) { e.printStackTrace(); } } } } scrollView.setOnScrollChangeListener(this); } } protected class AbsListViewScrollComponent implements AbsListView.OnScrollListener { int scrollY; int scrollDy; int lastScrolly; int lastScrollDy; RefreshKernel kernel; SparseArray recordSp = new SparseArray<>(0); AbsListView.OnScrollListener mScrollListener; AbsListViewScrollComponent(RefreshKernel kernel) { this.kernel = kernel; } @Override public void onScrollStateChanged(AbsListView view, int scrollState) { if (mScrollListener != null) { mScrollListener.onScrollStateChanged(view, scrollState); } } @Override public void onScroll(AbsListView absListView, int firstVisibleItem, int visibleItemCount, int totalItemCount) { if (mScrollListener != null) { mScrollListener.onScroll(absListView, firstVisibleItem, visibleItemCount, totalItemCount); } lastScrolly = scrollY; lastScrollDy = scrollDy; scrollY = getScrollY(absListView, firstVisibleItem); scrollDy = lastScrolly - scrollY; final int dy = lastScrollDy + scrollDy; if (totalItemCount > 0 && mMotionEvent == null) { RefreshLayout layout = kernel.getRefreshLayout(); if (dy > 0) { if (firstVisibleItem == 0 && layout.isEnableRefresh() && (layout.isEnableOverScrollBounce() || layout.isRefreshing()) && !canScrollUp(absListView)) { kernel.animSpinnerBounce(Math.min(dy, mHeaderHeight)); } } else if (dy < 0) { int lastVisiblePosition = absListView.getLastVisiblePosition(); if (lastVisiblePosition == totalItemCount - 1 && lastVisiblePosition > 0 && layout.isEnableLoadmore() && !canScrollDown(absListView)) { if (layout.getState() == RefreshState.None && layout.isEnableAutoLoadmore() && !layout.isLoadmoreFinished() && !layout.isEnablePureScrollMode()) { layout.autoLoadmore(0, 1); } else if (layout.isEnableOverScrollBounce() || layout.isLoading()) { kernel.animSpinnerBounce(Math.max(dy, -mFooterHeight)); } } } } } void attach(AbsListView listView) { //获得原始监听器,用作转发 Field[] declaredFields = AbsListView.class.getDeclaredFields(); if (declaredFields != null) { for (Field field : declaredFields) { if (AbsListView.OnScrollListener.class.equals(field.getType())) { try { field.setAccessible(true); Object listener = field.get(listView); if (listener != null && !listView.equals(listener)) { mScrollListener = (AbsListView.OnScrollListener) listener; } } catch (IllegalAccessException e) { e.printStackTrace(); } } } } listView.setOnScrollListener(this); } protected int getScrollY(AbsListView view, int firstVisibleItem) { View firstView = view.getChildAt(0); if (null != firstView) { ItemRecod itemRecord = recordSp.get(firstVisibleItem); if (null == itemRecord) { itemRecord = new ItemRecod(); } itemRecord.height = firstView.getHeight(); itemRecord.top = firstView.getTop(); recordSp.append(firstVisibleItem, itemRecord); int height = 0,lastheight = 0; for (int i = 0; i < firstVisibleItem; i++) { ItemRecod itemRecod = recordSp.get(i); if (itemRecod != null) { height += itemRecod.height; lastheight = itemRecod.height; } else { height += lastheight; } } ItemRecod itemRecod = recordSp.get(firstVisibleItem); if (null == itemRecod) { itemRecod = new ItemRecod(); } return height - itemRecod.top; } return 0; } class ItemRecod { int height = 0; int top = 0; } } protected class RecyclerViewScrollComponent extends RecyclerView.OnScrollListener { RefreshKernel kernel; RecyclerViewScrollComponent(RefreshKernel kernel) { this.kernel = kernel; } @Override public void onScrollStateChanged(RecyclerView recyclerView, int newState) { } @Override public void onScrolled(RecyclerView recyclerView, int dx, int dy) { if (mMotionEvent == null) { final RefreshLayout layout = kernel.getRefreshLayout(); if (dy < 0 && layout.isEnableRefresh() && (layout.isEnableOverScrollBounce() || layout.isRefreshing()) && !canScrollUp(recyclerView)) { kernel.animSpinnerBounce(Math.min(-dy * 2, mHeaderHeight)); } else if (dy > 0 && layout.isEnableLoadmore() && !canScrollDown(recyclerView)) { if (layout.getState() == RefreshState.None && layout.isEnableAutoLoadmore() && !layout.isLoadmoreFinished() && !layout.isEnablePureScrollMode()) { layout.autoLoadmore(0,1); } else if (layout.isEnableOverScrollBounce() || layout.isLoading()) { kernel.animSpinnerBounce(Math.max(-dy * 2, -mFooterHeight)); } } } } void attach(RecyclerView recyclerView) { recyclerView.addOnScrollListener(this); } } // // protected static int measureViewHeight(View view) { ViewGroup.LayoutParams p = view.getLayoutParams(); if (p == null) { p = new ViewGroup.LayoutParams(MATCH_PARENT,WRAP_CONTENT); } int childHeightSpec; int childWidthSpec = ViewGroup.getChildMeasureSpec(0, 0, p.width); if (p.height > 0) { childHeightSpec = MeasureSpec.makeMeasureSpec(p.height, MeasureSpec.EXACTLY); } else { childHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); } view.measure(childWidthSpec, childHeightSpec); return view.getMeasuredHeight(); } // protected class PagerPrimaryAdapter extends PagerAdapterWrapper { protected ViewPager mViewPager; PagerPrimaryAdapter(PagerAdapter wrapped) { super(wrapped); } void wrapper(PagerAdapter adapter) { wrapped = adapter; } @Override public void attachViewPager(ViewPager viewPager) { mViewPager = viewPager; super.attachViewPager(viewPager); } @Override public void setViewPagerObserver(DataSetObserver observer) { super.setViewPagerObserver(observer); if (observer == null) { wrapperViewPager(mViewPager, this); } } @Override public void setPrimaryItem(ViewGroup container, int position, Object object) { super.setPrimaryItem(container, position, object); if (object instanceof View) { mScrollableView = ((View) object); } else if (object instanceof Fragment) { mScrollableView = ((Fragment) object).getView(); } if (mScrollableView != null) { mScrollableView = findScrollableViewInternal(mScrollableView, true); if (mScrollableView instanceof NestedScrollingParent && !(mScrollableView instanceof NestedScrollingChild)) { mScrollableView = findScrollableViewInternal(mScrollableView, false); } } } } } ================================================ FILE: refresh-layout/src/main/java/com/scwang/smartrefresh/layout/impl/RefreshFooterWrapper.java ================================================ package com.scwang.smartrefresh.layout.impl; import android.support.annotation.NonNull; import android.view.View; import android.view.ViewGroup; import com.scwang.smartrefresh.layout.SmartRefreshLayout; import com.scwang.smartrefresh.layout.api.RefreshFooter; import com.scwang.smartrefresh.layout.api.RefreshKernel; import com.scwang.smartrefresh.layout.api.RefreshLayout; import com.scwang.smartrefresh.layout.constant.RefreshState; import com.scwang.smartrefresh.layout.constant.SpinnerStyle; /** * 刷新底部包装 * Created by SCWANG on 2017/5/26. */ public class RefreshFooterWrapper implements RefreshFooter { private static final String TAG_REFRESH_FOOTER_WRAPPER = "TAG_REFRESH_FOOTER_WRAPPER"; private View mWrapperView; private SpinnerStyle mSpinnerStyle; public RefreshFooterWrapper(View wrapper) { this.mWrapperView = wrapper; this.mWrapperView.setTag(TAG_REFRESH_FOOTER_WRAPPER.hashCode(), TAG_REFRESH_FOOTER_WRAPPER); } public static boolean isTagedFooter(View view) { return TAG_REFRESH_FOOTER_WRAPPER.equals(view.getTag(TAG_REFRESH_FOOTER_WRAPPER.hashCode())); } @NonNull public View getView() { return mWrapperView; } @Override public int onFinish(RefreshLayout layout, boolean success) { return 0; } @Override@Deprecated public void setPrimaryColors(int... colors) { } @Override public SpinnerStyle getSpinnerStyle() { if (mSpinnerStyle != null) { return mSpinnerStyle; } ViewGroup.LayoutParams params = mWrapperView.getLayoutParams(); if (params instanceof SmartRefreshLayout.LayoutParams) { mSpinnerStyle = ((SmartRefreshLayout.LayoutParams) params).spinnerStyle; if (mSpinnerStyle != null) { return mSpinnerStyle; } } if (params != null) { if (params.height == 0) { return mSpinnerStyle = SpinnerStyle.Scale; } } return mSpinnerStyle = SpinnerStyle.Translate; } @Override public void onInitialized(RefreshKernel kernel, int height, int extendHeight) { ViewGroup.LayoutParams params = mWrapperView.getLayoutParams(); if (params instanceof SmartRefreshLayout.LayoutParams) { kernel.requestDrawBackgoundForFooter(((SmartRefreshLayout.LayoutParams) params).backgroundColor); } } @Override public boolean isSupportHorizontalDrag() { return false; } @Override public void onHorizontalDrag(float percentX, int offsetX, int offsetMax) { } @Override public void onPullingUp(float percent, int offset, int footerHeight, int extendHeight) { } @Override public void onPullReleasing(float percent, int offset, int footerHeight, int extendHeight) { } @Override public void onStartAnimator(RefreshLayout layout, int footerHeight, int extendHeight) { } @Override public void onStateChanged(RefreshLayout refreshLayout, RefreshState oldState, RefreshState newState) { } @Override public boolean setLoadmoreFinished(boolean finished) { return false; } } ================================================ FILE: refresh-layout/src/main/java/com/scwang/smartrefresh/layout/impl/RefreshHeaderWrapper.java ================================================ package com.scwang.smartrefresh.layout.impl; import android.support.annotation.NonNull; import android.view.View; import android.view.ViewGroup; import com.scwang.smartrefresh.layout.SmartRefreshLayout; import com.scwang.smartrefresh.layout.api.RefreshHeader; import com.scwang.smartrefresh.layout.api.RefreshKernel; import com.scwang.smartrefresh.layout.api.RefreshLayout; import com.scwang.smartrefresh.layout.constant.RefreshState; import com.scwang.smartrefresh.layout.constant.SpinnerStyle; /** * 刷新头部包装 * Created by SCWANG on 2017/5/26. */ public class RefreshHeaderWrapper implements RefreshHeader { private static final String TAG_REFRESH_HEADER_WRAPPER = "TAG_REFRESH_HEADER_WRAPPER"; private View mWrapperView; private SpinnerStyle mSpinnerStyle; public RefreshHeaderWrapper(View wrapper) { this.mWrapperView = wrapper; this.mWrapperView.setTag(TAG_REFRESH_HEADER_WRAPPER.hashCode(), TAG_REFRESH_HEADER_WRAPPER); } public static boolean isTagedHeader(View view) { return TAG_REFRESH_HEADER_WRAPPER.equals(view.getTag(TAG_REFRESH_HEADER_WRAPPER.hashCode())); } @NonNull public View getView() { return mWrapperView; } @Override public int onFinish(RefreshLayout layout, boolean success) { return 0; } @Override@Deprecated public void setPrimaryColors(int... colors) { } @NonNull @Override public SpinnerStyle getSpinnerStyle() { if (mSpinnerStyle != null) { return mSpinnerStyle; } ViewGroup.LayoutParams params = mWrapperView.getLayoutParams(); if (params instanceof SmartRefreshLayout.LayoutParams) { mSpinnerStyle = ((SmartRefreshLayout.LayoutParams) params).spinnerStyle; if (mSpinnerStyle != null) { return mSpinnerStyle; } } if (params != null) { if (params.height == ViewGroup.LayoutParams.MATCH_PARENT) { return mSpinnerStyle = SpinnerStyle.Scale; } } return mSpinnerStyle = SpinnerStyle.Translate; } @Override public void onInitialized(RefreshKernel kernel, int height, int extendHeight) { ViewGroup.LayoutParams params = mWrapperView.getLayoutParams(); if (params instanceof SmartRefreshLayout.LayoutParams) { kernel.requestDrawBackgoundForHeader(((SmartRefreshLayout.LayoutParams) params).backgroundColor); } } @Override public boolean isSupportHorizontalDrag() { return false; } @Override public void onHorizontalDrag(float percentX, int offsetX, int offsetMax) { } @Override public void onPullingDown(float percent, int offset, int headHeight, int extendHeight) { } @Override public void onReleasing(float percent, int offset, int headHeight, int extendHeight) { } @Override public void onStartAnimator(RefreshLayout layout, int headHeight, int extendHeight) { } @Override public void onStateChanged(RefreshLayout refreshLayout, RefreshState oldState, RefreshState newState) { } } ================================================ FILE: refresh-layout/src/main/java/com/scwang/smartrefresh/layout/impl/ScrollBoundaryDeciderAdapter.java ================================================ package com.scwang.smartrefresh.layout.impl; import android.view.MotionEvent; import android.view.View; import com.scwang.smartrefresh.layout.api.ScrollBoundaryDecider; import com.scwang.smartrefresh.layout.util.ScrollBoundaryUtil; /** * 滚动边界 * Created by SCWANG on 2017/7/8. */ @SuppressWarnings("WeakerAccess") public class ScrollBoundaryDeciderAdapter implements ScrollBoundaryDecider { // protected MotionEvent mActionEvent; protected ScrollBoundaryDecider boundary; protected boolean mEnableLoadmoreWhenContentNotFull; void setScrollBoundaryDecider(ScrollBoundaryDecider boundary){ this.boundary = boundary; } void setActionEvent(MotionEvent event) { mActionEvent = event; } // // @Override public boolean canRefresh(View content) { if (boundary != null) { return boundary.canRefresh(content); } return ScrollBoundaryUtil.canRefresh(content, mActionEvent); } @Override public boolean canLoadmore(View content) { if (boundary != null) { return boundary.canLoadmore(content); } if (mEnableLoadmoreWhenContentNotFull) { return !ScrollBoundaryUtil.canScrollDown(content, mActionEvent); } return ScrollBoundaryUtil.canLoadmore(content, mActionEvent); } public void setEnableLoadmoreWhenContentNotFull(boolean enable) { mEnableLoadmoreWhenContentNotFull = enable; } // } ================================================ FILE: refresh-layout/src/main/java/com/scwang/smartrefresh/layout/internal/ProgressDrawable.java ================================================ package com.scwang.smartrefresh.layout.internal; import android.animation.ValueAnimator; import android.graphics.Canvas; import android.graphics.ColorFilter; import android.graphics.Paint; import android.graphics.Path; import android.graphics.PixelFormat; import android.graphics.Rect; import android.graphics.drawable.Animatable; import android.graphics.drawable.Drawable; import android.support.annotation.NonNull; import android.view.animation.LinearInterpolator; /** * 旋转动画 * Created by SCWANG on 2017/6/16. */ public class ProgressDrawable extends Drawable implements Animatable { private int mProgressDegree = 0; private ValueAnimator mValueAnimator; private Path mPath = new Path(); private Paint mPaint = new Paint(); public ProgressDrawable() { mPaint.setStyle(Paint.Style.FILL); mPaint.setAntiAlias(true); mPaint.setColor(0xffaaaaaa); setupAnimators(); } public void setColor(int color) { mPaint.setColor(color); } // @Override public void draw(@NonNull Canvas canvas) { Rect bounds = getBounds(); int width = bounds.width(); int height = bounds.height(); canvas.save(); canvas.rotate(mProgressDegree, (width) / 2, (height) / 2); final int r = Math.max(1, width / 20); for (int i = 0; i < 12; i++) { mPath.reset(); mPath.addCircle(width - r, height / 2, r, Path.Direction.CW); mPath.addRect(width - 5 * r, height / 2 - r, width - r, height / 2 + r, Path.Direction.CW); mPath.addCircle(width - 5 * r, height / 2, r, Path.Direction.CW); mPaint.setAlpha((i+5) * 0x11); canvas.rotate(30, (width) / 2, (height) / 2); canvas.drawPath(mPath, mPaint); } canvas.restore(); } @Override public void setAlpha(int alpha) { mPaint.setAlpha(alpha); } @Override public void setColorFilter(ColorFilter cf) { mPaint.setColorFilter(cf); } @Override public int getOpacity() { return PixelFormat.TRANSLUCENT; } // private void setupAnimators() { mValueAnimator = ValueAnimator.ofInt(30, 3600); mValueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { int value = (int) animation.getAnimatedValue(); mProgressDegree = 30 * (value / 30); invalidateSelf(); } }); mValueAnimator.setDuration(10000); mValueAnimator.setInterpolator(new LinearInterpolator()); mValueAnimator.setRepeatCount(ValueAnimator.INFINITE); mValueAnimator.setRepeatMode(ValueAnimator.RESTART); } @Override public void start() { if (!mValueAnimator.isRunning()) { mValueAnimator.start(); } } @Override public void stop() { if (mValueAnimator.isRunning()) { mValueAnimator.cancel(); } } @Override public boolean isRunning() { return mValueAnimator.isRunning(); } public int width() { return getBounds().width(); } public int height() { return getBounds().height(); } } ================================================ FILE: refresh-layout/src/main/java/com/scwang/smartrefresh/layout/internal/pathview/PathParser.java ================================================ /* * Copyright (C) 2015 The Android Open Source Project * * 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.scwang.smartrefresh.layout.internal.pathview; import android.graphics.Matrix; import android.graphics.Path; import android.os.Build; import android.util.Log; import java.util.ArrayList; import java.util.List; // This class is a duplicate from the PathParser.java of frameworks/base, with slight // update on incompatible API like copyOfRange(). class PathParser { private static final String LOGTAG = "PathParser"; // Copy from Arrays.copyOfRange() which is only available from API level 9. /** * Copies elements from {@code original} into a new array, from indexes start (inclusive) to * end (exclusive). The original order of elements is preserved. * If {@code end} is greater than {@code original.length}, the result is padded * with the value {@code 0.0f}. * * @param original the original array * @param start the start index, inclusive * @param end the end index, exclusive * @return the new array * @throws ArrayIndexOutOfBoundsException if {@code start < 0 || start > original.length} * @throws IllegalArgumentException if {@code start > end} * @throws NullPointerException if {@code original == null} */ static float[] copyOfRange(float[] original, int start, int end) { if (start > end) { throw new IllegalArgumentException(); } int originalLength = original.length; if (start < 0 || start > originalLength) { throw new ArrayIndexOutOfBoundsException(); } int resultLength = end - start; int copyLength = Math.min(resultLength, originalLength - start); float[] result = new float[resultLength]; System.arraycopy(original, start, result, 0, copyLength); return result; } public static List transformScale(float ratioWidth, float ratioHeight, List orginPaths, List orginSvgs) { Matrix matrix = new Matrix(); matrix.setScale(ratioWidth, ratioHeight); List paths = new ArrayList<>(); if (Build.VERSION.SDK_INT > 16) { for (Path path : orginPaths) { Path npath = new Path(); path.transform(matrix, npath); paths.add(npath); } } else { for (String svgPath : orginSvgs) { Path path = new Path(); PathDataNode[] nodes = createNodesFromPathData(svgPath); transformScaleNodes(ratioWidth, ratioHeight, nodes); PathDataNode.nodesToPath(nodes, path); paths.add(path); } } return paths; } private static void transformScaleNodes(float ratioWidth, float ratioHeight, PathDataNode[] node) { for (int i = 0; i < node.length; i++) { transformScaleCommand(ratioWidth, ratioHeight, node[i].type, node[i].params); } } private static void transformScaleCommand(float ratioWidth, float ratioHeight, char cmd, float[] val) { int incr = 2; switch (cmd) { case 'z': case 'Z': break; case 'm': case 'M': case 'l': case 'L': case 't': case 'T': incr = 2; break; case 'h': case 'H': case 'v': case 'V': incr = 1; break; case 'c': case 'C': incr = 6; break; case 's': case 'S': case 'q': case 'Q': incr = 4; break; case 'a': case 'A': incr = 7; break; } for (int k = 0; k < val.length; k += incr) { switch (cmd) { case 'm': // moveto - Start a new sub-path (relative) case 'M': // moveto - Start a new sub-path case 'l': // lineto - Draw a line from the current point (relative) case 'L': // lineto - Draw a line from the current point case 't': // Draws a quadratic Bézier curve(reflective control point)(relative) case 'T': // Draws a quadratic Bézier curve (reflective control point) val[k] *= ratioWidth; val[k + 1] *= ratioHeight; break; case 'h': // horizontal lineto - Draws a horizontal line (relative) case 'H': // horizontal lineto - Draws a horizontal line val[k] *= ratioWidth; break; case 'v': // vertical lineto - Draws a vertical line from the current point (r) case 'V': // vertical lineto - Draws a vertical line from the current point val[k] *= ratioHeight; break; case 'c': // curveto - Draws a cubic Bézier curve (relative) case 'C': // curveto - Draws a cubic Bézier curve val[k] *= ratioWidth; val[k + 1] *= ratioHeight; val[k + 2] *= ratioWidth; val[k + 3] *= ratioHeight; val[k + 4] *= ratioWidth; val[k + 5] *= ratioHeight; break; case 's': // smooth curveto - Draws a cubic Bézier curve (reflective cp) case 'S': // shorthand/smooth curveto Draws a cubic Bézier curve(reflective cp) case 'q': // Draws a quadratic Bézier (relative) case 'Q': // Draws a quadratic Bézier val[k] *= ratioWidth; val[k + 1] *= ratioHeight; val[k + 2] *= ratioWidth; val[k + 3] *= ratioHeight; case 'a': // Draws an elliptical arc case 'A': // Draws an elliptical arc val[k] *= ratioWidth; val[k + 1] *= ratioHeight; val[k + 5] *= ratioWidth; val[k + 6] *= ratioHeight; break; } } } /** * @param pathData The string representing a path, the same as "d" string in svg file. * @return the generated Path object. */ public static Path createPathFromPathData(String pathData) { Path path = new Path(); PathDataNode[] nodes = createNodesFromPathData(pathData); if (nodes != null) { try { PathDataNode.nodesToPath(nodes, path); } catch (RuntimeException e) { throw new RuntimeException("Error in parsing " + pathData, e); } return path; } return null; } /** * @param pathData The string representing a path, the same as "d" string in svg file. * @return an array of the PathDataNode. */ public static PathDataNode[] createNodesFromPathData(String pathData) { if (pathData == null) { return null; } int start = 0; int end = 1; ArrayList list = new ArrayList(); while (end < pathData.length()) { end = nextStart(pathData, end); String s = pathData.substring(start, end).trim(); if (s.length() > 0) { float[] val = getFloats(s); addNode(list, s.charAt(0), val); } start = end; end++; } if ((end - start) == 1 && start < pathData.length()) { addNode(list, pathData.charAt(start), new float[0]); } return list.toArray(new PathDataNode[list.size()]); } /** * @param source The array of PathDataNode to be duplicated. * @return a deep copy of the source. */ public static PathDataNode[] deepCopyNodes(PathDataNode[] source) { if (source == null) { return null; } PathDataNode[] copy = new PathParser.PathDataNode[source.length]; for (int i = 0; i < source.length; i++) { copy[i] = new PathDataNode(source[i]); } return copy; } /** * @param nodesFrom The source path represented in an array of PathDataNode * @param nodesTo The target path represented in an array of PathDataNode * @return whether the nodesFrom can morph into nodesTo */ public static boolean canMorph(PathDataNode[] nodesFrom, PathDataNode[] nodesTo) { if (nodesFrom == null || nodesTo == null) { return false; } if (nodesFrom.length != nodesTo.length) { return false; } for (int i = 0; i < nodesFrom.length; i++) { if (nodesFrom[i].type != nodesTo[i].type || nodesFrom[i].params.length != nodesTo[i].params.length) { return false; } } return true; } /** * Update the target's data to match the source. * Before calling this, make sure canMorph(target, source) is true. * * @param target The target path represented in an array of PathDataNode * @param source The source path represented in an array of PathDataNode */ public static void updateNodes(PathDataNode[] target, PathDataNode[] source) { for (int i = 0; i < source.length; i++) { target[i].type = source[i].type; for (int j = 0; j < source[i].params.length; j++) { target[i].params[j] = source[i].params[j]; } } } private static int nextStart(String s, int end) { char c; while (end < s.length()) { c = s.charAt(end); // Note that 'e' or 'E' are not valid path commands, but could be // used for floating point numbers' scientific notation. // Therefore, when searching for next command, we should ignore 'e' // and 'E'. if ((((c - 'A') * (c - 'Z') <= 0) || ((c - 'a') * (c - 'z') <= 0)) && c != 'e' && c != 'E') { return end; } end++; } return end; } private static void addNode(ArrayList list, char cmd, float[] val) { list.add(new PathDataNode(cmd, val)); } private static class ExtractFloatResult { // We need to return the position of the next separator and whether the // next float starts with a '-' or a '.'. int mEndPosition; boolean mEndWithNegOrDot; ExtractFloatResult() { } } /** * Parse the floats in the string. * This is an optimized version of parseFloat(s.split(",|\\s")); * * @param s the string containing a command and list of floats * @return array of floats */ private static float[] getFloats(String s) { if (s.charAt(0) == 'z' | s.charAt(0) == 'Z') { return new float[0]; } try { float[] results = new float[s.length()]; int count = 0; int startPosition = 1; int endPosition = 0; ExtractFloatResult result = new ExtractFloatResult(); int totalLength = s.length(); // The startPosition should always be the first character of the // current number, and endPosition is the character after the current // number. while (startPosition < totalLength) { extract(s, startPosition, result); endPosition = result.mEndPosition; if (startPosition < endPosition) { results[count++] = Float.parseFloat( s.substring(startPosition, endPosition)); } if (result.mEndWithNegOrDot) { // Keep the '-' or '.' sign with next number. startPosition = endPosition; } else { startPosition = endPosition + 1; } } return copyOfRange(results, 0, count); } catch (NumberFormatException e) { throw new RuntimeException("error in parsing \"" + s + "\"", e); } } /** * Calculate the position of the next comma or space or negative sign * * @param s the string to search * @param start the position to start searching * @param result the result of the extraction, including the position of the * the starting position of next number, whether it is ending with a '-'. */ private static void extract(String s, int start, ExtractFloatResult result) { // Now looking for ' ', ',', '.' or '-' from the start. int currentIndex = start; boolean foundSeparator = false; result.mEndWithNegOrDot = false; boolean secondDot = false; boolean isExponential = false; for (; currentIndex < s.length(); currentIndex++) { boolean isPrevExponential = isExponential; isExponential = false; char currentChar = s.charAt(currentIndex); switch (currentChar) { case ' ': case ',': foundSeparator = true; break; case '-': // The negative sign following a 'e' or 'E' is not a separator. if (currentIndex != start && !isPrevExponential) { foundSeparator = true; result.mEndWithNegOrDot = true; } break; case '.': if (!secondDot) { secondDot = true; } else { // This is the second dot, and it is considered as a separator. foundSeparator = true; result.mEndWithNegOrDot = true; } break; case 'e': case 'E': isExponential = true; break; } if (foundSeparator) { break; } } // When there is nothing found, then we put the end position to the end // of the string. result.mEndPosition = currentIndex; } /** * Each PathDataNode represents one command in the "d" attribute of the svg * file. * An array of PathDataNode can represent the whole "d" attribute. */ public static class PathDataNode { /*package*/ char type; float[] params; PathDataNode(char type, float[] params) { this.type = type; this.params = params; } PathDataNode(PathDataNode n) { type = n.type; params = copyOfRange(n.params, 0, n.params.length); } /** * Convert an array of PathDataNode to Path. * * @param node The source array of PathDataNode. * @param path The target Path object. */ public static void nodesToPath(PathDataNode[] node, Path path) { float[] current = new float[6]; char previousCommand = 'm'; for (int i = 0; i < node.length; i++) { addCommand(path, current, previousCommand, node[i].type, node[i].params); previousCommand = node[i].type; } } /** * The current PathDataNode will be interpolated between the * nodeFrom and nodeTo according to the * fraction. * * @param nodeFrom The start value as a PathDataNode. * @param nodeTo The end value as a PathDataNode * @param fraction The fraction to interpolate. */ public void interpolatePathDataNode(PathDataNode nodeFrom, PathDataNode nodeTo, float fraction) { for (int i = 0; i < nodeFrom.params.length; i++) { params[i] = nodeFrom.params[i] * (1 - fraction) + nodeTo.params[i] * fraction; } } private static void addCommand(Path path, float[] current, char previousCmd, char cmd, float[] val) { int incr = 2; float currentX = current[0]; float currentY = current[1]; float ctrlPointX = current[2]; float ctrlPointY = current[3]; float currentSegmentStartX = current[4]; float currentSegmentStartY = current[5]; float reflectiveCtrlPointX; float reflectiveCtrlPointY; switch (cmd) { case 'z': case 'Z': path.close(); // Path is closed here, but we need to move the pen to the // closed position. So we cache the segment's starting position, // and restore it here. currentX = currentSegmentStartX; currentY = currentSegmentStartY; ctrlPointX = currentSegmentStartX; ctrlPointY = currentSegmentStartY; path.moveTo(currentX, currentY); break; case 'm': case 'M': case 'l': case 'L': case 't': case 'T': incr = 2; break; case 'h': case 'H': case 'v': case 'V': incr = 1; break; case 'c': case 'C': incr = 6; break; case 's': case 'S': case 'q': case 'Q': incr = 4; break; case 'a': case 'A': incr = 7; break; } for (int k = 0; k < val.length; k += incr) { switch (cmd) { case 'm': // moveto - Start a new sub-path (relative) currentX += val[k + 0]; currentY += val[k + 1]; if (k > 0) { // According to the spec, if a moveto is followed by multiple // pairs of coordinates, the subsequent pairs are treated as // implicit lineto commands. path.rLineTo(val[k + 0], val[k + 1]); } else { path.rMoveTo(val[k + 0], val[k + 1]); currentSegmentStartX = currentX; currentSegmentStartY = currentY; } break; case 'M': // moveto - Start a new sub-path currentX = val[k + 0]; currentY = val[k + 1]; if (k > 0) { // According to the spec, if a moveto is followed by multiple // pairs of coordinates, the subsequent pairs are treated as // implicit lineto commands. path.lineTo(val[k + 0], val[k + 1]); } else { path.moveTo(val[k + 0], val[k + 1]); currentSegmentStartX = currentX; currentSegmentStartY = currentY; } break; case 'l': // lineto - Draw a line from the current point (relative) path.rLineTo(val[k + 0], val[k + 1]); currentX += val[k + 0]; currentY += val[k + 1]; break; case 'L': // lineto - Draw a line from the current point path.lineTo(val[k + 0], val[k + 1]); currentX = val[k + 0]; currentY = val[k + 1]; break; case 'h': // horizontal lineto - Draws a horizontal line (relative) path.rLineTo(val[k + 0], 0); currentX += val[k + 0]; break; case 'H': // horizontal lineto - Draws a horizontal line path.lineTo(val[k + 0], currentY); currentX = val[k + 0]; break; case 'v': // vertical lineto - Draws a vertical line from the current point (r) path.rLineTo(0, val[k + 0]); currentY += val[k + 0]; break; case 'V': // vertical lineto - Draws a vertical line from the current point path.lineTo(currentX, val[k + 0]); currentY = val[k + 0]; break; case 'c': // curveto - Draws a cubic Bézier curve (relative) path.rCubicTo(val[k + 0], val[k + 1], val[k + 2], val[k + 3], val[k + 4], val[k + 5]); ctrlPointX = currentX + val[k + 2]; ctrlPointY = currentY + val[k + 3]; currentX += val[k + 4]; currentY += val[k + 5]; break; case 'C': // curveto - Draws a cubic Bézier curve path.cubicTo(val[k + 0], val[k + 1], val[k + 2], val[k + 3], val[k + 4], val[k + 5]); currentX = val[k + 4]; currentY = val[k + 5]; ctrlPointX = val[k + 2]; ctrlPointY = val[k + 3]; break; case 's': // smooth curveto - Draws a cubic Bézier curve (reflective cp) reflectiveCtrlPointX = 0; reflectiveCtrlPointY = 0; if (previousCmd == 'c' || previousCmd == 's' || previousCmd == 'C' || previousCmd == 'S') { reflectiveCtrlPointX = currentX - ctrlPointX; reflectiveCtrlPointY = currentY - ctrlPointY; } path.rCubicTo(reflectiveCtrlPointX, reflectiveCtrlPointY, val[k + 0], val[k + 1], val[k + 2], val[k + 3]); ctrlPointX = currentX + val[k + 0]; ctrlPointY = currentY + val[k + 1]; currentX += val[k + 2]; currentY += val[k + 3]; break; case 'S': // shorthand/smooth curveto Draws a cubic Bézier curve(reflective cp) reflectiveCtrlPointX = currentX; reflectiveCtrlPointY = currentY; if (previousCmd == 'c' || previousCmd == 's' || previousCmd == 'C' || previousCmd == 'S') { reflectiveCtrlPointX = 2 * currentX - ctrlPointX; reflectiveCtrlPointY = 2 * currentY - ctrlPointY; } path.cubicTo(reflectiveCtrlPointX, reflectiveCtrlPointY, val[k + 0], val[k + 1], val[k + 2], val[k + 3]); ctrlPointX = val[k + 0]; ctrlPointY = val[k + 1]; currentX = val[k + 2]; currentY = val[k + 3]; break; case 'q': // Draws a quadratic Bézier (relative) path.rQuadTo(val[k + 0], val[k + 1], val[k + 2], val[k + 3]); ctrlPointX = currentX + val[k + 0]; ctrlPointY = currentY + val[k + 1]; currentX += val[k + 2]; currentY += val[k + 3]; break; case 'Q': // Draws a quadratic Bézier path.quadTo(val[k + 0], val[k + 1], val[k + 2], val[k + 3]); ctrlPointX = val[k + 0]; ctrlPointY = val[k + 1]; currentX = val[k + 2]; currentY = val[k + 3]; break; case 't': // Draws a quadratic Bézier curve(reflective control point)(relative) reflectiveCtrlPointX = 0; reflectiveCtrlPointY = 0; if (previousCmd == 'q' || previousCmd == 't' || previousCmd == 'Q' || previousCmd == 'T') { reflectiveCtrlPointX = currentX - ctrlPointX; reflectiveCtrlPointY = currentY - ctrlPointY; } path.rQuadTo(reflectiveCtrlPointX, reflectiveCtrlPointY, val[k + 0], val[k + 1]); ctrlPointX = currentX + reflectiveCtrlPointX; ctrlPointY = currentY + reflectiveCtrlPointY; currentX += val[k + 0]; currentY += val[k + 1]; break; case 'T': // Draws a quadratic Bézier curve (reflective control point) reflectiveCtrlPointX = currentX; reflectiveCtrlPointY = currentY; if (previousCmd == 'q' || previousCmd == 't' || previousCmd == 'Q' || previousCmd == 'T') { reflectiveCtrlPointX = 2 * currentX - ctrlPointX; reflectiveCtrlPointY = 2 * currentY - ctrlPointY; } path.quadTo(reflectiveCtrlPointX, reflectiveCtrlPointY, val[k + 0], val[k + 1]); ctrlPointX = reflectiveCtrlPointX; ctrlPointY = reflectiveCtrlPointY; currentX = val[k + 0]; currentY = val[k + 1]; break; case 'a': // Draws an elliptical arc // (rx ry x-axis-rotation large-arc-flag sweep-flag x y) drawArc(path, currentX, currentY, val[k + 5] + currentX, val[k + 6] + currentY, val[k + 0], val[k + 1], val[k + 2], val[k + 3] != 0, val[k + 4] != 0); currentX += val[k + 5]; currentY += val[k + 6]; ctrlPointX = currentX; ctrlPointY = currentY; break; case 'A': // Draws an elliptical arc drawArc(path, currentX, currentY, val[k + 5], val[k + 6], val[k + 0], val[k + 1], val[k + 2], val[k + 3] != 0, val[k + 4] != 0); currentX = val[k + 5]; currentY = val[k + 6]; ctrlPointX = currentX; ctrlPointY = currentY; break; } previousCmd = cmd; } current[0] = currentX; current[1] = currentY; current[2] = ctrlPointX; current[3] = ctrlPointY; current[4] = currentSegmentStartX; current[5] = currentSegmentStartY; } private static void drawArc(Path p, float x0, float y0, float x1, float y1, float a, float b, float theta, boolean isMoreThanHalf, boolean isPositiveArc) { /* Convert rotation angle from degrees to radians */ double thetaD = Math.toRadians(theta); /* Pre-compute rotation matrix entries */ double cosTheta = Math.cos(thetaD); double sinTheta = Math.sin(thetaD); /* Transform (x0, y0) and (x1, y1) into unit space */ /* using (inverse) rotation, followed by (inverse) scale */ double x0p = (x0 * cosTheta + y0 * sinTheta) / a; double y0p = (-x0 * sinTheta + y0 * cosTheta) / b; double x1p = (x1 * cosTheta + y1 * sinTheta) / a; double y1p = (-x1 * sinTheta + y1 * cosTheta) / b; /* Compute differences and averages */ double dx = x0p - x1p; double dy = y0p - y1p; double xm = (x0p + x1p) / 2; double ym = (y0p + y1p) / 2; /* Solve for intersecting unit circles */ double dsq = dx * dx + dy * dy; if (dsq == 0.0) { Log.w(LOGTAG, " Points are coincident"); return; /* Points are coincident */ } double disc = 1.0 / dsq - 1.0 / 4.0; if (disc < 0.0) { Log.w(LOGTAG, "Points are too far apart " + dsq); float adjust = (float) (Math.sqrt(dsq) / 1.99999); drawArc(p, x0, y0, x1, y1, a * adjust, b * adjust, theta, isMoreThanHalf, isPositiveArc); return; /* Points are too far apart */ } double s = Math.sqrt(disc); double sdx = s * dx; double sdy = s * dy; double cx; double cy; if (isMoreThanHalf == isPositiveArc) { cx = xm - sdy; cy = ym + sdx; } else { cx = xm + sdy; cy = ym - sdx; } double eta0 = Math.atan2((y0p - cy), (x0p - cx)); double eta1 = Math.atan2((y1p - cy), (x1p - cx)); double sweep = (eta1 - eta0); if (isPositiveArc != (sweep >= 0)) { if (sweep > 0) { sweep -= 2 * Math.PI; } else { sweep += 2 * Math.PI; } } cx *= a; cy *= b; double tcx = cx; cx = cx * cosTheta - cy * sinTheta; cy = tcx * sinTheta + cy * cosTheta; arcToBezier(p, cx, cy, a, b, x0, y0, thetaD, eta0, sweep); } /** * Converts an arc to cubic Bezier segments and records them in p. * * @param p The target for the cubic Bezier segments * @param cx The x coordinate center of the ellipse * @param cy The y coordinate center of the ellipse * @param a The radius of the ellipse in the horizontal direction * @param b The radius of the ellipse in the vertical direction * @param e1x E(eta1) x coordinate of the starting point of the arc * @param e1y E(eta2) y coordinate of the starting point of the arc * @param theta The angle that the ellipse bounding rectangle makes with horizontal plane * @param start The start angle of the arc on the ellipse * @param sweep The angle (positive or negative) of the sweep of the arc on the ellipse */ private static void arcToBezier(Path p, double cx, double cy, double a, double b, double e1x, double e1y, double theta, double start, double sweep) { // Taken from equations at: http://spaceroots.org/documents/ellipse/node8.html // and http://www.spaceroots.org/documents/ellipse/node22.html // Maximum of 45 degrees per cubic Bezier segment int numSegments = (int) Math.ceil(Math.abs(sweep * 4 / Math.PI)); double eta1 = start; double cosTheta = Math.cos(theta); double sinTheta = Math.sin(theta); double cosEta1 = Math.cos(eta1); double sinEta1 = Math.sin(eta1); double ep1x = (-a * cosTheta * sinEta1) - (b * sinTheta * cosEta1); double ep1y = (-a * sinTheta * sinEta1) + (b * cosTheta * cosEta1); double anglePerSegment = sweep / numSegments; for (int i = 0; i < numSegments; i++) { double eta2 = eta1 + anglePerSegment; double sinEta2 = Math.sin(eta2); double cosEta2 = Math.cos(eta2); double e2x = cx + (a * cosTheta * cosEta2) - (b * sinTheta * sinEta2); double e2y = cy + (a * sinTheta * cosEta2) + (b * cosTheta * sinEta2); double ep2x = -a * cosTheta * sinEta2 - b * sinTheta * cosEta2; double ep2y = -a * sinTheta * sinEta2 + b * cosTheta * cosEta2; double tanDiff2 = Math.tan((eta2 - eta1) / 2); double alpha = Math.sin(eta2 - eta1) * (Math.sqrt(4 + (3 * tanDiff2 * tanDiff2)) - 1) / 3; double q1x = e1x + alpha * ep1x; double q1y = e1y + alpha * ep1y; double q2x = e2x - alpha * ep2x; double q2y = e2y - alpha * ep2y; // Adding this no-op call to workaround a proguard related issue. p.rLineTo(0, 0); p.cubicTo((float) q1x, (float) q1y, (float) q2x, (float) q2y, (float) e2x, (float) e2y); eta1 = eta2; e1x = e2x; e1y = e2y; ep1x = ep2x; ep1y = ep2y; } } } } ================================================ FILE: refresh-layout/src/main/java/com/scwang/smartrefresh/layout/internal/pathview/PathsDrawable.java ================================================ package com.scwang.smartrefresh.layout.internal.pathview; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.ColorFilter; import android.graphics.Paint; import android.graphics.Path; import android.graphics.PixelFormat; import android.graphics.Rect; import android.graphics.Region; import android.graphics.drawable.Drawable; import android.support.annotation.NonNull; import java.util.ArrayList; import java.util.List; /** * 路径 * Created by SCWANG on 2017/6/1. */ public class PathsDrawable extends Drawable { protected Paint mPaint; protected List mPaths; protected List mColors; protected int mWidth = 1,mHeight = 1; protected int mStartX = 0,mStartY = 0; protected int mOrginWidth; protected int mOrginHeight; protected static final Region REGION = new Region(); protected static final Region MAX_CLIP = new Region(Integer.MIN_VALUE, Integer.MIN_VALUE,Integer.MAX_VALUE, Integer.MAX_VALUE); protected List mOrginPaths; protected List mOrginSvgs; public PathsDrawable() { mPaint = new Paint(); mPaint.setColor(0xff11bbff); mPaint.setStyle(Paint.Style.FILL); mPaint.setAntiAlias(true); } protected void onMeasure() { Integer top = null,left = null,right = null,bottom = null; if (mPaths != null) { for (Path path : mPaths) { REGION.setPath(path, MAX_CLIP); Rect bounds = REGION.getBounds(); top = Math.min(top == null ? bounds.top : top, bounds.top); left = Math.min(left == null ? bounds.left : left, bounds.left); right = Math.max(right == null ? bounds.right : right, bounds.right); bottom = Math.max(bottom == null ? bounds.bottom : bottom, bounds.bottom); } } mStartX = left == null ? 0 : left; mStartY = top == null ? 0 : top; mWidth = right == null ? 0 : right - mStartX; mHeight = bottom == null ? 0 : bottom - mStartY; if (mOrginWidth == 0) { mOrginWidth = mWidth; } if (mOrginHeight == 0) { mOrginHeight = mHeight; } Rect bounds = getBounds(); super.setBounds(bounds.left, bounds.top, bounds.left + mWidth, bounds.top + mHeight); } @Override public void setBounds(int left, int top, int right, int bottom) { final int width = right - left; final int height = bottom - top; if (mOrginPaths != null && mOrginPaths.size() > 0 && (width != mWidth || height != mHeight)) { float ratioWidth = 1f * width / mOrginWidth; float ratioHeight = 1f * height / mOrginHeight; mPaths = PathParser.transformScale(ratioWidth, ratioHeight, mOrginPaths, mOrginSvgs); onMeasure(); } else { super.setBounds(left, top, right, bottom); } } public void setBounds(@NonNull Rect bounds) { setBounds(bounds.left, bounds.top, bounds.right, bounds.bottom); } public void parserPaths(String... paths) { mOrginWidth = mOrginHeight = 0; mOrginSvgs = new ArrayList<>(); mPaths = mOrginPaths = new ArrayList<>(); for (String path : paths) { mOrginSvgs.add(path); mOrginPaths.add(PathParser.createPathFromPathData(path)); } onMeasure(); } public void parserColors(int... colors) { mColors = new ArrayList<>(); for (int color : colors) { mColors.add(color); } } // @Override public void draw(@NonNull Canvas canvas) { Rect bounds = getBounds(); int width = bounds.width(); int height = bounds.height(); if (mPaint.getAlpha() == 0xFF) { canvas.save(); canvas.translate(bounds.left-mStartX, bounds.top-mStartY); if (mPaths != null) { for (int i = 0; i < mPaths.size(); i++) { if (mColors != null && i < mColors.size()) { mPaint.setColor(mColors.get(i)); } canvas.drawPath(mPaths.get(i), mPaint); } mPaint.setAlpha(0xFF); } canvas.restore(); } else { createCachedBitmapIfNeeded(width, height); if (!canReuseCache()) { updateCachedBitmap(width, height); updateCacheStates(); } canvas.drawBitmap(mCachedBitmap, bounds.left, bounds.top, mPaint); } } @Override public void setAlpha(int alpha) { mPaint.setAlpha(alpha); } @Override public void setColorFilter(ColorFilter cf) { mPaint.setColorFilter(cf); } @Override public int getOpacity() { return PixelFormat.TRANSLUCENT; } // // public int width() { return getBounds().width(); } public int height() { return getBounds().height(); } public void setGeometricWidth(int width) { Rect bounds = getBounds(); float rate = 1f * width / bounds.width(); setBounds( (int) (bounds.left * rate), (int) (bounds.top * rate), (int) (bounds.right * rate), (int) (bounds.bottom * rate) ); } public void setGeometricHeight(int height) { Rect bounds = getBounds(); float rate = 1f * height / bounds.height(); setBounds( (int) (bounds.left * rate), (int) (bounds.top * rate), (int) (bounds.right * rate), (int) (bounds.bottom * rate) ); } public Paint getPaint() { return mPaint; } // // private Bitmap mCachedBitmap; private boolean mCacheDirty; public void updateCachedBitmap(int width, int height) { mCachedBitmap.eraseColor(Color.TRANSPARENT); Canvas tmpCanvas = new Canvas(mCachedBitmap); drawCachedBitmap(tmpCanvas); } private void drawCachedBitmap(Canvas canvas) { canvas.translate(-mStartX, -mStartY); if (mPaths != null) { for (int i = 0; i < mPaths.size(); i++) { if (mColors != null && i < mColors.size()) { mPaint.setColor(mColors.get(i)); } canvas.drawPath(mPaths.get(i), mPaint); } } } public void createCachedBitmapIfNeeded(int width, int height) { if (mCachedBitmap == null || !canReuseBitmap(width, height)) { mCachedBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); mCacheDirty = true; } } public boolean canReuseBitmap(int width, int height) { if (width == mCachedBitmap.getWidth() && height == mCachedBitmap.getHeight()) { return true; } return false; } public boolean canReuseCache() { if (!mCacheDirty) { return true; } return false; } public void updateCacheStates() { // Use shallow copy here and shallow comparison in canReuseCache(), // likely hit cache miss more, but practically not much difference. mCacheDirty = false; } // } ================================================ FILE: refresh-layout/src/main/java/com/scwang/smartrefresh/layout/internal/pathview/PathsView.java ================================================ package com.scwang.smartrefresh.layout.internal.pathview; import android.content.Context; import android.graphics.Canvas; import android.util.AttributeSet; import android.view.View; /** * 路径视图 * Created by SCWANG on 2017/5/29. */ public class PathsView extends View { protected PathsDrawable mPathsDrawable = new PathsDrawable(); public PathsView(Context context) { super(context); this.initView(context, null, 0); } public PathsView(Context context, AttributeSet attrs) { super(context, attrs); this.initView(context, attrs, 0); } public PathsView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); this.initView(context, attrs, defStyleAttr); } private void initView(Context context, AttributeSet attrs, int defStyleAttr) { mPathsDrawable = new PathsDrawable(); } @Override protected void onFinishInflate() { super.onFinishInflate(); if (getTag() instanceof String) { parserPaths(getTag().toString()); } } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { setMeasuredDimension(resolveSize(mPathsDrawable.width()+getPaddingLeft()+getPaddingRight(), widthMeasureSpec), resolveSize(mPathsDrawable.height()+getPaddingTop()+getPaddingBottom(), heightMeasureSpec)); } @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { super.onLayout(changed, left, top, right, bottom); mPathsDrawable.setBounds(getPaddingLeft(), getPaddingTop(), Math.max((right - left) - getPaddingRight(), getPaddingLeft()), Math.max((bottom - top) - getPaddingTop(), getPaddingTop())); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); mPathsDrawable.draw(canvas); } public void parserPaths(String... paths) { mPathsDrawable.parserPaths(paths); } public void parserColors(int... colors) { mPathsDrawable.parserColors(colors); } } ================================================ FILE: refresh-layout/src/main/java/com/scwang/smartrefresh/layout/listener/AnimationEndListener.java ================================================ package com.scwang.smartrefresh.layout.listener; /** * 动画 * Created by SCWANG on 2017/6/21. */ public interface AnimationEndListener { void onAnimationEnd(); } ================================================ FILE: refresh-layout/src/main/java/com/scwang/smartrefresh/layout/listener/OnLoadmoreListener.java ================================================ package com.scwang.smartrefresh.layout.listener; import com.scwang.smartrefresh.layout.api.RefreshLayout; /** * 加载更多监听器 * Created by SCWANG on 2017/5/26. */ public interface OnLoadmoreListener { void onLoadmore(RefreshLayout refreshlayout); } ================================================ FILE: refresh-layout/src/main/java/com/scwang/smartrefresh/layout/listener/OnMultiPurposeListener.java ================================================ package com.scwang.smartrefresh.layout.listener; import com.scwang.smartrefresh.layout.api.RefreshFooter; import com.scwang.smartrefresh.layout.api.RefreshHeader; /** * 多功能监听器 * Created by SCWANG on 2017/5/26. */ public interface OnMultiPurposeListener extends OnRefreshLoadmoreListener, OnStateChangedListener { void onHeaderPulling(RefreshHeader header, float percent, int offset, int headerHeight, int extendHeight); void onHeaderReleasing(RefreshHeader header, float percent, int offset, int headerHeight, int extendHeight); void onHeaderStartAnimator(RefreshHeader header, int headerHeight, int extendHeight); void onHeaderFinish(RefreshHeader header, boolean success); void onFooterPulling(RefreshFooter footer, float percent, int offset, int footerHeight, int extendHeight); void onFooterReleasing(RefreshFooter footer, float percent, int offset, int footerHeight, int extendHeight); void onFooterStartAnimator(RefreshFooter footer, int footerHeight, int extendHeight); void onFooterFinish(RefreshFooter footer, boolean success); } ================================================ FILE: refresh-layout/src/main/java/com/scwang/smartrefresh/layout/listener/OnRefreshListener.java ================================================ package com.scwang.smartrefresh.layout.listener; import com.scwang.smartrefresh.layout.api.RefreshLayout; /** * 刷新监听器 * Created by SCWANG on 2017/5/26. */ public interface OnRefreshListener { void onRefresh(RefreshLayout refreshlayout); } ================================================ FILE: refresh-layout/src/main/java/com/scwang/smartrefresh/layout/listener/OnRefreshLoadmoreListener.java ================================================ package com.scwang.smartrefresh.layout.listener; /** * 刷新加载组合监听器 * Created by SCWANG on 2017/5/26. */ public interface OnRefreshLoadmoreListener extends OnRefreshListener, OnLoadmoreListener{ } ================================================ FILE: refresh-layout/src/main/java/com/scwang/smartrefresh/layout/listener/OnStateChangedListener.java ================================================ package com.scwang.smartrefresh.layout.listener; import com.scwang.smartrefresh.layout.api.RefreshLayout; import com.scwang.smartrefresh.layout.constant.RefreshState; /** * 刷新状态改变监听器 * Created by SCWANG on 2017/5/26. */ public interface OnStateChangedListener { /** * 状态改变事件 {@link RefreshState} * @param refreshLayout RefreshLayout * @param oldState 改变之前的状态 * @param newState 改变之后的状态 */ void onStateChanged(RefreshLayout refreshLayout, RefreshState oldState, RefreshState newState); } ================================================ FILE: refresh-layout/src/main/java/com/scwang/smartrefresh/layout/listener/SimpleMultiPurposeListener.java ================================================ package com.scwang.smartrefresh.layout.listener; import com.scwang.smartrefresh.layout.api.RefreshFooter; import com.scwang.smartrefresh.layout.api.RefreshHeader; import com.scwang.smartrefresh.layout.api.RefreshLayout; import com.scwang.smartrefresh.layout.constant.RefreshState; /** * 多功能监听器 * Created by SCWANG on 2017/5/26. */ public class SimpleMultiPurposeListener implements OnMultiPurposeListener { @Override public void onHeaderPulling(RefreshHeader header, float percent, int offset, int headerHeight, int extendHeight) { } @Override public void onHeaderReleasing(RefreshHeader header, float percent, int offset, int footerHeight, int extendHeight) { } @Override public void onHeaderStartAnimator(RefreshHeader header, int footerHeight, int extendHeight) { } @Override public void onHeaderFinish(RefreshHeader header, boolean success) { } @Override public void onFooterPulling(RefreshFooter footer, float percent, int offset, int footerHeight, int extendHeight) { } @Override public void onFooterReleasing(RefreshFooter footer, float percent, int offset, int footerHeight, int extendHeight) { } @Override public void onFooterStartAnimator(RefreshFooter footer, int headHeight, int extendHeight) { } @Override public void onFooterFinish(RefreshFooter footer, boolean success) { } @Override public void onRefresh(RefreshLayout refreshlayout) { } @Override public void onLoadmore(RefreshLayout refreshlayout) { } @Override public void onStateChanged(RefreshLayout refreshLayout, RefreshState oldState, RefreshState newState) { } } ================================================ FILE: refresh-layout/src/main/java/com/scwang/smartrefresh/layout/util/ColorUtils.java ================================================ /* * Copyright 2015 The Android Open Source Project * * 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.scwang.smartrefresh.layout.util; import android.graphics.Color; import android.support.annotation.ColorInt; import android.support.annotation.FloatRange; import android.support.annotation.IntRange; import android.support.annotation.NonNull; import android.support.annotation.VisibleForTesting; /** * A set of color-related utility methods, building upon those available in {@code Color}. */ public final class ColorUtils { private static final double XYZ_WHITE_REFERENCE_X = 95.047; private static final double XYZ_WHITE_REFERENCE_Y = 100; private static final double XYZ_WHITE_REFERENCE_Z = 108.883; private static final double XYZ_EPSILON = 0.008856; private static final double XYZ_KAPPA = 903.3; private static final int MIN_ALPHA_SEARCH_MAX_ITERATIONS = 10; private static final int MIN_ALPHA_SEARCH_PRECISION = 1; private static final ThreadLocal TEMP_ARRAY = new ThreadLocal<>(); private ColorUtils() {} /** * Composite two potentially translucent colors over each other and returns the result. */ public static int compositeColors(@ColorInt int foreground, @ColorInt int background) { int bgAlpha = Color.alpha(background); int fgAlpha = Color.alpha(foreground); int a = compositeAlpha(fgAlpha, bgAlpha); int r = compositeComponent(Color.red(foreground), fgAlpha, Color.red(background), bgAlpha, a); int g = compositeComponent(Color.green(foreground), fgAlpha, Color.green(background), bgAlpha, a); int b = compositeComponent(Color.blue(foreground), fgAlpha, Color.blue(background), bgAlpha, a); return Color.argb(a, r, g, b); } private static int compositeAlpha(int foregroundAlpha, int backgroundAlpha) { return 0xFF - (((0xFF - backgroundAlpha) * (0xFF - foregroundAlpha)) / 0xFF); } private static int compositeComponent(int fgC, int fgA, int bgC, int bgA, int a) { if (a == 0) return 0; return ((0xFF * fgC * fgA) + (bgC * bgA * (0xFF - fgA))) / (a * 0xFF); } /** * Returns the luminance of a color as a float between {@code 0.0} and {@code 1.0}. *

Defined as the Y component in the XYZ representation of {@code color}.

*/ @FloatRange(from = 0.0, to = 1.0) public static double calculateLuminance(@ColorInt int color) { final double[] result = getTempDouble3Array(); colorToXYZ(color, result); // Luminance is the Y component return result[1] / 100; } /** * Returns the contrast ratio between {@code foreground} and {@code background}. * {@code background} must be opaque. *

* Formula defined * here. */ public static double calculateContrast(@ColorInt int foreground, @ColorInt int background) { if (Color.alpha(background) != 255) { throw new IllegalArgumentException("background can not be translucent: #" + Integer.toHexString(background)); } if (Color.alpha(foreground) < 255) { // If the foreground is translucent, composite the foreground over the background foreground = compositeColors(foreground, background); } final double luminance1 = calculateLuminance(foreground) + 0.05; final double luminance2 = calculateLuminance(background) + 0.05; // Now return the lighter luminance divided by the darker luminance return Math.max(luminance1, luminance2) / Math.min(luminance1, luminance2); } /** * Calculates the minimum alpha value which can be applied to {@code foreground} so that would * have a contrast value of at least {@code minContrastRatio} when compared to * {@code background}. * * @param foreground the foreground color * @param background the opaque background color * @param minContrastRatio the minimum contrast ratio * @return the alpha value in the range 0-255, or -1 if no value could be calculated */ public static int calculateMinimumAlpha(@ColorInt int foreground, @ColorInt int background, float minContrastRatio) { if (Color.alpha(background) != 255) { throw new IllegalArgumentException("background can not be translucent: #" + Integer.toHexString(background)); } // First lets check that a fully opaque foreground has sufficient contrast int testForeground = setAlphaComponent(foreground, 255); double testRatio = calculateContrast(testForeground, background); if (testRatio < minContrastRatio) { // Fully opaque foreground does not have sufficient contrast, return error return -1; } // Binary search to find a value with the minimum value which provides sufficient contrast int numIterations = 0; int minAlpha = 0; int maxAlpha = 255; while (numIterations <= MIN_ALPHA_SEARCH_MAX_ITERATIONS && (maxAlpha - minAlpha) > MIN_ALPHA_SEARCH_PRECISION) { final int testAlpha = (minAlpha + maxAlpha) / 2; testForeground = setAlphaComponent(foreground, testAlpha); testRatio = calculateContrast(testForeground, background); if (testRatio < minContrastRatio) { minAlpha = testAlpha; } else { maxAlpha = testAlpha; } numIterations++; } // Conservatively return the max of the range of possible alphas, which is known to pass. return maxAlpha; } /** * Convert RGB components to HSL (hue-saturation-lightness). *

    *
  • outHsl[0] is Hue [0 .. 360)
  • *
  • outHsl[1] is Saturation [0...1]
  • *
  • outHsl[2] is Lightness [0...1]
  • *
* * @param r red component value [0..255] * @param g green component value [0..255] * @param b blue component value [0..255] * @param outHsl 3-element array which holds the resulting HSL components */ public static void RGBToHSL(@IntRange(from = 0x0, to = 0xFF) int r, @IntRange(from = 0x0, to = 0xFF) int g, @IntRange(from = 0x0, to = 0xFF) int b, @NonNull float[] outHsl) { final float rf = r / 255f; final float gf = g / 255f; final float bf = b / 255f; final float max = Math.max(rf, Math.max(gf, bf)); final float min = Math.min(rf, Math.min(gf, bf)); final float deltaMaxMin = max - min; float h, s; float l = (max + min) / 2f; if (max == min) { // Monochromatic h = s = 0f; } else { if (max == rf) { h = ((gf - bf) / deltaMaxMin) % 6f; } else if (max == gf) { h = ((bf - rf) / deltaMaxMin) + 2f; } else { h = ((rf - gf) / deltaMaxMin) + 4f; } s = deltaMaxMin / (1f - Math.abs(2f * l - 1f)); } h = (h * 60f) % 360f; if (h < 0) { h += 360f; } outHsl[0] = constrain(h, 0f, 360f); outHsl[1] = constrain(s, 0f, 1f); outHsl[2] = constrain(l, 0f, 1f); } /** * Convert the ARGB color to its HSL (hue-saturation-lightness) components. *
    *
  • outHsl[0] is Hue [0 .. 360)
  • *
  • outHsl[1] is Saturation [0...1]
  • *
  • outHsl[2] is Lightness [0...1]
  • *
* * @param color the ARGB color to convert. The alpha component is ignored * @param outHsl 3-element array which holds the resulting HSL components */ public static void colorToHSL(@ColorInt int color, @NonNull float[] outHsl) { RGBToHSL(Color.red(color), Color.green(color), Color.blue(color), outHsl); } /** * Convert HSL (hue-saturation-lightness) components to a RGB color. *
    *
  • hsl[0] is Hue [0 .. 360)
  • *
  • hsl[1] is Saturation [0...1]
  • *
  • hsl[2] is Lightness [0...1]
  • *
* If hsv values are out of range, they are pinned. * * @param hsl 3-element array which holds the input HSL components * @return the resulting RGB color */ @ColorInt public static int HSLToColor(@NonNull float[] hsl) { final float h = hsl[0]; final float s = hsl[1]; final float l = hsl[2]; final float c = (1f - Math.abs(2 * l - 1f)) * s; final float m = l - 0.5f * c; final float x = c * (1f - Math.abs((h / 60f % 2f) - 1f)); final int hueSegment = (int) h / 60; int r = 0, g = 0, b = 0; switch (hueSegment) { case 0: r = Math.round(255 * (c + m)); g = Math.round(255 * (x + m)); b = Math.round(255 * m); break; case 1: r = Math.round(255 * (x + m)); g = Math.round(255 * (c + m)); b = Math.round(255 * m); break; case 2: r = Math.round(255 * m); g = Math.round(255 * (c + m)); b = Math.round(255 * (x + m)); break; case 3: r = Math.round(255 * m); g = Math.round(255 * (x + m)); b = Math.round(255 * (c + m)); break; case 4: r = Math.round(255 * (x + m)); g = Math.round(255 * m); b = Math.round(255 * (c + m)); break; case 5: case 6: r = Math.round(255 * (c + m)); g = Math.round(255 * m); b = Math.round(255 * (x + m)); break; } r = constrain(r, 0, 255); g = constrain(g, 0, 255); b = constrain(b, 0, 255); return Color.rgb(r, g, b); } /** * Set the alpha component of {@code color} to be {@code alpha}. */ @ColorInt public static int setAlphaComponent(@ColorInt int color, @IntRange(from = 0x0, to = 0xFF) int alpha) { if (alpha < 0 || alpha > 255) { throw new IllegalArgumentException("alpha must be between 0 and 255."); } return (color & 0x00ffffff) | (alpha << 24); } /** * Convert the ARGB color to its CIE Lab representative components. * * @param color the ARGB color to convert. The alpha component is ignored * @param outLab 3-element array which holds the resulting LAB components */ public static void colorToLAB(@ColorInt int color, @NonNull double[] outLab) { RGBToLAB(Color.red(color), Color.green(color), Color.blue(color), outLab); } /** * Convert RGB components to its CIE Lab representative components. * *
    *
  • outLab[0] is L [0 ...1)
  • *
  • outLab[1] is a [-128...127)
  • *
  • outLab[2] is b [-128...127)
  • *
* * @param r red component value [0..255] * @param g green component value [0..255] * @param b blue component value [0..255] * @param outLab 3-element array which holds the resulting LAB components */ public static void RGBToLAB(@IntRange(from = 0x0, to = 0xFF) int r, @IntRange(from = 0x0, to = 0xFF) int g, @IntRange(from = 0x0, to = 0xFF) int b, @NonNull double[] outLab) { // First we convert RGB to XYZ RGBToXYZ(r, g, b, outLab); // outLab now contains XYZ XYZToLAB(outLab[0], outLab[1], outLab[2], outLab); // outLab now contains LAB representation } /** * Convert the ARGB color to it's CIE XYZ representative components. * *

The resulting XYZ representation will use the D65 illuminant and the CIE * 2° Standard Observer (1931).

* *
    *
  • outXyz[0] is X [0 ...95.047)
  • *
  • outXyz[1] is Y [0...100)
  • *
  • outXyz[2] is Z [0...108.883)
  • *
* * @param color the ARGB color to convert. The alpha component is ignored * @param outXyz 3-element array which holds the resulting LAB components */ public static void colorToXYZ(@ColorInt int color, @NonNull double[] outXyz) { RGBToXYZ(Color.red(color), Color.green(color), Color.blue(color), outXyz); } /** * Convert RGB components to it's CIE XYZ representative components. * *

The resulting XYZ representation will use the D65 illuminant and the CIE * 2° Standard Observer (1931).

* *
    *
  • outXyz[0] is X [0 ...95.047)
  • *
  • outXyz[1] is Y [0...100)
  • *
  • outXyz[2] is Z [0...108.883)
  • *
* * @param r red component value [0..255] * @param g green component value [0..255] * @param b blue component value [0..255] * @param outXyz 3-element array which holds the resulting XYZ components */ public static void RGBToXYZ(@IntRange(from = 0x0, to = 0xFF) int r, @IntRange(from = 0x0, to = 0xFF) int g, @IntRange(from = 0x0, to = 0xFF) int b, @NonNull double[] outXyz) { if (outXyz.length != 3) { throw new IllegalArgumentException("outXyz must have a length of 3."); } double sr = r / 255.0; sr = sr < 0.04045 ? sr / 12.92 : Math.pow((sr + 0.055) / 1.055, 2.4); double sg = g / 255.0; sg = sg < 0.04045 ? sg / 12.92 : Math.pow((sg + 0.055) / 1.055, 2.4); double sb = b / 255.0; sb = sb < 0.04045 ? sb / 12.92 : Math.pow((sb + 0.055) / 1.055, 2.4); outXyz[0] = 100 * (sr * 0.4124 + sg * 0.3576 + sb * 0.1805); outXyz[1] = 100 * (sr * 0.2126 + sg * 0.7152 + sb * 0.0722); outXyz[2] = 100 * (sr * 0.0193 + sg * 0.1192 + sb * 0.9505); } /** * Converts a color from CIE XYZ to CIE Lab representation. * *

This method expects the XYZ representation to use the D65 illuminant and the CIE * 2° Standard Observer (1931).

* *
    *
  • outLab[0] is L [0 ...1)
  • *
  • outLab[1] is a [-128...127)
  • *
  • outLab[2] is b [-128...127)
  • *
* * @param x X component value [0...95.047) * @param y Y component value [0...100) * @param z Z component value [0...108.883) * @param outLab 3-element array which holds the resulting Lab components */ public static void XYZToLAB(@FloatRange(from = 0f, to = XYZ_WHITE_REFERENCE_X) double x, @FloatRange(from = 0f, to = XYZ_WHITE_REFERENCE_Y) double y, @FloatRange(from = 0f, to = XYZ_WHITE_REFERENCE_Z) double z, @NonNull double[] outLab) { if (outLab.length != 3) { throw new IllegalArgumentException("outLab must have a length of 3."); } x = pivotXyzComponent(x / XYZ_WHITE_REFERENCE_X); y = pivotXyzComponent(y / XYZ_WHITE_REFERENCE_Y); z = pivotXyzComponent(z / XYZ_WHITE_REFERENCE_Z); outLab[0] = Math.max(0, 116 * y - 16); outLab[1] = 500 * (x - y); outLab[2] = 200 * (y - z); } /** * Converts a color from CIE Lab to CIE XYZ representation. * *

The resulting XYZ representation will use the D65 illuminant and the CIE * 2° Standard Observer (1931).

* *
    *
  • outXyz[0] is X [0 ...95.047)
  • *
  • outXyz[1] is Y [0...100)
  • *
  • outXyz[2] is Z [0...108.883)
  • *
* * @param l L component value [0...100) * @param a A component value [-128...127) * @param b B component value [-128...127) * @param outXyz 3-element array which holds the resulting XYZ components */ public static void LABToXYZ(@FloatRange(from = 0f, to = 100) final double l, @FloatRange(from = -128, to = 127) final double a, @FloatRange(from = -128, to = 127) final double b, @NonNull double[] outXyz) { final double fy = (l + 16) / 116; final double fx = a / 500 + fy; final double fz = fy - b / 200; double tmp = Math.pow(fx, 3); final double xr = tmp > XYZ_EPSILON ? tmp : (116 * fx - 16) / XYZ_KAPPA; final double yr = l > XYZ_KAPPA * XYZ_EPSILON ? Math.pow(fy, 3) : l / XYZ_KAPPA; tmp = Math.pow(fz, 3); final double zr = tmp > XYZ_EPSILON ? tmp : (116 * fz - 16) / XYZ_KAPPA; outXyz[0] = xr * XYZ_WHITE_REFERENCE_X; outXyz[1] = yr * XYZ_WHITE_REFERENCE_Y; outXyz[2] = zr * XYZ_WHITE_REFERENCE_Z; } /** * Converts a color from CIE XYZ to its RGB representation. * *

This method expects the XYZ representation to use the D65 illuminant and the CIE * 2° Standard Observer (1931).

* * @param x X component value [0...95.047) * @param y Y component value [0...100) * @param z Z component value [0...108.883) * @return int containing the RGB representation */ @ColorInt public static int XYZToColor(@FloatRange(from = 0f, to = XYZ_WHITE_REFERENCE_X) double x, @FloatRange(from = 0f, to = XYZ_WHITE_REFERENCE_Y) double y, @FloatRange(from = 0f, to = XYZ_WHITE_REFERENCE_Z) double z) { double r = (x * 3.2406 + y * -1.5372 + z * -0.4986) / 100; double g = (x * -0.9689 + y * 1.8758 + z * 0.0415) / 100; double b = (x * 0.0557 + y * -0.2040 + z * 1.0570) / 100; r = r > 0.0031308 ? 1.055 * Math.pow(r, 1 / 2.4) - 0.055 : 12.92 * r; g = g > 0.0031308 ? 1.055 * Math.pow(g, 1 / 2.4) - 0.055 : 12.92 * g; b = b > 0.0031308 ? 1.055 * Math.pow(b, 1 / 2.4) - 0.055 : 12.92 * b; return Color.rgb( constrain((int) Math.round(r * 255), 0, 255), constrain((int) Math.round(g * 255), 0, 255), constrain((int) Math.round(b * 255), 0, 255)); } /** * Converts a color from CIE Lab to its RGB representation. * * @param l L component value [0...100] * @param a A component value [-128...127] * @param b B component value [-128...127] * @return int containing the RGB representation */ @ColorInt public static int LABToColor(@FloatRange(from = 0f, to = 100) final double l, @FloatRange(from = -128, to = 127) final double a, @FloatRange(from = -128, to = 127) final double b) { final double[] result = getTempDouble3Array(); LABToXYZ(l, a, b, result); return XYZToColor(result[0], result[1], result[2]); } /** * Returns the euclidean distance between two LAB colors. */ public static double distanceEuclidean(@NonNull double[] labX, @NonNull double[] labY) { return Math.sqrt(Math.pow(labX[0] - labY[0], 2) + Math.pow(labX[1] - labY[1], 2) + Math.pow(labX[2] - labY[2], 2)); } private static float constrain(float amount, float low, float high) { return amount < low ? low : (amount > high ? high : amount); } private static int constrain(int amount, int low, int high) { return amount < low ? low : (amount > high ? high : amount); } private static double pivotXyzComponent(double component) { return component > XYZ_EPSILON ? Math.pow(component, 1 / 3.0) : (XYZ_KAPPA * component + 16) / 116; } /** * Blend between two ARGB colors using the given ratio. * *

A blend ratio of 0.0 will result in {@code color1}, 0.5 will give an even blend, * 1.0 will result in {@code color2}.

* * @param color1 the first ARGB color * @param color2 the second ARGB color * @param ratio the blend ratio of {@code color1} to {@code color2} */ @ColorInt public static int blendARGB(@ColorInt int color1, @ColorInt int color2, @FloatRange(from = 0.0, to = 1.0) float ratio) { final float inverseRatio = 1 - ratio; float a = Color.alpha(color1) * inverseRatio + Color.alpha(color2) * ratio; float r = Color.red(color1) * inverseRatio + Color.red(color2) * ratio; float g = Color.green(color1) * inverseRatio + Color.green(color2) * ratio; float b = Color.blue(color1) * inverseRatio + Color.blue(color2) * ratio; return Color.argb((int) a, (int) r, (int) g, (int) b); } /** * Blend between {@code hsl1} and {@code hsl2} using the given ratio. This will interpolate * the hue using the shortest angle. * *

A blend ratio of 0.0 will result in {@code hsl1}, 0.5 will give an even blend, * 1.0 will result in {@code hsl2}.

* * @param hsl1 3-element array which holds the first HSL color * @param hsl2 3-element array which holds the second HSL color * @param ratio the blend ratio of {@code hsl1} to {@code hsl2} * @param outResult 3-element array which holds the resulting HSL components */ public static void blendHSL(@NonNull float[] hsl1, @NonNull float[] hsl2, @FloatRange(from = 0.0, to = 1.0) float ratio, @NonNull float[] outResult) { if (outResult.length != 3) { throw new IllegalArgumentException("result must have a length of 3."); } final float inverseRatio = 1 - ratio; // Since hue is circular we will need to interpolate carefully outResult[0] = circularInterpolate(hsl1[0], hsl2[0], ratio); outResult[1] = hsl1[1] * inverseRatio + hsl2[1] * ratio; outResult[2] = hsl1[2] * inverseRatio + hsl2[2] * ratio; } /** * Blend between two CIE-LAB colors using the given ratio. * *

A blend ratio of 0.0 will result in {@code lab1}, 0.5 will give an even blend, * 1.0 will result in {@code lab2}.

* * @param lab1 3-element array which holds the first LAB color * @param lab2 3-element array which holds the second LAB color * @param ratio the blend ratio of {@code lab1} to {@code lab2} * @param outResult 3-element array which holds the resulting LAB components */ public static void blendLAB(@NonNull double[] lab1, @NonNull double[] lab2, @FloatRange(from = 0.0, to = 1.0) double ratio, @NonNull double[] outResult) { if (outResult.length != 3) { throw new IllegalArgumentException("outResult must have a length of 3."); } final double inverseRatio = 1 - ratio; outResult[0] = lab1[0] * inverseRatio + lab2[0] * ratio; outResult[1] = lab1[1] * inverseRatio + lab2[1] * ratio; outResult[2] = lab1[2] * inverseRatio + lab2[2] * ratio; } @VisibleForTesting static float circularInterpolate(float a, float b, float f) { if (Math.abs(b - a) > 180) { if (b > a) { a += 360; } else { b += 360; } } return (a + ((b - a) * f)) % 360; } private static double[] getTempDouble3Array() { double[] result = TEMP_ARRAY.get(); if (result == null) { result = new double[3]; TEMP_ARRAY.set(result); } return result; } } ================================================ FILE: refresh-layout/src/main/java/com/scwang/smartrefresh/layout/util/DelayedRunable.java ================================================ package com.scwang.smartrefresh.layout.util; public class DelayedRunable implements Runnable { public long delayMillis; public Runnable runnable = null; public DelayedRunable(Runnable runnable) { this.runnable = runnable; } public DelayedRunable(Runnable runnable, long delayMillis) { this.runnable = runnable; this.delayMillis = delayMillis; } @Override public void run() { if (runnable != null) { runnable.run(); runnable = null; } } } ================================================ FILE: refresh-layout/src/main/java/com/scwang/smartrefresh/layout/util/DensityUtil.java ================================================ package com.scwang.smartrefresh.layout.util; import android.content.res.Resources; public class DensityUtil { float density; public DensityUtil() { density = Resources.getSystem().getDisplayMetrics().density; } /** * 根据手机的分辨率从 dp 的单位 转成为 px(像素) */ public static int dp2px(float dpValue) { return (int) (0.5f + dpValue * Resources.getSystem().getDisplayMetrics().density); } /** * 根据手机的分辨率从 px(像素) 的单位 转成为 dp */ public static float px2dp(float pxValue) { return (pxValue / Resources.getSystem().getDisplayMetrics().density); } /** * 根据手机的分辨率从 dp 的单位 转成为 px(像素) */ public int dip2px(float dpValue) { return (int) (0.5f + dpValue * density); } /** * 根据手机的分辨率从 px(像素) 的单位 转成为 dp */ public float px2dip(float pxValue) { return (pxValue / density); } } ================================================ FILE: refresh-layout/src/main/java/com/scwang/smartrefresh/layout/util/ScrollBoundaryUtil.java ================================================ package com.scwang.smartrefresh.layout.util; import android.graphics.PointF; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import android.widget.AbsListView; /** * 滚动边界 * Created by SCWANG on 2017/7/8. */ @SuppressWarnings("WeakerAccess") public class ScrollBoundaryUtil { // public static boolean canRefresh(View targetView, MotionEvent event) { if (canScrollUp(targetView)) { return false; } if (targetView instanceof ViewGroup && event != null) { ViewGroup viewGroup = (ViewGroup) targetView; final int childCount = viewGroup.getChildCount(); PointF point = new PointF(); for (int i = childCount; i > 0; i--) { View child = viewGroup.getChildAt(i - 1); if (isTransformedTouchPointInView(viewGroup,child, event.getX(), event.getY() , point)) { event = MotionEvent.obtain(event); event.offsetLocation(point.x, point.y); return canRefresh(child, event); } } } return true; } public static boolean canLoadmore(View targetView, MotionEvent event) { if (!canScrollDown(targetView) && canScrollUp(targetView)) { return true; } if (targetView instanceof ViewGroup && event != null) { ViewGroup viewGroup = (ViewGroup) targetView; final int childCount = viewGroup.getChildCount(); PointF point = new PointF(); for (int i = 0; i < childCount; i++) { View child = viewGroup.getChildAt(i); if (isTransformedTouchPointInView(viewGroup,child, event.getX(), event.getY() , point)) { event = MotionEvent.obtain(event); event.offsetLocation(point.x, point.y); return canLoadmore(child, event); } } } return false; } public static boolean canScrollDown(View targetView, MotionEvent event) { if (canScrollDown(targetView)) { return true; } if (targetView instanceof ViewGroup && event != null) { ViewGroup viewGroup = (ViewGroup) targetView; final int childCount = viewGroup.getChildCount(); PointF point = new PointF(); for (int i = 0; i < childCount; i++) { View child = viewGroup.getChildAt(i); if (isTransformedTouchPointInView(viewGroup,child, event.getX(), event.getY() , point)) { event = MotionEvent.obtain(event); event.offsetLocation(point.x, point.y); return canScrollDown(child, event); } } } return false; } public static boolean canScrollUp(View targetView) { if (android.os.Build.VERSION.SDK_INT < 14) { if (targetView instanceof AbsListView) { final AbsListView absListView = (AbsListView) targetView; return absListView.getChildCount() > 0 && (absListView.getFirstVisiblePosition() > 0 || absListView.getChildAt(0) .getTop() < absListView.getPaddingTop()); } else { return targetView.getScrollY() > 0; } } else { return targetView.canScrollVertically(-1); } } public static boolean canScrollDown(View targetView) { if (android.os.Build.VERSION.SDK_INT < 14) { if (targetView instanceof AbsListView) { final AbsListView absListView = (AbsListView) targetView; return absListView.getChildCount() > 0 && (absListView.getLastVisiblePosition() < absListView.getChildCount() - 1 || absListView.getChildAt(absListView.getChildCount() - 1).getBottom() > absListView.getPaddingBottom()); } else { return targetView.getScrollY() < 0; } } else { return targetView.canScrollVertically(1); } } // // public static boolean pointInView(View view, float localX, float localY, float slop) { final float left = /*Math.max(view.getPaddingLeft(), 0)*/ - slop; final float top = /*Math.max(view.getPaddingTop(), 0)*/ - slop; final float width = view.getWidth()/* - Math.max(view.getPaddingLeft(), 0) - Math.max(view.getPaddingRight(), 0)*/; final float height = view.getHeight()/* - Math.max(view.getPaddingTop(), 0) - Math.max(view.getPaddingBottom(), 0)*/; return localX >= left && localY >= top && localX < ((width) + slop) && localY < ((height) + slop); } public static boolean isTransformedTouchPointInView(ViewGroup group, View child, float x, float y,PointF outLocalPoint) { final float[] point = new float[2]; point[0] = x; point[1] = y; transformPointToViewLocal(group, child, point); final boolean isInView = pointInView(child, point[0], point[1], 0); if (isInView && outLocalPoint != null) { outLocalPoint.set(point[0]-x, point[1]-y); } return isInView; } public static void transformPointToViewLocal(ViewGroup group, View child, float[] point) { point[0] += group.getScrollX() - child.getLeft(); point[1] += group.getScrollY() - child.getTop(); } // } ================================================ FILE: refresh-layout/src/main/java/com/scwang/smartrefresh/layout/util/ViscousFluidInterpolator.java ================================================ package com.scwang.smartrefresh.layout.util; import android.view.animation.Interpolator; public class ViscousFluidInterpolator implements Interpolator { /** Controls the viscous fluid effect (how much of it). */ private static final float VISCOUS_FLUID_SCALE = 8.0f; private static final float VISCOUS_FLUID_NORMALIZE; private static final float VISCOUS_FLUID_OFFSET; static { // must be set to 1.0 (used in viscousFluid()) VISCOUS_FLUID_NORMALIZE = 1.0f / viscousFluid(1.0f); // account for very small floating-point error VISCOUS_FLUID_OFFSET = 1.0f - VISCOUS_FLUID_NORMALIZE * viscousFluid(1.0f); } private static float viscousFluid(float x) { x *= VISCOUS_FLUID_SCALE; if (x < 1.0f) { x -= (1.0f - (float)Math.exp(-x)); } else { float start = 0.36787944117f; // 1/e == exp(-1) x = 1.0f - (float)Math.exp(1.0f - x); x = start + x * (1.0f - start); } return x; } @Override public float getInterpolation(float input) { final float interpolated = VISCOUS_FLUID_NORMALIZE * viscousFluid(input); if (interpolated > 0) { return interpolated + VISCOUS_FLUID_OFFSET; } return interpolated; } } ================================================ FILE: refresh-layout/src/main/res/values/attrs.xml ================================================ ================================================ FILE: refresh-layout/src/main/res/values/strings.xml ================================================ SmartRefreshLayout ================================================ FILE: refresh-layout/src/test/java/com/scwang/smartrefresh/layout/ExampleUnitTest.java ================================================ package com.scwang.smartrefresh.layout; import org.junit.Test; import static org.junit.Assert.*; /** * Example local unit test, which will execute on the development machine (host). * * @see Testing documentation */ public class ExampleUnitTest { @Test public void addition_isCorrect() throws Exception { assertEquals(4, 2 + 2); } } ================================================ FILE: settings.gradle ================================================ include ':app', ':refresh-layout', ':refresh-header', ':refresh-footer'