unity UGUI 解决ScrollView加载大量Item导致卡顿的问题

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/lxt610/article/details/90036080


1、引言

  我们在平常的开发中常常碰到列表类的数据处理!典型的像玩家列表这种可能数量非常庞大,可能有几百个!我们假设一次全部创建可能一下子就导致app安顿崩溃!下面我们带着问题一起分析。

2、问题分析

  事实上我们我们在床架这些Item子节点的时候非常浪费性能,大量的Item导致手机内存不足而使得卡顿崩溃。我们在创建时,没有必要为每个成员创建一个专门的Item,只需要为可见部分创建铺满多1个的时候就可以了,一般这个数量不会太多!也就是:

实际创建数量 = content的高度 / 一个Item高度的高度 + 1

就可以了,在滚动的时候我们移动这些Item的位置,同时更新数据就可以了。好了分析完了,我们上代码了

3、代码部分

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

namespace Tools.UI
{
    public class UIScrollControl : MonoBehaviour
    {
    
    #region property        
        //======控制组件

        /// <summary>
        /// 滑动框体 - UI组件
        /// </summary>
        private ScrollRect scroll_rect;

        /// <summary>
        /// 滑动条
        /// </summary>
        public Scrollbar scroll_bar;

        //======逻辑数据

        /// <summary>
        /// 滑动子对象 - 列表
        /// </summary>
        private List<ScrollChild> all_child_list = new List<ScrollChild>();

        /// <summary>
        /// 总数目
        /// </summary>
        private int total_count;

        /// <summary>
        /// 当前下标
        /// </summary>
        private int cur_index;

        /// <summary>
        /// 内容位置
        /// </summary>
        private float start_content_pos;

        /// <summary>
        /// 滑动框高度
        /// </summary>
        private float rect_high;

        /// <summary>
        /// 起始滑动项位置
        /// </summary>
        private Vector2 start_scrollChild_pos;

        /// <summary>
        /// content初始大小
        /// </summary>
        private Vector2 start_content_size;

        /// <summary>
        /// 是否开始标识
        /// </summary>
        private bool is_start = false;
    #endregion

    #region 外部调用
        /// <summary>
        /// 打开控制
        /// </summary>
        /// <param name="_temp_obj"></param>
        /// <param name="_total_count"></param>
        /// <param name="_rect_high"></param>
        /// <param name="_action"></param>
        public void OpenControl(GameObject _temp_obj, int _total_count, float _rect_high,
           System.Action<int, GameObject> _action = null)
        {
            if (is_start == false)
            {
                scroll_rect = transform.GetComponent<ScrollRect>();
                if (scroll_rect == null || scroll_rect.content == null)
                {
                    Debug.LogError("ScrollRect组件错误");
                    return;
                }
                start_content_size = scroll_rect.content.sizeDelta;
            }
            Clear();  
                    
            float _show_area_high = start_content_size.y;
            Vector2 _startPos = Vector2.zero;
            float _heigh = Mathf.Max(_total_count * _rect_high, start_content_size.y);
            _startPos = new Vector3(0, (start_content_size.y - _heigh) / 2, 0);
            scroll_rect.content.sizeDelta = new Vector2(start_content_size.x, _heigh);
            scroll_rect.content.localPosition = _startPos;
            OpenControl(_temp_obj, _total_count, _rect_high, _show_area_high,  new Vector2(0, (_heigh - _rect_high) / 2), _action);
        }

