无限列表【UIGridView】应用示例

39 篇文章 1 订阅
36 篇文章 0 订阅

 UIGridView 源码

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

//Introduction: 无限列表
//Content上禁止挂载ContentSizeFilter和LayOutGroup之类组件
[DisallowMultipleComponent]
[RequireComponent(typeof(ScrollRect))]
public class UIGridView : MonoBehaviour
{
    private Action<Transform, int> _handle;

    public enum Arrangement
    {
        Horizontal = 0,
        Vertical,
    }

    public enum HorizontalAlign
    {
        Left,
        Middle,
        Right,
    }

    public enum VerticalAlign
    {
        Top,
        Middle,
        Bottom,
    }
    public Arrangement arrangement = Arrangement.Vertical;

    // 当选择水平或垂直流动是有用,指每行/列最大个数
    public int MaxPerLine
    {
        get { return maxPerLine; }
        set { SetMaxPerLine(value); }
    }

    public HorizontalAlign horizontalAlign = HorizontalAlign.Left;
    public VerticalAlign verticalAlign = VerticalAlign.Top;
    public Vector2 viewPort;

    public float rowSpace = 0;
    public float columuSpace = 0;
    public float marginTop = 0;
    public float marginBottom = 0;
    public float marginLeft = 0;
    public float marginRight = 0;

    public int maxPerLine;
    public int childCount; //需要渲染的总数据个数
    public GameObject item;
    

    public GameObject Child
    {
        get { return item; }
        set { SetItem(value); }
    }

    public int ChildCount
    {
        get { return childCount; }
        set { SetChildCount(value, true); }
    }

    public Vector2 ViewPort
    {
        get { return viewPort; }
        set { SetViewPort(value); }
    }

    
    ScrollRect scrollRect;
       
    RectTransform content;
    Vector2 itemSize;
    List<Transform> items;
    Dictionary<int, int> contains;
    List<int> outOfContains;
    
    int scrollLineIndex; //当前第一个元素索引
    int totalCount; //在UI中显示的个数(不乘以maxPerLine)
    Vector2 startPos; //第一个元素所在位置
    int startIndex; //当前渲染起始坐标
    int endIndex; //当前渲染结束坐标
        

    void Start()
    {
        maxPerLine = maxPerLine == 0 ? 1 : maxPerLine;
        items = new List<Transform>();
        contains = new Dictionary<int, int>();
        outOfContains = new List<int>();
        scrollRect = transform.GetComponent<ScrollRect>();
        content = scrollRect.content;
        if (content == null)
        {
            Debug.Log("ScrollRect " + scrollRect.gameObject.name + " Has No Content, Please Check And Retry.");
            return;
        }
        //viewPort = scrollRect.viewport.rect.size;

        if (item != null)
            SetItem(item);

        content.anchorMax = new Vector2(0, 1);
        content.anchorMin = new Vector2(0, 1);
        content.pivot = new Vector2(0, 1);
        ReBuild();
    }

    public void ReBuild()
    {
        if (scrollRect == null || content == null || item == null) return;
        ResetChildren();

        Vector2 maskSize = viewPort;
        int count = 0;

        if (arrangement == Arrangement.Horizontal)
        {
            count = Mathf.CeilToInt(maskSize.x / itemSize.x) + 1; //横向列数
            startPos = Vector2.zero;
            startPos.x = marginLeft;
            if (verticalAlign == VerticalAlign.Top)
            {
                startPos.y = -marginTop;
            }
            else if (verticalAlign == VerticalAlign.Middle)
            {
                startPos.y = -(maskSize.y * 0.5f - (itemSize.y * maxPerLine + (maxPerLine - 1) * rowSpace) * 0.5f);
            }
            else if (verticalAlign == VerticalAlign.Bottom)
            {
                startPos.y = -(maskSize.y - marginBottom - itemSize.y * maxPerLine - rowSpace * (maxPerLine - 1));
            }
        }
        else if (arrangement == Arrangement.Vertical)
        {
            count = Mathf.CeilToInt(maskSize.y / itemSize.y) + 1; //竖向行数
            startPos = Vector2.zero;
            startPos.y = -marginTop; //重置开始节点位置
            if (horizontalAlign == HorizontalAlign.Left)
            {
                startPos.x = marginLeft;
            }
            else if (horizontalAlign == HorizontalAlign.Middle)
            {
                startPos.x = (maskSize.x * 0.5f - (itemSize.x * maxPerLine + (maxPerLine - 1) * columuSpace) * 0.5f);
            }
            else if (horizontalAlign == HorizontalAlign.Right)
            {
                startPos.x = maskSize.x - marginRight - itemSize.x * maxPerLine - columuSpace * (maxPerLine - 1);
            }
        }
        totalCount = count;

        SetChildCount(childCount, true);
        BackTop();

        scrollRect.onValueChanged.RemoveAllListeners();
        scrollRect.onValueChanged.AddListener(OnValueChanged);
    }


