11.【cocos2d-x 源碼分析】:UI系統的詳細分析(中)

對應源碼位置:cocos2d-x-3.3\cocos\ui\UI*

Layout的原理

這裏選取少量的重點部分。

//找當前節點的下一個可以聚焦的節點
//主要看思想  已經刪了很多了
Widget* Layout::getNextFocusedWidget(FocusDirection direction, Widget *current)
{
    Widget *nextWidget = nullptr;
    ssize_t previousWidgetPos = _children.getIndex(current);
    //選取下一個 孩子節點
    previousWidgetPos = previousWidgetPos + 1;  
    if (previousWidgetPos < _children.size())
    {
    	//找下一個Widget
        nextWidget = this->getChildWidgetByIndex(previousWidgetPos);
        //handle widget
        if (nextWidget)
        {
        	//可以聚焦再看
            if (nextWidget->isFocusEnabled())
            {
                //如果是個 Layout 就在裏面找 直到找到一個控件
                Layout* layout = dynamic_cast<Layout*>(nextWidget);
                if (layout)
                {
                    layout->_isFocusPassing = true;
                    return layout->findNextFocusedWidget(direction, layout);
                }
                else
                {
                	//否則 就是你了 回調一下
                    this->dispatchFocusEvent(current, nextWidget);
                    return nextWidget;
                }
            }
            else
            {
                return this->getNextFocusedWidget(direction, nextWidget);
            }
        }
        else
        {
            return current;
        }
    }
}
//看看 Layout具體怎麼繪製
//這就是Node 節點都有的 visit
void Layout::visit(Renderer *renderer, const Mat4 &parentTransform, uint32_t parentFlags)
{
    if (!_visible)
    {
        return;
    }
    //空函數
    adaptRenderers();
    //核心部分  處理佈局 後面詳細
    doLayout();
    //先假裝  不滿足 直走else
    if (_clippingEnabled)
    {
        switch (_clippingType)
        {
            case ClippingType::STENCIL:
                stencilClippingVisit(renderer, parentTransform, parentFlags);
                break;
            case ClippingType::SCISSOR:
                scissorClippingVisit(renderer, parentTransform, parentFlags);
                break;
            default:
                break;
        }
    }
    //就是以前普通的visit
    else
    {
        Widget::visit(renderer, parentTransform, parentFlags);
    }
}
//處理佈局的核心
void Layout::doLayout()
{
    //如果佈局沒有變化
    if (!_doLayoutDirty)
    {
        return;
    }
    //排序
    sortAllChildren();
	//創建 Layoutmanager
    LayoutManager* executant = this->createLayoutManager();
    
    if (executant)
    {
    	//核心部分
        executant->doLayout(this);
    }
    
    _doLayoutDirty = false;
}
//就是根據佈局的類型生成對應的manager
LayoutManager* Layout::createLayoutManager()
{
    LayoutManager* exe = nullptr;
    switch (_layoutType)
    {
        case Type::VERTICAL:
            exe = LinearVerticalLayoutManager::create();
            break;
        case Type::HORIZONTAL:
            exe = LinearHorizontalLayoutManager::create();
            break;
        case Type::RELATIVE:
            exe = RelativeLayoutManager::create();
            break;
        default:
            break;
    }
    return exe;

}

LayoutManager 的分析


class CC_GUI_DLL LayoutManager : public Ref
{
public:
    virtual ~LayoutManager(){};
    LayoutManager(){};
    //虛函數
    virtual void doLayout(LayoutProtocol *layout) = 0;
    
    friend class Layout;
};
//垂直的線性佈局
class CC_GUI_DLL LinearVerticalLayoutManager : public LayoutManager
{
private:
    LinearVerticalLayoutManager(){};
    virtual ~LinearVerticalLayoutManager(){};
    static LinearVerticalLayoutManager* create();
    virtual void doLayout(LayoutProtocol *layout) override;
    
    friend class Layout;
};
//水平的線性佈局
class CC_GUI_DLL LinearHorizontalLayoutManager : public LayoutManager
{
private:
    LinearHorizontalLayoutManager(){};
    virtual ~LinearHorizontalLayoutManager(){};
    static LinearHorizontalLayoutManager* create();
    virtual void doLayout(LayoutProtocol *layout) override;
    
