制作一个简易的UGUI无限滑动框(Unity)

Posted chenggg

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了制作一个简易的UGUI无限滑动框(Unity)相关的知识,希望对你有一定的参考价值。

市面上有很多这种无限拖拽的插件 但是功能细化的太严重了 改的话有些耗时 如果没有太多严苛的需求没必要改工程量比较大的插件 完全可以自己写一个 

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class ItemRect
{
    public Rect mRect;

    //格子的索引 因为是用来关联位置的 所以不存在删除或修改操作
    public readonly int itemIndex;

    //如果这个位置上存在显示的UI那么将格子的isUse属性设置为true
    public bool isUse = false;

    public ItemRect(float x,float y,float width,float height,int itemIndex)
    {
        mRect = new Rect(x,y,width,height);
        this.itemIndex = itemIndex;
    }

}


public interface IItemExecute {

    //刷新数据
    void Execute_FinshData();
    //绑定UI
    void BindUgui();
}

//抽象任何地方出每一个滑动单元的公共方法
public class ItemPropoty : MonoBehaviour, IItemExecute
{
    //储存的数据
    public object Data;
    public object DataKey;

    public ItemRect mDRect;

    public delegate void FreshFunc(int index);
    //在初始化的时候给到刷新数据的方法 
    public FreshFunc onUpdata;


    public virtual void Execute_FinshData() {
        BindUgui();
        if (onUpdata != null)
            onUpdata(mDRect.itemIndex);
    }

    public void Execute_PlacePos()
    {
        (transform as RectTransform).sizeDelta = mDRect.mRect.size;
        (transform as RectTransform).localPosition = mDRect.mRect.position;
        BindUgui();
    }

    /// <summary>
    /// 绑定UGUI 用名称查询的方式绑定UI类索引
    /// </summary>
    public virtual void BindUgui() {

    }

}


/// <summary>
/// 定制滑动单元
/// </summary>

  这个用来给定格子位置  在初始化的时候就确认所有格子的位置 这样不用动态的计算 

  这里我把那个UI的绑定设计为 尽量使用名字去查询UI 在bindUgui中去查询和添加按钮还有一些其他的回调

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

public class ItemUnit_1 : ItemPropoty
{
    Button btn;
    Text txt;

    public override void BindUgui()
    {
        btn = transform.GetComponentInChildren<Button>();
        txt = transform.GetComponentInChildren<Text>();
        txt.text = mDRect.itemIndex.ToString();
        btn.onClick.AddListener(() => {
            txt.text = mDRect.itemIndex + " : " + mDRect.itemIndex;

        });

    }

    public void AddImage() {
    }
    public void ChangeImage() {

    }
}

  

把ItemPropoty放在拖拽的单元上面
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using System.Linq;
public class CustomScroll : MonoBehaviour
{
    //用于显示的单元
    public GameObject itemUnit;
    private RectTransform item;
    //这个用来存储UI显示的实例
    private List<Transform> itemList;
    //记录所有ui应该显示的位置
    private Dictionary<int, ItemRect> itemRectDic;


    //显示的画布
    public RectTransform mDisplayRect;

    //上拉是否还有显示单位
    private bool? isHavePrevious = null;
    public bool? IsHavePrevious { get => isHavePrevious; }

    //下拉是否还有显示单位
    private bool? isHaveSubsequent = null;
    public bool? IsHaveSubsequent { get => isHaveSubsequent; }


    private ScrollRect scrollRect;

    //显示的行数  需要计算
    private int scrollRow;
    public int ScrollRow { get => scrollRow; }

    [Header("行间隙")]
    public float interval_Veritical;
    public float Interval_Veritical { get => interval_Veritical + item.rect.height; }

    //显示的列数
    private int scrollColumn;
    public int ScrollColumn { get => scrollColumn; }

    [Header("列间隙")]
    public float interval_Horizontal;
    public float Interval_Horizontal { get => interval_Horizontal + item.rect.width;}

    void Start()
    {
        IntializeScroll();
        IntializeDisplayCount(2, 30);

        //TODO FIX 测试用
        isLoad = true;
    }

