Unity插件SuperScrollView详解(进阶篇)

本文向读者提供SuperScrollView插件内常用Example的演示和代码分析,意在遇到相应开发需求时,可以快速找到对应的Example和代码,并理解基本实现思路

一.ChangeItemHeightDemo

运行时改变item的高度.

代码简述:item展开/收缩时,调用OnItemSizeChanged,并改变item的UI

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

namespace SuperScrollView
{
    public class ListItem8 : MonoBehaviour
    {
        public Text mNameText;
        public GameObject mExpandContentRoot;
        public Text mClickTip;
        public Button mExpandBtn;

        int mItemDataIndex = -1;
        bool mIsExpand;
        public void Init()
        {
            mExpandBtn.onClick.AddListener( OnExpandBtnClicked );
        }

        public void OnExpandChanged()
        {
            RectTransform rt = gameObject.GetComponent<RectTransform>();
            if (mIsExpand)
            {
                rt.SetSizeWithCurrentAnchors(RectTransform.Axis.Vertical, 284f);
                mExpandContentRoot.SetActive(true);
                mClickTip.text = "Shrink";
            }
            else
            {
                rt.SetSizeWithCurrentAnchors(RectTransform.Axis.Vertical, 143f);
                mExpandContentRoot.SetActive(false);
                mClickTip.text = "Expand";
            }
        }

        void OnExpandBtnClicked()
        {
            ItemData data = DataSourceMgr.Get.GetItemDataByIndex(mItemDataIndex);
            if (data == null)
            {
                return;
            }
            mIsExpand = !mIsExpand;
            data.mIsExpand = mIsExpand;
            OnExpandChanged();
            LoopListViewItem2 item2 = gameObject.GetComponent<LoopListViewItem2>();
            item2.ParentListView.OnItemSizeChanged(item2.ItemIndex);
        }

        public void SetItemData(ItemData itemData, int itemIndex)
        {
            mItemDataIndex = itemIndex;
            mNameText.text = itemData.mName;
            mIsExpand = itemData.mIsExpand;
            OnExpandChanged();
        }
    }
}

二.ChatMsgListViewDemo(Important)

聊天信息展示

代码简述1:item根据头像在左侧或是右侧,分为2个prefab,在OnGetItemByIndex回调中,根据条件itemData.mPersonId == 0,将两个prefab名称传给方法NewListViewItem

        LoopListViewItem2 OnGetItemByIndex(LoopListView2 listView, int index)
        {
            if (index < 0 || index >= ChatMsgDataSourceMgr.Get.TotalItemCount)
            {
                return null;
            }

            ChatMsg itemData = ChatMsgDataSourceMgr.Get.GetChatMsgByIndex(index);
            if (itemData == null)
            {
                return null;
            }
            LoopListViewItem2 item = null;
            if (itemData.mPersonId == 0)
            {
                item = listView.NewListViewItem("ItemPrefab1");
            }
            else
            {
                item = listView.NewListViewItem("ItemPrefab2");
            }
            ListItem4 itemScript = item.GetComponent<ListItem4>();
            if (item.IsInitHandlerCalled == false)
            {
                item.IsInitHandlerCalled = true;
                itemScript.Init();
            }
            itemScript.SetItemData(itemData,index);
            return item;
        }

代码简述2:将Item分为字符串/图片/等等;根据每种类型,在Item更新UI的时候,设置Item的高度,设置mItemBg的高度和颜色。对于字符串类型的item,Text组件上使用ContentSizeFilter组件,实现高度自适应,得出的高度作为给mItemBg和Item高度赋值的数据来源。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

namespace SuperScrollView
{
    public class ListItem4 : MonoBehaviour
    {
        public Text mMsgText;
        public Image mMsgPic;
        public Image mIcon;
        public Image mItemBg;
        public Image mArrow;
        public Text mIndexText;
        int mItemIndex = -1;

        public int ItemIndex
        {
            get
            {
                return mItemIndex;
            }
        }
        public void Init()
        {

        }

      
        public void SetItemData(ChatMsg itemData, int itemIndex)
        {
            mIndexText.text = itemIndex.ToString();
            PersonInfo person = ChatMsgDataSourceMgr.Get.GetPersonInfo(itemData.mPersonId);
            mItemIndex = itemIndex;
            if(itemData.mMsgType == MsgTypeEnum.Str)
            {
                mMsgPic.gameObject.SetActive(false);
                mMsgText.gameObject.SetActive(true);
                mMsgText.text = itemData.mSrtMsg;
                mMsgText.GetComponent<ContentSizeFitter>().SetLayoutVertical();
                mIcon.sprite = ResManager.Get.GetSpriteByName(person.mHeadIcon);
                Vector2 size = mItemBg.GetComponent<RectTransform>().sizeDelta;
                size.x = mMsgText.GetComponent<RectTransform>().sizeDelta.x + 20;
                size.y = mMsgText.GetComponent<RectTransform>().sizeDelta.y + 20;
                mItemBg.GetComponent<RectTransform>().sizeDelta = size;
                if(person.mId == 0)
                {
                    mItemBg.color = new Color32(160, 231, 90, 255);
                    mArrow.color = mItemBg.color;
                }
                else
                {
                    mItemBg.color = Color.white;
                    mArrow.color = mItemBg.color;
                }
                RectTransform tf = gameObject.GetComponent<RectTransform>();
                float y = size.y;
                if (y < 75)
                {
                    y = 75;
                }
                tf.SetSizeWithCurrentAnchors(RectTransform.Axis.Vertical, y);
            }
            else
            {
                mMsgPic.gameObject.SetActive(true);
                mMsgText.gameObject.SetActive(false);
                mMsgPic.sprite = ResManager.Get.GetSpriteByName(itemData.mPicMsgSpriteName); 
                mMsgPic.SetNativeSize();
                mIcon.sprite = ResManager.Get.GetSpriteByName(person.mHeadIcon);
                Vector2 size = mItemBg.GetComponent<RectTransform>().sizeDelta;
                size.x = mMsgPic.GetComponent<RectTransform>().sizeDelta.x + 20;
                size.y = mMsgPic.GetComponent<RectTransform>().sizeDelta.y + 20;
                mItemBg.GetComponent<RectTransform>().sizeDelta = size;
                if (person.mId == 0)
                {
                    mItemBg.color = new Color32(160, 231, 90, 255);
                    mArrow.color = mItemBg.color;
                }
                else
                {
                    mItemBg.color = Color.white;
                    mArrow.color = mItemBg.color;
                }
                RectTransform tf = gameObject.GetComponent<RectTransform>();
                float y = size.y;
                if (y < 75)
                {
                    y = 75;
                }
                tf.SetSizeWithCurrentAnchors(RectTransform.Axis.Vertical, y);

            }
            

        }


    }
}