    //列表滚动
    private void OnValueChanged(Vector2 vec)
    {
        switch (arrangement)
        {
            case Arrangement.Horizontal:
                vec.x = Mathf.Clamp(vec.x, 0, 1);
                break;
            case Arrangement.Vertical:
                vec.y = Mathf.Clamp(vec.y, 0, 1);
                break;
        }

        int curLineIndex = GetCurLineIndex();
        if (curLineIndex != scrollLineIndex)
            UpdateRectItem(curLineIndex, false);
    }

    private int GetCurLineIndex()
    {
        switch (arrangement)
        {
            case Arrangement.Horizontal:
                return
                    Mathf.FloorToInt(Mathf.Abs(content.anchoredPosition.x < 0.1f ? content.anchoredPosition.x : 0.1f - marginLeft) /
                                        (columuSpace + itemSize.x));
            case Arrangement.Vertical:
                return
                    Mathf.FloorToInt(Mathf.Abs(content.anchoredPosition.y > -0.1f ? content.anchoredPosition.y : -0.1f - marginTop) /
                                        (rowSpace + itemSize.y));
        }
        return 0;
    }

    private void UpdateRectItem(int curLineIndex, bool forceRender)
    {
        if (curLineIndex < 0)
            return;
        startIndex = curLineIndex * maxPerLine;
        endIndex = (curLineIndex + totalCount) * maxPerLine;
        if (endIndex >= childCount)
            endIndex = childCount;

        contains.Clear(); //渲染序号
        outOfContains.Clear(); //items的索引
        for (int i = 0; i < items.Count; i++)//如果当前已渲染的item中包含
        {
            int index = int.Parse(items[i].gameObject.name);
            if (index < startIndex || index >= endIndex)
            {
                outOfContains.Add(i);
                items[i].gameObject.SetActive(false);
            }
            else
            {
                items[i].gameObject.SetActive(true);
                contains.Add(index, i);
            }
        }

        // *************更改渲染****************
        for (int i = startIndex; i < endIndex; i++)
        {
            if (!contains.ContainsKey(i))
            {
                Transform child = items[outOfContains[0]];
                outOfContains.RemoveAt(0);
                child.gameObject.SetActive(true);
                int row = i / maxPerLine;
                int col = i % maxPerLine;
                if (arrangement == Arrangement.Vertical)
                    child.localPosition = startPos +
                                            new Vector2(col * itemSize.x + (col) * columuSpace,
                                                -row * itemSize.y - (row) * rowSpace);
                else
                    child.localPosition = startPos +
                                            new Vector2(row * itemSize.x + (row) * columuSpace,
                                                -col * itemSize.y - (col) * rowSpace);
                child.gameObject.name = i.ToString();
                if (_handle != null)
                    _handle(child, i);
            }
            else if (forceRender)
            {
                if (_handle != null)
                    _handle(items[contains[i]], i);
            }
        }

        scrollLineIndex = curLineIndex;
    }

    /// 移除当前所有
    private void ResetChildren()
    {
        items.Clear();
        for (int i = 0; i < content.childCount; i++)
        {
            Transform child = content.GetChild(i);
            child.gameObject.SetActive(false);
        }
    }
    
