7垂直布局

1.简介

CVerticalLayoutUI,其所有的直接子控件都是从上到下,垂直分布的,故称为垂直布局。垂直布局继承于 CContainerUI,其中大部分方法 CContainerUI 已经实现。

CContainerUI为所有容器类的基础类,其实现了 IContainerUI 接口中的所有方法。如果想要实现新的布局类型,可以继承此方法,并重写其中的 SetPos() 函数。

本节将介绍 IContainerUI 接口函数在 CContainerUI 中的实现 和 CVerticalLayoutUI 中 SetPos() 的实现。

2. IContainerUI 接口

class IContainerUI
{
public:
    virtual CControlUI* GetItemAt(int iIndex) const = 0;
    virtual int GetItemIndex(CControlUI* pControl) const  = 0;
    virtual bool SetItemIndex(CControlUI* pControl, int iIndex)  = 0;
    virtual int GetCount() const = 0;
    virtual bool Add(CControlUI* pControl) = 0;
    virtual bool AddAt(CControlUI* pControl, int iIndex)  = 0;
    virtual bool Remove(CControlUI* pControl) = 0;
    virtual bool RemoveAt(int iIndex)  = 0;
    virtual void RemoveAll() = 0;
};

3. CContainerUI 对 IContainerUI 接口的实现

3.1 获取第i个子控件:GetItemAt()

CControlUI* CContainerUI::GetItemAt(int iIndex) const
{
    if( iIndex < 0 || iIndex >= m_items.GetSize() ) 
        return NULL;
    return static_cast<CControlUI*>(m_items[iIndex]);
}

3.2 获取子控件的序号:GetItemIndex()

int CContainerUI::GetItemIndex(CControlUI* pControl) const
{
    for( int it = 0; it < m_items.GetSize(); it++ ) 
    {
        if( static_cast<CControlUI*>(m_items[it]) == pControl ) 
        {
            return it;
        }
    }

    //不存在返回-1
    return -1;
}

3.3 为子控件设置序号:SetItemIndex()

bool CContainerUI::SetItemIndex(CControlUI* pControl, int iIndex)
{
    for( int it = 0; it < m_items.GetSize(); it++ ) 
    {
        if( static_cast<CControlUI*>(m_items[it]) == pControl ) 
        {
            //重新排序的话需要更新子控件位置
            NeedUpdate(); 

            m_items.Remove(it);
            return m_items.InsertAt(iIndex, pControl);
        }
    }

    return false;
}

3.4 获取子控件个数:GetCount()

int CContainerUI::GetCount() const
{
    return m_items.GetSize();
}

3.5 添加子控件(尾部):Add()

bool CContainerUI::Add(CControlUI* pControl)
{
    if( pControl == NULL) return false;

    if( m_pManager != NULL ) 
    {
        //设pControl的父控件为this
        m_pManager->InitControls(pControl, this);
    }
    if( IsVisible() ) 
    {
        //位置需要更新
        NeedUpdate(); 
    }
    else 
    {
        pControl->SetInternVisible(false);
    }

    return m_items.Add(pControl);   
}

3.6 在特定位置添加子控件:AddAt()

bool CContainerUI::AddAt(CControlUI* pControl, int iIndex)
{
    if( pControl == NULL) return false;

    if( m_pManager != NULL ) 
    {
        //设pControl的父控件为this
        m_pManager->InitControls(pControl, this);
    }

    if( IsVisible() ) 
        NeedUpdate();
    else 
        pControl->SetInternVisible(false);

    return m_items.InsertAt(iIndex, pControl);
}

3.7 删除某个子控件:Remove()

默认情况下,删除的子控件会被 delete 掉。

bool CContainerUI::Remove(CControlUI* pControl)
{
    if( pControl == NULL) return false;

    for( int it = 0; it < m_items.GetSize(); it++ ) 
    {
        if( static_cast<CControlUI*>(m_items[it]) == pControl ) 
        {
            //位置需要更新
            NeedUpdate();

            if( m_bAutoDestroy ) //是否销毁,默认true
            {
                if( m_bDelayedDestroy && m_pManager ) 
                    m_pManager->AddDelayedCleanup(pControl);             
                else 
                    delete pControl;
            }
            return m_items.Remove(it);
        }
    }
    return false;
}

