转载请说明原出处,谢谢~~:http://blog.csdn.net/zhuhongshu/article/details/42950733
BUG 一:padding导致其他控件宽度计算错误
今天在写项目的一个布局时,用到了最常用的相对布局属性padding:在一个纵向容器里,给其中的各个子元素设置了padding属性来做相对布局。但是出现了很奇怪的现象:容器的最后一个元素本应该在最底部,但是实际却留出了一部分空白。
实际上这个bug早在我写仿酷狗时就遇到了,当时没有很注意,就用了绝对布局去解决了(一般情况下用绝对布局不是好习惯)。现在恍然大悟,原来是个bug。我这里用仿酷狗的布局配合截图说明一下这个bug。
仿酷狗的主界面的最底部(习惯上我称这里成为状态栏),他的布局代码和效果图如下:
可以看到,如果按照正常布局来讲,因为有倒数第三个Control的占位效果,所以倒数第二的Label和倒数第一的Control控件,应该处于整个横向布局的末尾部分。而现在奇怪的是他们距离末尾还有一段距离,如图:
这个bug是由于横向布局的子控件,使用了padding属性,导致倒数第三个占位控件的宽度计算错误造成的。修复bug后的理想状态如下:
BUG 二:padding属性的right和bottom字段导致自身宽度或者高度错误
给某一个控件的padding属性指定了right或者bottom字段后,就会发现这个控件的宽度会自动加上padding.right的值(或者高度自动加上padding.bottom)的值,导致了控件的畸形。而padding的功能仅仅应该是控制控件的位置而不影响控件的宽高度。
BUG修复:
这个bug是由于横向布局和纵向布局在计算子控件的位置时的疏漏导致的,也就是SetPos函数的bug。现在拿横向布局分析一下bug产生的原因,我在代码中稍微做了一下注释,方便理解。
void CHorizontalLayoutUI::SetPos(RECT rc)
{
CControlUI::SetPos(rc);
rc = m_rcItem;
rc.left += m_rcInset.left;
rc.top += m_rcInset.top;
rc.right -= m_rcInset.right;
rc.bottom -= m_rcInset.bottom;
if( m_items.GetSize() == 0) {
ProcessScrollBar(rc, 0, 0);
return;
}
if( m_pVerticalScrollBar && m_pVerticalScrollBar->IsVisible() ) rc.right -= m_pVerticalScrollBar->GetFixedWidth();
if( m_pHorizontalScrollBar && m_pHorizontalScrollBar->IsVisible() ) rc.bottom -= m_pHorizontalScrollBar->GetFixedHeight();
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 cxFixed = 0;
int nEstimateNum = 0;
// redrain 第一轮计算得到各种信息,不做实际布局处理
for( int it1 = 0; it1 < m_items.GetSize(); it1++ ) {
CControlUI* pControl = static_cast(m_items[it1]);
if( !pControl->IsVisible() ) continue;
if( pControl->IsFloat() ) continue;
SIZE sz = pControl->EstimateSize(szAvailable);
if( sz.cx == 0 ) {
// redrain 记录需要自动计算宽度的子控件的数量
nAdjustables++;
}
else {
if( sz.cx < pControl->GetMinWidth() ) sz.cx = pControl->GetMinWidth();
if( sz.cx > pControl->GetMaxWidth() ) sz.cx = pControl->GetMaxWidth();
}
cxFixed += sz.cx + pControl->GetPadding().left + pControl->GetPadding().right;
// redrain 记录需要做相对布局的子控件的数量
nEstimateNum++;
}
// redrain cxFixed保存了所有相对布局的控件占用的宽度(包括了padding属性好childpadding属性占用的宽度)
cxFixed += (nEstimateNum - 1) * m_iChildPadding;
int cxExpand = 0;
int cxNeeded = 0;
// redrain cxExpand保存需要自动计算宽度的子控件的宽度
if( nAdjustables > 0 ) cxExpand = MAX(0, (szAvailable.cx - cxFixed) / nAdjustables);
// redrain szRemaining保存除已被布局的子控件以外的剩余空间
SIZE szRemaining = szAvailable;
int iPosX = rc.left;
if( m_pHorizontalScrollBar && m_pHorizontalScrollBar->IsVisible() ) {
iPosX -= m_pHorizontalScrollBar->GetScrollPos();
}
int iAdjustable = 0;
// redrain cxFixedRemaining记录当前还未被布局过的所有子控件的总宽度
int cxFixedRemaining = cxFixed;
for( int it2 = 0; it2 < m_items.GetSize(); it2++ ) {
CControlUI* pControl = static_cast(m_items[it2]);
if( !pControl->IsVisible() ) continue;
if( pControl->IsFloat() ) {
SetFloatPos(it2);
continue;
}
RECT rcPadding = pControl->GetPadding();
szRemaining.cx -= rcPadding.left;
SIZE sz = pControl->EstimateSize(szRemaining);
if( sz.cx == 0 ) {
iAdjustable++;
sz.cx = cxExpand;
// redrain 这里判断如果是最后一个需要自动计算宽度的元素则做出不同的处理
if( iAdjustable == nAdjustables ) {
sz.cx = MAX(0, szRemaining.cx - rcPadding.right - cxFixedRemaining);
}
if( sz.cx < pControl->GetMinWidth() ) sz.cx = pControl->GetMinWidth();
if( sz.cx > pControl->GetMaxWidth() ) sz.cx = pControl->GetMaxWidth();
}
else {
if( sz.cx < pControl->GetMinWidth() ) sz.cx = pControl->GetMinWidth();
if( sz.cx > pControl->GetMaxWidth() ) sz.cx = pControl->GetMaxWidth();
// redrain bug出现在这里,cxFixedRemaining只减去了被布局的控件的宽度,而没有减去padding属性和childpadding属性占据的宽度
cxFixedRemaining -= sz.cx;
}
sz.cy = pControl->GetFixedHeight();
if( sz.cy == 0 ) sz.cy = rc.bottom - rc.top - rcPadding.top - rcPadding.bottom;
if( sz.cy < 0 ) sz.cy = 0;
if( sz.cy < pControl->GetMinHeight() ) sz.cy = pControl->GetMinHeight();
if( sz.cy > pControl->GetMaxHeight() ) sz.cy = pControl->GetMaxHeight();
// redrain bug2,对宽度的错误计算,不应该加上rcPadding.right的值
RECT rcCtrl = { iPosX + rcPadding.left, rc.top + rcPadding.top, iPosX + sz.cx + rcPadding.left + rcPadding.right, rc.top + rcPadding.top + sz.cy};
pControl->SetPos(rcCtrl);
iPosX += sz.cx + m_iChildPadding + rcPadding.left + rcPadding.right;
cxNeeded += sz.cx + rcPadding.left + rcPadding.right;
szRemaining.cx -= sz.cx + m_iChildPadding + rcPadding.right;
}
cxNeeded += (nEstimateNum - 1) * m_iChildPadding;
// Process the scrollbar
ProcessScrollBar(rc, cxNeeded, 0);
}
我以及把关键的变量和代码都做了注释,读完代码后很清楚的看到,最后一个子控件的位置和cxFixedRemaining变量密切相关,而cxFixedRemaining变量的计算错误导致了bug1。所以应该把出现bug1的代码改为如下代码:
for( int it2 = 0; it2 < m_items.GetSize(); it2++ ) {
// 省略
else {
if( sz.cx < pControl->GetMinWidth() ) sz.cx = pControl->GetMinWidth();
if( sz.cx > pControl->GetMaxWidth() ) sz.cx = pControl->GetMaxWidth();
cxFixedRemaining -= sz.cx + rcPadding.left + rcPadding.right ;
}
cxFixedRemaining -= m_iChildPadding;
// 省略
出现bug2的代码修改为:
RECT rcCtrl = { iPosX + rcPadding.left, rc.top + rcPadding.top, iPosX + sz.cx + rcPadding.left , rc.top + rcPadding.top + sz.cy};
纵向布局的bug代码与这个类似,我就不再分析了。直接给出修复代码:
cyFixedRemaining -= sz.cy + rcPadding.top + rcPadding.bottom;
}
cyFixedRemaining -= m_iChildPadding;
RECT rcCtrl = { iPosX + rcPadding.left, iPosY + rcPadding.top, iPosX + rcPadding.left + sz.cx, iPosY + sz.cy + rcPadding.top };
总结:
duilib存在很多细节上的bug,在使用过程中常常能发觉出存在bug。这个bug的修复也已经同步到我的库里了。下载地址:点击打开链接
Redrain 2015.1.21