Перейти к содержанию
Fire Monkey от А до Я
  • 0

Возвращаясь к вопросу о подсчете высоты текста TListView


HyperZen

Вопрос

Добрый вечер! Все-таки, как корректно подсчитывать высоту итемов данного компонента, если учитывать, что текст может быть достаточно большим.

Использую код с данного сайта, все работает корректно, пока не встречается текст, чуть больше одного абзаца:

function TextHeight(const AText: string; aTextSettings: TTextSettings; const MaxWidth: Single): Single;
// uses FMX.Graphics, FMX.TextLayout, FMX.Types, Math
var
  Layout: TTextLayout;
  aRect: TRectF;
  aWW: Boolean;
begin
  Result := 24;
  if AText.IsEmpty then
    Exit;

  aWW := Pos(#13#10, AText) > 0;
  if (aTextSettings.WordWrap) or (aWW) then
    aRect := RectF(0, 0, MaxWidth, MaxSingle)
  else
    aRect := RectF(0, 0, MaxSingle, MaxSingle);
  Layout := TTextLayoutManager.DefaultTextLayout.Create;
  try
    Layout.BeginUpdate;
    Layout.TopLeft := aRect.TopLeft;
    Layout.MaxSize := PointF(aRect.Width, aRect.Height);
    Layout.Text := AText;
    Layout.WordWrap := aTextSettings.WordWrap;
    Layout.HorizontalAlign := TTextAlign.Leading;
    Layout.VerticalAlign := TTextAlign.Leading;
    Layout.Font.Assign(aTextSettings.Font);
    Layout.Color := aTextSettings.FontColor;
    Layout.RightToLeft := false;
    Layout.EndUpdate;
    aRect := Layout.TextRect;
  finally
    FreeAndNil(Layout);
  end;
  Result := aRect.Bottom;
end;

И вызов в onUpdateObject:

procedure TForm12.ListView1UpdateObjects(const Sender: TObject; const AItem: TListViewItem);
var
  TS: TTextSettings;
begin
  TS := TTextSettings.Create(nil);
  // задаем параметры текста для расчета "Text"
  TS.WordWrap:= True;
  TS.Font.Assign(ListView1.ItemAppearanceObjects.ItemObjects.Text.Font);
  AItem.Height := Trunc(TextHeight(AItem.Text, TS, ListView1.Width));
  FreeAndNil(TS);
end;

Получаем:

1.jpg

Изменено пользователем HyperZen
Ссылка на комментарий

Рекомендуемые сообщения

  • 0
1 час назад, Brovin Yaroslav сказал:

Вот пример вычисления высоты итема. ListViewVariableHeightItems2.zip

Я этот пример показывал на лонче в питере.

Отличный пример!!! Начинаю его "копать"!

Ярослав, почему после стольких чаяний пользователей EMB никак не введут AutoItemHeight в ListViewItem?

Ссылка на комментарий
  • 0
  • Администраторы

Сразу не могу вам ответить. Но могу предположить, что итем умеет отображать не только текст и имеет возможность различных вариантов расположения отображаемых данных. Именно по этому и нельзя заранее знать, какой способ вы будите использовать.

Ссылка на комментарий
  • 0

Ярослав! Все работает как надо, но при некоторых размерах шрифта (я подозреваю те размеры, про которые мы писали выше) все-таки вычисления некорректны - это касается платформы Windows.

На конкретном устройстве - все работает корректно с любым размером шрифтов!

Изменено пользователем HyperZen
Ссылка на комментарий
  • 0

Нашел-таки глюк/баг (не знаю как назвать правильно) - все работает корректно в Windows.

А вот что касается Android, то размер шрифта необходимо указывать вручную обязательно (даже если используете в ListViewItem неизменный размер шрифта), иначе время от времени наблюдаются артефакты в виде наложения текста, или уползания его на следующий Item.

  Drawable := TListItemText(AItem.View.FindDrawable('txtMain'));
  Text := Drawable.Text;

  Drawable.Font.Size := ListView1.ItemAppearanceObjects.ItemObjects.Text.Font.Size;

- эта строчка решила проблему.

Ссылка на комментарий
  • 0
function GetTextHeight(text: string; width: single; wordwrap: boolean; font: tfont;
      HAlign, VAlign: TTextAlign; Trimming: TTextTrimming): single;
begin
      if FTextLyout = nil then
        FTextLyout := TTextLayoutManager.DefaultTextLayout.Create;
      FTextLyout.BeginUpdate;
    try
      // Инициализируем текстовый слой для корректного вычисления
      // размеров отображаемого текста
      FTextLyout.Text := text;
      FTextLyout.MaxSize := TPointF.Create(Width, 10000);
      FTextLyout.WordWrap := WordWrap;
      FTextLyout.Font := Font;
      FTextLyout.Trimming:= Trimming;
      FTextLyout.HorizontalAlign := HAlign;
      FTextLyout.VerticalAlign := VAlign;
    finally
      FTextLyout.EndUpdate;
    end;
    Result:=FTextLyout.Height;
end;
procedure TMainForm.GroupLVUpdateObjects(const Sender: TObject;
  const AItem: TListViewItem);
var
  lvi: TListViewItem;
  t: Single;
begin
    lvi:= TListView(Sender).Items[AItem.Index];
    t:= GetTextHeight(lvi.Text,
          lvi.Objects.TextObject.Width,
          lvi.Objects.TextObject.WordWrap,
          lvi.Objects.TextObject.Font,
          TTExtAlign(lvi.Objects.TextObject.Align),
          TTExtAlign(lvi.Objects.TextObject.VertAlign)
           );

    lvi.Height:= Round(t) + 30;

end;

у меня работает. 

Ссылка на комментарий
  • 0

Почитал, посмотрел все варианты и сделал работу над ошибками. Переделал свой, показанный выше, код. Проверил на Windows и Android, с стилем по умолчанию и другими стилями - работает безупречно. Так же не нуждается в подгонках типа "Height:= Round(t) + 30;".

Преимущество кода в том что на вход подаем ATextObject : TListItemText, т.е. можно легко использовать для вычисления Text или Detail. 

function TFormMain.CalculateListItemObjectHeight(ATextObject : TListItemText) : Single;
begin
  FTextLayout.BeginUpdate;
  try
    FTextLayout.Text:=ATextObject.Text;
    FTextLayout.MaxSize:=TPointF.Create(ATextObject.Width, TTextLayout.MaxLayoutSize.Y);
    FTextLayout.Font.Assign(ATextObject.Font);
    FTextLayout.WordWrap:=ATextObject.WordWrap;
    FTextLayout.Trimming:=ATextObject.Trimming;
    FTextLayout.HorizontalAlign:=ATextObject.TextAlign;
    FTextLayout.VerticalAlign:=ATextObject.TextVertAlign;
  finally
    FTextLayout.EndUpdate;
  end;
  Result:=FTextLayout.Height;
end;

Пример использования: у меня Text непредсказуемой длины, вверху ListItem, по всей ширине ListItem. Под Text располагаются слева Image, справа Detail. Текст Detail может быть от пары слов, до нескольких предложений

procedure TFormMain.ListViewActionsUpdateObjects(const Sender: TObject; const AItem: TListViewItem);
Var AItemTextHeight, AItemDetailHeight, AItemImageHeight, AItemHeight : Single;
    AListView: TListView;
begin
  AListView:=TListView(Sender);
  AItem.Objects.TextObject.Width:=AListView.Width - AListView.ItemSpaces.Left - // Вычисляем ширину Text
                                  AListView.ItemSpaces.Right - DefaultScrollBarWidth;
  AItemTextHeight:=CalculateListItemObjectHeight(AItem.Objects.TextObject); // Вычисляем высоту Text
  AItem.Objects.DetailObject.PlaceOffset.Y:=AItemTextHeight; // Устанавливаем смещение для Detail
  AItem.Objects.ImageObject.PlaceOffset.Y:=AItemTextHeight;  // Устанавливаем смещение для Image
  AItemDetailHeight:=CalculateListItemObjectHeight(AItem.Objects.DetailObject);  // Вычисляем высоту Text

  AItemImageHeight:=AItem.Objects.ImageObject.Height;
  AItem.Objects.DetailObject.Height:=AItemImageHeight;
  AItemHeight:=AItemTextHeight + Max(AItemDetailHeight, AItemImageHeight); // Вычисляем высоту всего Item
  AItem.Height:=Round(AItemHeight);
end;

 

Ссылка на комментарий
  • 0

у меня аналогичная функция содержит еще один важный момент

иногда ATextObject не имеет установленной ширины, т.е. значение по умолчанию=0!

тогда нужно либо всегда задавать ее при создании итема, либо передавать ее отдельным параметром

думаю над созданием наследников TDrawable которые кроме Offset имеют свойства Margins

OFF:

по идее, все это уже пройдено в HTML когда настала эпоха DIV-верстки. Мы пытаемся наощупь идти по тем же граблям

Ссылка на комментарий
  • 0
5 минут назад, krapotkin сказал:

у меня аналогичная функция содержит еще один важный момент

иногда ATextObject не имеет установленной ширины, т.е. значение по умолчанию=0!

тогда нужно либо всегда задавать ее при создании итема, либо передавать ее отдельным параметром

думаю над созданием наследников TDrawable которые кроме Offset имеют свойства Margins

OFF:

по идее, все это уже пройдено в HTML когда настала эпоха DIV-верстки. Мы пытаемся наощупь идти по тем же граблям

Полностью согласен. Ширину я устанавливаю в ListViewActionsUpdateObjects, и только после этого вызываю функцию.

Ссылка на комментарий
  • 0

От эмбр: http://community.embarcadero.com/blogs?view=entry&id=8542

procedure TVariableHeight.ListView1UpdateObjects(const Sender: TObject;
  const AItem: TListViewItem);
var
  Drawable: TListItemText;
  SizeImg: TListItemImage;
  Text: string;
  AvailableWidth: Single;
begin
  SizeImg := TListItemImage(AItem.View.FindDrawable('imgSize'));
  AvailableWidth := TListView(Sender).Width - TListView(Sender).ItemSpaces.Left
    - TListView(Sender).ItemSpaces.Right - SizeImg.Width;

  // Find the text drawable which is used to calcualte item size.
  // For dynamic appearance, use item name.
  // For classic appearances use TListViewItem.TObjectNames.Text
  // Drawable := TListItemText(AItem.View.FindDrawable(TListViewItem.TObjectNames.Text));
  Drawable := TListItemText(AItem.View.FindDrawable('txtMain'));
  Text := Drawable.Text;

  // Randomize the font when updating for the first time
  if Drawable.TagFloat = 0 then
  begin
    Drawable.Font.Size := 1; // Ensure that default font sizes do not play against us
    Drawable.Font.Size := 10 + Random(4) * 4;

    Drawable.TagFloat := Drawable.Font.Size;
    if Text.Length < 100 then
      Drawable.Font.Style := [TFontStyle.fsBold];
  end;

  // Calculate item height based on text in the drawable
  AItem.Height := GetTextHeight(Drawable, AvailableWidth, Text);
  Drawable.Height := AItem.Height;
  Drawable.Width := AvailableWidth;

  SizeImg.OwnsBitmap := False;
  SizeImg.Bitmap := GetDimensionBitmap(SizeImg.Width, AItem.Height);
end;

 

Ссылка на комментарий
  • 0
В 07.06.2016 в 21:44, Brovin Yaroslav сказал:

Вот пример вычисления высоты итема. ListViewVariableHeightItems2.zip

Ярослав, сегодня, вновь тестируя различные размеры шрифтов, обнаружил, что если по умолчанию выставить размер шрифта 14 (под Андроид), расчет все равно неверный - не хватает буквально 5% высоты, добавлять ее вручную не хочется, потому как при всех других размерах все корректно. Если изменить размер шрифта в рантайме - и вернуть на 14, то все опять корректно. Неправильный подсчет только при запуске приложения и в случае, если размер стоит = 14.

Вышел из положения добавляя при расчете 0.1 к размеру шрифта:

  Drawable.Font.Size := spnBoxFontSize.Value + 0.1; // при старте программы размер выставляется относительно значения SpinBox'а

Изменено пользователем HyperZen
Ссылка на комментарий
  • 0
4 часа назад, HyperZen сказал:

Ярослав, сегодня, вновь тестируя различные размеры шрифтов, обнаружил, что если по умолчанию выставить размер шрифта 14 (под Андроид), расчет все равно неверный - не хватает буквально 5% высоты, добавлять ее вручную не хочется, потому как при всех других размерах все корректно. Если изменить размер шрифта в рантайме - и вернуть на 14, то все опять корректно. Неправильный подсчет только при запуске приложения и в случае, если размер стоит = 14.

Вышел из положения добавляя при расчете 0.1 к размеру шрифта:

  Drawable.Font.Size := spnBoxFontSize.Value + 0.1; // при старте программы размер выставляется относительно значения SpinBox'а

Попробуйте проверить отладкой какой размер шрифта попадает в GetTextHeight. Потому что по умолчанию размер 12, а в стиле размер может быть 18. Так же вы не выполняете ListView.ApplyStyleLookup после заполнения ListView. 

Очень рекомендую перед заполнением ListView выполнять код

      AListView.BeginUpdate;
      AListView.OnUpdateObjects:=Nil;

После заполнения:

      AListView.EndUpdate;      
      ListViewUpdateAll(AListView); // В цикле применяет ListViewUpdateObjects ко всем элементам
      AListView.ApplyStyleLookup;
      AListView.OnUpdateObjects:= ListViewUpdateObjects;

 

Ссылка на комментарий
  • 0
21 час назад, Евгений Корепов сказал:

Попробуйте проверить отладкой какой размер шрифта попадает в GetTextHeight. Потому что по умолчанию размер 12, а в стиле размер может быть 18.

Проверил, размер шрифта попадает тот, который у меня грузится в SpinEdit во время создания формы, и он же устанавливается сразу в момент все той же загрузки. Стилей не использую.

Ваш пример выше хороший, но не могу его допилить для использования TListView в DynamicAppearance режиме (если его вообще надо допиливать - просто не получилось :) ).

