cocosLua 之 Layout布局管理

版本: cocosV3.10


简介

Layout作为cocos2dx UI中的基础容器,支持裁切,支持布局。

针对于后者而言,ScrollView,ListView,TableView,PageView等这些容器的布局都通过Layout进行拓展支持的,简单了解下它们的继承结构。

ScrollView
Layout
TableView
ListView
PageView

Layout的C++代码继承结构:

Widget
LayoutParameterProtocol
ProtectedNode
Layout
LayoutProtocol
  • LayoutProtocol主要用于布局管理器LayoutManager创建相关,及对布局的操作doLayout相关

  • LayoutParameterProtocol主要用于获取子节点布局元素LayoutParameter相关,其代码接口:

class CC_GUI_DLL LayoutParameterProtocol
{
public:
    virtual ~LayoutParameterProtocol(){}
		// 获取布局元素相关
    virtual LayoutParameter* getLayoutParameter() const= 0;
};

cocos2dx的Layout实现布局管理,其原理先简单了解下,这样有助于后面内容的理解:

  • 通过setLayoutType设置布局类型,它支持绝对,水平,垂直,相对四种布局,默认为绝对布局类型。

  • 除了绝对布局类型外,其他的类型都会遍历获取Layout下的子节点,将节点添加到布局元素LayoutParameter

  • 布局元素LayoutParameter主要用于设置布局子节点的对齐方式和四周边距等。它主要有两种:

    • LinearLayoutParameter:线性布局元素,主要处理水平,垂直布局的元素节点
    • RelativeLayoutParameter:相对布局元素,主要处理相对布局的元素节点
  • 在Layout节点执行visit遍历的时候,会主动调用doLayout,它主要用于对Layout节点添加不同类型的布局管理器LayoutManager

  • 官方提供了三种布局管理器,他们均继承于LayoutManager

    • LinearVerticalLayoutManager: 垂直布局管理器
    • LinearHorizontalLayoutManager:水平布局管理器
    • RelativeLayoutManager:相对布局管理器
  • 布局管理器中都存在同一方法接口: doLayout, 它就是计算Layout子节点位置相关。即获取Layout下所有的子节点, 获取子节点所对应的Parammeter, 然后根据布局元素的对齐方式和边距计算出新的位置。

需要注意的是:除绝对布局外,所有子节点通过setPosition设定的位置相关将不在有效。必须通过布局元素LayoutParameter的相关接口设定。


LayoutManager布局管理器

主要用于对Layout的子节点执行布局操作,包含三种:

  • LinearVerticalLayoutManager: 垂直布局管理器
  • LinearHorizontalLayoutManager:水平布局管理器
  • RelativeLayoutManager:相对布局管理器

继承结构:

LayoutManager
Ref
LinearVerticalLayoutManager
LinearHorizontalLayoutManager
RelativeLayoutManager

LayoutManager被LayoutProtocol接口封装

class CC_GUI_DLL LayoutProtocol
{
public:
    LayoutProtocol(){}
    virtual ~LayoutProtocol(){}

    // 创建布局管理器
    virtual LayoutManager* createLayoutManager() = 0;
    // 返回布局的内容大小,即所有子节点的大小和,不包含边距和间隔
    virtual Size getLayoutContentSize()const = 0;
    // 获取布局内的所有子节点
    virtual const Vector<Node*>& getLayoutElements()const = 0;
    // 执行布局,即计算子节点的位置和大小
    virtual void doLayout() = 0;
};

其主要的实现方法为: doLayout, 以垂直布局管理器为例,简单看下代码逻辑:

void LinearHorizontalLayoutManager::doLayout(LayoutProtocol* layout)
{
    // 获取布局内容大小
    Size layoutSize = layout->getLayoutContentSize();
    // 获取Layout节点内的所有子节点
    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->getBoundingBox().size;
                float finalPosX = leftBoundary + (ap.x * cs.width);
                float finalPosY = layoutSize.height - (1.0f - ap.y) * cs.height;
                switch (childGravity)
                {
                    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;
                }
                // 获取布局元素的边距,计算最终位置
                Margin mg = layoutParameter->getMargin();
                finalPosX += mg.left;
                finalPosY -= mg.top;
                child->setPosition(Vec2(finalPosX, finalPosY));
                leftBoundary = child->getRightBoundary() + mg.right;
            }
        }
    }
}

LayoutParameter布局元素

它被LayoutParameterProtocol接口封装

