├── LoopScrollRect ├── LoopScrollRectItem.cs ├── ILoopScrollCell.cs ├── ILoopScrollCellReturn.cs ├── Editor │ ├── LoopScrollRectInspector.cs.meta │ └── LoopScrollRectInspector.cs ├── InitOnStart.cs ├── LoopScrollDataSource.cs ├── LoopVerticalScrollRect.cs ├── LoopHorizontalScrollRect.cs ├── LoopScrollPrefabSource.cs └── LoopScrollRect.cs └── README.md /LoopScrollRect/LoopScrollRectItem.cs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zxsean/LoopScrollRect/HEAD/LoopScrollRect/LoopScrollRectItem.cs -------------------------------------------------------------------------------- /LoopScrollRect/ILoopScrollCell.cs: -------------------------------------------------------------------------------- 1 | namespace UnityEngine.UI 2 | { 3 | public interface ILoopScrollCell 4 | { 5 | void ScrollCellIndex(int _idx); 6 | } 7 | } -------------------------------------------------------------------------------- /LoopScrollRect/ILoopScrollCellReturn.cs: -------------------------------------------------------------------------------- 1 | namespace UnityEngine.UI 2 | { 3 | public interface ILoopScrollCellReturn 4 | { 5 | void ScrollCellReturn(); 6 | } 7 | } -------------------------------------------------------------------------------- /LoopScrollRect/Editor/LoopScrollRectInspector.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 216d44a40b90b944db6c5f4624768e58 3 | timeCreated: 1439395663 4 | licenseType: Free 5 | MonoImporter: 6 | serializedVersion: 2 7 | defaultReferences: [] 8 | executionOrder: 0 9 | icon: {instanceID: 0} 10 | userData: 11 | assetBundleName: 12 | assetBundleVariant: 13 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # LoopScrollRect 2 | 基于https://github.com/qiankanglai/LoopScrollRect 3 | 简化了LoopScrollPrefabSource的操作,同时增加了2个回调接口,方便日常使用. 4 | m_provideDataCallBack,等同于原版的item初始化流程,不过不需要单独在item上挂载脚本. 5 | m_clickItemCallBack,点击回调. 6 | 7 | ## 更新 8 | 9 | 1.0.1 2020-8-28 10 | [+]添加一个item入场动画,默认开启. 11 | [*]合并主干版本代码. 12 | [+]添加Refill使用offset参数后,如果offset为最后一页的index时,没法一页填充满. 13 | [+]添加一个修正offset值的方法,会自动修正index的值,避免出现item被吞了的情况(原版有警告). 14 | [+]添加一个接口,用于默认实现go回收事件. -------------------------------------------------------------------------------- /LoopScrollRect/InitOnStart.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | using System.Collections; 3 | using UnityEngine.UI; 4 | 5 | namespace SG 6 | { 7 | [RequireComponent(typeof(UnityEngine.UI.LoopScrollRect))] 8 | [DisallowMultipleComponent] 9 | public class InitOnStart : MonoBehaviour 10 | { 11 | public int totalCount = -1; 12 | void Start() 13 | { 14 | var ls = GetComponent(); 15 | ls.totalCount = totalCount; 16 | ls.RefillCells(); 17 | } 18 | } 19 | } -------------------------------------------------------------------------------- /LoopScrollRect/LoopScrollDataSource.cs: -------------------------------------------------------------------------------- 1 | namespace UnityEngine.UI 2 | { 3 | public abstract class LoopScrollDataSource 4 | { 5 | public abstract void ProvideData(Transform transform, int idx); 6 | } 7 | 8 | public class LoopScrollSendIndexSource : LoopScrollDataSource 9 | { 10 | public static readonly LoopScrollSendIndexSource Instance = new LoopScrollSendIndexSource(); 11 | 12 | private LoopScrollSendIndexSource() 13 | { } 14 | 15 | public override void ProvideData(Transform transform, int idx) 16 | { 17 | transform.SendMessage("ScrollCellIndex", idx); 18 | } 19 | } 20 | 21 | public class LoopScrollArraySource : LoopScrollDataSource 22 | { 23 | private T[] objectsToFill; 24 | 25 | public LoopScrollArraySource(T[] objectsToFill) 26 | { 27 | this.objectsToFill = objectsToFill; 28 | } 29 | 30 | public override void ProvideData(Transform transform, int idx) 31 | { 32 | transform.SendMessage("ScrollCellContent", objectsToFill[idx]); 33 | } 34 | } 35 | } -------------------------------------------------------------------------------- /LoopScrollRect/Editor/LoopScrollRectInspector.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | using UnityEngine.UI; 3 | using UnityEditor; 4 | 5 | [CustomEditor(typeof(LoopScrollRect), true)] 6 | public class LoopScrollRectInspector : Editor 7 | { 8 | int index = 0; 9 | float speed = 1000; 10 | public override void OnInspectorGUI () 11 | { 12 | base.OnInspectorGUI(); 13 | EditorGUILayout.Space(); 14 | 15 | LoopScrollRect scroll = (LoopScrollRect)target; 16 | 17 | if (GUILayout.Button("Create PoolObject")) 18 | { 19 | var _tf = scroll.transform.Find(LoopScrollPrefabSource.__POOLSNAME); 20 | 21 | if (!_tf) 22 | { 23 | GameObject _gameObject = new GameObject(LoopScrollPrefabSource.__POOLSNAME); 24 | 25 | _gameObject.transform.SetParent(scroll.transform); 26 | _gameObject.transform.localScale = Vector3.one; 27 | _gameObject.transform.localPosition = Vector3.zero; 28 | 29 | _tf = _gameObject.transform; 30 | } 31 | 32 | scroll.prefabSource.PoolGameObject = _tf; 33 | 34 | Debug.Log("Create Suc!"); 35 | } 36 | 37 | GUI.enabled = Application.isPlaying; 38 | 39 | EditorGUILayout.BeginHorizontal(); 40 | if(GUILayout.Button("Clear")) 41 | { 42 | scroll.ClearCells(); 43 | } 44 | if (GUILayout.Button("Refresh")) 45 | { 46 | scroll.RefreshCells(); 47 | } 48 | if(GUILayout.Button("Refill")) 49 | { 50 | scroll.RefillCells(); 51 | } 52 | if(GUILayout.Button("RefillFromEnd")) 53 | { 54 | scroll.RefillCellsFromEnd(); 55 | } 56 | EditorGUILayout.EndHorizontal(); 57 | 58 | EditorGUIUtility.labelWidth = 45; 59 | float w = (EditorGUIUtility.currentViewWidth - 100) / 2; 60 | EditorGUILayout.BeginHorizontal(); 61 | index = EditorGUILayout.IntField("Index", index, GUILayout.Width(w)); 62 | speed = EditorGUILayout.FloatField("Speed", speed, GUILayout.Width(w)); 63 | if(GUILayout.Button("Scroll", GUILayout.Width(45))) 64 | { 65 | scroll.SrollToCell(index, speed); 66 | } 67 | EditorGUILayout.EndHorizontal(); 68 | } 69 | } -------------------------------------------------------------------------------- /LoopScrollRect/LoopVerticalScrollRect.cs: -------------------------------------------------------------------------------- 1 | namespace UnityEngine.UI 2 | { 3 | [AddComponentMenu("UI/Loop Vertical Scroll Rect", 51)] 4 | [DisallowMultipleComponent] 5 | public class LoopVerticalScrollRect : LoopScrollRect 6 | { 7 | protected override float GetTemplateSize() 8 | { 9 | float _size = contentSpacing; 10 | if (m_GridLayout != null) 11 | { 12 | _size += m_GridLayout.cellSize.y; 13 | } 14 | else 15 | { 16 | _size += LayoutUtility.GetPreferredHeight(m_prefabSource.TemplateGo.GetComponent()); 17 | } 18 | return _size; 19 | } 20 | 21 | 22 | protected override float GetSize(RectTransform item) 23 | { 24 | float size = contentSpacing; 25 | if (m_GridLayout != null) 26 | { 27 | size += m_GridLayout.cellSize.y; 28 | } 29 | else 30 | { 31 | size += LayoutUtility.GetPreferredHeight(item); 32 | } 33 | return size; 34 | } 35 | 36 | protected override float GetDimension(Vector2 vector) 37 | { 38 | return vector.y; 39 | } 40 | 41 | protected override Vector2 GetVector(float value) 42 | { 43 | return new Vector2(0, value); 44 | } 45 | 46 | protected override void Awake() 47 | { 48 | base.Awake(); 49 | directionSign = -1; 50 | 51 | GridLayoutGroup layout = content.GetComponent(); 52 | if (layout != null && layout.constraint != GridLayoutGroup.Constraint.FixedColumnCount) 53 | { 54 | Debug.LogError("[LoopHorizontalScrollRect] unsupported GridLayoutGroup constraint"); 55 | } 56 | } 57 | 58 | protected override bool UpdateItems(Bounds viewBounds, Bounds contentBounds) 59 | { 60 | bool changed = false; 61 | 62 | if (viewBounds.min.y < contentBounds.min.y) 63 | { 64 | float size = NewItemAtEnd(), totalSize = size; 65 | while (size > 0 && viewBounds.min.y < contentBounds.min.y - totalSize) 66 | { 67 | size = NewItemAtEnd(); 68 | totalSize += size; 69 | } 70 | if (totalSize > 0) 71 | changed = true; 72 | } 73 | 74 | if (viewBounds.max.y > contentBounds.max.y) 75 | { 76 | float size = NewItemAtStart(), totalSize = size; 77 | while (size > 0 && viewBounds.max.y > contentBounds.max.y + totalSize) 78 | { 79 | size = NewItemAtStart(); 80 | totalSize += size; 81 | } 82 | if (totalSize > 0) 83 | changed = true; 84 | } 85 | 86 | if (viewBounds.min.y > contentBounds.min.y + threshold) 87 | { 88 | float size = DeleteItemAtEnd(), totalSize = size; 89 | while (size > 0 && viewBounds.min.y > contentBounds.min.y + threshold + totalSize) 90 | { 91 | size = DeleteItemAtEnd(); 92 | totalSize += size; 93 | } 94 | if (totalSize > 0) 95 | changed = true; 96 | } 97 | 98 | if (viewBounds.max.y < contentBounds.max.y - threshold) 99 | { 100 | float size = DeleteItemAtStart(), totalSize = size; 101 | while (size > 0 && viewBounds.max.y < contentBounds.max.y - threshold - totalSize) 102 | { 103 | size = DeleteItemAtStart(); 104 | totalSize += size; 105 | } 106 | if (totalSize > 0) 107 | changed = true; 108 | } 109 | 110 | return changed; 111 | } 112 | } 113 | } -------------------------------------------------------------------------------- /LoopScrollRect/LoopHorizontalScrollRect.cs: -------------------------------------------------------------------------------- 1 | namespace UnityEngine.UI 2 | { 3 | [AddComponentMenu("UI/Loop Horizontal Scroll Rect", 50)] 4 | [DisallowMultipleComponent] 5 | public class LoopHorizontalScrollRect : LoopScrollRect 6 | { 7 | protected override float GetTemplateSize() 8 | { 9 | float _size = contentSpacing; 10 | 11 | if (m_GridLayout != null) 12 | { 13 | _size += m_GridLayout.cellSize.x; 14 | } 15 | else 16 | { 17 | _size += LayoutUtility.GetPreferredWidth(m_prefabSource.TemplateGo.GetComponent()); 18 | } 19 | 20 | return _size; 21 | } 22 | 23 | protected override float GetSize(RectTransform _item) 24 | { 25 | float size = contentSpacing; 26 | if (m_GridLayout != null) 27 | { 28 | size += m_GridLayout.cellSize.x; 29 | } 30 | else 31 | { 32 | size += LayoutUtility.GetPreferredWidth(_item); 33 | } 34 | return size; 35 | } 36 | 37 | protected override float GetDimension(Vector2 vector) 38 | { 39 | return -vector.x; 40 | } 41 | 42 | protected override Vector2 GetVector(float value) 43 | { 44 | return new Vector2(-value, 0); 45 | } 46 | 47 | protected override void Awake() 48 | { 49 | base.Awake(); 50 | directionSign = 1; 51 | 52 | GridLayoutGroup layout = content.GetComponent(); 53 | if (layout != null && layout.constraint != GridLayoutGroup.Constraint.FixedRowCount) 54 | { 55 | Debug.LogError("[LoopHorizontalScrollRect] unsupported GridLayoutGroup constraint"); 56 | } 57 | } 58 | 59 | protected override bool UpdateItems(Bounds viewBounds, Bounds contentBounds) 60 | { 61 | bool changed = false; 62 | 63 | if (viewBounds.max.x > contentBounds.max.x) 64 | { 65 | float size = NewItemAtEnd(), totalSize = size; 66 | while (size > 0 && viewBounds.max.x > contentBounds.max.x + totalSize) 67 | { 68 | size = NewItemAtEnd(); 69 | totalSize += size; 70 | } 71 | if (totalSize > 0) 72 | changed = true; 73 | } 74 | 75 | if (viewBounds.min.x < contentBounds.min.x) 76 | { 77 | float size = NewItemAtStart(), totalSize = size; 78 | while (size > 0 && viewBounds.min.x < contentBounds.min.x - totalSize) 79 | { 80 | size = NewItemAtStart(); 81 | totalSize += size; 82 | } 83 | if (totalSize > 0) 84 | changed = true; 85 | } 86 | 87 | if (viewBounds.max.x < contentBounds.max.x - threshold) 88 | { 89 | float size = DeleteItemAtEnd(), totalSize = size; 90 | while (size > 0 && viewBounds.max.x < contentBounds.max.x - threshold - totalSize) 91 | { 92 | size = DeleteItemAtEnd(); 93 | totalSize += size; 94 | } 95 | if (totalSize > 0) 96 | changed = true; 97 | } 98 | 99 | if (viewBounds.min.x > contentBounds.min.x + threshold) 100 | { 101 | float size = DeleteItemAtStart(), totalSize = size; 102 | while (size > 0 && viewBounds.min.x > contentBounds.min.x + threshold + totalSize) 103 | { 104 | size = DeleteItemAtStart(); 105 | totalSize += size; 106 | } 107 | if (totalSize > 0) 108 | changed = true; 109 | } 110 | 111 | return changed; 112 | } 113 | } 114 | } -------------------------------------------------------------------------------- /LoopScrollRect/LoopScrollPrefabSource.cs: -------------------------------------------------------------------------------- 1 | using Client.Library; 2 | using Client.Library.TMNSUtility; 3 | using System; 4 | using System.Collections.Generic; 5 | 6 | namespace UnityEngine.UI 7 | { 8 | [System.Serializable] 9 | public class LoopScrollPrefabSource 10 | { 11 | public const string __POOL_NAME = "[ItemPool]"; 12 | 13 | /// 14 | /// 池节点 15 | /// 16 | public Transform m_poolGameObject; 17 | 18 | public GameObject m_gameObject; 19 | 20 | public string m_assetName; 21 | 22 | public int m_poolSize = 5; 23 | 24 | private bool m_inited = false; 25 | 26 | /// 27 | /// 池 28 | /// 29 | private Queue m_queue = new Queue(); 30 | 31 | /// 32 | /// 模板 33 | /// 34 | private GameObject m_templateGo; 35 | 36 | /// 37 | /// 模板 38 | /// 39 | public GameObject TemplateGo => m_templateGo; 40 | 41 | [HideInInspector] 42 | public static Func m_loadGoFromAB; 43 | 44 | [HideInInspector] 45 | public static Action m_unloadAB; 46 | 47 | /// 48 | /// 池节点 49 | /// 50 | public Transform PoolGameObject 51 | { 52 | set => m_poolGameObject = value; 53 | } 54 | 55 | public virtual GameObject GetObject() 56 | { 57 | if (!m_inited) 58 | { 59 | InitPool(); 60 | 61 | m_inited = true; 62 | } 63 | 64 | return GetObjectFromPool(); 65 | } 66 | 67 | public virtual void ReturnObject(Transform _go) 68 | { 69 | _go.SendMessage("ScrollCellReturn", SendMessageOptions.DontRequireReceiver); 70 | 71 | ReturnObjectToPool(_go.gameObject); 72 | } 73 | 74 | /// 75 | /// 初始化池 76 | /// 77 | private void InitPool() 78 | { 79 | if (m_gameObject == null && 80 | m_assetName == null) 81 | { 82 | Debug.LogError("no set template!!!"); 83 | 84 | return; 85 | } 86 | 87 | if (m_assetName != null && 88 | m_gameObject == null) 89 | { 90 | if (m_loadGoFromAB != null) 91 | { 92 | m_templateGo = Object.Instantiate(m_loadGoFromAB(m_assetName)); 93 | } 94 | else 95 | { 96 | LogUtils.LogError("m_loadGoFromAB is null!"); 97 | 98 | return; 99 | } 100 | } 101 | else 102 | { 103 | if (m_gameObject == null) 104 | { 105 | LogUtils.LogError("m_gameObject is null!"); 106 | 107 | return; 108 | } 109 | 110 | //m_templateGo = GameObject.Instantiate(m_gameObject); 111 | m_templateGo = m_gameObject; 112 | 113 | m_gameObject.SetActive(false); 114 | } 115 | 116 | if (m_poolGameObject == null && 117 | m_gameObject != null) 118 | { 119 | m_poolGameObject = m_gameObject.transform; 120 | } 121 | 122 | int _size = m_poolSize - m_queue.Count; 123 | 124 | m_templateGo.transform.SetParent(m_poolGameObject); 125 | 126 | // 创建池 127 | for (int i = 0; i < _size; ++i) 128 | { 129 | GameObject _poolItem = Object.Instantiate(m_templateGo); 130 | 131 | _poolItem.name = _poolItem.name.Replace("(Clone)", ""); 132 | 133 | _poolItem.SetParent(m_poolGameObject); 134 | 135 | m_queue.Enqueue(_poolItem); 136 | } 137 | 138 | m_templateGo.SetActive(false); 139 | } 140 | 141 | public void DeInitPool() 142 | { 143 | if (m_assetName != null && 144 | m_gameObject == null) 145 | { 146 | if (m_unloadAB != null) 147 | { 148 | m_unloadAB(m_assetName); 149 | } 150 | else 151 | { 152 | Debug.LogError("m_unloadAB is null!"); 153 | } 154 | } 155 | } 156 | 157 | private GameObject GetObjectFromPool() 158 | { 159 | if (m_queue.Count > 0) 160 | { 161 | return m_queue.Dequeue(); 162 | } 163 | else 164 | { 165 | for (int i = 0; i < m_poolSize; ++i) 166 | { 167 | GameObject _poolItem = Object.Instantiate(m_templateGo); 168 | 169 | _poolItem.name = _poolItem.name.Replace("(Clone)", ""); 170 | 171 | _poolItem.SetParent(m_poolGameObject); 172 | 173 | m_queue.Enqueue(_poolItem); 174 | } 175 | 176 | m_poolSize *= 2; 177 | 178 | return m_queue.Dequeue(); 179 | } 180 | } 181 | 182 | private void ReturnObjectToPool(GameObject _go) 183 | { 184 | if (m_queue.Count == m_poolSize) 185 | { 186 | Object.Destroy(_go); 187 | } 188 | else 189 | { 190 | _go.SetActive(false); 191 | m_queue.Enqueue(_go); 192 | 193 | _go.transform.SetParent(m_poolGameObject); 194 | } 195 | } 196 | } 197 | } -------------------------------------------------------------------------------- /LoopScrollRect/LoopScrollRect.cs: -------------------------------------------------------------------------------- 1 | using Client.Library; 2 | using DG.Tweening; 3 | using System; 4 | using System.Collections; 5 | using System.Collections.Generic; 6 | using UnityEngine.Events; 7 | using UnityEngine.EventSystems; 8 | 9 | namespace UnityEngine.UI 10 | { 11 | [AddComponentMenu("")] 12 | [DisallowMultipleComponent] 13 | [RequireComponent(typeof(RectTransform))] 14 | public abstract class LoopScrollRect : UIBehaviour, IInitializePotentialDragHandler, IBeginDragHandler, IEndDragHandler, IDragHandler, IScrollHandler, ICanvasElement, ILayoutElement, ILayoutGroup 15 | { 16 | [Tooltip("Prefab Source")] 17 | public LoopScrollPrefabSource m_prefabSource; 18 | 19 | [Tooltip("Total count, negative means INFINITE mode")] 20 | public int m_totalCount; 21 | 22 | [HideInInspector] 23 | [NonSerialized] 24 | public LoopScrollDataSource m_dataSource = LoopScrollSendIndexSource.Instance; 25 | 26 | /// 27 | /// 准备数据用回调,和上面的dataSource一样,2选1用即可,看习惯 28 | /// 29 | [HideInInspector] 30 | [NonSerialized] 31 | public Action m_provideDataCallBack; 32 | 33 | /// 34 | /// 点击事件 35 | /// 36 | public Action m_clickItemCallBack; 37 | 38 | private bool m_stopMovementFlag = false; 39 | 40 | public object[] objectsToFill 41 | { 42 | // wrapper for forward compatbility 43 | set 44 | { 45 | if (value != null) 46 | { 47 | m_dataSource = new LoopScrollArraySource(value); 48 | } 49 | else 50 | { 51 | m_dataSource = LoopScrollSendIndexSource.Instance; 52 | } 53 | } 54 | } 55 | 56 | protected float threshold = 0; 57 | 58 | [Tooltip("Reverse direction for dragging")] 59 | public bool reverseDirection = false; 60 | 61 | [Tooltip("Rubber scale for outside")] 62 | public float rubberScale = 1; 63 | 64 | protected int itemTypeStart = 0; 65 | protected int itemTypeEnd = 0; 66 | 67 | protected abstract float GetTemplateSize(); 68 | 69 | protected abstract float GetSize(RectTransform item); 70 | 71 | protected abstract float GetDimension(Vector2 vector); 72 | 73 | protected abstract Vector2 GetVector(float value); 74 | 75 | protected int directionSign = 0; 76 | 77 | private float m_templateSize = -1; 78 | 79 | protected float TemplateSize 80 | { 81 | get 82 | { 83 | if (m_templateSize >= 0) 84 | { 85 | return m_templateSize; 86 | } 87 | 88 | m_templateSize = GetTemplateSize(); 89 | 90 | return m_templateSize; 91 | } 92 | } 93 | 94 | private float m_ContentSpacing = -1; 95 | protected GridLayoutGroup m_GridLayout = null; 96 | 97 | protected float contentSpacing 98 | { 99 | get 100 | { 101 | if (m_ContentSpacing >= 0) 102 | { 103 | return m_ContentSpacing; 104 | } 105 | m_ContentSpacing = 0; 106 | if (content != null) 107 | { 108 | HorizontalOrVerticalLayoutGroup layout1 = content.GetComponent(); 109 | if (layout1 != null) 110 | { 111 | m_ContentSpacing = layout1.spacing; 112 | } 113 | m_GridLayout = content.GetComponent(); 114 | if (m_GridLayout != null) 115 | { 116 | m_ContentSpacing = Mathf.Abs(GetDimension(m_GridLayout.spacing)); 117 | } 118 | } 119 | return m_ContentSpacing; 120 | } 121 | } 122 | 123 | private int m_ContentConstraintCount = 0; 124 | 125 | protected int contentConstraintCount 126 | { 127 | get 128 | { 129 | if (m_ContentConstraintCount > 0) 130 | { 131 | return m_ContentConstraintCount; 132 | } 133 | m_ContentConstraintCount = 1; 134 | if (content != null) 135 | { 136 | GridLayoutGroup layout2 = content.GetComponent(); 137 | if (layout2 != null) 138 | { 139 | if (layout2.constraint == GridLayoutGroup.Constraint.Flexible) 140 | { 141 | Debug.LogWarning("[LoopScrollRect] Flexible not supported yet"); 142 | } 143 | m_ContentConstraintCount = layout2.constraintCount; 144 | } 145 | } 146 | return m_ContentConstraintCount; 147 | } 148 | } 149 | 150 | // the first line 151 | private int StartLine 152 | { 153 | get 154 | { 155 | return Mathf.CeilToInt((float)(itemTypeStart) / contentConstraintCount); 156 | } 157 | } 158 | 159 | // how many lines we have for now 160 | private int CurrentLines 161 | { 162 | get 163 | { 164 | return Mathf.CeilToInt((float)(itemTypeEnd - itemTypeStart) / contentConstraintCount); 165 | } 166 | } 167 | 168 | // how many lines we have in total 169 | private int TotalLines 170 | { 171 | get 172 | { 173 | return Mathf.CeilToInt((float)(m_totalCount) / contentConstraintCount); 174 | } 175 | } 176 | 177 | protected virtual bool UpdateItems(Bounds viewBounds, Bounds contentBounds) 178 | { 179 | return false; 180 | } 181 | 182 | //==========LoopScrollRect========== 183 | 184 | public enum MovementType 185 | { 186 | Unrestricted, // Unrestricted movement -- can scroll forever 187 | Elastic, // Restricted but flexible -- can go past the edges, but springs back in place 188 | Clamped, // Restricted movement where it's not possible to go past the edges 189 | } 190 | 191 | public enum ScrollbarVisibility 192 | { 193 | Permanent, 194 | AutoHide, 195 | AutoHideAndExpandViewport, 196 | } 197 | 198 | [Serializable] 199 | public class ScrollRectEvent : UnityEvent { } 200 | 201 | [SerializeField] 202 | private RectTransform m_Content; 203 | 204 | public RectTransform content { get { return m_Content; } set { m_Content = value; } } 205 | 206 | [SerializeField] 207 | private bool m_Horizontal = true; 208 | 209 | public bool horizontal { get { return m_Horizontal; } set { m_Horizontal = value; } } 210 | 211 | [SerializeField] 212 | private bool m_Vertical = true; 213 | 214 | public bool vertical { get { return m_Vertical; } set { m_Vertical = value; } } 215 | 216 | [SerializeField] 217 | private MovementType m_MovementType = MovementType.Elastic; 218 | 219 | public MovementType movementType { get { return m_MovementType; } set { m_MovementType = value; } } 220 | 221 | [SerializeField] 222 | private float m_Elasticity = 0.1f; // Only used for MovementType.Elastic 223 | 224 | public float elasticity { get { return m_Elasticity; } set { m_Elasticity = value; } } 225 | 226 | [SerializeField] 227 | private bool m_Inertia = true; 228 | 229 | public bool inertia { get { return m_Inertia; } set { m_Inertia = value; } } 230 | 231 | [SerializeField] 232 | private float m_DecelerationRate = 0.135f; // Only used when inertia is enabled 233 | 234 | public float decelerationRate { get { return m_DecelerationRate; } set { m_DecelerationRate = value; } } 235 | 236 | [SerializeField] 237 | private float m_ScrollSensitivity = 1.0f; 238 | 239 | public float scrollSensitivity { get { return m_ScrollSensitivity; } set { m_ScrollSensitivity = value; } } 240 | 241 | [SerializeField] 242 | private RectTransform m_Viewport; 243 | 244 | public RectTransform viewport { get { return m_Viewport; } set { m_Viewport = value; SetDirtyCaching(); } } 245 | 246 | [SerializeField] 247 | private Scrollbar m_HorizontalScrollbar; 248 | 249 | public Scrollbar horizontalScrollbar 250 | { 251 | get 252 | { 253 | return m_HorizontalScrollbar; 254 | } 255 | set 256 | { 257 | if (m_HorizontalScrollbar) 258 | m_HorizontalScrollbar.onValueChanged.RemoveListener(SetHorizontalNormalizedPosition); 259 | m_HorizontalScrollbar = value; 260 | if (m_HorizontalScrollbar) 261 | m_HorizontalScrollbar.onValueChanged.AddListener(SetHorizontalNormalizedPosition); 262 | SetDirtyCaching(); 263 | } 264 | } 265 | 266 | [SerializeField] 267 | private Scrollbar m_VerticalScrollbar; 268 | 269 | public Scrollbar verticalScrollbar 270 | { 271 | get 272 | { 273 | return m_VerticalScrollbar; 274 | } 275 | set 276 | { 277 | if (m_VerticalScrollbar) 278 | m_VerticalScrollbar.onValueChanged.RemoveListener(SetVerticalNormalizedPosition); 279 | m_VerticalScrollbar = value; 280 | if (m_VerticalScrollbar) 281 | m_VerticalScrollbar.onValueChanged.AddListener(SetVerticalNormalizedPosition); 282 | SetDirtyCaching(); 283 | } 284 | } 285 | 286 | [SerializeField] 287 | private ScrollbarVisibility m_HorizontalScrollbarVisibility; 288 | 289 | public ScrollbarVisibility horizontalScrollbarVisibility { get { return m_HorizontalScrollbarVisibility; } set { m_HorizontalScrollbarVisibility = value; SetDirtyCaching(); } } 290 | 291 | [SerializeField] 292 | private ScrollbarVisibility m_VerticalScrollbarVisibility; 293 | 294 | public ScrollbarVisibility verticalScrollbarVisibility { get { return m_VerticalScrollbarVisibility; } set { m_VerticalScrollbarVisibility = value; SetDirtyCaching(); } } 295 | 296 | [SerializeField] 297 | private float m_HorizontalScrollbarSpacing; 298 | 299 | public float horizontalScrollbarSpacing { get { return m_HorizontalScrollbarSpacing; } set { m_HorizontalScrollbarSpacing = value; SetDirty(); } } 300 | 301 | [SerializeField] 302 | private float m_VerticalScrollbarSpacing; 303 | 304 | public float verticalScrollbarSpacing { get { return m_VerticalScrollbarSpacing; } set { m_VerticalScrollbarSpacing = value; SetDirty(); } } 305 | 306 | [SerializeField] 307 | private ScrollRectEvent m_OnValueChanged = new ScrollRectEvent(); 308 | 309 | public ScrollRectEvent onValueChanged { get { return m_OnValueChanged; } set { m_OnValueChanged = value; } } 310 | 311 | // The offset from handle position to mouse down position 312 | private Vector2 m_PointerStartLocalCursor = Vector2.zero; 313 | 314 | private Vector2 m_ContentStartPosition = Vector2.zero; 315 | 316 | private RectTransform m_ViewRect; 317 | 318 | protected RectTransform viewRect 319 | { 320 | get 321 | { 322 | if (m_ViewRect == null) 323 | m_ViewRect = m_Viewport; 324 | if (m_ViewRect == null) 325 | m_ViewRect = (RectTransform)transform; 326 | return m_ViewRect; 327 | } 328 | } 329 | 330 | private Bounds m_ContentBounds; 331 | private Bounds m_ViewBounds; 332 | 333 | private Vector2 m_Velocity; 334 | public Vector2 velocity { get { return m_Velocity; } set { m_Velocity = value; } } 335 | 336 | private bool m_Dragging; 337 | 338 | private Vector2 m_PrevPosition = Vector2.zero; 339 | private Bounds m_PrevContentBounds; 340 | private Bounds m_PrevViewBounds; 341 | 342 | [NonSerialized] 343 | private bool m_HasRebuiltLayout = false; 344 | 345 | private bool m_HSliderExpand; 346 | private bool m_VSliderExpand; 347 | private float m_HSliderHeight; 348 | private float m_VSliderWidth; 349 | 350 | [System.NonSerialized] 351 | private RectTransform m_Rect; 352 | 353 | private RectTransform rectTransform 354 | { 355 | get 356 | { 357 | if (m_Rect == null) 358 | m_Rect = GetComponent(); 359 | return m_Rect; 360 | } 361 | } 362 | 363 | private RectTransform m_HorizontalScrollbarRect; 364 | private RectTransform m_VerticalScrollbarRect; 365 | 366 | private DrivenRectTransformTracker m_Tracker; 367 | 368 | protected LoopScrollRect() 369 | { 370 | flexibleWidth = -1; 371 | } 372 | 373 | //==========LoopScrollRect========== 374 | 375 | public void ClearCells() 376 | { 377 | if (Application.isPlaying) 378 | { 379 | itemTypeStart = 0; 380 | itemTypeEnd = 0; 381 | m_totalCount = 0; 382 | objectsToFill = null; 383 | for (int i = content.childCount - 1; i >= 0; i--) 384 | { 385 | m_prefabSource.ReturnObject(content.GetChild(i)); 386 | } 387 | } 388 | } 389 | 390 | public void SrollToCell(int index, float speed) 391 | { 392 | if (m_totalCount >= 0 && (index < 0 || index >= m_totalCount)) 393 | { 394 | Debug.LogWarningFormat("invalid index {0}", index); 395 | return; 396 | } 397 | if (speed <= 0) 398 | { 399 | Debug.LogWarningFormat("invalid speed {0}", speed); 400 | return; 401 | } 402 | StopAllCoroutines(); 403 | StartCoroutine(ScrollToCellCoroutine(index, speed)); 404 | } 405 | 406 | private IEnumerator ScrollToCellCoroutine(int index, float speed) 407 | { 408 | bool needMoving = true; 409 | while (needMoving) 410 | { 411 | yield return null; 412 | if (!m_Dragging) 413 | { 414 | float move = 0; 415 | if (index < itemTypeStart) 416 | { 417 | move = -Time.deltaTime * speed; 418 | } 419 | else if (index >= itemTypeEnd) 420 | { 421 | move = Time.deltaTime * speed; 422 | } 423 | else 424 | { 425 | m_ViewBounds = new Bounds(viewRect.rect.center, viewRect.rect.size); 426 | var m_ItemBounds = GetBounds4Item(index); 427 | var offset = 0.0f; 428 | if (directionSign == -1) 429 | offset = reverseDirection ? (m_ViewBounds.min.y - m_ItemBounds.min.y) : (m_ViewBounds.max.y - m_ItemBounds.max.y); 430 | else if (directionSign == 1) 431 | offset = reverseDirection ? (m_ItemBounds.max.x - m_ViewBounds.max.x) : (m_ItemBounds.min.x - m_ViewBounds.min.x); 432 | // check if we cannot move on 433 | if (m_totalCount >= 0) 434 | { 435 | if (offset > 0 && itemTypeEnd == m_totalCount && !reverseDirection) 436 | { 437 | m_ItemBounds = GetBounds4Item(m_totalCount - 1); 438 | // reach bottom 439 | if ((directionSign == -1 && m_ItemBounds.min.y > m_ViewBounds.min.y) || 440 | (directionSign == 1 && m_ItemBounds.max.x < m_ViewBounds.max.x)) 441 | { 442 | needMoving = false; 443 | break; 444 | } 445 | } 446 | else if (offset < 0 && itemTypeStart == 0 && reverseDirection) 447 | { 448 | m_ItemBounds = GetBounds4Item(0); 449 | if ((directionSign == -1 && m_ItemBounds.max.y < m_ViewBounds.max.y) || 450 | (directionSign == 1 && m_ItemBounds.min.x > m_ViewBounds.min.x)) 451 | { 452 | needMoving = false; 453 | break; 454 | } 455 | } 456 | } 457 | 458 | float maxMove = Time.deltaTime * speed; 459 | if (Mathf.Abs(offset) < maxMove) 460 | { 461 | needMoving = false; 462 | move = offset; 463 | } 464 | else 465 | move = Mathf.Sign(offset) * maxMove; 466 | } 467 | if (move != 0) 468 | { 469 | Vector2 offset = GetVector(move); 470 | content.anchoredPosition += offset; 471 | m_PrevPosition += offset; 472 | m_ContentStartPosition += offset; 473 | } 474 | } 475 | } 476 | StopMovement(); 477 | UpdatePrevData(); 478 | } 479 | 480 | public void RefreshCells() 481 | { 482 | if (Application.isPlaying && this.isActiveAndEnabled) 483 | { 484 | itemTypeEnd = itemTypeStart; 485 | // recycle items if we can 486 | for (int i = 0; i < content.childCount; i++) 487 | { 488 | if (itemTypeEnd < m_totalCount) 489 | { 490 | var _tf = content.GetChild(i); 491 | 492 | if (m_provideDataCallBack != null) 493 | { 494 | m_provideDataCallBack(_tf, itemTypeEnd); 495 | } 496 | else 497 | { 498 | m_dataSource.ProvideData(_tf, itemTypeEnd); 499 | } 500 | 501 | // 绑定点击事件 502 | BindClickCallBack(_tf, itemTypeEnd); 503 | 504 | itemTypeEnd++; 505 | } 506 | else 507 | { 508 | m_prefabSource.ReturnObject(content.GetChild(i)); 509 | i--; 510 | } 511 | } 512 | } 513 | } 514 | 515 | public void RefillCellsFromEnd(int _offset = 0, bool _fixMod = true) 516 | { 517 | if (!Application.isPlaying || m_prefabSource == null) 518 | return; 519 | 520 | StopAllAnimation(); 521 | 522 | StopMovement(); 523 | 524 | if (_fixMod) 525 | { 526 | int _mod = _offset % contentConstraintCount; 527 | 528 | if (_mod != 0) 529 | { 530 | _offset = _offset - _mod; 531 | } 532 | } 533 | 534 | itemTypeEnd = reverseDirection ? _offset : m_totalCount - _offset; 535 | itemTypeStart = itemTypeEnd; 536 | 537 | if (m_totalCount >= 0 && itemTypeStart % contentConstraintCount != 0) 538 | { 539 | Debug.LogWarning("Grid will become strange since we can't fill items in the last line"); 540 | } 541 | 542 | for (int i = m_Content.childCount - 1; i >= 0; i--) 543 | { 544 | m_prefabSource.ReturnObject(m_Content.GetChild(i)); 545 | } 546 | 547 | float sizeToFill = 0, sizeFilled = 0; 548 | if (directionSign == -1) 549 | sizeToFill = viewRect.rect.size.y; 550 | else 551 | sizeToFill = viewRect.rect.size.x; 552 | 553 | while (sizeToFill > sizeFilled) 554 | { 555 | float size = reverseDirection ? NewItemAtEnd() : NewItemAtStart(); 556 | if (size <= 0) break; 557 | sizeFilled += size; 558 | } 559 | 560 | Vector2 pos = m_Content.anchoredPosition; 561 | float dist = Mathf.Max(0, sizeFilled - sizeToFill); 562 | if (reverseDirection) 563 | dist = -dist; 564 | if (directionSign == -1) 565 | pos.y = dist; 566 | else if (directionSign == 1) 567 | pos.x = -dist; 568 | m_Content.anchoredPosition = pos; 569 | } 570 | 571 | public void RefillCells(bool _playAnim = true, int _offset = 0, bool _fillViewRect = false, bool _fixMod = true) 572 | { 573 | if (!Application.isPlaying || m_prefabSource == null) 574 | return; 575 | 576 | StopAllAnimation(); 577 | 578 | Debug.LogError(m_Dragging); 579 | 580 | StopMovement(); 581 | 582 | if ((m_totalCount - _offset) < (viewRect.rect.height / TemplateSize * contentConstraintCount)) 583 | { 584 | if (m_totalCount % contentConstraintCount == 0) 585 | { 586 | RefillCellsFromEnd(0); 587 | 588 | return; 589 | } 590 | else 591 | { 592 | _offset = _offset - (int)(((viewRect.rect.height / TemplateSize) - 1) * contentConstraintCount); 593 | } 594 | } 595 | 596 | if (_fixMod) 597 | { 598 | int _mod = _offset % contentConstraintCount; 599 | 600 | if (_mod != 0) 601 | { 602 | _offset = _offset - _mod; 603 | } 604 | } 605 | 606 | itemTypeStart = reverseDirection ? m_totalCount - _offset : _offset; 607 | itemTypeEnd = itemTypeStart; 608 | 609 | if (m_totalCount >= 0 && itemTypeStart % contentConstraintCount != 0) 610 | { 611 | Debug.LogWarning("Grid will become strange since we can't fill items in the first line"); 612 | } 613 | 614 | // Don't `Canvas.ForceUpdateCanvases();` here, or it will new/delete cells to change itemTypeStart/End 615 | for (int i = m_Content.childCount - 1; i >= 0; i--) 616 | { 617 | m_prefabSource.ReturnObject(m_Content.GetChild(i)); 618 | } 619 | 620 | float sizeToFill = 0, sizeFilled = 0; 621 | // m_ViewBounds may be not ready when RefillCells on Start 622 | if (directionSign == -1) 623 | sizeToFill = viewRect.rect.size.y; 624 | else 625 | sizeToFill = viewRect.rect.size.x; 626 | 627 | float itemSize = 0; 628 | 629 | while (sizeToFill > sizeFilled) 630 | { 631 | float size = reverseDirection ? NewItemAtStart(_playAnim) : NewItemAtEnd(_playAnim); 632 | if (size <= 0) break; 633 | else itemSize = size; 634 | sizeFilled += size; 635 | } 636 | 637 | if (_fillViewRect && itemSize > 0 && sizeFilled < sizeToFill) 638 | { 639 | int itemsToAddCount = (int)((sizeToFill - sizeFilled) / itemSize); //calculate how many items can be added above the offset, so it still is visible in the view 640 | int newOffset = _offset - itemsToAddCount; 641 | if (newOffset < 0) newOffset = 0; 642 | if (newOffset != _offset) RefillCells(false, newOffset); //refill again, with the new offset value, and now with fillViewRect disabled. 643 | } 644 | 645 | Vector2 pos = m_Content.anchoredPosition; 646 | if (directionSign == -1) 647 | pos.y = 0; 648 | else if (directionSign == 1) 649 | pos.x = 0; 650 | m_Content.anchoredPosition = pos; 651 | 652 | if (_playAnim) 653 | { 654 | PlayEnterAnimation(); 655 | } 656 | } 657 | 658 | protected float NewItemAtStart(bool _playAnim = false) 659 | { 660 | if (m_totalCount >= 0 && itemTypeStart - contentConstraintCount < 0) 661 | { 662 | return 0; 663 | } 664 | float size = 0; 665 | for (int i = 0; i < contentConstraintCount; i++) 666 | { 667 | itemTypeStart--; 668 | RectTransform newItem = InstantiateNextItem(itemTypeStart, _playAnim); 669 | newItem.SetAsFirstSibling(); 670 | size = Mathf.Max(GetSize(newItem), size); 671 | } 672 | threshold = Mathf.Max(threshold, size * 1.5f); 673 | 674 | if (!reverseDirection) 675 | { 676 | Vector2 offset = GetVector(size); 677 | content.anchoredPosition += offset; 678 | m_PrevPosition += offset; 679 | m_ContentStartPosition += offset; 680 | } 681 | 682 | return size; 683 | } 684 | 685 | protected float DeleteItemAtStart() 686 | { 687 | // special case: when moving or dragging, we cannot simply delete start when we've reached the end 688 | if (((m_Dragging || m_Velocity != Vector2.zero) && m_totalCount >= 0 && itemTypeEnd >= m_totalCount - 1) 689 | || content.childCount == 0) 690 | { 691 | return 0; 692 | } 693 | 694 | float size = 0; 695 | for (int i = 0; i < contentConstraintCount; i++) 696 | { 697 | RectTransform oldItem = content.GetChild(0) as RectTransform; 698 | size = Mathf.Max(GetSize(oldItem), size); 699 | m_prefabSource.ReturnObject(oldItem); 700 | 701 | itemTypeStart++; 702 | 703 | if (content.childCount == 0) 704 | { 705 | break; 706 | } 707 | } 708 | 709 | if (!reverseDirection) 710 | { 711 | Vector2 offset = GetVector(size); 712 | content.anchoredPosition -= offset; 713 | m_PrevPosition -= offset; 714 | m_ContentStartPosition -= offset; 715 | } 716 | return size; 717 | } 718 | 719 | protected float NewItemAtEnd(bool _playAnim = false) 720 | { 721 | if (m_totalCount >= 0 && itemTypeEnd >= m_totalCount) 722 | { 723 | return 0; 724 | } 725 | float size = 0; 726 | // issue 4: fill lines to end first 727 | int count = contentConstraintCount - (content.childCount % contentConstraintCount); 728 | for (int i = 0; i < count; i++) 729 | { 730 | RectTransform newItem = InstantiateNextItem(itemTypeEnd, _playAnim); 731 | size = Mathf.Max(GetSize(newItem), size); 732 | itemTypeEnd++; 733 | if (m_totalCount >= 0 && itemTypeEnd >= m_totalCount) 734 | { 735 | break; 736 | } 737 | } 738 | threshold = Mathf.Max(threshold, size * 1.5f); 739 | 740 | if (reverseDirection) 741 | { 742 | Vector2 offset = GetVector(size); 743 | content.anchoredPosition -= offset; 744 | m_PrevPosition -= offset; 745 | m_ContentStartPosition -= offset; 746 | } 747 | 748 | return size; 749 | } 750 | 751 | protected float DeleteItemAtEnd() 752 | { 753 | if (((m_Dragging || m_Velocity != Vector2.zero) && m_totalCount >= 0 && itemTypeStart < contentConstraintCount) 754 | || content.childCount == 0) 755 | { 756 | return 0; 757 | } 758 | 759 | float size = 0; 760 | for (int i = 0; i < contentConstraintCount; i++) 761 | { 762 | RectTransform oldItem = content.GetChild(content.childCount - 1) as RectTransform; 763 | size = Mathf.Max(GetSize(oldItem), size); 764 | m_prefabSource.ReturnObject(oldItem); 765 | 766 | itemTypeEnd--; 767 | if (itemTypeEnd % contentConstraintCount == 0 || content.childCount == 0) 768 | { 769 | break; //just delete the whole row 770 | } 771 | } 772 | 773 | if (reverseDirection) 774 | { 775 | Vector2 offset = GetVector(size); 776 | content.anchoredPosition += offset; 777 | m_PrevPosition += offset; 778 | m_ContentStartPosition += offset; 779 | } 780 | return size; 781 | } 782 | 783 | private RectTransform InstantiateNextItem(int _itemIdx, bool _playAnim = false) 784 | { 785 | GameObject _prefab = m_prefabSource.GetObject(); 786 | 787 | if (_playAnim) 788 | { 789 | _prefab.GetOrAddComponent().alpha = 0f; 790 | } 791 | 792 | RectTransform nextItem = _prefab.transform as RectTransform; 793 | nextItem.transform.SetParent(content, false); 794 | nextItem.gameObject.SetActive(true); 795 | 796 | if (m_provideDataCallBack != null) 797 | { 798 | m_provideDataCallBack(nextItem, _itemIdx); 799 | } 800 | else 801 | { 802 | m_dataSource.ProvideData(nextItem, _itemIdx); 803 | } 804 | 805 | // 绑定点击事件 806 | BindClickCallBack(nextItem, _itemIdx); 807 | 808 | return nextItem; 809 | } 810 | 811 | //==========LoopScrollRect========== 812 | 813 | public virtual void Rebuild(CanvasUpdate executing) 814 | { 815 | if (executing == CanvasUpdate.Prelayout) 816 | { 817 | UpdateCachedData(); 818 | } 819 | 820 | if (executing == CanvasUpdate.PostLayout) 821 | { 822 | UpdateBounds(); 823 | UpdateScrollbars(Vector2.zero); 824 | UpdatePrevData(); 825 | 826 | m_HasRebuiltLayout = true; 827 | } 828 | } 829 | 830 | public virtual void LayoutComplete() 831 | { } 832 | 833 | public virtual void GraphicUpdateComplete() 834 | { } 835 | 836 | private void UpdateCachedData() 837 | { 838 | Transform transform = this.transform; 839 | m_HorizontalScrollbarRect = m_HorizontalScrollbar == null ? null : m_HorizontalScrollbar.transform as RectTransform; 840 | m_VerticalScrollbarRect = m_VerticalScrollbar == null ? null : m_VerticalScrollbar.transform as RectTransform; 841 | 842 | // These are true if either the elements are children, or they don't exist at all. 843 | bool viewIsChild = (viewRect.parent == transform); 844 | bool hScrollbarIsChild = (!m_HorizontalScrollbarRect || m_HorizontalScrollbarRect.parent == transform); 845 | bool vScrollbarIsChild = (!m_VerticalScrollbarRect || m_VerticalScrollbarRect.parent == transform); 846 | bool allAreChildren = (viewIsChild && hScrollbarIsChild && vScrollbarIsChild); 847 | 848 | m_HSliderExpand = allAreChildren && m_HorizontalScrollbarRect && horizontalScrollbarVisibility == ScrollbarVisibility.AutoHideAndExpandViewport; 849 | m_VSliderExpand = allAreChildren && m_VerticalScrollbarRect && verticalScrollbarVisibility == ScrollbarVisibility.AutoHideAndExpandViewport; 850 | m_HSliderHeight = (m_HorizontalScrollbarRect == null ? 0 : m_HorizontalScrollbarRect.rect.height); 851 | m_VSliderWidth = (m_VerticalScrollbarRect == null ? 0 : m_VerticalScrollbarRect.rect.width); 852 | } 853 | 854 | /// 855 | /// 绑定点击事件 856 | /// 857 | /// 858 | /// 859 | private void BindClickCallBack(Transform _tf, int _index) 860 | { 861 | // 绑定点击事件 862 | if (m_clickItemCallBack != null) 863 | { 864 | // 获取IDragHandler防止出现拖拽不动 865 | if (_tf.GetComponent() != null) 866 | { 867 | //if (_component is LoopScrollRectItem) 868 | //{ 869 | // // 不处理 870 | //} 871 | { 872 | Debug.LogError("Item already has IDragHandler!"); 873 | 874 | return; 875 | } 876 | } 877 | // 添加 878 | var _component = _tf.gameObject.GetComponent(); 879 | 880 | if (_component == null) 881 | { 882 | _component = _tf.gameObject.AddComponent(); 883 | } 884 | 885 | _component.Index = _index; 886 | 887 | if (_component.m_clickItemCallBack == null) 888 | { 889 | _component.m_clickItemCallBack = m_clickItemCallBack; 890 | } 891 | } 892 | } 893 | 894 | protected override void Awake() 895 | { 896 | m_prefabSource.PoolGameObject = this.transform.Find(LoopScrollPrefabSource.__POOL_NAME); 897 | } 898 | 899 | protected override void OnEnable() 900 | { 901 | base.OnEnable(); 902 | 903 | if (m_HorizontalScrollbar) 904 | m_HorizontalScrollbar.onValueChanged.AddListener(SetHorizontalNormalizedPosition); 905 | if (m_VerticalScrollbar) 906 | m_VerticalScrollbar.onValueChanged.AddListener(SetVerticalNormalizedPosition); 907 | 908 | CanvasUpdateRegistry.RegisterCanvasElementForLayoutRebuild(this); 909 | } 910 | 911 | protected override void OnDisable() 912 | { 913 | CanvasUpdateRegistry.UnRegisterCanvasElementForRebuild(this); 914 | 915 | if (m_HorizontalScrollbar) 916 | m_HorizontalScrollbar.onValueChanged.RemoveListener(SetHorizontalNormalizedPosition); 917 | if (m_VerticalScrollbar) 918 | m_VerticalScrollbar.onValueChanged.RemoveListener(SetVerticalNormalizedPosition); 919 | 920 | m_HasRebuiltLayout = false; 921 | m_Tracker.Clear(); 922 | m_Velocity = Vector2.zero; 923 | LayoutRebuilder.MarkLayoutForRebuild(rectTransform); 924 | base.OnDisable(); 925 | } 926 | 927 | protected override void OnDestroy() 928 | { 929 | m_prefabSource.DeInitPool(); 930 | 931 | base.OnDestroy(); 932 | } 933 | 934 | public override bool IsActive() 935 | { 936 | return base.IsActive() && m_Content != null; 937 | } 938 | 939 | private void EnsureLayoutHasRebuilt() 940 | { 941 | if (!m_HasRebuiltLayout && !CanvasUpdateRegistry.IsRebuildingLayout()) 942 | Canvas.ForceUpdateCanvases(); 943 | } 944 | 945 | public virtual void StopMovement() 946 | { 947 | if (m_Velocity != Vector2.one) 948 | { 949 | m_stopMovementFlag = true; 950 | } 951 | 952 | m_Velocity = Vector2.zero; 953 | } 954 | 955 | public virtual void OnScroll(PointerEventData data) 956 | { 957 | if (!IsActive()) 958 | return; 959 | 960 | StopAllAnimation(); 961 | 962 | EnsureLayoutHasRebuilt(); 963 | UpdateBounds(); 964 | 965 | Vector2 delta = data.scrollDelta; 966 | // Down is positive for scroll events, while in UI system up is positive. 967 | delta.y *= -1; 968 | if (vertical && !horizontal) 969 | { 970 | if (Mathf.Abs(delta.x) > Mathf.Abs(delta.y)) 971 | delta.y = delta.x; 972 | delta.x = 0; 973 | } 974 | if (horizontal && !vertical) 975 | { 976 | if (Mathf.Abs(delta.y) > Mathf.Abs(delta.x)) 977 | delta.x = delta.y; 978 | delta.y = 0; 979 | } 980 | 981 | Vector2 position = m_Content.anchoredPosition; 982 | position += delta * m_ScrollSensitivity; 983 | if (m_MovementType == MovementType.Clamped) 984 | position += CalculateOffset(position - m_Content.anchoredPosition); 985 | 986 | SetContentAnchoredPosition(position); 987 | UpdateBounds(); 988 | } 989 | 990 | public virtual void OnInitializePotentialDrag(PointerEventData eventData) 991 | { 992 | if (eventData.button != PointerEventData.InputButton.Left) 993 | return; 994 | 995 | m_Velocity = Vector2.zero; 996 | } 997 | 998 | public virtual void OnBeginDrag(PointerEventData eventData) 999 | { 1000 | if (eventData.button != PointerEventData.InputButton.Left) 1001 | return; 1002 | 1003 | if (!IsActive()) 1004 | return; 1005 | 1006 | StopAllAnimation(); 1007 | 1008 | UpdateBounds(); 1009 | 1010 | m_PointerStartLocalCursor = Vector2.zero; 1011 | RectTransformUtility.ScreenPointToLocalPointInRectangle(viewRect, eventData.position, eventData.pressEventCamera, out m_PointerStartLocalCursor); 1012 | m_ContentStartPosition = m_Content.anchoredPosition; 1013 | m_Dragging = true; 1014 | } 1015 | 1016 | public virtual void OnEndDrag(PointerEventData eventData) 1017 | { 1018 | if (eventData.button != PointerEventData.InputButton.Left) 1019 | return; 1020 | 1021 | m_Dragging = false; 1022 | } 1023 | 1024 | public virtual void OnDrag(PointerEventData eventData) 1025 | { 1026 | if (eventData.button != PointerEventData.InputButton.Left) 1027 | return; 1028 | 1029 | if (!IsActive()) 1030 | return; 1031 | 1032 | Vector2 localCursor; 1033 | if (!RectTransformUtility.ScreenPointToLocalPointInRectangle(viewRect, eventData.position, eventData.pressEventCamera, out localCursor)) 1034 | return; 1035 | 1036 | UpdateBounds(); 1037 | 1038 | var pointerDelta = localCursor - m_PointerStartLocalCursor; 1039 | Vector2 position = m_ContentStartPosition + pointerDelta; 1040 | 1041 | // Offset to get content into place in the view. 1042 | Vector2 offset = CalculateOffset(position - m_Content.anchoredPosition); 1043 | position += offset; 1044 | if (m_MovementType == MovementType.Elastic) 1045 | { 1046 | //==========LoopScrollRect========== 1047 | if (offset.x != 0) 1048 | position.x = position.x - RubberDelta(offset.x, m_ViewBounds.size.x) * rubberScale; 1049 | if (offset.y != 0) 1050 | position.y = position.y - RubberDelta(offset.y, m_ViewBounds.size.y) * rubberScale; 1051 | //==========LoopScrollRect========== 1052 | } 1053 | 1054 | SetContentAnchoredPosition(position); 1055 | } 1056 | 1057 | protected virtual void SetContentAnchoredPosition(Vector2 position) 1058 | { 1059 | if (!m_Horizontal) 1060 | position.x = m_Content.anchoredPosition.x; 1061 | if (!m_Vertical) 1062 | position.y = m_Content.anchoredPosition.y; 1063 | 1064 | if (position != m_Content.anchoredPosition) 1065 | { 1066 | Debug.Log(position); 1067 | 1068 | m_Content.anchoredPosition = position; 1069 | UpdateBounds(true); 1070 | } 1071 | } 1072 | 1073 | protected virtual void LateUpdate() 1074 | { 1075 | if (!m_Content) 1076 | return; 1077 | 1078 | if (m_stopMovementFlag) 1079 | { 1080 | m_stopMovementFlag = false; 1081 | 1082 | return; 1083 | } 1084 | 1085 | EnsureLayoutHasRebuilt(); 1086 | UpdateScrollbarVisibility(); 1087 | UpdateBounds(); 1088 | float deltaTime = Time.unscaledDeltaTime; 1089 | Vector2 offset = CalculateOffset(Vector2.zero); 1090 | if (!m_Dragging && (offset != Vector2.zero || m_Velocity != Vector2.zero)) 1091 | { 1092 | Vector2 position = m_Content.anchoredPosition; 1093 | for (int axis = 0; axis < 2; axis++) 1094 | { 1095 | // Apply spring physics if movement is elastic and content has an offset from the view. 1096 | if (m_MovementType == MovementType.Elastic && offset[axis] != 0) 1097 | { 1098 | float speed = m_Velocity[axis]; 1099 | position[axis] = Mathf.SmoothDamp(m_Content.anchoredPosition[axis], m_Content.anchoredPosition[axis] + offset[axis], ref speed, m_Elasticity, Mathf.Infinity, deltaTime); 1100 | m_Velocity[axis] = speed; 1101 | } 1102 | // Else move content according to velocity with deceleration applied. 1103 | else if (m_Inertia) 1104 | { 1105 | m_Velocity[axis] *= Mathf.Pow(m_DecelerationRate, deltaTime); 1106 | if (Mathf.Abs(m_Velocity[axis]) < 1) 1107 | m_Velocity[axis] = 0; 1108 | position[axis] += m_Velocity[axis] * deltaTime; 1109 | } 1110 | // If we have neither elaticity or friction, there shouldn't be any velocity. 1111 | else 1112 | { 1113 | m_Velocity[axis] = 0; 1114 | } 1115 | } 1116 | 1117 | if (m_Velocity != Vector2.zero) 1118 | { 1119 | if (m_MovementType == MovementType.Clamped) 1120 | { 1121 | offset = CalculateOffset(position - m_Content.anchoredPosition); 1122 | position += offset; 1123 | } 1124 | 1125 | SetContentAnchoredPosition(position); 1126 | } 1127 | } 1128 | 1129 | if (m_Dragging && m_Inertia) 1130 | { 1131 | Vector3 newVelocity = (m_Content.anchoredPosition - m_PrevPosition) / deltaTime; 1132 | m_Velocity = Vector3.Lerp(m_Velocity, newVelocity, deltaTime * 10); 1133 | } 1134 | 1135 | if (m_ViewBounds != m_PrevViewBounds || m_ContentBounds != m_PrevContentBounds || m_Content.anchoredPosition != m_PrevPosition) 1136 | { 1137 | UpdateScrollbars(offset); 1138 | m_OnValueChanged.Invoke(normalizedPosition); 1139 | UpdatePrevData(); 1140 | } 1141 | } 1142 | 1143 | private void UpdatePrevData() 1144 | { 1145 | if (m_Content == null) 1146 | m_PrevPosition = Vector2.zero; 1147 | else 1148 | m_PrevPosition = m_Content.anchoredPosition; 1149 | m_PrevViewBounds = m_ViewBounds; 1150 | m_PrevContentBounds = m_ContentBounds; 1151 | } 1152 | 1153 | private void UpdateScrollbars(Vector2 offset) 1154 | { 1155 | if (m_HorizontalScrollbar) 1156 | { 1157 | //==========LoopScrollRect========== 1158 | if (m_ContentBounds.size.x > 0 && m_totalCount > 0) 1159 | { 1160 | m_HorizontalScrollbar.size = Mathf.Clamp01((m_ViewBounds.size.x - Mathf.Abs(offset.x)) / m_ContentBounds.size.x * CurrentLines / TotalLines); 1161 | } 1162 | //==========LoopScrollRect========== 1163 | else 1164 | m_HorizontalScrollbar.size = 1; 1165 | 1166 | m_HorizontalScrollbar.value = horizontalNormalizedPosition; 1167 | } 1168 | 1169 | if (m_VerticalScrollbar) 1170 | { 1171 | //==========LoopScrollRect========== 1172 | if (m_ContentBounds.size.y > 0 && m_totalCount > 0) 1173 | { 1174 | m_VerticalScrollbar.size = Mathf.Clamp01((m_ViewBounds.size.y - Mathf.Abs(offset.y)) / m_ContentBounds.size.y * CurrentLines / TotalLines); 1175 | } 1176 | //==========LoopScrollRect========== 1177 | else 1178 | m_VerticalScrollbar.size = 1; 1179 | 1180 | m_VerticalScrollbar.value = verticalNormalizedPosition; 1181 | } 1182 | } 1183 | 1184 | public Vector2 normalizedPosition 1185 | { 1186 | get 1187 | { 1188 | return new Vector2(horizontalNormalizedPosition, verticalNormalizedPosition); 1189 | } 1190 | set 1191 | { 1192 | SetNormalizedPosition(value.x, 0); 1193 | SetNormalizedPosition(value.y, 1); 1194 | } 1195 | } 1196 | 1197 | public float horizontalNormalizedPosition 1198 | { 1199 | get 1200 | { 1201 | UpdateBounds(); 1202 | //==========LoopScrollRect========== 1203 | if (m_totalCount > 0 && itemTypeEnd > itemTypeStart) 1204 | { 1205 | //TODO: consider contentSpacing 1206 | float elementSize = m_ContentBounds.size.x / CurrentLines; 1207 | float totalSize = elementSize * TotalLines; 1208 | float offset = m_ContentBounds.min.x - elementSize * StartLine; 1209 | 1210 | if (totalSize <= m_ViewBounds.size.x) 1211 | return (m_ViewBounds.min.x > offset) ? 1 : 0; 1212 | return (m_ViewBounds.min.x - offset) / (totalSize - m_ViewBounds.size.x); 1213 | } 1214 | else 1215 | return 0.5f; 1216 | //==========LoopScrollRect========== 1217 | } 1218 | set 1219 | { 1220 | SetNormalizedPosition(value, 0); 1221 | } 1222 | } 1223 | 1224 | public float verticalNormalizedPosition 1225 | { 1226 | get 1227 | { 1228 | UpdateBounds(); 1229 | //==========LoopScrollRect========== 1230 | if (m_totalCount > 0 && itemTypeEnd > itemTypeStart) 1231 | { 1232 | //TODO: consider contentSpacinge 1233 | float elementSize = m_ContentBounds.size.y / CurrentLines; 1234 | float totalSize = elementSize * TotalLines; 1235 | float offset = m_ContentBounds.max.y + elementSize * StartLine; 1236 | 1237 | if (totalSize <= m_ViewBounds.size.y) 1238 | return (offset > m_ViewBounds.max.y) ? 1 : 0; 1239 | return (offset - m_ViewBounds.max.y) / (totalSize - m_ViewBounds.size.y); 1240 | } 1241 | else 1242 | return 0.5f; 1243 | //==========LoopScrollRect========== 1244 | } 1245 | set 1246 | { 1247 | SetNormalizedPosition(value, 1); 1248 | } 1249 | } 1250 | 1251 | private void SetHorizontalNormalizedPosition(float value) 1252 | { 1253 | SetNormalizedPosition(value, 0); 1254 | } 1255 | 1256 | private void SetVerticalNormalizedPosition(float value) 1257 | { 1258 | SetNormalizedPosition(value, 1); 1259 | } 1260 | 1261 | private void SetNormalizedPosition(float value, int axis) 1262 | { 1263 | //==========LoopScrollRect========== 1264 | if (m_totalCount <= 0 || itemTypeEnd <= itemTypeStart) 1265 | return; 1266 | //==========LoopScrollRect========== 1267 | 1268 | EnsureLayoutHasRebuilt(); 1269 | UpdateBounds(); 1270 | 1271 | //==========LoopScrollRect========== 1272 | Vector3 localPosition = m_Content.localPosition; 1273 | float newLocalPosition = localPosition[axis]; 1274 | if (axis == 0) 1275 | { 1276 | float elementSize = m_ContentBounds.size.x / CurrentLines; 1277 | float totalSize = elementSize * TotalLines; 1278 | float offset = m_ContentBounds.min.x - elementSize * StartLine; 1279 | 1280 | newLocalPosition += m_ViewBounds.min.x - value * (totalSize - m_ViewBounds.size[axis]) - offset; 1281 | } 1282 | else if (axis == 1) 1283 | { 1284 | float elementSize = m_ContentBounds.size.y / CurrentLines; 1285 | float totalSize = elementSize * TotalLines; 1286 | float offset = m_ContentBounds.max.y + elementSize * StartLine; 1287 | 1288 | newLocalPosition -= offset - value * (totalSize - m_ViewBounds.size.y) - m_ViewBounds.max.y; 1289 | } 1290 | //==========LoopScrollRect========== 1291 | 1292 | if (Mathf.Abs(localPosition[axis] - newLocalPosition) > 0.01f) 1293 | { 1294 | localPosition[axis] = newLocalPosition; 1295 | m_Content.localPosition = localPosition; 1296 | m_Velocity[axis] = 0; 1297 | UpdateBounds(true); 1298 | } 1299 | } 1300 | 1301 | private static float RubberDelta(float overStretching, float viewSize) 1302 | { 1303 | return (1 - (1 / ((Mathf.Abs(overStretching) * 0.55f / viewSize) + 1))) * viewSize * Mathf.Sign(overStretching); 1304 | } 1305 | 1306 | protected override void OnRectTransformDimensionsChange() 1307 | { 1308 | SetDirty(); 1309 | } 1310 | 1311 | private bool hScrollingNeeded 1312 | { 1313 | get 1314 | { 1315 | if (Application.isPlaying) 1316 | return m_ContentBounds.size.x > m_ViewBounds.size.x + 0.01f; 1317 | return true; 1318 | } 1319 | } 1320 | 1321 | private bool vScrollingNeeded 1322 | { 1323 | get 1324 | { 1325 | if (Application.isPlaying) 1326 | return m_ContentBounds.size.y > m_ViewBounds.size.y + 0.01f; 1327 | return true; 1328 | } 1329 | } 1330 | 1331 | public virtual void CalculateLayoutInputHorizontal() 1332 | { 1333 | } 1334 | 1335 | public virtual void CalculateLayoutInputVertical() 1336 | { 1337 | } 1338 | 1339 | public virtual float minWidth { get { return -1; } } 1340 | public virtual float preferredWidth { get { return -1; } } 1341 | public virtual float flexibleWidth { get; private set; } 1342 | 1343 | public virtual float minHeight { get { return -1; } } 1344 | public virtual float preferredHeight { get { return -1; } } 1345 | public virtual float flexibleHeight { get { return -1; } } 1346 | 1347 | public virtual int layoutPriority { get { return -1; } } 1348 | 1349 | public virtual void SetLayoutHorizontal() 1350 | { 1351 | m_Tracker.Clear(); 1352 | 1353 | if (m_HSliderExpand || m_VSliderExpand) 1354 | { 1355 | m_Tracker.Add(this, viewRect, 1356 | DrivenTransformProperties.Anchors | 1357 | DrivenTransformProperties.SizeDelta | 1358 | DrivenTransformProperties.AnchoredPosition); 1359 | 1360 | // Make view full size to see if content fits. 1361 | viewRect.anchorMin = Vector2.zero; 1362 | viewRect.anchorMax = Vector2.one; 1363 | viewRect.sizeDelta = Vector2.zero; 1364 | viewRect.anchoredPosition = Vector2.zero; 1365 | 1366 | // Recalculate content layout with this size to see if it fits when there are no scrollbars. 1367 | LayoutRebuilder.ForceRebuildLayoutImmediate(content); 1368 | m_ViewBounds = new Bounds(viewRect.rect.center, viewRect.rect.size); 1369 | m_ContentBounds = GetBounds(); 1370 | } 1371 | 1372 | // If it doesn't fit vertically, enable vertical scrollbar and shrink view horizontally to make room for it. 1373 | if (m_VSliderExpand && vScrollingNeeded) 1374 | { 1375 | viewRect.sizeDelta = new Vector2(-(m_VSliderWidth + m_VerticalScrollbarSpacing), viewRect.sizeDelta.y); 1376 | 1377 | // Recalculate content layout with this size to see if it fits vertically 1378 | // when there is a vertical scrollbar (which may reflowed the content to make it taller). 1379 | LayoutRebuilder.ForceRebuildLayoutImmediate(content); 1380 | m_ViewBounds = new Bounds(viewRect.rect.center, viewRect.rect.size); 1381 | m_ContentBounds = GetBounds(); 1382 | } 1383 | 1384 | // If it doesn't fit horizontally, enable horizontal scrollbar and shrink view vertically to make room for it. 1385 | if (m_HSliderExpand && hScrollingNeeded) 1386 | { 1387 | viewRect.sizeDelta = new Vector2(viewRect.sizeDelta.x, -(m_HSliderHeight + m_HorizontalScrollbarSpacing)); 1388 | m_ViewBounds = new Bounds(viewRect.rect.center, viewRect.rect.size); 1389 | m_ContentBounds = GetBounds(); 1390 | } 1391 | 1392 | // If the vertical slider didn't kick in the first time, and the horizontal one did, 1393 | // we need to check again if the vertical slider now needs to kick in. 1394 | // If it doesn't fit vertically, enable vertical scrollbar and shrink view horizontally to make room for it. 1395 | if (m_VSliderExpand && vScrollingNeeded && viewRect.sizeDelta.x == 0 && viewRect.sizeDelta.y < 0) 1396 | { 1397 | viewRect.sizeDelta = new Vector2(-(m_VSliderWidth + m_VerticalScrollbarSpacing), viewRect.sizeDelta.y); 1398 | } 1399 | } 1400 | 1401 | public virtual void SetLayoutVertical() 1402 | { 1403 | UpdateScrollbarLayout(); 1404 | m_ViewBounds = new Bounds(viewRect.rect.center, viewRect.rect.size); 1405 | m_ContentBounds = GetBounds(); 1406 | } 1407 | 1408 | private void UpdateScrollbarVisibility() 1409 | { 1410 | if (m_VerticalScrollbar && m_VerticalScrollbarVisibility != ScrollbarVisibility.Permanent && m_VerticalScrollbar.gameObject.activeSelf != vScrollingNeeded) 1411 | m_VerticalScrollbar.gameObject.SetActive(vScrollingNeeded); 1412 | 1413 | if (m_HorizontalScrollbar && m_HorizontalScrollbarVisibility != ScrollbarVisibility.Permanent && m_HorizontalScrollbar.gameObject.activeSelf != hScrollingNeeded) 1414 | m_HorizontalScrollbar.gameObject.SetActive(hScrollingNeeded); 1415 | } 1416 | 1417 | private void UpdateScrollbarLayout() 1418 | { 1419 | if (m_VSliderExpand && m_HorizontalScrollbar) 1420 | { 1421 | m_Tracker.Add(this, m_HorizontalScrollbarRect, 1422 | DrivenTransformProperties.AnchorMinX | 1423 | DrivenTransformProperties.AnchorMaxX | 1424 | DrivenTransformProperties.SizeDeltaX | 1425 | DrivenTransformProperties.AnchoredPositionX); 1426 | m_HorizontalScrollbarRect.anchorMin = new Vector2(0, m_HorizontalScrollbarRect.anchorMin.y); 1427 | m_HorizontalScrollbarRect.anchorMax = new Vector2(1, m_HorizontalScrollbarRect.anchorMax.y); 1428 | m_HorizontalScrollbarRect.anchoredPosition = new Vector2(0, m_HorizontalScrollbarRect.anchoredPosition.y); 1429 | if (vScrollingNeeded) 1430 | m_HorizontalScrollbarRect.sizeDelta = new Vector2(-(m_VSliderWidth + m_VerticalScrollbarSpacing), m_HorizontalScrollbarRect.sizeDelta.y); 1431 | else 1432 | m_HorizontalScrollbarRect.sizeDelta = new Vector2(0, m_HorizontalScrollbarRect.sizeDelta.y); 1433 | } 1434 | 1435 | if (m_HSliderExpand && m_VerticalScrollbar) 1436 | { 1437 | m_Tracker.Add(this, m_VerticalScrollbarRect, 1438 | DrivenTransformProperties.AnchorMinY | 1439 | DrivenTransformProperties.AnchorMaxY | 1440 | DrivenTransformProperties.SizeDeltaY | 1441 | DrivenTransformProperties.AnchoredPositionY); 1442 | m_VerticalScrollbarRect.anchorMin = new Vector2(m_VerticalScrollbarRect.anchorMin.x, 0); 1443 | m_VerticalScrollbarRect.anchorMax = new Vector2(m_VerticalScrollbarRect.anchorMax.x, 1); 1444 | m_VerticalScrollbarRect.anchoredPosition = new Vector2(m_VerticalScrollbarRect.anchoredPosition.x, 0); 1445 | if (hScrollingNeeded) 1446 | m_VerticalScrollbarRect.sizeDelta = new Vector2(m_VerticalScrollbarRect.sizeDelta.x, -(m_HSliderHeight + m_HorizontalScrollbarSpacing)); 1447 | else 1448 | m_VerticalScrollbarRect.sizeDelta = new Vector2(m_VerticalScrollbarRect.sizeDelta.x, 0); 1449 | } 1450 | } 1451 | 1452 | private void UpdateBounds(bool updateItems = false) 1453 | { 1454 | m_ViewBounds = new Bounds(viewRect.rect.center, viewRect.rect.size); 1455 | m_ContentBounds = GetBounds(); 1456 | 1457 | if (m_Content == null) 1458 | return; 1459 | 1460 | // ============LoopScrollRect============ 1461 | // Don't do this in Rebuild 1462 | if (Application.isPlaying && updateItems && UpdateItems(m_ViewBounds, m_ContentBounds)) 1463 | { 1464 | Canvas.ForceUpdateCanvases(); 1465 | m_ContentBounds = GetBounds(); 1466 | } 1467 | // ============LoopScrollRect============ 1468 | 1469 | // Make sure content bounds are at least as large as view by adding padding if not. 1470 | // One might think at first that if the content is smaller than the view, scrolling should be allowed. 1471 | // However, that's not how scroll views normally work. 1472 | // Scrolling is *only* possible when content is *larger* than view. 1473 | // We use the pivot of the content rect to decide in which directions the content bounds should be expanded. 1474 | // E.g. if pivot is at top, bounds are expanded downwards. 1475 | // This also works nicely when ContentSizeFitter is used on the content. 1476 | Vector3 contentSize = m_ContentBounds.size; 1477 | Vector3 contentPos = m_ContentBounds.center; 1478 | Vector3 excess = m_ViewBounds.size - contentSize; 1479 | if (excess.x > 0) 1480 | { 1481 | contentPos.x -= excess.x * (m_Content.pivot.x - 0.5f); 1482 | contentSize.x = m_ViewBounds.size.x; 1483 | } 1484 | if (excess.y > 0) 1485 | { 1486 | contentPos.y -= excess.y * (m_Content.pivot.y - 0.5f); 1487 | contentSize.y = m_ViewBounds.size.y; 1488 | } 1489 | 1490 | m_ContentBounds.size = contentSize; 1491 | m_ContentBounds.center = contentPos; 1492 | } 1493 | 1494 | private readonly Vector3[] m_Corners = new Vector3[4]; 1495 | 1496 | private Bounds GetBounds() 1497 | { 1498 | if (m_Content == null) 1499 | return new Bounds(); 1500 | 1501 | var vMin = new Vector3(float.MaxValue, float.MaxValue, float.MaxValue); 1502 | var vMax = new Vector3(float.MinValue, float.MinValue, float.MinValue); 1503 | 1504 | var toLocal = viewRect.worldToLocalMatrix; 1505 | m_Content.GetWorldCorners(m_Corners); 1506 | for (int j = 0; j < 4; j++) 1507 | { 1508 | Vector3 v = toLocal.MultiplyPoint3x4(m_Corners[j]); 1509 | vMin = Vector3.Min(v, vMin); 1510 | vMax = Vector3.Max(v, vMax); 1511 | } 1512 | 1513 | var bounds = new Bounds(vMin, Vector3.zero); 1514 | bounds.Encapsulate(vMax); 1515 | return bounds; 1516 | } 1517 | 1518 | private Bounds GetBounds4Item(int index) 1519 | { 1520 | if (m_Content == null) 1521 | return new Bounds(); 1522 | 1523 | var vMin = new Vector3(float.MaxValue, float.MaxValue, float.MaxValue); 1524 | var vMax = new Vector3(float.MinValue, float.MinValue, float.MinValue); 1525 | 1526 | var toLocal = viewRect.worldToLocalMatrix; 1527 | int offset = index - itemTypeStart; 1528 | if (offset < 0 || offset >= m_Content.childCount) 1529 | return new Bounds(); 1530 | var rt = m_Content.GetChild(offset) as RectTransform; 1531 | if (rt == null) 1532 | return new Bounds(); 1533 | rt.GetWorldCorners(m_Corners); 1534 | for (int j = 0; j < 4; j++) 1535 | { 1536 | Vector3 v = toLocal.MultiplyPoint3x4(m_Corners[j]); 1537 | vMin = Vector3.Min(v, vMin); 1538 | vMax = Vector3.Max(v, vMax); 1539 | } 1540 | 1541 | var bounds = new Bounds(vMin, Vector3.zero); 1542 | bounds.Encapsulate(vMax); 1543 | return bounds; 1544 | } 1545 | 1546 | private Vector2 CalculateOffset(Vector2 delta) 1547 | { 1548 | Vector2 offset = Vector2.zero; 1549 | if (m_MovementType == MovementType.Unrestricted) 1550 | return offset; 1551 | if (m_MovementType == MovementType.Clamped) 1552 | { 1553 | if (m_totalCount < 0) 1554 | return offset; 1555 | if (GetDimension(delta) < 0 && itemTypeStart > 0) 1556 | return offset; 1557 | if (GetDimension(delta) > 0 && itemTypeEnd < m_totalCount) 1558 | return offset; 1559 | } 1560 | 1561 | Vector2 min = m_ContentBounds.min; 1562 | Vector2 max = m_ContentBounds.max; 1563 | 1564 | if (m_Horizontal) 1565 | { 1566 | min.x += delta.x; 1567 | max.x += delta.x; 1568 | if (min.x > m_ViewBounds.min.x) 1569 | offset.x = m_ViewBounds.min.x - min.x; 1570 | else if (max.x < m_ViewBounds.max.x) 1571 | offset.x = m_ViewBounds.max.x - max.x; 1572 | } 1573 | 1574 | if (m_Vertical) 1575 | { 1576 | min.y += delta.y; 1577 | max.y += delta.y; 1578 | if (max.y < m_ViewBounds.max.y) 1579 | offset.y = m_ViewBounds.max.y - max.y; 1580 | else if (min.y > m_ViewBounds.min.y) 1581 | offset.y = m_ViewBounds.min.y - min.y; 1582 | } 1583 | 1584 | return offset; 1585 | } 1586 | 1587 | protected void SetDirty() 1588 | { 1589 | if (!IsActive()) 1590 | return; 1591 | 1592 | LayoutRebuilder.MarkLayoutForRebuild(rectTransform); 1593 | } 1594 | 1595 | protected void SetDirtyCaching() 1596 | { 1597 | if (!IsActive()) 1598 | return; 1599 | 1600 | CanvasUpdateRegistry.RegisterCanvasElementForLayoutRebuild(this); 1601 | LayoutRebuilder.MarkLayoutForRebuild(rectTransform); 1602 | } 1603 | 1604 | #if UNITY_EDITOR 1605 | 1606 | protected override void OnValidate() 1607 | { 1608 | SetDirtyCaching(); 1609 | } 1610 | 1611 | #endif 1612 | 1613 | private readonly List m_tweeners = new List(); 1614 | 1615 | private const string __TASK_KEY = "LoopScrollPlayEnterAnimation"; 1616 | 1617 | public void StopAllAnimation() 1618 | { 1619 | for (int i = 0; i < m_tweeners.Count; ++i) 1620 | { 1621 | m_tweeners[i].DoKill(true); 1622 | } 1623 | 1624 | m_tweeners.Clear(); 1625 | } 1626 | 1627 | public void PlayEnterAnimation() 1628 | { 1629 | StopAllAnimation(); 1630 | 1631 | TimeManager.Instance.StopTimer(__TASK_KEY); 1632 | 1633 | TimeManager.Instance.AddFrameDelayedAction(1, () => 1634 | { 1635 | var _constCount = this.contentConstraintCount; 1636 | List _list = new List(); 1637 | 1638 | for (int i = 0; i < this.content.childCount; ++i) 1639 | { 1640 | _list.Add(this.content.GetChild(i)); 1641 | } 1642 | 1643 | for (int i = 0; i < _list.Count; ++i) 1644 | { 1645 | var _delay = Mathf.Floor(i / _constCount) * 0.05f; 1646 | 1647 | var _slotObj = _list[i].gameObject; 1648 | var _slotTrans = _list[i].transform as RectTransform; 1649 | var _pos = _slotTrans.anchoredPosition; 1650 | Vector2 _toPos = new Vector2(_pos.x, _pos.y); 1651 | 1652 | _pos.y = _pos.y - 100; 1653 | 1654 | _slotTrans.anchoredPosition = _pos; 1655 | 1656 | var _tweener1 = TweenUtils.MoveToByAnchored(_slotTrans, _toPos.x, _toPos.y, 0.2f) 1657 | .SetDelay(_delay); 1658 | var _tweener2 = TweenUtils.ObjectFadeTo(_slotObj, 1, 0.5f) 1659 | .SetDelay(_delay); 1660 | 1661 | m_tweeners.Add(_tweener1); 1662 | m_tweeners.Add(_tweener2); 1663 | } 1664 | }, __TASK_KEY); 1665 | } 1666 | 1667 | public bool IsAnimationComplete() 1668 | { 1669 | for (int i = m_tweeners.Count - 1; i >= 0; --i) 1670 | { 1671 | if (!m_tweeners[i].IsActive()) 1672 | { 1673 | m_tweeners.RemoveAt(i); 1674 | } 1675 | } 1676 | 1677 | return m_tweeners.Count == 0; 1678 | } 1679 | 1680 | public bool IndexInPage(int _index) 1681 | { 1682 | return _index >= itemTypeStart && _index < itemTypeEnd; 1683 | } 1684 | } 1685 | } --------------------------------------------------------------------------------