    // 创建新节点
    private RectTransform CreateItem(int index)
    {
        Transform child;
        if (content.childCount > index)
        {
            child = content.GetChild(index);
        }
        else
        {
            GameObject obj = GameObject.Instantiate(item) as GameObject;
            obj.transform.SetParent(content);
            obj.transform.localScale = Vector3.one;
            child = obj.transform;
        }
        child.gameObject.name = index.ToString();
        items.Add(child);

        return child as RectTransform;
    }

    // 设置资源
    public void SetItem(GameObject child)
    {
        if (child == null) return;
        this.item = child;
        RectTransform itemTrans = child.transform as RectTransform;
        itemTrans.pivot = new Vector2(0, 1);
        itemSize = itemTrans.sizeDelta;
        //ReBuild();
    }

    // 更新需要渲染的个数
    public void SetChildCount(int value, bool forceRender)
    {
        if (value < 0) childCount = 0;
        else childCount = value;

        if (totalCount <= 0)//还未初始化
            return;
        if (value > items.Count && items.Count < maxPerLine * totalCount)
        {
            //当前格子数量少于应生成的数量
            int count = items.Count;
            int max = value < maxPerLine * totalCount ? value : maxPerLine * totalCount;
            for (int i = count; i < max; i++)
            {
                int row = i / maxPerLine;
                int col = i % maxPerLine;
                RectTransform child = CreateItem(i);
                if (arrangement == Arrangement.Vertical)
                    child.localPosition = startPos +
                                            new Vector2(col * itemSize.x + (col) * columuSpace,
                                                -row * itemSize.y - (row) * rowSpace);
                else
                    child.localPosition = startPos +
                                            new Vector2(row * itemSize.x + (row) * columuSpace,
                                                -col * itemSize.y - (col) * rowSpace);
            }
        }

        if (content == null) return;

        int rc = Mathf.CeilToInt((float)childCount / (float)maxPerLine); //设置content的大小
        if (arrangement == Arrangement.Horizontal)
        {
            content.sizeDelta = new Vector2(marginLeft + marginRight + itemSize.x * rc + columuSpace * (rc - 1),
                viewPort.y);
            if (content.sizeDelta.x > viewPort.x && content.anchoredPosition.x < viewPort.x - content.sizeDelta.x)
                content.anchoredPosition = new Vector2(viewPort.x - content.sizeDelta.x, content.anchoredPosition.y);
        }
        else
        {
            content.sizeDelta = new Vector2(viewPort.x, marginTop + marginBottom + itemSize.y * rc + rowSpace * (rc - 1));
            if (content.sizeDelta.y > viewPort.y && content.anchoredPosition.y > content.sizeDelta.y - viewPort.y)
                content.anchoredPosition = new Vector2(content.anchoredPosition.x, content.sizeDelta.y - viewPort.y);
        }
        UpdateRectItem(GetCurLineIndex(), true);
    }

    // 添加子节点
    public void AddChild(int index)
    {
        if (index < 0) return;
        startIndex = scrollLineIndex * maxPerLine;
        endIndex = (scrollLineIndex + totalCount) * maxPerLine;
        SetChildCount(childCount + 1, index >= startIndex && index < endIndex);
    }

    // 删除子节点
    public void RemoveChild(int index)
    {
        if (index < 0 || index >= childCount) return;
        startIndex = scrollLineIndex * maxPerLine;
        endIndex = (scrollLineIndex + totalCount) * maxPerLine;
        SetChildCount(childCount - 1, index >= startIndex && index < endIndex);
    }


    // <summary>
    // 设置显示窗口大小(现在貌似可以废弃了)
    public void SetViewPort(Vector2 port)
    {
        if (port == viewPort) return;
        viewPort = port;
        //ReBuild();
    }

    // 设置行列最大
    public void SetMaxPerLine(int max)
    {
        maxPerLine = max;
        //ReBuild();
    }
    
    // 返回顶部
    public void BackTop()
    {
        content.localPosition = Vector3.zero;
        UpdateRectItem(0, true);
    }


