11.【cocos2d-x 源码分析】:UI系统的详细分析(中)

本文深入剖析了cocos2d-x 3.3版本UI系统的核心原理,重点关注了UI组件的实现细节,为读者揭示了UI系统的工作机制。文章详述了部分关键源码,为后续分析具体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控件 的实现。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值