본문 바로가기

파이어몽키

[모바일앱예제] 사이드바 형태 메뉴(Sidebar drawer menu) 만들기

☜ 목록으로 돌아가기

시작하기에 앞서

이 글은 처음부터 기능을 따라하며 만드는 것 보다는 제공되는 예제코드를 참고해 기능을 익히도록 설명되어 있습니다. 


예제를 통해 기능을 완전히 익히신 후 새로운 프로젝트에 기능을 떼어 붙이며 본인 것으로 만들면 더 좋습니다.

그럼 시작하겠습니다.

데스크탑 어플리케이션에서는 메뉴가 필요하면 메인메뉴나 팝업메뉴 형태로 제공했습니다. 하지만 모바일에서는 데스크탑 어플리케이션처럼 일반화된 메뉴 형식이 정해지지 않아 각각의 앱에서 필요한 형태로 메뉴를 구현해 사용하는 것이 일반적입니다.
이번 글은 모바일 앱에서 자주 사용되는 사이드바 형태의 메뉴를 파이어몽키를 통해 개발하는 예제에 대해 알아봅니다.
예제에서는 아래와 같이 두가지 형태의 사이드바 예제를 제공합니다.

사이드바가 나오는 형태

DrawerType1Demo

메인패널이 옆으로 밀리는 형태 

DrawerType2Demo

 


소스코드


예제의 설명은 두개의 사이드바 예제가 대부분 동일하기 때문에 하나에 대한 설명을 하고, 다른 예제는 차이점을 위주로만 설명하겠습니다.

사이드바가 나오는 형태

사이드바가 메인화면 위로 나오는 형태의 예제(DrawerType1Demo)입니다. 

다음 순서로 설명합니다.

  1. 화면(UI)구성
  2. 구동 시 컨트롤 초기화 코드 구현
  3. 사이드바 동작 구현

❑ 화면구성


예제의 화면구성은 메인화면과 사이드바 그리고 사이드바 헬퍼(lytSidebarHelper)로 구성됩니다.

메인화면(lytMain)

메인화면은 여러분이 필요한 방식으로 자유롭게 구성하면 됩니다. 예제에서는 툴바(상단)에 메뉴버튼(좌측)을 두어 사이드바를 표시하고 감추는 역할을 합니다.

사이드바(lytSidebar)

사이드바는 일반적으로 메뉴를 구성하기 위해 사용됩니다. 예제에서는 상단에 프로필 정보와 아래에 메뉴 목록을 ListBox를 이용해 구성했습니다.


ListBox 메뉴는 메뉴만 두는 것보다는 그룹으로 구성하면 같은 종류의 메뉴에 대한 접근성이 높습니다. 그룹은 TListBoxGroupHeader를 사용하면 됩니다. ListBox 구성하는 방법은 다음 링크르 통해 자세히 알아보시기 바랍니다.

메뉴 아이템은 아이콘을 이용해  글자만 제공하는 것 보다 직관적입니다.아이콘 지정은 ListBoxItem의 ItemData.Bitmap 항목을 통해 등록할 수 있습니다.


TIP. 메뉴 아이콘 이미지 파일 포멧은 PNG로 준비합니다.

PNG(투명 배경이미지)로 메뉴 아이콘을 사용하면 메뉴 아이템의 배경이 변경되어도 아이콘의 배경색을 바꾸기 위해 아이콘 디자인 작업을 다시하지 않아도 됩니다.


TIP. 메뉴 아이콘의 크기 조정

메뉴 아이템에 아이콘을 등록하면 기본 크기로 다소 큼지막하게 등록이 됩니다. 제가 몇가지 크기로 등록해 보니 40 x 40의 크기가 배포 후 폰에서 볼때 적당한 크기 같습니다. 아래의 아이콘 크기를 조정하는 방법으로 여러분의 스타일에 맞게 아이콘 크기를 조정해 등록하기 바랍니다.


