UGUI制作自适应大小的滚动视图

方法一:使用UGUI自带的scrollview组件

缺点:占内存,耗费性能

方法二:代码制作自己的滚动视图

一、LoopList 类

功能

  1. 定义了滚动列表的相关属性,如 offSetY(项之间的间隔)、_itemHeight(单个项的高度)、_content(滚动视图的内容容器)、_itemsList(存储所有的 LoopListItem 实例)和 _modelsList(存储外部数据模型 LoopListItemModel)。
  2. 在 Start 方法中进行了一系列初始化操作,包括模拟数据获取(GetModel)、确定要显示的项数量(GetShowItemNum)、实例化项(SpawnItem)、设置内容大小(SetContentSize)以及注册滚动事件监听器(transform.GetComponent<ScrollRect>().onValueChanged.AddListener(ValueChange))。
  3. ValueChange 方法在滚动视图的值发生变化时被调用,遍历所有的 LoopListItem 实例并调用它们的 OnValueChange 方法以更新状态。
  4. GetShowItemNum 方法根据滚动视图的高度、项的高度和间隔计算要显示的项的数量。
  5. SpawnItem 方法根据给定的数量和项的预制体实例化 LoopListItem 实例,并设置它们的初始状态和添加到列表中。
  6. GetData 方法根据索引从 _modelsList 中返回相应的 LoopListItemModel,如果索引超出范围则返回一个新的默认模型。
  7. GetModel 方法从资源中加载所有的 Sprite,并为每个 Sprite 创建一个 LoopListItemModel 实例添加到 _modelsList 中。
  8. SetContentSize 方法根据项的数量、高度和间隔设置滚动视图内容容器的大小。

分三个类的优点

  1. 职责分离:LoopList 类主要负责管理整个滚动列表的逻辑,包括数据获取、项的实例化、滚动事件处理和内容大小设置等。将这些功能集中在一个类中,使得滚动列表的整体逻辑更加清晰,易于维护和扩展。
  2. 可重用性:如果在其他地方需要使用类似的滚动列表功能,只需要复用 LoopList 类,并根据具体需求调整一些参数和数据来源即可。

二、LoopListItem 类

功能

  1. 定义了单个项的相关属性,如 Rect(项的矩形变换)、Icon(项的图像)、Des(项的文本描述)、_content(父物体的内容容器)、_offSetY(项之间的间隔)、_showItemNum(要显示的项数量)、_id(项的唯一标识)和 _model(项的数据模型)。
  2. Init 方法用于初始化项的状态,包括设置父物体的内容容器、间隔、显示项数量,并调用 ChangeId 方法进行进一步的初始化。
  3. AddGetDataListener 方法用于注册一个委托,以便在需要时获取数据模型。
  4. OnValueChange 方法在滚动视图的值发生变化时被调用,用于更新项的状态。它通过调用 UpdateIdRange 和 JudgeSelfId 方法来确定项的新状态,并根据需要调用 ChangeId 方法进行更新。
  5. UpdateIdRange 方法根据内容容器的位置和项的高度、间隔计算项的起始和结束 id
  6. JudgeSelfId 方法根据项的当前 id 和计算得到的起始、结束 id 来判断项是否需要更新状态,如果需要则调用 ChangeId 方法。
  7. ChangeId 方法用于更新项的状态,包括设置 id、获取数据模型、更新图像和文本显示,以及设置项的位置。
  8. JudgeIdVaild 方法用于判断给定的 id 是否有效,通过比较数据模型是否与默认模型不同来判断。
  9. SetPos 方法根据项的 id、高度和间隔设置项的位置。

分三个类的优点

  1. 单一职责原则:LoopListItem 类专注于单个项的逻辑,包括状态更新、位置计算、数据显示等。这样使得每个类的职责更加明确,代码更易于理解和维护。
  2. 可扩展性:如果需要对单个项进行更多的定制和扩展,可以在 LoopListItem 类中进行,而不会影响到滚动列表的整体逻辑。

