listview获取最接近的widget的bug和node节点的增加ValueMapIntKey的建议

1.ListView的bug
(1)原来的算法是有问题的distance取的是距离的绝对值,因此二分查找不适用这种场合,绝对值打破了有序的前提
(2)listview关注Y轴的widget因此只需要提供Y轴的就可以
static Widget* findClosestItem(const Vec2& targetPosition, const Vector<Widget*>& items, const Vec2& itemAnchorPoint, ssize_t firstIndex, float distanceFromFirst, ssize_t lastIndex, float distanceFromLast)
{
CCASSERT(firstIndex >= 0 && lastIndex < items.size() && firstIndex <= lastIndex, “”);
if (firstIndex == lastIndex)
{
return items.at(firstIndex);
}
if (lastIndex - firstIndex == 1)
{
if (fabs(distanceFromFirst) <= fabs(distanceFromLast))
{
return items.at(firstIndex);
}
else
{
return items.at(lastIndex);
}
}

// Binary search
ssize_t midIndex = (firstIndex + lastIndex) / 2;
Vec2 itemPosition = calculateItemPositionWithAnchor(items.at(midIndex), itemAnchorPoint);
float distanceFromMid = itemPosition.y - targetPosition.y;
if (distanceFromMid > 0)
{
    // Right half
    return findClosestItem(targetPosition, items, itemAnchorPoint, midIndex, distanceFromMid, lastIndex, distanceFromLast);
}
else
{
    // Left half
    return findClosestItem(targetPosition, items, itemAnchorPoint, firstIndex, distanceFromFirst, midIndex, distanceFromMid);
}

}

Widget* ListView::getClosestItemToPosition(const Vec2& targetPosition, const Vec2& itemAnchorPoint) const
{
if (_items.empty())
{
return nullptr;
}

// Find the closest item through binary search
ssize_t firstIndex = 0;
Vec2 firstPosition = calculateItemPositionWithAnchor(_items.at(firstIndex), itemAnchorPoint);
float distanceFromFirst = firstPosition.y - targetPosition.y;

ssize_t lastIndex = _items.size() - 1;
Vec2 lastPosition = calculateItemPositionWithAnchor(_items.at(lastIndex), itemAnchorPoint);
float distanceFromLast = lastPosition.y - targetPosition.y;

return findClosestItem(targetPosition, _items, itemAnchorPoint, firstIndex, distanceFromFirst, lastIndex, distanceFromLast);

}
2.建议在Node节点增加ValueMapIntKey m_mapRevertData;
这个可以实现自定义数据非常的实用
1.目前实现了一套动态ListView看不见的区域全部noinit或者pause&visible=false,可视区域才resume起来,可以利用上面的RevertData保存临时数据
2.实现聊天框:利用第一套的ListView,实现RichText(看成是Widget),性能非常高,测试在2000多条的情况下(接近20000个节点),能够保持60帧,大部分都是parse状态,只有可视区的在绘制
建议加入对控制有很大好处
3.另外cocosreader这个希望改掉component,不然性能上不去,只要是widget都会加入update队列scheduleUpdate,虽然都是空转都是节点多了非常影响性能(原来的聊天框是ListView加载csb实现,虽然都是最简单的文本和图片(还不是动画的),2000个节点的时候基本就只有20帧不到了,大部分消耗在update上面)

附上dylistview的实现,目前我是和cocos绑定,动态创建cocos文件,也支持动态绑定node节点,只要实现了接口

class CListViewDyLoadLayout;
class CDyListView_ItemInterface
{
public:
//用于listview动态初始化的函数
virtual void ListViewItemDyCreateByLayout(CListViewDyLoadLayout* pListView){}
//必须支持选中和非选中状态
virtual void ListViewItemSelectedByListView(cocos2d::Touch* pTouch){}
virtual void ListViewItemUnSelectedByListView(cocos2d::Touch* pTouch){}
};

//////////////////////////////////////////////////////////////////////////////////////////////
//动态加载listview使用layout
class CListViewDyLoadLayout : public cocos2d::ui::Widget
{
public:
CListViewDyLoadLayout();
virtual ~CListViewDyLoadLayout();

//初始化函数
void InitLayout(cocos2d::ui::ListView* pList);
//绑定子节点
void BindChild(cocos2d::Node* pChild);
void NoInView();
//选中
void SelectItem(cocos2d::ui::ListView* pList, cocos2d::Touch* pTouch);
//非选中
void UnSelectItem(cocos2d::ui::ListView* pList, cocos2d::Touch* pTouch);
//设置加载的节点内容
void SetLoadCSBFile(const char* pCSBFile){ m_strFileName = pCSBFile; }
//获取创建的节点
cocos2d::Node* GetCreateNode(){ return m_pCreateNode; }

protected:
cocos2d::Node* m_pCreateNode;
basiclib::CBasicString m_strFileName;
};

