在MonoTouch中自定義表格
為什么要定制表格?
表格在很多iPhone應用程序中都是必需的UI元素。雖然對于應用程序開發而言,這并非是一項新發明,鑒于設備尺寸等方面的限制,表格在iPhone中的功能是非常固定的。
蘋果在其SDK中,直接內置了很多風格來讓你定制表格。不過,在你最初創建表格的時候,它看起來非常簡單。在沒有進行任何定制的時候,你可以為表格選擇兩種基本風格,默認風格和分組風格:
在對表格中的單元格進行一點調整后,你就可以添加圖標和說明文字:
你甚至能改變單元格的字體和顏色,然而,有時候這樣還是不足夠。如果你真的想完全改變基本的風格,創建一個復雜的UI,那么你必須創建自己的自定義單元格控件。下面的截圖是定制后的效果,這個應用程序使用完全定制的單元格來顯示內容:
幸好,蘋果讓我們有便捷的方法來完成這樣的定制,就是允許我們創建自己的定制UITableViewCell 控件,并把其用于UITableView 控件中。下面,讓我們來逐步創建一個應用程序,學習如何利用UITableView 和UITableViewCell 控件來創建上圖所示效果的例子。
示例應用程序
那么接下來,我們就來創建應用程序。首先,打開MonoDevelop,如下圖所示:
接著,新建一個iPhone MonoTouch項目。從;File菜單中,選擇;New Soluton:
從C# iPhone 模板中選擇;iPhone Window-based Project,輸入解決方案的名稱;Example_CustomUITableViewCells:
在項目特性對話框上點擊;OK,這些內容和我們要做的應用程序無關:
我們的項目現在創建好了,如下圖所示:
通過在Solution Explorer窗口里雙擊MainWindow.xib,以便在Interface Builder中打開它,之后你會看到如下內容:
我們首先拖一個UITableView控件到我們的MainWindow中,效果如下:
我們需要在AppDelegate類中添加一個outlet,以便我們能編程訪問這個Table View。
在Library窗口中,選擇頂部的;Classes標簽頁,接著在下拉列表中選擇;Other Classes。確保AppDelegate已經選中,在Library窗口的下半部點擊;Outlets標簽頁:
讓我們來創建一個名為;tblMain的outlet,通過點擊;+按鈕,在名稱列輸入;tblMain,你的outlet應該類似下圖的:
接著,把outlet關聯到我們之前添加的UITableView上。在Document窗口中選擇AppDelegate,在Connections Inspector窗口中,就會看到新創建的tblMain outlet:
從連接管理器中把右側的outlet的圓點拖動到Document窗口或Window Designer的表格上,就可以把它們關聯到一起:
現在,我們得到了一個關聯過的表格,讓我們回到MonoDevelop,編寫一些代碼。
在項目上按右鍵,在上下文菜單中選擇;Add,接著選擇;New Folder,把文件夾命名為;Code;。
接著創建一個命名為;BasicTableViewItem.cs的類。即,在項目上再次按右鍵,在上下文菜單中選擇;Add,接著選擇;New File。命名類,并點擊;New:
在這個代碼文件中,輸入如下代碼:
{
//==================================
/// <summary>
/// Represents our item in the table
/// </summary>
public class BasicTableViewItem
{
public string Name { get; set; }
public string SubHeading { get; set; }
public string ImageName { get; set; }
public BasicTableViewItem ()
{
}
}
//====================================
}
這個類相當簡單,它表示了一個在單元格中的項目,并具有如下屬性:
- Name 這個項目所顯示的文本
- SubHeading 顯示在項目名稱下面的文本
- ImageName 這個項目所顯示的圖片名稱
我們將在后面會看到如何使用這個類。
接著,在;Code文件夾中,創建另外一個類,命名為;BasicTableViewItemGroup,并輸入如下代碼:
using System.Collections.Generic;
namespace Example_CustomUITableViewCells
{
//========================================================================
/// <summary>
/// A group that contains table items
/// </summary>
public class BasicTableViewItemGroup
{
public string Name { get; set; }
public string Footer { get; set; }
public List<BasicTableViewItem> Items
{
get { return this._items; }
set { this._items = value; }
}
protected List<BasicTableViewItem> _items = new List<BasicTableViewItem>();
public BasicTableViewItemGroup ()
{
}
}
//========================================================================
}
這也是一個相當簡單的類。它用于項目分組的容器。具有如下屬性:
- Name 分組的名稱。通常顯示在分組的頂部。
- Footer 顯示在當前組中所有項目的底部文字
- Items 分組所包含的BasicTableViewItem的List<>集合。我們在其構造的時候實例化這個集合,以便在添加項目的時候無需手動去實例化它。
接著,再在同一個文件夾中創建另外一個類,命名為;BasicTableViewSource,并輸入如下代碼:
using System.Collections.Generic;
using MonoTouch.UIKit;
namespace Example_CustomUITableViewCells
{
//========================================================================
/// <summary>
/// Combined DataSource and Delegate for our UITableView
/// </summary>
public class BasicTableViewSource : UITableViewSource
{
//---- declare vars
protected List<BasicTableViewItemGroup> _tableItems;
string _cellIdentifier = "BasicTableViewCell";
public BasicTableViewSource (List<BasicTableViewItemGroup> items)
{
this._tableItems = items;
}
/// <summary>
/// Called by the TableView to determine how many sections(groups) there are.
/// </summary>
public override int NumberOfSections (UITableView tableView)
{
return this._tableItems.Count;
}
/// <summary>
/// Called by the TableView to determine how many cells to create for that particular section.
/// </summary>
public override int RowsInSection (UITableView tableview, int section)
{
return this._tableItems[section].Items.Count;
}
/// <summary>
/// Called by the TableView to retrieve the header text for the particular section(group)
/// </summary>
public override string TitleForHeader (UITableView tableView, int section)
{
return this._tableItems[section].Name;
}
/// <summary>
/// Called by the TableView to retrieve the footer text for the particular section(group)
/// </summary>
public override string TitleForFooter (UITableView tableView, int section)
{
return this._tableItems[section].Footer;
}
/// <summary>
/// Called by the TableView to get the actual UITableViewCell to render for the particular section and row
/// </summary>
public override UITableViewCell GetCell (UITableView tableView, MonoTouch.FoundationNSIndexPath indexPath)
{
//---- declare vars
UITableViewCell cell = tableView.DequeueReusableCell (this._cellIdentifier);
//---- if there are no cells to reuse, create a new one
if (cell == null)
{
cell = new UITableViewCell (UITableViewCellStyle.Default, this._cellIdentifier);
}
//---- create a shortcut to our item
BasicTableViewItem item = this._tableItems[indexPath.Section].Items[indexPath.Row];
cell.TextLabel.Text = item.Name;
return cell;
}
}
//========================================================================
}
UITableViewSource 類負責處理我們數據的綁定,也處理用戶和表格的交互。在我們的例子中,處理用戶交互超出本篇文章的范圍,不過我們依然需要完成數據綁定的功能。
這個類比其他兩個稍微有點復雜,它的成員逐一說明如下:
- 變量聲明我們聲明了兩個變量,_tableItems,用于獲取表格對象的本地變量,_cellIdentifier,在后面在重用表格單元格的時候提供一個關鍵字。
- 構造器構造器接受一個名為items 的參數,其類型是List<BasicTableViewItemGroup>。在我們的例子中,我們強制類和項目分組一起實例化,以便我們確信他們都是有效的。
- NumberOfSections 這個方法用于獲取在表格中創建的分組數目。在我們的例子中,就返回_tableItems的分組數目。
- RowsInSection 這個方法用于獲取表格中特定分組的項目數目。根據特定的分組索引,返回其包含項目的數量。
- TitleForHeader 這個方法返回某個分組標頭的文本。就是返回當前分組對象BasicTableViewItemGroup 的Name 屬性。
- TitleForFooter 這個方法返回某個分組標腳的文本。類似TitleForHeader,不過這次是返回Footer屬性。
- GetCell 這個方法根據特定分組和行返回實際的UITableCell 控件,這也是數據綁定大量工作發生的地方。首先,我們在相關的UITableView上調用DequeueReusableCell 。這樣做主要是因為性能原因。因為iPhone限制了處理器的耗能,如果每次顯示都必須創建新的UITableViewCell 的話,在遇到很長列表的時候可能會變得非常慢。為了解決這個問題,UITableView 控件中后臺維護著一個UITableViewCell 控件緩存池,在單元格滾動出可視范圍的時候,它會放入到這個緩存池中以備重用。這也就是_cellIdentifier變量的用武之處。
好,現在我們編寫好了相關的類,就要開始添加一些圖片,以便可以在項目上顯示圖標。
首先,在項目中類似之前創建;Code文件夾那樣,創建一個名為;Images的文件夾。把如下的圖片按照對應的名稱逐一保存到你的硬盤上:
圖片 |
名稱 |
|
PawIcon.png |
|
LightBulbIcon.png |
|
PushPinIcon.png |
|
TargetIcon.png |
一旦保存好,就要把他們添加到項目中。在;Images文件夾上點右鍵,選擇;Add Files。選擇你保存好的圖片,勾中;Override default build action,在下拉列表中選擇;Content。這很重要。Content是讓你在運行時可以加載并使用它們的唯一構建動作。
你的解決方案現在應該如下所示:
為了確保你之前的步驟都是正確的,可以通過編譯項目來檢驗。要進行編譯,按住Apple Key(蘋果鍵),再按;B鍵,或者從;Build菜單中選擇Build All;。
一切正常的話,我們就準備好了相關的類和圖片。讓我們來編輯應用程序代碼以使用它們!
在Main.cs上雙擊,顯示出代碼編輯器。在AppDelegate類中,添加如下代碼行:
This is a class variable to hold our BasicTableViewSource that we created earlier.
這是一個類變量,用來保存我們早期創建的BasicTableViewSource 實例。
接著,把如下方法復制到AppDelegate類中:
/// Creates a set of table items.
/// </summary>
protected void CreateTableItems ()
{
List<BasicTableViewItemGroup> tableItems = new List<BasicTableViewItemGroup> ();
//---- declare vars
BasicTableViewItemGroup tGroup;
//---- birds
tGroup = new BasicTableViewItemGroup() { Name = "Birds", Footer = "Birds have wings, and sometimes use them." };
tGroup.Items.Add (new BasicTableViewItem() { Name = "Crow", SubHeading = "AKA, Raven.", ImageName = "PawIcon.png" });
tGroup.Items.Add (new BasicTableViewItem() { Name = "Chicken", SubHeading = "Males are called roosters.", ImageName = "PushPinIcon.png" });
tGroup.Items.Add (new BasicTableViewItem() { Name = "Turkey", SubHeading = "Eaten at thanksgiving.", ImageName = "LightBulbIcon.png" });
tableItems.Add (tGroup);
//---- fish
tGroup = new BasicTableViewItemGroup() { Name = "Fish", Footer = "Fish live in water. Mostly." };
tGroup.Items.Add (new BasicTableViewItem() { Name = "Trout", SubHeading = "Rainbow is a popular kind.", ImageName = "TargetIcon.png" });
tGroup.Items.Add (new BasicTableViewItem() { Name = "Salmon", SubHeading = "Good sushi.", ImageName = "PawIcon.png" });
tGroup.Items.Add (new BasicTableViewItem() { Name = "Cod", SubHeading = "Flat fish.", ImageName = "LightBulbIcon.png" });
tableItems.Add (tGroup);
//---- mammals
tGroup = new BasicTableViewItemGroup() { Name = "Mammals", Footer = "Mammals nurse their young." };
tGroup.Items.Add (new BasicTableViewItem() { Name = "Deer", SubHeading = "Bambi.", ImageName = "PushPinIcon.png" });
tGroup.Items.Add (new BasicTableViewItem() { Name = "Bats", SubHeading = "Fly at night.", ImageName = "TargetIcon.png" });
tableItems.Add (tGroup);
this._tableViewSource = new BasicTableViewSource(tableItems);
}
我們將調用這個方法來創建數據源,完成在表格中顯示數據的過程。
最終,在AppDelegate 類中的FinishedLaunching 方法中,在調用window.MakeKeyAndVisible 之前加入如下代碼。
this.tblMain.Source = this._tableViewSource;
通過調用這個方法,我們就創建了一個表格數據源,并把數據源賦值給表格。
最終的Main.cs應該像這樣:
using System.Collections.Generic;
using System.Linq;
using MonoTouch.Foundation;
using MonoTouch.UIKit;
namespace Example_CustomUITableViewCells
{
public class Application
{
static void Main (string[] args)
{
UIApplication.Main (args);
}
}
// The name AppDelegate is referenced in the MainWindow.xib file.
public partial class AppDelegate : UIApplicationDelegate
{
BasicTableViewSource _tableViewSource;
// This method is invoked when the application has loaded its UI and its ready to run
public override bool FinishedLaunching (UIApplication app, NSDictionary options)
{
this.CreateTableItems ();
this.tblMain.Source = this._tableViewSource;
// If you have defined a view, add it here:
// window.AddSubview (navigationController.View);
window.MakeKeyAndVisible ();
return true;
}
// This method is required in iPhoneOS 3.0
public override void OnActivated (UIApplication application)
{
}
/// <summary>
/// Creates a set of table items.
/// </summary>
protected void CreateTableItems ()
{
List<BasicTableViewItemGroup> tableItems = new List<BasicTableViewItemGroup> ();
//---- declare vars
BasicTableViewItemGroup tGroup;
//---- birds
tGroup = new BasicTableViewItemGroup() { Name = "Birds", Footer = "Birds have wings, and sometimes use them." };
tGroup.Items.Add (new BasicTableViewItem() { Name = "Crow", SubHeading = "AKA, Raven.", ImageName = "PawIcon.png" });
tGroup.Items.Add (new BasicTableViewItem() { Name = "Chicken", SubHeading = "Males are called roosters.", ImageName = "PushPinIcon.png" });
tGroup.Items.Add (new BasicTableViewItem() { Name = "Turkey", SubHeading = "Eaten at thanksgiving.", ImageName = "LightBulbIcon.png" });
tableItems.Add (tGroup);
//---- fish
tGroup = new BasicTableViewItemGroup() { Name = "Fish", Footer = "Fish live in water. Mostly." };
tGroup.Items.Add (new BasicTableViewItem() { Name = "Trout", SubHeading = "Rainbow is a popular kind.", ImageName = "TargetIcon.png" });
tGroup.Items.Add (new BasicTableViewItem() { Name = "Salmon", SubHeading = "Good sushi.", ImageName = "PawIcon.png" });
tGroup.Items.Add (new BasicTableViewItem() { Name = "Cod", SubHeading = "Flat fish.", ImageName = "LightBulbIcon.png" });
tableItems.Add (tGroup);
//---- mammals
tGroup = new BasicTableViewItemGroup() { Name = "Mammals", Footer = "Mammals nurse their young." };
tGroup.Items.Add (new BasicTableViewItem() { Name = "Deer", SubHeading = "Bambi.", ImageName = "PushPinIcon.png" });
tGroup.Items.Add (new BasicTableViewItem() { Name = "Bats", SubHeading = "Fly at night.", ImageName = "TargetIcon.png" });
tableItems.Add (tGroup);
this._tableViewSource = new BasicTableViewSource(tableItems);
}
}
}
讓我們來運行下應用程序,看看什么樣子。要運行應用程序,按住蘋果鍵,并按Enter按鈕,或者從Run菜單中選擇Debug。
當我們運行它之后,就可以看到下圖所示效果:
腳標看上去稍顯怪異。讓我們來把表格的類型改為Grouped 后重新運行一下。要這樣做,需要在Interface Builder中打開MainWindow.xib ,然后在Document窗口上點擊Table View來編輯表格風格,接著在Inspector窗口,通過風格下拉列表來改變為;Grouped:
保存文件,切換回MonoDevelop,再次運行應用程序,你就可以看到如下效果了:
現在腳標就看著順眼多了。
讓我們再來處理其他地方。在項目中要顯示一些圖片和子標題,那么下面就要著手編寫。
停止應用程序,接著雙擊BasicTableViewSource.cs 類來編輯它。在GetCell method方法中把如下代碼行:
改為:
這樣就把UITableViewCell 的風格改變為Subtitle ,其會在單元格中顯示文本的第二行。
你能選用的只有很少的風格。然而要注意,只有在Default 和Subtitle 風格中才支持圖片。
現在,在這行之后:
增加:
cell.DetailTextLabel.Text = item.SubHeading;
if(!string.IsNullOrEmpty(item.ImageName))
{
cell.ImageView.Image = UIImage.FromFile("Images/" + item.ImageName );
}
UITableViewCells 的原來樣子就會被改變為如下所示:
UITableViewCell 控件原本帶有兩個主要部分,單元格主體區,其是單元格顯示內容的全部區域;單元格內容區,其通常用來放置內容在里面。如果附加區未使用的話,單元格內容區就會擴展占滿整個單元格主體區。類似地,如果圖片區不使用的話,標簽區會覆蓋圖片的地方。
讓我們來看 一下如何在代碼中使用這些區域。第一行代碼告訴單元格,右邊會有一個Checkmark 。接著我們添加SubHeading 文本到DetailTextLabel 上。最后,我們在單元格上設置一個圖片。
很容易就可以通過文件來創建圖片對象。簡單地使用UIImage.FromFile構造函數,把圖片文件的路徑傳遞就去。由于之前把圖片文件的編譯類型設為Content ,只要項目中的文件夾結構就是文件系統的,那么就可以容易地訪問它們。
現在運行應用程序,就得到下面的效果:
非常好。如果在Interface Builder 中把表格風格改回Plain(雙擊打開MainWindow.xib ,選擇表格,在下拉列表中選擇風格為;Plain,并保存),那么再次運行,就得到:
還是很漂亮的。
在iPhone OS 3.0之后,我們也能定義一些其他屬性,包括TextColor、SelectedTextColor、SelectedImage、BackgroundView、SelectedBackgroundView等等。這雖然讓你有了更多的個性化設置,不過有時候,我們需要完全不同的顯示效果。
要完成這個工作,必須創建自己的表格單元格。蘋果允許我們通過繼承UITableViewCell ,并創建其中我們所想的任何內容。
那么,讓我們來創建自定義的表格樣式。首先,在項目中創建一個名為;Controls的新文件夾。在其中,我們打算用View Controller來創建新視圖。在Controls文件夾上點右鍵,選擇;Add,接著;New File,從左欄選擇;iPhone并在右邊選擇;View Interface Definition with Controller,并命名為;CustomTableViewCell:
在Interface Builder打開CustomTableViewCell.xib 。就會顯示空白視圖:
我們不想使用標準視圖,而要創建一個UITableViewCell,所以我們會刪除這個視圖并用一個單元格對象來替換它。
按住蘋果鍵(Apple),按退格鍵,或者從編輯窗口中選擇;Delete就可以從Document窗口中刪除這個視圖。
接著,從Library窗口中拖一個Table View Cell到你的Document窗口中:
如果你在新添加到表格單元格上雙擊,就會打開設計器,你會看到一個設計界面來讓你添加控件:
那么,現在讓我們來使用Interface Builder來設計自定義單元格。拖動右下角的讓單元格變高點。接著,從Library窗口,拖一個UIImageView 控件和幾個UILabels控件:
你可以使用Attributes Inspector 窗口來改變UILabel 控件的大小,以及顏色等等屬性。不用看上去完全和上圖一樣,只需相關的元素都在對應位置就行。
現在,我們完成了自定義的設計,就來為自定義單元格添加outlet,和File’s Owner的內容,以便我們能訪問它們。之前,我們把outlet直接添加到AppDelegate中,不過這里,要把他們添加到File’s Owner中,以便可以在所具有的視圖上創建類。
在Document窗口中選擇;File’s Owner,接著在Library窗口中在頂部選擇;Classes,在下拉列表中選擇;Other Classes。選擇我們的CustomTableViewCell 類,接著在窗口的下半部選擇;Outlets標簽頁。添加名為;cellMain、;lblHeading、;lblSubHeading和;imgMain:的Outlets:
把Outlets和我們之前添加的控件關聯起來,就像對表格控件所做的那樣。
確保Document窗口的;File’s Owner;是被選中的,從Connection Inspector中把圓點拖到Document窗口的控件上:
最后一件我們必須在Interface Builder中完成的事情,就是提供一個單元格標識。在Document窗口上點擊Table View Cell;,接著在Attributes Inspector上把Identifier 屬性設置為MyCustomTableCellView;:
這讓我們的自定義單元格模板可以被重用,正如之前我們在代碼中實現的那樣。
我們已經完成了Interface Builder中事情了,那么保存文件,返回到MonoDevelop中。雙擊CustomTableCellView.xib.cs 打開這個類文件。
我們要對這個類進行一點編輯。首先,我們來添加一些屬性到類里面:
{
get { return this.cellMain; }
}
public string Heading
{
get { return this.lblHeading.Text; }
set { this.lblHeading.Text = value; }
}
public string SubHeading
{
get { return this.lblSubHeading.Text; }
set { this.lblSubHeading.Text = value; }
}
public UIImage Image
{
get { return this.imgMain.Image; }
set { this.imgMain.Image = value; }
}
我們來看一下這些屬性:
- Cell 這個屬性讓這個自定義單元格的調用者可以直接訪問單元格本身,以便在UITableViewSource的GetCell 調用中,我們能直接從一個屬性簡單地返回,過一會我們會看到怎么弄。
- Heading 和SubHeading 這個屬性暴露了可以直接訪問表格單元格中UILabel 控件的文本值。
- Image 這個屬性直接暴露表格單元格中的UIImage 控件。
接下來,我們需要編輯其中一個構造器。文件在被MonoDevelop創建好后,文件中的最后一個構造器看起來如下:
{
Initialize ();
}
這是一個非常簡單的構造器,如果你讀過Mono文檔關于基類構造說明的話,就會明白這里它會初始化這個類,并用我們傳遞進去的名稱來加載Nib(編譯好的.xib文件)文件。然而,文檔中沒有提到的是,基類構造器是異步的。如果你使用這種方式來調用,那么Nib好像未被立刻加載,在我們給自定義單元格設置諸如Heading屬性的時候,我們會得到討厭的空引用錯誤。
為了修正這個問題,我們必須確保Nib文件同步地加載,即直到加載完成,方法是不會返回。
那么,就需要編輯構造器,以便看起來如下:
{
//---- this next line forces the loading of the xib file to be synchronous
MonoTouch.Foundation.NSBundle.MainBundle.LoadNib ("CustomTableViewCell", this, null);
Initialize ();
}
在這里,我們首先要做到就是,把調用基類構造器的代碼注釋掉。我們會手動地進行來調用那個代碼,所以在這里不用再調一次。接下來,我們添加一個對LoadNib方法的調用。它實際上和基類構造器所做的事情是一樣的,除了不會強制進行異步處理。現在,我們確保了Nib以及任何東西都能初始化,以便我們能訪問它。
完整的類看上去如下所示:
using MonoTouch.Foundation;
using MonoTouch.UIKit;
using System;
namespace Example_CustomUITableViewCells
{
//========================================================================
public partial class CustomTableViewCell : UIViewController
{
#region Constructors
// The IntPtr and initWithCoder constructors are required for controllers that need
// to be able to be created from a xib rather than from managed code
public CustomTableViewCell (IntPtr handle) : base(handle)
{
Initialize ();
}
[Export("initWithCoder:")]
public CustomTableViewCell (NSCoder coder) : base(coder)
{
Initialize ();
}
public CustomTableViewCell ()// : base("CustomTableViewCell", null)
{
//---- this next line forces the loading of the xib file to be synchronous
MonoTouch.Foundation.NSBundle.MainBundle.LoadNib ("CustomTableViewCell", this, null);
Initialize ();
}
void Initialize ()
{
}
#endregion
public UITableViewCell Cell
{
get { return this.cellMain; }
}
public string Heading
{
get { return this.lblHeading.Text; }
set { this.lblHeading.Text = value; }
}
public string SubHeading
{
get { return this.lblSubHeading.Text; }
set { this.lblSubHeading.Text = value; }
}
public UIImage Image
{
get { return this.imgMain.Image; }
set { this.imgMain.Image = value; }
}
}
//========================================================================
}
好,現在構建好自定義單元格類了,接著往下走,來創建自定義UITableViewSource 類。
在Code;文件夾中,創建一個新的類,復制如下代碼進去:
using System.Collections.Generic;
using MonoTouch.UIKit;
namespace Example_CustomUITableViewCells
{
//========================================================================
/// <summary>
/// Combined DataSource and Delegate for our UITableView with custom cells
/// </summary>
public class CustomTableViewSource : UITableViewSource
{
//---- declare vars
protected List<BasicTableViewItemGroup> _tableItems;
protected string _customCellIdentifier = "MyCustomTableCellView";
protected Dictionary<int, CustomTableViewCell> _cellControllers =
new Dictionary<int, CustomTableViewCell>();
public CustomTableViewSource (List<BasicTableViewItemGroup> items)
{
this._tableItems = items;
}
/// <summary>
/// Called by the TableView to determine how many sections(groups) there are.
/// </summary>
public override int NumberOfSections (UITableView tableView)
{
return this._tableItems.Count;
}
/// <summary>
/// Called by the TableView to determine how many cells to create for that particular section.
/// </summary>
public override int RowsInSection (UITableView tableview, int section)
{
return this._tableItems[section].Items.Count;
}
/// <summary>
/// Called by the TableView to retrieve the header text for the particular section(group)
/// </summary>
public override string TitleForHeader (UITableView tableView, int section)
{
return this._tableItems[section].Name;
}
/// <summary>
/// Called by the TableView to retrieve the footer text for the particular section(group)
/// </summary>
public override string TitleForFooter (UITableView tableView, int section)
{
return this._tableItems[section].Footer;
}
/// <summary>
/// Called by the TableView to retreive the height of the row for the particular section and row
/// </summary>
public override float GetHeightForRow (UITableView tableView, MonoTouch.FoundationNSIndexPath indexPath)
{
return 109f;
}
/// <summary>
/// Called by the TableView to get the actual UITableViewCell to render for the particular section and row
/// </summary>
public override UITableViewCell GetCell (UITableView tableView, MonoTouch.FoundationNSIndexPath indexPath)
{
//---- declare vars
UITableViewCell cell = tableView.DequeueReusableCell (this._customCellIdentifier);
CustomTableViewCell customCellController = null;
//---- if there are no cells to reuse, create a new one
if (cell == null)
{
customCellController = new CustomTableViewCell ();
// retreive the cell from our custom cell controller
cell = customCellController.Cell;
// give the cell a unique ID, so we can match it up to the controller
cell.Tag = Environment.TickCount;
// store our controller with the unique ID we gave our cell
this._cellControllers.Add (cell.Tag, customCellController);
}
else
{
// retreive our controller via it's unique ID
customCellController = this._cellControllers[cell.Tag];
}
//---- create a shortcut to our item
BasicTableViewItem item = this._tableItems[indexPath.Section].Items[indexPath.Row];
//---- set our cell properties
customCellController.Heading = item.Name;
customCellController.SubHeading = item.SubHeading;
if (!string.IsNullOrEmpty (item.ImageName))
{
customCellController.Image = UIImage.FromFile ("Images/" + item.ImageName);
}
//---- return the custom cell
return cell;
}
}
//========================================================================}
}
如果你瀏覽一遍這個代碼,就看到它幾乎和BasicTableViewSource 一樣,只有少許改變。
要注意到第一件事,是聲明部分:
protected List<BasicTableViewItemGroup> _tableItems;
protected string _customCellIdentifier = "MyCustomTableCellView";
protected Dictionary<int, CustomTableViewCell> _cellControllers =
new Dictionary<int, CustomTableViewCell>();
我們添加了一個名為_cellControllers新變量。我們將在后面看到如何使用它,它會保存著自定義單元格的集合。
下一個增加的東西是GetHeightForRow 方法:
/// Called by the TableView to retreive the height of the row for the particular section and row
/// </summary>
public override float GetHeightForRow (UITableView tableView, MonoTouch.FoundationNSIndexPath indexPath)
{
return 109f;
}
當我們創建自定義單元格控制器之后,我們讓它比通常的單元格要高。在數據綁定的過程中,CocoaTouch 需要知道要給這個單元格分配多少空間。這就是要調用GetHeightForRow 方法的緣由。如果我們不告知新的大小,那么系統會按照標準尺寸去調整布局,我們就會看到怪異的結果。
如果我們在Interface Builder中打開我們的單元格,并通過Size Inspector查看,會知道單元格的高度上多少。在我們的例子中,它是109像素,不過你的可能會有所不同。
最后改變的一塊地方是,GetCell方法:
/// Called by the TableView to get the actual UITableViewCell to render for the particular section and row
/// </summary>
public override UITableViewCell GetCell (UITableView tableView, MonoTouch.FoundationNSIndexPath indexPath)
{
//---- declare vars
UITableViewCell cell = tableView.DequeueReusableCell (this._customCellIdentifier);
CustomTableViewCell customCellController = null;
//---- if there are no cells to reuse, create a new one
if (cell == null)
{
customCellController = new CustomTableViewCell ();
// retreive the cell from our custom cell controller
cell = customCellController.Cell;
// give the cell a unique ID, so we can match it up to the controller
cell.Tag = Environment.TickCount;
// store our controller with the unique ID we gave our cell
this._cellControllers.Add (cell.Tag, customCellController);
}
else
{
// retreive our controller via it's unique ID
customCellController = this._cellControllers[cell.Tag];
}
//---- create a shortcut to our item
BasicTableViewItem item = this._tableItems[indexPath.Section].Items[indexPath.Row];
//---- set our cell properties
customCellController.Heading = item.Name;
customCellController.SubHeading = item.SubHeading;
if (!string.IsNullOrEmpty (item.ImageName))
{
customCellController.Image = UIImage.FromFile ("Images/" + item.ImageName);
}
//---- return the custom cell
return cell;
}
這個方法開始非常類似于BasicTableViewSource 類,我們嘗試通過DequeueReusableCell 的調用來從緩存池中重用的一個單元格。從這之后,就變得復雜了。如果我們不能在重用的時候得到單元格,那么我們必須去創建一個新的。它就是由我們的CustomTableViewCell 類所創建的,它實際上是一個包含著自定義單元格的控制器。
一旦我們創建了控制器,我們就把cell變量賦值控制器的Cell屬性。
我們接著為單元格添加了一個唯一標識符,以便我們在后面能把它和正確的CustomTableViewCell 控制器關聯在一起。我們使用Environment.TickCount ,因為它提供了比較穩妥的唯一性值。如果我們想,也可以使用Guid.NewGuid,不過調用它稍微有點昂貴。
接下來,我們使用相同的tag標識,來創建存儲在CustomTableViewCell 的字典對象。
這也就是我們下面這行代碼顯而易見的原因:
{
// retreive our controller via it's unique ID
customCellController = this._cellControllers[cell.Tag];
}
如果我們找到一個可重用的單元格,那么就需要通過控制器來設置相關屬性。為了這樣,必須使用之前我們使用過的標識符從Dictionary中提取出來。
到這里,已經涉及了對這個類的大部分改變。接下來的幾行,為了改變控制器類中的屬性,進行了相對于單元格本身較簡單的修改。
好了!我們快要完成了。打開Main.cs文件,我們對AppDelegate類進行兩個地方的改變,讓其使用我們的CustomTableViewSource 而非BasicTableViewSource。
首先,注釋掉這行:
并緊接著后添加這行:
接著,在CreateTableItems 方法中,注釋掉這行:
并緊接著后面添加這行:
我們修改后的AppDelegate 類看起來如下:
public partial class AppDelegate : UIApplicationDelegate
{
//BasicTableViewSource _tableViewSource;
CustomTableViewSource _tableViewSource;
// This method is invoked when the application has loaded its UI and its ready to run
public override bool FinishedLaunching (UIApplication app, NSDictionary options)
{
this.CreateTableItems ();
this.tblMain.Source = this._tableViewSource;
// If you have defined a view, add it here:
// window.AddSubview (navigationController.View);
window.MakeKeyAndVisible ();
return true;
}
// This method is required in iPhoneOS 3.0
public override void OnActivated (UIApplication application)
{
}
/// <summary>
/// Creates a set of table items.
/// </summary>
protected void CreateTableItems ()
{
List<BasicTableViewItemGroup> tableItems = new List<BasicTableViewItemGroup> ();
//---- declare vars
BasicTableViewItemGroup tGroup;
//---- birds
tGroup = new BasicTableViewItemGroup() { Name = "Birds", Footer = "Birds have wings, and sometimes use them." };
tGroup.Items.Add (new BasicTableViewItem() { Name = "Crow", SubHeading = "AKA, Raven.", ImageName = "PawIcon.png" });
tGroup.Items.Add (new BasicTableViewItem() { Name = "Chicken", SubHeading = "Males are called roosters.", ImageName = "PushPinIcon.png" });
tGroup.Items.Add (new BasicTableViewItem() { Name = "Turkey", SubHeading = "Eaten at thanksgiving.", ImageName = "LightBulbIcon.png" });
tableItems.Add (tGroup);
//---- fish
tGroup = new BasicTableViewItemGroup() { Name = "Fish", Footer = "Fish live in water. Mostly." };
tGroup.Items.Add (new BasicTableViewItem() { Name = "Trout", SubHeading = "Rainbow is a popular kind.", ImageName = "TargetIcon.png" });
tGroup.Items.Add (new BasicTableViewItem() { Name = "Salmon", SubHeading = "Good sushi.", ImageName = "PawIcon.png" });
tGroup.Items.Add (new BasicTableViewItem() { Name = "Cod", SubHeading = "Flat fish.", ImageName = "LightBulbIcon.png" });
tableItems.Add (tGroup);
//---- mammals
tGroup = new BasicTableViewItemGroup() { Name = "Mammals", Footer = "Mammals nurse their young." };
tGroup.Items.Add (new BasicTableViewItem() { Name = "Deer", SubHeading = "Bambi.", ImageName = "PushPinIcon.png" });
tGroup.Items.Add (new BasicTableViewItem() { Name = "Bats", SubHeading = "Fly at night.", ImageName = "TargetIcon.png" });
tableItems.Add (tGroup);
//this._tableViewSource = new BasicTableViewSource(tableItems);
this._tableViewSource = new CustomTableViewSource(tableItems);
}
}
運行這個應用程序,就能看到下面的效果:
如果你在Interface Builder中把表格風格改為Grouped,保存改變,再次運行程序,你就會看到下圖:
恭喜!做了這么多的工作,終于完成了,不過你也學到了如何在MonoTouch中自定義表格。現在,我們已經讓其運行起來了,來看一下在進行自定義單元格的時候,要考慮到一些事項。
性能考慮
如果沒有正確地處理自定義單元格的開發,在使用UITableView 的時候會造成嚴重的性能問題。特別在你有很多行的時候更是如此。iPhone,雖然令人印象深刻,不過卻只有性能有限的處理器,而為了獲得用戶所期望的較好響應能力,你應該考慮幾個性能優化方式。
另外,盡早部署到設備上經常性地評估自定義表格的性能,是一種可取的做法。模擬器就是一個模擬器而已,并非真正的設備。它比設備運行的要快得多,如果你依賴于模擬器來測量應用程序的性能,那么你很可能會在部署到設備上的時候大失所望。
而且,在你測試自定義表格的時候,如果用戶能添加自己的行,那么你應該測試應用程序有很多行的性能。如果它依然;活蹦亂跳,具有很好的響應,那么你就能確信你已經開發正確,而你的用戶應該不會對性能失望。
- 單元格重用 - 單元格重用是獲得具有良好響應表格的第一個技術。特別在具有大量的行的時候,更是需要。如果你不重用單元格,那么iPhone OS不得不在每個單元格顯示的時候去創建一個新的實例。這馬上就會變成一個嚴重的問題。為了確定你的單元格是否正被重用,建議在單元格重用代碼中使用Console.WriteLine 把重用的過程打印到應用程序控制臺。確保在第一次顯示界面的時候,添加了足夠多的行,以便在滾動屏幕的時候,OS有機會重用老的已經被滾動出屏幕的單元格。本文章的例子就展示了如何正確地重用單元格,如果你的單元格沒有重用,研究一下代碼示例,確保所有實現都是正確的。
- 緩存行高 - 表格會經常性地獲取行的高度。實際上,它會在每次單元格創建的時候都獲取一遍,在某些時候甚至會更頻繁。如果你基于單元格內容來計算行高,那么確保要緩存這個值,以便你不必總是計算它。實際上,你可能會在單元格控制器中創建一個屬性,計算,然后緩存之。
- 緩存圖片 - 如果你正使用圖片,考慮緩存它們,以便不必每次顯示的時候都從文件中載入。如果單元格之間會共享圖片,可以考慮把他們放到一個字典對象中,并從里面來獲取圖片實例。要小心不要加載太多圖片。用到可供你支配的iPhone內存的一半是不太正常的,因此如果你有大量的圖片,你就要做出選擇,是從文件中加載它們,還是在緩存字典中只保留一定數量的圖片,沒有的時候再從文件中加載。
- 遠離透明效果 - 在iPhone上執行的一個非常昂貴的操作就是渲染透明效果。在iPhone上有兩個繪圖系統,CoreGraphics和CoreAnimation。CoreGraphics 利用GPU,而CoreAnimation根據要處理內容的快慢也利用主處理器,不過通常主要使用GPU。iPhone的GPU的問題在于它沒有針對顏色混合進行優化。因此,你應該盡可能嘗試避免透明效果。如果你確實不能避免,那么你應該通過重寫DrawRect 方法來自己完成混合過程,并直接處理繪圖計算,關于這點我們將在下一條中詳細講到。
- 手動在DrawRect中繪制單元格 - 要解決上面提到的性能問題的最后努力,就是重寫DrawRect 方法,自己執行繪圖。然而這也有缺點。首先,在技術上相對復雜,第二,它會占用大量內存。就這點而言,它更適合那些沒有大量行的表格,不過這樣也會占用大量的處理器資源。對這個技術更多的信息,可以參考蘋果示例程序的第15個例子TableViewSuite,下面會給出注釋。
- 避免復雜的圖形計算 - 正如透明效果問題一下,盡量遠離那些需要計算的圖形元素,比如在顯示的時候把圖片處理為漸變效果。
- 編程創建單元格 - 如果你已經想方設法進行優化了,依然還是需要進一步提升性能,那么你可以不用Interface Builder來創建自定義UITableViewCell ,而是編程手寫這個控件。關于編程構建視圖的更多信息,可訪問Craig Dunn的博客帖子。要學習如何不使用Interface Builder來創建MonoTouch應用程序,可以閱讀MonoTouch.info里面列出的文章,在這里:http://monotouch.info/Tags/NoIB
展示進行性能優化的更多例子,可以訪問蘋果的TableViewSuite示例應用程序。
它雖然是用Objective-C編寫的,不過概念是相同的。