原帖:http://www.cnblogs.com/suoluo/p/5535420.html
上述文章是昨天项目遇到这种需求无意间搜到的,不过这篇的缺陷是只能水平实现滑动居中,所以我原基础上更改了一下。
1. 仅适用于水平方向和垂直方向(=-= 复制修改)拖动的ScrollRect。
2. ScrollRect中的Grid必须使用GridLayoutGroup。(虽然是居中表现 ChildAligenment也【不要】选择成Center Middle)
3. 由于需要知道ScrollRect的宽度以便计算中心位置,故ScrollRect的Anchors的四个小三角中的上面或者下面的一对角不得分离,不然宽度计算出错,即需要:Anchors.Min.x == Anchors.Max.x。最好四角合一。(解释一下,因为不同的Anchors下 content的localPosition坐标不同,所以用到的公式不同,这个公式可以根据自己的Anchors需求自己列出)
4. 由于是通过设置ScrollRect's content的localPosition实现,故需要将ScrollRect的中心点Pivot与content的中心点均置于自身最左边(0, 0.5)。(这条内容我没有尝试,因为时间紧张还没有看水平的东西,直接修改成垂直的了)
5. 由于第一个与最后一个子物体需要停留在中间,故ScrollRect的Movement Type需要设置为Unrestricted。该项会在运行时自动设置。(这条不需要说明了,不过表现垂直的时候要在content下添加ContentSizeFitter,并且把Vertical Fit 选择MinSize)
上面几条是我个人的理解,也不可能全对,欢迎指出。
下面附上代码
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.EventSystems;
/// <summary>
///
/// 拖动ScrollRect结束时始终让一个子物体位于中心位置。
///
/// </summary>
public class CenterOnChild : MonoBehaviour, IEndDragHandler, IDragHandler
{
public enum AxisType
{
Vertical,
Horizontal
}
public AxisType m_AxisType = AxisType.Vertical;
//将子物体拉到中心位置时的速度
public float centerSpeed = 40f;
//注册该事件获取当拖动结束时位于中心位置的子物体
public delegate void OnCenterHandler(GameObject centerChild);
public event OnCenterHandler onCenter;
public ScrollRect _scrollView;
public Transform _container;
private List<float> _childrenPos = new List<float>();
private float _targetPos;
private bool _centering = false;
void Awake()
{
if (_scrollView == null)
{
Debug.LogError("CenterOnChild: No ScrollRect");
return;
}
GridLayoutGroup grid;
grid = _container.GetComponent<GridLayoutGroup>();
if (grid == null)
{
Debug.LogError("CenterOnChild: No GridLayoutGroup on the ScrollRect's content");
return;
}
_scrollView.movementType = ScrollRect.MovementType.Unrestricted;
//计算第一个子物体位于中心时的位置
float childPosY;
float childPosX;
switch (m_AxisType)
{
case AxisType.Vertical:
childPosY = _container.localPosition.y - (_scrollView.GetComponent<RectTransform>().rect.height * 0.5f - grid.cellSize.y * 0.5f);//垂直的公式
_childrenPos.Add(childPosY);
//缓存所有子物体位于中心时的位置
for (int i = 0; i < _container.childCount - 1; i++)
{
childPosY += grid.cellSize.y + grid.spacing.y;
_childrenPos.Add(childPosY);
}
break;
case AxisType.Horizontal:
childPosX = _scrollView.GetComponent<RectTransform>().rect.width * 0.5f - grid.cellSize.x * 0.5f;//水平的公式
//缓存所有子物体位于中心时的位置
for (int i = 0; i < _container.childCount - 1; i++)
{
childPosX += grid.cellSize.x + grid.spacing.x;
_childrenPos.Add(childPosX);
}
_childrenPos.Add(childPosX);
break;
}
}
void Update()
{
if (_centering)
{
Vector3 v = _container.localPosition;
switch (m_AxisType)
{
case AxisType.Vertical:
v.y = Mathf.Lerp(_container.localPosition.y, _targetPos, centerSpeed * Time.deltaTime);
_container.localPosition = v;
if (Mathf.Abs(_container.localPosition.y - _targetPos) < 0.01f)
{
_centering = false;
}
break;
case AxisType.Horizontal:
v.x = Mathf.Lerp(_container.localPosition.x, _targetPos, centerSpeed * Time.deltaTime);
_container.localPosition = v;
if (Mathf.Abs(_container.localPosition.x - _targetPos) < 0.01f)
{
_centering = false;
}
break;
}
}
}
public void OnEndDrag(PointerEventData eventData)
{
_centering = true;
switch (m_AxisType)
{
case AxisType.Vertical:
_targetPos = FindClosestPos(_container.localPosition.y);
break;
case AxisType.Horizontal:
_targetPos = FindClosestPos(_container.localPosition.x);
break;
}
}
public void OnDrag(PointerEventData eventData)
{
_centering = false;
}
private float FindClosestPos(float currentPos)
{
int childIndex = 0;
float closest = 0;
float distance = Mathf.Infinity;
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;
childIndex = i;
}
}
GameObject centerChild = _container.GetChild(childIndex).gameObject;
if (onCenter != null)
onCenter(centerChild);
return closest;
}
}
上图是项目需求的3个垂直Scroll
因为我的表现是垂直的 Anchor的Y轴相等