Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

I have a ListView in a ScrollView to show comments and I would like to do the following:

When the user swipes down, first the ScrollView should fully scroll down as the list is at the bottom. Once it's fully down, the Listiew should start scrolling.

Similarly, when the user is scrolling up, first the ListView (order reversed here!) should scroll up, before the ScrollView starts scrolling.

So far I have done the following:

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;
        }
    });

The logs trigger at the right moment, however the ScrollView won't move even though I return false.

I also tried to add v.getParent().requestDisallowInterceptTouchEvent(false); to the statements, but that did not work either.

How can I make it work?

like image 707
Gooey Avatar asked Sep 17 '15 13:09

Gooey


2 Answers

You can create a custom ScrollView and ListView and override

onInterceptTouchEvent(MotionEvent event)

like this:

@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);
}

This way every Down Touch on the ListView while you are at the bottom of ScrollView will go to the ListView.

This is just a sketch implementation but I think you could get started from this by doing the same thing to detect when you are at the top of the ListView

As a note I would try an easier implementation by using just a ListView with the current content of you ScrollView added as the header of ListView by using listView.addHeaderView(scrollViewContent). I don't know if this fits your needs.

EDIT:

Detecting when should start scrolling the ScrollView. Keeping a reference of ListView in the ScrollView. When the user is scrolling up and the the ListView is at the top let the event be consumed by the 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;
}

The method listIsAtTop in ListView looks like this :

public boolean listIsAtTop()   {
    if(getChildCount() == 0) return true;
    return (getChildAt(0).getTop() == 0 && getFirstVisiblePosition() ==0);
}
like image 80
GeorgeP Avatar answered Oct 21 '22 08:10

GeorgeP


You should not put ListView inside ScrollView, but you can achieve your requirement by using ExpandableHeightListView. This will make full height Listview inside ScrollView. No need add 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>

And another way to achieve your requirement is RecyclerView with header.

like image 25
Kishan Vaghela Avatar answered Oct 21 '22 09:10

Kishan Vaghela