当 Listview 在 top/bottom 时将运动事件传递给父 Scrollview

Pass motion event to parent Scrollview when Listview at top/bottom

我在 ScrollView 中有一个 ListView 来显示评论,我想执行以下操作:

当用户向下滑动时,首先 ScrollView 应该完全向下滚动,因为列表位于底部。一旦完全向下,Listiew 应该开始滚动。

同样,当用户向上滚动时,首先 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;
        }
    });

日志在正确的时刻触发,但是 ScrollView 不会移动,即使我 return false。

我也尝试在语句中添加 v.getParent().requestDisallowInterceptTouchEvent(false);,但这也没有用。

我怎样才能让它发挥作用?

试试这个:

  1. 如果到达scrollview Bottom,首先查找。将 onScrollChanged 注册到您的滚动视图

    @Override
    protected void onScrollChanged(int l, int t, int oldl, int oldt) 
    {
        // Grab the last child placed in the ScrollView, we need it to determinate the bottom position.
        View view = (View) getChildAt(getChildCount()-1);
    
        // Calculate the scrolldiff
        int diff = (view.getBottom()-(getHeight()+getScrollY()));
    
        // if diff is zero, then the bottom has been reached
        if( diff == 0 )
        {
            // notify that we have reached the bottom
            // Add code here to start scrolling the ListView
            Log.d(ScrollTest.LOG_TAG, "MyScrollView: Bottom has been reached" );
        }
    
        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)
                // you have reached at top of lustView
                {
                    java.lang.System.out.println("you have reached at top of lustView!");
                }
                else {
                    if ((firstVisibleItem + visibleItemCount) == totalItemCount) {
                        // put your stuff here
                        java.lang.System.out.println("end of the line reached!");
                    }
                }
    
            }
        });
    

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

你不应该把 ListView 放在 ScrollView 里面,但是你可以使用 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 和 header。

您可以创建自定义 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 和您的当前内容 ScrollView 作为 ListView 的 header 添加的 listView.addHeaderView(scrollViewContent) 来实现更简单的实现].不知道合不合你的需求

编辑:

正在检测何时应该开始滚动 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);
}