效果
左下角按钮代表的红点路径是右边两个按钮的中间路径,代表某个窗口,实际项目中遇到过以窗口为路径终点的红点。例如每次登录都有个针对于打开充值界面的红点,打开后红点就消失,这个红点不针对于按钮
思路
参考网站:
LeetCode 208:实现Trie(前缀树)——C#实现
【游戏开发实战】手把手教你在Unity中使用lua实现红点系统(前缀树 | 数据结构 | 设计模式 | 算法 | 含工程源码)
另外觉得这篇的写法不是很好
基于前缀树的红点系统 - movin2333 - 博客园
基于前缀树生成的红点树。
比如我们插入abc、abh、acg三个单词,在树中的结构是这样:
那如果我再插入abc,怎么办呢?结构依然是上面那样,节点本身会记录字母出现的次数,比如我们设计节点存储的信息如下:
所以插入两次abc后,树节点的信息如下,
当我们要去树中查询abc出现过几次的时候,只需要把abc分割成a、b、c,从根节点依次往下查询是否存在a、b、c,最终返回c节点的endCnt即可,如果想查询以ab为前缀的单词在树中出现了多少次,则分割为a、b后,从根节点往下查询a、b,然后返回b节点的passCnt即可,这也是前缀树的命名的由来。
只需要在上面的基础上,给节点加一个红点数的数据即可,如下 我们将红点进行规范命名:层级1||||层级2||||层级3,例Root||||ModelA||||ModelA_Sub_1,我们把它以|符号分割,然后插入树中,树变成这样子: 我们再插入一个Root||||ModelA||||ModelA_Sub_2,树变成这样子:
我们再插入Root||||ModelB||||ModelB_Sub_1,树变成这样子: 当我们要查询ModelA有多少个红点的时候,则通过Root||||ModelA来查询,以||||为分割符,从根节点出发,找到ModelA节点后,返回ModelA的redpointCnt即为对应的红点数。
代码
单个红点类
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;
public class RedPointTreeNode
{
private string name;
public string Name => name;
private int passCount;
public int PassCount
{
get => passCount;
private set
{
passCount = value;
RedPointCount = passCount + endCount;
}
}
private int endCount;
public int EndCount
{
get => endCount;
private set
{
endCount = value;
RedPointCount = passCount + endCount;
}
}
private int _redPointCount;
public int RedPointCount
{
get => _redPointCount;
set
{
_redPointCount = value;
redPointCountUpdateCallback?.Invoke(_redPointCount);
}
}
private UnityAction<int> redPointCountUpdateCallback;
private List<RedPointTreeNode> children = new List<RedPointTreeNode>();
public List<RedPointTreeNode> Children => children;
public void SetName(string name)
{
this.name = name;
}
public void PlusPassCount()
{
PassCount++;
}
public void MinusOnePassCount()
{
PassCount--;
}
public void ChangeSpecificPassCount(int specificCount)
{
PassCount += specificCount;
}
public void MinusEndCount()
{
EndCount--;
}
public void ChangeSpecificEndCount(int specificCount)
{
EndCount += specificCount;
}
public void PlusEndCount()
{
EndCount++;
}
public void AddRedDotCountUpdateCallback(UnityAction<int> updateCallback)
{
this.redPointCountUpdateCallback += updateCallback;
}
public void RemoveRedDotCountUpdateCallback(UnityAction<int> updateCallback)
{
this.redPointCountUpdateCallback -= updateCallback;
}
public void ClearRedDotCountUpdateCallback()
{
this.redPointCountUpdateCallback = null;
}
public void AddChild(RedPointTreeNode childNode)
{
children.Add(childNode);
}
public RedPointTreeNode GetChild(string nodeName)
{
foreach (var child in children)
{
if (child.Name.Equals(nodeName))
{
return child;
}
}
return null;
}
public void RemoveChild(RedPointTreeNode childNode)
{
if (children.Contains(childNode))
{
children.Remove(childNode);
}
}
}
使用方式
先放使用方式比较容易看下去,这里只是比较简单地测试了一下,没有问题,复杂一些的树没测试过,可能会有点问题。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class TestRedDot : MonoBehaviour
{
private const string redDotWindowName = "RedDot";
private const string redDotButton1Name = "RedDotButton1";
private const string redDotButton2Name = "RedDotButton2";
private string redDotButton1Str = redDotWindowName + RedPointTreeConst.splitChar[0] + redDotButton1Name;
private string redDotButton2Str = redDotWindowName + RedPointTreeConst.splitChar[0] + redDotButton2Name;
private string redDotWinStr = redDotWindowName ;
[SerializeField]
private Text redDotButton1Text;
[SerializeField]
private Text redDotButton2Text;
[SerializeField]
private Text redDotWinText;
[SerializeField]
private Button redDotButton1;
[SerializeField]
private Button redDotButton2;
[SerializeField]
private Button redDotButtonWin;
void Start()
{
RedPointTree.Instance.PlusReddotCount(redDotButton1Str);
RedPointTree.Instance.PlusReddotCount(redDotButton2Str);
RedPointTree.Instance.PlusReddotCount(redDotWinStr);
redDotButton1Text.text = RedPointTree.Instance.GetNodeRedPointCount(redDotButton1Str).ToString();
redDotButton2Text.text = RedPointTree.Instance.GetNodeRedPointCount(redDotButton2Str).ToString();
redDotWinText.text = RedPointTree.Instance.GetNodeRedPointCount(redDotWinStr).ToString();
RedPointTree.Instance.AddRedPointCountChangedCallback(redDotButton1Str, redDotCount =>
{
redDotButton1Text.text = redDotCount.ToString();
});
RedPointTree.Instance.AddRedPointCountChangedCallback(redDotButton2Str, redDotCount =>
{
redDotButton2Text.text = redDotCount.ToString();
});
RedPointTree.Instance.AddRedPointCountChangedCallback(redDotWinStr, redDotCount =>
{
redDotWinText.text = redDotCount.ToString();
});
redDotButton1.onClick.AddListener(OnRedDotButton1Click);
redDotButton2.onClick.AddListener(OnRedDotButton2Click);
redDotButtonWin.onClick.AddListener(OnRedDotButtonWinClick);
}
public void OnRedDotButton1Click()
{
RedPointTree.Instance.MinusReddotCount(redDotButton1Str);
}
public void OnRedDotButtonWinClick()
{
RedPointTree.Instance.MinusReddotCount(redDotWinStr);
}
public void OnRedDotButton2Click()
{
RedPointTree.Instance.MinusReddotCount(redDotButton2Str);
}
void Update()
{
}
}
根据实际的需求,红点树只开放获取红点路径下的节点的红点数目,红点路径下的节点的红点数目加一或者减一。
红点树的主要逻辑是在减少红点数目的时候,从上往下判断某节点的红点数目是否有为0,为0 则其下的节点的红点都为0.即删除节点。
在增加红点数目的时候,则根据路径看是否增加节点。
这种类型的红点树需要UI上的节点都知道自己所有父UI的名字,才能组合成红点路径,这样的路径需要另外的UI框架来生成。
红点树打算和UI本身的树分开来做,合并到一起后面这个树会因为职责扩散而显得很复杂 更改容易影响到其他模块,不容易维护
如果确保了节点的名字和UI树本身的名字是用的都是常量。一般不会出现UI树节点找红点树,即使红点数存在对应的节点也找不到的情况
红点树插入节点
void InsertNodeIfNone(string modulePathString)
{
if (string.IsNullOrEmpty(modulePathString))
{
return;
}
RedPointTreeNode matchNode = SearchNode(modulePathString);
if (matchNode != null)
{
return;
}
string[] nodeWinNames = modulePathString.Split(RedPointTreeConst.splitChar, StringSplitOptions.None);
RedPointTreeNode currentNode = rootNode;
foreach (var nodeWinName in nodeWinNames)
{
if (!string.IsNullOrEmpty(nodeWinName))
{
RedPointTreeNode childNode = currentNode.GetChild(nodeWinName);
if (childNode != null)
{
currentNode = childNode;
continue;
}
childNode = new RedPointTreeNode();
childNode.SetName(nodeWinName);
currentNode.AddChild(childNode);
currentNode = childNode;
}
}
}
红点树删除节点
void DeleteNode(string modulePathString)
{
bool deleteNoRedDot = false;
RedPointTreeNode currentNode = rootNode;
string[] nodeWinNames = modulePathString.Split(RedPointTreeConst.splitChar, StringSplitOptions.None);
RedPointTreeNode currentNodeParent = null;
RedPointTreeNode childNode;
foreach (var nodeWinName in nodeWinNames)
{
childNode = currentNode.GetChild(nodeWinName);
if (childNode == null)
{
break;
}
else
{
currentNodeParent = currentNode;
currentNode = childNode;
if (childNode.RedPointCount == 0)
{
deleteNoRedDot = true;
break;
}
}
}
if (currentNode != null && currentNodeParent != null && deleteNoRedDot)
{
currentNodeParent.RemoveChild(currentNode);
}
}
红点树查找节点
RedPointTreeNode SearchNode(string modulePathString)
{
RedPointTreeNode currentNode = rootNode;
string[] nodeWinNames = modulePathString.Split(RedPointTreeConst.splitChar, StringSplitOptions.None);
foreach (var nodeWinName in nodeWinNames)
{
currentNode = currentNode.GetChild(nodeWinName);
if (currentNode == null)
{
return null;
}
}
return currentNode;
}
红点树更改节点红点数目
void ChangeRedPointCount(string modulePathString, int deltaCount)
{
if (string.IsNullOrEmpty(modulePathString))
{
return;
}
RedPointTreeNode matchNode = SearchNode(modulePathString);
if (matchNode == null)
{
return;
}
if (deltaCount < 0 && matchNode.EndCount + deltaCount < 0)
{
deltaCount = -matchNode.EndCount;
}
RedPointTreeNode currentNode = rootNode;
RedPointTreeNode childNode = rootNode;
string[] nodeWinNames = modulePathString.Split(RedPointTreeConst.splitChar, StringSplitOptions.None);
for (int i = 0; i < nodeWinNames.Length; i++)
{
string nodeWinName = nodeWinNames[i];
childNode = currentNode.GetChild(nodeWinName);
if (childNode == null)
{
break;
}
if (i < nodeWinNames.Length - 1)
{
childNode.ChangeSpecificPassCount(deltaCount);
}
currentNode = childNode;
}
if (currentNode != null)
{
currentNode.ChangeSpecificEndCount(deltaCount);
}
}
对外暴露的方法
public int GetNodeRedPointCount(string modulePathString)
{
RedPointTreeNode matchNode = SearchNode(modulePathString);
if (matchNode != null)
{
return matchNode.RedPointCount;
}
return 0;
}
public void PlusReddotCount(string modulePathString)
{
InsertNodeIfNone(modulePathString);
ChangeRedPointCount(modulePathString, 1);
DeleteNode(modulePathString);
}
public void MinusReddotCount(string modulePathString)
{
InsertNodeIfNone(modulePathString);
ChangeRedPointCount(modulePathString, -1);
DeleteNode(modulePathString);
}
public void AddRedPointCountChangedCallback(string modulePathString, UnityAction<int> callback)
{
RedPointTreeNode matchNode = SearchNode(modulePathString);
if (matchNode != null)
{
matchNode.AddRedDotCountUpdateCallback(callback);
}
}
Demo
下载地址
|