class CDyListViewInterface
{
public:
///////////////////////////////////////////////////////////////////////////////////////////////////
//listview加入到动态创建实现的函数
void AddListViewToDyCreateList(cocos2d::ui::ListView* pList);
///////////////////////////////////////////////////////////////////////////////////////////////////
//创建动态listview对象
void ReLoadListViewItems(cocos2d::ui::ListView* pList, const std::function<void(cocos2d::ui::ListView*)>& func);
//删除listview的某一个
void DeleteListViewRow(cocos2d::ui::ListView* pList, cocos2d::Node* pRow);
protected:
//提供函数删除listview的所有数据
void RemoveDyListViewItems(cocos2d::ui::ListView* pList);
//滚动计算list的初始化layout
void CalcDyListViewLayout(cocos2d::ui::ListView* pList);
//加入动态队列之后如果有选中的item实现这个函数
virtual void DyListViewSelectItemChange(cocos2d::ui::ListView* pList, ssize_t sIndex, cocos2d::Touch* pTouch){}
protected:
//加载完成下一帧显示的调用
virtual void OnNextFPSShowDyListView(float fDelay) = 0;
virtual void OnNextFPSClearItems(cocos2d::Vectorcocos2d::ui::Widget*& items) = 0;
protected:
//显示第一个画面
void ShowDyListViewByInit();
protected:
//需要加载plist
cocos2d::Vectorcocos2d::ui::ListView* m_vtListViewCalcListView;
};

#include “dylistviewinterface.h”
#include “cocosuiscene.h”

using namespace cocos2d;
using namespace cocos2d::ui;
using namespace cocos2d::experimental;

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
CListViewDyLoadLayout::CListViewDyLoadLayout()
{
m_pCreateNode = NULL;
}

CListViewDyLoadLayout::~CListViewDyLoadLayout()
{

}
void CListViewDyLoadLayout::InitLayout(cocos2d::ui::ListView* pList)
{
if (m_pCreateNode)
{
if (!m_pCreateNode->isVisible())
{
m_pCreateNode->resume();
m_pCreateNode->setVisible(true);
}
return;
}
setTouchEnabled(true);
m_pCreateNode = (LoadCSBFileByName(m_strFileName.c_str(), true));
CDyListView_ItemInterface* pCocosLayer = dynamic_cast<CDyListView_ItemInterface*>(m_pCreateNode);
if (pCocosLayer)
{
pCocosLayer->ListViewItemDyCreateByLayout(this);
}

setContentSize(m_pCreateNode->getContentSize());
m_pCreateNode->setCameraMask(getCameraMask());
addChild(m_pCreateNode);
//大小改变要求listview重新排列
pList->requestDoLayout();

}

//绑定子节点
void CListViewDyLoadLayout::BindChild(cocos2d::Node* pChild)
{
setTouchEnabled(true);
m_pCreateNode = pChild;
setContentSize(m_pCreateNode->getContentSize());
addChild(m_pCreateNode);
}

