diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 0000000..9466725 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,12 @@ +# These are supported funding model platforms + +github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] +patreon: # Replace with a single Patreon username +open_collective: # Replace with a single Open Collective username +ko_fi: # Replace with a single Ko-fi username +tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel +community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry +liberapay: # Replace with a single Liberapay username +issuehunt: # Replace with a single IssueHunt username +otechie: # Replace with a single Otechie username +custom: https://raw.githubusercontent.com/getActivity/Donate/master/picture/pay_ali.png diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000..ec4bb38 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1 @@ +blank_issues_enabled: false \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/issue_zh_template_bug.yml b/.github/ISSUE_TEMPLATE/issue_zh_template_bug.yml new file mode 100644 index 0000000..907aab2 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/issue_zh_template_bug.yml @@ -0,0 +1,173 @@ +name: 提交 Bug +description: 请告诉我框架存在的问题,我会协助你解决此问题! +title: "[Bug]:" +labels: ["bug"] + +body: + - type: markdown + attributes: + value: | + ## [【警告:请务必按照 issue 模板填写,不要抱有侥幸心理,一旦发现 issue 没有按照模板认真填写,一律直接关闭】](https://github.com/getActivity/IssueTemplateGuide) + - type: input + id: input_id_1 + attributes: + label: 框架版本【必填】 + description: 请输入你使用的框架版本 + validations: + required: true + - type: textarea + id: input_id_2 + attributes: + label: 问题描述【必填】 + description: 请输入你对这个问题的描述 + validations: + required: true + - type: textarea + id: input_id_3 + attributes: + label: 复现步骤【必填】 + description: 请输入问题的复现步骤 + validations: + required: true + - type: dropdown + id: input_id_4 + attributes: + label: 是否必现【必填】 + multiple: false + options: + - 未选择 + - 是 + - 否 + validations: + required: true + - type: input + id: input_id_5 + attributes: + label: 项目 targetSdkVersion【必填】 + validations: + required: true + - type: input + id: input_id_6 + attributes: + label: 出现问题的手机信息【必填】 + description: 请填写出现问题的品牌和机型 + validations: + required: true + - type: input + id: input_id_7 + attributes: + label: 出现问题的安卓版本【必填】 + description: 请填写出现问题的 Android 版本 + validations: + required: true + - type: dropdown + id: input_id_8 + attributes: + label: 问题信息的来源渠道【必填】 + multiple: true + options: + - 自己遇到的 + - Bugly 看到的 + - 用户反馈 + - 其他渠道 + - type: input + id: input_id_9 + attributes: + label: 是部分机型还是所有机型都会出现【必答】 + description: 部分/全部(例如:某为,某 Android 版本会出现) + validations: + required: true + - type: dropdown + id: input_id_10 + attributes: + label: 框架最新的版本是否存在这个问题【必答】 + description: 如果用的是旧版本的话,建议升级看问题是否还存在 + multiple: false + options: + - 未选择 + - 是 + - 否 + validations: + required: true + - type: dropdown + id: input_id_11 + attributes: + label: 框架文档是否提及了该问题【必答】 + description: 文档会提供最常见的问题解答,可以先看看是否有自己想要的 + multiple: false + options: + - 未选择 + - 是 + - 否 + validations: + required: true + - type: dropdown + id: input_id_12 + attributes: + label: 是否已经查阅框架文档但还未能解决的【必答】 + description: 如果查阅了文档但还是没有解决的话,可以选择是 + multiple: false + options: + - 未选择 + - 是 + - 否 + validations: + required: true + - type: dropdown + id: input_id_13 + attributes: + label: issue 列表中是否有人曾提过类似的问题【必答】 + description: 可以在 issue 列表在搜索问题关键字,参考一下别人的解决方案 + multiple: false + options: + - 未选择 + - 是 + - 否 + validations: + required: true + - type: dropdown + id: input_id_14 + attributes: + label: 是否已经搜索过了 issue 列表但还未能解决的【必答】 + description: 如果搜索过了 issue 列表但是问题没有解决的话,可以选择是 + multiple: false + options: + - 未选择 + - 是 + - 否 + validations: + required: true + - type: dropdown + id: input_id_15 + attributes: + label: 是否可以通过 Demo 来复现该问题【必答】 + description: 排查一下是不是自己的项目代码写得有问题导致的 + multiple: false + options: + - 未选择 + - 是 + - 否 + validations: + required: true + - type: textarea + id: input_id_16 + attributes: + label: 提供报错堆栈 + description: 如果有报错的话必填,注意不要拿被混淆过的代码堆栈上来 + render: text + validations: + required: false + - type: textarea + id: input_id_17 + attributes: + label: 提供截图或视频 + description: 根据需要提供,此项不强制 + validations: + required: false + - type: textarea + id: input_id_18 + attributes: + label: 提供解决方案 + description: 如果已经解决了的话,此项不强制 + validations: + required: false \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/issue_zh_template_question.yml b/.github/ISSUE_TEMPLATE/issue_zh_template_question.yml new file mode 100644 index 0000000..b4159ba --- /dev/null +++ b/.github/ISSUE_TEMPLATE/issue_zh_template_question.yml @@ -0,0 +1,65 @@ +name: 提出疑问 +description: 提出你的困惑,我会给你解答 +title: "[疑惑]:" +labels: ["question"] + +body: + - type: markdown + attributes: + value: | + ## [【警告:请务必按照 issue 模板填写,不要抱有侥幸心理,一旦发现 issue 没有按照模板认真填写,一律直接关闭】](https://github.com/getActivity/IssueTemplateGuide) + - type: textarea + id: input_id_1 + attributes: + label: 问题描述【必填】 + description: 请描述一下你的问题(注意:如果确定是框架 bug 请不要在这里提,否则一概不受理) + validations: + required: true + - type: dropdown + id: input_id_2 + attributes: + label: 框架文档是否提及了该问题【必答】 + description: 文档会提供最常见的问题解答,可以先看看是否有自己想要的 + multiple: false + options: + - 未选择 + - 是 + - 否 + validations: + required: true + - type: dropdown + id: input_id_3 + attributes: + label: 是否已经查阅框架文档但还未能解决的【必答】 + description: 如果查阅了文档但还是没有解决的话,可以选择是 + multiple: false + options: + - 未选择 + - 是 + - 否 + validations: + required: true + - type: dropdown + id: input_id_4 + attributes: + label: issue 列表中是否有人曾提过类似的问题【必答】 + description: 可以在 issue 列表在搜索问题关键字,参考一下别人的解决方案 + multiple: false + options: + - 未选择 + - 是 + - 否 + validations: + required: true + - type: dropdown + id: input_id_5 + attributes: + label: 是否已经搜索过了 issue 列表但还未能解决的【必答】 + description: 如果搜索过了 issue 列表但是问题没有解决的话,可以选择是 + multiple: false + options: + - 未选择 + - 是 + - 否 + validations: + required: true \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/issue_zh_template_suggest.yml b/.github/ISSUE_TEMPLATE/issue_zh_template_suggest.yml new file mode 100644 index 0000000..f5fea21 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/issue_zh_template_suggest.yml @@ -0,0 +1,60 @@ +name: 提交建议 +description: 请告诉我框架的不足之处,让我做得更好! +title: "[建议]:" +labels: ["help wanted"] + +body: + - type: markdown + attributes: + value: | + ## [【警告:请务必按照 issue 模板填写,不要抱有侥幸心理,一旦发现 issue 没有按照模板认真填写,一律直接关闭】](https://github.com/getActivity/IssueTemplateGuide) + - type: textarea + id: input_id_1 + attributes: + label: 你觉得框架有什么不足之处?【必答】 + description: 你可以描述框架有什么令你不满意的地方 + validations: + required: true + - type: dropdown + id: input_id_2 + attributes: + label: issue 是否有人曾提过类似的建议?【必答】 + description: 一旦出现重复提问我将不会再次解答 + multiple: false + options: + - 未选择 + - 是 + - 否 + validations: + required: true + - type: dropdown + id: input_id_3 + attributes: + label: 框架文档是否提及了该问题【必答】 + description: 文档会提供最常见的问题解答,可以先看看是否有自己想要的 + multiple: false + options: + - 未选择 + - 是 + - 否 + validations: + required: true + - type: dropdown + id: input_id_4 + attributes: + label: 是否已经查阅框架文档但还未能解决的【必答】 + description: 如果查阅了文档但还是没有解决的话,可以选择是 + multiple: false + options: + - 未选择 + - 是 + - 否 + validations: + required: true + - type: textarea + id: input_id_5 + attributes: + label: 你觉得该怎么去完善会比较好?【非必答】 + description: 你可以提供一下自己的想法或者做法供作者参考 + validations: + required: false \ No newline at end of file diff --git a/.github/workflows/android.yml b/.github/workflows/android.yml new file mode 100644 index 0000000..635d695 --- /dev/null +++ b/.github/workflows/android.yml @@ -0,0 +1,15 @@ +name: Android CI + +on: [push] + +jobs: + build: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + - name: set up JDK 1.8 + uses: actions/setup-java@v1 + with: + java-version: 1.8 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d342ae5 --- /dev/null +++ b/.gitignore @@ -0,0 +1,11 @@ +.gradle +.idea +.cxx +.externalNativeBuild +build +captures + +._* +*.iml +.DS_Store +local.properties \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..87f74b5 --- /dev/null +++ b/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, October 2021 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2021 Huang JinQun + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/NestedScrollWebView.gif b/NestedScrollWebView.gif deleted file mode 100644 index f04c1b9..0000000 Binary files a/NestedScrollWebView.gif and /dev/null differ diff --git a/README.md b/README.md index 3923fdd..e3c0e2f 100644 --- a/README.md +++ b/README.md @@ -1,15 +1,188 @@ -# 支持嵌套滚动的WebView +# 嵌套滚动布局 -![](NestedScrollWebView.gif) +* 项目地址:[Github](https://github.com/getActivity/NestedScrollLayout) -> [点击此处下载apk](https://raw.githubusercontent.com/getActivity/Markdown/master/app.apk) +* 可以扫码下载 Demo 进行演示或者测试,如果扫码下载不了的,[点击此处可直接下载](https://github.com/getActivity/NestedScrollLayout/releases/download/2.0/NestedScrollLayout.apk) -> 最新日志:解决ToolBar滚动的过快的Bug +![](picture/download_demo_apk_qr_code.png) + +* 众所周知,WebView、LinearLayout、FrameLayout、RelativeLayout 是不支持 NestedScroll(嵌套滚动)特性的,于是就有了这个库,专门解决这一问题 + +![](picture/dynamic_figure.gif) + +#### 集成步骤 + +* 如果你的项目 Gradle 配置是在 `7.0` 以下,需要在 `build.gradle` 文件中加入 + +```groovy +allprojects { + repositories { + // JitPack 远程仓库:https://jitpack.io + maven { url 'https://jitpack.io' } + } +} +``` + +* 如果你的 Gradle 配置是 `7.0` 及以上,则需要在 `settings.gradle` 文件中加入 + +```groovy +dependencyResolutionManagement { + repositories { + // JitPack 远程仓库:https://jitpack.io + maven { url 'https://jitpack.io' } + } +} +``` + +* 配置完远程仓库后,在项目 app 模块下的 `build.gradle` 文件中加入远程依赖 + +```groovy +android { + // 支持 JDK 1.8 及以上 + compileOptions { + targetCompatibility JavaVersion.VERSION_1_8 + sourceCompatibility JavaVersion.VERSION_1_8 + } +} + +dependencies { + // 嵌套滚动布局:https://github.com/getActivity/NestedScrollLayout + implementation 'com.github.getActivity:NestedScrollLayout:2.0' +} +``` + +#### AndroidX 兼容 + +* 如果项目是基于 **AndroidX** 包,请在项目 `gradle.properties` 文件中加入 + +```text +# 表示将第三方库迁移到 AndroidX +android.enableJetifier = true +``` + +* 如果项目是基于 **Support** 包则不需要加入此配置 + +#### 框架用法 + +* NestedScrollWebView + +```xml + +``` + +* NestedScrollFrameLayout + +```xml + + + +``` + +* NestedScrollLinearLayout + +```xml + + + + +``` + +* NestedScrollRelativeLayout + +```xml + + + + +``` + +* NestedScrollViewPager + +```xml + +``` + +#### 开源项目列表 + +* 安卓技术中台:[AndroidProject](https://github.com/getActivity/AndroidProject) ![](https://img.shields.io/github/stars/getActivity/AndroidProject.svg) ![](https://img.shields.io/github/forks/getActivity/AndroidProject.svg) + +* 安卓技术中台 Kt 版:[AndroidProject-Kotlin](https://github.com/getActivity/AndroidProject-Kotlin) ![](https://img.shields.io/github/stars/getActivity/AndroidProject-Kotlin.svg) ![](https://img.shields.io/github/forks/getActivity/AndroidProject-Kotlin.svg) + +* 权限框架:[XXPermissions](https://github.com/getActivity/XXPermissions) ![](https://img.shields.io/github/stars/getActivity/XXPermissions.svg) ![](https://img.shields.io/github/forks/getActivity/XXPermissions.svg) + +* 吐司框架:[Toaster](https://github.com/getActivity/Toaster) ![](https://img.shields.io/github/stars/getActivity/Toaster.svg) ![](https://img.shields.io/github/forks/getActivity/Toaster.svg) + +* 网络框架:[EasyHttp](https://github.com/getActivity/EasyHttp) ![](https://img.shields.io/github/stars/getActivity/EasyHttp.svg) ![](https://img.shields.io/github/forks/getActivity/EasyHttp.svg) + +* 标题栏框架:[TitleBar](https://github.com/getActivity/TitleBar) ![](https://img.shields.io/github/stars/getActivity/TitleBar.svg) ![](https://img.shields.io/github/forks/getActivity/TitleBar.svg) + +* 悬浮窗框架:[EasyWindow](https://github.com/getActivity/EasyWindow) ![](https://img.shields.io/github/stars/getActivity/EasyWindow.svg) ![](https://img.shields.io/github/forks/getActivity/EasyWindow.svg) + +* Shape 框架:[ShapeView](https://github.com/getActivity/ShapeView) ![](https://img.shields.io/github/stars/getActivity/ShapeView.svg) ![](https://img.shields.io/github/forks/getActivity/ShapeView.svg) + +* 语种切换框架:[MultiLanguages](https://github.com/getActivity/MultiLanguages) ![](https://img.shields.io/github/stars/getActivity/MultiLanguages.svg) ![](https://img.shields.io/github/forks/getActivity/MultiLanguages.svg) + +* Gson 解析容错:[GsonFactory](https://github.com/getActivity/GsonFactory) ![](https://img.shields.io/github/stars/getActivity/GsonFactory.svg) ![](https://img.shields.io/github/forks/getActivity/GsonFactory.svg) + +* 日志查看框架:[Logcat](https://github.com/getActivity/Logcat) ![](https://img.shields.io/github/stars/getActivity/Logcat.svg) ![](https://img.shields.io/github/forks/getActivity/Logcat.svg) + +* Android 版本适配:[AndroidVersionAdapter](https://github.com/getActivity/AndroidVersionAdapter) ![](https://img.shields.io/github/stars/getActivity/AndroidVersionAdapter.svg) ![](https://img.shields.io/github/forks/getActivity/AndroidVersionAdapter.svg) + +* Android 代码规范:[AndroidCodeStandard](https://github.com/getActivity/AndroidCodeStandard) ![](https://img.shields.io/github/stars/getActivity/AndroidCodeStandard.svg) ![](https://img.shields.io/github/forks/getActivity/AndroidCodeStandard.svg) + +* Android 资源大汇总:[AndroidIndex](https://github.com/getActivity/AndroidIndex) ![](https://img.shields.io/github/stars/getActivity/AndroidIndex.svg) ![](https://img.shields.io/github/forks/getActivity/AndroidIndex.svg) + +* Android 开源排行榜:[AndroidGithubBoss](https://github.com/getActivity/AndroidGithubBoss) ![](https://img.shields.io/github/stars/getActivity/AndroidGithubBoss.svg) ![](https://img.shields.io/github/forks/getActivity/AndroidGithubBoss.svg) + +* Studio 精品插件:[StudioPlugins](https://github.com/getActivity/StudioPlugins) ![](https://img.shields.io/github/stars/getActivity/StudioPlugins.svg) ![](https://img.shields.io/github/forks/getActivity/StudioPlugins.svg) + +* 表情包大集合:[EmojiPackage](https://github.com/getActivity/EmojiPackage) ![](https://img.shields.io/github/stars/getActivity/EmojiPackage.svg) ![](https://img.shields.io/github/forks/getActivity/EmojiPackage.svg) + +* AI 资源大汇总:[AiIndex](https://github.com/getActivity/AiIndex) ![](https://img.shields.io/github/stars/getActivity/AiIndex.svg) ![](https://img.shields.io/github/forks/getActivity/AiIndex.svg) + +* 省市区 Json 数据:[ProvinceJson](https://github.com/getActivity/ProvinceJson) ![](https://img.shields.io/github/stars/getActivity/ProvinceJson.svg) ![](https://img.shields.io/github/forks/getActivity/ProvinceJson.svg) + +* Markdown 语法文档:[MarkdownDoc](https://github.com/getActivity/MarkdownDoc) ![](https://img.shields.io/github/stars/getActivity/MarkdownDoc.svg) ![](https://img.shields.io/github/forks/getActivity/MarkdownDoc.svg) + +#### 微信公众号:Android轮子哥 + +![](https://raw.githubusercontent.com/getActivity/Donate/master/picture/official_ccount.png) + +#### Android 技术 Q 群:10047167 + +#### 如果您觉得我的开源库帮你节省了大量的开发时间,请扫描下方的二维码随意打赏,要是能打赏个 10.24 :monkey_face:就太:thumbsup:了。您的支持将鼓励我继续创作:octocat:([点击查看捐赠列表](https://github.com/getActivity/Donate)) + +![](https://raw.githubusercontent.com/getActivity/Donate/master/picture/pay_ali.png) ![](https://raw.githubusercontent.com/getActivity/Donate/master/picture/pay_wechat.png) + +#### 广告区 + +* 我现在任腾讯云服务器推广大使,大家如果有购买服务器的需求,可以通过下面的链接购买 + +[![](https://upload-dianshi-1255598498.file.myqcloud.com/upload/nodir/345X200-9ae456f58874df499adf7c331c02cb0fed12b81d.jpg)](https://curl.qcloud.com/A6cYskvv) + +[【腾讯云】云服务器、云数据库、COS、CDN、短信等云产品特惠热卖中](https://curl.qcloud.com/A6cYskvv) + +[![](https://upload-dianshi-1255598498.file.myqcloud.com/345-200-b28f7dee9552f4241ea6a543f15a9798049701d4.jpg)](https://curl.qcloud.com/up4fQsdn) + +[【腾讯云】中小企业福利专场,多款刚需产品,满足企业通用场景需求](https://curl.qcloud.com/up4fQsdn) ## License ```text -Copyright 2018 Huang Jinqun +Copyright 2018 Huang JinQun Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -22,4 +195,4 @@ distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. -``` +``` \ No newline at end of file diff --git a/app/AppSignature.jks b/app/AppSignature.jks new file mode 100644 index 0000000..82bab2f Binary files /dev/null and b/app/AppSignature.jks differ diff --git a/app/build.gradle b/app/build.gradle new file mode 100644 index 0000000..2b9eaee --- /dev/null +++ b/app/build.gradle @@ -0,0 +1,58 @@ +apply plugin: 'com.android.application' + +android { + compileSdkVersion 28 + + defaultConfig { + applicationId "com.hjq.nested.scroll.demo" + minSdkVersion 21 + targetSdkVersion 28 + versionCode 20 + versionName "2.0" + testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" + } + + // Apk 签名的那些事:https://www.jianshu.com/p/a1f8e5896aa2 + signingConfigs { + config { + storeFile file(StoreFile) + storePassword StorePassword + keyAlias KeyAlias + keyPassword KeyPassword + } + } + + buildTypes { + debug { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + signingConfig signingConfigs.config + } + + release { + minifyEnabled true + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + signingConfig signingConfigs.config + } + } + + applicationVariants.all { variant -> + // apk 输出文件名配置 + variant.outputs.all { output -> + outputFileName = rootProject.getName() + '.apk' + } + } +} + +dependencies { + // 依赖 libs 目录下所有的 jar 和 aar 包 + implementation fileTree(include: ['*.jar', '*.aar'], dir: 'libs') + + implementation project(':library') + + implementation 'com.android.support:appcompat-v7:28.0.0' + implementation 'com.android.support:design:28.0.0' + + // 底部导航栏:https://github.com/aurelhubert/ahbottomnavigation + implementation 'com.aurelhubert:ahbottomnavigation:2.2.0' +} \ No newline at end of file diff --git a/app/gradle.properties b/app/gradle.properties new file mode 100644 index 0000000..97eeed7 --- /dev/null +++ b/app/gradle.properties @@ -0,0 +1,4 @@ +StoreFile = AppSignature.jks +StorePassword = AndroidProject +KeyAlias = AndroidProject +KeyPassword = AndroidProject \ No newline at end of file diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 0000000..325e97d --- /dev/null +++ b/app/proguard-rules.pro @@ -0,0 +1,25 @@ +# Add project specific ProGuard rules here. +# By default, the flags in this file are appended to flags specified +# in D:\SDK\Studio\sdk/tools/proguard/proguard-android.txt +# You can edit the include path and order by changing the proguardFiles +# directive in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# Add any project specific keep options here: + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..0a0b031 --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/java/com/hjq/nested/scroll/demo/MainActivity.java b/app/src/main/java/com/hjq/nested/scroll/demo/MainActivity.java new file mode 100644 index 0000000..388bd5c --- /dev/null +++ b/app/src/main/java/com/hjq/nested/scroll/demo/MainActivity.java @@ -0,0 +1,92 @@ +package com.hjq.nested.scroll.demo; + +import android.os.Bundle; +import android.support.v4.app.Fragment; +import android.support.v4.app.FragmentPagerAdapter; +import android.support.v4.view.ViewPager; +import android.support.v7.app.AppCompatActivity; +import android.support.v7.widget.Toolbar; + +import com.aurelhubert.ahbottomnavigation.AHBottomNavigation; +import com.aurelhubert.ahbottomnavigation.AHBottomNavigationItem; +import com.hjq.nested.scroll.demo.fragment.FrameLayoutFragment; +import com.hjq.nested.scroll.demo.fragment.LinearLayoutFragment; +import com.hjq.nested.scroll.demo.fragment.RelativeLayoutFragment; +import com.hjq.nested.scroll.demo.fragment.WebViewFragment; +import com.hjq.nested.scroll.layout.NestedScrollFrameLayout; +import com.hjq.nested.scroll.layout.NestedScrollLinearLayout; +import com.hjq.nested.scroll.layout.NestedScrollRelativeLayout; +import com.hjq.nested.scroll.layout.NestedScrollWebView; + +import java.util.ArrayList; +import java.util.List; + +public class MainActivity extends AppCompatActivity { + + private Toolbar mToolbar; + private AHBottomNavigation mBottomNavigationView; + private ViewPager mViewPager; + + private final List mFragmentSet = new ArrayList<>(); + private final List mFragmentTitleSet = new ArrayList<>(); + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_main); + + mToolbar = findViewById(R.id.tb_main_toolbar); + mViewPager = findViewById(R.id.ahb_main_pager); + mBottomNavigationView = findViewById(R.id.ahb_main_navigation); + + mBottomNavigationView.addItem(new AHBottomNavigationItem("WebView", R.drawable.ic_android)); + mBottomNavigationView.addItem(new AHBottomNavigationItem("LinearLayout", R.drawable.ic_android)); + mBottomNavigationView.addItem(new AHBottomNavigationItem("FrameLayout", R.drawable.ic_android)); + mBottomNavigationView.addItem(new AHBottomNavigationItem("RelativeLayout", R.drawable.ic_android)); + + mFragmentSet.add(new WebViewFragment()); + mFragmentTitleSet.add(NestedScrollWebView.class.getSimpleName()); + mFragmentSet.add(new LinearLayoutFragment()); + mFragmentTitleSet.add(NestedScrollLinearLayout.class.getSimpleName()); + mFragmentSet.add(new FrameLayoutFragment()); + mFragmentTitleSet.add(NestedScrollFrameLayout.class.getSimpleName()); + mFragmentSet.add(new RelativeLayoutFragment()); + mFragmentTitleSet.add(NestedScrollRelativeLayout.class.getSimpleName()); + + mViewPager.setOffscreenPageLimit(mFragmentSet.size()); + mViewPager.setAdapter(new FragmentPagerAdapter(getSupportFragmentManager()) { + @Override + public Fragment getItem(int position) { + return mFragmentSet.get(position); + } + + @Override + public int getCount() { + return mFragmentSet.size(); + } + }); + + mViewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() { + + @Override + public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {} + + @Override + public void onPageSelected(int position) { + mBottomNavigationView.setCurrentItem(position, false); + mToolbar.setTitle(mFragmentTitleSet.get(position)); + } + + @Override + public void onPageScrollStateChanged(int state) {} + }); + + mBottomNavigationView.setOnTabSelectedListener(new AHBottomNavigation.OnTabSelectedListener() { + @Override + public boolean onTabSelected(int position, boolean wasSelected) { + mViewPager.setCurrentItem(position); + return true; + } + }); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/hjq/nested/scroll/demo/fragment/FrameLayoutFragment.java b/app/src/main/java/com/hjq/nested/scroll/demo/fragment/FrameLayoutFragment.java new file mode 100644 index 0000000..83e930d --- /dev/null +++ b/app/src/main/java/com/hjq/nested/scroll/demo/fragment/FrameLayoutFragment.java @@ -0,0 +1,20 @@ +package com.hjq.nested.scroll.demo.fragment; + +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.v4.app.Fragment; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import com.hjq.nested.scroll.demo.R; + +public class FrameLayoutFragment extends Fragment { + + @Nullable + @Override + public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { + return inflater.inflate(R.layout.fragment_frame_layout, container, false); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/hjq/nested/scroll/demo/fragment/LinearLayoutFragment.java b/app/src/main/java/com/hjq/nested/scroll/demo/fragment/LinearLayoutFragment.java new file mode 100644 index 0000000..20d0bdc --- /dev/null +++ b/app/src/main/java/com/hjq/nested/scroll/demo/fragment/LinearLayoutFragment.java @@ -0,0 +1,20 @@ +package com.hjq.nested.scroll.demo.fragment; + +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.v4.app.Fragment; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import com.hjq.nested.scroll.demo.R; + +public class LinearLayoutFragment extends Fragment { + + @Nullable + @Override + public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { + return inflater.inflate(R.layout.fragment_linear_layout, container, false); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/hjq/nested/scroll/demo/fragment/RelativeLayoutFragment.java b/app/src/main/java/com/hjq/nested/scroll/demo/fragment/RelativeLayoutFragment.java new file mode 100644 index 0000000..0f567d3 --- /dev/null +++ b/app/src/main/java/com/hjq/nested/scroll/demo/fragment/RelativeLayoutFragment.java @@ -0,0 +1,20 @@ +package com.hjq.nested.scroll.demo.fragment; + +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.v4.app.Fragment; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import com.hjq.nested.scroll.demo.R; + +public class RelativeLayoutFragment extends Fragment { + + @Nullable + @Override + public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { + return inflater.inflate(R.layout.fragment_relative_layout, container, false); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/hjq/nested/scroll/demo/fragment/WebViewFragment.java b/app/src/main/java/com/hjq/nested/scroll/demo/fragment/WebViewFragment.java new file mode 100644 index 0000000..d47b29d --- /dev/null +++ b/app/src/main/java/com/hjq/nested/scroll/demo/fragment/WebViewFragment.java @@ -0,0 +1,25 @@ +package com.hjq.nested.scroll.demo.fragment; + +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.v4.app.Fragment; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.webkit.WebViewClient; + +import com.hjq.nested.scroll.demo.R; +import com.hjq.nested.scroll.layout.NestedScrollWebView; + +public class WebViewFragment extends Fragment { + + @Nullable + @Override + public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { + NestedScrollWebView nestedScrollWebView = (NestedScrollWebView) inflater.inflate(R.layout.fragment_web_view, container, false); + nestedScrollWebView.setWebViewClient(new WebViewClient()); + nestedScrollWebView.loadUrl("https://github.com/getActivity"); + return nestedScrollWebView; + } +} \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_android.png b/app/src/main/res/drawable/ic_android.png new file mode 100644 index 0000000..324e72c Binary files /dev/null and b/app/src/main/res/drawable/ic_android.png differ diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml new file mode 100644 index 0000000..38eddb1 --- /dev/null +++ b/app/src/main/res/layout/activity_main.xml @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_frame_layout.xml b/app/src/main/res/layout/fragment_frame_layout.xml new file mode 100644 index 0000000..d42a75a --- /dev/null +++ b/app/src/main/res/layout/fragment_frame_layout.xml @@ -0,0 +1,12 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_linear_layout.xml b/app/src/main/res/layout/fragment_linear_layout.xml new file mode 100644 index 0000000..ee6c26d --- /dev/null +++ b/app/src/main/res/layout/fragment_linear_layout.xml @@ -0,0 +1,13 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_relative_layout.xml b/app/src/main/res/layout/fragment_relative_layout.xml new file mode 100644 index 0000000..eab772f --- /dev/null +++ b/app/src/main/res/layout/fragment_relative_layout.xml @@ -0,0 +1,12 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_web_view.xml b/app/src/main/res/layout/fragment_web_view.xml new file mode 100644 index 0000000..289cd44 --- /dev/null +++ b/app/src/main/res/layout/fragment_web_view.xml @@ -0,0 +1,5 @@ + + \ No newline at end of file diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/app/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000..28750c5 Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000..73d70bb Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000..ac6cdba Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/app/src/main/res/values-v23/styles.xml b/app/src/main/res/values-v23/styles.xml new file mode 100644 index 0000000..a54646f --- /dev/null +++ b/app/src/main/res/values-v23/styles.xml @@ -0,0 +1,17 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml new file mode 100644 index 0000000..20be045 --- /dev/null +++ b/app/src/main/res/values/colors.xml @@ -0,0 +1,6 @@ + + + #896961 + #896961 + #FF0033 + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml new file mode 100644 index 0000000..5fe730a --- /dev/null +++ b/app/src/main/res/values/strings.xml @@ -0,0 +1,4 @@ + + + NestedScrollLayout + \ No newline at end of file diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml new file mode 100644 index 0000000..7f9384d --- /dev/null +++ b/app/src/main/res/values/styles.xml @@ -0,0 +1,15 @@ + + + + + + + \ No newline at end of file diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000..3ddc64c --- /dev/null +++ b/build.gradle @@ -0,0 +1,38 @@ +buildscript { + repositories { + // 阿里云云效仓库:https://maven.aliyun.com/mvn/guide + maven { url 'https://maven.aliyun.com/repository/public' } + maven { url 'https://maven.aliyun.com/repository/google' } + // 华为开源镜像:https://mirrors.huaweicloud.com/ + maven { url 'https://repo.huaweicloud.com/repository/maven/' } + // JitPack 远程仓库:https://jitpack.io + maven { url 'https://jitpack.io' } + mavenCentral() + google() + // noinspection JcenterRepositoryObsolete + jcenter() + } + dependencies { + classpath 'com.android.tools.build:gradle:4.1.2' + } +} + +allprojects { + repositories { + maven { url 'https://maven.aliyun.com/repository/public' } + maven { url 'https://maven.aliyun.com/repository/google' } + maven { url 'https://repo.huaweicloud.com/repository/maven/' } + maven { url 'https://jitpack.io' } + mavenCentral() + google() + // noinspection JcenterRepositoryObsolete + jcenter() + } + + // 将构建文件统一输出到项目根目录下的 build 文件夹 + setBuildDir(new File(rootDir, "build/${path.replaceAll(':', '/')}")) +} + +task clean(type: Delete) { + delete rootProject.buildDir +} \ No newline at end of file diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 0000000..89196d1 --- /dev/null +++ b/gradle.properties @@ -0,0 +1,18 @@ +# Project-wide Gradle settings. + +# IDE (e.g. Android Studio) users: +# Gradle settings configured through the IDE *will override* +# any settings specified in this file. + +# For more details on how to configure your build environment visit +# http://www.gradle.org/docs/current/userguide/build_environment.html + +# Specifies the JVM arguments used for the daemon process. +# The setting is particularly useful for tweaking memory settings. +org.gradle.jvmargs=-Xmx1536m + +# When configured, Gradle will run in incubating parallel mode. +# This option should only be used with decoupled projects. More details, visit +# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects +# org.gradle.parallel=true + diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..13372ae Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..e9329ac --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +zipStoreBase = GRADLE_USER_HOME +zipStorePath = wrapper/dists +distributionBase = GRADLE_USER_HOME +distributionPath = wrapper/dists +distributionUrl = https\://services.gradle.org/distributions/gradle-6.5-all.zip \ No newline at end of file diff --git a/gradlew b/gradlew new file mode 100644 index 0000000..9d82f78 --- /dev/null +++ b/gradlew @@ -0,0 +1,160 @@ +#!/usr/bin/env bash + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn ( ) { + echo "$*" +} + +die ( ) { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; +esac + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules +function splitJvmOpts() { + JVM_OPTS=("$@") +} +eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS +JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" + +exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..8a0b282 --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,90 @@ +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windowz variants + +if not "%OS%" == "Windows_NT" goto win9xME_args +if "%@eval[2+2]" == "4" goto 4NT_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* +goto execute + +:4NT_args +@rem Get arguments from the 4NT Shell from JP Software +set CMD_LINE_ARGS=%$ + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/library/build.gradle b/library/build.gradle new file mode 100644 index 0000000..62a019f --- /dev/null +++ b/library/build.gradle @@ -0,0 +1,58 @@ +apply plugin: 'com.android.library' + +android { + compileSdkVersion 26 + + defaultConfig { + minSdkVersion 21 + versionCode 20 + versionName "2.0" + } + + android.libraryVariants.all { variant -> + // aar 输出文件名配置 + variant.outputs.all { output -> + outputFileName = "${rootProject.name}-${android.defaultConfig.versionName}.aar" + } + } +} + +dependencies { + // noinspection GradleCompatible + implementation 'com.android.support:appcompat-v7:28.0.0' +} + +afterEvaluate { + // 排除 BuildConfig.class 和 R.class + generateReleaseBuildConfig.enabled = false + generateDebugBuildConfig.enabled = false + generateReleaseResValues.enabled = false + generateDebugResValues.enabled = false +} + +// 防止编码问题 +tasks.withType(Javadoc) { + options.addStringOption('Xdoclint:none', '-quiet') + options.addStringOption('encoding', 'UTF-8') + options.addStringOption('charSet', 'UTF-8') +} + +task sourcesJar(type: Jar) { + from android.sourceSets.main.java.srcDirs + classifier = 'sources' +} + +task javadoc(type: Javadoc) { + source = android.sourceSets.main.java.srcDirs + classpath += project.files(android.getBootClasspath().join(File.pathSeparator)) +} + +task javadocJar(type: Jar, dependsOn: javadoc) { + classifier = 'javadoc' + from javadoc.destinationDir +} + +artifacts { + archives javadocJar + archives sourcesJar +} \ No newline at end of file diff --git a/library/src/main/AndroidManifest.xml b/library/src/main/AndroidManifest.xml new file mode 100644 index 0000000..95cdda4 --- /dev/null +++ b/library/src/main/AndroidManifest.xml @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/library/src/main/java/com/hjq/nested/scroll/layout/NestedScrollFrameLayout.java b/library/src/main/java/com/hjq/nested/scroll/layout/NestedScrollFrameLayout.java new file mode 100644 index 0000000..66685b2 --- /dev/null +++ b/library/src/main/java/com/hjq/nested/scroll/layout/NestedScrollFrameLayout.java @@ -0,0 +1,286 @@ +package com.hjq.nested.scroll.layout; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.support.annotation.NonNull; +import android.support.v4.view.NestedScrollingChild; +import android.support.v4.view.NestedScrollingChildHelper; +import android.support.v4.view.NestedScrollingParent; +import android.support.v4.view.NestedScrollingParentHelper; +import android.support.v4.view.ViewCompat; +import android.util.AttributeSet; +import android.view.MotionEvent; +import android.view.VelocityTracker; +import android.view.View; +import android.view.ViewConfiguration; +import android.view.ViewParent; +import android.widget.FrameLayout; + +/** + * author : Android 轮子哥 + * github : https://github.com/getActivity/NestedScrollLayout + * time : 2023/07/02 + * desc : 支持嵌套滚动的 FrameLayout + */ +public class NestedScrollFrameLayout extends FrameLayout implements NestedScrollingChild, NestedScrollingParent { + + private static final int INVALID_POINTER = -1; + + private final NestedScrollingChildHelper mChildHelper; + private final NestedScrollingParentHelper mParentHelper; + + private final int[] mScrollConsumed = new int[2]; + private final int[] mScrollOffset = new int[2]; + + private final float mTouchSlop; + private final float mMaximumVelocity; + private final float mMinimumVelocity; + + private int mLastMotionY; + private int mActivePointerId; + + private VelocityTracker mVelocityTracker; + + private boolean mBeingDragged; + + public NestedScrollFrameLayout(Context context) { + this(context, null, 0); + } + + public NestedScrollFrameLayout(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public NestedScrollFrameLayout(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + setWillNotDraw(false); + mChildHelper = new NestedScrollingChildHelper(this); + mParentHelper = new NestedScrollingParentHelper(this); + setNestedScrollingEnabled(true); + mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop(); + mMaximumVelocity = ViewConfiguration.get(getContext()).getScaledMaximumFlingVelocity(); + mMinimumVelocity = ViewConfiguration.get(getContext()).getScaledMinimumFlingVelocity(); + } + + @SuppressLint("ClickableViewAccessibility") + @Override + public boolean onTouchEvent(MotionEvent ev) { + MotionEvent event = MotionEvent.obtain(ev); + initVelocityTrackerIfNotExists(); + final int actionMasked = event.getActionMasked(); + + switch (actionMasked) { + case MotionEvent.ACTION_DOWN: { + mVelocityTracker.addMovement(ev); + + mLastMotionY = (int) event.getY(); + mActivePointerId = event.getPointerId(0); + startNestedScroll(ViewCompat.SCROLL_AXIS_VERTICAL); + break; + } + case MotionEvent.ACTION_MOVE: + final int activePointerIndex = event.findPointerIndex(mActivePointerId); + if (activePointerIndex == -1) { + break; + } + + final int y = (int) event.getY(activePointerIndex); + int deltaY = mLastMotionY - y; + + if (dispatchNestedPreScroll(0, deltaY, mScrollConsumed, mScrollOffset)) { + deltaY -= mScrollConsumed[1]; + event.offsetLocation(0, mScrollOffset[1]); + } + if (!mBeingDragged && Math.abs(mLastMotionY - y) > mTouchSlop) { + final ViewParent parent = getParent(); + if (parent != null) { + parent.requestDisallowInterceptTouchEvent(true); + } + mBeingDragged = true; + if (deltaY > 0) { + deltaY -= mTouchSlop; + } else { + deltaY += mTouchSlop; + } + } + + if (mBeingDragged) { + mVelocityTracker.addMovement(ev); + + mLastMotionY = y - mScrollOffset[1]; + if (dispatchNestedScroll(0, 0, 0, deltaY, mScrollOffset)) { + mLastMotionY -= mScrollOffset[1]; + event.offsetLocation(0, mScrollOffset[1]); + } + } + break; + case MotionEvent.ACTION_CANCEL: + case MotionEvent.ACTION_UP: + if (mBeingDragged) { + mVelocityTracker.computeCurrentVelocity(1000, mMaximumVelocity); + int initialVelocity = (int) mVelocityTracker.getYVelocity(mActivePointerId); + + if (Math.abs(initialVelocity) > mMinimumVelocity) { + flingWithNestedDispatch(-initialVelocity) ; + } + } + mActivePointerId = INVALID_POINTER; + endDrag(); + break; + case MotionEvent.ACTION_POINTER_DOWN: { + final int index = event.getActionIndex(); + mLastMotionY = (int) event.getY(index); + mActivePointerId = event.getPointerId(index); + break; + } + case MotionEvent.ACTION_POINTER_UP: + onSecondaryPointerUp(event); + mLastMotionY = (int) event.getY(event.findPointerIndex(mActivePointerId)); + break; + default: + break; + } + + event.recycle(); + return true; + } + + private void onSecondaryPointerUp(MotionEvent ev) { + final int pointerIndex = (ev.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK) + >> MotionEvent.ACTION_POINTER_INDEX_SHIFT; + final int pointerId = ev.getPointerId(pointerIndex); + if (pointerId == mActivePointerId) { + final int newPointerIndex = pointerIndex == 0 ? 1 : 0; + mLastMotionY = (int) ev.getY(newPointerIndex); + mActivePointerId = ev.getPointerId(newPointerIndex); + if (mVelocityTracker != null) { + mVelocityTracker.clear(); + } + } + } + + private void endDrag() { + mBeingDragged = false; + recycleVelocityTracker(); + stopNestedScroll(); + } + + private void flingWithNestedDispatch(int velocityY) { + if (!dispatchNestedPreFling(0, velocityY)) { + dispatchNestedFling(0, velocityY, true); + } + } + + private void initVelocityTrackerIfNotExists() { + if (mVelocityTracker == null) { + mVelocityTracker = VelocityTracker.obtain(); + } + } + + private void recycleVelocityTracker() { + if (mVelocityTracker != null) { + mVelocityTracker.recycle(); + mVelocityTracker = null; + } + } + + @Override + public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) { + if (disallowIntercept) { + recycleVelocityTracker(); + } + super.requestDisallowInterceptTouchEvent(disallowIntercept); + } + + // NestedScrollingChild + + @Override + public void setNestedScrollingEnabled(boolean enabled) { + mChildHelper.setNestedScrollingEnabled(enabled); + } + + @Override + public boolean isNestedScrollingEnabled() { + return mChildHelper.isNestedScrollingEnabled(); + } + + @Override + public boolean startNestedScroll(int axes) { + return mChildHelper.startNestedScroll(axes); + } + + @Override + public void stopNestedScroll() { + mChildHelper.stopNestedScroll(); + } + + @Override + public boolean hasNestedScrollingParent() { + return mChildHelper.hasNestedScrollingParent(); + } + + @Override + public boolean dispatchNestedScroll(int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed, int[] offsetInWindow) { + return mChildHelper.dispatchNestedScroll(dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, offsetInWindow); + } + + @Override + public boolean dispatchNestedPreScroll(int dx, int dy, int[] consumed, int[] offsetInWindow) { + return mChildHelper.dispatchNestedPreScroll(dx, dy, consumed, offsetInWindow); + } + + @Override + public boolean dispatchNestedFling(float velocityX, float velocityY, boolean consumed) { + return mChildHelper.dispatchNestedFling(velocityX, velocityY, consumed); + } + + @Override + public boolean dispatchNestedPreFling(float velocityX, float velocityY) { + return mChildHelper.dispatchNestedPreFling(velocityX, velocityY); + } + + // NestedScrollingParent + + @Override + public boolean onStartNestedScroll(@NonNull View child, @NonNull View target, int nestedScrollAxes) { + return (nestedScrollAxes & ViewCompat.SCROLL_AXIS_VERTICAL) != 0; + } + + @Override + public void onNestedScrollAccepted(@NonNull View child, @NonNull View target, int nestedScrollAxes) { + mParentHelper.onNestedScrollAccepted(child, target, nestedScrollAxes); + startNestedScroll(ViewCompat.SCROLL_AXIS_VERTICAL); + } + + @Override + public void onStopNestedScroll(@NonNull View target) { + mParentHelper.onStopNestedScroll(target); + stopNestedScroll(); + } + + @Override + public void onNestedScroll(@NonNull View target, int dxConsumed, int dyConsumed, int dxUnconsumed, + int dyUnconsumed) { + dispatchNestedScroll(dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, null); + } + + @Override + public void onNestedPreScroll(@NonNull View target, int dx, int dy, @NonNull int[] consumed) { + dispatchNestedPreScroll(dx, dy, consumed, null); + } + + @Override + public boolean onNestedFling(@NonNull View target, float velocityX, float velocityY, boolean consumed) { + return dispatchNestedFling(velocityX, velocityY, consumed); + } + + @Override + public boolean onNestedPreFling(@NonNull View target, float velocityX, float velocityY) { + return dispatchNestedPreFling(velocityX, velocityY); + } + + @Override + public int getNestedScrollAxes() { + return mParentHelper.getNestedScrollAxes(); + } +} \ No newline at end of file diff --git a/library/src/main/java/com/hjq/nested/scroll/layout/NestedScrollLinearLayout.java b/library/src/main/java/com/hjq/nested/scroll/layout/NestedScrollLinearLayout.java new file mode 100644 index 0000000..bb672da --- /dev/null +++ b/library/src/main/java/com/hjq/nested/scroll/layout/NestedScrollLinearLayout.java @@ -0,0 +1,286 @@ +package com.hjq.nested.scroll.layout; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.support.annotation.NonNull; +import android.support.v4.view.NestedScrollingChild; +import android.support.v4.view.NestedScrollingChildHelper; +import android.support.v4.view.NestedScrollingParent; +import android.support.v4.view.NestedScrollingParentHelper; +import android.support.v4.view.ViewCompat; +import android.util.AttributeSet; +import android.view.MotionEvent; +import android.view.VelocityTracker; +import android.view.View; +import android.view.ViewConfiguration; +import android.view.ViewParent; +import android.widget.LinearLayout; + +/** + * author : Android 轮子哥 + * github : https://github.com/getActivity/NestedScrollLayout + * time : 2023/07/02 + * desc : 支持嵌套滚动的 LinearLayout + */ +public class NestedScrollLinearLayout extends LinearLayout implements NestedScrollingChild, NestedScrollingParent { + + private static final int INVALID_POINTER = -1; + + private final NestedScrollingChildHelper mChildHelper; + private final NestedScrollingParentHelper mParentHelper; + + private final int[] mScrollConsumed = new int[2]; + private final int[] mScrollOffset = new int[2]; + + private final float mTouchSlop; + private final float mMaximumVelocity; + private final float mMinimumVelocity; + + private int mLastMotionY; + private int mActivePointerId; + + private VelocityTracker mVelocityTracker; + + private boolean mBeingDragged; + + public NestedScrollLinearLayout(Context context) { + this(context, null, 0); + } + + public NestedScrollLinearLayout(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public NestedScrollLinearLayout(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + setWillNotDraw(false); + mChildHelper = new NestedScrollingChildHelper(this); + mParentHelper = new NestedScrollingParentHelper(this); + setNestedScrollingEnabled(true); + mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop(); + mMaximumVelocity = ViewConfiguration.get(getContext()).getScaledMaximumFlingVelocity(); + mMinimumVelocity = ViewConfiguration.get(getContext()).getScaledMinimumFlingVelocity(); + } + + @SuppressLint("ClickableViewAccessibility") + @Override + public boolean onTouchEvent(MotionEvent ev) { + MotionEvent event = MotionEvent.obtain(ev); + initVelocityTrackerIfNotExists(); + final int actionMasked = event.getActionMasked(); + + switch (actionMasked) { + case MotionEvent.ACTION_DOWN: { + mVelocityTracker.addMovement(ev); + + mLastMotionY = (int) event.getY(); + mActivePointerId = event.getPointerId(0); + startNestedScroll(ViewCompat.SCROLL_AXIS_VERTICAL); + break; + } + case MotionEvent.ACTION_MOVE: + final int activePointerIndex = event.findPointerIndex(mActivePointerId); + if (activePointerIndex == -1) { + break; + } + + final int y = (int) event.getY(activePointerIndex); + int deltaY = mLastMotionY - y; + + if (dispatchNestedPreScroll(0, deltaY, mScrollConsumed, mScrollOffset)) { + deltaY -= mScrollConsumed[1]; + event.offsetLocation(0, mScrollOffset[1]); + } + if (!mBeingDragged && Math.abs(mLastMotionY - y) > mTouchSlop) { + final ViewParent parent = getParent(); + if (parent != null) { + parent.requestDisallowInterceptTouchEvent(true); + } + mBeingDragged = true; + if (deltaY > 0) { + deltaY -= mTouchSlop; + } else { + deltaY += mTouchSlop; + } + } + + if (mBeingDragged) { + mVelocityTracker.addMovement(ev); + + mLastMotionY = y - mScrollOffset[1]; + if (dispatchNestedScroll(0, 0, 0, deltaY, mScrollOffset)) { + mLastMotionY -= mScrollOffset[1]; + event.offsetLocation(0, mScrollOffset[1]); + } + } + break; + case MotionEvent.ACTION_CANCEL: + case MotionEvent.ACTION_UP: + if (mBeingDragged) { + mVelocityTracker.computeCurrentVelocity(1000, mMaximumVelocity); + int initialVelocity = (int) mVelocityTracker.getYVelocity(mActivePointerId); + + if (Math.abs(initialVelocity) > mMinimumVelocity) { + flingWithNestedDispatch(-initialVelocity) ; + } + } + mActivePointerId = INVALID_POINTER; + endDrag(); + break; + case MotionEvent.ACTION_POINTER_DOWN: { + final int index = event.getActionIndex(); + mLastMotionY = (int) event.getY(index); + mActivePointerId = event.getPointerId(index); + break; + } + case MotionEvent.ACTION_POINTER_UP: + onSecondaryPointerUp(event); + mLastMotionY = (int) event.getY(event.findPointerIndex(mActivePointerId)); + break; + default: + break; + } + + event.recycle(); + return true; + } + + private void onSecondaryPointerUp(MotionEvent ev) { + final int pointerIndex = (ev.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK) + >> MotionEvent.ACTION_POINTER_INDEX_SHIFT; + final int pointerId = ev.getPointerId(pointerIndex); + if (pointerId == mActivePointerId) { + final int newPointerIndex = pointerIndex == 0 ? 1 : 0; + mLastMotionY = (int) ev.getY(newPointerIndex); + mActivePointerId = ev.getPointerId(newPointerIndex); + if (mVelocityTracker != null) { + mVelocityTracker.clear(); + } + } + } + + private void endDrag() { + mBeingDragged = false; + recycleVelocityTracker(); + stopNestedScroll(); + } + + private void flingWithNestedDispatch(int velocityY) { + if (!dispatchNestedPreFling(0, velocityY)) { + dispatchNestedFling(0, velocityY, true); + } + } + + private void initVelocityTrackerIfNotExists() { + if (mVelocityTracker == null) { + mVelocityTracker = VelocityTracker.obtain(); + } + } + + private void recycleVelocityTracker() { + if (mVelocityTracker != null) { + mVelocityTracker.recycle(); + mVelocityTracker = null; + } + } + + @Override + public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) { + if (disallowIntercept) { + recycleVelocityTracker(); + } + super.requestDisallowInterceptTouchEvent(disallowIntercept); + } + + // NestedScrollingChild + + @Override + public void setNestedScrollingEnabled(boolean enabled) { + mChildHelper.setNestedScrollingEnabled(enabled); + } + + @Override + public boolean isNestedScrollingEnabled() { + return mChildHelper.isNestedScrollingEnabled(); + } + + @Override + public boolean startNestedScroll(int axes) { + return mChildHelper.startNestedScroll(axes); + } + + @Override + public void stopNestedScroll() { + mChildHelper.stopNestedScroll(); + } + + @Override + public boolean hasNestedScrollingParent() { + return mChildHelper.hasNestedScrollingParent(); + } + + @Override + public boolean dispatchNestedScroll(int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed, int[] offsetInWindow) { + return mChildHelper.dispatchNestedScroll(dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, offsetInWindow); + } + + @Override + public boolean dispatchNestedPreScroll(int dx, int dy, int[] consumed, int[] offsetInWindow) { + return mChildHelper.dispatchNestedPreScroll(dx, dy, consumed, offsetInWindow); + } + + @Override + public boolean dispatchNestedFling(float velocityX, float velocityY, boolean consumed) { + return mChildHelper.dispatchNestedFling(velocityX, velocityY, consumed); + } + + @Override + public boolean dispatchNestedPreFling(float velocityX, float velocityY) { + return mChildHelper.dispatchNestedPreFling(velocityX, velocityY); + } + + // NestedScrollingParent + + @Override + public boolean onStartNestedScroll(@NonNull View child, @NonNull View target, int nestedScrollAxes) { + return (nestedScrollAxes & ViewCompat.SCROLL_AXIS_VERTICAL) != 0; + } + + @Override + public void onNestedScrollAccepted(@NonNull View child, @NonNull View target, int nestedScrollAxes) { + mParentHelper.onNestedScrollAccepted(child, target, nestedScrollAxes); + startNestedScroll(ViewCompat.SCROLL_AXIS_VERTICAL); + } + + @Override + public void onStopNestedScroll(@NonNull View target) { + mParentHelper.onStopNestedScroll(target); + stopNestedScroll(); + } + + @Override + public void onNestedScroll(@NonNull View target, int dxConsumed, int dyConsumed, int dxUnconsumed, + int dyUnconsumed) { + dispatchNestedScroll(dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, null); + } + + @Override + public void onNestedPreScroll(@NonNull View target, int dx, int dy, @NonNull int[] consumed) { + dispatchNestedPreScroll(dx, dy, consumed, null); + } + + @Override + public boolean onNestedFling(@NonNull View target, float velocityX, float velocityY, boolean consumed) { + return dispatchNestedFling(velocityX, velocityY, consumed); + } + + @Override + public boolean onNestedPreFling(@NonNull View target, float velocityX, float velocityY) { + return dispatchNestedPreFling(velocityX, velocityY); + } + + @Override + public int getNestedScrollAxes() { + return mParentHelper.getNestedScrollAxes(); + } +} \ No newline at end of file diff --git a/library/src/main/java/com/hjq/nested/scroll/layout/NestedScrollRelativeLayout.java b/library/src/main/java/com/hjq/nested/scroll/layout/NestedScrollRelativeLayout.java new file mode 100644 index 0000000..9336f69 --- /dev/null +++ b/library/src/main/java/com/hjq/nested/scroll/layout/NestedScrollRelativeLayout.java @@ -0,0 +1,286 @@ +package com.hjq.nested.scroll.layout; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.support.annotation.NonNull; +import android.support.v4.view.NestedScrollingChild; +import android.support.v4.view.NestedScrollingChildHelper; +import android.support.v4.view.NestedScrollingParent; +import android.support.v4.view.NestedScrollingParentHelper; +import android.support.v4.view.ViewCompat; +import android.util.AttributeSet; +import android.view.MotionEvent; +import android.view.VelocityTracker; +import android.view.View; +import android.view.ViewConfiguration; +import android.view.ViewParent; +import android.widget.RelativeLayout; + +/** + * author : Android 轮子哥 + * github : https://github.com/getActivity/NestedScrollLayout + * time : 2023/07/02 + * desc : 支持嵌套滚动的 FrameLayout + */ +public class NestedScrollRelativeLayout extends RelativeLayout implements NestedScrollingChild, NestedScrollingParent { + + private static final int INVALID_POINTER = -1; + + private final NestedScrollingChildHelper mChildHelper; + private final NestedScrollingParentHelper mParentHelper; + + private final int[] mScrollConsumed = new int[2]; + private final int[] mScrollOffset = new int[2]; + + private final float mTouchSlop; + private final float mMaximumVelocity; + private final float mMinimumVelocity; + + private int mLastMotionY; + private int mActivePointerId; + + private VelocityTracker mVelocityTracker; + + private boolean mBeingDragged; + + public NestedScrollRelativeLayout(Context context) { + this(context, null, 0); + } + + public NestedScrollRelativeLayout(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public NestedScrollRelativeLayout(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + setWillNotDraw(false); + mChildHelper = new NestedScrollingChildHelper(this); + mParentHelper = new NestedScrollingParentHelper(this); + setNestedScrollingEnabled(true); + mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop(); + mMaximumVelocity = ViewConfiguration.get(getContext()).getScaledMaximumFlingVelocity(); + mMinimumVelocity = ViewConfiguration.get(getContext()).getScaledMinimumFlingVelocity(); + } + + @SuppressLint("ClickableViewAccessibility") + @Override + public boolean onTouchEvent(MotionEvent ev) { + MotionEvent event = MotionEvent.obtain(ev); + initVelocityTrackerIfNotExists(); + final int actionMasked = event.getActionMasked(); + + switch (actionMasked) { + case MotionEvent.ACTION_DOWN: { + mVelocityTracker.addMovement(ev); + + mLastMotionY = (int) event.getY(); + mActivePointerId = event.getPointerId(0); + startNestedScroll(ViewCompat.SCROLL_AXIS_VERTICAL); + break; + } + case MotionEvent.ACTION_MOVE: + final int activePointerIndex = event.findPointerIndex(mActivePointerId); + if (activePointerIndex == -1) { + break; + } + + final int y = (int) event.getY(activePointerIndex); + int deltaY = mLastMotionY - y; + + if (dispatchNestedPreScroll(0, deltaY, mScrollConsumed, mScrollOffset)) { + deltaY -= mScrollConsumed[1]; + event.offsetLocation(0, mScrollOffset[1]); + } + if (!mBeingDragged && Math.abs(mLastMotionY - y) > mTouchSlop) { + final ViewParent parent = getParent(); + if (parent != null) { + parent.requestDisallowInterceptTouchEvent(true); + } + mBeingDragged = true; + if (deltaY > 0) { + deltaY -= mTouchSlop; + } else { + deltaY += mTouchSlop; + } + } + + if (mBeingDragged) { + mVelocityTracker.addMovement(ev); + + mLastMotionY = y - mScrollOffset[1]; + if (dispatchNestedScroll(0, 0, 0, deltaY, mScrollOffset)) { + mLastMotionY -= mScrollOffset[1]; + event.offsetLocation(0, mScrollOffset[1]); + } + } + break; + case MotionEvent.ACTION_CANCEL: + case MotionEvent.ACTION_UP: + if (mBeingDragged) { + mVelocityTracker.computeCurrentVelocity(1000, mMaximumVelocity); + int initialVelocity = (int) mVelocityTracker.getYVelocity(mActivePointerId); + + if (Math.abs(initialVelocity) > mMinimumVelocity) { + flingWithNestedDispatch(-initialVelocity) ; + } + } + mActivePointerId = INVALID_POINTER; + endDrag(); + break; + case MotionEvent.ACTION_POINTER_DOWN: { + final int index = event.getActionIndex(); + mLastMotionY = (int) event.getY(index); + mActivePointerId = event.getPointerId(index); + break; + } + case MotionEvent.ACTION_POINTER_UP: + onSecondaryPointerUp(event); + mLastMotionY = (int) event.getY(event.findPointerIndex(mActivePointerId)); + break; + default: + break; + } + + event.recycle(); + return true; + } + + private void onSecondaryPointerUp(MotionEvent ev) { + final int pointerIndex = (ev.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK) + >> MotionEvent.ACTION_POINTER_INDEX_SHIFT; + final int pointerId = ev.getPointerId(pointerIndex); + if (pointerId == mActivePointerId) { + final int newPointerIndex = pointerIndex == 0 ? 1 : 0; + mLastMotionY = (int) ev.getY(newPointerIndex); + mActivePointerId = ev.getPointerId(newPointerIndex); + if (mVelocityTracker != null) { + mVelocityTracker.clear(); + } + } + } + + private void endDrag() { + mBeingDragged = false; + recycleVelocityTracker(); + stopNestedScroll(); + } + + private void flingWithNestedDispatch(int velocityY) { + if (!dispatchNestedPreFling(0, velocityY)) { + dispatchNestedFling(0, velocityY, true); + } + } + + private void initVelocityTrackerIfNotExists() { + if (mVelocityTracker == null) { + mVelocityTracker = VelocityTracker.obtain(); + } + } + + private void recycleVelocityTracker() { + if (mVelocityTracker != null) { + mVelocityTracker.recycle(); + mVelocityTracker = null; + } + } + + @Override + public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) { + if (disallowIntercept) { + recycleVelocityTracker(); + } + super.requestDisallowInterceptTouchEvent(disallowIntercept); + } + + // NestedScrollingChild + + @Override + public void setNestedScrollingEnabled(boolean enabled) { + mChildHelper.setNestedScrollingEnabled(enabled); + } + + @Override + public boolean isNestedScrollingEnabled() { + return mChildHelper.isNestedScrollingEnabled(); + } + + @Override + public boolean startNestedScroll(int axes) { + return mChildHelper.startNestedScroll(axes); + } + + @Override + public void stopNestedScroll() { + mChildHelper.stopNestedScroll(); + } + + @Override + public boolean hasNestedScrollingParent() { + return mChildHelper.hasNestedScrollingParent(); + } + + @Override + public boolean dispatchNestedScroll(int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed, int[] offsetInWindow) { + return mChildHelper.dispatchNestedScroll(dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, offsetInWindow); + } + + @Override + public boolean dispatchNestedPreScroll(int dx, int dy, int[] consumed, int[] offsetInWindow) { + return mChildHelper.dispatchNestedPreScroll(dx, dy, consumed, offsetInWindow); + } + + @Override + public boolean dispatchNestedFling(float velocityX, float velocityY, boolean consumed) { + return mChildHelper.dispatchNestedFling(velocityX, velocityY, consumed); + } + + @Override + public boolean dispatchNestedPreFling(float velocityX, float velocityY) { + return mChildHelper.dispatchNestedPreFling(velocityX, velocityY); + } + + // NestedScrollingParent + + @Override + public boolean onStartNestedScroll(@NonNull View child, @NonNull View target, int nestedScrollAxes) { + return (nestedScrollAxes & ViewCompat.SCROLL_AXIS_VERTICAL) != 0; + } + + @Override + public void onNestedScrollAccepted(@NonNull View child, @NonNull View target, int nestedScrollAxes) { + mParentHelper.onNestedScrollAccepted(child, target, nestedScrollAxes); + startNestedScroll(ViewCompat.SCROLL_AXIS_VERTICAL); + } + + @Override + public void onStopNestedScroll(@NonNull View target) { + mParentHelper.onStopNestedScroll(target); + stopNestedScroll(); + } + + @Override + public void onNestedScroll(@NonNull View target, int dxConsumed, int dyConsumed, int dxUnconsumed, + int dyUnconsumed) { + dispatchNestedScroll(dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, null); + } + + @Override + public void onNestedPreScroll(@NonNull View target, int dx, int dy, @NonNull int[] consumed) { + dispatchNestedPreScroll(dx, dy, consumed, null); + } + + @Override + public boolean onNestedFling(@NonNull View target, float velocityX, float velocityY, boolean consumed) { + return dispatchNestedFling(velocityX, velocityY, consumed); + } + + @Override + public boolean onNestedPreFling(@NonNull View target, float velocityX, float velocityY) { + return dispatchNestedPreFling(velocityX, velocityY); + } + + @Override + public int getNestedScrollAxes() { + return mParentHelper.getNestedScrollAxes(); + } +} \ No newline at end of file diff --git a/library/src/main/java/com/hjq/nested/scroll/layout/NestedScrollViewPager.java b/library/src/main/java/com/hjq/nested/scroll/layout/NestedScrollViewPager.java new file mode 100644 index 0000000..4adc200 --- /dev/null +++ b/library/src/main/java/com/hjq/nested/scroll/layout/NestedScrollViewPager.java @@ -0,0 +1,124 @@ +package com.hjq.nested.scroll.layout; + +import android.content.Context; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.v4.view.NestedScrollingChild; +import android.support.v4.view.NestedScrollingChildHelper; +import android.support.v4.view.NestedScrollingParent; +import android.support.v4.view.NestedScrollingParentHelper; +import android.support.v4.view.ViewCompat; +import android.support.v4.view.ViewPager; +import android.util.AttributeSet; +import android.view.View; + +/** + * author : Android 轮子哥 + * github : https://github.com/getActivity/NestedScrollLayout + * time : 2023/07/02 + * desc : 支持嵌套滚动的 ViewPager + */ +public class NestedScrollViewPager extends ViewPager implements NestedScrollingParent, NestedScrollingChild { + + private final NestedScrollingParentHelper mParentHelper; + private final NestedScrollingChildHelper mChildHelper; + + public NestedScrollViewPager(@NonNull Context context) { + this(context, null); + } + + public NestedScrollViewPager(@NonNull Context context, @Nullable AttributeSet attrs) { + super(context, attrs); + + mParentHelper = new NestedScrollingParentHelper(this); + mChildHelper = new NestedScrollingChildHelper(this); + setNestedScrollingEnabled(true); + } + + @Override + public void setNestedScrollingEnabled(boolean enabled) { + mChildHelper.setNestedScrollingEnabled(enabled); + } + + @Override + public boolean isNestedScrollingEnabled() { + return mChildHelper.isNestedScrollingEnabled(); + } + + @Override + public boolean startNestedScroll(int axes) { + return mChildHelper.startNestedScroll(axes); + } + + @Override + public void stopNestedScroll() { + mChildHelper.stopNestedScroll(); + } + + @Override + public boolean hasNestedScrollingParent() { + return mChildHelper.hasNestedScrollingParent(); + } + + @Override + public boolean dispatchNestedScroll(int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed, int[] offsetInWindow) { + return mChildHelper.dispatchNestedScroll(dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, offsetInWindow); + } + + @Override + public boolean dispatchNestedPreScroll(int dx, int dy, int[] consumed, int[] offsetInWindow) { + return mChildHelper.dispatchNestedPreScroll(dx, dy, consumed, offsetInWindow); + } + + @Override + public boolean dispatchNestedFling(float velocityX, float velocityY, boolean consumed) { + return mChildHelper.dispatchNestedFling(velocityX, velocityY, consumed); + } + + @Override + public boolean dispatchNestedPreFling(float velocityX, float velocityY) { + return mChildHelper.dispatchNestedPreFling(velocityX, velocityY); + } + + @Override + public boolean onStartNestedScroll(@NonNull View child, @NonNull View target, int nestedScrollAxes) { + return (nestedScrollAxes & ViewCompat.SCROLL_AXIS_VERTICAL) != 0; + } + + @Override + public void onNestedScrollAccepted(@NonNull View child, @NonNull View target, int nestedScrollAxes) { + mParentHelper.onNestedScrollAccepted(child, target, nestedScrollAxes); + startNestedScroll(ViewCompat.SCROLL_AXIS_VERTICAL); + } + + @Override + public void onStopNestedScroll(@NonNull View target) { + mParentHelper.onStopNestedScroll(target); + stopNestedScroll(); + } + + @Override + public void onNestedScroll(@NonNull View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed) { + dispatchNestedScroll(dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, null); + } + + @Override + public void onNestedPreScroll(@NonNull View target, int dx, int dy, @NonNull int[] consumed) { + dispatchNestedPreScroll(dx, dy, consumed, null); + } + + @Override + public boolean onNestedFling(@NonNull View target, float velocityX, float velocityY, boolean consumed) { + return dispatchNestedFling(velocityX, velocityY, consumed); + } + + @Override + public boolean onNestedPreFling(@NonNull View target, float velocityX, float velocityY) { + return dispatchNestedPreFling(velocityX, velocityY); + } + + @Override + public int getNestedScrollAxes() { + return mParentHelper.getNestedScrollAxes(); + } +} \ No newline at end of file diff --git a/NestedScrollWebView.java b/library/src/main/java/com/hjq/nested/scroll/layout/NestedScrollWebView.java similarity index 81% rename from NestedScrollWebView.java rename to library/src/main/java/com/hjq/nested/scroll/layout/NestedScrollWebView.java index 76452da..8a26428 100644 --- a/NestedScrollWebView.java +++ b/library/src/main/java/com/hjq/nested/scroll/layout/NestedScrollWebView.java @@ -1,9 +1,7 @@ -package com.hjq.md.widget; +package com.hjq.nested.scroll.layout; import android.annotation.SuppressLint; import android.content.Context; -import android.os.Build; -import android.support.annotation.RequiresApi; import android.support.v4.view.NestedScrollingChild; import android.support.v4.view.NestedScrollingChildHelper; import android.support.v4.view.ViewCompat; @@ -12,51 +10,42 @@ import android.webkit.WebView; /** - * NestedScrollWebView + * author : Android 轮子哥 + * github : https://github.com/getActivity/NestedScrollLayout + * time : 2020/08/18 + * desc : 支持嵌套滚动的 WebView */ public class NestedScrollWebView extends WebView implements NestedScrollingChild { - private NestedScrollingChildHelper mChildHelper; + private final NestedScrollingChildHelper mChildHelper; private int mLastMotionY; private final int[] mScrollOffset = new int[2]; private final int[] mScrollConsumed = new int[2]; - private int mNestedYOffset; + private int mNestedOffsetY; private boolean mChange; public NestedScrollWebView(Context context) { super(context); - init(); } public NestedScrollWebView(Context context, AttributeSet attrs) { super(context, attrs); - init(); } public NestedScrollWebView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); - init(); } - @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) public NestedScrollWebView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); - init(); } - public NestedScrollWebView(Context context, AttributeSet attrs, int defStyleAttr, boolean privateBrowsing) { - super(context, attrs, defStyleAttr, privateBrowsing); - init(); - } - - private void init() { - if (mChildHelper == null) { - mChildHelper = new NestedScrollingChildHelper(this); - setNestedScrollingEnabled(true); - } + { + mChildHelper = new NestedScrollingChildHelper(this); + setNestedScrollingEnabled(true); } @SuppressLint("ClickableViewAccessibility") @@ -69,12 +58,12 @@ public boolean onTouchEvent(MotionEvent event) { final int action = event.getActionMasked(); if (action == MotionEvent.ACTION_DOWN) { - mNestedYOffset = 0; + mNestedOffsetY = 0; } int y = (int) event.getY(); - event.offsetLocation(0, mNestedYOffset); + event.offsetLocation(0, mNestedOffsetY); switch (action) { case MotionEvent.ACTION_DOWN: @@ -89,7 +78,7 @@ public boolean onTouchEvent(MotionEvent event) { if (dispatchNestedPreScroll(0, deltaY, mScrollConsumed, mScrollOffset)) { deltaY -= mScrollConsumed[1]; trackedEvent.offsetLocation(0, mScrollOffset[1]); - mNestedYOffset += mScrollOffset[1]; + mNestedOffsetY += mScrollOffset[1]; } mLastMotionY = y - mScrollOffset[1]; @@ -102,20 +91,20 @@ public boolean onTouchEvent(MotionEvent event) { if (dispatchNestedScroll(0, dyConsumed, 0, dyUnconsumed, mScrollOffset)) { mLastMotionY -= mScrollOffset[1]; trackedEvent.offsetLocation(0, mScrollOffset[1]); - mNestedYOffset += mScrollOffset[1]; + mNestedOffsetY += mScrollOffset[1]; } - if(mScrollConsumed[1]==0 && mScrollOffset[1]==0) { - if(mChange){ + if(mScrollConsumed[1] == 0 && mScrollOffset[1] == 0) { + if(mChange) { mChange =false; trackedEvent.setAction(MotionEvent.ACTION_DOWN); super.onTouchEvent(trackedEvent); - }else { + } else { result = super.onTouchEvent(trackedEvent); } trackedEvent.recycle(); - }else{ - if(!mChange){ + } else{ + if(!mChange) { mChange = true; super.onTouchEvent(MotionEvent.obtain(0,0,MotionEvent.ACTION_CANCEL,0,0,0)); } @@ -125,16 +114,21 @@ public boolean onTouchEvent(MotionEvent event) { //trackedEvent.recycle(); break; case MotionEvent.ACTION_POINTER_DOWN: + case MotionEvent.ACTION_POINTER_UP: case MotionEvent.ACTION_UP: case MotionEvent.ACTION_CANCEL: stopNestedScroll(); result = super.onTouchEvent(event); break; + default: + break; } return result; } - // NestedScrollingChild + /** + * {@link NestedScrollingChild} + */ @Override public void setNestedScrollingEnabled(boolean enabled) { diff --git a/logo.png b/logo.png new file mode 100644 index 0000000..652e5ac Binary files /dev/null and b/logo.png differ diff --git a/picture/download_demo_apk_qr_code.png b/picture/download_demo_apk_qr_code.png new file mode 100644 index 0000000..eed0374 Binary files /dev/null and b/picture/download_demo_apk_qr_code.png differ diff --git a/picture/dynamic_figure.gif b/picture/dynamic_figure.gif new file mode 100644 index 0000000..298f259 Binary files /dev/null and b/picture/dynamic_figure.gif differ diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 0000000..3306997 --- /dev/null +++ b/settings.gradle @@ -0,0 +1 @@ +include ':app', ':library'