轉載請說明原出處,謝謝~~:http://blog.csdn.net/zhuhongshu/article/details/42950733
BUG 一:padding導致其他控件寬度計算錯誤
今天在寫項目的一個佈局時,用到了最常用的相對佈局屬性padding:在一個縱向容器裏,給其中的各個子元素設置了padding屬性來做相對佈局。但是出現了很奇怪的現象:容器的最後一個元素本應該在最底部,但是實際卻留出了一部分空白。
實際上這個bug早在我寫仿酷狗時就遇到了,當時沒有很注意,就用了絕對佈局去解決了(一般情況下用絕對佈局不是好習慣)。現在恍然大悟,原來是個bug。我這裏用仿酷狗的佈局配合截圖說明一下這個bug。
仿酷狗的主界面的最底部(習慣上我稱這裏成爲狀態欄),他的佈局代碼和效果圖如下:
<HorizontalLayout name="Main_Status" height="30" inset="77,0,0,0" bkimage="UI\statusbar\status_bk.png"><!-- 狀態欄 -->
<Button name="btn_add_music" width="31" height="30" padding="0,0,0,0" normalimage="UI\statusbar\add_normal.png" hotimage="UI\statusbar\add_hover.png" pushedimage="UI\statusbar\add_down.png" />
<Button width="31" height="30" padding="40,0,0,0" normalimage="UI\statusbar\locate_normal.png" hotimage="UI\statusbar\locate_hover.png" pushedimage="UI\statusbar\locate_down.png" />
<Button width="31" height="30" padding="40,0,0,0" normalimage="UI\statusbar\search_normal.png" hotimage="UI\statusbar\search_hover.png" pushedimage="UI\statusbar\search_down.png" />
<Button width="31" height="30" padding="40,0,0,0" normalimage="UI\statusbar\setting_normal.png" hotimage="UI\statusbar\setting_hover.png" pushedimage="UI\statusbar\setting_down.png" name="MusicItem" menu="true" />
<Control /><!-- 佔位 -->
<Label name="lbl_Main_Bottom_Info" text="Redrain仿酷狗音樂盒^_^~~ QQ:491646717 2014.9.9" textpadding="0,2,0,0" width="30" font="0" />
<Control width="7" height="7" padding="40,19,0,0" bkimage="UI\sizebox.png" />
</HorizontalLayout>
可以看到,如果按照正常佈局來講,因爲有倒數第三個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<CControlUI*>(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<CControlUI*>(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