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
好了,今天的分享就到这里,如有不足之处,还望大家及时指正,随时欢迎探讨交流!!!
喜欢的朋友们,请帮顶、点赞、评论!您的肯定是我写作的不竭动力!