├── .github ├── ISSUE_TEMPLATE.md └── PULL_REQUEST_TEMPLATE.md ├── .gitignore ├── LICENSE ├── README.md ├── bintray-android.gradle ├── build.gradle ├── docs └── contributing.md ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── library ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── me │ │ └── zhanghai │ │ └── android │ │ └── fastscroll │ │ ├── AutoMirrorDrawable.java │ │ ├── DefaultAnimationHelper.java │ │ ├── FastScrollNestedScrollView.java │ │ ├── FastScrollScrollView.java │ │ ├── FastScrollWebView.java │ │ ├── FastScroller.java │ │ ├── FastScrollerBuilder.java │ │ ├── FixItemDecorationRecyclerView.java │ │ ├── Md2PopupBackground.java │ │ ├── PopupStyles.java │ │ ├── PopupTextProvider.java │ │ ├── Predicate.java │ │ ├── RecyclerViewHelper.java │ │ ├── SimpleFastScrollView.java │ │ ├── SimpleFastScrollViewMixin.java │ │ ├── SimpleViewHelper.java │ │ └── Utils.java │ └── res │ ├── drawable │ ├── afs_md2_thumb.xml │ ├── afs_md2_track.xml │ ├── afs_popup_background.xml │ ├── afs_thumb.xml │ ├── afs_thumb_stateful.xml │ └── afs_track.xml │ └── values │ └── dimens.xml ├── sample ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── me │ │ └── zhanghai │ │ └── android │ │ └── fastscroll │ │ └── sample │ │ ├── AppBarLayoutLiftOnScrollHack.java │ │ ├── CoordinatorScrollingFrameLayout.java │ │ ├── License.java │ │ ├── LocaleListAdapter.java │ │ ├── MainActivity.java │ │ ├── MainFragment.java │ │ ├── NestedScrollViewFragment.java │ │ ├── RecyclerViewFragment.java │ │ ├── RecyclerViewGridFragment.java │ │ ├── RecyclerViewListClassicFragment.java │ │ ├── RecyclerViewListFragment.java │ │ ├── RecyclerViewListStatefulFragment.java │ │ ├── ScrollViewFragment.java │ │ ├── ScrollingViewOnApplyWindowInsetsListener.java │ │ └── WebViewFragment.java │ ├── launcher_icon-web.png │ └── res │ ├── drawable │ ├── add_icon_white_24dp.xml │ ├── info_icon_control_normal_24dp.xml │ └── menu_icon_control_normal_24dp.xml │ ├── layout │ ├── list_item.xml │ ├── main_fragment.xml │ ├── nested_scroll_view_fragment.xml │ ├── recycler_view_fragment.xml │ ├── scroll_view_fragment.xml │ └── web_view_fragment.xml │ ├── menu │ ├── main.xml │ └── navigation.xml │ ├── mipmap-hdpi │ └── launcher_icon.png │ ├── mipmap-mdpi │ └── launcher_icon.png │ ├── mipmap-xhdpi │ └── launcher_icon.png │ ├── mipmap-xxhdpi │ └── launcher_icon.png │ ├── mipmap-xxxhdpi │ └── launcher_icon.png │ ├── raw │ └── license.txt │ ├── values-night │ └── colors.xml │ ├── values-sw600dp │ └── dimens.xml │ ├── values-v23 │ └── themes.xml │ ├── values-v27 │ └── themes.xml │ └── values │ ├── colors.xml │ ├── dimens.xml │ ├── strings.xml │ └── themes.xml ├── screenshot ├── default.png └── md2.png └── settings.gradle /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ## Expected Behavior 2 | 3 | 4 | ## Actual Behavior 5 | 6 | 7 | ## Steps to Reproduce the Problem 8 | 9 | 1. 10 | 1. 11 | 1. 12 | 13 | ## Specifications 14 | 15 | - Version: 16 | - Platform: -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | Fixes # 2 | 3 | > It's a good idea to open an issue first for discussion. 4 | 5 | - [ ] Tests pass 6 | - [ ] Appropriate changes to README are included in PR -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.gradle/ 2 | /.idea/ 3 | /build/ 4 | /captures/ 5 | /local.properties 6 | .DS_Store 7 | *.iml 8 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # AndroidFastScroll 2 | 3 | Fast scroll for Android `RecyclerView` and more. 4 | 5 | This is not an officially supported Google product. 6 | 7 | ## Why AndroidFastScroll? 8 | 9 | - Fully customizable: Override track, thumb, popup, animation and scrolling. 10 | - Easy-to-use defaults: Predefined default style, Material Design 2 style and animation. 11 | - Extensive view support: Out-of-box support for `RecyclerView`, `ScrollView`, `NestedScrollView` and `WebView`, plus any view with a `ViewHelper` implementation. 12 | - Window insets friendly: Support setting a separate padding for scrollbar. 13 | - Clean implementation: Decoupled touch handling, animation and scrolling logic. 14 | 15 | ## Preview 16 | 17 | Google Play 18 | 19 | [Sample APK](//github.com/zhanghai/AndroidFastScroll/releases/download/v1.0.0/sample-release.apk) 20 | 21 |

22 |

