企业🤖AI智能体构建引擎,智能编排和调试,一键部署,支持私有化部署方案 广告
在 FMXUI 中,有 TListExView 和 TListViewEx 两个ListView。其中第一个是对Delphi原有TListView的功能扩展版,用法和原有的基本一样。TListViewEx 则是 FMXUI 原创的一个 ListView , 今天我们要介绍的就是它了。 # 一、 IListAdapter 数据适配器 TListViewEx 的设计思想与Java原生安卓开发类似,使用了数据与显示分离的适配器模式。组件本身不存放数据,只负责显示控制。用户可以使用自带的几个简单的数据适配器或者实现IListAdapter接口,打造自己的数据适配器。通过自带义数据适配器,我们可以实现任意样式的列表。所以,这里的 IListAdapter 接口的重要性不言而预。 我们来看看 IListAdapter 的定义: ![复制代码](https://common.cnblogs.com/images/copycode.gif) ~~~ /// <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; ~~~ ![复制代码](https://common.cnblogs.com/images/copycode.gif) * GetCount: 返回数据的大小(总行数) * GetItemID: 指定索引项数据的ID(可以直接使用索引号) * GetItem: 获取指定索引的数据 * IndexOf: 查找数据,返回索引号 * GetView: 根据索引号,返回对应的可视对象(这个超级重要!!) * GetItemViewtype: 返回指定索引号的视图类型(这个也是重点) * IsEmpty: 判断列表是否为空(Count = 0) * IsEnabled: 判断某行数据是否有效 * ItemDefaultHeight: 默认行高度(很重要,返回所有视图类型中的最大默认行高) * Clear: 清空数据 * Repaint: 重绘 * NotifyDataChanged: 通知列表框组件数据已经更新,需要重新绘制 * Count: 数据的总数 在接口中, 最常用的 GetView , GetItemViewType, ItemDefaultHeight , GetCount 这四个。一般情况我们只需要继承 TListAdapterBase 或者 TListAdapter , 然后重载这四个函数就可以了。 # 二、 使用 ListView 我们实现如下效果。 ![](http://images2015.cnblogs.com/blog/666150/201612/666150-20161227143647461-2109463344.png) ## 第一步: 创建自定义列表项视图。 添加一个Frame,命名为 CustomListView\_ListItem 。然后设计成这样: ![](http://images2015.cnblogs.com/blog/666150/201612/666150-20161227143836648-932689975.png) ## 第二步: 创建数据适配器 ![复制代码](https://common.cnblogs.com/images/copycode.gif) ~~~ 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; ~~~ ![复制代码](https://common.cnblogs.com/images/copycode.gif) ## 第三步、应用数据适配器 在窗口上添加 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; ~~~ 在窗口释放事件中,释放资源 ![复制代码](https://common.cnblogs.com/images/copycode.gif) ~~~ procedure TCustomListview.DoFree; begin inherited; ListView.Adapter := nil; FAdapter := nil; FreeAndNil(FList); end; ~~~ ![复制代码](https://common.cnblogs.com/images/copycode.gif) 添加测试数据的代码 ![复制代码](https://common.cnblogs.com/images/copycode.gif) ~~~ 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; ~~~ ![复制代码](https://common.cnblogs.com/images/copycode.gif) 注意: 数据变更后,需要及时调用 NotifyDataChanged 来通知 ListView 更新显示。 # 三、 下拉刷新和上拉加载更多 TListViewEx 也实现了下拉刷新和上拉加载更多的功能。在属性面板中启用相应的选项即可。 * EnablePullRefresh: 是否启用下拉刷新 * EnablePullLoad: 是否启用上拉加载更多 * OnInitFooter: 加载自定义 Footer 事件, 如果不设置,将在需要时加载默认的 Footer * OnInitHeader: 加载自定义 Header 事件, 如果不设置,将在需要时加载默认的 Header * OnPullRefresh: 下拉刷新事件 * OnPullLoad: 上拉加载更多事件 TListViewEx 允许自定义 Footer 和 Header, 只需要在上述相应的事件中,初始化为对应的视图就可以了。自定义视图的实现方式也是新建一个 Frame 就可以了, 参考 ListItem 和默认的实现。不同的时需要实现 IListViewHeader 接口。 示例: ![复制代码](https://common.cnblogs.com/images/copycode.gif) ~~~ 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; ~~~