對應源碼位置: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控件 的實現。