暂存翻页容器相关内容

This commit is contained in:
2025-07-25 07:52:00 +08:00
parent 49f616f4c7
commit 3a4027561a
3 changed files with 507 additions and 88 deletions
+3 -3
View File
@@ -10,7 +10,7 @@ namespace XericUI.Core.Vessel
/// <param name="obj"></param>
/// <typeparam name="T0"></typeparam>
/// <returns></returns>
public static bool IsValid<T0>(this ObjectVessel<T0> obj)
public static bool IsValidVessel<T0>(this ObjectVessel<T0> obj)
where T0 : Object
=> obj != null && obj.Target != null;
/// <summary>
@@ -20,7 +20,7 @@ namespace XericUI.Core.Vessel
/// <typeparam name="T0"></typeparam>
/// <typeparam name="T1"></typeparam>
/// <returns></returns>
public static bool IsValid<T0, T1>(this ObjectVessel<T0, T1> obj)
public static bool IsValidVessel<T0, T1>(this ObjectVessel<T0, T1> obj)
where T0 : Object
where T1 : Object
=> obj != null && obj.Target0 != null && obj.Target1 != null;
@@ -32,7 +32,7 @@ namespace XericUI.Core.Vessel
/// <typeparam name="T1"></typeparam>
/// <typeparam name="T2"></typeparam>
/// <returns></returns>
public static bool IsValid<T0, T1, T2>(this ObjectVessel<T0, T1, T2> obj)
public static bool IsValidVessel<T0, T1, T2>(this ObjectVessel<T0, T1, T2> obj)
where T0 : Object
where T1 : Object
where T2 : Object
+229 -29
View File
@@ -1,9 +1,13 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using JetBrains.Annotations;
using Sirenix.OdinInspector;
using UnityEngine;
using UnityEngine.UI;
using XericLibrary.Runtime.MacroLibrary;
using XericUI.Core.Vessel;
using Component = UnityEngine.Component;
namespace XericUI.FlipPage
{
@@ -16,6 +20,17 @@ namespace XericUI.FlipPage
{
#region 界面属性
/// <summary>
/// 候选页数量
/// </summary>
#if ODIN_INSPECTOR
[LabelText("候选页按最多显示")]
[BoxGroup("设定", centerLabel: true)]
#endif
[Tooltip("候选页按最多数量展示")]
[SerializeField]
protected bool inMaximumCandidatesNumber = true;
/// <summary>
/// 首页按钮
/// </summary>
@@ -52,20 +67,38 @@ namespace XericUI.FlipPage
/// 总页面数量文本
/// </summary>
#if ODIN_INSPECTOR
[Space(2)]
[LabelText("总页面数量文本")] [BoxGroup("组件")]
#endif
public ObjectVessel<TMPro.TMP_Text> labelTextTotalPageCount =
new ObjectVessel<TMPro.TMP_Text>();
/// <summary>
/// 总页面数量文本
/// 总页面数量文本格式化
/// </summary>
#if ODIN_INSPECTOR
[LabelText("总页面数量文本")] [BoxGroup("组件")]
[LabelText("总页面数量文本格式化")] [BoxGroup("设定")]
#endif
public string totalPageCountFormat = "共$1页";
/// <summary>
/// 页面索引文本格式化
/// </summary>
#if ODIN_INSPECTOR
[LabelText("页面索引文本格式化")] [BoxGroup("设定")]
#endif
public string pageIndexFormat = "$1";
/// <summary>
/// 页面选择组件列表
/// </summary>
#if ODIN_INSPECTOR
[LabelText("页面选择组件列表")] [BoxGroup("组件")]
// [ListDrawerSettings(ShowIndexLabels = true)]
#endif
public List<ObjectVessel<Button, TMPro.TMP_Text>> pageSelectButton =
new List<ObjectVessel<Button, TMPro.TMP_Text>>();
[Tooltip("首个组件应当作为主要的节点管理")]
public List<ObjectVessel<Selectable, TMPro.TMP_Text>> pageSelectButton =
new List<ObjectVessel<Selectable, TMPro.TMP_Text>>();
/// <summary>
/// 页面跳转输入框
@@ -73,8 +106,7 @@ namespace XericUI.FlipPage
#if ODIN_INSPECTOR
[LabelText("页面跳转输入框")] [BoxGroup("组件")]
#endif
public ObjectVessel<TMPro.TMP_InputField> inputFieldFlipPageIndex =
new ObjectVessel<TMPro.TMP_InputField>();
public ObjectVessel<TMPro.TMP_InputField> inputFieldFlipPageIndex;
/// <summary>
/// 翻页到索引按钮池
@@ -86,26 +118,114 @@ namespace XericUI.FlipPage
#endregion
#region 字段属性
protected ToggleGroup ToggleGroup;
#endregion
#region 类内类型
/// <summary>
/// 页面与组件信息
/// </summary>
public class PageComponentInfo : PageInfo
{
/// <summary>
/// 页面翻页按钮
/// </summary>
#if ODIN_INSPECTOR
[LabelText("页面翻页按钮")]
#endif
[CanBeNull]
internal ObjectVessel<Selectable, TMPro.TMP_Text> pageIndexSelectable;
protected internal override void OnPageVisible(bool quickVisit)
{
if (quickVisit)
return;
// 自动补全外部交互动画
if (pageIndexSelectable.IsValidVessel() &&
pageIndexSelectable.Target0 is Toggle toggle &&
!toggle.isOn)
{
toggle.SetIsOnWithoutNotify(true);
}
}
protected internal override void OnPageInvisible(bool quickVisit)
{
}
}
/// <summary>
/// 项目与组件信息
/// </summary>
public class ItemComponentInfo : ItemInfo
{
protected internal override void OnItemVisible()
{
}
protected internal override void OnItemInvisible()
{
}
}
#endregion
#region 生命周期
#if UNITY_EDITOR
protected override void OnValidate()
{
base.OnValidate();
// pageButtonPool.Prefab.GetComponent<Selectable>();
}
#endif
protected override void Awake()
{
base.Awake();
if (buttonStartPage.IsValid()) // 翻到开头
if (buttonStartPage.IsValidVessel()) // 翻到开头
buttonStartPage.Target.onClick.AddListener(FlipPageToStart);
if (buttonPreviousPage.IsValid()) // 翻到上一页
if (buttonPreviousPage.IsValidVessel()) // 翻到上一页
buttonPreviousPage.Target.onClick.AddListener(FlipPageToPrevious);
if (buttonNextPage.IsValid()) // 翻到下一页
if (buttonNextPage.IsValidVessel()) // 翻到下一页
buttonNextPage.Target.onClick.AddListener(FlipPageToNext);
if (buttoneEndPage.IsValid()) // 翻到结尾
if (buttoneEndPage.IsValidVessel()) // 翻到结尾
buttoneEndPage.Target.onClick.AddListener(FlipPageToEnd);
if (inputFieldFlipPageIndex.IsValidVessel()) // 翻到指定
inputFieldFlipPageIndex.Target.onValueChanged.AddListener(s => FlipPage(s));
pageButtonPool.Init();
// 根据组件类型创建相关内容
var prefabSelectable = pageButtonPool.Prefab.GetComponent<Selectable>();
switch (prefabSelectable)
{
// 单选项就创建但选项组
case Toggle toggle:
ToggleGroup = transform.GetComponentAnyway<ToggleGroup>();
break;
case Button button:
break;
default:
break;
}
}
private void Update()
protected override void OnFlipPage()
{
if (labelTextTotalPageCount.IsValid()) // 页数
labelTextTotalPageCount.Target.text = PageInfos.Count.ToString();
base.OnFlipPage();
// 刷新所有成员的层级顺序
for (int i = 0; i < pageSelectButton.Count; i++)
{
pageSelectButton[i].Target0.transform.SetSiblingIndex(i);
}
}
public override void FlipPage(int pageIndex, bool forceRefresh = false)
@@ -114,10 +234,88 @@ namespace XericUI.FlipPage
}
public override void AddPage(PageInfo pageInfo)
public override void AddPageFirst(PageInfo pageInfo)
{
base.AddPage(pageInfo);
pageSelectButton.Add(GetPageIndexButton());
if (pageInfo is not PageComponentInfo componentPageInfo)
{
Debug.LogError($"页面容器{currentPage}不支持当前管理器,其必须由{nameof(FlipPageComponentManager)}.{nameof(PageComponentInfo)}继承/声名,否则无法支持后续功能", this);
return;
}
AddPageIndexButton(componentPageInfo);
base.AddPageFirst(pageInfo);
RefreshLabelTextTotalPageCount();
RefreshPageIndeButtonState();
}
public override void AddPageLast(PageInfo pageInfo)
{
if (pageInfo is not PageComponentInfo componentPageInfo)
{
Debug.LogError($"页面容器{currentPage}不支持当前管理器,其必须由{nameof(FlipPageComponentManager)}.{nameof(PageComponentInfo)}继承/声名,否则无法支持后续功能", this);
return;
}
AddPageIndexButton(componentPageInfo);
base.AddPageLast(pageInfo);
RefreshLabelTextTotalPageCount();
RefreshPageIndeButtonState();
}
public override void RemovePage(int pageIndex)
{
base.RemovePage(pageIndex);
pageButtonPool.DirectlyRelease(pageSelectButton[pageIndex].Target0.gameObject);
pageSelectButton.RemoveAt(pageIndex);
RefreshPageIndeButtonState();
}
/// <summary>
/// 在页面上添加按钮
/// </summary>
/// <param name="pageInfo"></param>
private void AddPageIndexButton(PageComponentInfo pageInfo)
{
var pageIndexSelectable = GetPageIndexButton();
pageInfo.pageIndexSelectable = pageIndexSelectable;
}
/// <summary>
/// 刷新所有页面按钮的可见性,索引
/// </summary>
private void RefreshPageIndeButtonState()
{
var min = currentPageIndex - candidatesNumber / 2;
var max = currentPageIndex + candidatesNumber / 2;
for (int i = 0; i < pageSelectButton.Count; i++)
{
pageSelectButton[i].Target0.transform.SetSiblingIndex(i);
// 显示数量控制
if (inMaximumCandidatesNumber)
pageSelectButton[i].Target0.gameObject.SetActive(min <= i && i <= max);
else
pageSelectButton[i].Target0.gameObject.SetActive(min < i && i < max);
// 更新索引
if (pageSelectButton[i].Target0.gameObject.activeSelf)
{
pageSelectButton[i].Target1.text = MacroRegex.RegexReplace(pageIndexFormat, i);
}
}
}
/// <summary>
/// 更新总页数
/// </summary>
private void RefreshLabelTextTotalPageCount()
{
if (labelTextTotalPageCount.IsValidVessel()) // 总页数更新
labelTextTotalPageCount.Target.text = MacroRegex.RegexReplace(totalPageCountFormat, PageInfos.Count.ToString());
}
#endregion
@@ -128,24 +326,26 @@ namespace XericUI.FlipPage
/// 获取页面索引按钮
/// </summary>
/// <returns></returns>
protected virtual ObjectVessel<Button, TMPro.TMP_Text> GetPageIndexButton()
protected virtual ObjectVessel<Selectable, TMPro.TMP_Text> GetPageIndexButton()
{
var obj = pageButtonPool.Get();
var button = obj.GetComponent<Button>() ?? obj.GetComponentInChildren<Button>();
var selectable = obj.GetComponent<Selectable>() ?? obj.GetComponentInChildren<Selectable>();
var text = obj.GetComponent<TMPro.TMP_Text>() ?? obj.GetComponentInChildren<TMPro.TMP_Text>();
// var vessel = new ObjectVessel<Button, TMPro.TMP_Text>(button, text);
// return vessel;
return null;
var vessel = new ObjectVessel<Selectable, TMPro.TMP_Text>(selectable, text);
return vessel;
}
/// <summary>
/// 释放页面索引按钮
/// </summary>
protected virtual void ReleasePageIndexButton(Component comp)
protected virtual void ListenPageSelectable(ObjectVessel<Selectable, TMPro.TMP_Text> selectableVessel, Action<int> selectPageAction)
{
pageButtonPool.IndirectRelease(comp);
if (selectableVessel.Target0 is Button button)
{
button.onClick.AddListener(() =>
{
selectPageAction?.Invoke(currentPageIndex);
});
}
}
#endregion
}
}
+275 -56
View File
@@ -10,19 +10,14 @@ using XericUI.Core.Vessel;
namespace XericUI.FlipPage
{
public delegate void PageIndexAction(int pageIndex, int pageOffset, int totalPageCount);
/// <summary>
/// 翻页管理器
/// </summary>
/// <code>
/// 翻页允许按页,按项目进行管理。
/// </code>
public class XericFlipPageIndexVesselBase : XericUIBehaciour
{
#region 委托事件
public event PageIndexAction OnPageFlip;
#endregion
#region 设定参数
/// <summary>
@@ -32,30 +27,123 @@ namespace XericUI.FlipPage
[LabelText("候选页数量")]
[BoxGroup("设定", centerLabel: true)]
#endif
[Tooltip("最大同时呈现的页面选择器数量")]
[SerializeField]
private int candidatesNumber = 1;
protected float candidatesNumber = 1;
/// <summary>
/// 翻页可以跨越索引边接(循环索引)
/// </summary>
#if ODIN_INSPECTOR
[LabelText("翻页时允许跨越索引循环")]
[BoxGroup("设定", centerLabel: true)]
[BoxGroup("设定")]
#endif
[SerializeField]
private bool canPassIndexBoundary;
protected bool canPassIndexBoundary;
/// <summary>
/// 页面默认最大项目数量
/// </summary>
#if ODIN_INSPECTOR
[LabelText("单个页面内默认最多项目数量")]
[BoxGroup("设定")]
#endif
[SerializeField]
protected int defaultItemCountPerPage = -1;
#endregion
#region 类内类型
/// <summary>
/// 页面信息
/// </summary>
[Serializable]
public class PageInfo
public abstract class PageInfo
{
#region 字段属性
/// <summary>
/// 页面名称
/// </summary>
#if ODIN_INSPECTOR
[LabelText("页面名称")]
#endif
public string Name = DateTime.Now.Ticks.ToString();
public string name = DateTime.Now.Ticks.ToString();
/// <summary>
/// 页面内项目列表
/// </summary>
#if ODIN_INSPECTOR
[LabelText("页面项目信息")]
#endif
public List<ItemInfo> itemInfos = new List<ItemInfo>();
/// <summary>
/// 从零开始计数的页面下标
/// </summary>
#if ODIN_INSPECTOR
[LabelText("页面索引[0, n]")]
#endif
public int pageNumber { get; internal set; }
#endregion
#region 生命周期
/// <summary>
/// 页面显示时
/// </summary>
/// <param name="quickVisit">快速浏览(用于区分动画和逻辑,为真时,不应当在动画事件后继续加载数据)</param>
protected internal abstract void OnPageVisible(bool quickVisit);
/// <summary>
/// 页面隐藏时
/// </summary>
/// <param name="quickVisit">快速浏览(用于区分动画和逻辑,为真时,不应当在动画事件后继续加载数据)</param>
protected internal abstract void OnPageInvisible(bool quickVisit);
#endregion
public override string ToString()
{
return $"[{nameof(PageInfo)}] ({name})";
}
}
/// <summary>
/// 项目信息
/// </summary>
[Serializable]
public abstract class ItemInfo
{
#region 字段属性
#if ODIN_INSPECTOR
[LabelText("项目名称")]
#endif
public string name = DateTime.Now.Ticks.ToString();
#endregion
#region 生命周期
/// <summary>
/// 当项目显示时
/// </summary>
protected internal abstract void OnItemVisible();
/// <summary>
/// 当项目隐藏时
/// </summary>
protected internal abstract void OnItemInvisible();
#endregion
public override string ToString()
{
return $"[{nameof(ItemInfo)}] ({name})";
}
}
#endregion
@@ -63,19 +151,40 @@ namespace XericUI.FlipPage
#region 字段属性
/// <summary>
/// 当前页面
/// 当前选中的页面索引
/// </summary>
private int _currentPageIndex = 0;
#if ODIN_INSPECTOR
[LabelText("当前选中页面索引")]
#endif
[SerializeField]
protected int currentPageIndex = 0;
/// <summary>
/// 所有页面
/// </summary>
private LinkedList<PageInfo> _pageInfos = new LinkedList<PageInfo>();
/// <summary>
/// 所有列表项目
/// </summary>
private HashSet<ItemInfo> _itemInfos = new HashSet<ItemInfo>();
/// <summary>
/// 当前选中页面节点
/// </summary>
public LinkedListNode<PageInfo> currentPageNode = null;
/// <summary>
/// 所有页面
/// </summary>
public LinkedList<PageInfo> PageInfos => _pageInfos;
/// <summary>
/// 当前选中页面内容
/// </summary>
public PageInfo currentPage => currentPageNode.Value;
#endregion
#region 生命周期
@@ -88,17 +197,40 @@ namespace XericUI.FlipPage
protected override void Start()
{
base.Start();
FlipPage(_currentPageIndex);
FlipPage(currentPageIndex);
}
#endregion
#region 翻页管理
/// <summary>
/// 当发生翻页时(这会在整个翻页动作流程的最后执行)
/// </summary>
protected virtual void OnFlipPage()
{
}
/// <summary>
/// 通过指令筛选页面
/// </summary>
/// <param name="pageNumber"></param>
/// <param name="forceRefresh"></param>
public virtual void FlipPage(string pageNumber, bool forceRefresh = false)
{
if (float.TryParse(pageNumber, out var index))
{
FlipPage((int)index, forceRefresh);
}
}
/// <summary>
/// 翻到指定页
/// </summary>
/// <param name="pageIndex"></param>
/// <param name="quickSwitchPage">快速翻页,</param>
/// <param name="forceRefresh"></param>
public virtual void FlipPage(int pageIndex, bool forceRefresh = false)
{
@@ -109,20 +241,54 @@ namespace XericUI.FlipPage
else
pageIndex = MacroMath.PMod(pageIndex, _pageInfos.Count - 1);
// 检查翻页数量有效
if (!forceRefresh && _currentPageIndex == pageIndex)
if (!forceRefresh && currentPageIndex == pageIndex)
return;
// 翻页计算
var pageIndexOffset = _currentPageIndex - pageIndex;
var lastPageIndex = _currentPageIndex;
_currentPageIndex = pageIndex;
try
var pageIndexOffset = pageIndex - currentPageIndex;
var lastPageIndex = currentPageIndex;
// 如果节点不存在,通过索引寻找
if (currentPageNode == null)
currentPageNode = _pageInfos.GetOrderNode().LandedIndex(currentPageIndex);
// 如果索引偏移,逐个移动
else if (pageIndexOffset > 0)
{
// 翻页事件委托
OnPageFlip?.Invoke(pageIndex, pageIndexOffset, _pageInfos.Count);
for (var i = 0; i < pageIndexOffset; i++)
{
var next = currentPageNode!.Next;
SwitchPage(next, _pageInfos.Last, i + 1 == pageIndexOffset);
}
}
catch (Exception e)
else if (pageIndexOffset < 0)
{
Debug.LogError($"翻页时委托错误已捕获\n{e.Message}\n{e.Data}");
for (var i = 0; i > pageIndexOffset; i--)
{
var previa = currentPageNode!.Previous;
SwitchPage(previa, _pageInfos.First, i - 1 == pageIndexOffset);
}
}
// 没有翻动有效页,退出
else
return;
currentPageIndex = pageIndex;
OnFlipPage();
// 翻页
void SwitchPage(LinkedListNode<PageInfo> newPageNode, LinkedListNode<PageInfo> defualtPageNode, bool quickPage)
{
if (newPageNode != null)
{
currentPage.OnPageInvisible(quickPage);
currentPageNode = newPageNode;
currentPage.OnPageVisible(quickPage);
}
if (canPassIndexBoundary)
{
currentPage.OnPageInvisible(quickPage);
currentPageNode = defualtPageNode;
currentPage.OnPageVisible(quickPage);
}
}
}
@@ -142,7 +308,7 @@ namespace XericUI.FlipPage
[HorizontalGroup("翻页"), Button("翻到上一页")]
#endif
public void FlipPageToPrevious()
=> FlipPage(_currentPageIndex - 1);
=> FlipPage(currentPageIndex - 1);
/// <summary>
/// 翻到下一页
@@ -151,7 +317,7 @@ namespace XericUI.FlipPage
[HorizontalGroup("翻页"), Button("翻到下一页")]
#endif
public void FlipPageToNext()
=> FlipPage(_currentPageIndex + 1);
=> FlipPage(currentPageIndex + 1);
/// <summary>
/// 翻到结尾
@@ -162,58 +328,111 @@ namespace XericUI.FlipPage
public void FlipPageToEnd()
=> FlipPage(_pageInfos.Count - 1);
/// <summary>
/// 添加页面
/// 添加页面在开头
/// </summary>
public virtual void AddPage(PageInfo pageInfo)
public virtual void AddPageFirst(PageInfo pageInfo)
{
if (pageInfo == null)
{
Debug.LogError($"不能添加一个空的页面内容到管理器中!", this);
return;
}
// 添加页面
_pageInfos.AddFirst(pageInfo);
// 添加页面下标
pageInfo.pageNumber = 0;
// 更新后面页面的索引
RefreshAllPageNumber();
// 刷新页面
FlipPage(currentPageIndex, true);
}
/// <summary>
/// 添加页面在结尾
/// </summary>
public virtual void AddPageLast(PageInfo pageInfo)
{
if (pageInfo == null)
{
Debug.LogError($"不能添加一个空的页面内容到管理器中!", this);
return;
}
// 添加页面下标
pageInfo.pageNumber = _pageInfos.Count;
// 添加页面
_pageInfos.AddLast(pageInfo);
// 刷新页面
FlipPage(_currentPageIndex, true);
FlipPage(currentPageIndex, true);
}
/// <summary>
/// 移除页面
/// </summary>
public void RemovePage(int pageIndex)
public virtual void RemovePage(int pageIndex)
{
RemovePage(IndexOfPage(pageIndex));
}
/// <summary>
/// 移除页面
/// </summary>
/// <param name="pageRef"></param>
public void RemovePage(PageInfo pageRef)
{
_pageInfos.Remove(pageRef);
if (0 > pageIndex || pageIndex > _pageInfos.Count)
{
Debug.LogError($"不能移除不在范围内的索引{pageIndex}!", this);
return;
}
// 获取页面节点
var page = _pageInfos.GetOrderNode().LandedIndex(pageIndex);
// 更新后面页面的索引
var tempPage = page;
var tempPageNumber = page.Value.pageNumber;
while (tempPage.Next != null)
{
tempPage = tempPage.Next;
tempPage.Value.pageNumber = tempPageNumber++;
}
// 如果移除的是当前页面,执行关闭操作
if (currentPageNode == page)
{
page.Value.OnPageInvisible(true);
currentPageNode = null;
}
_pageInfos.Remove(page);
// 刷新页面
FlipPage(_currentPageIndex, true);
FlipPage(currentPageIndex, true);
}
/// <summary>
/// 清除所有页面
/// </summary>
public virtual void CleanPage()
{
_pageInfos.Clear();
currentPageNode = null;
currentPageIndex = 0;
}
/// <summary>
/// 交换AB页面
/// </summary>
public void SwapPages(int pageIndexA, int pageIndexB)
public virtual void SwapPages(int pageIndexA, int pageIndexB)
{
var a = _pageInfos.GetOrderNode().LandedIndex(pageIndexA);
var b = _pageInfos.GetOrderNode().LandedIndex(pageIndexB);
// 交换节点
_pageInfos.SwapNodes(a, b);
// 交换页面索引
(a.Value.pageNumber, b.Value.pageNumber) = (b.Value.pageNumber, a.Value.pageNumber);
// 刷新页面
FlipPage(_currentPageIndex, true);
FlipPage(currentPageIndex, true);
}
/// <summary>
/// 选择索引下的页面
/// 刷新所有页面索引
/// </summary>
/// <param name="pageIndex"></param>
/// <returns></returns>
public PageInfo IndexOfPage(int pageIndex)
public void RefreshAllPageNumber()
{
return _pageInfos.LandedIndex(pageIndex);
var index = 0;
foreach (var page in _pageInfos)
{
page.pageNumber = index++;
}
}
#endregion