背景
在最初的UniVue框架中,所有数据绑定都是基于实现IBindableModel接口的,只要我想要实现某个数据绑定到UI上,我就不得不去创建一个向对应的类,如果要绑定的数据是一个整体,比如:一个Player类,这个类创建就有意义。但是现在有个需求我想要实时显示当前倒计时的时间,放在最初的版本中为了实现将这个只有一个属性的int值与UI进行绑定,我可能不得不创建一个TimeData类,如下所示:
public sead class TimeData : IBindableModel
{
private int _timer;
public int Timer
{
get=>_timer;
set
{
if(value!=_timer)
{
_timer = value;
NotifyUIUpdate("Timer", value);
}
}
}
}
这样显得太冗余了,显得很笨重。基于这一点,有没有能够实现对于这样一些单个的可能绑定的类型能够不用创建额外的类也能实现数据绑定呢?(UniVue中的数据绑定到UI都是基于属性的,支持的属性类型有int、bool、string、float、枚举、Sprite(Unity中的精灵图)、List<bool>、List<int>、List<string>、List<枚举>、List<float>、List<Sprite>。)
为此这两天改进了Model层,使用了AtomModel来实现那些只需要绑定单个属性数据的场景,以及GroupModel这种更加灵活的模型。使用GroupModel可以实现任何你自定义的类型,多个AtomModel的组合也是能够实现的。
具体使用例子
在今天的更新的仓库项目(https://github.com/Avalon712/UniVue-Develop)测试场景ModelTest可以看见ModelTest这个类中的数据定义:(这里使用了源生器自动生成了那些低级的代码)
//这里使用了UniVue.SourceGenerator来自动生成代码(即c#源生成器,使用源生器使得Model层看起来很简单明了)
public sealed partial class Player
{
[AutoNotify] private string _name;
[AutoNotify] private int _level;
[AutoNotify] private int _stars;
[AutoNotify] private int _Exp;
[AutoNotify] private int _HP;
[AutoNotify] private Tag _tag;
[AutoNotify] private Profession _profession;
}
public enum Profession
{
Rider,
Archer,
Saber,
}
[Flags]
public enum Tag
{
Tag1 = 1,
Tag2 = 2,
Tag3 = 4,
Tag4 = 8,
Tag5 = 16
}
在场景中可以看到下面三个视图,分别代表当模型的数据绑定实现。
UniVue - ModelTest
下面是测试工程源码(这儿只看核心部分)
private void Start()
{
FlexibleView view = new(playerControlView);
FlexibleView view1 = new(Player_PlayerInfoView);
FlexibleView view2 = new(GroupModel_PlayerInfoView);
FlexibleView view3 = new(AtomModel_PlayerInfoView);
//使用Player原生模式进行数据绑定
Player player = new()
{
Name = "Jerry",
Level = 21,
Stars = 2,
Exp = 1528,
HP = 62,
Tag = Tag.Tag3 | Tag.Tag5,
Profession = Profession.Saber
};
view1.BindModel(player, true);
view.BindModel(player, true); //将玩家数据绑定到控制面板视图上,方便后面修改数据
//使用GroupModel进行数据绑定
GroupModel group = new("Player", 7);
group.AddProperty(new StringProperty(group, nameof(player.Name), player.Name))
.AddProperty(new IntProperty(group, nameof(player.Level), player.Level))
.AddProperty(new IntProperty(group, nameof(player.Stars), player.Stars))
.AddProperty(new IntProperty(group, nameof(player.Exp), player.Exp))
.AddProperty(new IntProperty(group, nameof(player.HP), player.HP))
.AddProperty(new EnumProperty<Tag>(group, nameof(player.Tag), player.Tag))
.AddProperty(new EnumProperty<Profession>(group, nameof(player.Profession), player.Profession));
view2.BindModel(group, true);
//使用AtomModel进行数据绑定 --> 如果属性较多不推荐使用这种方式,这儿只是演示AtomModel的使用
AtomModel<string> nameAtom = AtomModelBuilder.Build("Player", nameof(player.Name), player.Name);
AtomModel<int> levelAtom = AtomModelBuilder.Build("Player", nameof(player.Level), player.Level);
AtomModel<int> starsAtom = AtomModelBuilder.Build("Player", nameof(player.Stars), player.Stars);
AtomModel<int> expAtom = AtomModelBuilder.Build("Player", nameof(player.Exp), player.Exp);
AtomModel<int> hpAtom = AtomModelBuilder.Build("Player", nameof(player.HP), player.HP);
AtomModel<Tag> tagAtom = AtomModelBuilder.Build("Player", nameof(player.Tag), player.Tag);
AtomModel<Profession> professionAtom = AtomModelBuilder.Build("Player", nameof(player.Profession), player.Profession);
view3.BindModel(nameAtom, true)
.BindModel(levelAtom, true)
.BindModel(starsAtom, true)
.BindModel(expAtom, true)
.BindModel(hpAtom, true)
.BindModel(tagAtom, true)
.BindModel(professionAtom, true);
}
逻辑很简单就是创建测试数据然后分别绑定到三个视图上。
注意本次范例中对AtomModel的使用只是一个作为例子讲解,不推荐使用很多AtomModel去组合构成,因为每次BindModel是由一定开销的,反复绑定那么多次很不明智。
同时AtomModel和GroupModel全部不会进行值类型装箱操作、也没有反射。性能方面不用担心,为了实现这些细微的性能优化,可以经过了很深的代码架构设计。没办法,如果装箱肯定是很方便的,但是考虑到数据更新频繁的话,会在堆上开辟那么多小对象,这一笔开销还是能省则省吧。当然如果本身就是引用类型的话,就不会有这个影响。