三.ClickAndLoadMoreDemo

运行时,最后一个item是个button,点击后button变成loading状态,经过一秒,加载更多数据并刷新ScrollView

代码简述:在OnGetItemByIndex中,根据是否为最后一个Item,差异化处理item。对于最后一个Item,处理它未点击,加载中的显示。刷新Item时,获取新的数据后,调用SetListItemCount和RefreshAllShownItem完成ScrollView的刷新。

        LoopListViewItem2 OnGetItemByIndex(LoopListView2 listView, int index)
        {
            if (index < 0)
            {
                return null;
            }
            LoopListViewItem2 item = null;
            if (index == DataSourceMgr.Get.TotalItemCount)
            {
                item = listView.NewListViewItem("ItemPrefab0");
                if (item.IsInitHandlerCalled == false)
                {
                    item.IsInitHandlerCalled = true;
                    ListItem11 itemScript0 = item.GetComponent<ListItem11>();
                    itemScript0.mRootButton.onClick.AddListener(OnLoadMoreBtnClicked);
                }
                UpdateLoadingTip(item);
                return item;
            }
            ItemData itemData = DataSourceMgr.Get.GetItemDataByIndex(index);
            if (itemData == null)
            {
                return null;
            }
            item = listView.NewListViewItem("ItemPrefab1");
            ListItem2 itemScript = item.GetComponent<ListItem2>();
            if (item.IsInitHandlerCalled == false)
            {
                item.IsInitHandlerCalled = true;
                itemScript.Init();
            }
            itemScript.SetItemData(itemData, index);
            return item;
        }

        void UpdateLoadingTip(LoopListViewItem2 item)
        {
            if (item == null)
            {
                return;
            }
            ListItem11 itemScript0 = item.GetComponent<ListItem11>();
            if (itemScript0 == null)
            {
                return;
            }
            if (mLoadingTipStatus == LoadingTipStatus.None)
            {
                itemScript0.mText.text = "Click to Load More";
                itemScript0.mWaitingIcon.SetActive(false);
            }
            else if (mLoadingTipStatus == LoadingTipStatus.WaitLoad)
            {
                itemScript0.mWaitingIcon.SetActive(true);
                itemScript0.mText.text = "Loading ...";
            }
        }
       
        void OnLoadMoreBtnClicked()
        {
            if (mLoopListView.ShownItemCount == 0)
            {
                return;
            }
            if (mLoadingTipStatus != LoadingTipStatus.None)
            {
                return;
            }
            LoopListViewItem2 item = mLoopListView.GetShownItemByItemIndex(DataSourceMgr.Get.TotalItemCount);
            if (item == null)
            {
                return;
            }
            mLoadingTipStatus = LoadingTipStatus.WaitLoad;
            UpdateLoadingTip(item);
            DataSourceMgr.Get.RequestLoadMoreDataList(mLoadMoreCount, OnDataSourceLoadMoreFinished);
        }

        void OnDataSourceLoadMoreFinished()
        {
            if (mLoopListView.ShownItemCount == 0)
            {
                return;
            }
            if (mLoadingTipStatus == LoadingTipStatus.WaitLoad)
            {
                mLoadingTipStatus = LoadingTipStatus.None;
                mLoopListView.SetListItemCount(DataSourceMgr.Get.TotalItemCount + 1, false);
                mLoopListView.RefreshAllShownItem();
            }
        }
        public void RequestLoadMoreDataList(int loadCount,System.Action onLoadMoreFinished)
        {
            mLoadMoreCount = loadCount;
            mDataLoadLeftTime = 1;
            mOnLoadMoreFinished = onLoadMoreFinished;
            mIsWaitLoadingMoreData = true;
        }

        public void Update()
        {
            if (mIsWaittingRefreshData)
            {
                mDataRefreshLeftTime -= Time.deltaTime;
                if (mDataRefreshLeftTime <= 0)
                {
                    mIsWaittingRefreshData = false;
                    DoRefreshDataSource();
                    if (mOnRefreshFinished != null)
                    {
                        mOnRefreshFinished();
                    }
                }
            }
            if (mIsWaitLoadingMoreData)
            {
                mDataLoadLeftTime -= Time.deltaTime;
                if (mDataLoadLeftTime <= 0)
                {
                    mIsWaitLoadingMoreData = false;
                    DoLoadMoreDataSource();
                    if (mOnLoadMoreFinished != null)
                    {
                        mOnLoadMoreFinished();
                    }
                }
            }

        }

