- Home /
 
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?
Answer by nnikolas · May 02, 2019 at 10:23 PM
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:
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.
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 @nnikolas,
i tried to use your code, and i understand the intention, but somehow when in release my horizontal drag, the content snap back to position of the innerscroll rect.
do you have the same experience?
Hi @nnikolas , $$anonymous$$y intention is same but somehow it's not working with the above code. Horizontal drag into inner scroll rect is not having horizontal drag of outer scroll rect. Please help.
Hi, just tried the test rig again on 2018.4.30 and it works for me. The hierarchy is like this:
 ScrollView
   Viewport
      Content
          InnerScrollView
              Viewport2
                  Content2
                      Image
 
                  so that "ScrollView" has horizontal and vertical allowed (size 600x600, default settings), "Content" is large (lets say 2000x2000), "InnerScrollView" is only vertical, size for example 1000x1000, in the center of "Content"; and "Content2" is again large (2000x2000). When you drag anywhere out of "InnerScrollView", only "Content" will move, if you start to drag verticaly inside "InnerScrollView" only "Content2" will move. If you start to drag horizontaly inside "InnerScrollView" (and then you can drag how you like since only the start is important) again only "Content" will move. I don't know if this helps.
@nnikolas this code is very helpful and works. Can you please explain it in some sort of short summary?
@AhmadYusaf I've added setup and short summary in the answer. I hope this answers all of the unknowns.
Answer by Uzair-Mahmood · Apr 30, 2019 at 04:12 PM
Hi, Working on the same thing now and require the same result as you. Wondering if you got it working?
Your answer