modify
This commit is contained in:
parent
c9bc460ad7
commit
9d941a179d
@ -6,7 +6,8 @@
|
||||
"GUID:5553d74549d54e74cb548b3ab58a8483",
|
||||
"GUID:75b6f2078d190f14dbda4a5b747d709c",
|
||||
"GUID:a19b414bea3b97240a91aeab9a8eab36",
|
||||
"GUID:198eb6af143bbc4488e2779d96697e06"
|
||||
"GUID:198eb6af143bbc4488e2779d96697e06",
|
||||
"GUID:80ecb87cae9c44d19824e70ea7229748"
|
||||
],
|
||||
"includePlatforms": [],
|
||||
"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