modify
This commit is contained in:
parent
c9bc460ad7
commit
9d941a179d
@ -6,7 +6,8 @@
|
|||||||
"GUID:5553d74549d54e74cb548b3ab58a8483",
|
"GUID:5553d74549d54e74cb548b3ab58a8483",
|
||||||
"GUID:75b6f2078d190f14dbda4a5b747d709c",
|
"GUID:75b6f2078d190f14dbda4a5b747d709c",
|
||||||
"GUID:a19b414bea3b97240a91aeab9a8eab36",
|
"GUID:a19b414bea3b97240a91aeab9a8eab36",
|
||||||
"GUID:198eb6af143bbc4488e2779d96697e06"
|
"GUID:198eb6af143bbc4488e2779d96697e06",
|
||||||
|
"GUID:80ecb87cae9c44d19824e70ea7229748"
|
||||||
],
|
],
|
||||||
"includePlatforms": [],
|
"includePlatforms": [],
|
||||||
"excludePlatforms": [],
|
"excludePlatforms": [],
|
||||||
|
8
Runtime/RecyclerView.meta
Normal file
8
Runtime/RecyclerView.meta
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 20aaf5daa7c6f1144be40271786f5aca
|
||||||
|
folderAsset: yes
|
||||||
|
DefaultImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
8
Runtime/RecyclerView/Adapter.meta
Normal file
8
Runtime/RecyclerView/Adapter.meta
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: c55a613d7aa2a27448539712f0cf3875
|
||||||
|
folderAsset: yes
|
||||||
|
DefaultImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
198
Runtime/RecyclerView/Adapter/Adapter.cs
Normal file
198
Runtime/RecyclerView/Adapter/Adapter.cs
Normal file
@ -0,0 +1,198 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace AlicizaX.UI.RecyclerView
|
||||||
|
{
|
||||||
|
public class Adapter<T> : IAdapter
|
||||||
|
{
|
||||||
|
protected RecyclerView recyclerView;
|
||||||
|
|
||||||
|
protected List<T> list;
|
||||||
|
protected Action<T> onItemClick;
|
||||||
|
|
||||||
|
protected int choiceIndex = -1;
|
||||||
|
public int ChoiceIndex
|
||||||
|
{
|
||||||
|
get => choiceIndex;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
SetChoiceIndex(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Adapter(RecyclerView recyclerView) : this(recyclerView, new List<T>(), null)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public Adapter(RecyclerView recyclerView, List<T> list) : this(recyclerView, list, null)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public Adapter(RecyclerView recyclerView, List<T> list, Action<T> onItemClick)
|
||||||
|
{
|
||||||
|
this.recyclerView = recyclerView;
|
||||||
|
this.list = list;
|
||||||
|
this.onItemClick = onItemClick;
|
||||||
|
|
||||||
|
this.recyclerView.Adapter = this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual int GetItemCount()
|
||||||
|
{
|
||||||
|
return list == null ? 0 : list.Count;
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual int GetRealCount()
|
||||||
|
{
|
||||||
|
return GetItemCount();
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual string GetViewName(int index)
|
||||||
|
{
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual void OnBindViewHolder(ViewHolder viewHolder, int index)
|
||||||
|
{
|
||||||
|
if (index < 0 || index >= GetItemCount()) return;
|
||||||
|
|
||||||
|
T data = list[index];
|
||||||
|
|
||||||
|
viewHolder.BindViewData(data);
|
||||||
|
viewHolder.BindItemClick(data, t =>
|
||||||
|
{
|
||||||
|
SetChoiceIndex(index);
|
||||||
|
onItemClick?.Invoke(data);
|
||||||
|
});
|
||||||
|
viewHolder.BindChoiceState(index == choiceIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual void NotifyDataChanged()
|
||||||
|
{
|
||||||
|
recyclerView.RequestLayout();
|
||||||
|
recyclerView.Refresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual void SetList(List<T> list)
|
||||||
|
{
|
||||||
|
this.list = list;
|
||||||
|
recyclerView.Reset();
|
||||||
|
NotifyDataChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
public T GetData(int index)
|
||||||
|
{
|
||||||
|
if (index < 0 || index >= GetItemCount()) return default;
|
||||||
|
|
||||||
|
return list[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Add(T item)
|
||||||
|
{
|
||||||
|
list.Add(item);
|
||||||
|
NotifyDataChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AddRange(IEnumerable<T> collection)
|
||||||
|
{
|
||||||
|
list.AddRange(collection);
|
||||||
|
NotifyDataChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Insert(int index, T item)
|
||||||
|
{
|
||||||
|
list.Insert(index, item);
|
||||||
|
NotifyDataChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void InsertRange(int index, IEnumerable<T> collection)
|
||||||
|
{
|
||||||
|
list.InsertRange(index, collection);
|
||||||
|
NotifyDataChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Remove(T item)
|
||||||
|
{
|
||||||
|
int index = list.IndexOf(item);
|
||||||
|
RemoveAt(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RemoveAt(int index)
|
||||||
|
{
|
||||||
|
if (index < 0 || index >= GetItemCount()) return;
|
||||||
|
|
||||||
|
list.RemoveAt(index);
|
||||||
|
NotifyDataChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RemoveRange(int index, int count)
|
||||||
|
{
|
||||||
|
list.RemoveRange(index, count);
|
||||||
|
NotifyDataChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RemoveAll(Predicate<T> match)
|
||||||
|
{
|
||||||
|
list.RemoveAll(match);
|
||||||
|
NotifyDataChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Clear()
|
||||||
|
{
|
||||||
|
list.Clear();
|
||||||
|
NotifyDataChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Reverse(int index, int count)
|
||||||
|
{
|
||||||
|
list.Reverse(index, count);
|
||||||
|
NotifyDataChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Reverse()
|
||||||
|
{
|
||||||
|
list.Reverse();
|
||||||
|
NotifyDataChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Sort(Comparison<T> comparison)
|
||||||
|
{
|
||||||
|
list.Sort(comparison);
|
||||||
|
NotifyDataChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetOnItemClick(Action<T> onItemClick)
|
||||||
|
{
|
||||||
|
this.onItemClick = onItemClick;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void SetChoiceIndex(int index)
|
||||||
|
{
|
||||||
|
if (index == choiceIndex) return;
|
||||||
|
|
||||||
|
if (choiceIndex != -1)
|
||||||
|
{
|
||||||
|
if (TryGetViewHolder(choiceIndex, out var viewHolder))
|
||||||
|
{
|
||||||
|
viewHolder.BindChoiceState(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
choiceIndex = index;
|
||||||
|
|
||||||
|
if (choiceIndex != -1)
|
||||||
|
{
|
||||||
|
if (TryGetViewHolder(choiceIndex, out var viewHolder))
|
||||||
|
{
|
||||||
|
viewHolder.BindChoiceState(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool TryGetViewHolder(int index, out ViewHolder viewHolder)
|
||||||
|
{
|
||||||
|
viewHolder = recyclerView.ViewProvider.GetViewHolder(index);
|
||||||
|
return viewHolder != null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
11
Runtime/RecyclerView/Adapter/Adapter.cs.meta
Normal file
11
Runtime/RecyclerView/Adapter/Adapter.cs.meta
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: f2fe0ab8191b4944db29445f6f796a50
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
128
Runtime/RecyclerView/Adapter/GroupAdapter.cs
Normal file
128
Runtime/RecyclerView/Adapter/GroupAdapter.cs
Normal file
@ -0,0 +1,128 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace AlicizaX.UI.RecyclerView
|
||||||
|
{
|
||||||
|
public class GroupAdapter : Adapter<GroupData>
|
||||||
|
{
|
||||||
|
private readonly List<GroupData> showList = new();
|
||||||
|
private string groupViewName;
|
||||||
|
|
||||||
|
public GroupAdapter(RecyclerView recyclerView, string groupViewName) : base(recyclerView)
|
||||||
|
{
|
||||||
|
this.groupViewName = groupViewName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public GroupAdapter(RecyclerView recyclerView, List<GroupData> list) : base(recyclerView, list)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public GroupAdapter(RecyclerView recyclerView, List<GroupData> list, Action<GroupData> onItemClick) : base(recyclerView, list, onItemClick)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public override int GetItemCount()
|
||||||
|
{
|
||||||
|
return showList.Count;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string GetViewName(int index)
|
||||||
|
{
|
||||||
|
return showList[index].viewName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void OnBindViewHolder(ViewHolder viewHolder, int index)
|
||||||
|
{
|
||||||
|
if (index < 0 || index >= GetItemCount()) return;
|
||||||
|
|
||||||
|
GroupData data = showList[index];
|
||||||
|
|
||||||
|
viewHolder.BindViewData(data);
|
||||||
|
viewHolder.BindItemClick(data, t =>
|
||||||
|
{
|
||||||
|
if (data.viewName == groupViewName)
|
||||||
|
{
|
||||||
|
data.bExpand = !data.bExpand;
|
||||||
|
NotifyDataChanged();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
SetChoiceIndex(index);
|
||||||
|
onItemClick?.Invoke(data);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void NotifyDataChanged()
|
||||||
|
{
|
||||||
|
foreach (var data in list)
|
||||||
|
{
|
||||||
|
CreateGroup(data.type);
|
||||||
|
}
|
||||||
|
|
||||||
|
var groupList = showList.FindAll(data => data.viewName == groupViewName);
|
||||||
|
for (int i = 0; i < groupList.Count; i++)
|
||||||
|
{
|
||||||
|
int index = showList.IndexOf(groupList[i]);
|
||||||
|
Collapse(index);
|
||||||
|
if (groupList[i].bExpand)
|
||||||
|
{
|
||||||
|
Expand(index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var group in groupList)
|
||||||
|
{
|
||||||
|
if (list.FindAll(data => data.type == group.type).Count == 0)
|
||||||
|
{
|
||||||
|
showList.Remove(group);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
base.NotifyDataChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void SetList(List<GroupData> list)
|
||||||
|
{
|
||||||
|
showList.Clear();
|
||||||
|
base.SetList(list);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CreateGroup(int type)
|
||||||
|
{
|
||||||
|
var groupData = showList.Find(data => data.type == type && data.viewName == groupViewName);
|
||||||
|
if (groupData == null)
|
||||||
|
{
|
||||||
|
groupData = new GroupData(type, groupViewName, type.ToString());
|
||||||
|
showList.Add(groupData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Expand(int index)
|
||||||
|
{
|
||||||
|
var expandList = list.FindAll(data => data.type == showList[index].type);
|
||||||
|
showList.InsertRange(index + 1, expandList);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Collapse(int index)
|
||||||
|
{
|
||||||
|
var collapseList = showList.FindAll(data => data.type == showList[index].type && data.viewName != groupViewName);
|
||||||
|
showList.RemoveRange(index + 1, collapseList.Count);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class GroupData
|
||||||
|
{
|
||||||
|
public bool bExpand;
|
||||||
|
public int type;
|
||||||
|
public string viewName;
|
||||||
|
public string name;
|
||||||
|
|
||||||
|
public GroupData(int type, string viewName, string name)
|
||||||
|
{
|
||||||
|
this.type = type;
|
||||||
|
this.viewName = viewName;
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
11
Runtime/RecyclerView/Adapter/GroupAdapter.cs.meta
Normal file
11
Runtime/RecyclerView/Adapter/GroupAdapter.cs.meta
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 1afc560670c303d42b28cc143ed25a8f
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
15
Runtime/RecyclerView/Adapter/IAdapter.cs
Normal file
15
Runtime/RecyclerView/Adapter/IAdapter.cs
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
namespace AlicizaX.UI.RecyclerView
|
||||||
|
{
|
||||||
|
public interface IAdapter
|
||||||
|
{
|
||||||
|
int GetItemCount();
|
||||||
|
|
||||||
|
int GetRealCount();
|
||||||
|
|
||||||
|
string GetViewName(int index);
|
||||||
|
|
||||||
|
void OnBindViewHolder(ViewHolder viewHolder, int index);
|
||||||
|
|
||||||
|
void NotifyDataChanged();
|
||||||
|
}
|
||||||
|
}
|
11
Runtime/RecyclerView/Adapter/IAdapter.cs.meta
Normal file
11
Runtime/RecyclerView/Adapter/IAdapter.cs.meta
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: fc979439548585f4097747c34ed12270
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
36
Runtime/RecyclerView/Adapter/LoopAdapter.cs
Normal file
36
Runtime/RecyclerView/Adapter/LoopAdapter.cs
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace AlicizaX.UI.RecyclerView
|
||||||
|
{
|
||||||
|
public class LoopAdapter<T> : Adapter<T>
|
||||||
|
{
|
||||||
|
public LoopAdapter(RecyclerView recyclerView) : base(recyclerView)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public LoopAdapter(RecyclerView recyclerView, List<T> list) : base(recyclerView, list)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public LoopAdapter(RecyclerView recyclerView, List<T> list, Action<T> onItemClick) : base(recyclerView, list, onItemClick)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public override int GetItemCount()
|
||||||
|
{
|
||||||
|
return int.MaxValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override int GetRealCount()
|
||||||
|
{
|
||||||
|
return list == null ? 0 : list.Count;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void OnBindViewHolder(ViewHolder viewHolder, int index)
|
||||||
|
{
|
||||||
|
index %= list.Count;
|
||||||
|
base.OnBindViewHolder(viewHolder, index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
11
Runtime/RecyclerView/Adapter/LoopAdapter.cs.meta
Normal file
11
Runtime/RecyclerView/Adapter/LoopAdapter.cs.meta
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 852ebd0fa448a374aad55404886bfaae
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
43
Runtime/RecyclerView/Adapter/MixedAdapter.cs
Normal file
43
Runtime/RecyclerView/Adapter/MixedAdapter.cs
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace AlicizaX.UI.RecyclerView
|
||||||
|
{
|
||||||
|
public class MixedAdapter : Adapter<MixedData>
|
||||||
|
{
|
||||||
|
public MixedAdapter(RecyclerView recyclerView) : base(recyclerView)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public MixedAdapter(RecyclerView recyclerView, List<MixedData> list) : base(recyclerView, list)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public MixedAdapter(RecyclerView recyclerView, List<MixedData> list, Action<MixedData> onItemClick) : base(recyclerView, list, onItemClick)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string GetViewName(int index)
|
||||||
|
{
|
||||||
|
return list[index].viewName;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class MixedData
|
||||||
|
{
|
||||||
|
public string viewName;
|
||||||
|
public string name;
|
||||||
|
public string icon;
|
||||||
|
public int number;
|
||||||
|
public int percent;
|
||||||
|
|
||||||
|
public MixedData(string viewName, string name, string icon, int number, int percent)
|
||||||
|
{
|
||||||
|
this.viewName = viewName;
|
||||||
|
this.name = name;
|
||||||
|
this.icon = icon;
|
||||||
|
this.number = number;
|
||||||
|
this.percent = percent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
11
Runtime/RecyclerView/Adapter/MixedAdapter.cs.meta
Normal file
11
Runtime/RecyclerView/Adapter/MixedAdapter.cs.meta
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 5c335290acc1c8b45aa87e6fc2b00b0c
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
192
Runtime/RecyclerView/EaseUtil.cs
Normal file
192
Runtime/RecyclerView/EaseUtil.cs
Normal file
@ -0,0 +1,192 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
public class EaseUtil
|
||||||
|
{
|
||||||
|
public static double EaseInSine(float x)
|
||||||
|
{
|
||||||
|
return 1 - Math.Cos(x * Math.PI / 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static double EaseOutSine(float x)
|
||||||
|
{
|
||||||
|
return Math.Sin(x * Math.PI / 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static double EaseInOutSine(float x)
|
||||||
|
{
|
||||||
|
return -(Math.Cos(Math.PI * x) - 1) / 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static double EaseInQuad(float x)
|
||||||
|
{
|
||||||
|
return x * x;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static double EaseOutQuad(float x)
|
||||||
|
{
|
||||||
|
return 1 - (1 - x) * (1 - x);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static double EaseInOutQuad(float x)
|
||||||
|
{
|
||||||
|
return x < 0.5 ? 2 * x * x : 1 - Math.Pow(-2 * x + 2, 2) / 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static double EaseInQuart(float x)
|
||||||
|
{
|
||||||
|
return Math.Pow(x, 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static double EaseOutQuart(float x)
|
||||||
|
{
|
||||||
|
return 1 - Math.Pow(1 - x, 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static double EaseInOutQuart(float x)
|
||||||
|
{
|
||||||
|
return x < 0.5 ? 8 * x * x * x * x : 1 - Math.Pow(-2 * x + 2, 4) / 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static double EaseInCubic(float x)
|
||||||
|
{
|
||||||
|
return Math.Pow(x, 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static double EaseOutCubic(float x)
|
||||||
|
{
|
||||||
|
return 1 - Math.Pow(1 - x, 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static double EaseInOutCubic(float x)
|
||||||
|
{
|
||||||
|
return x < 0.5f ? 4 * x * x * x : 1 - Math.Pow(-2 * x + 2, 3) / 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static double EaseInQuint(float x)
|
||||||
|
{
|
||||||
|
return Math.Pow(x, 5);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static double EaseOutQuint(float x)
|
||||||
|
{
|
||||||
|
return 1 - Math.Pow(1 - x, 5);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static double EaseInOutQuint(float x)
|
||||||
|
{
|
||||||
|
return x < 0.5f ? 16 * Math.Pow(x, 5) : 1 - Math.Pow(-2 * x + 2, 5) / 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static double EaseInExpo(float x)
|
||||||
|
{
|
||||||
|
return x == 0 ? 0 : Math.Pow(2, 10 * x - 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static double EaseOutExpo(float x)
|
||||||
|
{
|
||||||
|
return x == 1 ? 1 : 1 - Math.Pow(2, -10 * x);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static double EaseInOutExpo(float x)
|
||||||
|
{
|
||||||
|
return x == 0 ? 0 :
|
||||||
|
x == 1 ? 1 :
|
||||||
|
x < 0.5 ? Math.Pow(2, 20 * x - 10) / 2 :
|
||||||
|
(2 - Math.Pow(2, -20 * x + 10)) / 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static double EaseInCirc(float x)
|
||||||
|
{
|
||||||
|
return 1 - Math.Sqrt(1 - Math.Pow(x, 2));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static double EaseOutCirc(float x)
|
||||||
|
{
|
||||||
|
return Math.Sqrt(1 - Math.Pow(x - 1, 2));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static double EaseInOutCirc(float x)
|
||||||
|
{
|
||||||
|
return x < 0.5 ? (1 - Math.Sqrt(1 - Math.Pow(2 * x, 2))) / 2 : (Math.Sqrt(1 - Math.Pow(-2 * x + 2, 2)) + 1) / 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static double EaseInBack(float x)
|
||||||
|
{
|
||||||
|
double c1 = 1.70158;
|
||||||
|
double c3 = c1 + 1;
|
||||||
|
|
||||||
|
return c3 * x * x * x - c1 * x * x;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static double EaseOutBack(float x)
|
||||||
|
{
|
||||||
|
double c1 = 1.70158;
|
||||||
|
double c3 = c1 + 1;
|
||||||
|
|
||||||
|
return 1 + c3 * Math.Pow(x - 1, 3) + c1 * Math.Pow(x - 1, 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static double EaseInOutBack(float x)
|
||||||
|
{
|
||||||
|
double c1 = 1.70158;
|
||||||
|
double c2 = c1 * 1.525;
|
||||||
|
|
||||||
|
return x < 0.5 ?
|
||||||
|
Math.Pow(2 * x, 2) * ((c2 + 1) * 2 * x - c2) / 2 :
|
||||||
|
(Math.Pow(2 * x - 2, 2) * ((c2 + 1) * (x * 2 - 2) + c2) + 2) / 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static double EaseInElastic(float x)
|
||||||
|
{
|
||||||
|
double c4 = 2 * Math.PI / 3;
|
||||||
|
return x == 0 ? 0 : x == 1 ? 1 : -Math.Pow(2, 10 * x - 10) * Math.Sin((x * 10 - 10.75) * c4);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static double EaseOutElastic(float x)
|
||||||
|
{
|
||||||
|
double c4 = 2 * Math.PI / 3;
|
||||||
|
return x == 0 ? 0 : x == 1 ? 1 : Math.Pow(2, -10 * x) * Math.Sin((x * 10 - 0.75) * c4) + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static double EaseInOutElastic(float x)
|
||||||
|
{
|
||||||
|
double c5 = 2 * Math.PI / 4.5;
|
||||||
|
return x == 0 ? 0 :
|
||||||
|
x == 1 ? 1 :
|
||||||
|
x < 0.5 ? -(Math.Pow(2, 20 * x - 10) * Math.Sin((20 * x - 11.125) * c5)) / 2 :
|
||||||
|
Math.Pow(2, -20 * x + 10) * Math.Sin((20 * x - 11.125) * c5) / 2 + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static double EaseInBounce(float x)
|
||||||
|
{
|
||||||
|
return 1 - EaseOutBounce(1 - x);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static double EaseOutBounce(float x)
|
||||||
|
{
|
||||||
|
double n1 = 7.5625;
|
||||||
|
double d1 = 2.75;
|
||||||
|
|
||||||
|
if (x < 1 / d1)
|
||||||
|
{
|
||||||
|
return n1 * x * x;
|
||||||
|
}
|
||||||
|
else if (x < 2 / d1)
|
||||||
|
{
|
||||||
|
return n1 * (x -= (float)(1.5 / d1)) * x + 0.75;
|
||||||
|
}
|
||||||
|
else if (x < 2.5 / d1)
|
||||||
|
{
|
||||||
|
return n1 * (x -= (float)(2.25 / d1)) * x + 0.9375;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return n1 * (x -= (float)(2.625 / d1)) * x + 0.984375;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static double EaseInOutBounce(float x)
|
||||||
|
{
|
||||||
|
return x < 0.5 ? (1 - EaseOutBounce(1 - 2 * x)) / 2 : (1 + EaseOutBounce(2 * x - 1)) / 2;
|
||||||
|
}
|
||||||
|
}
|
3
Runtime/RecyclerView/EaseUtil.cs.meta
Normal file
3
Runtime/RecyclerView/EaseUtil.cs.meta
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: d42e2db77425447490cb9f68003e818b
|
||||||
|
timeCreated: 1741771999
|
8
Runtime/RecyclerView/Layout.meta
Normal file
8
Runtime/RecyclerView/Layout.meta
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: c2c36f0a0862000438099a52b9b5e69c
|
||||||
|
folderAsset: yes
|
||||||
|
DefaultImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
121
Runtime/RecyclerView/Layout/CircleLayoutManager.cs
Normal file
121
Runtime/RecyclerView/Layout/CircleLayoutManager.cs
Normal file
@ -0,0 +1,121 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace AlicizaX.UI.RecyclerView
|
||||||
|
{
|
||||||
|
public class CircleLayoutManager : LayoutManager
|
||||||
|
{
|
||||||
|
private float radius;
|
||||||
|
private new CircleDirection direction;
|
||||||
|
private float intervalAngle;
|
||||||
|
private float initalAngle;
|
||||||
|
|
||||||
|
public override RecyclerView RecyclerView
|
||||||
|
{
|
||||||
|
get => recyclerView;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
recyclerView = value;
|
||||||
|
recyclerView.SetScroller(recyclerView.gameObject.AddComponent<CircleScroller>());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public CircleLayoutManager(CircleDirection direction = CircleDirection.Positive, float initalAngle = 0)
|
||||||
|
{
|
||||||
|
this.direction = direction;
|
||||||
|
this.initalAngle = initalAngle;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override Vector2 CalculateContentSize()
|
||||||
|
{
|
||||||
|
Vector2 size = viewProvider.CalculateViewSize(0);
|
||||||
|
radius = (Mathf.Min(viewportSize.x, viewportSize.y) - Mathf.Min(size.x, size.y)) / 2f - Mathf.Max(padding.x, padding.y);
|
||||||
|
intervalAngle = adapter.GetItemCount() > 0 ? 360f / adapter.GetItemCount() : 0;
|
||||||
|
|
||||||
|
return viewportSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override Vector2 CalculateContentOffset()
|
||||||
|
{
|
||||||
|
return Vector2.zero;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override Vector2 CalculateViewportOffset()
|
||||||
|
{
|
||||||
|
return Vector2.zero;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Layout(ViewHolder viewHolder, int index)
|
||||||
|
{
|
||||||
|
viewHolder.RectTransform.anchoredPosition3D = CalculatePosition(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override Vector2 CalculatePosition(int index)
|
||||||
|
{
|
||||||
|
float angle = index * intervalAngle;
|
||||||
|
angle = direction == CircleDirection.Positive ? angle : -angle;
|
||||||
|
angle += initalAngle + ScrollPosition;
|
||||||
|
float radian = angle * (Mathf.PI / 180f);
|
||||||
|
float x = radius * Mathf.Sin(radian);
|
||||||
|
float y = radius * Mathf.Cos(radian);
|
||||||
|
|
||||||
|
return new Vector2(x, y);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override int GetStartIndex()
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override int GetEndIndex()
|
||||||
|
{
|
||||||
|
return adapter.GetItemCount() - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool IsFullVisibleStart(int index) => false;
|
||||||
|
|
||||||
|
public override bool IsFullInvisibleStart(int index) => false;
|
||||||
|
|
||||||
|
public override bool IsFullVisibleEnd(int index) => false;
|
||||||
|
|
||||||
|
public override bool IsFullInvisibleEnd(int index) => false;
|
||||||
|
|
||||||
|
public override bool IsVisible(int index) => true;
|
||||||
|
|
||||||
|
public override float IndexToPosition(int index)
|
||||||
|
{
|
||||||
|
float position = index * intervalAngle;
|
||||||
|
|
||||||
|
return -position;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override int PositionToIndex(float position)
|
||||||
|
{
|
||||||
|
int index = Mathf.RoundToInt(position / intervalAngle);
|
||||||
|
return -index;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void DoItemAnimation()
|
||||||
|
{
|
||||||
|
List<ViewHolder> viewHolders = viewProvider.ViewHolders;
|
||||||
|
for (int i = 0; i < viewHolders.Count; i++)
|
||||||
|
{
|
||||||
|
float angle = i * intervalAngle + initalAngle;
|
||||||
|
angle = direction == CircleDirection.Positive ? angle + ScrollPosition : angle - ScrollPosition;
|
||||||
|
float delta = (angle - initalAngle) % 360;
|
||||||
|
delta = delta < 0 ? delta + 360 : delta;
|
||||||
|
delta = delta > 180 ? 360 - delta : delta;
|
||||||
|
float scale = delta < intervalAngle ? (1.4f - delta / intervalAngle) : 1;
|
||||||
|
scale = Mathf.Max(scale, 1);
|
||||||
|
|
||||||
|
viewHolders[i].RectTransform.localScale = Vector3.one * scale;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum CircleDirection
|
||||||
|
{
|
||||||
|
Positive,
|
||||||
|
Negative
|
||||||
|
}
|
||||||
|
}
|
11
Runtime/RecyclerView/Layout/CircleLayoutManager.cs.meta
Normal file
11
Runtime/RecyclerView/Layout/CircleLayoutManager.cs.meta
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 158658be786595e46a209d2b2162b4e2
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
124
Runtime/RecyclerView/Layout/GridLayoutManager.cs
Normal file
124
Runtime/RecyclerView/Layout/GridLayoutManager.cs
Normal file
@ -0,0 +1,124 @@
|
|||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace AlicizaX.UI.RecyclerView
|
||||||
|
{
|
||||||
|
public class GridLayoutManager : LayoutManager
|
||||||
|
{
|
||||||
|
private Vector2 cellSize;
|
||||||
|
|
||||||
|
public GridLayoutManager(int unit = 1)
|
||||||
|
{
|
||||||
|
this.unit = unit;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override Vector2 CalculateContentSize()
|
||||||
|
{
|
||||||
|
cellSize = viewProvider.CalculateViewSize(0);
|
||||||
|
|
||||||
|
int row = Mathf.CeilToInt(adapter.GetItemCount() / (float)unit);
|
||||||
|
float len;
|
||||||
|
if (direction == Direction.Vertical)
|
||||||
|
{
|
||||||
|
len = row * (cellSize.y + spacing.y) - spacing.y;
|
||||||
|
return new Vector2(contentSize.x, len + padding.y * 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
len = row * (cellSize.x + spacing.x) - spacing.x;
|
||||||
|
return new Vector2(len, contentSize.y + padding.x * 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override Vector2 CalculatePosition(int index)
|
||||||
|
{
|
||||||
|
int row = index / unit;
|
||||||
|
int column = index % unit;
|
||||||
|
float x, y;
|
||||||
|
if (direction == Direction.Vertical)
|
||||||
|
{
|
||||||
|
x = column * (cellSize.x + spacing.x);
|
||||||
|
y = row * (cellSize.y + spacing.y) - ScrollPosition;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
x = row * (cellSize.x + spacing.x) - ScrollPosition;
|
||||||
|
y = column * (cellSize.y + spacing.y);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Vector2(x + padding.x, y + padding.y);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override Vector2 CalculateContentOffset()
|
||||||
|
{
|
||||||
|
float width, height;
|
||||||
|
if (alignment == Alignment.Center)
|
||||||
|
{
|
||||||
|
width = Mathf.Min(contentSize.x, viewportSize.x);
|
||||||
|
height = Mathf.Min(contentSize.y, viewportSize.y);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
width = viewportSize.x;
|
||||||
|
height = viewportSize.y;
|
||||||
|
}
|
||||||
|
return new Vector2((width - cellSize.x) / 2, (height - cellSize.y) / 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override Vector2 CalculateViewportOffset()
|
||||||
|
{
|
||||||
|
float width, height;
|
||||||
|
if (alignment == Alignment.Center)
|
||||||
|
{
|
||||||
|
width = Mathf.Min(contentSize.x, viewportSize.x);
|
||||||
|
height = Mathf.Min(contentSize.y, viewportSize.y);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
width = viewportSize.x;
|
||||||
|
height = viewportSize.y;
|
||||||
|
}
|
||||||
|
return new Vector2((width - cellSize.x) / 2, (height - cellSize.y) / 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override int GetStartIndex()
|
||||||
|
{
|
||||||
|
float len = direction == Direction.Vertical ? cellSize.y + spacing.y : cellSize.x + spacing.x;
|
||||||
|
int index = Mathf.FloorToInt(ScrollPosition / len) * unit;
|
||||||
|
return Mathf.Max(0, index);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override int GetEndIndex()
|
||||||
|
{
|
||||||
|
float viewLength = direction == Direction.Vertical ? viewportSize.y : viewportSize.x;
|
||||||
|
float len = direction == Direction.Vertical ? cellSize.y + spacing.y : cellSize.x + spacing.x;
|
||||||
|
int index = Mathf.FloorToInt((ScrollPosition + viewLength) / len) * unit;
|
||||||
|
return Mathf.Min(index, adapter.GetItemCount() - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override float IndexToPosition(int index)
|
||||||
|
{
|
||||||
|
int row = index / unit;
|
||||||
|
float len, viewLength, position;
|
||||||
|
if (direction == Direction.Vertical)
|
||||||
|
{
|
||||||
|
len = row * (cellSize.y + spacing.y);
|
||||||
|
viewLength = viewportSize.y;
|
||||||
|
position = len + viewLength > contentSize.y ? contentSize.y - viewportSize.y : len;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
len = row * (cellSize.x + spacing.x);
|
||||||
|
viewLength = viewportSize.x;
|
||||||
|
position = len + viewLength > contentSize.x ? contentSize.x - viewportSize.x : len;
|
||||||
|
}
|
||||||
|
|
||||||
|
return position;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override int PositionToIndex(float position)
|
||||||
|
{
|
||||||
|
float len = direction == Direction.Vertical ? cellSize.y + spacing.y : cellSize.x + spacing.x;
|
||||||
|
int index = Mathf.RoundToInt(position / len);
|
||||||
|
|
||||||
|
return index * unit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
11
Runtime/RecyclerView/Layout/GridLayoutManager.cs.meta
Normal file
11
Runtime/RecyclerView/Layout/GridLayoutManager.cs.meta
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 7e4f9a076f276cd4a8db0055fd37e220
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
115
Runtime/RecyclerView/Layout/ILayoutManager.cs
Normal file
115
Runtime/RecyclerView/Layout/ILayoutManager.cs
Normal file
@ -0,0 +1,115 @@
|
|||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace AlicizaX.UI.RecyclerView
|
||||||
|
{
|
||||||
|
public interface ILayoutManager
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 滚动时,刷新整个页面的布局
|
||||||
|
/// </summary>
|
||||||
|
void UpdateLayout();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 为 ViewHolder 设置布局
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="viewHolder"></param>
|
||||||
|
/// <param name="index"></param>
|
||||||
|
void Layout(ViewHolder viewHolder, int index);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 设置 Content 大小
|
||||||
|
/// </summary>
|
||||||
|
void SetContentSize();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 计算 Content 的大小
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
Vector2 CalculateContentSize();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 计算第 index 个 ViewHolder 到顶部的距离
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="index"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
Vector2 CalculatePosition(int index);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 计算 ViewHolder 相对于内容长度的偏移
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
Vector2 CalculateContentOffset();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 计算 ViewHolder 相对于视口的偏移
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
Vector2 CalculateViewportOffset();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取当前显示的第一个 ViewHolder 下标
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
int GetStartIndex();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取当前显示的最后一个 ViewHolder 下标
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
int GetEndIndex();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 数据下标转换成在布局中对应的位置
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="index"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
float IndexToPosition(int index);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 在布局中的位置转换成数据下标
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="position"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
int PositionToIndex(float position);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 滚动时,item 对应的动画
|
||||||
|
/// </summary>
|
||||||
|
void DoItemAnimation();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 判断第一个 ViewHolder 是否完全可见
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="index">数据的真实下标</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
bool IsFullVisibleStart(int index);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 判断第一个 ViewHolder 是否完全不可见
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="index">数据的真实下标</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
bool IsFullInvisibleStart(int index);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 判定最后一个 ViewHolder 是否完全可见
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="index"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
bool IsFullVisibleEnd(int index);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 判定最后一个 ViewHolder 是否完全不可见
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="index"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
bool IsFullInvisibleEnd(int index);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 判定第 index ViewHolder是否可见
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="index"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
bool IsVisible(int index);
|
||||||
|
}
|
||||||
|
}
|
11
Runtime/RecyclerView/Layout/ILayoutManager.cs.meta
Normal file
11
Runtime/RecyclerView/Layout/ILayoutManager.cs.meta
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 09e4099cf54916a41b7da73277ab988a
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
230
Runtime/RecyclerView/Layout/LayoutManager.cs
Normal file
230
Runtime/RecyclerView/Layout/LayoutManager.cs
Normal file
@ -0,0 +1,230 @@
|
|||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace AlicizaX.UI.RecyclerView
|
||||||
|
{
|
||||||
|
public abstract class LayoutManager : ILayoutManager
|
||||||
|
{
|
||||||
|
protected Vector2 viewportSize;
|
||||||
|
public Vector2 ViewportSize
|
||||||
|
{
|
||||||
|
get => viewportSize;
|
||||||
|
private set => viewportSize = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Vector2 contentSize;
|
||||||
|
public Vector2 ContentSize
|
||||||
|
{
|
||||||
|
get => contentSize;
|
||||||
|
private set => contentSize = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Vector2 contentOffset;
|
||||||
|
public Vector2 ContentOffset
|
||||||
|
{
|
||||||
|
get => contentOffset;
|
||||||
|
private set => contentOffset = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Vector2 viewportOffset;
|
||||||
|
public Vector2 ViewportOffset
|
||||||
|
{
|
||||||
|
get => viewportOffset;
|
||||||
|
private set => viewportOffset = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected IAdapter adapter;
|
||||||
|
public IAdapter Adapter
|
||||||
|
{
|
||||||
|
get => adapter;
|
||||||
|
set => adapter = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected ViewProvider viewProvider;
|
||||||
|
public ViewProvider ViewProvider
|
||||||
|
{
|
||||||
|
get => viewProvider;
|
||||||
|
set => viewProvider = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected RecyclerView recyclerView;
|
||||||
|
public virtual RecyclerView RecyclerView
|
||||||
|
{
|
||||||
|
get => recyclerView;
|
||||||
|
set => recyclerView = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Direction direction;
|
||||||
|
public Direction Direction
|
||||||
|
{
|
||||||
|
get => direction;
|
||||||
|
set => direction = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Alignment alignment;
|
||||||
|
public Alignment Alignment
|
||||||
|
{
|
||||||
|
get => alignment;
|
||||||
|
set => alignment = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Vector2 spacing;
|
||||||
|
public Vector2 Spacing
|
||||||
|
{
|
||||||
|
get => spacing;
|
||||||
|
set => spacing = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Vector2 padding;
|
||||||
|
public Vector2 Padding
|
||||||
|
{
|
||||||
|
get => padding;
|
||||||
|
set => padding = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected int unit = 1;
|
||||||
|
public int Unit
|
||||||
|
{
|
||||||
|
get => unit;
|
||||||
|
set => unit = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected bool canScroll;
|
||||||
|
public bool CanScroll
|
||||||
|
{
|
||||||
|
get => canScroll;
|
||||||
|
set => canScroll = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public float ScrollPosition => recyclerView.GetScrollPosition();
|
||||||
|
|
||||||
|
public LayoutManager() { }
|
||||||
|
|
||||||
|
public void SetContentSize()
|
||||||
|
{
|
||||||
|
viewportSize = recyclerView.GetComponent<RectTransform>().rect.size;
|
||||||
|
contentSize = CalculateContentSize();
|
||||||
|
contentOffset = CalculateContentOffset();
|
||||||
|
viewportOffset = CalculateViewportOffset();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void UpdateLayout()
|
||||||
|
{
|
||||||
|
foreach (var viewHolder in viewProvider.ViewHolders)
|
||||||
|
{
|
||||||
|
Layout(viewHolder, viewHolder.Index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual void Layout(ViewHolder viewHolder, int index)
|
||||||
|
{
|
||||||
|
Vector2 pos = CalculatePosition(index);
|
||||||
|
Vector3 position = direction == Direction.Vertical ?
|
||||||
|
new Vector3(pos.x - contentOffset.x, -pos.y + contentOffset.y, 0) :
|
||||||
|
new Vector3(pos.x - contentOffset.x, -pos.y + contentOffset.y, 0);
|
||||||
|
viewHolder.RectTransform.anchoredPosition3D = position;
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract Vector2 CalculateContentSize();
|
||||||
|
|
||||||
|
public abstract Vector2 CalculatePosition(int index);
|
||||||
|
|
||||||
|
public abstract Vector2 CalculateContentOffset();
|
||||||
|
|
||||||
|
public abstract Vector2 CalculateViewportOffset();
|
||||||
|
|
||||||
|
public abstract int GetStartIndex();
|
||||||
|
|
||||||
|
public abstract int GetEndIndex();
|
||||||
|
|
||||||
|
public abstract float IndexToPosition(int index);
|
||||||
|
|
||||||
|
public abstract int PositionToIndex(float position);
|
||||||
|
|
||||||
|
public virtual void DoItemAnimation() { }
|
||||||
|
|
||||||
|
public virtual bool IsFullVisibleStart(int index)
|
||||||
|
{
|
||||||
|
Vector2 vector2 = CalculatePosition(index);
|
||||||
|
float position = direction == Direction.Vertical ? vector2.y : vector2.x;
|
||||||
|
return position + GetOffset() >= 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual bool IsFullInvisibleStart(int index)
|
||||||
|
{
|
||||||
|
Vector2 vector2 = CalculatePosition(index + unit);
|
||||||
|
float position = direction == Direction.Vertical ? vector2.y : vector2.x;
|
||||||
|
return position + GetOffset() < 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual bool IsFullVisibleEnd(int index)
|
||||||
|
{
|
||||||
|
Vector2 vector2 = CalculatePosition(index + unit);
|
||||||
|
float position = direction == Direction.Vertical ? vector2.y : vector2.x;
|
||||||
|
float viewLength = direction == Direction.Vertical ? viewportSize.y : viewportSize.x;
|
||||||
|
return position + GetOffset() <= viewLength;
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual bool IsFullInvisibleEnd(int index)
|
||||||
|
{
|
||||||
|
Vector2 vector2 = CalculatePosition(index);
|
||||||
|
float position = direction == Direction.Vertical ? vector2.y : vector2.x;
|
||||||
|
float viewLength = direction == Direction.Vertical ? viewportSize.y : viewportSize.x;
|
||||||
|
return position + GetOffset() > viewLength;
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual bool IsVisible(int index)
|
||||||
|
{
|
||||||
|
float position, viewLength;
|
||||||
|
viewLength = direction == Direction.Vertical ? viewportSize.y : viewportSize.x;
|
||||||
|
|
||||||
|
Vector2 vector2 = CalculatePosition(index);
|
||||||
|
position = direction == Direction.Vertical ? vector2.y : vector2.x;
|
||||||
|
if (position + GetOffset() > 0 && position + GetOffset() <= viewLength)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
vector2 = CalculatePosition(index + unit);
|
||||||
|
position = direction == Direction.Vertical ? vector2.y : vector2.x;
|
||||||
|
if (position + GetOffset() > 0 && position + GetOffset() <= viewLength)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual float GetFitContentSize()
|
||||||
|
{
|
||||||
|
float len;
|
||||||
|
if (direction == Direction.Vertical)
|
||||||
|
{
|
||||||
|
len = alignment == Alignment.Center ? Mathf.Min(contentSize.y, viewportSize.y) : viewportSize.y;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
len = alignment == Alignment.Center ? Mathf.Min(contentSize.x, viewportSize.x) : viewportSize.x;
|
||||||
|
}
|
||||||
|
return len;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual float GetOffset()
|
||||||
|
{
|
||||||
|
return direction == Direction.Vertical ? -contentOffset.y + viewportOffset.y : -contentOffset.x + viewportOffset.x;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum Direction
|
||||||
|
{
|
||||||
|
Vertical = 1,
|
||||||
|
Horizontal = 2,
|
||||||
|
Custom = 10
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum Alignment
|
||||||
|
{
|
||||||
|
Left,
|
||||||
|
Center,
|
||||||
|
Top
|
||||||
|
}
|
||||||
|
}
|
11
Runtime/RecyclerView/Layout/LayoutManager.cs.meta
Normal file
11
Runtime/RecyclerView/Layout/LayoutManager.cs.meta
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 959827c7e00900b47844bd9482d829ef
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
102
Runtime/RecyclerView/Layout/LinearLayoutManager.cs
Normal file
102
Runtime/RecyclerView/Layout/LinearLayoutManager.cs
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace AlicizaX.UI.RecyclerView
|
||||||
|
{
|
||||||
|
public class LinearLayoutManager : LayoutManager
|
||||||
|
{
|
||||||
|
protected float lineHeight;
|
||||||
|
|
||||||
|
public LinearLayoutManager() { }
|
||||||
|
|
||||||
|
public override Vector2 CalculateContentSize()
|
||||||
|
{
|
||||||
|
Vector2 size = viewProvider.CalculateViewSize(0);
|
||||||
|
lineHeight = direction == Direction.Vertical ? size.y : size.x;
|
||||||
|
|
||||||
|
int index = adapter.GetItemCount();
|
||||||
|
float position;
|
||||||
|
if (direction == Direction.Vertical)
|
||||||
|
{
|
||||||
|
position = index * (lineHeight + spacing.y) - spacing.y;
|
||||||
|
return new Vector2(contentSize.x, position + padding.y * 2);
|
||||||
|
}
|
||||||
|
position = index * (lineHeight + spacing.x) - spacing.x;
|
||||||
|
return new Vector2(position + padding.x * 2, contentSize.y);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override Vector2 CalculatePosition(int index)
|
||||||
|
{
|
||||||
|
float position;
|
||||||
|
if (direction == Direction.Vertical)
|
||||||
|
{
|
||||||
|
position = index * (lineHeight + spacing.y) - ScrollPosition;
|
||||||
|
return new Vector2(0, position + padding.y);
|
||||||
|
}
|
||||||
|
position = index * (lineHeight + spacing.x) - ScrollPosition;
|
||||||
|
return new Vector2(position + padding.x, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override Vector2 CalculateContentOffset()
|
||||||
|
{
|
||||||
|
float len = GetFitContentSize();
|
||||||
|
if (direction == Direction.Vertical)
|
||||||
|
{
|
||||||
|
return new Vector2(0, (len - lineHeight) / 2);
|
||||||
|
}
|
||||||
|
return new Vector2((len - lineHeight) / 2, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override Vector2 CalculateViewportOffset()
|
||||||
|
{
|
||||||
|
if (direction == Direction.Vertical)
|
||||||
|
{
|
||||||
|
return new Vector2(0, (viewportSize.y - lineHeight) / 2);
|
||||||
|
}
|
||||||
|
return new Vector2((viewportSize.x - lineHeight) / 2, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override int GetStartIndex()
|
||||||
|
{
|
||||||
|
float len = direction == Direction.Vertical ? lineHeight + spacing.y : lineHeight + spacing.x;
|
||||||
|
int index = Mathf.FloorToInt(ScrollPosition / len);
|
||||||
|
return Mathf.Max(0, index);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override int GetEndIndex()
|
||||||
|
{
|
||||||
|
float viewLength = direction == Direction.Vertical ? viewportSize.y : viewportSize.x;
|
||||||
|
float len = direction == Direction.Vertical ? lineHeight + spacing.y : lineHeight + spacing.x;
|
||||||
|
int index = Mathf.FloorToInt((ScrollPosition + viewLength) / len);
|
||||||
|
return Mathf.Min(index, adapter.GetItemCount() - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override float IndexToPosition(int index)
|
||||||
|
{
|
||||||
|
if (index < 0 || index >= adapter.GetItemCount()) return 0;
|
||||||
|
|
||||||
|
float len, viewLength, position;
|
||||||
|
if (direction == Direction.Vertical)
|
||||||
|
{
|
||||||
|
len = index * (lineHeight + spacing.y);
|
||||||
|
viewLength = viewportSize.y;
|
||||||
|
position = len + viewLength > contentSize.y ? contentSize.y - viewportSize.y : len;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
len = index * (lineHeight + spacing.x);
|
||||||
|
viewLength = viewportSize.x;
|
||||||
|
position = len + viewLength > contentSize.x ? contentSize.x - viewportSize.x : len;
|
||||||
|
}
|
||||||
|
|
||||||
|
return position;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override int PositionToIndex(float position)
|
||||||
|
{
|
||||||
|
float len = direction == Direction.Vertical ? lineHeight + spacing.y : lineHeight + spacing.x;
|
||||||
|
int index = Mathf.RoundToInt(position / len);
|
||||||
|
|
||||||
|
return index;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
11
Runtime/RecyclerView/Layout/LinearLayoutManager.cs.meta
Normal file
11
Runtime/RecyclerView/Layout/LinearLayoutManager.cs.meta
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 36d9270c4a025694397715d3c16cde7e
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
136
Runtime/RecyclerView/Layout/MixedLayoutManager.cs
Normal file
136
Runtime/RecyclerView/Layout/MixedLayoutManager.cs
Normal file
@ -0,0 +1,136 @@
|
|||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace AlicizaX.UI.RecyclerView
|
||||||
|
{
|
||||||
|
public class MixedLayoutManager : LayoutManager
|
||||||
|
{
|
||||||
|
public MixedLayoutManager() { }
|
||||||
|
|
||||||
|
public override Vector2 CalculateContentSize()
|
||||||
|
{
|
||||||
|
int index = adapter.GetItemCount();
|
||||||
|
float position = 0;
|
||||||
|
for (int i = 0; i < index; i++)
|
||||||
|
{
|
||||||
|
position += GetLength(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
return direction == Direction.Vertical ?
|
||||||
|
new Vector2(contentSize.x, position - spacing.y + padding.y * 2) :
|
||||||
|
new Vector2(position - spacing.x + padding.x * 2, contentSize.y);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override Vector2 CalculatePosition(int index)
|
||||||
|
{
|
||||||
|
// TODO 优化点,将 position 定义成全局变量
|
||||||
|
float position = 0;
|
||||||
|
for (int i = 0; i < index; i++)
|
||||||
|
{
|
||||||
|
position += GetLength(i);
|
||||||
|
}
|
||||||
|
position -= ScrollPosition;
|
||||||
|
return direction == Direction.Vertical ? new Vector2(0, position + padding.y) : new Vector2(position + padding.x, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override Vector2 CalculateContentOffset()
|
||||||
|
{
|
||||||
|
Vector2 size = viewProvider.CalculateViewSize(0);
|
||||||
|
float len = GetFitContentSize();
|
||||||
|
if (direction == Direction.Vertical)
|
||||||
|
{
|
||||||
|
return new Vector2(0, (len - size.y) / 2);
|
||||||
|
}
|
||||||
|
return new Vector2((len - size.x) / 2, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override Vector2 CalculateViewportOffset()
|
||||||
|
{
|
||||||
|
Vector2 size = viewProvider.CalculateViewSize(0);
|
||||||
|
if (direction == Direction.Vertical)
|
||||||
|
{
|
||||||
|
return new Vector2(0, (viewportSize.y - size.y) / 2);
|
||||||
|
}
|
||||||
|
return new Vector2((viewportSize.x - size.x) / 2, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override int GetStartIndex()
|
||||||
|
{
|
||||||
|
float position = 0;
|
||||||
|
float contentPosition = ScrollPosition;
|
||||||
|
int itemCount = adapter.GetItemCount();
|
||||||
|
for (int i = 0; i < itemCount; i++)
|
||||||
|
{
|
||||||
|
position += GetLength(i);
|
||||||
|
|
||||||
|
if (position > contentPosition)
|
||||||
|
{
|
||||||
|
return Mathf.Max(0, i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override int GetEndIndex()
|
||||||
|
{
|
||||||
|
float position = 0;
|
||||||
|
float viewLength = direction == Direction.Vertical ? viewportSize.y : viewportSize.x;
|
||||||
|
int itemCount = adapter.GetItemCount();
|
||||||
|
for (int i = 0; i < itemCount; i++)
|
||||||
|
{
|
||||||
|
position += GetLength(i);
|
||||||
|
|
||||||
|
if (position > ScrollPosition + viewLength)
|
||||||
|
{
|
||||||
|
return Mathf.Min(i, adapter.GetItemCount() - 1); ;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return itemCount - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
private float GetLength(int index)
|
||||||
|
{
|
||||||
|
Vector2 size = viewProvider.CalculateViewSize(index);
|
||||||
|
if (index < adapter.GetItemCount() - 1)
|
||||||
|
{
|
||||||
|
size += spacing;
|
||||||
|
}
|
||||||
|
float len = direction == Direction.Vertical ? size.y : size.x;
|
||||||
|
return len;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override float IndexToPosition(int index)
|
||||||
|
{
|
||||||
|
Vector2 position = CalculatePosition(index);
|
||||||
|
if (direction == Direction.Vertical)
|
||||||
|
{
|
||||||
|
position.y = Mathf.Max(0, position.y);
|
||||||
|
position.y = Mathf.Min(position.y, contentSize.y - viewportSize.y);
|
||||||
|
return position.y;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
position.x = Mathf.Max(0, position.x);
|
||||||
|
position.x = Mathf.Min(position.x, contentSize.x - viewportSize.x);
|
||||||
|
return position.x;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override int PositionToIndex(float position)
|
||||||
|
{
|
||||||
|
float len = 0;
|
||||||
|
|
||||||
|
int itemCount = adapter.GetItemCount();
|
||||||
|
for (int i = 0; i < itemCount; i++)
|
||||||
|
{
|
||||||
|
len += GetLength(i);
|
||||||
|
|
||||||
|
if (len >= position)
|
||||||
|
{
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
11
Runtime/RecyclerView/Layout/MixedLayoutManager.cs.meta
Normal file
11
Runtime/RecyclerView/Layout/MixedLayoutManager.cs.meta
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: d296ceb3ab7786f43a94281a728d5205
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
87
Runtime/RecyclerView/Layout/PageLayoutManager.cs
Normal file
87
Runtime/RecyclerView/Layout/PageLayoutManager.cs
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace AlicizaX.UI.RecyclerView
|
||||||
|
{
|
||||||
|
public class PageLayoutManager : LinearLayoutManager
|
||||||
|
{
|
||||||
|
private float minScale;
|
||||||
|
|
||||||
|
public PageLayoutManager(float minScale = 0.9f)
|
||||||
|
{
|
||||||
|
this.minScale = minScale;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override Vector2 CalculateContentSize()
|
||||||
|
{
|
||||||
|
Vector2 size = viewProvider.CalculateViewSize(0);
|
||||||
|
lineHeight = direction == Direction.Vertical ? size.y : size.x;
|
||||||
|
|
||||||
|
int index = adapter.GetItemCount();
|
||||||
|
float position;
|
||||||
|
if (direction == Direction.Vertical)
|
||||||
|
{
|
||||||
|
position = index * (lineHeight + spacing.y) - spacing.y;
|
||||||
|
position += viewportSize.y - lineHeight;
|
||||||
|
return new Vector2(contentSize.x, position + padding.y * 2);
|
||||||
|
}
|
||||||
|
position = index * (lineHeight + spacing.x) - spacing.x;
|
||||||
|
position += viewportSize.x - lineHeight;
|
||||||
|
return new Vector2(position + padding.x * 2, contentSize.y);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override Vector2 CalculatePosition(int index)
|
||||||
|
{
|
||||||
|
float position;
|
||||||
|
if (direction == Direction.Vertical)
|
||||||
|
{
|
||||||
|
position = index * (lineHeight + spacing.y) - ScrollPosition;
|
||||||
|
return new Vector2(0, position + padding.y);
|
||||||
|
}
|
||||||
|
position = index * (lineHeight + spacing.x) - ScrollPosition;
|
||||||
|
return new Vector2(position + padding.x, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override Vector2 CalculateContentOffset()
|
||||||
|
{
|
||||||
|
return Vector2.zero;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override Vector2 CalculateViewportOffset()
|
||||||
|
{
|
||||||
|
return Vector2.zero;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override float GetOffset()
|
||||||
|
{
|
||||||
|
float offset = direction == Direction.Vertical ? viewportSize.y - lineHeight : viewportSize.x - lineHeight;
|
||||||
|
return offset / 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override int PositionToIndex(float position)
|
||||||
|
{
|
||||||
|
float len = direction == Direction.Vertical ? lineHeight + spacing.y : lineHeight + spacing.x;
|
||||||
|
float pos = IndexToPosition(recyclerView.CurrentIndex);
|
||||||
|
// 根据是前划还是后划,来加减偏移量
|
||||||
|
int index = position > pos ? Mathf.RoundToInt(position / len + 0.25f) : Mathf.RoundToInt(position / len - 0.25f);
|
||||||
|
|
||||||
|
return index;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void DoItemAnimation()
|
||||||
|
{
|
||||||
|
List<ViewHolder> viewHolders = viewProvider.ViewHolders;
|
||||||
|
for (int i = 0; i < viewHolders.Count; i++)
|
||||||
|
{
|
||||||
|
float viewPos = direction == Direction.Vertical ?
|
||||||
|
-viewHolders[i].RectTransform.anchoredPosition.y :
|
||||||
|
viewHolders[i].RectTransform.anchoredPosition.x;
|
||||||
|
float scale = 1 - Mathf.Min(Mathf.Abs(viewPos) * 0.0006f, 1f);
|
||||||
|
scale = Mathf.Max(scale, minScale);
|
||||||
|
|
||||||
|
viewHolders[i].RectTransform.localScale = Vector3.one * scale;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
11
Runtime/RecyclerView/Layout/PageLayoutManager.cs.meta
Normal file
11
Runtime/RecyclerView/Layout/PageLayoutManager.cs.meta
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 4fccdae6dae870c42bc27f435a94e621
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
8
Runtime/RecyclerView/ObjectPool.meta
Normal file
8
Runtime/RecyclerView/ObjectPool.meta
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: a22aa56c3da6d724e97aafd9f3e7ed67
|
||||||
|
folderAsset: yes
|
||||||
|
DefaultImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
11
Runtime/RecyclerView/ObjectPool/IMixedObjectFactory.cs
Normal file
11
Runtime/RecyclerView/ObjectPool/IMixedObjectFactory.cs
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
|
||||||
|
public interface IMixedObjectFactory<T> where T : class
|
||||||
|
{
|
||||||
|
T Create(string typeName);
|
||||||
|
|
||||||
|
void Destroy(string typeName, T obj);
|
||||||
|
|
||||||
|
void Reset(string typeName, T obj);
|
||||||
|
|
||||||
|
bool Validate(string typeName, T obj);
|
||||||
|
}
|
11
Runtime/RecyclerView/ObjectPool/IMixedObjectFactory.cs.meta
Normal file
11
Runtime/RecyclerView/ObjectPool/IMixedObjectFactory.cs.meta
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 30f89ee61eb3f0949b5300bd9b7cc577
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
8
Runtime/RecyclerView/ObjectPool/IMixedObjectPool.cs
Normal file
8
Runtime/RecyclerView/ObjectPool/IMixedObjectPool.cs
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
public interface IMixedObjectPool<T> : IDisposable where T : class
|
||||||
|
{
|
||||||
|
T Allocate(string typeName);
|
||||||
|
|
||||||
|
void Free(string typeName, T obj);
|
||||||
|
}
|
11
Runtime/RecyclerView/ObjectPool/IMixedObjectPool.cs.meta
Normal file
11
Runtime/RecyclerView/ObjectPool/IMixedObjectPool.cs.meta
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 3f4c13a4827ebad4a9ff08e636fbc67e
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
28
Runtime/RecyclerView/ObjectPool/IObjectFactory.cs
Normal file
28
Runtime/RecyclerView/ObjectPool/IObjectFactory.cs
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
|
||||||
|
public interface IObjectFactory<T> where T : class
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 创建对象
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
T Create();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 销毁对象
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="obj"></param>
|
||||||
|
void Destroy(T obj);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 重置对象
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="obj"></param>
|
||||||
|
void Reset(T obj);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 验证对象
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="obj"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
bool Validate(T obj);
|
||||||
|
}
|
11
Runtime/RecyclerView/ObjectPool/IObjectFactory.cs.meta
Normal file
11
Runtime/RecyclerView/ObjectPool/IObjectFactory.cs.meta
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 77a642084db01624c8e5876605195d49
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
23
Runtime/RecyclerView/ObjectPool/IObjectPool.cs
Normal file
23
Runtime/RecyclerView/ObjectPool/IObjectPool.cs
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
public interface IObjectPool : IDisposable
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 从池子中分配一个可用对象,没有的话就创建一个
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
object Allocate();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 将对象回收到池子中去,如果池中的对象数量已经超过了 maxSize,则直接销毁该对象
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="obj"></param>
|
||||||
|
void Free(object obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface IObjectPool<T> : IObjectPool, IDisposable where T : class
|
||||||
|
{
|
||||||
|
new T Allocate();
|
||||||
|
|
||||||
|
void Free(T obj);
|
||||||
|
}
|
11
Runtime/RecyclerView/ObjectPool/IObjectPool.cs.meta
Normal file
11
Runtime/RecyclerView/ObjectPool/IObjectPool.cs.meta
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: dd12164a5eea20e41bf7f3b7704f4b33
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
5
Runtime/RecyclerView/ObjectPool/IPooledObject.cs
Normal file
5
Runtime/RecyclerView/ObjectPool/IPooledObject.cs
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
|
||||||
|
public interface IPooledObject
|
||||||
|
{
|
||||||
|
void Free();
|
||||||
|
}
|
11
Runtime/RecyclerView/ObjectPool/IPooledObject.cs.meta
Normal file
11
Runtime/RecyclerView/ObjectPool/IPooledObject.cs.meta
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: b174ccb64b3938c449d4a69a3262d8d5
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
101
Runtime/RecyclerView/ObjectPool/MixedObjectPool.cs
Normal file
101
Runtime/RecyclerView/ObjectPool/MixedObjectPool.cs
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Concurrent;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
public class MixedObjectPool<T> : IMixedObjectPool<T> where T : class
|
||||||
|
{
|
||||||
|
private const int DEFAULT_MAX_SIZE_PER_TYPE = 10;
|
||||||
|
|
||||||
|
private readonly ConcurrentDictionary<string, List<T>> entries;
|
||||||
|
private readonly ConcurrentDictionary<string, int> typeSize;
|
||||||
|
private readonly IMixedObjectFactory<T> factory;
|
||||||
|
|
||||||
|
private int defaultMaxSizePerType;
|
||||||
|
|
||||||
|
public MixedObjectPool(IMixedObjectFactory<T> factory) : this(factory, DEFAULT_MAX_SIZE_PER_TYPE)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public MixedObjectPool(IMixedObjectFactory<T> factory, int defaultMaxSizePerType)
|
||||||
|
{
|
||||||
|
this.factory = factory;
|
||||||
|
this.defaultMaxSizePerType = defaultMaxSizePerType;
|
||||||
|
|
||||||
|
if (defaultMaxSizePerType <= 0)
|
||||||
|
{
|
||||||
|
throw new ArgumentException("The maxSize must be greater than 0.");
|
||||||
|
}
|
||||||
|
|
||||||
|
entries = new ConcurrentDictionary<string, List<T>>();
|
||||||
|
typeSize = new ConcurrentDictionary<string, int>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public T Allocate(string typeName)
|
||||||
|
{
|
||||||
|
if (entries.TryGetValue(typeName, out List<T> list) && list.Count > 0)
|
||||||
|
{
|
||||||
|
T obj = list[0];
|
||||||
|
list.RemoveAt(0);
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
return factory.Create(typeName);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Free(string typeName, T obj)
|
||||||
|
{
|
||||||
|
if (obj == null) return;
|
||||||
|
|
||||||
|
if (!factory.Validate(typeName, obj))
|
||||||
|
{
|
||||||
|
factory.Destroy(typeName, obj);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int maxSize = GetMaxSize(typeName);
|
||||||
|
List<T> list = entries.GetOrAdd(typeName, n => new List<T>());
|
||||||
|
if (list.Count >= maxSize)
|
||||||
|
{
|
||||||
|
factory.Destroy(typeName, obj);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
factory.Reset(typeName, obj);
|
||||||
|
list.Add(obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int GetMaxSize(string typeName)
|
||||||
|
{
|
||||||
|
if (typeSize.TryGetValue(typeName, out int size))
|
||||||
|
{
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
return defaultMaxSizePerType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetMaxSize(string typeName, int value)
|
||||||
|
{
|
||||||
|
typeSize.AddOrUpdate(typeName, value, (key, oldValue) => value);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual void Clear()
|
||||||
|
{
|
||||||
|
foreach (var kv in entries)
|
||||||
|
{
|
||||||
|
string typeName = kv.Key;
|
||||||
|
List<T> list = kv.Value;
|
||||||
|
|
||||||
|
if (list == null || list.Count <= 0) continue;
|
||||||
|
|
||||||
|
list.ForEach(e => factory.Destroy(typeName, e));
|
||||||
|
list.Clear();
|
||||||
|
}
|
||||||
|
entries.Clear();
|
||||||
|
typeSize.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
Clear();
|
||||||
|
GC.SuppressFinalize(this);
|
||||||
|
}
|
||||||
|
}
|
11
Runtime/RecyclerView/ObjectPool/MixedObjectPool.cs.meta
Normal file
11
Runtime/RecyclerView/ObjectPool/MixedObjectPool.cs.meta
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 0a62e68f43eac4b419140191eb09ea56
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
108
Runtime/RecyclerView/ObjectPool/ObjectPool.cs
Normal file
108
Runtime/RecyclerView/ObjectPool/ObjectPool.cs
Normal file
@ -0,0 +1,108 @@
|
|||||||
|
using System;
|
||||||
|
using System.Threading;
|
||||||
|
|
||||||
|
public class ObjectPool<T> : IObjectPool<T> where T : class
|
||||||
|
{
|
||||||
|
private int maxSize;
|
||||||
|
private int initialSize;
|
||||||
|
protected readonly T[] entries = null;
|
||||||
|
protected readonly IObjectFactory<T> factory;
|
||||||
|
|
||||||
|
public ObjectPool(IObjectFactory<T> factory) : this(factory, Environment.ProcessorCount * 2)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public ObjectPool(IObjectFactory<T> factory, int maxSize) : this(factory, 0, maxSize)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public ObjectPool(IObjectFactory<T> factory, int initialSize, int maxSize)
|
||||||
|
{
|
||||||
|
this.factory = factory;
|
||||||
|
this.initialSize = initialSize;
|
||||||
|
this.maxSize = maxSize;
|
||||||
|
this.entries = new T[maxSize];
|
||||||
|
|
||||||
|
if (maxSize < initialSize)
|
||||||
|
{
|
||||||
|
throw new ArgumentException("The maxSize must be greater than or equal to the initialSize.");
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < initialSize; i++)
|
||||||
|
{
|
||||||
|
entries[i] = factory.Create();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public int MaxSize => maxSize;
|
||||||
|
|
||||||
|
public int InitialSize => initialSize;
|
||||||
|
|
||||||
|
public virtual T Allocate()
|
||||||
|
{
|
||||||
|
for (var i = 0; i < entries.Length; i++)
|
||||||
|
{
|
||||||
|
T value = entries[i];
|
||||||
|
if (value == null) continue;
|
||||||
|
|
||||||
|
if (Interlocked.CompareExchange(ref entries[i], null, value) == value)
|
||||||
|
{
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return factory.Create();
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual void Free(T obj)
|
||||||
|
{
|
||||||
|
if (obj == null) return;
|
||||||
|
|
||||||
|
if (!factory.Validate(obj))
|
||||||
|
{
|
||||||
|
factory.Destroy(obj);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
factory.Reset(obj);
|
||||||
|
|
||||||
|
for (var i = 0; i < entries.Length; i++)
|
||||||
|
{
|
||||||
|
if (Interlocked.CompareExchange(ref entries[i], obj, null) == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
factory.Destroy(obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
object IObjectPool.Allocate()
|
||||||
|
{
|
||||||
|
return Allocate();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Free(object obj)
|
||||||
|
{
|
||||||
|
Free((T)obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual void Clear()
|
||||||
|
{
|
||||||
|
for (var i = 0; i < entries.Length; i++)
|
||||||
|
{
|
||||||
|
var value = Interlocked.Exchange(ref entries[i], null);
|
||||||
|
|
||||||
|
if (value != null)
|
||||||
|
{
|
||||||
|
factory.Destroy(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
Clear();
|
||||||
|
GC.SuppressFinalize(this);
|
||||||
|
}
|
||||||
|
}
|
11
Runtime/RecyclerView/ObjectPool/ObjectPool.cs.meta
Normal file
11
Runtime/RecyclerView/ObjectPool/ObjectPool.cs.meta
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 30999e5e03e2b434996100b09960468f
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
36
Runtime/RecyclerView/ObjectPool/UnityComponentFactory.cs
Normal file
36
Runtime/RecyclerView/ObjectPool/UnityComponentFactory.cs
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
public class UnityComponentFactory<T> : IObjectFactory<T> where T : Component
|
||||||
|
{
|
||||||
|
private T template;
|
||||||
|
private Transform parent;
|
||||||
|
|
||||||
|
public UnityComponentFactory(T template, Transform parent)
|
||||||
|
{
|
||||||
|
this.template = template;
|
||||||
|
this.parent = parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
public T Create()
|
||||||
|
{
|
||||||
|
T obj = Object.Instantiate(template, parent);
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Destroy(T obj)
|
||||||
|
{
|
||||||
|
Object.Destroy(obj.gameObject);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Reset(T obj)
|
||||||
|
{
|
||||||
|
obj.gameObject.SetActive(false);
|
||||||
|
obj.gameObject.transform.position = Vector3.zero;
|
||||||
|
obj.gameObject.transform.rotation = Quaternion.identity;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Validate(T obj)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 3e2e3a0444783a7469d494ad630dc705
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
35
Runtime/RecyclerView/ObjectPool/UnityGameObjectFactory.cs
Normal file
35
Runtime/RecyclerView/ObjectPool/UnityGameObjectFactory.cs
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
public class UnityGameObjectFactory : IObjectFactory<GameObject>
|
||||||
|
{
|
||||||
|
protected GameObject template;
|
||||||
|
protected Transform parent;
|
||||||
|
|
||||||
|
public UnityGameObjectFactory(GameObject template, Transform parent)
|
||||||
|
{
|
||||||
|
this.template = template;
|
||||||
|
this.parent = parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual GameObject Create()
|
||||||
|
{
|
||||||
|
return Object.Instantiate(template, parent);
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual void Reset(GameObject obj)
|
||||||
|
{
|
||||||
|
obj.SetActive(false);
|
||||||
|
obj.transform.position = Vector3.zero;
|
||||||
|
obj.transform.rotation = Quaternion.identity;
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual void Destroy(GameObject obj)
|
||||||
|
{
|
||||||
|
Object.Destroy(obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual bool Validate(GameObject obj)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 7373bce96f2a515499c52b060ad9e01e
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
@ -0,0 +1,61 @@
|
|||||||
|
using System.Collections;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
public class UnityMixedComponentFactory<T> : IMixedObjectFactory<T> where T : Component
|
||||||
|
{
|
||||||
|
protected T template;
|
||||||
|
protected Transform parent;
|
||||||
|
protected List<T> list;
|
||||||
|
|
||||||
|
private Dictionary<string, T> dict = new();
|
||||||
|
|
||||||
|
public UnityMixedComponentFactory(T template, Transform parent)
|
||||||
|
{
|
||||||
|
this.template = template;
|
||||||
|
this.parent = parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
public UnityMixedComponentFactory(List<T> list, Transform parent)
|
||||||
|
{
|
||||||
|
this.list = list;
|
||||||
|
this.parent = parent;
|
||||||
|
|
||||||
|
foreach (var data in list)
|
||||||
|
{
|
||||||
|
dict[data.name] = data;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public UnityMixedComponentFactory(Dictionary<string, T> dict, Transform parent)
|
||||||
|
{
|
||||||
|
this.dict = dict;
|
||||||
|
this.parent = parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
public T Create(string typeName)
|
||||||
|
{
|
||||||
|
T obj = Object.Instantiate(dict[typeName], parent);
|
||||||
|
obj.transform.position = Vector3.zero;
|
||||||
|
obj.transform.rotation = Quaternion.identity;
|
||||||
|
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Destroy(string typeName, T obj)
|
||||||
|
{
|
||||||
|
Object.Destroy(obj.gameObject);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Reset(string typeName, T obj)
|
||||||
|
{
|
||||||
|
obj.gameObject.SetActive(false);
|
||||||
|
obj.transform.position = Vector3.zero;
|
||||||
|
obj.transform.rotation = Quaternion.identity;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Validate(string typeName, T obj)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 5c2d58d006be3fc458b8c6761d76bc88
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
@ -0,0 +1,40 @@
|
|||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
public class UnityMixedGameObjectFactory : IMixedObjectFactory<GameObject>
|
||||||
|
{
|
||||||
|
protected GameObject template;
|
||||||
|
protected Transform parent;
|
||||||
|
|
||||||
|
public UnityMixedGameObjectFactory(GameObject template, Transform parent)
|
||||||
|
{
|
||||||
|
this.template = template;
|
||||||
|
this.parent = parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
public GameObject Create(string typeName)
|
||||||
|
{
|
||||||
|
GameObject obj = Object.Instantiate(template, parent);
|
||||||
|
GameObject model = Object.Instantiate(Resources.Load<GameObject>("ObjectPools/" + typeName), obj.transform);
|
||||||
|
model.transform.position = Vector3.zero;
|
||||||
|
model.transform.rotation = Quaternion.identity;
|
||||||
|
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Destroy(string typeName, GameObject obj)
|
||||||
|
{
|
||||||
|
Object.Destroy(obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Reset(string typeName, GameObject obj)
|
||||||
|
{
|
||||||
|
obj.SetActive(false);
|
||||||
|
obj.transform.position = Vector3.zero;
|
||||||
|
obj.transform.rotation = Quaternion.identity;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Validate(string typeName, GameObject obj)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 16c8995b85215c6458119d581eea60b0
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
369
Runtime/RecyclerView/RecyclerView.cs
Normal file
369
Runtime/RecyclerView/RecyclerView.cs
Normal file
@ -0,0 +1,369 @@
|
|||||||
|
using System;
|
||||||
|
using UnityEngine;
|
||||||
|
using UnityEngine.UI;
|
||||||
|
|
||||||
|
namespace AlicizaX.UI.RecyclerView
|
||||||
|
{
|
||||||
|
public class RecyclerView : MonoBehaviour
|
||||||
|
{
|
||||||
|
[SerializeField] private Direction direction;
|
||||||
|
public Direction Direction
|
||||||
|
{
|
||||||
|
get => direction;
|
||||||
|
set => direction = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
[SerializeField] private Alignment alignment;
|
||||||
|
public Alignment Alignment
|
||||||
|
{
|
||||||
|
get => alignment;
|
||||||
|
set => alignment = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
[SerializeField] private Vector2 spacing;
|
||||||
|
public Vector2 Spacing
|
||||||
|
{
|
||||||
|
get => spacing;
|
||||||
|
set => spacing = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
[SerializeField] private Vector2 padding;
|
||||||
|
public Vector2 Padding
|
||||||
|
{
|
||||||
|
get => padding;
|
||||||
|
set => padding = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
[SerializeField] private bool scroll;
|
||||||
|
public bool Scroll
|
||||||
|
{
|
||||||
|
get => scroll;
|
||||||
|
set => scroll = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
[SerializeField] private bool snap;
|
||||||
|
public bool Snap
|
||||||
|
{
|
||||||
|
get => snap;
|
||||||
|
set => snap = value & scroll;
|
||||||
|
}
|
||||||
|
|
||||||
|
[SerializeField, Range(1f, 50f)] private float scrollSpeed = 7f;
|
||||||
|
public float ScrollSpeed
|
||||||
|
{
|
||||||
|
get => scrollSpeed;
|
||||||
|
set => scrollSpeed = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
[SerializeField, Range(10f, 50f)] private float wheelSpeed = 30f;
|
||||||
|
public float WheeelSpeed
|
||||||
|
{
|
||||||
|
get => wheelSpeed;
|
||||||
|
set => wheelSpeed = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
[SerializeField] private ViewHolder[] templates;
|
||||||
|
public ViewHolder[] Templates
|
||||||
|
{
|
||||||
|
get => templates;
|
||||||
|
set => templates = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
private ViewProvider viewProvider;
|
||||||
|
private LayoutManager layoutManager;
|
||||||
|
private Scroller scroller;
|
||||||
|
private Scrollbar scrollbar;
|
||||||
|
|
||||||
|
private int startIndex, endIndex;
|
||||||
|
private int currentIndex;
|
||||||
|
public int CurrentIndex
|
||||||
|
{
|
||||||
|
get => currentIndex;
|
||||||
|
set => currentIndex = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool CanScroll => true;
|
||||||
|
|
||||||
|
private RectTransform content;
|
||||||
|
public RectTransform Content
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (content == null)
|
||||||
|
{
|
||||||
|
content = transform.GetChild(0).GetComponent<RectTransform>();
|
||||||
|
}
|
||||||
|
return content;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public ViewProvider ViewProvider
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
viewProvider ??= templates.Length > 1 ? new MixedViewProvider(this, templates) : new SimpleViewProvider(this, templates);
|
||||||
|
return viewProvider;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Scroller Scroller
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (scroller == null)
|
||||||
|
{
|
||||||
|
if (scroll)
|
||||||
|
{
|
||||||
|
scroller = gameObject.AddComponent<Scroller>();
|
||||||
|
ConfigScroller();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return scroller;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Scrollbar Scrollbar
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (scrollbar == null)
|
||||||
|
{
|
||||||
|
scrollbar = GetComponentInChildren<Scrollbar>();
|
||||||
|
if (scrollbar != null)
|
||||||
|
{
|
||||||
|
scrollbar.gameObject.SetActive(scroll);
|
||||||
|
scrollbar.onValueChanged.AddListener(OnScrollbarChanged);
|
||||||
|
scrollbar.gameObject.AddComponent<ScrollbarEx>().OnDragEnd = OnScrollbarDragEnd;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return scrollbar;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public IAdapter Adapter { get; set; }
|
||||||
|
|
||||||
|
public LayoutManager LayoutManager => layoutManager;
|
||||||
|
|
||||||
|
public Action<int> OnIndexChanged;
|
||||||
|
public Action OnScrollValueChanged;
|
||||||
|
|
||||||
|
private void OnValidate()
|
||||||
|
{
|
||||||
|
if (scroller != null)
|
||||||
|
{
|
||||||
|
scroller.ScrollSpeed = scrollSpeed;
|
||||||
|
scroller.WheelSpeed = wheelSpeed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnScrollChanged(float pos)
|
||||||
|
{
|
||||||
|
layoutManager.UpdateLayout();
|
||||||
|
|
||||||
|
if (Scrollbar != null)
|
||||||
|
{
|
||||||
|
Scrollbar.SetValueWithoutNotify(pos / Scroller.MaxPosition);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (layoutManager.IsFullInvisibleStart(startIndex))
|
||||||
|
{
|
||||||
|
viewProvider.RemoveViewHolder(startIndex);
|
||||||
|
startIndex += layoutManager.Unit;
|
||||||
|
}
|
||||||
|
else if (layoutManager.IsFullVisibleStart(startIndex))
|
||||||
|
{
|
||||||
|
if (startIndex == 0)
|
||||||
|
{
|
||||||
|
// TODO Do something, eg: Refresh
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
startIndex -= layoutManager.Unit;
|
||||||
|
viewProvider.CreateViewHolder(startIndex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (layoutManager.IsFullInvisibleEnd(endIndex))
|
||||||
|
{
|
||||||
|
viewProvider.RemoveViewHolder(endIndex);
|
||||||
|
endIndex -= layoutManager.Unit;
|
||||||
|
}
|
||||||
|
else if (layoutManager.IsFullVisibleEnd(endIndex))
|
||||||
|
{
|
||||||
|
if (endIndex >= viewProvider.GetItemCount() - layoutManager.Unit)
|
||||||
|
{
|
||||||
|
// TODO Do something, eg: Load More
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
endIndex += layoutManager.Unit;
|
||||||
|
viewProvider.CreateViewHolder(endIndex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 使用滚动条快速定位时,刷新整个列表
|
||||||
|
if (!layoutManager.IsVisible(startIndex) || !layoutManager.IsVisible(endIndex))
|
||||||
|
{
|
||||||
|
Refresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
layoutManager.DoItemAnimation();
|
||||||
|
|
||||||
|
OnScrollValueChanged?.Invoke();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnMoveStoped()
|
||||||
|
{
|
||||||
|
if (Snap)
|
||||||
|
{
|
||||||
|
SnapTo();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnScrollbarChanged(float ratio)
|
||||||
|
{
|
||||||
|
Scroller.ScrollToRatio(ratio);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnScrollbarDragEnd()
|
||||||
|
{
|
||||||
|
if (Scroller.Position < Scroller.MaxPosition)
|
||||||
|
{
|
||||||
|
if (Snap)
|
||||||
|
{
|
||||||
|
SnapTo();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Reset()
|
||||||
|
{
|
||||||
|
viewProvider?.Reset();
|
||||||
|
if (scroller != null)
|
||||||
|
{
|
||||||
|
scroller.Position = 0;
|
||||||
|
}
|
||||||
|
if (scrollbar != null)
|
||||||
|
{
|
||||||
|
scrollbar.SetValueWithoutNotify(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetLayoutManager(LayoutManager layoutManager)
|
||||||
|
{
|
||||||
|
this.layoutManager = layoutManager;
|
||||||
|
|
||||||
|
ViewProvider.Adapter = Adapter;
|
||||||
|
ViewProvider.LayoutManager = layoutManager;
|
||||||
|
|
||||||
|
this.layoutManager.RecyclerView = this;
|
||||||
|
this.layoutManager.Adapter = Adapter;
|
||||||
|
this.layoutManager.ViewProvider = viewProvider;
|
||||||
|
this.layoutManager.Direction = direction;
|
||||||
|
this.layoutManager.Alignment = alignment;
|
||||||
|
this.layoutManager.Spacing = spacing;
|
||||||
|
this.layoutManager.Padding = padding;
|
||||||
|
this.layoutManager.CanScroll = CanScroll;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetScroller(Scroller newScroller)
|
||||||
|
{
|
||||||
|
if (!scroll) return;
|
||||||
|
|
||||||
|
if (scroller != null)
|
||||||
|
{
|
||||||
|
scroller.OnValueChanged.RemoveListener(OnScrollChanged);
|
||||||
|
scroller.OnMoveStoped.RemoveListener(OnMoveStoped);
|
||||||
|
Destroy(scroller);
|
||||||
|
}
|
||||||
|
|
||||||
|
scroller = newScroller;
|
||||||
|
ConfigScroller();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ConfigScroller()
|
||||||
|
{
|
||||||
|
scroller.ScrollSpeed = scrollSpeed;
|
||||||
|
scroller.WheelSpeed = wheelSpeed;
|
||||||
|
scroller.Snap = Snap;
|
||||||
|
scroller.OnValueChanged.AddListener(OnScrollChanged);
|
||||||
|
scroller.OnMoveStoped.AddListener(OnMoveStoped);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Refresh()
|
||||||
|
{
|
||||||
|
ViewProvider.Clear();
|
||||||
|
|
||||||
|
startIndex = layoutManager.GetStartIndex();
|
||||||
|
endIndex = layoutManager.GetEndIndex();
|
||||||
|
for (int i = startIndex; i <= endIndex; i += layoutManager.Unit)
|
||||||
|
{
|
||||||
|
ViewProvider.CreateViewHolder(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
layoutManager.DoItemAnimation();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RequestLayout()
|
||||||
|
{
|
||||||
|
layoutManager.SetContentSize();
|
||||||
|
|
||||||
|
if (Scroller == null) return;
|
||||||
|
|
||||||
|
Scroller.Direction = direction;
|
||||||
|
Scroller.ViewSize = layoutManager.ViewportSize;
|
||||||
|
Scroller.ContentSize = layoutManager.ContentSize;
|
||||||
|
|
||||||
|
if (Scrollbar != null && Scroller.ContentSize != Vector2.zero)
|
||||||
|
{
|
||||||
|
if ((direction == Direction.Vertical && layoutManager.ContentSize.y <= layoutManager.ViewportSize.y) ||
|
||||||
|
(direction == Direction.Horizontal && layoutManager.ContentSize.x <= layoutManager.ViewportSize.x) ||
|
||||||
|
(direction == Direction.Custom))
|
||||||
|
{
|
||||||
|
Scrollbar.gameObject.SetActive(false);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Scrollbar.gameObject.SetActive(true);
|
||||||
|
Scrollbar.direction = direction == Direction.Vertical ?
|
||||||
|
Scrollbar.Direction.TopToBottom :
|
||||||
|
Scrollbar.Direction.LeftToRight;
|
||||||
|
Scrollbar.size = direction == Direction.Vertical ?
|
||||||
|
Scroller.ViewSize.y / Scroller.ContentSize.y :
|
||||||
|
Scroller.ViewSize.x / Scroller.ContentSize.x;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public float GetScrollPosition()
|
||||||
|
{
|
||||||
|
return Scroller ? Scroller.Position : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ScrollTo(int index, bool smooth = false)
|
||||||
|
{
|
||||||
|
if (!scroll) return;
|
||||||
|
|
||||||
|
Scroller.ScrollTo(layoutManager.IndexToPosition(index), smooth);
|
||||||
|
if (!smooth)
|
||||||
|
{
|
||||||
|
Refresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
index %= Adapter.GetItemCount();
|
||||||
|
index = index < 0 ? Adapter.GetItemCount() + index : index;
|
||||||
|
|
||||||
|
if (currentIndex != index)
|
||||||
|
{
|
||||||
|
currentIndex = index;
|
||||||
|
OnIndexChanged?.Invoke(currentIndex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SnapTo()
|
||||||
|
{
|
||||||
|
var index = layoutManager.PositionToIndex(GetScrollPosition());
|
||||||
|
ScrollTo(index, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
11
Runtime/RecyclerView/RecyclerView.cs.meta
Normal file
11
Runtime/RecyclerView/RecyclerView.cs.meta
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 7efd8e83d2092b347952108134dc37eb
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
8
Runtime/RecyclerView/Scroller.meta
Normal file
8
Runtime/RecyclerView/Scroller.meta
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: b79ba677d829b904794e3b4a09cb67c2
|
||||||
|
folderAsset: yes
|
||||||
|
DefaultImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
61
Runtime/RecyclerView/Scroller/CircleScroller.cs
Normal file
61
Runtime/RecyclerView/Scroller/CircleScroller.cs
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
using UnityEngine;
|
||||||
|
using UnityEngine.EventSystems;
|
||||||
|
|
||||||
|
namespace AlicizaX.UI.RecyclerView
|
||||||
|
{
|
||||||
|
public class CircleScroller : Scroller
|
||||||
|
{
|
||||||
|
private Vector2 centerPosition;
|
||||||
|
|
||||||
|
private void Awake()
|
||||||
|
{
|
||||||
|
RectTransform rectTransform = GetComponent<RectTransform>();
|
||||||
|
Vector2 position = transform.position;
|
||||||
|
Vector2 size = rectTransform.sizeDelta;
|
||||||
|
|
||||||
|
if (rectTransform.pivot.x == 0)
|
||||||
|
{
|
||||||
|
centerPosition.x = position.x + size.x / 2f;
|
||||||
|
}
|
||||||
|
else if (rectTransform.pivot.x == 0.5f)
|
||||||
|
{
|
||||||
|
centerPosition.x = position.x;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
centerPosition.x = position.x - size.x / 2f;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rectTransform.pivot.y == 0)
|
||||||
|
{
|
||||||
|
centerPosition.y = position.y + size.y / 2f;
|
||||||
|
}
|
||||||
|
else if (rectTransform.pivot.y == 0.5f)
|
||||||
|
{
|
||||||
|
centerPosition.y = position.y;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
centerPosition.y = position.y - size.y / 2f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal override float GetDelta(PointerEventData eventData)
|
||||||
|
{
|
||||||
|
float delta;
|
||||||
|
if (Mathf.Abs(eventData.delta.x) > Mathf.Abs(eventData.delta.y))
|
||||||
|
{
|
||||||
|
delta = eventData.position.y > centerPosition.y ? eventData.delta.x : -eventData.delta.x;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
delta = eventData.position.x < centerPosition.x ? eventData.delta.y : -eventData.delta.y;
|
||||||
|
}
|
||||||
|
return delta * 0.1f;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Elastic()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
11
Runtime/RecyclerView/Scroller/CircleScroller.cs.meta
Normal file
11
Runtime/RecyclerView/Scroller/CircleScroller.cs.meta
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 85235544e637a4a4f93ad88a5cfdac0a
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
15
Runtime/RecyclerView/Scroller/IScroller.cs
Normal file
15
Runtime/RecyclerView/Scroller/IScroller.cs
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
using UnityEngine.Events;
|
||||||
|
|
||||||
|
namespace AlicizaX.UI.RecyclerView
|
||||||
|
{
|
||||||
|
public interface IScroller
|
||||||
|
{
|
||||||
|
float Position { get; set; }
|
||||||
|
|
||||||
|
void ScrollTo(float position, bool smooth = false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ScrollerEvent : UnityEvent<float> { }
|
||||||
|
public class MoveStopEvent : UnityEvent { }
|
||||||
|
public class DraggingEvent : UnityEvent<bool> { }
|
||||||
|
}
|
11
Runtime/RecyclerView/Scroller/IScroller.cs.meta
Normal file
11
Runtime/RecyclerView/Scroller/IScroller.cs.meta
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 3928a884003099344b231a9d554dc5da
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
80
Runtime/RecyclerView/Scroller/ScrollbarEx.cs
Normal file
80
Runtime/RecyclerView/Scroller/ScrollbarEx.cs
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
using System;
|
||||||
|
using PrimeTween;
|
||||||
|
using UnityEngine;
|
||||||
|
using UnityEngine.EventSystems;
|
||||||
|
using UnityEngine.UI;
|
||||||
|
|
||||||
|
namespace AlicizaX.UI.RecyclerView
|
||||||
|
{
|
||||||
|
public class ScrollbarEx : MonoBehaviour, IBeginDragHandler, IEndDragHandler, IPointerEnterHandler, IPointerExitHandler
|
||||||
|
{
|
||||||
|
private RectTransform handle;
|
||||||
|
private Scrollbar scrollbar;
|
||||||
|
|
||||||
|
public Action OnDragEnd;
|
||||||
|
|
||||||
|
private bool dragging;
|
||||||
|
private bool hovering;
|
||||||
|
|
||||||
|
private void Awake()
|
||||||
|
{
|
||||||
|
scrollbar = GetComponent<Scrollbar>();
|
||||||
|
handle = scrollbar.handleRect;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnBeginDrag(PointerEventData eventData)
|
||||||
|
{
|
||||||
|
dragging = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnEndDrag(PointerEventData eventData)
|
||||||
|
{
|
||||||
|
dragging = false;
|
||||||
|
if (!hovering)
|
||||||
|
{
|
||||||
|
if (scrollbar.direction == Scrollbar.Direction.TopToBottom ||
|
||||||
|
scrollbar.direction == Scrollbar.Direction.BottomToTop)
|
||||||
|
{
|
||||||
|
Tween.ScaleX(handle, 1f, 0.2f);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Tween.ScaleY(handle, 1f, 0.2f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
OnDragEnd?.Invoke();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnPointerEnter(PointerEventData eventData)
|
||||||
|
{
|
||||||
|
hovering = true;
|
||||||
|
if (scrollbar.direction == Scrollbar.Direction.TopToBottom ||
|
||||||
|
scrollbar.direction == Scrollbar.Direction.BottomToTop)
|
||||||
|
{
|
||||||
|
Tween.ScaleX(handle, 2f, 0.2f);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Tween.ScaleY(handle, 2f, 0.2f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnPointerExit(PointerEventData eventData)
|
||||||
|
{
|
||||||
|
hovering = false;
|
||||||
|
if (!dragging)
|
||||||
|
{
|
||||||
|
if (scrollbar.direction == Scrollbar.Direction.TopToBottom ||
|
||||||
|
scrollbar.direction == Scrollbar.Direction.BottomToTop)
|
||||||
|
{
|
||||||
|
Tween.ScaleX(handle, 1f, 0.2f);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Tween.ScaleY(handle, 1f, 0.2f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
11
Runtime/RecyclerView/Scroller/ScrollbarEx.cs.meta
Normal file
11
Runtime/RecyclerView/Scroller/ScrollbarEx.cs.meta
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 682cfe39b0fffe544be8d5c11eb369e5
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
245
Runtime/RecyclerView/Scroller/Scroller.cs
Normal file
245
Runtime/RecyclerView/Scroller/Scroller.cs
Normal file
@ -0,0 +1,245 @@
|
|||||||
|
using System.Collections;
|
||||||
|
using UnityEngine;
|
||||||
|
using UnityEngine.EventSystems;
|
||||||
|
|
||||||
|
namespace AlicizaX.UI.RecyclerView
|
||||||
|
{
|
||||||
|
public class Scroller : MonoBehaviour, IScroller, IBeginDragHandler, IEndDragHandler, IDragHandler, IScrollHandler
|
||||||
|
{
|
||||||
|
protected float position;
|
||||||
|
public float Position { get => position; set => position = value; }
|
||||||
|
|
||||||
|
protected float velocity;
|
||||||
|
public float Velocity => velocity;
|
||||||
|
|
||||||
|
protected Direction direction;
|
||||||
|
public Direction Direction
|
||||||
|
{
|
||||||
|
get => direction;
|
||||||
|
set => direction = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 内容所需要大小
|
||||||
|
/// </summary>
|
||||||
|
protected Vector2 contentSize;
|
||||||
|
public Vector2 ContentSize
|
||||||
|
{
|
||||||
|
get => contentSize;
|
||||||
|
set => contentSize = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 所在 View 的真实大小
|
||||||
|
/// </summary>
|
||||||
|
protected Vector2 viewSize;
|
||||||
|
public Vector2 ViewSize
|
||||||
|
{
|
||||||
|
get => viewSize;
|
||||||
|
set => viewSize = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected float scrollSpeed = 1f;
|
||||||
|
public float ScrollSpeed
|
||||||
|
{
|
||||||
|
get => scrollSpeed;
|
||||||
|
set => scrollSpeed = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected float wheelSpeed = 30f;
|
||||||
|
public float WheelSpeed
|
||||||
|
{
|
||||||
|
get => wheelSpeed;
|
||||||
|
set => wheelSpeed = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected bool snap;
|
||||||
|
public bool Snap
|
||||||
|
{
|
||||||
|
get => snap;
|
||||||
|
set => snap = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected ScrollerEvent scrollerEvent = new();
|
||||||
|
protected MoveStopEvent moveStopEvent = new();
|
||||||
|
protected DraggingEvent draggingEvent = new();
|
||||||
|
|
||||||
|
public float MaxPosition => direction == Direction.Vertical ?
|
||||||
|
Mathf.Max(contentSize.y - viewSize.y, 0) :
|
||||||
|
Mathf.Max(contentSize.x - viewSize.x, 0);
|
||||||
|
|
||||||
|
public float ViewLength => direction == Direction.Vertical ? viewSize.y : viewSize.x;
|
||||||
|
|
||||||
|
public ScrollerEvent OnValueChanged { get => scrollerEvent; set => scrollerEvent = value; }
|
||||||
|
|
||||||
|
public MoveStopEvent OnMoveStoped { get => moveStopEvent; set => moveStopEvent = value; }
|
||||||
|
|
||||||
|
public DraggingEvent OnDragging { get => draggingEvent; set => draggingEvent = value; }
|
||||||
|
|
||||||
|
// 停止滑动的时间,但此时并未释放鼠标按键
|
||||||
|
private float dragStopTime = 0f;
|
||||||
|
|
||||||
|
public virtual void ScrollTo(float position, bool smooth = false)
|
||||||
|
{
|
||||||
|
if (position == this.position) return;
|
||||||
|
|
||||||
|
if (!smooth)
|
||||||
|
{
|
||||||
|
this.position = position;
|
||||||
|
OnValueChanged?.Invoke(this.position);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
StopAllCoroutines();
|
||||||
|
StartCoroutine(MoveTo(position));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual void ScrollToRatio(float ratio)
|
||||||
|
{
|
||||||
|
ScrollTo(MaxPosition * ratio, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnBeginDrag(PointerEventData eventData)
|
||||||
|
{
|
||||||
|
OnDragging?.Invoke(true);
|
||||||
|
StopAllCoroutines();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnEndDrag(PointerEventData eventData)
|
||||||
|
{
|
||||||
|
Inertia();
|
||||||
|
Elastic();
|
||||||
|
OnDragging?.Invoke(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnDrag(PointerEventData eventData)
|
||||||
|
{
|
||||||
|
dragStopTime = Time.time;
|
||||||
|
|
||||||
|
velocity = GetDelta(eventData);
|
||||||
|
position += velocity;
|
||||||
|
|
||||||
|
OnValueChanged?.Invoke(position);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnScroll(PointerEventData eventData)
|
||||||
|
{
|
||||||
|
StopAllCoroutines();
|
||||||
|
|
||||||
|
float rate = GetScrollRate() * wheelSpeed;
|
||||||
|
velocity = direction == Direction.Vertical ? -eventData.scrollDelta.y * rate : eventData.scrollDelta.x * rate;
|
||||||
|
position += velocity;
|
||||||
|
|
||||||
|
OnValueChanged?.Invoke(position);
|
||||||
|
|
||||||
|
Elastic();
|
||||||
|
}
|
||||||
|
|
||||||
|
internal virtual float GetDelta(PointerEventData eventData)
|
||||||
|
{
|
||||||
|
float rate = GetScrollRate();
|
||||||
|
return direction == Direction.Vertical ? eventData.delta.y * rate : -eventData.delta.x * rate;
|
||||||
|
}
|
||||||
|
|
||||||
|
private float GetScrollRate()
|
||||||
|
{
|
||||||
|
float rate = 1f;
|
||||||
|
if (position < 0)
|
||||||
|
{
|
||||||
|
rate = Mathf.Max(0, 1 - (Mathf.Abs(position) / ViewLength));
|
||||||
|
}
|
||||||
|
else if (position > MaxPosition)
|
||||||
|
{
|
||||||
|
rate = Mathf.Max(0, 1 - (Mathf.Abs(position - MaxPosition) / ViewLength));
|
||||||
|
}
|
||||||
|
return rate;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 松手时的惯性滑动
|
||||||
|
/// </summary>
|
||||||
|
protected virtual void Inertia()
|
||||||
|
{
|
||||||
|
// 松手时的时间 离 停止滑动的时间 超过一定时间,则认为此次惯性滑动无效
|
||||||
|
if (!snap && (Time.time - dragStopTime) > 0.01f) return;
|
||||||
|
|
||||||
|
if (Mathf.Abs(velocity) > 0.1f)
|
||||||
|
{
|
||||||
|
StopAllCoroutines();
|
||||||
|
StartCoroutine(InertiaTo());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
OnMoveStoped?.Invoke();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 滑动到顶部/底部之后,松手时回弹
|
||||||
|
/// </summary>
|
||||||
|
protected virtual void Elastic()
|
||||||
|
{
|
||||||
|
if (position < 0)
|
||||||
|
{
|
||||||
|
StopAllCoroutines();
|
||||||
|
StartCoroutine(ElasticTo(0));
|
||||||
|
}
|
||||||
|
else if (position > MaxPosition)
|
||||||
|
{
|
||||||
|
StopAllCoroutines();
|
||||||
|
StartCoroutine(ElasticTo(MaxPosition));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
IEnumerator InertiaTo()
|
||||||
|
{
|
||||||
|
float timer = 0f;
|
||||||
|
float p = position;
|
||||||
|
float v = velocity > 0 ? Mathf.Min(velocity, 100) : Mathf.Max(velocity, -100);
|
||||||
|
float duration = snap ? 0.1f : 1f;
|
||||||
|
while (timer < duration)
|
||||||
|
{
|
||||||
|
float y = (float)EaseUtil.EaseOutCirc(timer) * 40;
|
||||||
|
timer += Time.deltaTime;
|
||||||
|
position = p + y * v;
|
||||||
|
|
||||||
|
Elastic();
|
||||||
|
|
||||||
|
OnValueChanged?.Invoke(position);
|
||||||
|
|
||||||
|
yield return new WaitForEndOfFrame();
|
||||||
|
}
|
||||||
|
|
||||||
|
OnMoveStoped?.Invoke();
|
||||||
|
}
|
||||||
|
|
||||||
|
IEnumerator ElasticTo(float targetPos)
|
||||||
|
{
|
||||||
|
yield return ToPosition(targetPos, 7);
|
||||||
|
}
|
||||||
|
|
||||||
|
IEnumerator MoveTo(float targetPos)
|
||||||
|
{
|
||||||
|
yield return ToPosition(targetPos, scrollSpeed);
|
||||||
|
}
|
||||||
|
|
||||||
|
IEnumerator ToPosition(float targetPos, float speed)
|
||||||
|
{
|
||||||
|
float startPos = position;
|
||||||
|
float time = Time.deltaTime;
|
||||||
|
while (Mathf.Abs(targetPos - position) > 0.1f)
|
||||||
|
{
|
||||||
|
position = Mathf.Lerp(startPos, targetPos, time * speed);
|
||||||
|
OnValueChanged?.Invoke(position);
|
||||||
|
|
||||||
|
time += Time.deltaTime;
|
||||||
|
|
||||||
|
yield return new WaitForEndOfFrame();
|
||||||
|
}
|
||||||
|
|
||||||
|
position = targetPos;
|
||||||
|
OnValueChanged?.Invoke(position);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
11
Runtime/RecyclerView/Scroller/Scroller.cs.meta
Normal file
11
Runtime/RecyclerView/Scroller/Scroller.cs.meta
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 7b7de4cb3a1546e4a9ade6b8dbf8af92
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
8
Runtime/RecyclerView/ViewHolder.meta
Normal file
8
Runtime/RecyclerView/ViewHolder.meta
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 194b0a1eb83729844850e1873dbc0118
|
||||||
|
folderAsset: yes
|
||||||
|
DefaultImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
8
Runtime/RecyclerView/ViewHolder/Example.meta
Normal file
8
Runtime/RecyclerView/ViewHolder/Example.meta
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 72a222ebb6ae56346b65b78fa3d60143
|
||||||
|
folderAsset: yes
|
||||||
|
DefaultImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
19
Runtime/RecyclerView/ViewHolder/Example/SimpleViewHolder.cs
Normal file
19
Runtime/RecyclerView/ViewHolder/Example/SimpleViewHolder.cs
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
using AlicizaX.UI.RecyclerView;
|
||||||
|
using TMPro;
|
||||||
|
|
||||||
|
public sealed class SimpleViewHolder : ViewHolder
|
||||||
|
{
|
||||||
|
private TMP_Text simpleText;
|
||||||
|
|
||||||
|
public override void FindUI()
|
||||||
|
{
|
||||||
|
simpleText = transform.Find("SimpleText").GetComponent<TMP_Text>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void BindViewData<T>(T data)
|
||||||
|
{
|
||||||
|
string text = data as string;
|
||||||
|
|
||||||
|
simpleText.text = text;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 846ae5a2cd8b619459ecbadfc91e58f1
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
59
Runtime/RecyclerView/ViewHolder/ViewHolder.cs
Normal file
59
Runtime/RecyclerView/ViewHolder/ViewHolder.cs
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
using System;
|
||||||
|
using UnityEngine;
|
||||||
|
using UnityEngine.UI;
|
||||||
|
|
||||||
|
namespace AlicizaX.UI.RecyclerView
|
||||||
|
{
|
||||||
|
public abstract class ViewHolder : MonoBehaviour
|
||||||
|
{
|
||||||
|
private bool isStarted;
|
||||||
|
|
||||||
|
private RectTransform rectTransform;
|
||||||
|
public RectTransform RectTransform
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (rectTransform == null)
|
||||||
|
{
|
||||||
|
rectTransform = GetComponent<RectTransform>();
|
||||||
|
}
|
||||||
|
return rectTransform;
|
||||||
|
}
|
||||||
|
private set
|
||||||
|
{
|
||||||
|
rectTransform = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Name { get; set; }
|
||||||
|
public int Index { get; set; }
|
||||||
|
|
||||||
|
public Vector2 SizeDelta => RectTransform.sizeDelta;
|
||||||
|
|
||||||
|
public virtual void OnStart()
|
||||||
|
{
|
||||||
|
if (!isStarted)
|
||||||
|
{
|
||||||
|
isStarted = true;
|
||||||
|
FindUI();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual void OnStop() { }
|
||||||
|
|
||||||
|
public abstract void FindUI();
|
||||||
|
|
||||||
|
public abstract void BindViewData<T>(T data);
|
||||||
|
|
||||||
|
public virtual void BindItemClick<T>(T data, Action<T> action)
|
||||||
|
{
|
||||||
|
if (TryGetComponent(out Button button))
|
||||||
|
{
|
||||||
|
button.onClick.RemoveAllListeners();
|
||||||
|
button.onClick.AddListener(() => action?.Invoke(data));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual void BindChoiceState(bool state) { }
|
||||||
|
}
|
||||||
|
}
|
11
Runtime/RecyclerView/ViewHolder/ViewHolder.cs.meta
Normal file
11
Runtime/RecyclerView/ViewHolder/ViewHolder.cs.meta
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 9d43d46f3524d814fbb2fb2f03e3c610
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
8
Runtime/RecyclerView/ViewProvider.meta
Normal file
8
Runtime/RecyclerView/ViewProvider.meta
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 789e3dbb330a44f42b73d50f128888e4
|
||||||
|
folderAsset: yes
|
||||||
|
DefaultImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
63
Runtime/RecyclerView/ViewProvider/MixedViewProvider.cs
Normal file
63
Runtime/RecyclerView/ViewProvider/MixedViewProvider.cs
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace AlicizaX.UI.RecyclerView
|
||||||
|
{
|
||||||
|
public class MixedViewProvider : ViewProvider
|
||||||
|
{
|
||||||
|
[SerializeField] private ViewHolder chatLeftViewHolder;
|
||||||
|
[SerializeField] private ViewHolder chatRightViewHolder;
|
||||||
|
|
||||||
|
private IMixedObjectPool<ViewHolder> objectPool;
|
||||||
|
private Dictionary<string, ViewHolder> dict = new();
|
||||||
|
|
||||||
|
public MixedViewProvider(RecyclerView recyclerView, ViewHolder[] templates) : base(recyclerView, templates)
|
||||||
|
{
|
||||||
|
foreach (var template in templates)
|
||||||
|
{
|
||||||
|
dict[template.name] = template;
|
||||||
|
}
|
||||||
|
|
||||||
|
UnityMixedComponentFactory<ViewHolder> factory = new(dict, recyclerView.Content);
|
||||||
|
objectPool = new MixedObjectPool<ViewHolder>(factory);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override ViewHolder GetTemplate(string viewName)
|
||||||
|
{
|
||||||
|
if (templates == null || templates.Length == 0)
|
||||||
|
{
|
||||||
|
throw new NullReferenceException("ViewProvider templates can not null or empty.");
|
||||||
|
}
|
||||||
|
return dict[viewName];
|
||||||
|
}
|
||||||
|
|
||||||
|
public override ViewHolder[] GetTemplates()
|
||||||
|
{
|
||||||
|
if (templates == null || templates.Length == 0)
|
||||||
|
{
|
||||||
|
throw new NullReferenceException("ViewProvider templates can not null or empty.");
|
||||||
|
}
|
||||||
|
return dict.Values.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override ViewHolder Allocate(string viewName)
|
||||||
|
{
|
||||||
|
var viewHolder = objectPool.Allocate(viewName);
|
||||||
|
viewHolder.gameObject.SetActive(true);
|
||||||
|
return viewHolder;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Free(string viewName, ViewHolder viewHolder)
|
||||||
|
{
|
||||||
|
objectPool.Free(viewName, viewHolder);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Reset()
|
||||||
|
{
|
||||||
|
Clear();
|
||||||
|
objectPool.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
11
Runtime/RecyclerView/ViewProvider/MixedViewProvider.cs.meta
Normal file
11
Runtime/RecyclerView/ViewProvider/MixedViewProvider.cs.meta
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: ef7ee3380d8e82c4f832f1e2535fe795
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
51
Runtime/RecyclerView/ViewProvider/SimpleViewProvider.cs
Normal file
51
Runtime/RecyclerView/ViewProvider/SimpleViewProvider.cs
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace AlicizaX.UI.RecyclerView
|
||||||
|
{
|
||||||
|
public sealed class SimpleViewProvider : ViewProvider
|
||||||
|
{
|
||||||
|
private readonly IObjectPool<ViewHolder> objectPool;
|
||||||
|
|
||||||
|
public SimpleViewProvider(RecyclerView recyclerView, ViewHolder[] templates) : base(recyclerView, templates)
|
||||||
|
{
|
||||||
|
UnityComponentFactory<ViewHolder> factory = new(GetTemplate(), recyclerView.Content);
|
||||||
|
objectPool = new ObjectPool<ViewHolder>(factory, 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override ViewHolder GetTemplate(string viewName = "")
|
||||||
|
{
|
||||||
|
if (templates == null || templates.Length == 0)
|
||||||
|
{
|
||||||
|
throw new NullReferenceException("ViewProvider templates can not null or empty.");
|
||||||
|
}
|
||||||
|
return templates[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
public override ViewHolder[] GetTemplates()
|
||||||
|
{
|
||||||
|
if (templates == null || templates.Length == 0)
|
||||||
|
{
|
||||||
|
throw new NullReferenceException("ViewProvider templates can not null or empty.");
|
||||||
|
}
|
||||||
|
return templates;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override ViewHolder Allocate(string viewName)
|
||||||
|
{
|
||||||
|
var viewHolder = objectPool.Allocate();
|
||||||
|
viewHolder.gameObject.SetActive(true);
|
||||||
|
return viewHolder;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Free(string viewName, ViewHolder viewHolder)
|
||||||
|
{
|
||||||
|
objectPool.Free(viewHolder);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Reset()
|
||||||
|
{
|
||||||
|
Clear();
|
||||||
|
objectPool.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
11
Runtime/RecyclerView/ViewProvider/SimpleViewProvider.cs.meta
Normal file
11
Runtime/RecyclerView/ViewProvider/SimpleViewProvider.cs.meta
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: f6100883160a6b841a1d7084de2dcd76
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
131
Runtime/RecyclerView/ViewProvider/ViewProvider.cs
Normal file
131
Runtime/RecyclerView/ViewProvider/ViewProvider.cs
Normal file
@ -0,0 +1,131 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace AlicizaX.UI.RecyclerView
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 提供和管理 ViewHolder
|
||||||
|
/// </summary>
|
||||||
|
public abstract class ViewProvider
|
||||||
|
{
|
||||||
|
private readonly List<ViewHolder> viewHolders = new();
|
||||||
|
|
||||||
|
public IAdapter Adapter { get; set; }
|
||||||
|
public LayoutManager LayoutManager { get; set; }
|
||||||
|
|
||||||
|
public List<ViewHolder> ViewHolders => viewHolders;
|
||||||
|
|
||||||
|
protected RecyclerView recyclerView;
|
||||||
|
protected ViewHolder[] templates;
|
||||||
|
|
||||||
|
public ViewProvider(RecyclerView recyclerView, ViewHolder[] templates)
|
||||||
|
{
|
||||||
|
this.recyclerView = recyclerView;
|
||||||
|
this.templates = templates;
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract ViewHolder GetTemplate(string viewName);
|
||||||
|
|
||||||
|
public abstract ViewHolder[] GetTemplates();
|
||||||
|
|
||||||
|
public abstract ViewHolder Allocate(string viewName);
|
||||||
|
|
||||||
|
public abstract void Free(string viewName, ViewHolder viewHolder);
|
||||||
|
|
||||||
|
public abstract void Reset();
|
||||||
|
|
||||||
|
public void CreateViewHolder(int index)
|
||||||
|
{
|
||||||
|
for (int i = index; i < index + LayoutManager.Unit; i++)
|
||||||
|
{
|
||||||
|
if (i > Adapter.GetItemCount() - 1) break;
|
||||||
|
|
||||||
|
string viewName = Adapter.GetViewName(i);
|
||||||
|
var viewHolder = Allocate(viewName);
|
||||||
|
viewHolder.OnStart();
|
||||||
|
viewHolder.Name = viewName;
|
||||||
|
viewHolder.Index = i;
|
||||||
|
viewHolders.Add(viewHolder);
|
||||||
|
|
||||||
|
LayoutManager.Layout(viewHolder, i);
|
||||||
|
Adapter.OnBindViewHolder(viewHolder, i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RemoveViewHolder(int index)
|
||||||
|
{
|
||||||
|
for (int i = index; i < index + LayoutManager.Unit; i++)
|
||||||
|
{
|
||||||
|
if (i > Adapter.GetItemCount() - 1) break;
|
||||||
|
|
||||||
|
int viewHolderIndex = GetViewHolderIndex(i);
|
||||||
|
|
||||||
|
if (viewHolderIndex < 0 || viewHolderIndex >= viewHolders.Count) return;
|
||||||
|
|
||||||
|
var viewHolder = viewHolders[viewHolderIndex];
|
||||||
|
viewHolders.RemoveAt(viewHolderIndex);
|
||||||
|
viewHolder.OnStop();
|
||||||
|
Free(viewHolder.Name, viewHolder);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 根据数据的下标获取对应的 ViewHolder
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="index">数据的下标</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public ViewHolder GetViewHolder(int index)
|
||||||
|
{
|
||||||
|
foreach (var viewHolder in viewHolders)
|
||||||
|
{
|
||||||
|
if (viewHolder.Index == index)
|
||||||
|
{
|
||||||
|
return viewHolder;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 根据数据的下标获取 ViewHolder 的下标
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="index">数据的下标</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public int GetViewHolderIndex(int index)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < viewHolders.Count; i++)
|
||||||
|
{
|
||||||
|
if (viewHolders[i].Index == index)
|
||||||
|
{
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Clear()
|
||||||
|
{
|
||||||
|
foreach (var viewHolder in viewHolders)
|
||||||
|
{
|
||||||
|
Free(viewHolder.Name, viewHolder);
|
||||||
|
}
|
||||||
|
viewHolders.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 计算 ViewHolder 的尺寸
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="index"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public Vector2 CalculateViewSize(int index)
|
||||||
|
{
|
||||||
|
Vector2 size = GetTemplate(Adapter.GetViewName(index)).SizeDelta;
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int GetItemCount()
|
||||||
|
{
|
||||||
|
return Adapter == null ? 0 : Adapter.GetItemCount();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
11
Runtime/RecyclerView/ViewProvider/ViewProvider.cs.meta
Normal file
11
Runtime/RecyclerView/ViewProvider/ViewProvider.cs.meta
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: b1c2cd7d2729fb4488ffd0e25e0fbf43
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
Loading…
Reference in New Issue
Block a user