概述
1.最外层DataForm为空壳编辑数据用。可以有多个DataForm,例如福利DataForm,抽奖DataForm
2.Menu层为左边栏层,每个DataForm可以使用不同样式的MenuForm预制体
3.DataForm中使用ReorderList,可排列配置
4.有定位功能,跳转到对应页签
5.DataForm具有树状图管理,1级,2级菜单
6.PageForm为每个页签的具体生命周期脚本,由DataForm控制。需要显示时如果没加载过,从资源加载。
TabViewDataForm
树状图数据
每个主UI使用共用的脚本,用于编辑左边页签数据结构。主UI,即分配ID,可以通过UI管理器加载出来。例如福利主UI,其中包含多个子页签
树状图菜单分为3种,1级无展开,1级带展开,2级
树状图数据
public List<TabView> m_listItem = new List<TabView>(4);
/// <summary>
/// 面板上编辑TreeView数据,包含1级,2级
/// </summary>
[System.Serializable]
public class TabView
{
public TabViewItem m_item;//1级
public List<TabViewItem> m_listSubItem = null; //2级列表
}
//每个菜单的data
[System.Serializable]
public class TabViewItem
{
public string m_name; //无用字段
public string m_assetPath; //资源路径,从Assets/开始
public string m_dicKey; //字典key,用于多语言
public string m_chName; //中文注释名,程序不用,策划可以面板上看排列
}
使用ReorderableList自定义面板编辑
生成TabView枚举
从外部需要跳转到主UI树状图的某个菜单,给1级,2级菜单分配唯一id
规则为枚举名为预制体名+TabViewForm
枚举里每项为这个页签的assetPath路径中的预制体名。例如assetPath 为 Assets/TestTabView0_0.prefab,那么名字为TestTabView0_0
值每个页签first * 1000+ second。
每次如果UI预制体修改了树状图顺序,重新生成一遍TabView枚举
public enum TestTabViewMainFormTabViewEnum
{
TestTabView0_0 = 0,
TestTabView0_1 = 1,
TestTabView1_0 = 1000,
TestTabView1_1 = 1001,
TestTabView2 = 2000,
}
加载TabViewMenuForm
主UI(即绑定TabViewDataForm)的空格UI实例化,OnInit加载TabViewMenuForm预制体
protected override void OnInit(object userData)
{
//实例化创建MenuForm
StarForce.GameEntry.Resource.LoadAsset(m_menuFormAssetPath, m_LoadResourceCallbacks);
}
加载完毕,设置跳转参数到MenuForm中
private void LoadAssetSuccessCallback(string AssetName, object asset, float duration, object userData)
{
if (AssetName == m_menuFormAssetPath)
{
m_menu = inObj.GetComponent<TabViewMenuForm>();
m_menu.SetInfo(this, m_openFistIdx, m_openSecondIdx);
m_assetMenu = obj;//保存Menu预制体加载在内存中的asset,用于UI销毁时引用-1
}
打开第几菜单并传递参数
主UI打开时传入参数
//打开TabView,可定位第几个
public class TabViewOpenData
{
public int m_idx; //打开第几个 first * 位数 + second
public object m_param; //额外参数,传递给page
}
TabViewOpenData openData = new TabViewOpenData();
openData.m_idx = (int)TestTabViewMainFormTabViewEnum.TestTabView1_1;
openData.m_param = "1234";
StarForce.GameEntry.UI.OpenUIFormById((int)UIFormId.TestTabViewMainForm, openData);
在主UI打开时
如果MenuForm加载过,调入传入参数idx,确定打开第几个
如果MenuForm未加载过,保存参数,等加载完成回调后打开第几个
protected override void OnOpen(object userData)
{
base.OnOpen(userData);
DataInit();
if (userData != null)
{
UIFormParams openUserData = userData as UIFormParams;
if (openUserData.UserData != null)
{
m_openData = openUserData.UserData as TabViewOpenData;
GetFirstSecondByIdx(m_openData.m_idx, out m_openFistIdx, out m_openSecondIdx);
if (m_menu != null)
{
//已经加载成功过menu,直接用。否则等加载结束调用
m_menu.Select(m_openFistIdx, m_openSecondIdx);
}
}
else
{
//没有传入参数,默认打开第0个
m_openFistIdx = 0;
m_openSecondIdx = 0;
if (m_menu != null)
{
//已经加载成功过menu,直接用。否则等加载结束调用
m_menu.Select(m_openFistIdx, m_openSecondIdx);
}
}
}
切换Page
树状菜单点击时或者传入参数打开时
如果跟上次点击idx不一致,把上Page置为false,判断idx的page是否存在,存在即置为true,不存在即等待加载
public void OnClickMenu(int first,int second)
{
PublicFunc.Log($"点击菜单{first}-{second}");
int key = GetIdxByFirstSecond(first, second);
if (key != m_lastClickIdx)
{
SetPageActive(m_lastClickIdx, false);
if (SetPageActive(key, true) == false)
{
//操作当前页打开失败,说明未加载Page,执行加载逻辑
TabViewItem tabViewItem = GetDataByFirstSecond(first, second);
m_nameSetActiveAfterLoad = tabViewItem.m_assetPath; //这个每次点都只会让最新的SetActive true
TabViewPage tabViewPage = new TabViewPage();
tabViewPage.m_firstIdx = first;
tabViewPage.m_secondIdx = second;
tabViewPage.m_data = tabViewItem;
StarForce.GameEntry.Resource.LoadAsset(tabViewItem.m_assetPath, m_LoadResourceCallbacks, tabViewPage);
}
m_lastClickIdx = key;
}
//只要点击过menu一次,不管是主动点,还是传递参数点。open参数都需要无效
m_openData = null;
}
销毁时所有asset引用-1
这里不是直接把ab加载出来的asset卸载,而是把asset的引用-1,然后为0的asset会放入待回收池,等待回收池容量满时,卸载未使用的ab(即ab所有加载出来的asset引用= 0)
protected override void OnDestroy()
{
base.OnDestroy();
//已经加载过的page的asset 进行asset引用-1
foreach (var item in m_dicPage)
{
if (item.Value.m_asset != null)
{
StarForce.GameEntry.Resource.UnloadAsset(item.Value.m_asset);
}
}
//Menu.asset 引用 - 1,如果别的UI还复用此Menu,如果引用不为0,不会卸载Menu.bundle
if (m_assetMenu != null)
{
StarForce.GameEntry.Resource.UnloadAsset(m_assetMenu);
}
m_LoadResourceCallbacks = null;
}
TabViewMenuForm
所有界面可复用几个MenuForm,Menu具有不同的样式,即UI图片不同。注意不可使用TabViewMenuForm的OnEnable,OnDisable,因为生命周期是跟随主UI的,因为主UI的OnOpen,OnClose中去调用MenuForm
创建Menu
Menu分为3种,1级无展开,1级带展开,2级
滚动层使用自动布局
FirstMenu使用布局高度
SecondMenu使用布局高度
加载时按照索引加载
void CreateAllMenu()
{
for (int i = 0; i < m_dataForm.m_listItem.Count; i++)
{
GameObject first = GameObject.Instantiate(m_objFirstMenu, m_transContent);
TabViewMenuItem item = first.GetComponent<TabViewMenuItem>();
item.m_firstIdx = i;
item.m_secondIdx = 0;
first.SetActive(true);
TabViewItem firstData = m_dataForm.m_listItem[i].m_item;
item.SetData(firstData);
item.m_isExpend = false;
item.m_isSelect = false;
m_dicUIFirstMenu.Add(item.m_firstIdx, item);
int childCount = 0;
if (m_dataForm.m_listItem[i].m_listSubItem != null)
{
List<TabViewMenuItem> listSub = new List<TabViewMenuItem>(4);
m_dicUISecondMenu.Add(item.m_firstIdx, listSub);
childCount = m_dataForm.m_listItem[i].m_listSubItem.Count;
for (int secondIdx = 0; secondIdx < m_dataForm.m_listItem[i].m_listSubItem.Count; secondIdx++)
{
GameObject second = GameObject.Instantiate(m_objSecondMenu, m_transContent);
并把1级(有展开与无展开),2级记录到字典中
public Dictionary<int, TabViewMenuItem> m_dicUIFirstMenu = new Dictionary<int, TabViewMenuItem>(8); //1级字典
public Dictionary<int, List<TabViewMenuItem>> m_dicUISecondMenu = new Dictionary<int, List<TabViewMenuItem>>(8); //2级
跳转定位
public void Select(int firstIdx = 0, int secondIdx = 0)
{
//全部1级收缩,ui恢复为默认状态
InitFirstUI();
//新的first 打开
TabViewMenuItem newFirst;
if (m_dicUIFirstMenu.TryGetValue(firstIdx, out newFirst))
{
ExpendSecond(firstIdx, true);
newFirst.SetExpend(true);
newFirst.SetSelect(true);
}
//如果存在2级,选中
SelectSecond(firstIdx, secondIdx, true);
m_dataForm.OnClickMenu(firstIdx, secondIdx);
JumpTo(firstIdx);
m_lastFirstMenu = firstIdx;
m_lastSecondMenu = secondIdx;
}
树状图滚动层作为相应处理,把fist置为可视区第一行
1.如果contentHeight <= 可视区height,不处理
2.如果需要定位的menu在最后面,只需要contentHeight - 可视区height 即为content需要上拉的高度
//跳转到第几项
public void JumpTo(int firstIdx)
{
float cotentHeight = m_transContent.GetComponent<RectTransform>().rect.height;
float diffY = m_firstMenuHeight * firstIdx;
Vector3 oldPos = Vector3.zero;
if (cotentHeight <= m_viewHeight)
{
diffY = 0;
}
else if (m_viewHeight - (cotentHeight - diffY) > 0)
{
//最后一行超过了
diffY = cotentHeight - m_viewHeight;
}
oldPos.y += diffY;
m_transContent.localPosition = oldPos;
点击处理
1.点击第一级无展开,即向主UI传递,加载/显示Page
2.点击第一级展开,作为展开,收缩处理。如果展开,判断每次主UI打开,有没选择过该First下的某个,没有选中Second = 0的,向主UI传递。如果选择过,选择上次选中的second向主UI传递
3.点击2级,向主UI传递
TabViewPageForm
所有的page,继承于此。作用
1.用于控制生命周期,跟Menu一样,不能使用OnEnable,OnDisable,会导致数据有问题,即主UI被覆盖,然后关闭别的UI,恢复主UI,不该初始化时初始化了数据
2.传递到主UI的参数最终传递到Page
public class TabViewPageForm : MonoBehaviour
{
public virtual void OnInit(object userData = null)
{
}
public virtual void OnOpen(object userData = null)
{
}
继承PageForm的类示例
public class TestTabViewPageForm : TabViewPageForm
{
public UnityEngine.UI.Text m_text;
public override void OnInit(object userData = null)
{
}
public override void OnOpen(object userData = null)
{
if (userData != null)
{
string param = userData as string;
m_text.text = param;
PublicFunc.Log($"传入参数{param}");
}
}
在TabViewDataForm中如果打开主UI第一次跳转page,在加载/显示时会传递参数
if (m_openData != null)
{
if (m_openData.m_idx == idx)
{
page.OnInit(m_openData.m_param);
page.OnOpen(m_openData.m_param);
m_openData = null;
}
else
{
page.OnInit();
page.OnOpen();
}
}
流程图
效果演示
跳转1_1,并传入参数1234
TabViewOpenData openData = new TabViewOpenData();
openData.m_idx = (int)TestTabViewMainFormTabViewEnum.TestTabView1_1;
openData.m_param = "1234";
StarForce.GameEntry.UI.OpenUIFormById((int)UIFormId.TestTabViewMainForm, openData);
点击不同页签,切换PageView