- 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?