在MonoTouch中自定義表格

來源: InfoQ  發布時間: 2011-01-21 00:49  閱讀: 1749 次  推薦: 0   原文鏈接   [收藏]  
摘要:表格在iPhone應用程序中很常見,不過鑒于iPhone的一些限制,它本身的功能是非常固定的。然而,蘋果提供了便捷的定制能力,本篇文章就是要講解如何創建高定制化的iPhone表格。

  為什么要定制表格?

  表格在很多iPhone應用程序中都是必需的UI元素。雖然對于應用程序開發而言,這并非是一項新發明,鑒于設備尺寸等方面的限制,表格在iPhone中的功能是非常固定的。

  蘋果在其SDK中,直接內置了很多風格來讓你定制表格。不過,在你最初創建表格的時候,它看起來非常簡單。在沒有進行任何定制的時候,你可以為表格選擇兩種基本風格,默認風格和分組風格:

  在對表格中的單元格進行一點調整后,你就可以添加圖標和說明文字:

  你甚至能改變單元格的字體和顏色,然而,有時候這樣還是不足夠。如果你真的想完全改變基本的風格,創建一個復雜的UI,那么你必須創建自己的自定義單元格控件。下面的截圖是定制后的效果,這個應用程序使用完全定制的單元格來顯示內容:

  幸好,蘋果讓我們有便捷的方法來完成這樣的定制,就是允許我們創建自己的定制UITableViewCell 控件,并把其用于UITableView 控件中。下面,讓我們來逐步創建一個應用程序,學習如何利用UITableViewUITableViewCell 控件來創建上圖所示效果的例子。

  示例應用程序

  那么接下來,我們就來創建應用程序。首先,打開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:

  在這個代碼文件中,輸入如下代碼:

 
namespace Example_CustomUITableViewCells
{

//==================================
/// <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;
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;
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 這個方法返回某個分組標頭的文本。就是返回當前分組對象BasicTableViewItemGroupName 屬性。
  • 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類中,添加如下代碼行:

 
BasicTableViewSource _tableViewSource;

  This is a class variable to hold our BasicTableViewSource that we created earlier.

  這是一個類變量,用來保存我們早期創建的BasicTableViewSource 實例。

  接著,把如下方法復制到AppDelegate類中:

 
/// <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);
}

  我們將調用這個方法來創建數據源,完成在表格中顯示數據的過程。

  最終,在AppDelegate 類中的FinishedLaunching 方法中,在調用window.MakeKeyAndVisible 之前加入如下代碼。

 
this.CreateTableItems ();
this.tblMain.Source = this._tableViewSource;

  通過調用這個方法,我們就創建了一個表格數據源,并把數據源賦值給表格。

  最終的Main.cs應該像這樣:

 
using System;
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方法中把如下代碼行:

 
cell = new UITableViewCell (UITableViewCellStyle.Default, this._cellIdentifier);

  改為:

 
cell = new UITableViewCell (UITableViewCellStyle.Subtitle, this._cellIdentifier);

  這樣就把UITableViewCell 的風格改變為Subtitle ,其會在單元格中顯示文本的第二行。

  你能選用的只有很少的風格。然而要注意,只有在DefaultSubtitle 風格中才支持圖片。

  現在,在這行之后:

 
cell.TextLabel.Text = item.Name;

  增加:

 
cell.Accessory = UITableViewCellAccessory.Checkmark;
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 打開這個類文件。

  我們要對這個類進行一點編輯。首先,我們來添加一些屬性到類里面:

 
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; }
}

  我們來看一下這些屬性:

  • Cell 這個屬性讓這個自定義單元格的調用者可以直接訪問單元格本身,以便在UITableViewSourceGetCell 調用中,我們能直接從一個屬性簡單地返回,過一會我們會看到怎么弄。
  • Heading 和SubHeading 這個屬性暴露了可以直接訪問表格單元格中UILabel 控件的文本值。
  • Image 這個屬性直接暴露表格單元格中的UIImage 控件。

  接下來,我們需要編輯其中一個構造器。文件在被MonoDevelop創建好后,文件中的最后一個構造器看起來如下:

 
