UGUI ScrollRect滑动居中CenterOnChild实现
NGUI有一个UICenterOnChild脚本,可以轻松实现ScrollView中拖动子物体后保持一个子物体位于中心位置。然而UGUI就没这么方便了,官方并没有类似功能的脚本。网上找到一些运行效果都不对,可能因为UGUI需要配置的东西太多,RectTransfrom不同设置效果就不一样。故自己实现了该功能,使用时的配置如下:
1. 仅适用于水平方向拖动的ScrollRect。
2. ScrollRect中的Grid必须使用GridLayoutGroup。
3. 由于需要知道ScrollRect的宽度以便计算中心位置,故ScrollRect的Anchors的四个小三角中的上面或者下面的一对角不得分离,不然宽度计算出错,即需要:Anchors.Min.x == Anchors.Max.x。最好四角合一。
4. 由于是通过设置ScrollRect's content的localPosition实现,故需要将ScrollRect的中心点Pivot与content的中心点均置于自身最左边(0, 0.5)。
5. 由于第一个与最后一个子物体需要停留在中间,故ScrollRect的Movement Type需要设置为Unrestricted。该项会在运行时自动设置。
代码如下:
1 /// <summary> 2 /// 3 /// 拖动ScrollRect结束时始终让一个子物体位于中心位置。 4 /// 5 /// </summary> 6 public class CenterOnChild : MonoBehaviour, IEndDragHandler, IDragHandler 7 { 8 //将子物体拉到中心位置时的速度 9 public float centerSpeed = 9f; 10 11 //注册该事件获取当拖动结束时位于中心位置的子物体 12 public delegate void OnCenterHandler (GameObject centerChild); 13 public event OnCenterHandler onCenter; 14 15 private ScrollRect _scrollView; 16 private Transform _container; 17 18 private List<float> _childrenPos = new List<float> (); 19 private float _targetPos; 20 private bool _centering = false; 21 22 void Awake () 23 { 24 _scrollView = GetComponent<ScrollRect> (); 25 if (_scrollView == null) 26 { 27 Debug.LogError ("CenterOnChild: No ScrollRect"); 28 return; 29 } 30 _container = _scrollView.content; 31 32 GridLayoutGroup grid; 33 grid = _container.GetComponent<GridLayoutGroup> (); 34 if (grid == null) 35 { 36 Debug.LogError ("CenterOnChild: No GridLayoutGroup on the ScrollRect's content"); 37 return; 38 } 39 40 _scrollView.movementType = ScrollRect.MovementType.Unrestricted; 41 42 //计算第一个子物体位于中心时的位置 43 float childPosX = _scrollView.GetComponent<RectTransform> ().rect.width * 0.5f - grid.cellSize.x * 0.5f; 44 _childrenPos.Add (childPosX); 45 //缓存所有子物体位于中心时的位置 46 for (int i = 0; i < _container.childCount - 1; i++) 47 { 48 childPosX -= grid.cellSize.x + grid.spacing.x; 49 _childrenPos.Add (childPosX); 50 } 51 } 52 53 void Update () 54 { 55 if (_centering) 56 { 57 Vector3 v = _container.localPosition; 58 v.x = Mathf.Lerp (_container.localPosition.x, _targetPos, centerSpeed * Time.deltaTime); 59 _container.localPosition = v; 60 if (Mathf.Abs (_container.localPosition.x - _targetPos) < 0.01f) 61 { 62 _centering = false; 63 } 64 } 65 } 66 67 public void OnEndDrag (PointerEventData eventData) 68 { 69 _centering = true; 70 _targetPos = FindClosestPos (_container.localPosition.x); 71 } 72 73 public void OnDrag (PointerEventData eventData) 74 { 75 _centering = false; 76 } 77 78 private float FindClosestPos (float currentPos) 79 { 80 int childIndex = 0; 81 float closest = 0; 82 float distance = Mathf.Infinity; 83 84 for (int i = 0; i < _childrenPos.Count; i++) 85 { 86 float p = _childrenPos[i]; 87 float d = Mathf.Abs (p - currentPos); 88 if (d < distance) 89 { 90 distance = d; 91 closest = p; 92 childIndex = i; 93 } 94 } 95 96 GameObject centerChild = _container.GetChild (childIndex).gameObject; 97 if (onCenter != null) 98 onCenter (centerChild); 99 100 return closest; 101 } 102 }
由于已缓存所有子物体的位置信息,故该代码简单修改可以扩展功能,如增加到上一项、到下一项、跳转到指定项等功能。