본문 바로가기

파이어몽키

태블릿용 멀티컬럼 리스트뷰 ItemAppearace 만들기

모바일(폰과 패드)용 목록을 만드는 컴포넌트는 대표적으로 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 속성에 새로운 항목이 추가된 것을 볼 수 있습니다.

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.