-
TableViewNode카테고리 없음 2017. 12. 18. 18:58728x90
#include "stod/precompiled.h" #include "stod/TableViewNode.h" #include "stod/Property.h" #include "stod/Kernel.h" #include "stod/TimeMgr.h" namespace stod { //---------------------------------------------------------------------------- const char* TableViewNode::EVENT_LOAD_PREV_PAGE = "EVENT_LOAD_PREV_PAGE"; const char* TableViewNode::EVENT_LOAD_NEXT_PAGE = "EVENT_LOAD_NEXT_PAGE"; //---------------------------------------------------------------------------- TableViewNode::TableViewNode(): type(TYPE_VERTICAL), cellCount(0), scrollPos(0), lastAcc(0), deaccelFactor(1000), gap(0), snap(false), alwaysUpdateCells(false), prototypeCellTag(-1), prototypeCellNode(0), panel(0), touchLayer(0), loadCellDelegate(nullptr) { } //---------------------------------------------------------------------------- void TableViewNode::onCreate() { ScrollLayer* tl = ScrollLayer::create(); tl->target = this; this->setNode(tl); } //---------------------------------------------------------------------------- void TableViewNode::onSetNode(cocos2d::Node* node) { BaseType::onSetNode(node); this->scrollLayer = dynamic_cast<ScrollLayer*>(node); this->ignoreAnchorPointForPosition(false); this->node->setAnchorPoint(cocos2d::Point(0, 1)); this->panel = static_cast<stod::Node*>(stod::Kernel::instance()->create("Node")); this->panel->setSerializable(false); this->panel->setHidden(true); this->addChild(this->panel); //터치 이벤트 등록 this->touchLayer = static_cast<stod::Layer*>(stod::Kernel::instance()->create("Layer")); this->touchLayer->setSerializable(false); this->touchLayer->setHidden(true); this->touchLayer->addEventHandler(stod::EVENT_TOUCH_BEGIN, this, [this](void* arg) { this->scrollLayer->unschedule(schedule_selector(TableViewNode::ScrollLayer::deaccel)); this->lastAcc = 0; }); this->touchLayer->addEventHandler(stod::EVENT_TOUCH_MOVE, this, [this](void* arg) { cocos2d::Touch* touch = static_cast<cocos2d::Touch*>(arg); float cur_scroll = this->getScrollPos(); float delta = 0; if(this->type == TYPE_VERTICAL) delta = touch->getDelta().y; else delta = -touch->getDelta().x; if(((cur_scroll + delta) < 0) || ((cur_scroll + delta) >= this->getLimitPos())) delta = delta / 10; this->setScrollPos(cur_scroll + delta, false); this->lastAcc = (delta + this->lastAcc) / 2; }); this->touchLayer->addEventHandler(stod::EVENT_TOUCH_END, this, [this](void*) { this->correctScrollPos(); }); this->touchLayer->setTouchEnabled(true); this->touchLayer->setSwallowTouches(false); this->addChild(this->touchLayer); } //---------------------------------------------------------------------------- void TableViewNode::onAddChild(Node* child, int zorder) { if(child->getTag() == this->prototypeCellTag) { child->setVisible(false); this->setPrototypeCellTag(this->prototypeCellTag); } if(this->touchLayer) this->touchLayer->setVisible(true); if(this->panel) this->panel->setVisible(true); } //---------------------------------------------------------------------------- void TableViewNode::onRemoveChild(Node* child) { if(this->prototypeCellNode == child) { this->prototypeCellNode->setVisible(true); this->prototypeCellNode = 0; } } //---------------------------------------------------------------------------- void TableViewNode::setCellCount(int count) { this->cellCount = count; this->setScrollPos(this->scrollPos); } //---------------------------------------------------------------------------- void TableViewNode::setScrollPos(float v) { this->scrollPos = v; if(nullptr == this->prototypeCellNode) return; float cell_pitch = this->getCellPitch(); int end_index = static_cast<int>(MIN(this->cellCount, ceil((v + this->getScrollSize()) / cell_pitch))); if (end_index <= 0) { this->scrollPos = 0; end_index = static_cast<int>(MIN(this->cellCount, ceil(( + this->getScrollSize()) / cell_pitch))); } int begin_index = static_cast<int>(MIN(end_index, MAX(0, this->scrollPos / cell_pitch))); //1. 이전과 현재 <보여지는 cell 리스트>를 비교해서 보이지 않게된 cell 을 dealloc 해서 pool로 되돌린다. for(auto i : this->prevRelevantVisibleCells) { //여전히 보여진다면 continue if(begin_index <= i.first && i.first < end_index) continue; i.second->removeFromParent(); this->cellPool.dealloc(i.second); } //2. <보여지는 cell 리스트>에 새롭게 보여지는 Cell 들을 추가해준다. for(int i=begin_index;i<end_index;++i) { //보여지는 리스트에 이미 존재한다면 continue if(this->prevRelevantVisibleCells.end() != this->prevRelevantVisibleCells.find(i)) continue; Node* cell = this->cellPool.cloneAlloc(); cell->setTag(-1); cell->setHidden(true); cell->setSerializable(false); cell->setVisible(true); this->panel->addChild(cell); this->prevRelevantVisibleCells.insert(std::make_pair(i, cell)); if (this->loadCellDelegate && !this->alwaysUpdateCells) this->loadCellDelegate(i, cell); } //3. prevRelevantVisibleCells 을 업데이트 한다 for(auto i=this->prevRelevantVisibleCells.begin();i!=this->prevRelevantVisibleCells.end();) { if(begin_index > i->first || i->first >= end_index) { STL_LIST_ERASE(this->prevRelevantVisibleCells, i); } else ++i; } //4. cell 위치 설정 float sh = this->scrollSize.height; float scroll_between = v; if(v > 0) scroll_between = static_cast<float>(((int)v % (int)this->getCellPitch())); float pitch = 0; for(auto i : this->prevRelevantVisibleCells) { if(this->type == TYPE_VERTICAL) { i.second->node->setPosition(0, scroll_between + sh + pitch); pitch -= cell_pitch; } else { i.second->node->setPosition(-scroll_between + pitch, sh); pitch += cell_pitch; } } //5. 항상 모든 cell 들을 update 한다면 if (this->alwaysUpdateCells) this->updateCells(); } //---------------------------------------------------------------------------- void TableViewNode::setScrollPos(float v, bool check_correct) { float pos = v; if (check_correct) { float limit = this->getLimitPos(); if(pos < 0) pos = 0; if(pos >= limit) pos = limit; } this->setScrollPos(pos); if (check_correct) this->correctScrollPos(); } //---------------------------------------------------------------------------- float TableViewNode::getLimitPos() { if(this->prototypeCellNode == 0) return 0; return (this->getCellPitch() * this->cellCount) - this->getScrollSize(); } //---------------------------------------------------------------------------- void TableViewNode::resize_cell_pool() { for(auto i : this->prevRelevantVisibleCells) i.second->removeFromParent(); this->prevRelevantVisibleCells.clear(); if(this->prototypeCellNode == 0) return; int cell_pool_size = static_cast<int>(this->getContentHeight() / this->prototypeCellNode->getContentHeight() + 1); this->cellPool.clear(); this->cellPool.setCloneSrc(this->prototypeCellNode); this->cellPool.cloneResize(cell_pool_size); } //---------------------------------------------------------------------------- void TableViewNode::resetScroll() { this->setScrollPos(0); } //---------------------------------------------------------------------------- void TableViewNode::setScrollPosByIndex(int index) { this->setScrollPos(index * this->getCellPitch(), true); } //---------------------------------------------------------------------------- int TableViewNode::getScrollIndex() { return static_cast<int> (std::ceil(this->getScrollPos()) / (this->getCellPitch())); } //---------------------------------------------------------------------------- void TableViewNode::setScrollType(int i) { if (i < 0 || i > TableViewNode::TYPE_HORIZONTAL) i = TableViewNode::TYPE_HORIZONTAL; else this->type = i; this->updateScrollRect(); } //---------------------------------------------------------------------------- void TableViewNode::setPrototypeCellTag(int tag) { if(tag == -1) return; this->prototypeCellTag = tag; for(auto i : this->children) { if(i->getTag() != tag) continue; this->prototypeCellNode = i; this->resize_cell_pool(); this->resetScroll(); break; } } //---------------------------------------------------------------------------- void TableViewNode::updateScrollRect() { this->touchLayer->node->setContentSize( cocos2d::Size(this->getContentWidth(), this->getContentHeight())); this->setScrollSize(cocos2d::Size( this->getContentWidth(), this->getContentHeight())); this->resetScroll(); } //---------------------------------------------------------------------------- void TableViewNode::correctScrollPos() { float tgt = 0, cur = 0, container_size = 0, scroll_size = 0; cur = this->scrollPos; container_size = this->getContainerSize(); scroll_size = this->getScrollSize(); float limit = this->getLimitPos(); this->scrollLayer->unschedule(schedule_selector(TableViewNode::ScrollLayer::deaccel)); if(cur < 0 || cur >= limit || container_size < scroll_size) { if(cur < 0 || container_size < scroll_size) tgt = 0; else if(cur >= limit) tgt = limit; this->scrollLayer->stopActionByTag(999); this->scrollLayer->runAction(cocos2d::EaseExponentialOut::create( cocos2d::ActionFloat::create(0.2f, cur, tgt, [this](float value) { this->setScrollPos(value); })))->setTag(999); if(cur < 0) { this->dispatchEvent(EVENT_LOAD_PREV_PAGE, this); } else if(cur >= limit || container_size < scroll_size) { this->dispatchEvent(EVENT_LOAD_NEXT_PAGE, this); } } else { if(std::abs(this->lastAcc) > 3) { this->scrollLayer->schedule(schedule_selector(TableViewNode::ScrollLayer::deaccel)); } else { if (this->snap) this->snapScrollPos(this->lastAcc<0); } } } //---------------------------------------------------------------------------- void TableViewNode::deaccel(float dt) { float deaccel_factor = this->deaccelFactor; float l = this->lastAcc, g = 0; if(this->lastAcc > 0) g = -1 * deaccel_factor; else g = deaccel_factor; this->lastAcc += g * dt * dt; if((l >= 0 && this->lastAcc <= 0) || (l <= 0 && this->lastAcc >= 0)) { this->scrollLayer->unschedule(schedule_selector(TableViewNode::ScrollLayer::deaccel)); if (this->snap) this->snapScrollPos(l<0); } float limit = this->getLimitPos(); float scroll_pos = this->getScrollPos(); this->setScrollPos(scroll_pos + this->lastAcc); float cur = this->getScrollPos(); if(cur < 0 || cur >= limit) { if(cur < 0) this->setScrollPos(0, false); else if(cur > limit) this->setScrollPos(limit, false); this->scrollLayer->unschedule(schedule_selector(TableViewNode::ScrollLayer::deaccel)); } } //---------------------------------------------------------------------------- void TableViewNode::snapScrollPos(bool dir) { int index = static_cast<int>( (this->getScrollPos() / this->getCellPitch() + 0.5f)); //if (dir) ++index; int tgt = index * this->getCellPitch(); this->scrollLayer->stopActionByTag(999); this->scrollLayer->runAction(cocos2d::EaseExponentialInOut::create( cocos2d::ActionFloat::create(0.3f, this->getScrollPos(), tgt, [this](float value) { this->setScrollPos(value); })))->setTag(999);; } //---------------------------------------------------------------------------- void TableViewNode::setScrollSize(const cocos2d::Size& size) { this->scrollSize = size; } //---------------------------------------------------------------------------- void TableViewNode::setSnap(bool value) { this->snap = value; } //---------------------------------------------------------------------------- void TableViewNode::updateCells() { if(nullptr == this->loadCellDelegate) return; for(auto iter : this->prevRelevantVisibleCells) this->loadCellDelegate(iter.first, iter.second); } //---------------------------------------------------------------------------- void TableViewNode::setContentWidth(float w) { BaseType::setContentWidth(w); this->updateScrollRect(); } //---------------------------------------------------------------------------- void TableViewNode::setContentHeight(float h) { BaseType::setContentHeight(h); this->updateScrollRect(); } //---------------------------------------------------------------------------- void TableViewNode::bindProperty() { BIND_PROPERTY(int, scroll_type, setScrollType, getScrollType, { return value == TYPE_VERTICAL; }); BIND_PROPERTY(int, cell_count, setCellCount, getCellCount, { return value == 0; }); BIND_PROPERTY(int, prototype_cell_tag, setPrototypeCellTag, getPrototypeCellTag, { return value == -1; }); BIND_PROPERTY(float, scroll_pos, setScrollPos, getScrollPos, { return value == 0; }); BIND_PROPERTY(float, gap, setGap, getGap, { return value == 0; }); BIND_PROPERTY(bool, snap, setSnap, isSnap, { return value == false; }); BIND_PROPERTY(bool, always_update_cells, setAlwaysUpdateCells, isAlwaysUpdateCells, { return value == false; }); BIND_PROPERTY(float, scroll_deaccel_factor, setScrollDeaccelFactor, getScrollDeaccelFactor, { return value == 8000.0f; }); } //---------------------------------------------------------------------------- TableViewNode::ScrollLayer::~ScrollLayer() { } //---------------------------------------------------------------------------- void TableViewNode::ScrollLayer::beforeDraw() { _beforeDrawCommand.init(_globalZOrder); _beforeDrawCommand.func = std::bind(&TableViewNode::ScrollLayer::onBeforeDraw, this); cocos2d::Director::getInstance()->getRenderer()->addCommand(&_beforeDrawCommand); } //---------------------------------------------------------------------------- void TableViewNode::ScrollLayer::onBeforeDraw() { glEnable(GL_SCISSOR_TEST); cocos2d::Point screenPos = this->convertToWorldSpaceAR(cocos2d::Point::ZERO); float s = this->getScale(); cocos2d::Director::getInstance()->getOpenGLView()->setScissorInPoints( screenPos.x, (screenPos.y - this->target->scrollSize.height), this->target->scrollSize.width * s, this->target->scrollSize.height * s); } //---------------------------------------------------------------------------- void TableViewNode::ScrollLayer::afterDraw() { _afterDrawCommand.init(_globalZOrder); _afterDrawCommand.func = std::bind(&TableViewNode::ScrollLayer::onAfterDraw, this); cocos2d::Director::getInstance()->getRenderer()->addCommand(&_afterDrawCommand); } //---------------------------------------------------------------------------- void TableViewNode::ScrollLayer::onAfterDraw() { glDisable(GL_SCISSOR_TEST); } //---------------------------------------------------------------------------- void TableViewNode::ScrollLayer::visit (cocos2d::Renderer *renderer, const cocos2d::Mat4& parentTransform, uint32_t parentFlags) { // quick return if not visible if (!isVisible()) { return; } uint32_t flags = processParentFlags(parentTransform, parentFlags); // IMPORTANT: // To ease the migration to v3.0, we still support the kmGL stack, // but it is deprecated and your code should not rely on it cocos2d::Director::getInstance()->pushMatrix(cocos2d::MATRIX_STACK_TYPE::MATRIX_STACK_MODELVIEW); cocos2d::Director::getInstance()->loadMatrix(cocos2d::MATRIX_STACK_TYPE::MATRIX_STACK_MODELVIEW, _modelViewTransform); this->beforeDraw(); if (!_children.empty()) { int i=0; // draw children zOrder < 0 for( ; i < _children.size(); i++ ) { Node *child = _children.at(i); if ( child->getLocalZOrder() < 0 ) { child->visit(renderer, _modelViewTransform, flags); } else { break; } } // this draw this->draw(renderer, _modelViewTransform, flags); // draw children zOrder >= 0 for( ; i < _children.size(); i++ ) { Node *child = _children.at(i); child->visit(renderer, _modelViewTransform, flags); } } else { this->draw(renderer, _modelViewTransform, flags); } this->afterDraw(); cocos2d::Director::getInstance()->popMatrix(cocos2d::MATRIX_STACK_TYPE::MATRIX_STACK_MODELVIEW); } }
TableViewNode.cpp
#pragma once #include <functional> #include "stod/Layer.h" #include "stod/GridSizer3.h" #include "stod/NodePool.h" namespace stod { /** @brief cell 을 재활용해서 ScrollView 를 최적화 하는 노드 setLoadCellDelegate에 지정된 lambda 가 호출될때마다 사용측에서는 TableView 에 지정된 cell 에 데이터를 채워줘야 한다 Events - EVENT_LOAD_PREV_PAGE : 처음인데 유저가 스크롤를 더 땡겼을때 발생 - EVENT_LOAD_NEXT_PAGE : 맨 끝인데 유저가 스크롤를 더 밀었을때 발생 */ class TableViewNode : public stod::Derive<TableViewNode, stod::Layer> { public: static const char* EVENT_LOAD_PREV_PAGE; static const char* EVENT_LOAD_NEXT_PAGE; enum { TYPE_VERTICAL, TYPE_HORIZONTAL, }; typedef std::function<void(int index, Node* cell)> LoadCellDelegate; public: TableViewNode(); void setLoadCellDelegate(const LoadCellDelegate& delegate) { this->loadCellDelegate = delegate; } //전체 원소 갯수를 지정한다. (화면에 보이는거 말고, 전체 원소갯수) void setCellCount(int count); int getCellCount() { return this->cellCount; } void setScrollPos(float v); void setScrollPos(float v, bool check_correct); float getScrollPos() { return this->scrollPos; } void resetScroll(); void setScrollPosByIndex(int index); int getScrollIndex(); void setScrollSize(const cocos2d::Size& size); //화면을 갱신한다. void updateCells(); void setPrototypeCellTag(int tag); int getPrototypeCellTag() { return this->prototypeCellTag; } void setScrollType(int i); int getScrollType() { return this->type; } void setGap(float gap) { this->gap = gap; this->setScrollPos(this->scrollPos); } float getGap() { return this->gap; } void setScrollDeaccelFactor(float v) { this->deaccelFactor = v; } float getScrollDeaccelFactor() { return this->deaccelFactor; } void updateScrollRect(); float getLimitPos(); float getContainerSize() { return (this->getCellPitch() * this->cellCount); } float getScrollSize() { if(this->type == TYPE_VERTICAL) return this->scrollSize.height; else return this->scrollSize.width; } float getCellPitch() { if(this->prototypeCellNode == 0) return 0; if(this->type == TYPE_VERTICAL) return this->prototypeCellNode->getContentHeight() + this->gap; else return this->prototypeCellNode->getContentWidth() + this->gap; } void correctScrollPos(); void deaccel(float dt); void snapScrollPos(bool dir); void setSnap(bool value); bool isSnap() { return this->snap; } void setAlwaysUpdateCells(bool value) { this->alwaysUpdateCells = value; } bool isAlwaysUpdateCells() { return this->alwaysUpdateCells; } void setTouchEnabled(bool value) { this->touchLayer->setTouchEnabled(value); } bool isTouchEnabled() { return this->touchLayer->isTouchEnabled(); } OVERRIDE void setContentWidth(float w); OVERRIDE void setContentHeight(float h); OVERRIDE void onCreate(); OVERRIDE void onSetNode(cocos2d::Node* node); OVERRIDE void onAddChild(Node* child, int zorder); OVERRIDE void onRemoveChild(Node* child); OVERRIDE void setX(float x) { this->node->setPositionX(static_cast<float>(static_cast<int>(x))); } OVERRIDE void setY(float y) { this->node->setPositionY(static_cast<float>(static_cast<int>(y))); } static void bindProperty(); private: void resize_cell_pool(); private: class ScrollLayer : public cocos2d::Layer { public: virtual~ScrollLayer(); CREATE_FUNC(ScrollLayer); void deaccel(float dt) { this->target->deaccel(dt); } void updateTweenAction(float value, const std::string& key); void beforeDraw(); void afterDraw(); void onBeforeDraw(); void onAfterDraw(); OVERRIDE void visit(cocos2d::Renderer *renderer, const cocos2d::Mat4 &parentTransform, uint32_t parentFlags); TableViewNode* target; cocos2d::CustomCommand _beforeDrawCommand; cocos2d::CustomCommand _afterDrawCommand; }; cocos2d::Layer* scrollLayer; private: int type; int cellCount; float scrollPos; float lastAcc; float deaccelFactor; float gap; std::map<int, Node*> prevRelevantVisibleCells; cocos2d::Size scrollSize; bool snap; bool alwaysUpdateCells; int prototypeCellTag; Node* prototypeCellNode; NodePool<Node> cellPool; stod::Node* panel; stod::Layer* touchLayer; LoadCellDelegate loadCellDelegate; }; }
TableViewNode.h
728x90