参考链接

起因

最近项目需要用到 Drag Icon,同时也是 ScrollRect 的组件,但是 Icon 的拖拽事件和 ScrollRect 的拖拽事件相互冲突

刚好看到了上面链接的例子,但是上面的例子有些不跟手,我把代码统一了一下接口,并加入新的跟随,方便日后新的组件使用

思路

这里的思路非常简单,如果需要拖拽 ScrollRect 下的 Icon,那么在 Icon 对应的拖拽开始、拖拽、拖拽结束,3个事件中加入判断,如果是拖拽Icon,则调用拖拽Icon逻辑,如果是拖拽 ScrollRect,则调用 ScrollRect 对应的事件

代码提供 Icon 拖拽时 3个方法的重载,实现时直接 override 即可

PS:原文作者在 Awake 中初始化代码中的组件,这里我全部使用检视面板中拖拽的方式,否则当 Icon创建较多时,每个组件都会挨个去找父物体的节点,不如在编译时直接保存

组件代码

[DisallowMultipleComponent]
    public class IDragableScrollViewItem : MonoBehaviour, IBeginDragHandler, IEndDragHandler, IDragHandler {
        public  bool       dragOnSurfaces = true;
        public  Transform  DragParent;
        public  Transform  OriginParent;
        public ScrollRect Scroll;


        private Transform     _drag;
        private bool          _isDragItem;
        private Vector3       _touchOffset;
        private RectTransform _draggingPlanes;
        
        private void Awake() { InitializeData(); }

        protected virtual void InitializeData() {
            Input.multiTouchEnabled = false;
            _isDragItem             = false;
            _drag                   = transform;
        }

        public void OnBeginDrag(PointerEventData eventData) {
            Vector2 touchDeltaPosition = Vector2.zero;
#if UNITY_EDITOR
            float delta_x = Input.GetAxis("Mouse X");
            float delta_y = Input.GetAxis("Mouse Y");
            touchDeltaPosition = new Vector2(delta_x, delta_y);

#elif UNITY_ANDROID || UNITY_IPHONE
            touchDeltaPosition = Input.GetTouch(0).deltaPosition;
#endif
            if(Mathf.Abs(touchDeltaPosition.x) < Mathf.Abs(touchDeltaPosition.y)){
                
                _isDragItem = true;

                _drag.SetParent(DragParent);
                _drag.SetAsLastSibling();

                OnItemBeginDrag();

                SetDraggedPosition(eventData);
            }
            else{
                _isDragItem = false;
                Scroll?.OnBeginDrag(eventData);
            }
        }

        public void OnDrag(PointerEventData eventData) {
            if(_isDragItem){
                SetDraggedPosition(eventData);
                OnItemDrag();
            }
            else{ Scroll?.OnDrag(eventData); }
        }

        public void OnEndDrag(PointerEventData eventData) {
            if(_isDragItem){
                _drag.SetParent(OriginParent);
                (_drag as RectTransform).anchoredPosition = Vector2.zero;
                OnItemEndDrag();
            }
            else{ Scroll?.OnEndDrag(eventData); }
        }

        protected virtual void OnItemEndDrag() { }

        protected virtual void OnItemDrag() { }

        protected virtual void OnItemBeginDrag() { }


        private void SetDraggedPosition(PointerEventData eventData) {
            if(dragOnSurfaces                                            &&
               eventData.pointerEnter                            != null &&
               eventData.pointerEnter.transform as RectTransform != null){
                _draggingPlanes = eventData.pointerEnter.transform as RectTransform;
            }

            var     rt = _drag.transform as RectTransform;
            Vector3 globalMousePos;
            if(RectTransformUtility.ScreenPointToWorldPointInRectangle(
                _draggingPlanes,
                eventData.position,
                eventData.pressEventCamera,
                out globalMousePos
            )){ rt.position = globalMousePos; }
        }
    }

使用参考

这里因为我的 Icon 需要捕获 Drop 事件,所以在开始和结束时,需要取消对射线的 Block

public class DragExample : IDragableScrollViewItem {
    public Image Icon;

    protected override void OnItemBeginDrag() {
        Icon.raycastTarget = false;
    }

    protected override void OnItemDrag() {
            
    }

    protected override void OnItemEndDrag() {
        Icon.raycastTarget = true;
    }
}

What doesn’t kill you makes you stronger.