class CC_GUI_DLL LayoutParameterProtocol
{
public:
    virtual ~LayoutParameterProtocol(){}
		// 获取节点的布局元素
    virtual LayoutParameter* getLayoutParameter() const= 0;
};

主要用于对布局元素设置对齐方式和边距相关,主要有两种:

  • LinearLayoutParameter:线性布局元素,主要处理水平,垂直布局的元素节点
  • RelativeLayoutParameter:相对布局元素,主要处理相对布局的元素节点

继承结构:

LayoutParameter
Ref
LinearLayoutParameter
RelativeLayoutParameter

其主要的方法:

// 设置线性布局对齐方式, 支持左,右,上,下,垂直居中,水平居中等
void setGravity(LinearGravity gravity);
// 设置元素的边距,即左,右,上,下
void setMargin(const Margin& margin);

Layout UI布局

Layout UI布局的实现,简单的理解就是对子节点添加布局元素,然后通过布局管理器对布局元素进行位置调整。我们看下代码的具体实现:

  • 设置布局类型,它会将所有的子节点根据对应的布局类型设置到布局元素中
void Layout::setLayoutType(Type type)
{
    _layoutType = type;
    for (auto& child : _children)
    {
        Widget* widgetChild = dynamic_cast<Widget*>(child);
        if (widgetChild)
        {
            // 将子节点设置对应的布局元素
            supplyTheLayoutParameterLackToChild(static_cast<Widget*>(child));
        }
    }
    _doLayoutDirty = true;
}

void Layout::supplyTheLayoutParameterLackToChild(Widget *child)
{
    if (!child){
        return;
    }
  
  	/*
  	主要接口是setLayoutParameter,接口实现在Widget中
		1. 如果布局类型为绝对类型,则不设置布局元素,节点设置setPosition有效
		2. 如果布局类型为水平,垂直类型,则布局元素为LinearLayoutParameter,节点设置setPosition无效
		3. 如果布局类型为相对类型,则布局元素为RelativeLayoutParameter,节点设置setPosition无效
  	*/
    switch (_layoutType)
    {
        case Type::ABSOLUTE:
            break;
        case Type::HORIZONTAL:
        case Type::VERTICAL:
        {
            LinearLayoutParameter* layoutParameter = dynamic_cast<LinearLayoutParameter*>(child->getLayoutParameter());
            if (!layoutParameter)
            {
                child->setLayoutParameter(LinearLayoutParameter::create());
            }
            break;
        }
        case Type::RELATIVE:
        {
            RelativeLayoutParameter* layoutParameter = dynamic_cast<RelativeLayoutParameter*>(child->getLayoutParameter());
            if (!layoutParameter)
            {
                child->setLayoutParameter(RelativeLayoutParameter::create());
            }
            break;
        }
        default:
            break;
    }
}
  • 节点在遍历的时候会调用visit接口
void Layout::visit(Renderer *renderer, const Mat4 &parentTransform, uint32_t parentFlags)
{
    if (!_visible){
        return;
    }
    
    adaptRenderers();
    // 节点操作
    doLayout();
    
  	// 裁切相关,暂不关注
    if (_clippingEnabled){
        switch (_clippingType){
            case ClippingType::STENCIL:
                stencilClippingVisit(renderer, parentTransform, parentFlags);
                break;
            case ClippingType::SCISSOR:
                scissorClippingVisit(renderer, parentTransform, parentFlags);
                break;
        }
    }else {
        Widget::visit(renderer, parentTransform, parentFlags);
    }
}
  • Layout::doLayout主要执行对节点的操作
void Layout::doLayout()
{
  	/*
  	检测是否执行布局操作,默认为true, 注意:
  	1. 在onEnter,addChild, removeChild, removeAllChilren中均为true
  	2. 通过Layout::requestDoLayout()可以将标记设置为true
  	这样做的目的:
  	1. 有助于检测子节点变化产生的布局改变
  	2. 有助于减少不必要的逻辑刷新
  	*/
    if (!_doLayoutDirty){
        return;
    }
    
    sortAllChildren();
		// 添加布局管理器,对布局进行位置计算
    LayoutManager* executant = this->createLayoutManager();
    if (executant){
        executant->doLayout(this);
    }
    _doLayoutDirty = false;
}
  • 布局管理器的布局计算,千万要注意的是:使用Layout布局一定要设置布局类型,否则将无效。

实现简单的水平布局的Lua实例:

