木骰

NGUI:一个可以截断的ScrollView

     有一天美术给了我一张UI界面的效果图,在我原本是滚动框的中间放了个大按钮,这就导致了我这个地方不能用再用滚动框了,于是我就想能不能把这个滚动框截断,让其中的Item在经过这个大按钮的时候能够一半出现在按钮左边,一半出现在按钮右边。于是就有了这个可截断的ScrollView。起初还以为这个要从渲染的层面做呢。其实完全不用,还是借鉴了NGUI自身的UIWrapContent。

    用的是组合多个UIScrollView的方式。重点在于能够让两个ScrollView之间能够无缝连接,看起来好像真的是被截断的一样。还有就是要把握号RealIndex,效果出来之后,数据准不准就完全看这个realIndex了。

     BreakScrollView

贴源码。一共就两个脚本,

BreakScrollView
负责组织这些多个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());
    }
}
示例工程:https://github.com/mutou1994/BreakScrollView
— 于 共写了11178个字
— 文内使用到的标签:

发表评论

电子邮件地址不会被公开。 必填项已用*标注

*