아이콘 크기 조절하기

  1. ListBoxItem의 ItemData.Bitmap 속성을 더블클릭 해서 Bitmap Editor를 호출
  2. Resize 버튼 클릭해 크기조절 컨트롤 호출
  3. 가로, 세로 크기를 입력하고 OK 버튼을 눌러 아이콘 크기를 변경
단, 한번 작은 크기로 조절 후 다시 크게 늘리면 작은 이미지가 늘어나 해상도가 깨지므로 큰 이미지를 작은 크기로 조절합니다. 만약 크기를 늘려야할 필요가 있다면 원본이미지를 다시 불러와 줄이는 방식으로 진행해야합니다.


사이드바 헬퍼(lytSidebarHelper)

사이드바 헬퍼는 사이드바가 표시된 상태에서 사이드바 이외의 영역을 터치(클릭)하면 사이드바가 닫히도록 하는 역할을 하는 레이어입니다.

사이드바가 표시될 때 사이드바 헬퍼는 나머지 영역을 덮어 아무 영역이나 터치 시 클릭이벤트를 이용해 사이드바가 닫히도록 합니다.

사이드바 헬퍼를 구성할때 Z-Order(같은 자식간의 정렬순서, 즉 어떤 컨트롤이 위에 표시될지 여부)를 이용해 사이드바가 사이드바 헬퍼의 위에 위차하도록 해야합니다.


Structure 창에 보이는 것과 같이 Form1에는 3개의 자식(lytMain, lytSidebar, lytSidebarHelpr)을 가지고 있습니다.

사이드바를 표시하고 사이드바 헬퍼를 구성하기 위해서는 제일 아래에 lytMain을 두고 그위에 lytSidebarHelper, 제일 위에 lytSidebar를 구성해야만 사이드바의 메뉴를 클릭하고, 사이드바 헬퍼를 클릭할 때 사이드바가 닫히는 클릭이벤트가 발생합니다.


TIP. Z-Order 조정하는 방법

디자인타임에서 지정하는 방법

폼 및 컨트롤 하위에(자식으로) 컨트롤을 추가할때 나중에 추가된 컨트롤이 더 앞쪽의 Z-Order를 갖습니다.

이미 추가된 컨트롤의 경우 컨트롤의 팝업 메뉴 또는 Structure 창의 컨트롤들의 팝업 메뉴 중 Control 메뉴를 통해 제일 앞으로, 제일 뒤로 Z-Order를 변경할 수 있습니다.


소스코드에서 지정하는 방법

소스코드 상에서는 컨트롤들의 SendToBack, BringToFront 메소드를 이용해 동적으로 Z-Order를 변경할 수 있습니다.

이 예제의 구동 시에도 사이드바 헬퍼와 사이드바의 Z-Order를 소스를 통해 조정합니다.


❑ 구동 시 컨트롤 초기화

디자인 타임에서 구성한 사이드바와 사이드바 헬퍼의 위치, 크기, 정렬등의 속성은 구동시 설정됩니다.

특히 사이드바의 너비와 같이 화면의 비율과 연관이 있는 컨트롤의 경우 다양한 기기의 화면크기에 맞춰야만 보기가 좋기 때문에 구동 시 기기의 해상도에 맞춰 설정하는 것이 좋습니다.

procedure TForm1.InitControls;
begin
  // 사이드바의 너비는 스마트폰 너비의 70% 사용(최대 280)
  lytSidebar.Width := Max(Self.Width * 0.7, 280);
  lytSidebar.Height := ClientHeight - tbTitle.Height;

  // 사이드바의 초기값 설정
  lytSidebar.Position.Point := PointF(-lytSidebar.Width, tbTitle.Height);

  // SidebarHelper는 사이드바외의 다른 영역을 누르면 닫히도록 하기위한 용돈
  lytSidebarHelper.Align := TAlignLayout.Contents;
  lytSidebarHelper.Visible := False;

  lytSidebarHelper.BringToFront;
  lytSidebar.BringToFront;
end;

위의 소스코드와 같이 초기 구동 시 InitControls 메소드에서 사이드바 컨트롤들의 초기값을 지정합니다.

