├── app
├── .gitignore
├── src
│ ├── main
│ │ ├── res
│ │ │ ├── values
│ │ │ │ ├── strings.xml
│ │ │ │ ├── colors.xml
│ │ │ │ └── styles.xml
│ │ │ ├── mipmap-xhdpi
│ │ │ │ ├── bg_hot.png
│ │ │ │ ├── ic_drag.png
│ │ │ │ ├── ic_launcher.png
│ │ │ │ └── ic_launcher_round.png
│ │ │ ├── mipmap-hdpi
│ │ │ │ ├── ic_launcher.png
│ │ │ │ └── ic_launcher_round.png
│ │ │ ├── mipmap-mdpi
│ │ │ │ ├── ic_launcher.png
│ │ │ │ └── ic_launcher_round.png
│ │ │ ├── mipmap-xxhdpi
│ │ │ │ ├── ic_launcher.png
│ │ │ │ └── ic_launcher_round.png
│ │ │ ├── mipmap-xxxhdpi
│ │ │ │ ├── ic_launcher.png
│ │ │ │ └── ic_launcher_round.png
│ │ │ ├── drawable
│ │ │ │ ├── bg_border.xml
│ │ │ │ └── ic_launcher_background.xml
│ │ │ ├── mipmap-anydpi-v26
│ │ │ │ ├── ic_launcher.xml
│ │ │ │ └── ic_launcher_round.xml
│ │ │ ├── layout
│ │ │ │ ├── fragment_tab3.xml
│ │ │ │ ├── fragment_tab1.xml
│ │ │ │ ├── item_my.xml
│ │ │ │ ├── item_dragger.xml
│ │ │ │ ├── activity_example_webview.xml
│ │ │ │ ├── activity_recycler.xml
│ │ │ │ ├── fragment_tab2.xml
│ │ │ │ ├── activity_example_webview_refresh.xml
│ │ │ │ ├── activity_example5.xml
│ │ │ │ ├── activity_main.xml
│ │ │ │ ├── activity_nested_scroll.xml
│ │ │ │ ├── activity_three.xml
│ │ │ │ ├── activity_example1.xml
│ │ │ │ ├── activity_example2.xml
│ │ │ │ ├── activity_example3.xml
│ │ │ │ └── activity_example4.xml
│ │ │ └── drawable-v24
│ │ │ │ └── ic_launcher_foreground.xml
│ │ ├── java
│ │ │ └── com
│ │ │ │ └── fc
│ │ │ │ └── nestedscrollview
│ │ │ │ └── example
│ │ │ │ ├── Example3Activity.kt
│ │ │ │ ├── NestedScrollNestedScrollActivity.kt
│ │ │ │ ├── NestedScrollRecyclerViewActivity.kt
│ │ │ │ ├── fragment
│ │ │ │ ├── Tab2Fragment.kt
│ │ │ │ ├── Tab3Fragment.kt
│ │ │ │ └── Tab1Fragment.kt
│ │ │ │ ├── ThreeNestedActivity.kt
│ │ │ │ ├── WebViewExampleActivity.kt
│ │ │ │ ├── WebViewRefreshExampleActivity.kt
│ │ │ │ ├── MyDraggerRecyclerAdapter.kt
│ │ │ │ ├── MyRecyclerAdapter.kt
│ │ │ │ ├── MainActivity.kt
│ │ │ │ ├── Example4Activity.kt
│ │ │ │ ├── Example1Activity.kt
│ │ │ │ ├── Example2Activity.kt
│ │ │ │ └── Example5Activity.kt
│ │ └── AndroidManifest.xml
│ ├── test
│ │ └── java
│ │ │ └── com
│ │ │ └── fc
│ │ │ └── nestedscrollview
│ │ │ └── ExampleUnitTest.kt
│ └── androidTest
│ │ └── java
│ │ └── com
│ │ └── fc
│ │ └── nestedscrollview
│ │ └── ExampleInstrumentedTest.kt
├── proguard-rules.pro
└── build.gradle
├── library
├── .gitignore
├── src
│ ├── main
│ │ ├── res
│ │ │ └── values
│ │ │ │ ├── strings.xml
│ │ │ │ └── attrs.xml
│ │ ├── AndroidManifest.xml
│ │ └── java
│ │ │ └── com
│ │ │ └── fc
│ │ │ └── nestedscrollview
│ │ │ ├── OnScrollStateListener.java
│ │ │ ├── FCFlingView.java
│ │ │ ├── FcNestedUtil.java
│ │ │ ├── FCSwipeRefreshLayout.java
│ │ │ ├── FCRecyclerView.java
│ │ │ ├── FCWebView.java
│ │ │ └── FCNestedScrollView.java
│ └── test
│ │ └── java
│ │ └── com
│ │ └── fc
│ │ └── nestedscrollview
│ │ └── ExampleUnitTest.java
├── proguard-rules.pro
└── build.gradle
├── settings.gradle
├── 123.gif
├── 222.gif
├── 333.gif
├── 444.gif
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── .gitignore
├── gradle.properties
├── gradlew.bat
├── README.md
└── gradlew
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/library/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app', ':library'
2 |
--------------------------------------------------------------------------------
/123.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/403462630/FCNestedScrollView/HEAD/123.gif
--------------------------------------------------------------------------------
/222.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/403462630/FCNestedScrollView/HEAD/222.gif
--------------------------------------------------------------------------------
/333.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/403462630/FCNestedScrollView/HEAD/333.gif
--------------------------------------------------------------------------------
/444.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/403462630/FCNestedScrollView/HEAD/444.gif
--------------------------------------------------------------------------------
/library/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | library
3 |
4 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | FCNestedScrollView
3 |
4 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/403462630/FCNestedScrollView/HEAD/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/bg_hot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/403462630/FCNestedScrollView/HEAD/app/src/main/res/mipmap-xhdpi/bg_hot.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_drag.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/403462630/FCNestedScrollView/HEAD/app/src/main/res/mipmap-xhdpi/ic_drag.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/403462630/FCNestedScrollView/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/403462630/FCNestedScrollView/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/403462630/FCNestedScrollView/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/403462630/FCNestedScrollView/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/403462630/FCNestedScrollView/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/library/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/403462630/FCNestedScrollView/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/403462630/FCNestedScrollView/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/403462630/FCNestedScrollView/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/403462630/FCNestedScrollView/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/403462630/FCNestedScrollView/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | /local.properties
4 | /.idea/workspace.xml
5 | /.idea/libraries
6 | .DS_Store
7 | /build
8 | /captures
9 | .externalNativeBuild
10 | .idea/
11 |
--------------------------------------------------------------------------------
/library/src/main/java/com/fc/nestedscrollview/OnScrollStateListener.java:
--------------------------------------------------------------------------------
1 | package com.fc.nestedscrollview;
2 |
3 | public interface OnScrollStateListener {
4 | public void onScrollStart();
5 | public void onScrollEnd();
6 | }
--------------------------------------------------------------------------------
/app/src/main/res/drawable/bg_border.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #3F51B5
4 | #303F9F
5 | #FF4081
6 |
7 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Wed Mar 28 15:38:04 CST 2018
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-6.5-all.zip
7 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/library/src/main/java/com/fc/nestedscrollview/FCFlingView.java:
--------------------------------------------------------------------------------
1 | package com.fc.nestedscrollview;
2 |
3 | public interface FCFlingView {
4 |
5 | /**
6 | * 判断是否能滚动
7 | * @param direction < 0 向下滚,> 0 向上滚
8 | * @return
9 | */
10 | public boolean canFling(int direction);
11 |
12 | /**
13 | * 滚动实现
14 | * @param velocityY 加速度
15 | */
16 | public void fling(int velocityY);
17 | }
18 |
--------------------------------------------------------------------------------
/app/src/main/java/com/fc/nestedscrollview/example/Example3Activity.kt:
--------------------------------------------------------------------------------
1 | package com.fc.nestedscrollview.example
2 |
3 | import android.os.Bundle
4 | import androidx.appcompat.app.AppCompatActivity
5 |
6 | class Example3Activity : AppCompatActivity() {
7 |
8 | override fun onCreate(savedInstanceState: Bundle?) {
9 | super.onCreate(savedInstanceState)
10 | setContentView(R.layout.activity_example3)
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/app/src/main/java/com/fc/nestedscrollview/example/NestedScrollNestedScrollActivity.kt:
--------------------------------------------------------------------------------
1 | package com.fc.nestedscrollview.example
2 |
3 | import android.os.Bundle
4 | import androidx.appcompat.app.AppCompatActivity
5 |
6 | class NestedScrollNestedScrollActivity : AppCompatActivity() {
7 |
8 | override fun onCreate(savedInstanceState: Bundle?) {
9 | super.onCreate(savedInstanceState)
10 | setContentView(R.layout.activity_nested_scroll)
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/app/src/test/java/com/fc/nestedscrollview/ExampleUnitTest.kt:
--------------------------------------------------------------------------------
1 | package com.fc.nestedscrollview
2 |
3 | import org.junit.Test
4 |
5 | import org.junit.Assert.*
6 |
7 | /**
8 | * Example local unit test, which will execute on the development machine (host).
9 | *
10 | * See [testing documentation](http://d.android.com/tools/testing).
11 | */
12 | class ExampleUnitTest {
13 | @Test
14 | fun addition_isCorrect() {
15 | assertEquals(4, 2 + 2)
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/library/src/test/java/com/fc/nestedscrollview/ExampleUnitTest.java:
--------------------------------------------------------------------------------
1 | package com.fc.nestedscrollview;
2 |
3 | import org.junit.Test;
4 |
5 | import static org.junit.Assert.*;
6 |
7 | /**
8 | * Example local unit test, which will execute on the development machine (host).
9 | *
10 | * @see Testing documentation
11 | */
12 | public class ExampleUnitTest {
13 | @Test
14 | public void addition_isCorrect() throws Exception {
15 | assertEquals(4, 2 + 2);
16 | }
17 | }
--------------------------------------------------------------------------------
/app/src/main/res/layout/fragment_tab3.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/fragment_tab1.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/item_my.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
14 |
15 |
19 |
--------------------------------------------------------------------------------
/app/src/androidTest/java/com/fc/nestedscrollview/ExampleInstrumentedTest.kt:
--------------------------------------------------------------------------------
1 | package com.fc.nestedscrollview
2 |
3 |
4 | import androidx.test.InstrumentationRegistry
5 | import androidx.test.runner.AndroidJUnit4
6 | import org.junit.Test
7 | import org.junit.runner.RunWith
8 |
9 | import org.junit.Assert.*
10 |
11 | /**
12 | * Instrumented test, which will execute on an Android device.
13 | *
14 | * See [testing documentation](http://d.android.com/tools/testing).
15 | */
16 | @RunWith(AndroidJUnit4::class)
17 | class ExampleInstrumentedTest {
18 | @Test
19 | fun useAppContext() {
20 | // Context of the app under test.
21 | val appContext = InstrumentationRegistry.getTargetContext()
22 | assertEquals("com.fc.nestedscrollview", appContext.packageName)
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 |
3 | # IDE (e.g. Android Studio) users:
4 | # Gradle settings configured through the IDE *will override*
5 | # any settings specified in this file.
6 |
7 | # For more details on how to configure your build environment visit
8 | # http://www.gradle.org/docs/current/userguide/build_environment.html
9 |
10 | # Specifies the JVM arguments used for the daemon process.
11 | # The setting is particularly useful for tweaking memory settings.
12 | org.gradle.jvmargs=-Xmx1536m
13 |
14 | # When configured, Gradle will run in incubating parallel mode.
15 | # This option should only be used with decoupled projects. More details, visit
16 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
17 | # org.gradle.parallel=true
18 | android.useAndroidX=true
19 | android.enableJetifier=true
20 |
--------------------------------------------------------------------------------
/app/src/main/java/com/fc/nestedscrollview/example/NestedScrollRecyclerViewActivity.kt:
--------------------------------------------------------------------------------
1 | package com.fc.nestedscrollview.example
2 |
3 | import android.os.Bundle
4 | import androidx.appcompat.app.AppCompatActivity
5 | import androidx.recyclerview.widget.LinearLayoutManager
6 | import kotlinx.android.synthetic.main.activity_recycler.*
7 |
8 | class NestedScrollRecyclerViewActivity : AppCompatActivity() {
9 |
10 | private var myAdapter : MyRecyclerAdapter? = null
11 |
12 | override fun onCreate(savedInstanceState: Bundle?) {
13 | super.onCreate(savedInstanceState)
14 | setContentView(R.layout.activity_recycler)
15 | myAdapter = MyRecyclerAdapter("item")
16 | recycler_view.apply {
17 | layoutManager = LinearLayoutManager(this@NestedScrollRecyclerViewActivity)
18 | adapter = myAdapter
19 | }
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/app/src/main/java/com/fc/nestedscrollview/example/fragment/Tab2Fragment.kt:
--------------------------------------------------------------------------------
1 | package com.fc.nestedscrollview.example.fragment
2 |
3 | import android.os.Bundle
4 | import android.view.LayoutInflater
5 | import android.view.View
6 | import android.view.ViewGroup
7 | import androidx.fragment.app.Fragment
8 | import com.fc.nestedscrollview.example.R
9 | import kotlinx.android.synthetic.main.fragment_tab2.*
10 |
11 | /**
12 | * Created by fangcan on 2018/4/2.
13 | */
14 | class Tab2Fragment : Fragment() {
15 |
16 | override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
17 | return inflater!!.inflate(R.layout.fragment_tab2, container, false)
18 | }
19 |
20 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
21 | super.onViewCreated(view, savedInstanceState)
22 | recycler_view.logId = "child"
23 | recycler_view.enableLog = true
24 | }
25 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/fc/nestedscrollview/example/ThreeNestedActivity.kt:
--------------------------------------------------------------------------------
1 | package com.fc.nestedscrollview.example
2 |
3 | import android.os.Bundle
4 | import androidx.appcompat.app.AppCompatActivity
5 | import androidx.recyclerview.widget.LinearLayoutManager
6 | import kotlinx.android.synthetic.main.activity_three.*
7 |
8 | class ThreeNestedActivity : AppCompatActivity() {
9 |
10 | private var myAdapter : MyRecyclerAdapter? = null
11 |
12 | override fun onCreate(savedInstanceState: Bundle?) {
13 | super.onCreate(savedInstanceState)
14 | setContentView(R.layout.activity_three)
15 | myAdapter = MyRecyclerAdapter("item")
16 | recycler_view.apply {
17 | layoutManager = LinearLayoutManager(this@ThreeNestedActivity)
18 | adapter = myAdapter
19 | }
20 | nested_scroll_view.setLinkChildView(nested_scroll_view_child)
21 | nested_scroll_view_child.setLinkChildView(recycler_view)
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/app/src/main/java/com/fc/nestedscrollview/example/WebViewExampleActivity.kt:
--------------------------------------------------------------------------------
1 | package com.fc.nestedscrollview.example
2 |
3 | import android.annotation.SuppressLint
4 | import android.os.Bundle
5 | import android.webkit.WebChromeClient
6 | import android.webkit.WebViewClient
7 | import androidx.appcompat.app.AppCompatActivity
8 | import kotlinx.android.synthetic.main.activity_example_webview.*
9 |
10 | class WebViewExampleActivity : AppCompatActivity() {
11 |
12 | @SuppressLint("SetJavaScriptEnabled")
13 | override fun onCreate(savedInstanceState: Bundle?) {
14 | super.onCreate(savedInstanceState)
15 | setContentView(R.layout.activity_example_webview)
16 | web_view.webChromeClient = WebChromeClient()
17 | web_view.webViewClient = WebViewClient()
18 | web_view.settings.builtInZoomControls = true
19 | web_view.settings.javaScriptEnabled = true
20 | web_view.settings.domStorageEnabled = true
21 | web_view.loadUrl("http://www.baidu.com/")
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
22 | -keep class android.support.v7.widget.RecyclerView{*;}
23 | -keep class android.support.v4.widget.NestedScrollView{*;}
24 |
25 | -keep class androidx.recyclerview.widget.RecyclerView{*;}
26 | -keep class androidx.core.widget.NestedScrollView{*;}
27 |
28 | #-keep class android.widget.OverScroller{*;}
--------------------------------------------------------------------------------
/app/src/main/java/com/fc/nestedscrollview/example/WebViewRefreshExampleActivity.kt:
--------------------------------------------------------------------------------
1 | package com.fc.nestedscrollview.example
2 |
3 | import android.annotation.SuppressLint
4 | import android.os.Bundle
5 | import android.webkit.WebChromeClient
6 | import android.webkit.WebViewClient
7 | import androidx.appcompat.app.AppCompatActivity
8 | import kotlinx.android.synthetic.main.activity_example_webview_refresh.*
9 |
10 | class WebViewRefreshExampleActivity : AppCompatActivity() {
11 |
12 | @SuppressLint("SetJavaScriptEnabled")
13 | override fun onCreate(savedInstanceState: Bundle?) {
14 | super.onCreate(savedInstanceState)
15 | setContentView(R.layout.activity_example_webview_refresh)
16 | web_view.webChromeClient = WebChromeClient()
17 | web_view.webViewClient = WebViewClient()
18 | web_view.settings.builtInZoomControls = true
19 | web_view.settings.javaScriptEnabled = true
20 | web_view.settings.domStorageEnabled = true
21 | web_view.loadUrl("http://www.baidu.com/")
22 |
23 | swipe_refresh.setOnRefreshListener {
24 | swipe_refresh.postDelayed({
25 | swipe_refresh.isRefreshing = false
26 | }, 5_000)
27 | }
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/app/src/main/java/com/fc/nestedscrollview/example/MyDraggerRecyclerAdapter.kt:
--------------------------------------------------------------------------------
1 | package com.fc.nestedscrollview.example
2 |
3 | import android.view.View
4 | import com.chad.library.adapter.base.BaseItemDraggableAdapter
5 | import com.chad.library.adapter.base.BaseViewHolder
6 | import kotlinx.android.extensions.ContainerOptions
7 | import kotlinx.android.extensions.LayoutContainer
8 | import kotlinx.android.synthetic.main.item_dragger.*
9 |
10 | /**
11 | * Created by fangcan on 2018/3/28.
12 | */
13 | class MyDraggerRecyclerAdapter(data: List) : BaseItemDraggableAdapter(R.layout.item_dragger, data) {
14 |
15 | override fun convert(helper: MyViewHolder, item: String?) {
16 | helper!!.setText(R.id.tv_content, item)
17 | }
18 |
19 | override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
20 | super.onBindViewHolder(holder, position)
21 | (holder as MyViewHolder).bindData(position, getItem(position)!!)
22 | }
23 |
24 |
25 | @ContainerOptions
26 | class MyViewHolder(override val containerView: View?) : BaseViewHolder(containerView), LayoutContainer {
27 |
28 | fun bindData(position: Int, text: String) {
29 | tv_content.text = text
30 | }
31 | }
32 | }
--------------------------------------------------------------------------------
/library/src/main/res/values/attrs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/app/src/main/java/com/fc/nestedscrollview/example/MyRecyclerAdapter.kt:
--------------------------------------------------------------------------------
1 | package com.fc.nestedscrollview.example
2 |
3 | import android.view.LayoutInflater
4 | import android.view.View
5 | import android.view.ViewGroup
6 | import androidx.recyclerview.widget.RecyclerView
7 | import kotlinx.android.extensions.ContainerOptions
8 | import kotlinx.android.extensions.LayoutContainer
9 | import kotlinx.android.synthetic.main.item_my.*
10 |
11 | /**
12 | * Created by fangcan on 2018/3/28.
13 | */
14 | class MyRecyclerAdapter(var text: String) : RecyclerView.Adapter() {
15 |
16 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
17 | return MyViewHolder(LayoutInflater.from(parent!!.context).inflate(R.layout.item_my, parent, false))
18 | }
19 |
20 | override fun getItemCount(): Int {
21 | return 20
22 | }
23 |
24 | override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
25 | (holder as MyViewHolder).bindData(position, text)
26 | }
27 |
28 |
29 | @ContainerOptions
30 | class MyViewHolder(override val containerView: View) : RecyclerView.ViewHolder(containerView), LayoutContainer {
31 |
32 | fun bindData(position: Int, text: String) {
33 | tv_text.text = text + "_" + position
34 | }
35 | }
36 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/fc/nestedscrollview/example/fragment/Tab3Fragment.kt:
--------------------------------------------------------------------------------
1 | package com.fc.nestedscrollview.example.fragment
2 |
3 | import android.os.Bundle
4 | import android.view.LayoutInflater
5 | import android.view.View
6 | import android.view.ViewGroup
7 | import android.webkit.WebChromeClient
8 | import android.webkit.WebViewClient
9 | import androidx.fragment.app.Fragment
10 | import com.fc.nestedscrollview.example.R
11 | import kotlinx.android.synthetic.main.fragment_tab3.*
12 |
13 | /**
14 | * Created by fangcan on 2018/4/2.
15 | */
16 | class Tab3Fragment : Fragment() {
17 |
18 | override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
19 | return inflater!!.inflate(R.layout.fragment_tab3, container, false)
20 | }
21 |
22 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
23 | super.onViewCreated(view, savedInstanceState)
24 |
25 | web_view.webChromeClient = WebChromeClient()
26 | web_view.webViewClient = WebViewClient()
27 | web_view.settings.builtInZoomControls = true
28 | web_view.settings.javaScriptEnabled = true
29 | web_view.settings.domStorageEnabled = true
30 | web_view.settings.setSupportZoom(false)
31 | web_view.setOnLongClickListener { true }
32 | web_view.loadUrl("http://news.baidu.com/")
33 | }
34 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/fc/nestedscrollview/example/MainActivity.kt:
--------------------------------------------------------------------------------
1 | package com.fc.nestedscrollview.example
2 |
3 | import android.content.Intent
4 | import android.os.Bundle
5 | import android.view.View
6 | import androidx.appcompat.app.AppCompatActivity
7 |
8 | class MainActivity : AppCompatActivity() {
9 |
10 | override fun onCreate(savedInstanceState: Bundle?) {
11 | super.onCreate(savedInstanceState)
12 | setContentView(R.layout.activity_main)
13 | }
14 |
15 | fun gotoExample1(view: View) {
16 | startActivity(Intent(this, Example1Activity::class.java))
17 | }
18 |
19 | fun gotoExample2(view: View) {
20 | startActivity(Intent(this, Example2Activity::class.java))
21 | }
22 |
23 | fun gotoExample3(view: View) {
24 | startActivity(Intent(this, Example3Activity::class.java))
25 | }
26 |
27 | fun gotoExample4(view: View) {
28 | startActivity(Intent(this, Example4Activity::class.java))
29 | }
30 |
31 | fun gotoExample5(view: View) {
32 | startActivity(Intent(this, Example5Activity::class.java))
33 | }
34 |
35 | fun gotoWebviewExample(view: View) {
36 | startActivity(Intent(this, WebViewExampleActivity::class.java))
37 | }
38 |
39 | fun gotoWebviewRefreshExample(view: View) {
40 | startActivity(Intent(this, WebViewRefreshExampleActivity::class.java))
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/app/src/main/java/com/fc/nestedscrollview/example/Example4Activity.kt:
--------------------------------------------------------------------------------
1 | package com.fc.nestedscrollview.example
2 |
3 | import android.os.Bundle
4 | import androidx.appcompat.app.AppCompatActivity
5 | import kotlinx.android.synthetic.main.activity_example4.*
6 |
7 | class Example4Activity : AppCompatActivity() {
8 |
9 | override fun onCreate(savedInstanceState: Bundle?) {
10 | super.onCreate(savedInstanceState)
11 | setContentView(R.layout.activity_example4)
12 |
13 | swipe_refresh.setOnRefreshListener {
14 | swipe_refresh.postDelayed({
15 | swipe_refresh.isRefreshing = false
16 | }, 5_000)
17 | }
18 |
19 | swipe_refresh1.setOnRefreshListener {
20 | swipe_refresh1.postDelayed({
21 | swipe_refresh1.isRefreshing = false
22 | }, 5_000)
23 | }
24 |
25 | swipe_refresh2.setOnRefreshListener {
26 | swipe_refresh2.postDelayed({
27 | swipe_refresh2.isRefreshing = false
28 | }, 5_000)
29 | }
30 |
31 | swipe_refresh3.setOnRefreshListener {
32 | swipe_refresh3.postDelayed({
33 | swipe_refresh3.isRefreshing = false
34 | }, 5_000)
35 | }
36 |
37 | swipe_refresh4.setOnRefreshListener {
38 | swipe_refresh4.postDelayed({
39 | swipe_refresh4.isRefreshing = false
40 | }, 5_000)
41 | }
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/item_dragger.xml:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
18 |
19 |
30 |
31 |
40 |
--------------------------------------------------------------------------------
/library/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.library'
2 | apply plugin: 'com.github.dcendents.android-maven'
3 |
4 | group="com.github.403462630"
5 |
6 | android {
7 | compileSdkVersion 28
8 |
9 | defaultConfig {
10 | minSdkVersion 16
11 | targetSdkVersion 26
12 | versionCode 1
13 | versionName "1.0"
14 |
15 | }
16 |
17 | buildTypes {
18 | release {
19 | minifyEnabled false
20 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
21 | }
22 | }
23 |
24 | compileOptions {
25 | sourceCompatibility JavaVersion.VERSION_1_8
26 | targetCompatibility JavaVersion.VERSION_1_8
27 | }
28 | }
29 |
30 | //task sourcesJar(type: Jar) {
31 | // from android.sourceSets.main.java.srcDirs
32 | // classifier = 'sources'
33 | //}
34 | //
35 | //task javadoc(type: Javadoc) {
36 | // options.encoding = "utf-8"
37 | // source = android.sourceSets.main.java.srcDirs
38 | // classpath += project.files(android.getBootClasspath().join(File.pathSeparator))
39 | //}
40 | //
41 | //task javadocJar(type: Jar, dependsOn: javadoc) {
42 | // classifier = 'javadoc'
43 | // from javadoc.destinationDir
44 | //}
45 | //
46 | //artifacts {
47 | //// archives javadocJar
48 | // archives sourcesJar
49 | //}
50 |
51 | dependencies {
52 | implementation fileTree(dir: 'libs', include: ['*.jar'])
53 | testImplementation 'junit:junit:4.13.2'
54 |
55 | compileOnly 'androidx.appcompat:appcompat:1.2.0'
56 | implementation 'androidx.recyclerview:recyclerview:1.1.0'
57 | implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'
58 | }
59 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_example_webview.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
13 |
14 |
18 |
19 |
20 |
21 |
25 |
26 |
32 |
33 |
34 |
39 |
40 |
41 |
42 |
43 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_recycler.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
13 |
14 |
18 |
19 |
20 |
21 |
25 |
26 |
32 |
33 |
34 |
39 |
40 |
41 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/fragment_tab2.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
12 |
13 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/app/src/main/java/com/fc/nestedscrollview/example/Example1Activity.kt:
--------------------------------------------------------------------------------
1 | package com.fc.nestedscrollview.example
2 |
3 | import android.os.Bundle
4 | import androidx.appcompat.app.AppCompatActivity
5 | import androidx.recyclerview.widget.LinearLayoutManager
6 | import kotlinx.android.synthetic.main.activity_example1.*
7 |
8 | class Example1Activity : AppCompatActivity() {
9 |
10 | private var myAdapter1 : MyRecyclerAdapter? = null
11 | private var myAdapter2 : MyRecyclerAdapter? = null
12 | private var myAdapter3 : MyRecyclerAdapter? = null
13 | private var myAdapter4 : MyRecyclerAdapter? = null
14 |
15 | override fun onCreate(savedInstanceState: Bundle?) {
16 | super.onCreate(savedInstanceState)
17 | setContentView(R.layout.activity_example1)
18 | myAdapter1 = MyRecyclerAdapter("我先滚动")
19 | myAdapter2 = MyRecyclerAdapter("我最后滚动")
20 | myAdapter3 = MyRecyclerAdapter("我先向下滚")
21 | myAdapter4 = MyRecyclerAdapter("我先向上滚")
22 |
23 | recycler_view1.apply {
24 | layoutManager = LinearLayoutManager(this@Example1Activity)
25 | adapter = myAdapter1
26 | }
27 |
28 | recycler_view2.apply {
29 | layoutManager = LinearLayoutManager(this@Example1Activity)
30 | adapter = myAdapter2
31 | }
32 |
33 | // recycler_view3.isDraggingItem = true
34 | recycler_view3.apply {
35 | layoutManager = LinearLayoutManager(this@Example1Activity)
36 | adapter = myAdapter3
37 | }
38 |
39 | recycler_view4.apply {
40 | layoutManager = LinearLayoutManager(this@Example1Activity)
41 | adapter = myAdapter4
42 | }
43 |
44 | tv_scroll_by.setOnClickListener {
45 | recycler_view3.nestedScrollBy(100)
46 | // recycler_view3.scrollBy(0, 100)
47 | }
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_example_webview_refresh.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
13 |
14 |
18 |
19 |
20 |
21 |
25 |
26 |
32 |
33 | s
34 |
38 |
39 |
44 |
45 |
46 |
47 |
48 |
49 |
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 |
3 | apply plugin: 'kotlin-android'
4 |
5 | apply plugin: 'kotlin-android-extensions'
6 |
7 | android {
8 | compileSdkVersion 28
9 | defaultConfig {
10 | applicationId "com.fc.nestedscrollview.example"
11 | minSdkVersion 16
12 | targetSdkVersion 26
13 | versionCode 1
14 | versionName "1.0"
15 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
16 | }
17 | buildTypes {
18 | release {
19 | minifyEnabled true
20 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
21 | }
22 | debug {
23 | minifyEnabled false
24 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
25 | }
26 | }
27 |
28 | androidExtensions {
29 | experimental = true //注意:此功能还在实验阶段
30 | }
31 | lintOptions {
32 | abortOnError false
33 | }
34 | }
35 |
36 | dependencies {
37 | implementation fileTree(dir: 'libs', include: ['*.jar'])
38 | implementation"org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
39 | implementation 'androidx.appcompat:appcompat:1.2.0'
40 | implementation 'androidx.recyclerview:recyclerview:1.1.0'
41 | implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'
42 | implementation 'com.google.android.material:material:1.3.0-alpha04'
43 | implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
44 | testImplementation 'junit:junit:4.13.2'
45 | androidTestImplementation 'androidx.test:runner:1.3.0'
46 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
47 | implementation project(":library")
48 | // implementation 'fc.nestedscrollview.androidx:library:1.0.0'
49 | implementation 'com.github.CymChad:BaseRecyclerViewAdapterHelper:2.9.49-androidx'
50 | // implementation 'com.github.403462630:FCNestedScrollView:1.0.0'
51 | }
52 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable-v24/ic_launcher_foreground.xml:
--------------------------------------------------------------------------------
1 |
7 |
12 |
13 |
19 |
22 |
25 |
26 |
27 |
28 |
34 |
35 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_example5.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
13 |
14 |
18 |
19 |
24 |
25 |
26 |
32 |
33 |
42 |
43 |
48 |
49 |
50 |
51 |
52 |
53 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
13 |
14 |
19 |
20 |
25 |
26 |
31 |
32 |
37 |
38 |
43 |
44 |
49 |
50 |
55 |
56 |
57 |
--------------------------------------------------------------------------------
/app/src/main/java/com/fc/nestedscrollview/example/Example2Activity.kt:
--------------------------------------------------------------------------------
1 | package com.fc.nestedscrollview.example
2 |
3 | import android.os.Bundle
4 | import androidx.appcompat.app.AppCompatActivity
5 | import androidx.recyclerview.widget.LinearLayoutManager
6 | import kotlinx.android.synthetic.main.activity_example2.*
7 |
8 | class Example2Activity : AppCompatActivity() {
9 |
10 | private var myAdapter1 : MyRecyclerAdapter? = null
11 | private var myAdapter2 : MyRecyclerAdapter? = null
12 | private var myAdapter3 : MyRecyclerAdapter? = null
13 | private var myAdapter4 : MyRecyclerAdapter? = null
14 |
15 | override fun onCreate(savedInstanceState: Bundle?) {
16 | super.onCreate(savedInstanceState)
17 | setContentView(R.layout.activity_example2)
18 | myAdapter1 = MyRecyclerAdapter("我先滚动")
19 | myAdapter2 = MyRecyclerAdapter("我最后滚动")
20 | myAdapter3 = MyRecyclerAdapter("我先向下滚")
21 | myAdapter4 = MyRecyclerAdapter("我先向上滚")
22 |
23 | recycler_view1.apply {
24 | layoutManager = LinearLayoutManager(this@Example2Activity)
25 | adapter = myAdapter1
26 | }
27 |
28 | recycler_view2.apply {
29 | layoutManager = LinearLayoutManager(this@Example2Activity)
30 | adapter = myAdapter2
31 | }
32 |
33 | recycler_view3.apply {
34 | layoutManager = LinearLayoutManager(this@Example2Activity)
35 | adapter = myAdapter3
36 | }
37 |
38 | recycler_view4.apply {
39 | layoutManager = LinearLayoutManager(this@Example2Activity)
40 | adapter = myAdapter4
41 | }
42 |
43 | swipe_refresh.setOnRefreshListener {
44 | swipe_refresh.postDelayed({
45 | swipe_refresh.isRefreshing = false
46 | }, 5_000)
47 | }
48 |
49 | swipe_refresh1.setOnRefreshListener {
50 | swipe_refresh1.postDelayed({
51 | swipe_refresh1.isRefreshing = false
52 | }, 5_000)
53 | }
54 |
55 | swipe_refresh2.setOnRefreshListener {
56 | swipe_refresh2.postDelayed({
57 | swipe_refresh2.isRefreshing = false
58 | }, 5_000)
59 | }
60 |
61 | swipe_refresh3.setOnRefreshListener {
62 | swipe_refresh3.postDelayed({
63 | swipe_refresh3.isRefreshing = false
64 | }, 5_000)
65 | }
66 |
67 | swipe_refresh4.setOnRefreshListener {
68 | swipe_refresh4.postDelayed({
69 | swipe_refresh4.isRefreshing = false
70 | }, 5_000)
71 | }
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/app/src/main/java/com/fc/nestedscrollview/example/Example5Activity.kt:
--------------------------------------------------------------------------------
1 | package com.fc.nestedscrollview.example
2 |
3 | import android.os.Bundle
4 | import android.view.ViewGroup
5 | import android.view.ViewTreeObserver
6 | import android.widget.LinearLayout
7 | import androidx.appcompat.app.AppCompatActivity
8 | import androidx.fragment.app.Fragment
9 | import androidx.fragment.app.FragmentManager
10 | import androidx.fragment.app.FragmentPagerAdapter
11 | import com.fc.nestedscrollview.example.fragment.Tab1Fragment
12 | import com.fc.nestedscrollview.example.fragment.Tab2Fragment
13 | import com.fc.nestedscrollview.example.fragment.Tab3Fragment
14 | import kotlinx.android.synthetic.main.activity_example5.*
15 |
16 | class Example5Activity : AppCompatActivity() {
17 |
18 | override fun onCreate(savedInstanceState: Bundle?) {
19 | super.onCreate(savedInstanceState)
20 | setContentView(R.layout.activity_example5)
21 | nested_scroll_view.logId = "parent"
22 | nested_scroll_view.enableLog = true
23 | nested_scroll_view.viewTreeObserver.addOnGlobalLayoutListener (object : ViewTreeObserver.OnGlobalLayoutListener {
24 | override fun onGlobalLayout() {
25 | var height = nested_scroll_view.height
26 | if (height != 0) {
27 | ll_page_container.layoutParams = LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, height)
28 | if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN) {
29 | nested_scroll_view.viewTreeObserver.removeOnGlobalLayoutListener(this)
30 | } else {
31 | nested_scroll_view.viewTreeObserver.removeGlobalOnLayoutListener(this)
32 | }
33 | }
34 | }
35 | })
36 | initViewPage()
37 | }
38 |
39 | private fun initViewPage() {
40 | view_pager.adapter = MyViewPageAdapter(supportFragmentManager)
41 | tab_layout.setupWithViewPager(view_pager)
42 | view_pager.offscreenPageLimit = 3
43 | }
44 |
45 | private class MyViewPageAdapter(fragmentManager: FragmentManager) : FragmentPagerAdapter(fragmentManager) {
46 |
47 | private val titles = listOf("tab1", "tab2", "tab3")
48 | override fun getItem(position: Int): Fragment {
49 | return when (position) {
50 | 0 -> Tab1Fragment()
51 | 1 -> Tab2Fragment()
52 | else -> Tab3Fragment()
53 | }
54 | }
55 |
56 | override fun getCount(): Int {
57 | return 3
58 | }
59 |
60 | override fun getPageTitle(position: Int): CharSequence {
61 | return titles[position]
62 | }
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/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 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
12 | set DEFAULT_JVM_OPTS=
13 |
14 | set DIRNAME=%~dp0
15 | if "%DIRNAME%" == "" set DIRNAME=.
16 | set APP_BASE_NAME=%~n0
17 | set APP_HOME=%DIRNAME%
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 Windowz variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 | if "%@eval[2+2]" == "4" goto 4NT_args
53 |
54 | :win9xME_args
55 | @rem Slurp the command line arguments.
56 | set CMD_LINE_ARGS=
57 | set _SKIP=2
58 |
59 | :win9xME_args_slurp
60 | if "x%~1" == "x" goto execute
61 |
62 | set CMD_LINE_ARGS=%*
63 | goto execute
64 |
65 | :4NT_args
66 | @rem Get arguments from the 4NT Shell from JP Software
67 | set CMD_LINE_ARGS=%$
68 |
69 | :execute
70 | @rem Setup the command line
71 |
72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
73 |
74 | @rem Execute Gradle
75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
76 |
77 | :end
78 | @rem End local scope for the variables with windows NT shell
79 | if "%ERRORLEVEL%"=="0" goto mainEnd
80 |
81 | :fail
82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 | rem the _cmd.exe /c_ return code!
84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
85 | exit /b 1
86 |
87 | :mainEnd
88 | if "%OS%"=="Windows_NT" endlocal
89 |
90 | :omega
91 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_nested_scroll.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
13 |
14 |
18 |
19 |
20 |
21 |
25 |
26 |
32 |
33 |
34 |
39 |
40 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_three.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
14 |
15 |
19 |
20 |
21 |
22 |
26 |
27 |
33 |
34 |
35 |
41 |
42 |
46 |
47 |
51 |
52 |
53 |
54 |
58 |
59 |
65 |
66 |
67 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ## FCNestedScrollView
2 |
3 | 主要是用来解决滚动嵌套问题,比如NestedScrollView 嵌套 RecyclerView,看了网上很多别人实现的代码,大部分都是让RecyclerView的高度等于所有item高度的总和;如果item比较少,可以这样实现,但是如果item比较多,并且可以一直上拉加载更多,这样的话就会导致界面严重卡顿,存在性能问题,因为没有用到recyclerview的缓存机制
4 |
5 | 而此项目的实现是被嵌套的RecyclerView定高,然后手动解决手势冲突问题,这样的话即使item再多也不存在性能问题
6 |
7 |  
8 |
9 |
10 |  
11 |
12 | maven地址
13 |
14 | ```
15 | maven { url 'https://jitpack.io' }
16 | ```
17 | gradle依赖
18 |
19 | ```
20 | implementation 'com.github.403462630:FCNestedScrollView:1.0.0'
21 | ```
22 |
23 | ### 此项目提供四个view
24 |
25 | - FCNestedScrollView
26 | - FCRecyclerView
27 | - FCWebView
28 | - FCSwipeRefreshLayout
29 |
30 | ### 注意
31 |
32 | - 支持FCNestedScrollView 嵌套 FCNestedScrollView、FCRecyclerView、FCWebView
33 | - 支持嵌套下拉刷新(目前只扩展了系统自带的SwipeRefreshLayout, 如果只是在最外层的滚动视图添加下拉刷新,则无任何限制)
34 | - FCNestedScrollView支持多层嵌套(最少支持三层嵌套,更多层嵌套没测试过,按道理应该也可以)
35 | - 不支持FCRecyclerView 嵌套FCNestedScrollView、FCWebView(基本上没这种需求)
36 |
37 |
38 | ### 三种可嵌套滚动view用法如下
39 |
40 | ### 自定义属性
41 |
42 | ##### fc\_scroll\_mode如下:
43 |
44 | - none parentview优先滚动,然后自己消费
45 | - all (默认)自己优先滚动,然后parentview消费
46 | - up 自己优先向上滚,然后parentview消费
47 | - down 自己优先向下滚,然后parentview消费
48 |
49 | ##### fc\_is_linked\_parent如下:
50 |
51 | - true (默认)fling时,当自己滚动完成之后 传递给parentview继续fling
52 | - false fling时,当自己滚动完成之后 不传递给parentview继续fling
53 |
54 | ##### fc\_is\_linked\_child 如下:
55 |
56 | - true (默认)当自己滚到到顶部或底部的时候,联动childview继续fling
57 | - false 当自己滚到到顶部或底部的时候,不联动childview继续fling
58 |
59 | ##### fc\_is\_nested\_scroll\_enabled(目前只有FCWebView支持,因为webview里的内容复杂、不可控,导致可能存在兼容性,建议 如果webview没有嵌套在其它可滚动视图里面,建议设置成false(或者使用原生的webview),否则设置成true)
60 |
61 | - true (默认)
62 | - false 不会添加任何手势处理,全使用系统默认的手势
63 |
64 | ##### fc\_is\_pull\_refresh\_intercept(仅FCSwipeRefreshLayout支持)
65 |
66 | - true 下拉时,优先处理下拉刷新
67 | - false (默认)下拉时,最后处理下拉刷新
68 |
69 | ## Example
70 |
71 | ```
72 |
78 |
79 |
83 |
84 |
85 | ......
86 |
87 |
88 |
93 |
98 |
99 |
100 |
101 |
102 | ```
103 |
104 | ## 混淆
105 |
106 | ```
107 | # support package
108 | -keep class android.support.v7.widget.RecyclerView{*;}
109 | -keep class android.support.v4.widget.NestedScrollView{*;}
110 |
111 | # androidx package
112 | -keep class androidx.recyclerview.widget.RecyclerView{*;}
113 | -keep class androidx.core.widget.NestedScrollView{*;}
114 |
115 | ```
116 |
117 |
118 |
119 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_example1.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
12 |
13 |
21 |
22 |
29 |
30 |
37 |
38 |
45 |
46 |
54 |
55 |
65 |
66 |
67 |
68 |
78 |
79 |
80 |
81 |
91 |
92 |
93 |
94 |
104 |
105 |
106 |
107 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
10 | DEFAULT_JVM_OPTS=""
11 |
12 | APP_NAME="Gradle"
13 | APP_BASE_NAME=`basename "$0"`
14 |
15 | # Use the maximum available, or set MAX_FD != -1 to use that value.
16 | MAX_FD="maximum"
17 |
18 | warn ( ) {
19 | echo "$*"
20 | }
21 |
22 | die ( ) {
23 | echo
24 | echo "$*"
25 | echo
26 | exit 1
27 | }
28 |
29 | # OS specific support (must be 'true' or 'false').
30 | cygwin=false
31 | msys=false
32 | darwin=false
33 | case "`uname`" in
34 | CYGWIN* )
35 | cygwin=true
36 | ;;
37 | Darwin* )
38 | darwin=true
39 | ;;
40 | MINGW* )
41 | msys=true
42 | ;;
43 | esac
44 |
45 | # Attempt to set APP_HOME
46 | # Resolve links: $0 may be a link
47 | PRG="$0"
48 | # Need this for relative symlinks.
49 | while [ -h "$PRG" ] ; do
50 | ls=`ls -ld "$PRG"`
51 | link=`expr "$ls" : '.*-> \(.*\)$'`
52 | if expr "$link" : '/.*' > /dev/null; then
53 | PRG="$link"
54 | else
55 | PRG=`dirname "$PRG"`"/$link"
56 | fi
57 | done
58 | SAVED="`pwd`"
59 | cd "`dirname \"$PRG\"`/" >/dev/null
60 | APP_HOME="`pwd -P`"
61 | cd "$SAVED" >/dev/null
62 |
63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
64 |
65 | # Determine the Java command to use to start the JVM.
66 | if [ -n "$JAVA_HOME" ] ; then
67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
68 | # IBM's JDK on AIX uses strange locations for the executables
69 | JAVACMD="$JAVA_HOME/jre/sh/java"
70 | else
71 | JAVACMD="$JAVA_HOME/bin/java"
72 | fi
73 | if [ ! -x "$JAVACMD" ] ; then
74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
75 |
76 | Please set the JAVA_HOME variable in your environment to match the
77 | location of your Java installation."
78 | fi
79 | else
80 | JAVACMD="java"
81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
82 |
83 | Please set the JAVA_HOME variable in your environment to match the
84 | location of your Java installation."
85 | fi
86 |
87 | # Increase the maximum file descriptors if we can.
88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
89 | MAX_FD_LIMIT=`ulimit -H -n`
90 | if [ $? -eq 0 ] ; then
91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
92 | MAX_FD="$MAX_FD_LIMIT"
93 | fi
94 | ulimit -n $MAX_FD
95 | if [ $? -ne 0 ] ; then
96 | warn "Could not set maximum file descriptor limit: $MAX_FD"
97 | fi
98 | else
99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
100 | fi
101 | fi
102 |
103 | # For Darwin, add options to specify how the application appears in the dock
104 | if $darwin; then
105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
106 | fi
107 |
108 | # For Cygwin, switch paths to Windows format before running java
109 | if $cygwin ; then
110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
112 | JAVACMD=`cygpath --unix "$JAVACMD"`
113 |
114 | # We build the pattern for arguments to be converted via cygpath
115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
116 | SEP=""
117 | for dir in $ROOTDIRSRAW ; do
118 | ROOTDIRS="$ROOTDIRS$SEP$dir"
119 | SEP="|"
120 | done
121 | OURCYGPATTERN="(^($ROOTDIRS))"
122 | # Add a user-defined pattern to the cygpath arguments
123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
125 | fi
126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
127 | i=0
128 | for arg in "$@" ; do
129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
131 |
132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
134 | else
135 | eval `echo args$i`="\"$arg\""
136 | fi
137 | i=$((i+1))
138 | done
139 | case $i in
140 | (0) set -- ;;
141 | (1) set -- "$args0" ;;
142 | (2) set -- "$args0" "$args1" ;;
143 | (3) set -- "$args0" "$args1" "$args2" ;;
144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
150 | esac
151 | fi
152 |
153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
154 | function splitJvmOpts() {
155 | JVM_OPTS=("$@")
156 | }
157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
159 |
160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
161 |
--------------------------------------------------------------------------------
/library/src/main/java/com/fc/nestedscrollview/FcNestedUtil.java:
--------------------------------------------------------------------------------
1 | package com.fc.nestedscrollview;
2 |
3 | import android.widget.OverScroller;
4 |
5 | import androidx.core.widget.NestedScrollView;
6 | import androidx.recyclerview.widget.RecyclerView;
7 |
8 | import java.lang.reflect.Field;
9 |
10 | public class FcNestedUtil {
11 |
12 | public static float getCurrVelocityY(RecyclerView recyclerView) {
13 | try {
14 | OverScroller overScroller = getOverScroller(recyclerView);
15 | if (overScroller != null) {
16 | return getCurrVelocityY(overScroller);
17 | }
18 | } catch (Throwable e) {
19 | e.printStackTrace();
20 | }
21 | return 0;
22 | }
23 |
24 | public static float getCurrVelocityX(RecyclerView recyclerView) {
25 | try {
26 | OverScroller overScroller = getOverScroller(recyclerView);
27 | if (overScroller != null) {
28 | return getCurrVelocityX(overScroller);
29 | }
30 | } catch (Throwable e) {
31 | e.printStackTrace();
32 | }
33 | return 0;
34 | }
35 |
36 | public static float[] getCurrVelocityXY(RecyclerView recyclerView) {
37 | try {
38 | OverScroller overScroller = getOverScroller(recyclerView);
39 | if (overScroller != null) {
40 | return new float[]{getCurrVelocityX(overScroller), getCurrVelocityY(overScroller)};
41 | }
42 | } catch (Throwable e) {
43 | e.printStackTrace();
44 | }
45 | return new float[] {0, 0};
46 | }
47 |
48 | public static float getCurrVelocityY(NestedScrollView nestedScrollView) {
49 | try {
50 | OverScroller overScroller = getOverScroller(nestedScrollView);
51 | if (overScroller != null) {
52 | return getCurrVelocityY(overScroller);
53 | }
54 | } catch (Throwable e) {
55 | e.printStackTrace();
56 | }
57 | return 0;
58 | }
59 |
60 | public static OverScroller getOverScroller(RecyclerView recyclerView) {
61 | OverScroller overScroller = null;
62 | try {
63 | Class cls = recyclerView.getClass();
64 | String name = RecyclerView.class.getName();
65 | while (cls != null) {
66 | if (cls.getName().equals(name)) {
67 | Field viewFlinger = cls.getDeclaredField("mViewFlinger");
68 | viewFlinger.setAccessible(true);
69 | Object viewFlingerObject = viewFlinger.get(recyclerView);
70 | Field field = viewFlingerObject.getClass().getDeclaredField("mScroller");
71 | if (field != null) {
72 | field.setAccessible(true);
73 | Object object = field.get(viewFlingerObject);
74 | if (object instanceof OverScroller) {
75 | overScroller = (OverScroller) object;
76 | }
77 | }
78 | break;
79 | } else {
80 | cls = cls.getSuperclass();
81 | }
82 | }
83 | } catch (Exception e) {
84 | e.printStackTrace();
85 | }
86 | return overScroller;
87 | }
88 |
89 | public static OverScroller getOverScroller(NestedScrollView nestedScrollView) {
90 | Class cls = nestedScrollView.getClass();
91 | String name = NestedScrollView.class.getName();
92 | OverScroller overScroller = null;
93 | try {
94 | while (cls != null) {
95 | if (cls.getName().equals(name)) {
96 | Field field = cls.getDeclaredField("mScroller");
97 | if (field != null) {
98 | field.setAccessible(true);
99 | Object object = field.get(nestedScrollView);
100 | if (object instanceof OverScroller) {
101 | overScroller = (OverScroller) object;
102 | }
103 | }
104 | break;
105 | } else {
106 | cls = cls.getSuperclass();
107 | }
108 | }
109 | } catch (Exception e) {
110 | e.printStackTrace();
111 | }
112 | return overScroller;
113 | }
114 |
115 | public static float getCurrVelocityX(OverScroller overScroller) {
116 | try {
117 | Field mScrollerX = overScroller.getClass().getDeclaredField("mScrollerX");
118 | mScrollerX.setAccessible(true);
119 | Object splineOverScroller = mScrollerX.get(overScroller);
120 | Field field1 = splineOverScroller.getClass().getDeclaredField("mCurrVelocity");
121 | field1.setAccessible(true);
122 | return (float) field1.get(splineOverScroller);
123 | } catch (Exception e) {
124 | return 0;
125 | }
126 | }
127 |
128 | public static float getCurrVelocityY(OverScroller overScroller) {
129 | try {
130 | Field mScrollerY = overScroller.getClass().getDeclaredField("mScrollerY");
131 | mScrollerY.setAccessible(true);
132 | Object splineOverScroller = mScrollerY.get(overScroller);
133 | Field field1 = splineOverScroller.getClass().getDeclaredField("mCurrVelocity");
134 | field1.setAccessible(true);
135 | return (float) field1.get(splineOverScroller);
136 | } catch (Exception e) {
137 | return 0;
138 | }
139 | }
140 | }
141 |
--------------------------------------------------------------------------------
/library/src/main/java/com/fc/nestedscrollview/FCSwipeRefreshLayout.java:
--------------------------------------------------------------------------------
1 | package com.fc.nestedscrollview;
2 |
3 | import android.content.Context;
4 | import android.content.res.TypedArray;
5 | import android.util.AttributeSet;
6 | import android.view.View;
7 |
8 | import androidx.annotation.NonNull;
9 | import androidx.annotation.Nullable;
10 | import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
11 |
12 | import java.lang.reflect.Field;
13 |
14 | /**
15 | * Created by fangcan on 2018/3/31.
16 | */
17 |
18 | public class FCSwipeRefreshLayout extends SwipeRefreshLayout implements FCFlingView {
19 | private Field draggingField;
20 | private View targetView;
21 | /** 当自己滚到顶或底部的时候 否联动child滚动 */
22 | private boolean isLinkedChild;
23 | /** 是否拦截下拉操作,优先处理下拉刷新 */
24 | private boolean isPullRefreshIntercept;
25 | public FCSwipeRefreshLayout(Context context) {
26 | this(context, null);
27 | }
28 |
29 | public FCSwipeRefreshLayout(Context context, AttributeSet attrs) {
30 | super(context, attrs);
31 | TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.FCSwipeRefreshLayout);
32 | isLinkedChild = typedArray.getBoolean(R.styleable.FCSwipeRefreshLayout_fc_is_linked_child, true);
33 | isPullRefreshIntercept = typedArray.getBoolean(R.styleable.FCSwipeRefreshLayout_fc_is_pull_refresh_intercept, false);
34 | typedArray.recycle();
35 | try {
36 | draggingField = getClass().getSuperclass().getDeclaredField("mTotalUnconsumed");
37 | draggingField.setAccessible(true);
38 | } catch (Throwable e) {
39 | e.printStackTrace();
40 | }
41 | }
42 |
43 | public void setPullRefreshIntercept(boolean pullRefreshIntercept) {
44 | isPullRefreshIntercept = pullRefreshIntercept;
45 | }
46 |
47 | @Override
48 | public void onNestedScrollAccepted(View child, View target, int axes) {
49 | super.onNestedScrollAccepted(child, target, axes);
50 | this.targetView = target;
51 | }
52 |
53 | @Override
54 | public void onNestedPreScroll(View target, int dx, int dy, int[] consumed, int type) {
55 | super.onNestedPreScroll(target, dx, dy, consumed, type);
56 | }
57 |
58 | @Override
59 | public void onNestedScroll(@NonNull View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed, int type, @NonNull int[] consumed) {
60 | super.onNestedScroll(target, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, type, consumed);
61 | }
62 |
63 | @Override
64 | public boolean dispatchNestedPreScroll(int dx, int dy, int[] consumed, int[] offsetInWindow) {
65 | return super.dispatchNestedPreScroll(dx, dy, consumed, offsetInWindow);
66 | }
67 |
68 | @Override
69 | public void dispatchNestedScroll(int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed, @Nullable int[] offsetInWindow, int type, @NonNull int[] consumed) {
70 | if (dyUnconsumed < 0 && !isRefreshing()) {
71 | // return false;
72 | } else {
73 | super.dispatchNestedScroll(dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, offsetInWindow, type, consumed);
74 | }
75 | }
76 |
77 | @Override
78 | public boolean dispatchNestedScroll(int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed, int[] offsetInWindow) {
79 | if (dyUnconsumed < 0 && !isRefreshing()) {
80 | return false;
81 | } else {
82 | return super.dispatchNestedScroll(dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, offsetInWindow);
83 | }
84 | }
85 |
86 | @Override
87 | public boolean dispatchNestedPreFling(float velocityX, float velocityY) {
88 | if (isPullRefreshIntercept && velocityY < 0) {
89 | return false;
90 | } else {
91 | boolean isDragging = false;
92 | try {
93 | if (!isRefreshing()) {
94 | //是否处于下拉状态
95 | float value = draggingField.getFloat(this);
96 | isDragging = value > 0;
97 | }
98 | } catch (Throwable e) {
99 | e.printStackTrace();
100 | }
101 | return !isDragging && super.dispatchNestedPreFling(velocityX, velocityY);
102 | }
103 | }
104 |
105 | @Override
106 | public boolean dispatchNestedFling(float velocityX, float velocityY, boolean consumed) {
107 | if (isPullRefreshIntercept && velocityY < 0) {
108 | return false;
109 | } else {
110 | boolean isDragging = false;
111 | try {
112 | if (!isRefreshing()) {
113 | //是否处于下拉状态
114 | float value = draggingField.getFloat(this);
115 | isDragging = value > 0;
116 | }
117 | } catch (Throwable e) {
118 | e.printStackTrace();
119 | }
120 | return !isDragging && super.dispatchNestedFling(velocityX, velocityY, consumed);
121 | }
122 | }
123 |
124 | @Override
125 | public boolean canFling(int direction) {
126 | return true;
127 | }
128 |
129 | @Override
130 | public void fling(int velocityY) {
131 | if (isLinkedChild && this.targetView != null) {
132 | if (this.targetView.canScrollVertically((int) velocityY)) {
133 | if (this.targetView instanceof FCRecyclerView) {
134 | ((FCRecyclerView)this.targetView).fling(0, (int) velocityY);
135 | } else if (this.targetView instanceof FCNestedScrollView) {
136 | ((FCNestedScrollView)this.targetView).fling((int) velocityY);
137 | } else if (this.targetView instanceof FCWebView) {
138 | ((FCWebView)this.targetView).flingScroll(0, (int) velocityY);
139 | }
140 | }
141 | }
142 | }
143 | }
144 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_launcher_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
10 |
15 |
20 |
25 |
30 |
35 |
40 |
45 |
50 |
55 |
60 |
65 |
70 |
75 |
80 |
85 |
90 |
95 |
100 |
105 |
110 |
115 |
120 |
125 |
130 |
135 |
140 |
145 |
150 |
155 |
160 |
165 |
170 |
171 |
--------------------------------------------------------------------------------
/app/src/main/java/com/fc/nestedscrollview/example/fragment/Tab1Fragment.kt:
--------------------------------------------------------------------------------
1 | package com.fc.nestedscrollview.example.fragment
2 |
3 | import android.graphics.Rect
4 | import android.os.Bundle
5 | import android.util.Log
6 | import android.view.LayoutInflater
7 | import android.view.View
8 | import android.view.ViewGroup
9 | import androidx.core.widget.NestedScrollView
10 | import androidx.fragment.app.Fragment
11 | import androidx.recyclerview.widget.ItemTouchHelper
12 | import androidx.recyclerview.widget.LinearLayoutManager
13 | import androidx.recyclerview.widget.RecyclerView
14 | import com.chad.library.adapter.base.callback.ItemDragAndSwipeCallback
15 | import com.chad.library.adapter.base.listener.OnItemDragListener
16 | import com.fc.nestedscrollview.example.MyDraggerRecyclerAdapter
17 | import com.fc.nestedscrollview.example.R
18 | import kotlinx.android.synthetic.main.fragment_tab1.*
19 |
20 | /**
21 | * Created by fangcan on 2018/4/2.
22 | */
23 | class Tab1Fragment : Fragment() {
24 | private var itemDragAdapter: MyDraggerRecyclerAdapter? = null
25 | private var mItemTouchHelper: ItemTouchHelper? = null
26 | private var mItemDragAndSwipeCallback: ItemDragAndSwipeCallback? = null
27 | private var nestedScrollView: NestedScrollView? = null
28 | override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
29 | return return inflater!!.inflate(R.layout.fragment_tab1, container, false)
30 | }
31 |
32 | private fun getViewLocation(view: View) : Rect {
33 | val location = intArrayOf(0, 0)
34 | view.getLocationInWindow(location)
35 | val height = view.height
36 | val width = view.width
37 | val rect = Rect()
38 | rect.left = location[0]
39 | rect.top = location[1]
40 | rect.bottom = rect.top + height
41 | rect.right = rect.left + width
42 | return rect
43 | }
44 |
45 | fun ifNeedScrollToBottomOrTop(recyclerView: RecyclerView, source: RecyclerView.ViewHolder, y: Int) {
46 | nestedScrollView?.let {
47 | val rect = getViewLocation(recyclerView)
48 | val scrollViewRect = getViewLocation(it)
49 | Log.i("ifNeedScrollToBottom", "height: ${recyclerView.height}, itemView.height: ${source.itemView.height}, scrollViewRect.top: ${scrollViewRect.top}, scrollViewRect.bottom: ${scrollViewRect.bottom}, y: $y, rect.top: ${rect.top} rect.bottom: ${rect.bottom}")
50 | if (y + source.itemView.height + rect.top > scrollViewRect.bottom) {
51 | if (it.canScrollVertically(1)) {
52 | Log.i("ifNeedScrollToBottom", "scroll to bottom")
53 | // 1. scroll to recyclerView bottom position
54 | val bottomOffset = rect.bottom - scrollViewRect.bottom
55 | it.smoothScrollBy(0, bottomOffset)
56 | // 2. scroll itemView height distance
57 | // it.smoothScrollBy(0, source.itemView.height)
58 | // 3. scroll to scrollview bottom
59 | // it.smoothScrollTo(0, it.getChildAt(0).height)
60 | }
61 | } else if (y + rect.top < scrollViewRect.top) {
62 | if (it.canScrollVertically(-1)) {
63 | Log.i("ifNeedScrollToBottom", "scroll to top")
64 | // 1. scroll to recyclerView top position
65 | val topOffset = rect.top - scrollViewRect.top
66 | it.scrollBy(0, topOffset)
67 | // 2. scroll itemView height distance
68 | // it.smoothScrollBy(0, -source.itemView.height)
69 | // 3. scroll to scrollview top
70 | // it.smoothScrollTo(0, 0)
71 | }
72 | }
73 | }
74 | }
75 |
76 | private fun findNestedScrollView(recyclerView: RecyclerView) : NestedScrollView? {
77 | var view = recyclerView.parent
78 | while (view != null) {
79 | if (view is NestedScrollView) {
80 | return view
81 | }
82 | view = view.parent
83 | }
84 | return null
85 | }
86 |
87 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
88 | super.onViewCreated(view, savedInstanceState)
89 |
90 | nestedScrollView = findNestedScrollView(recycler_view)
91 | itemDragAdapter = MyDraggerRecyclerAdapter(generateData(50))
92 | mItemDragAndSwipeCallback = object: ItemDragAndSwipeCallback(itemDragAdapter) {
93 | override fun onMoved(recyclerView: RecyclerView, source: RecyclerView.ViewHolder, fromPos: Int, target: RecyclerView.ViewHolder, toPos: Int, x: Int, y: Int) {
94 | super.onMoved(recyclerView, source, fromPos, target, toPos, x, y)
95 | ifNeedScrollToBottomOrTop(recyclerView!!, source!!, y)
96 | }
97 | }
98 | mItemTouchHelper = ItemTouchHelper(mItemDragAndSwipeCallback!!)
99 | mItemTouchHelper!!.attachToRecyclerView(recycler_view)
100 | itemDragAdapter!!.enableDragItem(mItemTouchHelper!!)
101 | itemDragAdapter!!.setOnItemDragListener(onItemDragListener)
102 | itemDragAdapter!!.setToggleDragOnLongPress(true)
103 | recycler_view.apply {
104 | layoutManager = LinearLayoutManager(activity)
105 | recycler_view.adapter = itemDragAdapter
106 | }
107 | }
108 |
109 | private fun generateData(size: Int): ArrayList {
110 | val data = ArrayList()
111 | for (i in 0 until size) {
112 | data.add("item $i")
113 | }
114 | return data
115 | }
116 |
117 | private var onItemDragListener: OnItemDragListener = object : OnItemDragListener {
118 | private var startPosition = 0
119 |
120 | override fun onItemDragStart(viewHolder: RecyclerView.ViewHolder, pos: Int) {
121 | startPosition = pos
122 | Log.d("Tab1Fragment", "drag start")
123 | }
124 |
125 | override fun onItemDragMoving(source: RecyclerView.ViewHolder, from: Int, target: RecyclerView.ViewHolder, to: Int) {
126 | // Log.d("Tab1Fragment", "onItemDragMoving")
127 | }
128 |
129 | override fun onItemDragEnd(viewHolder: RecyclerView.ViewHolder, endPosition: Int) {
130 | Log.d("Tab1Fragment", "onItemDragEnd")
131 | }
132 | }
133 | }
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_example2.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
12 |
13 |
17 |
18 |
26 |
27 |
34 |
35 |
42 |
43 |
50 |
51 |
58 |
59 |
67 |
68 |
74 |
75 |
76 |
77 |
78 |
79 |
87 |
88 |
94 |
95 |
96 |
97 |
98 |
99 |
107 |
108 |
114 |
115 |
116 |
117 |
118 |
119 |
127 |
128 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_example3.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
11 |
12 |
20 |
21 |
28 |
29 |
36 |
37 |
44 |
45 |
52 |
53 |
63 |
64 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
92 |
93 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
121 |
122 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
150 |
151 |
155 |
156 |
157 |
158 |
159 |
160 |
161 |
162 |
163 |
164 |
165 |
166 |
167 |
168 |
169 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_example4.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
12 |
13 |
17 |
18 |
26 |
27 |
34 |
35 |
42 |
43 |
50 |
51 |
58 |
59 |
67 |
68 |
74 |
75 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
103 |
104 |
110 |
111 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
139 |
140 |
146 |
147 |
151 |
152 |
153 |
154 |
155 |
156 |
157 |
158 |
159 |
160 |
161 |
162 |
163 |
164 |
165 |
166 |
167 |
175 |
176 |
184 |
185 |
189 |
190 |
191 |
192 |
193 |
194 |
195 |
196 |
197 |
198 |
199 |
200 |
201 |
202 |
203 |
204 |
205 |
206 |
--------------------------------------------------------------------------------
/library/src/main/java/com/fc/nestedscrollview/FCRecyclerView.java:
--------------------------------------------------------------------------------
1 | package com.fc.nestedscrollview;
2 |
3 | import android.content.Context;
4 | import android.content.res.TypedArray;
5 | import android.os.Handler;
6 | import android.util.AttributeSet;
7 | import android.util.Log;
8 | import android.view.MotionEvent;
9 | import android.view.ViewParent;
10 |
11 | import androidx.annotation.Nullable;
12 | import androidx.core.view.ViewCompat;
13 | import androidx.core.widget.NestedScrollView;
14 | import androidx.recyclerview.widget.RecyclerView;
15 |
16 | import java.util.UUID;
17 | import java.util.concurrent.CopyOnWriteArraySet;
18 |
19 | /**
20 | * Created by fangcan on 2018/3/28.
21 | */
22 |
23 | public class FCRecyclerView extends RecyclerView {
24 | public static final int MODEL_ALL = 0; //自己优先滚动
25 | public static final int MODEL_UP = 1; //自己优先向上滚动
26 | public static final int MODEL_DOWN = 2; //自己优先向下滚动
27 | public static final int MODEL_NONE = 3; //自己最后滚动
28 | private static final String TAG = "FCRecycleView";
29 |
30 | private int nestedScrollModel = MODEL_NONE;
31 | /** 当滚到顶或底部的时候 否联动parent滚动 */
32 | private boolean isLinkedParent;
33 | private boolean isDraggingItem = false;
34 | private NestedScrollView nestedScrollView;
35 | private Handler handler = new Handler();
36 | private boolean isNestedScrollBy = false;
37 | // 测试看日志专用
38 | public boolean enableLog;
39 | private String uuid;
40 |
41 | public int lastDy;
42 |
43 | private boolean isStartScroll = false;
44 | private CopyOnWriteArraySet onScrollStateListeners = new CopyOnWriteArraySet<>();
45 |
46 | public void addOnScrollStateListener(OnScrollStateListener onScrollStateListener) {
47 | onScrollStateListeners.add(onScrollStateListener);
48 | }
49 |
50 | public void removeOnScrollStateListener(OnScrollStateListener onScrollStateListener) {
51 | onScrollStateListeners.remove(onScrollStateListener);
52 | }
53 |
54 | public FCRecyclerView(Context context) {
55 | this(context, null);
56 | }
57 |
58 | public FCRecyclerView(Context context, @Nullable AttributeSet attrs) {
59 | this(context, attrs, 0);
60 | }
61 |
62 | public FCRecyclerView(Context context, @Nullable AttributeSet attrs, int defStyle) {
63 | super(context, attrs, defStyle);
64 | uuid = UUID.randomUUID().toString();
65 | TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.FCRecyclerView);
66 | nestedScrollModel = typedArray.getInt(R.styleable.FCRecyclerView_fc_scroll_mode, MODEL_NONE);
67 | isLinkedParent = typedArray.getBoolean(R.styleable.FCRecyclerView_fc_is_linked_parent, true);
68 | typedArray.recycle();
69 | }
70 |
71 | private void notifyScrollStart() {
72 | for (OnScrollStateListener onScrollStateListener : onScrollStateListeners) {
73 | onScrollStateListener.onScrollStart();
74 | }
75 | }
76 |
77 | private void notifyScrollEnd() {
78 | for (OnScrollStateListener onScrollStateListener : onScrollStateListeners) {
79 | onScrollStateListener.onScrollEnd();
80 | }
81 | }
82 |
83 | public int getNestedScrollModel() {
84 | return nestedScrollModel;
85 | }
86 |
87 | public void setNestedScrollModel(int nestedScrollModel) {
88 | this.nestedScrollModel = nestedScrollModel;
89 | }
90 |
91 | public boolean isDraggingItem() {
92 | return isDraggingItem;
93 | }
94 |
95 | public void setDraggingItem(boolean draggingItem) {
96 | isDraggingItem = draggingItem;
97 | }
98 |
99 | @Override
100 | public void onScrollStateChanged(int state) {
101 | super.onScrollStateChanged(state);
102 | Log.i("onScrollStateChanged", "" + state);
103 | if (isNestedScrollBy == true && state == RecyclerView.SCROLL_STATE_IDLE) {
104 | isNestedScrollBy = false;
105 | stopNestedScroll(ViewCompat.TYPE_NON_TOUCH);
106 | }
107 | if (!onScrollStateListeners.isEmpty()) {
108 | if (state == RecyclerView.SCROLL_STATE_DRAGGING || state == RecyclerView.SCROLL_STATE_SETTLING) {
109 | if (!isStartScroll) {
110 | isStartScroll = true;
111 | notifyScrollStart();
112 | }
113 | } else if (state == RecyclerView.SCROLL_STATE_IDLE) {
114 | if (isStartScroll) {
115 | isStartScroll = false;
116 | notifyScrollEnd();
117 | }
118 | }
119 | }
120 | }
121 |
122 | public void nestedScrollBy(int dy) {
123 | isNestedScrollBy = true;
124 | startNestedScroll(ViewCompat.SCROLL_AXIS_VERTICAL, ViewCompat.TYPE_NON_TOUCH);
125 | smoothScrollBy(0, dy);
126 | }
127 |
128 | public void nestedScrollToPosition(int position) {
129 | isNestedScrollBy = true;
130 | startNestedScroll(ViewCompat.SCROLL_AXIS_VERTICAL, ViewCompat.TYPE_NON_TOUCH);
131 | scrollToPosition(position);
132 | }
133 |
134 | private NestedScrollView initNestedScrollView() {
135 | ViewParent view = getParent();
136 | while (view != null) {
137 | if (view instanceof NestedScrollView) {
138 | nestedScrollView = (NestedScrollView) view;
139 | }
140 | view = view.getParent();
141 | }
142 | return null;
143 | }
144 |
145 | @Override
146 | public void scrollBy(int x, int y) {
147 | if (isDraggingItem()) {
148 | if (nestedScrollView == null) {
149 | initNestedScrollView();
150 | }
151 | if (nestedScrollView != null) {
152 | nestedScrollView.scrollBy(x, y);
153 | } else {
154 | super.scrollBy(x, y);
155 | }
156 | } else {
157 | super.scrollBy(x, y);
158 | }
159 | }
160 |
161 | @Override
162 | public boolean fling(int velocityX, int velocityY) {
163 | log("fling: velocityY " + velocityY);
164 | return super.fling(velocityX, velocityY);
165 | }
166 |
167 | /**
168 | * 当滚到顶或底部的时候 否联动parent滚动
169 | * @param direction < 0 向下滚,> 0 向上滚
170 | * @return
171 | */
172 | protected boolean isLinkedParentFling(int direction) {
173 | return isLinkedParent;
174 | }
175 |
176 | public void setLinkedParent(boolean linkedParent) {
177 | isLinkedParent = linkedParent;
178 | }
179 |
180 | protected boolean isCanScrollVertically(int direction) {
181 | boolean flag = false;
182 | switch (getNestedScrollModel()) {
183 | case MODEL_ALL:
184 | flag = canScrollVertically(direction);
185 | break;
186 | case MODEL_DOWN:
187 | flag = direction < 0 && canScrollVertically(direction);
188 | break;
189 | case MODEL_UP:
190 | flag = direction > 0 && canScrollVertically(direction);
191 | break;
192 | case MODEL_NONE:
193 | flag = false;
194 | break;
195 | }
196 | return flag;
197 | }
198 |
199 | @Override
200 | public boolean onTouchEvent(MotionEvent ev) {
201 | if (ev.getAction() == MotionEvent.ACTION_DOWN) {
202 | lastDy = 0;
203 | }
204 | return super.onTouchEvent(ev);
205 | }
206 |
207 | @Override
208 | public boolean onInterceptTouchEvent(MotionEvent ev) {
209 | if (ev.getAction() == MotionEvent.ACTION_DOWN) {
210 | lastDy = 0;
211 | }
212 | return super.onInterceptTouchEvent(ev);
213 | }
214 |
215 | @Override
216 | public boolean startNestedScroll(int axes, int type) {
217 | log("startNestedScroll: axes " + axes + ", type " + type);
218 | lastDy = 0;
219 | return super.startNestedScroll(axes, type);
220 | }
221 |
222 | @Override
223 | public void stopNestedScroll(int type) {
224 | log("stopNestedScroll: type " + type);
225 | super.stopNestedScroll(type);
226 | lastDy = 0;
227 | }
228 |
229 | @Override
230 | public boolean hasNestedScrollingParent(int type) {
231 | boolean flag = super.hasNestedScrollingParent(type);
232 | log("hasNestedScrollingParent: " + flag);
233 | return flag;
234 | }
235 |
236 | @Override
237 | public boolean dispatchNestedPreScroll(int dx, int dy, int[] consumed, int[] offsetInWindow, int type) {
238 | if (!isNestedScrollingEnabled()) return false;
239 | // Log.i(TAG, "dispatchNestedPreScroll: " + type);
240 | lastDy = dy;
241 | if (isCanScrollVertically(dy)) {
242 | if (enableLog) {
243 | log("dispatchNestedPreScroll: false-自己消费1, dy " + dy + ", consumed(" + consumed[0] + ", " + consumed[1] + "), type " + type);
244 | }
245 | return false;
246 | } else {
247 | if (type == ViewCompat.TYPE_TOUCH || isLinkedParentFling(dy)) {
248 | if (enableLog) {
249 | log("dispatchNestedPreScroll: dy " + dy + ", consumed(" + consumed[0] + ", " + consumed[1] + "), type " + type);
250 | }
251 | return super.dispatchNestedPreScroll(dx, dy, consumed, offsetInWindow, type);
252 | } else {
253 | if (enableLog) {
254 | log("dispatchNestedPreScroll: false-自己消费2, dy " + dy + ", consumed(" + consumed[0] + ", " + consumed[1] + "), type " + type);
255 | }
256 | return false;
257 | }
258 | }
259 | }
260 |
261 | @Override
262 | public boolean dispatchNestedScroll(int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed, int[] offsetInWindow, int type) {
263 | if (type == ViewCompat.TYPE_TOUCH || isLinkedParentFling(dyUnconsumed)) {
264 | if (enableLog) {
265 | log("dispatchNestedScroll: dyConsumed " + dyConsumed + ", dyUnconsumed " + dyUnconsumed + ", " + "type " + type);
266 | }
267 | return super.dispatchNestedScroll(dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, offsetInWindow, type);
268 | } else {
269 | if (enableLog) {
270 | log("dispatchNestedScroll: false-自己消费, dyConsumed " + dyConsumed + ", dyUnconsumed " + dyUnconsumed + ", " + "type " + type);
271 | }
272 | return false;
273 | }
274 | }
275 |
276 | @Override
277 | public boolean dispatchNestedPreFling(float velocityX, float velocityY) {
278 | if (isCanScrollVertically((int) velocityY)) {
279 | log("dispatchNestedPreFling: false-自己消费 velocityY " + velocityY);
280 | return false;
281 | } else {
282 | log("dispatchNestedPreFling: velocityY " + velocityY);
283 | if (isNestedScrollingEnabled() && lastDy * velocityY < 0) {
284 | log("onNestedPreFling: lastDy " + lastDy + ", velocityY " + velocityY + ", velocityY 异常", true);
285 | return true;
286 | }
287 | return super.dispatchNestedPreFling(velocityX, velocityY);
288 | }
289 | }
290 |
291 | @Override
292 | public boolean dispatchNestedFling(float velocityX, float velocityY, boolean consumed) {
293 | // return super.dispatchNestedFling(velocityX, velocityY, consumed && isCanScrollVertically((int) velocityY));
294 | log("dispatchNestedFling: velocityY " + velocityY + ", consumed " + consumed);
295 | return super.dispatchNestedFling(velocityX, velocityY, consumed);
296 | }
297 |
298 | private void log(String message) {
299 | log(message, false);
300 | }
301 |
302 | private void log(String message, boolean isForce) {
303 | if (enableLog || isForce) {
304 | Log.i("FCNested", "[" + uuid + "-FCRecyclerView] " + message);
305 | }
306 | }
307 | }
308 |
--------------------------------------------------------------------------------
/library/src/main/java/com/fc/nestedscrollview/FCWebView.java:
--------------------------------------------------------------------------------
1 | package com.fc.nestedscrollview;
2 |
3 | import android.content.Context;
4 | import android.content.res.TypedArray;
5 | import android.util.AttributeSet;
6 | import android.util.Log;
7 | import android.view.MotionEvent;
8 | import android.view.VelocityTracker;
9 | import android.view.ViewConfiguration;
10 | import android.webkit.WebView;
11 |
12 | import androidx.core.view.MotionEventCompat;
13 | import androidx.core.view.NestedScrollingChildHelper;
14 | import androidx.core.view.ViewCompat;
15 |
16 | /**
17 | * Created by fangcan on 2018/3/31.
18 | */
19 |
20 | public class FCWebView extends WebView {
21 | private static final String TAG = "FCWebView";
22 | public static final int MODEL_ALL = 0; //自己优先滚动
23 | public static final int MODEL_UP = 1; //自己优先向上滚动
24 | public static final int MODEL_DOWN = 2; //自己优先向下滚动
25 | public static final int MODEL_NONE = 3; //自己最后滚动
26 |
27 | private final int[] mScrollOffset = new int[2];
28 | private final int[] mScrollConsumed = new int[2];
29 | private int mNestedOffsetY;
30 | private int mLastY;
31 |
32 | private NestedScrollingChildHelper mChildHelper;
33 | private VelocityTracker mVelocityTracker;
34 | private final int mTouchSlop;
35 | private final int mMinimumVelocity;
36 | private final int mMaximumVelocity;
37 | private float parentConsumed;
38 |
39 | private int nestedScrollModel = MODEL_ALL;
40 | // private float startFlingVelocity;
41 | // private long startFlingTime;
42 | // private float consumedDistance;
43 | // private boolean isFling;
44 |
45 | public FCWebView(Context context) {
46 | this(context, null);
47 | }
48 |
49 | public FCWebView(Context context, AttributeSet attrs) {
50 | this(context, attrs, android.R.attr.webViewStyle);
51 | }
52 |
53 | public FCWebView(Context context, AttributeSet attrs, int defStyleAttr) {
54 | super(context, attrs, defStyleAttr);
55 |
56 | TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.FCWebView);
57 | nestedScrollModel = typedArray.getInt(R.styleable.FCWebView_fc_scroll_mode, MODEL_ALL);
58 | boolean isNestedScrollEnable = typedArray.getBoolean(R.styleable.FCWebView_fc_is_nested_scroll_enabled, true);
59 | typedArray.recycle();
60 |
61 | mChildHelper = new NestedScrollingChildHelper(this);
62 | final ViewConfiguration configuration = ViewConfiguration.get(getContext());
63 | mTouchSlop = configuration.getScaledTouchSlop();
64 | mMinimumVelocity = configuration.getScaledMinimumFlingVelocity();
65 | mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
66 | setNestedScrollingEnabled(isNestedScrollEnable);
67 | }
68 |
69 | /**
70 | * 当滚到顶或底部的时候 否联动parent滚动
71 | * @param direction < 0 向下滚,> 0 向上滚
72 | * @return
73 | */
74 | protected boolean isLinkedParentFling(int direction) {
75 | return true;
76 | }
77 |
78 | protected boolean isCanScrollVertically(int direction) {
79 | boolean flag = false;
80 | switch (nestedScrollModel) {
81 | case MODEL_ALL:
82 | flag = canScrollVertically(direction);
83 | break;
84 | case MODEL_DOWN:
85 | flag = direction < 0 && canScrollVertically(direction);
86 | break;
87 | case MODEL_UP:
88 | flag = direction > 0 && canScrollVertically(direction);
89 | break;
90 | case MODEL_NONE:
91 | flag = false;
92 | break;
93 | }
94 | return flag;
95 | }
96 |
97 | /**
98 | * 计算加速度
99 | */
100 | private float calculateVelocity(float startVelocity, float consumedDistance, long duration) {
101 | float velocity1 = consumedDistance / duration * 1000;
102 | float velocity2 = consumedDistance * 8;
103 | if (Math.abs(startVelocity) > mMaximumVelocity) {
104 | return Math.min(Math.abs(startVelocity - velocity1), mMaximumVelocity);
105 | } else {
106 | return Math.min((Math.abs(startVelocity - velocity1) + Math.abs(startVelocity - velocity2)) / 2, mMaximumVelocity);
107 | }
108 | }
109 |
110 | @Override
111 | protected void onScrollChanged(int left, int top, int oldLeft, int oldTop) {
112 | super.onScrollChanged(left, top, oldLeft, oldTop);
113 | // int dy = top - oldTop;
114 | // if (dy != 0 && isNestedScrollEnable) {
115 | // consumedDistance += dy;
116 | // if (isFling && !canScrollVertically(dy)) {
117 | // long duration = System.currentTimeMillis() - this.startFlingTime;
118 | // int velocityY;
119 | // if (dy < 0) {
120 | // velocityY = (int) -calculateVelocity(startFlingVelocity, consumedDistance, duration);
121 | // } else {
122 | // velocityY = (int) calculateVelocity(startFlingVelocity, consumedDistance, duration);
123 | // }
124 | // Log.i(TAG, "consumedDistance: " + consumedDistance + ", velocityY: " + velocityY + ", startFlingVelocity: " + startFlingVelocity + ", startFlingTime: " + duration);
125 | // if (velocityY != 0) {
126 | // if (isLinkedParentFling(dy)) {
127 | // startNestedScroll(ViewCompat.SCROLL_AXIS_VERTICAL);
128 | // dispatchNestedPreFling(0, velocityY);
129 | // stopNestedScroll();
130 | // }
131 | // }
132 | // isFling = false;
133 | // }
134 | // }
135 | }
136 |
137 | @Override
138 | public boolean onTouchEvent(MotionEvent ev) {
139 | if (!isNestedScrollingEnabled()) {
140 | return super.onTouchEvent(ev);
141 | } else {
142 | boolean returnValue = false;
143 |
144 | MotionEvent event = MotionEvent.obtain(ev);
145 | final int action = MotionEventCompat.getActionMasked(event);
146 | if (action == MotionEvent.ACTION_DOWN) {
147 | mNestedOffsetY = 0;
148 | parentConsumed = 0;
149 | // consumedDistance = 0;
150 | // startFlingTime = System.currentTimeMillis();
151 | // isFling = false;
152 | }
153 | int eventY = (int) event.getY();
154 | event.offsetLocation(0, mNestedOffsetY);
155 |
156 | if (mVelocityTracker == null) {
157 | mVelocityTracker = VelocityTracker.obtain();
158 | }
159 | boolean eventAddedToVelocityTracker = false;
160 | switch (action) {
161 | case MotionEvent.ACTION_MOVE:
162 | int deltaY = mLastY - eventY;
163 | if (dispatchNestedPreScroll(0, deltaY, mScrollConsumed, mScrollOffset)) {
164 | deltaY -= mScrollConsumed[1];
165 | parentConsumed += mScrollConsumed[1];
166 | mLastY = eventY - mScrollOffset[1];
167 | mNestedOffsetY += mScrollOffset[1];
168 | }
169 | if (deltaY != 0) {
170 | // if (deltaY > 0) {
171 | event.offsetLocation(0, -mNestedOffsetY);
172 | // }
173 | returnValue = super.onTouchEvent(event);
174 | // if (deltaY < 0) {
175 | if (isCanScrollVertically(deltaY)) {
176 | mLastY = eventY;
177 | } else {
178 | mLastY = eventY - mScrollOffset[1];
179 | if (dispatchNestedScroll(0, 0, 0, deltaY, mScrollOffset)) {
180 | mLastY -= mScrollOffset[1];
181 | event.offsetLocation(0, mScrollOffset[1]);
182 | mNestedOffsetY += mScrollOffset[1];
183 | }
184 | }
185 | // }
186 | }
187 | break;
188 | case MotionEvent.ACTION_DOWN:
189 | returnValue = super.onTouchEvent(event);
190 | mLastY = eventY;
191 | // start NestedScroll
192 | startNestedScroll(ViewCompat.SCROLL_AXIS_VERTICAL);
193 | break;
194 | case MotionEvent.ACTION_UP:
195 | final int actionIndex = MotionEventCompat.getActionIndex(event);
196 | mVelocityTracker.addMovement(event);
197 | eventAddedToVelocityTracker = true;
198 | mVelocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
199 | int mScrollPointerId = MotionEventCompat.getPointerId(event, actionIndex);
200 | float vY = -mVelocityTracker.getYVelocity(mScrollPointerId);
201 | Log.i(TAG, "mVelocityTracker vY: " + vY);
202 | // startFlingVelocity = vY;
203 | // 产生 fling 事件
204 | if (Math.abs(vY) > mMinimumVelocity) {
205 | if (!dispatchNestedPreFling(0, vY)) {
206 | dispatchNestedFling(0, vY, true);
207 | }
208 | // startFlingTime = System.currentTimeMillis();
209 | // consumedDistance = 0;
210 | // isFling = true;
211 | }
212 | if (Math.abs(parentConsumed) < mTouchSlop) {//防止触发点击事件
213 | returnValue = super.onTouchEvent(event);
214 | }
215 | resetTouch();
216 | break;
217 | case MotionEvent.ACTION_CANCEL:
218 | eventAddedToVelocityTracker = true;
219 | returnValue = super.onTouchEvent(event);
220 | resetTouch();
221 | break;
222 | }
223 |
224 | if (!eventAddedToVelocityTracker) {
225 | mVelocityTracker.addMovement(event);
226 | }
227 | return returnValue;
228 | }
229 | }
230 |
231 | private void resetTouch() {
232 | if (mVelocityTracker != null) {
233 | mVelocityTracker.clear();
234 | }
235 | stopNestedScroll();
236 | }
237 |
238 | // Nested Scroll implements
239 | @Override
240 | public void setNestedScrollingEnabled(boolean enabled) {
241 | mChildHelper.setNestedScrollingEnabled(enabled);
242 | }
243 |
244 | @Override
245 | public boolean isNestedScrollingEnabled() {
246 | return mChildHelper.isNestedScrollingEnabled();
247 | }
248 |
249 | @Override
250 | public boolean startNestedScroll(int axes) {
251 | return mChildHelper.startNestedScroll(axes);
252 | }
253 |
254 | @Override
255 | public void stopNestedScroll() {
256 | mChildHelper.stopNestedScroll();
257 | }
258 |
259 | @Override
260 | public boolean hasNestedScrollingParent() {
261 | return mChildHelper.hasNestedScrollingParent();
262 | }
263 |
264 | @Override
265 | public boolean dispatchNestedScroll(int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed,
266 | int[] offsetInWindow) {
267 | return mChildHelper.dispatchNestedScroll(dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, offsetInWindow);
268 | }
269 |
270 | @Override
271 | public boolean dispatchNestedPreScroll(int dx, int dy, int[] consumed, int[] offsetInWindow) {
272 | if (isCanScrollVertically(dy)) {
273 | return false;
274 | } else {
275 | return mChildHelper.dispatchNestedPreScroll(dx, dy, consumed, offsetInWindow);
276 | }
277 | }
278 |
279 | @Override
280 | public boolean dispatchNestedFling(float velocityX, float velocityY, boolean consumed) {
281 | return mChildHelper.dispatchNestedFling(velocityX, velocityY, consumed);
282 | }
283 |
284 | @Override
285 | public boolean dispatchNestedPreFling(float velocityX, float velocityY) {
286 | if (isCanScrollVertically((int) velocityY)) {//如果我没有滚动到最顶上,则不传递给parent
287 | return false;
288 | } else {
289 | return mChildHelper.dispatchNestedPreFling(velocityX, velocityY);
290 | }
291 | }
292 | }
293 |
--------------------------------------------------------------------------------
/library/src/main/java/com/fc/nestedscrollview/FCNestedScrollView.java:
--------------------------------------------------------------------------------
1 | package com.fc.nestedscrollview;
2 |
3 | import android.content.Context;
4 | import android.content.res.TypedArray;
5 | import android.util.AttributeSet;
6 | import android.util.Log;
7 | import android.view.MotionEvent;
8 | import android.view.View;
9 | import android.webkit.WebView;
10 |
11 | import androidx.annotation.NonNull;
12 | import androidx.annotation.Nullable;
13 | import androidx.core.view.NestedScrollingChild2;
14 | import androidx.core.view.ViewCompat;
15 | import androidx.core.widget.NestedScrollView;
16 | import androidx.recyclerview.widget.RecyclerView;
17 |
18 | import java.util.UUID;
19 | import java.util.concurrent.CopyOnWriteArraySet;
20 |
21 | /**
22 | * Created by fangcan on 2018/3/28.
23 | */
24 |
25 | public class FCNestedScrollView extends NestedScrollView {
26 | private static final String TAG = "FCNestedScrollView";
27 | public static final int MODEL_ALL = 0; //自己优先滚动
28 | public static final int MODEL_UP = 1; //自己优先向上滚动
29 | public static final int MODEL_DOWN = 2; //自己优先向下滚动
30 | public static final int MODEL_NONE = 3; //自己最后滚动
31 |
32 | /** 当滚到顶或底部的时候 需要fling 的child view */
33 | private View linkChildView;
34 | private View targetFlingView;
35 | private boolean isFling;
36 |
37 | private int nestedScrollModel = MODEL_ALL;
38 | /** 当滚到顶或底部的时候 否联动parent滚动 */
39 | private boolean isLinkedParent;
40 | /** 当自己滚到顶或底部的时候 否联动child滚动 */
41 | private boolean isLinkedChild;
42 | private boolean isNestedScrolling2Enabled = true;
43 | // 测试看日志专用
44 | public String logId;
45 | public boolean enableLog = false;
46 |
47 | public int lastDy;
48 |
49 | private CopyOnWriteArraySet listeners = new CopyOnWriteArraySet<>();
50 | private CopyOnWriteArraySet onScrollStateListeners = new CopyOnWriteArraySet<>();
51 |
52 | private long lastScrollUpdate = -1;
53 | private int scrollTaskInterval = 100;
54 | private boolean isTouched = false;
55 |
56 | // 用来修复 NestedScrollView 嵌套滚动 bug
57 | private boolean nonTouchFlag = false;
58 | private NestedScrollingChild2 nonTouchTargetView = null;
59 |
60 | private Runnable scrollingRunnable = new Runnable() {
61 | @Override
62 | public void run() {
63 | long time = System.currentTimeMillis() - lastScrollUpdate;
64 | if ((!isTouched && time > scrollTaskInterval) || time > scrollTaskInterval * 3) {
65 | // Scrolling has stopped.
66 | lastScrollUpdate = -1;
67 | notifyScrollEnd();
68 | } else {
69 | postDelayed(this, scrollTaskInterval);
70 | }
71 | }
72 | };
73 |
74 | public void addOnScrollStateListener(OnScrollStateListener onScrollStateListener) {
75 | onScrollStateListeners.add(onScrollStateListener);
76 | }
77 |
78 | public void removeOnScrollStateListener(OnScrollStateListener onScrollStateListener) {
79 | onScrollStateListeners.remove(onScrollStateListener);
80 | }
81 |
82 | private NestedScrollView.OnScrollChangeListener defaultListener = new OnScrollChangeListener() {
83 | @Override
84 | public void onScrollChange(NestedScrollView nestedScrollView, int i, int i1, int i2, int i3) {
85 | if (listeners != null && !listeners.isEmpty()) {
86 | for (NestedScrollView.OnScrollChangeListener listener : listeners) {
87 | if (listener != null) {
88 | listener.onScrollChange(nestedScrollView, i, i1, i2, i3);
89 | }
90 | }
91 | }
92 | }
93 | };
94 |
95 | public FCNestedScrollView(Context context) {
96 | this(context, null);
97 | }
98 |
99 | public FCNestedScrollView(Context context, AttributeSet attrs) {
100 | this(context, attrs, 0);
101 | }
102 |
103 | public FCNestedScrollView(Context context, AttributeSet attrs, int defStyleAttr) {
104 | super(context, attrs, defStyleAttr);
105 | logId = UUID.randomUUID().toString();
106 |
107 | TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.FCNestedScrollView);
108 | nestedScrollModel = typedArray.getInt(R.styleable.FCNestedScrollView_fc_scroll_mode, MODEL_ALL);
109 | isLinkedParent = typedArray.getBoolean(R.styleable.FCNestedScrollView_fc_is_linked_parent, true);
110 | isLinkedChild = typedArray.getBoolean(R.styleable.FCNestedScrollView_fc_is_linked_child, true);
111 | typedArray.recycle();
112 |
113 | setOnScrollChangeListener(defaultListener);
114 | }
115 |
116 | public void addOnScrollChangeListener(NestedScrollView.OnScrollChangeListener listener) {
117 | if (listener != null) {
118 | listeners.add(listener);
119 | }
120 | }
121 |
122 | public void removeOnScrollChangeListener(NestedScrollView.OnScrollChangeListener listener) {
123 | listeners.remove(listener);
124 | }
125 |
126 | private void notifyScrollStart() {
127 | for (OnScrollStateListener onScrollStateListener : onScrollStateListeners) {
128 | onScrollStateListener.onScrollStart();
129 | }
130 | }
131 |
132 | private void notifyScrollEnd() {
133 | for (OnScrollStateListener onScrollStateListener : onScrollStateListeners) {
134 | onScrollStateListener.onScrollEnd();
135 | }
136 | }
137 |
138 | public boolean isNestedScrolling2Enabled() {
139 | return isNestedScrolling2Enabled;
140 | }
141 |
142 | public void setNestedScrolling2Enabled(boolean enabled) {
143 | this.isNestedScrolling2Enabled = enabled;
144 | }
145 |
146 | public void setLinkChildView(View linkChildView) {
147 | this.linkChildView = linkChildView;
148 | }
149 |
150 | protected View getLinkedFlingView() {
151 | if (targetFlingView != null) {
152 | return targetFlingView;
153 | } else {
154 | return linkChildView;
155 | }
156 | }
157 |
158 | /**
159 | * 当滚到顶或底部的时候 否联动parent滚动
160 | * @param direction < 0 向下滚,> 0 向上滚
161 | * @return
162 | */
163 | protected boolean isLinkedParentFling(int direction) {
164 | return isLinkedParent;
165 | }
166 |
167 | public void setLinkedParent(boolean linkedParent) {
168 | isLinkedParent = linkedParent;
169 | }
170 |
171 | /**
172 | * 当滚到顶或底部的时候 否联动child滚动
173 | * @param direction < 0 向下滚,> 0 向上滚
174 | * @return
175 | */
176 | protected boolean isLinkedChildFling(int direction, View flingView) {
177 | if (!isLinkedChild || flingView == null) {
178 | return false;
179 | }
180 | if (flingView instanceof FCFlingView) {
181 | return ((FCFlingView) flingView).canFling(direction);
182 | } else {
183 | return flingView.canScrollVertically(direction);
184 | }
185 | }
186 |
187 | /**
188 | * 当滚到顶或底部的时候, 联动child滚动
189 | * @param flingView
190 | * @param velocityY
191 | */
192 | protected void linkedChildFling(View flingView, int velocityY) {
193 | if (flingView instanceof FCFlingView) {
194 | ((FCFlingView) flingView).fling(velocityY);
195 | } else if (flingView instanceof RecyclerView) {
196 | ((RecyclerView) flingView).fling(0, velocityY);
197 | } else if (flingView instanceof NestedScrollView) {
198 | ((NestedScrollView) flingView).fling(velocityY);
199 | } else if (flingView instanceof WebView) {
200 | ((WebView) flingView).flingScroll(0, velocityY);
201 | }
202 | }
203 |
204 | protected boolean isCanScrollVertically(int direction) {
205 | boolean flag = false;
206 | switch (nestedScrollModel) {
207 | case MODEL_ALL:
208 | flag = canScrollVertically(direction);
209 | break;
210 | case MODEL_DOWN:
211 | flag = direction < 0 && canScrollVertically(direction);
212 | break;
213 | case MODEL_UP:
214 | flag = direction > 0 && canScrollVertically(direction);
215 | break;
216 | case MODEL_NONE:
217 | flag = false;
218 | break;
219 | }
220 | return flag;
221 | }
222 |
223 | @Override
224 | protected void onScrollChanged(int left, int top, int oldLeft, int oldTop) {
225 | super.onScrollChanged(left, top, oldLeft, oldTop);
226 | int dy = top - oldTop;
227 | if (dy != 0) {
228 | if (isFling && !canScrollVertically(dy)) {
229 | View view = getLinkedFlingView();
230 | if (isLinkedChildFling(dy, view)) {
231 | // Log.i(TAG, "onScrollChanged linkedChildFling ");
232 | int velocityY = (int) FcNestedUtil.getCurrVelocityY(this);
233 | if (lastDy * velocityY < 0) {
234 | log("onScrollChanged: lastDy " + lastDy + ", velocityY " + velocityY + ", velocityY 异常", true);
235 | } else {
236 | if (velocityY != 0) {
237 | linkedChildFling(view, velocityY);
238 | }
239 | }
240 | }
241 | isFling = false;
242 | }
243 | }
244 |
245 | if (!onScrollStateListeners.isEmpty()) {
246 | if (lastScrollUpdate == -1) {
247 | notifyScrollStart();
248 | postDelayed(scrollingRunnable, scrollTaskInterval);
249 | }
250 | lastScrollUpdate = System.currentTimeMillis();
251 | }
252 | }
253 |
254 | @Override
255 | public boolean onTouchEvent(MotionEvent ev) {
256 | if (ev.getAction() == MotionEvent.ACTION_DOWN) {
257 | lastDy = 0;
258 | isTouched = true;
259 | } else if (ev.getAction() == MotionEvent.ACTION_UP) {
260 | isTouched = false;
261 | }
262 | return super.onTouchEvent(ev);
263 | }
264 |
265 | @Override
266 | public boolean onInterceptTouchEvent(MotionEvent ev) {
267 | if (ev.getAction() == MotionEvent.ACTION_DOWN) {
268 | lastDy = 0;
269 | targetFlingView = null;
270 | isFling = false;
271 | isTouched = true;
272 | if (nonTouchFlag && nonTouchTargetView != null) {
273 | nonTouchTargetView.stopNestedScroll(ViewCompat.TYPE_NON_TOUCH);
274 | }
275 | } else if (ev.getAction() == MotionEvent.ACTION_UP) {
276 | isTouched = false;
277 | }
278 | return super.onInterceptTouchEvent(ev);
279 | }
280 |
281 | @Override
282 | public boolean startNestedScroll(int axes, int type) {
283 | log("startNestedScroll: axes " + axes + ", type " + type);
284 | boolean flag = super.startNestedScroll(axes, type);
285 | lastDy = 0;
286 | return flag;
287 | }
288 |
289 | @Override
290 | public void stopNestedScroll(int type) {
291 | log("stopNestedScroll: type " + type);
292 | super.stopNestedScroll(type);
293 | lastDy = 0;
294 | }
295 |
296 | @Override
297 | public boolean hasNestedScrollingParent(int type) {
298 | boolean flag = super.hasNestedScrollingParent(type);
299 | log("hasNestedScrollingParent: " + flag);
300 | return flag;
301 | }
302 |
303 | @Override
304 | public boolean dispatchNestedPreFling(float velocityX, float velocityY) {
305 | if (isCanScrollVertically((int) velocityY)) {
306 | log("dispatchNestedPreFling: false-自己消费, velocityY " + velocityY);
307 | return false;
308 | } else {
309 | log("dispatchNestedPreFling: velocityY " + velocityY);
310 | if (isNestedScrollingEnabled() && lastDy * velocityY < 0) {
311 | log("onNestedPreFling: lastDy " + lastDy + ", velocityY " + velocityY + ", velocityY 异常", true);
312 | return true;
313 | }
314 | return super.dispatchNestedPreFling(velocityX, velocityY);
315 | }
316 | }
317 |
318 | @Override
319 | public boolean dispatchNestedFling(float velocityX, float velocityY, boolean consumed) {
320 | isFling = velocityY != 0 && canScrollVertically((int) velocityY);
321 | if (!isFling && !consumed) {
322 | View view = getLinkedFlingView();
323 | if (isLinkedChildFling((int) velocityY, view)) {
324 | log("dispatchNestedFling: false-child消费, velocityY " + velocityY);
325 | linkedChildFling(view, (int) velocityY);
326 | return false;
327 | } else {
328 | log("dispatchNestedFling: velocityY " + velocityY + ", consumed " + consumed);
329 | return super.dispatchNestedFling(velocityX, velocityY, consumed);
330 | }
331 | } else {
332 | log("dispatchNestedFling: velocityY " + velocityY + ", consumed " + consumed);
333 | return super.dispatchNestedFling(velocityX, velocityY, consumed);
334 | }
335 | }
336 |
337 | @Override
338 | public boolean dispatchNestedPreScroll(int dx, int dy, int[] consumed, int[] offsetInWindow, int type) {
339 | if (!isNestedScrollingEnabled()) return false;
340 | lastDy = dy;
341 | if (isCanScrollVertically(dy)) {
342 | if (enableLog) {
343 | log("dispatchNestedPreScroll: false-自己消费1, dy " + dy + ", consumed(" + consumed[0] + ", " + consumed[1] + "), type " + type);
344 | }
345 | return false;
346 | } else {
347 | if (type == ViewCompat.TYPE_TOUCH) {
348 | if (enableLog) {
349 | log("dispatchNestedPreScroll: dy " + dy + ", consumed(" + consumed[0] + ", " + consumed[1] + "), type " + type);
350 | }
351 | return super.dispatchNestedPreScroll(dx, dy, consumed, offsetInWindow, type);
352 | } else {
353 | if (isLinkedParentFling(dy)) {
354 | if (enableLog) {
355 | log("dispatchNestedPreScroll: dy " + dy + ", consumed(" + consumed[0] + ", " + consumed[1] + "), type " + type);
356 | }
357 | return super.dispatchNestedPreScroll(dx, dy, consumed, offsetInWindow, type);
358 | } else {
359 | if (enableLog) {
360 | log("dispatchNestedPreScroll: false-自己消费2, dy " + dy + ", consumed(" + consumed[0] + ", " + consumed[1] + "), type " + type);
361 | }
362 | return false;
363 | }
364 | }
365 | }
366 | }
367 |
368 | @Override
369 | public boolean dispatchNestedScroll(int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed, int[] offsetInWindow, int type) {
370 | if (type == ViewCompat.TYPE_TOUCH) {
371 | if (enableLog) {
372 | log("dispatchNestedScroll: dyConsumed " + dyConsumed + ", dyUnconsumed " + dyUnconsumed + ", " + "type " + type);
373 | }
374 | return super.dispatchNestedScroll(dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, offsetInWindow, type);
375 | } else {
376 | if (isLinkedParentFling(dyUnconsumed)) {
377 | if (enableLog) {
378 | log("dispatchNestedScroll: dyConsumed " + dyConsumed + ", dyUnconsumed " + dyUnconsumed + ", " + "type " + type);
379 | }
380 | return super.dispatchNestedScroll(dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, offsetInWindow, type);
381 | } else {
382 | if (enableLog) {
383 | log("dispatchNestedScroll: false-自己消费, dyConsumed " + dyConsumed + ", dyUnconsumed " + dyUnconsumed + ", " + "type " + type);
384 | }
385 | return false;
386 | }
387 | }
388 | }
389 |
390 | @Override
391 | public void fling(int velocityY) {
392 | log("fling: velocityY " + velocityY);
393 | super.fling(velocityY);
394 | isFling = true;
395 | }
396 |
397 | @Override
398 | public boolean onNestedFling(View target, float velocityX, float velocityY, boolean consumed) {
399 | targetFlingView = target;
400 | isFling = true;
401 | if (!consumed && !canScrollVertically((int) velocityY)) {
402 | View view = getLinkedFlingView();
403 | if (isLinkedChildFling((int) velocityY, view)) {
404 | log("onNestedFling: child消费, velocityY " + velocityY);
405 | linkedChildFling(view, (int) velocityY);
406 | return true;
407 | }
408 | }
409 | log("onNestedFling: velocityY " + velocityY + ", consumed " + consumed);
410 | return super.onNestedFling(target, velocityX, velocityY, consumed);
411 | }
412 |
413 | /**
414 | * @return
415 | */
416 | @Override
417 | public boolean onNestedPreFling(View target, float velocityX, float velocityY) {
418 | //先让parent处理
419 | log("onNestedPreFling: velocityY " + velocityY);
420 | boolean flag = super.onNestedPreFling(target, velocityX, velocityY);
421 | //如果parent没有处理,我再处理
422 | if (!flag) {
423 | flag = canScrollVertically((int) velocityY);
424 | if (flag) {
425 | log("onNestedPreFling: 手动调用onNestedFling, velocityY " + velocityY + ", consumed false");
426 | onNestedFling(target, velocityX, velocityY, false);
427 | }
428 | }
429 | // Log.i(TAG, "onNestedPreFling " + flag);
430 | return flag;
431 | }
432 |
433 | @Override
434 | public boolean onStartNestedScroll(@NonNull View child, @NonNull View target, int axes, int type) {
435 | log("onStartNestedScroll: axes " + axes + ", type " + type);
436 | boolean flag = super.onStartNestedScroll(child, target, axes, type);
437 | if (flag) {
438 | if (type == ViewCompat.TYPE_NON_TOUCH && target instanceof NestedScrollingChild2) {
439 | nonTouchFlag = true;
440 | nonTouchTargetView = (NestedScrollingChild2) target;
441 | }
442 | }
443 | return flag;
444 | }
445 |
446 | @Override
447 | public void onNestedScrollAccepted(@NonNull View child, @NonNull View target, int axes, int type) {
448 | log("onNestedScrollAccepted: axes " + axes + ", type " + type);
449 | super.onNestedScrollAccepted(child, target, axes, type);
450 | }
451 |
452 | @Override
453 | public void onStopNestedScroll(@NonNull View target, int type) {
454 | log("onNestedScrollAccepted: type " + type);
455 | super.onStopNestedScroll(target, type);
456 | if (type == ViewCompat.TYPE_NON_TOUCH) {
457 | nonTouchTargetView = null;
458 | nonTouchFlag = false;
459 | }
460 | }
461 |
462 | @Override
463 | public void onNestedScroll(@NonNull View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed, int type) {
464 | if (type == ViewCompat.TYPE_TOUCH || isNestedScrolling2Enabled()) {
465 | final int oldScrollY = getScrollY();
466 | scrollBy(0, dyUnconsumed);
467 | if (enableLog) {
468 | log("onNestedScroll: 先消费, 后传递 dyUnconsumed " + dyUnconsumed + ", type " + type);
469 | }
470 | final int myConsumed = getScrollY() - oldScrollY;
471 | final int myUnconsumed = dyUnconsumed - myConsumed;
472 | dispatchNestedScroll(0, myConsumed, 0, myUnconsumed, null, type);
473 | } else {
474 | log("onNestedScroll: 不消费, 不传递");
475 | }
476 | }
477 |
478 | @Override
479 | public void onNestedPreScroll(@NonNull View target, int dx, int dy, @Nullable int[] consumed, int type) {
480 | if (type == ViewCompat.TYPE_TOUCH || isNestedScrolling2Enabled()) {
481 | if (enableLog) {
482 | log("onNestedPreScroll: 先传递,后消费, dy " + dy + ", type " + type);
483 | }
484 | dispatchNestedPreScroll(dx, dy, consumed, null, type);
485 | if (dy != consumed[1] && canScrollVertically(dy)) {
486 | scrollBy(0, dy - consumed[1]);//减去parent消费的距离
487 | consumed[1] = dy;
488 | }
489 | } else {
490 | log("onNestedPreScroll: 不消费, 不传递");
491 | }
492 | }
493 |
494 | private void log(String message) {
495 | log(message, false);
496 | }
497 |
498 | private void log(String message, boolean isForce) {
499 | if (enableLog || isForce) {
500 | Log.i("FCNested", "[" + logId + "-FCNestedScrollView] " + message);
501 | }
502 | }
503 | }
504 |
--------------------------------------------------------------------------------