NGUI 滑动组件:UIScrollView
目录:NGUI源码学习
UIScrollView:滑动组件,核心方法是Press、Drag、CalculateConstrainHelper。
核心方法、属性:
UIScrollView:滑动列表 property: smoothDragStart:按下时立即开始滑动还是添加过渡状态 restrictWithinPanel:滑动结束时是否调用RestrictWithinBounds,让content尽可能多地位于panel内 canMoveHorizontally/canMoveVertically:是否支持横向/纵向滑动,根据movement判断 shouldMoveHorizontally/shouldMoveVertically:根据包围盒大小、panel裁剪区域判断是否可以滑动 shouldMove:所有显示内容是否都不再裁剪范围内,根据包围盒bound和裁剪区域判断 function: OnScrollBar:滚动条拉动事件 RestrictWithinBounds:让content尽可能多地位于panel内,返回此操作所需的Vector2偏移量(位移Vector2,让content尽可能多地位于panel内) UpdateScrollbars:更新滚动条 SetDragAmount:更新uiPanel的坐标、clipOffset、scrollBar ResetPosition:重置回原点 UpdatePosition、OnScrollBar:调用SetDragAmount更新panel坐标 MoveRelative:移动UIPanel的显示内容 Press:把滑动后缓冲的动力归零、关闭滑动后的缓冲运动(滑动后是指手指离开屏幕)、记录按下点的世界坐标、创建一个平面;其实就是为滑动做准备.如果为false,会尽量把界面拉回裁剪区域内 drag:计算滑动偏移、计算缓冲动力、移动 LateUpdate:计算滑动结束后的一个缓冲效果,就是手指松开以后会继续滑动一段距离,比较平滑
movement:滑动方向
dragEffect:滑动效果,是否有惯性
constrainOnDrag:看代码是处理MomentumAndSpring效果下,滑动超过边界的情况
Vector3 constraint = CalculateConstrainHelper(canMoveHorizontally, canMoveVertically); if (constrainOnDrag) RestrictWithinBoundsHelper(true, constraint); public bool RestrictWithinBoundsHelper (bool instant, Vector3 constraint) { if (constraint.sqrMagnitude > 0.1f) { if (!instant && dragEffect == DragEffect.MomentumAndSpring) { // Spring back into place Vector3 pos = mTrans.localPosition + constraint; pos.x = Mathf.Round(pos.x); pos.y = Mathf.Round(pos.y); SpringPanel.Begin(mPanel.gameObject, pos, 8f); } else { // Jump back into place MoveRelative(constraint); // Clear the momentum in the constrained direction if (Mathf.Abs(constraint.x) > 0.01f) mMomentum.x = 0; if (Mathf.Abs(constraint.y) > 0.01f) mMomentum.y = 0; if (Mathf.Abs(constraint.z) > 0.01f) mMomentum.z = 0; mScroll = 0f; } return true; } return false; }
disableDragIfFits:当界面全部位于视图内(裁剪区域)时,true:无法拖动;false:可以拖动内容到边界外,不过会有阻力
smoothDragStart:按下时立即开始滑动还是从0速度开始,当开始拖动滑块的时候,如果勾上了,则有一个从 0 变成拖动速度的平滑现象,如果不勾,则开始拖动时就与拖动速度一样
restrictWithinPanel:滑动结束时是否调用RestrictWithinBounds,让content尽可能多地位于panel内
Momenturm Amount:滑动后,惯性滑行的距离
开始之前,我们先思考下,在unity里要实现拖动一张图片,最方便的实现方法是什么?
其实就3步:鼠标点触摸touch是一样的
1.在鼠标按下时记录按下点的坐标,
2.鼠标抬起时,把当前的鼠标位置坐标减去按下前的坐标,这就是滑动的偏移量,当然一般会在update轮询鼠标坐标,以便在拖动时更新拖动对象的坐标。NGUI的滑动事件是在UICamera的update方法里实现的,通过go.SendMessage方法分发出去,所以只需要在Drag处理逻辑就可以了。
3.这边还可能涉及到坐标的转换,点击坐标一般是屏幕坐标。
Ray ray = smoothDragStart ? UICamera.currentCamera.ScreenPointToRay(UICamera.currentTouch.pos - mDragStartOffset) : UICamera.currentCamera.ScreenPointToRay(UICamera.currentTouch.pos); float dist = 0f; if (mPlane.Raycast(ray, out dist)) { Vector3 currentPos = ray.GetPoint(dist); Vector3 offset = currentPos - mLastPos; mLastPos = currentPos; }
NGUI的UIScrollView实现逻辑跟上面是一样:
1.通过UIDragScrollView监听UICamera发出的OnPress/OnDrag/OnScroll事件,然后调用UIScrollView对应的接口。
2.在Press/Drag里处理拖动事件。
3.UIScrollView在拖动结束的时候会把滑动内容尽可能的显示在裁剪区域内,这是可选的。比如下面的图1会被修改成图2显示,尽可能多的让图片和裁剪区域重合。
|
4.UIScrollView实现了惯性的效果,简单说就是手指抬起后,还会惯性滑动一段距离。
SpringPanel:移动panel,配合ScrollView使用,移动的是UIPanel的裁剪区域clipOffset。
function:
Update:unity的Update函数只在OnEnable的时候调用,在这边轮询AdvanceTowardsPosition方法实现坐标移动
AdvanceTowardsPosition:根据strength、target对坐标差值,最后赋值给mPanel.clipOffset实现panel位移效果
Begin:开始动画,实际是设置了组件的enabled = true
end:结束动画,实际是设置了组件的enabled = false
UIWrapContent:基于UIScrollView实现循环滑动列表。
核心方法WrapContent。
原理实现:
1.NGUI的UIScrollView滑动时,实际上时修改的UIPanel的LocalPosition和clipOffset,拿在初始化的时候就可以确定每个item(data)的LocalPosition。
例如第i行j列,相对于父节点的坐标:
private Vector3 GetItemLocalPos(int i, int j) { float posX = size.x * i; float posY = size.y * j; if (m_moveType == UIScrollView.Movement.Vertical) { posX = size.x * j; posY = size.y * i; } Vector3 localPos = new Vector3(posX, -posY, 0); return localPos; }
初始化完成的大概是这样的,无非就是按顺序排序子对象
2.监听滑动事件,UIPanel的clipOffset发生变化时,会触发onClipMove事件,我们监听这个事件,然后在事件里实现交换逻辑。
3.交换逻辑。
1.WrapContent的交换逻辑:轮询每个对象,如果对象不在裁剪区域内,移动对象(最下面的移动到最上面,最左侧的移动到最右侧)。
Transform t = mChildren[i]; float distance = t.localPosition.x - center.x; float extents = itemSize * mChildren.Count * 0.5f; if (distance < -extents) { Vector3 pos = t.localPosition; pos.x += ext2; distance = pos.x - center.x; int realIndex = Mathf.RoundToInt(pos.x / itemSize); if (minIndex == maxIndex || (minIndex <= realIndex && realIndex <= maxIndex)) { t.localPosition = pos; UpdateItem(t, i); } else allWithinRange = false; } else if (distance > extents) { Vector3 pos = t.localPosition; pos.x -= ext2; distance = pos.x - center.x; int realIndex = Mathf.RoundToInt(pos.x / itemSize); if (minIndex == maxIndex || (minIndex <= realIndex && realIndex <= maxIndex)) { t.localPosition = pos; UpdateItem(t, i); } else allWithinRange = false; }
2.优化逻辑:监听本次滑动的距离,滑动超过一格才处理,而且只处理最边缘一列(最下面、最上面、最左侧、最右侧)。
//滑动事件 private void OnPanelLoopClipMove(UIPanel panel) { Vector3 delata = m_panelInitPos - panel.transform.localPosition; float distance = -1; int index; //当前显示出来的第一个格子,在grid数据中的index distance = delata.y != 0 ? delata.y : delata.x; // 满的时候向上滑不管它(滑到头了) if (distance > 0 && m_moveType == UIScrollView.Movement.Vertical) return; if (distance < 0 && m_moveType == UIScrollView.Movement.Horizontal) return; CalDistance(distance, out distance, out index); // 拖拽不满一个单元格 if (index == m_lastDataIndex) return; // 拉到底了 if (index + m_fillCount >= m_dataArrayNum) { index = m_dataArrayNum - m_fillCount; } // 重刷 int offset = Math.Abs(index - m_lastDataIndex); for (int i = 1; i <= offset; i++) { //上(左)移动到下(右) MoveGridItem(m_lastDataIndex < index); } m_lastDataIndex = index; } private void CalDistance(float distance, out float targetDis, out int index) { FixBoxPostion(); targetDis = Mathf.Abs(distance); index = Mathf.FloorToInt(targetDis / cellLength); } private void MoveGridItem(bool isTopToBottom) { // 判断是否是 上(左)移动到下(右) int curIndex; int itemIndex; int sign; if (isTopToBottom) { curIndex = m_maxIndex + 1; itemIndex = 0; sign = 1; } else { curIndex = m_minIndex - 1; itemIndex = m_items.Count - 1; sign = -1; } List<Transform> items; items = m_items[itemIndex]; int targetIndex = itemIndex == 0 ? m_items.Count - 1 : 0; m_items.Remove(items); m_items.Insert(targetIndex, items); for (int i = 0; i < items.Count; i++) { int dataIndex = curIndex * maxPerLine + i; if (dataIndex < 0) { break; } if (dataIndex > m_loopMax - 1) { break; } if (m_listener != null) { int index = Array.IndexOf(m_itemsTran, items[i]); m_listener(index, dataIndex); } items[i].localPosition = m_itemPos[dataIndex]; } m_minIndex += sign; m_maxIndex += sign; }
4.分发事件通知obj。
m_listener(index, dataIndex); index是对象数组的下标,dataIndex是数据数组的下表
local item = self._friendItems[objIdx+1] --lua
local data = self._friendDatas[dataIdx+1]
5. UIWrapContent待扩展功能:
1.只支持单行或单列。
2.固定子节点,适配不方便。
3.轮询全部节点没必要。
一直想把之前工作、学习时记录的文档整理到博客上,一方面温故而知新,一方面和大家一起学习 -程序小白