Ссылка на комментарий
  • 0

Подниму тему. Вот совершенно элементарный код:

procedure TForm1.Button2Click(Sender: TObject);
var
   Item : TListViewItem;
   i, CountStringsInHeader : Integer;
   hHeader : single;

begin
   CountStringsInHeader := 5;
   if CountStringsInHeader > 0 then begin
      hHeader := 0;
      ListView3.BeginUpdate;
      try
         if ListView3.Items.Count <> 0 then begin
            ListView3.ScrollViewPos := 0;
            while ListView3.Items.Count > 0 do ListView3.Items.Delete(0);
         end;
         for I := 0 to CountStringsInHeader-1 do begin
            Item := ListView3.Items.Add;
            Item.Text := i.ToString;
            Item.Detail := 'Detail: '+i.ToString;
            hHeader := hHeader + Item.Height;
         end;
      finally
         ListView3.EndUpdate;
         layListHeader.Height := hHeader + laySeparator.Height;
         layListHeader.Visible := True;
      end;
   end;
   ListView2.BeginUpdate;
   try
      if ListView2.Items.Count <> 0 then begin
         ListView2.ScrollViewPos := 0;
         while ListView2.Items.Count > 0 do ListView2.Items.Delete(0);
      end;
      for I := CountStringsInHeader to 4 do begin
         Item := ListView2.Items.Add;
         Item.Text := i.ToString;
         Item.Detail := 'Detail: '+i.ToString;
      end;
   finally
      ListView2.EndUpdate;
   end;