        /// <summary>
        /// 打开控制
        /// </summary>
        /// <param name="_temp_obj"></param>
        /// <param name="_total_count"></param>
        /// <param name="_rect_high"></param>
        /// <param name="_show_area_high"></param>
        /// <param name="_startPos"></param>
        /// <param name="_action"></param>
        private void OpenControl(GameObject _temp_obj, int _total_count, float _rect_high, float _show_area_high, Vector2 _startPos,
            System.Action<int, GameObject> _action = null)
        {

            //检查参数是否合法
            if(_temp_obj == null)
            {
                Debug.LogError("参数有误,_temp_obj == null");
                return;
            }

            if(_total_count <= 0 || _rect_high <= 0 || _show_area_high <= 0)
            {
                if (scroll_bar != null)
                {
                    scroll_bar.gameObject.SetActive(false);
                }
        //        Debug.LogError(string.Format("参数有误,_totall_count:{0} _rect_high:{1} _show_area_high:{2}",_total_count, _rect_high, _show_area_high));
                return;
            }

            scroll_rect = transform.GetComponent<ScrollRect>();
            if (scroll_rect == null || scroll_rect.content == null)
            {
                Debug.LogError("ScrollRect组件错误");
                return;
            }

            //产生对象
            int _n = Mathf.CeilToInt(_show_area_high / _rect_high) + 1;
            int _new_count = Mathf.Min(_n, _total_count);
            for (int i = 0; i < _new_count; i++)
            {
                GameObject _obj;
                //if (0 == i)
                //{
                //    if (_temp_obj)
                //    {
                //        _temp_obj.gameObject.SetActive(true);
                //    }
                //    _obj = _temp_obj;
                //}
                //else
                //{
                    _obj = GameObject.Instantiate(_temp_obj);
                //}
                _obj.SetActive(true);
                _obj.name = string.Format("cell_{0}", i + 1);
                _obj.transform.SetParent(scroll_rect.content.transform);
                _obj.transform.localRotation = Quaternion.identity;
                _obj.transform.localScale = Vector3.one;
                all_child_list.Add(new ScrollChild(_obj, _action));
            }

            //设置参数
            total_count = _total_count;
            rect_high = _rect_high;
            cur_index = 0;
            start_scrollChild_pos = _startPos;
            if (scroll_bar != null)
            {
                scroll_bar.gameObject.SetActive(total_count > _n);
            }
            
            //刷新布局
            RefreshLayout();                   

            scroll_rect.onValueChanged.AddListener(OnScrollRect);
            start_content_pos = scroll_rect.content.anchoredPosition.y;
            if (scroll_bar != null)
            {
                if (scroll_bar.gameObject.activeSelf)
                {
                    scroll_bar.onValueChanged.AddListener(OnScrollBar);
                }
                else
                {
                    scroll_bar.onValueChanged.RemoveAllListeners() ;
                }                   
            }

            //改变标识            
            is_start = true;
        }


        /// <summary>
        /// 移除一个
        /// </summary>
        public void RemoveOneChild(int _index)
        {
            total_count--;
            
            if (total_count <= all_child_list.Count)
            {
                cur_index = 0;
                ScrollChild child = all_child_list[all_child_list.Count - 1];
                Destroy(child.Go);
                all_child_list.Remove(child);
            }
            else
            {
                if (cur_index >= total_count - all_child_list.Count)
                {
                    cur_index = total_count - all_child_list.Count - 1;
                }
            }
            float _heigh = Mathf.Max(total_count * rect_high, start_content_size.y);
            scroll_rect.content.sizeDelta = new Vector2(start_content_size.x,_heigh);
            start_content_pos = (start_content_size.y - _heigh) / 2; 
            start_scrollChild_pos = new Vector2(0, (_heigh - rect_high) / 2);
            RefreshLayout();
        }

        /// <summary>
        /// 引导相关
        /// </summary>
        /// <param name="_index"></param>
        /// <returns></returns>
        public GameObject GuideGetObj(int _index)
        {
            if (_index - cur_index < 0 || _index - cur_index >= all_child_list.Count)
            {
                return null;
            }
            ScrollChild _child = all_child_list[_index - cur_index];
            return _child.Go;
        }

        /// <summary>
        /// 用来给外部调用的
        /// </summary>
        public void RefreshLayoutUI()
        {
            RefreshLayout();
        }

        /// <summary>
        /// 刷新单独的一个item
        /// </summary>
        /// <param name="index"></param>
        public void RefreshIndex(int index)
        {
            int i = index - cur_index;
            if (i < 0 || i >= all_child_list.Count)
            {
                return;
            }
            ScrollChild _child = all_child_list[i];
            Vector2 _pos = start_scrollChild_pos;
            _pos.y -= index * rect_high;
            _child.Refresh(index, _pos);
        }

        /// <summary>
        /// 滑动面板移动到index去
        /// </summary>
        public void MoveToIndex(int index)
        {
            float _heigh = Mathf.Max(total_count * rect_high, start_content_size.y);
            scroll_rect.content.localPosition = new Vector3(0, (start_content_size.y - index * _heigh) / 2, 0);
            RefreshLayout();
        }
    #endregion

    #region 逻辑处理
        /// <summary>
        /// 刷新
        /// </summary>
        private void RefreshLayout()
        {
            for (int i = 0; i < all_child_list.Count; i++)
            {
                ScrollChild _child = all_child_list[i];
                int _index = cur_index + i;
                Vector2 _pos = start_scrollChild_pos;
                _pos.y -= _index * rect_high;
                _child.Refresh(_index, _pos);
            }
        }
    #endregion

    #region 滑动事件
        /// <summary>
        /// 滑动框体 - 滑动事件
        /// </summary>
        /// <param name="_offset"></param>
        private void OnScrollRect(Vector2 _offset)
        {
            if (scroll_bar != null)
            {
                if (is_scroll_bar)
                {
                    is_scroll_bar = false;
                    return;
                }

                is_scroll_rect = true;
                is_scroll_bar = false;

                scroll_bar.value = 1 - _offset.y;
  
            }
            
            //是否发生变化            
            float _move_value = scroll_rect.content.anchoredPosition.y - start_content_pos;
            int _index = (int)(_move_value / rect_high);
            _index = Mathf.Clamp(_index, 0, total_count - all_child_list.Count);

            if (_index == cur_index)
            {
                return;
            }
            cur_index = _index;

            //刷新布局
            RefreshLayout();
        }

