Skip to content

锚点布局 (Anchor Layout)

Warning

The current page still doesn't have a translation for this language.

You can read it through google translate.

概述

锚点布局(Anchor Layout)是 SOUI5.1 新增的一种布局方式,它允许子元素相对于父容器的九个锚点进行定位。这种布局方式非常适合需要精确控制子元素位置的场景,比如浮动按钮、标签定位等。

锚点定义

锚点布局定义了九个锚点位置:

  1. APT_Left_Top (0) - 左上角
  2. APT_Center_Top (1) - 上中
  3. APT_Right_Top (2) - 右上角
  4. APT_Left_Center (3) - 左中
  5. APT_Center_Center (4) - 正中心
  6. APT_Right_Center (5) - 右中
  7. APT_Left_Bottom (6) - 左下角
  8. APT_Center_Bottom (7) - 下中
  9. 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);

注意事项

  1. 坐标系:锚点布局使用的是相对于父容器的坐标系,原点在父容器左上角。
  2. 偏移计算:当使用比例偏移时,offsetX和offsetY的值会乘以控件自身的宽度和高度。
  3. 尺寸优先级:size属性优先级高于单独的width和height属性。
  4. 动画支持:锚点布局天然支持属性动画,可以方便地实现位置变换效果。
  5. pos属性格式:pos属性必须包含三个值,分别是x坐标、y坐标和锚点类型。
  6. 居中实现:居中不是通过设置pos="0.5,0.5,4"这样的坐标值实现的,而是通过设置锚点为中心点,然后使用offset负值偏移实现的。
  7. 偏移量使用:offset属性的比例偏移是以控件自身大小为基准的,通常使用小数值,如-0.5表示偏移一半控件大小。
  8. 自定义锚点:可以通过设置回调函数实现自定义锚点类型,扩展锚点布局的功能。自定义锚点的回调函数需要遵循PFN_Position2Point签名,接收AnchorPos、父容器矩形、子控件尺寸、缩放值和用户数据等参数。

适用场景

锚点布局适用于以下场景:

  1. 浮动元素定位:如悬浮按钮、提示标签等
  2. 精确位置控制:需要将元素放置在特定位置的场景
  3. 动画效果:配合属性动画实现复杂的移动效果
  4. 响应式设计:利用比例定位实现响应式布局
  5. 自定义锚点需求:需要特殊锚点位置的场景

通过合理使用锚点布局,可以轻松实现复杂的界面布局效果,特别是在需要精确定位元素的场景中非常有用。