第一次在正式项目里用UGUI,遇到不少问题。其中ScrollRect是比较让人恼火的。看了很多网上已有教程和原代码,终于做出满足项目需求的样子来了。简单分享一下。如有错误的地方,希望大家可以指出,一起进步!
制作一个滚动列表,首先就会想到ScrollRect这个组件。但与NGUI的不同,NGUI的UIScrollView把很多功能都写好了,或者都写到滚动列表专用的几个脚本里。但UGUI的各组件更加分离,比如ScrollRect里面的布局,不是在做滚动列表才使用,而是对所有布局情况下都通用的。我们要做的是把几个通用组件合理组合起来,做出某种特定功能的东西。
首先来看下我的功能树目录:
跟官方的例子很像吧。这里的ScrollPanel是我的一个界面根节点,比如你可以把它当成一个道具界面,ScrollRect是这个界面的主要内容,放了很多道具。整个ScrollRect分了3层,中间层ViewPoint是用来指定滚动窗口大小的,出了ViewPoint范围的Item就看不见了。Content节点是放布局组件的,你可以用水平布局或垂直布局等。
ScrollRect节点上的组件如下:
主要就是挂ScrollRect脚本,在两处红框分别挂Content节点和ViewPoint节点。因为我的例子里是做水平滚动的,所有在Horizontal里打勾就行了。然后MovementType指定为Elastic,就是有弹性,在Content内容大小大小ViewPoint可视区域大小时,会把Content的内容自动弹回边界位置。
ViewPoint节点的组件如下:
这里要做的是指定滚动可视区域的大小,比如我指定大小为720*240,效果如下:
白色区域,就是在ViewPoint里指定的可视区域,Item出超出这个区域就会看不见了。其实白色区域不需要显示出来,把Mask组件的Show Mask Graphic的勾取消就行了。
Content节点的组件如下:
这个组件非常关键,用了两个UGUI的重要组件,一个是Horizontal Layout Group,水平布局组。用来把Content下面的子节点自动水平布局,Padding用来指定上下左右的偏移量,注意,Left和Right尽量一致,否则Content在自动调整大小时会有问题。Content Size Fitter组件用于让Content自动根据其子节点的大小来调整自身的大小。如果没有这个组件,在动态添加Item时,Content的大小就不会自动调整大小了,在拖动时就会发现有些位置拖不过去。因为Content的大小一定要比所有子节点的大小要大,才能正常显示。你可以试一下把这个组件去掉,然后在运行时动态添加item,看看有什么情况发生。当然,有些人喜欢自己写布局或自己写代码动态调整Content的大小。我喜欢用UGUI的组件,既然有现成的,就把它用得如鱼得水。
注意看我这里的Pivot(轴心点),我把它的x设置为0。如果不设置为0会怎样,看看打开界面后的效果。
这是我运行时打开界面的效果,第一个item没有显示在第一的位置,这是为什么。因为我们用了Content Size Fitter组件,这个组件在自动调整UI元素的大小时,是根据UI元素自身的轴心点来扩展大小的,也就是说,如果我Content的轴心点在0.5*0.5位置的话,Content的大小就会向四周扩大。如果我把Pivot设置为0*0.5,效果如下:
Content就会从轴心点(0,0.5)向外扩大,那前面的item就可以在打开界面时就看得到了。
有些界面我们会打开了关闭再打开,如果第一次打开这个滚动界面并且发生了拖动,不做任何处理只是简单隐藏界面的话,下次打开Content还是停留在上一次拖动的位置。
怎么解决?只要在代码里加一句代码随便设置一下Content的位置,它就会自动回到起始位置了。比如:
如果我们的Item是可以点击的,而且是自己重写了EventTrigger或封装了点击回调脚本,那可能会出现一个问题,在item上面拖动时没有效果,ScrollRect的拖动事件被屏蔽了。如果你的Item直接用Button组件来做的话是没问题的,但很多同学喜欢根据需求或更加便利地使用点击事件就自己写了点击回调脚本。比如,雨松大神的那个代码,如果用那种方法做按钮并把按钮放到ScrollRect上的话,在item上拖动就没效果了。为什么?因为EventTrigger继承了IDragHandler这些接口,如果你自己写的事件触发器是继承了EventTrigger,事件触发器挂在Item上就会把拖动事件截取,但是自己又没有实现IDraghandler,所以在item上面拖动就没任何反应了。所以在自己写点击按钮回调脚本时,可以根据自己需求单独继承IPointerClickHandler,不要什么接口都继承。
还有最后一个问题,策划可以需要我们自动把ScrollRect定位到某一个item上,比如在打开界面时ScrollRect就自动滚动到item9的位置,让item9显示在ViewPoint中间。这个要怎么做呢?
在NGUI里,UIScrollView有一个参数可以设置当前滚动的百分比,只要计算要滚动到的Item在所有Item中的位置比例就可以了。但在UGUI中没有这个功能。我们需要手动计算应该把Content定位到哪个位置。
///
///
指定一个
item
让其定位到
ScrollRect
中间
///
///
需要定位到的目标
public
void
CenterOnItem
(
RectTransform target
)
{
// Item is here
var itemCenterPositionInScroll
=
GetWorldPointInWidget
(
scrollRect
.
GetComponent
<
RectTransform
>(),
GetWidgetWorldPoint
(
target
));
Debug
.
Log
(
"Item Anchor Pos In Scroll: "
+
itemCenterPositionInScroll
);
// But must be here
var targetPositionInScroll
=
GetWorldPointInWidget
(
scrollRect
.
GetComponent
<
RectTransform
>(),
GetWidgetWorldPoint
(
viewPointTransform
));
Debug
.
Log
(
"Target Anchor Pos In Scroll: "
+
targetPositionInScroll
);
// So it has to move this distance
var difference
=
targetPositionInScroll
-
itemCenterPositionInScroll
;
difference
.
z
=
0f
;
var newNormalizedPosition
=
new
Vector2
(
difference
.
x
/
(
contentTransform
.
rect
.
width
-
viewPointTransform
.
rect
.
width
),
difference
.
y
/
(
contentTransform
.
rect
.
height
-
viewPointTransform
.
rect
.
height
));
newNormalizedPosition
=
scrollRect
.
normalizedPosition
-
newNormalizedPosition
;
newNormalizedPosition
.
x
=
Mathf
.
Clamp01
(
newNormalizedPosition
.
x
);
newNormalizedPosition
.
y
=
Mathf
.
Clamp01
(
newNormalizedPosition
.
y
);
DOTween
.
To
(()
=>
scrollRect
.
normalizedPosition
,
x
=>
scrollRect
.
normalizedPosition
=
x
,
newNormalizedPosition
,
3
);
}
Vector3 GetWidgetWorldPoint
(
RectTransform target
)
{
//pivot position + item size has to be included
var pivotOffset
=
new
Vector3
(
(
0.5f
-
target
.
pivot
.
x
)
*
target
.
rect
.
size
.
x
,
(
0.5f
-
target
.
pivot
.
y
)
*
target
.
rect
.
size
.
y
,
0f
);
var localPosition
=
target
.
localPosition
+
pivotOffset
;
return
target
.
parent
.
TransformPoint
(
localPosition
);
}
Vector3 GetWorldPointInWidget
(
RectTransform target
,
Vector3 worldPoint
)
{
return
target
.
InverseTransformPoint
(
worldPoint
);
}
我直接上代码了,就是调用CenterOnItem这个方法,传入一个item的RectTransform。上面用到了DoTween插件,使得在定位时可以平滑一点,不懂这个的直接百度DoTween。还有一种方法,也是计算好Item的目标位置,直接设置Content的位置
void
CenterToSelected
(
GameObject selected
)
{
var target
=
selected
.
GetComponent
<
RectTransform
>();
Vector3 maskCenterPos
=
viewPointTransform
.
position
+
(
Vector3
)
viewPointTransform
.
rect
.
center
;
Debug
.
Log
(
"Mask Center Pos: "
+
maskCenterPos
);
Vector3 itemCenterPos
=
target
.
position
;
Debug
.
Log
(
"Item Center Pos: "
+
itemCenterPos
);
Vector3 difference
=
maskCenterPos
-
itemCenterPos
;
difference
.
z
=
0
;
Vector3 newPos
=
contentTransform
.
position
+
difference
;
DOTween
.
To
(()
=>
contentTransform
.
position
,
x
=>
contentTransform
.
position
=
x
,
newPos
,
5
);
}
其实都差不多,设置ScrollRect.normalizedPosition,在源代码里也是设置Content的位置。而上一种方法,我把移动比例设置在0到1的范围,这在定位前面几个和最后几个item时,就不会跳(呵呵 呵呵呵呵我都不知道怎么讲)。总之自己多试一下吧,我也折腾了一下午,最后看了外国论坛和源代码才最终搞定了,好累。准备跨年啦,祝同学们新年快乐!
转载请注明出处!