-- 设置setLayoutType即可
local layout_1 = ccui.Layout:create()
layout_1:setPosition(cc.p(display.width/4, display.height*4/5))
layout_1:setLayoutType(ccui.LayoutType.HORIZONTAL)
layout_1:setAnchorPoint(cc.p(0.5, 0.5))
layout_1:setContentSize(200, 100)
self:addChild(layout_1)

local layoutSize = layout_1:getContentSize()
for i = 1, 3 do 
  local img = ccui.ImageView:create(Res.BTN_D)
  img:setContentSize(100, 80)
  layout_1:addChild(img)
end 

对子节点进行操作的垂直布局相关Lua实例:

local layout_2 = ccui.Layout:create() 
layout_2:setPosition(cc.p(display.width/4, display.height*3/5))
layout_2:setLayoutType(ccui.LayoutType.VERTICAL)
layout_2:setAnchorPoint(cc.p(0.5, 0.5))
layout_2:setContentSize(100, 100)
self:addChild(layout_2)

local layoutSize = layout_2:getContentSize()
for i = 1, 3 do 
  local img = ccui.ImageView:create(Res.BTN_D)
  layout_2:addChild(img)

  -- 创建布局参数
  local paramMeter = ccui.LinearLayoutParameter:create()
  -- 设置节点在线性布局中的对齐方式
  paramMeter:setGravity(ccui.LinearGravity.centerHorizontal)
  -- 设置节点的外边距,即与相邻节点之间的距离
  paramMeter:setMargin({left = 10, top = 10, right = 10, bottom = 10 })
  -- 设置节点的布局参数
  img:setLayoutParameter(paramMeter)
end

延伸

官方针对于布局相关,额外的提供了一些其他接口的使用。主要有两种:

  1. HBox/VBox/RelativeBox 都继承于Layout,其本质是对布局接口的一次简要封装
HBox
Layout
VBox
RelativeBox

以HBox为例,简单看下其实现逻辑:

HBox* HBox::create()
{
    HBox* widget = new (std::nothrow) HBox();
    if (widget && widget->init())
    {
        widget->autorelease();
        return widget;
    }
    CC_SAFE_DELETE(widget);
    return nullptr;
}

bool HBox::init()
{
    if (Layout::init())
    {
      	// 设置布局类型为水平
        setLayoutType(Layout::Type::HORIZONTAL);
        return true;
    }
    return false;
}

其他的与之类似,三个接口的实现说白了:

  • HBox 默认节点为水平布局,setLayoutType(Layout::Type::HORIZONTAL)
  • VBox 默认节点为垂直布局, setLayoutType(Layout::Type::VERTICAL)
  • RelativeBox 默认节点为相对布局,setLayoutType(Layout::Type::RELATIVE)

  1. LayoutComponent 布局组件

这个与Layout的布局没有什么关系,它是通过子节点的百分比相关来设置节点的位置和大小相关。

Component
Ref
LayoutComponent

相关的Lua实例:

--[[
* setActiveEnabled 设置是否激活布局组件。
* bindLayoutComponent 绑定布局组件
* refreshLayout 刷新布局
* setPercentOnlyEnabled 设置是否仅使用百分比
* setPercentWidthEnabled/isPercentWidthEnabled 设置/是否 启用宽度百分比。
* setPercentHeightEnabled/isPercentHeightEnabled 设置/是否 启用高度百分比。 
* setUsingPercentContentSize/getUsingPercentContentSize 使用百分比内容大小。 
* setPercentWidth/getPercentWidth 设置/获取宽度的百分比值
* setPercentHeight/getPercentHeight 设置/获取高度的百分比值
* setSize/getSize 设置/获取组件大小
* setSizeWidth/getSizeWidth 设置/获取组件宽度
* setSizeHeight/getSizeHeight 设置/获取组件高度
* setStretchWidthEnabled/setStretchHeightEnabled 设置宽度/高度是否自动拉伸
* setLeftMargin/setRightMargin/setTopMargin/setBottomMargin 设置左/右/上/下边距
* getLeftMargin/getRightMargin/getTopMargin/getBottomMargin 获取左/右/上/下边距
* setHorizontalEdge/getHorizontalEdge 设置/获取水平边缘
* setVerticalEdge/getVerticalEdge 设置/获取垂直边缘
* setPositionPercentX/getPositionPercentX 设置/获取 X 轴位置百分比
* setPositionPercentY/getPositionPercentY 设置/获取 Y 轴位置百分比
* setPercentContentSize/getPercentContentSize 设置/获取百分比内容大小
* setAnchorPosition/getAnchorPosition 设置/获取锚点位置
* setPositionPercentXEnabled/ isPositionPercentXEnabled 设置是否启用X轴位置百分比
* setPositionPercentYEnabled/ isPositionPercentYEnabled 设置是否启用Y轴位置百分比
* isStretchWidthEnabled/isStretchHeightEnabled 检查宽度/高度是否自动拉伸
]]
local layer = cc.LayerColor:create()
layer:setColor(cc.c3b(50, 100, 0))
layer:setOpacity(100)
layer:setContentSize(cc.size(200, 200))
self:addChild(layer)