四.HorizontalGalleryDemo(Important)

滚动时改变Item的Scale和透明度

代码简述:每帧对alpha和localScale赋值,具体取值参考代码写法

        void LateUpdate()
        {
            mLoopListView.UpdateAllShownItemSnapData();
            int count = mLoopListView.ShownItemCount;
            for (int i = 0; i < count; ++i)
            {
                LoopListViewItem2 item = mLoopListView.GetShownItemByIndex(i);
                ListItem5 itemScript = item.GetComponent<ListItem5>();
                float scale = 1 - Mathf.Abs(item.DistanceWithViewPortSnapCenter) / 700f;
                scale = Mathf.Clamp(scale, 0.4f, 1);
                itemScript.mContentRootObj.GetComponent<CanvasGroup>().alpha = scale;
                itemScript.mContentRootObj.transform.localScale = new Vector3(scale, scale, 1);
            }
        }

五.PageViewDemo

翻页视图

Editor Setting如下:

代码整体来说有一定难度,有的细节不太好理解,主要原因是使用了不常用的API(SetSnapTargetItemIndex),而且官方文档和代码注释没对这些API做有效解释。遇到翻页开发需求,先一模一样的抄,有必要理解的时候再精细研究。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

namespace SuperScrollView
{
    public class DotElem
    {
        public GameObject mDotElemRoot;
        public GameObject mDotSmall;
        public GameObject mDotBig;
    }
    public class PageViewDemoScript : MonoBehaviour
    {
        public LoopListView2 mLoopListView;
        Button mBackButton;
        int mPageCount = 5;
        public Transform mDotsRootObj;
        List<DotElem> mDotElemList = new List<DotElem>();
        void Start()
        {
            InitDots();
            LoopListViewInitParam initParam = LoopListViewInitParam.CopyDefaultInitParam();
            initParam.mSnapVecThreshold = 99999;
            mLoopListView.mOnEndDragAction = OnEndDrag;
            mLoopListView.mOnSnapNearestChanged = OnSnapNearestChanged;
            mLoopListView.InitListView(mPageCount, OnGetItemByIndex, initParam);

            mBackButton = GameObject.Find("ButtonPanel/BackButton").GetComponent<Button>();
            mBackButton.onClick.AddListener(OnBackBtnClicked);
        }
        void InitDots()
        {
            int childCount = mDotsRootObj.childCount;
            for (int i = 0; i < childCount; ++i)
            {
                Transform tf = mDotsRootObj.GetChild(i);
                DotElem elem = new DotElem();
                elem.mDotElemRoot = tf.gameObject;
                elem.mDotSmall = tf.Find("dotSmall").gameObject;
                elem.mDotBig = tf.Find("dotBig").gameObject;
                ClickEventListener listener = ClickEventListener.Get(elem.mDotElemRoot);
                int index = i;
                listener.SetClickEventHandler(delegate (GameObject obj) { OnDotClicked(index); });
                mDotElemList.Add(elem);
            }
        }
        void OnDotClicked(int index)
        {
            int curNearestItemIndex = mLoopListView.CurSnapNearestItemIndex;
            if (curNearestItemIndex < 0 || curNearestItemIndex >= mPageCount)
            {
                return;
            }
            if(index == curNearestItemIndex)
            {
                return;
            }
            mLoopListView.SetSnapTargetItemIndex(index);
            
        }
        void UpdateAllDots()
        {
            int curNearestItemIndex = mLoopListView.CurSnapNearestItemIndex;
            if(curNearestItemIndex < 0 || curNearestItemIndex >= mPageCount)
            {
                return;
            }
            int count = mDotElemList.Count;
            if(curNearestItemIndex >= count)
            {
                return;
            }
            for(int i = 0;i<count;++i)
            {
                DotElem elem = mDotElemList[i];
                if(i != curNearestItemIndex)
                {
                    elem.mDotSmall.SetActive(true);
                    elem.mDotBig.SetActive(false);
                }
                else
                {
                    elem.mDotSmall.SetActive(false);
                    elem.mDotBig.SetActive(true);
                }
            }
        }