end;

Почему в строке 

hHeader := hHeader + Item.Height;

Item.Height всегда равно 0? Техт маленький, мне не нужно расширять item, просто точно вычислить высоту Layout в который вписан ListView.

Ссылка на комментарий
  • 0
25 минут назад, dervish00 сказал:

 


hHeader := hHeader + Item.Height;

Item.Height всегда равно 0? Техт маленький, мне не нужно расширять item, просто точно вычислить высоту Layout в который вписан ListView.

Потому что перед этой строкой вы делаете

Item := ListView3.Items.Add;
Item.Text := i.ToString;
Item.Detail := 'Detail: '+i.ToString;

Вы добавили Item, но не дали возможность его отрисовать. Т.е. ListView3 добавил себе итем, но еще ничего о нем не знает. Параметры Item вам нужно выставлять в событии ListView3UpdatedObjects - тут уже будет известна высота.

Ссылка на комментарий
  • 0
11 часов назад, Евгений Корепов сказал:

Вы добавили Item, но не дали возможность его отрисовать. Т.е. ListView3 добавил себе итем, но еще ничего о нем не знает. Параметры Item вам нужно выставлять в событии ListView3UpdatedObjects - тут уже будет известна высота.

Ну все не так просто - OnUpdatINGObjects возникает при прорисовке конкретного Item. Мне же нужно посчитать сумму их высот. Да и вообще - я, честно говоря, думал, что данным событием нужно пользоваться если тип ItemAppearance будет Custom или DynamicAppearance, у меня же в примере вполне себе классический тип - ListItemRightDetail, не нужно ничего добавлять - ни рисунков, никакого сложного форматирования, просто Item и Detail

