Home » Android » android – ScrollView Inside ScrollView

android – ScrollView Inside ScrollView

Posted by: admin April 23, 2020 Leave a comment

Questions:

I Know people from Google have asked us not to put Scrollable view inside another Scrollable view but is there any official statement from them directing us not to do so?

How to&Answers:

Is this close enough?

You should never use a
HorizontalScrollView with a ListView,
since ListView takes care of its own
scrolling. Most importantly, doing
this defeats all of the important
optimizations in ListView for dealing
with large lists, since it effectively
forces the ListView to display its
entire list of items to fill up the
infinite container supplied by
HorizontalScrollView.

http://developer.android.com/reference/android/widget/HorizontalScrollView.html

UPDATE:

Since you may be forced to use a two dimensional scrollview, you may consider using this:
Internet archive of blog.gorges.us/2010/06/android-two-dimensional-scrollview/

I haven’t used this but it may be a reasonable approach.

Answer:

Try this one

Note: Here parentScrollView means Outer ScrollView And childScrollView means Innner ScrollView

parentScrollView.setOnTouchListener(new View.OnTouchListener() {

        @Override
        public boolean onTouch(View v, MotionEvent event) {
        Log.v(TAG, "PARENT TOUCH");

        findViewById(R.id.child_scroll).getParent()
                .requestDisallowInterceptTouchEvent(false);
        return false;
    }
});

childScrollView.setOnTouchListener(new View.OnTouchListener() {

        @Override
        public boolean onTouch(View v, MotionEvent event) {
        Log.v(TAG, "CHILD TOUCH");

        // Disallow the touch request for parent scroll on touch of  child view
        v.getParent().requestDisallowInterceptTouchEvent(true);
        return false;
    }
});

Answer:

Atul Bhardwaj’s answer above is the correct way to do it. But in case someone needs to apply it to a ScrollView where you have less control of the parent, I think this is flexible enough and just the way it’s supposed to work:

private void makeMyScrollSmart() {
    myScroll.setOnTouchListener(new View.OnTouchListener() {
        @Override
        public boolean onTouch(View __v, MotionEvent __event) {
            if (__event.getAction() == MotionEvent.ACTION_DOWN) {
                //  Disallow the touch request for parent scroll on touch of child view
                requestDisallowParentInterceptTouchEvent(__v, true);
            } else if (__event.getAction() == MotionEvent.ACTION_UP || __event.getAction() == MotionEvent.ACTION_CANCEL) {
                // Re-allows parent events
                requestDisallowParentInterceptTouchEvent(__v, false);
            }
            return false;
        }
    });
}

private void requestDisallowParentInterceptTouchEvent(View __v, Boolean __disallowIntercept) {
    while (__v.getParent() != null && __v.getParent() instanceof View) {
        if (__v.getParent() instanceof ScrollView) {
            __v.getParent().requestDisallowInterceptTouchEvent(__disallowIntercept);
        }
        __v = (View) __v.getParent();
    }
}

What the function does is add a touch listener to myScroll that disables the parent’s touch intercept when a touch starts in the child, and then enables it back when the touch actually ends. You don’t need a reference to the parent ScrollView and it doesn’t have to be the immediate parent… it’ll travel the display list until it finds it.

Best of both worlds, in my opinion.

Answer:

childScrollView.setOnTouchListener(new View.OnTouchListener() {

      @Override
    public boolean onTouch(View v, MotionEvent event) {

        int action = event.getAction();
        switch (action) {
        case MotionEvent.ACTION_DOWN:
           // Disallow ScrollView to intercept touch events.
          v.getParent().requestDisallowInterceptTouchEvent(true);
           break;

        case MotionEvent.ACTION_UP:
            // Allow ScrollView to intercept touch events.
            v.getParent().requestDisallowInterceptTouchEvent(false);
            break;
        }

        return false;
    }
});

v.getParent() = parent scrollView.

Answer:

Here is a possible solution. When reached the end of a child ScrollView it passes the control to the parent ScrollView to scroll. It works with ScrollView and ListView inside a ScrollView.

Step 1 – set the parent OnTouchListener

parentScroll.setOnTouchListener(new View.OnTouchListener() {
    public boolean onTouch(View v, MotionEvent event) {
        v.getParent().requestDisallowInterceptTouchEvent(false);
        return false;
    }
});

Step 2 – set the childrens OnTouchListener (ScrollView or ListView)

aChildScrollView.setOnTouchListener(new View.OnTouchListener() {
    public boolean onTouch(View v, MotionEvent event)
    {
        v.getParent().requestDisallowInterceptTouchEvent(shouldRequestDisallowIntercept((ViewGroup) v, event));
        return false;
    }
});
aListView.setOnTouchListener(new View.OnTouchListener() {
    public boolean onTouch(View v, MotionEvent event) {
        v.getParent().requestDisallowInterceptTouchEvent(shouldRequestDisallowIntercept((ViewGroup) v, event));
        return false;
    }
});

