当某些结果需要像文件图标显示一样的时候, 结果超多上万条甚至更多的时候,虚拟的容器非常重要!
一、虚拟 TileLayout 思路
1, 数据是动态加载的,需要一个提供数据的虚基类 IVirtualDataProvider, 用于用户自定义数据接口类型添加到容器中;
2,滚动条是由容器中的 Item 数量决定的 Range , 因此需要重写 ProcessScrollBar 函数 和 SetPos ;
3,数据动态获取是根据滚动条的改变而动态获取的,因此需要重写SetScrollPos 函数, 在这个函数中去从IVirtualDataProvider * 加载数据到容器中;
4,SetPos 中需要重写 设置Item 位置的部分代码;
5,通过滚动条的位置去确定当前需要显示的数据在哪一行,应该取IVirtualDataProvider * 中的哪一部分数据。int GetTopElementIndex(int &bottom); 接口用来确定当前显示区域第一行数据的索引;
6,增加 Item 类, 用于把它做成 List 一样,CVirTileItemUI 类 相当于 CListItemUI,增加SetIndex, GetIndex接口, 用来记录显示的数据索引;
7,需要一个缓存TileLayout 每一行控件最大高度的 map ,用来计算 Range ,需要一个 默认的子项高度m_nOwnerElementHeight, 如果没有缓存的行, 计算滚动条位置所在的行时使用该子项高度m_nOwnerElementHeight计算;
二、代码
/**
* @brief 虚拟列表接口类
* 提供开发者对子项数据管理个性化定制
*/
class IVirtualDataProvider
{
public:
/**
* @brief 创建一个子项
* @return 返回创建后的子项指针
*/
virtual CControlUI* CreateElement() abstract;
/**
* @brief 填充指定子项
* @param[in] control 子项控件指针
* @param[in] index 索引
* @return 返回创建后的子项指针
*/
virtual void FillElement(CControlUI *pControl, int index) abstract;
/**
* @brief 获取子项总数
* @return 返回子项总数
*/
virtual int GetElementtCount() abstract;
// /**
// * @brief 删除指定项
// * @param[in] index 子项索引
// * @return 无
// */
// virtual void RemoveAt(int index) abstract;
};
class CVirTileItemUI : public CContainerUI
{
public:
CVirTileItemUI();
~CVirTileItemUI();
void SetIndex(const int & iIndex);
int GetIndex();
private:
int m_iIndex;
};
class CVirTileLayoutUI : public CTileLayoutUI
{
public:
CVirTileLayoutUI();
~CVirTileLayoutUI();
/**
* @brief 设置数据代理对象
* @param[in] pProvider 开发者需要重写 IVirtualDataProvider 的接口来作为数据代理对象
* @return 无
*/
virtual void SetDataProvider(IVirtualDataProvider * pDataProvider);
virtual IVirtualDataProvider * GetDataProvider();
/**
* @brief 设置子项高度
* @param[in] nHeight 高度值
* @return 无
*/
virtual void SetElementHeight(int nHeight);
/**
* @brief 初始化子项
* @param[in] nMaxItemCount 初始化数量,默认 50
* @return 无
*/
virtual void InitElement(int nMaxItemCount = 50);
/**
* @brief 刷新列表
* @return 无
*/
virtual void Refresh();
/**
* @brief 删除所有子项
* @return 无
*/
virtual void RemoveAll() override;
/**
* @brief 设置是否强制重新布局
* @param[in] bForce 设置为 true 为强制,否则为不强制
* @return 无
*/
void SetForceArrange(bool bForce);
/**
* @brief 获取当前所有可见控件的索引
* @param[out] collection 索引列表
* @return 无
*/
void GetDisplayCollection(std::vector<int>& collection);
//重写 SetPos 旨在重写 ProcessScrollBar 重设滚动条 Range
virtual void SetPos(RECT rc, bool bNeedInvalidate = true);
/// 重写父类接口,提供个性化功能
virtual void SetScrollPos(SIZE szPos, bool bMsg = true);
/**
* @brief 重新布局子项
* @param[in] bForce 是否强制重新布局
* @return 无
*/
void ReArrangeChild(bool bForce);
void SetColumns(int nCols);
// /************************************************************************
// *函数名称:ReSizeColumn
// *参 数:RECT rcSize
// *返 回 值:
// *函数功能:当容器大小改变的时候 重新计算列的大小
// ************************************************************************/
// void ReSizeColumn(RECT rcSize);
protected:
enum ScrollDirection
{
kScrollUp = -1,
kScrollDown = 1
};
//range 会跟随大小改变而改变 //默认按照最大的列表高度设置 range
virtual void ProcessScrollBar(RECT rc, int cxRequired, int cyRequired);
//设置实际需要的滚动条位置
void SetPosInternally(RECT rc);
/**
* @brief 创建一个子项
* @return 返回创建后的子项指针
*/
CControlUI* CreateElement();
/**
* @brief 填充指定子项
* @param[in] control 子项控件指针
* @param[in] index 索引
* @return 返回创建后的子项指针
*/
void FillElement(CControlUI *pControl, int iIndex);
/**
* @brief 获取元素总数
* @return 返回元素总指数
*/
int GetElementCount();
/**
* @brief 使用默认布局
* @return 成功返回 true,否则返回 false
*/
bool UseDefaultLayout();
/**
* @brief 得到n个元素对应的高度和,
* @param[in] nCount 要得到多少元素的高度,-1表示全部元素
* @return 返回指定数量元素的高度和
*/
int CalcElementsHeight(int nCount);
/**
* @brief 得到可见范围内第一个元素的前一个元素索引
* @param[out] bottom 返回上一个元素的 bottom 值
* @return 返回上一个元素的索引
*/
int GetTopElementIndex(int &bottom);
/**
* @brief 判断某个元素是否在可见范围内
* @param[in] iIndex 元素索引
* @return 返回 true 表示可见,否则为不可见
*/
bool IsElementDisplay(int iIndex);
/**
* @brief 判断是否要重新布局
* @param[out] direction 向上滚动还是向下滚动
* @return true 为需要重新布局,否则为 false
*/
bool NeedReArrange(ScrollDirection &direction);
/**
* @brief 设置虚拟 Item 位置
* @param[In] CVirTileItemUI* pItem 需要设置位置的Item
* @void
*/
void SetVirItemPos(CVirTileItemUI* pItem);
/**
* @brief 计算滚动条需要的 范围
* @int
*/
int CalScrollNeedRange();
/**
* @brief 设置虚拟 Item 位置
* @param[Out] int& nItemWidth Item宽度
* @POINT 返回起始位置点
*/
POINT GetItemStartPos(int& nItemWidth);
protected:
std::map<int, int > m_mapRowHeight;//保存高度 m_nColumns 改变需要清空
int m_iTopIndex = 0;
IVirtualDataProvider* m_pDataProvider;
int m_nOwnerElementHeight; // 每个项的高度
int m_nOwnerItemCount; // 列表真实控件数量上限
int m_nMaxRow;
int m_nOldYScrollPos;
bool m_bArrangedOnce;
bool m_bForceArrange; // 强制布局标记
bool m_bScrollProcess = false;//防止SetPos 重复调用, 导致死循环
int m_iSelIndex = -1;
std::vector<int> m_vSelIndex;//选中索引
};
三、效果图如下:
四、Demo
由于一个人改写, 只花了大概一天多一点的时间, 可能存在不少BUG , Demo 下面下载链接:
https://download.youkuaiyun.com/download/qwerdf10010/11178760