在 FMXUI 中,有 TListExView 和 TListViewEx 兩個ListView。其中第一個是對Delphi原有TListView的功能擴展版,用法和原有的基本一樣。TListViewEx 則是 FMXUI 原創的一個 ListView , 今天我們要介紹的就是它了。
一、 IListAdapter 數據適配器
TListViewEx 的設計思想與Java原生安卓開發類似,使用了數據與顯示分離的適配器模式。組件本身不存放數據,只負責顯示控制。用戶可以使用自帶的幾個簡單的數據適配器或者實現IListAdapter接口,打造自己的數據適配器。通過自帶義數據適配器,我們可以實現任意樣式的列表。所以,這里的 IListAdapter 接口的重要性不言而預。
我們來看看 IListAdapter 的定義:
/// <summary> /// 列表適配器接口 /// </summary> IListAdapter = interface ['{5CC5F4AB-2D8C-4A84-98A7-51566E38EA47}'] function GetCount: Integer; function GetItemID(const Index: Integer): Int64; function GetItem(const Index: Integer): Pointer; function IndexOf(const AItem: Pointer): Integer; function GetView(const Index: Integer; ConvertView: TViewBase; Parent: TViewGroup): TViewBase; function GetItemViewType(const Index: Integer): Integer; function IsEmpty: Boolean; function IsEnabled(const Index: Integer): Boolean; function ItemDefaultHeight: Single; procedure Clear; procedure Repaint; procedure NotifyDataChanged; property Count: Integer read GetCount; property Items[const Index: Integer]: Pointer read GetItem; default; end;
- GetCount: 返回數據的大小(總行數)
- GetItemID: 指定索引項數據的ID(可以直接使用索引號)
- GetItem: 獲取指定索引的數據
- IndexOf: 查找數據,返回索引號
- GetView: 根據索引號,返回對應的可視對象(這個超級重要!!)
- GetItemViewtype: 返回指定索引號的視圖類型(這個也是重點)
- IsEmpty: 判斷列表是否為空(Count = 0)
- IsEnabled: 判斷某行數據是否有效
- ItemDefaultHeight: 默認行高度(很重要,返回所有視圖類型中的最大默認行高)
- Clear: 清空數據
- Repaint: 重繪
- NotifyDataChanged: 通知列表框組件數據已經更新,需要重新繪制
- Count: 數據的總數
在接口中, 最常用的 GetView , GetItemViewType, ItemDefaultHeight , GetCount 這四個。一般情況我們只需要繼承 TListAdapterBase 或者 TListAdapter , 然后重載這四個函數就可以了。
二、 使用 ListView
我們實現如下效果。
第一步: 創建自定義列表項視圖。
添加一個Frame,命名為 CustomListView_ListItem 。然后設計成這樣:
第二步: 創建數據適配器
type TDataItem = record Name: string; Phone: string; Color: TAlphaColor; end; TCustomListDataAdapter = class(TListAdapterBase) private [Weak] FList: TList<TDataItem>; protected function GetCount: Integer; override; function ItemDefaultHeight: Single; override; function GetItem(const Index: Integer): Pointer; override; function IndexOf(const AItem: Pointer): Integer; override; function GetView(const Index: Integer; ConvertView: TViewBase; Parent: TViewGroup): TViewBase; override; public constructor Create(const AList: TList<TDataItem>); end; .... { TCustomListDataAdapter } constructor TCustomListDataAdapter.Create(const AList: TList<TDataItem>); begin FList := AList; end; function TCustomListDataAdapter.GetCount: Integer; begin if Assigned(FList) then Result := FList.Count else Result := 0; end; function TCustomListDataAdapter.GetItem(const Index: Integer): Pointer; begin Result := nil; end; function TCustomListDataAdapter.GetView(const Index: Integer; ConvertView: TViewBase; Parent: TViewGroup): TViewBase; var ViewItem: TCustomListView_ListItem; Item: TDataItem; begin if (ConvertView = nil) or (not (ConvertView.ClassType = TCustomListView_ListItem)) then begin ViewItem := TCustomListView_ListItem.Create(Parent); ViewItem.Parent := Parent; ViewItem.Width := Parent.Width; ViewItem.CanFocus := False; end else ViewItem := TObject(ConvertView) as TCustomListView_ListItem; Item := FList.Items[Index]; ViewItem.BeginUpdate; ViewItem.TextView1.Text := Item.Name; ViewItem.TextView2.Text := Item.Phone; ViewItem.View1.Background.ItemDefault.Color := Item.Color; ViewItem.EndUpdate; Result := TViewBase(ViewItem); end; function TCustomListDataAdapter.IndexOf(const AItem: Pointer): Integer; begin Result := -1; end; function TCustomListDataAdapter.ItemDefaultHeight: Single; begin Result := 72; end;
第三步、應用數據適配器
在窗口上添加 TListViewEx,命名為 ListView。 在窗口初始化事件中, 初始化數據適配器。
procedure TCustomListview.DoCreate; begin inherited; FList := TList<TDataItem>.Create(); FAdapter := TCustomListDataAdapter.Create(FList); end;
在窗口 Show 事件中,為 ListView 指定數據適配器。
procedure TCustomListview.DoShow; begin inherited; ListView.Adapter := FAdapter; AddItems(20); // 添加20行測試數據 end;
在窗口釋放事件中,釋放資源
procedure TCustomListview.DoFree; begin inherited; ListView.Adapter := nil; FAdapter := nil; FreeAndNil(FList); end;
添加測試數據的代碼
procedure TCustomListview.AddItems(const Count: Integer); var I: Integer; Item: TDataItem; begin for I := 0 to Count - 1 do begin Item.Name := '用戶名稱' + IntToStr(I); if I mod 2 = 0 then Item.Color := TAlphaColorRec.Crimson else Item.Color := TAlphaColorRec.Yellow; Item.Phone := '131 0000 0000'; FList.Add(Item); end; FAdapter.NotifyDataChanged; end;
注意: 數據變更后,需要及時調用 NotifyDataChanged 來通知 ListView 更新顯示。
三、 下拉刷新和上拉加載更多
TListViewEx 也實現了下拉刷新和上拉加載更多的功能。在屬性面板中啟用相應的選項即可。
- EnablePullRefresh: 是否啟用下拉刷新
- EnablePullLoad: 是否啟用上拉加載更多
- OnInitFooter: 加載自定義 Footer 事件, 如果不設置,將在需要時加載默認的 Footer
- OnInitHeader: 加載自定義 Header 事件, 如果不設置,將在需要時加載默認的 Header
- OnPullRefresh: 下拉刷新事件
- OnPullLoad: 上拉加載更多事件
TListViewEx 允許自定義 Footer 和 Header, 只需要在上述相應的事件中,初始化為對應的視圖就可以了。自定義視圖的實現方式也是新建一個 Frame 就可以了, 參考 ListItem 和默認的實現。不同的時需要實現 IListViewHeader 接口。
示例:
procedure TCustomListview.ListViewPullLoad(Sender: TObject); begin DelayExecute(1, procedure (Sender: TObject) begin AddItems(20); ListView.PullLoadComplete; end ); end; procedure TCustomListview.ListViewPullRefresh(Sender: TObject); begin Hint('正在加載數據'); DelayExecute(2, procedure (Sender: TObject) begin FList.Clear; AddItems(20); ListView.PullRefreshComplete; end ); end;