Step 3 – here are the required magic methods for the correct functionality

protected boolean shouldRequestDisallowIntercept(ViewGroup scrollView, MotionEvent event) {
    boolean disallowIntercept = true;
    float yOffset = getYOffset(event);

    if (scrollView instanceof ListView) {
        ListView listView = (ListView) scrollView;
        if (yOffset < 0 && listView.getFirstVisiblePosition() == 0 && listView.getChildAt(0).getTop() >= 0) {
            disallowIntercept = false;
        }
        else if (yOffset > 0 && listView.getLastVisiblePosition() == listView.getAdapter().getCount() - 1 && listView.getChildAt(listView.getChildCount() - 1).getBottom() <= listView.getHeight()) {
            disallowIntercept = false;
        }
    }
    else {
        float scrollY = scrollView.getScrollY();
        disallowIntercept = !((scrollY == 0 && yOffset < 0) || (scrollView.getHeight() + scrollY == scrollView.getChildAt(0).getHeight() && yOffset >= 0));

    }

    return disallowIntercept;
}

protected float getYOffset(MotionEvent ev) {
    final int historySize = ev.getHistorySize();
    final int pointerCount = ev.getPointerCount();

    if (historySize > 0 && pointerCount > 0) {
        float lastYOffset = ev.getHistoricalY(pointerCount - 1, historySize - 1);
        float currentYOffset = ev.getY(pointerCount - 1);

        float dY = lastYOffset - currentYOffset;

        return dY;
    }

    return 0;
}

Answer:

[…] is there any official statement from them directing us not to do so?

I think there is though I can’t seem to find it in my notes. I know I found such a statement when trying to have a scroll view in a list activity. I think there is actually a logical focus “bug” in the way the Android UI system deals with nested scrollables which probably should be better detected and communicated to the developer. But my advice is…

In the end it is better to consider a single scrollable view for the sake of the user anyway. It’s like having scroll bars inside scroll bars on an HTML page; it may be legal but its a terrible user experience.

Answer:

I found a very good solution. Please use this code.

    parentScrollView.setOnTouchListener(new View.OnTouchListener() {

        public boolean onTouch(View v, MotionEvent event) {

            Utils.showLog("PARENT TOUCH");
            findViewById(R.id.activity_mesh_child_scrollView).getParent().requestDisallowInterceptTouchEvent(false);
            return false;
        }
    });

    childScrollView.setOnTouchListener(new View.OnTouchListener() {

        public boolean onTouch(View v, MotionEvent event) {

            Utils.showLog("CHILD TOUCH");
            // Disallow the touch request for parent scroll on touch of child view
            v.getParent().requestDisallowInterceptTouchEvent(true);
            return false;
        }
    });

This will surely work. Please try and let me know if not working.

Answer:

Actually, there is an official statement about it, on a quite old video called “the world of ListView“. They say not to put any scrollable view inside another one (when both are in the same direction).

However, now we have a new view that allows both views to scroll at the same time, probably to show a cool effect:

https://developer.android.com/reference/android/support/v4/widget/NestedScrollView.html

I didn’t find any example for this, so what I wrote is just a guess of what it does and what it’s used for.

Answer:

Android support v4 library has a class called NestedScrollView.

Try Nested Scroll View: http://ivankocijan.xyz/android-nestedscrollview/

Answer:

You can put a ScrollView inside another ScrollView. Just extend the child ScrollView to override the onTouchEvent method. Like so

import android.content.Context;
import android.util.AttributeSet;
import android.view.MotionEvent;

public class ChildScrollView extends android.widget.ScrollView {
    private int parent_id;

    public ChildScrollView(Context context) {
        super(context);
    }

    public ChildScrollView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public ChildScrollView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event){
        requestDisallowInterceptTouchEvent(true);
        return super.onTouchEvent(event);
    }
}

Answer:

Here, i have created a sample project related to ScrollView inside a ScrollView. One View is Scrollable Both ways. Check It Out :-

MainActivity.java –

activity_main.xml –

list_item.xml(for ListView) –

Answer:

If anyone is looking for an answer to this, I had a slightly different implementation. I extended the ScrollView class and implemented onTouchListener in the child, and set it to self in the constructor.

In the onTouch callback, if the motion event object came with a value for pointer count as 2, I returned true, otherwise false. This way, if two fingers were moving on screen, it would consider it as a pinch to zoom, otherwise would consider it as normal scroll. I didn’t request for parent touch disable etc.

Answer:

Its not only that Google says its bad practice, it just doesnt make much sense. Supose you have two vertical scrollable views nested one inside the other. When you move you finger over the scroll views, which one do you want to move, the inner or the outer one?

You should rethink your UI desing to something that does not require this, there a many ways to make a great UI and still keep it really simple.