        void OnSnapNearestChanged(LoopListView2 listView, LoopListViewItem2 item)
        {
            UpdateAllDots();
        }
        void OnBackBtnClicked()
        {
            UnityEngine.SceneManagement.SceneManager.LoadScene("Menu");
        }
        LoopListViewItem2 OnGetItemByIndex(LoopListView2 listView, int pageIndex)
        {
            if (pageIndex < 0 || pageIndex >= mPageCount)
            {
                return null;
            }

            LoopListViewItem2 item = listView.NewListViewItem("ItemPrefab1");
            ListItem14 itemScript = item.GetComponent<ListItem14>();
            if (item.IsInitHandlerCalled == false)
            {
                item.IsInitHandlerCalled = true;
                itemScript.Init();
            }
            List<ListItem14Elem> elemList = itemScript.mElemItemList;
            int count = elemList.Count;
            int picBeginIndex = pageIndex * count;
            int i = 0;
            for(;i< count;++i)
            {
                ItemData itemData = DataSourceMgr.Get.GetItemDataByIndex(picBeginIndex+i);
                if(itemData == null)
                {
                    break;
                }
                ListItem14Elem elem = elemList[i];
                elem.mRootObj.SetActive(true);
                elem.mIcon.sprite = ResManager.Get.GetSpriteByName(itemData.mIcon);
                elem.mName.text = itemData.mName;
            }
            if(i < count)
            {
                for(;i< count;++i)
                {
                    elemList[i].mRootObj.SetActive(false);
                }
            }
            return item;
        }
        void OnEndDrag()
        {
            float vec = mLoopListView.ScrollRect.velocity.x;
            int curNearestItemIndex = mLoopListView.CurSnapNearestItemIndex;
            LoopListViewItem2 item = mLoopListView.GetShownItemByItemIndex(curNearestItemIndex);
            if(item == null)
            {
                mLoopListView.ClearSnapData();
                return;
            }
            if (Mathf.Abs(vec) < 50f)
            {
                mLoopListView.SetSnapTargetItemIndex(curNearestItemIndex);
                return;
            }
            Vector3 pos = mLoopListView.GetItemCornerPosInViewPort(item, ItemCornerEnum.LeftTop);
            if(pos.x > 0)
            {
                if (vec > 0)
                {
                    mLoopListView.SetSnapTargetItemIndex(curNearestItemIndex - 1);
                }
                else
                {
                    mLoopListView.SetSnapTargetItemIndex(curNearestItemIndex);
                }
            }
            else if (pos.x < 0)
            {
                if (vec > 0)
                {
                    mLoopListView.SetSnapTargetItemIndex(curNearestItemIndex);
                }
                else
                {
                    mLoopListView.SetSnapTargetItemIndex(curNearestItemIndex+1);
                }
            }
            else
            {
                if (vec > 0)
                {
                    mLoopListView.SetSnapTargetItemIndex(curNearestItemIndex-1);
                }
                else
                {
                    mLoopListView.SetSnapTargetItemIndex(curNearestItemIndex + 1);
                }
            }
        }
    }
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

namespace SuperScrollView
{
    public class ListItem14Elem
    {
        public GameObject mRootObj;
        public Image mIcon;
        public Text mName;
    }
    public class ListItem14 : MonoBehaviour
    {

        public List<ListItem14Elem> mElemItemList = new List<ListItem14Elem>();

        public void Init()
        {
            int childCount = transform.childCount;
            for(int i= 0;i<childCount;++i)
            {
                Transform tf = transform.GetChild(i);
                ListItem14Elem elem = new ListItem14Elem();
                elem.mRootObj = tf.gameObject;
                elem.mIcon = tf.Find("ItemIcon").GetComponent<Image>();
                elem.mName = tf.Find("ItemIcon/name").GetComponent<Text>();
                mElemItemList.Add(elem);
            }
        }

    }
}

六.PullAndLoadMoreDemo(Important)

滚动到最后一个Item后,通过拖拽加载更多Item

代码简述:在OnGetItemByIndex中,根据是否为最后一个Item,差异化处理item。对于最后一个Item,处理它OnDraging事件和OnEndDrag事件的UI。刷新Item时,获取新的数据后,调用SetListItemCount和RefreshAllShownItem完成ScrollView的刷新。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

namespace SuperScrollView
{
    public class PullToLoadMoreDemoScript : MonoBehaviour
    {
        public LoopListView2 mLoopListView;
        LoadingTipStatus mLoadingTipStatus = LoadingTipStatus.None;
        float mLoadingTipItemHeight = 100;
        int mLoadMoreCount = 20;

        Button mScrollToButton;
        InputField mScrollToInput;
        Button mBackButton;
        // Use this for initialization
        void Start()
        {
            // totalItemCount +1 because the last "load more" banner is also a item.
            mLoopListView.InitListView(DataSourceMgr.Get.TotalItemCount + 1, OnGetItemByIndex);
            mLoopListView.mOnDragingAction = OnDraging;
            mLoopListView.mOnEndDragAction = OnEndDrag;
            mScrollToButton = GameObject.Find("ButtonPanel/buttonGroup2/ScrollToButton").GetComponent<Button>();
            mScrollToInput = GameObject.Find("ButtonPanel/buttonGroup2/ScrollToInputField").GetComponent<InputField>();
            mBackButton = GameObject.Find("ButtonPanel/BackButton").GetComponent<Button>();
        }

        LoopListViewItem2 OnGetItemByIndex(LoopListView2 listView, int index)
        {
            if (index < 0)
            {
                return null;
            }
            LoopListViewItem2 item = null;
            if (index == DataSourceMgr.Get.TotalItemCount)
            {
                item = listView.NewListViewItem("ItemPrefab0");
                UpdateLoadingTip(item);
                return item;
            }
            ItemData itemData = DataSourceMgr.Get.GetItemDataByIndex(index);
            if (itemData == null)
            {
                return null;
            }
            item = listView.NewListViewItem("ItemPrefab1");
            ListItem2 itemScript = item.GetComponent<ListItem2>();
            if (item.IsInitHandlerCalled == false)
            {
                item.IsInitHandlerCalled = true;
                itemScript.Init();
            }
            if(index == DataSourceMgr.Get.TotalItemCount -1)
            {
                item.Padding = 0;
            }
            itemScript.SetItemData(itemData, index);
            return item;
        }

