最近好久沒有寫博客了,今天分享一下關於工作中遇到的關於Visibility變化而引起的布局變化的問題。
先以一個小例子開場,需求: 新浪微博 添加賬號界面中需要有“登陸”按鈕,當用戶點擊此按鈕后,需要把按鈕換成ProgressRing。如果登陸不成功,彈出錯誤信息並把ProgressRing替換成“登陸”按鈕。
如下圖:



在按鈕與ProgressRing切換的時候我們的一般做法是兩種:
1. 用兩個Visibility屬性隱藏其中按鈕,然后再顯示;
2. 刪除按鈕、添加ProgressRing;刪除ProgressRing,添加按鈕;
第二種比較浪費資源,所以大家都把第一種作為較為常用的方法,即: 用Visibility屬性來控制某些UI元素的隱藏與否,特別是需要重復切換的。
現在切入正題,先上圖:

想必玩過Win8開發的童鞋們一定不陌生,這是在VS中創建GridView模版的應用,啟動后的首頁面。
需求是:點擊某一項,使其從界面中消失,但是布局不變,即:如果我任意刪除一個單元,后面的單元會補充到前面去,中間不會有空白空間。
乍一看跟上面例子的需求類似,可以在數據集的單元數據中添加個Visibility類型的屬性,通過綁定到ItemTemplate中的最外層布局控件上來實現隱藏。但是你真的這么做了,卻發現結果是這樣的:

Wow! 為什么? 如果你看看GridView的代碼,並試圖修改點內容,或許會有所發現:
<GridView
x:Name="itemGridView"
AutomationProperties.AutomationId="ItemGridView"
AutomationProperties.Name="Grouped Items"
Grid.RowSpan="2"
Padding="116,137,40,46"
ItemsSource="{Binding Source={StaticResource groupedItemsViewSource}}"
ItemTemplate="{StaticResource Standard250x250ItemTemplate}"
SelectionMode="None"
IsSwipeEnabled="false"
IsItemClickEnabled="True"
ItemClick="ItemView_ItemClick">
<GridView.ItemsPanel>
<ItemsPanelTemplate>
<VirtualizingStackPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</GridView.ItemsPanel>
<GridView.GroupStyle>
<GroupStyle>
<GroupStyle.HeaderTemplate>
<DataTemplate>
<Grid Margin="1,0,0,6">
<Button
AutomationProperties.Name="Group Title"
Click="Header_Click"
Style="{StaticResource TextPrimaryButtonStyle}" >
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Title}" Margin="3,-7,10,10" Style="{StaticResource GroupHeaderTextStyle}" />
<TextBlock Text="{StaticResource ChevronGlyph}" FontFamily="Segoe UI Symbol" Margin="0,-7,0,10" Style="{StaticResource GroupHeaderTextStyle}"/>
</StackPanel>
</Button>
</Grid>
</DataTemplate>
</GroupStyle.HeaderTemplate>
<GroupStyle.Panel>
<ItemsPanelTemplate>
<VariableSizedWrapGrid Orientation="Vertical" Margin="0,0,80,0"/>
</ItemsPanelTemplate>
</GroupStyle.Panel>
</GroupStyle>
</GridView.GroupStyle>
</GridView>
關鍵因素1:<GroupStyle.Panel>中的 VariableSizedWrapGrid
如果把<GroupStyle.Panel>中的 VariableSizedWrapGrid替換成<StackPanel Orientation="Horizontal" Margin="0,0,80,0"/>,你會發現效果變成了這樣:

Group Title 1 中的 Item Titile 2的項被刪除掉了以后,后面的元素都向前移動了,證明VariableSizedWrapGrid控件內部對Visibility變化導致的布局變化的支持度不是很好。(你注意到在ItemTitile2項被隱藏后,為什么中間的空間會比較大,不像其他元素一樣? 這個留給大家去研究吧,賣個關子。。。)
關鍵因素2:WrapGrid
或許你的應用中不需要分組,也會出現類似的情況,那可能是這樣的:
<GridView
x:Name="itemGridView"
AutomationProperties.AutomationId="ItemGridView"
AutomationProperties.Name="Grouped Items"
Grid.RowSpan="2"
Padding="116,137,40,46"
ItemsSource="{Binding Source={StaticResource groupedItemsViewSource}}"
ItemTemplate="{StaticResource Standard250x250ItemTemplate}"
SelectionMode="None"
IsSwipeEnabled="false"
IsItemClickEnabled="True"
ItemClick="ItemView_ItemClick">
<GridView.ItemsPanel>
<ItemsPanelTemplate>
<WrapGrid Orientation="Vertical"/>
</ItemsPanelTemplate>
</GridView.ItemsPanel>
</GridView>

同樣的,隱藏某一項后,后面的元素並不會填充被隱藏元素的空間,而只是隱藏了那一項,剩下的位置不動。。。

那你試試把WrapGrid換成 VirtualizingStackPanel,問題解決了。

你或許在問,如果這樣改,空白的空間是解決了,但是我們的整體布局也被打亂了。。。
沒錯,其實這些都只是下下策,更多的是為了告訴大家,這幾個控件對Visibility屬性變化而引起的布局變化的支持程度,而相比較之下,或許從數據源中刪除數據才是更好的解決方案,即解決了空白空間的問題,也解決了布局被打亂的問題。
