Unity——Scroll View 滑动居中、居中的放大、其他的缩小(支持横竖Scroll View)

一、效果

二、上代码,两种方式。

(第一种是在Update中用插值完成的,如果不想再项目中看到Update,下面的另一种改了一段用DoTween实现的,需要导入一下DoTween)

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;

public enum ScrollDir
{
	Horizontal,
	Vertical
}

public class CenterOnChild : MonoBehaviour, IEndDragHandler, IDragHandler, IBeginDragHandler
{
	public ScrollDir Dir = ScrollDir.Horizontal;

	/// 
	/// 是否正在居中
	/// 
	private bool _isCentering = false;

	[Header("居中过程移动速度")]
	public float MoveToCenterSpeed = 10f;
	[Header("中心点放大倍数")]
	public float CenterScale = 1f;
	[Header("非中心点放大倍数")]
	public float UnCenterScale = 0.9f;
	private ScrollRect _scrollView;

	private Transform _content;

	private List _childrenPos = new List();
	private float _targetPos;

	/// 
	/// 当前中心child索引
	/// 
	private int _curCenterChildIndex = -1;

	/// 
	/// 当前中心ChildItem
	/// 
	public GameObject CurCenterChildItem
	{
		get
		{
			GameObject centerChild = null;
			if (_content != null && _curCenterChildIndex >= 0 && _curCenterChildIndex < _content.childCount)
			{
				centerChild = _content.GetChild(_curCenterChildIndex).gameObject;
			}
			return centerChild;
		}
	}
	/// 
	/// 根据拖动来改变每一个子物体的缩放
	/// 
	public void SetCellScale()
	{
		GameObject centerChild = null;
		for (int i = 0; i < _content.childCount; i++)
		{
			centerChild = _content.GetChild(i).gameObject;
			if (i == _curCenterChildIndex)
				centerChild.transform.localScale = CenterScale * Vector3.one;
			else
				centerChild.transform.localScale = UnCenterScale * Vector3.one;
		}
	}
	void Awake()
	{
		_scrollView = GetComponent();
		if (_scrollView == null)
		{
			Debug.LogError("ScrollRect is null");
			return;
		}
		_content = _scrollView.content;

		LayoutGroup layoutGroup = null;
		layoutGroup = _content.GetComponent();

		if (layoutGroup == null)
		{
			Debug.LogError("LayoutGroup component is null");
		}
		_scrollView.movementType = ScrollRect.MovementType.Unrestricted;
		float spacing = 0f;
		//根据dir计算坐标,Horizontal:存x,Vertical:存y
		switch (Dir)
		{
			case ScrollDir.Horizontal:
				if (layoutGroup is HorizontalLayoutGroup)
				{
					float childPosX = _scrollView.GetComponent().rect.width * 0.5f - GetChildItemWidth(0) * 0.5f;
					spacing = (layoutGroup as HorizontalLayoutGroup).spacing;
					_childrenPos.Add(childPosX);
					for (int i = 1; i < _content.childCount; i++)
					{
						childPosX -= GetChildItemWidth(i) * 0.5f + GetChildItemWidth(i - 1) * 0.5f + spacing;
						_childrenPos.Add(childPosX);
					}
				}
				else if (layoutGroup is GridLayoutGroup)
				{
					GridLayoutGroup grid = layoutGroup as GridLayoutGroup;
					float childPosX = _scrollView.GetComponent().rect.width * 0.5f - grid.cellSize.x * 0.5f;
					_childrenPos.Add(childPosX);
					for (int i = 0; i < _content.childCount - 1; i++)
					{
						childPosX -= grid.cellSize.x + grid.spacing.x;
						_childrenPos.Add(childPosX);
					}
				}
				else
				{
					Debug.LogError("Horizontal ScrollView is using VerticalLayoutGroup");
				}
				break;
			case ScrollDir.Vertical:
				if (layoutGroup is VerticalLayoutGroup)
				{
					float childPosY = -_scrollView.GetComponent().rect.height * 0.5f + GetChildItemHeight(0) * 0.5f;
					spacing = (layoutGroup as VerticalLayoutGroup).spacing;
					_childrenPos.Add(childPosY);
					for (int i = 1; i < _content.childCount; i++)
					{
						childPosY += GetChildItemHeight(i) * 0.5f + GetChildItemHeight(i - 1) * 0.5f + spacing;
						_childrenPos.Add(childPosY);
					}
				}
				else if (layoutGroup is GridLayoutGroup)
				{
					GridLayoutGroup grid = layoutGroup as GridLayoutGroup;
					float childPosY = -_scrollView.GetComponent().rect.height * 0.5f + grid.cellSize.y * 0.5f;
					_childrenPos.Add(childPosY);
					for (int i = 1; i < _content.childCount; i++)
					{
						childPosY += grid.cellSize.y + grid.spacing.y;
						_childrenPos.Add(childPosY);
					}
				}
				else
				{
					Debug.LogError("Vertical ScrollView is using HorizontalLayoutGroup");
				}
				break;
		}
	}
	private float GetChildItemWidth(int index)
	{
		return (_content.GetChild(index) as RectTransform).sizeDelta.x;
	}