    //做一些初始化的任务
    private void IntializeScroll()
    {
        itemList = new List<Transform>();

        waitChangeList = new List<ItemPropoty>();

        //GetComponentInParent<ScrollRect>().movementType = ScrollRect.MovementType.Clamped;

        scrollRect = GetComponentInParent<ScrollRect>();

        itemRectDic = new Dictionary<int, ItemRect>();
        item = itemUnit.transform as RectTransform;

        //初始化位置记录
        previousPagePos = transform.position.y;

    }

    /// <summary>
    /// 先计算出显示的行列
    /// </summary>
    public void IntializeDisplayCount(int columnCount, int itemCount) {
        SetContentSizeDelta(columnCount, itemCount);
        IntializeDisplayCount(scrollRow, scrollColumn, itemCount);
    }

    private void SetContentSizeDelta(int columnCount, int itemCount) {
        //若乱设置0或复负数直接按1处理
        scrollColumn = Mathf.Max(1, columnCount);
        //计算出显示的最大行数  item的长度
        scrollRow = itemCount / scrollColumn + 1;
        //设置显示画布的大小
        (transform as RectTransform).sizeDelta = new Vector2(
            scrollColumn * Interval_Horizontal,
            (scrollRow - 1) * Interval_Veritical);
    }

    //
    public void IntializeDisplayCount(int rowCount,int columnCount, int itemCount)
    {
        //先初始化好格子的位置 在初始化位置的同时设置好显示内容的尺寸
        AddItemPosition(itemCount);
        //计算当前显示的数量 存在一个占半格子的问题 暂时未处理 可以使用rect.Overlaps 查看是否相交 根据相交的位置判断加的排数
        var pageSize = (transform.parent.parent as RectTransform).sizeDelta;
        //因为间距的问题没处理所以临时加2
        var pageArea = (int)(pageSize.x / Interval_Horizontal)* (int)(pageSize.y / Interval_Veritical);
        //TODO FIX
        int maxDsiplayNum = (int)pageArea + scrollColumn+2;
            
        //Debug.Log("当前最大的显示数量 : "+maxDsiplayNum);

        for (int i = 0; i < scrollRow && i< maxDsiplayNum; i++)
        {
           
            Transform tmpItem = GameObject.Instantiate(itemUnit).transform;
            itemList.Add(tmpItem);
            tmpItem.localScale = Vector3.one;
            tmpItem.SetParent(transform, false);
            //tmpItem.gameObject.AddComponent<ItemUnit_1>();
            //tmpItem.gameObject.layer = mDisplayRect.gameObject.layer;
            tmpItem.gameObject.SetActive(false);
            tmpItem.name = i.ToString();
        }
        BenginDisplay();
    }

    /// <summary>
    /// 开始显示UI单元
    /// </summary>
    public void BenginDisplay() {
        //标记显示头
        for (int i = 0; i < itemRectDic.Count; i++)
        {
            if (i == itemList.Count)
                break;
          
            //得到物体身上的属性
            var tmp = itemList[i].GetComponent<ItemPropoty>();
            //拿到对应的值
            itemRectDic.TryValueGet(i,out tmp.mDRect);
            //给定位置
            tmp.Execute_PlacePos();
            //将他设置为可见
            itemList[i].gameObject.SetActive(true);

            tmp.mDRect.isUse = true;

        }
    }


    private int allItemCount = 0;
    public int AllItemCount { get => allItemCount; }

    [Header("x,y轴的初始边距")]
    public int distance_x = 100;
    public int distance_y = -50;

    /// <summary>
    /// 判断元数是否为追加
    /// </summary>
    /// <param name="isSuperaddition"></param>
    public void AddItemPosition(int nxtCount) {

        int curRow,curColumn;
        int tmp = itemRectDic.Count;
        allItemCount += nxtCount;
        SetContentSizeDelta(scrollColumn,allItemCount);
        for (int i = tmp; i < tmp + nxtCount; i++)
        {
            curRow = i / scrollColumn;
            curColumn = i % scrollColumn;
            var itemPos = new ItemRect(curColumn*Interval_Horizontal + distance_x,
                -curRow*Interval_Veritical + distance_y,
                item.rect.width,
                item.rect.height,
                i);
            itemRectDic.Add(i,itemPos);
        }
    }