        void UpdateLoadingTip(LoopListViewItem2 item)
        {
            if (item == null)
            {
                return;
            }
            ListItem0 itemScript0 = item.GetComponent<ListItem0>();
            if(itemScript0 == null)
            {
                return;
            }
            if (mLoadingTipStatus == LoadingTipStatus.None)
            {
                itemScript0.mRoot.SetActive(false);
                item.CachedRectTransform.SetSizeWithCurrentAnchors(RectTransform.Axis.Vertical, 0);
            }
            else if (mLoadingTipStatus == LoadingTipStatus.WaitRelease)
            {
                itemScript0.mRoot.SetActive(true);
                itemScript0.mText.text = "Release to Load More";
                itemScript0.mArrow.SetActive(true);
                itemScript0.mWaitingIcon.SetActive(false);
                item.CachedRectTransform.SetSizeWithCurrentAnchors(RectTransform.Axis.Vertical, mLoadingTipItemHeight);
            }
            else if (mLoadingTipStatus == LoadingTipStatus.WaitLoad)
            {
                itemScript0.mRoot.SetActive(true);
                itemScript0.mArrow.SetActive(false);
                itemScript0.mWaitingIcon.SetActive(true);
                itemScript0.mText.text = "Loading ...";
                item.CachedRectTransform.SetSizeWithCurrentAnchors(RectTransform.Axis.Vertical, mLoadingTipItemHeight);
            }
        }

        void OnDraging()
        {
            if (mLoopListView.ShownItemCount == 0)
            {
                return;
            }
            if (mLoadingTipStatus != LoadingTipStatus.None && mLoadingTipStatus != LoadingTipStatus.WaitRelease)
            {
                return;
            }
            LoopListViewItem2 item = mLoopListView.GetShownItemByItemIndex(DataSourceMgr.Get.TotalItemCount);
            if (item == null)
            {
                return;
            }
            LoopListViewItem2 item1 = mLoopListView.GetShownItemByItemIndex(DataSourceMgr.Get.TotalItemCount-1);
            if (item1 == null)
            {
                return;
            }
            float y  = mLoopListView.GetItemCornerPosInViewPort(item1,ItemCornerEnum.LeftBottom).y;
            if(y + mLoopListView.ViewPortSize >= mLoadingTipItemHeight)
            {
                if (mLoadingTipStatus != LoadingTipStatus.None)
                {
                    return;
                }
                mLoadingTipStatus = LoadingTipStatus.WaitRelease;
                UpdateLoadingTip(item);
            }
            else
            {
                if (mLoadingTipStatus != LoadingTipStatus.WaitRelease)
                {
                    return;
                }
                mLoadingTipStatus = LoadingTipStatus.None;
                UpdateLoadingTip(item);
            }
        }
        void OnEndDrag()
        {
            if (mLoopListView.ShownItemCount == 0)
            {
                return;
            }
            if (mLoadingTipStatus != LoadingTipStatus.None && mLoadingTipStatus != LoadingTipStatus.WaitRelease)
            {
                return;
            }
            LoopListViewItem2 item = mLoopListView.GetShownItemByItemIndex(DataSourceMgr.Get.TotalItemCount);
            if (item == null)
            {
                return;
            }
            mLoopListView.OnItemSizeChanged(item.ItemIndex);
            if (mLoadingTipStatus != LoadingTipStatus.WaitRelease)
            {
                return;
            }
            mLoadingTipStatus = LoadingTipStatus.WaitLoad;
            UpdateLoadingTip(item);
            DataSourceMgr.Get.RequestLoadMoreDataList(mLoadMoreCount, OnDataSourceLoadMoreFinished);
        }

        void OnDataSourceLoadMoreFinished()
        {
            if (mLoopListView.ShownItemCount == 0)
            {
                return;
            }
            if (mLoadingTipStatus == LoadingTipStatus.WaitLoad)
            {
                mLoadingTipStatus = LoadingTipStatus.None;
                mLoopListView.SetListItemCount(DataSourceMgr.Get.TotalItemCount + 1, false);
                mLoopListView.RefreshAllShownItem();
            }
        }
    }
}

七.SpinDatePickerDemo

3个ScrollView都可以滚动,吸附,取中间值,拼凑出一个最终的值的Demo

代码量不多也不难理解,比较值得一提的是将处理颜色的方法赋值给mOnSnapNearestChanged,在吸附后自动调用。另外OnGetItemByIndexForMonth和通用的回调不一样,不需要进行return判断。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

namespace SuperScrollView
{