    friend class Layout;
};
//具體實現 選一個  其思想就是 計算出最終的位置  相對佈局更加複雜
void LinearHorizontalLayoutManager::doLayout(LayoutProtocol* layout)
{
	//獲取佈局本身的大小
    Size layoutSize = layout->getLayoutContentSize();
    //獲取佈局的孩子節點
    Vector<Node*> container = layout->getLayoutElements();
    float leftBoundary = 0.0f;
    for (auto& subWidget : container)
    {
    	//獲取這個控件
        Widget* child = dynamic_cast<Widget*>(subWidget);
        if (child)
        {
            LinearLayoutParameter* layoutParameter = dynamic_cast<LinearLayoutParameter*>(child->getLayoutParameter());
            if (layoutParameter)
            {
                LinearLayoutParameter::LinearGravity childGravity = layoutParameter->getGravity();
                Vec2 ap = child->getAnchorPoint();
                Size cs = child->getContentSize();
                //計算最終位置
                //x的位置 找到錨點位置 確定錨點位置的座標
                float finalPosX = leftBoundary + (ap.x * cs.width);
                float finalPosY = layoutSize.height - (1.0f - ap.y) * cs.height;
                //此時爲 top的方式
                switch (childGravity)
                {
                    case LinearLayoutParameter::LinearGravity::NONE:
                    case LinearLayoutParameter::LinearGravity::TOP:
                        break;
                    case LinearLayoutParameter::LinearGravity::BOTTOM:
                    	//放在底部
                        finalPosY = ap.y * cs.height;
                        break;
                     //垂直 居中
                    case LinearLayoutParameter::LinearGravity::CENTER_VERTICAL:
                        finalPosY = layoutSize.height / 2.0f - cs.height * (0.5f - ap.y);
                        break;
                    default:
                        break;
                }
                //再加上外邊距
                Margin mg = layoutParameter->getMargin();
                finalPosX += mg.left;
                finalPosY -= mg.top;
                child->setPosition(Vec2(finalPosX, finalPosY));
                leftBoundary = child->getRightBoundary() + mg.right;
            }
        }
    }
}

重點分析RelativeLayoutManager

很長,但很有意思。