    /// <summary>
    /// 查询所有排位置的最值元素 
    /// 做个优化 查询每一排的第一个元素就行了  这样换排的时候 如果是上一排就用index-1 下一排就是 index+scrollColumn(列数)
    /// </summary>
    /// <param name="isMax"></param>
    /// <returns></returns>
    public Transform GetBestValueItem(bool isMax)
    {
        if (itemList == null)
        {
            return null;
        }
        Transform bestTmp = itemList[0];
        for (int i = 0; i < itemList.Count; i += scrollColumn)
        {

            bool result = isMax ? bestTmp.position.y < itemList[i].position.y 
                : bestTmp.position.y > itemList[i].position.y;
            if (result)
            {
                bestTmp = itemList[i];
            }
        }
       // Debug.Log(bestTmp.name);
        return bestTmp;
    }

    /// <summary>
    /// 查询最值同时把index传出去
    /// </summary>
    /// <param name="isMax"></param>
    /// <param name="index"></param>
    /// <returns></returns>
    public Transform GetBestValueItem(bool isMax,out int index)
    {
        var tmp = GetBestValueItem(isMax);
        index = tmp.GetComponent<ItemPropoty>().mDRect.itemIndex;
        return tmp;
    }




    private List<ItemPropoty> waitChangeList;
    /// <summary>
    /// 查找到元素Y轴位置最小的元素
    /// </summary>
    /// <param name="item"></param>
    private void ExecuteChangeLocation(Transform item,bool isDown)
    {
        Vector3 minItemPosition = GetBestValueItem(!isDown).position;
       
        //开始收集同排的元素位置
        for (int i = 0; i < itemList.Count; i++)
        {
            if (itemList[i].Comparer_Position_Y(item.position)) {
                waitChangeList.SortAdd(itemList[i].GetComponent<ItemPropoty>());
            }
        }
        ChangeLineLocation(isDown);
    }

    /// <summary>
    /// 根据刷新方式做出更改位置操作
    /// </summary>
    /// <param name="isDown"></param>
    private void ChangeLineLocation(bool isDown) {
        if (waitChangeList == null || waitChangeList.Count == 0) {
            Debug.LogError("翻车了: ChangeLineLocation 查询失败");
            return;
        }

        //拿到当前最低/高位置的格子
        var bestValue = GetBestValueItem(!isDown).GetComponent<ItemPropoty>();
        
        #region
        /*
        int listIndex,itemIndex;
        for (int i = 0; i < waitChangeList.Count; i++) {
            if (isDown)
            {
                itemIndex = bestValue.mDRect.itemIndex + i + scrollColumn;
                listIndex = i;
            }
            else
            {
                itemIndex = bestValue.mDRect.itemIndex - (waitChangeList.Count - 1);
                listIndex = scrollColumn - (waitChangeList.Count - i);
            }

            if (itemRectDic.ContainsKey(itemIndex))
            {

                //当往下显示的时候将上边隐藏部位的格子释放掉
                waitChangeList[listIndex].mDRect.isUse = false;
                //查询到左下角的首元素加上列数个单位之后 正好是下一行的位置
                waitChangeList[listIndex].mDRect = itemRectDic[itemIndex];
                waitChangeList[listIndex].Execute_PlacePos();
                //标记新的占用位置
                waitChangeList[listIndex].mDRect.isUse = true;
            }
        }
         */
        #endregion

        if (isDown)
        {
            //Debug.Log("发现最底面的格子 : "+bestValue.name);
            for (int i = 0; i < waitChangeList.Count; i++)
            {
                //当再次刷新出现越界的时候    就是画布底面已经没有位置了不需要UI再往下刷新了 跳出去
                if (itemRectDic.ContainsKey(bestValue.mDRect.itemIndex + i + scrollColumn))
                {

                    //当往下显示的时候将上边隐藏部位的格子释放掉
                    waitChangeList[i].mDRect.isUse = false;
                    //查询到左下角的首元素加上列数个单位之后 正好是下一行的位置
                    waitChangeList[i].mDRect = itemRectDic[bestValue.mDRect.itemIndex + i + scrollColumn];
                    waitChangeList[i].Execute_PlacePos();
                    //标记新的占用位置
                    waitChangeList[i].mDRect.isUse = true;
                }
            }
        }
        else
        {                                                                     
            for (int i = scrollColumn; i > 0 ; i--)
            {
                //Debug.Log("获取的值"+(bestValue.mDRect.itemIndex - i));
                //当再次刷新出现越界的时候    就是画布底面已经没有位置了不需要UI再往下刷新了 跳出去
                if (itemRectDic.ContainsKey(bestValue.mDRect.itemIndex - i))
                {
                    //Debug.Log("拾取格子"+(bestValue.mDRect.itemIndex - i));
                    //当往下显示的时候将上边隐藏部位的格子释放掉
                    waitChangeList[scrollColumn -i].mDRect.isUse = false;
                    //查询到左下角的首元素加上列数个单位之后 正好是下一行的位置
                    waitChangeList[scrollColumn - i].mDRect = itemRectDic[bestValue.mDRect.itemIndex -i];
                    waitChangeList[scrollColumn - i].Execute_PlacePos();
                    //标记新的占用位置
                    waitChangeList[scrollColumn - i].mDRect.isUse = true;
                }
            }
        }

        //改完格子清理表
        waitChangeList.Clear();
    }

