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