Vector<Widget*> RelativeLayoutManager::getAllWidgets(cocos2d::ui::LayoutProtocol *layout)
{
	//獲取所有孩子節點 
    Vector<Node*> container = layout->getLayoutElements();
    Vector<Widget*> widgetChildren;
    for (auto& subWidget : container)
    {
    	//看看  是不是 Widget 子類
        Widget* child = dynamic_cast<Widget*>(subWidget);
        if (child)
        {
        	//獲取 layoutParameter 
            RelativeLayoutParameter* layoutParameter = dynamic_cast<RelativeLayoutParameter*>(child->getLayoutParameter());
            layoutParameter->_put = false;
            //未處理佈局數據加1
            _unlayoutChildCount++;
            //加入到vector
            widgetChildren.pushBack(child);
        }
    }
    return widgetChildren;

}
//獲取相對的Widget   
Widget* RelativeLayoutManager::getRelativeWidget(Widget* widget)
{
    Widget* relativeWidget = nullptr;
    RelativeLayoutParameter* layoutParameter = dynamic_cast<RelativeLayoutParameter*>(widget->getLayoutParameter());
    //獲取其相對的控件的名字
    const std::string relativeName = layoutParameter->getRelativeToWidgetName();
    //看有沒有
    if (!relativeName.empty())
    {
        for (auto& sWidget : _widgetChildren)
        {
            if (sWidget)
            {
            	//遍歷子節點  看看有沒有他所說的那個名字的節點
                RelativeLayoutParameter* rlayoutParameter = dynamic_cast<RelativeLayoutParameter*>(sWidget->getLayoutParameter());
                if (rlayoutParameter &&  rlayoutParameter->getRelativeName() == relativeName)
                {
                    relativeWidget = sWidget;
                    _relativeWidgetLP = rlayoutParameter;
                    break;
                }
            }
        }
    }
    return relativeWidget;
}
//核心方法
 void RelativeLayoutManager::doLayout(LayoutProtocol *layout)
{
    //獲取所有的孩子控件
    _widgetChildren = this->getAllWidgets(layout);
    //如果未處理節點數目大於0
    while (_unlayoutChildCount > 0)
    {
        for (auto& subWidget : _widgetChildren)
        {
        	//當前要處理的 控件 _widget 爲內部變量
            _widget = static_cast<Widget*>(subWidget);
            //獲取參數
            RelativeLayoutParameter* layoutParameter = dynamic_cast<RelativeLayoutParameter*>(_widget->getLayoutParameter());
            
            if (layoutParameter)
            {
                if (layoutParameter->_put)
                {
                    continue;
                }
                
               //根據相對位置計算  返回值表示計算成功了沒 成功了就繼續
                bool ret = this->caculateFinalPositionWithRelativeWidget(layout);
                if (!ret) {
                    continue;
                }
                //計算 附加上邊距的最終值。
                this->caculateFinalPositionWithRelativeAlign();
           
            	//成功了
                _widget->setPosition(Vec2(_finalPositionX, _finalPositionY));
                //這個計算好了
                layoutParameter->_put = true;
            }
        }
        _unlayoutChildCount--;

    }
    _widgetChildren.clear();
}   
bool RelativeLayoutManager::caculateFinalPositionWithRelativeWidget(LayoutProtocol *layout)
{
	//獲取 錨點 和大小
    Vec2 ap = _widget->getAnchorPoint();
    Size cs = _widget->getContentSize();
    //最終位置
    _finalPositionX = 0.0f;
    _finalPositionY = 0.0f;
    //獲取相對節點
    Widget* relativeWidget = this->getRelativeWidget(_widget);
    //獲取參數
    RelativeLayoutParameter* layoutParameter = dynamic_cast<RelativeLayoutParameter*>(_widget->getLayoutParameter());
	//相對關係
    RelativeLayoutParameter::RelativeAlign align = layoutParameter->getAlign();
	//獲取佈局的大小
    Size layoutSize = layout->getLayoutContentSize();
    switch (align)
    {
        case RelativeLayoutParameter::RelativeAlign::NONE:
        //位於父級空間的 左上
        case RelativeLayoutParameter::RelativeAlign::PARENT_TOP_LEFT:
            _finalPositionX = ap.x * cs.width;
            _finalPositionY = layoutSize.height - ((1.0f - ap.y) * cs.height);
            break;
        case RelativeLayoutParameter::RelativeAlign::PARENT_TOP_CENTER_HORIZONTAL:
        //位於父級空間的 頂部的水平居中
            _finalPositionX = layoutSize.width * 0.5f - cs.width * (0.5f - ap.x);
            _finalPositionY = layoutSize.height - ((1.0f - ap.y) * cs.height);
            break;
        //位於父級空間的 頂部的右側
        case RelativeLayoutParameter::RelativeAlign::PARENT_TOP_RIGHT:
            _finalPositionX = layoutSize.width - ((1.0f - ap.x) * cs.width);
            _finalPositionY = layoutSize.height - ((1.0f - ap.y) * cs.height);
            break;
        //位於父級空間的 左邊 垂直居中
        case RelativeLayoutParameter::RelativeAlign::PARENT_LEFT_CENTER_VERTICAL:
            _finalPositionX = ap.x * cs.width;
            _finalPositionY = layoutSize.height * 0.5f - cs.height * (0.5f - ap.y);
            break;
        //位於父級空間的 正中間
        case RelativeLayoutParameter::RelativeAlign::CENTER_IN_PARENT:
            _finalPositionX = layoutSize.width * 0.5f - cs.width * (0.5f - ap.x);
            _finalPositionY = layoutSize.height * 0.5f - cs.height * (0.5f - ap.y);
            break;
        //位於父級空間的 右邊 垂直居中
        case RelativeLayoutParameter::RelativeAlign::PARENT_RIGHT_CENTER_VERTICAL:
            _finalPositionX = layoutSize.width - ((1.0f - ap.x) * cs.width);
            _finalPositionY = layoutSize.height * 0.5f - cs.height * (0.5f - ap.y);
            break;
        //位於父級空間的 左下角
        case RelativeLayoutParameter::RelativeAlign::PARENT_LEFT_BOTTOM:
            _finalPositionX = ap.x * cs.width;
            _finalPositionY = ap.y * cs.height;
            break;
         //位於父級空間的 底部的水平居中
        case RelativeLayoutParameter::RelativeAlign::PARENT_BOTTOM_CENTER_HORIZONTAL:
            _finalPositionX = layoutSize.width * 0.5f - cs.width * (0.5f - ap.x);
            _finalPositionY = ap.y * cs.height;
            break;
           //位於父級空間的 右下角
        case RelativeLayoutParameter::RelativeAlign::PARENT_RIGHT_BOTTOM:
            _finalPositionX = layoutSize.width - ((1.0f - ap.x) * cs.width);
            _finalPositionY = ap.y * cs.height;
            break;
         //位於其它控件的上面 左側對齊   
        case RelativeLayoutParameter::RelativeAlign::LOCATION_ABOVE_LEFTALIGN:
            if (relativeWidget)
            {	
            	//相對的那個控件 還沒有計算  不行
                if (_relativeWidgetLP && !_relativeWidgetLP->_put)
                {
                    return false;
                }
                //相對於的控件 已經計算過了
                //獲取相對者的 上邊界與左邊界
                float locationTop = relativeWidget->getTopBoundary();
                float locationLeft = relativeWidget->getLeftBoundary();
                //計算 自己的位置
                _finalPositionY = locationTop + ap.y * cs.height;
                _finalPositionX = locationLeft + ap.x * cs.width;
            }
            break;
        //位於其它控件的上面 居中 
        case RelativeLayoutParameter::RelativeAlign::LOCATION_ABOVE_CENTER:
            if (relativeWidget)
            {
                if (_relativeWidgetLP && !_relativeWidgetLP->_put)
                {
                    return false;
                }
                Size rbs = relativeWidget->getContentSize();
                float locationTop = relativeWidget->getTopBoundary();
                
                _finalPositionY = locationTop + ap.y * cs.height;
                _finalPositionX = relativeWidget->getLeftBoundary() + rbs.width * 0.5f + ap.x * cs.width - cs.width * 0.5f;
            }
            break;
            //上面右邊對齊
        case RelativeLayoutParameter::RelativeAlign::LOCATION_ABOVE_RIGHTALIGN:
            if (relativeWidget)
            {
                if (_relativeWidgetLP && !_relativeWidgetLP->_put)
                {
                    return false;
                }
                float locationTop = relativeWidget->getTopBoundary();
                float locationRight = relativeWidget->getRightBoundary();
                _finalPositionY = locationTop + ap.y * cs.height;
                _finalPositionX = locationRight - (1.0f - ap.x) * cs.width;
            }
            break;
            //左側 頂部對齊
        case RelativeLayoutParameter::RelativeAlign::LOCATION_LEFT_OF_TOPALIGN:
            if (relativeWidget)
            {
                if (_relativeWidgetLP && !_relativeWidgetLP->_put)
                {
                    return false;
                }
                float locationTop = relativeWidget->getTopBoundary();
                float locationLeft = relativeWidget->getLeftBoundary();
                _finalPositionY = locationTop - (1.0f - ap.y) * cs.height;
                _finalPositionX = locationLeft - (1.0f - ap.x) * cs.width;
            }
            break;
            //左側居中
        case RelativeLayoutParameter::RelativeAlign::LOCATION_LEFT_OF_CENTER:
            if (relativeWidget)
            {
                if (_relativeWidgetLP && !_relativeWidgetLP->_put)
                {
                    return false;
                }
                Size rbs = relativeWidget->getContentSize();
                float locationLeft = relativeWidget->getLeftBoundary();
                _finalPositionX = locationLeft - (1.0f - ap.x) * cs.width;
                
                _finalPositionY = relativeWidget->getBottomBoundary() + rbs.height * 0.5f + ap.y * cs.height - cs.height * 0.5f;
            }
            break;
            //左側底部對齊
        case RelativeLayoutParameter::RelativeAlign::LOCATION_LEFT_OF_BOTTOMALIGN:
            if (relativeWidget)
            {
                if (_relativeWidgetLP && !_relativeWidgetLP->_put)
                {
                    return false;
                }
                float locationBottom = relativeWidget->getBottomBoundary();
                float locationLeft = relativeWidget->getLeftBoundary();
                _finalPositionY = locationBottom + ap.y * cs.height;
                _finalPositionX = locationLeft - (1.0f - ap.x) * cs.width;
            }
            break;
            //右側 頂部對齊
        case RelativeLayoutParameter::RelativeAlign::LOCATION_RIGHT_OF_TOPALIGN:
            if (relativeWidget)
            {
                if (_relativeWidgetLP && !_relativeWidgetLP->_put)
                {
                    return false;
                }
                float locationTop = relativeWidget->getTopBoundary();
                float locationRight = relativeWidget->getRightBoundary();
                _finalPositionY = locationTop - (1.0f - ap.y) * cs.height;
                _finalPositionX = locationRight + ap.x * cs.width;
            }
            break;
            //右側 居中
        case RelativeLayoutParameter::RelativeAlign::LOCATION_RIGHT_OF_CENTER:
            if (relativeWidget)
            {
                if (_relativeWidgetLP && !_relativeWidgetLP->_put)
                {
                    return false;
                }
                Size rbs = relativeWidget->getContentSize();
                float locationRight = relativeWidget->getRightBoundary();
                _finalPositionX = locationRight + ap.x * cs.width;
                
                _finalPositionY = relativeWidget->getBottomBoundary() + rbs.height * 0.5f + ap.y * cs.height - cs.height * 0.5f;
            }
            break;
            //右側 底部對齊
        case RelativeLayoutParameter::RelativeAlign::LOCATION_RIGHT_OF_BOTTOMALIGN:
            if (relativeWidget)
            {
                if (_relativeWidgetLP && !_relativeWidgetLP->_put)
                {
                    return false;
                }
                float locationBottom = relativeWidget->getBottomBoundary();
                float locationRight = relativeWidget->getRightBoundary();
                _finalPositionY = locationBottom + ap.y * cs.height;
                _finalPositionX = locationRight + ap.x * cs.width;
            }
            break;
            //下方 左對齊
        case RelativeLayoutParameter::RelativeAlign::LOCATION_BELOW_LEFTALIGN:
            if (relativeWidget)
            {
                if (_relativeWidgetLP && !_relativeWidgetLP->_put)
                {
                    return false;
                }
                float locationBottom = relativeWidget->getBottomBoundary();
                float locationLeft = relativeWidget->getLeftBoundary();
                _finalPositionY = locationBottom - (1.0f - ap.y) * cs.height;
                _finalPositionX = locationLeft + ap.x * cs.width;
            }
            break;
            //下方居中
        case RelativeLayoutParameter::RelativeAlign::LOCATION_BELOW_CENTER:
            if (relativeWidget)
            {
                if (_relativeWidgetLP && !_relativeWidgetLP->_put)
                {
                    return false;
                }
                Size rbs = relativeWidget->getContentSize();
                float locationBottom = relativeWidget->getBottomBoundary();
                
                _finalPositionY = locationBottom - (1.0f - ap.y) * cs.height;
                _finalPositionX = relativeWidget->getLeftBoundary() + rbs.width * 0.5f + ap.x * cs.width - cs.width * 0.5f;
            }
            break;
            //下方 右側對齊
        case RelativeLayoutParameter::RelativeAlign::LOCATION_BELOW_RIGHTALIGN:
            if (relativeWidget)
            {
                if (_relativeWidgetLP && !_relativeWidgetLP->_put)
                {
                    return false;
                }
                float locationBottom = relativeWidget->getBottomBoundary();
                float locationRight = relativeWidget->getRightBoundary();
                _finalPositionY = locationBottom - (1.0f - ap.y) * cs.height;
                _finalPositionX = locationRight - (1.0f - ap.x) * cs.width;
            }
            break;
        default:
            break;
    }
    return true;
}
//看情況  處理邊距  
void RelativeLayoutManager::caculateFinalPositionWithRelativeAlign()
{
    RelativeLayoutParameter* layoutParameter = dynamic_cast<RelativeLayoutParameter*>(_widget->getLayoutParameter());
    
    Margin mg = layoutParameter->getMargin();
   
    RelativeLayoutParameter::RelativeAlign align = layoutParameter->getAlign();
    
    //handle margin
    switch (align)
    {
        case RelativeLayoutParameter::RelativeAlign::NONE:
        case RelativeLayoutParameter::RelativeAlign::PARENT_TOP_LEFT:
            _finalPositionX += mg.left;
            _finalPositionY -= mg.top;
            break;
        case RelativeLayoutParameter::RelativeAlign::PARENT_TOP_CENTER_HORIZONTAL:
            _finalPositionY -= mg.top;
            break;
        case RelativeLayoutParameter::RelativeAlign::PARENT_TOP_RIGHT:
            _finalPositionX -= mg.right;
            _finalPositionY -= mg.top;
            break;
        case RelativeLayoutParameter::RelativeAlign::PARENT_LEFT_CENTER_VERTICAL:
            _finalPositionX += mg.left;
            break;
        case RelativeLayoutParameter::RelativeAlign::CENTER_IN_PARENT:
            break;
        case RelativeLayoutParameter::RelativeAlign::PARENT_RIGHT_CENTER_VERTICAL:
            _finalPositionX -= mg.right;
            break;
        case RelativeLayoutParameter::RelativeAlign::PARENT_LEFT_BOTTOM:
            _finalPositionX += mg.left;
            _finalPositionY += mg.bottom;
            break;
        case RelativeLayoutParameter::RelativeAlign::PARENT_BOTTOM_CENTER_HORIZONTAL:
            _finalPositionY += mg.bottom;
            break;
        case RelativeLayoutParameter::RelativeAlign::PARENT_RIGHT_BOTTOM:
            _finalPositionX -= mg.right;
            _finalPositionY += mg.bottom;
            break;
            
        case RelativeLayoutParameter::RelativeAlign::LOCATION_ABOVE_LEFTALIGN:
            _finalPositionY += mg.bottom;
            _finalPositionX += mg.left;
            break;
        case RelativeLayoutParameter::RelativeAlign::LOCATION_ABOVE_RIGHTALIGN:
            _finalPositionY += mg.bottom;
            _finalPositionX -= mg.right;
            break;
        case RelativeLayoutParameter::RelativeAlign::LOCATION_ABOVE_CENTER:
            _finalPositionY += mg.bottom;
            break;
            
        case RelativeLayoutParameter::RelativeAlign::LOCATION_LEFT_OF_TOPALIGN:
            _finalPositionX -= mg.right;
            _finalPositionY -= mg.top;
            break;
        case RelativeLayoutParameter::RelativeAlign::LOCATION_LEFT_OF_BOTTOMALIGN:
            _finalPositionX -= mg.right;
            _finalPositionY += mg.bottom;
            break;
        case RelativeLayoutParameter::RelativeAlign::LOCATION_LEFT_OF_CENTER:
            _finalPositionX -= mg.right;
            break;
            
        case RelativeLayoutParameter::RelativeAlign::LOCATION_RIGHT_OF_TOPALIGN:
            _finalPositionX += mg.left;
            _finalPositionY -= mg.top;
            break;
        case RelativeLayoutParameter::RelativeAlign::LOCATION_RIGHT_OF_BOTTOMALIGN:
            _finalPositionX += mg.left;
            _finalPositionY += mg.bottom;
            break;
        case RelativeLayoutParameter::RelativeAlign::LOCATION_RIGHT_OF_CENTER:
            _finalPositionX += mg.left;
            break;
            
        case RelativeLayoutParameter::RelativeAlign::LOCATION_BELOW_LEFTALIGN:
            _finalPositionY -= mg.top;
            _finalPositionX += mg.left;
            break;
        case RelativeLayoutParameter::RelativeAlign::LOCATION_BELOW_RIGHTALIGN:
            _finalPositionY -= mg.top;
            _finalPositionX -= mg.right;
            break;
        case RelativeLayoutParameter::RelativeAlign::LOCATION_BELOW_CENTER:
            _finalPositionY -= mg.top;
            break;
        default:
            break;
    }
}

最後

下一篇分析 具體 UI控件 的實現。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章