    // 返回底部
    public void BackBottom()
    {
        if (arrangement == Arrangement.Vertical)
        {
            content.localPosition = new Vector3(0, -viewPort.y + content.sizeDelta.y, 0);
        }
        else
        {
            content.localPosition = new Vector3(viewPort.x - content.sizeDelta.x, 0);
        }
        UpdateRectItem(Mathf.CeilToInt((float)childCount / (float)maxPerLine) - totalCount + 1, true);
    }

    public void RefreshViewItem()
    {
        UpdateRectItem(scrollLineIndex, true);
    }


    public void SetArrangement(int arr)
    {
        arrangement = (Arrangement)arr;
    }

    public void SetHorizontal(int h)
    {
        horizontalAlign = (HorizontalAlign)h;
    }

    public void SetVerticle(int v)
    {
        verticalAlign = (VerticalAlign)v;
    }

    public void AddChangeItemListener(Action<Transform, int> handle)
    {
        _handle = handle;
    }
}

 示例:
Lua应用示例:

local Tool = require("Tool")
 
local SprogExtendWindow = {}

local mediate = nil
local gridView = nil
local extendItem = nil
local extendItemInfo = nil
local sortList = nil
local extendCfgs = nil

local rewardState = 
{
    UNFINISH = 0,  --可领取
    UNREACH  = 1,  --未达成
    FINISHED = 2,  --已领取
}

function SprogExtendWindow.Init(data)

	mediate = data

    -- 创建ITEM实例
    ResMgr.LoadAssets("prefab", { "SprogExtendItem" },function(objs)
        extendItem = objs[0]
    end)

	extendItemInfo = {} -- 服务器下发数据
    sortList = {} -- 前端已排序处理,用于显示的数据

    SprogExtendWindow.InitGridView()
    SprogExtendWindow.RegistEvents()

    SprogExtendWindow.UpdateAllExtendInfo()
	return SprogExtendWindow
end

function SprogExtendWindow.InitGridView()

    gridView = mediate:FindChild("Layer_Sprite/panelGroup/extendPanel/bottom/Scroll View"):GetComponent("UIGridView") -- 获取UIGridView组件
    gridView.maxPerLine = 1
    gridView.rowSpace = 1
    gridView:SetItem(extendItem)
    gridView:SetViewPort(Vector2(565,325))

    -- 在ScrollView拖动时,UIGridView会回调即将出现的item,客户端只需要填入相应的UI数据.
    -- PS: 此函数会不停地调用,得考虑性能问题,避免处理过大的数据
    gridView:AddChangeItemListener(function(transform, index)
        local index = index + 1

        if sortList and sortList[index] then
            local item = sortList[index]
            Tool.subGetObject(transform, "time", "Text").text = item.config.time .. " " .. item.config.id
            Tool.subGetObject(transform, "content", "Text").text = item.config.name
            Tool.subGetObject(transform, "image", "Image").sprite = Tool.LoadImgSpriteFromAb("image", item.config.img)

            SprogExtendWindow.SetStateInfo(item.state, transform)

            transform:FindChild("Button").onClick = function(obj, eventData)
                SprogExtendWindow.CheckExtendItemInfo(item.config.id, transform)
            end
        end
	end)

end


function SprogExtendWindow.RegistEvents()

	mediate:AddClick("Layer_Sprite/panelGroup/extendPanel/top/btn_num", function () 
		log("按钮1")
	end)

	mediate:AddClick("Layer_Sprite/panelGroup/extendPanel/top/btn_link", function () 
		log("按钮2")
    end)
        
	mediate:AddClick("Layer_Sprite/panelGroup/extendPanel/top/btn_share", function () 
		log("按钮3")
	end)
end

function SprogExtendWindow.InitScrollView()

end