	private float GetChildItemHeight(int index)
	{
		return (_content.GetChild(index) as RectTransform).sizeDelta.y;
	}
	//这里是直接在update函数中用插值实现的,做项目可以改为dotewwn实现
	void Update()
	{
		if (_isCentering)
		{
			Vector3 v = _content.localPosition;
			switch (Dir)
			{
				case ScrollDir.Horizontal:
					v.x = Mathf.Lerp(_content.localPosition.x, _targetPos, MoveToCenterSpeed * Time.deltaTime);
					_content.localPosition = v;
					if (Math.Abs(_content.localPosition.x - _targetPos) < 0.01f)
					{
						_isCentering = false;
					}
					break;
				case ScrollDir.Vertical:
					v.y = Mathf.Lerp(_content.localPosition.y, _targetPos, MoveToCenterSpeed * Time.deltaTime);
					_content.localPosition = v;
					if (Math.Abs(_content.localPosition.y - _targetPos) < 0.01f)
					{
						_isCentering = false;
					}
					break;
			}
		}
	}
	
	public void OnDrag(PointerEventData eventData)
	{
		//这里会一直调用 实时更新中心点的下标 并作出缩放改变
		switch (Dir)
		{
			case ScrollDir.Horizontal:
				_targetPos = FindClosestChildPos(_content.localPosition.x, out _curCenterChildIndex);
				break;
			case ScrollDir.Vertical:
				_targetPos = FindClosestChildPos(_content.localPosition.y, out _curCenterChildIndex);
				break;
		}
		SetCellScale();
	}

	public void OnEndDrag(PointerEventData eventData)
	{
		//如果只需要在拖动结束的时候刷新中心点 在这里调用更好 拖动结束的时候 才会刷新一次中心下标
		//switch (Dir)
		//{
		//	case ScrollDir.Horizontal:
		//		_targetPos = FindClosestChildPos(_content.localPosition.x, out _curCenterChildIndex);
		//		break;
		//	case ScrollDir.Vertical:
		//		_targetPos = FindClosestChildPos(_content.localPosition.y, out _curCenterChildIndex);
		//		break;
		//}
		_isCentering = true;
	}

	public void OnBeginDrag(PointerEventData eventData)
	{
		_isCentering = false;
		_curCenterChildIndex = -1;
	}

	private float FindClosestChildPos(float currentPos, out int curCenterChildIndex)
	{
		float closest = 0;
		float distance = Mathf.Infinity;
		curCenterChildIndex = -1;
		for (int i = 0; i < _childrenPos.Count; i++)
		{
			float p = _childrenPos[i];
			float d = Mathf.Abs(p - currentPos);
			if (d < distance)
			{
				distance = d;
				closest = p;
				curCenterChildIndex = i;
			}
			else
				break;
		}
		return closest;
	}
}

DoTween实现的代码(需要导入DoTween才可以用。)

DoTween下载地址

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;
using DG.Tweening;

public class CenterOnChild_byDoTween : MonoBehaviour, IEndDragHandler, IDragHandler, IBeginDragHandler
{
	public ScrollDir Dir = ScrollDir.Horizontal;

	[Header("对齐中点速度")]
	public float ToCenterTime = 0.5f;
	[Header("中心点放大倍数")]
	public float CenterScale = 1f;
	[Header("非中心点放大倍数")]
	public float UnCenterScale = 0.9f;
	private ScrollRect _scrollView;

	private Transform _content;
	private RectTransform _content_recttsf;
	private List _childrenPos = new List();
	private float _targetPos;

	/// 
	/// 当前中心child索引
	/// 
	private int _curCenterChildIndex = -1;

