using UnityEngine; using UnityEngine.UI; using System.Collections; using System; using System.Collections.Generic; namespace EnhancedUI.EnhancedScroller { /// /// This delegate handles the visibility changes of cell views /// /// The cell view that changed visibility public delegate void CellViewVisibilityChangedDelegate(EnhancedScrollerCellView cellView); //委托 当可见性改变时,传入的是当前改变的CellView对象(对象继承自EnhancedScrollerCellView) 事件在 AddCellView和_RecycleCell中触发 /// /// This delegate handles the scrolling callback of the ScrollRect. /// /// The scroller that called the delegate /// The scroll value of the scroll rect /// The scroll position in pixels from the start of the scroller public delegate void ScrollerScrolledDelegate(EnhancedScroller scroller, Vector2 val, float scrollPosition); //委托 当scroll valuechange 时,在onvaluechange事件中触发 /// /// This delegate handles the snapping of the scroller. /// /// The scroller that called the delegate /// The index of the cell view snapped on (this may be different than the data index in case of looping) /// The index of the data the view snapped on public delegate void ScrollerSnappedDelegate(EnhancedScroller scroller, int cellIndex, int dataIndex); //委托 当snap完成时, 在SnapJumpComplete时触发(当开启snap时,每次滑动后都会去进行定位,当定位后就会调用SnapJumpComplete) /// /// This delegate handles the change in state of the scroller (scrolling or not scrolling) /// /// The scroller that changed state /// Whether or not the scroller is scrolling public delegate void ScrollerScrollingChangedDelegate(EnhancedScroller scroller, bool scrolling); //委托 当scroll.velocity开始不为0 或为0 时 ,在Update中触发(即当开始滑动时或当停止滑动时触发,以velocity为基准,为0和不为0时分别触发一次) /// /// This delegate handles the change in state of the scroller (jumping or not jumping) /// /// The scroller that changed state /// Whether or not the scroller is tweening public delegate void ScrollerTweeningChangedDelegate(EnhancedScroller scroller, bool tweening); //委托 当开始播放Tween和停止Tween时调用 ,在JumpToDataIndex中如果使用Tween进行过渡定位时,会在开始播放时和结束时各触发一次 /// /// The EnhancedScroller allows you to easily set up a dynamic scroller that will recycle views for you. This means /// that using only a handful of views, you can display thousands of rows. This will save memory and processing /// power in your application. /// [RequireComponent(typeof(ScrollRect))] public class EnhancedScroller : MonoBehaviour { #region Public /// /// The direction this scroller is handling /// public enum ScrollDirectionEnum//滑动方式 { Vertical, Horizontal } /// /// Which side of a cell to reference. /// For vertical scrollers, before means above, after means below. /// For horizontal scrollers, before means to left of, after means to the right of. /// public enum CellViewPositionEnum//当前CellViewPrefab的上方还是下方 { Before, After } /// /// This will set how the scroll bar should be shown based on the data. If no scrollbar /// is attached, then this is ignored. OnlyIfNeeded will hide the scrollbar based on whether /// the scroller is looping or there aren't enough items to scroll. /// public enum ScrollbarVisibilityEnum//是否有滑动条,在looping模式下是不会显示的 { OnlyIfNeeded, Always, Never } /// /// The direction the scroller is handling /// public ScrollDirectionEnum scrollDirection; /// /// The number of pixels between cell views, starting after the first cell view /// public float spacing; /// /// The padding inside of the scroller: top, bottom, left, right. /// public RectOffset padding; /// /// Whether the scroller should loop the cell views /// [SerializeField] private bool loop; /// /// Whether the scollbar should be shown /// [SerializeField] private ScrollbarVisibilityEnum scrollbarVisibility; /// /// Whether snapping is turned on /// public bool snapping;//是否开启定位 /// /// This is the speed that will initiate the snap. When the /// scroller slows down to this speed it will snap to the location /// specified. /// public float snapVelocityThreshold;//检测定位的阀值,当低于这个值的时候就会触发定位 /// /// The snap offset to watch for. When the snap occurs, this /// location in the scroller will be how which cell to snap to /// is determined. /// Typically, the offset is in the range 0..1, with 0 being /// the top / left of the scroller and 1 being the bottom / right. /// In most situations the watch offset and the jump offset /// will be the same, they are just separated in case you need /// that added functionality. /// public float snapWatchOffset;//定位后当前CellVeiw在可视区域内偏移,当为0时定位在最上方,为1时定位在最下方 /// /// The snap location to move the cell to. When the snap occurs, /// this location in the scroller will be where the snapped cell /// is moved to. /// Typically, the offset is in the range 0..1, with 0 being /// the top / left of the scroller and 1 being the bottom / right. /// In most situations the watch offset and the jump offset /// will be the same, they are just separated in case you need /// that added functionality. /// public float snapJumpToOffset;//设置定位的偏移,实质上结果和snapWatchOffset一样,也是在0~1之间,增加这个属性主要是考虑到可能会出现一些特殊情况要使用到它 /// /// Once the cell has been snapped to the scroller location, this /// value will determine how the cell is centered on that scroller /// location. /// Typically, the offset is in the range 0..1, with 0 being /// the top / left of the cell and 1 being the bottom / right. /// public float snapCellCenterOffset;//设置被定位的CellVeiw定位后的中心偏移,0~1之间取值,如果默认的话会以它的上部为基准,不般来说应该设置为0.5,定位到正中心 /// /// Whether to include the spacing between cells when determining the /// cell offset centering. /// public bool snapUseCellSpacing;//开启这个属性的时候,计算定位后的偏移将包括它的上下两个间距,否则即使有间距也不会纳入计算范围内 /// /// What function to use when interpolating between the current /// scroll position and the snap location. This is also known as easing. /// If you want to go immediately to the snap location you can either /// set the snapTweenType to immediate or set the snapTweenTime to zero. /// public TweenType snapTweenType;//定位时播放动画的类型 /// /// The time it takes to interpolate between the current scroll /// position and the snap location. /// If you want to go immediately to the snap location you can either /// set the snapTweenType to immediate or set the snapTweenTime to zero. /// public float snapTweenTime;//播放动画的总时长 public Action OnCompLoad; /// /// This delegate is called when a cell view is hidden or shown /// public CellViewVisibilityChangedDelegate cellViewVisibilityChanged; /// /// This delegate is called when the scroll rect scrolls /// public ScrollerScrolledDelegate scrollerScrolled; /// /// This delegate is called when the scroller has snapped to a position /// public ScrollerSnappedDelegate scrollerSnapped; /// /// This delegate is called when the scroller has started or stopped scrolling /// public ScrollerScrollingChangedDelegate scrollerScrollingChanged; /// /// This delegate is called when the scroller has started or stopped tweening /// public ScrollerTweeningChangedDelegate scrollerTweeningChanged; /// /// The Delegate is what the scroller will call when it needs to know information about /// the underlying data or views. This allows a true MVC process. /// public IEnhancedScrollerDelegate Delegate { get { return _delegate; } set { _delegate = value; _reloadData = true; } } /*这里要将一个实现了IEnhancedScrollerDelegate接口的类的对象赋值过来,这个对象用于控制整个Scroll,通过实现这个接口的几个方法 * 可以定义: 1. 当前CellVeiw的总数目 * * 2. 某dataIndex对应的CellView元素的size * * 3.如何获取(从循环池里取,或实例化)一个CellView * 并得到其CellView对象,而这个对象又继承自EnhancedScrollerCellView, * 所以实际上获取了这个CellView的 : * 1.cellIdentifier(scroll存在不同Prefab时用于区分的字符串) * 2.cellIndex dataIndex (dataIndex代表的是要循环的cellview元素索引,而cellIndex代表的是集合内元素的索引 * 当没有开启循环模式的时候,集合和元素是一致的,它们并没有什么不同,都能对应上相应的cellview元素 * ,但当开启循环后,集合会被扩充(比如扩充3倍),此时dataIndex的整个周期才是真正对应cellview元素的整个周期) * 3. active 是否是激活状态 * 4.提供一个虚方法供子类重写用来实现一些具体的功能 * 5.通常这个对象本身也会提供一个方法用于对其进行初始化(比如Start()或者直接提供一个公有方法供外部主动调用) * *这个对象是整个scroll的核心,通过实现它继承的接口,定义了scroll下CellView的总数目,每个CellView的size, * 通过调用 EnhancedScroller 中的 ReloadData()方法来初始化整个Scroll * 通过实现GetCellView方法,可以调用EnhancedScrolelr类下的GetCellView()方法,传入相应的CellView Prefab,通过定义的cellIdentifier * 来从循环池里取对应的CellVeiw,如果没找到则实例化一个,而得到的这个对象又包含其相关的属性,索引和初始化的方法 * 通过声明一系列的事件可以绑定EnhancedScroller中的委托用于各类事件的触发 * */ /// /// The absolute position in pixels from the start of the scroller /// public float ScrollPosition { get { return _scrollPosition; } set { // make sure the position is in the bounds of the current set of views value = Mathf.Clamp(value, 0, GetScrollPositionForCellViewIndex(_cellViewSizeArray.Count - 1, CellViewPositionEnum.Before)); //将传入的Value的大小限制在整个_cellViewSizeArray集合内(注意是最后一个元素的上方,但是这个集合的长度也可能是复制过后的) // only if the value has changed if (_scrollPosition != value) { _scrollPosition = value; if (scrollDirection == ScrollDirectionEnum.Vertical) { // set the vertical position scrollRect.verticalNormalizedPosition = 1f - (_scrollPosition / _ScrollSize); //这里要注意,如果集合进行过复制,那么这里的_scrollSize也一样会比原来的增加,因为_ScrollSize永远比content的总大小小一个可视区大小 } else { // set the horizontal position scrollRect.horizontalNormalizedPosition = (_scrollPosition / _ScrollSize); } // flag that we need to refresh _refreshActive = true; } } } /// /// Whether the scroller should loop the resulting cell views. /// Looping creates three sets of internal size data, attempting /// to keep the scroller in the middle set. If the scroller goes /// outside of this set, it will jump back into the middle set, /// giving the illusion of an infinite set of data. /// public bool Loop { get { return loop; } set { // only if the value has changed if (loop != value)//如果相关属性发生了变化 { // get the original position so that when we turn looping on // we can jump back to this position var originalScrollPosition = _scrollPosition; loop = value; // call resize to generate more internal elements if loop is on, // remove the elements if loop is off _Resize(false); if (loop) { // set the new scroll position based on the middle set of data + the original position ScrollPosition = _loopFirstScrollPosition + originalScrollPosition; } else { // set the new scroll position based on the original position and the first loop position ScrollPosition = originalScrollPosition - _loopFirstScrollPosition; } // update the scrollbars ScrollbarVisibility = scrollbarVisibility; } } } /// /// Sets how the visibility of the scrollbars should be handled /// public ScrollbarVisibilityEnum ScrollbarVisibility { get { return scrollbarVisibility; } set { scrollbarVisibility = value; // only if the scrollbar exists if (_scrollbar != null) { // make sure we actually have some cell views if (_cellViewOffsetArray != null && _cellViewOffsetArray.Count > 0) { if (_cellViewOffsetArray.Last() < ScrollRectSize || loop) { // if the size of the scrollable area is smaller than the scroller // or if we have looping on, hide the scrollbar unless the visibility // is set to Always. _scrollbar.SetActive(scrollbarVisibility == ScrollbarVisibilityEnum.Always); } else { // if the size of the scrollable areas is larger than the scroller // or looping is off, then show the scrollbars unless visibility // is set to Never. _scrollbar.SetActive(scrollbarVisibility != ScrollbarVisibilityEnum.Never); } } } } } /// /// This is the velocity of the scroller. /// public Vector2 Velocity { get { return scrollRect.velocity; } set { scrollRect.velocity = value; } } /// /// The linear velocity is the velocity on one axis. /// The scroller should only be moving one one axix. /// public float LinearVelocity //实质上是操作ScrollRect.velocity //Get : 将向量转为float类型 //Set:用一个float类型构造一个相应的向量 //外部方法可以通过访问这个属性来使scroll运动起来 { get { // return the velocity component depending on which direction this is scrolling return (scrollDirection == ScrollDirectionEnum.Vertical ? scrollRect.velocity.y : scrollRect.velocity.x); } set { // set the appropriate component of the velocity if (scrollDirection == ScrollDirectionEnum.Vertical) { scrollRect.velocity = new Vector2(0, value); } else { scrollRect.velocity = new Vector2(value, 0); } } } /// /// Whether the scroller is scrolling or not /// public bool IsScrolling { get; private set; } /// /// Whether the scroller is tweening or not /// public bool IsTweening { get; private set; } /// /// This is the first cell view index showing in the scroller's visible area /// public int StartCellViewIndex//可视区内第一个能看到的元素索引(只要能看到就行,不一定要完全能看到) { get { return _activeCellViewsStartIndex; } } /// /// This is the last cell view index showing in the scroller's visible area /// public int EndCellViewIndex { get { return _activeCellViewsEndIndex; } } /// /// This is the first data index showing in the scroller's visible area /// public int StartDataIndex//当循环开启时dataIndex代表循环内的索引,当开启新一轮循环时,dataIndex也会重新计数 { get { return _activeCellViewsStartIndex % NumberOfCells; } } /// /// This is the last data index showing in the scroller's visible area /// public int EndDataIndex { get { return _activeCellViewsEndIndex % NumberOfCells; } } public int ActiveCellCnt { get { return _activeCellViews.Count; } } /// /// This is the number of cells in the scroller /// public int NumberOfCells//CellView的总个数,从最上方滑动到最下方总共显示的数目 { get { return (_delegate != null ? _delegate.GetNumberOfCells(this) : 0); } } /// /// The size of the visible portion of the scroller /// public float ScrollRectSize//得到Scroll的高或宽 { get { if (scrollDirection == ScrollDirectionEnum.Vertical) return _scrollRectTransform.rect.height; else return _scrollRectTransform.rect.width; } } public event Action OnFirstLoadAllEvent; private bool m_IsLoadAll = false; /// /// Create a cell view, or recycle one if it already exists /// /// The prefab to use to create the cell view /// public EnhancedScrollerCellView GetCellView(EnhancedScrollerCellView cellPrefab) { // see if there is a view to recycle var cellView = _GetRecycledCellView(cellPrefab); if (cellView == null) { // no recyleable cell found, so we create a new view // and attach it to our container var go = Instantiate(cellPrefab.gameObject); cellView = go.GetComponent(); cellView.transform.SetParent(_container); } Vector3 pos = cellView.transform.localPosition; if (pos.x == float.NaN || pos.y == float.NaN) { pos = Vector3.zero; } pos.z = 0; cellView.transform.localPosition = pos; return cellView; /*这个方法会通过一个cellPrefab来取出一个cellView对象,首先会先从缓冲池里取,如果有相同cellIdentifier属性的物体 * 就把这个对象从缓冲池取出来,如果没有的话就实例化一个 */ } /// /// This resets the internal size list and refreshes the cell views /// public void ReloadData() { _reloadData = false; _scrollPosition = 0; // recycle all the active cells so // that we are sure to get fresh views _RecycleAllCells(); // if we have a delegate handling our data, then // call the resize if (_delegate != null) _Resize(false); //这个方法用于初始化这个组件或重新加载这个组件 /*1.将_reloadData置为false,用于标记此组件已经初始化,Update()方法不用再重新执行 *2.将_scrollPosition 赋值为 0,当它为0时,后续的可视区域会以它为基准,滑动条将移动到最上边或最左边 *3.如果有可见的元素,全部移动到缓冲池 *4.执行Resize * 1)循环模式下,扩充循环集合到3倍的大小(实际上在循环模式下,不管怎么滑动都只会在中间那个循环内) * 2)如果传入false则定位到起始点,如果传入true则定位到当前_position(循环模式下定位到中间那个循环的对应点) */ } /// /// This calls the RefreshCellView method on each active cell. /// If you override the RefreshCellView method in your cells /// then you can update the UI without having to reload the data. /// Note: this will not change the cell sizes, you will need /// to call ReloadData for that to work. /// public void RefreshActiveCellViews() //这是提供给外界的一个方法,用于去主动操作当前Scroll可视区内所有的可见元素 { for (var i = 0; i < _activeCellViews.Count; i++) { _activeCellViews[i].RefreshCellView();//每一个可见元素都可以重写这个方法来具体执行某段逻辑 } if (this.Delegate != null) { this.Delegate.OnRebuildComplete(); } } public EnhancedScrollerCellView GetActiveCellView(int _index) { for (int i = 0; i < _activeCellViews.Count; i++) { if (_activeCellViews[i].index == _index) { return _activeCellViews[i]; } } return null; } public SmallList GetActiveCellViews() { return _activeCellViews; } internal float GetCellSize(int _dataIndex) { if (_dataIndex >= 0 && _dataIndex < _cellViewSizeArray.Count) { return _cellViewSizeArray[_dataIndex]; } return 0; } /// /// Removes all the recycled cell views. This should only be used after you /// load in a completely different set of cell views that will not use the /// recycled views. This will call garbage collection. /// public void ClearRecycled()//主动调用这个方法会立即清空缓存池并释放掉它占用的内存,可以配合上面的方法动态更新scroll { for (var i = 0; i < _recycledCellViews.Count; i++) { DestroyImmediate(_recycledCellViews[i].gameObject); } _recycledCellViews.Clear(); } /// /// Turn looping on or off. This is just a helper function so /// you don't have to keep track of the state of the looping /// in your own scripts. /// public void ToggleLoop()//主动调用会切换当前的循环模式 { Loop = !loop; } /// /// Jump to a position in the scroller based on a dataIndex. This overload allows you /// to specify a specific offset within a cell as well. /// /// he data index to jump to /// The offset from the start (top / left) of the scroller in the range 0..1. /// Outside this range will jump to the location before or after the scroller's viewable area /// The offset from the start (top / left) of the cell in the range 0..1 /// Whether to calculate in the spacing of the scroller in the jump /// What easing to use for the jump /// How long to interpolate to the jump point /// This delegate is fired when the jump completes /// 跳转到dataIndex对应的cellview元素,可以选择相应的tween动画 public void JumpToDataIndex(int dataIndex, float scrollerOffset = 0, float cellOffset = 0, bool useSpacing = true, TweenType tweenType = TweenType.immediate, float tweenTime = 0f, Action jumpComplete = null ) { var cellOffsetPosition = 0f; if (cellOffset != 0) { // calculate the cell offset position // get the cell's size var cellSize = (_delegate != null ? _delegate.GetCellViewSize(this, dataIndex) : 0); if (useSpacing) { // if using spacing add spacing from one side cellSize += spacing; // if this is not a bounday cell, then add spacing from the other side if (dataIndex > 0 && dataIndex < (NumberOfCells - 1)) cellSize += spacing; } // calculate the position based on the size of the cell and the offset within that cell cellOffsetPosition = cellSize * cellOffset; } var newScrollPosition = 0f; // cache the offset for quicker calculation var offset = -(scrollerOffset * ScrollRectSize) + cellOffsetPosition; if (loop) { // if looping, then we need to determine the closest jump position. // we do that by checking all three sets of data locations, and returning the closest one // get the scroll positions for each data set. // Note: we are calculating the position based on the cell view index, not the data index here var set1Position = GetScrollPositionForCellViewIndex(dataIndex, CellViewPositionEnum.Before) + offset; var set2Position = GetScrollPositionForCellViewIndex(dataIndex + NumberOfCells, CellViewPositionEnum.Before) + offset; var set3Position = GetScrollPositionForCellViewIndex(dataIndex + (NumberOfCells * 2), CellViewPositionEnum.Before) + offset; // get the offsets of each scroll position from the current scroll position var set1Diff = (Mathf.Abs(_scrollPosition - set1Position)); var set2Diff = (Mathf.Abs(_scrollPosition - set2Position)); var set3Diff = (Mathf.Abs(_scrollPosition - set3Position)); // choose the smallest offset from the current position (the closest position) if (set1Diff < set2Diff) { if (set1Diff < set3Diff) { newScrollPosition = set1Position; } else { newScrollPosition = set3Position; } } else { if (set2Diff < set3Diff) { newScrollPosition = set2Position; } else { newScrollPosition = set3Position; } } } else { // not looping, so just get the scroll position from the dataIndex newScrollPosition = GetScrollPositionForDataIndex(dataIndex, CellViewPositionEnum.Before) + offset; } // clamp the scroll position to a valid location newScrollPosition = Mathf.Clamp(newScrollPosition, 0, GetScrollPositionForCellViewIndex(_cellViewSizeArray.Count - 1, CellViewPositionEnum.Before)); // if spacing is used, adjust the final position if (useSpacing) { // move back by the spacing if necessary newScrollPosition = Mathf.Clamp(newScrollPosition - spacing, 0, GetScrollPositionForCellViewIndex(_cellViewSizeArray.Count - 1, CellViewPositionEnum.Before)); } // start tweening if (tweenType == TweenType.immediate || tweenTime == 0) { // if the easing is immediate or the time is zero, just jump to the end position ScrollPosition = newScrollPosition; return; } StartCoroutine(TweenPosition(tweenType, tweenTime, ScrollPosition, newScrollPosition, jumpComplete)); } public void Tween(TweenType tweenType, float tweenTime, float newPosition, Action jumpComplete = null) { StartCoroutine(TweenPosition(tweenType, tweenTime, ScrollPosition, newPosition, jumpComplete)); } /// /// Jump to a position in the scroller based on a dataIndex. /// /// The data index to jump to /// Whether you should jump before or after the cell view [System.Obsolete("This is an obsolete method, please use the version of this function with a cell offset.")] //过时的方法,可以选择定位到元素上方或下方(左右同理),可以通过上面的方法调整偏移达到同样的效果 public void JumpToDataIndex(int dataIndex, CellViewPositionEnum position = CellViewPositionEnum.Before, bool useSpacing = true) { // if looping is on, we need to jump to the middle set of data, otherwise just use the dataIndex for the cellIndex ScrollPosition = GetScrollPositionForDataIndex(dataIndex, position); // if spacing is used, adjust the final position if (useSpacing) { if (position == CellViewPositionEnum.Before) ScrollPosition = _scrollPosition - spacing; else ScrollPosition = _scrollPosition + spacing; } } /// /// Snaps the scroller on command. This is called internally when snapping is set to true and the velocity /// has dropped below the threshold. You can use this to manually snap whenever you like. /// /// 当开启锁定位置功能时,在滑动条滑动后元素会进行相应的定位(定位的约束在面板中可以进行设置) public void Snap() { if (NumberOfCells == 0) return; // set snap jumping to true so other events won't process while tweening _snapJumping = true; // stop the scroller LinearVelocity = 0; // cache the current inertia state and turn off inertia _snapInertia = scrollRect.inertia; scrollRect.inertia = false; // calculate the snap position var snapPosition = ScrollPosition + (ScrollRectSize * Mathf.Clamp01(snapWatchOffset)); // get the cell view index of cell at the watch location _snapCellViewIndex = GetCellViewIndexAtPosition(snapPosition); // get the data index of the cell at the watch location _snapDataIndex = _snapCellViewIndex % NumberOfCells; // jump the snapped cell to the jump offset location and center it on the cell offset JumpToDataIndex(_snapDataIndex, snapJumpToOffset, snapCellCenterOffset, snapUseCellSpacing, snapTweenType, snapTweenTime, SnapJumpComplete); } /// /// Gets the scroll position in pixels from the start of the scroller based on the cellViewIndex /// /// The cell index to look for. This is used instead of dataIndex in case of looping /// Do we want the start or end of the cell view's position /// public float GetScrollPositionForCellViewIndex(int cellViewIndex, CellViewPositionEnum insertPosition)//根据相关的索引从_cellViewOffsetArray里取对应的position { /*这个方法的主要作用是通过一个索引来返回position,而这个position实际上可以认为是可滑动的content向上或向左滑动了多少 * 因为之前通过_cellViewOffsetArray来存储了每个cellView下方相对于左上的偏移(TextAnchor.UpperLeft)所以通过索引实际上就能取出这个偏移量 * 再通过Before(取上或左) After(取下或右)来判断最终position的值 * -----scrollRect的NormallizedPosition实际上是可滑动区域的大小向另一方滑动中偏移量的比例-------------- * 所以可以通过postion和可滑动区域大小的比值来定义滑动条滑动到了哪个位置 */ if (NumberOfCells == 0 || _cellViewOffsetArray.Count == 0) return 0;//如果scroll没有子物体直接返回 if (cellViewIndex <= 0 && insertPosition == CellViewPositionEnum.Before) { return 0;//如果是第一个子物体且要得到它的上方位置,直接返回0 } else { if (cellViewIndex < _cellViewOffsetArray.Count) { // the index is in the range of cell view offsets if (insertPosition == CellViewPositionEnum.Before) { // return the previous cell view's offset + the spacing between cell views return _cellViewOffsetArray[cellViewIndex - 1] + spacing + (scrollDirection == ScrollDirectionEnum.Vertical ? padding.top : padding.left); //返回当前索引上方的值 //这里要注意一点就是一般来说调用这个方法的时候就是处在loop模式之下,这里的_cellViewOffsetArray是经过复制后的集合 } else { // return the offset of the cell view (offset is after the cell) return _cellViewOffsetArray[cellViewIndex] + (scrollDirection == ScrollDirectionEnum.Vertical ? padding.top : padding.left); //返回当前索引下方的值 } } else { // get the start position of the last cell (the offset of the second to last cell) return _cellViewOffsetArray[_cellViewOffsetArray.Count - 2]; //返回最后一个索引的上方值 } } } /// /// Gets the scroll position in pixels from the start of the scroller based on the dataIndex /// /// The data index to look for /// Do we want the start or end of the cell view's position /// public float GetScrollPositionForDataIndex(int dataIndex, CellViewPositionEnum insertPosition) { return GetScrollPositionForCellViewIndex(loop ? _delegate.GetNumberOfCells(this) + dataIndex : dataIndex, insertPosition); //通过dataIndex来确定ScrollPosition,要注意的是GetScrollPositionForCellViewIndex需要的是cellIndex而不是dataIndex //所以在循环模式下,dataIndex要加上所有元素的数量(因为在循环模式下,dataIndex对应的元素永远都是在集合中的第二组) } /// /// Gets the index of a cell view at a given position /// /// The pixel offset from the start of the scroller /// public int GetCellViewIndexAtPosition(float position) { // call the overrloaded method on the entire range of the list return _GetCellIndexAtPosition(position, 0, _cellViewOffsetArray.Count - 1); } #endregion #region Private /// /// Cached reference to the scrollRect /// private ScrollRect m_ScrollRect; public ScrollRect scrollRect { get { if (m_ScrollRect == null) { m_ScrollRect = this.GetComponent(); } return m_ScrollRect; } } /// /// Cached reference to the scrollRect's transform /// private RectTransform _scrollRectTransform; /// /// Cached reference to the scrollbar if it exists /// private Scrollbar _scrollbar; /// /// Cached reference to the active cell view container /// private RectTransform _container; /// /// Cached reference to the layout group that handles view positioning /// private HorizontalOrVerticalLayoutGroup _layoutGroup; public HorizontalOrVerticalLayoutGroup LayoutGroup { get { return _layoutGroup; } } /// /// Reference to the delegate that will tell this scroller information /// about the underlying data /// private IEnhancedScrollerDelegate _delegate; /// /// Flag to tell the scroller to reload the data /// private bool _reloadData; /// /// Flag to tell the scroller to refresh the active list of cell views /// private bool _refreshActive; /// /// List of views that have been recycled /// private SmallList _recycledCellViews = new SmallList(); /// /// Cached reference to the element used to offset the first visible cell view /// private LayoutElement _firstPadder; /// /// Cached reference to the element used to keep the cell views at the correct size /// private LayoutElement _lastPadder; /// /// Cached reference to the container that holds the recycled cell views /// private RectTransform _recycledCellViewContainer; /// /// Internal list of cell view sizes. This is created when the data is reloaded /// to speed up processing. /// private SmallList _cellViewSizeArray = new SmallList(); /// /// Internal list of cell view offsets. Each cell view offset is an accumulation /// of the offsets previous to it. /// This is created when the data is reloaded to speed up processing. /// private SmallList _cellViewOffsetArray = new SmallList(); /// /// The scrollers position /// private float _scrollPosition; /// /// The list of cell views that are currently being displayed /// private SmallList _activeCellViews = new SmallList(); /// /// The index of the first cell view that is being displayed /// private int _activeCellViewsStartIndex; /// /// The index of the last cell view that is being displayed /// private int _activeCellViewsEndIndex; /// /// The index of the first element of the middle section of cell view sizes. /// Used only when looping /// private int _loopFirstCellIndex; /// /// The index of the last element of the middle seciton of cell view sizes. /// used only when looping /// private int _loopLastCellIndex; /// /// The scroll position of the first element of the middle seciotn of cell views. /// Used only when looping /// private float _loopFirstScrollPosition; /// /// The scroll position of the last element of the middle section of cell views. /// Used only when looping /// private float _loopLastScrollPosition; /// /// The position that triggers the scroller to jump to the end of the middle section /// of cell views. This keeps the scroller in the middle section as much as possible. /// private float _loopFirstJumpTrigger; /// /// The position that triggers the scroller to jump to the start of the middle section /// of cell views. This keeps the scroller in the middle section as much as possible. /// private float _loopLastJumpTrigger; /// /// The cached value of the last scroll rect size. This is checked every frame to see /// if the scroll rect has resized. If so, it will refresh. /// private float _lastScrollRectSize; /// /// The cached value of the last loop setting. This is checked every frame to see /// if looping was toggled. If so, it will refresh. /// private bool _lastLoop; /// /// The cell view index we are snapping to /// private int _snapCellViewIndex; /// /// The data index we are snapping to /// private int _snapDataIndex; /// /// Whether we are currently jumping due to a snap /// private bool _snapJumping; /// /// What the previous inertia setting was before the snap jump. /// We cache it here because we need to turn off inertia while /// manually tweeing. /// private bool _snapInertia; /// /// The cached value of the last scrollbar visibility setting. This is checked every /// frame to see if the scrollbar visibility needs to be changed. /// private ScrollbarVisibilityEnum _lastScrollbarVisibility; /// /// Where in the list we are /// private enum ListPositionEnum { First, Last } /// /// The size of the active cell view container minus the visibile portion /// of the scroller /// public float _ScrollSize//这里并不是指scrllrect的大小,而是指真正在滑动的区域的大小 /*scrollrect的normallizePosition实际上就是这个真正能滑动的区域的大小 * 在滑动过程中归一化的值,而这个大小就是所有子物体的大小与可视化区域的差 * 当content的大小等于所有子物体的大小时,就可以简化操作直接用content的大小来代替 */ { get { if (scrollDirection == ScrollDirectionEnum.Vertical) { if (_container != null && _scrollRectTransform != null) return _container.rect.height - _scrollRectTransform.rect.height; else return 0; } else { if (_container != null && _scrollRectTransform != null) return _container.rect.width - _scrollRectTransform.rect.width; else return 0; } } } /// /// This function will create an internal list of sizes and offsets to be used in all calculations. /// It also sets up the loop triggers and positions and initializes the cell views. /// /// If true, then the scroller will try to go back to the position it was at before the resize private void _Resize(bool keepPosition) { // cache the original position var originalScrollPosition = _scrollPosition; // clear out the list of cell view sizes and create a new list _cellViewSizeArray.Clear(); var offset = _AddCellViewSizes();//每次清空后重新添加一次,并返回这个集合的值的和 // if looping, we need to create three sets of size data if (loop) { // if the cells don't entirely fill up the scroll area, // make some more size entries to fill it up if (offset < ScrollRectSize)//所有子物体相加没有这块可视区域大 { int additionalRounds = Mathf.CeilToInt(ScrollRectSize / offset); _DuplicateCellViewSizes(additionalRounds, _cellViewSizeArray.Count);//如果子物体的个数不足以填充可视区域,要进行复制 } // set up the loop indices _loopFirstCellIndex = _cellViewSizeArray.Count; _loopLastCellIndex = _loopFirstCellIndex + _cellViewSizeArray.Count - 1; //这里的两个值是在集合没有进行任何复制时赋值的,_loopFirstCellIndex是第二次循环的第一个元素索引,_loopLastCellIndex是第二次循环的最后一个索引 // create two more copies of the cell sizes _DuplicateCellViewSizes(2, _cellViewSizeArray.Count); /*当使用循环模式的时候,如果子物体不足以填充可视区就进行一次复制,使元素能够滑动 *当元素足够多,能够在可视区滑动的时候,再进行两次复制(最终结果是三个循环) * firstCellIndex为第一个循环的第一个物体索引,比如10个元素,第一个循环的第一个索引就是10,因为是从(0~9) * lastCellIndex为第一个循环的最后一个物体索引,比如10元素(0~9)+ 10 = 19 * 要注意: 上面说的复制在这里并没有复制游戏对象,只是对集合进行了一些扩充,这只是为了方便理解 */ } // calculate the offsets of each cell view _CalculateCellViewOffsets();//计算了第一个元素下方相对原点的偏移(其中也包括了间距) // set the size of the active cell view container based on the number of cell views there are and each of their sizes if (scrollDirection == ScrollDirectionEnum.Vertical) _container.sizeDelta = new Vector2(_container.sizeDelta.x, _cellViewOffsetArray.Last() + padding.top + padding.bottom); else _container.sizeDelta = new Vector2(_cellViewOffsetArray.Last() + padding.left + padding.right, _container.sizeDelta.y); //设置content的大小,根据_cellViewOffsetArray的最后一个值就可以得到所有元素和它们的间距的总和,再加上padding的值就可以让content和所有子物体完美匹配 //且因为之前设置了pivot的原因,直接设置content的大小并不会让它往两边缩放,而是顺着我们需要的方向进行缩放 // if looping, set up the loop positions and triggers if (loop) { _loopFirstScrollPosition = GetScrollPositionForCellViewIndex(_loopFirstCellIndex, CellViewPositionEnum.Before) + (spacing * 0.5f); _loopLastScrollPosition = GetScrollPositionForCellViewIndex(_loopLastCellIndex, CellViewPositionEnum.After) - ScrollRectSize + (spacing * 0.5f); _loopFirstJumpTrigger = _loopFirstScrollPosition - ScrollRectSize; _loopLastJumpTrigger = _loopLastScrollPosition + ScrollRectSize; //firstJumpTrigger是第一个循环的最后一个索引的下方 - 可视区大小,lastJumpTrigger是第二次循环的最后一个索引的下方 } // create the visibile cells _ResetVisibleCellViews();//根据可视区两端索引(通过_scrollPosition来计算索引)来重建可视区域内的子物体 if (this.Delegate != null) { this.Delegate.OnRebuildComplete(); } // if we need to maintain our original position if (keepPosition)//如果为true则保持调用这个方法时对应的位置 { ScrollPosition = originalScrollPosition; } else { if (loop) { ScrollPosition = _loopFirstScrollPosition;//循环模式时定义的是第二次循环的起始点(相当于两边都存在一个循环,定位在中间那个循环的起始点) } else { ScrollPosition = 0; } } // set up the visibility of the scrollbar ScrollbarVisibility = scrollbarVisibility; if (!m_IsLoadAll) { m_IsLoadAll = true; if (OnFirstLoadAllEvent != null) { OnFirstLoadAllEvent(); } } } /// /// Creates a list of cell view sizes for faster access /// /// private float _AddCellViewSizes()//创建一个用于存放所有子物体的大小的集合,并返回所有子物体相加后的总尺寸 { var offset = 0f; // add a size for each row in our data based on how many the delegate tells us to create for (var i = 0; i < NumberOfCells; i++) { // add the size of this cell based on what the delegate tells us to use. Also add spacing if this cell isn't the first one _cellViewSizeArray.Add(_delegate.GetCellViewSize(this, i) + (i == 0 ? 0 : _layoutGroup.spacing)); offset += _cellViewSizeArray[_cellViewSizeArray.Count - 1]; } return offset; } /// /// Create a copy of the cell view sizes. This is only used in looping /// /// How many times the copy should be made /// How many cells to copy private void _DuplicateCellViewSizes(int numberOfTimes, int cellCount)//当使用循环模式时,如果子物体不足以填充可视区域,就进行复制 { for (var i = 0; i < numberOfTimes; i++) //注意此处的复制并不是复制游戏对象,而只是集合的扩充 { for (var j = 0; j < cellCount; j++) { _cellViewSizeArray.Add(_cellViewSizeArray[j] + (j == 0 ? _layoutGroup.spacing : 0));//存储子物体size的集合也要跟着变 /*j == 0 ? _layoutGroup.spacing : 0 注意这个三元表达式,在进行复制时,如果是复制集合的第一个元素要多加一个spacing * 因为滑动条中只有第一个元素前方或左方是没有spacing的,所以当复制n份时,就要增加n个spacing */ } } } /// /// Calculates the offset of each cell, accumulating the values from previous cells /// private void _CalculateCellViewOffsets()//此处的偏移集合存储的是当前索引下的物体下方相对原点的偏移量,也就是说最后一个元素的大小就是所有子物体大小加间距的总和 { _cellViewOffsetArray.Clear(); var offset = 0f; for (var i = 0; i < _cellViewSizeArray.Count; i++) { offset += _cellViewSizeArray[i]; _cellViewOffsetArray.Add(offset); } } /// /// Get a recycled cell with a given identifier if available /// /// The prefab to check for /// private EnhancedScrollerCellView _GetRecycledCellView(EnhancedScrollerCellView cellPrefab)//从缓存池中取一个cellPrefab { for (var i = 0; i < _recycledCellViews.Count; i++) { if (_recycledCellViews[i].cellIdentifier == cellPrefab.cellIdentifier) { // the cell view was found, so we use this recycled one. // we also remove it from the recycled list var cellView = _recycledCellViews.RemoveAt(i); return cellView; } } return null; } /// /// This sets up the visible cells, adding and recycling as necessary /// private void _ResetVisibleCellViews()//通过某个_scrollPosition来判断可视区两端能看到的索引,再通过这两个索引来重建可视区内的子物体,并更新相应的集合 { int startIndex; int endIndex; // calculate the range of the visible cells _CalculateCurrentActiveCellRange(out startIndex, out endIndex);//这个方法返回当前_scrollPosition对应的可视区两端能看到的子物体索引(并不一定要完全在可视区内) // go through each previous active cell and recycle it if it no longer falls in the range var i = 0; SmallList remainingCellIndices = new SmallList(); while (i < _activeCellViews.Count) { if (_activeCellViews[i].cellIndex < startIndex || _activeCellViews[i].cellIndex > endIndex) { _RecycleCell(_activeCellViews[i]);//如果原本可见的元素不在可视区域内将其移动到_RecycleCell节点下 } else { // this cell index falls in the new range, so we add its // index to the reusable list remainingCellIndices.Add(_activeCellViews[i].cellIndex); //剩下的肯定是可见元素,放入remainingCellIndices集合中,这里要注意的是,这一集合并不一定能够填充整个可视区域, //startIndex和endIndex内的部分索引可能并没有对应的可见子物体 i++; } } if (remainingCellIndices.Count == 0)//在跳转至较远位置时,会导致原来可见元素全部都不可见了,又或者此组件刚进行初始化时,并没有任何可见元素 { // there were no previous active cells remaining, // this list is either brand new, or we jumped to // an entirely different part of the list. // just add all the new cell views for (i = startIndex; i <= endIndex; i++) { _AddCellView(i, ListPositionEnum.Last); /*正序添加,当remainingCellIndices集合没有元素时,直接按顺序添加就可以了,先添加的在前面,后添加的在后面 * 在当前_scrollPosition下能看到的就只有从startIndex到endIndex这几个元素,只需要实例化(从缓冲池取)这几个元素就行了*/ } } else { // we are able to reuse some of the previous // cell views // first add the views that come before the // previous list, going backward so that the // new views get added to the front for (i = endIndex; i >= startIndex; i--) { if (i < remainingCellIndices.First()) { _AddCellView(i, ListPositionEnum.First);//倒序添加,即当前添加的元素一定在第一个,完成循环后第一个被添加的元素就放到了最后一个 } /*当remainingCellIndices集合中存在元素时,说明集合中的元素是可见的,但是不一定能够完全填充可视区,所以还需要根据情况来添加新的可见元素 * 如果此集合的最小元素索引大于startIndex和endIndex之内的某一索引,说明这一索引对应的子物体要进行实例化(从缓冲池取),且一定排在这个集合的第一个元素之前 * 这里要注意的是循环中i是递减的,当索引i比集合中最小的元素索引都要小时说明了i索引此时并没有可对应的可见子物体,实例化子物体时要 * 将它设置在最前面,以此类推,循环完成后,后添加的会比先添加的排在前面 */ } // next add teh views that come after the // previous list, going forward and adding // at the end of the list for (i = startIndex; i <= endIndex; i++) { if (i > remainingCellIndices.Last()) { _AddCellView(i, ListPositionEnum.Last); } /*当此集合最大的元素索引小于startIndex到endIndex中某一索引时,说明这一索引一样没有对应的可见子物体, * 又因为此循环是递增的,所以先进行实例化(缓冲池取)的子物体一定是要比后添加的子物体排在前面 */ } } // update the start and end indices _activeCellViewsStartIndex = startIndex; _activeCellViewsEndIndex = endIndex; // adjust the padding elements to offset the cell views correctly _SetPadders();//当可见区域被填充满后,就可以将其它区域设置为padding } /// /// Recycles all the active cells /// private void _RecycleAllCells()//移除全部可见元素到缓冲池 { while (_activeCellViews.Count > 0) _RecycleCell(_activeCellViews[0]); _activeCellViewsStartIndex = 0; _activeCellViewsEndIndex = 0; } /// /// Recycles one cell view /// /// public void _RecycleCell(EnhancedScrollerCellView cellView)//从_activeCellVeiws集合中移除元素到_RecycleCell集合中 { // remove the cell view from the active list _activeCellViews.Remove(cellView); // add the cell view to the recycled list _recycledCellViews.Add(cellView); // move the GameObject to the recycled container cellView.transform.SetParent(_recycledCellViewContainer); // reset the cellView's properties cellView.dataIndex = 0; cellView.cellIndex = 0; cellView.active = false; if (cellViewVisibilityChanged != null) cellViewVisibilityChanged(cellView); } /// /// Creates a cell view, or recycles if it can /// /// The index of the cell view /// Whether to add the cell to the beginning or the end private void _AddCellView(int cellIndex, ListPositionEnum listPosition) { //通过某个索引添加(从缓冲池中取)子物体到_activeCellViews集合中,并触发相关事件 //这个方法本身也会根据传过来的索引来初始化CellView if (NumberOfCells == 0) return; // get the dataIndex. Modulus is used in case of looping so that the first set of cells are ignored var dataIndex = cellIndex % NumberOfCells; // request a cell view from the delegate var cellView = _delegate.GetCellView(this, dataIndex, cellIndex); // set the cell's properties cellView.cellIndex = cellIndex; cellView.dataIndex = dataIndex; cellView.active = true; // add the cell view to the active container cellView.transform.SetParent(_container, false); cellView.transform.localScale = Vector3.one; // add a layout element to the cellView LayoutElement layoutElement = cellView.GetComponent(); if (layoutElement == null) layoutElement = cellView.gameObject.AddComponent(); // set the size of the layout element if (scrollDirection == ScrollDirectionEnum.Vertical) layoutElement.minHeight = _cellViewSizeArray[cellIndex] - (cellIndex > 0 ? _layoutGroup.spacing : 0); else layoutElement.minWidth = _cellViewSizeArray[cellIndex] - (cellIndex > 0 ? _layoutGroup.spacing : 0); // add the cell to the active list if (listPosition == ListPositionEnum.First) _activeCellViews.AddStart(cellView); else _activeCellViews.Add(cellView); // set the hierarchy position of the cell view in the container if (listPosition == ListPositionEnum.Last) cellView.transform.SetSiblingIndex(_container.childCount - 2);//这里的添加方式决定了两个padder的位置是夹着所有的cellView的 else if (listPosition == ListPositionEnum.First) cellView.transform.SetSiblingIndex(1); // call the visibility change delegate if available if (cellViewVisibilityChanged != null) cellViewVisibilityChanged(cellView); } /// /// This function adjusts the two padders that control the first cell view's /// offset and the overall size of each cell. /// private void _SetPadders()//当处理完可见区域后,Padder就可以计算出来,padder的作用就是用来支撑整个scroll内的元素,保证它们的位置 { if (NumberOfCells == 0) return; // calculate the size of each padder var firstSize = _cellViewOffsetArray[_activeCellViewsStartIndex] - _cellViewSizeArray[_activeCellViewsStartIndex]; var lastSize = _cellViewOffsetArray.Last() - _cellViewOffsetArray[_activeCellViewsEndIndex]; if (scrollDirection == ScrollDirectionEnum.Vertical) { // set the first padder and toggle its visibility _firstPadder.minHeight = firstSize; _firstPadder.SetActive(_firstPadder.minHeight > 0); // set the last padder and toggle its visibility _lastPadder.minHeight = lastSize; _lastPadder.SetActive(_lastPadder.minHeight > 0); } else { // set the first padder and toggle its visibility _firstPadder.minWidth = firstSize; _firstPadder.SetActive(_firstPadder.minWidth > 0); // set the last padder and toggle its visibility _lastPadder.minWidth = lastSize; _lastPadder.SetActive(_lastPadder.minWidth > 0); } } /// /// This function is called if the scroller is scrolled, updating the active list of cells /// private void _RefreshActive() { _refreshActive = false; int startIndex; int endIndex; var velocity = Vector2.zero; // if looping, check to see if we scrolled past a trigger if (loop) { if (_scrollPosition < _loopFirstJumpTrigger) { velocity = scrollRect.velocity; ScrollPosition = _loopLastScrollPosition - (_loopFirstJumpTrigger - _scrollPosition); scrollRect.velocity = velocity; } else if (_scrollPosition > _loopLastJumpTrigger) { velocity = scrollRect.velocity; ScrollPosition = _loopFirstScrollPosition + (_scrollPosition - _loopLastJumpTrigger); scrollRect.velocity = velocity; } //循环模式下要将ScrollPosition限制在第二个循环内,这里的几个变量都是用于控制这一属性的 /*注意这里的ScrollPosition的赋值操作,只有当在循环模式下,且滑动的偏移已经达到了触发_loopJumpTrigger的时候 * 它才会发生赋值操作,当它被赋值以后,即此时的content的位置发生了改变,然后会再次调用此方法刷新可视区,直到再次触发loopJumpTirgger * 之前,ScrollPosition都不会再次被赋值了 */ } // get the range of visibile cells _CalculateCurrentActiveCellRange(out startIndex, out endIndex); // if the index hasn't changed, ignore and return if (startIndex == _activeCellViewsStartIndex && endIndex == _activeCellViewsEndIndex) return; // recreate the visibile cells _ResetVisibleCellViews(); if (this.Delegate != null) { this.Delegate.OnRebuildComplete(); } } /// /// Determines which cells can be seen /// /// The index of the first cell visible /// The index of the last cell visible private void _CalculateCurrentActiveCellRange(out int startIndex, out int endIndex)//这个方法返回的是基于_scrollPosition得到的可视区域两端索引 { startIndex = 0; endIndex = 0; // get the positions of the scroller var startPosition = _scrollPosition; var endPosition = _scrollPosition + (scrollDirection == ScrollDirectionEnum.Vertical ? _scrollRectTransform.rect.height : _scrollRectTransform.rect.width); // calculate each index based on the positions startIndex = GetCellViewIndexAtPosition(startPosition); endIndex = GetCellViewIndexAtPosition(endPosition); } /// /// Gets the index of a cell at a given position based on a subset range. /// This function uses a recursive binary sort to find the index faster. /// /// The pixel offset from the start of the scroller /// The first index of the range /// The last index of the rnage /// private int _GetCellIndexAtPosition(float position, int startIndex, int endIndex)//这个方法的startIndex为0 ,endIndex为_cellViewOffsetArray的元素数量(但会根据计算的值进行调整) { // if the range is invalid, then we found our index, return the start index if (startIndex >= endIndex) return startIndex; // determine the middle point of our binary search var middleIndex = (startIndex + endIndex) / 2; // if the middle index is greater than the position, then search the last // half of the binary tree, else search the first half if ((_cellViewOffsetArray[middleIndex] + (scrollDirection == ScrollDirectionEnum.Vertical ? padding.top : padding.left)) >= position) return _GetCellIndexAtPosition(position, startIndex, middleIndex); else return _GetCellIndexAtPosition(position, middleIndex + 1, endIndex); } /// /// Caches and initializes the scroller /// void Awake() { GameObject go; // cache some components //_scrollRect = this.GetComponent(); _scrollRectTransform = scrollRect.GetComponent(); // destroy any content objects if they exist. Likely there will be // one at design time because Unity gives errors if it can't find one. if (scrollRect.content != null) { DestroyImmediate(scrollRect.content.gameObject);//如果存在content则销毁 } // Create a new active cell view container with a layout group go = new GameObject("Container", typeof(RectTransform)); go.transform.SetParent(_scrollRectTransform); if (scrollDirection == ScrollDirectionEnum.Vertical) go.AddComponent(); else go.AddComponent(); _container = go.GetComponent(); // set the containers anchor and pivot if (scrollDirection == ScrollDirectionEnum.Vertical) { _container.anchorMin = new Vector2(0, 1); _container.anchorMax = Vector2.one; _container.pivot = new Vector2(0.5f, 1f); //设置了content的锚点和轴心,主要作用是便于计算,以它上方中心的点到scroll上方的距离为它的y轴值,它的长度即为它的Height属性 //当设置它的高的时候就是设置了他的大小 } else { _container.anchorMin = Vector2.zero; _container.anchorMax = new Vector2(0, 1f); _container.pivot = new Vector2(0, 0.5f);//同上 } _container.offsetMax = Vector2.zero; _container.offsetMin = Vector2.zero; _container.localScale = Vector3.one; Vector3 pos = _container.transform.localPosition; pos.z = 0; _container.transform.localPosition = pos; //将它的位置固定在scrol上方或左方,通过这个位置和上面设置的锚点来保证设置大小的时候不会跑偏 scrollRect.content = _container; // cache the scrollbar if it exists if (scrollDirection == ScrollDirectionEnum.Vertical) { _scrollbar = scrollRect.verticalScrollbar; } else { _scrollbar = scrollRect.horizontalScrollbar; } // cache the layout group and set up its spacing and padding _layoutGroup = _container.GetComponent(); _layoutGroup.spacing = spacing; _layoutGroup.padding = padding; _layoutGroup.childAlignment = TextAnchor.UpperLeft;//添加锚点 _layoutGroup.childForceExpandHeight = true; _layoutGroup.childForceExpandWidth = true; // force the scroller to scroll in the direction we want //_scrollRect.horizontal = scrollDirection == ScrollDirectionEnum.Horizontal; //_scrollRect.vertical = scrollDirection == ScrollDirectionEnum.Vertical; // create the padder objects go = new GameObject("First Padder", typeof(RectTransform), typeof(LayoutElement)); go.transform.SetParent(_container, false); _firstPadder = go.GetComponent(); go = new GameObject("Last Padder", typeof(RectTransform), typeof(LayoutElement)); go.transform.SetParent(_container, false); _lastPadder = go.GetComponent(); // create the recycled cell view container go = new GameObject("Recycled Cells", typeof(RectTransform)); go.transform.SetParent(scrollRect.transform, false); _recycledCellViewContainer = go.GetComponent(); _recycledCellViewContainer.SetActive(false); //实例化了两个padder和一个缓冲池 // set up the last values for updates _lastScrollRectSize = ScrollRectSize; _lastLoop = loop; _lastScrollbarVisibility = scrollbarVisibility; //一些变量进行了初始化 if(OnCompLoad!=null) OnCompLoad(); inited = true; } void Update() { if (_reloadData)//当外部脚本设置Delegate的时候就会将_reloadData置为true { // if the reload flag is true, then reload the data ReloadData();//理论上不用在外部调用ReloadData()因为当成功赋值Delegate后这个方法就会执行一次 //但如果想在update之前执行就主动调用ReloadData() } // if the scroll rect size has changed and looping is on, // or the loop setting has changed, then we need to resize if ( (loop && _lastScrollRectSize != ScrollRectSize)//当Scroll的大小发生改变并且开启循环时 || (loop != _lastLoop)//当循环开关发生了改变 ) { _Resize(true); _lastScrollRectSize = ScrollRectSize; _lastLoop = loop; } // update the scroll bar visibility if it has changed if (_lastScrollbarVisibility != scrollbarVisibility)//如果滑动条的可见性发生改变也需要更新一下相关变量 { ScrollbarVisibility = scrollbarVisibility; _lastScrollbarVisibility = scrollbarVisibility; } // determine if the scroller has started or stopped scrolling // and call the delegate if so. if (LinearVelocity != 0 && !IsScrolling)//当Scroll.velocity不为0时说明在滑动,此时如果IsScrolling为false(标记为没有滑动) { //触发当滑动开始时的事件 IsScrolling = true; if (scrollerScrollingChanged != null) scrollerScrollingChanged(this, true); } else if (LinearVelocity == 0 && IsScrolling)//同理触发当滑动结束时的事件,这里的两个事件都只会在一个滑动周期时各执行一次 { IsScrolling = false; if (scrollerScrollingChanged != null) scrollerScrollingChanged(this, false); } } void LateUpdate() { if (_refreshActive)//当设置ScrollPosition时就会将_refreshActive置为true,此时就会调用_RefreshActive()方法 { // if the refresh toggle is on, then // refresh the list _RefreshActive(); } } void OnEnable() { // when the scroller is enabled, add a listener to the onValueChanged handler scrollRect.onValueChanged.AddListener(_ScrollRect_OnValueChanged);//当显示时注册 } void OnDisable() { // when the scroller is disabled, remove the listener scrollRect.onValueChanged.RemoveListener(_ScrollRect_OnValueChanged);//当隐藏时移除注册 } /// /// Handler for when the scroller changes value /// /// The scroll rect's value private void _ScrollRect_OnValueChanged(Vector2 val) { // set the internal scroll position if (scrollDirection == ScrollDirectionEnum.Vertical) { _scrollPosition = (1f - val.y) * _ScrollSize;//注意这里只是给私有字段赋值,并不会调用相关属性的Set方法(如果调用了Set方法会导致无限循环,Set方法会触发valuechange,valuechange又触发Set) } else { _scrollPosition = val.x * _ScrollSize; } _refreshActive = true;//标记要进行刷新 // call the handler if it exists if (scrollerScrolled != null) scrollerScrolled(this, val, _scrollPosition);//滑动过程中触发的相关事件 // if the snapping is turned on, handle it if (snapping && !_snapJumping)//如果开启锁定位置,但此时没有定位 { // if the speed has dropped below the threshhold velocity if (Mathf.Abs(LinearVelocity) <= snapVelocityThreshold) { // Call the snap function Snap();//进行定位 } } _RefreshActive();//刷新 /*在滑动的过程中,通过NormalizePosition来求得_scrollPosition * 如果满足定位条件就进行定位 * 以上代码执行完成后就会去刷新整个Scroll */ } /// /// This is fired by the tweener when the snap tween is completed /// private void SnapJumpComplete()//当定位完成时 { // reset the snap jump to false and restore the inertia state _snapJumping = false; scrollRect.inertia = _snapInertia; // fire the scroller snapped delegate if (scrollerSnapped != null) scrollerSnapped(this, _snapCellViewIndex, _snapDataIndex); } #endregion #region Tweening /// /// The easing type /// public enum TweenType { immediate, linear, spring, easeInQuad, easeOutQuad, easeInOutQuad, easeInCubic, easeOutCubic, easeInOutCubic, easeInQuart, easeOutQuart, easeInOutQuart, easeInQuint, easeOutQuint, easeInOutQuint, easeInSine, easeOutSine, easeInOutSine, easeInExpo, easeOutExpo, easeInOutExpo, easeInCirc, easeOutCirc, easeInOutCirc, easeInBounce, easeOutBounce, easeInOutBounce, easeInBack, easeOutBack, easeInOutBack, easeInElastic, easeOutElastic, easeInOutElastic } private float _tweenTimeLeft; public bool inited; /// /// Moves the scroll position over time between two points given an easing function. When the /// tween is complete it will fire the jumpComplete delegate. /// /// The type of easing to use /// The amount of time to interpolate /// The starting scroll position /// The ending scroll position /// The action to fire when the tween is complete /// IEnumerator TweenPosition(TweenType tweenType, float time, float start, float end, Action tweenComplete) { if (tweenType == TweenType.immediate || time == 0) { // if the easing is immediate or the time is zero, just jump to the end position ScrollPosition = end; } else { // zero out the velocity scrollRect.velocity = Vector2.zero; // fire the delegate for the tween start IsTweening = true; if (scrollerTweeningChanged != null) scrollerTweeningChanged(this, true); _tweenTimeLeft = 0; var newPosition = 0f; // while the tween has time left, use an easing function while (_tweenTimeLeft < time) { switch (tweenType) { case TweenType.linear: newPosition = linear(start, end, (_tweenTimeLeft / time)); break; case TweenType.spring: newPosition = spring(start, end, (_tweenTimeLeft / time)); break; case TweenType.easeInQuad: newPosition = easeInQuad(start, end, (_tweenTimeLeft / time)); break; case TweenType.easeOutQuad: newPosition = easeOutQuad(start, end, (_tweenTimeLeft / time)); break; case TweenType.easeInOutQuad: newPosition = easeInOutQuad(start, end, (_tweenTimeLeft / time)); break; case TweenType.easeInCubic: newPosition = easeInCubic(start, end, (_tweenTimeLeft / time)); break; case TweenType.easeOutCubic: newPosition = easeOutCubic(start, end, (_tweenTimeLeft / time)); break; case TweenType.easeInOutCubic: newPosition = easeInOutCubic(start, end, (_tweenTimeLeft / time)); break; case TweenType.easeInQuart: newPosition = easeInQuart(start, end, (_tweenTimeLeft / time)); break; case TweenType.easeOutQuart: newPosition = easeOutQuart(start, end, (_tweenTimeLeft / time)); break; case TweenType.easeInOutQuart: newPosition = easeInOutQuart(start, end, (_tweenTimeLeft / time)); break; case TweenType.easeInQuint: newPosition = easeInQuint(start, end, (_tweenTimeLeft / time)); break; case TweenType.easeOutQuint: newPosition = easeOutQuint(start, end, (_tweenTimeLeft / time)); break; case TweenType.easeInOutQuint: newPosition = easeInOutQuint(start, end, (_tweenTimeLeft / time)); break; case TweenType.easeInSine: newPosition = easeInSine(start, end, (_tweenTimeLeft / time)); break; case TweenType.easeOutSine: newPosition = easeOutSine(start, end, (_tweenTimeLeft / time)); break; case TweenType.easeInOutSine: newPosition = easeInOutSine(start, end, (_tweenTimeLeft / time)); break; case TweenType.easeInExpo: newPosition = easeInExpo(start, end, (_tweenTimeLeft / time)); break; case TweenType.easeOutExpo: newPosition = easeOutExpo(start, end, (_tweenTimeLeft / time)); break; case TweenType.easeInOutExpo: newPosition = easeInOutExpo(start, end, (_tweenTimeLeft / time)); break; case TweenType.easeInCirc: newPosition = easeInCirc(start, end, (_tweenTimeLeft / time)); break; case TweenType.easeOutCirc: newPosition = easeOutCirc(start, end, (_tweenTimeLeft / time)); break; case TweenType.easeInOutCirc: newPosition = easeInOutCirc(start, end, (_tweenTimeLeft / time)); break; case TweenType.easeInBounce: newPosition = easeInBounce(start, end, (_tweenTimeLeft / time)); break; case TweenType.easeOutBounce: newPosition = easeOutBounce(start, end, (_tweenTimeLeft / time)); break; case TweenType.easeInOutBounce: newPosition = easeInOutBounce(start, end, (_tweenTimeLeft / time)); break; case TweenType.easeInBack: newPosition = easeInBack(start, end, (_tweenTimeLeft / time)); break; case TweenType.easeOutBack: newPosition = easeOutBack(start, end, (_tweenTimeLeft / time)); break; case TweenType.easeInOutBack: newPosition = easeInOutBack(start, end, (_tweenTimeLeft / time)); break; case TweenType.easeInElastic: newPosition = easeInElastic(start, end, (_tweenTimeLeft / time)); break; case TweenType.easeOutElastic: newPosition = easeOutElastic(start, end, (_tweenTimeLeft / time)); break; case TweenType.easeInOutElastic: newPosition = easeInOutElastic(start, end, (_tweenTimeLeft / time)); break; } if (loop) { // if we are looping, we need to make sure the new position isn't past the jump trigger. // if it is we need to reset back to the jump position on the other side of the area. if (end > start && newPosition > _loopLastJumpTrigger) { //DesignDebug.Log("name: " + name + " went past the last jump trigger, looping back around"); newPosition = _loopFirstScrollPosition + (newPosition - _loopLastJumpTrigger); } else if (start > end && newPosition < _loopFirstJumpTrigger) { //DesignDebug.Log("name: " + name + " went past the first jump trigger, looping back around"); newPosition = _loopLastScrollPosition - (_loopFirstJumpTrigger - newPosition); } } // set the scroll position to the tweened position ScrollPosition = newPosition; // increase the time elapsed _tweenTimeLeft += Time.unscaledDeltaTime; yield return null; } // the time has expired, so we make sure the final scroll position // is the actual end position. ScrollPosition = end; } // the tween jump is complete, so we fire the delegate if (tweenComplete != null) tweenComplete(); // fire the delegate for the tween ending IsTweening = false; if (scrollerTweeningChanged != null) scrollerTweeningChanged(this, false); } private float linear(float start, float end, float val) { return Mathf.Lerp(start, end, val); } private static float spring(float start, float end, float val) { val = Mathf.Clamp01(val); val = (Mathf.Sin(val * Mathf.PI * (0.2f + 2.5f * val * val * val)) * Mathf.Pow(1f - val, 2.2f) + val) * (1f + (1.2f * (1f - val))); return start + (end - start) * val; } private static float easeInQuad(float start, float end, float val) { end -= start; return end * val * val + start; } private static float easeOutQuad(float start, float end, float val) { end -= start; return -end * val * (val - 2) + start; } private static float easeInOutQuad(float start, float end, float val) { val /= .5f; end -= start; if (val < 1) return end / 2 * val * val + start; val--; return -end / 2 * (val * (val - 2) - 1) + start; } private static float easeInCubic(float start, float end, float val) { end -= start; return end * val * val * val + start; } private static float easeOutCubic(float start, float end, float val) { val--; end -= start; return end * (val * val * val + 1) + start; } private static float easeInOutCubic(float start, float end, float val) { val /= .5f; end -= start; if (val < 1) return end / 2 * val * val * val + start; val -= 2; return end / 2 * (val * val * val + 2) + start; } private static float easeInQuart(float start, float end, float val) { end -= start; return end * val * val * val * val + start; } private static float easeOutQuart(float start, float end, float val) { val--; end -= start; return -end * (val * val * val * val - 1) + start; } private static float easeInOutQuart(float start, float end, float val) { val /= .5f; end -= start; if (val < 1) return end / 2 * val * val * val * val + start; val -= 2; return -end / 2 * (val * val * val * val - 2) + start; } private static float easeInQuint(float start, float end, float val) { end -= start; return end * val * val * val * val * val + start; } private static float easeOutQuint(float start, float end, float val) { val--; end -= start; return end * (val * val * val * val * val + 1) + start; } private static float easeInOutQuint(float start, float end, float val) { val /= .5f; end -= start; if (val < 1) return end / 2 * val * val * val * val * val + start; val -= 2; return end / 2 * (val * val * val * val * val + 2) + start; } private static float easeInSine(float start, float end, float val) { end -= start; return -end * Mathf.Cos(val / 1 * (Mathf.PI / 2)) + end + start; } private static float easeOutSine(float start, float end, float val) { end -= start; return end * Mathf.Sin(val / 1 * (Mathf.PI / 2)) + start; } private static float easeInOutSine(float start, float end, float val) { end -= start; return -end / 2 * (Mathf.Cos(Mathf.PI * val / 1) - 1) + start; } private static float easeInExpo(float start, float end, float val) { end -= start; return end * Mathf.Pow(2, 10 * (val / 1 - 1)) + start; } private static float easeOutExpo(float start, float end, float val) { end -= start; return end * (-Mathf.Pow(2, -10 * val / 1) + 1) + start; } private static float easeInOutExpo(float start, float end, float val) { val /= .5f; end -= start; if (val < 1) return end / 2 * Mathf.Pow(2, 10 * (val - 1)) + start; val--; return end / 2 * (-Mathf.Pow(2, -10 * val) + 2) + start; } private static float easeInCirc(float start, float end, float val) { end -= start; return -end * (Mathf.Sqrt(1 - val * val) - 1) + start; } private static float easeOutCirc(float start, float end, float val) { val--; end -= start; return end * Mathf.Sqrt(1 - val * val) + start; } private static float easeInOutCirc(float start, float end, float val) { val /= .5f; end -= start; if (val < 1) return -end / 2 * (Mathf.Sqrt(1 - val * val) - 1) + start; val -= 2; return end / 2 * (Mathf.Sqrt(1 - val * val) + 1) + start; } private static float easeInBounce(float start, float end, float val) { end -= start; float d = 1f; return end - easeOutBounce(0, end, d - val) + start; } private static float easeOutBounce(float start, float end, float val) { val /= 1f; end -= start; if (val < (1 / 2.75f)) { return end * (7.5625f * val * val) + start; } else if (val < (2 / 2.75f)) { val -= (1.5f / 2.75f); return end * (7.5625f * (val) * val + .75f) + start; } else if (val < (2.5 / 2.75)) { val -= (2.25f / 2.75f); return end * (7.5625f * (val) * val + .9375f) + start; } else { val -= (2.625f / 2.75f); return end * (7.5625f * (val) * val + .984375f) + start; } } private static float easeInOutBounce(float start, float end, float val) { end -= start; float d = 1f; if (val < d / 2) return easeInBounce(0, end, val * 2) * 0.5f + start; else return easeOutBounce(0, end, val * 2 - d) * 0.5f + end * 0.5f + start; } private static float easeInBack(float start, float end, float val) { end -= start; val /= 1; float s = 1.70158f; return end * (val) * val * ((s + 1) * val - s) + start; } private static float easeOutBack(float start, float end, float val) { float s = 1.70158f; end -= start; val = (val / 1) - 1; return end * ((val) * val * ((s + 1) * val + s) + 1) + start; } private static float easeInOutBack(float start, float end, float val) { float s = 1.70158f; end -= start; val /= .5f; if ((val) < 1) { s *= (1.525f); return end / 2 * (val * val * (((s) + 1) * val - s)) + start; } val -= 2; s *= (1.525f); return end / 2 * ((val) * val * (((s) + 1) * val + s) + 2) + start; } private static float easeInElastic(float start, float end, float val) { end -= start; float d = 1f; float p = d * .3f; float s = 0; float a = 0; if (val == 0) return start; val = val / d; if (val == 1) return start + end; if (a == 0f || a < Mathf.Abs(end)) { a = end; s = p / 4; } else { s = p / (2 * Mathf.PI) * Mathf.Asin(end / a); } val = val - 1; return -(a * Mathf.Pow(2, 10 * val) * Mathf.Sin((val * d - s) * (2 * Mathf.PI) / p)) + start; } private static float easeOutElastic(float start, float end, float val) { end -= start; float d = 1f; float p = d * .3f; float s = 0; float a = 0; if (val == 0) return start; val = val / d; if (val == 1) return start + end; if (a == 0f || a < Mathf.Abs(end)) { a = end; s = p / 4; } else { s = p / (2 * Mathf.PI) * Mathf.Asin(end / a); } return (a * Mathf.Pow(2, -10 * val) * Mathf.Sin((val * d - s) * (2 * Mathf.PI) / p) + end + start); } private static float easeInOutElastic(float start, float end, float val) { end -= start; float d = 1f; float p = d * .3f; float s = 0; float a = 0; if (val == 0) return start; val = val / (d / 2); if (val == 2) return start + end; if (a == 0f || a < Mathf.Abs(end)) { a = end; s = p / 4; } else { s = p / (2 * Mathf.PI) * Mathf.Asin(end / a); } if (val < 1) { val = val - 1; return -0.5f * (a * Mathf.Pow(2, 10 * val) * Mathf.Sin((val * d - s) * (2 * Mathf.PI) / p)) + start; } val = val - 1; return a * Mathf.Pow(2, -10 * val) * Mathf.Sin((val * d - s) * (2 * Mathf.PI) / p) * 0.5f + end + start; } #endregion } }