3.8 根据序号删除某个子控件:RemoveAt()

bool CContainerUI::RemoveAt(int iIndex)
{
    CControlUI* pControl = GetItemAt(iIndex);
    if (pControl != NULL) 
    {
        return CContainerUI::Remove(pControl); //
    }

    return false;
}

3.9 移除所有子控件:RemoveAll()

下面的实现方式比循环调用 RemoveAt() 更加高效。

void CContainerUI::RemoveAll()
{
    for( int it = 0; m_bAutoDestroy && it < m_items.GetSize(); it++ ) 
    {
        if( m_bDelayedDestroy && m_pManager ) 
            m_pManager->AddDelayedCleanup(static_cast<CControlUI*>(m_items[it]));             
        else 
            delete static_cast<CControlUI*>(m_items[it]);
    }
    m_items.Empty();
    NeedUpdate();
}

4. CVerticalLayoutUI 中 SetPos() 实现

void CVerticalLayoutUI::SetPos(RECT rc)
{
    CControlUI::SetPos(rc);
    rc = m_rcItem;

    // 为了内间距和垂直水平滑动条留出空间
    // 此时 rc 为用于显示子控件的空间位置及大小
    rc.left += m_rcInset.left;
    rc.top += m_rcInset.top;
    rc.right -= m_rcInset.right;
    rc.bottom -= m_rcInset.bottom;
    if( m_pVerticalScrollBar && m_pVerticalScrollBar->IsVisible() ) 
        rc.right -= m_pVerticalScrollBar->GetFixedWidth();
    if( m_pHorizontalScrollBar && m_pHorizontalScrollBar->IsVisible() ) 
        rc.bottom -= m_pHorizontalScrollBar->GetFixedHeight();

    // 没有子控件就没必须继续了
    if( m_items.GetSize() == 0) 
    {
        ProcessScrollBar(rc, 0, 0);
        return;
    }

    // 垂直布局的情况下,如果有水平滚动条,水平显示空间会相应增加
    SIZE szAvailable = { rc.right - rc.left, rc.bottom - rc.top };
    if( m_pHorizontalScrollBar && m_pHorizontalScrollBar->IsVisible() ) 
        szAvailable.cx += m_pHorizontalScrollBar->GetScrollRange();

    // 循环遍历子控件,计算所需总空间高度及可调整和显示元素个数
    int nAdjustables = 0; // 可调高度的子控件个数
    int cyFixed = 0;  // 总体需要的空间高度(未包含可调高度的子控件高度)
    int nEstimateNum = 0; // 显示且非float子控件个数
    for( int it1 = 0; it1 < m_items.GetSize(); it1++ ) 
    {
        CControlUI* pControl = static_cast<CControlUI*>(m_items[it1]);
        if( !pControl->IsVisible() ) continue;
        if( pControl->IsFloat() ) continue;
        SIZE sz = pControl->EstimateSize(szAvailable);
        if( sz.cy == 0 ) 
        {
            nAdjustables++;
        }
        else 
        {
            if( sz.cy < pControl->GetMinHeight() ) 
                sz.cy = pControl->GetMinHeight();
            if( sz.cy > pControl->GetMaxHeight() ) 
                sz.cy = pControl->GetMaxHeight();
        }
        cyFixed += sz.cy + pControl->GetPadding().top + pControl->GetPadding().bottom;
        nEstimateNum++;
    }
    cyFixed += (nEstimateNum - 1) * m_iChildPadding;

    // 计算可调高度的子控件的设置高度
    // 子控件的高度并不一定设置为此(当子控件设置了最小高度属性时)
    int cyExpand = 0;
    if( nAdjustables > 0 ) 
        cyExpand = MAX(0, (szAvailable.cy - cyFixed) / nAdjustables);

    // 设置画布的初始位置
    int iPosY = rc.top;
    if( m_pVerticalScrollBar && m_pVerticalScrollBar->IsVisible() ) 
    {
        iPosY -= m_pVerticalScrollBar->GetScrollPos();
    }
    int iPosX = rc.left;
    if( m_pHorizontalScrollBar && m_pHorizontalScrollBar->IsVisible() ) 
    {
        iPosX -= m_pHorizontalScrollBar->GetScrollPos();
    }

    // 循环为各个子控件设置位置及大小
    int cyNeeded = 0; //
    SIZE szRemaining = szAvailable; // 剩余空间大小
    int iAdjustable = 0; // 第几个可调高度子控件
    int cyFixedRemaining = cyFixed; // 仍需要的高度空间
    for( int it2 = 0; it2 < m_items.GetSize(); it2++ ) 
    {
        CControlUI* pControl = static_cast<CControlUI*>(m_items[it2]);
        if( !pControl->IsVisible() ) continue;
        if( pControl->IsFloat() ) 
        {
            SetFloatPos(it2);
            continue;
        }

        RECT rcPadding = pControl->GetPadding();
        szRemaining.cy -= rcPadding.top;
        SIZE sz = pControl->EstimateSize(szRemaining);

        // 设置子控件高度
        if( sz.cy == 0 ) 
        {
            // 设置可调高度子控件的高度
            iAdjustable++;
            sz.cy = cyExpand;
            if( iAdjustable == nAdjustables ) 
            {
                sz.cy = MAX(0, szRemaining.cy - rcPadding.bottom - cyFixedRemaining);
            } 
            if( sz.cy < pControl->GetMinHeight() ) 
                sz.cy = pControl->GetMinHeight();
            if( sz.cy > pControl->GetMaxHeight() ) 
                sz.cy = pControl->GetMaxHeight();

            // 由于在预估高度的时候未计算该高度,故不减去
            // cyFixedRemaining -= sz.cy;
        }
        else
        {
            // 设置不可调子控件的高度
            if( sz.cy < pControl->GetMinHeight() ) 
                sz.cy = pControl->GetMinHeight();
            if( sz.cy > pControl->GetMaxHeight() ) 
                sz.cy = pControl->GetMaxHeight();

            // 由于在预估高度的时候计算了该高度,在此减去
            cyFixedRemaining -= sz.cy + rcPadding.top + rcPadding.bottom;
        }

        // 设置子控件宽度
        sz.cx = pControl->GetFixedWidth();
        if( sz.cx == 0 ) 
            sz.cx = szAvailable.cx - rcPadding.left - rcPadding.right;
        if( sz.cx < 0 ) 
            sz.cx = 0;
        if( sz.cx < pControl->GetMinWidth() ) 
            sz.cx = pControl->GetMinWidth();
        if( sz.cx > pControl->GetMaxWidth() ) 
            sz.cx = pControl->GetMaxWidth();

        // 设置子控件的位置及大小
        RECT rcCtrl = { iPosX + rcPadding.left, 
                        iPosY + rcPadding.top, 
                        iPosX + rcPadding.left + sz.cx, 
                        iPosY + sz.cy + rcPadding.top + rcPadding.bottom };
        pControl->SetPos(rcCtrl);

        // 更新画布的 Y 坐标
        iPosY += sz.cy + m_iChildPadding + rcPadding.top + rcPadding.bottom;

        // 累计所需要的 Y 的空间高度
        cyNeeded += sz.cy + rcPadding.top + rcPadding.bottom;

        // 控件剩余可视控件大小
        szRemaining.cy -= sz.cy + m_iChildPadding + rcPadding.bottom;
    }
    cyNeeded += (nEstimateNum - 1) * m_iChildPadding;

    //刷新滚动条
    ProcessScrollBar(rc, 0, cyNeeded);
}

大致步骤如下:

1. 计算可用于显示子控件的空间位置及大小;
2. 循环遍历子控件,计算出所需整体空间高度、可调高度子控件个数;
3. 根据滚动条的位置,初始化画布的位置坐标;
4. 循环为各个子控件设置位置及大小:
    1. 如果为可调高度子控件:设置为 a.扩展后的高度 b.最小高度与最大高度之间
    2. 如果为不可调高度子控件:直接根据子控件的预设高度进行设置
5. 刷新滚动条的滚动范围。

5.介绍

当然水平布局跟垂直布局也很相似,不解释了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值