三、LoopListItemModel 结构体

功能

定义了一个数据模型,包含一个 Sprite(图像)和一个 string(描述),用于存储单个项的数据。同时提供了一个构造函数,方便创建具有特定图像和描述的 LoopListItemModel 实例。

分三个类的优点

  1. 数据封装:将项的数据封装在一个单独的结构体中,使得数据的管理更加清晰和规范。可以方便地在不同的类中传递和使用这个数据模型,而不需要直接操作图像和文本等具体的数据。
  2. 可维护性:如果需要修改项的数据结构,只需要在 LoopListItemModel 结构体中进行修改,而不会影响到其他两个类的逻辑。

综上所述,将这三个类分开可以实现更好的代码组织、职责分离、可重用性和可维护性。每个类都专注于特定的功能,使得整个代码结构更加清晰,易于理解和扩展。

1.LoopList

LoopList 类主要负责管理整个滚动列表的逻辑

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

public class LoopList : MonoBehaviour
{
    //item上下之间的间隔
    public float offSetY;
    private float _itemHeight;
    private RectTransform _content;
    private List<LoopListItem> _itemsList;//永久存放item的数据
    private List<LoopListItemModel> _modelsList;//用来存储外部进来的数据,把外部的图片当做精灵,给到item
    void Start()
    {
        _itemsList=new List<LoopListItem>();
        _modelsList = new List<LoopListItemModel>();
        //模拟数据获取
        GetModel();
        _content = transform.Find("Viewport/Content").GetComponent<RectTransform>();
        GameObject itemPrefab = Resources.Load<GameObject>("LoopListItem");
        _itemHeight=itemPrefab.GetComponent<RectTransform>().rect.height;
        int num = GetShowItemNum(_itemHeight,offSetY);
        SpawnItem(num,itemPrefab);
        SetContentSize();
        //AddListener 方法用于注册一个事件处理程序(在这里是 ValueChange 方法),当 ScrollRect 的值发生变化时,该方法会被调用。
        transform.GetComponent<ScrollRect>().onValueChanged.AddListener(ValueChange);
        
    }



    //ValueChange 是一个方法,当 onValueChanged 事件被触发时,它将被调用。
    //ScrollRect 的 onValueChanged 事件定义了一个特定的委托类型,通常是 UnityAction<Vector2>。
    //这意味着任何添加到这个事件的监听器(比如 ValueChange 方法)都必须接收一个 Vector2 类型的参数。
    private void ValueChange(Vector2 data)
    {
        foreach(LoopListItem item in _itemsList)
        {
            item.OnValueChange();
        } 
            
    }

    private int GetShowItemNum(float itemHeight,float offsety)
    {
        float height=GetComponent<RectTransform>().rect.height;
        return Mathf.CeilToInt(height/(_itemHeight+offsety))+1;
    }

    private void SpawnItem(int num,GameObject itemPrefab)
    {
        //临时变量用在父物体中存储生成的item
        GameObject Temp = null;
        //临时变量用在子物体本身的列表中存储item
        LoopListItem itemTemp = null;
        for (int i = 0; i < num; i++)
        {
            Temp=Instantiate(itemPrefab, _content);
            itemTemp=Temp.AddComponent<LoopListItem>();
            itemTemp.AddGetDataListener(GetData);
            itemTemp.Init(i,offSetY,num);
            _itemsList.Add(itemTemp);
        }
    }


    private LoopListItemModel GetData(int index)
    {
        if (index<0||index>=_modelsList.Count)
        {
            return new LoopListItemModel();
        }
        return _modelsList[index];
    }


    //获得外部的模型资源的精灵当做item
    private void GetModel()
    {
       
        foreach(var sprite in Resources.LoadAll<Sprite>("Icon"))
        {
            //使用构造函数生成一个新的实例
            _modelsList.Add(new LoopListItemModel(sprite,sprite.name));
        }
    }