23 | 24 | ## Implementation 25 | 26 | This library is loosely based on the following AOSP implementations: 27 | 28 | - Framework `ListView` [`FastScroller`](https://android.googlesource.com/platform/frameworks/base/+/master/core/java/android/widget/FastScroller.java). 29 | - AndroidX `RecyclerView` [`FastScroller`](https://android.googlesource.com/platform/frameworks/support/+/androidx-master-dev/recyclerview/recyclerview/src/main/java/androidx/recyclerview/widget/FastScroller.java). 30 | - Launcher3 [`RecyclerViewFastScroller`](https://android.googlesource.com/platform/packages/apps/Launcher3/+/refs/heads/master/src/com/android/launcher3/views/RecyclerViewFastScroller.java). 31 | 32 | ## Integration 33 | 34 | Gradle: 35 | 36 | ```gradle 37 | implementation 'me.zhanghai.android.fastscroll:library:1.0.0' 38 | ``` 39 | 40 | ## Usage 41 | 42 | Simply create a `FastScroller` with `FastScrollerBuilder`, and enjoy! 43 | 44 | ```java 45 | new FastScrollerBuilder(recyclerView).build(); 46 | ``` 47 | 48 | You can also implement [`PopupTextProvider`](library/src/main/java/me/zhanghai/android/fastscroll/PopupTextProvider.java) in your `RecyclerView.Adapter` to show a popup. 49 | 50 | For more customization, please use the methods on [`FastScrollerBuilder`](library/src/main/java/me/zhanghai/android/fastscroll/FastScrollerBuilder.java). Namely: 51 | 52 | - `setViewHelper()` allows providing a custom `ViewHelper` to support more views. 53 | - `setPadding()` allows setting a custom padding for the scrollbar, instead of the padding of the view. 54 | - `setTrackDrawable()` and `setThumbDrawable()` allow setting custom drawables for the scrollbar. The `android:state_pressed` state will be updated for them so you can use a selector. 55 | - `setPopupStyle()` allows customizing the popup view with a lambda that will receive the view. 56 | - `setAnimationHelper()` allows providing a custom `AnimationHelper` to use an alternative scrollbar animation. 57 | - `disableScrollbarAutoHide()` allows disabling the auto hide animation for scrollbar. This implies using a `DefaultAnimationHelper`. 58 | - `useDefaultStyle()` and `useMd2Style()` allow using the predefined styles, which sets the drawables and popup style. `useDefaultStyle()`, as its name suggests, is the default style when a `FastScrollerBuilder` is created. 59 | 60 | The default `ViewHelper` implementation for `RecyclerView` supports both `LinearLayoutManager` and `GridLayoutManager`, but assumes that each item has the same height when calculating scroll, as there's no common way to deal with variable item height. If you know how to measure for scrolling in your specific case, you can provide your own `ViewHelper` implementation and fast scroll will work correctly again. 61 | 62 | You can also refer to the [sample app source](sample/src/main/java/me/zhanghai/android/fastscroll/sample) for how things like window insets and lift on scroll are implemented. 63 | 64 | ## License 65 | 66 | Copyright 2019 Google LLC 67 | 68 | Licensed under the Apache License, Version 2.0 (the "License"); 69 | you may not use this file except in compliance with the License. 70 | You may obtain a copy of the License at 71 | 72 | https://www.apache.org/licenses/LICENSE-2.0 73 | 74 | Unless required by applicable law or agreed to in writing, software 75 | distributed under the License is distributed on an "AS IS" BASIS, 76 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 77 | See the License for the specific language governing permissions and 78 | limitations under the License. 79 | -------------------------------------------------------------------------------- /bintray-android.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.github.dcendents.android-maven' 2 | apply plugin: 'com.jfrog.bintray' 3 | 4 | task sourcesJar(type: Jar) { 5 | from android.sourceSets.main.java.srcDirs 6 | classifier 'sources' 7 | } 8 | task javadoc(type: Javadoc) { 9 | source android.sourceSets.main.java.srcDirs 10 | classpath += files(android.getBootClasspath().join(File.pathSeparator)) 11 | } 12 | task javadocJar(type: Jar, dependsOn: javadoc) { 13 | from javadoc.destinationDir 14 | classifier 'javadoc' 15 | javadoc.failOnError = false 16 | } 17 | 18 | artifacts { 19 | archives sourcesJar 20 | archives javadocJar 21 | } 22 | 23 | install { 24 | repositories.mavenInstaller { 25 | pom { 26 | project { 27 | packaging 'aar' 28 | name project.name 29 | description POM_DESCRIPTION 30 | url POM_URL 31 | licenses { 32 | license { 33 | name POM_LICENCE_NAME 34 | url POM_LICENCE_URL 35 | } 36 | } 37 | developers { 38 | developer { 39 | id POM_DEVELOPER_ID 40 | name POM_DEVELOPER_NAME 41 | email POM_DEVELOPER_EMAIL 42 | } 43 | } 44 | scm { 45 | connection POM_SCM_CONNECTION 46 | developerConnection POM_SCM_DEVELOPER_CONNECTION 47 | url POM_SCM_URL 48 | } 49 | } 50 | } 51 | } 52 | } 53 | 54 | def getBintrayKey() { 55 | return hasProperty('BINTRAY_KEY') ? BINTRAY_KEY : System.getenv('BINTRAY_KEY') ?: 56 | System.console() != null ? System.console().readLine("\nBintray key: ") : "" 57 | } 58 | 59 | bintray { 60 | user = BINTRAY_USER 61 | key = getBintrayKey() 62 | configurations = ['archives'] 63 | pkg { 64 | repo = BINTRAY_REPO 65 | name = project.name 66 | desc = POM_DESCRIPTION 67 | websiteUrl = POM_URL 68 | issueTrackerUrl = BINTRAY_ISSUE_TRACKER_URL 69 | vcsUrl = BINTRAY_VCS_URL 70 | githubRepo = BINTRAY_GITHUB_REPO 71 | publicDownloadNumbers = true 72 | licenses = [BINTRAY_LICENSE] 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | repositories { 3 | google() 4 | jcenter() 5 | } 6 | dependencies { 7 | classpath 'com.android.tools.build:gradle:3.5.1' 8 | classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.+' 9 | } 10 | } 11 | 12 | allprojects { 13 | group GROUP 14 | version VERSION 15 | repositories { 16 | google() 17 | jcenter() 18 | } 19 | } 20 | 21 | task clean(type: Delete) { 22 | delete rootProject.buildDir 23 | } 24 | -------------------------------------------------------------------------------- /docs/contributing.md: -------------------------------------------------------------------------------- 1 | # How to Contribute 2 | 3 | We'd love to accept your patches and contributions to this project. There are 4 | just a few small guidelines you need to follow. 5 | 6 | ## Contributor License Agreement 7 | 8 | Contributions to this project must be accompanied by a Contributor License 9 | Agreement. You (or your employer) retain the copyright to your contribution; 10 | this simply gives us permission to use and redistribute your contributions as 11 | part of the project. Head over to to see 12 | your current agreements on file or to sign a new one. 13 | 14 | You generally only need to submit a CLA once, so if you've already submitted one 15 | (even if it was for a different project), you probably don't need to do it 16 | again. 17 | 18 | ## Code reviews 19 | 20 | All submissions, including submissions by project members, require review. We 21 | use GitHub pull requests for this purpose. Consult 22 | [GitHub Help](https://help.github.com/articles/about-pull-requests/) for more 23 | information on using pull requests. 24 | 25 | ## Community Guidelines 26 | 27 | This project follows [Google's Open Source Community 28 | Guidelines](https://opensource.google/conduct/). 29 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | # IDE (e.g. Android Studio) users: 3 | # Gradle settings configured through the IDE *will override* 4 | # any settings specified in this file. 5 | # For more details on how to configure your build environment visit 6 | # http://www.gradle.org/docs/current/userguide/build_environment.html 7 | # Specifies the JVM arguments used for the daemon process. 8 | # The setting is particularly useful for tweaking memory settings. 9 | org.gradle.jvmargs=-Xmx1536m 10 | # When configured, Gradle will run in incubating parallel mode. 11 | # This option should only be used with decoupled projects. More details, visit 12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 13 | # org.gradle.parallel=true 14 | 15 | GROUP=me.zhanghai.android.fastscroll 16 | VERSION=1.0.0 17 | VERSION_CODE=1 18 | 19 | POM_DESCRIPTION=RecyclerView with fast scroll support 20 | POM_URL=https://github.com/zhanghai/AndroidFastScroll 21 | POM_LICENCE_NAME=The Apache Software License, Version 2.0 22 | POM_LICENCE_URL=http://www.apache.org/licenses/LICENSE-2.0.txt 23 | POM_DEVELOPER_ID=zhanghai 24 | POM_DEVELOPER_NAME=Hai Zhang 25 | POM_DEVELOPER_EMAIL=zhanghai@google.com 26 | POM_SCM_CONNECTION=scm:git@github.com:zhanghai/AndroidFastScroll.git 27 | POM_SCM_DEVELOPER_CONNECTION=scm:git@github.com:zhanghai/AndroidFastScroll.git 28 | POM_SCM_URL=https://github.com/zhanghai/AndroidFastScroll 29 | 30 | BINTRAY_REPO=AndroidFastScroll 31 | BINTRAY_ISSUE_TRACKER_URL=https://github.com/zhanghai/AndroidFastScroll/issues 32 | BINTRAY_VCS_URL=https://github.com/zhanghai/AndroidFastScroll 33 | BINTRAY_GITHUB_REPO=zhanghai/AndroidFastScroll 34 | BINTRAY_LICENSE=Apache-2.0 35 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RikkaW/AndroidFastScroll/a48c8b26b57ae1f0f583c0ce0339839d79bb55ee/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Fri Sep 27 19:30:44 PDT 2019 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-all.zip 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS="" 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn () { 37 | echo "$*" 38 | } 39 | 40 | die () { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Escape application args 158 | save () { 159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 160 | echo " " 161 | } 162 | APP_ARGS=$(save "$@") 163 | 164 | # Collect all arguments for the java command, following the shell quoting and substitution rules 165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 166 | 167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 169 | cd "$(dirname "$0")" 170 | fi 171 | 172 | exec "$JAVACMD" "$@" 173 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /library/.gitignore: -------------------------------------------------------------------------------- 1 | /.externalNativeBuild/ 2 | /build/ 3 | /out/ 4 | -------------------------------------------------------------------------------- /library/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | 3 | android { 4 | compileSdkVersion 29 5 | buildToolsVersion '28.0.3' 6 | defaultConfig { 7 | minSdkVersion 21 8 | targetSdkVersion 29 9 | versionCode Integer.parseInt(VERSION_CODE) 10 | versionName VERSION 11 | consumerProguardFiles 'proguard-rules.pro' 12 | } 13 | compileOptions { 14 | sourceCompatibility JavaVersion.VERSION_1_8 15 | targetCompatibility JavaVersion.VERSION_1_8 16 | } 17 | buildTypes { 18 | release { 19 | minifyEnabled false 20 | } 21 | } 22 | } 23 | 24 | dependencies { 25 | implementation 'androidx.appcompat:appcompat-resources:1.1.0' 26 | implementation 'androidx.recyclerview:recyclerview:1.1.0-rc01' 27 | } 28 | 29 | buildscript { 30 | repositories { 31 | jcenter() 32 | } 33 | dependencies { 34 | classpath 'com.github.dcendents:android-maven-gradle-plugin:2.1' 35 | } 36 | } 37 | apply from: '../bintray-android.gradle' 38 | -------------------------------------------------------------------------------- /library/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /library/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /library/src/main/java/me/zhanghai/android/fastscroll/AutoMirrorDrawable.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package me.zhanghai.android.fastscroll; 18 | 19 | import android.annotation.SuppressLint; 20 | import android.graphics.Canvas; 21 | import android.graphics.Rect; 22 | import android.graphics.drawable.Drawable; 23 | import android.view.View; 24 | 25 | import androidx.annotation.NonNull; 26 | import androidx.appcompat.graphics.drawable.DrawableWrapper; 27 | import androidx.core.graphics.drawable.DrawableCompat; 28 | 29 | @SuppressLint("RestrictedApi") 30 | class AutoMirrorDrawable extends DrawableWrapper { 31 | 32 | public AutoMirrorDrawable(@NonNull Drawable drawable) { 33 | super(drawable); 34 | } 35 | 36 | @Override 37 | public void draw(@NonNull Canvas canvas) { 38 | if (needMirroring()) { 39 | float centerX = getBounds().exactCenterX(); 40 | canvas.scale(-1, 1, centerX, 0); 41 | super.draw(canvas); 42 | canvas.scale(-1, 1, centerX, 0); 43 | } else { 44 | super.draw(canvas); 45 | } 46 | } 47 | 48 | @Override 49 | public boolean onLayoutDirectionChanged(int layoutDirection) { 50 | super.onLayoutDirectionChanged(layoutDirection); 51 | return true; 52 | } 53 | 54 | @Override 55 | public boolean isAutoMirrored() { 56 | return true; 57 | } 58 | 59 | private boolean needMirroring() { 60 | return DrawableCompat.getLayoutDirection(this) == View.LAYOUT_DIRECTION_RTL; 61 | } 62 | 63 | @Override 64 | public boolean getPadding(@NonNull Rect padding) { 65 | boolean hasPadding = super.getPadding(padding); 66 | if (needMirroring()) { 67 | int paddingStart = padding.left; 68 | int paddingEnd = padding.right; 69 | padding.left = paddingEnd; 70 | padding.right = paddingStart; 71 | } 72 | return hasPadding; 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /library/src/main/java/me/zhanghai/android/fastscroll/DefaultAnimationHelper.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package me.zhanghai.android.fastscroll; 18 | 19 | import android.view.View; 20 | import android.view.animation.Interpolator; 21 | 22 | import androidx.annotation.NonNull; 23 | import androidx.interpolator.view.animation.FastOutLinearInInterpolator; 24 | import androidx.interpolator.view.animation.LinearOutSlowInInterpolator; 25 | 26 | public class DefaultAnimationHelper implements FastScroller.AnimationHelper { 27 | 28 | private static final int SHOW_DURATION_MILLIS = 150; 29 | private static final int HIDE_DURATION_MILLIS = 200; 30 | private static final Interpolator SHOW_SCROLLBAR_INTERPOLATOR = 31 | new LinearOutSlowInInterpolator(); 32 | private static final Interpolator HIDE_SCROLLBAR_INTERPOLATOR = 33 | new FastOutLinearInInterpolator(); 34 | private static final int AUTO_HIDE_SCROLLBAR_DELAY_MILLIS = 1500; 35 | 36 | @NonNull 37 | private final View mView; 38 | 39 | private boolean mScrollbarAutoHideEnabled = true; 40 | 41 | private boolean mShowingScrollbar = true; 42 | private boolean mShowingPopup; 43 | 44 | public DefaultAnimationHelper(@NonNull View view) { 45 | mView = view; 46 | } 47 | 48 | @Override 49 | public void showScrollbar(@NonNull View trackView, @NonNull View thumbView) { 50 | 51 | if (mShowingScrollbar) { 52 | return; 53 | } 54 | mShowingScrollbar = true; 55 | 56 | trackView.animate() 57 | .alpha(1) 58 | .translationX(0) 59 | .setDuration(SHOW_DURATION_MILLIS) 60 | .setInterpolator(SHOW_SCROLLBAR_INTERPOLATOR) 61 | .start(); 62 | thumbView.animate() 63 | .alpha(1) 64 | .translationX(0) 65 | .setDuration(SHOW_DURATION_MILLIS) 66 | .setInterpolator(SHOW_SCROLLBAR_INTERPOLATOR) 67 | .start(); 68 | } 69 | 70 | @Override 71 | public void hideScrollbar(@NonNull View trackView, @NonNull View thumbView) { 72 | 73 | if (!mShowingScrollbar) { 74 | return; 75 | } 76 | mShowingScrollbar = false; 77 | 78 | boolean isLayoutRtl = mView.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL; 79 | int width = Math.max(trackView.getWidth(), thumbView.getWidth()); 80 | float translationX; 81 | if (isLayoutRtl) { 82 | translationX = trackView.getLeft() == 0 ? -width : 0; 83 | } else { 84 | translationX = trackView.getRight() == mView.getWidth() ? width : 0; 85 | } 86 | trackView.animate() 87 | .alpha(0) 88 | .translationX(translationX) 89 | .setDuration(HIDE_DURATION_MILLIS) 90 | .setInterpolator(HIDE_SCROLLBAR_INTERPOLATOR) 91 | .start(); 92 | thumbView.animate() 93 | .alpha(0) 94 | .translationX(translationX) 95 | .setDuration(HIDE_DURATION_MILLIS) 96 | .setInterpolator(HIDE_SCROLLBAR_INTERPOLATOR) 97 | .start(); 98 | } 99 | 100 | @Override 101 | public boolean isScrollbarAutoHideEnabled() { 102 | return mScrollbarAutoHideEnabled; 103 | } 104 | 105 | public void setScrollbarAutoHideEnabled(boolean enabled) { 106 | mScrollbarAutoHideEnabled = enabled; 107 | } 108 | 109 | @Override 110 | public int getScrollbarAutoHideDelayMillis() { 111 | return AUTO_HIDE_SCROLLBAR_DELAY_MILLIS; 112 | } 113 | 114 | @Override 115 | public void showPopup(@NonNull View popupView) { 116 | 117 | if (mShowingPopup) { 118 | return; 119 | } 120 | mShowingPopup = true; 121 | 122 | popupView.animate() 123 | .alpha(1) 124 | .setDuration(SHOW_DURATION_MILLIS) 125 | .start(); 126 | } 127 | 128 | @Override 129 | public void hidePopup(@NonNull View popupView) { 130 | 131 | if (!mShowingPopup) { 132 | return; 133 | } 134 | mShowingPopup = false; 135 | 136 | popupView.animate() 137 | .alpha(0) 138 | .setDuration(HIDE_DURATION_MILLIS) 139 | .start(); 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /library/src/main/java/me/zhanghai/android/fastscroll/FastScrollNestedScrollView.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package me.zhanghai.android.fastscroll; 18 | 19 | import android.annotation.SuppressLint; 20 | import android.content.Context; 21 | import android.graphics.Canvas; 22 | import android.util.AttributeSet; 23 | import android.view.MotionEvent; 24 | 25 | import androidx.annotation.AttrRes; 26 | import androidx.annotation.NonNull; 27 | import androidx.annotation.Nullable; 28 | import androidx.core.widget.NestedScrollView; 29 | 30 | @SuppressLint("MissingSuperCall") 31 | public class FastScrollNestedScrollView extends NestedScrollView implements SimpleFastScrollView { 32 | 33 | @NonNull 34 | private SimpleFastScrollViewMixin mMixin; 35 | 36 | public FastScrollNestedScrollView(@NonNull Context context) { 37 | super(context); 38 | 39 | init(); 40 | } 41 | 42 | public FastScrollNestedScrollView(@NonNull Context context, @Nullable AttributeSet attrs) { 43 | super(context, attrs); 44 | 45 | init(); 46 | } 47 | 48 | public FastScrollNestedScrollView(@NonNull Context context, @Nullable AttributeSet attrs, 49 | @AttrRes int defStyleAttr) { 50 | super(context, attrs, defStyleAttr); 51 | 52 | init(); 53 | } 54 | 55 | private void init() { 56 | mMixin = new SimpleFastScrollViewMixin(new SimpleFastScrollViewMixin.ViewAccessor() { 57 | @Override 58 | public void superDraw(@NonNull Canvas canvas) { 59 | FastScrollNestedScrollView.super.draw(canvas); 60 | } 61 | @Override 62 | public void superOnScrollChanged(int left, int top, int oldLeft, int oldTop) { 63 | FastScrollNestedScrollView.super.onScrollChanged(left, top, oldLeft, oldTop); 64 | } 65 | @Override 66 | public boolean superOnInterceptTouchEvent(@NonNull MotionEvent event) { 67 | return FastScrollNestedScrollView.super.onInterceptTouchEvent(event); 68 | } 69 | @Override 70 | public boolean superOnTouchEvent(@NonNull MotionEvent event) { 71 | return FastScrollNestedScrollView.super.onTouchEvent(event); 72 | } 73 | @Override 74 | public int computeVerticalScrollRange() { 75 | return FastScrollNestedScrollView.this.computeVerticalScrollRange(); 76 | } 77 | @Override 78 | public int computeVerticalScrollOffset() { 79 | return FastScrollNestedScrollView.this.computeVerticalScrollOffset(); 80 | } 81 | }); 82 | } 83 | 84 | @Override 85 | public void setOnPreDrawListener(@Nullable Runnable listener) { 86 | mMixin.setOnPreDrawListener(listener); 87 | } 88 | 89 | @Override 90 | public void draw(@NonNull Canvas canvas) { 91 | mMixin.draw(canvas); 92 | } 93 | 94 | @Override 95 | public void setOnScrollChangedListener(@Nullable Runnable listener) { 96 | mMixin.setOnScrollChangedListener(listener); 97 | } 98 | 99 | @Override 100 | protected void onScrollChanged(int left, int top, int oldLeft, int oldTop) { 101 | mMixin.onScrollChanged(left, top, oldLeft, oldTop); 102 | } 103 | 104 | @Override 105 | public void setOnTouchEventListener(@Nullable Predicate listener) { 106 | mMixin.setOnTouchEventListener(listener); 107 | } 108 | 109 | @Override 110 | public boolean onInterceptTouchEvent(@NonNull MotionEvent event) { 111 | return mMixin.onInterceptTouchEvent(event); 112 | } 113 | 114 | @Override 115 | public boolean onTouchEvent(@NonNull MotionEvent event) { 116 | return mMixin.onTouchEvent(event); 117 | } 118 | 119 | @Override 120 | public int getScrollRange() { 121 | return mMixin.getScrollRange() + getPaddingTop() + getPaddingBottom(); 122 | } 123 | 124 | @Override 125 | public int getScrollOffset() { 126 | return mMixin.getScrollOffset(); 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /library/src/main/java/me/zhanghai/android/fastscroll/FastScrollScrollView.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package me.zhanghai.android.fastscroll; 18 | 19 | import android.annotation.SuppressLint; 20 | import android.content.Context; 21 | import android.graphics.Canvas; 22 | import android.util.AttributeSet; 23 | import android.view.MotionEvent; 24 | import android.widget.ScrollView; 25 | 26 | import androidx.annotation.AttrRes; 27 | import androidx.annotation.NonNull; 28 | import androidx.annotation.Nullable; 29 | import androidx.annotation.StyleRes; 30 | 31 | @SuppressLint("MissingSuperCall") 32 | public class FastScrollScrollView extends ScrollView implements SimpleFastScrollView { 33 | 34 | @NonNull 35 | private SimpleFastScrollViewMixin mMixin; 36 | 37 | public FastScrollScrollView(@NonNull Context context) { 38 | super(context); 39 | 40 | init(); 41 | } 42 | 43 | public FastScrollScrollView(@NonNull Context context, @Nullable AttributeSet attrs) { 44 | super(context, attrs); 45 | 46 | init(); 47 | } 48 | 49 | public FastScrollScrollView(@NonNull Context context, @Nullable AttributeSet attrs, 50 | @AttrRes int defStyleAttr) { 51 | super(context, attrs, defStyleAttr); 52 | 53 | init(); 54 | } 55 | 56 | public FastScrollScrollView(@NonNull Context context, @Nullable AttributeSet attrs, 57 | @AttrRes int defStyleAttr, @StyleRes int defStyleRes) { 58 | super(context, attrs, defStyleAttr, defStyleRes); 59 | 60 | init(); 61 | } 62 | 63 | private void init() { 64 | setVerticalScrollBarEnabled(false); 65 | mMixin = new SimpleFastScrollViewMixin(new SimpleFastScrollViewMixin.ViewAccessor() { 66 | @Override 67 | public void superDraw(@NonNull Canvas canvas) { 68 | FastScrollScrollView.super.draw(canvas); 69 | } 70 | @Override 71 | public void superOnScrollChanged(int left, int top, int oldLeft, int oldTop) { 72 | FastScrollScrollView.super.onScrollChanged(left, top, oldLeft, oldTop); 73 | } 74 | @Override 75 | public boolean superOnInterceptTouchEvent(@NonNull MotionEvent event) { 76 | return FastScrollScrollView.super.onInterceptTouchEvent(event); 77 | } 78 | @Override 79 | public boolean superOnTouchEvent(@NonNull MotionEvent event) { 80 | return FastScrollScrollView.super.onTouchEvent(event); 81 | } 82 | @Override 83 | public int computeVerticalScrollRange() { 84 | return FastScrollScrollView.this.computeVerticalScrollRange(); 85 | } 86 | @Override 87 | public int computeVerticalScrollOffset() { 88 | return FastScrollScrollView.this.computeVerticalScrollOffset(); 89 | } 90 | }); 91 | } 92 | 93 | @Override 94 | public void setOnPreDrawListener(@Nullable Runnable listener) { 95 | mMixin.setOnPreDrawListener(listener); 96 | } 97 | 98 | @Override 99 | public void draw(@NonNull Canvas canvas) { 100 | mMixin.draw(canvas); 101 | } 102 | 103 | @Override 104 | public void setOnScrollChangedListener(@Nullable Runnable listener) { 105 | mMixin.setOnScrollChangedListener(listener); 106 | } 107 | 108 | @Override 109 | protected void onScrollChanged(int left, int top, int oldLeft, int oldTop) { 110 | mMixin.onScrollChanged(left, top, oldLeft, oldTop); 111 | } 112 | 113 | @Override 114 | public void setOnTouchEventListener(@Nullable Predicate listener) { 115 | mMixin.setOnTouchEventListener(listener); 116 | } 117 | 118 | @Override 119 | public boolean onInterceptTouchEvent(@NonNull MotionEvent event) { 120 | return mMixin.onInterceptTouchEvent(event); 121 | } 122 | 123 | @Override 124 | public boolean onTouchEvent(@NonNull MotionEvent event) { 125 | return mMixin.onTouchEvent(event); 126 | } 127 | 128 | @Override 129 | public int getScrollRange() { 130 | return mMixin.getScrollRange() + getPaddingTop() + getPaddingBottom(); 131 | } 132 | 133 | @Override 134 | public int getScrollOffset() { 135 | return mMixin.getScrollOffset(); 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /library/src/main/java/me/zhanghai/android/fastscroll/FastScrollWebView.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package me.zhanghai.android.fastscroll; 18 | 19 | import android.annotation.SuppressLint; 20 | import android.content.Context; 21 | import android.graphics.Canvas; 22 | import android.util.AttributeSet; 23 | import android.view.MotionEvent; 24 | import android.webkit.WebView; 25 | 26 | import androidx.annotation.AttrRes; 27 | import androidx.annotation.NonNull; 28 | import androidx.annotation.Nullable; 29 | import androidx.annotation.StyleRes; 30 | 31 | @SuppressLint("MissingSuperCall") 32 | public class FastScrollWebView extends WebView implements SimpleFastScrollView { 33 | 34 | @NonNull 35 | private SimpleFastScrollViewMixin mMixin; 36 | 37 | public FastScrollWebView(@NonNull Context context) { 38 | super(context); 39 | 40 | init(); 41 | } 42 | 43 | public FastScrollWebView(@NonNull Context context, @Nullable AttributeSet attrs) { 44 | super(context, attrs); 45 | 46 | init(); 47 | } 48 | 49 | public FastScrollWebView(@NonNull Context context, @Nullable AttributeSet attrs, 50 | @AttrRes int defStyleAttr) { 51 | super(context, attrs, defStyleAttr); 52 | 53 | init(); 54 | } 55 | 56 | public FastScrollWebView(@NonNull Context context, @Nullable AttributeSet attrs, 57 | @AttrRes int defStyleAttr, @StyleRes int defStyleRes) { 58 | super(context, attrs, defStyleAttr, defStyleRes); 59 | 60 | init(); 61 | } 62 | 63 | private void init() { 64 | setVerticalScrollBarEnabled(false); 65 | mMixin = new SimpleFastScrollViewMixin(new SimpleFastScrollViewMixin.ViewAccessor() { 66 | @Override 67 | public void superDraw(@NonNull Canvas canvas) { 68 | FastScrollWebView.super.draw(canvas); 69 | } 70 | @Override 71 | public void superOnScrollChanged(int left, int top, int oldLeft, int oldTop) { 72 | FastScrollWebView.super.onScrollChanged(left, top, oldLeft, oldTop); 73 | } 74 | @Override 75 | public boolean superOnInterceptTouchEvent(@NonNull MotionEvent event) { 76 | return FastScrollWebView.super.onInterceptTouchEvent(event); 77 | } 78 | @Override 79 | public boolean superOnTouchEvent(@NonNull MotionEvent event) { 80 | return FastScrollWebView.super.onTouchEvent(event); 81 | } 82 | @Override 83 | public int computeVerticalScrollRange() { 84 | return FastScrollWebView.this.computeVerticalScrollRange(); 85 | } 86 | @Override 87 | public int computeVerticalScrollOffset() { 88 | return FastScrollWebView.this.computeVerticalScrollOffset(); 89 | } 90 | }); 91 | } 92 | 93 | @Override 94 | public void setOnPreDrawListener(@Nullable Runnable listener) { 95 | mMixin.setOnPreDrawListener(listener); 96 | } 97 | 98 | @Override 99 | public void draw(@NonNull Canvas canvas) { 100 | mMixin.draw(canvas); 101 | } 102 | 103 | @Override 104 | public void setOnScrollChangedListener(@Nullable Runnable listener) { 105 | mMixin.setOnScrollChangedListener(listener); 106 | } 107 | 108 | @Override 109 | protected void onScrollChanged(int left, int top, int oldLeft, int oldTop) { 110 | mMixin.onScrollChanged(left, top, oldLeft, oldTop); 111 | } 112 | 113 | @Override 114 | public void setOnTouchEventListener(@Nullable Predicate listener) { 115 | mMixin.setOnTouchEventListener(listener); 116 | } 117 | 118 | @Override 119 | public boolean onInterceptTouchEvent(@NonNull MotionEvent event) { 120 | return mMixin.onInterceptTouchEvent(event); 121 | } 122 | 123 | @Override 124 | public boolean onTouchEvent(@NonNull MotionEvent event) { 125 | return mMixin.onTouchEvent(event); 126 | } 127 | 128 | @Override 129 | public int getScrollRange() { 130 | return mMixin.getScrollRange(); 131 | } 132 | 133 | @Override 134 | public int getScrollOffset() { 135 | return mMixin.getScrollOffset(); 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /library/src/main/java/me/zhanghai/android/fastscroll/FastScroller.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package me.zhanghai.android.fastscroll; 18 | 19 | import android.content.Context; 20 | import android.graphics.Rect; 21 | import android.graphics.drawable.Drawable; 22 | import android.text.TextUtils; 23 | import android.view.Gravity; 24 | import android.view.MotionEvent; 25 | import android.view.View; 26 | import android.view.ViewConfiguration; 27 | import android.view.ViewGroup; 28 | import android.view.ViewGroupOverlay; 29 | import android.widget.FrameLayout; 30 | import android.widget.TextView; 31 | 32 | import java.util.Objects; 33 | 34 | import androidx.annotation.NonNull; 35 | import androidx.annotation.Nullable; 36 | import androidx.core.math.MathUtils; 37 | import androidx.core.util.Consumer; 38 | 39 | public class FastScroller { 40 | 41 | private final int mScrollbarSlop; 42 | private final int mTouchSlop; 43 | 44 | @NonNull 45 | private final ViewGroup mView; 46 | @NonNull 47 | private final ViewHelper mViewHelper; 48 | @Nullable 49 | private Rect mUserPadding; 50 | @NonNull 51 | private final AnimationHelper mAnimationHelper; 52 | 53 | private final int mTrackWidth; 54 | private final int mThumbWidth; 55 | private final int mThumbHeight; 56 | 57 | @NonNull 58 | private final View mTrackView; 59 | @NonNull 60 | private final View mThumbView; 61 | @NonNull 62 | private final TextView mPopupView; 63 | 64 | private boolean mScrollbarEnabled; 65 | private int mThumbOffset; 66 | 67 | private float mDownX; 68 | private float mDownY; 69 | private float mLastY; 70 | private float mDragStartY; 71 | private int mDragStartThumbOffset; 72 | private boolean mDragging; 73 | 74 | @NonNull 75 | private final Runnable mAutoHideScrollbarRunnable = this::autoHideScrollbar; 76 | 77 | @NonNull 78 | private final Rect mTempRect = new Rect(); 79 | 80 | public FastScroller(@NonNull ViewGroup view, @NonNull ViewHelper viewHelper, 81 | @Nullable Rect padding, @NonNull Drawable trackDrawable, 82 | @NonNull Drawable thumbDrawable, @NonNull Consumer popupStyle, 83 | @NonNull AnimationHelper animationHelper) { 84 | 85 | mScrollbarSlop = view.getResources().getDimensionPixelOffset(R.dimen.afs_scrollbar_slop); 86 | Context context = view.getContext(); 87 | mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop(); 88 | 89 | mView = view; 90 | mViewHelper = viewHelper; 91 | mUserPadding = padding; 92 | mAnimationHelper = animationHelper; 93 | 94 | mTrackWidth = trackDrawable.getIntrinsicWidth(); 95 | mThumbWidth = thumbDrawable.getIntrinsicWidth(); 96 | mThumbHeight = thumbDrawable.getIntrinsicHeight(); 97 | 98 | mTrackView = new View(context); 99 | mTrackView.setBackground(trackDrawable); 100 | mThumbView = new View(context); 101 | mThumbView.setBackground(thumbDrawable); 102 | mPopupView = new TextView(context); 103 | mPopupView.setLayoutParams(new FrameLayout.LayoutParams( 104 | ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT)); 105 | popupStyle.accept(mPopupView); 106 | 107 | ViewGroupOverlay overlay = mView.getOverlay(); 108 | overlay.add(mTrackView); 109 | overlay.add(mThumbView); 110 | overlay.add(mPopupView); 111 | 112 | postAutoHideScrollbar(); 113 | mPopupView.setAlpha(0); 114 | 115 | mViewHelper.addOnPreDrawListener(this::onPreDraw); 116 | mViewHelper.addOnScrollChangedListener(this::onScrollChanged); 117 | mViewHelper.addOnTouchEventListener(this::onTouchEvent); 118 | } 119 | 120 | public void setPadding(int left, int top, int right, int bottom) { 121 | if (mUserPadding != null && mUserPadding.left == left && mUserPadding.top == top 122 | && mUserPadding.right == right && mUserPadding.bottom == bottom) { 123 | return; 124 | } 125 | if (mUserPadding == null) { 126 | mUserPadding = new Rect(); 127 | } 128 | mUserPadding.set(left, top, right, bottom); 129 | mView.invalidate(); 130 | } 131 | 132 | public void setPadding(@Nullable Rect padding) { 133 | if (Objects.equals(mUserPadding, padding)) { 134 | return; 135 | } 136 | if (padding != null) { 137 | if (mUserPadding == null) { 138 | mUserPadding = new Rect(); 139 | } 140 | mUserPadding.set(padding); 141 | } else { 142 | mUserPadding = null; 143 | } 144 | mView.invalidate(); 145 | } 146 | 147 | @NonNull 148 | private Rect getPadding() { 149 | if (mUserPadding != null) { 150 | mTempRect.set(mUserPadding); 151 | } else { 152 | mTempRect.set(mView.getPaddingLeft(), mView.getPaddingTop(), mView.getPaddingRight(), 153 | mView.getPaddingBottom()); 154 | } 155 | return mTempRect; 156 | } 157 | 158 | private void onPreDraw() { 159 | 160 | updateScrollbarState(); 161 | mTrackView.setVisibility(mScrollbarEnabled ? View.VISIBLE : View.INVISIBLE); 162 | mThumbView.setVisibility(mScrollbarEnabled ? View.VISIBLE : View.INVISIBLE); 163 | if (!mScrollbarEnabled) { 164 | mPopupView.setVisibility(View.INVISIBLE); 165 | return; 166 | } 167 | 168 | int layoutDirection = mView.getLayoutDirection(); 169 | mTrackView.setLayoutDirection(layoutDirection); 170 | mThumbView.setLayoutDirection(layoutDirection); 171 | mPopupView.setLayoutDirection(layoutDirection); 172 | 173 | boolean isLayoutRtl = layoutDirection == View.LAYOUT_DIRECTION_RTL; 174 | int viewWidth = mView.getWidth(); 175 | int viewHeight = mView.getHeight(); 176 | 177 | Rect padding = getPadding(); 178 | int trackLeft = isLayoutRtl ? padding.left : viewWidth - padding.right - mTrackWidth; 179 | layoutView(mTrackView, trackLeft, padding.top, trackLeft + mTrackWidth, 180 | viewHeight - padding.bottom); 181 | int thumbLeft = isLayoutRtl ? padding.left : viewWidth - padding.right - mThumbWidth; 182 | int thumbTop = padding.top + mThumbOffset; 183 | layoutView(mThumbView, thumbLeft, thumbTop, thumbLeft + mThumbWidth, 184 | thumbTop + mThumbHeight); 185 | 186 | String popupText = mViewHelper.getPopupText(); 187 | boolean hasPopup = !TextUtils.isEmpty(popupText); 188 | mPopupView.setVisibility(hasPopup ? View.VISIBLE : View.INVISIBLE); 189 | if (hasPopup) { 190 | FrameLayout.LayoutParams popupLayoutParams = (FrameLayout.LayoutParams) 191 | mPopupView.getLayoutParams(); 192 | if (!Objects.equals(mPopupView.getText(), popupText)) { 193 | mPopupView.setText(popupText); 194 | int widthMeasureSpec = ViewGroup.getChildMeasureSpec( 195 | View.MeasureSpec.makeMeasureSpec(viewWidth, View.MeasureSpec.EXACTLY), 196 | padding.left + padding.right + mThumbWidth + popupLayoutParams.leftMargin 197 | + popupLayoutParams.rightMargin, popupLayoutParams.width); 198 | int heightMeasureSpec = ViewGroup.getChildMeasureSpec( 199 | View.MeasureSpec.makeMeasureSpec(viewHeight, View.MeasureSpec.EXACTLY), 200 | padding.top + padding.bottom + popupLayoutParams.topMargin 201 | + popupLayoutParams.bottomMargin, popupLayoutParams.height); 202 | mPopupView.measure(widthMeasureSpec, heightMeasureSpec); 203 | } 204 | int popupWidth = mPopupView.getMeasuredWidth(); 205 | int popupHeight = mPopupView.getMeasuredHeight(); 206 | int popupLeft = isLayoutRtl ? padding.left + mThumbWidth + popupLayoutParams.leftMargin 207 | : viewWidth - padding.right - mThumbWidth - popupLayoutParams.rightMargin 208 | - popupWidth; 209 | int popupAnchorY; 210 | switch (popupLayoutParams.gravity & Gravity.HORIZONTAL_GRAVITY_MASK) { 211 | case Gravity.LEFT: 212 | default: 213 | popupAnchorY = 0; 214 | break; 215 | case Gravity.CENTER_HORIZONTAL: 216 | popupAnchorY = popupHeight / 2; 217 | break; 218 | case Gravity.RIGHT: 219 | popupAnchorY = popupHeight; 220 | break; 221 | } 222 | int thumbAnchorY; 223 | switch (popupLayoutParams.gravity & Gravity.VERTICAL_GRAVITY_MASK) { 224 | case Gravity.TOP: 225 | default: 226 | thumbAnchorY = mThumbView.getPaddingTop(); 227 | break; 228 | case Gravity.CENTER_VERTICAL: { 229 | int thumbPaddingTop = mThumbView.getPaddingTop(); 230 | thumbAnchorY = thumbPaddingTop + (mThumbHeight - thumbPaddingTop 231 | - mThumbView.getPaddingBottom()) / 2; 232 | break; 233 | } 234 | case Gravity.BOTTOM: 235 | thumbAnchorY = mThumbHeight - mThumbView.getPaddingBottom(); 236 | break; 237 | } 238 | int popupTop = MathUtils.clamp(thumbTop + thumbAnchorY - popupAnchorY, 239 | padding.top + popupLayoutParams.topMargin, 240 | viewHeight - padding.bottom - popupLayoutParams.bottomMargin - popupHeight); 241 | layoutView(mPopupView, popupLeft, popupTop, popupLeft + popupWidth, 242 | popupTop + popupHeight); 243 | } 244 | } 245 | 246 | private void updateScrollbarState() { 247 | int scrollOffsetRange = getScrollOffsetRange(); 248 | mScrollbarEnabled = scrollOffsetRange > 0; 249 | mThumbOffset = mScrollbarEnabled ? getThumbOffsetRange() * mViewHelper.getScrollOffset() 250 | / scrollOffsetRange : 0; 251 | } 252 | 253 | private void layoutView(@NonNull View view, int left, int top, int right, int bottom) { 254 | int scrollX = mView.getScrollX(); 255 | int scrollY = mView.getScrollY(); 256 | view.layout(scrollX + left, scrollY + top, scrollX + right, scrollY + bottom); 257 | } 258 | 259 | private void onScrollChanged() { 260 | 261 | updateScrollbarState(); 262 | if (!mScrollbarEnabled) { 263 | return; 264 | } 265 | 266 | mAnimationHelper.showScrollbar(mTrackView, mThumbView); 267 | postAutoHideScrollbar(); 268 | } 269 | 270 | private boolean onTouchEvent(@NonNull MotionEvent event) { 271 | 272 | if (!mScrollbarEnabled) { 273 | return false; 274 | } 275 | 276 | float eventX = event.getX(); 277 | float eventY = event.getY(); 278 | Rect padding = getPadding(); 279 | switch (event.getAction()) { 280 | case MotionEvent.ACTION_DOWN: 281 | 282 | mDownX = eventX; 283 | mDownY = eventY; 284 | 285 | if (mTrackView.getAlpha() > 0 && isInsideView(mTrackView, eventX, eventY)) { 286 | mDragStartY = eventY; 287 | if (isInsideView(mThumbView, eventX, eventY)) { 288 | mDragStartThumbOffset = mThumbOffset; 289 | } else { 290 | mDragStartThumbOffset = (int) (eventY - padding.top - mThumbHeight / 2f); 291 | scrollToThumbOffset(mDragStartThumbOffset); 292 | } 293 | setDragging(true); 294 | } 295 | break; 296 | case MotionEvent.ACTION_MOVE: 297 | 298 | if (!mDragging && isNearView(mTrackView, mDownX, mDownY) 299 | && Math.abs(eventY - mDownY) > mTouchSlop) { 300 | if (isNearView(mThumbView, mDownX, mDownY)) { 301 | mDragStartY = mLastY; 302 | mDragStartThumbOffset = mThumbOffset; 303 | } else { 304 | mDragStartY = eventY; 305 | mDragStartThumbOffset = (int) (eventY - padding.top - mThumbHeight / 2f); 306 | scrollToThumbOffset(mDragStartThumbOffset); 307 | } 308 | setDragging(true); 309 | } 310 | 311 | if (mDragging) { 312 | int thumbOffset = mDragStartThumbOffset + (int) (eventY - mDragStartY); 313 | scrollToThumbOffset(thumbOffset); 314 | } 315 | break; 316 | case MotionEvent.ACTION_UP: 317 | case MotionEvent.ACTION_CANCEL: 318 | 319 | setDragging(false); 320 | break; 321 | } 322 | 323 | mLastY = eventY; 324 | 325 | return mDragging; 326 | } 327 | 328 | private boolean isInsideView(@NonNull View view, float x, float y) { 329 | x += mView.getScrollX(); 330 | y += mView.getScrollY(); 331 | return x >= view.getLeft() && x < view.getRight() && y >= view.getTop() 332 | && y < view.getBottom(); 333 | } 334 | 335 | private boolean isNearView(@NonNull View view, float x, float y) { 336 | x += mView.getScrollX(); 337 | y += mView.getScrollY(); 338 | return x >= view.getLeft() - mScrollbarSlop && x < view.getRight() + mScrollbarSlop 339 | && y >= view.getTop() - mScrollbarSlop && y < view.getBottom() + mScrollbarSlop; 340 | } 341 | 342 | private void scrollToThumbOffset(int thumbOffset) { 343 | int thumbOffsetRange = getThumbOffsetRange(); 344 | thumbOffset = MathUtils.clamp(thumbOffset, 0, thumbOffsetRange); 345 | int scrollOffset = getScrollOffsetRange() * thumbOffset / thumbOffsetRange; 346 | mViewHelper.scrollTo(scrollOffset); 347 | } 348 | 349 | private int getScrollOffsetRange() { 350 | return mViewHelper.getScrollRange() - mView.getHeight(); 351 | } 352 | 353 | private int getThumbOffsetRange() { 354 | Rect padding = getPadding(); 355 | return mView.getHeight() - padding.top - padding.bottom - mThumbHeight; 356 | } 357 | 358 | private void setDragging(boolean dragging) { 359 | 360 | if (mDragging == dragging) { 361 | return; 362 | } 363 | mDragging = dragging; 364 | 365 | if (mDragging) { 366 | mView.getParent().requestDisallowInterceptTouchEvent(true); 367 | } 368 | 369 | mTrackView.setPressed(mDragging); 370 | mThumbView.setPressed(mDragging); 371 | 372 | if (mDragging) { 373 | cancelAutoHideScrollbar(); 374 | mAnimationHelper.showScrollbar(mTrackView, mThumbView); 375 | mAnimationHelper.showPopup(mPopupView); 376 | } else { 377 | postAutoHideScrollbar(); 378 | mAnimationHelper.hidePopup(mPopupView); 379 | } 380 | } 381 | 382 | private void postAutoHideScrollbar() { 383 | cancelAutoHideScrollbar(); 384 | if (mAnimationHelper.isScrollbarAutoHideEnabled()) { 385 | mView.postDelayed(mAutoHideScrollbarRunnable, 386 | mAnimationHelper.getScrollbarAutoHideDelayMillis()); 387 | } 388 | } 389 | 390 | private void autoHideScrollbar() { 391 | if (mDragging) { 392 | return; 393 | } 394 | mAnimationHelper.hideScrollbar(mTrackView, mThumbView); 395 | } 396 | 397 | private void cancelAutoHideScrollbar() { 398 | mView.removeCallbacks(mAutoHideScrollbarRunnable); 399 | } 400 | 401 | public interface ViewHelper { 402 | 403 | void addOnPreDrawListener(@NonNull Runnable onPreDraw); 404 | 405 | void addOnScrollChangedListener(@NonNull Runnable onScrollChanged); 406 | 407 | void addOnTouchEventListener(@NonNull Predicate onTouchEvent); 408 | 409 | int getScrollRange(); 410 | 411 | int getScrollOffset(); 412 | 413 | void scrollTo(int offset); 414 | 415 | @Nullable 416 | default String getPopupText() { 417 | return null; 418 | } 419 | } 420 | 421 | public interface AnimationHelper { 422 | 423 | void showScrollbar(@NonNull View trackView, @NonNull View thumbView); 424 | 425 | void hideScrollbar(@NonNull View trackView, @NonNull View thumbView); 426 | 427 | boolean isScrollbarAutoHideEnabled(); 428 | 429 | int getScrollbarAutoHideDelayMillis(); 430 | 431 | void showPopup(@NonNull View popupView); 432 | 433 | void hidePopup(@NonNull View popupView); 434 | } 435 | } 436 | -------------------------------------------------------------------------------- /library/src/main/java/me/zhanghai/android/fastscroll/FastScrollerBuilder.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package me.zhanghai.android.fastscroll; 18 | 19 | import android.content.Context; 20 | import android.graphics.Rect; 21 | import android.graphics.drawable.Drawable; 22 | import android.view.ViewGroup; 23 | import android.webkit.WebView; 24 | import android.widget.ScrollView; 25 | import android.widget.TextView; 26 | 27 | import androidx.annotation.NonNull; 28 | import androidx.annotation.Nullable; 29 | import androidx.appcompat.content.res.AppCompatResources; 30 | import androidx.core.util.Consumer; 31 | import androidx.core.widget.NestedScrollView; 32 | import androidx.recyclerview.widget.RecyclerView; 33 | 34 | public class FastScrollerBuilder { 35 | 36 | @NonNull 37 | private final ViewGroup mView; 38 | 39 | @Nullable 40 | private FastScroller.ViewHelper mViewHelper; 41 | 42 | @Nullable 43 | private Rect mPadding; 44 | 45 | @NonNull 46 | private Drawable mTrackDrawable; 47 | 48 | @NonNull 49 | private Drawable mThumbDrawable; 50 | 51 | @NonNull 52 | private Consumer mPopupStyle; 53 | 54 | @Nullable 55 | private FastScroller.AnimationHelper mAnimationHelper; 56 | 57 | public FastScrollerBuilder(@NonNull ViewGroup view) { 58 | mView = view; 59 | useDefaultStyle(); 60 | } 61 | 62 | public void setViewHelper(@Nullable FastScroller.ViewHelper viewHelper) { 63 | mViewHelper = viewHelper; 64 | } 65 | 66 | @NonNull 67 | public FastScrollerBuilder setPadding(int left, int top, int right, int bottom) { 68 | if (mPadding == null) { 69 | mPadding = new Rect(); 70 | } 71 | mPadding.set(left, top, right, bottom); 72 | return this; 73 | } 74 | 75 | @NonNull 76 | public FastScrollerBuilder setPadding(@Nullable Rect padding) { 77 | if (padding != null) { 78 | if (mPadding == null) { 79 | mPadding = new Rect(); 80 | } 81 | mPadding.set(padding); 82 | } else { 83 | mPadding = null; 84 | } 85 | return this; 86 | } 87 | 88 | @NonNull 89 | public FastScrollerBuilder setTrackDrawable(@NonNull Drawable trackDrawable) { 90 | mTrackDrawable = trackDrawable; 91 | return this; 92 | } 93 | 94 | @NonNull 95 | public FastScrollerBuilder setThumbDrawable(@NonNull Drawable thumbDrawable) { 96 | mThumbDrawable = thumbDrawable; 97 | return this; 98 | } 99 | 100 | @NonNull 101 | public FastScrollerBuilder setPopupStyle(@NonNull Consumer popupStyle) { 102 | mPopupStyle = popupStyle; 103 | return this; 104 | } 105 | 106 | @NonNull 107 | public FastScrollerBuilder useDefaultStyle() { 108 | Context context = mView.getContext(); 109 | mTrackDrawable = AppCompatResources.getDrawable(context, R.drawable.afs_track); 110 | mThumbDrawable = AppCompatResources.getDrawable(context, R.drawable.afs_thumb); 111 | mPopupStyle = PopupStyles.DEFAULT; 112 | return this; 113 | } 114 | 115 | @NonNull 116 | public FastScrollerBuilder useMd2Style() { 117 | Context context = mView.getContext(); 118 | mTrackDrawable = AppCompatResources.getDrawable(context, R.drawable.afs_md2_track); 119 | mThumbDrawable = AppCompatResources.getDrawable(context, R.drawable.afs_md2_thumb); 120 | mPopupStyle = PopupStyles.MD2; 121 | return this; 122 | } 123 | 124 | public void setAnimationHelper(@Nullable FastScroller.AnimationHelper animationHelper) { 125 | mAnimationHelper = animationHelper; 126 | } 127 | 128 | public void disableScrollbarAutoHide() { 129 | DefaultAnimationHelper animationHelper = new DefaultAnimationHelper(mView); 130 | animationHelper.setScrollbarAutoHideEnabled(false); 131 | mAnimationHelper = animationHelper; 132 | } 133 | 134 | @NonNull 135 | public FastScroller build() { 136 | return new FastScroller(mView, getOrCreateViewHelper(), mPadding, mTrackDrawable, 137 | mThumbDrawable, mPopupStyle, getOrCreateAnimationHelper()); 138 | } 139 | 140 | @NonNull 141 | private FastScroller.ViewHelper getOrCreateViewHelper() { 142 | if (mViewHelper != null) { 143 | return mViewHelper; 144 | } 145 | if (mView instanceof RecyclerView) { 146 | return new RecyclerViewHelper((RecyclerView) mView); 147 | } else if (mView instanceof SimpleFastScrollView) { 148 | return new SimpleViewHelper<>((ViewGroup & SimpleFastScrollView) mView); 149 | } 150 | if (mView instanceof NestedScrollView) { 151 | throw new UnsupportedOperationException("Please use " 152 | + FastScrollNestedScrollView.class.getSimpleName() + " instead of " 153 | + NestedScrollView.class.getSimpleName() + "for fast scroll"); 154 | } else if (mView instanceof ScrollView) { 155 | throw new UnsupportedOperationException("Please use " 156 | + FastScrollScrollView.class.getSimpleName() + " instead of " 157 | + ScrollView.class.getSimpleName() + "for fast scroll"); 158 | } else if (mView instanceof WebView) { 159 | throw new UnsupportedOperationException("Please use " 160 | + FastScrollWebView.class.getSimpleName() + " instead of " 161 | + WebView.class.getSimpleName() + "for fast scroll"); 162 | } else { 163 | throw new UnsupportedOperationException(mView.getClass().getSimpleName() 164 | + " is not supported for fast scroll"); 165 | } 166 | } 167 | 168 | @NonNull 169 | private FastScroller.AnimationHelper getOrCreateAnimationHelper() { 170 | if (mAnimationHelper != null) { 171 | return mAnimationHelper; 172 | } 173 | return new DefaultAnimationHelper(mView); 174 | } 175 | } 176 | -------------------------------------------------------------------------------- /library/src/main/java/me/zhanghai/android/fastscroll/FixItemDecorationRecyclerView.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package me.zhanghai.android.fastscroll; 18 | 19 | import android.content.Context; 20 | import android.graphics.Canvas; 21 | import android.graphics.Rect; 22 | import android.util.AttributeSet; 23 | import android.view.View; 24 | 25 | import androidx.annotation.AttrRes; 26 | import androidx.annotation.NonNull; 27 | import androidx.annotation.Nullable; 28 | import androidx.recyclerview.widget.RecyclerView; 29 | 30 | public class FixItemDecorationRecyclerView extends RecyclerView { 31 | 32 | public FixItemDecorationRecyclerView(@NonNull Context context) { 33 | super(context); 34 | } 35 | 36 | public FixItemDecorationRecyclerView(@NonNull Context context, @Nullable AttributeSet attrs) { 37 | super(context, attrs); 38 | } 39 | 40 | public FixItemDecorationRecyclerView(@NonNull Context context, @Nullable AttributeSet attrs, 41 | @AttrRes int defStyleAttr) { 42 | super(context, attrs, defStyleAttr); 43 | } 44 | 45 | @Override 46 | protected void dispatchDraw(@NonNull Canvas canvas) { 47 | for (int i = 0, count = getItemDecorationCount(); i < count; ++i) { 48 | FixItemDecoration decor = (FixItemDecoration) getItemDecorationAt(i); 49 | decor.getItemDecoration().onDraw(canvas, this, decor.getState()); 50 | } 51 | super.dispatchDraw(canvas); 52 | for (int i = 0, count = getItemDecorationCount(); i < count; ++i) { 53 | FixItemDecoration decor = (FixItemDecoration) getItemDecorationAt(i); 54 | decor.getItemDecoration().onDrawOver(canvas, this, decor.getState()); 55 | } 56 | } 57 | 58 | @Override 59 | public void addItemDecoration(@NonNull ItemDecoration decor, int index) { 60 | super.addItemDecoration(new FixItemDecoration(decor), index); 61 | } 62 | 63 | @NonNull 64 | @Override 65 | public ItemDecoration getItemDecorationAt(int index) { 66 | return ((FixItemDecoration) super.getItemDecorationAt(index)).getItemDecoration(); 67 | } 68 | 69 | @Override 70 | public void removeItemDecoration(@NonNull ItemDecoration decor) { 71 | if (!(decor instanceof FixItemDecoration)) { 72 | for (int i = 0, count = getItemDecorationCount(); i < count; ++i) { 73 | FixItemDecoration fixDecor = (FixItemDecoration) super.getItemDecorationAt(i); 74 | if (fixDecor.getItemDecoration() == decor) { 75 | decor = fixDecor; 76 | break; 77 | } 78 | } 79 | } 80 | super.removeItemDecoration(decor); 81 | } 82 | 83 | private static class FixItemDecoration extends ItemDecoration { 84 | 85 | @NonNull 86 | private final ItemDecoration mItemDecoration; 87 | 88 | private State mState; 89 | 90 | private FixItemDecoration(@NonNull ItemDecoration itemDecoration) { 91 | mItemDecoration = itemDecoration; 92 | } 93 | 94 | @NonNull 95 | public ItemDecoration getItemDecoration() { 96 | return mItemDecoration; 97 | } 98 | 99 | public State getState() { 100 | return mState; 101 | } 102 | 103 | @Override 104 | public void onDraw(@NonNull Canvas c, @NonNull RecyclerView parent, @NonNull State state) { 105 | mState = state; 106 | } 107 | 108 | @Override 109 | @SuppressWarnings("deprecation") 110 | public void onDraw(@NonNull Canvas c, @NonNull RecyclerView parent) {} 111 | 112 | @Override 113 | public void onDrawOver(@NonNull Canvas c, @NonNull RecyclerView parent, 114 | @NonNull State state) {} 115 | 116 | @Override 117 | @SuppressWarnings("deprecation") 118 | public void onDrawOver(@NonNull Canvas c, @NonNull RecyclerView parent) {} 119 | 120 | @Override 121 | @SuppressWarnings("deprecation") 122 | public void getItemOffsets(@NonNull Rect outRect, int itemPosition, 123 | @NonNull RecyclerView parent) { 124 | mItemDecoration.getItemOffsets(outRect, itemPosition, parent); 125 | } 126 | 127 | @Override 128 | public void getItemOffsets(@NonNull Rect outRect, @NonNull View view, 129 | @NonNull RecyclerView parent, @NonNull State state) { 130 | mItemDecoration.getItemOffsets(outRect, view, parent, state); 131 | } 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /library/src/main/java/me/zhanghai/android/fastscroll/Md2PopupBackground.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package me.zhanghai.android.fastscroll; 18 | 19 | import android.content.Context; 20 | import android.content.res.Resources; 21 | import android.graphics.Canvas; 22 | import android.graphics.ColorFilter; 23 | import android.graphics.Matrix; 24 | import android.graphics.Outline; 25 | import android.graphics.Paint; 26 | import android.graphics.Path; 27 | import android.graphics.PixelFormat; 28 | import android.graphics.Rect; 29 | import android.graphics.drawable.Drawable; 30 | import android.view.View; 31 | 32 | import androidx.annotation.NonNull; 33 | import androidx.annotation.Nullable; 34 | import androidx.core.graphics.drawable.DrawableCompat; 35 | 36 | class Md2PopupBackground extends Drawable { 37 | 38 | @NonNull 39 | private final Paint mPaint; 40 | private final int mPaddingStart; 41 | private final int mPaddingEnd; 42 | 43 | @NonNull 44 | private final Path mPath = new Path(); 45 | 46 | @NonNull 47 | private final Matrix mTempMatrix = new Matrix(); 48 | 49 | public Md2PopupBackground(@NonNull Context context) { 50 | mPaint = new Paint(); 51 | mPaint.setAntiAlias(true); 52 | mPaint.setColor(Utils.getColorFromAttrRes(android.R.attr.colorControlActivated, context)); 53 | mPaint.setStyle(Paint.Style.FILL); 54 | Resources resources = context.getResources(); 55 | mPaddingStart = resources.getDimensionPixelOffset(R.dimen.afs_md2_popup_padding_start); 56 | mPaddingEnd = resources.getDimensionPixelOffset(R.dimen.afs_md2_popup_padding_end); 57 | } 58 | 59 | @Override 60 | public void draw(@NonNull Canvas canvas) { 61 | canvas.drawPath(mPath, mPaint); 62 | } 63 | 64 | @Override 65 | public boolean onLayoutDirectionChanged(int layoutDirection) { 66 | updatePath(); 67 | return true; 68 | } 69 | 70 | @Override 71 | public void setAlpha(int alpha) {} 72 | 73 | @Override 74 | public void setColorFilter(@Nullable ColorFilter colorFilter) {} 75 | 76 | @Override 77 | public boolean isAutoMirrored() { 78 | return true; 79 | } 80 | 81 | private boolean needMirroring() { 82 | return DrawableCompat.getLayoutDirection(this) == View.LAYOUT_DIRECTION_RTL; 83 | } 84 | 85 | @Override 86 | public int getOpacity() { 87 | return PixelFormat.TRANSLUCENT; 88 | } 89 | 90 | @Override 91 | protected void onBoundsChange(@NonNull Rect bounds) { 92 | updatePath(); 93 | } 94 | 95 | private void updatePath() { 96 | 97 | mPath.reset(); 98 | 99 | Rect bounds = getBounds(); 100 | float width = bounds.width(); 101 | float height = bounds.height(); 102 | float r = height / 2; 103 | float sqrt2 = (float) Math.sqrt(2); 104 | // Ensure we are convex. 105 | width = Math.max(r + sqrt2 * r, width); 106 | pathArcTo(mPath, r, r, r, 90, 180); 107 | float o1X = width - sqrt2 * r; 108 | pathArcTo(mPath, o1X, r, r, -90, 45f); 109 | float r2 = r / 5; 110 | float o2X = width - sqrt2 * r2; 111 | pathArcTo(mPath, o2X, r, r2, -45, 90); 112 | pathArcTo(mPath, o1X, r, r, 45f, 45f); 113 | mPath.close(); 114 | 115 | if (needMirroring()) { 116 | mTempMatrix.setScale(-1, 1, width / 2, 0); 117 | } else { 118 | mTempMatrix.reset(); 119 | } 120 | mTempMatrix.postTranslate(bounds.left, bounds.top); 121 | mPath.transform(mTempMatrix); 122 | } 123 | 124 | private static void pathArcTo(@NonNull Path path, float centerX, float centerY, float radius, 125 | float startAngle, float sweepAngle) { 126 | path.arcTo(centerX - radius, centerY - radius, centerX + radius, centerY + radius, 127 | startAngle, sweepAngle, false); 128 | } 129 | 130 | @Override 131 | public boolean getPadding(@NonNull Rect padding) { 132 | if (needMirroring()) { 133 | padding.set(mPaddingEnd, 0, mPaddingStart, 0); 134 | } else { 135 | padding.set(mPaddingStart, 0, mPaddingEnd, 0); 136 | } 137 | return true; 138 | } 139 | 140 | @Override 141 | public void getOutline(@NonNull Outline outline) { 142 | outline.setConvexPath(mPath); 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /library/src/main/java/me/zhanghai/android/fastscroll/PopupStyles.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package me.zhanghai.android.fastscroll; 18 | 19 | import android.content.Context; 20 | import android.content.res.Resources; 21 | import android.text.TextUtils; 22 | import android.util.TypedValue; 23 | import android.view.Gravity; 24 | import android.widget.FrameLayout; 25 | import android.widget.TextView; 26 | 27 | import androidx.appcompat.content.res.AppCompatResources; 28 | import androidx.core.util.Consumer; 29 | 30 | public class PopupStyles { 31 | 32 | private PopupStyles() {} 33 | 34 | public static Consumer DEFAULT = popupView -> { 35 | Resources resources = popupView.getResources(); 36 | int minimumSize = resources.getDimensionPixelSize(R.dimen.afs_popup_min_size); 37 | popupView.setMinimumWidth(minimumSize); 38 | popupView.setMinimumHeight(minimumSize); 39 | FrameLayout.LayoutParams layoutParams = (FrameLayout.LayoutParams) 40 | popupView.getLayoutParams(); 41 | layoutParams.gravity = Gravity.RIGHT | Gravity.CENTER_VERTICAL; 42 | layoutParams.setMarginEnd(resources.getDimensionPixelOffset(R.dimen.afs_popup_margin_end)); 43 | popupView.setLayoutParams(layoutParams); 44 | Context context = popupView.getContext(); 45 | popupView.setBackground(new AutoMirrorDrawable(AppCompatResources.getDrawable(context, 46 | R.drawable.afs_popup_background))); 47 | popupView.setEllipsize(TextUtils.TruncateAt.MIDDLE); 48 | popupView.setGravity(Gravity.CENTER); 49 | popupView.setIncludeFontPadding(false); 50 | popupView.setSingleLine(true); 51 | popupView.setTextColor(Utils.getColorFromAttrRes(android.R.attr.textColorPrimaryInverse, 52 | context)); 53 | popupView.setTextSize(TypedValue.COMPLEX_UNIT_PX, resources.getDimensionPixelSize( 54 | R.dimen.afs_popup_text_size)); 55 | }; 56 | 57 | public static Consumer MD2 = popupView -> { 58 | Resources resources = popupView.getResources(); 59 | popupView.setMinimumWidth(resources.getDimensionPixelSize( 60 | R.dimen.afs_md2_popup_min_width)); 61 | popupView.setMinimumHeight(resources.getDimensionPixelSize( 62 | R.dimen.afs_md2_popup_min_height)); 63 | FrameLayout.LayoutParams layoutParams = (FrameLayout.LayoutParams) 64 | popupView.getLayoutParams(); 65 | layoutParams.gravity = Gravity.CENTER_HORIZONTAL | Gravity.TOP; 66 | layoutParams.setMarginEnd(resources.getDimensionPixelOffset( 67 | R.dimen.afs_md2_popup_margin_end)); 68 | popupView.setLayoutParams(layoutParams); 69 | Context context = popupView.getContext(); 70 | popupView.setBackground(new Md2PopupBackground(context)); 71 | popupView.setElevation(resources.getDimensionPixelOffset(R.dimen.afs_md2_popup_elevation)); 72 | popupView.setEllipsize(TextUtils.TruncateAt.MIDDLE); 73 | popupView.setGravity(Gravity.CENTER); 74 | popupView.setIncludeFontPadding(false); 75 | popupView.setSingleLine(true); 76 | popupView.setTextColor(Utils.getColorFromAttrRes(android.R.attr.textColorPrimaryInverse, 77 | context)); 78 | popupView.setTextSize(TypedValue.COMPLEX_UNIT_PX, resources.getDimensionPixelSize( 79 | R.dimen.afs_md2_popup_text_size)); 80 | }; 81 | } 82 | -------------------------------------------------------------------------------- /library/src/main/java/me/zhanghai/android/fastscroll/PopupTextProvider.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package me.zhanghai.android.fastscroll; 18 | 19 | import androidx.annotation.NonNull; 20 | 21 | public interface PopupTextProvider { 22 | 23 | @NonNull 24 | String getPopupText(int position); 25 | } 26 | -------------------------------------------------------------------------------- /library/src/main/java/me/zhanghai/android/fastscroll/Predicate.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package me.zhanghai.android.fastscroll; 18 | 19 | @FunctionalInterface 20 | public interface Predicate { 21 | 22 | boolean test(T t); 23 | } 24 | -------------------------------------------------------------------------------- /library/src/main/java/me/zhanghai/android/fastscroll/RecyclerViewHelper.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package me.zhanghai.android.fastscroll; 18 | 19 | import android.graphics.Canvas; 20 | import android.graphics.Rect; 21 | import android.view.MotionEvent; 22 | import android.view.View; 23 | 24 | import androidx.annotation.NonNull; 25 | import androidx.annotation.Nullable; 26 | import androidx.recyclerview.widget.GridLayoutManager; 27 | import androidx.recyclerview.widget.LinearLayoutManager; 28 | import androidx.recyclerview.widget.RecyclerView; 29 | 30 | class RecyclerViewHelper implements FastScroller.ViewHelper { 31 | 32 | @NonNull 33 | private final RecyclerView mView; 34 | 35 | @NonNull 36 | private final Rect mTempRect = new Rect(); 37 | 38 | public RecyclerViewHelper(@NonNull RecyclerView view) { 39 | mView = view; 40 | } 41 | 42 | @Override 43 | public void addOnPreDrawListener(@NonNull Runnable onPreDraw) { 44 | mView.addItemDecoration(new RecyclerView.ItemDecoration() { 45 | @Override 46 | public void onDraw(@NonNull Canvas canvas, @NonNull RecyclerView parent, 47 | @NonNull RecyclerView.State state) { 48 | onPreDraw.run(); 49 | } 50 | }); 51 | } 52 | 53 | @Override 54 | public void addOnScrollChangedListener(@NonNull Runnable onScrollChanged) { 55 | mView.addOnScrollListener(new RecyclerView.OnScrollListener() { 56 | @Override 57 | public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) { 58 | onScrollChanged.run(); 59 | } 60 | }); 61 | } 62 | 63 | @Override 64 | public void addOnTouchEventListener(@NonNull Predicate onTouchEvent) { 65 | mView.addOnItemTouchListener(new RecyclerView.SimpleOnItemTouchListener() { 66 | @Override 67 | public boolean onInterceptTouchEvent(@NonNull RecyclerView recyclerView, 68 | @NonNull MotionEvent event) { 69 | return onTouchEvent.test(event); 70 | } 71 | @Override 72 | public void onTouchEvent(@NonNull RecyclerView recyclerView, 73 | @NonNull MotionEvent event) { 74 | onTouchEvent.test(event); 75 | } 76 | }); 77 | } 78 | 79 | @Override 80 | public int getScrollRange() { 81 | int itemCount = getItemCount(); 82 | if (itemCount == 0) { 83 | return 0; 84 | } 85 | int itemHeight = getItemSize(); 86 | if (itemHeight == 0) { 87 | return 0; 88 | } 89 | return mView.getPaddingTop() + itemCount * itemHeight + mView.getPaddingBottom(); 90 | } 91 | 92 | @Override 93 | public int getScrollOffset() { 94 | int firstItemPosition = getFirstItemPosition(); 95 | if (firstItemPosition == RecyclerView.NO_POSITION) { 96 | return 0; 97 | } 98 | int itemHeight = getItemSize(); 99 | int firstItemTop = getFirstItemOffset(); 100 | return mView.getPaddingTop() + firstItemPosition * itemHeight - firstItemTop; 101 | } 102 | 103 | @Override 104 | public void scrollTo(int offset) { 105 | // Stop any scroll in progress for RecyclerView. 106 | mView.stopScroll(); 107 | offset -= mView.getPaddingTop(); 108 | int itemHeight = getItemSize(); 109 | // firstItemPosition should be non-negative even if paddingTop is greater than item height. 110 | int firstItemPosition = Math.max(0, offset / itemHeight); 111 | int firstItemTop = firstItemPosition * itemHeight - offset; 112 | scrollToPositionWithOffset(firstItemPosition, firstItemTop); 113 | } 114 | 115 | @Nullable 116 | @Override 117 | public String getPopupText() { 118 | RecyclerView.Adapter adapter = mView.getAdapter(); 119 | if (!(adapter instanceof PopupTextProvider)) { 120 | return null; 121 | } 122 | PopupTextProvider popupTextProvider = (PopupTextProvider) adapter; 123 | int position = getFirstItemAdapterPosition(); 124 | if (position == RecyclerView.NO_POSITION) { 125 | return null; 126 | } 127 | return popupTextProvider.getPopupText(position); 128 | } 129 | 130 | private int getItemCount() { 131 | LinearLayoutManager linearLayoutManager = getVerticalLinearLayoutManager(); 132 | if (linearLayoutManager == null) { 133 | return 0; 134 | } 135 | int itemCount = linearLayoutManager.getItemCount(); 136 | if (itemCount == 0) { 137 | return 0; 138 | } 139 | if (linearLayoutManager instanceof GridLayoutManager) { 140 | GridLayoutManager gridLayoutManager = (GridLayoutManager) linearLayoutManager; 141 | itemCount = (itemCount - 1) / gridLayoutManager.getSpanCount() + 1; 142 | } 143 | return itemCount; 144 | } 145 | 146 | private int getItemSize() { 147 | if (mView.getChildCount() == 0) { 148 | return 0; 149 | } 150 | View itemView = mView.getChildAt(0); 151 | mView.getDecoratedBoundsWithMargins(itemView, mTempRect); 152 | return mTempRect.height(); 153 | } 154 | 155 | private int getFirstItemPosition() { 156 | int position = getFirstItemAdapterPosition(); 157 | LinearLayoutManager linearLayoutManager = getVerticalLinearLayoutManager(); 158 | if (linearLayoutManager == null) { 159 | return RecyclerView.NO_POSITION; 160 | } 161 | if (linearLayoutManager instanceof GridLayoutManager) { 162 | GridLayoutManager gridLayoutManager = (GridLayoutManager) linearLayoutManager; 163 | position /= gridLayoutManager.getSpanCount(); 164 | } 165 | return position; 166 | } 167 | 168 | private int getFirstItemAdapterPosition() { 169 | if (mView.getChildCount() == 0) { 170 | return RecyclerView.NO_POSITION; 171 | } 172 | View itemView = mView.getChildAt(0); 173 | LinearLayoutManager linearLayoutManager = getVerticalLinearLayoutManager(); 174 | if (linearLayoutManager == null) { 175 | return RecyclerView.NO_POSITION; 176 | } 177 | return linearLayoutManager.getPosition(itemView); 178 | } 179 | 180 | private int getFirstItemOffset() { 181 | if (mView.getChildCount() == 0) { 182 | return RecyclerView.NO_POSITION; 183 | } 184 | View itemView = mView.getChildAt(0); 185 | mView.getDecoratedBoundsWithMargins(itemView, mTempRect); 186 | return mTempRect.top; 187 | } 188 | 189 | private void scrollToPositionWithOffset(int position, int offset) { 190 | LinearLayoutManager linearLayoutManager = getVerticalLinearLayoutManager(); 191 | if (linearLayoutManager == null) { 192 | return; 193 | } 194 | if (linearLayoutManager instanceof GridLayoutManager) { 195 | GridLayoutManager gridLayoutManager = (GridLayoutManager) linearLayoutManager; 196 | position *= gridLayoutManager.getSpanCount(); 197 | } 198 | // LinearLayoutManager actually takes offset from paddingTop instead of top of RecyclerView. 199 | offset -= mView.getPaddingTop(); 200 | linearLayoutManager.scrollToPositionWithOffset(position, offset); 201 | } 202 | 203 | @Nullable 204 | private LinearLayoutManager getVerticalLinearLayoutManager() { 205 | RecyclerView.LayoutManager layoutManager = mView.getLayoutManager(); 206 | if (!(layoutManager instanceof LinearLayoutManager)) { 207 | return null; 208 | } 209 | LinearLayoutManager linearLayoutManager = (LinearLayoutManager) layoutManager; 210 | if (linearLayoutManager.getOrientation() != RecyclerView.VERTICAL) { 211 | return null; 212 | } 213 | return linearLayoutManager; 214 | } 215 | } 216 | -------------------------------------------------------------------------------- /library/src/main/java/me/zhanghai/android/fastscroll/SimpleFastScrollView.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package me.zhanghai.android.fastscroll; 18 | 19 | import android.view.MotionEvent; 20 | 21 | import androidx.annotation.NonNull; 22 | 23 | public interface SimpleFastScrollView { 24 | 25 | void setOnPreDrawListener(@NonNull Runnable onPreDraw); 26 | 27 | void setOnScrollChangedListener(@NonNull Runnable onScrollChanged); 28 | 29 | void setOnTouchEventListener(@NonNull Predicate onTouchEvent); 30 | 31 | int getScrollRange(); 32 | 33 | int getScrollOffset(); 34 | } 35 | -------------------------------------------------------------------------------- /library/src/main/java/me/zhanghai/android/fastscroll/SimpleFastScrollViewMixin.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package me.zhanghai.android.fastscroll; 18 | 19 | import android.graphics.Canvas; 20 | import android.view.MotionEvent; 21 | 22 | import androidx.annotation.NonNull; 23 | import androidx.annotation.Nullable; 24 | 25 | public class SimpleFastScrollViewMixin implements SimpleFastScrollView { 26 | 27 | @NonNull 28 | private final ViewAccessor mView; 29 | 30 | @Nullable 31 | private Runnable mOnPreDrawListener; 32 | 33 | @Nullable 34 | private Runnable mOnScrollChangedListener; 35 | 36 | @Nullable 37 | private Predicate mOnTouchEventListener; 38 | private boolean mListenerInterceptingTouchEvent; 39 | 40 | public SimpleFastScrollViewMixin(@NonNull ViewAccessor view) { 41 | mView = view; 42 | } 43 | 44 | @Override 45 | public void setOnPreDrawListener(@Nullable Runnable listener) { 46 | mOnPreDrawListener = listener; 47 | } 48 | 49 | public void draw(@NonNull Canvas canvas) { 50 | 51 | if (mOnPreDrawListener != null) { 52 | mOnPreDrawListener.run(); 53 | } 54 | 55 | mView.superDraw(canvas); 56 | } 57 | 58 | @Override 59 | public void setOnScrollChangedListener(@Nullable Runnable listener) { 60 | mOnScrollChangedListener = listener; 61 | } 62 | 63 | public void onScrollChanged(int left, int top, int oldLeft, int oldTop) { 64 | mView.superOnScrollChanged(left, top, oldLeft, oldTop); 65 | 66 | if (mOnScrollChangedListener != null) { 67 | mOnScrollChangedListener.run(); 68 | } 69 | } 70 | 71 | @Override 72 | public void setOnTouchEventListener(@Nullable Predicate listener) { 73 | mOnTouchEventListener = listener; 74 | } 75 | 76 | public boolean onInterceptTouchEvent(@NonNull MotionEvent event) { 77 | 78 | if (mOnTouchEventListener != null && mOnTouchEventListener.test(event)) { 79 | 80 | int actionMasked = event.getActionMasked(); 81 | if (actionMasked != MotionEvent.ACTION_UP 82 | && actionMasked != MotionEvent.ACTION_CANCEL) { 83 | mListenerInterceptingTouchEvent = true; 84 | } 85 | 86 | if (actionMasked != MotionEvent.ACTION_CANCEL) { 87 | MotionEvent cancelEvent = MotionEvent.obtain(event); 88 | cancelEvent.setAction(MotionEvent.ACTION_CANCEL); 89 | mView.superOnInterceptTouchEvent(cancelEvent); 90 | cancelEvent.recycle(); 91 | } else { 92 | mView.superOnInterceptTouchEvent(event); 93 | } 94 | 95 | return true; 96 | } 97 | 98 | return mView.superOnInterceptTouchEvent(event); 99 | } 100 | 101 | public boolean onTouchEvent(@NonNull MotionEvent event) { 102 | 103 | if (mOnTouchEventListener != null) { 104 | if (mListenerInterceptingTouchEvent) { 105 | 106 | mOnTouchEventListener.test(event); 107 | 108 | int actionMasked = event.getActionMasked(); 109 | if (actionMasked == MotionEvent.ACTION_UP 110 | || actionMasked == MotionEvent.ACTION_CANCEL) { 111 | mListenerInterceptingTouchEvent = false; 112 | } 113 | 114 | return true; 115 | } else { 116 | int actionMasked = event.getActionMasked(); 117 | if (actionMasked != MotionEvent.ACTION_DOWN && mOnTouchEventListener.test(event)) { 118 | 119 | if (actionMasked != MotionEvent.ACTION_UP 120 | && actionMasked != MotionEvent.ACTION_CANCEL) { 121 | mListenerInterceptingTouchEvent = true; 122 | } 123 | 124 | if (actionMasked != MotionEvent.ACTION_CANCEL) { 125 | MotionEvent cancelEvent = MotionEvent.obtain(event); 126 | cancelEvent.setAction(MotionEvent.ACTION_CANCEL); 127 | mView.superOnTouchEvent(cancelEvent); 128 | cancelEvent.recycle(); 129 | } else { 130 | mView.superOnTouchEvent(event); 131 | } 132 | 133 | return true; 134 | } 135 | } 136 | } 137 | 138 | return mView.superOnTouchEvent(event); 139 | } 140 | 141 | @Override 142 | public int getScrollRange() { 143 | return mView.computeVerticalScrollRange(); 144 | } 145 | 146 | @Override 147 | public int getScrollOffset() { 148 | return mView.computeVerticalScrollOffset(); 149 | } 150 | 151 | public interface ViewAccessor { 152 | 153 | void superDraw(@NonNull Canvas canvas); 154 | 155 | void superOnScrollChanged(int left, int top, int oldLeft, int oldTop); 156 | 157 | boolean superOnInterceptTouchEvent(@NonNull MotionEvent event); 158 | 159 | boolean superOnTouchEvent(@NonNull MotionEvent event); 160 | 161 | int computeVerticalScrollRange(); 162 | 163 | int computeVerticalScrollOffset(); 164 | } 165 | } 166 | -------------------------------------------------------------------------------- /library/src/main/java/me/zhanghai/android/fastscroll/SimpleViewHelper.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package me.zhanghai.android.fastscroll; 18 | 19 | import android.view.MotionEvent; 20 | import android.view.ViewGroup; 21 | 22 | import androidx.annotation.NonNull; 23 | 24 | public class SimpleViewHelper 25 | implements FastScroller.ViewHelper { 26 | 27 | @NonNull 28 | private final T mView; 29 | 30 | public SimpleViewHelper(@NonNull T view) { 31 | mView = view; 32 | } 33 | 34 | @Override 35 | public void addOnPreDrawListener(@NonNull Runnable onPreDraw) { 36 | mView.setOnPreDrawListener(onPreDraw); 37 | } 38 | 39 | @Override 40 | public void addOnScrollChangedListener(@NonNull Runnable onScrollChanged) { 41 | mView.setOnScrollChangedListener(onScrollChanged); 42 | } 43 | 44 | @Override 45 | public void addOnTouchEventListener(@NonNull Predicate onTouchEvent) { 46 | mView.setOnTouchEventListener(onTouchEvent); 47 | } 48 | 49 | @Override 50 | public int getScrollRange() { 51 | return mView.getScrollRange(); 52 | } 53 | 54 | @Override 55 | public int getScrollOffset() { 56 | return mView.getScrollOffset(); 57 | } 58 | 59 | @Override 60 | public void scrollTo(int offset) { 61 | mView.scrollTo(mView.getScrollX(), offset); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /library/src/main/java/me/zhanghai/android/fastscroll/Utils.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package me.zhanghai.android.fastscroll; 18 | 19 | import android.content.Context; 20 | import android.content.res.TypedArray; 21 | import android.util.DisplayMetrics; 22 | import android.util.TypedValue; 23 | 24 | import androidx.annotation.AttrRes; 25 | import androidx.annotation.ColorInt; 26 | import androidx.annotation.NonNull; 27 | import androidx.appcompat.content.res.AppCompatResources; 28 | 29 | class Utils { 30 | 31 | @ColorInt 32 | public static int getColorFromAttrRes(@AttrRes int attrRes, @NonNull Context context) { 33 | TypedArray a = context.obtainStyledAttributes(new int[] { attrRes }); 34 | int resId; 35 | try { 36 | resId = a.getResourceId(0, 0); 37 | } finally { 38 | a.recycle(); 39 | } 40 | if (resId == 0) { 41 | return 0; 42 | } 43 | return AppCompatResources.getColorStateList(context, resId).getDefaultColor(); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /library/src/main/res/drawable/afs_md2_thumb.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 18 | 19 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /library/src/main/res/drawable/afs_md2_track.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 18 | 19 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /library/src/main/res/drawable/afs_popup_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 18 | 19 | 23 | 24 | 28 | 29 | 32 | 33 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /library/src/main/res/drawable/afs_thumb.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 18 | 19 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /library/src/main/res/drawable/afs_thumb_stateful.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 18 | 19 | 20 | 21 | 22 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /library/src/main/res/drawable/afs_track.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 18 | 19 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /library/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 18 | 19 | 20 | 21 | 16dp 22 | 23 | 88dp 24 | 16dp 25 | 45dp 26 | 27 | 78dp 28 | 64dp 29 | 14dp 30 | 16dp 31 | 29dp 32 | 3dp 33 | 34dp 34 | 35 | -------------------------------------------------------------------------------- /sample/.gitignore: -------------------------------------------------------------------------------- 1 | /.externalNativeBuild/ 2 | /build/ 3 | /out/ 4 | -------------------------------------------------------------------------------- /sample/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 29 5 | buildToolsVersion '28.0.3' 6 | defaultConfig { 7 | applicationId 'me.zhanghai.android.fastscroll.sample' 8 | minSdkVersion 21 9 | targetSdkVersion 29 10 | versionCode Integer.parseInt(VERSION_CODE) 11 | versionName VERSION 12 | } 13 | compileOptions { 14 | sourceCompatibility JavaVersion.VERSION_1_8 15 | targetCompatibility JavaVersion.VERSION_1_8 16 | } 17 | signingConfigs { 18 | release { 19 | storeFile file(System.getenv("STORE_FILE") ?: "/dev/null") 20 | storePassword System.getenv("STORE_PASSWORD") 21 | keyAlias System.getenv("KEY_ALIAS") 22 | keyPassword System.getenv("KEY_PASSWORD") 23 | } 24 | } 25 | buildTypes { 26 | release { 27 | minifyEnabled true 28 | shrinkResources true 29 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 30 | signingConfig signingConfigs.release 31 | } 32 | } 33 | } 34 | 35 | dependencies { 36 | implementation 'androidx.appcompat:appcompat:1.1.0' 37 | implementation 'androidx.recyclerview:recyclerview:1.1.0-rc01' 38 | implementation 'com.google.android.material:material:1.2.0-alpha01' 39 | implementation 'com.jakewharton:butterknife:10.2.0' 40 | annotationProcessor 'com.jakewharton:butterknife-compiler:10.2.0' 41 | implementation project(':library') 42 | } 43 | -------------------------------------------------------------------------------- /sample/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /sample/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 18 | 19 | 23 | 24 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /sample/src/main/java/me/zhanghai/android/fastscroll/sample/AppBarLayoutLiftOnScrollHack.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package me.zhanghai.android.fastscroll.sample; 18 | 19 | import com.google.android.material.appbar.AppBarLayout; 20 | 21 | import androidx.annotation.NonNull; 22 | import androidx.coordinatorlayout.widget.CoordinatorLayout; 23 | 24 | public class AppBarLayoutLiftOnScrollHack { 25 | 26 | private AppBarLayoutLiftOnScrollHack() {} 27 | 28 | public static void hack(@NonNull AppBarLayout appBarLayout, int liftOnScrollTargetViewId) { 29 | appBarLayout.getViewTreeObserver().addOnPreDrawListener(() -> { 30 | // Invalidate the cached view reference so that this works after replacing fragment. 31 | appBarLayout.setLiftOnScrollTargetViewId(liftOnScrollTargetViewId); 32 | // Call AppBarLayout.Behavior.onNestedPreScroll() with dy == 0 to update lifted state. 33 | CoordinatorLayout.Behavior behavior = 34 | ((CoordinatorLayout.LayoutParams) appBarLayout.getLayoutParams()).getBehavior(); 35 | CoordinatorLayout coordinatorLayout = (CoordinatorLayout) appBarLayout.getParent(); 36 | behavior.onNestedPreScroll(coordinatorLayout, appBarLayout, coordinatorLayout, 0, 0, 37 | null, 0); 38 | return true; 39 | }); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /sample/src/main/java/me/zhanghai/android/fastscroll/sample/CoordinatorScrollingFrameLayout.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package me.zhanghai.android.fastscroll.sample; 18 | 19 | import android.content.Context; 20 | import android.util.AttributeSet; 21 | import android.view.View; 22 | import android.view.WindowInsets; 23 | import android.widget.FrameLayout; 24 | 25 | import com.google.android.material.appbar.AppBarLayout; 26 | 27 | import androidx.annotation.AttrRes; 28 | import androidx.annotation.NonNull; 29 | import androidx.annotation.Nullable; 30 | import androidx.annotation.StyleRes; 31 | import androidx.coordinatorlayout.widget.CoordinatorLayout; 32 | import androidx.core.view.WindowInsetsCompat; 33 | 34 | public class CoordinatorScrollingFrameLayout extends FrameLayout 35 | implements CoordinatorLayout.AttachedBehavior { 36 | 37 | public CoordinatorScrollingFrameLayout(@NonNull Context context) { 38 | super(context); 39 | 40 | init(); 41 | } 42 | 43 | public CoordinatorScrollingFrameLayout(@NonNull Context context, @Nullable AttributeSet attrs) { 44 | super(context, attrs); 45 | 46 | init(); 47 | } 48 | 49 | public CoordinatorScrollingFrameLayout(@NonNull Context context, @Nullable AttributeSet attrs, 50 | @AttrRes int defStyleAttr) { 51 | super(context, attrs, defStyleAttr); 52 | 53 | init(); 54 | } 55 | 56 | public CoordinatorScrollingFrameLayout(@NonNull Context context, @Nullable AttributeSet attrs, 57 | @AttrRes int defStyleAttr, @StyleRes int defStyleRes) { 58 | super(context, attrs, defStyleAttr, defStyleRes); 59 | 60 | init(); 61 | } 62 | 63 | private void init() { 64 | setFitsSystemWindows(true); 65 | } 66 | 67 | @NonNull 68 | @Override 69 | public WindowInsets onApplyWindowInsets(@NonNull WindowInsets insets) { 70 | return insets; 71 | } 72 | 73 | @NonNull 74 | @Override 75 | public CoordinatorLayout.Behavior getBehavior() { 76 | return new Behavior(); 77 | } 78 | 79 | private static class Behavior extends AppBarLayout.ScrollingViewBehavior { 80 | 81 | @Override 82 | public boolean onMeasureChild(@NonNull CoordinatorLayout parent, @NonNull View child, 83 | int parentWidthMeasureSpec, int widthUsed, 84 | int parentHeightMeasureSpec, int heightUsed) { 85 | WindowInsetsCompat parentInsets = parent.getLastWindowInsets(); 86 | if (parentInsets != null) { 87 | int parentHeightSize = MeasureSpec.getSize(parentHeightMeasureSpec); 88 | parentHeightSize -= parentInsets.getSystemWindowInsetTop() 89 | + parentInsets.getSystemWindowInsetBottom(); 90 | int parentHeightMode = MeasureSpec.getMode(parentHeightMeasureSpec); 91 | parentHeightMeasureSpec = MeasureSpec.makeMeasureSpec(parentHeightSize, 92 | parentHeightMode); 93 | } 94 | return super.onMeasureChild(parent, child, parentWidthMeasureSpec, widthUsed, 95 | parentHeightMeasureSpec, heightUsed); 96 | } 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /sample/src/main/java/me/zhanghai/android/fastscroll/sample/License.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package me.zhanghai.android.fastscroll.sample; 18 | 19 | import android.content.Context; 20 | 21 | import java.io.IOException; 22 | import java.io.InputStream; 23 | 24 | import androidx.annotation.NonNull; 25 | import androidx.annotation.Nullable; 26 | 27 | public class License { 28 | 29 | @Nullable 30 | private static String sLicense; 31 | 32 | private License() {} 33 | 34 | @NonNull 35 | public static String get(@NonNull Context context) { 36 | if (sLicense == null) { 37 | try (InputStream inputStream = context.getResources().openRawResource(R.raw.license)) { 38 | byte[] bytes = new byte[inputStream.available()]; 39 | inputStream.read(bytes); 40 | sLicense = new String(bytes); 41 | } catch (IOException e) { 42 | throw new AssertionError(e); 43 | } 44 | } 45 | return sLicense; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /sample/src/main/java/me/zhanghai/android/fastscroll/sample/LocaleListAdapter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package me.zhanghai.android.fastscroll.sample; 18 | 19 | import android.text.TextUtils; 20 | import android.view.LayoutInflater; 21 | import android.view.View; 22 | import android.view.ViewGroup; 23 | import android.widget.TextView; 24 | 25 | import java.util.Locale; 26 | 27 | import androidx.annotation.NonNull; 28 | import androidx.recyclerview.widget.RecyclerView; 29 | import butterknife.BindView; 30 | import butterknife.ButterKnife; 31 | import me.zhanghai.android.fastscroll.PopupTextProvider; 32 | 33 | public class LocaleListAdapter extends RecyclerView.Adapter 34 | implements PopupTextProvider { 35 | 36 | @NonNull 37 | private final Locale[] mLocales; 38 | 39 | public LocaleListAdapter() { 40 | setHasStableIds(true); 41 | 42 | mLocales = Locale.getAvailableLocales(); 43 | } 44 | 45 | @Override 46 | public int getItemCount() { 47 | return mLocales.length; 48 | } 49 | 50 | @NonNull 51 | private Locale getItem(int position) { 52 | return mLocales[position]; 53 | } 54 | 55 | @Override 56 | public long getItemId(int position) { 57 | return getItem(position).hashCode(); 58 | } 59 | 60 | @NonNull 61 | @Override 62 | public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { 63 | ViewHolder holder = new ViewHolder(LayoutInflater.from(parent.getContext()).inflate( 64 | R.layout.list_item, parent, false)); 65 | holder.itemView.setClickable(true); 66 | holder.itemView.setFocusable(true); 67 | holder.titleText.setEllipsize(TextUtils.TruncateAt.END); 68 | holder.titleText.setSingleLine(); 69 | holder.subtitleText.setEllipsize(TextUtils.TruncateAt.END); 70 | holder.subtitleText.setSingleLine(); 71 | return holder; 72 | } 73 | 74 | @Override 75 | public void onBindViewHolder(@NonNull ViewHolder holder, int position) { 76 | Locale locale = getItem(position); 77 | holder.titleText.setText(locale.toString()); 78 | holder.subtitleText.setText(locale.getDisplayName()); 79 | } 80 | 81 | @NonNull 82 | @Override 83 | public String getPopupText(int position) { 84 | Locale locale = getItem(position); 85 | return locale.toString().substring(0, 1).toUpperCase(); 86 | } 87 | 88 | static class ViewHolder extends RecyclerView.ViewHolder { 89 | 90 | @BindView(R.id.title) 91 | TextView titleText; 92 | @BindView(R.id.subtitle) 93 | TextView subtitleText; 94 | 95 | public ViewHolder(@NonNull View itemView) { 96 | super(itemView); 97 | 98 | ButterKnife.bind(this, itemView); 99 | } 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /sample/src/main/java/me/zhanghai/android/fastscroll/sample/MainActivity.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package me.zhanghai.android.fastscroll.sample; 18 | 19 | import android.content.res.Configuration; 20 | import android.os.Build; 21 | import android.os.Bundle; 22 | 23 | import androidx.annotation.NonNull; 24 | import androidx.annotation.Nullable; 25 | import androidx.appcompat.app.AppCompatActivity; 26 | 27 | public class MainActivity extends AppCompatActivity { 28 | 29 | @NonNull 30 | private MainFragment mMainFragment; 31 | 32 | @Override 33 | protected void onCreate(@Nullable Bundle savedInstanceState) { 34 | super.onCreate(savedInstanceState); 35 | 36 | // Calls ensureSubDecor(). 37 | findViewById(android.R.id.content); 38 | 39 | if (savedInstanceState == null) { 40 | mMainFragment = MainFragment.newInstance(); 41 | getSupportFragmentManager().beginTransaction() 42 | .add(android.R.id.content, mMainFragment) 43 | .commit(); 44 | } else { 45 | mMainFragment = (MainFragment) getSupportFragmentManager().findFragmentById( 46 | android.R.id.content); 47 | } 48 | } 49 | 50 | @Override 51 | public void onBackPressed() { 52 | if (mMainFragment.onBackPressed()) { 53 | return; 54 | } 55 | super.onBackPressed(); 56 | } 57 | 58 | @Override 59 | public void applyOverrideConfiguration(@NonNull Configuration overrideConfiguration) { 60 | // Workaround b/141132133. 61 | if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { 62 | return; 63 | } 64 | super.applyOverrideConfiguration(overrideConfiguration); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /sample/src/main/java/me/zhanghai/android/fastscroll/sample/MainFragment.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package me.zhanghai.android.fastscroll.sample; 18 | 19 | import android.content.ActivityNotFoundException; 20 | import android.content.Intent; 21 | import android.net.Uri; 22 | import android.os.Build; 23 | import android.os.Bundle; 24 | import android.view.LayoutInflater; 25 | import android.view.Menu; 26 | import android.view.MenuInflater; 27 | import android.view.MenuItem; 28 | import android.view.View; 29 | import android.view.ViewGroup; 30 | import android.widget.Toast; 31 | 32 | import com.google.android.material.appbar.AppBarLayout; 33 | import com.google.android.material.navigation.NavigationView; 34 | 35 | import androidx.annotation.IdRes; 36 | import androidx.annotation.NonNull; 37 | import androidx.annotation.Nullable; 38 | import androidx.appcompat.app.AppCompatActivity; 39 | import androidx.appcompat.widget.Toolbar; 40 | import androidx.core.view.GravityCompat; 41 | import androidx.drawerlayout.widget.DrawerLayout; 42 | import androidx.fragment.app.Fragment; 43 | import butterknife.BindView; 44 | import butterknife.ButterKnife; 45 | 46 | public class MainFragment extends Fragment { 47 | 48 | private static final Uri GITHUB_URI = Uri.parse( 49 | "https://github.com/zhanghai/AndroidFastScroll"); 50 | 51 | @BindView(R.id.drawer) 52 | DrawerLayout mDrawerLayout; 53 | @BindView(R.id.navigation) 54 | NavigationView mNavigationView; 55 | @BindView(R.id.toolbar) 56 | Toolbar mToolbar; 57 | @BindView(R.id.app_bar) 58 | AppBarLayout mAppBarLayout; 59 | 60 | @NonNull 61 | public static MainFragment newInstance() { 62 | return new MainFragment(); 63 | } 64 | 65 | @Override 66 | public void onCreate(@Nullable Bundle savedInstanceState) { 67 | super.onCreate(savedInstanceState); 68 | 69 | setHasOptionsMenu(true); 70 | } 71 | 72 | @Nullable 73 | @Override 74 | public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, 75 | @Nullable Bundle savedInstanceState) { 76 | return inflater.inflate(R.layout.main_fragment, container, false); 77 | } 78 | 79 | @Override 80 | public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { 81 | super.onViewCreated(view, savedInstanceState); 82 | 83 | ButterKnife.bind(this, view); 84 | } 85 | 86 | @Override 87 | public void onActivityCreated(@Nullable Bundle savedInstanceState) { 88 | super.onActivityCreated(savedInstanceState); 89 | 90 | AppCompatActivity activity = (AppCompatActivity) requireActivity(); 91 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { 92 | View decorView = activity.getWindow().getDecorView(); 93 | decorView.setSystemUiVisibility(decorView.getSystemUiVisibility() 94 | | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION 95 | | View.SYSTEM_UI_FLAG_LAYOUT_STABLE); 96 | } 97 | 98 | activity.setSupportActionBar(mToolbar); 99 | 100 | if (savedInstanceState == null) { 101 | setNavigationCheckedItem(R.id.recycler_view_list); 102 | } 103 | mNavigationView.setNavigationItemSelectedListener(this::onNavigationItemSelected); 104 | 105 | AppBarLayoutLiftOnScrollHack.hack(mAppBarLayout, R.id.scrolling_view); 106 | } 107 | 108 | @Override 109 | public void onCreateOptionsMenu(@NonNull Menu menu, @NonNull MenuInflater inflater) { 110 | inflater.inflate(R.menu.main, menu); 111 | } 112 | 113 | @Override 114 | public boolean onOptionsItemSelected(@NonNull MenuItem item) { 115 | switch (item.getItemId()) { 116 | case android.R.id.home: 117 | mDrawerLayout.openDrawer(GravityCompat.START); 118 | return true; 119 | case R.id.action_view_on_github: 120 | viewOnGitHub(); 121 | return true; 122 | default: 123 | return super.onOptionsItemSelected(item); 124 | } 125 | } 126 | 127 | public boolean onBackPressed() { 128 | if (mDrawerLayout.isDrawerOpen(GravityCompat.START)) { 129 | mDrawerLayout.closeDrawer(GravityCompat.START); 130 | return true; 131 | } 132 | return false; 133 | } 134 | 135 | private void viewOnGitHub() { 136 | try { 137 | startActivity(new Intent(Intent.ACTION_VIEW, GITHUB_URI)); 138 | } catch (ActivityNotFoundException e) { 139 | e.printStackTrace(); 140 | Toast.makeText(requireContext(), e.getLocalizedMessage(), Toast.LENGTH_SHORT).show(); 141 | } 142 | } 143 | 144 | private boolean onNavigationItemSelected(@NonNull MenuItem item) { 145 | int itemId = item.getItemId(); 146 | switch (item.getItemId()) { 147 | case R.id.recycler_view_list: 148 | case R.id.recycler_view_list_classic: 149 | case R.id.recycler_view_list_stateful: 150 | case R.id.recycler_view_grid: 151 | case R.id.scroll_view: 152 | case R.id.nested_scroll_view: 153 | case R.id.web_view: 154 | setNavigationCheckedItem(itemId); 155 | mDrawerLayout.closeDrawer(GravityCompat.START); 156 | return true; 157 | default: 158 | return false; 159 | } 160 | } 161 | 162 | private void setNavigationCheckedItem(@IdRes int itemId) { 163 | MenuItem item = mNavigationView.getCheckedItem(); 164 | if (item != null && item.getItemId() == itemId) { 165 | return; 166 | } 167 | Fragment fragment; 168 | switch (itemId) { 169 | case R.id.recycler_view_list: 170 | fragment = RecyclerViewListFragment.newInstance(); 171 | break; 172 | case R.id.recycler_view_list_classic: 173 | fragment = RecyclerViewListClassicFragment.newInstance(); 174 | break; 175 | case R.id.recycler_view_list_stateful: 176 | fragment = RecyclerViewListStatefulFragment.newInstance(); 177 | break; 178 | case R.id.recycler_view_grid: 179 | fragment = RecyclerViewGridFragment.newInstance(); 180 | break; 181 | case R.id.scroll_view: 182 | fragment = ScrollViewFragment.newInstance(); 183 | break; 184 | case R.id.nested_scroll_view: 185 | fragment = NestedScrollViewFragment.newInstance(); 186 | break; 187 | case R.id.web_view: 188 | fragment = WebViewFragment.newInstance(); 189 | break; 190 | default: 191 | throw new AssertionError(itemId); 192 | } 193 | getChildFragmentManager().beginTransaction() 194 | .replace(R.id.content, fragment) 195 | .commit(); 196 | mNavigationView.setCheckedItem(itemId); 197 | } 198 | } 199 | -------------------------------------------------------------------------------- /sample/src/main/java/me/zhanghai/android/fastscroll/sample/NestedScrollViewFragment.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package me.zhanghai.android.fastscroll.sample; 18 | 19 | import android.os.Bundle; 20 | import android.view.LayoutInflater; 21 | import android.view.View; 22 | import android.view.ViewGroup; 23 | import android.widget.TextView; 24 | 25 | import androidx.annotation.NonNull; 26 | import androidx.annotation.Nullable; 27 | import androidx.fragment.app.Fragment; 28 | import butterknife.BindView; 29 | import butterknife.ButterKnife; 30 | import me.zhanghai.android.fastscroll.FastScrollNestedScrollView; 31 | import me.zhanghai.android.fastscroll.FastScrollerBuilder; 32 | 33 | public class NestedScrollViewFragment extends Fragment { 34 | 35 | @BindView(R.id.scrolling_view) 36 | FastScrollNestedScrollView mScrollView; 37 | @BindView(R.id.text) 38 | TextView mTextView; 39 | 40 | @NonNull 41 | public static NestedScrollViewFragment newInstance() { 42 | return new NestedScrollViewFragment(); 43 | } 44 | 45 | @Nullable 46 | @Override 47 | public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, 48 | @Nullable Bundle savedInstanceState) { 49 | return inflater.inflate(R.layout.nested_scroll_view_fragment, container, false); 50 | } 51 | 52 | @Override 53 | public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { 54 | super.onViewCreated(view, savedInstanceState); 55 | 56 | ButterKnife.bind(this, view); 57 | } 58 | 59 | @Override 60 | public void onActivityCreated(@Nullable Bundle savedInstanceState) { 61 | super.onActivityCreated(savedInstanceState); 62 | 63 | mScrollView.setOnApplyWindowInsetsListener(new ScrollingViewOnApplyWindowInsetsListener()); 64 | new FastScrollerBuilder(mScrollView).useMd2Style().build(); 65 | mTextView.setText(License.get(mTextView.getContext())); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /sample/src/main/java/me/zhanghai/android/fastscroll/sample/RecyclerViewFragment.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package me.zhanghai.android.fastscroll.sample; 18 | 19 | import android.os.Bundle; 20 | import android.view.LayoutInflater; 21 | import android.view.View; 22 | import android.view.ViewGroup; 23 | 24 | import androidx.annotation.NonNull; 25 | import androidx.annotation.Nullable; 26 | import androidx.fragment.app.Fragment; 27 | import androidx.recyclerview.widget.LinearLayoutManager; 28 | import androidx.recyclerview.widget.RecyclerView; 29 | import butterknife.BindView; 30 | import butterknife.ButterKnife; 31 | import me.zhanghai.android.fastscroll.FastScroller; 32 | import me.zhanghai.android.fastscroll.FastScrollerBuilder; 33 | 34 | public abstract class RecyclerViewFragment extends Fragment { 35 | 36 | @BindView(R.id.scrolling_view) 37 | RecyclerView mRecyclerView; 38 | 39 | @Nullable 40 | @Override 41 | public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, 42 | @Nullable Bundle savedInstanceState) { 43 | return inflater.inflate(R.layout.recycler_view_fragment, container, false); 44 | } 45 | 46 | @Override 47 | public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { 48 | super.onViewCreated(view, savedInstanceState); 49 | 50 | ButterKnife.bind(this, view); 51 | } 52 | 53 | @Override 54 | public void onActivityCreated(@Nullable Bundle savedInstanceState) { 55 | super.onActivityCreated(savedInstanceState); 56 | 57 | mRecyclerView.setLayoutManager(createLayoutManager(mRecyclerView)); 58 | mRecyclerView.setAdapter(new LocaleListAdapter()); 59 | FastScroller fastScroller = createFastScroller(mRecyclerView); 60 | mRecyclerView.setOnApplyWindowInsetsListener( 61 | new ScrollingViewOnApplyWindowInsetsListener(mRecyclerView, fastScroller)); 62 | } 63 | 64 | @NonNull 65 | protected LinearLayoutManager createLayoutManager(@NonNull RecyclerView recyclerView) { 66 | return new LinearLayoutManager(recyclerView.getContext(), RecyclerView.VERTICAL, false); 67 | } 68 | 69 | @NonNull 70 | protected FastScroller createFastScroller(@NonNull RecyclerView recyclerView) { 71 | return new FastScrollerBuilder(recyclerView).useMd2Style().build(); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /sample/src/main/java/me/zhanghai/android/fastscroll/sample/RecyclerViewGridFragment.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package me.zhanghai.android.fastscroll.sample; 18 | 19 | import androidx.annotation.NonNull; 20 | import androidx.recyclerview.widget.GridLayoutManager; 21 | import androidx.recyclerview.widget.LinearLayoutManager; 22 | import androidx.recyclerview.widget.RecyclerView; 23 | 24 | public class RecyclerViewGridFragment extends RecyclerViewFragment { 25 | 26 | @NonNull 27 | public static RecyclerViewGridFragment newInstance() { 28 | return new RecyclerViewGridFragment(); 29 | } 30 | 31 | @NonNull 32 | @Override 33 | protected LinearLayoutManager createLayoutManager(@NonNull RecyclerView recyclerView) { 34 | return new GridLayoutManager(recyclerView.getContext(), 3, RecyclerView.VERTICAL, false); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /sample/src/main/java/me/zhanghai/android/fastscroll/sample/RecyclerViewListClassicFragment.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package me.zhanghai.android.fastscroll.sample; 18 | 19 | import androidx.annotation.NonNull; 20 | import androidx.recyclerview.widget.RecyclerView; 21 | import me.zhanghai.android.fastscroll.FastScroller; 22 | import me.zhanghai.android.fastscroll.FastScrollerBuilder; 23 | 24 | public class RecyclerViewListClassicFragment extends RecyclerViewFragment { 25 | 26 | @NonNull 27 | public static RecyclerViewListClassicFragment newInstance() { 28 | return new RecyclerViewListClassicFragment(); 29 | } 30 | 31 | @NonNull 32 | protected FastScroller createFastScroller(@NonNull RecyclerView recyclerView) { 33 | return new FastScrollerBuilder(recyclerView).build(); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /sample/src/main/java/me/zhanghai/android/fastscroll/sample/RecyclerViewListFragment.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package me.zhanghai.android.fastscroll.sample; 18 | 19 | import androidx.annotation.NonNull; 20 | import androidx.recyclerview.widget.LinearLayoutManager; 21 | import androidx.recyclerview.widget.RecyclerView; 22 | 23 | public class RecyclerViewListFragment extends RecyclerViewFragment { 24 | 25 | @NonNull 26 | public static RecyclerViewListFragment newInstance() { 27 | return new RecyclerViewListFragment(); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /sample/src/main/java/me/zhanghai/android/fastscroll/sample/RecyclerViewListStatefulFragment.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package me.zhanghai.android.fastscroll.sample; 18 | 19 | import androidx.annotation.NonNull; 20 | import androidx.appcompat.content.res.AppCompatResources; 21 | import androidx.recyclerview.widget.RecyclerView; 22 | import me.zhanghai.android.fastscroll.FastScroller; 23 | import me.zhanghai.android.fastscroll.FastScrollerBuilder; 24 | 25 | public class RecyclerViewListStatefulFragment extends RecyclerViewFragment { 26 | 27 | @NonNull 28 | public static RecyclerViewListStatefulFragment newInstance() { 29 | return new RecyclerViewListStatefulFragment(); 30 | } 31 | 32 | @NonNull 33 | protected FastScroller createFastScroller(@NonNull RecyclerView recyclerView) { 34 | return new FastScrollerBuilder(recyclerView) 35 | .setThumbDrawable(AppCompatResources.getDrawable(recyclerView.getContext(), 36 | R.drawable.afs_thumb_stateful)) 37 | .build(); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /sample/src/main/java/me/zhanghai/android/fastscroll/sample/ScrollViewFragment.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package me.zhanghai.android.fastscroll.sample; 18 | 19 | import android.os.Bundle; 20 | import android.view.LayoutInflater; 21 | import android.view.View; 22 | import android.view.ViewGroup; 23 | import android.widget.TextView; 24 | 25 | import androidx.annotation.NonNull; 26 | import androidx.annotation.Nullable; 27 | import androidx.fragment.app.Fragment; 28 | import butterknife.BindView; 29 | import butterknife.ButterKnife; 30 | import me.zhanghai.android.fastscroll.FastScrollScrollView; 31 | import me.zhanghai.android.fastscroll.FastScrollerBuilder; 32 | 33 | public class ScrollViewFragment extends Fragment { 34 | 35 | @BindView(R.id.scrolling_view) 36 | FastScrollScrollView mScrollView; 37 | @BindView(R.id.text) 38 | TextView mTextView; 39 | 40 | @NonNull 41 | public static ScrollViewFragment newInstance() { 42 | return new ScrollViewFragment(); 43 | } 44 | 45 | @Nullable 46 | @Override 47 | public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, 48 | @Nullable Bundle savedInstanceState) { 49 | return inflater.inflate(R.layout.scroll_view_fragment, container, false); 50 | } 51 | 52 | @Override 53 | public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { 54 | super.onViewCreated(view, savedInstanceState); 55 | 56 | ButterKnife.bind(this, view); 57 | } 58 | 59 | @Override 60 | public void onActivityCreated(@Nullable Bundle savedInstanceState) { 61 | super.onActivityCreated(savedInstanceState); 62 | 63 | mScrollView.setOnApplyWindowInsetsListener(new ScrollingViewOnApplyWindowInsetsListener()); 64 | new FastScrollerBuilder(mScrollView).useMd2Style().build(); 65 | mTextView.setText(License.get(mTextView.getContext())); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /sample/src/main/java/me/zhanghai/android/fastscroll/sample/ScrollingViewOnApplyWindowInsetsListener.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package me.zhanghai.android.fastscroll.sample; 18 | 19 | import android.graphics.Rect; 20 | import android.view.View; 21 | import android.view.WindowInsets; 22 | 23 | import androidx.annotation.NonNull; 24 | import androidx.annotation.Nullable; 25 | import me.zhanghai.android.fastscroll.FastScroller; 26 | 27 | public class ScrollingViewOnApplyWindowInsetsListener implements View.OnApplyWindowInsetsListener { 28 | 29 | @NonNull 30 | private final Rect mPadding = new Rect(); 31 | @Nullable 32 | private final FastScroller mFastScroller; 33 | 34 | public ScrollingViewOnApplyWindowInsetsListener(@Nullable View view, 35 | @Nullable FastScroller fastScroller) { 36 | if (view != null) { 37 | mPadding.set(view.getPaddingLeft(), view.getPaddingTop(), view.getPaddingRight(), 38 | view.getPaddingBottom()); 39 | } 40 | mFastScroller = fastScroller; 41 | } 42 | 43 | public ScrollingViewOnApplyWindowInsetsListener() { 44 | this(null, null); 45 | } 46 | 47 | @NonNull 48 | @Override 49 | public WindowInsets onApplyWindowInsets(@NonNull View view, @NonNull WindowInsets insets) { 50 | view.setPadding(mPadding.left + insets.getSystemWindowInsetLeft(), mPadding.top, 51 | mPadding.right + insets.getSystemWindowInsetRight(), 52 | mPadding.bottom + insets.getSystemWindowInsetBottom()); 53 | if (mFastScroller != null) { 54 | mFastScroller.setPadding(insets.getSystemWindowInsetLeft(), 0, 55 | insets.getSystemWindowInsetRight(), insets.getSystemWindowInsetBottom()); 56 | } 57 | return insets; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /sample/src/main/java/me/zhanghai/android/fastscroll/sample/WebViewFragment.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package me.zhanghai.android.fastscroll.sample; 18 | 19 | import android.annotation.SuppressLint; 20 | import android.os.Bundle; 21 | import android.util.Base64; 22 | import android.view.LayoutInflater; 23 | import android.view.View; 24 | import android.view.ViewGroup; 25 | import android.view.WindowInsets; 26 | import android.webkit.WebView; 27 | import android.webkit.WebViewClient; 28 | 29 | import androidx.annotation.NonNull; 30 | import androidx.annotation.Nullable; 31 | import androidx.fragment.app.Fragment; 32 | import butterknife.BindView; 33 | import butterknife.ButterKnife; 34 | import me.zhanghai.android.fastscroll.FastScrollWebView; 35 | import me.zhanghai.android.fastscroll.FastScrollerBuilder; 36 | 37 | public class WebViewFragment extends Fragment { 38 | 39 | @BindView(R.id.scrolling_view) 40 | FastScrollWebView mWebView; 41 | 42 | @NonNull 43 | public static WebViewFragment newInstance() { 44 | return new WebViewFragment(); 45 | } 46 | 47 | @Nullable 48 | @Override 49 | public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, 50 | @Nullable Bundle savedInstanceState) { 51 | return inflater.inflate(R.layout.web_view_fragment, container, false); 52 | } 53 | 54 | @Override 55 | public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { 56 | super.onViewCreated(view, savedInstanceState); 57 | 58 | ButterKnife.bind(this, view); 59 | } 60 | 61 | @Override 62 | @SuppressLint("SetJavaScriptEnabled") 63 | public void onActivityCreated(@Nullable Bundle savedInstanceState) { 64 | super.onActivityCreated(savedInstanceState); 65 | 66 | mWebView.getSettings().setJavaScriptEnabled(true); 67 | mWebView.setOnApplyWindowInsetsListener(new ScrollingViewOnApplyWindowInsetsListener() { 68 | @NonNull 69 | @Override 70 | public WindowInsets onApplyWindowInsets(@NonNull View view, 71 | @NonNull WindowInsets insets) { 72 | insets = super.onApplyWindowInsets(view, insets); 73 | updateWebViewPadding(); 74 | return insets; 75 | } 76 | }); 77 | mWebView.setWebViewClient(new WebViewClient() { 78 | @Override 79 | public void onPageFinished(@NonNull WebView view, @NonNull String url) { 80 | updateWebViewPadding(); 81 | } 82 | }); 83 | String html = getString(R.string.html_format, License.get(mWebView.getContext())); 84 | String data = Base64.encodeToString(html.getBytes(), Base64.NO_PADDING); 85 | mWebView.loadData(data, null, "base64"); 86 | new FastScrollerBuilder(mWebView).useMd2Style().build(); 87 | } 88 | 89 | private void updateWebViewPadding() { 90 | mWebView.loadUrl("javascript:(function () { document.body.style.margin = '" 91 | + pxToDp(mWebView.getPaddingTop()) + "px " 92 | + pxToDp(mWebView.getPaddingRight()) + "px " 93 | + pxToDp(mWebView.getPaddingBottom()) + "px " 94 | + pxToDp(mWebView.getPaddingLeft()) + "px'; })();"); 95 | } 96 | 97 | private float pxToDp(float px) { 98 | return px / getResources().getDisplayMetrics().density; 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /sample/src/main/launcher_icon-web.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RikkaW/AndroidFastScroll/a48c8b26b57ae1f0f583c0ce0339839d79bb55ee/sample/src/main/launcher_icon-web.png -------------------------------------------------------------------------------- /sample/src/main/res/drawable/add_icon_white_24dp.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 18 | 19 | 25 | 26 | 29 | 30 | -------------------------------------------------------------------------------- /sample/src/main/res/drawable/info_icon_control_normal_24dp.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 18 | 19 | 26 | 27 | 30 | 31 | -------------------------------------------------------------------------------- /sample/src/main/res/drawable/menu_icon_control_normal_24dp.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 18 | 19 | 26 | 27 | 30 | 31 | -------------------------------------------------------------------------------- /sample/src/main/res/layout/list_item.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 18 | 19 | 29 | 30 | 35 | 36 | 42 | -------------------------------------------------------------------------------- /sample/src/main/res/layout/main_fragment.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 18 | 19 | 26 | 27 | 31 | 32 | 41 | 42 | 48 | 49 | 50 | 54 | 55 | 60 | 61 | 67 | 68 | 69 | 70 | 77 | 78 | -------------------------------------------------------------------------------- /sample/src/main/res/layout/nested_scroll_view_fragment.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 18 | 19 | 26 | 27 | 37 | 38 | -------------------------------------------------------------------------------- /sample/src/main/res/layout/recycler_view_fragment.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 18 | 19 | 27 | -------------------------------------------------------------------------------- /sample/src/main/res/layout/scroll_view_fragment.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 18 | 19 | 26 | 27 | 37 | 38 | -------------------------------------------------------------------------------- /sample/src/main/res/layout/web_view_fragment.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 18 | 19 | 25 | -------------------------------------------------------------------------------- /sample/src/main/res/menu/main.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 18 | 19 | 22 | 23 | 29 | 30 | -------------------------------------------------------------------------------- /sample/src/main/res/menu/navigation.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 18 | 19 | 20 | 21 | 22 | 23 | 26 | 27 | 30 | 31 | 34 | 35 | 38 | 39 | 42 | 43 | 46 | 47 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /sample/src/main/res/mipmap-hdpi/launcher_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RikkaW/AndroidFastScroll/a48c8b26b57ae1f0f583c0ce0339839d79bb55ee/sample/src/main/res/mipmap-hdpi/launcher_icon.png -------------------------------------------------------------------------------- /sample/src/main/res/mipmap-mdpi/launcher_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RikkaW/AndroidFastScroll/a48c8b26b57ae1f0f583c0ce0339839d79bb55ee/sample/src/main/res/mipmap-mdpi/launcher_icon.png -------------------------------------------------------------------------------- /sample/src/main/res/mipmap-xhdpi/launcher_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RikkaW/AndroidFastScroll/a48c8b26b57ae1f0f583c0ce0339839d79bb55ee/sample/src/main/res/mipmap-xhdpi/launcher_icon.png -------------------------------------------------------------------------------- /sample/src/main/res/mipmap-xxhdpi/launcher_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RikkaW/AndroidFastScroll/a48c8b26b57ae1f0f583c0ce0339839d79bb55ee/sample/src/main/res/mipmap-xxhdpi/launcher_icon.png -------------------------------------------------------------------------------- /sample/src/main/res/mipmap-xxxhdpi/launcher_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RikkaW/AndroidFastScroll/a48c8b26b57ae1f0f583c0ce0339839d79bb55ee/sample/src/main/res/mipmap-xxxhdpi/launcher_icon.png -------------------------------------------------------------------------------- /sample/src/main/res/raw/license.txt: -------------------------------------------------------------------------------- 1 | ../../../../../LICENSE -------------------------------------------------------------------------------- /sample/src/main/res/values-night/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 18 | 19 | 20 | 21 | @color/color_secondary_dark 22 | 23 | @color/status_bar_background_compat_dark 24 | 25 | @color/system_window_scrim_dark 26 | 27 | -------------------------------------------------------------------------------- /sample/src/main/res/values-sw600dp/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 18 | 19 | 20 | 24dp 21 | 104dp 22 | 23 | -------------------------------------------------------------------------------- /sample/src/main/res/values-v23/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 18 | 19 | 20 | 24 | 24 | 44 | 45 | %1$s 46 | 47 | ]]> 48 | 49 | -------------------------------------------------------------------------------- /sample/src/main/res/values/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 18 | 19 | 20 | 23 | 31 |