版本: cocosV3.10
Layout作为cocos2dx UI中的基础容器,支持裁切,支持布局。
针对于后者而言,ScrollView,ListView,TableView,PageView
等这些容器的布局都通过Layout进行拓展支持的,简单了解下它们的继承结构。
Layout的C++代码继承结构:
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
的相关接口设定。
主要用于对Layout
的子节点执行布局操作,包含三种:
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& getLayoutElements()const = 0;
// 执行布局,即计算子节点的位置和大小
virtual void doLayout() = 0;
};
其主要的实现方法为: doLayout
, 以垂直布局管理器为例,简单看下代码逻辑:
void LinearHorizontalLayoutManager::doLayout(LayoutProtocol* layout)
{
// 获取布局内容大小
Size layoutSize = layout->getLayoutContentSize();
// 获取Layout节点内的所有子节点
Vector container = layout->getLayoutElements();
float leftBoundary = 0.0f;
for (auto& subWidget : container)
{
Widget* child = dynamic_cast(subWidget);
if (child)
{
// 获取子节点内的布局元素
LinearLayoutParameter* layoutParameter = dynamic_cast(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;
}
}
}
}
它被LayoutParameterProtocol
接口封装
class CC_GUI_DLL LayoutParameterProtocol
{
public:
virtual ~LayoutParameterProtocol(){}
// 获取节点的布局元素
virtual LayoutParameter* getLayoutParameter() const= 0;
};
主要用于对布局元素设置对齐方式和边距相关,主要有两种:
LinearLayoutParameter
:线性布局元素,主要处理水平,垂直布局的元素节点RelativeLayoutParameter
:相对布局元素,主要处理相对布局的元素节点继承结构:
其主要的方法:
// 设置线性布局对齐方式, 支持左,右,上,下,垂直居中,水平居中等
void setGravity(LinearGravity gravity);
// 设置元素的边距,即左,右,上,下
void setMargin(const Margin& margin);
Layout UI布局的实现,简单的理解就是对子节点添加布局元素,然后通过布局管理器对布局元素进行位置调整。我们看下代码的具体实现:
void Layout::setLayoutType(Type type)
{
_layoutType = type;
for (auto& child : _children)
{
Widget* widgetChild = dynamic_cast(child);
if (widgetChild)
{
// 将子节点设置对应的布局元素
supplyTheLayoutParameterLackToChild(static_cast(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(child->getLayoutParameter());
if (!layoutParameter)
{
child->setLayoutParameter(LinearLayoutParameter::create());
}
break;
}
case Type::RELATIVE:
{
RelativeLayoutParameter* layoutParameter = dynamic_cast(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;
}
实现简单的水平布局的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
官方针对于布局相关,额外的提供了一些其他接口的使用。主要有两种:
以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;
}
其他的与之类似,三个接口的实现说白了:
这个与Layout的布局没有什么关系,它是通过子节点的百分比相关来设置节点的位置和大小相关。
相关的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…