锚点布局 (Anchor Layout)¶
概述¶
锚点布局(Anchor Layout)是 SOUI5.1 新增的一种布局方式,它允许子元素相对于父容器的九个锚点进行定位。这种布局方式非常适合需要精确控制子元素位置的场景,比如浮动按钮、标签定位等。
锚点定义¶
锚点布局定义了九个锚点位置:
- APT_Left_Top (0) - 左上角
- APT_Center_Top (1) - 上中
- APT_Right_Top (2) - 右上角
- APT_Left_Center (3) - 左中
- APT_Center_Center (4) - 正中心
- APT_Right_Center (5) - 右中
- APT_Left_Bottom (6) - 左下角
- APT_Center_Bottom (7) - 下中
- APT_Right_Bottom (8) - 右下角
自定义锚点支持¶
除了内置的九个锚点之外,SOUI 还支持自定义锚点。通过设置 [SAnchorLayout] 的回调函数,可以实现任意锚点位置的计算。
在 SOUI 的实现中,[SAnchorLayout] 提供了 SetPosition2PointCallback 方法来设置自定义锚点计算回调。回调函数的签名是 PFN_Position2Point,定义如下:
typedef POINT (CALLBACK * PFN_Position2Point)(const AnchorPos &pos, const CRect &rcParent, const CSize &szChild, int nScale, void *userData);
自定义锚点的实现方法¶
// 定义自定义锚点回调函数
POINT CALLBACK CustomPosition2Point(const AnchorPos &pos, const CRect &rcParent, const CSize &szChild, int nScale, void *userData)
{
if(pos.type == 100) // 自定义锚点1
{
// 自定义锚点计算逻辑
// 这里可以根据AnchorPos中的x, y值和偏移量计算位置
CPoint pt;
pt.x = rcParent.left + (int)(rcParent.Width() * pos.x.fSize / 100.0f); // 使用百分比
pt.y = rcParent.top + (int)(rcParent.Height() * pos.y.fSize / 100.0f);
pt.x += pos.fOffsetX * szChild.cx; // 应用偏移量
pt.y += pos.fOffsetY * szChild.cy;
return pt;
}
else if(pos.type == 101) // 自定义锚点2
{
CPoint pt;
pt.x = rcParent.left + pos.x.fSize; // 使用像素值
pt.y = rcParent.top + pos.y.fSize;
pt.x += pos.fOffsetX * szChild.cx;
pt.y += pos.fOffsetY * szChild.cy;
return pt;
}
else
{
// 对于标准锚点,使用默认计算方法
return SAnchorLayout::DefaultPosition2Point(pos, rcParent, szChild, nScale, userData);
}
}
// 设置自定义锚点回调
SAnchorLayout* pAnchorLayout = (SAnchorLayout*)pWindow->GetLayout();
pAnchorLayout->SetPosition2PointCallback(CustomPosition2Point, nullptr);
实际应用示例:中国象棋游戏¶
在中国象棋游戏([games/cnchess])的实现中,自定义锚点用于棋盘坐标系统,这是一个很好的实际应用示例:
// 在CChessGame类中定义的自定义锚点计算函数
POINT CChessGame::ChessAnchor2Pos(const AnchorPos &pos, const CRect &rcParent, const CSize & szChild, int nScale, void * userData)
{
if(pos.type == 10) // 棋子锚点
{
// type == 10 是棋子锚点
CChessGame *pThis = (CChessGame *)userData;
CPoint pt = pThis->m_ptBoardOrigin; // 棋盘原点
pt.x += pThis->m_cellWidth * pos.x.fSize; // 根据列数计算x坐标
pt.y -= pThis->m_cellHeight * pos.y.fSize; // 根据行数计算y坐标
pt.x += pos.fOffsetX * szChild.cx; // 应用x方向偏移
pt.y += pos.fOffsetY * szChild.cy; // 应用y方向偏移
return pt;
}
else if(pos.type == 11) // 棋子阴影锚点
{
// 阴影锚点的特殊处理
CChessGame *pThis = (CChessGame *)userData;
CPoint pt = pThis->m_ptBoardOrigin;
pt.x += pThis->m_cellWidth * pos.x.fSize;
pt.y -= pThis->m_cellHeight * pos.y.fSize;
pt.x += pos.fOffsetX * szChild.cx;
pt.y += pos.fOffsetY * szChild.cx; // 注意这里使用宽度作为高度的偏移参考
return pt;
}
else
{
// 对于标准锚点,使用默认计算方法
return SAnchorLayout::DefaultPosition2Point(pos, rcParent, szChild, nScale, userData);
}
}
然后在象棋游戏中设置回调:
// 设置自定义锚点回调
pAnchorLayout->SetPosition2PointCallback(CChessAnchor2Pos, this);
这样就可以使用自定义的锚点类型,例如:
<!-- 使用自定义锚点10(棋子锚点) -->
<button pos="3,2,10" size="50,50">棋子</button>
<!-- 使用自定义锚点11(阴影锚点) -->
<image pos="3,2,11" size="50,40">阴影</image>
属性说明¶
锚点布局支持以下属性:
pos(位置)¶
定义子元素相对于父容器的锚点位置,需要三个值:x坐标、y坐标、锚点类型。
示例:
<!-- 使用锚点编号 -->
<button pos="0,0,0">左上角按钮</button>
<!-- 使用坐标和锚点类型 -->
<button pos="100,50,4">绝对坐标按钮</button>
offset(偏移)¶
定义子元素相对于锚点的偏移量,支持像素和比例两种方式。比例偏移是以控件自身大小为基准的,offsetX会乘以控件宽度,offsetY会乘以控件高度。
示例:
<!-- 像素偏移 -->
<button pos="0,0,0" offset="10,10">距离左上角(10,10)像素的按钮</button>
offsetX / offsetY(X/Y轴偏移)¶
单独定义子元素在X轴或Y轴上的偏移量,这个偏移量会乘以控件自身的宽度(X轴)或高度(Y轴)。通常使用小数形式,如-0.5表示偏移控件宽度或高度的一半。
示例:
<!-- X轴偏移控件宽度的一半(向左) -->
<button pos="0,0,0" offsetX="-0.5">X轴偏移按钮</button>
<!-- Y轴偏移控件高度的一半(向上) -->
<button pos="0,0,0" offsetY="-0.5">Y轴偏移按钮</button>
size(尺寸)¶
定义子元素的尺寸,支持具体的像素值、dp值或者其他单位。
示例:
<button size="100,30">固定尺寸按钮</button>
<button width="100dp" height="30dp">使用dp单位的按钮</button>
width/height(宽/高)¶
单独定义子元素的宽度或高度。
示例:
<button width="100" height="30">指定宽高的按钮</button>
使用示例¶
基本用法¶
<window layout="anchor" size="400,300">
<!-- 左上角按钮 -->
<button pos="0,0,0" offset="10,10" size="80,30">按钮1</button>
<!-- 右上角按钮 -->
<button pos="0,0,2" offset="-10,10" size="80,30">按钮2</button>
<!-- 居中按钮(通过锚点+偏移实现) -->
<button pos="0,0,4" offset="-0.5,-0.5" size="80,30">按钮3</button>
<!-- 左下角按钮 -->
<button pos="0,0,6" offset="10,-10" size="80,30">按钮4</button>
<!-- 右下角按钮 -->
<button pos="0,0,8" offset="-10,-10" size="80,30">按钮5</button>
</window>
使用比例定位¶
<window layout="anchor" size="400,300">
<!-- 水平居中顶部按钮 -->
<button pos="0,0,1" offsetX="-0.5" offsetY="10" size="100,30">顶部居中</button>
<!-- 垂直居中左侧按钮 -->
<button pos="0,0,3" offsetX="10" offsetY="-0.5" size="100,30">左侧居中</button>
<!-- 完全居中(通过偏移实现) -->
<button pos="0,0,4" offset="-0.5,-0.5" size="100,30">完全居中</button>
</window>
动态调整位置¶
锚点布局还支持动画效果,可以通过属性动画动态改变控件的位置:
// 创建位置关键帧
AnchorPos pos[] = {
{ APT_Left_Top, SLayoutSize(0, SLayoutSize::px), SLayoutSize(0, SLayoutSize::px), 0, 0 },
{ APT_Right_Bottom, SLayoutSize(0, SLayoutSize::px), SLayoutSize(0, SLayoutSize::px), 0, 0 }
};
// 创建属性动画
SPropertyAnimator* animator = SPropertyAnimator::ofPosition(
pButton,
LayoutProperty::POSITION,
pos,
ARRAYSIZE(pos),
sizeof(AnchorPos)
);
animator->setDuration(1000);
animator->start(pTimelineHandler);
在升级纸牌游戏中,大量使用了这种动画效果来移动卡牌:
// 移动卡牌到指定位置
AnchorPos fromPos = pParamStruct->pos;
AnchorPos toPos1 = fromPos;
toPos1.y.fSize += m_szCard.cy;
AnchorPos toPos2 = toPos1;
toPos2.x.fSize += m_szCard.cx;
AnchorPos poss[] = {
fromPos,
toPos1,
toPos2
};
SAutoRefPtr<IValueAnimator> pAnimator(
SPropertyAnimator::ofPosition(
pCard,
LayoutProperty::POSITION,
poss,
ARRAYSIZE(poss),
sizeof(AnchorPos)
),
FALSE
);
pAnimator->setDuration(nSpeed*2);
Util::StartAnimator(pAnimator, pCard);
注意事项¶
- 坐标系:锚点布局使用的是相对于父容器的坐标系,原点在父容器左上角。
- 偏移计算:当使用比例偏移时,offsetX和offsetY的值会乘以控件自身的宽度和高度。
- 尺寸优先级:size属性优先级高于单独的width和height属性。
- 动画支持:锚点布局天然支持属性动画,可以方便地实现位置变换效果。
- pos属性格式:pos属性必须包含三个值,分别是x坐标、y坐标和锚点类型。
- 居中实现:居中不是通过设置pos="0.5,0.5,4"这样的坐标值实现的,而是通过设置锚点为中心点,然后使用offset负值偏移实现的。
- 偏移量使用:offset属性的比例偏移是以控件自身大小为基准的,通常使用小数值,如-0.5表示偏移一半控件大小。
- 自定义锚点:可以通过设置回调函数实现自定义锚点类型,扩展锚点布局的功能。自定义锚点的回调函数需要遵循
PFN_Position2Point签名,接收AnchorPos、父容器矩形、子控件尺寸、缩放值和用户数据等参数。
适用场景¶
锚点布局适用于以下场景:
- 浮动元素定位:如悬浮按钮、提示标签等
- 精确位置控制:需要将元素放置在特定位置的场景
- 动画效果:配合属性动画实现复杂的移动效果
- 响应式设计:利用比例定位实现响应式布局
- 自定义锚点需求:需要特殊锚点位置的场景
通过合理使用锚点布局,可以轻松实现复杂的界面布局效果,特别是在需要精确定位元素的场景中非常有用。