在用FMX的ListBox和ComboBox等涉及TListBox的时候,都要人工设置ItemHeight。
如果ItemHeight是固定不变的,并且是定制使用ListItem的Style的话,可以直接在Style中设置ItemHeight。
如果要动态改变ItemHeight,或者是ItemHeight的大小未知,自动设置ItemHeight, 就要根据Item的文本大小来调整,因此要测量ListItem的文本高度TextHeight(一般不用考虑宽度),如果Scale不是1,还得乘以Scale。所以就涉及到测量文本的高度问题。
原来在VCL下,直接是TCanvas.MeasureText(),就可以得到实际的文本高度TextHight。但是在FMX下,最好不用TCanvas来测量,虽然TCanvas提供了MeasureText()和TextHeight()方法,其中TextHeight()调用MeasureText(),MeasureText()调用了TTextLayout的属性TextRect。
但是要注意,我们改变ListItem的Font的时候,ListItem.Canvas的Font是不变的,还是默认大小。一般情况下,默认Font的大小(在win10下,如果不改系统默认字体大小,这个值是12)太小了,我们都会设置的大一些。所以要用TCanvas的MeasureText()或TextHeight()方法测量的TextHeight大小,测量前我们得先改变ListItem.Canvas的Font大小,但是TCanvas.Font属性是只读的,不能改变整个Font,只能是单独改变TFont的各个属性,比如Font.Family,Font.Size,Font.Color等,比较麻烦。看看TCanvas的代码:
.........
property Stroke: TStrokeBrush read FStroke;
property Fill: TBrush read FFill write SetFill;
property Font: TFont read FFont;
property Matrix: TMatrix read FMatrix;
property Width: Integer read FWidth;
property Height: Integer read FHeight;
property Bitmap: TBitmap read FBitmap;
property Scale: Single read FScale;
.........
Fill和Stroke倒是可读写的,Font是只读的。
再看看MeasureText()和TextHeight():
procedure TCanvas.MeasureText(var ARect: TRectF; const AText: string; const WordWrap: Boolean; const Flags: TFillTextFlags; const ATextAlign, AVTextAlign: TTextAlign); var Layout: TTextLayout; begin if AText.IsEmpty then begin ARect.Right := ARect.Left; ARect.Bottom := ARect.Top; Exit; end; Layout := TTextLayoutManager.TextLayoutByCanvas(Self.ClassType).Create(Self); try Layout.BeginUpdate; Layout.TopLeft := ARect.TopLeft; Layout.MaxSize := PointF(ARect.Width, ARect.Height); Layout.Text := AText; Layout.WordWrap := WordWrap; Layout.HorizontalAlign := ATextAlign; Layout.VerticalAlign := AVTextAlign; Layout.Font := Self.Font; Layout.Color := Self.Fill.Color; Layout.RightToLeft := TFillTextFlag.RightToLeft in Flags; Layout.EndUpdate; ARect := Layout.TextRect; finally FreeAndNil(Layout); end; end; function TCanvas.TextHeight(const AText: string): Single; var R: TRectF; begin R := RectF(0, 0, 10000, 10000); MeasureText(R, AText, False, [], TTextAlign.Leading, TTextAlign.Leading); Result := R.Bottom; end;
是通过TTextLayout来实现。
所以干脆不用这个名不副实的MeasureText(),自己用TTextLayout来实现好了。
写个通用的设置字体和自动计算Item高度的方法:
//ListBoxItem 的文本高度自动计算,以ListItem[0]为准,如果是每个ListItem不一样,建议用Style方式实现
procedure SetListBoxItemFontAndHeight(AControl: TFmxObject; AFontName: string; AFontSize: Single; AFontColor: TAlphaColor; AutoItemHeight: Boolean); var i: Integer; AStyledSettings: TStyledSettings; aListBox: TListBox; LTextLayout: TTextLayout; begin AStyledSettings := AllStyledSettings; if not AFontName.IsEmpty then AStyledSettings := AStyledSettings - [TStyledSetting.Family]; if AFontSize > 8 then AStyledSettings := AStyledSettings - [TStyledSetting.Size]; if AFontColor <> TAlphaColors.Black then AStyledSettings := AStyledSettings - [TStyledSetting.FontColor]; aListBox := nil; if AControl is TListBox then aListBox := TListBox(AControl) else if AControl is TComboBox then begin TComboBox(AControl).DropDownKind := TDropDownKind.Custom; aListBox := TListBox(TComboBox(AControl).ListBox); end else Exit; if (aListBox = nil) or (aListBox.Count = 0) then Exit; for i := 0 to aListBox.Count-1 do begin aListBox.ListItems[i].StyledSettings := AStyledSettings; if not AFontName.IsEmpty then aListBox.ListItems[i].TextSettings.Font.Family := AFontName; if AFontSize > 8 then aListBox.ListItems[i].TextSettings.Font.Size := AFontSize; if AFontColor <> TAlphaColors.Black then aListBox.ListItems[i].TextSettings.FontColor := AFontColor; end; if not AutoItemHeight then Exit;
//计算ItemHeight LTextLayout := TTextLayoutManager.DefaultTextLayout.Create(); try LTextLayout.BeginUpdate; try LTextLayout.Font := aListBox.ListItems[0].Font; LTextLayout.Text := aListBox.Items[0]; finally LTextLayout.EndUpdate; end; aListBox.ItemHeight := LTextLayout.Height; //TextHeight finally LTextLayout.Free; end; end;