位运算的一种精妙应用
‘|’核心:单一|组合:(相同类型数据)扩展组合
‘&’核心:单一&组合:筛选是否存在相同点(该单一是否存在在该组合)
因为位运算是基于二进制形式的数据。因此可以通过枚举(或其他int类型的数据结构)来进行这种精妙的应用,只需给对象一个标志符(int类型的即可),将该标识符进行位运算即可做到 组合与筛选。
例子:过滤器模式,可以给过滤器的过滤类多一个标识符(如id)。用户在需要过滤时传入 过滤规则组合(‘|’运算组合的id)
然后在过滤算法对标识符(id)进行‘&’筛选出需要的过滤规则即可。
优点:非常灵活(在现有的基础上有新的需求,可以在不改变核心代码的情况下,仅通过改变自定义的组合来满足需求)。
缺点:增加复杂度,且后续维护相对不容易(其核心类可能会越来越臃肿)。而且二进制的位数是有限的,这代表无法无限扩展。
适用场景:适合一些相对比较简单、容易改变的 组合、筛选 的应用场景。并且应用场景在未来的扩展是有限的。
实际应用
例子:比如在一个需要显示各种资源剩余情况的UI。而在不同的界面需要显示不同的资源剩余情况UI。在商店需要显示当前金币;在好友列表需要显示亲密度;在属性界面需要显示当前可用属性点和金币。此时使用其灵活的组合就能快速满足需求的实现了。
过程:
1.设计信息显示导航栏预设体与类
将导航栏所有需要显示的信息都放在预制体中(需要划分好左右等结构)。在脚本中对应设计该预制体的类UINavigation。
当一个Panel需要显示导航栏中的部分信息(如金币、属性点),就直接在Panel创建放置的位置对象,在该对象下生成该导航栏类,并激活(create()方法-创建预设体设置父类等)。然后调用这个类并将需要的显示的信息进行激活SetStyle()
2.设计需要显示导航栏的UI类
在UI类中新建UINavigation类,并进行设置需要显示UI(style)即可
代码:
预设体例子:
public enum UINavigationStyle
{
//每个新的基数单位必须是前一个基数单位的2倍(平方)
//目的:保证组合后的枚举转换的二进制数的每一位都代表一个基数枚举(避免组合重叠紊乱)
//例如:需要一个组合枚举为Name1_2_5_6 = Name1|Name2|Name5|Name6 = 1|2|16|32 = 2^0+2^1+2^4+2^5 = 1+2+16+32 = 51。
//所以下一个数必须保证所有之前的数的位运算-或运算符要小于下一个数。即2的平方。
//针对枚举再通过泛型进行&运算来解析是否包含该枚举。
//转为int(二进制&运算),判定该位(枚举)是否为1(存在)。来返回bool值。此时可以使用该bool进行一些Active的设置等操作
None = 0x000, //0
Name1 = 0x001, //2^0
Name2 = 0x002, //2^1
Name3 = 0x004, //2^2
Name4 = 0x008, //2^3
Name5 = 0x010, //2^4 = 16,进一位
Name6 = 0x020, //2^5 = 32
Name1_5 = Name1|Name5,
Name1_2_5_6 = Name1 | Name2 | Name5 | Name6,
}
未完成的类
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using static GlobalPropertyMgr;
/// <summary>
/// 信息显示-导航栏
/// 说明:与UIPath读取的预设体对应,并读取目标预设体的所有组件
/// </summary>
public class UINavigation
{
public GameObject Go;
private static string UIPath = "UINavigation"; //UI组件预设体资源路径
private UINavigationStyle style = UINavigationStyle.None; //默认显示设置
/// <summary>
/// 初始化数据
/// </summary>
private void Init()
{
//根据生成的UINavigation预设体进行绑定组件
//...
}
/// <summary>
/// 数据清除
/// </summary>
private void Clear()
{
//将注册的事件移除
//...
}
/// <summary>
/// 创建UINavigation
/// 备注:创建后不再使用时用Clear()进行数据、事件清除
/// </summary>
/// <param name="parent">要绑定的父对象</param>
/// <returns></returns>
public static UINavigation Create(Transform parent)
{
//尝试创建组件
GameObject prefab = Resources.Load<GameObject>(UIPath);
UINavigation navigation = new UINavigation();
navigation.Go = GameObject.Instantiate(prefab, parent);
//预设体的组件绑定
navigation.Init();
return navigation;
}
/// <summary>
/// 根据style设定UINavigation
/// </summary>
/// <param name="style">需要的组合枚举</param>
public void SetStyle(UINavigationStyle style)
{
//自定义显示的部分UI与当前显示设定的不同
if(this.style!= style)
{
//判定是否需要重置
if (true)
{
//重置旧的UI设定
_UnSetStyle(style);
//重新设定UI
_SetStyle(style);
}
//更新当前显示设定为自定义显示设定
this.style = style;
}
}//SetStyle_end
private void _SetStyle(UINavigationStyle style)
{
//对style进行解析。逐个筛选,将style存在的每个目标的对应UI进行激活(Active=true),不存在的UI进行隐藏
//...
//使用ToolMgr的IsContain(style,目标对应的枚举)进行获取bool(如果目标枚举存在在style中)
//将style存在的每个目标的对应UI绑定的事件进行添加(注册)
//...
}
private void _UnSetStyle(UINavigationStyle style)
{
//将style存在的每个目标的对应UI绑定的事件进行移除(反注册)
//...
}
}//class_UINavigation_end
未完成的 筛选 方法(可以使用从元数据的泛型转int的方法)
/// <summary>
/// 判定一个枚举是否存在在组合的枚举中
/// </summary>
/// <typeparam name="T">枚举</typeparam>
/// <param name="flags">组合枚举</param>
/// <param name="flag">目标枚举</param>
/// <returns></returns>
public static bool IsContain<T>(T flags, T flag) where T : struct
{
//将T(枚举)转为int
//...
int flagsValue = 0;
int flagValue = 0;
//通过位运算:&,判定flag是否存在flags中。若存在则返回true
return (flagsValue & flagValue)!=0;
}
相关导航栏部分需要重构,上述代码就写了个框架。后续完成重构再进行实例演示。