사이드바의 너비(lytSidebar.Width)는 모바일 기기의 70%에 맞추되 최대 280으로 제한합니다.(70%와 280등 값은 여러분 메뉴의 길이에 맞게 조정해서 사용하세요.) 

사이드바의 높이는 전체 높이 중 툴바의 높이를 제외하고 표시되도록 설정합니다.


다음으로 사이드바의 초기 위치를 지정합니다. 사이드바의 X 좌표는 사이드바가 완전히 감춰지기 위해 사이드바의 너비만큼 음수좌표에 두고, Y좌표는 툴바 바로 아래에 위치하도록 툴바의 높이로 지정합니다.

사이드바 헬퍼의 경우 디자인 시에는 다른 컨트롤들을 제어하기위해 작게 위치했지만, 구동 시 폼의 전체화면으로 지정하고, 평상시에는 화면에 감춰놓습니다.

마지막으로 BringToFront 함수로 메인화면 위에 사이드바헬퍼를 사이드바를 제일 위로 설정합니다.


InitControls 함수는 앱이 구동 시 실행되어야 합니다. 앱의 구동시라고 하면 FormCreate와 FormShow가 있는데 해당 이벤트에서 컨트롤과 데이터 처리하는 루틴을 포함하게 되면 앱의 구동시간에 영향을 줍니다. 이 예제에서는 앱의 이벤트 핸들러를 등록해 앱이 구동된 이후 처리하도록 구현되어 있으며 자세한 내용은 모바일 앱 라이프 사이클 이벤트 처리하기 글을 통해 확인하시길 바랍니다.

procedure TForm1.FormCreate(Sender: TObject);
var
  EventService: IFMXApplicationEventService;
begin
  FInit := False;
  if TPlatformServices.Current.SupportsPlatformService(IFMXApplicationEventService, IInterface(EventService)) then
    EventService.SetApplicationEventHandler(HandleAppEvent)
  else
    InitData;
end;

function TForm1.HandleAppEvent(AAppEvent: TApplicationEvent;
  AContext: TObject): Boolean;
begin
  case AAppEvent of
    TApplicationEvent.FinishedLaunching:
        InitData;
    TApplicationEvent.BecameActive:
        InitData;
  end;
  Result := True;
end;

❑ 사이드바 동작구현

procedure TForm1.btnMenuClick(Sender: TObject);
begin
  ShowMenu := not ShowMenu;
end;

procedure TForm1.SetShowMenu(const Value: Boolean);
begin
  FShowMenu := Value;

  if FShowMenu then
  begin
    lytSidebar.AnimateFloat('Position.X', 0);
    lytSidebarHelper.Visible := True;
  end
  else
  begin
    lytSidebar.AnimateFloat('Position.X', -lytSidebar.Width);
    lytSidebarHelper.Visible := False;
  end;
end;

procedure TForm1.lytSidebarHelperClick(Sender: TObject);
begin
  ShowMenu := False;
end;

상단의 메뉴버튼을 누르면 사이드바를 보이고 감추는 역할(ShowMenu)을 합니다.

사이드바가 보여져야 할때는 사이드바의 가로좌표를 0으로 맞추고 감춰져야 할때는 사이드바의 너비만큼 음수좌표를 주어 화면에서 완전히 감추도록 구현합니다.

사이드바의 X 좌표 전환 시 애니메이션을 활용해 자연스러운 UI를 구성합니다.

TIP. 애니메이션 사용방법

컴포넌트를 이용하는 방법

  • 컨트롤 하위에 애니메이션 컴포넌트를 추가해 애니메이션을 사용할 수 있습니다.
  • 엠바카데로의 도움말을 통해 더 알아보실 수 있습니다.


소스코드를 이용하는 방법

  • 컨트롤의 AnimationXXX 메소드를 이용해 소스상에서 애니메이션을 사용할 수 있습니다.
  • 엠바카데로의 도움말을 통해 AnimationFloat 메소드에 대해 더 알아볼 수 있습니다.