        /// <summary>
        /// 滑动的是滑动框
        /// </summary>
        private bool is_scroll_rect = false;

        private bool is_scroll_bar = false;

        /// <summary>
        /// 滑动条滑动的时候执行
        /// </summary>
        /// <param name="_offset"></param>
        private void OnScrollBar(float _value)
        {

            if (is_scroll_rect)
            {
                is_scroll_rect = false;
                return;
            }

            is_scroll_bar = true;
            is_scroll_rect = false;

            scroll_rect.content.localPosition = new Vector3(scroll_rect.content.localPosition.x, start_content_pos + _value * (total_count * rect_high - start_content_size.y),0);

            //是否发生变化            
            float _move_value = scroll_rect.content.anchoredPosition.y - start_content_pos;
            int _index = (int)(_move_value / rect_high);
            _index = Mathf.Clamp(_index, 0, total_count - all_child_list.Count);

            if (_index == cur_index)
            {
                return;
            }
            cur_index = _index;

            //刷新布局
            RefreshLayout();
        }

        /// <summary>
        /// 清理数据
        /// </summary>
        private void Clear()
        {
            for (int i = 0; i < all_child_list.Count; i++)
            {
                all_child_list[i].Clear();
                Destroy(all_child_list[i].Go);
            }
            all_child_list.Clear();
            if (scroll_bar != null)
            {
                scroll_bar.value = 0;
            }
        }
    #endregion

    #region 内嵌类
        /// <summary>
        /// 被滑动的子物体
        /// </summary>
        public class ScrollChild
        {
            /// <summary>
            /// 对象
            /// </summary>
            private GameObject go;
            public GameObject Go { get { return go; } }
            /// <summary>
            /// 刷新回调
            /// </summary>
            private System.Action<int, GameObject> refresh_callBack;

            private RectTransform rect_tr;

            /// <summary>
            /// 构造函数
            /// </summary>
            /// <param name="_go"></param>
            /// <param name="_action"></param>
            public ScrollChild(GameObject _go, System.Action<int, GameObject> _action)
            {
                go = _go;
                rect_tr = _go.GetComponent<RectTransform>();
                refresh_callBack = _action;
            }

            /// <summary>
            /// 刷新
            /// </summary>
            /// <param name="_index"></param>
            /// <param name="_pos"></param>
            public void Refresh(int _index, Vector2 _pos)
            {
                rect_tr.anchoredPosition = _pos;
                rect_tr.localPosition = new Vector3(rect_tr.localPosition.x, rect_tr.localPosition.y, 0);
                if (refresh_callBack != null)
                {
                    refresh_callBack(_index, go);
                }
            }

            public void Clear()
            {
                refresh_callBack = null;
            }
        }
    #endregion

    }
}

  由于代码注释比较详尽,我在这里就不做说明了。

4、使用举例

4.1、场景搭建

我们搭建如图的场景
在这里插入图片描述
Scroll View需要挂上前面的UIScrollControl 脚本,content需要调整锚点如上图,Viewport的锚点设置如下图所示:
在这里插入图片描述

4.2、测试

  使用时我们只需要,把这个类挂在滚动列表上,调用外部调用部分就可以了。如:

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

public class Test : MonoBehaviour {


    public UIScrollControl scroll_rect;
  
    private List<string> strList = new List<string>() { "春秋","战国","五代", "十国", "夏金", "唐", "宋", "元" };

	// Use this for initialization
	void Start () {

        GameObject item = transform.Find("Item").gameObject;

        scroll_rect.OpenControl(item, strList.Count, 100, init);

    }

    private void init(int index,GameObject obj)
    {
        Text txt = obj.transform.FindChild("Text").GetComponent<Text>();
        txt.text = strList[index];
    }

    // Update is called once per frame
    void Update () {
	}
}

4.3、效果展示

  使用效果如图所示:
在这里插入图片描述
  我们看到这里只创建了4个Item.

5、Demo下载

  如果有不明白的童鞋可以在这里下载上面的演示Demo,同时demo里面包含了竖直和水平两个方向的优化代码。

6、结束语


The End
  好了,今天的分享就到这里,如有不足之处,还望大家及时指正,随时欢迎探讨交流!!!


喜欢的朋友们,请帮顶、点赞、评论!您的肯定是我写作的不竭动力!

猜你喜欢

转载自blog.csdn.net/lxt610/article/details/90036080