    private void SetContentSize()
    {
        float y = _modelsList.Count * _itemHeight + (_modelsList.Count - 1) * offSetY;
        _content.sizeDelta=new Vector2(_content.sizeDelta.x,y);
    }
}

2.LoopListItem

LoopListItem 类专注于单个项的更新逻辑

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

public class LoopListItem : MonoBehaviour
{
    private RectTransform _rect;
    public RectTransform Rect
    {
        get
        {
            if ( _rect == null )
            {
                _rect = GetComponent<RectTransform>();
            }
            return _rect;
        }

    }

    private Image _icon;
    public Image Icon
    {
        get
        {
            if ( _icon == null )
            {
                //找到子物体下的image物体,获得它的image组件
                _icon = transform.Find("Image").GetComponent<Image>();
            }
            return _icon;
        }
    }

    private Text _des;
    public Text Des
    {
        get
        {
            if ( _des == null )
            {
                //找到子物体下的text物体,获得它的text组件
                _des = transform.Find("Text").GetComponent<Text>();
            }
            return _des;
        }
    }

    //private int _startId,_endId;
    private RectTransform _content;
    public float _offSetY;
    private int _showItemNum;
    private int _id;
    private LoopListItemModel _model;
    public void Init(int id,float offsety,int showItemNum)
    {
        
        //获得_content物体的组件,用于后续计算id
        _content=transform.parent.GetComponent<RectTransform>();
        _offSetY = offsety;
        _showItemNum = showItemNum;
        ChangeId(id);
    }
    //允许外部方法通过 AddGetDataListener 方法传递一个符合 Func<int, LoopListItem> 签名的方法。
    //通过 _getData 字段保存这个方法的引用,以便在需要时调用。
    //定义委托
    //只能在类的构造函数或其他方法中将该方法分配给 _getData 委托:
    private Func<int, LoopListItemModel> _getData;
    //这个方法用来接受委托,并且注册_getData委托
    //间接调用委托
    public void AddGetDataListener( Func<int, LoopListItemModel> getData )
    {
        _getData = getData;
    }


    public void OnValueChange()
    {
        int startId=0, endId=0;
        UpdateIdRange(out startId,out endId);
        JudgeSelfId(startId,endId);
        
    }

    private void UpdateIdRange(out int startId,out int endId)
    {
        
        startId =Mathf.FloorToInt( _content.anchoredPosition.y / (Rect.rect.height + _offSetY));
        endId = startId + _showItemNum - 1;
    }

    private void JudgeSelfId(int _startId,int _endId)
    {
        if (_id<_startId)
        {
            ChangeId(_endId);
        }
        else if(_id>_endId)
        {
            ChangeId(_startId);
        }
    }


    private void ChangeId(int id)
    {
        if (_id != id && JudgeIdVaild(id))
        {
            _id = id;
            _model = _getData(id);
            Icon.sprite = _model.Icon;
            Des.text = _model.Describe;
            SetPos();
        }
    }

    private bool JudgeIdVaild(int id)
    {
        return !_getData(id).Equals(new LoopListItemModel());
    }

    private void SetPos()
    {
        Rect.anchoredPosition = new Vector2(0, -_id * (Rect.rect.height + _offSetY));
    }
}

3.LoopListItemModel.cs

用于存储单个项的数据模型

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

public struct LoopListItemModel 
{
    public Sprite Icon;
    public string Describe;
    //构造函数与类或结构体同名,并且没有返回类型
    //构造函数是 C# 中一个非常重要的概念,它允许你在创建对象时初始化对象的状态。通过构造函数重载,
    //可以创建不同的构造函数来满足不同的初始化需求。通过实例化结构体或类并调用其构造函数,可以创建具备特定属性值的对象,以便在程序中使用。
    public LoopListItemModel(Sprite icon,string describe)
    {
        Icon = icon;
        Describe = describe;
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值