Search Unity

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

Discussion in 'UGUI & TextMesh Pro' started by jhow77, Apr 18, 2017.

  1. jhow77

    jhow77

    Joined:
    Jan 10, 2017
    Posts:
    19
    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?
     
  2. jhow77

    jhow77

    Joined:
    Jan 10, 2017
    Posts:
    19
  3. methos5k

    methos5k

    Joined:
    Aug 3, 2015
    Posts:
    8,712
    It's not perfect but in my limited tests it kinda mostly works :)

    This code went on the inner (Vertical ScrollView)
    Code (CSharp):
    1. public class ScrollTest : ScrollRect {
    2.  
    3.     public
    4.     ScrollRect mOutterScroll;
    5.     bool draggingSide = false;
    6.     public override void OnInitializePotentialDrag(PointerEventData eventData)
    7.     {
    8.         base.OnInitializePotentialDrag(eventData);
    9.         mOutterScroll.OnInitializePotentialDrag(eventData);
    10.     }
    11.     public override void OnBeginDrag(PointerEventData eventData)
    12.     {
    13.         if (Mathf.Abs(eventData.delta.x) > Mathf.Abs(eventData.delta.y))
    14.         {
    15.             Vector2 newdelta = new Vector2(eventData.delta.x, 0);
    16.             eventData.delta = newdelta;
    17.             mOutterScroll.OnDrag(eventData);
    18.             draggingSide = true;
    19.         }
    20.         else base.OnBeginDrag(eventData);
    21.     }
    22.  
    23.     public override void OnDrag(PointerEventData eventData)
    24.     {
    25.         if (draggingSide)
    26.         {
    27.             Vector2 newdelta = new Vector2(eventData.delta.x,0);
    28.             eventData.delta = newdelta;
    29.             mOutterScroll.OnDrag(eventData);
    30.         }
    31.         else base.OnDrag(eventData);
    32.     }
    33.  
    34.     public override void OnEndDrag(PointerEventData eventData)
    35.     {
    36.         if (draggingSide)
    37.         {
    38.             mOutterScroll.OnEndDrag(eventData);
    39.             draggingSide = false;
    40.         }
    41.         else base.OnEndDrag(eventData);
    42.     }
    43. }
    And this little bit on the Outter (Horizontal) one
    Code (CSharp):
    1. public class ScrollTest2 : MonoBehaviour {
    2.  
    3.     [SerializeField]
    4.     ScrollTest mScrollTest;
    5.  
    6.     private void Start()
    7.     {
    8.         if (mScrollTest != null) mScrollTest.mOutterScroll = GetComponent<ScrollRect>();
    9.     }
    10. }
    11.  
    This is a small bit of a "jumpiness" sometimes when first choosing horizontal.. but nothing too major (at least in my test, which was a small content area). *This only happens if you try moving horizontally while inside the vertical scroll area; no jumpiness if you are not beginning the drag from inside that.
    There is a post I read recently online that had something similar to this, maybe a bit more refined if you find it :) Or fix this up a bit more nicely. lol
     
    Last edited: Apr 21, 2017
  4. methos5k

    methos5k

    Joined:
    Aug 3, 2015
    Posts:
    8,712
    If you "liked" it, does that mean it worked? :)
     
  5. jhow77

    jhow77

    Joined:
    Jan 10, 2017
    Posts:
    19
    Mainly wanted you to know that I'm not ignoring this and that I appreciate it. Still haven't had the chance to fully implement yet. But still took some useful stuff out of the recommendation. Will let you know when I do!

    Do you know if ScrollRect's OnInitializePotentialDrag does anything by default? And when it happens? (Like what would happen in between OnInitializePotentialDrag and OnBeginDrag?) I can't find anything useful in the docs about it.

    Thanks again @methos5k
     
  6. methos5k

    methos5k

    Joined:
    Aug 3, 2015
    Posts:
    8,712
    I can't remember without looking at the source again exactly what it does. But as far as I know, in terms of what happens in between, I think nothing.. I think those 2 events go one after the other (unless potential drag doesn't initialize, I guess).
    You can lookup the UI source code .. google it. :)

    And okay , so you hadn't tested it, yet. As I said in an earlier post, I tried this and everything worked well, except for the small issue I mentioned. Maybe you can sort that out or live with it .. ;)
     
  7. methos5k

    methos5k

    Joined:
    Aug 3, 2015
    Posts:
    8,712
    There is another option, too.. I kinda meant to include this before :) :
    Code (CSharp):
    1. using UnityEngine;
    2. using UnityEngine.UI;
    3. using UnityEngine.EventSystems;
    4. using System;
    5.  
    6. public class ScrollTestShort : MonoBehaviour, IDragHandler {
    7.  
    8.     [SerializeField]
    9.     float speed = 2f;
    10.     [SerializeField]
    11.     ScrollRect mParentScrollRect;
    12.     public void OnDrag(PointerEventData eventData)
    13.     {
    14.         if(Mathf.Abs(eventData.delta.y) >= Mathf.Abs(eventData.delta.x))
    15.         GetComponent<ScrollRect>().verticalNormalizedPosition += eventData.delta.y * speed * Time.deltaTime;
    16.         else
    17.         mParentScrollRect.horizontalNormalizedPosition -= eventData.delta.x * speed * Time.deltaTime;
    18.     }
    19. }
    I was just messing around w/ the 'speed' variable. For me, "0.001f" worked nicely, without Time.deltaTime, which I added just before posting.. but anyways; this script is pretty simple (r).
     
    e-motionagency likes this.
  8. e-motionagency

    e-motionagency

    Joined:
    Nov 24, 2016
    Posts:
    2
    Thanks methos5k, that really helped me overcome this.
     
  9. ElPato

    ElPato

    Joined:
    Mar 1, 2013
    Posts:
    2
    Line 17 should be mOutterScroll.OnBeginDrag((eventData); that's why the "jumpiness"
    Thanks! it was easy to solve this nested scrollrects issue with your code.
    If anybody is interested, this is what I did taking @methos5k code as a starting point

    Code (CSharp):
    1. public class NestedChildScrollRect : ScrollRect
    2.     {
    3.         ScrollRect _parentScroll;
    4.         bool _draggingParent = false;
    5.  
    6.         protected override void Awake() {
    7.             base.Awake();
    8.             _parentScroll = GetScrollParent(transform);
    9.         }
    10.  
    11.         ScrollRect GetScrollParent(Transform t) {
    12.             if (t.parent != null) {
    13.                 ScrollRect scroll = t.parent.GetComponent<ScrollRect>();
    14.                 if (scroll != null) return scroll;
    15.                 else return GetScrollParent(t.parent);
    16.             }
    17.             return null;
    18.         }
    19.  
    20.         bool IsPotentialParentDrag(Vector2 inputDelta) {
    21.             if (_parentScroll != null) {
    22.                 if (_parentScroll.horizontal && !_parentScroll.vertical) {
    23.                     return Mathf.Abs(inputDelta.x) > Mathf.Abs(inputDelta.y);
    24.                 }
    25.                 if (!_parentScroll.horizontal && _parentScroll.vertical) {
    26.                     return Mathf.Abs(inputDelta.x) < Mathf.Abs(inputDelta.y);
    27.                 }
    28.                 else return true;
    29.             }
    30.             return false;
    31.         }
    32.  
    33.         public override void OnInitializePotentialDrag(PointerEventData eventData) {
    34.             base.OnInitializePotentialDrag(eventData);
    35.             _parentScroll?.OnInitializePotentialDrag(eventData);
    36.         }
    37.  
    38.         public override void OnBeginDrag(PointerEventData eventData) {
    39.             if (IsPotentialParentDrag(eventData.delta)) {
    40.                 _parentScroll.OnBeginDrag(eventData);
    41.                 _draggingParent = true;
    42.             }
    43.             else {
    44.                 base.OnBeginDrag(eventData);
    45.             }
    46.         }
    47.  
    48.         public override void OnDrag(PointerEventData eventData) {
    49.             if (_draggingParent) {
    50.                 _parentScroll.OnDrag(eventData);
    51.             }
    52.             else {
    53.                 base.OnDrag(eventData);
    54.             }
    55.         }
    56.  
    57.         public override void OnEndDrag(PointerEventData eventData) {
    58.             base.OnEndDrag(eventData);
    59.             if (_parentScroll != null && _draggingParent) {
    60.                 _draggingParent = false;
    61.                 _parentScroll.OnEndDrag(eventData);
    62.             }
    63.         }
    64.     }
     
  10. Der-Kleine

    Der-Kleine

    Joined:
    May 14, 2015
    Posts:
    2
    Bit of an old thread revival, but starting off with ElPato's code I made a bunch of changes to support dragging both the nested scroll rect and parent scroll rect simultaneously. Movements of the nested rect are subtracted from the parent rect.

    This is intended to be used with a clamped (i.e. non-elastic) child scroll rect, allowing you to drag the parent scroll rect once the child scroll rect reaches its bounds.

    My script also handles the passing of mouse wheel inputs to the parent scroll rect in a similar manner, though this only supports single-axis nested scroll rects because of how unity converts vertical scroll wheel inputs into horizontal ones.

    Code (CSharp):
    1.  
    2. using UnityEngine;
    3. using UnityEngine.EventSystems;
    4. using UnityEngine.UI;
    5.  
    6. public class NestedScrollRect : ScrollRect
    7. {
    8.     ScrollRect _parentScroll;
    9.     bool m_bDraggingParent = false;
    10.  
    11.     protected override void Awake()
    12.     {
    13.         base.Awake();
    14.  
    15.         _parentScroll = GetScrollParent(transform);
    16.  
    17.         if(!_parentScroll)
    18.         {
    19.             Debug.LogWarning("NestedScrollRect - no parent scroll rect found.");
    20.         }  
    21.     }
    22.  
    23.     ScrollRect GetScrollParent(Transform t)
    24.     {
    25.         if (t.parent != null)
    26.         {
    27.             ScrollRect scroll = t.parent.GetComponent<ScrollRect>();
    28.             if (scroll != null) return scroll;
    29.             else return GetScrollParent(t.parent);
    30.         }
    31.         return null;
    32.     }
    33.  
    34.     public override void OnInitializePotentialDrag(PointerEventData eventData)
    35.     {
    36.         base.OnInitializePotentialDrag(eventData);
    37.     }
    38.  
    39.     public override void OnBeginDrag(PointerEventData eventData)
    40.     {
    41.         base.OnBeginDrag(eventData);
    42.     }
    43.  
    44.     public override void OnDrag(PointerEventData eventData)
    45.     {
    46.         base.OnDrag(eventData);
    47.         if (_parentScroll != null)
    48.         {
    49.             //Pass only directions that the child scroll rect isn't scrolling in
    50.             Vector2 v2ParentDragAmount = Vector2.zero;
    51.  
    52.             if (horizontal)
    53.             {
    54.                 bool bHorizontalEndReached = (horizontalScrollbar.value > 0.99f && eventData.delta.x < 0)
    55.                     || (horizontalScrollbar.value < 0.01f && eventData.delta.x > 0)
    56.                     || content.rect.width <= viewport.rect.width;
    57.  
    58.                 v2ParentDragAmount.x = bHorizontalEndReached ? eventData.delta.x : 0;
    59.             }
    60.             else
    61.                 v2ParentDragAmount.x = eventData.delta.x;
    62.  
    63.             if (vertical)
    64.             {
    65.                 bool bVerticalEndReached = (verticalScrollbar.value > 0.99f && eventData.delta.y < 0)
    66.                     || (verticalScrollbar.value < 0.01f && eventData.delta.y > 0)
    67.                     || content.rect.height <= viewport.rect.height;
    68.  
    69.                 v2ParentDragAmount.y = bVerticalEndReached ? eventData.delta.y : 0;
    70.             }
    71.             else
    72.                 v2ParentDragAmount.y = eventData.delta.y;
    73.  
    74.  
    75.             //Modify event data for parent scroll rect
    76.             eventData.delta = v2ParentDragAmount;
    77.  
    78.             //Start or end drag on parent scroll rect if necessary
    79.             if (v2ParentDragAmount != Vector2.zero)
    80.             {
    81.                 if (!m_bDraggingParent)
    82.                 {
    83.                     _parentScroll.OnInitializePotentialDrag(eventData);
    84.                     _parentScroll.OnBeginDrag(eventData);
    85.                     m_bDraggingParent = true;
    86.                 }
    87.                 _parentScroll.OnDrag(eventData);
    88.             }
    89.             else if (m_bDraggingParent)
    90.             {
    91.                 _parentScroll.OnEndDrag(eventData);
    92.                 m_bDraggingParent = false;
    93.             }
    94.         }
    95.     }
    96.  
    97.     public override void OnEndDrag(PointerEventData eventData)
    98.     {
    99.         base.OnEndDrag(eventData);
    100.         if (_parentScroll != null && m_bDraggingParent)
    101.         {
    102.             _parentScroll.OnEndDrag(eventData);
    103.             m_bDraggingParent = false;
    104.         }
    105.     }
    106.  
    107.     public override void OnScroll(PointerEventData data)
    108.     {
    109.         if(_parentScroll == null)
    110.         {
    111.             base.OnScroll(data);
    112.             return;
    113.         }
    114.  
    115.         //Only scroll until end of scroll view is reached, then pass scrolling to parent
    116.         float fVerticalScrollAmount = data.scrollDelta.y;
    117.         float fHorizontalScrollAmount = data.scrollDelta.x;
    118.         Vector2 v2ParentScrollAmount = Vector2.zero;
    119.         Vector2 v2ChildScrollAmount = data.scrollDelta;
    120.  
    121.         //Because the scrollbar values don't quite reach 0 or 1, a comparison is made with 0.99 or 0.01
    122.         if(vertical && !horizontal)
    123.         {
    124.             bool bEndReached = ( verticalScrollbar.value > 0.99f && fVerticalScrollAmount > 0)
    125.                 || (verticalScrollbar.value < 0.01f && fVerticalScrollAmount < 0)
    126.                 || content.rect.height <= viewport.rect.height;
    127.  
    128.             v2ParentScrollAmount = new Vector2(
    129.                 data.scrollDelta.x,
    130.                 bEndReached ? data.scrollDelta.y : 0);
    131.         }
    132.         else if(!vertical && horizontal)
    133.         {
    134.             bool bEndReached = (horizontalScrollbar.value > 0.99f && fHorizontalScrollAmount > 0)
    135.                 || (horizontalScrollbar.value < 0.01f && fHorizontalScrollAmount < 0)
    136.                 || content.rect.width <= viewport.rect.width;
    137.  
    138.             v2ParentScrollAmount = new Vector2(
    139.                 bEndReached ? data.scrollDelta.x : 0,
    140.                 data.scrollDelta.y);
    141.  
    142.             v2ChildScrollAmount = new Vector2(data.scrollDelta.x, 0);   //Don't pass vertical scrolling which gets converted into horizontal scrolling because that feels weird.
    143.         }
    144.  
    145.         base.OnScroll(new PointerEventData(EventSystem.current) { scrollDelta = v2ChildScrollAmount});
    146.         _parentScroll.OnScroll(new PointerEventData(EventSystem.current) { scrollDelta = v2ParentScrollAmount });
    147.     }
    148. }
    149.  
     
    Last edited: Oct 6, 2023
    marcozakaria likes this.
  11. criwe

    criwe

    Joined:
    Apr 16, 2013
    Posts:
    5
    I solved it by sending the PointerEventData to the parented ScrollRect at the wanted events like this:
    (Attach this script to the nested ScrollRect, in my case the vertical inside the horizontal one)

    Code (CSharp):
    1. using UnityEngine;
    2. using UnityEngine.UI;
    3. using UnityEngine.EventSystems;
    4. using UnityEngine.UIElements;
    5.  
    6. public class NestedScrollRect : MonoBehaviour, IBeginDragHandler, IDragHandler
    7. {
    8.     public ScrollRect parentScrollRect;
    9.  
    10.     public void OnBeginDrag(PointerEventData data)
    11.     {
    12.         Debug.Log("DRAGGING STARTED!");
    13.  
    14.         parentScrollRect.OnBeginDrag(data);
    15.     }
    16.  
    17.  
    18.     public void OnDrag(PointerEventData data)
    19.     {
    20.         Debug.Log("DRAGGING!");
    21.  
    22.         parentScrollRect.OnDrag(data);
    23.     }
    24. }