-- 左上
local leftTopSprite = cc.Sprite:create(Res.BTN_D)
local leftTop = ccui.LayoutComponent:bindLayoutComponent(leftTopSprite)
-- 设置水平边缘
leftTop:setHorizontalEdge(ccui.LayoutComponent.HorizontalEdge.Left)
-- 设置垂直边缘
leftTop:setVerticalEdge(ccui.LayoutComponent.VerticalEdge.Top)
layer:addChild(leftTopSprite)
-- 左下
local leftBottomSprite = cc.Sprite:create(Res.BTN_D)
local leftBottom = ccui.LayoutComponent:bindLayoutComponent(leftBottomSprite)
leftBottom:setHorizontalEdge(ccui.LayoutComponent.HorizontalEdge.Left)
leftBottom:setVerticalEdge(ccui.LayoutComponent.VerticalEdge.Bottom)
layer:addChild(leftBottomSprite)
-- 右上
local rightTopSprite = cc.Sprite:create(Res.BTN_D)
local rightTop = ccui.LayoutComponent:bindLayoutComponent(rightTopSprite)
rightTop:setHorizontalEdge(ccui.LayoutComponent.HorizontalEdge.Right)
rightTop:setVerticalEdge(ccui.LayoutComponent.VerticalEdge.Top)
layer:addChild(rightTopSprite)
-- 右下
local rightBottomSprite = cc.Sprite:create(Res.BTN_D)
local rightBottom = ccui.LayoutComponent:bindLayoutComponent(rightBottomSprite)
rightBottom:setHorizontalEdge(ccui.LayoutComponent.HorizontalEdge.Right)
rightBottom:setVerticalEdge(ccui.LayoutComponent.VerticalEdge.Bottom)
layer:addChild(rightBottomSprite)
-- 居中
local centerSprite = cc.Sprite:create(Res.BTN_D)
local center = ccui.LayoutComponent:bindLayoutComponent(rightBottomSprite)
center:setHorizontalEdge(ccui.LayoutComponent.HorizontalEdge.Center)
center:setVerticalEdge(ccui.LayoutComponent.VerticalEdge.Center)
layer:addChild(centerSprite)

拓展

todo…

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
布局管理器(Layout)是Qt框架中用于管理窗口部件位置和大小的类。它提供了一种方便的方式来自动调整和排列窗口部件,以适应不同的窗口大小和布局需求。以下是布局管理器的一些功能: 1. 自动调整大小:布局管理器可以根据窗口大小的变化自动调整包含的窗口部件的大小,以保持它们在布局中的相对位置和比例。 2. 自动布局布局管理器会根据一组规则和策略,自动将窗口部件按照预定的方式排列和布局,以适应窗口大小的变化。例如,水平布局(QHBoxLayout)会将窗口部件水平排列,垂直布局(QVBoxLayout)会将窗口部件垂直排列。 3. 灵活性:布局管理器可以根据需要进行嵌套和组合,以创建复杂的布局结构。例如,可以将水平布局和垂直布局嵌套在一起,以实现更复杂的布局效果。 4. 动态添加和移除:可以在运行时动态地添加或移除窗口部件,而无需手动调整它们的位置和大小。布局管理器会自动重新布局,以适应新的窗口部件。 5. 对齐和空白控制:布局管理器提供对窗口部件对齐方式的控制,例如左对齐、右对齐、居中对齐等。此外,还可以通过添加空白控件(QSpacerItem)来控制窗口部件之间的间距和空白区域。 通过使用布局管理器,Qt应用程序可以实现灵活和响应式的用户界面布局,减少了手动计算和调整窗口部件位置的工作量,提高了开发效率和用户体验。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

鹤九日

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值