    public class SpinDatePickerDemoScript : MonoBehaviour
    {
        public LoopListView2 mLoopListViewMonth;
        public LoopListView2 mLoopListViewDay;
        public LoopListView2 mLoopListViewHour;
        public Button mBackButton;
        static int[] mMonthDayCountArray = new int[] { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
        static string[] mMonthNameArray = new string[]
        {
            "Jan.",
            "Feb.",
            "Mar.",
            "Apr.",
            "May.",
            "Jun.",
            "Jul.",
            "Aug.",
            "Sep.",
            "Oct.",
            "Nov.",
            "Dec.",
        };
        int mCurSelectedMonth = 2;
        int mCurSelectedDay = 2;
        int mCurSelectedHour = 2;

        public int CurSelectedMonth
        {
            get { return mCurSelectedMonth; }
        }
        public int CurSelectedDay
        {
            get { return mCurSelectedDay; }
        }
        public int CurSelectedHour
        {
            get { return mCurSelectedHour; }
        }

        // Use this for initialization
        void Start()
        {
            //set all snap callback.
            mLoopListViewMonth.mOnSnapNearestChanged = OnMonthSnapTargetChanged;
            mLoopListViewDay.mOnSnapNearestChanged = OnDaySnapTargetChanged;
            mLoopListViewHour.mOnSnapNearestChanged = OnHourSnapTargetChanged;

            //init all superListView.
            mLoopListViewMonth.InitListView(-1, OnGetItemByIndexForMonth);
            mLoopListViewDay.InitListView(-1, OnGetItemByIndexForDay);
            mLoopListViewHour.InitListView(-1, OnGetItemByIndexForHour);

            mLoopListViewMonth.mOnSnapItemFinished = OnMonthSnapTargetFinished;


            mBackButton.onClick.AddListener(OnBackBtnClicked);
        }

        void OnBackBtnClicked()
        {
            UnityEngine.SceneManagement.SceneManager.LoadScene("Menu");
        }

        LoopListViewItem2 OnGetItemByIndexForHour(LoopListView2 listView, int index)
        {
            LoopListViewItem2 item = listView.NewListViewItem("ItemPrefab1");
            ListItem7 itemScript = item.GetComponent<ListItem7>();
            if (item.IsInitHandlerCalled == false)
            {
                item.IsInitHandlerCalled = true;
                itemScript.Init();
            }
            int firstItemVal = 1;
            int itemCount = 24;
            int val = 0;
            if(index >= 0)
            {
                val = index % itemCount;
            }
            else
            {
                val = itemCount + ((index + 1) % itemCount) - 1;
            }
            val = val + firstItemVal;
            itemScript.Value = val;
            itemScript.mText.text = val.ToString();
            return item;
        }


        LoopListViewItem2 OnGetItemByIndexForMonth(LoopListView2 listView, int index)
        {
            LoopListViewItem2 item = listView.NewListViewItem("ItemPrefab1");
            ListItem7 itemScript = item.GetComponent<ListItem7>();
            if (item.IsInitHandlerCalled == false)
            {
                item.IsInitHandlerCalled = true;
                itemScript.Init();
            }
            int firstItemVal = 1;
            int itemCount = 12;
            int val = 0;
            if (index >= 0)
            {
                val = index % itemCount;
            }
            else
            {
                val = itemCount + ((index+1) % itemCount)-1;
            }
            val = val + firstItemVal;
            itemScript.Value = val;
            itemScript.mText.text = mMonthNameArray[val-1];
            return item;
        }



        LoopListViewItem2 OnGetItemByIndexForDay(LoopListView2 listView, int index)
        {
            LoopListViewItem2 item = listView.NewListViewItem("ItemPrefab1");
            ListItem7 itemScript = item.GetComponent<ListItem7>();
            if (item.IsInitHandlerCalled == false)
            {
                item.IsInitHandlerCalled = true;
                itemScript.Init();
            }
            int firstItemVal = 1;
            int itemCount = mMonthDayCountArray[mCurSelectedMonth-1];
            int val = 0;
            if (index >= 0)
            {
                val = index % itemCount;
            }
            else
            {
                val = itemCount + ((index + 1) % itemCount) - 1;
            }
            val = val + firstItemVal;
            itemScript.Value = val;
            itemScript.mText.text = val.ToString();
            return item;
        }


        void OnMonthSnapTargetChanged(LoopListView2 listView, LoopListViewItem2 item)
        {
            int index = listView.GetIndexInShownItemList(item);
            if (index < 0)
            {
                return;
            }
            ListItem7 itemScript = item.GetComponent<ListItem7>();
            mCurSelectedMonth = itemScript.Value;
            OnListViewSnapTargetChanged(listView, index);
        }

        void OnDaySnapTargetChanged(LoopListView2 listView, LoopListViewItem2 item)
        {
            int index = listView.GetIndexInShownItemList(item);
            if (index < 0)
            {
                return;
            }
            ListItem7 itemScript = item.GetComponent<ListItem7>();
            mCurSelectedDay = itemScript.Value;
            OnListViewSnapTargetChanged(listView, index);
        }

        void OnHourSnapTargetChanged(LoopListView2 listView, LoopListViewItem2 item)
        {
            int index = listView.GetIndexInShownItemList(item);
            if (index < 0)
            {
                return;
            }
            ListItem7 itemScript = item.GetComponent<ListItem7>();
            mCurSelectedHour = itemScript.Value;
            OnListViewSnapTargetChanged(listView, index);
        }

        void OnMonthSnapTargetFinished(LoopListView2 listView, LoopListViewItem2 item)
        {
            LoopListViewItem2 item0 = mLoopListViewDay.GetShownItemByIndex(0);
            ListItem7 itemScript = item0.GetComponent<ListItem7>();
            int index = itemScript.Value - 1;
            mLoopListViewDay.RefreshAllShownItemWithFirstIndex(index);
        }


        void OnListViewSnapTargetChanged(LoopListView2 listView, int targetIndex)
        {
            int count = listView.ShownItemCount;
            for (int i = 0; i < count; ++i)
            {
                LoopListViewItem2 item2 = listView.GetShownItemByIndex(i);
                ListItem7 itemScript = item2.GetComponent<ListItem7>();
                if (i == targetIndex)
                {
                    itemScript.mText.color = Color.red;
                }
                else
                {
                    itemScript.mText.color = Color.black;
                }
            }
        }

    }

}

八.TreeViewDemo(Important)

Item可展开二级子Item的情况,这种情况是比较常用的,比如交易行,图鉴系统等。

代码概述:Item分为2种Prefab,一级Item和子Item,使用管理类TreeViewItemCountMgr对Item进行管理;在OnGetItemByIndex方法内,通过方法mTreeItemCountMgr.QueryTreeItemByTotalIndex获取到Item的Data,在进行prefab的分类处理。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

namespace SuperScrollView
{
    public class TreeViewDemoScript : MonoBehaviour
    {
        public LoopListView2 mLoopListView;
        Button mScrollToButton;
        Button mExpandAllButton;
        Button mCollapseAllButton;
        InputField mScrollToInputItem;
        InputField mScrollToInputChild;
        // an helper class for TreeView item showing.
        TreeViewItemCountMgr mTreeItemCountMgr = new TreeViewItemCountMgr();
        // Use this for initialization
        void Start()
        {
            int count = TreeViewDataSourceMgr.Get.TreeViewItemCount;
            //tells mTreeItemCountMgr there are how many TreeItems and every TreeItem has how many ChildItems.
            for (int i = 0; i < count; ++i)
            {
                int childCount = TreeViewDataSourceMgr.Get.GetItemDataByIndex(i).ChildCount;
                //second param "true" tells mTreeItemCountMgr this TreeItem is in expand status, that is to say all its children are showing.
                mTreeItemCountMgr.AddTreeItem(childCount, true);
            }
            //initialize the InitListView
            //mTreeItemCountMgr.GetTotalItemAndChildCount() return the total items count in the TreeView, include all TreeItems and all TreeChildItems.
            mLoopListView.InitListView(mTreeItemCountMgr.GetTotalItemAndChildCount(), OnGetItemByIndex);

            mExpandAllButton = GameObject.Find("ButtonPanel/buttonGroup1/ExpandAllButton").GetComponent<Button>();
            mScrollToButton = GameObject.Find("ButtonPanel/buttonGroup2/ScrollToButton").GetComponent<Button>();
            mCollapseAllButton = GameObject.Find("ButtonPanel/buttonGroup3/CollapseAllButton").GetComponent<Button>();
            mScrollToInputItem = GameObject.Find("ButtonPanel/buttonGroup2/ScrollToInputFieldItem").GetComponent<InputField>();
            mScrollToInputChild = GameObject.Find("ButtonPanel/buttonGroup2/ScrollToInputFieldChild").GetComponent<InputField>();
            mExpandAllButton.onClick.AddListener(OnExpandAllBtnClicked);
            mCollapseAllButton.onClick.AddListener(OnCollapseAllBtnClicked);
        }
        //when a TreeItem or TreeChildItem is getting in the scrollrect viewport, 
        //this method will be called with the item’ index as a parameter, 
        //to let you create the item and update its content.
        LoopListViewItem2 OnGetItemByIndex(LoopListView2 listView, int index)
        {
            if (index < 0)
            {
                return null;
            }

            /*to check the index'th item is a TreeItem or a TreeChildItem.for example,
             
             0  TreeItem0
             1      TreeChildItem0_0
             2      TreeChildItem0_1
             3      TreeChildItem0_2
             4      TreeChildItem0_3
             5  TreeItem1
             6      TreeChildItem1_0
             7      TreeChildItem1_1
             8      TreeChildItem1_2
             9  TreeItem2
             10     TreeChildItem2_0
             11     TreeChildItem2_1
             12     TreeChildItem2_2

             the first column value is the param 'index', for example, if index is 1,
             then we should return TreeChildItem0_0 to SuperScrollView, and if index is 5,
             then we should return TreeItem1 to SuperScrollView
            */

            TreeViewItemCountData countData = mTreeItemCountMgr.QueryTreeItemByTotalIndex(index);
            if(countData == null)
            {
                return null;
            }
            int treeItemIndex = countData.mTreeItemIndex;
            TreeViewItemData treeViewItemData = TreeViewDataSourceMgr.Get.GetItemDataByIndex(treeItemIndex);
            if (countData.IsChild(index) == false)// if is a TreeItem
            {
                //get a new TreeItem
                LoopListViewItem2 item = listView.NewListViewItem("ItemPrefab1");
                ListItem12 itemScript = item.GetComponent<ListItem12>();
                if (item.IsInitHandlerCalled == false)
                {
                    item.IsInitHandlerCalled = true;
                    itemScript.Init();
                    itemScript.SetClickCallBack(this.OnExpandClicked);
                }
                //update the TreeItem's content
                itemScript.mText.text = treeViewItemData.mName;
                itemScript.SetItemData(treeItemIndex, countData.mIsExpand);
                return item;
            }
            else // if is a TreeChildItem
            {
                //childIndex is from 0 to ChildCount.
                //for example, TreeChildItem0_0 is the 0'th child of TreeItem0
                //and TreeChildItem1_2 is the 2'th child of TreeItem1
                int childIndex = countData.GetChildIndex(index);
                ItemData itemData = treeViewItemData.GetChild(childIndex);
                if (itemData == null)
                {
                    return null;
                }
                //get a new TreeChildItem
                LoopListViewItem2 item = listView.NewListViewItem("ItemPrefab2");
                ListItem13 itemScript = item.GetComponent<ListItem13>();
                if (item.IsInitHandlerCalled == false)
                {
                    item.IsInitHandlerCalled = true;
                    itemScript.Init();
                }
                //update the TreeChildItem's content
                itemScript.SetItemData(itemData, treeItemIndex, childIndex);
                return item;
            }
        }
        public void OnExpandClicked(int index)
        {
            mTreeItemCountMgr.ToggleItemExpand(index);
            mLoopListView.SetListItemCount(mTreeItemCountMgr.GetTotalItemAndChildCount(),false);
            mLoopListView.RefreshAllShownItem();
        }
 