    //TODO fix
    /// <summary>
    /// 检测元素是否越界 目前的策略 用每排第一个元素进行Y轴判断移动位置
    /// 所有的元素位置都保存在字典里 找出越界的位置的刷新那一排的位置
    /// 判断元素向上越界还是向下越界
    /// </summary>
    /// <returns></returns>
    private Transform CheckCrossTheBorder(bool isDown)
    {
        //Debug.Log("我传入的值 : " + isDown);
        var tmpMaxItem = GetBestValueItem(isDown);
      //Debug.Log(tmpMaxItem.position.y+"     " + mDisplayRect.position.y);
        if(isDown)
            return tmpMaxItem.position.y > mDisplayRect.position.y ? tmpMaxItem : null;

        float pageBottom = mDisplayRect.position.y - mDisplayRect.sizeDelta.y;
        //Debug.Log("查询最小的位置 : "+pageBottom +"                     "+ mDisplayRect.position.y);

        return tmpMaxItem.position.y < pageBottom ? tmpMaxItem : null;
       
    }

    //查看当前的滑动方向
    private float previousPagePos;
    private bool? isSlideDown;
    // 当isSlideDown为空是表明当前无操作
    public bool? IsSlideDown { get => isSlideDown;}


    /// <summary>
    /// 查看当前滑动的方向 如果页面向上滑动那么Y轴是增加的 反之下滑  如果为空那么就是未参与滑动操作
    /// </summary>
    /// <returns></returns>
    private void CheckCurDirection() {
        if (transform.position.y == previousPagePos) {
            isSlideDown = null;
            return;
        }
        isSlideDown = transform.position.y < previousPagePos;
        previousPagePos = transform.position.y;
    }



    private void Update()
    {
        CheckCurDirection();
        if (isSlideDown == null)
            return;

        bool isTrue = (bool)isSlideDown;
        var changeItem = CheckCrossTheBorder(!isTrue);
        if (changeItem != null)
            ExecuteChangeLocation(changeItem, !isTrue);


        //TODO FIX 这里需要严重优化 暂时没考虑好具体方法
        if (!isTrue) {
            //Debug.Log("内容框底部点 : "+(transform.position.y-(transform as RectTransform).sizeDelta.y));
            //Debug.Log("显示框底部点 : "+(mDisplayRect.position.y-(mDisplayRect as RectTransform).sizeDelta.y));
            var contentPos = (transform.position.y - (transform as RectTransform).sizeDelta.y);
            var displayPos = (mDisplayRect.position.y - (mDisplayRect as RectTransform).sizeDelta.y);

            //TODO FIX 临时测试代码
            if (contentPos - displayPos > 80) {
                if (isLoad) {
                    transform.parent.parent.GetChild(1).GetComponent<loadState>().SetLoadBar(true);
                    StartCoroutine(LoadItem(20));
                    isLoad = false;

                }

            }
        }


    }




