ListView 与 ScrollView 的自定义事件处理

ListVew

import android.view.MotionEvent;
import android.widget.ListView;

public class CustomListView extends ListView {
private CustomScrollView parentScrollView;
private boolean isScrollEnabled = true;

public CustomListView(Context context) {
this(context, null);
}

public CustomListView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}

public CustomListView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}

/**
\* 设置父ScrollView,用于控制父容器的滚动行为
*/
public void setParentScrollView(CustomScrollView parentScrollView) {
this.parentScrollView = parentScrollView;
}

/**
\* 设置ListView是否可以滚动
*/
public void setScrollEnabled(boolean enabled) {
isScrollEnabled = enabled;
}

@Override
public boolean onTouchEvent(MotionEvent ev) {
if (!isScrollEnabled) {
// 如果禁用了滚动,直接返回false,不处理事件
return false;
}

switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
// 按下时通知父容器不要拦截事件
if (parentScrollView != null) {
parentScrollView.requestDisallowInterceptTouchEvent(true);
}
break;

case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
// 释放时允许父容器拦截事件
if (parentScrollView != null) {
parentScrollView.requestDisallowInterceptTouchEvent(false);
}
break;
}

return super.onTouchEvent(ev);
}

/**
\* 判断ListView是否在顶部
*/
public boolean isAtTop() {
if (getChildCount() == 0) {
return true;
}
return getFirstVisiblePosition() == 0 && getChildAt(0).getTop() >= 0;
}

/**
\* 判断ListView是否在底部
*/
public boolean isAtBottom() {
if (getChildCount() == 0 || getAdapter() == null) {
return true;
}
return getLastVisiblePosition() == getAdapter().getCount() - 1 &&
getChildAt(getChildCount() - 1).getBottom() <= getHeight();
}
}

ScrollView

import android.content.Context;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.ViewConfiguration;
import android.widget.ScrollView;

public class CustomScrollView extends ScrollView {
private int touchSlop;
private float downX;
private float downY;
private boolean isScrolling;
private boolean interceptScroll;

public CustomScrollView(Context context) {
this(context, null);
}

public CustomScrollView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}

public CustomScrollView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context);
}

private void init(Context context) {
touchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
interceptScroll = true;
}

/**
\* 设置是否拦截滚动事件
*/
public void setInterceptScroll(boolean interceptScroll) {
this.interceptScroll = interceptScroll;
}

@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
if (!interceptScroll) {
// 完全不拦截事件,让子View处理
return false;
}

switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
downX = ev.getX();
downY = ev.getY();
isScrolling = false;
// 必须返回false,让子View也能收到DOWN事件
return false;

case MotionEvent.ACTION_MOVE:
if (isScrolling) {
// 已经确定滚动,拦截事件
return true;
}

float moveX = ev.getX();
float moveY = ev.getY();
float dx = Math.abs(moveX - downX);
float dy = Math.abs(moveY - downY);

// 判断是否达到滚动阈值
if (dy > touchSlop && dy > dx) {
// 垂直方向移动超过阈值,且大于水平移动
isScrolling = true;
// 根据条件决定是否拦截事件
return shouldInterceptScroll(moveY - downY);
}
break;

case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
isScrolling = false;
break;
}

return super.onInterceptTouchEvent(ev);
}

/**
\* 根据滚动方向和当前位置决定是否拦截事件
*/
private boolean shouldInterceptScroll(float deltaY) {
if (deltaY > 0) {
// 向下滚动
return isAtTop();
} else {
// 向上滚动
return isAtBottom();
}
}

/**
\* 判断ScrollView是否在顶部
*/
private boolean isAtTop() {
return getScrollY() == 0;
}

/**
\* 判断ScrollView是否在底部
*/
private boolean isAtBottom() {
if (getChildCount() == 0) return true;
int contentHeight = getChildAt(0).getHeight() - getPaddingBottom() - getPaddingTop();
return getScrollY() >= contentHeight - getHeight();
}
}

MainActivity

import android.os.Bundle;
import android.view.View;
import android.widget.ArrayAdapter;
import androidx.appcompat.app.AppCompatActivity;

import java.util.ArrayList;
import java.util.List;

public class MainActivity extends AppCompatActivity {

private CustomScrollView customScrollView;
private CustomListView customListView;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

// 初始化自定义视图
customScrollView = findViewById(R.id.customScrollView);
customListView = findViewById(R.id.customListView);

// 设置ListView的父ScrollView,用于事件通信
customListView.setParentScrollView(customScrollView);

// 初始化ListView数据
List<String> dataList = new ArrayList<>();
for (int i = 1; i <= 30; i++) {
dataList.add("ListView Item " + i);
}

ArrayAdapter<String> adapter = new ArrayAdapter<>(
this, android.R.layout.simple_list_item_1, dataList);
customListView.setAdapter(adapter);

// 设置自定义滚动行为
setupCustomScrollBehavior();
}

private void setupCustomScrollBehavior() {
// 方法1:禁用ListView滚动,让ScrollView处理所有滚动
// customListView.setScrollEnabled(false);

// 方法2:智能滚动处理
customListView.setOnTouchListener((v, event) -> {
switch (event.getAction()) {
case MotionEvent.ACTION_MOVE:
// 计算移动方向
float deltaY = event.getY() - customListView.getDownY();

if (deltaY > 0 && customListView.isAtTop()) {
// 向下滚动且ListView在顶部,让父ScrollView处理
customScrollView.setInterceptScroll(true);
customListView.setScrollEnabled(false);
} else if (deltaY < 0 && customListView.isAtBottom()) {
// 向上滚动且ListView在底部,让父ScrollView处理
customScrollView.setInterceptScroll(true);
customListView.setScrollEnabled(false);
} else {
// 其他情况,让ListView处理滚动
customScrollView.setInterceptScroll(false);
customListView.setScrollEnabled(true);
}
break;
}
return false;
});
}
}

activity_main

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">

<com.example.CustomScrollView
android:id="@+id/customScrollView"
android:layout_width="match_parent"
android:layout_height="match_parent">

<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">

<!-- 顶部固定内容 -->
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="20dp"
android:text="这是顶部固定内容"
android:textSize="18sp"
android:background="#FF5722"
android:textColor="#FFFFFF"/>

<!-- 中间的ListView -->
<com.example.CustomListView
android:id="@+id/customListView"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>

<!-- 底部固定内容 -->
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="20dp"
android:text="这是底部固定内容"
android:textSize="18sp"
android:background="#2196F3"
android:textColor="#FFFFFF"/>
</LinearLayout>
</com.example.CustomScrollView>
</RelativeLayout>
posted @ 2026-06-22 18:32  疾风不问归途  阅读(4)  评论(0)    收藏  举报