본문 바로가기

파이어몽키

[모바일앱예제] 사이드바 형태 메뉴(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가지 형태의 사이드바 메뉴를 예제와 함께 알아봤습니다. 이 예제를 통해 여러분의 앱에 사이드바 메뉴를 쉽게 추가하시길 바랍니다.

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

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





  • 민재아범 2014.07.22 10:33

    XE5에서는 오류가 많이 나내요. XE6에서 작성하신건가요? fmx파일열어보니 XE6에서 속성등이 많이 변경되었나 보내요....
    소스상에 예)
    XE6 : TApplicationEvent.FinishedLaunching: <--- ????? ,
    XE5 : TApplicationEvent.aeFinishedLaunching:

  • 민재아범 2014.07.22 10:48

    XE5로 컨버젼 소스 있음 좋겠는데 , 무리한 부탁이죠? 소스 보면서 다시 작성해야 되나.. ^^ ;; 좋은 자료 감사드립니다.

    • Favicon of https://blog.hjf.pe.kr BlogIcon 험프리.김현수 2014.07.22 13:28 신고

      내용의 흐름만 보시면 크게 어렵지 않은 내용이어서 확실히 학습하신다 생각하시고 다시 작성하시면 오히려 민재아범님에게 더큰 도움이 될것입니다.^^
      컨버팅 소스 제공하는 대신 다양한 예제 제공하는데 더 집중하겠습니다.

  • 민재아범 2014.07.23 09:19

    이것과 상관은 없는 애기인데요. 현재 저희 회사 인트로 APP을 만들예정인데 다른건 대부분 구현 가능할 것도 같은데, 구성은 DataSnap(TCP/IP) , OracleDB , XE5 등 , 비디오 재생관련해서 현재 가장 막히고 있습니다. 이것때문에 더이상 진행이 되지 않고요. 구글링 및 아무리 찾아봐도 제가 필요한 자료는 어디에도 없내요. 원격서버에 있는 비디오파일 재생하는 방법 좀 알려주십시요. 제가 알고 있는 방법은
    1) ftp 및 tcp 및 http(s) 통신(Protocol)을 통한 파일 다운로드 받은 후 MediaPlay로 처리 : 파일 사이즈가 클경우 기다려야 하는 단점
    - 과연 소비자들이 기다릴지 ...
    2) http(웹브라우져) Protocol를 통한 처리 : 플랫폼의 웹브라우져에 의존이 된다는 점 : URL이 오픈되어 아이피등 노출

    * 요청드리는 구현은 : Datasnap을 통한 (Protocol : TCP , Http(s) , Etc ) App에서 비디오 영상을 재생

    • Favicon of https://blog.hjf.pe.kr BlogIcon 험프리.김현수 2014.07.23 09:40 신고

      (주제와 관련 없는 질문은 방명록에 남겨주시기 바랍니다.)

      다음 글을 참고하시면 도움이 되실 것입니다.
      http://www.fmxexpress.com/video-streaming-using-motion-jpeg-with-delphi-xe5-firemonkey-on-windows-and-ios/
      (정석으로 영상처리하게 되면 대단히 어려워 지므로 JPEG으로 변환해 모바일에서 재생하는 방법입니다. 단, 음성이 있을 경우 동기화하는 작업이 필요합니다. 쉽지는 않네요^^)

      만약 만족하지 못하셨다면 구글에서 "Firemonkey video streaming"으로 검색하시면 다양한 자료가 검색됩니다.

  • cider 2014.08.13 18:36

    문의드립니다..
    FMX-master\MobileUserInterface\SwipeMenuDemo 의 SwipeMenuDemo 파일을 보고있습니다..
    소스수정전혀하지않고, 폼화면에
    lytMain-lytPage에 button 컴포넌트를 한개 올려놓고 click 이벤트를 넣으려고하면,
    " Cannot find implementation of method Button1Click." 이라고 Error메시지가 뜹니다.
    소스안으로 들어가보면
    pprocedure TForm1.Button1Click(Sender: TObject);
    begin
    end;
    rocedure TForm1.btnMenuClick(Sender: TObject);
    형태는 이렇게 되어있습니다..

    위에서 P지우고 아래서 P붙여주면 소스상에서 수정하면 오류는 나지않습니다. 하지만 tbTitle 에 speedbutton을 붙이구, 이벤트를 넣어도 오류가 나서,. 이게 어떤이유에서 그런지 정확히 모르겠습니다.

    답변부탁드립니다.^^

    • Favicon of https://blog.hjf.pe.kr BlogIcon 험프리.김현수 2014.08.20 10:57 신고

      해당 이슈를 테스트 해봤는데요.
      HEX 에디터로 파일을 열면 $0A$0D$0A 으로 carriage return, line feed 순으로 나오지 않고 라인피드가 먼저나오는 경우 발생합니다.
      해결책은 메모장을 열고 음표모양(?)을 찾아 삭제하고 저장하면 해결됩니다. 해당 이슈는 제가 정리해서 글로 남기도록 하겠습니다.

      원인은 Git or SVN으로 소스코드를 받을 때 발생하는 것으로 의심되지만 정확하지는 않습니다.

      의견 감사합니다.

    • Favicon of https://blog.hjf.pe.kr BlogIcon 험프리.김현수 2015.12.30 14:57 신고

      GitHub에서 내려받은 코드가 간혹 포맷이 이상해지네요.

      이때 Format Source(Edit > Format Source: Ctrl + D) 실행하면 정상적으로 진행됩니다.

  • 굥굥 2014.09.21 16:20

    안녕하세요! 델파이 모바일 개발 초보 굥굥입니다.
    XE7 을 이용해 따라해보려고 하는데, 사이드바헬퍼라는 컴포넌트가 안보이는데 혹시 다른 이름으로 바뀌었나요?

    • Favicon of https://blog.hjf.pe.kr BlogIcon 험프리.김현수 2014.09.22 09:04 신고

      SideBarHelper는 컴포넌트가 아니라 lytSideBarHelper라는 이름의 TLayout 컴포넌트입니다. 스트럭쳐 창을 잘 보시면 컴포넌트 발견하실 수 있으실 거에요.
      그리고 XE7에서는 TMultiView 컴포넌트에서 해당 작업을 대부분 할 수 있습니다. 참고하세요.

  • QU 2014.09.26 14:53

    문의드립니다.
    lstSidebarMenu 메뉴리스트박스에서 동적으로 메뉴가 생성되고, 처음으로 생성되는 search demo 를 누르면 SearchFrame.pas 를 호출해서 화면을 보여주는형태로 소스가 구성되어있는데,

    저는 MainForm.pas에 ltyPage에 버튼컴포넌트 한개올리고, 버튼을누르면 lstSidebarMenu 메뉴리스트박스에서
    search demo 를 누르는것과 동일하게 SearchFrame.pas 를 호출해서 화면을 보여주는 이벤트를 넣고싶어서,
    이벤트를 클릭에 아래와같은 소스를 추가했습니다...

    procedure TForm1.Button1Click(Sender: TObject);
    begin
    //
    lstSidebarMenuItemClick(lstSidebarMenu,TlistboxItem(lstSidebarMenu.Items[0]));
    end;
    소스를 추가하면 " invalid class typecast " error가 나오는데,,,, 해결방법을잘모르겠습니다.
    혹시 방법을 알고계시다면 답변부탁드립니다 ㅠ_ㅜ...

  • buzz 2015.04.10 12:09

    예제에서 개인사진 디스플레이용으로 TCircle 을 사용하셨는데 스샷에서 보듯이
    외곽선이 깔끔하지 않고 계단 현상 처럼 나옵니다.(폰에서 볼때 거슬릴 정도로 나오네요)
    다른 이미지 파일이나 Solid 컬러 적용해도 마찬 가지로 외곽선이 지저분 하게 나옵니다.
    폰 특성인가 싶어 자바로 이미지를 원형 처리 하견 깔끔하게 나오거든요....
    http://xe5firemonkey.blogspot.tw/2014/09/firemonkey-how-to-enable-anti-aliasing.html
    에서 위에거 처럼 계단 현상이 나오는데 이거 어떻게 해결 방법이 없나요?