function SprogExtendWindow.UpdateAllExtendInfo()

    -- TEST,选用临时数据
    local data = 
    {
        [1] = {id = 1, type = 1, time = "2020.01.01", name = "床", img = "yxdt_tx1.png", vip = 1, liquanNum = 99999},
        [2] = {id = 2, type = 1, time = "2020.01.02", name = "前", img = "yxdt_tx1.png", vip = 2, liquanNum = 99999},
        [3] = {id = 3, type = 1, time = "2020.01.03", name = "明", img = "yxdt_tx1.png", vip = 3, liquanNum = 99999},
        [4] = {id = 4, type = 1, time = "2020.01.04", name = "月", img = "yxdt_tx1.png", vip = 4, liquanNum = 99999},
        [5] = {id = 5, type = 1, time = "2020.01.05", name = "光", img = "yxdt_tx1.png", vip = 5, liquanNum = 99999},
        [6] = {id = 6, type = 1, time = "2020.01.06", name = "床", img = "yxdt_tx1.png", vip = 6, liquanNum = 99999},
        [7] = {id = 7, type = 1, time = "2020.01.07", name = "前", img = "yxdt_tx1.png", vip = 7, liquanNum = 99999},   
        [8] = {id = 8, type = 1, time = "2020.01.08", name = "明", img = "yxdt_tx1.png", vip = 8, liquanNum = 99999},
        [9] = {id = 9, type = 1, time = "2020.01.09", name = "月", img = "yxdt_tx1.png", vip = 9, liquanNum = 99999},
        [10] = {id = 10, type = 1, time = "2020.01.10", name = "光", img = "yxdt_tx1.png", vip = 10, liquanNum = 99999},
        [11] = {id = 11, type = 1, time = "2020.01.01", name = "床", img = "yxdt_tx1.png", vip = 1, liquanNum = 99999},
        [12] = {id = 12, type = 1, time = "2020.01.02", name = "前", img = "yxdt_tx1.png", vip = 2, liquanNum = 99999},
        [13] = {id = 13, type = 1, time = "2020.01.03", name = "明", img = "yxdt_tx1.png", vip = 3, liquanNum = 99999},
        [14] = {id = 14, type = 1, time = "2020.01.04", name = "月", img = "yxdt_tx1.png", vip = 4, liquanNum = 99999},
        [15] = {id = 15, type = 1, time = "2020.01.05", name = "光", img = "yxdt_tx1.png", vip = 5, liquanNum = 99999},
        [16] = {id = 16, type = 1, time = "2020.01.06", name = "床", img = "yxdt_tx1.png", vip = 6, liquanNum = 99999},
        [17] = {id = 17, type = 1, time = "2020.01.07", name = "前", img = "yxdt_tx1.png", vip = 7, liquanNum = 99999},   
        [18] = {id = 18, type = 1, time = "2020.01.08", name = "明", img = "yxdt_tx1.png", vip = 8, liquanNum = 99999},
        [19] = {id = 19, type = 1, time = "2020.01.09", name = "月", img = "yxdt_tx1.png", vip = 9, liquanNum = 99999},
        [20] = {id = 20, type = 1, time = "2020.01.10", name = "光", img = "yxdt_tx1.png", vip = 10, liquanNum = 99999},
        [21] = {id = 21, type = 1, time = "2020.01.01", name = "床", img = "yxdt_tx1.png", vip = 1, liquanNum = 99999},
        [22] = {id = 22, type = 1, time = "2020.01.02", name = "前", img = "yxdt_tx1.png", vip = 2, liquanNum = 99999},
        [23] = {id = 23, type = 1, time = "2020.01.03", name = "明", img = "yxdt_tx1.png", vip = 3, liquanNum = 99999},
        [24] = {id = 24, type = 1, time = "2020.01.04", name = "月", img = "yxdt_tx1.png", vip = 4, liquanNum = 99999},
        [25] = {id = 25, type = 1, time = "2020.01.05", name = "光", img = "yxdt_tx1.png", vip = 5, liquanNum = 99999}
    } 

    extendItemInfo = {}
    for k, v in ipairs(data) do
        extendItemInfo[v.id] = 
        {   
            config = v, -- 本地配置
            state = math.random(0, 2) -- 随机一个状态
        }
    end

    SprogExtendWindow.OnSortAllExtendItem()

    gridView.ChildCount = #sortList -- 设置当前item总数
    gridView:ReBuild() -- 启动UIGridView
