前言
如题的功能在项目中经常用到,滚动的信息内容,我们用scrollbar的value来控制滚动是可以实现的,不过当value为1时,我们从0继续循环会造成有闪烁的情况而且比较突兀,经过一段时间的研究终于实现了该功能。
效果
分别方向的移动
实现
自动滚动的思路就是不断的增加某一个方向的偏移值就可以实现,循环滚动时,将最早移出的节点移至滚动队列的最尾端即可,同时计算新的偏移值并同步,让列表看不出抖动,即可实现一直循环滚动,在此过程中将禁用ScrollRect组件,因为ScrollRect组件在节点顺序变化时会造成显示抖动,同时还需根据排序的组件(HorizontalOrVerticalLayoutGroup/GridLayoutGroup)进行间隔的计算。当鼠标悬停时启用ScrollRect组件并暂停滚动,鼠标离开时启用自动滚动并禁用ScrollRect。
搭建UI
如图的搭建一个列表
定义滚动方向
public enum ScrollDir
{
BottomToTop = 1,
TopToBottom = 2,
LeftToRight = 3,
RightToLeft = 4
}
定义如上四个方向方便选择设置和分开处理。
初始化数值
scrollrect = gameObject.GetComponent<ScrollRect>();
scrolltran = scrollrect.GetComponent<RectTransform>();
LayoutGroup = scrollrect.content.GetComponent<HorizontalOrVerticalLayoutGroup>();
GridGroup = scrollrect.content.GetComponent<GridLayoutGroup>();
et = gameObject.GetComponent<EventTrigger>();
if (et == null)
et = gameObject.AddComponent<EventTrigger>();
if (LayoutGroup != null)
Space = LayoutGroup.spacing;
else if (GridGroup != null)
{
switch (AutoScrollDir)
{
case ScrollDir.BottomToTop://由底至顶滚动 向上
case ScrollDir.TopToBottom://由顶至底滚动 向下
Space = GridGroup.spacing.y;
break;
case ScrollDir.LeftToRight://由左至右滚动 →
case ScrollDir.RightToLeft://由右至左滚动 ←
Space = GridGroup.spacing.x;
break;
default:
Space = 0;
break;
}
}
if (LayoutGroup != null && scrollrect.content.childCount > 0)
{
ItemWidth = scrollrect.content.GetChild(0).GetComponent<RectTransform>().sizeDelta.x;
ItemHeight = scrollrect.content.GetChild(0).GetComponent<RectTransform>().sizeDelta.y;
}
else if (GridGroup != null)
{
ItemWidth = GridGroup.cellSize.x;
ItemHeight = GridGroup.cellSize.y;
}
AddETEvent(et, EventTriggerType.PointerEnter, OnPointerIn);
AddETEvent(et, EventTriggerType.PointerExit, OnPointerOut);
如上代码初始化时候,主要寻找相关组件,排序组件和滚动组件等,同时根据不同组件获取间隔数值,还有排序对象的高宽度,以及事件绑定。这里通过添加EventTrigger组件,并绑定PointerEnter 和 PointerExit来实现鼠标悬停和退出功能。
界面的配置如图:
自动滚动
void DoAutoScroll()
{
switch (AutoScrollDir)
{
case ScrollDir.BottomToTop:
{
if (scrollrect.content.sizeDelta.y > scrolltran.sizeDelta.y + (ItemHeight + Space))
{
scrollrect.content.anchoredPosition3D += new Vector3(0, step, 0);
if (scrollrect.content.anchoredPosition3D.y >= (scrollrect.content.sizeDelta.y - scrolltran.sizeDelta.y) / 2)
{
if (GridGroup != null && GridGroup.constraintCount > 1)
{
for (int i = 0; i < GridGroup.constraintCount; i++)
scrollrect.content.GetChild(0).transform.SetAsLastSibling();
scrollrect.content.anchoredPosition3D -= new Vector3(0, (ItemHeight + Space), 0);
}
else
{
scrollrect.content.GetChild(0).transform.SetAsLastSibling();
scrollrect.content.anchoredPosition3D -= new Vector3(0, (ItemHeight + Space), 0);
}
}
}
}
break;
case ScrollDir.TopToBottom:
{
if (scrollrect.content.sizeDelta.y > scrolltran.sizeDelta.y + (ItemHeight + Space))
{
scrollrect.content.anchoredPosition3D -= new Vector3(0, step, 0);
if (scrollrect.content.anchoredPosition3D.y <= (scrollrect.content.sizeDelta.y - scrolltran.sizeDelta.y) / 2)
{
if (GridGroup != null && GridGroup.constraintCount > 1)
{
for (int i = 0; i < GridGroup.constraintCount; i++)
scrollrect.content.GetChild(scrollrect.content.childCount - 1).transform.SetAsFirstSibling();
scrollrect.content.anchoredPosition3D += new Vector3(0, (ItemHeight + Space), 0);
}
else
{
scrollrect.content.GetChild(scrollrect.content.childCount - 1).transform.SetAsFirstSibling();
scrollrect.content.anchoredPosition3D += new Vector3(0, (ItemHeight + Space), 0);
}
}
}
}
break;
case ScrollDir.LeftToRight:
{
if (scrollrect.content.sizeDelta.x > scrolltran.sizeDelta.x + (ItemWidth + Space))
{
scrollrect.content.anchoredPosition3D += new Vector3(step, 0, 0);
if (scrollrect.content.anchoredPosition3D.x >= -(scrollrect.content.sizeDelta.x - scrolltran.sizeDelta.x) / 2)
{
if (GridGroup != null && GridGroup.constraintCount > 1)
{
for (int i = 0; i < GridGroup.constraintCount; i++)
scrollrect.content.GetChild(scrollrect.content.childCount - 1).transform.SetAsFirstSibling();
scrollrect.content.anchoredPosition3D -= new Vector3((ItemWidth + Space), 0, 0);
}
else
{
scrollrect.content.GetChild(scrollrect.content.childCount - 1).transform.SetAsFirstSibling();
scrollrect.content.anchoredPosition3D -= new Vector3((ItemWidth + Space), 0, 0);
}
}
}
}
break;
case ScrollDir.RightToLeft:
{
if (scrollrect.content.sizeDelta.x > scrolltran.sizeDelta.x + (ItemWidth + Space))
{
scrollrect.content.anchoredPosition3D -= new Vector3(step, 0, 0);
if (scrollrect.content.anchoredPosition3D.x <= -(scrollrect.content.sizeDelta.x - scrolltran.sizeDelta.x) / 2)
{
if (GridGroup != null && GridGroup.constraintCount > 1)
{
for (int i = 0; i < GridGroup.constraintCount; i++)
scrollrect.content.GetChild(0).transform.SetAsLastSibling();
scrollrect.content.anchoredPosition3D += new Vector3((ItemWidth + Space), 0, 0);
}
else
{
scrollrect.content.GetChild(0).transform.SetAsLastSibling();
scrollrect.content.anchoredPosition3D += new Vector3((ItemWidth + Space), 0, 0);
}
}
}
}
break;
default:
break;
}
}
这一段就是核心的代码,分别处理了四个方向滚动的过程,大致思路如前面提到的。主要还是Content具备自动滚动的条件:Content的高或者宽超过了视窗+1倍高宽和间隔的长度。开始自动滚动。 移动首个移出节点至队尾的条件:移动的位置已经超过一半。
其中的还有些GridLayoutGroup组件的处理,因为移动节点可能需要同时移动一排 或者一列,具体看脚本。有些特定的设置后面会进行说明。
工程源码
https://download.csdn.net/download/qq_33789001/33215369 如果打不开就是还没审核,最近审核很慢。
注意
这里特别注意的滚动页面的设置,我为了简便快速实现,这里就分成了横竖两个方向的设置做了固定适配。
横向
即从左到右或者从右到左的情况。Content如下设置:
竖向
即从上到下或者从下到上,Content:
如果不这样设置可能会有异常。
GridLayoutGroup的StartCorner设置也得是类似的设置,并且多行或者多列时需要固定值:
这个在移动节点时需要用到,不设置可能异常。也可尝试手动修改代码适配。
|