当ListView滑动到顶部/底部时,将触摸事件传递给父级ScrollView

9
我有一个在ScrollView中显示评论的ListView,我想要实现以下功能:
当用户向下滑动时,如果列表在底部,首先ScrollView应该完全滚动到底部。一旦它完全到达底部,ListView应该开始滚动。
同样地,当用户向上滚动时,首先ListView(这里顺序相反!)应该向上滚动,然后ScrollView开始滚动。
到目前为止,我已经完成了以下工作:
listView.setOnTouchListener(new View.OnTouchListener() {
        // Setting on Touch Listener for handling the touch inside ScrollView
        @Override
        public boolean onTouch(View v, MotionEvent event) {

            // If going up but the list is already at up, return false indicating we did not consume it.
            if(event.getAction() == MotionEvent.ACTION_UP) {
                if (listView.getChildCount() == 0 && listView.getChildAt(0).getTop() == 0) {
                    Log.e("Listview", "At top!");
                    return false;
                }
            }

            // Similar behaviour but when going down check if we are at the bottom.
            if( event.getAction() == MotionEvent.ACTION_DOWN) {
                if (listView.getLastVisiblePosition() == listView.getAdapter().getCount() - 1 &&
                        listView.getChildAt(listView.getChildCount() - 1).getBottom() <= listView.getHeight()) {
                    Log.e("Listview","At bottom!");
                    v.getParent().requestDisallowInterceptTouchEvent(false);
                    return false;
                }
            }
            v.getParent().requestDisallowInterceptTouchEvent(true);
            return false;
        }
    });

日志在正确的时刻触发了,但即使我返回false,ScrollView也不会移动。
我还尝试将 v.getParent().requestDisallowInterceptTouchEvent(false); 添加到语句中,但也没有起作用。
我该如何让它工作?

我尝试过将ListView嵌套在ScrollView中,但无法使其正常工作。最终我使用了两个NestedScrollView。 - PeonProgrammer
我也看了NestedScrollView类,但正如其他线程所指出的那样,与滚动视图中的LinearLayout相比,listviews确实具有一些额外的功能。 - Gooey
通常我建议在ListView中使用HeaderView,而不是将ListView嵌套在ScrollView中 - 在RecyclerView之前的嵌套滚动容器有些麻烦。 - Hai Zhang
3个回答

8

您可以创建自定义的ScrollViewListView,并覆盖它们的功能。

onInterceptTouchEvent(MotionEvent event)

像这样:

@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
    View view = (View) getChildAt(getChildCount()-1);
    int diff = (view.getBottom()-(getHeight()+getScrollY()));

    if( event.getAction() == MotionEvent.ACTION_DOWN) {
        if (diff ==0) {
            return false;
        }
    }

    return super.onInterceptTouchEvent(event);
}

这样,当您在ScrollView底部时,每次下滑触摸ListView都会进入ListView

这只是一个简单的实现,但我认为您可以从中开始,通过执行相同的操作来检测您是否处于ListView的顶部。

作为一条注释,我建议使用listView.addHeaderView(scrollViewContent)将您ScrollView的当前内容添加为ListView的标题,以简化实现。我不知道这是否符合您的需求。

编辑:

检测何时应开始滚动ScrollView。在ScrollView中保留对ListView的引用。当用户向上滚动并且ListView位于顶部时,让事件被ScrollView消耗。

private void init(){
    ViewConfiguration vc = ViewConfiguration.get(getContext());
    mTouchSlop = vc.getScaledTouchSlop();
}