void CListViewDyLoadLayout::NoInView()
{
setTouchEnabled(false);
if (m_pCreateNode)
{
//如果已经创建,但是没在显示区
if (m_pCreateNode->isVisible())
{
m_pCreateNode->pause();
m_pCreateNode->setVisible(false);
}
}
}
//选中
void CListViewDyLoadLayout::SelectItem(cocos2d::ui::ListView* pList, cocos2d::Touch* pTouch)
{
InitLayout(pList);
CDyListView_ItemInterface* pCocosLayer = dynamic_cast<CDyListView_ItemInterface*>(m_pCreateNode);
if (pCocosLayer)
pCocosLayer->ListViewItemSelectedByListView(pTouch);
}
//非选中
void CListViewDyLoadLayout::UnSelectItem(cocos2d::ui::ListView* pList, cocos2d::Touch* pTouch)
{
InitLayout(pList);
CDyListView_ItemInterface* pCocosLayer = dynamic_cast<CDyListView_ItemInterface*>(m_pCreateNode);
if (pCocosLayer)
pCocosLayer->ListViewItemUnSelectedByListView(pTouch);
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

void CDyListViewInterface::AddListViewToDyCreateList(cocos2d::ui::ListView* pList)
{
//默认插入不改变位置
pList->SetFixInnerPosition(true);
cocos2d::ui::ListView::ccScrollViewCallback callbackScroll = (Ref* pObject, cocos2d::ui::ScrollView::EventType eventScroll) -> void
{
switch (eventScroll)
{
case cocos2d::ui::ScrollView::EventType::CONTAINER_MOVED:
{
CalcDyListViewLayout(pList);
}
break;
}
};
pList->addEventListener(callbackScroll);

cocos2d::ui::ListView::ccListViewCallback callbackListView = (Ref* pObject, cocos2d::ui::ListView::EventType eventListView, cocos2d::Touch* pTouch) -> void
{
    switch (eventListView)
    {
    case cocos2d::ui::ListView::EventType::ON_SELECTED_ITEM_END:
    {
        //获取当前的选择
        ssize_t sIndex = pList->getCurSelectedIndex();
        if (sIndex >= 0)
        {
            CListViewDyLoadLayout* pPropLayOut = dynamic_cast<CListViewDyLoadLayout*>(pList->getItem(sIndex));
            if (pPropLayOut)
            {
                pPropLayOut->UnSelectItem(pList, pTouch);
            }
        }

    }
    break;
    case cocos2d::ui::ListView::EventType::ON_SELECTED_ITEM_CHANGE:
    {
        //获取当前的选择
        ssize_t sIndex = pList->getCurSelectedIndex();
        if (sIndex >= 0)
        {
            CListViewDyLoadLayout* pPropLayOut = dynamic_cast<CListViewDyLoadLayout*>(pList->getItem(sIndex));
            if (pPropLayOut)
            {
                pPropLayOut->SelectItem(pList, pTouch);
                DyListViewSelectItemChange(pList, sIndex, pTouch);
            }
        }
    }
    break;
    }
};
pList->addEventListener(callbackListView);

}

#define LISTREVERTKEYINIT_BEGIN 5000000
#define LISTREVERTKEYINIT_END 5000001
//滚动计算list的初始化layout
void CDyListViewInterface::CalcDyListViewLayout(cocos2d::ui::ListView* pList)
{
Widget* pWidGetFar = pList->getBottommostItemInCurrentView();
Widget* pWidGetNear = pList->getTopmostItemInCurrentView();
if (NULL == pWidGetFar || NULL == pWidGetNear)
return;
int nFarIndex = pList->getIndex(pWidGetFar);
int nNearIndex = pList->getIndex(pWidGetNear);
//默认多处理两个
nFarIndex++;
nNearIndex–;
if (nNearIndex < 0)
nNearIndex = 0;

ValueMapIntKey& valueMapData = pList->GetUserRevertData();
Value& vNear = valueMapData;
Value& vFar = valueMapData;
if (vNear.isNull() || vFar.isNull())
{
    int nTotalSize = pList->getItems().size();
    for (int i = 0; i < nTotalSize; i++)
    {
        CListViewDyLoadLayout* pPropLayOut = static_cast<CListViewDyLoadLayout*>(pList->getItem(i));
        if (pPropLayOut)
        {
            if (i >= nNearIndex && i <= nFarIndex)
            {
                //可视区域
                pPropLayOut->InitLayout(pList);
            }
            else
            {
                pPropLayOut->NoInView();
            }
        }
    }
}
else
{
    int nLastNear = vNear.asInt();
    int nLastFar = vFar.asInt();
    for (int i = nLastNear; i < nLastFar; i++)
    {
        if (i < nNearIndex || i > nFarIndex)
        {
            CListViewDyLoadLayout* pPropLayOut = static_cast<CListViewDyLoadLayout*>(pList->getItem(i));
            if (pPropLayOut)
            {
                pPropLayOut->NoInView();
            }
        }
    }
    for (int j = nNearIndex; j <= nFarIndex; j++)
    {
        CListViewDyLoadLayout* pPropLayOut = static_cast<CListViewDyLoadLayout*>(pList->getItem(j));
        if (pPropLayOut)
        {
            //可视区域
            pPropLayOut->InitLayout(pList);
        }
    }
}
valueMapData = nNearIndex;
valueMapData = nFarIndex;

}

//创建动态listview对象
void CDyListViewInterface::ReLoadListViewItems(cocos2d::ui::ListView* pList, const std::function<void(cocos2d::ui::ListView*)>& func)
{
RemoveDyListViewItems(pList);

func(pList);

//加载当前的应该加载的
m_vtListViewCalcListView.pushBack(pList);
OnNextFPSShowDyListView(0.0f);
//scheduleOnce(CC_SCHEDULE_SELECTOR(CDyListViewInterface::OnTimerToShowDyListView), 0.0f);

}

//提供函数删除listview的所有数据
void CDyListViewInterface::RemoveDyListViewItems(cocos2d::ui::ListView* pList)
{
//先清空原来的数据
cocos2d::Vectorcocos2d::ui::Widget*& vtNewItems = pList->getItems();
OnNextFPSClearItems(vtNewItems);
//ClearItemList(vtNewItems);
pList->removeAllItems();
}
//显示第一个画面
void CDyListViewInterface::ShowDyListViewByInit()
{
for (auto& listview : m_vtListViewCalcListView)
{
CalcDyListViewLayout(listview);
}
}

//删除listview的某一个
void CDyListViewInterface::DeleteListViewRow(cocos2d::ui::ListView* pList, cocos2d::Node* pRow)
{
CListViewDyLoadLayout* pLayOut = dynamic_cast<CListViewDyLoadLayout*>(pRow->getParent());
if (NULL == pLayOut)
{
pLayOut = dynamic_cast<CListViewDyLoadLayout*>(pRow);
}
if (NULL == pLayOut)
{
ASSERT(0);
return;
}
cocos2d::Vectorcocos2d::ui::Widget* clearData;
clearData.pushBack(pLayOut);
OnNextFPSClearItems(clearData);
pList->removeItem(pList->getIndex(pLayOut));
}

高手在民间呀