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 }
由於已緩存所有子物體的位置信息,故該代碼簡單修改可以擴展功能,如增加到上一項、到下一項、跳轉到指定項等功能。