using System; using UnityEngine; using FairyGUI.Utils; namespace FairyGUI { /// /// /// public class ScrollPane : EventDispatcher { /// /// 当前被拖拽的滚动面板。同一时间只能有一个在进行此操作。 /// public static ScrollPane draggingPane { get; private set; } ScrollType _scrollType; float _scrollStep; float _decelerationRate; Margin _scrollBarMargin; bool _bouncebackEffect; bool _touchEffect; bool _scrollBarDisplayAuto; bool _vScrollNone; bool _hScrollNone; bool _needRefresh; int _refreshBarAxis; bool _displayOnLeft; bool _snapToItem; internal bool _displayInDemand; bool _mouseWheelEnabled; bool _softnessOnTopOrLeftSide; bool _pageMode; Vector2 _pageSize; bool _inertiaDisabled; bool _maskDisabled; bool _floating; bool _dontClipMargin; float _xPos; float _yPos; Vector2 _viewSize; Vector2 _contentSize; Vector2 _overlapSize; Vector2 _containerPos; Vector2 _beginTouchPos; Vector2 _lastTouchPos; Vector2 _lastTouchGlobalPos; Vector2 _velocity; float _velocityScale; float _lastMoveTime; bool _dragged; bool _isHoldAreaDone; int _aniFlag; internal int _loop; int _headerLockedSize; int _footerLockedSize; bool _hover; int _tweening; Vector2 _tweenStart; Vector2 _tweenChange; Vector2 _tweenTime; Vector2 _tweenDuration; Action _refreshDelegate; TimerCallback _tweenUpdateDelegate; GTweenCallback1 _hideScrollBarDelegate; GComponent _owner; Container _maskContainer; Container _container; GScrollBar _hzScrollBar; GScrollBar _vtScrollBar; GComponent _header; GComponent _footer; Controller _pageController; EventListener _onScroll; EventListener _onScrollEnd; EventListener _onPullDownRelease; EventListener _onPullUpRelease; static int _gestureFlag; public static float TWEEN_TIME_GO = 0.3f; //调用SetPos(ani)时使用的缓动时间 public static float TWEEN_TIME_DEFAULT = 0.3f; //惯性滚动的最小缓动时间 public static float PULL_RATIO = 0.5f; //下拉过顶或者上拉过底时允许超过的距离占显示区域的比例 public ScrollPane(GComponent owner) { _onScroll = new EventListener(this, "onScroll"); _onScrollEnd = new EventListener(this, "onScrollEnd"); _scrollStep = UIConfig.defaultScrollStep; _softnessOnTopOrLeftSide = UIConfig.allowSoftnessOnTopOrLeftSide; _decelerationRate = UIConfig.defaultScrollDecelerationRate; _touchEffect = UIConfig.defaultScrollTouchEffect; _bouncebackEffect = UIConfig.defaultScrollBounceEffect; _mouseWheelEnabled = true; _pageSize = Vector2.one; _refreshDelegate = Refresh; _tweenUpdateDelegate = TweenUpdate; _hideScrollBarDelegate = __barTweenComplete; _owner = owner; _maskContainer = new Container(); _owner.rootContainer.AddChild(_maskContainer); _container = _owner.container; _container.SetXY(0, 0); _maskContainer.AddChild(_container); _owner.rootContainer.onMouseWheel.Add(__mouseWheel); _owner.rootContainer.onTouchBegin.Add(__touchBegin); _owner.rootContainer.onTouchMove.Add(__touchMove); _owner.rootContainer.onTouchEnd.Add(__touchEnd); } public void Setup(ByteBuffer buffer) { _scrollType = (ScrollType)buffer.ReadByte(); ScrollBarDisplayType scrollBarDisplay = (ScrollBarDisplayType)buffer.ReadByte(); int flags = buffer.ReadInt(); if (buffer.ReadBool()) { _scrollBarMargin.top = buffer.ReadInt(); _scrollBarMargin.bottom = buffer.ReadInt(); _scrollBarMargin.left = buffer.ReadInt(); _scrollBarMargin.right = buffer.ReadInt(); } string vtScrollBarRes = buffer.ReadS(); string hzScrollBarRes = buffer.ReadS(); string headerRes = buffer.ReadS(); string footerRes = buffer.ReadS(); _displayOnLeft = (flags & 1) != 0; _snapToItem = (flags & 2) != 0; _displayInDemand = (flags & 4) != 0; _pageMode = (flags & 8) != 0; if ((flags & 16) != 0) _touchEffect = true; else if ((flags & 32) != 0) _touchEffect = false; if ((flags & 64) != 0) _bouncebackEffect = true; else if ((flags & 128) != 0) _bouncebackEffect = false; _inertiaDisabled = (flags & 256) != 0; _maskDisabled = (flags & 512) != 0; _floating = (flags & 1024) != 0; _dontClipMargin = (flags & 2048) != 0; if (scrollBarDisplay == ScrollBarDisplayType.Default) { if (Application.isMobilePlatform) scrollBarDisplay = ScrollBarDisplayType.Auto; else scrollBarDisplay = UIConfig.defaultScrollBarDisplay; } if (scrollBarDisplay != ScrollBarDisplayType.Hidden) { if (_scrollType == ScrollType.Both || _scrollType == ScrollType.Vertical) { string res = vtScrollBarRes != null ? vtScrollBarRes : UIConfig.verticalScrollBar; if (!string.IsNullOrEmpty(res)) { _vtScrollBar = UIPackage.CreateObjectFromURL(res) as GScrollBar; if (_vtScrollBar == null) Debug.LogWarning("FairyGUI: cannot create scrollbar from " + res); else { _vtScrollBar.SetScrollPane(this, true); _owner.rootContainer.AddChild(_vtScrollBar.displayObject); } } } if (_scrollType == ScrollType.Both || _scrollType == ScrollType.Horizontal) { string res = hzScrollBarRes != null ? hzScrollBarRes : UIConfig.horizontalScrollBar; if (!string.IsNullOrEmpty(res)) { _hzScrollBar = UIPackage.CreateObjectFromURL(res) as GScrollBar; if (_hzScrollBar == null) Debug.LogWarning("FairyGUI: cannot create scrollbar from " + res); else { _hzScrollBar.SetScrollPane(this, false); _owner.rootContainer.AddChild(_hzScrollBar.displayObject); } } } _scrollBarDisplayAuto = scrollBarDisplay == ScrollBarDisplayType.Auto; if (_scrollBarDisplayAuto) { if (_vtScrollBar != null) _vtScrollBar.displayObject.visible = false; if (_hzScrollBar != null) _hzScrollBar.displayObject.visible = false; _owner.rootContainer.onRollOver.Add(__rollOver); _owner.rootContainer.onRollOut.Add(__rollOut); } } else _mouseWheelEnabled = false; if (Application.isPlaying) { if (headerRes != null) { _header = (GComponent)UIPackage.CreateObjectFromURL(headerRes); if (_header == null) Debug.LogWarning("FairyGUI: cannot create scrollPane header from " + headerRes); } if (footerRes != null) { _footer = (GComponent)UIPackage.CreateObjectFromURL(footerRes); if (_footer == null) Debug.LogWarning("FairyGUI: cannot create scrollPane footer from " + footerRes); } if (_header != null || _footer != null) _refreshBarAxis = (_scrollType == ScrollType.Both || _scrollType == ScrollType.Vertical) ? 1 : 0; } SetSize(owner.width, owner.height); } /// /// /// public void Dispose() { RemoveEventListeners(); if (_tweening != 0) Timers.inst.Remove(_tweenUpdateDelegate); if (draggingPane == this) draggingPane = null; _pageController = null; if (_hzScrollBar != null) _hzScrollBar.Dispose(); if (_vtScrollBar != null) _vtScrollBar.Dispose(); if (_header != null) _header.Dispose(); if (_footer != null) _footer.Dispose(); } /// /// Dispatched when scrolling. /// 在滚动时派发该事件。 /// public EventListener onScroll { get { return _onScroll ?? (_onScroll = new EventListener(this, "onScroll")); } } /// /// 在滚动结束时派发该事件。 /// public EventListener onScrollEnd { get { return _onScrollEnd ?? (_onScrollEnd = new EventListener(this, "onScrollEnd")); } } /// /// 向下拉过上边缘后释放则派发该事件。 /// public EventListener onPullDownRelease { get { return _onPullDownRelease ?? (_onPullDownRelease = new EventListener(this, "onPullDownRelease")); } } /// /// 向上拉过下边缘后释放则派发该事件。 /// public EventListener onPullUpRelease { get { return _onPullUpRelease ?? (_onPullUpRelease = new EventListener(this, "onPullUpRelease")); } } /// /// /// public GComponent owner { get { return _owner; } } /// /// /// public GScrollBar hzScrollBar { get { return _hzScrollBar; } } /// /// /// public GScrollBar vtScrollBar { get { return _vtScrollBar; } } /// /// /// public GComponent header { get { return _header; } } /// /// /// public GComponent footer { get { return _footer; } } /// /// 滚动到达边缘时是否允许回弹效果。 /// public bool bouncebackEffect { get { return _bouncebackEffect; } set { _bouncebackEffect = value; } } /// /// 是否允许拖拽内容区域进行滚动。 /// public bool touchEffect { get { return _touchEffect; } set { _touchEffect = value; } } /// /// 是否允许惯性滚动。 /// public bool inertiaDisabled { get { return _inertiaDisabled; } set { _inertiaDisabled = value; } } /// /// 是否允许在左/上边缘显示虚化效果。 /// public bool softnessOnTopOrLeftSide { get { return _softnessOnTopOrLeftSide; } set { _softnessOnTopOrLeftSide = value; } } /// /// 当调用ScrollPane.scrollUp/Down/Left/Right时,或者点击滚动条的上下箭头时,滑动的距离。 /// public float scrollStep { get { return _scrollStep; } set { _scrollStep = value; if (_scrollStep == 0) _scrollStep = UIConfig.defaultScrollStep; } } /// /// 滚动位置是否保持贴近在某个元件的边缘。 /// public bool snapToItem { get { return _snapToItem; } set { _snapToItem = value; } } /// /// 是否页面滚动模式。 /// public bool pageMode { get { return _pageMode; } set { _pageMode = value; } } /// /// /// public Controller pageController { get { return _pageController; } set { _pageController = value; } } /// /// 是否允许使用鼠标滚轮进行滚动。 /// public bool mouseWheelEnabled { get { return _mouseWheelEnabled; } set { _mouseWheelEnabled = value; } } /// /// 当处于惯性滚动时减速的速率。默认值是UIConfig.defaultScrollDecelerationRate。 /// 越接近1,减速越慢,意味着滑动的时间和距离更长。 /// public float decelerationRate { get { return _decelerationRate; } set { _decelerationRate = value; } } /// /// public bool isDragged { get { return _dragged; } } /// /// 当前X轴滚动位置百分比,0~1(包含)。 /// public float percX { get { return _overlapSize.x == 0 ? 0 : _xPos / _overlapSize.x; } set { SetPercX(value, false); } } /// /// 设置当前X轴滚动位置百分比,0~1(包含)。 /// /// /// 是否使用缓动到达目标。 public void SetPercX(float value, bool ani) { _owner.EnsureBoundsCorrect(); SetPosX(_overlapSize.x * Mathf.Clamp01(value), ani); } /// /// 当前Y轴滚动位置百分比,0~1(包含)。 /// public float percY { get { return _overlapSize.y == 0 ? 0 : _yPos / _overlapSize.y; } set { SetPercY(value, false); } } /// /// 设置当前Y轴滚动位置百分比,0~1(包含)。 /// /// /// 是否使用缓动到达目标。 public void SetPercY(float value, bool ani) { _owner.EnsureBoundsCorrect(); SetPosY(_overlapSize.y * Mathf.Clamp01(value), ani); } /// /// 当前X轴滚动位置,值范围是viewWidth与contentWidth之差。 /// public float posX { get { return _xPos; } set { SetPosX(value, false); } } /// /// 设置当前X轴滚动位置。 /// /// /// 是否使用缓动到达目标。 public void SetPosX(float value, bool ani) { _owner.EnsureBoundsCorrect(); if (_loop == 1) LoopCheckingNewPos(ref value, 0); value = Mathf.Clamp(value, 0, _overlapSize.x); if (value != _xPos) { _xPos = value; PosChanged(ani); } } /// /// 当前Y轴滚动位置,值范围是viewHeight与contentHeight之差。 /// public float posY { get { return _yPos; } set { SetPosY(value, false); } } /// /// 设置当前Y轴滚动位置。 /// /// /// 是否使用缓动到达目标。 public void SetPosY(float value, bool ani) { _owner.EnsureBoundsCorrect(); if (_loop == 2) LoopCheckingNewPos(ref value, 1); value = Mathf.Clamp(value, 0, _overlapSize.y); if (value != _yPos) { _yPos = value; PosChanged(ani); } } /// /// 返回当前滚动位置是否在最下边。 /// public bool isBottomMost { get { return _yPos == _overlapSize.y || _overlapSize.y == 0; } } /// /// 返回当前滚动位置是否在最右边。 /// public bool isRightMost { get { return _xPos == _overlapSize.x || _overlapSize.x == 0; } } /// /// 如果处于分页模式,返回当前在X轴的页码。 /// public int currentPageX { get { if (!_pageMode) return 0; int page = Mathf.FloorToInt(_xPos / _pageSize.x); if (_xPos - page * _pageSize.x > _pageSize.x * 0.5f) page++; return page; } set { if (!_pageMode) return; _owner.EnsureBoundsCorrect(); if (_overlapSize.x > 0) this.SetPosX(value * _pageSize.x, false); } } /// /// 如果处于分页模式,可设置X轴的页码。 /// /// /// 是否使用缓动到达目标。 public void SetCurrentPageX(int value, bool ani) { if (!_pageMode) return; _owner.EnsureBoundsCorrect(); if (_overlapSize.x > 0) this.SetPosX(value * _pageSize.x, ani); } /// /// 如果处于分页模式,返回当前在Y轴的页码。 /// public int currentPageY { get { if (!_pageMode) return 0; int page = Mathf.FloorToInt(_yPos / _pageSize.y); if (_yPos - page * _pageSize.y > _pageSize.y * 0.5f) page++; return page; } set { if (!_pageMode) return; _owner.EnsureBoundsCorrect(); if (_overlapSize.y > 0) this.SetPosY(value * _pageSize.y, false); } } /// /// 如果处于分页模式,可设置Y轴的页码。 /// /// /// 是否使用缓动到达目标。 public void SetCurrentPageY(int value, bool ani) { if (!_pageMode) return; _owner.EnsureBoundsCorrect(); if (_overlapSize.y > 0) this.SetPosY(value * _pageSize.y, ani); } /// /// 这个值与PosX不同在于,他反映的是实时位置,而PosX在有缓动过程的情况下只是终值。 /// public float scrollingPosX { get { return Mathf.Clamp(-_container.x, 0, _overlapSize.x); } } /// /// 这个值与PosY不同在于,他反映的是实时位置,而PosY在有缓动过程的情况下只是终值。 /// public float scrollingPosY { get { return Mathf.Clamp(-_container.y, 0, _overlapSize.y); } } /// /// 显示内容宽度。 /// public float contentWidth { get { return _contentSize.x; } } /// /// 显示内容高度。 /// public float contentHeight { get { return _contentSize.y; } } /// /// 显示区域宽度。 /// public float viewWidth { get { return _viewSize.x; } set { value = value + _owner.margin.left + _owner.margin.right; if (_vtScrollBar != null && !_floating) value += _vtScrollBar.width; _owner.width = value; } } /// /// 显示区域高度。 /// public float viewHeight { get { return _viewSize.y; } set { value = value + _owner.margin.top + _owner.margin.bottom; if (_hzScrollBar != null && !_floating) value += _hzScrollBar.height; _owner.height = value; } } /// /// /// public void ScrollTop() { ScrollTop(false); } /// /// /// /// public void ScrollTop(bool ani) { this.SetPercY(0, ani); } /// /// /// public void ScrollBottom() { ScrollBottom(false); } /// /// /// /// public void ScrollBottom(bool ani) { this.SetPercY(1, ani); } /// /// /// public void ScrollUp() { ScrollUp(1, false); } /// /// /// /// /// public void ScrollUp(float ratio, bool ani) { if (_pageMode) SetPosY(_yPos - _pageSize.y * ratio, ani); else SetPosY(_yPos - _scrollStep * ratio, ani); } /// /// /// public void ScrollDown() { ScrollDown(1, false); } /// /// /// /// /// public void ScrollDown(float ratio, bool ani) { if (_pageMode) SetPosY(_yPos + _pageSize.y * ratio, ani); else SetPosY(_yPos + _scrollStep * ratio, ani); } /// /// /// public void ScrollLeft() { ScrollLeft(1, false); } /// /// /// /// /// public void ScrollLeft(float ratio, bool ani) { if (_pageMode) SetPosX(_xPos - _pageSize.x * ratio, ani); else SetPosX(_xPos - _scrollStep * ratio, ani); } /// /// /// public void ScrollRight() { ScrollRight(1, false); } /// /// /// /// /// public void ScrollRight(float ratio, bool ani) { if (_pageMode) SetPosX(_xPos + _pageSize.x * ratio, ani); else SetPosX(_xPos + _scrollStep * ratio, ani); } /// /// /// /// obj can be any object on stage, not limited to the direct child of this container. public void ScrollToView(GObject obj) { ScrollToView(obj, false); } /// /// /// /// obj can be any object on stage, not limited to the direct child of this container. /// If moving to target position with animation public void ScrollToView(GObject obj, bool ani) { ScrollToView(obj, ani, false); } /// /// /// /// obj can be any object on stage, not limited to the direct child of this container. /// If moving to target position with animation /// If true, scroll to make the target on the top/left; If false, scroll to make the target any position in view. public void ScrollToView(GObject obj, bool ani, bool setFirst) { _owner.EnsureBoundsCorrect(); if (_needRefresh) Refresh(); Rect rect = new Rect(obj.x, obj.y, obj.width, obj.height); if (obj.parent != _owner) rect = obj.parent.TransformRect(rect, _owner); ScrollToView(rect, ani, setFirst); } /// /// /// /// Rect in local coordinates /// If moving to target position with animation /// If true, scroll to make the target on the top/left; If false, scroll to make the target any position in view. public void ScrollToView(Rect rect, bool ani, bool setFirst) { _owner.EnsureBoundsCorrect(); if (_needRefresh) Refresh(); if (_overlapSize.y > 0) { float bottom = _yPos + _viewSize.y; if (setFirst || rect.y <= _yPos || rect.height >= _viewSize.y) { if (rect.yMax >= bottom) //if an item size is large than viewSize, dont scroll return; if (_pageMode) this.SetPosY(Mathf.Floor(rect.y / _pageSize.y) * _pageSize.y, ani); else SetPosY(rect.y, ani); } else if (rect.yMax > bottom) { if (_pageMode) this.SetPosY(Mathf.Floor(rect.y / _pageSize.y) * _pageSize.y, ani); else if (rect.height <= _viewSize.y / 2) SetPosY(rect.y + rect.height * 2 - _viewSize.y, ani); else SetPosY(rect.y + Mathf.Min(rect.height - _viewSize.y, 0), ani); } } if (_overlapSize.x > 0) { float right = _xPos + _viewSize.x; if (setFirst || rect.x <= _xPos || rect.width >= _viewSize.x) { if (rect.xMax >= right) //if an item size is large than viewSize, dont scroll return; if (_pageMode) this.SetPosX(Mathf.Floor(rect.x / _pageSize.x) * _pageSize.x, ani); SetPosX(rect.x, ani); } else if (rect.xMax > right) { if (_pageMode) this.SetPosX(Mathf.Floor(rect.x / _pageSize.x) * _pageSize.x, ani); else if (rect.width <= _viewSize.x / 2) SetPosX(rect.x + rect.width * 2 - _viewSize.x, ani); else SetPosX(rect.x + Mathf.Min(rect.width - _viewSize.x, 0), ani); } } if (!ani && _needRefresh) Refresh(); } /// /// /// /// obj must be the direct child of this container /// public bool IsChildInView(GObject obj) { if (_overlapSize.y > 0) { float dist = obj.y + _container.y; if (dist <= -obj.height || dist >= _viewSize.y) return false; } if (_overlapSize.x > 0) { float dist = obj.x + _container.x; if (dist <= -obj.width || dist >= _viewSize.x) return false; } return true; } /// /// 当滚动面板处于拖拽滚动状态或即将进入拖拽状态时,可以调用此方法停止或禁止本次拖拽。 /// public void CancelDragging() { Stage.inst.RemoveTouchMonitor(_owner.rootContainer); if (draggingPane == this) draggingPane = null; _gestureFlag = 0; _dragged = false; } /// /// 设置Header固定显示。如果size为0,则取消固定显示。 /// /// Header显示的大小 public void LockHeader(int size) { if (_headerLockedSize == size) return; _headerLockedSize = size; if (!isDispatching("onPullDownRelease") && _container.xy[_refreshBarAxis] >= 0) { _tweenStart = _container.xy; _tweenChange = Vector2.zero; _tweenChange[_refreshBarAxis] = _headerLockedSize - _tweenStart[_refreshBarAxis]; _tweenDuration = new Vector2(TWEEN_TIME_DEFAULT, TWEEN_TIME_DEFAULT); StartTween(2); } } /// /// 设置Footer固定显示。如果size为0,则取消固定显示。 /// /// public void LockFooter(int size) { if (_footerLockedSize == size) return; _footerLockedSize = size; if (!isDispatching("onPullUpRelease") && _container.xy[_refreshBarAxis] <= -_overlapSize[_refreshBarAxis]) { _tweenStart = _container.xy; _tweenChange = Vector2.zero; float max = _overlapSize[_refreshBarAxis]; if (max == 0) max = Mathf.Max(_contentSize[_refreshBarAxis] + _footerLockedSize - _viewSize[_refreshBarAxis], 0); else max += _footerLockedSize; _tweenChange[_refreshBarAxis] = -max - _tweenStart[_refreshBarAxis]; _tweenDuration = new Vector2(TWEEN_TIME_DEFAULT, TWEEN_TIME_DEFAULT); StartTween(2); } } internal void OnOwnerSizeChanged() { SetSize(_owner.width, _owner.height); PosChanged(false); } internal void HandleControllerChanged(Controller c) { if (_pageController == c) { if (_scrollType == ScrollType.Horizontal) this.SetCurrentPageX(c.selectedIndex, true); else this.SetCurrentPageY(c.selectedIndex, true); } } void UpdatePageController() { if (_pageController != null && !_pageController.changing) { int index; if (_scrollType == ScrollType.Horizontal) index = this.currentPageX; else index = this.currentPageY; if (index < _pageController.pageCount) { Controller c = _pageController; _pageController = null; //防止HandleControllerChanged的调用 c.selectedIndex = index; _pageController = c; } } } internal void AdjustMaskContainer() { float mx, my; if (_displayOnLeft && _vtScrollBar != null && !_floating) mx = Mathf.FloorToInt(_owner.margin.left + _vtScrollBar.width); else mx = _owner.margin.left; my = _owner.margin.top; mx += _owner._alignOffset.x; my += _owner._alignOffset.y; _maskContainer.SetXY(mx, my); } void SetSize(float aWidth, float aHeight) { AdjustMaskContainer(); if (_hzScrollBar != null) { _hzScrollBar.y = aHeight - _hzScrollBar.height; if (_vtScrollBar != null) { _hzScrollBar.width = aWidth - _vtScrollBar.width - _scrollBarMargin.left - _scrollBarMargin.right; if (_displayOnLeft) _hzScrollBar.x = _scrollBarMargin.left + _vtScrollBar.width; else _hzScrollBar.x = _scrollBarMargin.left; } else { _hzScrollBar.width = aWidth - _scrollBarMargin.left - _scrollBarMargin.right; _hzScrollBar.x = _scrollBarMargin.left; } } if (_vtScrollBar != null) { if (!_displayOnLeft) _vtScrollBar.x = aWidth - _vtScrollBar.width; if (_hzScrollBar != null) _vtScrollBar.height = aHeight - _hzScrollBar.height - _scrollBarMargin.top - _scrollBarMargin.bottom; else _vtScrollBar.height = aHeight - _scrollBarMargin.top - _scrollBarMargin.bottom; _vtScrollBar.y = _scrollBarMargin.top; } _viewSize.x = aWidth; _viewSize.y = aHeight; if (_hzScrollBar != null && !_floating) _viewSize.y -= _hzScrollBar.height; if (_vtScrollBar != null && !_floating) _viewSize.x -= _vtScrollBar.width; _viewSize.x -= (_owner.margin.left + _owner.margin.right); _viewSize.y -= (_owner.margin.top + _owner.margin.bottom); _viewSize.x = Mathf.Max(1, _viewSize.x); _viewSize.y = Mathf.Max(1, _viewSize.y); _pageSize.x = _viewSize.x; _pageSize.y = _viewSize.y; HandleSizeChanged(); } internal void SetContentSize(float aWidth, float aHeight) { if (Mathf.Approximately(_contentSize.x, aWidth) && Mathf.Approximately(_contentSize.y, aHeight)) return; _contentSize.x = aWidth; _contentSize.y = aHeight; HandleSizeChanged(); } /// /// 内部使用。由虚拟列表调用。在滚动时修改显示内容的大小,需要进行修正,避免滚动跳跃。 /// /// /// /// /// internal void ChangeContentSizeOnScrolling(float deltaWidth, float deltaHeight, float deltaPosX, float deltaPosY) { bool isRightmost = _xPos == _overlapSize.x; bool isBottom = _yPos == _overlapSize.y; _contentSize.x += deltaWidth; _contentSize.y += deltaHeight; HandleSizeChanged(); if (_tweening == 1) { //如果原来滚动位置是贴边,加入处理继续贴边。 if (deltaWidth != 0 && isRightmost && _tweenChange.x < 0) { _xPos = _overlapSize.x; _tweenChange.x = -_xPos - _tweenStart.x; } if (deltaHeight != 0 && isBottom && _tweenChange.y < 0) { _yPos = _overlapSize.y; _tweenChange.y = -_yPos - _tweenStart.y; } } else if (_tweening == 2) { //重新调整起始位置,确保能够顺滑滚下去 if (deltaPosX != 0) { _container.x -= deltaPosX; _tweenStart.x -= deltaPosX; _xPos = -_container.x; } if (deltaPosY != 0) { _container.y -= deltaPosY; _tweenStart.y -= deltaPosY; _yPos = -_container.y; } } else if (_dragged) { if (deltaPosX != 0) { _container.x -= deltaPosX; _containerPos.x -= deltaPosX; _xPos = -_container.x; } if (deltaPosY != 0) { _container.y -= deltaPosY; _containerPos.y -= deltaPosY; _yPos = -_container.y; } } else { //如果原来滚动位置是贴边,加入处理继续贴边。 if (deltaWidth != 0 && isRightmost) { _xPos = _overlapSize.x; _container.x = -_xPos; } if (deltaHeight != 0 && isBottom) { _yPos = _overlapSize.y; _container.y = -_yPos; } } if (_pageMode) UpdatePageController(); } void HandleSizeChanged() { if (_displayInDemand) { _vScrollNone = _contentSize.y <= _viewSize.y; _hScrollNone = _contentSize.x <= _viewSize.x; if (_vtScrollBar != null && _hzScrollBar != null) { if (!_hScrollNone) _vtScrollBar.height = _owner.height - _hzScrollBar.height - _scrollBarMargin.top - _scrollBarMargin.bottom; else _vtScrollBar.height = _owner.height - _scrollBarMargin.top - _scrollBarMargin.bottom; if (!_vScrollNone) _hzScrollBar.width = _owner.width - _vtScrollBar.width - _scrollBarMargin.left - _scrollBarMargin.right; else _hzScrollBar.width = _owner.width - _scrollBarMargin.left - _scrollBarMargin.right; } } if (_vtScrollBar != null) { if (_contentSize.y == 0) _vtScrollBar.SetDisplayPerc(0); else _vtScrollBar.SetDisplayPerc(Mathf.Min(1, _viewSize.y / _contentSize.y)); } if (_hzScrollBar != null) { if (_contentSize.x == 0) _hzScrollBar.SetDisplayPerc(0); else _hzScrollBar.SetDisplayPerc(Mathf.Min(1, _viewSize.x / _contentSize.x)); } UpdateScrollBarVisible(); if (!_maskDisabled) { Rect rect = new Rect(-_owner._alignOffset.x, -_owner._alignOffset.y, _viewSize.x, _viewSize.y); if (_vScrollNone && _vtScrollBar != null) rect.width += _vtScrollBar.width; if (_hScrollNone && _hzScrollBar != null) rect.height += _hzScrollBar.height; if (_dontClipMargin) { rect.x -= _owner.margin.left; rect.width += (_owner.margin.left + _owner.margin.right); rect.y -= _owner.margin.top; rect.height += (_owner.margin.top + _owner.margin.bottom); } _maskContainer.clipRect = rect; } if (_scrollType == ScrollType.Horizontal || _scrollType == ScrollType.Both) _overlapSize.x = Mathf.CeilToInt(Math.Max(0, _contentSize.x - _viewSize.x)); else _overlapSize.x = 0; if (_scrollType == ScrollType.Vertical || _scrollType == ScrollType.Both) _overlapSize.y = Mathf.CeilToInt(Math.Max(0, _contentSize.y - _viewSize.y)); else _overlapSize.y = 0; //边界检查 _xPos = Mathf.Clamp(_xPos, 0, _overlapSize.x); _yPos = Mathf.Clamp(_yPos, 0, _overlapSize.y); float max = _overlapSize[_refreshBarAxis]; if (max == 0) max = Mathf.Max(_contentSize[_refreshBarAxis] + _footerLockedSize - _viewSize[_refreshBarAxis], 0); else max += _footerLockedSize; if (_refreshBarAxis == 0) _container.SetXY(Mathf.Clamp(_container.x, -max, _headerLockedSize), Mathf.Clamp(_container.y, -_overlapSize.y, 0)); else _container.SetXY(Mathf.Clamp(_container.x, -_overlapSize.x, 0), Mathf.Clamp(_container.y, -max, _headerLockedSize)); if (_header != null) { if (_refreshBarAxis == 0) _header.height = _viewSize.y; else _header.width = _viewSize.x; } if (_footer != null) { if (_refreshBarAxis == 0) _footer.height = _viewSize.y; else _footer.width = _viewSize.x; } UpdateScrollBarPos(); if (_pageMode) UpdatePageController(); } private void PosChanged(bool ani) { //只要有1处要求不要缓动,那就不缓动 if (_aniFlag == 0) _aniFlag = ani ? 1 : -1; else if (_aniFlag == 1 && !ani) _aniFlag = -1; _needRefresh = true; UpdateContext.OnBegin -= _refreshDelegate; UpdateContext.OnBegin += _refreshDelegate; } private void Refresh() { _needRefresh = false; UpdateContext.OnBegin -= _refreshDelegate; if (_owner.displayObject == null || _owner.displayObject.isDisposed) return; if (_pageMode || _snapToItem) { Vector2 pos = new Vector2(-_xPos, -_yPos); AlignPosition(ref pos, false); _xPos = -pos.x; _yPos = -pos.y; } Refresh2(); _onScroll.Call(); if (_needRefresh) //在onScroll事件里开发者可能修改位置,这里再刷新一次,避免闪烁 { _needRefresh = false; UpdateContext.OnBegin -= _refreshDelegate; Refresh2(); } UpdateScrollBarPos(); _aniFlag = 0; } void Refresh2() { if (_aniFlag == 1 && !_dragged) { Vector2 pos = new Vector2(); if (_overlapSize.x > 0) pos.x = -(int)_xPos; else { if (_container.x != 0) _container.x = 0; pos.x = 0; } if (_overlapSize.y > 0) pos.y = -(int)_yPos; else { if (_container.y != 0) _container.y = 0; pos.y = 0; } if (pos.x != _container.x || pos.y != _container.y) { _tweenDuration = new Vector2(TWEEN_TIME_GO, TWEEN_TIME_GO); _tweenStart = _container.xy; _tweenChange = pos - _tweenStart; StartTween(1); } else if (_tweening != 0) KillTween(); } else { if (_tweening != 0) KillTween(); _container.SetXY((int)-_xPos, (int)-_yPos); LoopCheckingCurrent(); } if (_pageMode) UpdatePageController(); } private void __touchBegin(EventContext context) { if (!_touchEffect) return; InputEvent evt = context.inputEvent; if (evt.button != 0) return; context.CaptureTouch(); Vector2 pt = _owner.GlobalToLocal(evt.position); if (_tweening != 0) { KillTween(); Stage.inst.CancelClick(evt.touchId); //立刻停止惯性滚动,可能位置不对齐,设定这个标志,使touchEnd时归位 _dragged = true; } else _dragged = false; _containerPos = _container.xy; _beginTouchPos = _lastTouchPos = pt; _lastTouchGlobalPos = evt.position; _isHoldAreaDone = false; _velocity = Vector2.zero; _velocityScale = 1; _lastMoveTime = Time.unscaledTime; } private void __touchMove(EventContext context) { if (!_touchEffect || draggingPane != null && draggingPane != this || GObject.draggingObject != null) //已经有其他拖动 return; InputEvent evt = context.inputEvent; Vector2 pt = _owner.GlobalToLocal(evt.position); if (float.IsNaN(pt.x)) return; int sensitivity; if (Stage.touchScreen) sensitivity = UIConfig.touchScrollSensitivity; else sensitivity = 8; float diff; bool sv = false, sh = false; if (_scrollType == ScrollType.Vertical) { if (!_isHoldAreaDone) { //表示正在监测垂直方向的手势 _gestureFlag |= 1; diff = Mathf.Abs(_beginTouchPos.y - pt.y); if (diff < sensitivity) return; if ((_gestureFlag & 2) != 0) //已经有水平方向的手势在监测,那么我们用严格的方式检查是不是按垂直方向移动,避免冲突 { float diff2 = Mathf.Abs(_beginTouchPos.x - pt.x); if (diff < diff2) //不通过则不允许滚动了 return; } } sv = true; } else if (_scrollType == ScrollType.Horizontal) { if (!_isHoldAreaDone) { _gestureFlag |= 2; diff = Mathf.Abs(_beginTouchPos.x - pt.x); if (diff < sensitivity) return; if ((_gestureFlag & 1) != 0) { float diff2 = Mathf.Abs(_beginTouchPos.y - pt.y); if (diff < diff2) return; } } sh = true; } else { _gestureFlag = 3; if (!_isHoldAreaDone) { diff = Mathf.Abs(_beginTouchPos.y - pt.y); if (diff < sensitivity) { diff = Mathf.Abs(_beginTouchPos.x - pt.x); if (diff < sensitivity) return; } } sv = sh = true; } Vector2 newPos = _containerPos + pt - _beginTouchPos; newPos.x = (int)newPos.x; newPos.y = (int)newPos.y; if (sv) { if (newPos.y > 0) { if (!_bouncebackEffect) _container.y = 0; else if (_header != null && _header.maxHeight != 0) _container.y = (int)Mathf.Min(newPos.y * 0.5f, _header.maxHeight); else _container.y = (int)Mathf.Min(newPos.y * 0.5f, _viewSize.y * PULL_RATIO); } else if (newPos.y < -_overlapSize.y) { if (!_bouncebackEffect) _container.y = -_overlapSize.y; else if (_footer != null && _footer.maxHeight > 0) _container.y = (int)Mathf.Max((newPos.y + _overlapSize.y) * 0.5f, -_footer.maxHeight) - _overlapSize.y; else _container.y = (int)Mathf.Max((newPos.y + _overlapSize.y) * 0.5f, -_viewSize.y * PULL_RATIO) - _overlapSize.y; } else _container.y = newPos.y; } if (sh) { if (newPos.x > 0) { if (!_bouncebackEffect) _container.x = 0; else if (_header != null && _header.maxWidth != 0) _container.x = (int)Mathf.Min(newPos.x * 0.5f, _header.maxWidth); else _container.x = (int)Mathf.Min(newPos.x * 0.5f, _viewSize.x * PULL_RATIO); } else if (newPos.x < 0 - _overlapSize.x) { if (!_bouncebackEffect) _container.x = -_overlapSize.x; else if (_footer != null && _footer.maxWidth > 0) _container.x = (int)Mathf.Max((newPos.x + _overlapSize.x) * 0.5f, -_footer.maxWidth) - _overlapSize.x; else _container.x = (int)Mathf.Max((newPos.x + _overlapSize.x) * 0.5f, -_viewSize.x * PULL_RATIO) - _overlapSize.x; } else _container.x = newPos.x; } //更新速度 float deltaTime = Time.unscaledDeltaTime; float elapsed = (Time.unscaledTime - _lastMoveTime) * 60 - 1; if (elapsed > 1) //速度衰减 _velocity = _velocity * Mathf.Pow(0.833f, elapsed); Vector2 deltaPosition = pt - _lastTouchPos; if (!sh) deltaPosition.x = 0; if (!sv) deltaPosition.y = 0; _velocity = Vector2.Lerp(_velocity, deltaPosition / deltaTime, deltaTime * 10); /*速度计算使用的是本地位移,但在后续的惯性滚动判断中需要用到屏幕位移,所以这里要记录一个位移的比例。 *后续的处理要使用这个比例但不使用坐标转换的方法的原因是,在曲面UI等异形UI中,还无法简单地进行屏幕坐标和本地坐标的转换。 */ Vector2 deltaGlobalPosition = _lastTouchGlobalPos - evt.position; if (deltaPosition.x != 0) _velocityScale = Mathf.Abs(deltaGlobalPosition.x / deltaPosition.x); else if (deltaPosition.y != 0) _velocityScale = Mathf.Abs(deltaGlobalPosition.y / deltaPosition.y); _lastTouchPos = pt; _lastTouchGlobalPos = evt.position; _lastMoveTime = Time.unscaledTime; //同步更新pos值 if (_overlapSize.x > 0) _xPos = Mathf.Clamp(-_container.x, 0, _overlapSize.x); if (_overlapSize.y > 0) _yPos = Mathf.Clamp(-_container.y, 0, _overlapSize.y); //循环滚动特别检查 if (_loop != 0) { newPos = _container.xy; if (LoopCheckingCurrent()) _containerPos += _container.xy - newPos; } draggingPane = this; _isHoldAreaDone = true; _dragged = true; UpdateScrollBarPos(); UpdateScrollBarVisible(); if (_pageMode) UpdatePageController(); _onScroll.Call(); } private void __touchEnd(EventContext context) { if (draggingPane == this) draggingPane = null; _gestureFlag = 0; if (!_dragged || !_touchEffect) { _dragged = false; return; } _dragged = false; _tweenStart = _container.xy; Vector2 endPos = _tweenStart; bool flag = false; if (_container.x > 0) { endPos.x = 0; flag = true; } else if (_container.x < -_overlapSize.x) { endPos.x = -_overlapSize.x; flag = true; } if (_container.y > 0) { endPos.y = 0; flag = true; } else if (_container.y < -_overlapSize.y) { endPos.y = -_overlapSize.y; flag = true; } if (flag) { _tweenChange = endPos - _tweenStart; if (_tweenChange.x < -UIConfig.touchDragSensitivity || _tweenChange.y < -UIConfig.touchDragSensitivity) DispatchEvent("onPullDownRelease", null); else if (_tweenChange.x > UIConfig.touchDragSensitivity || _tweenChange.y > UIConfig.touchDragSensitivity) DispatchEvent("onPullUpRelease", null); if (_headerLockedSize > 0 && endPos[_refreshBarAxis] == 0) { endPos[_refreshBarAxis] = _headerLockedSize; _tweenChange = endPos - _tweenStart; } else if (_footerLockedSize > 0 && endPos[_refreshBarAxis] == -_overlapSize[_refreshBarAxis]) { float max = _overlapSize[_refreshBarAxis]; if (max == 0) max = Mathf.Max(_contentSize[_refreshBarAxis] + _footerLockedSize - _viewSize[_refreshBarAxis], 0); else max += _footerLockedSize; endPos[_refreshBarAxis] = -max; _tweenChange = endPos - _tweenStart; } _tweenDuration.Set(TWEEN_TIME_DEFAULT, TWEEN_TIME_DEFAULT); } else { //更新速度 if (!_inertiaDisabled) { float elapsed = (Time.unscaledTime - _lastMoveTime) * 60 - 1; if (elapsed > 1) _velocity = _velocity * Mathf.Pow(0.833f, elapsed); //根据速度计算目标位置和需要时间 endPos = UpdateTargetAndDuration(_tweenStart); } else _tweenDuration.Set(TWEEN_TIME_DEFAULT, TWEEN_TIME_DEFAULT); Vector2 oldChange = endPos - _tweenStart; //调整目标位置 LoopCheckingTarget(ref endPos); if (_pageMode || _snapToItem) AlignPosition(ref endPos, true); _tweenChange = endPos - _tweenStart; if (_tweenChange.x == 0 && _tweenChange.y == 0) { UpdateScrollBarVisible(); return; } //如果目标位置已调整,随之调整需要时间 if (_pageMode || _snapToItem) { FixDuration(0, oldChange.x); FixDuration(1, oldChange.y); } } StartTween(2); } private void __mouseWheel(EventContext context) { if (!_mouseWheelEnabled) return; InputEvent evt = context.inputEvent; float delta = evt.mouseWheelDelta / Stage.devicePixelRatio; if (_snapToItem && Mathf.Abs(delta) < 1) delta = Mathf.Sign(delta); if (_overlapSize.x > 0 && _overlapSize.y == 0) { float step = _pageMode ? _pageSize.x : _scrollStep; SetPosX(_xPos + step * delta, false); } else { float step = _pageMode ? _pageSize.y : _scrollStep; SetPosY(_yPos + step * delta, false); } } private void __rollOver() { _hover = true; UpdateScrollBarVisible(); } private void __rollOut() { _hover = false; UpdateScrollBarVisible(); } internal void UpdateClipSoft() { Vector2 softness = _owner.clipSoftness; if (softness.x != 0 || softness.y != 0) { _maskContainer.clipSoftness = new Vector4( (_container.x >= 0 || !_softnessOnTopOrLeftSide) ? 0 : softness.x, (_container.y >= 0 || !_softnessOnTopOrLeftSide) ? 0 : softness.y, (-_container.x - _overlapSize.x >= 0) ? 0 : softness.x, (-_container.y - _overlapSize.y >= 0) ? 0 : softness.y); } else _maskContainer.clipSoftness = null; } private void UpdateScrollBarPos() { if (_vtScrollBar != null) _vtScrollBar.setScrollPerc(_overlapSize.y == 0 ? 0 : Mathf.Clamp(-_container.y, 0, _overlapSize.y) / _overlapSize.y); if (_hzScrollBar != null) _hzScrollBar.setScrollPerc(_overlapSize.x == 0 ? 0 : Mathf.Clamp(-_container.x, 0, _overlapSize.x) / _overlapSize.x); UpdateClipSoft(); CheckRefreshBar(); } public void UpdateScrollBarVisible() { if (_vtScrollBar != null) { if (_viewSize.y <= _vtScrollBar.minSize || _vScrollNone) _vtScrollBar.displayObject.visible = false; else UpdateScrollBarVisible2(_vtScrollBar); } if (_hzScrollBar != null) { if (_viewSize.x <= _hzScrollBar.minSize || _hScrollNone) _hzScrollBar.displayObject.visible = false; else UpdateScrollBarVisible2(_hzScrollBar); } } private void UpdateScrollBarVisible2(GScrollBar bar) { if (_scrollBarDisplayAuto) GTween.Kill(bar, TweenPropType.Alpha, false); if (_scrollBarDisplayAuto && !_hover && _tweening == 0 && !_dragged && !bar.gripDragging) { if (bar.displayObject.visible) GTween.To(1, 0, 0.5f).SetDelay(0.5f).OnComplete(_hideScrollBarDelegate).SetTarget(bar, TweenPropType.Alpha); } else { bar.alpha = 1; bar.displayObject.visible = true; } } private void __barTweenComplete(GTweener tweener) { GObject bar = (GObject)tweener.target; bar.alpha = 1; bar.displayObject.visible = false; } float GetLoopPartSize(float division, int axis) { return (_contentSize[axis] + (axis == 0 ? ((GList)_owner).columnGap : ((GList)_owner).lineGap)) / division; } /// /// 对当前的滚动位置进行循环滚动边界检查。当到达边界时,回退一半内容区域(循环滚动内容大小通常是真实内容大小的偶数倍)。 /// /// bool LoopCheckingCurrent() { bool changed = false; if (_loop == 1 && _overlapSize.x > 0) { if (_xPos < 0.001f) { _xPos += GetLoopPartSize(2, 0); changed = true; } else if (_xPos >= _overlapSize.x) { _xPos -= GetLoopPartSize(2, 0); changed = true; } } else if (_loop == 2 && _overlapSize.y > 0) { if (_yPos < 0.001f) { _yPos += GetLoopPartSize(2, 1); changed = true; } else if (_yPos >= _overlapSize.y) { _yPos -= GetLoopPartSize(2, 1); changed = true; } } if (changed) _container.SetXY((int)-_xPos, (int)-_yPos); return changed; } /// /// 对目标位置进行循环滚动边界检查。当到达边界时,回退一半内容区域(循环滚动内容大小通常是真实内容大小的偶数倍)。 /// /// void LoopCheckingTarget(ref Vector2 endPos) { if (_loop == 1) LoopCheckingTarget(ref endPos, 0); if (_loop == 2) LoopCheckingTarget(ref endPos, 1); } void LoopCheckingTarget(ref Vector2 endPos, int axis) { if (endPos[axis] > 0) { float halfSize = GetLoopPartSize(2, axis); float tmp = _tweenStart[axis] - halfSize; if (tmp <= 0 && tmp >= -_overlapSize[axis]) { endPos[axis] -= halfSize; _tweenStart[axis] = tmp; } } else if (endPos[axis] < -_overlapSize[axis]) { float halfSize = GetLoopPartSize(2, axis); float tmp = _tweenStart[axis] + halfSize; if (tmp <= 0 && tmp >= -_overlapSize[axis]) { endPos[axis] += halfSize; _tweenStart[axis] = tmp; } } } void LoopCheckingNewPos(ref float value, int axis) { if (_overlapSize[axis] == 0) return; float pos = axis == 0 ? _xPos : _yPos; bool changed = false; if (value < 0.001f) { value += GetLoopPartSize(2, axis); if (value > pos) { float v = GetLoopPartSize(6, axis); v = Mathf.CeilToInt((value - pos) / v) * v; pos = Mathf.Clamp(pos + v, 0, _overlapSize[axis]); changed = true; } } else if (value >= _overlapSize[axis]) { value -= GetLoopPartSize(2, axis); if (value < pos) { float v = GetLoopPartSize(6, axis); v = Mathf.CeilToInt((pos - value) / v) * v; pos = Mathf.Clamp(pos - v, 0, _overlapSize[axis]); changed = true; } } if (changed) { if (axis == 0) _container.x = -(int)pos; else _container.y = -(int)pos; } } /// /// 从oldPos滚动至pos,调整pos位置对齐页面、对齐item等(如果需要)。 /// /// /// void AlignPosition(ref Vector2 pos, bool inertialScrolling) { if (_pageMode) { pos.x = AlignByPage(pos.x, 0, inertialScrolling); pos.y = AlignByPage(pos.y, 1, inertialScrolling); } else if (_snapToItem) { float tmpX = -pos.x; float tmpY = -pos.y; float xDir = 0; float yDir = 0; if (inertialScrolling) { xDir = pos.x - _containerPos.x; yDir = pos.y - _containerPos.y; } _owner.GetSnappingPositionWithDir(ref tmpX, ref tmpY, xDir, yDir); if (pos.x < 0 && pos.x > -_overlapSize.x) pos.x = -tmpX; if (pos.y < 0 && pos.y > -_overlapSize.y) pos.y = -tmpY; } } /// /// 从oldPos滚动至pos,调整目标位置到对齐页面。 /// /// /// /// /// float AlignByPage(float pos, int axis, bool inertialScrolling) { int page; if (pos > 0) page = 0; else if (pos < -_overlapSize[axis]) page = Mathf.CeilToInt(_contentSize[axis] / _pageSize[axis]) - 1; else { page = Mathf.FloorToInt(-pos / _pageSize[axis]); float change = inertialScrolling ? (pos - _containerPos[axis]) : (pos - _container.xy[axis]); float testPageSize = Mathf.Min(_pageSize[axis], _contentSize[axis] - (page + 1) * _pageSize[axis]); float delta = -pos - page * _pageSize[axis]; //页面吸附策略 if (Mathf.Abs(change) > _pageSize[axis])//如果滚动距离超过1页,则需要超过页面的一半,才能到更下一页 { if (delta > testPageSize * 0.5f) page++; } else //否则只需要页面的1/3,当然,需要考虑到左移和右移的情况 { if (delta > testPageSize * (change < 0 ? UIConfig.defaultScrollPagingThreshold : (1 - UIConfig.defaultScrollPagingThreshold))) page++; } //重新计算终点 pos = -page * _pageSize[axis]; if (pos < -_overlapSize[axis]) //最后一页未必有pageSize那么大 pos = -_overlapSize[axis]; } //惯性滚动模式下,会增加判断尽量不要滚动超过一页 if (inertialScrolling) { float oldPos = _tweenStart[axis]; int oldPage; if (oldPos > 0) oldPage = 0; else if (oldPos < -_overlapSize[axis]) oldPage = Mathf.CeilToInt(_contentSize[axis] / _pageSize[axis]) - 1; else oldPage = Mathf.FloorToInt(-oldPos / _pageSize[axis]); int startPage = Mathf.FloorToInt(-_containerPos[axis] / _pageSize[axis]); if (Mathf.Abs(page - startPage) > 1 && Mathf.Abs(oldPage - startPage) <= 1) { if (page > startPage) page = startPage + 1; else page = startPage - 1; pos = -page * _pageSize[axis]; } } return pos; } /// /// 根据当前速度,计算滚动的目标位置,以及到达时间。 /// /// /// Vector2 UpdateTargetAndDuration(Vector2 orignPos) { Vector2 ret = Vector2.zero; ret.x = UpdateTargetAndDuration(orignPos.x, 0); ret.y = UpdateTargetAndDuration(orignPos.y, 1); return ret; } float UpdateTargetAndDuration(float pos, int axis) { float v = _velocity[axis]; float duration = 0; if (pos > 0) pos = 0; else if (pos < -_overlapSize[axis]) pos = -_overlapSize[axis]; else { //以屏幕像素为基准 float v2 = Mathf.Abs(v) * _velocityScale; //在移动设备上,需要对不同分辨率做一个适配,我们的速度判断以1136分辨率为基准 if (Stage.touchScreen) v2 *= 1136f / Mathf.Max(Screen.width, Screen.height); //这里有一些阈值的处理,因为在低速内,不希望产生较大的滚动(甚至不滚动) float ratio = 0; if (_pageMode || !Stage.touchScreen) { if (v2 > 500) ratio = Mathf.Pow((v2 - 500) / 500, 2); } else { if (v2 > 1000) ratio = Mathf.Pow((v2 - 1000) / 1000, 2); } if (ratio != 0) { if (ratio > 1) ratio = 1; v2 *= ratio; v *= ratio; _velocity[axis] = v; //算法:v*(_decelerationRate的n次幂)= 60,即在n帧后速度降为60(假设每秒60帧)。 duration = Mathf.Log(60 / v2, _decelerationRate) / 60; //计算距离要使用本地速度 //理论公式貌似滚动的距离不够,改为经验公式 //float change = (int)((v/ 60 - 1) / (1 - _decelerationRate)); float change = (int)(v * duration * 0.4f); pos += change; } } if (duration < TWEEN_TIME_DEFAULT) duration = TWEEN_TIME_DEFAULT; _tweenDuration[axis] = duration; return pos; } /// /// 根据修改后的tweenChange重新计算减速时间。 /// void FixDuration(int axis, float oldChange) { if (_tweenChange[axis] == 0 || Mathf.Abs(_tweenChange[axis]) >= Mathf.Abs(oldChange)) return; float newDuration = Mathf.Abs(_tweenChange[axis] / oldChange) * _tweenDuration[axis]; if (newDuration < TWEEN_TIME_DEFAULT) newDuration = TWEEN_TIME_DEFAULT; _tweenDuration[axis] = newDuration; } void StartTween(int type) { _tweenTime.Set(0, 0); _tweening = type; Timers.inst.AddUpdate(_tweenUpdateDelegate); UpdateScrollBarVisible(); } void KillTween() { if (_tweening == 1) //取消类型为1的tween需立刻设置到终点 { _container.xy = _tweenStart + _tweenChange; _onScroll.Call(); } _tweening = 0; Timers.inst.Remove(_tweenUpdateDelegate); UpdateScrollBarVisible(); _onScrollEnd.Call(); } void CheckRefreshBar() { if (_header == null && _footer == null) return; float pos = _container.xy[_refreshBarAxis]; if (_header != null) { if (pos > 0) { if (_header.displayObject.parent == null) _maskContainer.AddChildAt(_header.displayObject, 0); Vector2 vec; vec = _header.size; vec[_refreshBarAxis] = pos; _header.size = vec; } else { if (_header.displayObject.parent != null) _maskContainer.RemoveChild(_header.displayObject); } } if (_footer != null) { float max = _overlapSize[_refreshBarAxis]; if (pos < -max || max == 0 && _footerLockedSize > 0) { if (_footer.displayObject.parent == null) _maskContainer.AddChildAt(_footer.displayObject, 0); Vector2 vec; vec = _footer.xy; if (max > 0) vec[_refreshBarAxis] = pos + _contentSize[_refreshBarAxis]; else vec[_refreshBarAxis] = Mathf.Max(Mathf.Min(pos + _viewSize[_refreshBarAxis], _viewSize[_refreshBarAxis] - _footerLockedSize), _viewSize[_refreshBarAxis] - _contentSize[_refreshBarAxis]); _footer.xy = vec; vec = _footer.size; if (max > 0) vec[_refreshBarAxis] = -max - pos; else vec[_refreshBarAxis] = _viewSize[_refreshBarAxis] - _footer.xy[_refreshBarAxis]; _footer.size = vec; } else { if (_footer.displayObject.parent != null) _maskContainer.RemoveChild(_footer.displayObject); } } } void TweenUpdate(object param) { if (_owner.displayObject == null || _owner.displayObject.isDisposed) { Timers.inst.Remove(_tweenUpdateDelegate); return; } float nx = RunTween(0); float ny = RunTween(1); _container.SetXY(nx, ny); if (_tweening == 2) { if (_overlapSize.x > 0) _xPos = Mathf.Clamp(-nx, 0, _overlapSize.x); if (_overlapSize.y > 0) _yPos = Mathf.Clamp(-ny, 0, _overlapSize.y); if (_pageMode) UpdatePageController(); } if (_tweenChange.x == 0 && _tweenChange.y == 0) { _tweening = 0; Timers.inst.Remove(_tweenUpdateDelegate); LoopCheckingCurrent(); UpdateScrollBarPos(); UpdateScrollBarVisible(); _onScroll.Call(); _onScrollEnd.Call(); } else { UpdateScrollBarPos(); _onScroll.Call(); } } float RunTween(int axis) { float newValue; if (_tweenChange[axis] != 0) { _tweenTime[axis] += Time.unscaledDeltaTime; if (_tweenTime[axis] >= _tweenDuration[axis]) { newValue = _tweenStart[axis] + _tweenChange[axis]; _tweenChange[axis] = 0; } else { float ratio = EaseFunc(_tweenTime[axis], _tweenDuration[axis]); newValue = _tweenStart[axis] + (int)(_tweenChange[axis] * ratio); } float threshold1 = 0; float threshold2 = -_overlapSize[axis]; if (_headerLockedSize > 0 && _refreshBarAxis == axis) threshold1 = _headerLockedSize; if (_footerLockedSize > 0 && _refreshBarAxis == axis) { float max = _overlapSize[_refreshBarAxis]; if (max == 0) max = Mathf.Max(_contentSize[_refreshBarAxis] + _footerLockedSize - _viewSize[_refreshBarAxis], 0); else max += _footerLockedSize; threshold2 = -max; } if (_tweening == 2 && _bouncebackEffect) { if (newValue > 20 + threshold1 && _tweenChange[axis] > 0 || newValue > threshold1 && _tweenChange[axis] == 0)//开始回弹 { _tweenTime[axis] = 0; _tweenDuration[axis] = TWEEN_TIME_DEFAULT; _tweenChange[axis] = -newValue + threshold1; _tweenStart[axis] = newValue; } else if (newValue < threshold2 - 20 && _tweenChange[axis] < 0 || newValue < threshold2 && _tweenChange[axis] == 0)//开始回弹 { _tweenTime[axis] = 0; _tweenDuration[axis] = TWEEN_TIME_DEFAULT; _tweenChange[axis] = threshold2 - newValue; _tweenStart[axis] = newValue; } } else { if (newValue > threshold1) { newValue = threshold1; _tweenChange[axis] = 0; } else if (newValue < threshold2) { newValue = threshold2; _tweenChange[axis] = 0; } } } else newValue = _container.xy[axis]; return newValue; } static float EaseFunc(float t, float d) { return (t = t / d - 1) * t * t + 1;//cubicOut } } }