修复翻页中错误的重复调用委托。

修复翻页中错误的翻页状态复用。
优化翻页中的流程
This commit is contained in:
2026-04-03 19:27:16 +08:00
parent b32f817315
commit a77d8a50aa
3 changed files with 408 additions and 343 deletions
+60 -63
View File
@@ -1,4 +1,5 @@
using System; using System;
using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel; using System.ComponentModel;
using System.Linq; using System.Linq;
@@ -81,8 +82,7 @@ namespace XericUI.FlipPage
/// 总页面数量文本 /// 总页面数量文本
/// </summary> /// </summary>
#if ODIN_INSPECTOR #if ODIN_INSPECTOR
[Space(2)] [Space(2)] [LabelText("总页面数量文本")] [BoxGroup("组件")]
[LabelText("总页面数量文本")] [BoxGroup("组件")]
#endif #endif
public ObjectVessel<TMPro.TMP_Text> labelTextTotalPageCount = public ObjectVessel<TMPro.TMP_Text> labelTextTotalPageCount =
new ObjectVessel<TMPro.TMP_Text>(); new ObjectVessel<TMPro.TMP_Text>();
@@ -107,7 +107,8 @@ namespace XericUI.FlipPage
/// 页面选择组件列表 /// 页面选择组件列表
/// </summary> /// </summary>
#if ODIN_INSPECTOR #if ODIN_INSPECTOR
[LabelText("页面选择组件列表")] [BoxGroup("组件")] [LabelText("页面选择组件列表")]
[BoxGroup("组件")]
// [ListDrawerSettings(ShowIndexLabels = true)] // [ListDrawerSettings(ShowIndexLabels = true)]
#endif #endif
[Tooltip("首个组件应当作为主要的节点管理")] [Tooltip("首个组件应当作为主要的节点管理")]
@@ -128,13 +129,13 @@ namespace XericUI.FlipPage
#if ODIN_INSPECTOR #if ODIN_INSPECTOR
[LabelText("翻页到索引按钮池")] [BoxGroup("组件")] [LabelText("翻页到索引按钮池")] [BoxGroup("组件")]
#endif #endif
public XericLibrary.Runtime.MacroLibrary.MacroPool.UnionSet pageButtonPool = new XericLibrary.Runtime.MacroLibrary.MacroPool.UnionSet(); public XericLibrary.Runtime.MacroLibrary.MacroPool.UnionSet pageButtonPool =
new XericLibrary.Runtime.MacroLibrary.MacroPool.UnionSet();
#endregion #endregion
#region 字段属性 #region 字段属性
private ToggleGroup _toggleGroup; private ToggleGroup _toggleGroup;
protected ToggleGroup ToggleGroup protected ToggleGroup ToggleGroup
@@ -167,17 +168,14 @@ namespace XericUI.FlipPage
internal ObjectVessel<Selectable, TMPro.TMP_Text> pageSelectableVessel; internal ObjectVessel<Selectable, TMPro.TMP_Text> pageSelectableVessel;
protected internal override void OnPageVisible(bool quickVisit) protected internal override void OnPageVisible(bool quickVisit)
{ {
if (quickVisit) if (quickVisit) return;
return;
// 自动补全外部交互动画 // 自动补全外部交互动画
if (pageSelectableVessel.IsValidVessel()) if (pageSelectableVessel.IsValidVessel())
SwitchSelectableVesselType(pageSelectableVessel.Target0, SwitchSelectableVesselType(pageSelectableVessel.Target0,
null, asToggle: toggle =>
toggle =>
{ {
if (!toggle.isOn) if (!toggle.isOn)
toggle.SetIsOnWithoutNotify(true); toggle.SetIsOnWithoutNotify(true);
@@ -186,8 +184,8 @@ namespace XericUI.FlipPage
protected internal override void OnPageInvisible(bool quickVisit) protected internal override void OnPageInvisible(bool quickVisit)
{ {
}
}
} }
/// <summary> /// <summary>
@@ -218,7 +216,11 @@ namespace XericUI.FlipPage
protected override void Awake() protected override void Awake()
{ {
// 使用默认的
HowToCreatPageInfo = () => AddPage(new PageComponentInfo());
base.Awake(); base.Awake();
if (buttonStartPage.IsValidVessel()) // 翻到开头 if (buttonStartPage.IsValidVessel()) // 翻到开头
buttonStartPage.Target.onClick.AddListener(FlipPageToStart); buttonStartPage.Target.onClick.AddListener(FlipPageToStart);
if (buttonPreviousPage.IsValidVessel()) // 翻到上一页 if (buttonPreviousPage.IsValidVessel()) // 翻到上一页
@@ -233,7 +235,6 @@ namespace XericUI.FlipPage
// 根据组件类型创建相关内容 // 根据组件类型创建相关内容
// var prefabSelectable = pageButtonPool.Prefab.GetComponent<Selectable>(); // var prefabSelectable = pageButtonPool.Prefab.GetComponent<Selectable>();
} }
#endregion #endregion
@@ -254,8 +255,7 @@ namespace XericUI.FlipPage
componentPage.pageSelectableVessel.IsValidVessel()) componentPage.pageSelectableVessel.IsValidVessel())
{ {
SwitchSelectableVesselType(componentPage.pageSelectableVessel.Target0, SwitchSelectableVesselType(componentPage.pageSelectableVessel.Target0,
null, asToggle: toggle =>
toggle =>
{ {
if (!toggle.isOn) if (!toggle.isOn)
toggle.isOn = true; toggle.isOn = true;
@@ -270,15 +270,18 @@ namespace XericUI.FlipPage
RefreshPageIndeButtonState(); RefreshPageIndeButtonState();
} }
public override PageInfo AddPageFirst(PageInfo pageInfo) public override PageInfo AddPage(PageInfo pageInfo = null, bool isFirstOtherwiseLast = false)
{ {
if (pageInfo is not PageComponentInfo componentPageInfo) if (pageInfo is not PageComponentInfo componentPageInfo)
{ {
Debug.LogError($"页面容器{Currentpage}不支持当前管理器,其必须由{nameof(FlipPageComponentManager)}.{nameof(PageComponentInfo)}继承/声名,否则无法支持后续功能", this); Debug.LogError(
$"页面容器{Currentpage}不支持当前管理器,其必须由{nameof(FlipPageComponentManager)}.{nameof(PageComponentInfo)}继承/声名,否则无法支持后续功能",
this);
return null; return null;
} }
// 页面基础初始化 // 页面基础初始化
var result = base.AddPageFirst(pageInfo); var result = base.AddPage(pageInfo, isFirstOtherwiseLast);
// 添加选择按钮 // 添加选择按钮
AddPageIndexButton(componentPageInfo); AddPageIndexButton(componentPageInfo);
// 刷新选择按钮显示状态 // 刷新选择按钮显示状态
@@ -288,26 +291,6 @@ namespace XericUI.FlipPage
return result; return result;
} }
public override PageInfo AddPageLast(PageInfo pageInfo)
{
if (pageInfo is not PageComponentInfo componentPageInfo)
{
Debug.LogError($"页面容器{Currentpage}不支持当前管理器,其必须由{nameof(FlipPageComponentManager)}.{nameof(PageComponentInfo)}继承/声名,否则无法支持后续功能", this);
return null;
}
// 页面基础初始化
var result = base.AddPageLast(pageInfo);
// 添加选择按钮
AddPageIndexButton(componentPageInfo);
// 刷新选择按钮显示状态
RefreshPageIndeButtonState();
// 刷新总页数
RefreshLabelTextTotalPageCount();
return result;
}
public override void RemovePage(int pageIndex) public override void RemovePage(int pageIndex)
{ {
base.RemovePage(pageIndex); base.RemovePage(pageIndex);
@@ -319,41 +302,45 @@ namespace XericUI.FlipPage
RefreshLabelTextTotalPageCount(); RefreshLabelTextTotalPageCount();
} }
public override void ClearPages() public override void ClearPages(bool resetPool = true)
{ {
PageInfos PageInfos
.OfType<PageComponentInfo>() .OfType<PageComponentInfo>()
.Where(a => a.pageSelectableVessel.IsValidVessel()) .Where(a => a.pageSelectableVessel.IsValidVessel())
.ForEachDo(a => pageButtonPool.DirectlyRelease(a.pageSelectableVessel!.Target0.gameObject)); .ForEachDo(a => pageButtonPool.DirectlyRelease(a.pageSelectableVessel!.Target0.gameObject));
pageSelectButton.Clear(); pageSelectButton.Clear();
base.ClearPages(); base.ClearPages(resetPool);
RefreshLabelTextTotalPageCount(); RefreshLabelTextTotalPageCount();
} }
/// <summary> /// <summary>
/// 在页面上添加按钮 /// 添加页面对应的翻页按钮
/// </summary> /// </summary>
/// <param name="pageInfo"></param> /// <param name="pageInfo"></param>
protected virtual void AddPageIndexButton(PageComponentInfo pageInfo) protected virtual void AddPageIndexButton(PageComponentInfo pageInfo)
{ {
var pageIndexSelectable = GetPageIndexButton(); // 检查页面上的按钮引用
pageInfo.pageSelectableVessel = pageIndexSelectable; ObjectVessel<Selectable, TMP_Text> pageIndexSelectable = null;
if (pageInfo.pageSelectableVessel == null)
{
pageIndexSelectable = GetPageIndexButton();
pageInfo.pageSelectableVessel = pageIndexSelectable;
}
else
pageIndexSelectable = pageInfo.pageSelectableVessel;
// 将按钮对象添加到列表中便于管理 // 将按钮对象添加到列表中便于管理
pageSelectButton.Add(pageIndexSelectable); pageSelectButton.Add(pageIndexSelectable);
SwitchSelectableVesselType(pageIndexSelectable.Target0, SwitchSelectableVesselType(pageIndexSelectable.Target0,
button => button => { button.onClick.AddListener(() => FlipPage(pageInfo.pageNumber)); },
{
button.onClick.AddListener(() => FlipPage(pageInfo.pageNumber));
},
toggle => toggle =>
{ {
toggle.isOn = pageInfo.pageNumber == currentPageIndex; toggle.isOn = pageInfo.pageNumber == currentPageIndex;
toggle.group = ToggleGroup; toggle.group = ToggleGroup;
toggle.onValueChanged.AddListener(b => toggle.onValueChanged.AddListener(b =>
{ {
if (b) if (b) FlipPage(pageInfo.pageNumber);
FlipPage(pageInfo.pageNumber);
}); });
}); });
RefreshPageSelectableVesselLabel(pageInfo); RefreshPageSelectableVesselLabel(pageInfo);
@@ -422,7 +409,7 @@ namespace XericUI.FlipPage
#region 组件方法 #region 组件方法
/// <summary> /// <summary>
/// 获取页面索引按钮 /// 获取页面索引按钮(获取这个按钮的池化对象,随后应当将其绑定到页面上)
/// </summary> /// </summary>
/// <returns></returns> /// <returns></returns>
protected virtual ObjectVessel<Selectable, TMPro.TMP_Text> GetPageIndexButton() protected virtual ObjectVessel<Selectable, TMPro.TMP_Text> GetPageIndexButton()
@@ -434,39 +421,49 @@ namespace XericUI.FlipPage
return vessel; return vessel;
} }
protected virtual void ListenPageSelectable(ObjectVessel<Selectable, TMPro.TMP_Text> selectableVessel, Action<int> selectPageAction) /// <summary>
/// 注册一个委托到翻页按钮上。(事件会在调用容器清空后被自动回收)
/// </summary>
/// <param name="selectableVessel"></param>
/// <param name="selectPageAction"></param>
protected virtual void ListenPageSelectable(ObjectVessel<Selectable, TMPro.TMP_Text> selectableVessel,
Action<int> selectPageAction)
{ {
if (selectableVessel.Target0 is Button button) if (selectableVessel.Target0 is Button button)
{ {
button.onClick.AddListener(() => button.onClick.AddListener(() => { selectPageAction?.Invoke(currentPageIndex); });
{
selectPageAction?.Invoke(currentPageIndex);
});
} }
} }
/// <summary> /// <summary>
/// 处理可选对象类型,如果要支持更多的类型优先改此方法,以便获得其他所有位置的编译器提示 /// 处理可选对象类型.(如果要支持更多的类型,应优先改此方法,以便获得其他所有位置的编译器提示)
/// </summary> /// </summary>
/// <param name="selectable"></param> /// <param name="selectable"></param>
/// <param name="asButton"></param> /// <param name="asButton">如果是按钮</param>
/// <param name="asToggle"></param> /// <param name="asToggle">如果是单选项</param>
/// <returns></returns> /// <returns></returns>
protected static bool SwitchSelectableVesselType(Selectable selectable, Action<Button> asButton, Action<Toggle> asToggle) protected static bool SwitchSelectableVesselType(Selectable selectable,
Action<Button> asButton = null,
Action<Toggle> asToggle = null)
{ {
switch (selectable) switch (selectable)
{ {
case CircleSlider circleSlider: break; // asCircleSlider?.Invoke(circleSlider); break; case CircleSlider circleSlider: break; // asCircleSlider?.Invoke(circleSlider); break;
case TMP_Dropdown tmpDropdown: break; // asTmpDropdown?.Invoke(tmpDropdown); break; case TMP_Dropdown tmpDropdown: break; // asTmpDropdown?.Invoke(tmpDropdown); break;
case TMP_InputField tmpInputField: break; // asTmpInputField?.Invoke(tmpInputField); break; case TMP_InputField tmpInputField: break; // asTmpInputField?.Invoke(tmpInputField); break;
case Button button: asButton?.Invoke(button); return true; case Button button:
asButton?.Invoke(button);
return true;
case Dropdown dropdown: break; // asDropdown?.Invoke(dropdown); break; case Dropdown dropdown: break; // asDropdown?.Invoke(dropdown); break;
case InputField inputField: break; // asInputField?.Invoke(inputField); break; case InputField inputField: break; // asInputField?.Invoke(inputField); break;
case Scrollbar scrollbar: break; // asScrollbar?.Invoke(scrollbar); break; case Scrollbar scrollbar: break; // asScrollbar?.Invoke(scrollbar); break;
case Slider slider: break; // asSlider?.Invoke(slider); break; case Slider slider: break; // asSlider?.Invoke(slider); break;
case Toggle toggle: asToggle?.Invoke(toggle); return true; case Toggle toggle:
asToggle?.Invoke(toggle);
return true;
default: break; default: break;
} }
Debug.LogError($"目前不支持的可选类型:{selectable}"); Debug.LogError($"目前不支持的可选类型:{selectable}");
return false; return false;
} }
-31
View File
@@ -10,37 +10,6 @@ namespace XericUI.FlipPage
[AddComponentMenu("Xeric UI Vessel/FlipPage/General FilpPage Manager")] [AddComponentMenu("Xeric UI Vessel/FlipPage/General FilpPage Manager")]
public class GeneralDataFlipPage : FlipPageComponentManager public class GeneralDataFlipPage : FlipPageComponentManager
{ {
#region 生命周期
protected override void Awake()
{
base.Awake();
}
#endregion
#region 容器控制
/// <summary>
/// 添加一个数据项目
/// </summary>
/// <param name="item">数据项目</param>
/// <typeparam name="T"></typeparam>
public void AddItem<T>(T item)
where T : ItemComponentInfo
{
base.AddItem(item, () => AddPageLast(new PageComponentInfo()));
}
/// <summary>
/// 添加一个数据项目
/// </summary>
/// <param name="item">数据项目</param>
public override void AddItem(ItemInfo item)
{
base.AddItem(item, () => AddPageLast(new PageComponentInfo()));
}
#endregion
} }
} }
+296 -197
View File
@@ -1,4 +1,5 @@
using System; using System;
using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using Sirenix.OdinInspector; using Sirenix.OdinInspector;
@@ -25,6 +26,8 @@ namespace XericUI.FlipPage
/// 基本用法应该是,添加数据项目,这会自动分配到有效的页面中; /// 基本用法应该是,添加数据项目,这会自动分配到有效的页面中;
/// 如果页面项目已满,则新建一个页面继续填充项目。 /// 如果页面项目已满,则新建一个页面继续填充项目。
/// 或者直接创建页面,将需要的数据项目直接写在页面数据中。 /// 或者直接创建页面,将需要的数据项目直接写在页面数据中。
/// 管理器在添加数据时不能自己直接生成容器类型,但可以通过重写对象池,重设委托,或直接手动定义提供间接的页面类型;
/// 页面类型是管理数据的一个重要组成部分,它被池化,便于在各个地方进行管理。
/// ///
/// 使用FlipPage()翻页,这会立刻执行,并触发OnFlipPage函数, /// 使用FlipPage()翻页,这会立刻执行,并触发OnFlipPage函数,
/// currentPageIndex可以获取当前页数,Currentpage获取当前页面数据; /// currentPageIndex可以获取当前页数,Currentpage获取当前页面数据;
@@ -70,6 +73,9 @@ namespace XericUI.FlipPage
#region 字段属性 #region 字段属性
protected ObjectPool<PageInfo> PageInfoPool = null;
public Func<PageInfo> HowToCreatPageInfo = () => throw new Exception("需要翻页管理器实现相应的页面创建逻辑,并重写添加默认数据方法后才可以使用。");
/// <summary> /// <summary>
/// 当前选中的页面索引 /// 当前选中的页面索引
/// </summary> /// </summary>
@@ -79,17 +85,9 @@ namespace XericUI.FlipPage
[SerializeField] [SerializeField]
protected int currentPageIndex = 0; protected int currentPageIndex = 0;
#if UNITY_EDITOR && ODIN_INSPECTOR
/// <summary>
/// 显示调试列表
/// </summary>
private List<PageInfo> PageInfoLists => _pageInfos.ToList();
#endif
/// <summary> /// <summary>
/// 所有页面 /// 所有页面
/// </summary> /// </summary>
private readonly LinkedList<PageInfo> _pageInfos = new LinkedList<PageInfo>(); private readonly LinkedList<PageInfo> _pageInfos = new LinkedList<PageInfo>();
/// <summary> /// <summary>
@@ -139,13 +137,26 @@ namespace XericUI.FlipPage
/// </summary> /// </summary>
public int TotalPageCount => PageInfos.Count(); public int TotalPageCount => PageInfos.Count();
#if UNITY_EDITOR && ODIN_INSPECTOR
[LabelText("所有页面")]
[Obsolete("显示调试列表 属性仅编辑时有效,不要调用")]
private List<PageInfo> _pageInfoLists_ => _pageInfos.ToList();
#endif
#endregion #endregion
#region 生命周期 #region 生命周期
protected override void Awake()
{
base.Awake();
PageInfoPool = new ObjectPool<PageInfo>(CreatPageInfo, GetPageInfo, ReleasePageInfo, DestroyPageInfo);
}
protected override void Start() protected override void Start()
{ {
base.Start(); base.Start();
FlipPage(currentPageIndex); FlipPage(currentPageIndex);
} }
@@ -160,18 +171,20 @@ namespace XericUI.FlipPage
#region 翻页管理 #region 翻页管理
/// <summary> /// <summary>
/// 当发生翻页时(这会在整个翻页动作流程的最后执行) /// 当发生翻页时回调 (这会在整个翻页动作流程的最后执行,翻页过程中回调会在此之前播放)
/// </summary> /// </summary>
protected virtual void OnFlipPage() protected virtual void OnFlipPage()
{ {
} }
/// <summary> /// <summary>
/// 通过指令筛选页面 /// 通过指令翻到页面
/// </summary> /// </summary>
/// <param name="pageNumber"></param> /// <remarks>
/// <param name="forceRefresh"></param> /// 手动输入页数时执行此方法。
/// </remarks>
/// <param name="pageNumber">页数编号(或其他命令,但需要手动实现处理,默认只处理页数)</param>
/// <param name="forceRefresh">强制刷新(默认情况下如果当前页就是目标页时不会执行后续的翻页动作)</param>
public virtual void FlipPage(string pageNumber, bool forceRefresh = false) public virtual void FlipPage(string pageNumber, bool forceRefresh = false)
{ {
if (float.TryParse(pageNumber, out var index)) if (float.TryParse(pageNumber, out var index))
@@ -183,9 +196,14 @@ namespace XericUI.FlipPage
/// <summary> /// <summary>
/// 翻到指定页 /// 翻到指定页
/// </summary> /// </summary>
/// <param name="pageIndex"></param> /// <remarks>
/// <param name="quickSwitchPage">快速翻页,</param> /// 翻页时,当前页会触发一次 OnPageInvisible,目标页会触发一次 OnPageVisible;
/// <param name="forceRefresh"></param> /// 翻到目标页数过程中除当前页和目标页之外的所有页面会触发一次OnPageVisible,OnPageInvisible的快速翻页;
/// 在重写相关事件时,利用快速翻页状态可以跳过动画加载,仅执行相关的触发器。
/// 如果压根不希望调用期间所有页面的翻页浏览事件,可以执行 JumpPage。
/// </remarks>
/// <param name="pageIndex">目标页数</param>
/// <param name="forceRefresh">强制刷新(默认情况下如果当前页就是目标页时不会执行后续的翻页动作)</param>
public virtual void FlipPage(int pageIndex, bool forceRefresh = false) public virtual void FlipPage(int pageIndex, bool forceRefresh = false)
{ {
// 检查翻页索引有效 // 检查翻页索引有效
@@ -200,33 +218,33 @@ namespace XericUI.FlipPage
// 翻页计算 // 翻页计算
var pageIndexOffset = pageIndex - currentPageIndex; var pageIndexOffset = pageIndex - currentPageIndex;
var lastPageIndex = currentPageIndex; var lastPageIndex = currentPageIndex;
// 如果节点不存在,通过索引寻找 var quickPage = false; // 快速浏览页
bool visitOffsetDirIsNext;
// 如果节点不存在,通过索引直接命中
if (CurrentPageNode == null) if (CurrentPageNode == null)
{
CurrentPageNode = _pageInfos.GetOrderNode().LandedIndex(currentPageIndex); CurrentPageNode = _pageInfos.GetOrderNode().LandedIndex(currentPageIndex);
// 如果索引偏移,逐个移动 if (CurrentPageNode == null)
else if (pageIndexOffset > 0) Debug.LogError($"索引{currentPageIndex}下的节点不存在, 请检查后再试", this);
{ else
// 先关闭页面元素 Currentpage.OnPageVisible(false);
CurrentPageNode.Value.CallChildItemInvisible();
for (var i = 0; i < pageIndexOffset; i++)
{
var next = CurrentPageNode!.Next;
SwitchPage(next, _pageInfos.Last, i + 1 < pageIndexOffset);
}
} }
else if (pageIndexOffset < 0) // 如果索引偏移,逐个移动
else if (pageIndexOffset != 0)
{ {
// 判断方向
visitOffsetDirIsNext = pageIndexOffset > 0;
pageIndexOffset = Mathf.Abs(pageIndexOffset);
// 先关闭页面元素 // 先关闭页面元素
CurrentPageNode.Value.CallChildItemInvisible(); CurrentPageNode.Value.CallChildItemInvisible();
for (var i = 0; i > pageIndexOffset; i--) for (var i = 1; i < pageIndexOffset; i++)
{ SwitchPage(false);
var previa = CurrentPageNode!.Previous; SwitchPage(true);
SwitchPage(previa, _pageInfos.First, i - 1 > pageIndexOffset);
}
} }
// 没有翻动有效页,直接退出 // 没有翻动有效页,直接退出
else else
return; return;
// 可能时当前不存在任何页面 // 可能时当前不存在任何页面
if (CurrentPageNode == null) if (CurrentPageNode == null)
return; return;
@@ -234,26 +252,43 @@ namespace XericUI.FlipPage
CurrentPageNode.Value.CallChildItemVisible(); CurrentPageNode.Value.CallChildItemVisible();
currentPageIndex = pageIndex; currentPageIndex = pageIndex;
OnFlipPage(); OnFlipPage();
return;
// 翻页 void SwitchPage(bool isEndPage)
void SwitchPage(LinkedListNode<PageInfo> newPageNode, LinkedListNode<PageInfo> defualtPageNode, bool quickPage)
{ {
if (newPageNode != null) Currentpage.OnPageInvisible(quickPage);
{ quickPage = !isEndPage;
Currentpage.OnPageInvisible(quickPage); CurrentPageNode = visitOffsetDirIsNext ? CurrentPageNode.Next : CurrentPageNode.Previous;
CurrentPageNode = newPageNode; if (CurrentPageNode == null && canPassIndexBoundary)
Currentpage.OnPageVisible(quickPage); CurrentPageNode = visitOffsetDirIsNext ? _pageInfos.First : _pageInfos.Last;
} Currentpage.OnPageVisible(quickPage);
if (canPassIndexBoundary)
{
Currentpage.OnPageInvisible(quickPage);
CurrentPageNode = defualtPageNode;
Currentpage.OnPageVisible(quickPage);
}
} }
} }
/// <summary>
/// 径直跳转到页 (skip quick Page)
/// </summary>
/// <remarks>
/// 相比 FlipPage 方法,JumpPage会直接空降到页,不会调用期间其他页的浏览事件。
/// </remarks>
public virtual void JumpPage(int pageIndex, bool forceRefresh = false)
{
// 子项隐藏
CurrentPageNode?.Value.CallChildItemInvisible();
// 计算索引对应的页面
var page = _pageInfos.First;
while (pageIndex-- > 0 && page.Next != null)
page = page.Next;
// 只执行非快速浏览的回调
Currentpage?.OnPageInvisible(false);
CurrentPageNode = page;
Currentpage?.OnPageVisible(false);
currentPageIndex = pageIndex;
// 子项显示
CurrentPageNode?.Value.CallChildItemVisible();
}
/// <summary> /// <summary>
/// 翻到开头 /// 翻到开头
/// </summary> /// </summary>
@@ -291,44 +326,33 @@ namespace XericUI.FlipPage
=> FlipPage(_pageInfos.Count - 1); => FlipPage(_pageInfos.Count - 1);
/// <summary> /// <summary>
/// 添加页面在开头 /// 添加页面在开头
/// </summary> /// </summary>
public virtual PageInfo AddPageFirst(PageInfo pageInfo) /// <remarks>
/// 注意统一页面类型,如果没有指定自定义的页面,那么
/// </remarks>
public virtual PageInfo AddPage(PageInfo pageInfo = null, bool isFirstOtherwiseLast = false)
{ {
if (pageInfo == null) if (pageInfo == null)
{ pageInfo = PageInfoPool.Get();
Debug.LogError($"不能添加一个空的页面内容到管理器中!", this); else
return null; GetPageInfo(pageInfo);
}
// 添加页面下标
pageInfo.pageNumber = 0;
// 添加页面
_pageInfos.AddFirst(pageInfo);
// 更新后面页面的索引
RefreshAllPageNumber();
// 执行页面生命周期
pageInfo.OnPageCreated(this);
// 刷新页面
FlipPage(currentPageIndex, true);
return pageInfo;
}
/// <summary> if (isFirstOtherwiseLast)
/// 添加页面在结尾
/// </summary>
public virtual PageInfo AddPageLast(PageInfo pageInfo)
{
if (pageInfo == null)
{ {
Debug.LogError($"不能添加一个空的页面内容到管理器中!", this); // 添加页面(页面下标默认0)
return null; _pageInfos.AddFirst(pageInfo);
// 更新后面页面的索引
RefreshAllPageNumber();
}
else
{
// 添加页面下标
pageInfo.pageNumber = _pageInfos.Count;
// 添加页面
_pageInfos.AddLast(pageInfo);
} }
// 添加页面下标
pageInfo.pageNumber = _pageInfos.Count;
// 添加页面
_pageInfos.AddLast(pageInfo);
// 执行页面生命周期 // 执行页面生命周期
pageInfo.OnPageCreated(this); pageInfo.OnPageCreated(this);
// 刷新页面 // 刷新页面
@@ -342,16 +366,8 @@ namespace XericUI.FlipPage
/// <param name="pageInfo"></param> /// <param name="pageInfo"></param>
public virtual void RemovePage(PageInfo pageInfo) public virtual void RemovePage(PageInfo pageInfo)
{ {
// 如果移除的是当前页面,执行关闭操作 // 释放页面
if (CurrentPageNode.Value == pageInfo) PageInfoPool.Release(pageInfo);
{
pageInfo.OnPageInvisible(true);
CurrentPageNode = null;
}
// 执行页面生命周期
pageInfo.OnPageDestroy(this);
// 页面索引移除
_pageInfos.Remove(pageInfo);
// 刷新页面 // 刷新页面
FlipPage(currentPageIndex, true); FlipPage(currentPageIndex, true);
var index = PageInfos.IndexOf(a => a == pageInfo); var index = PageInfos.IndexOf(a => a == pageInfo);
@@ -368,6 +384,7 @@ namespace XericUI.FlipPage
Debug.LogError($"不能移除不在范围内的索引{pageIndex}!", this); Debug.LogError($"不能移除不在范围内的索引{pageIndex}!", this);
return; return;
} }
// 获取页面节点 // 获取页面节点
var page = _pageInfos.GetOrderNode().LandedIndex(pageIndex); var page = _pageInfos.GetOrderNode().LandedIndex(pageIndex);
// 更新后面页面的索引 // 更新后面页面的索引
@@ -378,16 +395,9 @@ namespace XericUI.FlipPage
tempPage = tempPage.Next; tempPage = tempPage.Next;
tempPage.Value.pageNumber = tempPageNumber++; tempPage.Value.pageNumber = tempPageNumber++;
} }
// 如果移除的是当前页面,执行关闭操作
if (CurrentPageNode == page) // 释放页面
{ PageInfoPool.Release(page.Value);
page.Value.OnPageInvisible(true);
CurrentPageNode = null;
}
// 执行页面生命周期
page.Value.OnPageDestroy(this);
// 页面索引移除
_pageInfos.Remove(page);
// 刷新页面 // 刷新页面
FlipPage(currentPageIndex, true); FlipPage(currentPageIndex, true);
} }
@@ -425,14 +435,69 @@ namespace XericUI.FlipPage
/// <summary> /// <summary>
/// 清除所有页面 (所有页面数据也会一同移除) /// 清除所有页面 (所有页面数据也会一同移除)
/// </summary> /// </summary>
/// <param name="resetPool">同时重置页面池</param>
/// <remarks>
/// 页面的引用会从管理器中释放,同时需要自行重写页面 OnPageDestroy 确保页面准确释放了各种东西。
/// 除非你确认你的页面数据对象类型是一致的,否则无论如何都建议清空页面时也清空对象池,不然会带来严重的类型污染。
/// </remarks>
#if ODIN_INSPECTOR #if ODIN_INSPECTOR
[HorizontalGroup("数据"), Button("清除所有页面")] [HorizontalGroup("数据"), Button("清除所有页面")]
#endif #endif
public virtual void ClearPages() public virtual void ClearPages(bool resetPool = true)
{ {
_pageInfos.Clear(); if (CurrentPageNode != null)
Currentpage.OnPageInvisible(true);
foreach (var pageInfo in _pageInfos)
{
pageInfo.itemInfos.Clear();
if (resetPool)
PageInfoPool.Release(pageInfo);
else
ReleasePageInfo(pageInfo); // 直接调用释放事件,不走对象池,不知道这样能不能减少重复的入栈开销?还是会让gc变得碎片化?
}
// 清空池
if (resetPool)
PageInfoPool.Clear();
// 重置当前页面
CurrentPageNode = null; CurrentPageNode = null;
currentPageIndex = 0; currentPageIndex = 0;
// 清空缓存
_pageInfos.Clear();
_itemInfos.Clear();
}
/// <summary>
/// 关闭所有页面
/// </summary>
public void InactivePages() => SetPagesActive(false);
/// <summary>
/// 重新激活所有页面
/// </summary>
public void ReactivePages() => SetPagesActive(true);
/// <summary>
/// 设置所有页面的激活状态
/// </summary>
/// <param name="active"></param>
public void SetPagesActive(bool active)
{
foreach (var pageInfo in _pageInfos)
pageInfo.pageActive = active;
FlipPage(0, true);
}
/// <summary>
/// 使用自定义的方法设置激活状态
/// </summary>
/// <param name="isActive"></param>
/// <typeparam name="T"></typeparam>
public void SetPagesActive<T>(Func<T, bool> isActive)
where T : PageInfo
{
foreach (var pageInfo in _pageInfos)
pageInfo.pageActive = isActive((T)pageInfo);
FlipPage(0, true);
} }
/// <summary> /// <summary>
@@ -464,41 +529,93 @@ namespace XericUI.FlipPage
#endregion #endregion
#region 数据项目池
/// <summary>
/// 定义对象池如何创建页面(建议直接覆盖委托)
/// </summary>
/// <returns></returns>
protected virtual PageInfo CreatPageInfo() => HowToCreatPageInfo();
/// <summary>
/// 定义对象池如何执行获取页面后的事件
/// </summary>
/// <param name="info"></param>
protected virtual void GetPageInfo(PageInfo info)
{
info.pageNumber = 0;
_pageInfos.AddLast(info);
}
/// <summary>
/// 定义对象池如何执行释放页面后的事件
/// </summary>
/// <param name="info"></param>
protected virtual void ReleasePageInfo(PageInfo info)
{
if (CurrentPageNode != null && Currentpage == info)
{
info.OnPageInvisible(true);
CurrentPageNode = null;
}
info.OnPageDestroy(this);
_pageInfos.Remove(info);
}
/// <summary>
/// 定义对象池如何执行销毁页面后的事件
/// </summary>
/// <param name="info"></param>
protected virtual void DestroyPageInfo(PageInfo info)
{
}
#endregion
#region 数据项目 #region 数据项目
/// <summary> /// <summary>
/// 添加数据项目 /// 添加一组数据项目(方法待优化)
/// </summary> /// </summary>
/// <param name="item"></param> /// <param name="items"></param>
/// <exception cref="Exception"></exception> /// <param name="customGetPage"></param>
public virtual void AddItem(ItemInfo item) public virtual void AddItems(IEnumerable<ItemInfo> items, Func<PageInfo> customGetPage = null)
{ {
AddItem(item, () => throw new Exception("需要翻页管理器实现相应的页面创建逻辑,并重写添加默认数据方法后才可以使用。")); foreach (var item in items)
AddItem(item, customGetPage);
} }
/// <summary> /// <summary>
/// 添加数据项目 /// 添加数据项目
/// </summary> /// </summary>
public virtual void AddItem(ItemInfo item, Func<PageInfo> addPage) /// <param name="item"></param>
/// <typeparam name="T">提供一个指定的数据类型,默认添加这个类型的页面</typeparam>
public virtual void AddItem<T>(ItemInfo item)
where T : PageInfo, new()
=> AddItem(item, () => new T());
/// <summary>
/// 添加数据项目
/// </summary>
/// <remarks>
/// 添加项目到页面中,
/// </remarks>
public virtual void AddItem(ItemInfo item, Func<PageInfo> customGetPage = null)
{ {
// 向前寻找空缺(这在页面都被准确填充的情况下最忧) var page = _pageInfos.Count <= 0 ? null : _pageInfos.Last.Value;
var firstVacancy = _pageInfos if (page != null &&
.GetReversedOrderNode() page.CheckPageAnyLack(item) &&
.LastOrDefault(a page.itemInfos.Count < DefaultItemCountPerPage)
=> a.Value.itemInfos.Count < DefaultItemCountPerPage);
if (firstVacancy != null)
{ {
firstVacancy.Value.itemInfos.Add(item); page.itemInfos.Add(item);
_itemInfos.Add(item); _itemInfos.Add(item);
return; return;
} }
if (addPage == null)
{
Debug.LogError("当前页面已满,数据项目期望顺位添加到新页面中,但未指定页面创建函数,添加过程已退出", this);
return;
}
// 新建一个页面存放数据 // 新建一个页面存放数据
addPage.Invoke().itemInfos.Add(item); page = customGetPage == null ? PageInfoPool.Get() : customGetPage();
page.itemInfos.Add(item);
_itemInfos.Add(item); _itemInfos.Add(item);
} }
@@ -508,32 +625,13 @@ namespace XericUI.FlipPage
/// <param name="item"></param> /// <param name="item"></param>
public virtual void RemoveItem(ItemInfo item) public virtual void RemoveItem(ItemInfo item)
{ {
_itemInfos.Remove(item);
foreach (var a in AllItemInfos foreach (var a in AllItemInfos
.Adjoint() .Adjoint()
.Where(a => a.obj == item) .Where(a => a.obj == item)
.Reverse()) .Reverse())
{ {
ItemIndex2PageIndex(a.index); ItemIndex2PageIndex(a.index);
}
}
public virtual void RemoveItem(int indexItem)
{
}
/// <summary>
/// 保留页面的状态下清空所有数据
/// </summary>
#if ODIN_INSPECTOR
[HorizontalGroup("数据"), Button("清除所有数据")]
#endif
public void ClearItemsWithoutPages()
{
foreach (var page in _pageInfos)
{
page.itemInfos.Clear();
} }
} }
@@ -553,9 +651,7 @@ namespace XericUI.FlipPage
/// <param name="itemIndex"></param> /// <param name="itemIndex"></param>
/// <returns></returns> /// <returns></returns>
public virtual int ItemIndex2PageIndex(int itemIndex) public virtual int ItemIndex2PageIndex(int itemIndex)
{ => itemIndex / DefaultItemCountPerPage;
return itemIndex / DefaultItemCountPerPage;
}
/// <summary> /// <summary>
/// 当前翻页管理器中包含给定数据项目 /// 当前翻页管理器中包含给定数据项目
@@ -580,6 +676,7 @@ namespace XericUI.FlipPage
BackupItemsFormPages(); BackupItemsFormPages();
return _backupItemInfos.OfType<T>(); return _backupItemInfos.OfType<T>();
} }
return AllItemInfos.OfType<T>(); return AllItemInfos.OfType<T>();
} }
@@ -593,56 +690,41 @@ namespace XericUI.FlipPage
} }
/// <summary> /// <summary>
/// 强制重排所有数据项目(从备份中恢复数据) /// 重排所有数据项目(从备份中恢复数据)
/// </summary> /// </summary>
public void RestoreItemsToPages() public void RestoreItemsToPages()
{ => RestoreItemsToPages(_backupItemInfos);
RestoreItemsToPages(_backupItemInfos);
}
/// <summary>
/// 强制重排所有数据项目(从备份中通过过滤器恢复数据)
/// </summary>
public void RestoreItemsByFilterToPages()
{
RestoreItemsToPages(_backupItemInfos.Where(a => a.PassFilter(this)).ToList());
}
/// <summary> /// <summary>
/// 强制重排所有数据项目(从给定备份中恢复数据) /// 重排所有数据项目(基于备份数据应用过滤器的数据)
/// </summary> /// </summary>
public void RestoreItemsToPages<T>(List<T> overrideDatas) public void RestoreItemsByFilterToPages()
where T : ItemInfo => RestoreItemsToPages(_backupItemInfos.Where(a => a.PassFilter(this)).ToList());
/// <summary>
/// 重排所有数据项目(从给定备份中恢复数据)
/// </summary>
/// <param name="overrideDatas"></param>
/// <param name="customGetPage">重新排列涉及页面布局整理</param>
/// <param name="allowClear"></param>
/// <typeparam name="TItemInfo"></typeparam>
public void RestoreItemsToPages<TItemInfo>(List<TItemInfo> overrideDatas, Func<PageInfo> customGetPage = null,
bool allowClear = true)
where TItemInfo : ItemInfo
{ {
if (overrideDatas == null || overrideDatas.Count <= 0) var collectionInvalid = overrideDatas is not { Count: > 0 };
if (collectionInvalid && !allowClear)
{ {
Debug.LogError($"重排数据需要一个有效的数据对象列表,如果希望将数据重置为空,可以使用Clear方法,而不是重排操作。", this); Debug.LogError($"当前重排数据期望使用一个有效的数据对象列表,如希望将数据重置为空,可以使用Clear方法,或重新允许重排操作清空列表。", this);
return; return;
} }
if (ItemIndex2PageIndex(overrideDatas.Count - 1) > _pageInfos.Count)
{ // 清空页面
Debug.LogError($"重排数据时,原有数据数量({overrideDatas.Count})超出现有面板数量({_pageInfos.Count}),已拒绝重排", this); ClearPages();
return; if (collectionInvalid) return;
} AddItems(overrideDatas, customGetPage);
// 执行重排 FlipPage(0);
var page = _pageInfos.First; return;
page.Value.itemInfos.Clear();
var pageIndex = 0;
for (int i = 0; i < overrideDatas.Count; i++)
{
var newPageIndex = ItemIndex2PageIndex(i);
if (pageIndex != newPageIndex)
{
pageIndex = newPageIndex;
page = page.Next;
if (page == null)
{
Debug.LogError($"重排数据时,页面链接错误,重排被迫中断", this);
return;
}
page.Value.itemInfos.Clear();
}
page.Value.itemInfos.Add(overrideDatas[i]);
}
} }
#endregion #endregion
@@ -681,6 +763,8 @@ namespace XericUI.FlipPage
#endif #endif
public int pageNumber { get; internal set; } public int pageNumber { get; internal set; }
public bool pageActive;
#endregion #endregion
#region 生命周期 #region 生命周期
@@ -705,32 +789,36 @@ namespace XericUI.FlipPage
// 释放列表 // 释放列表
if (itemInfos != null) if (itemInfos != null)
ListPool<ItemInfo>.Release(itemInfos); ListPool<ItemInfo>.Release(itemInfos);
} }
/// <summary> /// <summary>
/// 页面显示时 /// 页面显示时回调
/// </summary> /// </summary>
/// <param name="quickVisit">快速浏览(用于区分动画和逻辑,为真时,不应当在动画事件后继续加载数据)</param> /// <param name="quickVisit">快速浏览(用于区分动画和逻辑,为真时,不应当在动画事件后继续加载数据)</param>
/// <code> /// <remarks>
/// 页面中的成员会自动在非快速浏览模式下自动执行相应的激活函数 /// 页面中的成员会自动执行其相应的激活函数,不需要在这里手动调用。
/// </code> /// 快速浏览主要为了区分当前执行的是动画事件还是纯逻辑。
/// </remarks>
protected internal abstract void OnPageVisible(bool quickVisit); protected internal abstract void OnPageVisible(bool quickVisit);
/// <summary> /// <summary>
/// 页面隐藏时 /// 页面隐藏时回调
/// </summary> /// </summary>
/// <param name="quickVisit">快速浏览(用于区分动画和逻辑,为真时,不应当在动画事件后继续加载数据)</param> /// <param name="quickVisit">快速浏览(用于区分动画和逻辑,为真时,不应当在动画事件后继续加载数据)</param>
/// <code> /// <remarks>
/// 页面中的成员会自动在非快速浏览模式下自动执行相应的激活函数 /// 页面中的成员会自动执行其相应的激活函数,不需要在这里手动调用。
/// </code> /// 快速浏览主要为了区分当前执行的是动画事件还是纯逻辑。
/// </remarks>
protected internal abstract void OnPageInvisible(bool quickVisit); protected internal abstract void OnPageInvisible(bool quickVisit);
#endregion #endregion
/// <summary> /// <summary>
/// 传递子项激活回调事件 /// 子项激活回调(应呼叫子项激活)
/// </summary> /// </summary>
/// <remarks>
/// 仅页面触发快速浏览,子项没有这个功能
/// </remarks>
protected internal virtual void CallChildItemVisible() protected internal virtual void CallChildItemVisible()
{ {
itemInfos itemInfos
@@ -739,8 +827,11 @@ namespace XericUI.FlipPage
} }
/// <summary> /// <summary>
/// 传递子项非激活回调事件 /// 子项非激活回调(应呼叫子项隐藏)
/// </summary> /// </summary>
/// <remarks>
/// 仅页面触发快速浏览,子项没有这个功能
/// </remarks>
protected internal virtual void CallChildItemInvisible() protected internal virtual void CallChildItemInvisible()
{ {
itemInfos itemInfos
@@ -748,6 +839,15 @@ namespace XericUI.FlipPage
.ForEachDo(itemInfo => itemInfo.OnItemInvisible()); .ForEachDo(itemInfo => itemInfo.OnItemInvisible());
} }
/// <summary>
/// 检查页面是否存在可以放下对象的空缺
/// </summary>
/// <returns></returns>
protected internal virtual bool CheckPageAnyLack(ItemInfo item = null)
{
return true;
}
public override string ToString() public override string ToString()
{ {
return $"[{nameof(PageInfo)}] ({name})"; return $"[{nameof(PageInfo)}] ({name})";
@@ -780,7 +880,7 @@ namespace XericUI.FlipPage
#region 生命周期 #region 生命周期
/// <summary> /// <summary>
/// 当项目显示时 /// 当项目显示时回调
/// </summary> /// </summary>
/// <code> /// <code>
/// 这会在页面显示之后执行 /// 这会在页面显示之后执行
@@ -788,7 +888,7 @@ namespace XericUI.FlipPage
protected internal abstract void OnItemVisible(); protected internal abstract void OnItemVisible();
/// <summary> /// <summary>
/// 当项目隐藏时 /// 当项目隐藏时回调
/// </summary> /// </summary>
/// <code> /// <code>
/// 这会在页面关闭之前执行 /// 这会在页面关闭之前执行
@@ -815,6 +915,5 @@ namespace XericUI.FlipPage
} }
#endregion #endregion
} }
} }