NGUI:一个可以截断的ScrollView
有一天美术给了我一张UI界面的效果图,在我原本是滚动框的中间放了个大按钮,这就导致了我这个地方不能用再用滚动框了,于是我就想能不能把这个滚动框截断,让其中的Item在经过这个大按钮的时候能够一半出现在按钮左边,一半出现在按钮右边。于是就有了这个可截断的ScrollView。起初还以为这个要从渲染的层面做呢。其实完全不用,还是借鉴了NGUI自身的UIWrapContent。
用的是组合多个UIScrollView的方式。重点在于能够让两个ScrollView之间能够无缝连接,看起来好像真的是被截断的一样。还有就是要把握号RealIndex,效果出来之后,数据准不准就完全看这个realIndex了。
贴源码。一共就两个脚本,
BreakScrollView
负责组织这些多个ScrollView,
负责组织这些多个ScrollView,
public class BreakScrollView : MonoBehaviour {
//跟UIWrapContent一样的用来设置Item的一个回调
public delegate void OnInitializeItem(GameObject obj, int wrapIndex, int scrollIndex, int realIndex);
public int itemSize = 100;
public int minIndex=0;
public int maxIndex=0;
public bool cullContent=true;//是否隐藏超出边界的Item的标记
public bool mHorizontal = false;//滑动方向
public OnInitializeItem onInitializeItem;//设置Item的回调
protected List< UIScrollView > mScrolls;//众多的ScrollView
protected List< UIPanel > mPanels;//众多的Panel
protected List< BreakWrapContent > mWraps;//众多的WrapContent
//到当前ScrollView为止所需要的偏移补偿 也就是ScrollView的坐标需要偏移多少才能对接上一个ScrollView
protected List< float > mOffsets;
//到当前ScrollView为止,每个ScrollView能完整放下的Item的个数之和
protected List< int > mCapacity;
//到当前ScrollView为止,ScrollView Size的总长度
protected List< float > mScrollLengths;
protected virtual void Awake()
{
OnValidate();//用来限制minIndex和maxIndex,完全是从WrapContent抄来的
CacheAndInitScrollView();//缓存各ScrollView
ResetScrollPosition();//各ScrollView的复位 初始化它们的偏移量,设置好初始坐标
mPanels[0].onClipMove += OnHeadMove;//由第一个ScrollView的滑动 带动所有ScrollView的滑动,才能动的一致
}
void CacheAndInitScrollView()
{
if (mScrolls == null|| mCapacity==null||mPanels==null||mWraps==null|| mScrollLengths==null)
{
mScrolls = new List();
mPanels = new List();
mWraps = new List();
mCapacity = new List();
mScrollLengths = new List();
}
mScrolls.Clear();
mPanels.Clear();
mWraps.Clear();
mCapacity.Clear();
mScrollLengths.Clear();
for(int i=0, Imax = transform.childCount; i < Imax; i++)
{
var child = transform.GetChild(i);
var scroll = child.GetComponent();
var panel = child.GetComponent();
var wrap = scroll.GetComponentInChildren();
if (wrap == null)
wrap = scroll.transform.GetChild(0).gameObject.AddComponent();
int num = i > 0 ? mCapacity[i - 1] : 0;
float length = 0;
if(mHorizontal)
{
scroll.movement = UIScrollView.Movement.Horizontal ;
//累计到当前SV为止能完整放下的Item个数之和 包括当前的
num += (int)(panel.GetViewSize().x / (float)itemSize);
length = panel.GetViewSize().x;//当前SV的size
}
else
{
scroll.movement = UIScrollView.Movement.Vertical;
num += (int)(panel.GetViewSize().y / (float)itemSize);
length = panel.GetViewSize().y;
}
mScrolls.Add(scroll);
mPanels.Add(panel);
mWraps.Add(wrap);
mCapacity.Add(num);
mScrollLengths.Add((i > 0 ? mScrollLengths[i - 1] : 0) + length);//累计到当前SV的长度之和 包括当前
}
}
[ContextMenu("ResetScrollPosition")]
public virtual void ResetScrollPosition()
{
if (mScrolls == null || mCapacity == null || mPanels == null || mWraps == null|| mScrollLengths==null)
CacheAndInitScrollView();
if (mOffsets == null)
mOffsets = new List();
mOffsets.Clear();
float offset = 0;
for(int i=0, Imax=mScrolls.Count; i < Imax; i++)
{
Vector2 size = i>0?(mPanels[i - 1].GetViewSize()):Vector2.zero;
Vector2 pos = mScrolls[i].transform.localPosition;
if(mHorizontal)
{
offset += size.x - ((int)(size.x / itemSize)) * itemSize;
pos.x = -offset;
if (!Application.isPlaying)
{
mScrolls[i].transform.localPosition = pos;
mPanels[i].clipOffset = new Vector2(-pos.x, pos.y);
}
}
else
{
offset += size.y - ((int)(size.y / itemSize)) * itemSize;
pos.y = offset;
if (!Application.isPlaying)
{
mScrolls[i].transform.localPosition = pos;
mPanels[i].clipOffset = new Vector2(pos.x, -pos.y);
}
}
mOffsets.Add(offset);
SpringPanel.Begin(mScrolls[i].gameObject, pos, 10);
}
}
[ContextMenu("ResetWrapChildPosition")]
public virtual void ResetWrapChildPosition()
{
if (mScrolls == null || mCapacity == null || mPanels == null || mWraps == null|| mScrollLengths==null)
CacheAndInitScrollView();
for(int i=0, Imax=mWraps.Count; i < Imax; i++)
{
mWraps[i].ResetChildPostion();
}
}
public void SpringTo(float distance)
{
for(int i=0, Imax=mScrolls.Count; i < Imax; i++)
{
Vector3 pos = mScrolls[i].transform.localPosition;
if(mHorizontal)
{
pos.x += distance;
}else
{
pos.y += distance;
}
SpringPanel.Begin(mScrolls[i].gameObject, pos, 10);
}
}
public int GetRealIndex(Vector3 localposition,int scrollIndex)
{
if (mScrolls == null || mCapacity == null || mPanels == null || mWraps == null|| mScrollLengths==null)
CacheAndInitScrollView();
int capacity =scrollIndex>0? mCapacity[scrollIndex - 1]:0;
if (mHorizontal)
{ //这步是关键 只要加回到上一个SV为止能够完整放下的Item数就可以了。因为不完整的那个是和下一个SV共享的
//所以其实应该算成是下一个SV能完整放下的个数里 而下一个SV不能完整放下的那个又应该算到下下个SV,
//所以只需要加回能完整放下的个数就行了。得到的就是realIndex
//而localPos/itemSize得到的则是 假设当前SV是独立的情况下得到的realIndex,然而这个SV并不是独立的,
//它的第一个realIndex应该是上一个SV末尾不完整的那个Item的realIndex
return Mathf.RoundToInt(localposition.x / itemSize) + capacity;
}else
{
return Mathf.RoundToInt(-localposition.y / itemSize) + capacity;
}
}
void OnValidate()
{
if (maxIndex < minIndex)
maxIndex = minIndex;
if (minIndex > maxIndex)
maxIndex = minIndex;
}
/// 检查realIndex是否超出了所在SV的min和max
/// 比如第0个Item应该是在第一个SV,最后一个Item在最后一个SV 中间的也都一样 有min和max
///param name="scrollIndex"
///param name="realIndex"
public bool CheckIndexRange(int scrollIndex,int realIndex)
{
//maxIndex应该是由maxIndex减去当前SV之后所能放下的Item个数
int curMaxIndex = maxIndex - Mathf.FloorToInt((mScrollLengths[mScrollLengths.Count - 1] - mScrollLengths[scrollIndex])/itemSize);
//minIndex很显然的就是当前SV的起始Item的realIndex了。
int curMinIndex = minIndex + (scrollIndex>0?mCapacity[scrollIndex-1]:0);
return realIndex >= curMinIndex && realIndex < curMaxIndex;
//这里用来限制SV滑动的curMax和Min其实是有问题的,因为SV有个滑动的弹性,滑倒Max之后还能继续滑动一段距离,
//但是由于已经到达MaxIndex,后续的Item会被判定为超出限界而被隐藏,导致下个SV的首个Item不能到达当前SV的情况。
//但是暂时没有想到更好的办法,先用这个办法用着吧。
}
public void UpdateItem(GameObject obj,int index,int scrollIndex)
{
int realIndex = GetRealIndex(obj.transform.localPosition, scrollIndex);
if (minIndex!=maxIndex&&!CheckIndexRange(scrollIndex,realIndex))//判断超出限界则设置active为false
{
NGUITools.SetActive(obj, false);
return;
}else
{
//测试用,可删除
obj.GetComponent().text = realIndex.ToString();
if (onInitializeItem!=null)
onInitializeItem(obj, index, scrollIndex, realIndex);
}
}
/// 带头的SV滑动时带动其他SV滑动
/// param name="panel"
void OnHeadMove(UIPanel panel)
{
Vector3 pos0 = panel.transform.localPosition;
for (int i = 1, Imax = mScrolls.Count; i < Imax; i++)
{
Vector3 pos = mScrolls[i].transform.localPosition;
Vector2 clipOffset = mPanels[i].clipOffset;
if (mHorizontal)//根据缓存好的mOffset设置其他SV的坐标
{
pos.x = pos0.x - mOffsets[i];
mScrolls[i].transform.localPosition = pos;
clipOffset.x = -pos.x;
mPanels[i].clipOffset = clipOffset;
}
else
{
pos.y = pos0.y + mOffsets[i];
mScrolls[i].transform.localPosition = pos;
clipOffset.y = -pos.y;
mPanels[i].clipOffset = clipOffset;
}
}
}
}
BreakWrapContent
其实还是UIWrapContent的作用。用来响应BreakScrollView.
public class BreakWrapContent : MonoBehaviour {
//这个脚本基本上就只是一个UIWrapContent,只是本来在UIWrapContent用到的一些数据需要从BreakScrollView获取
BreakScrollView breakScroll;
BreakScrollView BreakScroll
{
get
{
if (breakScroll == null)
breakScroll = transform.parent.parent.GetComponent();
return breakScroll;
}
}
protected int itemSize { get { return BreakScroll.itemSize; } }
protected int minIndex { get { return BreakScroll.minIndex; } }
protected int maxIndex { get { return BreakScroll.maxIndex; } }
protected bool mHorizontal { get { return BreakScroll.mHorizontal; } }
protected bool cullContent { get { return BreakScroll.cullContent; } }
protected UIScrollView mScroll;
protected UIPanel mPanel;
protected bool mFirstTime = true;
protected List< Transform > mChilds;
protected virtual void Start()
{
CacheScrollView();
CacheChilds();
adjustWrapPos();
ResetChildPostion();
mPanel.onClipMove += OnMove;
WrapContent();
}
void CacheScrollView()
{
mScroll = transform.parent.GetComponent();
mPanel = transform.parent.GetComponent();
}
void CacheChilds()
{
if (mChilds == null)
mChilds = new List();
mChilds.Clear();
for(int i=0, Imax=transform.childCount; i < Imax; i++)
{
mChilds.Add(transform.GetChild(i));
}
}
/// 调整Wrap的起始坐标,使得ScrollView的复位坐标为0 详情见 http://blog.csdn.net/mutou_222/article/details/54975521
public void adjustWrapPos()
{
if (mScroll == null)
CacheScrollView();
if (mChilds == null)
CacheChilds();
Bounds b = NGUIMath.CalculateRelativeWidgetBounds(mScroll.transform, mChilds[0], true);
Vector2 pos = transform.localPosition;
if (mHorizontal)
{
pos.x = -(mPanel.GetViewSize().x / 2f - mPanel.baseClipRegion.x - b.extents.x - mPanel.clipSoftness.x);
}
else
{
pos.y = mPanel.GetViewSize().y / 2f + mPanel.baseClipRegion.y - b.extents.y - mPanel.clipSoftness.y;
}
transform.localPosition = pos;
}
[ContextMenu("ResetChildPosition")]
public virtual void ResetChildPostion()
{
if (mChilds == null)
CacheChilds();
for(int i=0, Imax=mChilds.Count; i < Imax; i++)
{
var child = mChilds[i];
Vector2 pos = Vector2.zero;
if (mHorizontal)
pos.x = i * itemSize;
else
pos.y = -i * itemSize;
child.localPosition = pos;
UpdateItem(child, i);
}
}
public void RefreshChildData()
{
for(int i=0, Imax=mChilds.Count; i < Imax; i++)
{
UpdateItem(mChilds[i], i);
}
}
protected virtual void OnMove(UIPanel panel)
{
WrapContent();
}
//基本上是原原本本的WrapContent了,修改了一些界限判定
void WrapContent()
{
float extents = itemSize * mChilds.Count * 0.5f;
Vector3[] corners = mPanel.worldCorners;
for (int i = 0; i < 4; ++i)
{
Vector3 v = corners[i];
v = transform.InverseTransformPoint(v);
corners[i] = v;
}
Vector3 center = Vector3.Lerp(corners[0], corners[2], 0.5f);
bool allWithinRange = true;
float ext2 = extents * 2f;
if (mHorizontal)
{
float min = corners[0].x - itemSize;
float max = corners[2].x + itemSize;
for (int i = 0, imax = mChilds.Count; i < imax; ++i)
{
Transform t = mChilds[i];
float distance = t.localPosition.x - center.x;
if (distance < -extents)
{
Vector3 pos = t.localPosition;
pos.x += ext2;
distance = pos.x - center.x;
int realIndex = BreakScroll.GetRealIndex(pos, mScroll.transform.GetSiblingIndex());
if (minIndex == maxIndex || BreakScroll.CheckIndexRange(mScroll.transform.GetSiblingIndex(),realIndex))
{
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 = BreakScroll.GetRealIndex(pos, mScroll.transform.GetSiblingIndex());
if (minIndex == maxIndex || BreakScroll.CheckIndexRange(mScroll.transform.GetSiblingIndex(), realIndex))
{
t.localPosition = pos;
UpdateItem(t, i);
}
else allWithinRange = false;
}
else if (mFirstTime) UpdateItem(t, i);
if (cullContent)
{
// distance += mPanel.clipOffset.x - transform.localPosition.x;
if (!UICamera.IsPressed(t.gameObject))
{
Vector3 pos = t.localPosition;
bool inRange =BreakScroll.CheckIndexRange(mScroll.transform.GetSiblingIndex(),
BreakScroll.GetRealIndex(t.localPosition, mScroll.transform.GetSiblingIndex()));
NGUITools.SetActive(t.gameObject, ( inRange&& pos.x> min && pos.x < max), false);
}
}
}
}
else
{
float min = corners[0].y - itemSize;
float max = corners[2].y + itemSize;
for (int i = 0, imax = mChilds.Count; i < imax; ++i)
{
Transform t = mChilds[i];
float distance = t.localPosition.y - center.y;
if (distance < -extents)
{
Vector3 pos = t.localPosition;
pos.y += ext2;
distance = pos.y - center.y;
int realIndex = BreakScroll.GetRealIndex(pos, mScroll.transform.GetSiblingIndex());
if (minIndex == maxIndex || BreakScroll.CheckIndexRange(mScroll.transform.GetSiblingIndex(), realIndex))
{
t.localPosition = pos;
UpdateItem(t, i);
}
else allWithinRange = false;
}
else if (distance > extents)
{
Vector3 pos = t.localPosition;
pos.y -= ext2;
distance = pos.y - center.y;
int realIndex = BreakScroll.GetRealIndex(pos, mScroll.transform.GetSiblingIndex());
if (minIndex == maxIndex || BreakScroll.CheckIndexRange(mScroll.transform.GetSiblingIndex(), realIndex))
{
t.localPosition = pos;
UpdateItem(t, i);
}
else allWithinRange = false;
}
else if (mFirstTime) UpdateItem(t, i);
if (cullContent)
{
// distance += mPanel.clipOffset.y - transform.localPosition.y;
if (!UICamera.IsPressed(t.gameObject))
{
Vector3 pos = t.localPosition;
bool inRange = BreakScroll.CheckIndexRange(mScroll.transform.GetSiblingIndex(),
BreakScroll.GetRealIndex(pos, mScroll.transform.GetSiblingIndex()));
NGUITools.SetActive(t.gameObject, (inRange&& pos.y > min && pos.y < max), false);
}
}
}
}
mScroll.restrictWithinPanel = !allWithinRange;
}
void UpdateItem(Transform trans,int index)
{
if (mScroll == null)
CacheScrollView();
BreakScroll.UpdateItem(trans.gameObject, index, mScroll.transform.GetSiblingIndex());
}
}
- 上一篇:到头啦!
- 下一篇: NGUI:关于ScrollView的ResetPosition 不能用于UIWrapContent