    //-----------------------------------------加载模块拓展----------------------------------------
    private bool isLoad =true;


    //是否结束异步等待
    public bool isAsyncWait = true;

    private IEnumerator LoadItem(int count)
    {
        while (isAsyncWait)
        {
            yield return new WaitForSeconds(0.3f);
        }

        //TODO FIX 测试用
        AddItemPosition(count);
        if (allItemCount > 80)
            isLoad = false;

        isAsyncWait = true;
    }

    //TODO FIX 测试用
  
}

internal static class ExetenceMethod_YC
{
    //允许三个单位的误差
    public static bool Comparer_Position_Y(this Transform my,Vector3 other)
    {
        if (my.position.y > other.y - 3 && my.position.y < other.y + 3)
            return true;
        return false;
    }

    public static void TryValueGet<TKey,TValue>(this Dictionary<TKey, TValue> my, TKey key, out TValue value) {
        if (!my.ContainsKey(key)) {
            Debug.LogError("键为空检测触发处 : Key = "+key);
            value = default(TValue);
            return;
        }
        my.TryGetValue(key,out value);
    }

    public static void SortAdd<T>(this IList<T> list,T TObj,bool isDescending = false) {
        if (typeof(T) == typeof(ItemPropoty)) {
            if (list.Count == 0)
            {
                list.Add(TObj);
                return;
            }
            int insertIndex = isDescending ? 0 : list.Count;
            if ((TObj as ItemPropoty).mDRect.itemIndex < (list[0] as ItemPropoty).mDRect.itemIndex)
            {
                list.Insert(list.Count - insertIndex, TObj);
            }
            else {
                list.Insert(insertIndex, TObj);
            }
        }
    }

    //关于处理间距的问题 计算时存再行列的数量误差 去掉最后一排的间距再进行计算 
    /*
          ----------------------------------------问题清单------------------------------------------------
          1,快速拖拽会发生丢失UI现象
          2,计算行列自动布局显示半个UI的优化
          3,拓展加载模块功能
          4,优化下拉判断选项
     */
}

  这里还有一些存在的问题...   现在懒得动 以后改...      有哪位朋友使用的话可以联系我qq : 973407312

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

public class loadState : MonoBehaviour
{
    public Text[] dot;

    public bool isLoading;
    
    public CustomScroll cs;

    private float nxtTime = 0;
    public void SetDot()
    {

    }


    public void SetLoadBar(bool isTrue)
    {
        isLoading = isTrue;
        gameObject.SetActive(true);
        StartCoroutine(Recover());

    }


    public void BarFadeAway()
    {

    }

    private int index=0;
    public void whirl() {
        index++;
        index %= dot.Length;
        StartCoroutine(Bright());
    }


    private IEnumerator Bright() {
        dot[index].gameObject.SetActive(false);
        yield return new WaitForSeconds(0.18f);
        dot[index].gameObject.SetActive(true);

    }

    private IEnumerator Recover()
    {
        yield return new WaitForSeconds(2);
        cs.isAsyncWait = false;
        gameObject.SetActive(false);
        isLoading = false;
    }



    void Update()
    {
        if (isLoading)
        {
            
            nxtTime += Time.deltaTime;
            if (nxtTime >= 0.2f) {
                whirl();
                nxtTime = 0;
            }
        }
    }
}

  可以像这样 制作一个加载提示条  这里弄的有些简陋  可以自己弄个Dotween做个渐入渐出 多给几条提示信息

  demo下载:链接:https://pan.baidu.com/s/1XS6wWAoaYWCA2GbgQUf4CQ 

  提取码:ms5r 

以上是关于制作一个简易的UGUI无限滑动框(Unity)的主要内容,如果未能解决你的问题,请参考以下文章

unity的ugui-8.scroll view无限循环列表

unity 中UGUI制作滚动条视图效果(按钮)

unity3d的UGUI如何制作一个提示框?就是鼠标放上去会显示提示信息,最好用自带的UI系统

Unity中实现嵌套滑动框

unity UGUI的控件与UI组件组合后都有哪些妙用

unity_UGUI养成之路03