using UnityEngine; using UnityEngine.UI; using UnityEngine.Events; using UnityEngine.EventSystems; using System.Collections.Generic; using System; using System.Linq; namespace UIWidgets { /// /// Base class for custom ListView with dynamic items heights. /// public class ListViewCustomHeight : ListViewCustom where TComponent : ListViewItem where TItem: IItemHeight { /// /// Calculate height automaticly without using IListViewItemHeight.Height. /// [SerializeField] [Tooltip("Calculate height automaticly without using IListViewItemHeight.Height.")] bool ForceAutoHeightCalculation = true; TComponent defaultItemCopy; RectTransform defaultItemCopyRect; /// /// Gets the default item copy. /// /// The default item copy. protected TComponent DefaultItemCopy { get { if (defaultItemCopy==null) { defaultItemCopy = Instantiate(DefaultItem) as TComponent; defaultItemCopy.transform.SetParent(DefaultItem.transform.parent, false); defaultItemCopy.gameObject.name = "DefaultItemCopy"; defaultItemCopy.gameObject.SetActive(false); Utilites.FixInstantiated(DefaultItem, defaultItemCopy); } return defaultItemCopy; } } /// /// Gets the RectTransform of DefaultItemCopy. /// /// RectTransform. protected RectTransform DefaultItemCopyRect { get { if (defaultItemCopyRect==null) { defaultItemCopyRect = defaultItemCopy.transform as RectTransform; } return defaultItemCopyRect; } } bool IsCanCalculateHeight; public ListViewCustomHeight() { IsCanCalculateHeight = typeof(IListViewItemHeight).IsAssignableFrom(typeof(TComponent)); } /// /// Awake this instance. /// protected override void Awake() { Start(); } /// /// Calculates the max count of visible items. /// protected override void CalculateMaxVisibleItems() { SetItemsHeight(DataSource, false); CalculateMaxVisibleItems(DataSource); } protected virtual void CalculateMaxVisibleItems(ObservableList data) { var height = scrollHeight; maxVisibleItems = data.OrderBy(GetItemHeight).TakeWhile(x => { height -= x.Height; return height > 0; }).Count() + 2; } /// /// Calculates the size of the item. /// protected override void CalculateItemSize() { var rect = DefaultItem.transform as RectTransform; #if UNITY_4_6 || UNITY_4_7 var layout_elements = rect.GetComponents().OfType(); #else var layout_elements = rect.GetComponents(); #endif if (itemHeight==0) { var preffered_height = layout_elements.Max(x => Mathf.Max(x.minHeight, x.preferredHeight)); itemHeight = (preffered_height > 0) ? preffered_height : rect.rect.height; } if (itemWidth==0) { var preffered_width = layout_elements.Max(x => Mathf.Max(x.minWidth, x.preferredWidth)); itemWidth = (preffered_width > 0) ? preffered_width : rect.rect.width; } } /// /// Scrolls to item with specifid index. /// /// Index. public override void ScrollTo(int index) { if (!CanOptimize()) { return ; } var top = GetScrollValue(); var bottom = GetScrollValue() + scrollHeight; var item_starts = ItemStartAt(index); var item_ends = ItemEndAt(index) + LayoutBridge.GetMargin(); if (item_starts < top) { SetScrollValue(item_starts); } else if (item_ends > bottom) { SetScrollValue(item_ends - GetScrollSize()); } } /// /// Calculates the size of the bottom filler. /// /// The bottom filler size. protected override float CalculateBottomFillerSize() { return GetItemsHeight(topHiddenItems + visibleItems, bottomHiddenItems); } /// /// Calculates the size of the top filler. /// /// The top filler size. protected override float CalculateTopFillerSize() { return GetItemsHeight(0, topHiddenItems); } float GetItemsHeight(int start, int count) { if (count==0) { return 0f; } var height = DataSource.GetRange(start, count).SumFloat(GetItemHeight); return Mathf.Max(0, height + (LayoutBridge.GetSpacing() * (count - 1))); } float GetItemHeight(TItem item) { return item.Height; } /// /// Gets the item position. /// /// The item position. /// Index. public override float GetItemPosition(int index) { return Mathf.Max(0, DataSource.GetRange(0, index).SumFloat(GetItemHeight) + (LayoutBridge.GetSpacing() * (index - 1))); } /// /// Gets the item position bottom. /// /// The item position bottom. /// Index. public override float GetItemPositionBottom(int index) { return GetItemPosition(index) + DataSource[index].Height - LayoutBridge.GetSpacing() + LayoutBridge.GetMargin() - GetScrollSize(); } /// /// Total height of items before specified index. /// /// Height. /// Index. float ItemStartAt(int index) { var height = DataSource.GetRange(0, index).SumFloat(GetItemHeight); return height + (LayoutBridge.GetSpacing() * index); } /// /// Total height of items before and with specified index. /// /// The . /// Index. float ItemEndAt(int index) { var height = DataSource.GetRange(0, index + 1).SumFloat(GetItemHeight); return height + (LayoutBridge.GetSpacing() * index); } /// /// Add the specified item. /// /// Item. /// Index of added item. public override int Add(TItem item) { if (item==null) { throw new ArgumentNullException("item", "Item is null."); } if (item.Height==0) { item.Height = CalculateItemHeight(item); } return base.Add(item); } /// /// Calculate and sets the height of the items. /// /// Items. /// If set to true force update. void SetItemsHeight(ObservableList items, bool forceUpdate = true) { items.ForEach(x => { if ((x.Height==0) || forceUpdate) { x.Height = CalculateItemHeight(x); } }); } /// /// Resize this instance. /// public override void Resize() { SetItemsHeight(DataSource, true); base.Resize(); } /// /// Updates the items. /// /// New items. protected override void SetNewItems(ObservableList newItems) { SetItemsHeight(newItems); CalculateMaxVisibleItems(newItems); base.SetNewItems(newItems); } /// /// Gets the height of the index by. /// /// The index by height. /// Height. int GetIndexByHeight(float height) { var spacing = LayoutBridge.GetSpacing(); return DataSource.TakeWhile((item, index) => { height -= item.Height; if (index > 0) { height -= spacing; } return height >= 0; }).Count(); } /// /// Gets the last index of the visible. /// /// The last visible index. /// If set to true strict. protected override int GetLastVisibleIndex(bool strict=false) { var last_visible_index = GetIndexByHeight(GetScrollValue() + scrollHeight); return (strict) ? last_visible_index : last_visible_index + 2; } /// /// Gets the first index of the visible. /// /// The first visible index. /// If set to true strict. protected override int GetFirstVisibleIndex(bool strict=false) { var first_visible_index = GetIndexByHeight(GetScrollValue()); if (strict) { return first_visible_index; } return Mathf.Min(first_visible_index, Mathf.Max(0, DataSource.Count - visibleItems)); } LayoutGroup defaultItemLayoutGroup; /// /// Gets the height of the item. /// /// The item height. /// Item. float CalculateItemHeight(TItem item) { if (defaultItemLayoutGroup==null) { defaultItemLayoutGroup = DefaultItemCopy.GetComponent(); } float height = 0f; if (!IsCanCalculateHeight || ForceAutoHeightCalculation) { if (defaultItemLayoutGroup!=null) { DefaultItemCopy.gameObject.SetActive(true); SetData(DefaultItemCopy, item); Utilites.UpdateLayout(defaultItemLayoutGroup); height = LayoutUtility.GetPreferredHeight(DefaultItemCopyRect); DefaultItemCopy.gameObject.SetActive(false); } } else { SetData(DefaultItemCopy, item); height = (DefaultItemCopy as IListViewItemHeight).Height; } return height; } /// /// Adds the callback. /// /// Item. /// Index. protected override void AddCallback(ListViewItem item, int index) { item.onResize.AddListener(SizeChanged); base.AddCallback(item, index); } /// /// Removes the callback. /// /// Item. /// Index. protected override void RemoveCallback(ListViewItem item, int index) { item.onResize.RemoveListener(SizeChanged); base.RemoveCallback(item, index); } void SizeChanged(int index, Vector2 size) { if (DataSource[index].Height!=size.y) { DataSource[index].Height = size.y; var old = maxVisibleItems; CalculateMaxVisibleItems(); if (maxVisibleItems > old) { UpdateView(); } else { ScrollUpdate(); } } } /// /// Calls specified function with each component. /// /// Func. public override void ForEachComponent(Action func) { base.ForEachComponent(func); func(DefaultItemCopy); } /// /// Gets the index of the nearest item. /// /// The nearest item index. /// Point. public override int GetNearestIndex(Vector2 point) { if (IsSortEnabled()) { return -1; } var index = GetIndexByHeight(-point.y); if (index!=(DataSource.Count - 1)) { var height = GetItemsHeight(0, index); var top = -point.y - height; var bottom = -point.y - (height + DataSource[index+1].Height + LayoutBridge.GetSpacing()); if (bottom < top) { index += 1; } } return Mathf.Min(index, DataSource.Count - 1); } #region ListViewPaginator support public override int GetNearestItemIndex() { return GetIndexByHeight(GetScrollValue()); } #endregion #if UNITY_EDITOR bool IsItemCanCalculateHeight() { return IsCanCalculateHeight; } #endif } }