public CustomTableViewCell () : base("CustomTableViewCell", null)
{
Initialize ();
}

  這是一個非常簡單的構造器,如果你讀過Mono文檔關于基類構造說明的話,就會明白這里它會初始化這個類,并用我們傳遞進去的名稱來加載Nib(編譯好的.xib文件)文件。然而,文檔中沒有提到的是,基類構造器是異步的。如果你使用這種方式來調用,那么Nib好像未被立刻加載,在我們給自定義單元格設置諸如Heading屬性的時候,我們會得到討厭的空引用錯誤。

  為了修正這個問題,我們必須確保Nib文件同步地加載,即直到加載完成,方法是不會返回。

  那么,就需要編輯構造器,以便看起來如下:

 
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 ();
}

  在這里,我們首先要做到就是,把調用基類構造器的代碼注釋掉。我們會手動地進行來調用那個代碼,所以在這里不用再調一次。接下來,我們添加一個對LoadNib方法的調用。它實際上和基類構造器所做的事情是一樣的,除了不會強制進行異步處理。現在,我們確保了Nib以及任何東西都能初始化,以便我們能訪問它。

  完整的類看上去如下所示:

 
using System;
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;
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 一樣,只有少許改變。

  要注意到第一件事,是聲明部分:

 
//---- declare vars
protected List<BasicTableViewItemGroup> _tableItems;
protected string _customCellIdentifier = "MyCustomTableCellView";
protected Dictionary<int, CustomTableViewCell> _cellControllers =
new Dictionary<int, CustomTableViewCell>();

  我們添加了一個名為_cellControllers新變量。我們將在后面看到如何使用它,它會保存著自定義單元格的集合。

  下一個增加的東西是GetHeightForRow 方法:

 
/// <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;
}

  當我們創建自定義單元格控制器之后,我們讓它比通常的單元格要高。在數據綁定的過程中,CocoaTouch 需要知道要給這個單元格分配多少空間。這就是要調用GetHeightForRow 方法的緣由。如果我們不告知新的大小,那么系統會按照標準尺寸去調整布局,我們就會看到怪異的結果。

  如果我們在Interface Builder中打開我們的單元格,并通過Size Inspector查看,會知道單元格的高度上多少。在我們的例子中,它是109像素,不過你的可能會有所不同。

  最后改變的一塊地方是,GetCell方法:

 
/// <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 類,我們嘗試通過DequeueReusableCell 的調用來從緩存池中重用的一個單元格。從這之后,就變得復雜了。如果我們不能在重用的時候得到單元格,那么我們必須去創建一個新的。它就是由我們的CustomTableViewCell 類所創建的,它實際上是一個包含著自定義單元格的控制器。

  一旦我們創建了控制器,我們就把cell變量賦值控制器的Cell屬性。

  我們接著為單元格添加了一個唯一標識符,以便我們在后面能把它和正確的CustomTableViewCell 控制器關聯在一起。我們使用Environment.TickCount ,因為它提供了比較穩妥的唯一性值。如果我們想,也可以使用Guid.NewGuid,不過調用它稍微有點昂貴。

  接下來,我們使用相同的tag標識,來創建存儲在CustomTableViewCell 的字典對象。

  這也就是我們下面這行代碼顯而易見的原因:

 
else
{
// retreive our controller via it's unique ID
customCellController = this._cellControllers[cell.Tag];
}

  如果我們找到一個可重用的單元格,那么就需要通過控制器來設置相關屬性。為了這樣,必須使用之前我們使用過的標識符從Dictionary中提取出來。

  到這里,已經涉及了對這個類的大部分改變。接下來的幾行,為了改變控制器類中的屬性,進行了相對于單元格本身較簡單的修改。

  好了!我們快要完成了。打開Main.cs文件,我們對AppDelegate類進行兩個地方的改變,讓其使用我們的CustomTableViewSource 而非BasicTableViewSource。

  首先,注釋掉這行:

 
BasicTableViewSource _tableViewSource;

  并緊接著后添加這行:

 
CustomTableViewSource _tableViewSource;

  接著,在CreateTableItems 方法中,注釋掉這行:

 
this._tableViewSource = new BasicTableViewSource(tableItems);

  并緊接著后面添加這行:

 
this._tableViewSource = new CustomTableViewSource(tableItems);

  我們修改后的AppDelegate 類看起來如下:

 
// The name AppDelegate is referenced in the MainWindow.xib file.
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編寫的,不過概念是相同的。

0
0
 
 
 

文章列表

arrow
arrow
    全站熱搜
    創作者介紹
    創作者 大師兄 的頭像
    大師兄

    IT工程師數位筆記本

    大師兄 發表在 痞客邦 留言(0) 人氣()