一、NestedScrollView
1. 什么是 NestedScrollView
NestedScrollView
是 Android 中一个用于处理垂直方向滚动的布局组件,它继承自 FrameLayout
,同时支持嵌套滑动(Nested Scrolling)机制。相比于传统的 ScrollView
,NestedScrollView
专为解决嵌套滚动冲突问题设计,能够与其他支持嵌套滑动的子视图(如 RecyclerView
、ViewPager
等)协同工作。
2. 定义
NestedScrollView
是 Android Jetpack 中的组件,用于容纳能够垂直滚动的视图。当页面布局的内容超过屏幕高度时,可以通过滚动展示全部内容。同时,NestedScrollView
在滚动的过程中与子视图可以进行事件协作。
3. 与ScrollView的区别
NestedScrollView
和 ScrollView
的主要区别在于它具备"嵌套滑动"(Nested Scrolling)功能。在 Android 中,嵌套滑动是一种滚动冲突处理机制,允许父视图和子视图协同工作,共同处理滑动事件。这种机制非常有用,特别是当你在一个滚动视图中嵌套另一个滚动视图时,它能够有效避免滑动冲突。
ScrollView
:不支持嵌套滑动,通常会出现父子滑动视图的事件冲突,导致滑动体验不佳。NestedScrollView
:内置嵌套滑动机制,能够更好地处理父子视图的滚动事件,使页面更加流畅。
4. 常见使用场景
- 表单页面 :在一个页面中,可能会有许多输入框、按钮等,当这些内容超过屏幕时,使用
NestedScrollView
可以让整个页面可滚动。 - 嵌套滚动视图 :当你在一个滚动视图(如
RecyclerView
)中嵌套了另一个滚动视图(如ViewPager
或HorizontalScrollView
)时,NestedScrollView
能避免滑动事件冲突。 - 与
CoordinatorLayout
结合 :NestedScrollView
可以与CoordinatorLayout
搭配使用,处理如CollapsingToolbarLayout
、AppBarLayout
等复杂的滚动联动效果。
5. 简单使用方法
下面是一个CoordinatorLayout中使用NestedScrollView并嵌套RecyclerView的简单用法:
xml
<androidx.coordinatorlayout.widget.CoordinatorLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<androidx.core.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:nestedScrollingEnabled="false" />
</LinearLayout>
</androidx.core.widget.NestedScrollView>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
CoordinatorLayout
是整个布局的容器,用于协调多个滚动视图的交互,尽管当前只涉及了NestedScrollView
。NestedScrollView
管理页面的垂直滚动,并能够与其他支持嵌套滚动的视图配合使用。它容纳了RecyclerView
并负责控制滚动。RecyclerView
展示的是具体的滚动内容,使用android:nestedScrollingEnabled="false"
让NestedScrollView
完全负责滚动处理,避免滚动冲突。
二、滚动冲突和滑动冲突
1. 区别
滚动冲突(Scroll Conflict):
滚动冲突发生在嵌套滚动视图中。例如,一个 ScrollView
内部嵌套了一个 RecyclerView
,或者一个 ViewPager
内嵌了一个 ScrollView
。当用户在一个滚动视图上滑动时,系统需要决定哪个视图应该接收滚动事件,从而可能导致滚动冲突。
滑动冲突(Touch Conflict):
滑动冲突通常发生在多个视图或组件尝试处理相同的触摸事件时。例如,一个 ViewPager
和一个 RecyclerView
都可以响应滑动手势,这会导致滑动冲突。滑动冲突通常涉及触摸事件的处理,而不是滚动事件的嵌套。
2. 处理方式
处理滚动冲突
滚动冲突通常发生在嵌套的滚动视图中,例如在一个 ScrollView
中嵌套了一个 RecyclerView
。以下是几种常见的解决方法:
- 禁用子视图的嵌套滚动
-
描述:禁用子视图的嵌套滚动功能,让父视图完全控制滚动行为。
-
示例:
xml<androidx.recyclerview.widget.RecyclerView android:id="@+id/recyclerView" android:layout_width="match_parent" android:layout_height="wrap_content" android:nestedScrollingEnabled="false" />
- 自定义
Behavior
-
描述 :创建自定义的
Behavior
类,控制滚动行为和滚动事件的传递。 -
示例:
javapackage com.example.nestedscrollviewtest; import android.content.Context; import android.util.AttributeSet; import android.view.View; import androidx.annotation.NonNull; import androidx.coordinatorlayout.widget.CoordinatorLayout; import androidx.core.view.ViewCompat; import androidx.core.widget.NestedScrollView; public class CustomBehavior extends CoordinatorLayout.Behavior<NestedScrollView> { public CustomBehavior() { super(); } public CustomBehavior(Context context, AttributeSet attrs) { super(context, attrs); } @Override public boolean onStartNestedScroll(@NonNull CoordinatorLayout coordinatorLayout, @NonNull NestedScrollView child, @NonNull View directTargetChild, @NonNull View target, int axes, int type) { // 控制是否响应嵌套滑动 return axes == ViewCompat.SCROLL_AXIS_VERTICAL; } @Override public void onNestedPreScroll(@NonNull CoordinatorLayout coordinatorLayout, @NonNull NestedScrollView child, @NonNull View target, int dx, int dy, @NonNull int[] consumed, int type) { // 处理嵌套滑动前的事件 } }
xml<androidx.coordinatorlayout.widget.CoordinatorLayout android:layout_width="match_parent" android:layout_height="match_parent" xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto"> <androidx.core.widget.NestedScrollView android:layout_width="match_parent" android:layout_height="match_parent" app:layout_behavior="com.example.nestedscrollviewtest.CustomBehavior"> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical"> <androidx.recyclerview.widget.RecyclerView android:id="@+id/recyclerView" android:layout_width="match_parent" android:layout_height="wrap_content" android:nestedScrollingEnabled="false" /> </LinearLayout> </androidx.core.widget.NestedScrollView> </androidx.coordinatorlayout.widget.CoordinatorLayout>
处理滑动冲突
1. 外部拦截(onInterceptTouchEvent
)
- 作用:用于拦截触摸事件,决定是否由当前视图处理该事件。
- 如何工作 :在父视图的
onInterceptTouchEvent
方法中,父视图会决定是否拦截事件并交给自己处理。如果返回true
,父视图将处理事件;如果返回false
,事件将传递给子视图处理。
NestedScrollView
中的外部拦截
NestedScrollView
作为一个容器视图,通常会处理其内部的滚动事件。为了确保它能够正确处理滚动事件,可以重写 NestedScrollView
的 onInterceptTouchEvent
方法来拦截触摸事件,并决定是否让其处理:
java
package com.example.nestedscrollviewtest;
import android.content.Context;
import android.util.AttributeSet;
import android.view.MotionEvent;
import androidx.core.widget.NestedScrollView;
public class CustomNestedScrollView extends NestedScrollView {
public CustomNestedScrollView(Context context) {
super(context);
}
public CustomNestedScrollView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public CustomNestedScrollView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
// 根据需要判断是否拦截事件
// 例如,可以根据事件的类型或滚动方向来决定是否拦截
return super.onInterceptTouchEvent(ev);
}
}
2. 内部拦截(onTouchEvent
)
- 作用:用于处理视图接收到的触摸事件。
- 如何工作 :在子视图的
onTouchEvent
方法中,子视图会处理传递给它的事件。如果子视图无法处理事件,它可以将事件传递给父视图或其他视图。
RecyclerView
的 onTouchEvent
方法负责处理其自己的触摸事件。通常,你不需要对 RecyclerView
的 onTouchEvent
进行特别处理,但要确保它正常工作。
java
public class CustomRecyclerView extends RecyclerView {
public CustomRecyclerView(Context context) {
super(context);
}
public CustomRecyclerView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public CustomRecyclerView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
public boolean onTouchEvent(MotionEvent e) {
// 默认情况下,RecyclerView 处理自己的触摸事件
return super.onTouchEvent(e);
}
}
3.拦截机制
滚动冲突和拦截机制
当你处理滚动冲突时(例如ScrollView
中嵌套 RecyclerView
),内部拦截和外部拦截的目的都是在父视图和子视图之间决定谁来处理滚动事件。在这种情况下,使用拦截机制是为了解决多个滚动视图对事件的争夺。
- 内部拦截法:子视图决定是否处理事件。
- 适用于子视图滚动逻辑复杂的情况,通常用于滚动冲突。
- 外部拦截法:父视图决定是否处理事件。
- 适用于父视图需要优先处理事件的情况,常用于解决滚动冲突。
滑动冲突和拦截机制
滑动冲突是由多个视图组件对触摸事件的竞争 引发的,而不是滚动事件的嵌套。在滑动冲突的场景下,拦截机制也同样适用。比如在ViewPager
和RecyclerView
这类视图中,事件分发的冲突会涉及滑动方向和手势的识别。
- 内部拦截法:子视图根据触摸事件的方向和类型,决定是否继续处理触摸事件或传递给父视图。
- 适用于需要子视图有更多自定义处理的滑动场景。
- 外部拦截法:父视图判断当前手势是否属于自己的处理范围,例如 ViewPager 判断是水平滑动,则拦截事件。
- 常用于父视图需要根据手势类型做判断的滑动冲突场景。
以上就是本篇博客的所有内容
已经到底啦!!