모바일(폰과 패드)용 목록을 만드는 컴포넌트는 대표적으로 ListView와 ListBox가 있습니다.
두 목록 컴포넌트의 차이점은 이름으로 알수 있듯이 목적에 차이가 있습니다.
ListView는
View 즉 보여주는 것을 목적으로 하기 때문에 목록을 빠르게 이동할 수 있지만 목록아이템을 꾸미는데 제한적입니다.
반면,
ListBox는
Box 즉 목록 아이템에 다른 아이템을 담아 자유롭게 목록을 구성할 수 있는 컴포넌트입니다. 목록을 원하는데로 꾸밀 수 있지만 많은 컴포넌트를 담는다면 스크롤이 상대적으로 느려질 수 있습니다.
두 목록 컴포넌트의 목적을 잘 이해하고 사용하시기 바랍니다.
ListView는 목록 아이템을 꾸미는데 제한적이라고 했는데요. 그 이유는 TListViewItem은 (TFMXObject를 상속받지 않았기 때문에)다른 컴포넌트를 올릴 수 없도록 설계되었습니다. 아이템 외관을 꾸미기 위해서는 ListView의 ItemAppearance 속성을 이용할 수 있습니다. 이 속성은 기본으로 7개의 항목만 제공됩니다.
기본 제공되는 ItemAppearance는 모바일 폰(작은화면)을 기준으로 제공합니다. 태블릿용 앱에서는 화면이 다소 허전해 질 수 있습니다.
이렇게 기본 제공되는 외관을 변경(항목 추가, 위치 이동)하기 위해서는 ItemAppearance 패키지 프로젝트를 직접 만들어 설치(Install) 후 사용할 수 있습니다.
ItemAppearance 패키지 제작
기본 샘플 참고
델파이 기본 샘플에서 ListView 샘플(Samples\Object Pascal\Mobile Samples\User Interface\ListView)을 참고할 수 있습니다.
ListView 샘플에는 상세정보를 여러건 보여주는 MultiDetailItem, 별점을 표시하는 RatingItem 등의 목록을 구성할 수 있는 패키지 프로젝트(*.dpk)가 있고, 그 패키지를 사용하는 샘플 프로젝트가 있습니다.
먼저 패키지 프로젝트를 열고 프로젝트 매니저에서 설치(오른쪽 마우스 > Install) 후 샘플 프로젝트를 열어 확인하기 바랍니다.
패키지 프로젝트를 열면 ItemAppearance 속성에 새로운 항목이 추가된 것을 볼 수 있습니다.
- 엠바카데로 문서 - Customizing FireMonkey ListView Appearance
MultiDetailHorzItem
제가 직접 4개의 열을 갖는 ItemAppearance를 만들어 테스트 해 봤습니다. 기본 제공되는 MultiDetailItem을 수정했습니다.
확실히 ListBox, Grid, StringGrid에 비해 목록을 스크롤하는 속도가 빠릅니다.
unit MultiDetailHorzAppearanceU;
interface
uses FMX.ListView, FMX.ListView.Types, System.Classes, System.SysUtils,
FMX.Types, System.UITypes, FMX.MobilePreview;
type
TMultiDetailHorzAppearanceNames = class
public const
ListItem = 'MultiDetailHorzItem';
ListItemCheck = ListItem + 'ShowCheck';
ListItemDelete = ListItem + 'Delete';
Detail1 = 'det1'; // Name of MultiDetail object/data
Detail2 = 'det2';
Detail3 = 'det3';
end;
implementation
uses System.Math, System.Rtti;
type
TMultiDetailHorzItemAppearance = class(TPresetItemObjects)
public const
cTextMarginAccessory = 8;
cDefaultHeight = 40;
private
FMultiDetail1: TTextObjectAppearance;
FMultiDetail2: TTextObjectAppearance;
FMultiDetail3: TTextObjectAppearance;
procedure SetMultiDetail1(const Value: TTextObjectAppearance);
procedure SetMultiDetail2(const Value: TTextObjectAppearance);
procedure SetMultiDetail3(const Value: TTextObjectAppearance);
protected
function DefaultHeight: Integer; override;
procedure UpdateSizes; override;
function GetGroupClass: TPresetItemObjects.TGroupClass; override;
procedure SetObjectData(const AListViewItem: TListViewItem; const AIndex: string; const AValue: TValue; var AHandled: Boolean); override;
public
constructor Create; override;
destructor Destroy; override;
published
property MultiDetail1: TTextObjectAppearance read FMultiDetail1 write SetMultiDetail1;
property MultiDetail2: TTextObjectAppearance read FMultiDetail2 write SetMultiDetail2;
property MultiDetail3: TTextObjectAppearance read FMultiDetail3 write SetMultiDetail3;
property Accessory;
end;
TMultiDetailHorzDeleteAppearance = class(TMultiDetailHorzItemAppearance)
private const
cDefaultGlyph = TGlyphButtonType.Delete;
public
constructor Create; override;
published
property GlyphButton;
end;
TMultiDetailShowCheckAppearance = class(TMultiDetailHorzItemAppearance)
private const
cDefaultGlyph = TGlyphButtonType.Checkbox;
public
constructor Create; override;
published
property GlyphButton;
end;
const
cMultiDetail1Member = 'Detail1';
cMultiDetail2Member = 'Detail2';
cMultiDetail3Member = 'Detail3';
constructor TMultiDetailHorzItemAppearance.Create;
begin
inherited;
Accessory.DefaultValues.AccessoryType := TAccessoryType.More;
Accessory.DefaultValues.Visible := True;
Accessory.RestoreDefaults;
Text.DefaultValues.VertAlign := TListItemAlign.Trailing;
Text.DefaultValues.TextVertAlign := TTextAlign.Center;
Text.DefaultValues.Visible := True;
Text.RestoreDefaults;
FMultiDetail1 := TTextObjectAppearance.Create;
FMultiDetail1.Name := TMultiDetailHorzAppearanceNames.Detail1;
FMultiDetail1.DefaultValues.Assign(Text.DefaultValues); // Start with same defaults as Text object
FMultiDetail1.DefaultValues.IsDetailText := True; // Use detail font
FMultiDetail1.VertAlign := TListItemAlign.Leading;
FMultiDetail1.Align := TListItemAlign.Trailing;
FMultiDetail1.TextVertAlign := TTextAlign.Center;
FMultiDetail1.RestoreDefaults;
FMultiDetail1.OnChange := Self.ItemPropertyChange;
FMultiDetail1.Owner := Self;
FMultiDetail2 := TTextObjectAppearance.Create;
FMultiDetail2.Name := TMultiDetailHorzAppearanceNames.Detail2;
FMultiDetail2.DefaultValues.Assign(FMultiDetail1.DefaultValues); // Start with same defaults as Text object
FMultiDetail2.VertAlign := TListItemAlign.Leading;
FMultiDetail2.Align := TListItemAlign.Trailing;
FMultiDetail2.TextVertAlign := TTextAlign.Center;
FMultiDetail2.RestoreDefaults;
FMultiDetail2.OnChange := Self.ItemPropertyChange;
FMultiDetail2.Owner := Self;
FMultiDetail3 := TTextObjectAppearance.Create;
FMultiDetail3.Name := TMultiDetailHorzAppearanceNames.Detail3;
FMultiDetail3.DefaultValues.Assign(FMultiDetail2.DefaultValues); // Start with same defaults as Text object
// FMultiDetail3.DefaultValues.Height := 20; // Move text down
FMultiDetail3.VertAlign := TListItemAlign.Leading;
FMultiDetail3.Align := TListItemAlign.Trailing;
FMultiDetail3.TextVertAlign := TTextAlign.Center;
FMultiDetail3.RestoreDefaults;
FMultiDetail3.OnChange := Self.ItemPropertyChange;
FMultiDetail3.Owner := Self;
// Define livebindings members that make up MultiDetail
FMultiDetail1.DataMembers :=
TObjectAppearance.TDataMembers.Create(
TObjectAppearance.TDataMember.Create(
cMultiDetail1Member, // Displayed by LiveBindings
Format('Data["%s"]', [TMultiDetailHorzAppearanceNames.Detail1]))); // Expression to access value from TListViewItem
FMultiDetail2.DataMembers :=
TObjectAppearance.TDataMembers.Create(
TObjectAppearance.TDataMember.Create(
cMultiDetail2Member, // Displayed by LiveBindings
Format('Data["%s"]', [TMultiDetailHorzAppearanceNames.Detail2]))); // Expression to access value from TListViewItem
FMultiDetail3.DataMembers :=
TObjectAppearance.TDataMembers.Create(
TObjectAppearance.TDataMember.Create(
cMultiDetail3Member, // Displayed by LiveBindings
Format('Data["%s"]', [TMultiDetailHorzAppearanceNames.Detail3]))); // Expression to access value from TListViewItem
GlyphButton.DefaultValues.VertAlign := TListItemAlign.Center;
GlyphButton.RestoreDefaults;
// Define the appearance objects
AddObject(Text, True);
AddObject(MultiDetail1, True);
AddObject(MultiDetail2, True);
AddObject(MultiDetail3, True);
AddObject(Image, True);
AddObject(Accessory, True);
AddObject(GlyphButton, IsItemEdit); // GlyphButton is only visible when in edit mode
end;
constructor TMultiDetailHorzDeleteAppearance.Create;
begin
inherited;
GlyphButton.DefaultValues.ButtonType := cDefaultGlyph;
GlyphButton.DefaultValues.Visible := True;
GlyphButton.RestoreDefaults;
end;
constructor TMultiDetailShowCheckAppearance.Create;
begin
inherited;
GlyphButton.DefaultValues.ButtonType := cDefaultGlyph;
GlyphButton.DefaultValues.Visible := True;
GlyphButton.RestoreDefaults;
end;
function TMultiDetailHorzItemAppearance.DefaultHeight: Integer;
begin
Result := cDefaultHeight;
end;
destructor TMultiDetailHorzItemAppearance.Destroy;
begin
FMultiDetail1.Free;
FMultiDetail2.Free;
FMultiDetail3.Free;
inherited;
end;
procedure TMultiDetailHorzItemAppearance.SetMultiDetail1(
const Value: TTextObjectAppearance);
begin
FMultiDetail1.Assign(Value);
end;
procedure TMultiDetailHorzItemAppearance.SetMultiDetail2(
const Value: TTextObjectAppearance);
begin
FMultiDetail2.Assign(Value);
end;
procedure TMultiDetailHorzItemAppearance.SetMultiDetail3(
const Value: TTextObjectAppearance);
begin
FMultiDetail3.Assign(Value);
end;
procedure TMultiDetailHorzItemAppearance.SetObjectData(
const AListViewItem: TListViewItem; const AIndex: string;
const AValue: TValue; var AHandled: Boolean);
begin
inherited;
end;
function TMultiDetailHorzItemAppearance.GetGroupClass: TPresetItemObjects.TGroupClass;
begin
Result := TMultiDetailHorzItemAppearance;
end;
procedure TMultiDetailHorzItemAppearance.UpdateSizes;
const
// Total Rate = 1.0
TextWidthRate = 0.4;
Det1WidthRate = 0.2;
Det2WidthRate = 0.2;
Det3WidthRate = 0.2;
var
LOuterHeight: Single;
LOuterWidth: Single;
LInternalWidth: Single;
LImagePlaceOffset: Single;
LImageTextPlaceOffset: Single;
begin
BeginUpdate;
try
inherited;
// Update the widths and positions of renderening objects within a TListViewItem
LOuterHeight := Height - Owner.ItemSpaces.Top - Owner.ItemSpaces.Bottom;
LOuterWidth := Owner.Width - Owner.ItemSpaces.Left - Owner.ItemSpaces.Right;
Text.InternalPlaceOffset.X :=
Image.ActualPlaceOffset.X + Image.ActualWidth + LImageTextPlaceOffset;
LInternalWidth := (LOuterWidth - Text.ActualPlaceOffset.X - Accessory.ActualWidth);
if Accessory.ActualWidth > 0 then
LInternalWidth := LInternalWidth - cTextMarginAccessory;
Text.InternalWidth := Max(1, LInternalWidth * TextWidthRate);
MultiDetail1.InternalWidth := LInternalWidth * Det1WidthRate;
MultiDetail1.InternalPlaceOffset.X := Text.InternalPlaceOffset.X + Text.InternalWidth;
MultiDetail2.InternalWidth := LInternalWidth * Det2WidthRate;
MultiDetail2.InternalPlaceOffset.X := MultiDetail1.InternalPlaceOffset.X + MultiDetail1.InternalWidth;
MultiDetail3.InternalWidth := LInternalWidth * Det3WidthRate;
MultiDetail3.InternalPlaceOffset.X := MultiDetail2.InternalPlaceOffset.X + MultiDetail2.InternalWidth;
finally
EndUpdate;
end;
end;
type
TOption = TCustomListView.TRegisterAppearanceOption;
const
sThisUnit = 'MultiDetailHorzAppearanceU'; // Will be added to the uses list when appearance is used
initialization
// MultiDetailItem group
TCustomListView.RegisterAppearance(
TMultiDetailHorzItemAppearance, TMultiDetailHorzAppearanceNames.ListItem,
[TOption.Item], sThisUnit);
TCustomListView.RegisterAppearance(
TMultiDetailHorzDeleteAppearance, TMultiDetailHorzAppearanceNames.ListItemDelete,
[TOption.ItemEdit], sThisUnit);
TCustomListView.RegisterAppearance(
TMultiDetailShowCheckAppearance, TMultiDetailHorzAppearanceNames.ListItemCheck,
[TOption.ItemEdit], sThisUnit);
finalization
TCustomListView.UnregisterAppearances(
TArray.Create(
TMultiDetailHorzItemAppearance, TMultiDetailHorzDeleteAppearance,
TMultiDetailShowCheckAppearance));
end.