Ссылка на комментарий
  • 0

Читаю, диву даюсь!

Что мешало сразу сделать корректным расчёт высоты. Хитрости, уловки, танцы с бубном... 

Я использовал :

function TMainForm.DoMeasureText(Canvas: TCanvas; AText: string; Width: single): single;
var
  R: TRectF;
begin
  R := RectF(0, 0, Width-9, 10000); // Width-54 = font.size x 3
  Canvas.MeasureText(R, AText, True, [], TTextAlign.Leading, TTextAlign.Leading);
  Result := R.Bottom;
end;

Но так и не понял всей премудрости. При шрифте 18, в одну строчку , на один RadioButton она выдаёт 43, 23 , а на какой-то 46,47. Где-то считает одну строчку за две.

Ссылка на комментарий
  • 0
54 минут назад, Hevard сказал:

Ох уж эти суровые челябинские пользователи! А если внимательнее "подпись" автора посмотреть?

Ну, то есть, циклопентанпергидрофенантрен тебя не смутил? ?

Ссылка на комментарий
  • 0
14 часов назад, Vitaldj сказал:

Ну, то есть, циклопентанпергидрофенантрен тебя не смутил?

Нет, ибо к IDE это никак не относится. Мне достаточно знания, что такое ацетилсалициловая кислота. ?

Ссылка на комментарий

Присоединяйтесь к обсуждению

Вы можете написать сейчас и зарегистрироваться позже. Если у вас есть аккаунт, авторизуйтесь, чтобы опубликовать от имени своего аккаунта.

Гость
Ответить на вопрос...

×   Вставлено с форматированием.   Вставить как обычный текст

  Разрешено использовать не более 75 эмодзи.

×   Ваша ссылка была автоматически встроена.   Отображать как обычную ссылку

×   Ваш предыдущий контент был восстановлен.   Очистить редактор

×   Вы не можете вставлять изображения напрямую. Загружайте или вставляйте изображения по ссылке.

  • Последние посетители   0 пользователей онлайн

    • Ни одного зарегистрированного пользователя не просматривает данную страницу
×
×
  • Создать...