end

-- 整理数据并排序
function SprogExtendWindow.OnSortAllExtendItem()

    -- 按rewardState分组, 可领取>未达成>已领取
    local templist = {}
    for index = 1, 3 do
        for _,v in ipairs(extendItemInfo) do
            if index == v.state + 1 then
                if templist[index] == nil then
                    templist[index] = {}
                end
                table.insert(templist[index], v)
            end
        end
    end

    -- 每组按VIP等级排序
    for _,v in ipairs(templist) do
        table.sort(v, function (a, b)
			return a.config.vip > b.config.vip
		end)
	end

    sortList = {}
    for _,v in ipairs(templist) do
        for _,m in ipairs(v) do
            table.insert(sortList, m)
        end
	end

    -- log("排序后列表 " .. tostring(sortList))
end

function SprogExtendWindow.SetStateInfo(state, obj)
    local text = nil
    local isInteract = nil
    if state == rewardState.UNREACH then
        text = "未达成"
        isInteract = false
    elseif state == rewardState.UNFINISH then
        text =  "可领取"
        isInteract = true
    elseif state == rewardState.FINISHED then
        text =  "已领取"
        isInteract = false
    end

    obj.transform:FindChild("Button").interactable = isInteract
    Tool.subGetObject(obj, "Button/Text", "Text").text = text
end

function SprogExtendWindow.CheckExtendItemInfo(id, obj)

    if extendItemInfo ~= nil and extendItemInfo[id] ~= nil then

        local item = extendItemInfo[id]

        local reqSuccess = function(www)
            --local data = Json.decode(www.text).data
            log("领取成功,data: " .. CC.uu.Dump(data))
            
            item.state = 2
            SprogExtendWindow.SetStateInfo(item.state, obj)
        end
        
        local reqFailed = function(err)
            log("领取失败:" .. tostring(err))
        end
    
        -- 发送请求
        local Url = Tool.UrlMgr.UrlMapping("http://172.0.0.1:8000/GetTestData", {id = id, channelId = id})
        Tool.HttpPost(Url, nil, reqSuccess, reqSuccess)
    end
end


function SprogExtendWindow.Destroy()


end

return SprogExtendWindow

 

Unity组件挂载:








 

Super ScrollView for UGUI提供基于UGUI ScrollRect的可轻松定制的ScrollView。它是一组C#脚本,可帮助您创建所需的ScrollView。这是非常强大的和高度优化的性能。 文件 Android演示应用程序 演示: - 聊天消息列表演示 - 水平画廊演示 - 垂直画廊演示 - GridView演示 - PageVew演示 - TreeVew演示 - 与稠粘头演示的TreeView - 旋转日期选择器 - 更改项目高度演示 - 下拉刷新演示 - 拉起来加载更多的演示 - 点击加载更多演示 - 选择并删除演示 - GridView删除项目演示 - 顶部到底部的演示 - 自下而上的演示 - 从左到右的演示 - 右侧演示 - 响应GridView演示 - TreeViewWithChildrenIndent演示 特征: - ListView和GridView和TreeView - 无限的项目 - 项目在不同的大小(高度/宽度) - 具有不同预制的物品 - 在初始时间大小未知的项目 - 垂直滚动视图(从上到下,从下到上) - 水平滚动视图(从左到右,从右到左) - 项目填充 - 滚动到指定的项目 - 滚动到具有偏移量的项目 - 项目计数在运行时更改 - 项目大小(高度/宽度)在运行时更改 - 物品捕捉到视口中的任何位置 - 项目循环,如微调 - 添加/删除项目 - 全部删除/删除所有项目 - 刷新并重新加载项目 - 使用池缓存项目,不要在运行时销毁项目 - 有效回收物品 - 平台无关 - UGUI支持 - 支持Unity平台(IOS / Android / Mac / PC / Console / Winphone / WebGL ...)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值