Nested Scroll Views: How can I pass drag control from an inner scroll view to its outer scroll view?

I currently have an outer horizontal scroll view. Somewhere within the content of that horizontal scrollview, I have an inner vertical scroll view.

Current functionality:
If I drag my finger inside the inner scroll view, it only moves the inner scroll view. The outer scroll view does not respond to the drag movement.

Desired functionality:
If I drag my finger vertically inside the inner scroll view, the inner scroll view will scroll vertically. If I drag my finger horizontally inside (or outside) the inner scroll view, the outer scroll view will scroll horizontally.

How can I achieve this functionality? How can I pass drag control from an inner scroll view to its outer scroll view?

Hi,

there are a few ways to do it. One could be extending EventTrigger, scripting it to be a kind of event dispatcher and puting that script on content gameobject to intercept pointer drags. Caveat of that approach would be that it will intercept all events, but that might be the intended functionality.

An another way would be extending ScrollRect with that kind of dispatcher and using it instead of inner scroll view. Possible implementation (so inspector view remains the same):

using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;

public class InnerScrollRect : ScrollRect
{
    //Reference to outer scroll rect
    ScrollRect outerScroll;

    //Helper flags
    bool sendToOuter;
    bool enteringDrag;

    protected override void Awake ()
    {
        base.Awake ();

        //Try to find outer scrollrect
        outerScroll = this.transform.parent.GetComponentInParent<ScrollRect> ();
    }

    public override void OnInitializePotentialDrag (PointerEventData eventData)
    {
        //Send through potential drag event (stoping inertia movement on both
        //scrollrect)
        if (outerScroll)
            outerScroll.OnInitializePotentialDrag (eventData);

        base.OnInitializePotentialDrag (eventData);
    }

    public override void OnBeginDrag (PointerEventData eventData)
    {
        //React only to touch (button) to which scrollrect normaly react
        if (eventData.button != PointerEventData.InputButton.Left)
            return;

        //Set flag to know when first drag frame occurs
        enteringDrag = true;

        if (outerScroll)
            outerScroll.OnBeginDrag (eventData);

        base.OnBeginDrag (eventData);
    }

    public override void OnDrag (PointerEventData eventData)
    {
        if (eventData.button != PointerEventData.InputButton.Left)
            return;

        //If this is first frame of drag we need to find out if drag
        //is horizontal or vertical
        if (enteringDrag && outerScroll != null)
        {
            bool horizontalDrag =
                (Mathf.Abs (eventData.pressPosition.x - eventData.position.x) >
                    Mathf.Abs (eventData.pressPosition.y - eventData.position.y));

            //Decide if future draging is to be sent to outer scrollrect
            if (horizontalDrag == outerScroll.horizontal)
                sendToOuter = true;

            //... and send end drag event to the other one.
            if (sendToOuter)
                base.OnEndDrag (eventData);
            else
                outerScroll.OnEndDrag (eventData);
        }
        enteringDrag = false;

        //Dispatch drag event to the correct scrollrect
        if (sendToOuter)
            outerScroll.OnDrag (eventData);
        else
            base.OnDrag (eventData);
    }

    public override void OnEndDrag (PointerEventData eventData)
    {
        if (eventData.button != PointerEventData.InputButton.Left)
            return;

        //Dispatch EndDrag event to the correct scrollrect
        if (sendToOuter)
        {
            outerScroll.OnEndDrag (eventData);
            sendToOuter = false;
        }
        else
        {
            base.OnEndDrag (eventData);
        }
    }

}

In this script, drag is “locked” after the first frame to allow only horizontal or only vertical. If it’s needed that control always flows through, then it’s simpler script where in every event method (OnBeginDrag, OnDrag…) it’s calling both objects (just like in OnInitializePotentialDrag in this example).

The setup (hierarchy):

ScrollView (default Unity ScrollRect, both vertical and horizontal enabled)
   Viewport
      Content
         InnerScrollView (this script, only vertical or horizontal)
            Viewport2
               Content2

Working summary:
mouse button press on InnerScrollView will be intercepted with this script and used to stop inertia on itself and also on parent ScrollView. This script will then wait for the first frame when drag occurs to decide if drag is in its enabled direction or not:

  1. If it is, then, this script will enter “self mode” and act upon the cursor drag movement, until the mouse button is released. It will not send any mouse events to the parent ScrollView.
  2. Otherwise, if the drag in the first frame is not in enabled direction, it will enter “other mode” and just send all the drag events to the parent ScrollView, until the mouse button is released.

Hope this helps. Cheers.

Hi,
Working on the same thing now and require the same result as you.
Wondering if you got it working?