UGUI的ScrollRect的解析


这几个看了些UGUI的源码,记录一下关于ScrollRect的原理。

ScrollRect不能单独使用,在Inspector里显示为:,Content为要滑动的容器,不能为空,Viewport为显示的容器,为空则为当前ScrollRect组件所在的RectTransform,单选项Horizontal和Vertical为横向滑动和纵向滑动,MovementType为滑动类型,HorizontalScrollBar和VerticalScrollBar为横向和纵向滑动条。一般只要设置Content和选择Horizontal和Vertical就可以。

自己写了一个滑动组件,代码如下:

using System.Collections;
using System.Collections.Generic; using UnityEngine; using UnityEngine.EventSystems; using UnityEngine.UI; public class ScrollRectTest : MonoBehaviour, IBeginDragHandler, IEndDragHandler, IDragHandler { public RectTransform content; private RectTransform viewRect; private Bounds contentBounds; private Bounds viewBounds; public bool horizontal; public bool vertical; private bool isDrag; private Vector2 pointStartLocalCursor; private Vector2 contentStartPosition; void Start() { viewRect = this.transform as RectTransform; } public void OnBeginDrag(PointerEventData eventData) { if(eventData.button == PointerEventData.InputButton.Left) { isDrag = true; UpdateBounds(); pointStartLocalCursor = Vector2.zero; RectTransformUtility.ScreenPointToLocalPointInRectangle(viewRect, eventData.position,eventData.pressEventCamera, out pointStartLocalCursor); contentStartPosition = content.anchoredPosition; } } public void OnEndDrag(PointerEventData eventData) { if (eventData.button == PointerEventData.InputButton.Left) { isDrag = false; } } public void OnDrag(PointerEventData eventData) { if (eventData.button == PointerEventData.InputButton.Left && isDrag == true) { Vector2 localCursor; if (!RectTransformUtility.ScreenPointToLocalPointInRectangle(viewRect, eventData.position, eventData.pressEventCamera, out localCursor)) { return; } UpdateBounds(); var cursorOffset = localCursor - pointStartLocalCursor; var position = contentStartPosition + cursorOffset; var offset = CalculateOffest(position - content.anchoredPosition); position += offset; SetContentAnchoredPosition(position); } } private void SetContentAnchoredPosition(Vector2 positoion) { if(!horizontal) { positoion.x = content.anchoredPosition.x; } if(!vertical) { positoion.y = content.anchoredPosition.y; } if(positoion != content.anchoredPosition) { content.anchoredPosition = positoion; } } private Vector2 CalculateOffest(Vector2 delta) { Vector2 offset = Vector2.zero; Vector2 min = contentBounds.min; Vector2 max = contentBounds.max; if(horizontal) { min.x += delta.x; max.x += delta.x; if(min.x > viewBounds.min.x) { offset.x = viewBounds.min.x - min.x; }else if(max.x < viewBounds.max.x) { offset.x = viewBounds.max.x - max.x; } } if(vertical) { min.y += delta.y; max.y += delta.y; if(min.y > viewBounds.min.y) { offset.y = viewBounds.min.y - min.y; }else if(max.y < viewBounds.max.y) { offset.y = viewBounds.max.y - max.y; } } return offset; } private void UpdateBounds() { viewBounds = new Bounds(viewRect.rect.center, viewRect.rect.size); if(content != null) { var vMin = new Vector3(float.MaxValue, float.MaxValue, float.MaxValue); var vMax = new Vector3(float.MinValue, float.MinValue, float.MinValue); var matrix = viewRect.worldToLocalMatrix; Vector3[] worldCorners = new Vector3[4]; content.GetWorldCorners(worldCorners); for(int i = 0; i < 4; i++) { var v = matrix.MultiplyPoint3x4(worldCorners[i]); vMin = Vector3.Min(v, vMin); vMax = Vector3.Max(v, vMax); } contentBounds = new Bounds(vMin, Vector3.zero); contentBounds.Encapsulate(vMax); Vector3 contentSize = contentBounds.size; Vector3 contentPos = contentBounds.center; Vector3 excess = viewBounds.size - contentSize; if(excess.x > 0) { contentSize.x = viewBounds.size.x; contentPos.x -= excess.x * (content.pivot.x - 0.5f); } if(excess.y > 0) { contentSize.y = viewBounds.size.y; contentPos.y -= excess.y * (content.pivot.y - 0.5f); } contentBounds.size = contentSize; contentBounds.center = contentPos; } } }

 

1.RectTransformUtility.ScreenPointToLocalPointInRectangle(viewRect, eventData.position,eventData.pressEventCamera, out pointStartLocalCursor);此方法用于确定你点击到滑动组件的位置。第一个参数是当前的滑动组件容器,第二个参数为你点击的世界坐标位置,
第三个参数为Ui的相机,第四个参数用来保存点击到第一个参数容器的位置.
2.contentBounds的计算。首先计算显示容器的边界,再计算要滑动的容器的边界。取两个都边界的最大size,得到contentBounds。var matrix = viewRect.worldToLocalMatrix;此方法用于获取显示容器的从世界坐标到本地坐标的转换矩阵。content.GetWorldCorners(worldCorners);此方法用于获取滑动的容器的四个角在世界坐标中的位置。依次是:左下,左上,右上,右下。var v = matrix.MultiplyPoint3x4(worldCorners[i]);用matrix这个转换矩阵转换坐标,此处是把世界坐标位置转换到显示容器所在的本地坐标位置。Vector3 excess = viewBounds.size - contentSize;用显示边界框减滑动容器的边界框,如果x大于0,说明显示容器更大,这时contentBounds的size.x等于显示容器边界框的size.x,这时由于size.x有变,所以中心点也要跟着变。

3.var offset = CalculateOffest(position - content.anchoredPosition);计算偏移,主要是为了使content在留出空白。比如:当min.y大于容器的min.y时,说明content太靠上了,这时需要修正,修正的大小时offset.y = 容器的min.y - min.y.


免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



 
粤ICP备18138465号  © 2018-2025 CODEPRJ.COM