├── Editor └── ScrollViewEditor.cs ├── GDropDown.cs ├── README.md ├── Samples.unity ├── Samples.unity.meta ├── Screenshot ├── 1.png ├── 2.png ├── 3.png └── 4.png └── ScrollView.cs /Editor/ScrollViewEditor.cs: -------------------------------------------------------------------------------- 1 | /* 2 | * Unity Module By Garson(https://github.com/garsonlab) 3 | * ------------------------------------------------------------------- 4 | * FileName: ScrollViewEditor 5 | * Date : 2018/08/11 6 | * Version : v1.0 7 | * Describe: 8 | */ 9 | using UnityEditor; 10 | using UnityEditor.AnimatedValues; 11 | using UnityEngine; 12 | using UnityEngine.UI; 13 | 14 | [CustomEditor(typeof(ScrollView), true)] 15 | [CanEditMultipleObjects] 16 | public class ScrollViewEditor : Editor 17 | { 18 | SerializedProperty m_ViewPort; 19 | SerializedProperty m_Content; 20 | SerializedProperty m_Prefab; 21 | 22 | SerializedProperty m_MotionType; 23 | SerializedProperty m_MovementType; 24 | SerializedProperty m_Elasticity; 25 | SerializedProperty m_Inertia; 26 | SerializedProperty m_DecelerationRate; 27 | SerializedProperty m_ScrollSensitivity; 28 | 29 | SerializedProperty m_Margin; 30 | SerializedProperty m_ItemSize; 31 | SerializedProperty m_Space; 32 | SerializedProperty m_FixedCount; 33 | SerializedProperty m_NumItems; 34 | SerializedProperty m_Loop; 35 | SerializedProperty m_AutoAttach; 36 | SerializedProperty m_AttachSnap; 37 | SerializedProperty m_ScaleCurve; 38 | SerializedProperty m_ScaleAnimation; 39 | 40 | SerializedProperty m_HorizontalScrollbar; 41 | SerializedProperty m_VerticalScrollbar; 42 | SerializedProperty m_HorizontalScrollbarVisibility; 43 | SerializedProperty m_VerticalScrollbarVisibility; 44 | SerializedProperty m_HorizontalScrollbarSpacing; 45 | SerializedProperty m_VerticalScrollbarSpacing; 46 | SerializedProperty m_OnValueChanged; 47 | SerializedProperty m_OnItemRender; 48 | AnimBool m_ShowElasticity; 49 | AnimBool m_ShowDecelerationRate; 50 | AnimBool m_ShowAutoAttach; 51 | AnimBool m_ShowScaleCurve; 52 | bool m_ViewportIsNotChild, m_HScrollbarIsNotChild, m_VScrollbarIsNotChild; 53 | static string s_HError = "For this visibility mode, the Viewport property and the Horizontal Scrollbar property both needs to be set to a Rect Transform that is a child to the Scroll View."; 54 | static string s_VError = "For this visibility mode, the Viewport property and the Vertical Scrollbar property both needs to be set to a Rect Transform that is a child to the Scroll View."; 55 | 56 | bool m_ScrollSetting; 57 | bool m_ScrollbarSetting; 58 | bool m_EventSetting; 59 | 60 | static string s_ScrollSettingLabel = "Scroll Setting"; 61 | static string s_ScrollbarSettingLabel = "Scrollbar Setting"; 62 | static string s_EventSettingLabel = "Event Setting"; 63 | static string s_WelcomInfo = "Welcom to use Infinite and Loop List"; 64 | static string s_ViewportNull = "Infinite and Loop List Error:\n Viewport property is Null"; 65 | static string s_ContentNull = "Infinite and Loop List Error:\n Content property is Null"; 66 | static string s_PrefabtNull = "Infinite and Loop List Warning:\n Prefab property is Null"; 67 | 68 | protected virtual void OnEnable() 69 | { 70 | m_Content = serializedObject.FindProperty("m_Content"); 71 | m_ViewPort = serializedObject.FindProperty("m_ViewPort"); 72 | m_Prefab = serializedObject.FindProperty("m_Prefab"); 73 | m_MotionType = serializedObject.FindProperty("m_MotionType"); 74 | 75 | m_MovementType = serializedObject.FindProperty("m_MovementType"); 76 | m_Elasticity = serializedObject.FindProperty("m_Elasticity"); 77 | m_Inertia = serializedObject.FindProperty("m_Inertia"); 78 | m_DecelerationRate = serializedObject.FindProperty("m_DecelerationRate"); 79 | m_ScrollSensitivity = serializedObject.FindProperty("m_ScrollSensitivity"); 80 | 81 | m_Margin = serializedObject.FindProperty("m_Margin"); 82 | m_FixedCount = serializedObject.FindProperty("m_FixedCount"); 83 | m_ItemSize = serializedObject.FindProperty("m_ItemSize"); 84 | m_Space = serializedObject.FindProperty("m_Space"); 85 | m_NumItems = serializedObject.FindProperty("m_NumItems"); 86 | m_Loop = serializedObject.FindProperty("m_Loop"); 87 | m_AutoAttach = serializedObject.FindProperty("m_AutoAttach"); 88 | m_AttachSnap = serializedObject.FindProperty("m_AttachSnap"); 89 | m_ScaleCurve = serializedObject.FindProperty("m_ScaleCurve"); 90 | m_ScaleAnimation = serializedObject.FindProperty("m_ScaleAnimation"); 91 | 92 | m_HorizontalScrollbar = serializedObject.FindProperty("m_HorizontalScrollbar"); 93 | m_VerticalScrollbar = serializedObject.FindProperty("m_VerticalScrollbar"); 94 | m_HorizontalScrollbarVisibility = serializedObject.FindProperty("m_HorizontalScrollbarVisibility"); 95 | m_VerticalScrollbarVisibility = serializedObject.FindProperty("m_VerticalScrollbarVisibility"); 96 | m_HorizontalScrollbarSpacing = serializedObject.FindProperty("m_HorizontalScrollbarSpacing"); 97 | m_VerticalScrollbarSpacing = serializedObject.FindProperty("m_VerticalScrollbarSpacing"); 98 | m_OnValueChanged = serializedObject.FindProperty("m_OnValueChanged"); 99 | this.m_OnItemRender = serializedObject.FindProperty("m_OnItemRender"); 100 | 101 | m_ShowElasticity = new AnimBool(Repaint); 102 | m_ShowDecelerationRate = new AnimBool(Repaint); 103 | m_ShowAutoAttach = new AnimBool(Repaint); 104 | m_ShowScaleCurve = new AnimBool(Repaint); 105 | SetAnimBools(true); 106 | 107 | m_ScrollSetting = true; 108 | m_ScrollbarSetting = true; 109 | m_EventSetting = true; 110 | } 111 | 112 | protected virtual void OnDisable() 113 | { 114 | m_ShowElasticity.valueChanged.RemoveListener(Repaint); 115 | m_ShowDecelerationRate.valueChanged.RemoveListener(Repaint); 116 | m_ShowAutoAttach.valueChanged.RemoveListener(Repaint); 117 | m_ShowScaleCurve.valueChanged.RemoveListener(Repaint); 118 | } 119 | 120 | void SetAnimBools(bool instant) 121 | { 122 | SetAnimBool(m_ShowElasticity, !m_MovementType.hasMultipleDifferentValues && m_MovementType.enumValueIndex == (int)ScrollView.MovementType.Elastic, instant); 123 | SetAnimBool(m_ShowDecelerationRate, !m_Inertia.hasMultipleDifferentValues && m_Inertia.boolValue == true, instant); 124 | 125 | SetAnimBool(m_ShowAutoAttach, m_AutoAttach.boolValue, instant); 126 | SetAnimBool(m_ShowScaleCurve, m_ScaleCurve.boolValue, instant); 127 | } 128 | 129 | void SetAnimBool(AnimBool a, bool value, bool instant) 130 | { 131 | if (instant) 132 | a.value = value; 133 | else 134 | a.target = value; 135 | } 136 | 137 | void CalculateCachedValues() 138 | { 139 | m_ViewportIsNotChild = false; 140 | m_HScrollbarIsNotChild = false; 141 | m_VScrollbarIsNotChild = false; 142 | if (targets.Length == 1) 143 | { 144 | Transform transform = ((ScrollView)target).transform; 145 | if (m_ViewPort.objectReferenceValue == null || ((RectTransform)m_ViewPort.objectReferenceValue).transform.parent != transform) 146 | m_ViewportIsNotChild = true; 147 | if (m_HorizontalScrollbar.objectReferenceValue == null || ((Scrollbar)m_HorizontalScrollbar.objectReferenceValue).transform.parent != transform) 148 | m_HScrollbarIsNotChild = true; 149 | if (m_VerticalScrollbar.objectReferenceValue == null || ((Scrollbar)m_VerticalScrollbar.objectReferenceValue).transform.parent != transform) 150 | m_VScrollbarIsNotChild = true; 151 | } 152 | } 153 | 154 | public override void OnInspectorGUI() 155 | { 156 | SetAnimBools(false); 157 | 158 | serializedObject.Update(); 159 | // Once we have a reliable way to know if the object changed, only re-cache in that case. 160 | CalculateCachedValues(); 161 | 162 | if(m_ViewPort.objectReferenceValue == null ) 163 | EditorGUILayout.HelpBox(s_ViewportNull, MessageType.Error); 164 | else if (m_Content.objectReferenceValue == null) 165 | EditorGUILayout.HelpBox(s_ContentNull, MessageType.Error); 166 | else if (m_Prefab.objectReferenceValue == null) 167 | EditorGUILayout.HelpBox(s_PrefabtNull, MessageType.Warning); 168 | else 169 | EditorGUILayout.HelpBox(s_WelcomInfo, MessageType.Info); 170 | 171 | 172 | EditorGUILayout.PropertyField(m_ViewPort); 173 | EditorGUILayout.PropertyField(m_Content); 174 | EditorGUILayout.PropertyField(m_Prefab); 175 | EditorGUILayout.Space(); 176 | 177 | #region ScrollSetting 178 | m_ScrollSetting = EditorGUILayout.Foldout(m_ScrollSetting, s_ScrollSettingLabel); 179 | if (m_ScrollSetting) 180 | { 181 | EditorGUILayout.PropertyField(m_MotionType); 182 | EditorGUILayout.PropertyField(m_MovementType); 183 | if (EditorGUILayout.BeginFadeGroup(m_ShowElasticity.faded)) 184 | { 185 | EditorGUI.indentLevel++; 186 | EditorGUILayout.PropertyField(m_Elasticity); 187 | EditorGUI.indentLevel--; 188 | } 189 | EditorGUILayout.EndFadeGroup(); 190 | 191 | EditorGUILayout.PropertyField(m_Inertia); 192 | if (EditorGUILayout.BeginFadeGroup(m_ShowDecelerationRate.faded)) 193 | { 194 | EditorGUI.indentLevel++; 195 | EditorGUILayout.PropertyField(m_DecelerationRate); 196 | EditorGUI.indentLevel--; 197 | } 198 | EditorGUILayout.EndFadeGroup(); 199 | 200 | EditorGUILayout.PropertyField(m_ScrollSensitivity); 201 | } 202 | #endregion 203 | 204 | EditorGUILayout.Space(); 205 | 206 | EditorGUILayout.PropertyField(m_Margin, true); 207 | EditorGUILayout.PropertyField(m_ItemSize, true); 208 | EditorGUILayout.PropertyField(m_Space, true); 209 | EditorGUILayout.Space(); 210 | 211 | EditorGUILayout.PropertyField(m_FixedCount); 212 | EditorGUILayout.PropertyField(m_NumItems); 213 | EditorGUILayout.Space(); 214 | 215 | EditorGUILayout.PropertyField(m_Loop); 216 | EditorGUILayout.PropertyField(m_AutoAttach); 217 | if (EditorGUILayout.BeginFadeGroup(m_ShowAutoAttach.faded)) 218 | { 219 | EditorGUILayout.PropertyField(m_AttachSnap, true); 220 | } 221 | EditorGUILayout.EndFadeGroup(); 222 | 223 | 224 | EditorGUILayout.PropertyField(m_ScaleCurve); 225 | if (EditorGUILayout.BeginFadeGroup(m_ShowScaleCurve.faded)) 226 | { 227 | EditorGUILayout.PropertyField(m_ScaleAnimation, true); 228 | } 229 | EditorGUILayout.EndFadeGroup(); 230 | 231 | #region ScrollBar 232 | EditorGUILayout.Space(); 233 | m_ScrollbarSetting = EditorGUILayout.Foldout(m_ScrollbarSetting, s_ScrollbarSettingLabel); 234 | if (m_ScrollbarSetting) 235 | { 236 | EditorGUILayout.PropertyField(m_HorizontalScrollbar); 237 | if (m_HorizontalScrollbar.objectReferenceValue && !m_HorizontalScrollbar.hasMultipleDifferentValues) 238 | { 239 | EditorGUI.indentLevel++; 240 | EditorGUILayout.PropertyField(m_HorizontalScrollbarVisibility, new GUIContent("Visibility")); 241 | 242 | if ((ScrollView.ScrollbarVisibility) m_HorizontalScrollbarVisibility.enumValueIndex == 243 | ScrollView.ScrollbarVisibility.AutoHideAndExpandViewport 244 | && !m_HorizontalScrollbarVisibility.hasMultipleDifferentValues) 245 | { 246 | if (m_ViewportIsNotChild || m_HScrollbarIsNotChild) 247 | EditorGUILayout.HelpBox(s_HError, MessageType.Error); 248 | EditorGUILayout.PropertyField(m_HorizontalScrollbarSpacing, new GUIContent("Spacing")); 249 | } 250 | 251 | EditorGUI.indentLevel--; 252 | } 253 | 254 | EditorGUILayout.PropertyField(m_VerticalScrollbar); 255 | if (m_VerticalScrollbar.objectReferenceValue && !m_VerticalScrollbar.hasMultipleDifferentValues) 256 | { 257 | EditorGUI.indentLevel++; 258 | EditorGUILayout.PropertyField(m_VerticalScrollbarVisibility, new GUIContent("Visibility")); 259 | 260 | if ((ScrollView.ScrollbarVisibility) m_VerticalScrollbarVisibility.enumValueIndex == 261 | ScrollView.ScrollbarVisibility.AutoHideAndExpandViewport 262 | && !m_VerticalScrollbarVisibility.hasMultipleDifferentValues) 263 | { 264 | if (m_ViewportIsNotChild || m_VScrollbarIsNotChild) 265 | EditorGUILayout.HelpBox(s_VError, MessageType.Error); 266 | EditorGUILayout.PropertyField(m_VerticalScrollbarSpacing, new GUIContent("Spacing")); 267 | } 268 | 269 | EditorGUI.indentLevel--; 270 | } 271 | } 272 | 273 | #endregion 274 | 275 | #region Event 276 | EditorGUILayout.Space(); 277 | m_EventSetting = EditorGUILayout.Foldout(m_EventSetting, s_EventSettingLabel); 278 | if (m_EventSetting) 279 | { 280 | EditorGUILayout.PropertyField(m_OnValueChanged, true); 281 | EditorGUILayout.PropertyField(this.m_OnItemRender, true); 282 | } 283 | 284 | #endregion 285 | 286 | serializedObject.ApplyModifiedProperties(); 287 | } 288 | } 289 | -------------------------------------------------------------------------------- /GDropDown.cs: -------------------------------------------------------------------------------- 1 | /* 2 | * Unity Module By Garson(https://github.com/garsonlab) 3 | * ------------------------------------------------------------------- 4 | * FileName: GDropDown 5 | * Date : 2018/08/11 6 | * Version : v1.0 7 | * Describe: 下拉列表, 支持无限循环滚动列表 8 | */ 9 | using System; 10 | using System.Collections.Generic; 11 | using UnityEngine.Events; 12 | 13 | namespace UnityEngine.UI 14 | { 15 | [AddComponentMenu("UI/GDropdown", 100)] 16 | [RequireComponent(typeof (RectTransform))] 17 | [DisallowMultipleComponent] 18 | public class GDropDown : MonoBehaviour 19 | { 20 | #region Template 21 | [SerializeField] 22 | private Toggle m_CaptionToggle; 23 | /// 24 | /// Button of Whole Component 25 | /// 26 | public Toggle CaptionToggle { get { return m_CaptionToggle; } set { SetCaptionButton(value);} } 27 | 28 | [Tooltip("Display Text of Selected Item")] 29 | [SerializeField] 30 | private Text m_CaptionText; 31 | /// 32 | /// Display Text of Selected Item 33 | /// 34 | public Text CaptionText { get { return m_CaptionText; } set { m_CaptionText = value; } } 35 | 36 | [Tooltip("Display Image of Selected Item")] 37 | [SerializeField] 38 | private Image m_CaptionImage; 39 | /// 40 | /// Display Image of Selected Item 41 | /// 42 | public Image CaptionImage { get { return m_CaptionImage; } set { m_CaptionImage = value; } } 43 | [Tooltip("Flag is Open List")] 44 | [SerializeField] 45 | private Transform m_Flag; 46 | private float m_defaultAngle; 47 | #endregion 48 | [Space] 49 | #region Other 50 | [SerializeField] 51 | private ScrollView m_ScrollView; 52 | 53 | [SerializeField] 54 | private int m_SelectIndex; 55 | /// 56 | /// Current Select Index 57 | /// 58 | public int selectIndex { get { return m_SelectIndex; } set { SetSelectIndex(value); }} 59 | /// 60 | /// Current Select Value 61 | /// 62 | public string selectValue 63 | { 64 | get { return m_DropData.Count > m_SelectIndex ? m_DropData[m_SelectIndex].text : ""; } 65 | set 66 | { 67 | int count = m_DropData.Count; 68 | for (int i = 0; i < count; i++) 69 | { 70 | if (m_DropData[i].text == value) 71 | { 72 | SetSelectIndex(i); 73 | break; 74 | } 75 | } 76 | } 77 | } 78 | 79 | /// 80 | /// Drop Down Value Changed, Used to Lua 81 | /// 82 | public Action OnValueChanged; 83 | 84 | [SerializeField] 85 | List m_DropData = new List(); 86 | 87 | [Serializable] 88 | public class GDropDownEvent : UnityEvent { } 89 | public GDropDownEvent onValueChanged = new GDropDownEvent(); 90 | 91 | private Dictionary m_Items = new Dictionary(); 92 | private RectTransform m_PointerMask; 93 | private Transform m_Canvas; 94 | #endregion 95 | 96 | void Awake() 97 | { 98 | if (m_Flag) 99 | m_defaultAngle = m_Flag.localEulerAngles.z; 100 | if (m_ScrollView) 101 | m_ScrollView.onItemRender.AddListener(OnItemRender); 102 | SetCaptionButton(m_CaptionToggle); 103 | m_CaptionToggle.isOn = false; 104 | SetSelectIndex(m_SelectIndex); 105 | CloseMask(); 106 | } 107 | 108 | public void AddOptions(GDropData[] options) 109 | { 110 | for (int i = 0; i < options.Length; i++) 111 | this.m_DropData.Add(options[i]); 112 | 113 | if (m_CaptionToggle.isOn) 114 | OpenMask(); 115 | } 116 | 117 | public void AddOptions(Sprite[] options) 118 | { 119 | for (int i = 0; i < options.Length; i++) 120 | this.m_DropData.Add(new GDropData(options[i])); 121 | 122 | if (m_CaptionToggle.isOn) 123 | OpenMask(); 124 | } 125 | 126 | public void AddOptions(string[] options) 127 | { 128 | for (int i = 0; i < options.Length; i++) 129 | this.m_DropData.Add(new GDropData(options[i])); 130 | 131 | if (m_CaptionToggle.isOn) 132 | OpenMask(); 133 | } 134 | 135 | public void RemoveAt(int index) 136 | { 137 | if(index < 0 || index >= m_DropData.Count) 138 | return; 139 | m_DropData.RemoveAt(index); 140 | if (index == m_SelectIndex) 141 | { 142 | SetSelectIndex(index - 1); 143 | } 144 | } 145 | 146 | public void RemoveValue(string value) 147 | { 148 | int index = -1; 149 | int count = m_DropData.Count; 150 | for (int i = 0; i < count; i++) 151 | { 152 | if (m_DropData[i].text == value) 153 | { 154 | index = i; 155 | break; 156 | } 157 | } 158 | if (index >= 0) 159 | { 160 | RemoveAt(index); 161 | } 162 | } 163 | 164 | public void ClearOptions() 165 | { 166 | this.m_DropData.Clear(); 167 | if (m_CaptionToggle.isOn) 168 | OpenMask(); 169 | } 170 | 171 | 172 | /// 173 | /// Refresh Display View 174 | /// 175 | private void RefreshShowValue() 176 | { 177 | if (m_DropData.Count > m_SelectIndex) 178 | { 179 | GDropData data = m_DropData[m_SelectIndex]; 180 | if (m_CaptionText) 181 | m_CaptionText.text = data.text; 182 | if (m_CaptionImage) 183 | m_CaptionImage.sprite = data.image; 184 | } 185 | else 186 | { 187 | if (m_CaptionText) 188 | m_CaptionText.text = ""; 189 | if (m_CaptionImage) 190 | m_CaptionImage.sprite = null; 191 | } 192 | } 193 | 194 | /// 195 | /// Render Item in List 196 | /// 197 | /// 198 | /// 199 | private void OnItemRender(int index, Transform child) 200 | { 201 | GItem item; 202 | if (!m_Items.TryGetValue(child, out item)) 203 | item = new GItem(this, child); 204 | 205 | if (m_DropData.Count > index) 206 | { 207 | item.Reset(m_DropData[index].text, m_DropData[index].image, index == m_SelectIndex); 208 | } 209 | } 210 | 211 | /// 212 | /// Set Cur Select Index When Click 213 | /// 214 | /// 215 | private void SetSelectIndex(int p) 216 | { 217 | p = Mathf.Clamp(p, 0, m_DropData.Count); 218 | m_SelectIndex = p; 219 | RefreshShowValue(); 220 | onValueChanged.Invoke(p); 221 | if (OnValueChanged != null) 222 | OnValueChanged(p); 223 | m_CaptionToggle.isOn = false; 224 | } 225 | 226 | /// 227 | /// Open Mask to Poniters Out of List 228 | /// 229 | private void OpenMask() 230 | { 231 | if (m_PointerMask == null) //Create Mask 232 | { 233 | GameObject o = new GameObject("Pointer Mask"); 234 | o.transform.SetParent(transform); 235 | Image mask = o.AddComponent(); 236 | mask.color = new Color(1, 1, 1, 0); 237 | m_PointerMask = o.transform as RectTransform; 238 | m_PointerMask.sizeDelta = new Vector2(Screen.width, Screen.height); 239 | Button btnMask = o.AddComponent