ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • TableViewNode
    카테고리 없음 2017. 12. 18. 18:58
    728x90

     

    #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

    댓글

Designed by Tistory.