publicclassTempListView : MonoBehaviour { #if UNITY_EDITOR #endif // check when prefab change // [OnValueChanged("OnPrefabChanged")] [SerializeField, Header("place holder, the listview should only contains one type of element")] TempListElementUI m_elementPrefab; RectTransform Container => transform as RectTransform; [SerializeField, ReadOnly] List<TempListElementUI> m_actualUsedComponents = new List<TempListElementUI>(0);
publicint Count => m_actualUsedComponents.Count; public TempListElementUI this[int index] => m_actualUsedComponents[index]; public IReadOnlyList<TempListElementUI> ElementList => m_actualUsedComponents;
public TempListElementUI Add() { TempListElementUI element = InternalAdd(); m_actualUsedComponents.Add(element); element.Show(); return element; }
publicvoidClear() { TempListElementUI element = null; while (m_actualUsedComponents.Count > 0) { element = m_actualUsedComponents[m_actualUsedComponents.Count - 1]; m_actualUsedComponents.RemoveAt(m_actualUsedComponents.Count - 1); InternalRemove(element); } }
publicvoidRemove(TempListElementUI instance) { if (m_actualUsedComponents.Remove(instance)) { instance.Hide(); } else { Debug.LogError($"listview_{this.gameObject.name} does not contains {instance.ElementRectTransform.name}, remove failed"); } }
publicvoidRemoveAt(int index) { if (index < 0 || index >= m_actualUsedComponents.Count) { Debug.LogError($"{index} is invalid for SimpleListView", this.gameObject); return; }
///<summary> /// ///</summary> ///<returns>-1 means element is not in the list</returns> publicintIndexOf(TempListElementUI instance) { try { return m_actualUsedComponents.IndexOf(instance); } catch (ArgumentOutOfRangeException) { return-1; } }
protectedvirtual TempListElementUI InternalAdd() { // can apply object pool here if (Application.isEditor && !Application.isPlaying) return UnityEditor.PrefabUtility.InstantiatePrefab(m_elementPrefab, Container) as TempListElementUI; return Instantiate(m_elementPrefab, Container); }
protectedvirtualvoidInternalRemove(TempListElementUI element) { // can apply object pool here if (element == null) return; element.Hide(); if (Application.isEditor && !Application.isPlaying) GameObject.DestroyImmediate(element.ElementRectTransform.gameObject); else GameObject.Destroy(element.ElementRectTransform.gameObject); }
#if UNITY_EDITOR
privatevoidOnTransformChildrenChanged() { if (Application.isEditor && !Application.isPlaying) FindPrefabInstances(); }
privatevoidOnPrefabChanged() { // remove pre objects int amount = m_actualUsedComponents.Count; for (int i = 0; i < m_actualUsedComponents.Count; i++) { GameObject.DestroyImmediate(m_actualUsedComponents[i].ElementRectTransform.gameObject); }
if (m_elementPrefab != null) { for (int i = 0; i < amount; i++) { RectTransform rectTransform = (RectTransform)UnityEditor.PrefabUtility.InstantiatePrefab(m_elementPrefab, Container); m_actualUsedComponents[i] = (rectTransform.GetComponent<TempListElementUI>()); } } else { m_actualUsedComponents.Clear(); } }
///<summary> /// Retrieves prefab instances in the transform ///</summary> [ContextMenu("find prefab instances")] privatevoidFindPrefabInstances() { bool hasPrefab = !(m_elementPrefab == null); TempListElementUI elementPrefab = m_elementPrefab.GetComponent<TempListElementUI>(); m_actualUsedComponents.Clear(); List<GameObject> toDeleteObjectList = new List<GameObject>(); foreach (Transform child in Container) { TempListElementUI childElement = child.GetComponent<TempListElementUI>(); if (childElement == null) { toDeleteObjectList.Add(child.gameObject); continue; }
if (hasPrefab) { GameObject detectPrefabGo = UnityEditor.PrefabUtility.GetCorrespondingObjectFromSource(child.gameObject); TempListElementUI detectPrefab = (detectPrefabGo == null) ? null : detectPrefabGo.GetComponent<TempListElementUI>(); if (elementPrefab == detectPrefab) { // same source prefab m_actualUsedComponents.Add(childElement); } else { // different source prefab, delete this one toDeleteObjectList.Add(child.gameObject); } } elseif (UnityEditor.PrefabUtility.IsAnyPrefabInstanceRoot(child.gameObject)) { // find the first prefab GameObject prefab = UnityEditor.PrefabUtility.GetCorrespondingObjectFromSource(child.gameObject); m_elementPrefab = prefab.GetComponent<TempListElementUI>(); m_actualUsedComponents.Add(childElement); hasPrefab = true; } }
for (int i = 0; i < toDeleteObjectList.Count; i++) { if (Application.isPlaying) GameObject.Destroy(toDeleteObjectList[i]); else GameObject.DestroyImmediate(toDeleteObjectList[i]); } }
[ContextMenu("editor time add")] privatevoidEditorTimeAdd() { if (Application.isPlaying) return; if (m_elementPrefab == null) { Debug.LogError("listview is missing element prefab"); return; } TempListElementUI spawnObject = (TempListElementUI)UnityEditor.PrefabUtility.InstantiatePrefab(m_elementPrefab, Container); m_actualUsedComponents.Add(spawnObject); }
[ContextMenu("editor time clear")] privatevoidEditorTimeClear() { if (Application.isPlaying) return; // remove pre objects TempListElementUI[] preObjects = Container.GetComponentsInChildren<TempListElementUI>(); for (int i = 0; i < preObjects.Length; i++) { GameObject.DestroyImmediate(preObjects[i].ElementRectTransform.gameObject); } m_actualUsedComponents.Clear(); }
[ContextMenu("test print elements")] privatevoidTestPrintListElements() { if (m_actualUsedComponents != null) { StringBuilder printText = new StringBuilder($"temp list view children_{m_actualUsedComponents.Count} :\n"); for (int i = 0; i < m_actualUsedComponents.Count; i++) { printText.AppendLine(m_actualUsedComponents[i].ElementRectTransform.name); } Debug.Log(printText); } }
#endif }
publicclassBoundlessScrollRectController : UIBehaviour { /* too much code, I will just show the draw */
privatevoidDrawContentItem() { IReadOnlyList<TempListElementUI> elementList = ElementList; int dataCount = m_simulatedDataCount; // TODO @Hiko use a general calculation later bool test = m_content.anchorMin != Vector2.up || m_content.anchorMax != Vector2.up || m_content.pivot != Vector2.up; if (test) { m_content.anchorMin = Vector2.up; m_content.anchorMax = Vector2.up; m_content.pivot = Vector2.up; } Vector3 dragContentAnchorPostion = m_content.anchoredPosition; Vector3 contentMove = dragContentAnchorPostion - SomeUtils.GetOffsetLocalPosition(m_content, SomeUtils.UIOffsetType.TopLeft); Vector2 itemSize = m_gridLayoutGroup.CellSize, spacing = m_gridLayoutGroup.Spacing;
// need to know the moving direction, then adjust it to prevent wrong draw float xMove = contentMove.x < 0 ? (-contentMove.x - padding.horizontal) : 0; xMove = Mathf.Clamp(xMove, 0.0f, Mathf.Abs(xMove)); float yMove = contentMove.y > 0 ? (contentMove.y - padding.vertical) : 0; yMove = Mathf.Clamp(yMove, 0.0f, Mathf.Abs(yMove));
// the column index of the top left item int tempColumnIndex = Mathf.FloorToInt((xMove + spacing.x) / (itemSize.x + spacing.x)); if (xMove % (itemSize.x + spacing.x) - itemSize.x > spacing.x) tempColumnIndex = Mathf.Clamp(tempColumnIndex - 1, 0, tempColumnIndex);
// the row index of the top left item int tempRowIndex = Mathf.FloorToInt((yMove + spacing.y) / (itemSize.y + spacing.y)); if (yMove % (itemSize.y + spacing.y) - itemSize.y > spacing.y) tempRowIndex = Mathf.Clamp(tempRowIndex - 1, 0, tempRowIndex);
Vector2Int rowTopLeftItemIndex = new Vector2Int(tempRowIndex, tempColumnIndex);
// x -> element amount on horizontal axis // y -> element amount on vertical axis Vector2Int contentRowColumnSize = new Vector2Int(rowDataCount, columnDataCount);
// deal with content from left to right (simple case) int dataIndex = 0, uiItemIndex = 0; Vector3 rowTopLeftPosition = new Vector3(padding.left, -padding.top, 0.0f), itemTopLeftPosition = Vector3.zero; for (int columnIndex = 0; columnIndex < m_viewItemCountInColumn; columnIndex++) { if (columnIndex + rowTopLeftItemIndex.x == columnDataCount) break;
// apply this on the target shows actual data public interface ISetupable<TData> { voidSetup(TData data); }
///<summary> /// target component should inherit from ISetupable ///</summary> ///<param name="component"></param> ///<param name="data"></param> ///<typeparam name="TData"></typeparam> publicstaticvoid ISetup<TComponent, TData>(this Component component, TData data) where TComponent : Component { // I guess this check should be fine :( if (component is ISetupable<TData> target) { target.Setup(data); return; } UnityEngine.Debug.LogError($"component_{component.name} is not a setupable"); }