書接上回的Windows phone UI虛擬化和數據虛擬化(一)我們學習了wp的ui虛擬化。
今天來和大家分享一下wp的數據虛擬化。
並同時感謝我的同事dgwutao在編寫此文時給我的巨大幫助,3ks!
1.什么是數據虛擬化及其優點。
--弱水三千,只取一瓢飲。百萬記錄,只載十幾條。
和ui虛擬化一樣,盡管我們要顯示的數據有成百上千條,但我們只在內存中,加載我們需要展示在屏幕上的
若干條。隨着列表的滑動,我們添加將要顯示在屏幕上的數據條目,刪除已經不再展示區的條目。保證內存中
加載的數據條目只有很少的一部分(具體數目根據情況定制)。
虛擬化的優點也顯而易見了,有效地節省內存。
2.怎么實現數據虛擬化
不知道各位看到這里,心里會不會說,通過上面的描述,這個,這個,不就是【點擊】/【滑動】 加載更多嗎?
話說滑動加載真心弱爆了。。。也不要糾結怎么判斷觸底沒觸底了。
來!開始我們的數據虛擬化!
接着用上一篇的Demo,我們來看一個基本的列表數據綁定。
【.xaml】
<phone:LongListSelector ItemsSource="{Binding ListProduct}"
【.cs】
這個是,我們通常的用法。沒有任何問題。但是,我們要實現我們想要的只加載屏幕上顯示的一頁(9條)數據。
該如何着手呢?保持List只有9條很容易,但是當list只有9條恰好一頁的時候,我們知道ListBox,LLS的滾動條。
根本就不會滾動。因為列表控件認為你的數據源不是1000條,而是9條。
那么列表控件是根據什么來判斷數據源的總數呢?
開始思考。。。3 。。。 2。。。。。1。。。對,答案就是集合的【Count】屬性。
所以我們第一步要做的就是,騙過這些列表控件。給他們一個"假的" 【Count】屬性
下面我們定義一個泛型類,繼承Ilist<T>等幾個接口。
public class VirtualizingCollection<T>:IList<T>,IList, INotifyPropertyChanged, INotifyCollectionChanged
{
public VirtualizingCollection(int count)
{
this.count = count;
}
public int Count
{
get { return count; }
set
{
count = value;
RaisePropertyChanged("Count");
}
}
public T this[int index]
{
get
{
Return null;
}
set
{
throw new NotImplementedException();
}
}
//----省略-----
}
So Easy! 我們只要在VirtualizingCollection初始化的時候給Count付個1000,就輕松的騙過那些傻傻的列表控件,可以盡情的滾動了。
盡管能刷刷的滾動了,但是我們發現,界面上一片空白,沒有任何數據。為啥呢?
控件在綁定后,遍歷數據源時。我們的索引 public T this[int index]還沒有實現!肯定是不會返回任何數據的。
這塊索引的實現並不是固定的實現方式,要根據具體的場景來看。
好,現在我們來假設這樣一個qq聊天的場景。
在進入qq會話頁面的時候會從本地數據庫里讀取聊天的歷史記錄。共有1000條記錄。每頁顯示9條。
這里我們可以按照傳統的分頁方式來加載。
在上面的類里,我們新建一個字典字段。
private readonly Dictionary<int, IList<T>> _pages = new Dictionary<int, IList<T>>();
這個字典就是我們所說的,對應屏幕上顯示的若干條數據(此例中我們保持每個時刻,內存中只有3-5屏數據)
Dictionary<int, IList<T>> int就是pageindex 頁碼, IList<T> 就是這一頁里的pagesize條數據(本例是9條)
下面是具體的代碼
private readonly int _pageSize = 9;
public T this[int index]
{
get
{
int pageIndex = index / PageSize;//請求的item的索引(index)所在的頁碼
int pageOffset = index % PageSize;//滑動列表當前頁產生的偏移
RequestPage(pageIndex);
//大於一屏的一半加載下一頁
if (pageOffset > PageSize / 2 && pageIndex < Count / PageSize)
{
RequestPage(pageIndex + 1);
}
// 小於一屏的一半加載上一頁
if (pageOffset < PageSize / 2 && pageIndex > 0)
{
RequestPage(pageIndex - 1);
}
//只保留,當前頁和上下兩頁,刪除多余的
if (_pages.Count >= 3 && _pages.ContainsKey(pageIndex - 2))
{
_pages.Remove(pageIndex - 2);
}
if (_pages.Count > 3 && _pages.ContainsKey(pageIndex + 2))
{
_pages.Remove(pageIndex + 2);
}
return _pages[pageIndex][pageOffset];
}
}
protected virtual void RequestPage(int pageIndex)
{
if (!_pages.ContainsKey(pageIndex))
{
_pages.Add(pageIndex, null);
LoadPage(pageIndex);
}
}
protected virtual void LoadPage(int pageIndex)
{
PopulatePage(pageIndex, FetchPage(pageIndex));
}
protected virtual void PopulatePage(int pageIndex, IList<T> page)
{
if (_pages.ContainsKey(pageIndex))
_pages[pageIndex] = page;
}
protected IList<T> FetchPage(int pageIndex)
{ //這里是從數據庫里取,列表請求的頁碼對應的數據
return (IList<T>)LocalData.FetchRange(pageIndex * PageSize, PageSize);
}
LocalDatd.cs
public static class LocalData
{
private static Random rnd = new Random();
private static List<Product> listProduct;
static LocalData()
{ //假設這里是存在本地數據庫里的聊天記錄的信息有1000條
listProduct = new List<Product>(1000);
for (int i = 0; i < 1000; i++)
{
listProduct.Add(new Product { Id = i, Name = "聊天記錄-" + rnd.Next(1000, 10000).ToString(), Category = "" });
}
}
//分頁取庫里的聊天記錄(模擬)
public static IList<Product> FetchRange(int skip, int take)
{
return listProduct.Skip(skip).Take(take).ToList();
}
}
在viewmodel中初始化
private VirtualizingCollection<Product> vList;
public VirtualizingCollection<Product> VList
{
get { return vList; }
set { vList = value; }
}
private static Random rnd=new Random ();
public MainPageViewModel()
{
vList = new VirtualizingCollection<Product>(1000);
}
我們運行,上下滑動,看一下效果s
我們看到內存中加載的頁數始終是3-5頁。而且滾動條已經顯示的是總數為1000時的高度。
這就是所謂的數據虛擬化了。
比起點擊加載,滑動加載,以數據虛擬化的方式,內存占用更少。(滑動加載數據源是不斷被add的並沒有delete)
另一點在用戶體驗上,滾動條的變化也給了用戶預期,列表到底有多少數據。
當然我這里只是演示性的demo。具體應用還有很多需要注意的地方。比如說如果數據來源於網絡,從磁盤讀取,較慢的時候。
就需要一些異步的的讀取處理。在實現索引器的時候我們這里是根據固定的頁數移除,還可以根據最近的加載時間判斷哪些頁面要移除等等。
最后我還要留個小小的問題。public class VirtualizingCollection<T>:IList<T>,IList在這里我實現了兩個很相近的接口。
誰能告訴我這么做是為啥?請留言回答:) 回答正確的,月薪15k以下的,你們老板對不起你!
最后的吶喊!所有關注/從事Windows Phone平台的道友,能不能在android,ios大行其道,wp淪為小平台的無奈下,
別再敝帚自珍!一起為wp平台多奉獻一些自己的經驗,多分享一些優質的資源。
能不能在您看完文章的此刻,順便給這些同在wp平台掙扎的道友,輕輕的,點一下【推薦】!!!
demo下載