        void OnExpandAllBtnClicked()
        {
            int count = mTreeItemCountMgr.TreeViewItemCount;
            for (int i = 0; i < count; ++i)
            {
                mTreeItemCountMgr.SetItemExpand(i, true);
            }
            mLoopListView.SetListItemCount(mTreeItemCountMgr.GetTotalItemAndChildCount(), false);
            mLoopListView.RefreshAllShownItem();
        }
        void OnCollapseAllBtnClicked()
        {
            int count = mTreeItemCountMgr.TreeViewItemCount;
            for (int i = 0; i < count; ++i)
            {
                mTreeItemCountMgr.SetItemExpand(i, false);
            }
            mLoopListView.SetListItemCount(mTreeItemCountMgr.GetTotalItemAndChildCount(), false);
            mLoopListView.RefreshAllShownItem();
        }
    }
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace SuperScrollView
{
    public class TreeViewItemCountData
    {
        public int mTreeItemIndex = 0;
        public int mChildCount = 0;
        public bool mIsExpand = true;
        public int mBeginIndex = 0;
        public int mEndIndex = 0;

        public bool IsChild(int index)
        {
            return (index != mBeginIndex);
        }

        public int GetChildIndex(int index)
        {
            if(IsChild(index) == false)
            {
                return -1;
            }
            return (index - mBeginIndex - 1);
        }

    }
    public class TreeViewItemCountMgr
    {

        List<TreeViewItemCountData> mTreeItemDataList = new List<TreeViewItemCountData>();
        TreeViewItemCountData mLastQueryResult = null;
        bool mIsDirty = true;
        public void AddTreeItem(int count, bool isExpand)
        {
            TreeViewItemCountData data = new TreeViewItemCountData();
            data.mTreeItemIndex = mTreeItemDataList.Count;
            data.mChildCount = count;
            data.mIsExpand = isExpand;
            mTreeItemDataList.Add(data);
            mIsDirty = true;
        }

        public void Clear()
        {
            mTreeItemDataList.Clear();
            mLastQueryResult = null;
            mIsDirty = true;
        }

        public TreeViewItemCountData GetTreeItem(int treeIndex)
        {
            if (treeIndex < 0 || treeIndex >= mTreeItemDataList.Count)
            {
                return null;
            }
            return mTreeItemDataList[treeIndex];
        }
        public void SetItemChildCount(int treeIndex, int count)
        {
            if (treeIndex < 0 || treeIndex >= mTreeItemDataList.Count)
            {
                return;
            }
            mIsDirty = true;
            TreeViewItemCountData data = mTreeItemDataList[treeIndex];
            data.mChildCount = count;
        }
        public void SetItemExpand(int treeIndex, bool isExpand)
        {
            if (treeIndex < 0 || treeIndex >= mTreeItemDataList.Count)
            {
                return;
            }
            mIsDirty = true;
            TreeViewItemCountData data = mTreeItemDataList[treeIndex];
            data.mIsExpand = isExpand;
        }

        public void ToggleItemExpand(int treeIndex)
        {
            if (treeIndex < 0 || treeIndex >= mTreeItemDataList.Count)
            {
                return;
            }
            mIsDirty = true;
            TreeViewItemCountData data = mTreeItemDataList[treeIndex];
            data.mIsExpand = !data.mIsExpand;
        }

        public bool IsTreeItemExpand(int treeIndex)
        {
            TreeViewItemCountData data = GetTreeItem(treeIndex);
            if (data == null)
            {
                return false;
            }
            return data.mIsExpand;
        }

        void UpdateAllTreeItemDataIndex()
        {
            if (mIsDirty == false)
            {
                return;
            }
            mLastQueryResult = null;
            mIsDirty = false;
            int count = mTreeItemDataList.Count;
            if (count == 0)
            {
                return;
            }
            TreeViewItemCountData data0 = mTreeItemDataList[0];
            data0.mBeginIndex = 0;
            data0.mEndIndex = (data0.mIsExpand ? data0.mChildCount : 0);
            int curEnd = data0.mEndIndex;
            for (int i = 1; i < count; ++i)
            {
                TreeViewItemCountData data = mTreeItemDataList[i];
                data.mBeginIndex = curEnd + 1;
                data.mEndIndex = data.mBeginIndex + (data.mIsExpand ? data.mChildCount : 0);
                curEnd = data.mEndIndex;
            }
        }

        public int TreeViewItemCount
        {
            get
            {
                return mTreeItemDataList.Count;
            }
        }

        public int GetTotalItemAndChildCount()
        {
            int count = mTreeItemDataList.Count;
            if (count == 0)
            {
                return 0;
            }
            UpdateAllTreeItemDataIndex();
            return mTreeItemDataList[count - 1].mEndIndex + 1;
        }
        public TreeViewItemCountData QueryTreeItemByTotalIndex(int totalIndex)
        {
            if (totalIndex < 0)
            {
                return null;
            }
            int count = mTreeItemDataList.Count;
            if (count == 0)
            {
                return null;
            }
            UpdateAllTreeItemDataIndex();
            if (mLastQueryResult != null)
            {
                if (mLastQueryResult.mBeginIndex <= totalIndex && mLastQueryResult.mEndIndex >= totalIndex)
                {
                    return mLastQueryResult;
                }
            }
            int low = 0;
            int high = count - 1;
            TreeViewItemCountData data = null;
            while (low <= high)
            {
                int mid = (low + high) / 2;
                data = mTreeItemDataList[mid];
                if (data.mBeginIndex <= totalIndex && data.mEndIndex >= totalIndex)
                {
                    mLastQueryResult = data;
                    return data;
                }
                else if (totalIndex > data.mEndIndex)
                {
                    low = mid + 1;
                }
                else
                {
                    high = mid - 1;
                }
            }
            return null;
        }

    }

}