スクロールビューのアイテムをドラッグ＆ドロップできるようにする

#環境
Unity5.6.1f1

#概要
水平スクロールビューのアイテム（の子供のイメージ等）をドラッグ＆ドロップできるようにします

#プログラム
シーンにはドロップ対象のエリアとなるImage等のGameObjectを配置しておく必要があります
ドロップ時にEventSystem.current.RaycastAllでドロップ対象のGameObjectを判定しています

##Assets/DragItem.cs

~~~c#
using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;

public class DragItem : MonoBehaviour, IPointerDownHandler, IPointerUpHandler, IBeginDragHandler, IDragHandler, IEndDragHandler
{
	public enum EventState
	{
		None,
		BeginDrag,
		Drag,
		Drop,
	}
	public Action<DragItem,Transform> onUpdateState = null;
	public PointerEventData pointerEventData { get{ return _pointerEventData; } }
	public EventState eventState { get{ return _eventState; } }
	public Transform originalParent { get{ return _originalParent; } }

	[SerializeField] private Transform _draggingParent = null; //ドラッグ中はここの子になる
	[SerializeField] private float _upBasedBeginDragDegree = 70.0f; //ドラッグ選択判定の角度
	[SerializeField] private float _upBasedBeginDragDistance = 10.0f; //ドラッグ選択判定の距離

	private ScrollRect _scrollRectParent = null;
	private Transform _originalParent = null;
	private Vector3 _originalLocalPos;
	private PointerEventData _upDragPointerEventData = null;
	private PointerEventData _pointerEventData = null;
	private EventState _eventState = EventState.None;

	private void Start()
	{
		_scrollRectParent = GetComponentInParent<ScrollRect>();
	}

	void IPointerDownHandler.OnPointerDown( PointerEventData eventData )
	{
		_upDragPointerEventData = eventData;
	}

	void IPointerUpHandler.OnPointerUp( PointerEventData eventData )
	{
		if( _upDragPointerEventData != null && _upDragPointerEventData.pointerId == eventData.pointerId )
			_upDragPointerEventData = null;
		EndDrag( eventData );
	}

	void IBeginDragHandler.OnBeginDrag( PointerEventData eventData )
	{
		if( _scrollRectParent != null )
			_scrollRectParent.OnBeginDrag( eventData );
	}
	
	void IDragHandler.OnDrag( PointerEventData eventData )
	{
		if( _upDragPointerEventData != null && _upDragPointerEventData.pointerId == eventData.pointerId )
		{
			var vec = _upDragPointerEventData.position - _upDragPointerEventData.pressPosition;
			var lenSq = vec.sqrMagnitude;
			var dot = Vector2.Dot( vec.normalized, Vector2.up );
			var ac = Mathf.Acos( dot ) * Mathf.Rad2Deg;
			if( ac < _upBasedBeginDragDegree )
			{
				if( lenSq > ( _upBasedBeginDragDistance * _upBasedBeginDragDistance ) )
				{
					StartDrag( _upDragPointerEventData );
					_upDragPointerEventData = null;
				}
			}
		}

		var isUpdate = UpdateDrag( eventData );
		if( isUpdate == false )
		{
			if( _scrollRectParent != null )
				_scrollRectParent.OnDrag( eventData );
		}
	}
	
	void IEndDragHandler.OnEndDrag( PointerEventData eventData )
	{
		if( _scrollRectParent != null )
			_scrollRectParent.OnEndDrag( eventData );
	}

	public bool Reset()
	{
		_eventState = EventState.None;
		transform.SetParent( _originalParent, false );
		transform.localPosition = _originalLocalPos;
		return true;
	}

	private void StartDrag( PointerEventData eventData )
	{
		_eventState = EventState.BeginDrag;
		_pointerEventData = eventData;
		_originalParent = transform.parent;
		_originalLocalPos = transform.localPosition;
		transform.SetParent( _draggingParent, true );

		if( onUpdateState != null )
			onUpdateState.Invoke( this, null );
	}

	private bool UpdateDrag( PointerEventData eventData )
	{
		if( _pointerEventData == null || _pointerEventData.pointerId != eventData.pointerId )
			return false;

		_eventState = EventState.Drag;
//		transform.SetParent( _draggingParent );
//		transform.position = eventData.pressEventCamera.ScreenToWorldPoint( eventData.position );
		transform.SetParent( _draggingParent, false );
		Vector2 pos;
		RectTransformUtility.ScreenPointToLocalPointInRectangle( _draggingParent as RectTransform, eventData.position, eventData.pressEventCamera, out pos );
		transform.localPosition = new Vector3( pos.x, pos.y, transform.position.z );

		var local = transform.localPosition;
		local.z = 0.0f;
		transform.localPosition = local;

		if( onUpdateState != null )
			onUpdateState.Invoke( this, null );
		return true;
	}

	private void EndDrag( PointerEventData eventData )
	{
		if( _pointerEventData == null || _pointerEventData.pointerId != eventData.pointerId )
			return;

		_eventState = EventState.Drop;
		var pointer = new PointerEventData( EventSystem.current );
		pointer.position = eventData.position;
		var results = new List<RaycastResult>();
		EventSystem.current.RaycastAll( pointer, results );
		Transform dropTarget = null;
		if( results.Count >= 2 )
		{
			for( int i = 0; i < results.Count; i++ )
			{
				var result = results[ i ];
				if( gameObject == result.gameObject )
				{
					var targetIndex = i + 1;
					if( targetIndex < results.Count )
					{
						Debug.Log( "targetIndex:" + targetIndex );
						dropTarget = results[ targetIndex ].gameObject.transform;
						break;
					}
					break;
				}
			}
		}

		if( onUpdateState != null )
			onUpdateState.Invoke( this, dropTarget );
		_pointerEventData = null;
	}
}
~~~
##Assets/TestDragItem.cs

~~~c#
using UnityEngine;
using UnityEngine.UI;

public class DragItemTest : MonoBehaviour
{
	[SerializeField] private ScrollRect _scrollRect = null;
	[SerializeField] private GameObject _itemPrefab = null;

	private void Start()
	{
		var content = _scrollRect.content;
		for( int i = 0; i < 20; i++ )
		{
			var itemObj = Instantiate<GameObject>( _itemPrefab );
			itemObj.SetActive( true );

			itemObj.name = i.ToString();
			var text = itemObj.GetComponentInChildren<Text>();
			text.text = "Drag&Drop" + i;

			//itemObjの子供にImageのGameObjectがあり、そこにDragItemもセットされている想定
			var dragItem = itemObj.GetComponentInChildren<DragItem>();
			dragItem.onUpdateState = OnDragItemUpdateState;

			itemObj.transform.SetParent( content, false );
		}
		_itemPrefab.gameObject.SetActive( false );
	}

	private void OnDragItemUpdateState( DragItem dragItem, Transform dropTarget )
	{
		var state = dragItem.eventState;
		switch( state )
		{
			case DragItem.EventState.BeginDrag:
				Debug.Log( "BeginDrag:" + dragItem.name );
				break;
			case DragItem.EventState.Drag:
				break;
			case DragItem.EventState.Drop:
				if( dropTarget != null )
				{
					Debug.Log( "Drop:" + dragItem.originalParent.name + " dropTarget:" + dropTarget.name );
					var content = _scrollRect.content;
					Destroy( dragItem.originalParent.gameObject );
					Destroy( dragItem.gameObject );
				}
				else
				{
					Debug.LogWarning( "Item None" );
				}
				break;
		}
	}

}
~~~