github仓库
稳定版本仓库:https://github.com/Avalon712/UniVue
开发版本仓库:https://github.com/Avalon712/UniVue-Develop
UniVue扩展框架-UniVue源生成器仓库:https://github.com/Avalon712/UniVue-SourceGenerator
更新说明
如果大家有在使用之前的版本或学习之前的版本一定要进行更新到至少是今天的版本,因为今天发现了一个过去的版本的一个重大的BUG(就是视图的BindModel()函数有可能会重复生成UIBundle,这个BUG配合LoopList和LoopGrid组件的功能,会导致UIBundle数量一直在重复叠加生成,非常严重的BUG。一定要记得更新。)
组件功能与使用介绍
UniVue中提供的LoopList和LoopGrid组件是UniVue中重要的组件。首先他们都是依赖UGUI的ScrollRect组件实现的可复用Item的滚动视图(即:如果背包有1000个物品,实际上实例化的GameObject的数量=可视域内的行数*可视域内的列数cols+一行或一列的数量,比如:可视域内只能看见6*6个格子,那么实际的生成的GameObject数量为6*7或7*6,至于是6*7还是7*6取决于滚动方向。对于ListWidget生成的数量也是同样的道理即比可视域的数量多一个)。其中ListWidget提供无限滚动的功能(感觉这个功能应该会用得很少,但是考虑到可以使用这个组件的无限循环滚动功能实现轮播图的效果就保留了),GridWidget则没有提供,因为我相信真实项目中一定不会用到一个背包去无限循环滚动的功能。
关于UniVue中使用的所有例子都能在UniVue-Develop(https://github.com/Avalon712/UniVue-Develop)这个仓库中看到。下面是两个组件的使用:
两个组件都支持水平滚动和垂直滚动。通过右上方的输出调试信息可以看见Views的数量。由于这两个组件会将ScrollRect的Content下的每个Item都创建一个FlexibleView,因此从这个这个View的数量可以看见实际实例化的GameObject数量。
另外这两个组件不依赖任何Unity中的布局组件,而是内部实现得有一套布局逻辑。
注意:ScrollRect的Content这个GameObject的锚点一定要设置合理。content的布局设置如下面的两个图。(同时content下的第一个Item必须存在,且锚点设置必须为左上角,位置为(0,0,0),因为后面实例化其它Item的GameObject时会将第一个Item作为预制体进行实例化)。
这两个组件再搭配上UniVue的数据模型双向绑定的功能,就能够实现数据的自更新,UI的实时响应。而且实现所有上述的功能大概你需要写的代码数量就是两行。一行创建视图,一行绑定数据。
关于性能方法,这是完全没得说的,我已经优化到极致了。没有使用什么对象池以及其它的功能,关于具体如何实现的,后面再将吧。
但是今天更新的内容一是优化的这两个组件的视图刷新逻辑和数据重新绑定。同时另外一个重大的更新是使用了UniVue中的ObservableList数据结构优化了之前再绑定List数据会对绑定的List数据进行拷贝(本次更新任然保留了这种模式,但是建议大家使用ObervableList的方式)。使用ObservableList能够完全实现数据驱动式。如果不使用ObservableList,采用原来的List方法,可能每次添加新数据、移除新数据、对数据排序、改变数据的顺序你都要进行两步操作:一是改变你自己维护的那么List数据,二是改变组件内部的维护的List数据。这样真的很麻烦,而且会对List进行拷贝的方式,如果要绑定的数据本来就很多,现在还要拷贝一份。我忍受不了这种低效的方式,所有设计了一个新的List集合来优化上面的低效。
ObservableList<T>数据结构的实现
这个集合的实现其实就是对C#自带的List进行包装修饰(装饰器模式),核心功能和List是一样的,但是对List的一些对数据内容的修改进行了封装,能够实现实现当内容发生改变时自动调用回调函数。这个类的使用和List的使用完全一样,List所有支持的操作它都能进行。同时支持注册回调事件、以及实现Java中将List变为一个不可变的集合(变为不可变的集合后,后面对集合内容的修改都会抛出异常)。其中提供的List扩展功能就是IObservableList接口定义的功能。ObservableList<T>这个类的实现代码太长了,建议大家可以去源码中查看,这个类定义在UniVue>Runtime>Utils>Extensions这个目录下,命名空间为UniVue.Utils。
/// <summary>
/// 事件调用顺序:OnAdded | OnRemoved | OnReplaced ----> OnChanged
/// </summary>
public interface IObservableList
{
/// <summary>
/// 获得当前回调函数的观察者的id
/// </summary>
/// <remarks>没有的话将返回int.MinValue</remarks>
int CurrentObserverId { get; }
int Capacity { get; set; }
int Count { get; }
V Get<V>(int index);
void Set<V>(V item, int index);
void Add<V>(V item);
bool Remove<V>(V item);
bool Contains<V>(V item);
void RemoveAt(int index);
int IndexOf<V>(V item);
void Sort();
void Sort<V>(IComparer<V> comparer);
/// <summary>
/// 当集合内容发生改变时调用(包括元素的增加、删除、替换、顺序改变)
/// </summary>
void AddListener_OnChanged(int observerId, Action<NotificationMode> onChanged);
/// <summary>
/// 当有元素被移除时回调(arg0: 被移除的元素 arg1: 被移除元素的索引;)
/// </summary>
void AddListener_OnRemoved<V>(int observerId, Action<V, int> onRemoved);
/// <summary>
/// 当有新数据添加时回调
/// </summary>
void AddListener_OnAdded<V>(int observerId, Action<V> onAdded);
/// <summary>
/// 当元素被替换时回调。arg0是元素的索引;arg1是替换前的元素;arg2是替换后的元素
/// </summary>
void AddListener_OnReplaced<V>(int observerId, Action<int, V, V> onReplaced);
void ClearObservers();
void RemoveListeners(int observerId);
/// <summary>
/// 当集合内容发生改变时调用(包括元素的增加、删除、替换、顺序改变)
/// </summary>
void RemoveListener_OnChanged(int observerId);
/// <summary>
/// 当有元素被移除时回调(arg0: 被移除元素的索引; arg1: 被移除的元素)
/// </summary>
void RemoveListener_OnRemoved(int observerId);
/// <summary>
/// 当有新数据添加时回调
/// </summary>
void RemoveListener_OnAdded(int observerId);
/// <summary>
/// 当元素被替换时回调。arg0是元素的索引;arg1是替换前的元素;arg2是替换后的元素
/// </summary>
void RemoveListener_OnReplaced(int observerId);
}
关于这种观察者模式、装饰器模式等等在UniVue中使用得非常多。以及为了防止各种值类型装箱操作内部有时候会使用很深的装饰器模式以及多态的使用。
下期功能更新预告
使用SuperGrid组件实现背包物品的拖拽以及将一个背包的物品拖拽到另外一个背包中。