You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Delphi FMX TListView ScrollTo失效:动态高度Item无法准确定位

TListView动态高度Item的ScrollTo定位问题解决

将TListView的Item设置为DynamicAppearance后,通过OnUpdateObject事件动态计算每个Item的高度,视觉显示正常,但原生ScrollTo功能无法准确定位目标Item。尝试自定义ListViewScrollTo方法计算滚动位置时,多数情况下ListView.Items[i].Height返回0,导致无法得到正确的滚动高度。

自定义滚动方法

procedure ListViewScrollTo(const aListView: TListview);
var i, lHeight: Integer;
begin
    aListView.Repaint;
    lHeight := 0;
    for i := 0 to aListView.Items.Count - 1 do
    begin
    if i = aListView.ItemIndex then
      break;
    lHeight := lHeight + aListView.Items[i].Height  + Round(aListView.ItemSpaces.top + aListView.ItemSpaces.Bottom);
  end;
  aListView.ScrollViewPos := lHeight;
end;

OnUpdateObject事件中的自动计算Item高度代码

function GetTextHeight(const D: TListItemText; const Width: single; const Text: string): integer;
var
  Layout: TTextLayout;
begin
  Layout := TTextLayoutManager.DefaultTextLayout.Create;
  try
    Layout.BeginUpdate;
    try
      Layout.Font.Assign(D.Font);
      Layout.VerticalAlign := D.TextVertAlign;
      Layout.HorizontalAlign := D.TextAlign;
      Layout.WordWrap := D.WordWrap;
      Layout.Trimming := D.Trimming;
      Layout.MaxSize := TPointF.Create(Width, TTextLayout.MaxLayoutSize.Y);
      Layout.Text := Text;
    finally
      Layout.EndUpdate;
    end;
    //Size needs to be adjusted for Scale of Pixel density...
    Result := Round(Layout.Height * GetScreenScale);
  finally
    Layout.Free;
  end;

end;


function ListViewAutoSize(const Sender: TObject; const AItem: TListViewItem; aCustomItemTextName: string): Integer;
var
  Drawable: TListItemText;
  Text: string;
  AvailableWidth: Single;
  Names: TStringList;
  SumHeight: Single;
begin
  TListView(Sender).BeginUpdate;
  SumHeight := 0;
  Names := TStringList.Create;
  try
    Names.Delimiter := ';';
    Names.StrictDelimiter := True;
    Names.DelimitedText := aCustomItemTextName;
    //do this for all items in aCustomItemTextName
    for var I := 0 to Names.Count - 1 do
    begin
      AvailableWidth := TListView(Sender).Width - TListView(Sender).ItemSpaces.Left - TListView(Sender).ItemSpaces.Right;

      Drawable := TListItemText(AItem.View.FindDrawable(Names[i])); //find item by name
      if assigned(Drawable) then
      begin
        //found
        if Drawable.Visible then
        begin
          Text := Trim(Drawable.Text);
          if Text <> '' then
          begin
            SumHeight := SumHeight;
            Drawable.PlaceOffset.Y := SumHeight ;
            Drawable.Height := GetTextHeight(Drawable, AvailableWidth, Text) * GetScreenScale;
            SumHeight := SumHeight + Drawable.Height;
            AItem.Height := Round(SumHeight);
            Drawable.Width := AvailableWidth;
          end else begin
            Drawable.Height := 0;
          end;
        end;
      end;
    end;
    //set Item size that everything is visible...
    AItem.Height := Round(SumHeight);
    Result := AItem.Height;
  finally
    Names.Free;
  end;
  TListView(Sender).EndUpdate;
end;

问题根源与解决方法

问题根源

ListView.Items[i].Height返回0,核心原因是未进入可视区域的Item不会触发OnUpdateObject事件,导致高度未被计算,始终保持初始值0。

解决方法

1. 预计算并缓存所有Item高度

在ListView加载完数据后,主动触发所有Item的高度计算,将结果缓存到数组或Item的Tag属性中,避免依赖OnUpdateObject的延迟计算。

示例代码:

var
  ItemHeights: array of Integer;

// 预计算所有Item高度
procedure PreCalculateItemHeights(const aListView: TListView; const CustomTextNames: string);
var
  i: Integer;
begin
  SetLength(ItemHeights, aListView.Items.Count);
  for i := 0 to aListView.Items.Count - 1 do
  begin
    // 调用现有高度计算方法
    ItemHeights[i] := ListViewAutoSize(aListView, aListView.Items[i], CustomTextNames);
    // 同时将高度存入Item.Tag,作为备选缓存方式
    aListView.Items[i].Tag := ItemHeights[i];
  end;
end;

2. 修改自定义滚动方法,使用缓存高度

不再直接读取Items[i].Height,改用预缓存的高度数据:

procedure ListViewScrollTo(const aListView: TListview);
var i, lHeight: Integer;
begin
  lHeight := 0;
  for i := 0 to aListView.ItemIndex - 1 do
  begin
    // 使用数组缓存的高度
    lHeight := lHeight + ItemHeights[i] + Round(aListView.ItemSpaces.Top + aListView.ItemSpaces.Bottom);
    // 若用Tag缓存则替换为:
    // lHeight := lHeight + aListView.Items[i].Tag + Round(aListView.ItemSpaces.Top + aListView.ItemSpaces.Bottom);
  end;
  aListView.ScrollViewPos := lHeight;
end;

3. 处理ListView宽度变化

当ListView宽度改变时,Item高度可能需要重新计算,需更新缓存:

procedure TForm1.ListViewResize(Sender: TObject);
begin
  PreCalculateItemHeights(TListView(Sender), '你的自定义文本控件名称');
end;

额外优化建议

  • 移除ListViewAutoSize中的BeginUpdate/EndUpdate调用,该方法在OnUpdateObject触发时已处于ListView的更新周期内,额外包裹会降低性能。
  • 若Item数量较多,可将预计算逻辑放在后台线程中执行,避免UI卡顿。

内容的提问来源于stack exchange,提问作者Marko

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.23 10:24:05