	/// 
	/// 当前中心ChildItem
	/// 
	public GameObject CurCenterChildItem
	{
		get
		{
			GameObject centerChild = null;
			if (_content != null && _curCenterChildIndex >= 0 && _curCenterChildIndex < _content.childCount)
			{
				centerChild = _content.GetChild(_curCenterChildIndex).gameObject;
			}
			return centerChild;
		}
	}
	/// 
	/// 根据拖动来改变每一个子物体的缩放
	/// 
	public void SetCellScale()
	{
		GameObject centerChild = null;
		for (int i = 0; i < _content.childCount; i++)
		{
			centerChild = _content.GetChild(i).gameObject;
			if (i == _curCenterChildIndex)
				centerChild.transform.localScale = CenterScale * Vector3.one;
			else
				centerChild.transform.localScale = UnCenterScale * Vector3.one;
		}
	}
	void Awake()
	{
		_scrollView = GetComponent();
		if (_scrollView == null)
		{
			Debug.LogError("ScrollRect is null");
			return;
		}
		_content = _scrollView.content;

		LayoutGroup layoutGroup = null;
		layoutGroup = _content.GetComponent();
		_content_recttsf = _content.GetComponent();
		if (layoutGroup == null)
		{
			Debug.LogError("LayoutGroup component is null");
		}
		_scrollView.movementType = ScrollRect.MovementType.Unrestricted;
		float spacing = 0f;
		//根据dir计算坐标,Horizontal:存x,Vertical:存y
		switch (Dir)
		{
			case ScrollDir.Horizontal:
				if (layoutGroup is HorizontalLayoutGroup)
				{
					float childPosX = _scrollView.GetComponent().rect.width * 0.5f - GetChildItemWidth(0) * 0.5f;
					spacing = (layoutGroup as HorizontalLayoutGroup).spacing;
					_childrenPos.Add(childPosX);
					for (int i = 1; i < _content.childCount; i++)
					{
						childPosX -= GetChildItemWidth(i) * 0.5f + GetChildItemWidth(i - 1) * 0.5f + spacing;
						_childrenPos.Add(childPosX);
					}
				}
				else if (layoutGroup is GridLayoutGroup)
				{
					GridLayoutGroup grid = layoutGroup as GridLayoutGroup;
					float childPosX = _scrollView.GetComponent().rect.width * 0.5f - grid.cellSize.x * 0.5f;
					_childrenPos.Add(childPosX);
					for (int i = 0; i < _content.childCount - 1; i++)
					{
						childPosX -= grid.cellSize.x + grid.spacing.x;
						_childrenPos.Add(childPosX);
					}
				}
				else
				{
					Debug.LogError("Horizontal ScrollView is using VerticalLayoutGroup");
				}
				break;
			case ScrollDir.Vertical:
				if (layoutGroup is VerticalLayoutGroup)
				{
					float childPosY = -_scrollView.GetComponent().rect.height * 0.5f + GetChildItemHeight(0) * 0.5f;
					spacing = (layoutGroup as VerticalLayoutGroup).spacing;
					_childrenPos.Add(childPosY);
					for (int i = 1; i < _content.childCount; i++)
					{
						childPosY += GetChildItemHeight(i) * 0.5f + GetChildItemHeight(i - 1) * 0.5f + spacing;
						_childrenPos.Add(childPosY);
					}
				}
				else if (layoutGroup is GridLayoutGroup)
				{
					GridLayoutGroup grid = layoutGroup as GridLayoutGroup;
					float childPosY = -_scrollView.GetComponent().rect.height * 0.5f + grid.cellSize.y * 0.5f;
					_childrenPos.Add(childPosY);
					for (int i = 1; i < _content.childCount; i++)
					{
						childPosY += grid.cellSize.y + grid.spacing.y;
						_childrenPos.Add(childPosY);
					}
				}
				else
				{
					Debug.LogError("Vertical ScrollView is using HorizontalLayoutGroup");
				}
				break;
		}
	}
	private float GetChildItemWidth(int index)
	{
		return (_content.GetChild(index) as RectTransform).sizeDelta.x;
	}

	private float GetChildItemHeight(int index)
	{
		return (_content.GetChild(index) as RectTransform).sizeDelta.y;
	}

	public void OnDrag(PointerEventData eventData)
	{
		//这里会一直调用 实时更新中心点的下标 并作出缩放改变 所以如果需求是拖动结束的时候 这里不调用FindClosestChildPos
		switch (Dir)
		{
			case ScrollDir.Horizontal:
				_targetPos = FindClosestChildPos(_content.localPosition.x, out _curCenterChildIndex);
				break;
			case ScrollDir.Vertical:
				_targetPos = FindClosestChildPos(_content.localPosition.y, out _curCenterChildIndex);
				break;
		}
		SetCellScale();
	}

	public void OnEndDrag(PointerEventData eventData)
	{
		_scrollView.StopMovement();
		switch (Dir)
		{
			case ScrollDir.Horizontal:
				_targetPos = FindClosestChildPos(_content.localPosition.x, out _curCenterChildIndex);
				_content.DOLocalMoveX(_targetPos, ToCenterTime);
				break;
			case ScrollDir.Vertical:
				_targetPos = FindClosestChildPos(_content.localPosition.y, out _curCenterChildIndex);
				_content.DOLocalMoveY(_targetPos, ToCenterTime);
				break;
		}
		SetCellScale();
	}

	public void OnBeginDrag(PointerEventData eventData)
	{
		_content.DOKill();
		_curCenterChildIndex = -1;
	}

	private float FindClosestChildPos(float currentPos, out int curCenterChildIndex)
	{
		float closest = 0;
		float distance = Mathf.Infinity;
		curCenterChildIndex = -1;
		for (int i = 0; i < _childrenPos.Count; i++)
		{
			float p = _childrenPos[i];
			float d = Mathf.Abs(p - currentPos);
			if (d < distance)
			{
				distance = d;
				closest = p;
				curCenterChildIndex = i;
			}
			else
				break;
		}
		return closest;
	}
}

 

 

三、备注

1.Scroll View锚点记得居中。

2.两个脚本公用了一个enum。

你可能感兴趣的:(Unit杂类)