2023-04-03 11:04:31 +08:00

1698 lines
63 KiB
C#

using System.Net;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.EventSystems;
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine.UI;
namespace BF
{
[DisallowMultipleComponent]
[RequireComponent(typeof(RectTransform))]
public abstract class ScrollRectBaseOld : UIBehaviour, IInitializePotentialDragHandler, IBeginDragHandler, IEndDragHandler, IDragHandler, IScrollHandler, ICanvasElement, ILayoutElement, ILayoutGroup
{
public enum MovementType
{
Unrestricted, // Unrestricted movement -- can scroll forever
Elastic, // Restricted but flexible -- can go past the edges, but springs back in place
Clamped, // Restricted movement where it's not possible to go past the edges
}
public enum ScrollbarVisibility
{
Permanent,
AutoHide,
AutoHideAndExpandViewport,
}
#region ----------------------- Expansion Field -------------------------
public BFCell bfCell;
public bool useBar = false;
private int totalCount; //Total cell count, negative means infinite loop mode
protected float startThreshold = -1;
protected float endThreshold = -1;
// [Tooltip("Reverse direction for dragging")]
// public bool reverseDirection = false;
[Tooltip("Rubber scale for outside")]
public float rubberScale = 1;
protected abstract float GetSize(RectTransform item);
protected abstract float GetDimension(Vector2 vector);
protected abstract Vector2 GetVector(float value);
protected int directionSign = 0; // horizontal will be 1 || vertical will be -1
protected GridLayoutGroup gridLayout = null;
private float contentSpacing = -1;
protected float ContentSpacing
{
get
{
if (contentSpacing >= 0)
return contentSpacing;
contentSpacing = 0;
if (content != null)
{
HorizontalOrVerticalLayoutGroup layout1 = content.GetComponent<HorizontalOrVerticalLayoutGroup>();
if (layout1 != null)
contentSpacing = layout1.spacing;
gridLayout = content.GetComponent<GridLayoutGroup>();
if (gridLayout != null)
contentSpacing = Mathf.Abs(GetDimension(gridLayout.spacing));
}
return contentSpacing;
}
}
private int contentConstraintCount = 0;
protected int ContentConstraintCount
{
get
{
if (contentConstraintCount > 0)
return contentConstraintCount;
contentConstraintCount = 1;
if (content != null)
{
GridLayoutGroup layout2 = content.GetComponent<GridLayoutGroup>();
if (layout2 != null)
{
if (layout2.constraint == GridLayoutGroup.Constraint.Flexible)
Debug.LogWarning("[LoopScrollRect] Flexible not supported yet");
contentConstraintCount = layout2.constraintCount;
}
}
return contentConstraintCount;
}
}
[SerializeField]
public Action<Vector2> onValueChanged;
RectTransform cacheRoot;//缓存root
bool initedCacheRoot;
List<CellObj> cachedCellList = new List<CellObj>();//缓存cellObj
private List<CellObj> allCells = new List<CellObj>();
private int objectIndex;
protected int curTopIndex = 0;
protected int curBottomIndex = 0;
protected int cellStartIndex = 0;
protected int cellEndIndex = 0;
protected int selectedIndex = 0;
public Action<GameObject> luaInstantiateCellAction;
public Action<int, int> luaRefreshAction;
public Action<bool, int> luaSetSelectedAction;
#endregion
#region ----------------------- Original Field -------------------------
public RectTransform content;
public bool horizontal = true;
public bool vertical = true;
public MovementType movementType = MovementType.Elastic;
public float elasticity = 0.1f; // Only used for MovementType.Elastic
public bool inertia = true;
public float decelerationRate = 0.135f; // Only used when inertia is enabled
public float scrollSensitivity = 1.0f;
public RectTransform viewport;
private bool scrolling = false;
// The offset from handle position to mouse down position
private Vector2 pointerStartLocalCursor = Vector2.zero;
private Vector2 contentStartPosition = Vector2.zero;
private Bounds contentBounds;
private Bounds viewBounds;
public Vector2 velocity;
private bool dragging;
private Vector2 prevPosition = Vector2.zero;
private Bounds prevContentBounds;
private Bounds prevViewBounds;
[NonSerialized]
private bool hasRebuiltLayout = false;
[NonSerialized]
private RectTransform rectTrans;
private RectTransform RectTrans
{
get
{
if (rectTrans == null)
rectTrans = GetComponent<RectTransform>();
return rectTrans;
}
}
private DrivenRectTransformTracker tracker;
#endregion
#region ----------------------- ScrollBar -------------------------
[SerializeField]
Scrollbar horizontalScrollbar;
public Scrollbar HorizontalScrollbar
{
get
{
return horizontalScrollbar;
}
set
{
if (horizontalScrollbar)
horizontalScrollbar.onValueChanged.RemoveListener(SetHorizontalNormalizedPosition);
horizontalScrollbar = value;
if (horizontalScrollbar)
horizontalScrollbar.onValueChanged.AddListener(SetHorizontalNormalizedPosition);
SetDirtyCaching();
}
}
[SerializeField]
Scrollbar verticalScrollbar;
public Scrollbar VerticalScrollbar
{
get
{
return verticalScrollbar;
}
set
{
if (verticalScrollbar)
verticalScrollbar.onValueChanged.RemoveListener(SetVerticalNormalizedPosition);
verticalScrollbar = value;
if (verticalScrollbar)
verticalScrollbar.onValueChanged.AddListener(SetVerticalNormalizedPosition);
SetDirtyCaching();
}
}
[SerializeField]
ScrollbarVisibility horizontalScrollbarVisibility;
public ScrollbarVisibility HorizontalScrollbarVisibility
{
get { return horizontalScrollbarVisibility; }
set { horizontalScrollbarVisibility = value; SetDirtyCaching(); }
}
[SerializeField]
ScrollbarVisibility verticalScrollbarVisibility;
public ScrollbarVisibility VerticalScrollbarVisibility
{
get { return verticalScrollbarVisibility; }
set { verticalScrollbarVisibility = value; SetDirtyCaching(); }
}
[SerializeField]
float horizontalScrollbarSpacing;
public float HorizontalScrollbarSpacing
{
get { return horizontalScrollbarSpacing; }
set { horizontalScrollbarSpacing = value; SetDirty(); }
}
[SerializeField]
float verticalScrollbarSpacing;
public float VerticalScrollbarSpacing
{
get { return verticalScrollbarSpacing; }
set { verticalScrollbarSpacing = value; SetDirty(); }
}
bool hSliderExpand;
bool vSliderExpand;
float hSliderHeight;
float vSliderWidth;
RectTransform horizontalScrollbarRect;
RectTransform verticalScrollbarRect;
#endregion
protected ScrollRectBaseOld()
{
flexibleWidth = -1;
}
protected override void OnEnable()
{
base.OnEnable();
if (useBar)
{
if (horizontalScrollbar)
horizontalScrollbar.onValueChanged.AddListener(SetHorizontalNormalizedPosition);
if (verticalScrollbar)
verticalScrollbar.onValueChanged.AddListener(SetVerticalNormalizedPosition);
}
CanvasUpdateRegistry.RegisterCanvasElementForLayoutRebuild(this);
}
protected override void OnDisable()
{
CanvasUpdateRegistry.UnRegisterCanvasElementForRebuild(this);
if (useBar)
{
if (horizontalScrollbar)
horizontalScrollbar.onValueChanged.RemoveListener(SetHorizontalNormalizedPosition);
if (verticalScrollbar)
verticalScrollbar.onValueChanged.RemoveListener(SetVerticalNormalizedPosition);
}
hasRebuiltLayout = false;
tracker.Clear();
velocity = Vector2.zero;
LayoutRebuilder.MarkLayoutForRebuild(RectTrans);
base.OnDisable();
}
protected override void Awake()
{
if (!initedCacheRoot)
InitCacheRoot();
}
void InitCacheRoot()
{
var cacheTransform = transform.Find(StringConst.CACHE_ROOT_NAME);
if (cacheTransform == null)
{
GameObject go = new GameObject(StringConst.CACHE_ROOT_NAME);
cacheRoot = go.AddComponent<RectTransform>();
cacheRoot.SetParent(transform);
cacheRoot.localPosition = Vector3.zero;
go.SetActive(false);
}
if (cacheRoot == null)
cacheRoot = cacheTransform.transform as RectTransform;
initedCacheRoot = true;
}
protected override void OnDestroy()
{
luaInstantiateCellAction = null;
}
void CacheCell(CellObj cell)
{
if (cell != null)
{
cell.gameObject.transform.SetParent(cacheRoot);
cachedCellList.Add(cell);
}
}
public void AddOnvalueChangedFunc(Action<Vector2> func)
{
onValueChanged = func;
}
public void AddOnInstantiateCellAction(Action<GameObject> action)
{
luaInstantiateCellAction = action;
}
public void AddRefreshAction(Action<int, int> action)
{
luaRefreshAction = action;
}
public void AddSetSelectedAction(Action<bool, int> action)
{
luaSetSelectedAction = action;
}
public void SetTotalCount(int count)
{
totalCount = count;
}
public int GetTotalCount()
{
return totalCount;
}
public void SetSelected(int index)
{
RefreshRealShowIndex();
selectedIndex = index;
int count = allCells.Count;
for (int i = 0; i < count; i++)
{
var cell = allCells[i];
luaSetSelectedAction?.Invoke(cell.index == index, cell.objectIndex);
}
}
public int GetSelectedIndex()
{
return selectedIndex;
}
public void AddCell()
{
RefreshRealShowIndex();
totalCount++;
if (IsNotFull())
{
float size = NewItemAtEnd();
while (size > 0 && IsNotFull())
{
size = NewItemAtEnd();
}
}
}
public virtual bool IsNotFull() { return false; }
public void RemoveCell(int index, bool alignFlag = true)
{
if (totalCount >= index + 1)
{
RefreshRealShowIndex();
if (index == selectedIndex)
{
selectedIndex = 0;
}
else
{
selectedIndex = Math.Max(0, index > selectedIndex ? selectedIndex : selectedIndex - 1);
}
cellStartIndex = index + 1 < cellStartIndex ? cellStartIndex - 1 : cellStartIndex;
if (cellEndIndex == totalCount)
{
totalCount--;
//已经滑到底端
DeleteItemAtEnd();
RefreshAll();
}
else
{
totalCount--;
RefreshAll();
}
}
}
public void ClearCells()
{
if (Application.isPlaying)
{
int count = cellEndIndex - cellStartIndex;
cellStartIndex = 0;
cellEndIndex = 0;
curTopIndex = 0;
curBottomIndex = 0;
selectedIndex = 0;
for (int i = count - 1; i >= 0; i--)
{
var gameObject = content.GetChild(i).gameObject;
CellObj cell = GetCell(gameObject);
if (cell != null)
{
CacheCell(cell);
}
}
}
}
protected CellObj GetCell(GameObject gameObject)
{
int count = allCells.Count;
for (int i = count - 1; i >= 0; i--)
{
if (allCells[i] != null && allCells[i].gameObject == gameObject)
{
return allCells[i];
}
}
return default;
}
public virtual void RefreshRealShowIndex()
{
curTopIndex = 0;
curBottomIndex = 0;
}
public void ScrollToCellImmediately(int index, bool alignFlag = true)
{
RefreshRealShowIndex();
if (alignFlag)
{
RefillCells(index);
}
else
{
RefillCellsFromEnd(index);
}
}
public void ScrollToCell(int index, float speed, bool alignFlag = true)
{
if (totalCount >= 0 && (index < 0 || index >= totalCount))
return;
if (speed <= 0)
return;
StopAllCoroutines();
StartCoroutine(ScrollToCellCoroutine(index, speed, alignFlag));
}
IEnumerator ScrollToCellCoroutine(int index, float speed, bool alignFlag = true)
{
bool needMoving = true;
while (needMoving)
{
yield return null;
if (!dragging)
{
float move = 0;
if (index < cellStartIndex)
move = -Time.deltaTime * speed;
else if (index >= cellEndIndex)
move = Time.deltaTime * speed;
else
{
viewBounds = new Bounds(viewport.rect.center, viewport.rect.size);
var m_ItemBounds = GetBounds4Item(index);
var offset = 0.0f;
if (directionSign == -1)
if (alignFlag)
{
offset = viewBounds.max.y - m_ItemBounds.max.y;
}
else
{
offset = viewBounds.max.y - m_ItemBounds.min.y;
}
else if (directionSign == 1)
if (alignFlag)
{
offset = m_ItemBounds.min.x - viewBounds.min.x;
}
else
{
offset = m_ItemBounds.min.x - viewBounds.max.x;
}
// check if we cannot move on
if (totalCount >= 0)
{
if (offset > 0 && cellEndIndex == totalCount && !alignFlag)
{
m_ItemBounds = GetBounds4Item(totalCount - 1);
// reach bottom
if ((directionSign == -1 && m_ItemBounds.min.y > viewBounds.min.y) ||
(directionSign == 1 && m_ItemBounds.max.x < viewBounds.max.x))
{
needMoving = false;
break;
}
}
else if (offset < 0 && cellStartIndex == 0 && alignFlag)
{
m_ItemBounds = GetBounds4Item(0);
if ((directionSign == -1 && m_ItemBounds.max.y < viewBounds.max.y) ||
(directionSign == 1 && m_ItemBounds.min.x > viewBounds.min.x))
{
needMoving = false;
break;
}
}
}
float maxMove = Time.deltaTime * speed;
if (Mathf.Abs(offset) < maxMove)
{
needMoving = false;
move = offset;
}
else
move = Mathf.Sign(offset) * maxMove;
}
if (Math.Abs(move) > 0)
{
Vector2 offset = GetVector(move);
content.anchoredPosition += offset;
prevPosition += offset;
contentStartPosition += offset;
}
}
}
StopMovement();
UpdatePrevData();
}
public void RefreshAll()
{
if (Application.isPlaying && this.isActiveAndEnabled)
{
for (var i = cellEndIndex - cellStartIndex - 1; i >= 0 ; i-- )
{
var gameObject = content.GetChild(i).gameObject;
CellObj cell = GetCell(gameObject);
cell.index = cellStartIndex + i;
luaRefreshAction?.Invoke(cell.index, cell.objectIndex);
}
}
}
public void RefillCellsFromEnd(int offset = 0)
{
StopMovement();
for (var i = cellEndIndex - cellStartIndex - 1; i >= 0 ; i-- )
{
var gameObject = content.GetChild(i).gameObject;
CacheCell(GetCell(gameObject));
}
cellEndIndex = totalCount - offset;
cellStartIndex = cellEndIndex;
curTopIndex = 0;
curBottomIndex = 0;
if (totalCount >= 0 && ContentConstraintCount != 1)
Debug.LogWarning("FromEnd ContentConstraintCount should be 1");
float sizeToFill = 0, sizeFilled = 0;
if (directionSign == -1)
sizeToFill = viewport.rect.size.y;
else
sizeToFill = viewport.rect.size.x;
bool sizeFlag = true;
while (sizeToFill > sizeFilled)
{
float size;
if (sizeFlag)
{
size = NewItemAtStart();
if (size <= 0) sizeFlag = false;
}
else
{
size = NewItemAtEnd();
if (size <= 0) break;
}
sizeFilled += size;
}
//溢出
Vector2 pos = content.anchoredPosition;
float dist = - Mathf.Max(0, sizeFilled - sizeToFill);
if (directionSign == -1)
pos.y = -dist;
else if (directionSign == 1)
pos.x = -dist;
content.anchoredPosition = pos;
}
public void RefiilCellsFromEndForFrame(int intervalFrame, int offset = 0)
{
if (gameObject.active)
{
StopMovement();
for (var i = cellEndIndex - cellStartIndex - 1; i >= 0 ; i-- )
{
var gameObject = content.GetChild(i).gameObject;
CacheCell(GetCell(gameObject));
}
cellEndIndex = totalCount - offset;
cellStartIndex = cellEndIndex;
curTopIndex = 0;
curBottomIndex = 0;
if (totalCount >= 0 && ContentConstraintCount != 1)
Debug.LogWarning("RefillFromEnd ContentConstraintCount should be 1");
StopCoroutine("RefillCellsFromEndForFrameCoroutine");
StartCoroutine("RefillCellsFromEndForFrameCoroutine", intervalFrame);
}
}
IEnumerator RefillCellsFromEndForFrameCoroutine(int intervaleFrame)
{
var waitForEndOfFatme = new WaitForEndOfFrame();
float sizeToFill = 0, sizeFilled = 0;
if (directionSign == -1)
sizeToFill = viewport.rect.size.y;
else
sizeToFill = viewport.rect.size.x;
bool sizeFlag = true;
while (sizeToFill > sizeFilled)
{
float size;
if (sizeFlag)
{
size = NewItemAtStart();
if (size <= 0) sizeFlag = false;
for (var i = 0; i < intervaleFrame; ++i)
{
yield return waitForEndOfFatme;
}
}
else
{
size = NewItemAtEnd();
if (size <= 0) break;
for (var i = 0; i < intervaleFrame; ++i)
{
yield return waitForEndOfFatme;
}
}
sizeFilled += size;
}
//溢出
Vector2 pos = content.anchoredPosition;
float dist = - Mathf.Max(0, sizeFilled - sizeToFill);
if (directionSign == -1)
pos.y = -dist;
else if (directionSign == 1)
pos.x = -dist;
content.anchoredPosition = pos;
}
public void RefillCells(int index = 0)
{
StopMovement();
for (var i = cellEndIndex - cellStartIndex - 1; i >= 0 ; i-- )
{
var gameObject = content.GetChild(i).gameObject;
CacheCell(GetCell(gameObject));
}
if (index > 0 && index > ContentConstraintCount)
{
var offset = index % ContentConstraintCount;
if (offset == 0)
{
cellStartIndex = index - ContentConstraintCount;
}
else
{
cellStartIndex = index - offset;
}
}
else
{
cellStartIndex = 0;
}
cellEndIndex = cellStartIndex;
curTopIndex = 0;
curBottomIndex = 0;
// Don't `Canvas.ForceUpdateCanvases();` here, or it will new/delete cells to change itemTypeStart/End
float sizeToFill = 0, sizeFilled = 0;
// m_ViewBounds may be not ready when RefillCells on Start
if (directionSign == -1)
sizeToFill = viewport.rect.size.y;
else
sizeToFill = viewport.rect.size.x;
bool sizeFlag = true;
while (sizeToFill > sizeFilled)
{
float size;
if (sizeFlag)
{
size = NewItemAtEnd();
if (size <= 0) sizeFlag = false;
}
else
{
size = NewItemAtStart();
if (size <= 0) break;
}
sizeFilled += size;
}
Vector2 pos = content.anchoredPosition;
if (directionSign == -1)
pos.y = 0;
else if (directionSign == 1)
pos.x = 0;
content.anchoredPosition = pos;
}
public void RefillCellsForFrame(int intervalFrame, int index = 0)
{
if (gameObject.active)
{
StopMovement();
for (var i = cellEndIndex - cellStartIndex - 1; i >= 0 ; i-- )
{
var gameObject = content.GetChild(i).gameObject;
CacheCell(GetCell(gameObject));
}
if (index > 0 && index > ContentConstraintCount)
{
var offset = index % ContentConstraintCount;
if (offset == 0)
{
cellStartIndex = index - ContentConstraintCount;
}
else
{
cellStartIndex = index - offset;
}
}
else
{
cellStartIndex = 0;
}
cellEndIndex = cellStartIndex;
curTopIndex = 0;
curBottomIndex = 0;
// Don't `Canvas.ForceUpdateCanvases();` here, or it will new/delete cells to change itemTypeStart/End
Vector2 pos = content.anchoredPosition;
if (directionSign == -1)
pos.y = 0;
else if (directionSign == 1)
pos.x = 0;
content.anchoredPosition = pos;
StopCoroutine("RefillCellsForFrameCoroutine");
StartCoroutine("RefillCellsForFrameCoroutine", intervalFrame);
}
}
IEnumerator RefillCellsForFrameCoroutine(int intervaleFrame)
{
var waitForEndOfFatme = new WaitForEndOfFrame();
float sizeToFill = 0, sizeFilled = 0;
// m_ViewBounds may be not ready when RefillCells on Start
if (directionSign == -1)
sizeToFill = viewport.rect.size.y;
else
sizeToFill = viewport.rect.size.x;
var sizeFlag = true;
while (sizeToFill > sizeFilled)
{
float size = 0;
if (sizeFlag)
{
if (totalCount >= 0 && cellEndIndex >= totalCount)
{
size = 0;
}
else
{
int count = ContentConstraintCount - (cellEndIndex - cellStartIndex) % ContentConstraintCount;
for (int i = 0; i < count; i++)
{
RectTransform newItem = InstantiateNextItem(cellEndIndex);
if (newItem == null)
{
break;
}
size = GetSize(newItem);
//size = Mathf.Max(GetSize(newItem), size);
cellEndIndex++;
if (totalCount >= 0 && cellEndIndex >= totalCount)
{
break;
}
for (var w = 0; w < intervaleFrame; ++w)
{
yield return waitForEndOfFatme;
}
}
endThreshold = size * 1.5f;
if (startThreshold < 0)
{
startThreshold = size * 1.5f;
}
}
if (size <= 0)
{
sizeFlag = false;
}
}
else
{
if (totalCount >= 0 && cellStartIndex - ContentConstraintCount < 0)
{
size = 0;
}
else
{
for (int i = 0; i < ContentConstraintCount; i++)
{
cellStartIndex--;
RectTransform newItem = InstantiateNextItem(cellStartIndex);
if (newItem == null)
{
size = 0;
}
newItem.SetAsFirstSibling();
size = GetSize(newItem);
//size = Mathf.Max(GetSize(newItem), size);
for (var w = 0; w < intervaleFrame; ++w)
{
yield return waitForEndOfFatme;
}
}
startThreshold = size * 1.5f;
if (endThreshold < 0)
{
endThreshold =size * 1.5f;
}
Vector2 offset = GetVector(size);
content.anchoredPosition += offset;
prevPosition += offset;
contentStartPosition += offset;
}
if (size <= 0)
{
break;
}
}
sizeFilled += size;
}
}
protected float NewItemAtStart()
{
if (totalCount >= 0 && cellStartIndex - ContentConstraintCount < 0)
return 0;
float size = 0;
for (int i = 0; i < ContentConstraintCount; i++)
{
cellStartIndex--;
RectTransform newItem = InstantiateNextItem(cellStartIndex);
if (newItem == null)
return 0;
newItem.SetAsFirstSibling();
size = GetSize(newItem);
//size = Mathf.Max(GetSize(newItem), size);
}
startThreshold = size * 1.5f;
if (endThreshold < 0)
{
endThreshold = size * 1.5f;
}
Vector2 offset = GetVector(size);
content.anchoredPosition += offset;
prevPosition += offset;
contentStartPosition += offset;
return size;
}
protected float DeleteItemAtStart()
{
// special case: when moving or dragging, we cannot simply delete start when we've reached the end
if (((dragging || velocity != Vector2.zero) && totalCount >= 0 && cellEndIndex >= totalCount - 1)
|| (cellEndIndex == 0 && cellStartIndex == 0))//content.childCount == 0)
return 0;
float size = 0;
for (int i = 0; i < ContentConstraintCount; i++)
{
RectTransform oldItem = content.GetChild(0) as RectTransform;
size = Mathf.Max(GetSize(oldItem), size);
CacheCell(GetCell(oldItem.gameObject));
cellStartIndex++;
if ((cellEndIndex == 0 && cellStartIndex == 0))//content.childCount == 0)
break;
}
Vector2 offset = GetVector(size);
content.anchoredPosition -= offset;
prevPosition -= offset;
contentStartPosition -= offset;
return size;
}
protected float NewItemAtEnd()
{
if (totalCount >= 0 && cellEndIndex >= totalCount)
return 0;
float size = 0;
// issue 4: fill lines to end first
//int count = ContentConstraintCount - (content.childCount % ContentConstraintCount);
int count = ContentConstraintCount - (cellEndIndex - cellStartIndex) % ContentConstraintCount;
for (int i = 0; i < count; i++)
{
RectTransform newItem = InstantiateNextItem(cellEndIndex);
if (newItem == null)
break;
size = GetSize(newItem);
//size = Mathf.Max(GetSize(newItem), size);
cellEndIndex++;
if (totalCount >= 0 && cellEndIndex >= totalCount)
{
break;
}
}
endThreshold = size * 1.5f;
if (startThreshold < 0)
{
startThreshold = size * 1.5f;
}
return size;
}
protected float DeleteItemAtEnd()
{
if (((dragging || velocity != Vector2.zero) && totalCount >= 0 && cellStartIndex < ContentConstraintCount)
|| (cellEndIndex == 0 && cellStartIndex == 0))//|| content.childCount == 0)
return 0;
float size = 0;
for (int i = 0; i < ContentConstraintCount; i++)
{
var index = cellEndIndex - cellStartIndex - 1;
if (index < 0 || index > content.childCount - 1)
{
Debug.LogWarning(string.Format("caijieLog try get child out of bounds index : {0}, childCount : {1}, totalCount : {2} ", index, content.childCount, totalCount));
break;
}
RectTransform oldItem = content.GetChild(index) as RectTransform;
size = Mathf.Max(GetSize(oldItem), size);
CacheCell(GetCell(oldItem.gameObject));
cellEndIndex--;
if (cellEndIndex % ContentConstraintCount == 0 || (cellEndIndex == 0 && cellStartIndex == 0) || totalCount >= 0 && cellEndIndex >= totalCount) //content.childCount == 0)
break; //just delete the whole row
}
return size;
}
RectTransform InstantiateNextItem(int index)
{
if (totalCount < index + 1)
{
return null;
}
RectTransform nextItemTrans = null;
CellObj nextCell = null;
if (cachedCellList.Count > 0)
{
int cachedCellIndex = cachedCellList.Count - 1;
nextItemTrans = cachedCellList[cachedCellIndex].gameObject.transform as RectTransform;
nextCell = cachedCellList[cachedCellIndex];
cachedCellList.RemoveAt(cachedCellIndex);
nextItemTrans.transform.SetParent(content, false);
nextItemTrans.gameObject.SetActive(true);
}else{
nextItemTrans = Instantiate(bfCell.CachedRectTransform);
nextItemTrans.SetParent(content, false);
nextItemTrans.gameObject.SetActive(true);
nextCell = new CellObj();
objectIndex++;
nextCell.objectIndex = objectIndex;
nextCell.gameObject = nextItemTrans.gameObject;
luaInstantiateCellAction?.Invoke(nextCell.gameObject);
}
nextCell.index = index;
allCells.Add(nextCell);
luaRefreshAction?.Invoke(index, nextCell.objectIndex);
return nextItemTrans;
}
public virtual void Rebuild(CanvasUpdate executing)
{
if (executing == CanvasUpdate.Prelayout)
if (useBar)
{
UpdateCachedData();
}
if (executing == CanvasUpdate.PostLayout)
{
UpdateBounds();
if (useBar)
{
UpdateScrollbars(Vector2.zero);
}
UpdatePrevData();
hasRebuiltLayout = true;
}
}
public virtual void LayoutComplete() { }
public virtual void GraphicUpdateComplete() { }
void UpdateCachedData()
{
if (!useBar)
{
return;
}
horizontalScrollbarRect = horizontalScrollbar == null ? null : horizontalScrollbar.transform as RectTransform;
verticalScrollbarRect = verticalScrollbar == null ? null : verticalScrollbar.transform as RectTransform;
// These are true if either the elements are children, or they don't exist at all.
bool viewIsChild = (viewport.parent == transform);
bool hScrollbarIsChild = (!horizontalScrollbarRect || horizontalScrollbarRect.parent == transform);
bool vScrollbarIsChild = (!verticalScrollbarRect || verticalScrollbarRect.parent == transform);
bool allAreChildren = (viewIsChild && hScrollbarIsChild && vScrollbarIsChild);
hSliderExpand = allAreChildren && horizontalScrollbarRect && HorizontalScrollbarVisibility == ScrollbarVisibility.AutoHideAndExpandViewport;
vSliderExpand = allAreChildren && verticalScrollbarRect && VerticalScrollbarVisibility == ScrollbarVisibility.AutoHideAndExpandViewport;
hSliderHeight = (horizontalScrollbarRect == null ? 0 : horizontalScrollbarRect.rect.height);
vSliderWidth = (verticalScrollbarRect == null ? 0 : verticalScrollbarRect.rect.width);
}
public override bool IsActive()
{
return base.IsActive() && content != null;
}
void EnsureLayoutHasRebuilt()
{
if (!hasRebuiltLayout && !CanvasUpdateRegistry.IsRebuildingLayout())
Canvas.ForceUpdateCanvases();
}
public virtual void StopMovement()
{
velocity = Vector2.zero;
}
public virtual void OnScroll(PointerEventData data)
{
if (!IsActive())
return;
EnsureLayoutHasRebuilt();
UpdateBounds();
Vector2 delta = data.scrollDelta;
// Down is positive for scroll events, while in UI system up is positive.
delta.y *= -1;
if (vertical && !horizontal)
{
if (Mathf.Abs(delta.x) > Mathf.Abs(delta.y))
delta.y = delta.x;
delta.x = 0;
}
if (horizontal && !vertical)
{
if (Mathf.Abs(delta.y) > Mathf.Abs(delta.x))
delta.x = delta.y;
delta.y = 0;
}
//2018new
if (data.IsScrolling())
scrolling = true;
Vector2 position = content.anchoredPosition;
position += delta * scrollSensitivity;
if (movementType == MovementType.Clamped)
position += CalculateOffset(position - content.anchoredPosition);
SetContentAnchoredPosition(position);
UpdateBounds();
}
public virtual void OnInitializePotentialDrag(PointerEventData eventData)
{
if (eventData.button != PointerEventData.InputButton.Left)
return;
velocity = Vector2.zero;
}
public virtual void OnBeginDrag(PointerEventData eventData)
{
if (eventData.button != PointerEventData.InputButton.Left)
return;
if (!IsActive())
return;
UpdateBounds();
pointerStartLocalCursor = Vector2.zero;
RectTransformUtility.ScreenPointToLocalPointInRectangle(viewport, eventData.position, eventData.pressEventCamera, out pointerStartLocalCursor);
contentStartPosition = content.anchoredPosition;
dragging = true;
}
public virtual void OnEndDrag(PointerEventData eventData)
{
if (eventData.button != PointerEventData.InputButton.Left)
return;
dragging = false;
}
public virtual void OnDrag(PointerEventData eventData)
{
if (eventData.button != PointerEventData.InputButton.Left)
return;
if (!IsActive())
return;
Vector2 localCursor;
if (!RectTransformUtility.ScreenPointToLocalPointInRectangle(viewport, eventData.position, eventData.pressEventCamera, out localCursor))
return;
UpdateBounds();
var pointerDelta = localCursor - pointerStartLocalCursor;
Vector2 position = contentStartPosition + pointerDelta;
// Offset to get content into place in the view.
Vector2 offset = CalculateOffset(position - content.anchoredPosition);
position += offset;
if (movementType == MovementType.Elastic)
{
if (offset.x != 0)
position.x = position.x - RubberDelta(offset.x, viewBounds.size.x) * rubberScale;
if (offset.y != 0)
position.y = position.y - RubberDelta(offset.y, viewBounds.size.y) * rubberScale;
}
SetContentAnchoredPosition(position);
}
protected virtual void SetContentAnchoredPosition(Vector2 position)
{
if (!horizontal)
position.x = content.anchoredPosition.x;
if (!vertical)
position.y = content.anchoredPosition.y;
if (position != content.anchoredPosition)
{
content.anchoredPosition = position;
UpdateBounds(true);
}
}
protected virtual void LateUpdate()
{
if (!content)
return;
EnsureLayoutHasRebuilt();
UpdateBounds();
float deltaTime = Time.unscaledDeltaTime;
Vector2 offset = CalculateOffset(Vector2.zero);
if (!dragging && (offset != Vector2.zero || velocity != Vector2.zero))
{
Vector2 position = content.anchoredPosition;
for (int axis = 0; axis < 2; axis++)
{
// Apply spring physics if movement is elastic and content has an offset from the view.
if (movementType == MovementType.Elastic && offset[axis] != 0)
{
float speed = velocity[axis];
//2018new
var smoothTime = elasticity;
// if (scrolling)
// {
// smoothTime *= 3.0f;
// }
position[axis] = Mathf.SmoothDamp(content.anchoredPosition[axis], content.anchoredPosition[axis] + offset[axis], ref speed, smoothTime, Mathf.Infinity, deltaTime);
if (Mathf.Abs(speed) < 1)
{
speed = 0;
}
velocity[axis] = speed;
}
// Else move content according to velocity with deceleration applied.
else if (inertia)
{
velocity[axis] *= Mathf.Pow(decelerationRate, deltaTime);
if (Mathf.Abs(velocity[axis]) < 1)
velocity[axis] = 0;
position[axis] += velocity[axis] * deltaTime;
}
// If we have neither elaticity or friction, there shouldn't be any velocity.
else
velocity[axis] = 0;
}
if (velocity != Vector2.zero)
{
if (movementType == MovementType.Clamped)
{
offset = CalculateOffset(position - content.anchoredPosition);
position += offset;
}
SetContentAnchoredPosition(position);
}
}
if (dragging && inertia)
{
Vector3 newVelocity = (content.anchoredPosition - prevPosition) / deltaTime;
velocity = Vector3.Lerp(velocity, newVelocity, deltaTime * 10);
}
if (viewBounds != prevViewBounds || contentBounds != prevContentBounds || content.anchoredPosition != prevPosition)
{
if (useBar)
{
UpdateScrollbars(offset);
}
onValueChanged?.Invoke(NormalizedPosition);
UpdatePrevData();
}
//2018new
if (useBar)
{
UpdateScrollbarVisibility();
}
scrolling = false;
hasRebuiltLayout = false;
}
void UpdatePrevData()
{
if (content == null)
prevPosition = Vector2.zero;
else
prevPosition = content.anchoredPosition;
prevViewBounds = viewBounds;
prevContentBounds = contentBounds;
}
void UpdateScrollbars(Vector2 offset)
{
if (horizontalScrollbar)
{
if (contentBounds.size.x > 0 && totalCount > 0)
horizontalScrollbar.size = Mathf.Clamp01((viewBounds.size.x - Mathf.Abs(offset.x)) / contentBounds.size.x * (cellEndIndex - cellStartIndex) / totalCount);
else
horizontalScrollbar.size = 1;
horizontalScrollbar.value = HorizontalNormalizedPosition;
}
if (verticalScrollbar)
{
if (contentBounds.size.y > 0 && totalCount > 0)
verticalScrollbar.size = Mathf.Clamp01((viewBounds.size.y - Mathf.Abs(offset.y)) / contentBounds.size.y * (cellEndIndex - cellStartIndex) / totalCount);
else
verticalScrollbar.size = 1;
verticalScrollbar.value = VerticalNormalizedPosition;
}
}
public Vector2 NormalizedPosition
{
get
{
return new Vector2(HorizontalNormalizedPosition, VerticalNormalizedPosition);
}
set
{
SetNormalizedPosition(value.x, 0);
SetNormalizedPosition(value.y, 1);
}
}
public float HorizontalNormalizedPosition
{
get
{
UpdateBounds();
if (totalCount > 0 && cellEndIndex > cellStartIndex)
{
//TODO: consider contentSpacing
float elementSize = contentBounds.size.x / (cellEndIndex - cellStartIndex);
float totalSize = elementSize * totalCount;
float offset = contentBounds.min.x - elementSize * cellStartIndex;
if (totalSize <= viewBounds.size.x)
return (viewBounds.min.x > offset) ? 1 : 0;
return (viewBounds.min.x - offset) / (totalSize - viewBounds.size.x);
}
else
return 0.5f;
}
set
{
SetNormalizedPosition(value, 0);
}
}
public float VerticalNormalizedPosition
{
get
{
UpdateBounds();
if (totalCount > 0 && cellEndIndex > cellStartIndex)
{
//TODO: consider contentSpacinge
float elementSize = contentBounds.size.y / (cellEndIndex - cellStartIndex);
float totalSize = elementSize * totalCount;
float offset = contentBounds.max.y + elementSize * cellStartIndex;
if (totalSize <= viewBounds.size.y)
return (offset > viewBounds.max.y) ? 1 : 0;
return (offset - viewBounds.max.y) / (totalSize - viewBounds.size.y);
}
else
return 0.5f;
}
set
{
SetNormalizedPosition(value, 1);
}
}
void SetHorizontalNormalizedPosition(float value)
{
SetNormalizedPosition(value, 0);
}
void SetVerticalNormalizedPosition(float value)
{
SetNormalizedPosition(value, 1);
}
void SetNormalizedPosition(float value, int axis)
{
if (totalCount <= 0 || cellEndIndex <= cellStartIndex)
return;
EnsureLayoutHasRebuilt();
UpdateBounds();
Vector3 localPosition = content.localPosition;
float newLocalPosition = localPosition[axis];
if (axis == 0)
{
float elementSize = contentBounds.size.x / (cellEndIndex - cellStartIndex);
float totalSize = elementSize * totalCount;
float offset = contentBounds.min.x - elementSize * cellStartIndex;
newLocalPosition += viewBounds.min.x - value * (totalSize - viewBounds.size[axis]) - offset;
}
else if (axis == 1)
{
float elementSize = contentBounds.size.y / (cellEndIndex - cellStartIndex);
float totalSize = elementSize * totalCount;
float offset = contentBounds.max.y + elementSize * cellStartIndex;
newLocalPosition -= offset - value * (totalSize - viewBounds.size.y) - viewBounds.max.y;
}
if (Mathf.Abs(localPosition[axis] - newLocalPosition) > 0.01f)
{
localPosition[axis] = newLocalPosition;
content.localPosition = localPosition;
velocity[axis] = 0;
UpdateBounds(true);
}
}
static float RubberDelta(float overStretching, float viewSize)
{
return (1 - (1 / ((Mathf.Abs(overStretching) * 0.55f / viewSize) + 1))) * viewSize * Mathf.Sign(overStretching);
}
protected override void OnRectTransformDimensionsChange()
{
SetDirty();
}
bool hScrollingNeeded
{
get
{
if (Application.isPlaying)
return contentBounds.size.x > viewBounds.size.x + 0.01f;
return true;
}
}
bool vScrollingNeeded
{
get
{
if (Application.isPlaying)
return contentBounds.size.y > viewBounds.size.y + 0.01f;
return true;
}
}
public virtual void CalculateLayoutInputHorizontal() { }
public virtual void CalculateLayoutInputVertical() { }
public virtual float minWidth { get { return -1; } }
public virtual float preferredWidth { get { return -1; } }
public virtual float flexibleWidth { get; private set; }
public virtual float minHeight { get { return -1; } }
public virtual float preferredHeight { get { return -1; } }
public virtual float flexibleHeight { get { return -1; } }
public virtual int layoutPriority { get { return -1; } }
public virtual void SetLayoutHorizontal()
{
tracker.Clear();
if (hSliderExpand || vSliderExpand)
{
tracker.Add(this, viewport,
DrivenTransformProperties.Anchors |
DrivenTransformProperties.SizeDelta |
DrivenTransformProperties.AnchoredPosition);
// Make view full size to see if content fits.
viewport.anchorMin = Vector2.zero;
viewport.anchorMax = Vector2.one;
viewport.sizeDelta = Vector2.zero;
viewport.anchoredPosition = Vector2.zero;
// Recalculate content layout with this size to see if it fits when there are no scrollbars.
LayoutRebuilder.ForceRebuildLayoutImmediate(content);
viewBounds = new Bounds(viewport.rect.center, viewport.rect.size);
contentBounds = GetBounds();
}
// If it doesn't fit vertically, enable vertical scrollbar and shrink view horizontally to make room for it.
if (vSliderExpand && vScrollingNeeded)
{
viewport.sizeDelta = new Vector2(-(vSliderWidth + verticalScrollbarSpacing), viewport.sizeDelta.y);
// Recalculate content layout with this size to see if it fits vertically
// when there is a vertical scrollbar (which may reflowed the content to make it taller).
LayoutRebuilder.ForceRebuildLayoutImmediate(content);
viewBounds = new Bounds(viewport.rect.center, viewport.rect.size);
contentBounds = GetBounds();
}
// If it doesn't fit horizontally, enable horizontal scrollbar and shrink view vertically to make room for it.
if (hSliderExpand && hScrollingNeeded)
{
viewport.sizeDelta = new Vector2(viewport.sizeDelta.x, -(hSliderHeight + horizontalScrollbarSpacing));
viewBounds = new Bounds(viewport.rect.center, viewport.rect.size);
contentBounds = GetBounds();
}
// If the vertical slider didn't kick in the first time, and the horizontal one did,
// we need to check again if the vertical slider now needs to kick in.
// If it doesn't fit vertically, enable vertical scrollbar and shrink view horizontally to make room for it.
if (vSliderExpand && vScrollingNeeded && viewport.sizeDelta.x == 0 && viewport.sizeDelta.y < 0)
viewport.sizeDelta = new Vector2(-(vSliderWidth + verticalScrollbarSpacing), viewport.sizeDelta.y);
}
public virtual void SetLayoutVertical()
{
if (useBar)
{
UpdateScrollbarLayout();
}
viewBounds = new Bounds(viewport.rect.center, viewport.rect.size);
contentBounds = GetBounds();
}
void UpdateScrollbarVisibility()
{
if (verticalScrollbar && verticalScrollbarVisibility != ScrollbarVisibility.Permanent && verticalScrollbar.gameObject.activeSelf != vScrollingNeeded)
verticalScrollbar.gameObject.SetActive(vScrollingNeeded);
if (horizontalScrollbar && horizontalScrollbarVisibility != ScrollbarVisibility.Permanent && horizontalScrollbar.gameObject.activeSelf != hScrollingNeeded)
horizontalScrollbar.gameObject.SetActive(hScrollingNeeded);
}
void UpdateScrollbarLayout()
{
if (!useBar)
{
return;
}
if (vSliderExpand && horizontalScrollbar)
{
tracker.Add(this, horizontalScrollbarRect,
DrivenTransformProperties.AnchorMinX |
DrivenTransformProperties.AnchorMaxX |
DrivenTransformProperties.SizeDeltaX |
DrivenTransformProperties.AnchoredPositionX);
horizontalScrollbarRect.anchorMin = new Vector2(0, horizontalScrollbarRect.anchorMin.y);
horizontalScrollbarRect.anchorMax = new Vector2(1, horizontalScrollbarRect.anchorMax.y);
horizontalScrollbarRect.anchoredPosition = new Vector2(0, horizontalScrollbarRect.anchoredPosition.y);
if (vScrollingNeeded)
horizontalScrollbarRect.sizeDelta = new Vector2(-(vSliderWidth + verticalScrollbarSpacing), horizontalScrollbarRect.sizeDelta.y);
else
horizontalScrollbarRect.sizeDelta = new Vector2(0, horizontalScrollbarRect.sizeDelta.y);
}
if (hSliderExpand && verticalScrollbar)
{
tracker.Add(this, verticalScrollbarRect,
DrivenTransformProperties.AnchorMinY |
DrivenTransformProperties.AnchorMaxY |
DrivenTransformProperties.SizeDeltaY |
DrivenTransformProperties.AnchoredPositionY);
verticalScrollbarRect.anchorMin = new Vector2(verticalScrollbarRect.anchorMin.x, 0);
verticalScrollbarRect.anchorMax = new Vector2(verticalScrollbarRect.anchorMax.x, 1);
verticalScrollbarRect.anchoredPosition = new Vector2(verticalScrollbarRect.anchoredPosition.x, 0);
if (hScrollingNeeded)
verticalScrollbarRect.sizeDelta = new Vector2(verticalScrollbarRect.sizeDelta.x, -(hSliderHeight + horizontalScrollbarSpacing));
else
verticalScrollbarRect.sizeDelta = new Vector2(verticalScrollbarRect.sizeDelta.x, 0);
}
}
void UpdateBounds(bool updateItems = false)
{
viewBounds = new Bounds(viewport.rect.center, viewport.rect.size);
contentBounds = GetBounds();
if (content == null)
return;
// Don't do this in Rebuild
if (Application.isPlaying && updateItems && UpdateItems(viewBounds, contentBounds))
{
Canvas.ForceUpdateCanvases();
contentBounds = GetBounds();
}
// Make sure content bounds arAdjustBoundse at least as large as view by adding padding if not.
// One might think at first that if the content is smaller than the view, scrolling should be allowed.
// However, that's not how scroll views normally work.
// Scrolling is *only* possible when content is *larger* than view.
// We use the pivot of the content rect to decide in which directions the content bounds should be expanded.
// E.g. if pivot is at top, bounds are expanded downwards.
// This also works nicely when ContentSizeFitter is used on the content.
Vector3 contentSize = contentBounds.size;
Vector3 contentPos = contentBounds.center;
Vector3 excess = viewBounds.size - contentSize;
if (excess.x > 0)
{
contentPos.x -= excess.x * (content.pivot.x - 0.5f);
contentSize.x = viewBounds.size.x;
}
if (excess.y > 0)
{
contentPos.y -= excess.y * (content.pivot.y - 0.5f);
contentSize.y = viewBounds.size.y;
}
contentBounds.size = contentSize;
contentBounds.center = contentPos;
}
protected virtual bool UpdateItems(Bounds viewBounds, Bounds contentBounds)
{
return false;
}
readonly Vector3[] m_Corners = new Vector3[4];
Bounds GetBounds()
{
if (content == null)
return new Bounds();
var vMin = new Vector3(float.MaxValue, float.MaxValue, float.MaxValue);
var vMax = new Vector3(float.MinValue, float.MinValue, float.MinValue);
var toLocal = viewport.worldToLocalMatrix;
content.GetWorldCorners(m_Corners);
for (int j = 0; j < 4; j++)
{
Vector3 v = toLocal.MultiplyPoint3x4(m_Corners[j]);
vMin = Vector3.Min(v, vMin);
vMax = Vector3.Max(v, vMax);
}
var bounds = new Bounds(vMin, Vector3.zero);
bounds.Encapsulate(vMax);
return bounds;
}
Bounds GetBounds4Item(int index)
{
if (content == null)
return new Bounds();
var vMin = new Vector3(float.MaxValue, float.MaxValue, float.MaxValue);
var vMax = new Vector3(float.MinValue, float.MinValue, float.MinValue);
var toLocal = viewport.worldToLocalMatrix;
int offset = index - cellStartIndex;
if (offset < 0 || offset >= cellEndIndex - cellStartIndex)//content.childCount)
return new Bounds();
RectTransform rectTransfrom = content.GetChild(offset) as RectTransform;
if (rectTransfrom == null)
return new Bounds();
rectTransfrom.GetWorldCorners(m_Corners);
for (int j = 0; j < 4; j++)
{
Vector3 v = toLocal.MultiplyPoint3x4(m_Corners[j]);
vMin = Vector3.Min(v, vMin);
vMax = Vector3.Max(v, vMax);
}
var bounds = new Bounds(vMin, Vector3.zero);
bounds.Encapsulate(vMax);
return bounds;
}
protected Bounds GetCellBounds(int index)
{
if (content == null)
return new Bounds();
var vMin = new Vector3(float.MaxValue, float.MaxValue, float.MaxValue);
var vMax = new Vector3(float.MinValue, float.MinValue, float.MinValue);
var toLocal = viewport.worldToLocalMatrix;
var rt = content.GetChild(index) as RectTransform;
if (rt == null)
return new Bounds();
rt.GetWorldCorners(m_Corners);
for (int j = 0; j < 4; j++)
{
Vector3 v = toLocal.MultiplyPoint3x4(m_Corners[j]);
vMin = Vector3.Min(v, vMin);
vMax = Vector3.Max(v, vMax);
}
var bounds = new Bounds(vMin, Vector3.zero);
bounds.Encapsulate(vMax);
return bounds;
}
Vector2 CalculateOffset(Vector2 delta)
{
Vector2 offset = Vector2.zero;
if (movementType == MovementType.Unrestricted)
return offset;
Vector2 min = contentBounds.min;
Vector2 max = contentBounds.max;
if (horizontal)
{
min.x += delta.x;
max.x += delta.x;
if (min.x > viewBounds.min.x)
offset.x = viewBounds.min.x - min.x;
else if (max.x < viewBounds.max.x)
offset.x = viewBounds.max.x - max.x;
}
if (vertical)
{
min.y += delta.y;
max.y += delta.y;
if (max.y < viewBounds.max.y)
offset.y = viewBounds.max.y - max.y;
else if (min.y > viewBounds.min.y)
offset.y = viewBounds.min.y - min.y;
}
return offset;
}
protected void SetDirty()
{
if (!IsActive())
return;
LayoutRebuilder.MarkLayoutForRebuild(RectTrans);
}
protected void SetDirtyCaching()
{
if (!IsActive())
return;
CanvasUpdateRegistry.RegisterCanvasElementForLayoutRebuild(this);
LayoutRebuilder.MarkLayoutForRebuild(RectTrans);
}
#if UNITY_EDITOR
protected override void OnValidate()
{
SetDirtyCaching();
}
#endif
}
}