메인패널이 옆으로 밀리는 형태

메인패널이 옆으로 밀리며 사이드바가 표시되는 형태의 예제(DrawerType2Demo)입니다. 

이 예제는 대부분 앞의 예제와 동일합니다. 항목별로 달라지는 부분만 설명합니다.

❑ 화면구성

메인화면을 구성하고, 사이드바를 만들고 사이드바 헬퍼를 사용하는 것은 동일하지만, 컨트롤의 계층구조를 다르게 구성해야 합니다..

컨트롤의 최종 구조는 사이드바(lytSidebar)가 앱의 전체화면을 덮도록 하고, 그 위에 메인화면(pnlMain)을 전체적으로 구성해 덮는 방식으로 구성됩니다.

사이드바를 표시하는 요청이 있으면 메인화면을 옆으로 이동해 사이드바를 보여줍니다.

그리고, 메인화면의 경우 이전 예제에서는 레이아웃(TLayout)을 사용했는데 레이아웃의 경우 뒷쪽의 컨트롤들이 비춰지기 때문에 패널(TPanel)로 합니다.

사이드바 헬퍼도 사이드바가 표시되면 메인화면을 누르면 사이드바가 닫히도록 해야 하므로 메인화면 위(자식으로)에 사이드바 핼퍼를 위치하도록 구성했습니다.

❑ 구동 시 컨트롤 초기화

procedure TForm1.InitControls;
begin
  // 메뉴의 너비는 스마트폰 너비의 70% 사용(최대 280)
  FDrawerWidth := Max(Self.Width * 0.7, 280);

  // 사이드바는 뒷쪽에서 화면을 가득채워야한다.
  lytSidebar.Align := TAlignLayout.Client;

  // SidebarHelper는 사이드바외의 다른 영역을 누르면 닫히도록 하기위한 용돈
  lytSidebarHelper.Align := TAlignLayout.Contents;
  lytSidebarHelper.Visible := False;

  // 사이드바가 메인화면 뒤에 있어야 한다.
  lytSidebar.SendToBack;
  pnlMain.BringToFront;
end;

FDrawerWidth는 사이드바가 표시되는 너비입니다. 즉, 메인화면이 옆으로 이동되는 거리입니다. 

사이드바는 전체화면으로 구성합니다. 사이드바 헬퍼도 메인화면에 가득차도록 합니다.

계층구조는 사이드바를 뒤로보내고 메인화면을 앞으로 가져오도록 합니다.

❑ 사이드바 동작구현

procedure TForm1.SetShowMenu(const Value: Boolean);
begin
  FShowMenu := Value;

  if FShowMenu then
  begin
    pnlMain.AnimateFloat('Position.X', FDrawerWidth);
    lytSidebarHelper.Visible := True;
  end
  else
  begin
    pnlMain.AnimateFloat('Position.X', 0);
    lytSidebarHelper.Visible := False;
  end;
end;

사이드바가 표시되고 감춰지기 위해서는 이전 예제에서는 Sidebar의 가록 위치를 변경했지만, 이번에는 메인화면의 가로 위치를 변경하도록 합니다. 

사이드바가 보여지기 위해서 메인화면의 X좌표를 FDrawerWidth로 설정하고 감춰질때는 0좌표로 이동하도록 처리합니다.

마치며

2가지 형태의 사이드바 메뉴를 예제와 함께 알아봤습니다. 이 예제를 통해 여러분의 앱에 사이드바 메뉴를 쉽게 추가하시길 바랍니다.

더불어 이 예제는 사이드바 메뉴를 구성하는 방식을 설명했지만 그 안에 리스트를 구성하는 방법, 애니메이션 사용법, 컨트롤간의 계층구조와 변경방법등이 다양하게 녹여넣었습니다.

이러한 기술들을 익히셔서 메뉴뿐 아니라 필요한 컨트롤들을 기본 컨트롤들을 조합해 구현하시는데 도움이 되었으면 좋겠습니다.