@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
    final int action = MotionEventCompat.getActionMasked(event);

    switch (action) {
        case MotionEvent.ACTION_DOWN:{
            yPrec = event.getY();
        }
        case MotionEvent.ACTION_MOVE: {
            final float dy = event.getY() - yPrec;

            if (dy > mTouchSlop) {
                // Start scrolling!
                mIsScrolling = true;
            }
            break;
        }
    }

    if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) {
        mIsScrolling = false;
    }

    // Calculate the scrolldiff
    View view = (View) getChildAt(getChildCount()-1);
    int diff = (view.getBottom()-(getHeight()+getScrollY()));

        if ((!mIsScrolling || !listViewReference.listIsAtTop()) && diff == 0) {
            if (event.getAction() == MotionEvent.ACTION_MOVE) {
                return false;
            }
    }

    return super.onInterceptTouchEvent(event);
}

@Override
public boolean onTouchEvent(MotionEvent ev) {
    final int action = MotionEventCompat.getActionMasked(ev);
    if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) {
        mIsScrolling = false;
    }
    return super.onTouchEvent(ev);
}


public void setListViewReference(MyListView listViewReference) {
    this.listViewReference = listViewReference;
}

ListView中的listIsAtTop方法如下所示:
public boolean listIsAtTop()   {
    if(getChildCount() == 0) return true;
    return (getChildAt(0).getTop() == 0 && getFirstVisiblePosition() ==0);
}

这种方法的问题在于listview和scrollview之间存在循环依赖。我尝试使用touch拦截方法实现了一些东西,但是当向上滚动时顺序被颠倒,因此scrollview必须首先“检查”listview是否完成。 - Gooey
请查看我对答案的修改。我进行了测试,它可以工作,但我不确定这是否是您想要实现的。您只需在ScrollView中设置对ListView的引用即可。代码可能需要一些额外的微调和清理。 - GeorgeP

4

不应该在ScrollView中放置ListView,但是您可以通过使用ExpandableHeightListView来实现您的要求。这将在ScrollView内创建一个完整高度的ListView,无需添加TouchListener。

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

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

        <RelativeLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content" >
             <!-- your content -->
        </RelativeLayout>

        <my.widget.ExpandableHeightListView
            android:layout_width="match_parent"
            android:layout_height="wrap_content" />
    </LinearLayout>
</ScrollView>

而另一种实现您需求的方式是使用带头部的RecyclerView


虽然它确实可以工作,但并不是我所要求的。这只是显示内容下面的所有项目,最好将视图放在LinearLayout中。 - Gooey

0

试试这个:

  1. 首先找到是否已经滚动到了ScrollView底部。将onScrollChanged注册到您的ScrollView中:

    @Override
    protected void onScrollChanged(int l, t, oldl, oldt) 
    {
        // 获取放置在ScrollView中的最后一个子项,我们需要它来确定底部位置。
        View view = (View) getChildAt(getChildCount()-1);
    
        // 计算滚动差异
        int diff = (view.getBottom()-(getHeight()+getScrollY()));
    
        // 如果diff为零,则已经到达底部
        if( diff == 0 )
        {
            // 通知已经到达底部
            // 在此添加代码以开始滚动ListView
            Log.d(ScrollTest.LOG_TAG, "MyScrollView: 已经到达底部" );
        }
    
        super.onScrollChanged(l, t, oldl, oldt);
    }
    
  2. 检查是否已经到达ListView的顶部。检查firstVisibleItem是否为0:

    tableListView.setOnScrollListener(new OnScrollListener() {
    
            public void onScrollStateChanged(AbsListView view, int scrollState) {
    
            }
    
            public void onScroll(AbsListView view, int firstVisibleItem,
                    int visibleItemCount, int totalItemCount) {
                if (visibleItemCount == 0)
                // 您已经到达了lustView的顶部
                {
                    java.lang.System.out.println("您已经到达了lustView的顶部!");
                }
                else {
                    if ((firstVisibleItem + visibleItemCount) == totalItemCount) {
                        // 在此放置您的内容
                        java.lang.System.out.println("已经到达了最后一行!");
                    }
                }
    
            }
        });
    

希望这对你有用。如果遇到任何问题,请告诉我。


我的代码已经可以检测到我是否到达了顶部或底部,但是我希望根据这个状态将触摸事件传递给父级滚动视图。 - Gooey

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接