[따라하기] 건강데이터 수집 및 기록 시스템 #1 - BLE 기반 스마트 체중계에서 실시간 데이터 받기

2015.10.02 13:06


위 동영상(건강데이터 수집 및 기록 시스템 데모) 중 스마트 체중계의 체중정보를 모바일에서 실시간으로 받아오는 기능을 따라하기로 진행합니다.


스마트 체중계에서 체중정보 수신앱 만들기

❑ 앱소개

앱소개

이 앱은 스마트체중계와 블루투스 LE로 연결 해 체중정보를  실시간으로 받아와 화면에 표시하는 앱입니다.

따라하기의 목적

스마트 체중계 연결을 통해 블루투스 LE 인터페이스를 제공하는 다양한 기기(또는 센서)에 연결하고 데이터를 수신하는 방법을 이해합니다.


따라하기에서는 다음 내용을 다룹니다.

  • RAD Studio로 멀티-디바이스(안드로이드, iOS, 윈도우, OS X) 앱 프로젝트 생성 및 화면 디자인
  • TBluetoothLE 컴포넌트로 스마트 체중계와 연결 및 데이터 수신(구독)
  • 실 기기에 배포 및 테스트(안드로이드 폰, 아이폰)

따라하기

0, 사전 준비사항

1, 빈 멀티-디바이스 애플리케이션 프로젝트 생성

2, 메인 UI 제작

3, 블루투스LE 컴포넌트 추가

4, 주변의 디바이스 탐색

5, 탐색된 디바이스 목록에서 디바이스 선택 후 서비스 탐색

6, 탐색된 서비스 목록에서 서비스를 선택 후 특성 구독

7, 특성 데이터 수신 후 체중 데이터 가져오기

8, 블루투스 권한설정 

9, 디바이스에 배포 및 테스트

0, 사전 준비사항

개발도구 준비하기

델파이(또는 RAD Studio)를 이용해 실습 진행합니다.(이 실습은 델파이 10 시애틀 버전으로 작성되었습니다. 델파이 XE8 이상에서 따라할 수 있습니다.) 

만약, 델파이가 설치되지 않았다면 아래의 페이지에서 델파이를 다운로드 받아 설치 후 진행하시기 바랍니다. 

스마트 체중계 준비하기

따라하기에서는 Wahoo Balance Bluetooth Smart Scale 제품을 이용해 실습을 진행합니다.

https://www.underarmour.com/en-us/wahoo-balance-bluetooth-smart-scale/pid1276806-100-OSFA


Wahoo Balance Bluetooth Smart Scale은 다음과 같은 디바이스 이름과 UUID를 갖습니다.

디바이스 이름 : Wahoo Scale v1.3

서비스 UUID : 00001901-0000-1000-8000-00805F9B34FB

특성 UUID : 00002B01-0000-1000-8000-00805F9B34FB

위 정보는 구현에 필요한 정보입니다.(시중의 다른 스마트 체중계는 위 값들이 다릅니다.)

BLE 구조에 대한 소개

블루투스 LE는 프로파일(Profile), 서비스(Service), 특성(Characteristic) 기반으로 아래 그림과 같이 프로파일은 하나 이상의 서비스를 갖고, 서비스는 하나이상의 특성을 갖는 계층구조로 구성되어 있습니다.



(출처: GENERIC ATTRIBUTE PROFILE (GATT) - https://developer.bluetooth.org/TechnologyOverview/Pages/GATT.aspx)


블루투스 LE 인터페이스를 제공하는 장비와 연결하고 데이터를 받기 위해 다음 과정이 필요합니다.(실제 구현도 다음 단계대로 진행합니다.)

1, 주변의 블루투스 LE 프로파일을 탐색합니다.

2, 탐색된 프로파일에서 서비스를 탐색합니다.

3, 탐색된 서비스에서 특성을 찾아 구독(subscribe)합니다.

4, 구독한 특성에서 발생되는 특성값에서 필요한 데이터를 추출합니다.

1, 빈 멀티-디바이스 애플리케이션 프로젝트 생성

  1. File > New > Multi-Device Application - Delphi 메뉴를 선택 하고, Blank Application을 선택해 프로젝트를 생성합니다.
  2. File > Save all 메뉴를 선택 해 유닛이름을 "MainForm.pas"로 프로젝트 이름을 "SmartScale.dproj"로 저장합니다.

2, 메인 UI를 제작

모바일 앱 스타일로 화면 구성하기 위해 폼디자이너 스타일을 Android로 변경합니다.


다음화면과 표를 이용해 체중정보 수신 앱 메인 UI를 개발합니다.


상위 오브젝트 

오브젝트 

속성 

값(또는 설명) 

 Form1

 ToolBar1

 

 

 Layout1

 Align

 Top
 Height 81
 Memo1

 Align

 Client

 ToolBar1

 Label1

 Align

 Client

 Text

 스마트 체중계 데모

 TextSettings.HorzAlign

 Center

 Switch1 Align Right
 Layout1 Text1

 Align

 Center

 Text

 0.0
 TextSettings.Font.Size

 30

 TextSettings.HorzAlign Trailing
 Width 114
 Text1 Text2 Position.X 120
 Text Kg

 TextSettings.HorzAlign

 Leading

3, 블루투스 LE 컴포넌트 추가

블루투스 LE 컴포넌트 추가

Tool Palette에서 TBluetoothLE 컴포넌트 선택 해 폼에 추가합니다.


필요한 변수, 메소드, 상수 추가

구현에 필요한 변수(디바이스, 서비스, 특성), 메소드 선언(시작, 중지), 상수(장비명과 서비스, 특성 UUID)를 아래와 같이 추가합니다.

  private
    { Private declarations }
    FBLEDevice: TBluetoothLEDevice;
    FBLEGattService: TBluetoothGattService;
    FBLEGattChar: TBluetoothGattCharacteristic;

    procedure StartScale; // 체중계 연결 동작 시작(블루투스LE 활성화, 주변 디바이스 탐색)
    procedure StopScale; // 체중계 연결 동작 중지(블루투스LE 비활성화, 특성 구독 해제)
  public
    { Public declarations }
  end;

const
  ScaleDeviceName = 'Wahoo';

  WEIGHT_SERVICE: TBluetoothUUID          = '{00001901-0000-1000-8000-00805F9B34FB}';
  WEIGHT_CHARACTERISTIC: TBluetoothUUID   = '{00002B01-0000-1000-8000-00805F9B34FB}';

메소드 구현부 생성

StartScale, StopScale 메소드 구현부를 자동 생성하기 위해 procedure StartScale;에 마우스 커서를 옮기고 키보드에서 Ctrl + Shift + C를 동시에 누릅니다.


4, 주변의 디바이스 탐색

주변 디바이스 탐색

Switch1의 OnSwitch 이벤트 핸드러 생성(Switch1 컴포넌트 선택 > Object Inspector 창에서 Event 탭 선택 > OnSwitch 우측의 콤보박스 더블클릭) 후 아래 코드를 입력합니다.

procedure TForm1.Switch1Switch(Sender: TObject);
begin
  if Switch1.IsChecked then
    StartScale
  else
    StopScale;
end;

StartScale 메소드에서 블루투스 LE를 활성화하고, 주변 디바이스를 탐색하는 코드를 다음과 같이 입력합니다.
(StopScale은 제일 마지막에 다시 구현합니다.)

procedure TForm1.StartScale;
begin
  BluetoothLE1.Enabled := True;
  BluetoothLE1.DiscoverDevices(1000); // 1초(1000 ms)동안 주변 디바이스 탐색, 완료 시 OnEndDiscoveryDevices 이벤트 발생
end;

5, 탐색된 디바이스 목록에서 디바이스 선택 후 서비스 탐색

StartScale 메소드의 DiscoverDevices 메소드 호출하면 (주변의 디바이스 탐색 후) OnEndDiscoverDevices 이벤트가 발생합니다. 

OnEndDiscoverDevices 이벤트는 파라메터로 탐색된 디바이스 목록(ADeviceList)을 제공합니다.

이 디바이스 목록에서 'Wahoo'(위에서 ScaleDeviceName 상수로 선언)로 시작하는 디바이스를 선택합니다.

디바이스를 선택했다면, 디바이스에서 제공하는 서비스 탐색을 시도합니다.

procedure TForm1.BluetoothLE1EndDiscoverDevices(const Sender: TObject;
  const ADeviceList: TBluetoothLEDeviceList);
var
  Device: TBluetoothLEDevice;
begin
  if ADeviceList.Count = 0 then
  begin
    Memo1.Lines.Add('발견된 디바이스가 없습니다.');
    Switch1.IsChecked := False; // 디바이스 미발견 시 스위치 끄기
    Exit;
  end;

  FBLEDevice := nil;
  Memo1.Lines.Add('Device List');
  for Device in ADeviceList do
  begin
    Memo1.Lines.Add(' - ' + Device.DeviceName); // 로그-디바이스 이름

    // 디바이스 이름으로 디바이스 선택
    if Device.DeviceName.StartsWith(ScaleDeviceName) then
    begin
      FBLEDevice := Device;
      Memo1.Lines.Add('디바이스를 찾았습니다.');
    end;
  end;

  if FBLEDevice = nil then
    Exit;

  // [TIP] 서비스 발견이 실패하는 경우가 종종 있음
    // 실패한 경우 한번더 발견요청
  if not FBLEDevice.DiscoverServices then
    FBLEDevice.DiscoverServices; // 서비스 탐색 완료 시 OnEndDiscoverServices 이벤트 발생
end;

6, 탐색된 서비스 목록에서 서비스를 선택 후 특성 구독

서비스 탐색(DiscoverServices)이 완료되면 OnEndDiscoverServices 이벤트가 발생합니다.

OnEndDiscoverServices 이벤트는 파라메터로 탐색된 서비스 목록(AServiceList)을 제공합니다.

이 서비스 목록에서 서비스 UUID와 같은 UUID를 갖는 서비스를 선택합니다.

선택한 서비스에서 특성 UUID로 특성을 찾습니다.

특성에서 체중정보를 포함한 특성데이터를 수신할 수 있도록 구독(SubscribeToCharacteristic) 합니다.

procedure TForm1.BluetoothLE1EndDiscoverServices(const Sender: TObject;
  const AServiceList: TBluetoothGattServiceList);
var
  Service: TBluetoothGattService;
begin
  Memo1.Lines.Add('Service List');
  for Service in AServiceList do
  begin
    Memo1.Lines.Add(' - ' + Service.UUID.ToString); // 로그-디바이스 이름
    if Service.UUID = WEIGHT_SERVICE then
    begin
      FBLEGattService := Service;
      Memo1.Lines.Add('서비스를 찾았습니다.');
    end;
  end;

  if FBLEGattService = nil then
  begin
    Memo1.Lines.Add('서비스를 찾지 못했습니다.');
    Exit;
  end;

  // [TIP][예외] 특성목록을 생성하기 위해 아래 코드 호출
    // GetCharacteristic 바로 호출 시 특성을 가져오지 못해
    // GetCharacteristics을 먼저 호출
  BluetoothLE1.GetCharacteristics(FBLEGattService);

  FBLEGattChar := BluetoothLE1.GetCharacteristic(FBLEGattService, WEIGHT_CHARACTERISTIC);
  if FBLEGattChar = nil then
  begin
    Memo1.Lines.Add('서비스 특성을 찾지 못했습니다.');
    Exit;
  end;

  if BluetoothLE1.SubscribeToCharacteristic(FBLEDevice, FBLEGattChar) then // 구독하면 OnCharacteristicRead 이벤트를 통해 특성 값을 받음
    Memo1.Lines.Add('특성에 구독했습니다.'); 
end;

7, 특성 데이터 수신 후 체중 데이터 가져오기

특성에 구독(SubscribeToCharacteristic)하면 TBluetooth 컴포넌트의 OnCharacteristicRead 이벤트로 특성 값을 받을 수 있습니다. 스마트 체중계의 경우 체중계의 체중이 변경될 때마다 특성 값을 받을 수 있습니다.

Wahoo Balance Blueeoth Smart Scale은 다음과 같은 코드로 특성값에서 체중 정보를 가져옵니다.

(특성 값을 분석하는 방법은 서비스마다 다릅니다.)

procedure TForm1.BluetoothLE1CharacteristicRead(const Sender: TObject;
  const ACharacteristic: TBluetoothGattCharacteristic;
  AGattStatus: TBluetoothGattStatus);
var
  Weight: Single;
begin
  Weight := (ACharacteristic.GetValueAsInteger shr 8) / 10;
    // 특성 값에서 앞의 3byte 이용, 마지막 숫자는 소숫점이므로 10으로 나누기

  Text1.Text := Format('%3.1f', [Weight]);
end;

마지막으로 앞에서 선언한 StopScale에 특성 구독을 해지하고, 블루투스 LE를 비활성화하는 코드를 추가합니다.

procedure TForm1.StopScale;
begin
  if Assigned(FBLEDevice) and Assigned(FBLEGattChar) then
    BluetoothLE1.UnSubscribeToCharacteristic(FBLEDevice, FBLEGattChar);
  BluetoothLE1.Enabled := False;
end;

8, 블루투스 권한설정

(이 과정은 안드로이드 플랫폼을 사용하지 않는 경우 생략할 수 있습니다.)

비콘은 블루투스 LE(Low Energy) 기반으로 통신합니다.  블루투스 사용권한을 설정합니다.

Project > Options 메뉴를 선택 후 Uses Permissions 화면으로 이동합니다.


Bluetooth와 Bluetooth admin 두개의 권한을 사용하도록 설정합니다.


9, 디바이스에 배포 및 테스트

처음 디바이스에 배포하는 경우 먼저 모바일 개발환경 설정하고 진행하기 바랍니다.


개발을 완료했습니다. 이제 준비된 개발환경으로 플랫폼 선택 후 Run > Run Without Debugging 메뉴를 이용해 실행하고 테스트 합니다.



테스트 방법:

  1. 디바이스의 블루투스 기능을 킵니다.
  2. 앱을 실행 후 스위치를 커서 주변의 블루투스 LE를 탐색해 스마트 체중계와 연결합니다.
    (메모 컨트롤에 "디바이스를 찾았습니다." > "서비스를 찾았습니다." > "특성에 구독했습니다." 메시지가 표시되면 정상 연결된 것입니다.)
  3. 체중계에 올라가면 실시간으로 앱에 체중정보가 표시되는 것을 확인합니다.

저작자 표시 비영리 동일 조건 변경 허락
신고
크리에이티브 커먼즈 라이선스
Creative Commons License

험프리.김현수 험프리.김현수 Firemonkey/모바일 앱 예제

  1. Blog Icon
    이승욱

    안녕하세요.
    지난 9월에 세미나를 교수님과 다녀온 후에 학과에서 간단하게 시연해보려고 계속 작업중에 있습니다. 비콘을 이용한 경보 앱은 구현을 하고 이번에는 체중계 앱을 작업하고 있는데 문제가 있어 조언을 얻고자 글을 남기게 되었습니다.
    기존에 포스팅 하신데로 모두 설정하였고 UUID도 맞게 입력이 되었는데 컴파일 하고 연결하려고 하면 디바이스가 1개 검색이 되지만 weight_service를 불러오지 못한다고 합니다.
    현재 작업하는 디바이스는 탭이고 개별로 블루투스로 연결해볼까 해서 연결하면 Wahoo Scale v1.3 에서 연결을 거절했다는 메시지가 뜨네요...
    답변해주시면 감사하겠습니다.

  2. 디바이스 발견 시 본문의 화면과 같이 Wahoo Scale v1.3으로 표시되나요?
    쳬중계를 연결할때 미리 체중계에 올라가 체중계를 키고 시도해 보세요.

    그리고, 혹시 모르니 다른 안드로이드 기기로도 테스트해보시기 바랍니다.
    (그럴리 없지만 체중계의 이상을 확인하려면 아이폰에서 Wahoo Wellness 앱을 설치해 연결을 확인해보세요. 무료입니다.)

    그래도 안되면 다음 링크에 데모 샘플코드가 있으니 해당 소스코드로 테스트해보시기 바랍니다.
    https://github.com/devgear/RADStudio_IoTDemos

    마지막으로 시간되시면 제가 사물인터넷 1일 과정 교육을 진행하고 있습니다.
    해당 교육에서 스마트 체중계 연결 실습도 진행되니 오프라인으로 확인하고 싶으시면 교육신청해주세요.^^
    http://devgear.co.kr/support/education/radstudio_iot.html

  3. Blog Icon
    김헌성

    안녕하세요~

    블루투스 저울로 스마트폰 앱을 만드는것을 공부하고 있습니다.

    혹시 체중계별로 UUID가 다르다고 하셨는데요..

    Wahoo Balance Bluetooth Smart Scale 이제품의 UUID는 어떻게 아신건가요??

    공개를 해주나요??아니면 다른방법으로 아는 방법이 있나요??

    저는 블루투스 정밀저울로 테스트를 해야해서요..ㅠ

    제가 테스트하려는 제품은 Smart Connect Bluetooth Kitchen Scale 입니다.

    답변주시면 감사하겠습니다. ㅎ

  4. Blog Icon
    박상우

    안녕하십니까. 비콘과 앱 개발을 처음 해보는 학생입니다.
    비콘에 대한 정확한 개념도 없이 막 시작하는 터라 궁금한 점이 한두가지가 아닌데, 초면에 무례함을 무릎쓰고 몇가지 질문 드려도 될까요?

    우선 첫 번째로 비콘을 이용하여 자동 출석체크 시스템을 만드려는데, 강의장이라고 인식되는 범위 내에 단말이 들어오면 단말에 앱 알람이 뜨며 해당 강의실의 출석 버튼이 오픈되고, 출석 버튼을 누르면 출석이 인정되게 하는 앱을 구현하고 싶습니다. 가능할지 궁금합니다.

    첫번째단계까지 구현하고 난 다음에는 출석 후 단말이 30분 이상 해당 지역에 없으면 출석인정이 안되도록 하고 싶은데, 비콘에 따라 로그를 남겨 그 기록을 바탕으로 빅데이터 처리를 할 수 있다고 해서 이 기능을 이용하고 싶은데 가능할지 궁금합니다.

    세 번째로는 비콘의 종류는 상관이 없는것인지, 비콘에 따라 구현이 다르게 되는지에 대한 궁금증입니다.

    바쁘신 와중에 질문드려 죄송합니다. 그리고 읽어주셔서 감사합니다.

  5. 1, 원하는 기능 구현 가능합니다. 미들웨어 형태의 서버가 필요하겠네요. 모바일 앱에서 비콘 신호를 받아 출석버튼을 활성화 하고 버튼 누르면 서버를 통해 출석처리하도록 요청하도록 하세요.

    2, 모바일 앱을 계속 켜놓고, (예로)1분마다 서버로 비콘과의 거리등을 전송하면 서버에서 30분이상 해당 지역에 있었는지 판단할 수 있습니다.

    3, 비콘이 아이비콘, 알트비콘 주종의 표준을 따랐다면 비콘의 종류와 관계없이 대부분의 비콘을 연동할 수 있습니다.

    비콘 연동하는 내용은 http://blog.hjf.pe.kr/384 을 참고하세요.

  6. Blog Icon
    박상우

    빠른 답변 대단히 감사드립니다. 혹시 eclipse에서도 구현할 수 있는 기능인가요? 제가 pascal어를 공부해본 적도, rad studio를 사용해본 적도 없어서 잘 할 수 있을지가 걱정이 됩니다..답변 다시한번 대단히 감사합니다. 큰 도움이 되었습니다.

  7. 개념과 구성만 잘 잡고 개발하신다면 툴(언어)가 문제가 되진 않을 것입니다.
    자바에서도 비슷한 기능을 제공하니까 살펴보시기 바랍니다.

    기회되면 델파이도 한번 관심 갖어주세요^^
    (델파이 교육과정 : http://devgear.co.kr/edu
    대학생분들은 5명까지 무료로 참석할 수 있습니다.)

[따라하기] 위험지역 경보 시스템 #1 - 비콘을 이용해 위험지역 진입 경보앱 만들기

2015.07.28 18:03


위 동영상(위험 지역 경보 시스템 데모) 중 위험 지역 진입 시 경보를 발생하는 앱을 따라하기를 통해 만들어 봅니다.

위험지역 진입 경보앱 만들기

❑ 앱 소개

앱 소개

이 앱은 위험지역에 진입 시 화면과 경고음으로 위험지역에 진입을 경고해 주는 (프로토 타입)앱입니다.


따라하기의 목적

따라하기를 통해 비콘을 이용한 위치기반 앱 개발을 시작합니다.


이 따라하기에서는 다음 내용을 다룹니다.

  • 비콘 컴포넌트를 이용해 비콘과의 거리를 활용합니다.
  • UI 컨트롤의 투명도에 애니메이션 효과를 적용해 화면경고 기능을 구현합니다.
  • 사운드 파일(*.mp3)을 앱과 배포해 경보음으로 사용합니다.

따라하기

0, 사전 준비사항

1, 빈 멀티-디바이스 애플리케이션 프로젝트를 생성하고 저장합니다.

2, 메인 UI를 만듭니다.

3, 위험 경보 UI를 추가합니다.

4, 위험경고 화면 효과, 경고음 기능을 추가합니다.

5, 비콘 컴포넌트 추가 후 이벤트를 만듭니다.

6, 비콘과 거리를 기반으로 위험지역 경보 이벤트를 발생합니다.

7, 블루투스 권한설정

8, 디바이스에 배포하고 테스트합니다.

0, 사전 준비사항

개발 도구 준비하기

델파이(또는 RAD Studio)를 실행합니다.(이 실습은 델파이 XE8 버전으로 작성되었습니다.)

만약, 델파이가 설치되지 않았다면 아래의 페이지에서 델파이를 다운로드 받아 설치 후 진행하시기 바랍니다.

따라하기에서 사용할 리소스 다운로드

DangerZoneAlertRes.zip


위 파일을 다운로드 후 적당한 위치에 압축해제하시기 바랍니다.

비콘 준비하기

비콘을 준비합니다.(시중에서 다양한 비콘을 판매하고 있습니다. 저는 Beacon B1을 이용해 진행했습니다.)
시중에서 판매되는 (표준을 지키는)대부분의 비콘과 연동됩니다.

비콘 정보 확인/설정하기

비콘의 UUID와 Major Id, Minor Id를 알아야 합니다. 제조사 사이트 또는 비콘 스캐너 앱을 통해 파악할 수 있습니다.
보통 같은 제품의 비콘은 동일한 UUID, Major Id, Minor Id로 설정되어있습니다. 비콘 별로 구별하기 위해 Major Id, Minor Id를 변경해야 하는데, 제조사의 안내에 따라 Major Id, Minor Id를 다르게 설정하기 바랍니다.(대부분 제조사에서 전용 앱을 제공합니다.)

1, 빈 멀티-디바이스 애플리케이션 프로젝트 생성

  1. File > New > Multi-Device Application - Delphi 메뉴를 선택 하고, Blank Application을 선택해 프로젝트를 생성합니다.
  2. File > Save all 메뉴를 선택 해 유닛이름을 "MainForm.pas"로 프로젝트 이름을 "DangerZoneAlert"으로 저장합니다.

2, 메인 UI를 만듭니다.

모바일 앱 스타일로 화면 구성하기 위해 폼디자이너 스타일을 Android로 변경합니다.


다음화면과 표를 이용해 위험지역 경보 앱 메인 UI를 개발합니다.
(이 따라하기는 비콘 활용 기능에 집중하기 위해 최소한의 UI로 구성했습니다.)


상위 오브젝트

오브젝트

속성

값(또는 설명)

 Form1

 ToolBar1

 

 

 ToolBar1

 Label1

 Align

 Client

 Text

 위험지역 경보 앱

 TextSettings.HorzAlign

 Center

 Switch1

 Align

 Right

 Form1

 Image1

 MultiResBitmap

 "factory.png 선택"

 StatusBar1

 Height

 32

 StatusBar1

 Label2

 Align

 Client

 Text

 위험지역과의 거리: 

3, 위험 경고 UI를 추가합니다.

위험 지역 진입 시 화면으로 경고를 주기위한 UI를 추가합니다. 붉은색 배경에 안내문구를 표시하고, 화면을 깜빡이게 합니다.

화면을 깜빡이는 기능은 다음 단계에서 진행합니다.


다음화면과 표를 이용해 위험 경보 UI를 추가합니다.


상위 오브젝트 

 오브젝트

 속성

 값(또는 설명)

 Form1

 Rectangle1

 Align

 Client

 Fill.Color

 Red

 Opacity

 0.8

 Rectangle1

 Layout1

 Align

 Center

 Width/Height

 228/186(적당히)

 Layout1

 Label3

 Align Text
 Text

 위 그림 참고(위험지역에 ...)

 lblDistance Align Bottom
 Name lblDistance

 TextSettings.Font.Size

 24
 TextSettings.HorzAlign Center

4, 위험경고 화면 효과, 경고음 기능을 추가합니다.

위험지역 진입을 알리기위해 경고음을 발생하고, 경고화면을 표시 후 화면을 깝빡여주는 기능을 구현합니다.


경고음을 실행하기위해 TMediaPlayer 컴포넌트를 경고화면을 깜빡이기 위해 TFloatAnimation 컴포넌트를 추가합니다.


Rectangle1의 FloatAnimation은 Opacity 속성 추가 메뉴(Create New TFloatAnimation)를 선택해 생성합니다.


상위 오브젝트

오브젝트

속성

값(또는 설명)

 Form1

 MediaPlayer1

 

 

 Rectangle1

 FloatAnimation1

 Autoreverse

 True

 Duration

 1

 Loop

 True

 PropertyName Opacity

 StartValue/StopValue

 0.8/0.2

경고음 사운드 파일 배포등록하기

구현에 앞서 경고음으로 사용할 사운드파일을 배포관리자에 등록힙니다.

  1. 메인메뉴에서 Project > Deployment 메뉴를 선택해 Deployment 윈도우를 표시합니다.
  2. Add files 버튼을 누르고 "alert.mp3"파일을 선택해 추가합니다.
  3. 배포 플랫폼 선택 콤보박스를 Android platform과 iOS Device platform으로 선택하고, 아래를 참고해 Remote Path를 수정합니다.

    • Android platform - assets\internal\
    • iOS Device platform - StartUp\Documents\

경고 기능 구현하기

private 영역에 아래 코드를 참고해 4개의 메소드를 선언합니다.

  private
    { Private declarations }
    procedure StartWarning; // 위험지역 진입 경고 시작
    procedure StopWarning;  // 위험지역 진입 경고 중지

    procedure StartSiren;   // 사이렌 시작
    procedure StopSiren;    // 사이렌 중지
  public

Ctrl + Shift + C 단축키(Complete Class at Cursor)를 이용해 메소드의 구현부를 자동완성합니다.

아래 코드를 참고해 구현부를 완성합니다.

procedure TForm1.StartSiren;
begin
  MediaPlayer1.FileName := TPath.Combine(TPath.GetDocumentsPath, 'alert.mp3');
  MediaPlayer1.Play;
end;

procedure TForm1.StopSiren;
begin
  MediaPlayer1.Stop;
end;

procedure TForm1.StartWarning;
begin
  Rectangle1.Visible := True;
  FloatAnimation1.Enabled := True;

  StartSiren;
end;

procedure TForm1.StopWarning;
begin
  Rectangle1.Visible := False;
  FloatAnimation1.Enabled := False;

  StopSiren;
end;

사운드파일 경로 설정을 위해 사용한 TPath가 선언된 System.IOUtils 유닛을 유즈절에 추가합니다.

5, 비콘 컴포넌트 추가 후 이벤트를 만듭니다.

비콘 컴포넌트(TBeacon)는 안드로이드, iOS, OSX를 지원합니다. 타겟 플랫폼을 안드로이드로 변경 후 폼위에 비콘 컴포넌트를 추가합니다.


모니터링 대상 비콘 등록

비콘을 모니터링하기 위해 비콘 정보를 등록합니다.

  1. Beacon1 컴포넌트의 MonitorizedRegions 속성을 선택 후 확장 버튼을 클릭합니다.

  2. 편집창에서 Add new 버튼을 눌러 항목을 추가합니다.

  3. 대상 비콘의 UUID, Major, Minor 값을 입력합니다.

(만약, Major, Minor 값을 -1로 설정하면 UUID만 이용해 대상을 탐색합니다.)


비콘 인스턴스 할당하기

비콘 변수를 선언하고, BeaconEnter, BeaconExit 이벤트에서 비콘 인스턴스를 할당해줍니다.


private 영역에 변수를 선언합니다.

  private
    { Private declarations }
    FBeacon: IBeacon;

 비콘 컴포넌트의 OnBeaconEnter, OnBeaconExit 이벤트 핸들러를 만들고 비콘 인스턴스를 할당합니다.

(이벤트 핸들러는 이벤트의 우측 영역을 더블클릭하면 생성됩니다.)


procedure TForm1.Beacon1BeaconEnter(const Sender: TObject;
  const ABeacon: IBeacon; const CurrentBeaconList: TBeaconList);
begin
  FBeacon := ABeacon;
end;

procedure TForm1.Beacon1BeaconExit(const Sender: TObject;
  const ABeacon: IBeacon; const CurrentBeaconList: TBeaconList);
begin
  FBeacon := nil;
end;

비콘 컴포넌트 동작을 활성화하기

스위치(Switch1)를 키면 비콘이 동작하도록 Switch1의 OnSwitch 이벤트 핸들러 생성 후 아래 코드를 입력합니다.

procedure TForm1.Switch1Switch(Sender: TObject);
begin
  Beacon1.Enabled := Switch1.IsChecked;
end;

6, 비콘과 거리를 기반으로 위험지역 경보 이벤트를 발생합니다.

이제 모든 준비는 완료되었고, 비콘과의 거리를 계산해 위험지역 진입 경보 이벤트를 발생하면 됩니다.

위험지역 진입에 대한 경보는 아래와 같은 기준으로 시작하고 중단합니다.

위험지역 진입 경보 기준

  1. 비콘과의 거리가 1.0미터 이하인 경우 위험지역으로 간주한다.
  2. 경보가 아닌 상태에서 위험지역에 3초이상 연속으로 머무른 경우 경보를 시작한다.
  3. 경보가 발생중인 상태에서 위험지역을 3초이상 연속으로 벗어난 경우 경보를 중단한다.

비콘과 거리를 주기적으로 확인하기 위해 타이머 컴포넌트(Timer1)를 폼에 추가합니다.


아래 변수를 private 영역에 선언합니다.

    FIsWarning: Boolean;          // 경고 중인가?
    FDangerAreaStaySecs,          // 위험지역에 머무른 시간(초)
    FDangerAreaOutSecs: Integer;  // 위험지역에서 벗어난 시간(초)


Timer1의 OnTimer 이벤트 핸드러를 생성하고, 아래 코드를 추가합니다.

procedure TForm1.Timer1Timer(Sender: TObject);
var
  InDangerArea: Boolean;  // 위험지역에 있는가?(비콘과의 거리가 1m 이내인가?)
begin
  if Assigned(FBeacon) then
  begin
    InDangerArea := FBeacon.Distance <= 1;

    // 경고 중 아님
      // 위험지역에 3초간 머무른 경우 경고 시작
    if not FIsWarning then
    begin
      if InDangerArea then
        Inc(FDangerAreaStaySecs)
      else
        FDangerAreaStaySecs := 0
      ;

      if FDangerAreaStaySecs >= 3 then
      begin
        FIsWarning := True;
        StartWarning;
        FDangerAreaStaySecs := 0
      end;
    end
    // 경고 중
      // 위험지역을 3초 이상 벗어난 경우 경고 중단
    else if FIsWarning then
    begin
      if not InDangerArea then
        Inc(FDangerAreaOutSecs)
      else
        FDangerAreaOutSecs := 0;

      if FDangerAreaOutSecs >= 3 then
      begin
        FIsWarning := False;
        StopWarning;
        FDangerAreaOutSecs := 0;
       end;
    end;

    // 위험지역과의 거리를 표시
    lblDistance.Text := FBeacon.Distance.ToString;
    Label3.Text := '위험지역과의 거리: ' + FBeacon.Distance.ToString + ' m';
  end;
end;

7, 블루투스 권한설정

(이 과정은 안드로이드 플랫폼을 사용하지 않는 경우 생략할 수 있습니다.)

비콘은 블루투스 LE(Low Energy) 기반으로 통신합니다.  블루투스 사용권한을 설정합니다.

Project > Options 메뉴를 선택 후 Uses Permissions 화면으로 이동합니다.


Bluetooth와 Bluetooth admin 두개의 권한을 사용하도록 설정합니다.

8, 디바이스에 배포하고 테스트합니다.

처음 디바이스에 배포하는 경우 먼저 모바일 개발환경 설정하고 진행하기 바랍니다.


개발을 완료했습니다. 이제 준비된 개발환경으로 플랫폼을 선택 후 Run > Run Without Debugging 메뉴를 이용해 실행하고 테스트 합니다.



테스트 방법 : 

  • 앱을 실행 후 비콘과 1미터 이내로 접근 후 3초이상 머무르면 경고가 발생
  • 경고 발생 중 1미터 초과 거리로 이동 후 3초이상 머무르면 경고 중단


참고자료



저작자 표시 비영리 동일 조건 변경 허락
신고
크리에이티브 커먼즈 라이선스
Creative Commons License

험프리.김현수 험프리.김현수 Firemonkey/모바일 앱 예제 Beacon, IOT, 비콘, 사물인터넷

  1. Blog Icon

    비밀댓글입니다

  2. 이번 따라하기에는 로그로 기록하고, 관리자에게 보고하는 내용이 포함되지 않았습니다.

    영상에서 사용된 앱에서는 주기적으로 클라우드에 위험지역과의 거리를 기록하고, 위험지역에 머무른 경우 관리자에게 푸쉬메시지(GCM)을 전송했습니다.

    이 내용에 관심이 있으시다면 제가 진행하는 세미나에서 자세한 이야기를 들어보세요.(8월 11일은 이미 신청완료 됐고, 9월 3일에 앵콜세미나가 진행됩니다.)
    http://tech.devgear.co.kr/board_in1/413343

  3. Blog Icon

    비밀댓글입니다

  4. 비콘은 XE8에서 추가된 컴포넌트입니다.

    업데이트 또는 트라이얼 버전으로 진행해주세요^^

  5. Blog Icon
    김태규

    그럼 따로 비콘 업체에서 제공해주는 api 가져다가 만들어야하나요?

    비콘 컴포넌트 없이도 제작은 가능하죠??

  6. 네 비콘 표준을 기반으로 직접 구현할 수 있습니다.(iBeacon, AltBeacon을 필요한 플랫폼 별로 구현해야 합니다.) 하지만 쉽지 않습니다.ㅠㅜ

    트라이얼로 테스트 해보시고 최신버전으로 업그레이드하셔서 컴포넌트 이용하는 것을 추천드립니다.

    그리고 모바일 개발 하시려면 잦은 모바일 OS업데이트로 델파이 버전도 빠르게 올라가기 때문에 업데이트 서브스크립션을 구매하시는 것이 아주 좋습니다.
    http://www.devgear.co.kr/products/rad-studio/update-subscription/

  7. Blog Icon
    정재용

    비콘과 미디어플레이어 경고 알람 기능이 포함된 소스는 구할 수 없을까요?

    비콘만 구현하면 잘 돌아가고,

    미디어만 구현해도 잘 돌아가는데,

    간단하게 섞을라 치면 어플이 안드로이드핸드폰에서 실행하자마자 꺼지네요.

    휴대폰 리소스 문제일까요?

    비교해보고싶어서 그런데 사용예제를 git으로 얻을 수 있을까 여쭤봅니다.

  8. 이 따라하기가 비콘과 미디어플레이어를 이용하고 있습니다.
    혹시 따라하기로 잘 안되시면 아래 깃헙링크에서 다운로드받아 테스트해보세요.
    (단, 배포: Project > Deployment 부분에서 파일 배포 부분을 잘 검토해 보세요.)
    https://github.com/devgear/RADStudioMobileIoTEdu/tree/master/20150828/BeaconDemo3

  9. Blog Icon
    박문수

    안녕하세요 이번에 비콘을 구매하여서 위험지역 경보 앱을 따라 제작하며

    추천해주신 비콘중에 Gimbal 제품을 구매하여 테스트 하는데 이 제품은 UUID가 따로 없는데

    혹시 이 제품으로 연동하는 방법을 알 수 있을까요?

  10. 제조사 홈페이지에서 찾아보시거나,
    모바일에서 Beacon Scan 기능을 제공하는 앱을 다운받아 비콘을 스캔해 보시기 바랍니다.
    안드로이드인 경우 Beacon Scanner, 아이폰인 경우 Locate 앱을 이용하면 됩니다.

  11. Blog Icon
    박문수

    답변 감사드립니다.
    이야기 해주신 어플은 스캔이 안되어서 다른 어플(BeaconFinder)로 하니깐 나와서 UUID를 입력 후 어플을 돌리면 비콘이 안잡히고 있습니다.
    그래서 다시 UUID 확인을 하면 특정위치 한자리가 변경이 되고 있습니다.
    혹시 이게 왜 이런지 아시는거 있으신가요??

  12. 이 현상은 비콘 스캐너 앱을 의심해봐야 할것 같네요.
    다양한 앱으로 시도해보시기 바랍니다.

    그리고 비콘 제조사 홈페이지에서 UUID를 확인해보시구요.
    제조사에서 제공하는 전용 앱이 있다면 해당 앱으로도 탐색해보세요.

  13. Blog Icon
    이원진

    안녕하세요? 좋은 자료 감사합니다.
    제가 하고자 하는것은 비콘을 자동으로 인식 하고자 하는데..
    보편적으로 매장내에서 비콘을 활용하고자 할경우에 uuid를 입력하는 절차가 있으면 곤란하고요
    uuid를 직접 입력하지 않고 uuid정보를 가져올수 있는 방법은 없을까요?

  14. Blog Icon
    MG

    다음 강의도 올려주세요~

  15. Blog Icon
    박한수

    안녕하세요 이 글을 읽고 비콘이 멀어질때 알람이 울리는 식으로 응용을 해보았습니다. 그리고 비콘을 하나에서 세개로 늘려서 해보았는데

    각 비콘에 동일하게 스위치를 따로 달아서 진행하였고 추가되는 비콘들은

    procedure TForm1.Beacon2BeaconEnter(const Sender: TObject;
    const ABeacon: IBeacon; const CurrentBeaconList: TBeaconList);
    begin
    GBeacon :=ABeacon;
    end;

    이처럼 GBeacon이나 HBeacon으로 선언하여 이벤트를 만들었습니다. 알람부분에서도 타이머를 추가하여 변수 (FIsWarning, StopWarning,FDangerAreaOutSecs 등)도 GBeacon을 위한 새로운 변수(GIsWarning, GDangerAreaOutSecs 등)로 따로 선언을 해 두었습니다.

    이렇게 하니 하나의 스위치를 켜서 할때는 각각 모두 잘 되는데 스위치를 여러개를 켜면 하나의 비콘만 인식을 하고 다른것은 인식을 못하였습니다. 혹시 어디가 잘못되었는지 알 수 있을까요?

"나의 도서관 앱" 개발 따라하기 - (2) 데이터베이스 만들기, 실제 데이터 연결

2014.10.20 15:07

업데이트

  • 2015-02-25 : "2. 프로젝트 소스에서 데이터베이스와 연결"에서 Fields를 생성해 자동증가 속성을 주도록 변경


이번 글에서는 감명깊게 읽은 도서 정보, 리뷰를 기록할 수 있는 "나의 도서관 앱" 개발과정의 두번째 따라하기를 진행합니다.

첫번째 사용자화면 만들기와 기능 구현하기를 먼저 진행하시기 바랍니다.

따라하기 2 - 데이터베이스 만들기, 실제 데이터 연결

  1. "나의 도서관"앱에서 사용할 데이터베이스 만들기 
  2. 프로젝트 소스에서 데이터베이스와 연결 
  3. 데이터와 화면요소 연결하기 
  4. 데이터 입력, 수정, 삭제 기능 구현하기 
  5. 데이터베이스 파일과 라이선스파일 배포 등록하기 
  6. 원하는 모바일 디바이스에 배포하고 실행! 끝~

1, "나의 도서관"앱에서 사용할 데이터베이스 만들기

앱에서 사용할 데이터베이스를 생성합니다. 데이터베이스는 엠바카데로 제품 중 무료로 사용할 수 있는 IBLite 기반으로 진행합니다.

  1. IB Console을 실행(시작 > 프로그램 > Embarcadero InterBase XE3)합니다.
  2. Database > Create Database 메뉴 선택 후 아래 정보를 입력 후 OK 버튼을 눌러 데이터베이스 생성합니다.
    • File Name : 프로젝트 파일 하위에 "DB" 폴더 생성 후 지정(파일명은 반드시 "BOOKLOG.GDB"(확장자 입력)로 지정)
    • Default Character Set :UTF8(한글입력 시 필수)
    • Password of user : masterkey
  3. 데이터베이스 접속 창이 표시되면 위에서 입력한 "masterkey" 비밀번호를 입력 후 Connect 버튼을 눌러 접속합니다.
    (Display Character Set은 UTF8로 설정되었는지 확인)
  4. Tool > Interactive SQL 메뉴를 선택 하고 아래의 쿼리 입력 후 실행(Query > Execute, F5)하여 테이블과 트리거를 생성합니다.

    /* 테이블 생성 */
    CREATE TABLE "BOOK_LOG" (
      "BOOK_SEQ"		INTEGER NOT NULL,
      "BOOK_TITLE"		VARCHAR(50) NOT NULL,
      "BOOK_AUTHOR"		VARCHAR(30) NOT NULL,
      "BOOK_PUBLISHER"	VARCHAR(30),
      "BOOK_PHONE"		VARCHAR(20),
      "BOOK_WEBSITE"	VARChAR(100),
      "BOOK_COMMENT"	VARCHAR(1000),
      "BOOK_THUMB"		BLOB SUB_TYPE 0 SEGMENT SIZE 80,
      "BOOK_IMAGE"		BLOB SUB_TYPE 0 SEGMENT SIZE 80,
      CONSTRAINT "BOOK_SEQ_PK" PRIMARY KEY ("BOOK_SEQ")
    );
    
    /* BOOK_SEQ 자동증가 */
    CREATE GENERATOR "BOOK_SEQ_GEN";
    CREATE TRIGGER "SET_BOOK_SEQ" FOR "BOOK_LOG"
    ACTIVE BEFORE INSERT POSITION 0 AS
    BEGIN
        new.BOOK_SEQ = gen_id(BOOK_SEQ_GEN, 1);
    END;
create_script.sql

2, 프로젝트 소스에서 데이터베이스와 연결

데이터 연결 컴포넌트를 이용해 프로젝트에서 앞에서 만든 BOOKLOG.GDB와 연결합니다

데이터 연결 작업은 DataAccessModule.pas에서 진행합니다.

데이터 연결 컴포넌트 설정

  1. 데이터 모듈에 TFDConnection을 추가합니다.
  2. 추가된 FDConnection1을 더블클릭 해 FireDAC Connection Editor를 표시하고 아래와 같이 설정합니다.
    • Drive ID : IB
    • Database : 앞에서 생성한 BOOKLOG.GDB 선택택
    • User_Name : sysdba
    • Password : masterkey
    • CharacterSet : UTF8
  3. Test 버튼을 눌러 연결을 확인하고 OK 버튼을 눌러 창을 닫습니다.
  4. FDConnection1LoginPrompt 속성을 False로 설정합니다.
  5. Connected 속성을 True로 설정합니다.

데이터 질의(Query) 컴포넌트 설정

  1. 데이터 모듈에 TFDQuery 컴포넌트를 추가합니다.
  2. FDQuery1의 Connection속성을 FDConnection1으로 설정(확인)합니다.
  3. CachedUpdates 속성을 True로 설정합니다.
  4. FDQuery1 컴포넌트를 더블클릭 후 아래 쿼리문을 설정합니다.
    • SQL Command : SELECT * FROM BOOK_LOG
    • Execute 버튼 클릭 해 쿼리문 확인
    • OK 버튼 클릭
  5. Active 속성을 True로 변경합니다.

    데이터 질의(Query) 컴포넌트 필드 추가 / 자동증가 필드 설정

    1. FDQuery1 선택 후 오른쪽 마우스 메뉴에서 Fields Editor... 메뉴를 실행합니다.
    2. Fields Editor 창의 오른쪽 마우스 메뉴에서 Add all fields 메뉴를 실행 해 모든 필드를 추가합니다.
    3. BOOK_SEQ(FDQuery1BOOK_SEQ)항목 선택 후 AutoGenerateValue 속성을 arAutoInc로 선택합니다.

    화면 구성시 샘플데이터로 사용했던 PrototypeBindSource1을 데이터 모듈에서 삭제(선택 후 Delete 키)합니다.


    3, 데이터와 화면요소 연결하기

    View > LiveBindings Designer 메뉴를 통해 라이브 바인딩 디자이너를 표시하고, 아래 그림을 참고해 화면요소에 데이터를 연결합니다. (지금은 데이터베이스에 데이터가 없으므로 연결을 해도 화면의 변화가 없습니다.)

    도서목록

    도서 상세보기

    새로운 도서추가

    4, 데이터 입력, 수정, 삭제 기능 구현하기

    화면에서 입력받은 데이터를 입력, 수정, 삭제하는 기능을 구현합니다.


    데이터 모듈에 데이터 처리 메소드 추가

    1. DataAccessModule.pas으로 이동 후 FDConnection1 컴포넌트의 BeforeConnect 이벤트에 아래의 코드를 입력합니다.
      procedure TDataModule1.FDConnection1BeforeConnect(Sender: TObject);
      begin
      // 윈도우가 아닌 경우 데이터베이스 경로를 배포경로로 조정
      {$IFNDEF MSWINDOWS}
        FDConnection1.Params.Values['Database'] := TPath.Combine(TPath.GetDocumentsPath, 'BOOKLOG.GDB');
      {$ENDIF}
      end;
    2. DataAccessModule.pas의 코드 에디터 창을 표시합니다.(폼 디자이너에서 F12)
    3. 상단 uses절에 FMX.Graphics, System.IOUtils를 추가합니다.
    4. 선언부 public 영역에 아래의 코드를 입력합니다.
        public
          { Public declarations }
          procedure Connect; // 데이터베이스 연결
          procedure AppendMode; // 입력 모드로 변경
          procedure EditMode; // 수정 모드로 변경
          procedure SetImage(ABitmap: TBitmap); // 이미지저장(본문, 목록의 썸네일 이미지)
          procedure SaveItem; // 항목 저장(입력/수정)
          procedure CancelItem; // 입력/수정 모드 취소
          procedure DeleteItem; // 선택항목 삭제
        end;
    5. 구현부(선언부에 입력 후 Ctrl + Shift + C 누르면 구현부 자동 생성)에 아래의 코드를 입력합니다.
      // 입력/수정 모드 취소
      procedure TDataModule1.CancelItem;
      begin
        if FDQuery1.UpdateStatus = TUpdateStatus.usInserted then
          FDQuery1.Cancel;
      end;
      
      // 데이터베이스 연결
      procedure TDataModule1.Connect;
      begin
      {$IFNDEF MSWINDOWS}
        FDConnection1.Params.Values['Database'] := TPath.Combine(TPath.GetDocumentsPath, 'BOOKLOG.GDB');
      {$ENDIF}
        FDConnection1.Connected := True;
        FDQuery1.Active := True;
      end;
      
      // 현재항목 삭제
      procedure TDataModule1.DeleteItem;
      begin
        FDQuery1.Delete;
        FDQuery1.ApplyUpdates(0);
        FDQuery1.CommitUpdates;
        FDQuery1.Refresh;
      end;
      
      // 수정모드
      procedure TDataModule1.EditMode;
      begin
        FDQuery1.Edit;
      end;
      
      // 입력모드
      procedure TDataModule1.AppendMode;
      begin
        FDQuery1.Insert;
      end;
      
      // 항목 저장
      procedure TDataModule1.SaveItem;
      begin
        FDQuery1.Post;
        FDQuery1.ApplyUpdates(0);
        FDQuery1.CommitUpdates;
        FDQuery1.Refresh;
      end;
      
      // 이미지 저장(본문이미지와 목록에 표시할 썸네일)
      procedure TDataModule1.SetImage(ABitmap: TBitmap);
      var
        Thumbnail: TBitmap;
        ImgStream, ThumbStream: TMemoryStream;
      begin
        if FDQuery1.UpdateStatus = TUpdateStatus.usUnmodified then
          FDQuery1.Edit;
      
        ImgStream := TMemoryStream.Create;
        ThumbStream := TMemoryStream.Create;
        try
          ABitmap.SaveToStream(ImgStream);
          Thumbnail := ABitmap.CreateThumbnail(100, 100);
          Thumbnail.SaveToStream(ThumbStream);
      
          (FDQuery1.FieldByName('BOOK_IMAGE') as TBlobField).LoadFromStream(ImgStream);
          (FDQuery1.FieldByName('BOOK_THUMB') as TBlobField).LoadFromStream(ThumbStream);
        finally
          ImgStream.Free;
          ThumbStream.Free;
        end;
      end;

    화면 기능과 데이터 처리 메소드 연결

    1. 폼 디자이너로 돌아와 아래의 코드를 참고해 데이터 처리기능을 추가합니다.

      procedure TForm1.FormCreate(Sender: TObject);
      begin
        TabControl1.TabPosition := TTabPosition.None;
        TabControl1.TabIndex := 0;
      
        OverflowMenu.Visible := False;
      
        vsbEditFocus.OnCalcContentBounds := CalcContentBoundsProc;
      
        DataModule1.Connect; // 데이터베이스 연결
      end;
      
      procedure TForm1.btnNewItemClick(Sender: TObject);
      begin
        DataModule1.AppendMode; // 입력 모드로 변경
        GotoNew;
      end;
      
      // 수정
      procedure TForm1.lstItemModifyClick(Sender: TObject);
      begin
        OverflowMenu.Visible := False;
        DataModule1.EditMode; // 수정 모드로 변경
        GotoNew;
      end;
      
      // 삭제
      procedure TForm1.lstItemDeleteClick(Sender: TObject);
      begin
        OverflowMenu.Visible := False;
      
        MessageDlg('해당 정보를 삭제하시겠습니까?', TMsgDlgType.mtWarning,
          [TMsgDlgBtn.mbYes, TMsgDlgBtn.mbNo], 0, procedure(const AResult: TModalResult)
          begin
            if AResult = mrYes then
            begin
              DataModule1.DeleteItem; // 선택항목 삭제
              GotoList;
            end;
          end);
      end;
      
      procedure TForm1.btnCancelClick(Sender: TObject);
      begin
        DataModule1.CancelItem; // 입력/수정 모드 취소
        GotoList;
      end;
      
      procedure TForm1.btnSaveItemClick(Sender: TObject);
      begin
        DataModule1.SaveItem; // 현재항목 저장
        GotoList;
      end;
      
      procedure TForm1.ChangeImageEvent(Image: TBitmap);
      begin
        imgNewItem.Bitmap.Assign(Image);
        DataModule1.SetImage(Image); // 이미지 저장
      end;

    5, 데이터베이스 파일과 라이선스파일 배포 등록하기

    이제 모든 개발이 완료되었습니다. IBLite 데이터베이스를 이용한 경우에는 IBLite 데이터베이스 파일과 IBLite 라이선스 파일과 라이브러리를 앱 배포시 함께 배포해야 합니다.


    IBLite 데이터베이스 파일 배포 등록하기

    1. Project > Deployment 메뉴를 선택해 배포 관리자 화면을 표시합니다.
    2. Add files 버튼을 누르고 1단계에서 만든 BOOKLOG.GDB 파일을 선택해 추가합니다.
    3. 설정선택 콤보박스를 Android platform과 iOS Device platform으로 선택하고 각각 아래를 참고해 Remote Path를 수정합니다.
      • Android platform - assets\internal\
      • iOS Device platform - StartUp\Documents\

    IBLite 라이선스 파일과 라이브러리 배포 등록하기

    1. IBLite, IBTOGO 테스트 라이선스 배포방법 페이지를 참고해 reg_iblite.txt 파일을 다운로드 합니다.
      1. https://reg.codegear.com/srs6/activation.do 페이지 이동
      2. 제품구매(또는 트라이얼 다운로드) 시 받은 라이선스 메일을 참고해 Serial Number와 Registration Code 입력 후 Next 버튼 클릭
      3. 하단에서 파일 다운로드 기능을 이용해 C:\Users\Public\Documents\Embarcadero\InterBase\redist\InterBaseXE3 경로에 파일 다운로드
      4. 다운로드된 텍스트파일(reg_XXXXXXX.txt)를 "reg_iblite.txt"로 이름 변경
    2. 배포 관리자 화면에서 Add Featured Files 버튼 클릭 후 Interbase TOGO 라이브러리 선택
      1. InterBase ToGo > iOSDevice 항목 선택 > reg_ibtogo.txt 선택해제
      2. InterBase ToGo > Android 항목 선택 > reg_ibtogo.txt 선택해제

    (옵션)앱 이름을 바꾸고 아이콘으로 단장하기

    앱의 이름 바꾸기

    1. Project > Option 메뉴 선택 후 Version Info 메뉴 선택 합니다.
    2. 상단의 타겟 플랫폼을 선택 후 플랫폼에 맞는 항목을 입력합니다.
      • 안드로이드 : label
      • iOS(아이폰) : CFBundleDisplayName
    3. 각각 '나의 도서관' 입력 후 OK 버튼으로 저장합니다.

    앱의 아이콘/스플래쉬 이미지 바꾸기

    1. Project > Option  메뉴 선택 후 Application 항목을 선택합니다.
    2. 상단의 Target에서 플랫폼을 선택 후 목록에 있는 아이콘과 스플래쉬 이미지를 지정합니다.
      (목록의 이미지 크기에 맞는 이미지를 준비해야 합니다. 샘플아이콘 다운로드)

    6, 원하는 모바일 디바이스에 배포하고 실행! 끝~

    • 배포를 원하는 플랫폼을 선택하고 Run > Run Without Debugging 메뉴를 통해 배포 및 실행합니다.
      (안드로이드 개발환경 설정은 앱메소드 튜토리얼 동영상을 통해 확인할 수 있습니다.)

    개발도구

    참고








    저작자 표시 비영리 동일 조건 변경 허락
    신고
    크리에이티브 커먼즈 라이선스
    Creative Commons License

    험프리.김현수 험프리.김현수 Firemonkey/모바일 앱 예제

    1. Blog Icon
      더스틴

      안녕하세요. 좋은 내용 올려주셔서 감사합니다.
      소스중에 아래 부분은 C++빌더로는 어떻게 표현되는 것인지요?
      (FDQuery1.FieldByName('BOOK_IMAGE') as TBlobField).LoadFromStream(ImgStream);

    2. 아래 링크를 참고하시면 도움이 되실것 같네요.^^(제가 C++ 문법이 짧아서요^^)
      http://codeback.net/cbuilder/using-tblobfield-and-tblobstream-in-cbuilder
      해결되시면 해결하신 내용 좀 남겨주세요. 제가 C++빌더로 컨버전해보고 싶네요.(가능하시면 소스코드 보내주시면 감사하겠습니다^^)

    3. Blog Icon
      더스틴

      위에 제가 질문 드린 내용은 해결되었습니다. 아래와 같이 하면 잘되더군요. ^^
      dynamic_cast<TBlobField*>(FDQuery1->FieldByName("BOOK_IMAGE"))->LoadFromStream(ImgStream);
      아무튼 빠른 피드백 감사합니다.
      김현수님의 블로그 내용이 많은 도움 됩니다. 정말 감사드려요.

    4. 네 도움이 되셨다고하니 기쁘네요.
      앞으로도 좋은 내용으로 보답하겠습니다.^^

    5. Blog Icon
      아이리스

      죄송한데 계속 ibconsol애서요. Bad parameters on attach or create database
      CHARACTER SET UTF8 is not defined 이런문구가 뜨면서 데이터베이스 생성이 안됩니다. 캐릭터셋을 none
      하면 만들어지기는 하는데 이번에 델파이에서는 type mismatch for field ' BOOK_TITLE', expecting: WideString actual:String. 이런 에러 문구가 떠서 엑티브를 할수가 없군요. 문자형셋이 안맞아서 그런거 같은데 해결법좀요

    6. Blog Icon
      하이길동

      안녕하세요 다름이 아니오라 db관련으로 궁금한것이 있어 이렇게 문의드립니다
      interbase 나 firebird로 된 sql문을 sqlite로 바꾸려면
      어떻게 해야 되는지 알고 싶어 문의드립니다
      sqlite에 없는 것 같은 generator 같은것(????)
      sqlite로 작성하니 여기서부터 에러가 계속 나서 ~
      "BOOK_THUMB" BLOB SUB_TYPE 0 SEGMENT SIZE 80,
      "BOOK_IMAGE" BLOB SUB_TYPE 0 SEGMENT SIZE 80,
      CONSTRAINT "BOOK_SEQ_PK" PRIMARY KEY ("BOOK_SEQ")
      );

      /* BOOK_SEQ 자동증가 */
      CREATE GENERATOR "BOOK_SEQ_GEN";
      CREATE TRIGGER "SET_BOOK_SEQ" FOR "BOOK_LOG"
      ACTIVE BEFORE INSERT POSITION 0 AS
      BEGIN
      new.BOOK_SEQ = gen_id(BOOK_SEQ_GEN, 1);
      END;

    7. 자동증가 필드를 지원하는 방식은 DB마다 다른데요.
      구글에서 "sqlite 자동증가"로 검색해 보시면 첫번째 링크인 볼랜드 포럼에서 다음 내용을 참고할 수 있네요.
      http://interbase.borlandforum.com/impboard/impboard.dll?action=read&db=bcb_tip&no=835

    8. Blog Icon
      이성열

      너무나 많은 도움 받고 갑니다.
      나도 모르게 북마크에 추가하게 되네요.

    9. 응원에 힘이납니다^^ 더 좋은 자료로 보답하겠습니다.

    재사용 가능한 프레임으로 UI 구현하기(사진, 웹페이지)

    2014.10.17 16:14

    사진 찍기와 웹페이지 표시와 같은 기능은 앱에서 많이 사용됩니다.

    이렇게 빈번히 사용하는 기능을 매번 구현하지 않고 프레임으로 구현 후 재사용하는 방법을 소개합니다.


    모바일 앱에서는 위와 같은 UI를 많이 사용합니다. 매번 해당 UI를 구현하려면 상당히 번거롭습니다.

    아래와 같이 짧은 코드로 위의 2가지 기능을 쉽게 사용할 수 있습니다.

    uses PhotoFrame, WebBrowserFrame;
    
    procedure TForm1.Button1Click(Sender: TObject);
    begin
      TfrPhoto.CreateAndShow(Self, ChangeImageEvent, nil);
    end;
    
    procedure TForm1.ChangeImageEvent(Image: TBitmap);
    begin
      Image1.Bitmap.Assign(Image);
    end;
    
    procedure TForm1.Button2Click(Sender: TObject);
    begin
      TfrWebBrowser.CreateAndShow(Self, Edit1.Text);
    end;
    
    procedure TForm1.FormKeyUp(Sender: TObject; var Key: Word; var KeyChar: Char;
      Shift: TShiftState);
    begin
      if Key = vkHardwareBack then
      begin
        if Assigned(frPhoto) then
        begin
          frPhoto.CloseFrame;
          Key := 0;
        end;
    
        if Assigned(frWebBrowser) then
        begin
          frWebBrowser.CloseFrame;
          Key := 0;
        end;
      end;
    end;

    위 소스코드에서는 TfrPhoto와 TfrWebBrowser를 이용해 사진기능과 웹브라우저 기능을 모두이용하고, 

    안드로이드 백버튼에 대한 처리까지 되어있습니다.

    (PhotoFrame에서 iOS와 안드로이드의 버튼 위치와 스타일도 플랫폼에 맞도록 지정했습니다.)


    사진기능과 웹브라우저 기능을 포함한 프레임등의 전체 소스코드는 제 Github 페이지를 참고하세요.


    보강할 기능이나 이런식의 프레임으로 처리할 기술이 있다면 댓글이나 메일로 말씀해 주시면 적극 반영하겠습니다.


    저작자 표시 비영리 동일 조건 변경 허락
    신고
    크리에이티브 커먼즈 라이선스
    Creative Commons License

    험프리.김현수 험프리.김현수 Firemonkey/모바일 앱 예제

    "나의 도서관 앱" 개발 따라하기 - (1) 사용자화면 만들기와 기능 구현하기

    2014.10.16 16:25

    "나의 도서관" 앱 개발 따라하기

    ❑ 앱 소개

    • 감명깊게 읽은 도서 정보를 기록하는 앱입니다.
    • 세미나 발표와 샘플소스 제공을 위해 다소 주제와 맞지 않은 기능이 포함되어 있습니다.
    • 해당 앱은 RAD Studio XE7(또는 AppMethod 1.15)로 개발 후 하나의 소스코드로 안드로이드와 iOS 앱을 동시에 개발하였습니다.

    따라하기를 통해 습득할 수 있는 기술

    • 앱 개발의 전반적인 흐름을 따라하며 익혀 볼 수 있습니다.
    • 기본적인 UI 컨트롤 사용법과 속성 사용법
    • 화면 구성에 도움이 되는 샘플데이터 생성(프로토타입 데이터 소스)
    • 화면요소와 데이터를 (소스코드 없이)시각적으로 연결하는 기술(라이브바인딩)
    • 이미 구현된 기능을 재활용할 수 있는 프래임 활용방법(TFrame)
    • 앱에 포함하여 배포할 수 있는 임베디드 데이터베이스 사용법(IBLite)
    • 앱에서 데이터 연결 후 입력/수정/삭제 과정
    총 2번의 따라하기로 "나의 도서관" 앱이 완성됩니다.
    1. 사용자화면 만드기, 기능 구현하기
    2. 실제 데이터베이스 연결, 배포
    이 따라하기는 따라하기 과정 중 나오는 기술에 대한 이해 없이 일단(무조건) 따라하며 완성하는 것을 목적으로 작성되었습니다. 따라하는 도중 잘 이해가 되지 않더라도 끝까지 따라해보시길 권장합니다.

    기술에 대한 설명은 글의 하단에 링크를 통해 제공하고 있습니다. 기타 궁금한 점은 댓글로 남겨주시기 바랍니다.
    ※ 해당 따라하기는 AppMethod를 통해 진행할 수 있습니다. 하단의 AppMethod 설치를 참고해 설치 후 진행하시기 바랍니다.

    따라하기 1 - 사용자 화면 만들기와 기능 구현하기

    1. 빈 멀티-디바이스 앱 하나를 선택합니다. 
    2. 사용자들에게 보여질 화면을 만듭니다. 
    3. 샘플 데이터 추가 후 화면요소와 연결해 화면 표시 후 테스트하기
    4. 사용자의 조작에 동작하는 기능코드를 작성합니다. 
    5. 전화걸기, 사진, 웹페이지 표시 기능 추가 
    6. 사용자화면 완성! 테스트 하기

    1, 빈 멀티-디바이스 앱 하나를 선택합니다.

    1. File > New > Multi-Device Application - Delphi 메뉴를 선택 하고, Blank Application을 선택해 프로젝트를 생성합니다.
    2. File > Save all 메뉴를 선택하고 유닛이름은 "MainForm.pas"로 프로젝트 이름은 'BookLogFMX"로 저장합니다.


    2, 사용자들에게 보여질 화면을 만듭니다.

    모바일 앱 스타일로 화면 구성을 위해 폼디자이너 스타일을 Android(또는 iOS)로 변경합니다.


    총 3개의 화면을 구성해야 합니다. 

    탭컨트롤

    여러화면을 구성하기 위해 탭컨트롤을 아래 표를 참고해 추가합니다.

     상위 오브젝트

    오브젝트 

    속성 

    값(또는 설명) 

     

     Form1

     Width / Height

     380 / 530(폼의 가장자리를 마우스로 적당한 크기로 조정)

     Form1

     TabControl1

     Align

     Client

     TabPosition

     Bottom
     (개발 시 탭이동 편의를 위해, 실행 시 탭은 표시하지 않음)

     TabControl1

     TabItem1(추가방법[각주:1])

     Text

     도서 목록

     Tabitem2

     Text

     도서 상세보기

     TabItem3

     Text

     새로운 도서 추가

    도서 목록

    "도서 목록"탭에서 아래 그림과 표를 참고해 화면 구성합니다.




     상위 오브젝트

    오브젝트 

    속성 

    값(또는 설명) 

     TabItem1

     ToolBar1

     

     

     ToolBar1 Label1  Align Contents
     StyleLookup

     toollabel 

     Text 나의 도서관
     TextSettings.HorzAlign Center

     btnNewItem

     (TButton)

     Name btnNewItem
     Align  Right
     StyleLookup additemButton

     Tabitem1

    ListView1AlignClient
     ItemApperance.ItemAppearance  ImageListItemBottomDetail

     ItemApperance.ItemHeight

     88

     ItemApperance.ItemObjects

    .Image.Width / Height

     60 / 80
      ItemApperance.ItemObjects

    .Text.WordWrap

     True


    도서 상세보기

    "도서 상세보기"탭에서 아래 그림과 표를 참고해 화면 구성합니다.


     상위 오브젝트

    오브젝트 

    속성 

    값(또는 설명) 

     TabItem2

     ToolBar2

     

     

     ToolBar2

     Label2

     Align Contents
     StyleLookup toollabel 
     Text 제목
     TextSettings.HorzAlign Center
     TextSettings.WordWrap False

     btnBackList
     (TButton)

     Name btnBackList
     Align  Left
     StyleLookup arrowlefttoolbutton

     btnDetail

     (TButton)

     Name btnDetail
     Align

     Right

     StyleLookup detailstoolbutton

     TabItem2

     Layout1

     Align

     Client

     Layout1 Panel1 Align Top
     Height 116
     Margins 8, 8, 8, 8
     Panel1

     ShadowEffect1

     Distance 2
     ShadowColor Gray

     Rectangle1

     Image1 Align Left
     Margins 8, 8, 8, 8

     Width

     75
     Rectangle1 Layout2 Align Client
     Layout2

     lblTitle(TLabel)

     Name

     lblTitle

     Align MostTop
     AutoSize

     True

     Margins 0, 8, 3, 16

     StyledSettings.Style

     False
     TextSettings.Font.Size

     16

     TextSettings.Font.Style.fsBold

     True
     lblAuthor(TLabel) Name

     lblAuthor

     Align Top
     Margins 3, 12, 3, 10

     Layout1

     ListBox1 Align Client
     ListBox1 

     ListBoxItem1[각주:2]
     ListBoxItem2
     ListBoxItem3
     ListBoxItem4

     StyleLookup
     Text
     listboxitemnodetail
     출판사, 연락처, 사이트, 감상평

     ListBoxitem4

     Height 150
     ListBoxItem1 lblPublisher(TLabel) Align
    Margins.Left
    (4개 공통)

     Client
    80
    (4개 공통)

     ListBoxItem2 lblPhone(TLabel)
     ListBoxItem3 lblWebSite(TLabel)
     ListBoxItem4 lblComment(TLabel)
     ListBoxItem2 lblPhone HitTest Ture
     ListBoxItem3 lblWebSite

     HitTest

     True
     ListBoxItem4 lblComment TextSettings.VertAlign Leading
     Tabitem2 OverflowMenu(TListBox) Name OverflowMenu
     Position.X / Position.Y

     Button3 아래로 이동

     Width / Height 88 / 96
     OverflowMenu

     lstItemModify,

     lstItemDelete

     (TListBoxItem)

     Name

     lstItemModify /  lstItemDelete

     StyleLookup listboxitemstyle
     Text

     수정 / 삭제

     TextSettings.HorzAlign Center
     ShadowEffect2  

    새로운 도서 추가

    "새로운 도서 추가"탭에서 아래 그림과 표를 참고해 화면 구성합니다.



     상위 오브젝트

    오브젝트 

    속성 

    값(또는 설명) 

     TabItem3

     ToolBar3

     

     

     ToolBar3

     Label3 

     Align Contents
     StyleLookup toolbutton 
     Text 도서 추가
     TextSettings.HorzAlign Center

     btnCancel

     (TButton)

     Name btnCancel
     Align  Left
     StyleLookup backtoolbutton
     Text 취소

     btnSaveitem

     (TButton)

     Name btnSaveItem
     Align

     Right

     StyleLookup

     donetoolbutton

     Text 

     저장

     TabItem3

     vsbEditForcus

     (TVertScrollBox)

     Name

     vsbEditFocus

     Align Client
     vsbEditForcus lytContentsNew(TLayout) Name lytContentsNew
     Align Client
     Layout3 Layout4 Align Top
     Height 113
     Layout4 Rectangle2 Align Center

     Fill.Kind

     None

     Width / Height

     100 / 100
     Rectangle2 imgNewItem(TImage) Name imgNewItem
     Align Client
     Margins 2, 2, 2, 2
     Layout3 ListBox2 Align Client
     ListBox2

     ListBoxItem7 ~ 12

     (6개 추가)

     StyleLookUp
     Text


     listboxitemnodetail
     제목, 저자, 출판사, 연락처, 사이트, 감상평

     ListBoxitem12

     Height 100

     ListBoxitem7

     edtTitle(TEdit)

     Align
     Margins
     Client
     7, 80, 5, 7

     ListBoxitem8

     edtAuthor(TEdit)

     ListBoxitem9

     edtPublisher(TEdit)

     ListBoxitem10

     edtPhone(TEdit)

     ListBoxitem11

     edtWebSite(TEdit)

     ListBoxitem12

     mmoComment(TMemo)
     mmoComment

     TextSettings.WordWrap

     True


    3, 샘플 데이터 추가 후 화면요소와 연결해 화면 표시 후 테스트하기

    화면 테스트를 위해 샘플(프로토타입) 데이터를 데이터 모듈에 추가합니다. 뒤에서 샘플 데이터를 실제 데이터베이스로 변경합니다.

    데이터 엑세스 기능을 구현하기 위해 데이터모듈 추가

    1. File > New > Other 메뉴를 선택 해 "New Items" 대화상자를 표시합니다.
    2. 왼쪽 트리메뉴에서 Object Pascal Projects > Object Pascal Files를 선택합니다.
    3. Data Module을 선택 하고 OK 버튼을 누릅니다.
    4. 데이터 모듈이 추가되면 File > Save 메뉴를 누르고 "DataAccessModule.pas"로 저장합니다.
    5. Object Inspect(속성창)에서 Name 속성을 dmDataAccess로 변경합니다.

    테스트용 샘플 데이터 추가

    1. 데이터모듈에 TPrototypeDataSource를 추가합니다.
    2. PrototypeDataSource1의 RecordCount30으로 변경합니다.
    3. PrototypeDataSource1을 더블클릭 후 Add New 버튼으로 위 그림을 참고해 항목 5개를 추가합니다.

    샘플데이터와 화면요소 연결하기

    1. MainForm으로 돌아와 위에서 추가한 데이터 모듈 사용하기 위해 File > Use unit 메뉴를 선택하고 "DataAccessModule.pas" 파일을 선택 후 OK 버튼을 누릅니다.
    2. View > LiveBindings Designer 메뉴를 통해 라이브 바인딩 디자이너를 표시하고, 아래 그림을 참고해 화면요소에 데이터를 연결합니다.(두 항목간 마우스로 드래그해 연결)

    도서 목록

    도서 상세보기

    새로운 도서 추가


    연결을 마치면 Live Bindings Designer를 닫습니다.(창 우측 상단 X 버튼)


    4, 사용자의 조작에 동작하는 기능코드를 작성합니다.

    사용자가 목록을 누르고 버튼을 누를때 동작하는 기능을 구현 합니다.

    화면(탭)이동 기능 구현

    1. 폼에 TActionList(ActionList1) 컴포넌트를 추가합니다.
    2. ActionList1을 더블클릭 후 Add Action > New Standard Action 메뉴를 선택 합니다.
    3. Tab > TChangeTabAction 항목 선택 후 OK 버튼을 눌러 추가합니다.
    4. 코드 에디터를 열고(폼 디자이너에서 F12 버튼) 선언부(코드 상단) private 영역에 아래 코드를 입력합니다.

        private
          { Private declarations }
          procedure GotoList;
          procedure GotoDetail;
          procedure GotoNew;
        public
          { Public declarations }
        end;
    5. 구현부(선언부 입력 후 Ctrl + Shift + C 단축키를 누르면 구현부의 구조가 자동 완성됩니다.)에 아래의 코드를 입력합니다.
      procedure TForm1.GotoDetail;
      begin
        ChangeTabAction1.Tab := TabItem2;
        ChangeTabAction1.ExecuteTarget(nil);
      end;
      
      procedure TForm1.GotoList;
      begin
        ChangeTabAction1.Tab := TabItem1;
        ChangeTabAction1.ExecuteTarget(nil);
      end;
      
      procedure TForm1.GotoNew;
      begin
        ChangeTabAction1.Tab := TabItem3;
        ChangeTabAction1.ExecuteTarget(nil);
      end;
    6. 도서 목록 탭에서 ListView1의 OnItemClick 이벤트에 아래의 코드를 입력[각주:3]합니다.

      procedure TForm1.ListView1ItemClick(const Sender: TObject;
        const AItem: TListViewItem);
      begin
        GotoDetail;
      end;
    7. 위 방식과 같이 아래의 표를 참고해 이벤트 핸들러 코드를 입력합니다.

      오브젝트 

      이벤트 

      소스코드 

       btnNewItem

       OnClick 

       GotoNew;

       btnBackList

       OnClick

       GotoList;
       btnCancel

       OnClick

       GotoList;

       btnSaveItem 

       OnClick

       GotoList;

    8. 폼을 선택하고 OnCreate 이벤트에 아래 코드를 입력합니다.(실행 시 탭을 감추고 첫번째 탭을 표시하는 코드입니다.)

      procedure TForm1.FormCreate(Sender: TObject);
      begin
        TabControl1.TabPosition := TTabPosition.None;
        TabControl1.TabIndex := 0;
      end;


    도서 상세정보 수정/삭제 팝업메뉴 구현

    1. 도서 상세정보 탭의 추가정보 버튼(btnDetail)과, 수정(lstItemModify), 삭제(lstItemDelete) 항목의 OnClick 이벤트에 아래를 참고해 코드를 추가합니다.

      procedure TForm1.btnDetailClick(Sender: TObject);
      begin
        OverflowMenu.Visible := not OverflowMenu.Visible;
        if OverflowMenu.Visible then
        begin
          OverflowMenu.ItemIndex := -1;
          OverflowMenu.BringToFront;
          OverflowMenu.ApplyStyleLookup;
          OverflowMenu.RealignContent;
          OverflowMenu.Position.X := Width - OverflowMenu.Width - 5;
          OverflowMenu.Position.Y := Toolbar2.Height;
        end;
      end;
      
      // 수정
      procedure TForm1.lstItemModifyClick(Sender: TObject);
      begin
        OverflowMenu.Visible := False;
        GotoNew;
      end;
      
      // 삭제
      procedure TForm1.lstItemDeleteClick(Sender: TObject);
      begin
        OverflowMenu.Visible := False;
      
        MessageDlg('해당 정보를 삭제하시겠습니까?', TMsgDlgType.mtWarning,
          [TMsgDlgBtn.mbYes, TMsgDlgBtn.mbNo], 0, procedure(const AResult: TModalResult)
          begin
            if AResult = mrYes then
            begin
              ShowMessage('삭제');
            end;
          end);
      end;
    2. 폼을 선택하고 OnCreate 이벤트에 아래 코드를 추가합니다.

      procedure TForm1.FormCreate(Sender: TObject);
      begin
        TabControl1.TabPosition := TTabPosition.None;
        TabControl1.TabIndex := 0;
      
        OverflowMenu.Visible := False; // 시작 시 팝업메뉴 감추기
      end;

    입력 시 키보드가 가려지지 않도록 입력박스 위치조정 기능 구현

    하단의 입력박스에 입력 시 키보드가 입력박스를 가리는 경우 키보드 위로 입력박스 위치를 조정하는 기능을 구현합니다.

    1. 선언부 private 영역에 아래 코드를 입력합니다.
        private
          { Private declarations }
          FKBBounds: TRectF;
          FNeedOffset: Boolean;
      
          procedure CalcContentBoundsProc(Sender: TObject; var ContentBounds: TRectF);
          procedure RestorePosition;
          procedure UpdateKBBounds;
    2. 구현부에 아래의 코드를 입력합니다.
      procedure TForm1.CalcContentBoundsProc(Sender: TObject;
        var ContentBounds: TRectF);
      begin
        if FNeedOffset and (FKBBounds.Top > 0) then
        begin
          ContentBounds.Bottom := Max(
                ContentBounds.Bottom, 2 * ClientHeight - FKBBounds.Top);
        end;
      end;
      
      procedure TForm1.RestorePosition;
      begin
        vsbEditFocus.ViewportPosition := PointF(vsbEditFocus.ViewportPosition.X, 0);
        lytContentsNew.Align := TAlignLayout.Client;
        vsbEditFocus.RealignContent;
      end;
      
      procedure TForm1.UpdateKBBounds;
      var
        LFocused : TControl;
        LFocusRect: TRectF;
      begin
        FNeedOffset := False;
        if Assigned(Focused) then
        begin
          LFocused := TControl(Focused.GetObject);
      
          LFocusRect := LFocused.AbsoluteRect;
          LFocusRect.Offset(vsbEditFocus.ViewportPosition);
          if (LFocusRect.IntersectsWith(TRectF.Create(FKBBounds))) and
             (LFocusRect.Bottom > FKBBounds.Top) then
          begin
            FNeedOffset := True;
            lytContentsNew.Align := TAlignLayout.Horizontal;
            vsbEditFocus.RealignContent;
            Application.ProcessMessages;
            vsbEditFocus.ViewportPosition := PointF(vsbEditFocus.ViewportPosition.X,
                                                    LFocusRect.Bottom - FKBBounds.Top);
          end;
        end;
        if not FNeedOffset then
          RestorePosition;
      end;
    3. 코드에서 수학함수(Max)를 사용하기 위해 구현부(implementation) uses 절에 System.math 유닛을 추가합니다.

      implementation
      
      {$R *.fmx}
      
      uses DataAccessModule, System.Math;
    4. 폼(Form1)의 OnFocusChanged, OnVirtualKeyboardShown, OnVirtualKeyboardHidden 이벤트에 아래 코드를 참고해 코드를 추가합니다.
      procedure TForm1.FormFocusChanged(Sender: TObject);
      begin
        UpdateKBBounds;
      end;
      
      procedure TForm1.FormVirtualKeyboardHidden(Sender: TObject;
        KeyboardVisible: Boolean; const Bounds: TRect);
      begin
        FKBBounds.Create(0, 0, 0, 0);
        FNeedOffset := False;
        RestorePosition;
      end;
      
      procedure TForm1.FormVirtualKeyboardShown(Sender: TObject;
        KeyboardVisible: Boolean; const Bounds: TRect);
      begin
        FKBBounds := TRectF.Create(Bounds);
        FKBBounds.TopLeft := ScreenToClient(FKBBounds.TopLeft);
        FKBBounds.BottomRight := ScreenToClient(FKBBounds.BottomRight);
        UpdateKBBounds;
      end;
    5. 폼의 OnCreate 이벤트에 아래 코드를 추가합니다.

      procedure TForm1.FormCreate(Sender: TObject);
      begin
        TabControl1.TabPosition := TTabPosition.None;
        TabControl1.TabIndex := 0;
      
        OverflowMenu.Visible := False;
      
        vsbEditFocus.OnCalcContentBounds := CalcContentBoundsProc; // 추가
      end;


    5, 전화걸기, 사진, 웹페이지 표시 기능 추가

    전화걸기, 사진 촬영, 불러오기, 웹페이지 표시 기능을 추가합니다.


    전화걸기 기능 추가하기

    1. 전화걸기 기능 사용을 위해 FMX.Platform과 FMX.PhoneDialer 유닛을 구현부(implementation) 유즈절에 추가합니다.

      implementation
      
      {$R *.fmx}
      
      uses DataAccessModule, System.Math, FMX.Platform, FMX.PhoneDialer;

    2. 도서 상세보기 탭의 lblPhone(연락처 항목)의 OnClick 이벤트 핸들러에 아래의 코드를 입력합니다.

      procedure TForm1.lblPhoneClick(Sender: TObject);
      var
        PhoneDlrSvc: IFMXPhoneDialerService;
      begin
        if TPlatformServices.Current.SupportsPlatformService(IFMXPhoneDialerService, IInterface(PhoneDlrSvc)) then
          PhoneDlrSvc.Call(lblPhone.Text);
      end;


    웹페이지 표시 기능, 사진 기능 추가하기

    웹페이지 기능과 사진 기능은 이미 구현된 소스코드를 다운받아 구현합니다.

    1. 카메라 기능, 웹브라우저 기능이 구현된 프레임 소스코드 를 다운로드 받고 압축 해제 후 프로젝트 파일(BookLogFMX.dpr) 저장경로의 하위에 Frames 폴더를 추가 후 Frames 폴더 안으로 복사합니다.

    2. Project Manager 윈도우에서 프로젝트 이름(BookLogFmx)에 마우스 오른쪽 팝업메뉴를 표시하고 Add... 메뉴를 선택 후 위 1번에서 복사한 "PhotoFrame.pas"를 선택 해 추가합니다.

    3. PhotoFrame 사용을 위해 구현부 uses절에 WebBrowserFrame, PhotoFrame을 추가합니다.

    4. "도서 상세보기" 탭의 lblWebSite(사이트 항목)의 OnClick 이벤트 핸들러에 아래의 코드를 입력합니다.

      procedure TForm1.lblWebSiteClick(Sender: TObject);
      begin
        TfrWebBrowser.CreateAndShow(Self, lblWebSite.Text);
      end;

    5. 선언부 private  영역에 아래의 메소드를 선언하는 코드를 추가합니다.

          procedure ChangeImageEvent(Image: TBitmap);

    6. 구현부에 아래의 코드를 추가합니다.

      procedure TForm1.ChangeImageEvent(Image: TBitmap);
      begin
        imgNewItem.Bitmap.Assign(Image);
      end;

    7. "새로운 도서 추가" 탭의 imgNewItem(사진)의 OnClick 이벤트 핸들러에 아래 코드를 입력합니다.

      procedure TForm1.imgNewItemClick(Sender: TObject);
      begin
        TfrPhoto.CreateAndShow(Self, ChangeImageEvent, nil);
      end;

    8. 안드로이드 백버튼을 누를때 기능이 닫히게 하기위해 폼(Form1)의 OnKeyUp 이벤트에 아래 코드를 입력합니다.
      procedure TForm1.FormKeyUp(Sender: TObject; var Key: Word; var KeyChar: Char;
        Shift: TShiftState);
      begin
        if Key = vkHardwareBack then
        begin
          if Assigned(frPhoto) then
          begin
            frPhoto.CloseFrame;
            Key := 0;
          end;
      
          if Assigned(frWebBrowser) then
          begin
            frWebBrowser.CloseFrame;
            Key := 0;
          end;
        end;
      end;


    6, 사용자화면 완성! 테스트 하기

    1차 사용자화면 개발완료되었습니다. 

    • 배포를 원하는 플랫폼을 선택하고 Run > Run Without Debugging 메뉴를 통해 배포 및 실행합니다.
      (안드로이드 개발환경 설정은 앱메소드 튜토리얼 동영상을 통해 확인할 수 있습니다.)

    2차 따라하기에서는 실제 사용할 데이터베이스를 만들고 앱에서 데이터 연결하는 내용을 진행합니다.

    관련글


    1. TabItem 추가는 TabControl 더블클릭 후 나오는 Items Designer 대화상자에서 TTabItem 선택 후 [Add Item] 버튼을 눌러 추가 가능 [본문으로]
    2. ListBoxItem 추가는 ListBox컴포넌트 더블클릭 후 나타나는 Items Designer 대화상자에서 TListBoxItem을 선택 후 [Add Item] 버튼을 눌러 추가 가능 [본문으로]
    3. Object Inspector 창의 Event 탭으로 이동 후 OnItemClick 글자 옆의 공백을 더블클릭하면 이벤트 핸들러 코드가 자동추가 [본문으로]
    저작자 표시 비영리 동일 조건 변경 허락
    신고
    크리에이티브 커먼즈 라이선스
    Creative Commons License

    험프리.김현수 험프리.김현수 Firemonkey/모바일 앱 예제

    8단계로 완성하는 "2014년 출시 앨범" 앱 개발 따라하기

    2014.10.06 15:39

    음원정보 제공 웹사이트(DiscoGS)의 정책 상 인증과정(OAuth 2.0)이 추가되어 부득이 아래 글을 따라할 수 없습니다. 

    하지만 아래 과정을 통해 웹서비스 연동하는 방안을 이해하는데 도움될 것입니다.


    이번 글에서는 올해(2014년도)에 출시된 앨범 목록과 앨범 수록곡을 확인할 수 있는 "2014년 출시 앨범" 앱을 8단계를 따라하며 만들어 보겠습니다.

    "2014년 출시 앨범" 앱 개발 따라하기

    ❑ 앱 소개

    • 2014년(올해) 출시된 앨범 목록 정보 제공
    • 앨범 선택 시 앨범정보와 수록곡 정보 제공
    • 앨범정보는 음반정보 제공 사이트 DiscoGS의 공개 API(http://api.dicogs.com) 이용

    따라하기를 통한 습득할 수 있는 기술
    • REST 클라이언트 라이브러리를 이용해 정보 조회
    • REST Debugger 개발도구를 이용해 REST API 분석 후 컴포넌트 재사용
    • Live Binding을 이용해 데이터와 화면 컨트롤 링크(마우스 연결)로 화면 출력
    • 투명도를 이용해 배경화면 설정
    따라하기는 Delphi XE7을 기준으로 작성되었습니다.

    따라하기

    1, 프로젝트 생성

    File > New > Multi-Device Application – Delphi 메뉴선택하여 Blank Application 선택해 프로젝트를 생성합니다.

    File > Save all 메뉴를 선택하고, 유닛이름은 기본(Unit1.pas)로 프로젝트 이름은 "music2014.dproj"로 저장합니다.

    2, 화면 레이아웃 구성

    아래의 그림과 표를 참고해 화면에 컴포넌트를 추가하고 레이아웃을 구성합니다.


     상위 오브젝트

    오브젝트 

    속성 

    값(또는 설명) 

     Form1

     MultiView1

     Visible

     True 

     Form1

     Layout1 

     Align

     Client 

     MultiView1

     MasterToolBar

     Align

     Top 

     MasterToolBar

     MasterLabel

     Align

     Client 

     StyleLookup

     toollabel

     Text

     2014년 출시 앨범

     TextSettings.HorzAlign

     Center

     MultiView1

     ListView1 

     Align 

     Client 

     CanSwipeDelete False
     Layout1

     DetailToolbar 

     Align 

     Top 

     DetailToolbar

     DetailLabel

     Align Contents
     StyleLookup toollabel 

     Text

     앨범정보
     MasterButton Align Left 

     StyleLookup

     detailstoolbutton 
     Layout1 ListBox1  Align  Top 
     Height 153 
     ListBox1

     ListBoxGroupHeader1 

     Text  앨범정보 

     ListBoxItem1

     Text 앨범명
     StyleLookup listboxitemrightdetail

     ListBoxItem2

     Text

     출시년도

     StyleLookup

     listboxitemrightdetail

     ListBoxGroupHeader2

     Text 수록곡
     Layout1 ListView1  Align  Client 
     CanSwipeDelete  False 
     ItemAppearance.ItemAppearance ListItemRightDetail 

     ItemAppearanceObject.

     ItemObject.Accessory.Visible

     False 

    3, 웹서비스 데이터 분석

    웹서비스 API 분석은 RAD Studio에서 제공하는 REST 분석도구인 REST Debugger를 이용합니다.

    Tools > REST Debugger 메뉴로 REST Debugger를 실행하고 다음 순서대로 작업합니다.

    1. Request 탭에서 "http://api.discogs.com"  입력
    2. Parameters 탭에서 Resource 박스에 다음 내용 입력 [Send Request] 버튼 클릭
      • database/search?type=release&per_page=20&country={country}&year={year}
    3. Request Parameters 수정(항목 더블클릭 또는 선택 후 [Edit] 버튼 클릭)
      • country=South Korea
      • year=2014
    4. JSON Root Element 항목에 results 입력 [Apply] 버튼 클릭
    5. 하단 Tabular Data 탭에서 데이터 확인 [Copy Components] 버튼으로 컴포넌트 클립보드에 복사

    4, REST 클라이언트 컴포넌트 추가

    폼디자이너 화면으로 돌아와서 클립보드에 복사된 컴포넌트 붙여넣기(Ctrl-V)로 컴포넌트를 생성합니다.

    수록곡 정보 표시용 컴포넌트 추가를 위해 3번과 같은 방법으로 다음 정보를 참고해 한 세트를 더 생성합니다.

      1. Parameters 탭에서 Resource 박스에 다음 내용 입력  [Send Request] 버튼 클릭
        • releases/{_id}
      2. [Edit] 버튼으로 Request Parameters 수정
        • _id : 5583330
      3. JSON Root Element 항목에 tracklist 입력  [Apply] 버튼클릭
      4. 데이터 로딩 확인  [Copy Components] 버튼으로 Components 클립보드에 복사 후 폼디자이너에서 컴포넌트 붙여넣기


    2개의 RESTRequest 컴포넌트를 더블클릭 하거나 마우스 우클릭 메뉴의 Execute를 이용해 요청을 확인합니다.

    5, Live Binding 데이터 연결

    View > LiveBindings Designer 메뉴를 실행하여 그림과 같이 FDMemTable 항목과 ListView ListBox 항목에 마우스 드래그로 데이터를 연결합니다.

    ListBoxItem의 ItemData.Detail을 표시하기 위해 ListBoxItem 영역의 [...] 버튼을 누르고 detail로 검색 후 멤버를 추가 후 진행할 수 있습니다.

    6, 데이터 요청 구현

    Live Binding 정상적으로 연결 되었으면 그림과 같이 데이터를 불러 있습니다.


    실행 시 데이터를 로드하고 앨범 선택 시 수록곡 정보를 가져오는 코드를 아래와 같이 추가합니다.

    오브젝트 

    이벤트 

    소스코드 

     Form1

    OnShow 

     RESTRequest1.Execute; 

     RESTRequest2.Params.ParameterByName('_id').Value := 

                                                          FDMemTable1.FieldValues['id']; 

     RESTRequest2.Execute; 

     MultiView1.ShowMaster; // 시작 시 메뉴 표시

     ListView1

    OnItemClick 

     MultiView1.HideMaster;

     RESTRequest2.Params.ParameterByName('_id').Value := 

                                                          FDMemTable1.FieldValues['id'];

     RESTRequest2.Execute;

    7, 앱 배경화면 꾸미기

    배경화면을 꾸미기 위해 메인화면(Layout1)에 Rectangle 컴포넌트를 추가하도록 아래의 표를 참고해 구성합니다.



     상위 오브젝트

    오브젝트 

    속성 

    값(또는 설명) 

     Layout1

     Rectangle1

     Align Contents
     Fill.Kind Bitmap 
     Fill.Bitmap.Bitmap

     아래 이미지 파일을 다운로드 후 선택

     


     Fill.Bitmap.WrapMode

     TileStretch 

     Hittest

     False 

     Opacity

     0.3


    8, 배포

    모든 개발이 완료되었습니다. 프로젝트 매니저에서 Target Platform을 선택 후 Run > Run Without Debugging 메뉴를 선택 해 앱을 실행 해 결과 화면을 확인합니다.


    ❑ 프로젝트 소스코드

    관련글



    저작자 표시 비영리 동일 조건 변경 허락
    신고
    크리에이티브 커먼즈 라이선스
    Creative Commons License

    험프리.김현수 험프리.김현수 Firemonkey/모바일 앱 예제 Delphi XE7, discogs, Rest Client, RESTful

    예제로 배우는 모바일 앱개발

    2014.07.16 15:40


    안녕하세요. 험프리.김현수입니다.


    RAD Studio앱메소드(AppMethod)는 파이어몽키 프레임워크(데브기어 소개자료, FireMonkeyX)를 이용해 모바일 앱을 쉽고 멋지게 만들 수 있습니다.
    이미 다양한 기술을 설명하는 글과 샘플이 있어 그 것들을 잘 참고한다면 여러분들도 쉽게 앱을 만들 수 있습니다.

    하지만, 
    음식도 먹어본 사람이 맛을 알듯이, 새로운 영역의 모바일 개발의 경우 어떻게 시작할지, 무엇부터 해야 할지 막막할 수 있습니다. 그런경우 차근차근 책과 강좌를 통해 처음부터 익힐 수도 있지만, 뚝딱뚝딱 예제를 돌려보고 만저보며 우선 만들어 본 후 모르는 부분을 찾아서 익히는 방법도 있습니다.

    이 글의 목적

    이 글에서는 모바일 앱 개발을 시도해 보며 익히시려는 분들을 위해 실전앱에서 바로 사용할 수 있는 예제와 설명을 제공합니다.


    모바일 앱 만들기를 처음 시도할 경우. 완성도 있는 예제와 소스가 있어 참고한다면 아주 큰 도움이 됩니다.
    예제소스를 기반으로 화면을 바꿔보고 기능을 추가해 첫번째 앱을 빠르게 만들고,  앱을 만들며 궁금했던 내용을 찾아보거나 필요한 기능 추가를 위해 관련 기술과 예제을 찾아 추가하다 보면 어느덧 익숙해질 것 입니다.

    이번 글은 완성도 있는(실용적인) 모바일 앱 예제 제공에 목적을 둡니다. 
    예제의 소스코드를 제공하고 예제의 구조와 사용법을 설명해 여러분들의 모바일 앱 개발의 시작을 돕는 것이 목적입니다. 
    예제에서 사용되는 참고할 만한 기술은 도움말과 팁을 제공해 추가 학습할 수 있도록 하겠습니다.

    이글의 수준
    • 이 글은 기본적인 툴과 컴포넌트에 대한 사용법과 Object Pascal 문법은 다루지 않습니다. 
    • 모바일 개발이 처음이더라도 이전에 델파이 등의 프로그래밍 개발 경험이 있는 사용자를 대상을 작성합니다.

    이 글은 여러분의 의견이 중요합니다. 글의 난이도가 어렵가나, 필요한 예제와 주제에 대한를 댓글과 메일로 피드백 주시면 최대한 참고해 글을 작성하도록 하겠습니다.

    제공되는 소스코드

    이글에서 사용되는 모든 예제소스는 아래의 GitHub에 있습니다.(수시로 추가, 수정됩니다.)

    대부분의 예제은 Object Pascal로 제공되지만 최대한 C++ 문법의 예제도 추가하도록 하겠습니다.

    모바일 사용자 인터페이스(User Interface) 구성

    모바일 UI의 경우 모바일의 화면(디스플레이) 특성상 좁은 화면에 데이터를 효율적으로 표시해야 하고, 터치 인터페이스를 통해 화면을 제어해야 합니다. 
    이미 다양한 모바일 UI 기법들이 모바일에서 사용되고 있습니다. 모바일에서 자주 사용되는 UI를 파이어몽키를 통해 어떻게 구성하는지 예제을 통해 함께 알아보고 익히도록 합니다.

    ❑ 사이드바 형태 메뉴(Sidebar drawer menu) 만들기 - 자세히

    모바일에서는 화면을 최대한 사용하기 위해서 좌측/우측의 사이드바를 통해 메뉴를 제공하는 UI가 많이 사용됩니다.

    옆의 그림과 같이 사이드바 메뉴가 나왔다 들어가는 형태의 메뉴를 구성하는 예제에 대해 소개합니다.


    이 예제는 다음 요소들로 구성됩니다.

    • 사이드바 : 상단 정보영역(사진과 이름 등)과 메뉴(ListBox)로 구성, 그림자 효과(Effect)

    • 툴바(상단) : 좌측의 메뉴 버튼을 누르면 메뉴가 나오고 다시 누르면 들어갑니다. 이때 애니메이션(TFloatAnimation)을 이용해 자연스럽게 표현됩니다.

    • 사이드바 배경 : 사이드바가 나오면 사이드바 외의 영역에 회색배경으로 표시되고 배경을 누르면 사이드바가 닫힙니다.

    난이도 : ★☆☆ 활용도 : ★★★


    ❑ 프레임을 활용해 화면을 분리해 구현하는 방법 - 준비중

    모바일 앱에 기능과 화면을 추가하면 그에 비례하여 메인 폼과 소스코드가 많아져 복잡해 집니다. 이럴 경우 프레임(TFrame)을 이용해 화면과 기능을 분리할 수 있습니다.

    같은기능을 폰과 태블릿으로 해상도별 분리해 개발할 경우에도 프레임을 이용해 화면과 기능을 제활용 할 수 있습니다.

    그리고 프레임에서 필요한 메소드를 Interface를 이용해 추가하고 메인 폼에서 프레임의 Unit을 추가(uses)하지 않고 사용하는 기법을 설명합니다.

    난이도 : ★★★ 활용도 : ★★☆


    ❑ Swipe menu - 사이드바 메뉴를 끌어서 표시/감추기 -자세히

    Swipe menu는 유투브, 페이스북등의 모바일 앱에서 일반적으로 사용하는 메뉴 호출 방식입니다.

    손가락으로 화면을 끌어서 메뉴를 표시하고 다시 손가락으로 밀어서 메뉴를 닫는 터치 인터페이스 입니다.


    파이어몽키에서는 터치 인터페이스를 지원하는 방식으로 제스처(Gesture)와 마우스 이벤트(Down, Move, Up 등)를 제공합니다. 

    이 예제에서는 그중 제스처를 이용해 Swipe 메뉴를 구현하고 왜 제스처를 이용하는지 설명하였습니다.


    이번 예제는 다음의 항목을 학습하실 수 있습니다.

    • 제스처 이벤트와 EventInfo란?
    • 이동된 좌표를 계산하고 다른 컨트롤을 제어하는 방법
    • 인터페이스(Interface)를 이용해 공통기능을 사용하는 방법


    이 예제는 앞에서 설명한 사이드바 형태의 메뉴 만들기와 프레임을 활용해 UI를 분리해 구현하는 방법 선행학습이 필요 합니다.

    난이도 : ★★★ 활용도 : ★★☆

    ☞ 자세히보기


    모바일에서 데이터 사용하기

    기업(Enterprise)용 모바일 앱의 경우 UI를 구성하고, 데이터를 연결하는 것 만으로 주요 기능이 완성되었다 할 수 있습니다.

    이번에는 모바일에서 사용할 수 있는 다양한 데이터들과 연결하고 사용하는 방법을 소개합니다. 


    데이터 연결하는 기능에 집중하기 위해 사원정보 관리 기능으로 총 4가지의 방식으로 데이터에 연결하는 방법을 알아보겠습니다.

    • IBLite - 앱과 함께 배포해 사용할 수 있는 임베디드 데이터베이스 중 하나인 IBLite
    • REST Client - 이미 구축된 WAS등의 웹서비스나 다양한 OpenAPI에 RESTful 방식으로 접속
    • Data Snap - RAD Studio와 AppMethod로 가장 쉽게 구성할 수 있는 미들웨어에 접속
    • BaaS Client - 클라우드 서비스 중 백엔드기능을 제공하는 BaaS에 연동

    ❑ 공통으로 사용할 UI 구성

    IBLite, REST Client, DataSnap, BaaS Client를 통해 모바일에서 데이터 사용하는 방법을 알아볼 것입니다. 앞의 4가지 데이터는 공통적으로 사원정보 데이터를 이용할 것이고, 사원정보를 처리하는 UI는 이번에 만드는 UI를 공통적으로 사용할 것입니다.

    ❑ 모바일에서 IBLite 접속 - 준비중

     

    모바일 앱 배포 시 함께 배포 해 사용하는 임베디드(포함) 데이터베이스 중 IBLite를 이용해 로컬 데이터베이스를 사용하는 방법에 대해 알아봅니다. 


    다음 내용을 학습하실 수 있습니다.

    • IBLite 데이터베이스 파일 생성

    • IBLite 사용하는 모바일 앱 개발

    • IBLite 라이선스 등록

    • 모바일 앱 배포 시 파일 및 라이선스 함께 배포


    ❑ REST Client를 통해 웹서비스에 접속 - 준비중

    웹서비스의 데이터를 모바일에서 사용합니다.

    ❑ DataSnap 미들웨어에 접속 - 준비중

    3티어 환경을 DataSnap을 이용해 구성합니다.

    ❑ BaaS(Cloud Service)의 데이터 사용 - 준비중 

    클라우드 서비스 중 모바일의 백엔드 기능을 모바일에서 사용합니다.
    • 파일과 데이터 스토리지
    • 사용자 가입과 정보조회
    • 원격 푸시 메시지 전송 및 수신

    ❑ 데이터를 나눠서 가져오는 기법 - 준비중


    교육용 샘플

    처음 AppMethod 또는 RAD Studio를 접하는 사용자들에게 최소한의 코딩으로 결과물을 확인할 수 있는 샘플을 제공합니다.

    ❑ 계산기 만들기 

      (준비 중)


      저작자 표시 비영리 동일 조건 변경 허락
      신고
      크리에이티브 커먼즈 라이선스
      Creative Commons License

      험프리.김현수 험프리.김현수 Firemonkey/모바일 앱 예제

      1. Blog Icon
        초보앱개발자

        안녕하세요 이번에 XE8를 구매하여 사용하는 초보 앱개발자 입니다.

        다름이 아니라 간단히 ios 어플을 만든다음 바로 제 아이폰에 컴파일 하고 싶은데

        어떻게 해야하는지 혹시 나와있는 자료가 있을까요?

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

      2014.07.16 15:09

      ☜ 목록으로 돌아가기

      시작하기에 앞서

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


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

      그럼 시작하겠습니다.

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

      사이드바가 나오는 형태

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

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

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





      저작자 표시 비영리 동일 조건 변경 허락
      신고
      크리에이티브 커먼즈 라이선스
      Creative Commons License

      험프리.김현수 험프리.김현수 Firemonkey/모바일 앱 예제

      1. Blog Icon
        민재아범

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

      2. 네... XE6에서 Prefix가 대부분 빠졌습니다.
        그래서 XE5에서는 일부 손을 보셔야합니다.^^

      3. Blog Icon
        민재아범

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

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

      5. Blog Icon
        민재아범

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

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

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

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

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

      7. Blog Icon
        cider

        문의드립니다..
        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을 붙이구, 이벤트를 넣어도 오류가 나서,. 이게 어떤이유에서 그런지 정확히 모르겠습니다.

        답변부탁드립니다.^^

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

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

        의견 감사합니다.

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

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

      10. Blog Icon
        굥굥

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

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

      12. Blog Icon
        QU

        문의드립니다.
        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가 나오는데,,,, 해결방법을잘모르겠습니다.
        혹시 방법을 알고계시다면 답변부탁드립니다 ㅠ_ㅜ...

      13. Blog Icon
        buzz

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

      [모바일앱예제] 유튜브, 페이스북에서 사용되는 Swipe 메뉴 샘플

      2014.07.15 09:43

       목록으로 돌아가기

      시작하기에 앞서


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


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

      만약, 내용이 어려운 경우 해당 프로젝트를 다른 이름으로 저장 후 Frame 부분에 기능을 추가해 앱을 개발해도 무방합니다.
      그럼 시작합니다.

      이번 글에서는 스와이프(Swipe) 메뉴를 파이어몽키에서 구현하는 방법을 설명합니다.

      Swipe menu는 유투브, 페이스북등의 앱에서 많이 사용하는 손가락으로 끌어서 메뉴를 호출하는 방식입니다.


      이번 글은 아래 글의 연장이므로 아래 글을 먼저 보시기 바랍니다.

      (링크가 만들어지면 링크를 추가하겠습니다.)


      최종 구현되는 앱의 모습은 아래의 동영상과 같습니다.


      소스코드

      Gesture 이벤트

      Swipe menu 구현의 핵심은 폼의 Gesture 이벤트입니다. 메인폼의 Gesture 이벤트 코드를 먼저 보고 설명합니다.

      procedure TForm1.FormGesture(Sender: TObject;
        const EventInfo: TGestureEventInfo; var Handled: Boolean);
      var
        MovePos: TPointF;
      begin
        if EventInfo.GestureID = igiPan then
        begin
          // Touch event 시작
          if TInteractiveGestureFlag.gfBegin in EventInfo.Flags then
          begin
            FSwipeData.Direction := TSwipeDirection.None;
            FSwipeData.MouseDownPos := EventInfo.Location;
          end
          // Touch event 이동
          else if EventInfo.Flags = [] then
          begin
            if FSwipeData.Direction = TSwipeDirection.None then
            begin
              MovePos := EventInfo.Location - FSwipeData.MouseDownPos;
      
              // Android의 경우 첫번째 EventInfo.Location과 FSwipeData.MouseDownPos가 같음
              if MovePos = PointF(0, 0) then
                Exit;
              // SWIPE_MOVE_MINVALUE(10)이상 움직인 경우 시작
              if FSwipeData.StartPos.Distance(EventInfo.Location) > SWIPE_MOVE_MINVALUE then
              begin
                if (Abs(MovePos.X) > Abs(MovePos.Y) * 2) then
                begin
                  FSwipeData.Direction := TSwipeDirection.Horizontal;
                  DoSwipeBegin(FSwipeData.MouseDownPos);
                end
                else
                begin
                  FSwipeData.Direction := TSwipeDirection.Etc;
                end;
              end;
            end;
      
            if FSwipeData.Direction = TSwipeDirection.Horizontal then
              DoSwipe(EventInfo.Location);
          end
          // Touch event 끝(손가락을 뗌)
          else if TInteractiveGestureFlag.gfEnd in EventInfo.Flags then
          begin
            if FSwipeData.Direction = TSwipeDirection.Horizontal then
              DoSwipeEnd(EventInfo.Location);
            FSwipeData.Direction := TSwipeDirection.None;
          end;
        end;

      위 소스코드에서 아래의 내용을 알아야 합니다.

      • 제스처 이벤트 정보(EventInfo)
      • 터치 이벤트의 시작과 끝
      • Swipe 이벤트의 시작 조건

      ❑ 제스처 이벤트 정보(EventInfo)

      Gesture 이벤트가 발생 시 제스처 이벤트의 정보가 EventInfo 파라메터에 담겨 전달됩니다. 

      EventInfo는 TGestureEventInfo 타입의 구조체(record)이며 다음과 같은 정보가 전달됩니다.

      • GestureID : 제스처의 종류(Pan, Rotate, TwoFingerTap, PressAndTap, LongTap, DoubleTap)
      • Location : 제스처 이벤트 발생 좌표(TPointF)
      • Flags : 제스처 상태(TInteractiveGestureFlag) 집합
      • 기타 Angle, InertiaVector, Distance, TapLocation
      • 제스처 이벤트 정보의 자세한 정보는 TGestureEventInfo 도움말을 통해 더 자세히 알아보세요.

      소스코드에서 사용되는 속성은 GestureID, Location, Flags 3가지입니다.

      ❑ 터치 이벤트의 시작과 끝

      Swipe menu는 제스처 이벤트 중 끄는방식의 Pan 제스처를 이용합니다.(GestureID = igiPan)

      폼에서 Pan 제스처 사용을 위해서는 폼의 속성에서 Touch.InterctiveGestures.Pan 속성을 True로 설정해야지 Pan 동작 시 OnGesture 이벤트가 발생합니다.



      Pan(끄는) 이벤트의 시작(손가락을 붙일 때)과 끝(손가락을 뗄 때)은 EventInfo.Flags 항목을 통해 판단할 수 있습니다.


      소스코드를 참고해 보면 아래와 같이 터치 시작, 터치 이동, 터치 종료를 판단할 수 있습니다.

      • 터치 시작 : if TInteractiveGestureFlag.gfBegin in EventInfo.Flags then
      • 터치 이동 : if EventInfo.Flags = [] then
      • 터치 종료 : if TInteractiveGestureFlag.gfEnd in EventInfo.Flags then

      ❑ Swipe 이벤트의 시작조건

      Swipe 이벤트의 시작은 터치 시작 이벤트와 별도로 진행됩니다. 왜냐하면, Swipe 이벤트의 경우 터치 이후 좌/우로 끄는 동작 이후 발생하기 때문입니다. 

      Swipe 이벤트는 터치 시작(gfBegin) 이벤트 이후 터치 이동 시 아래 2가지 조건을 만족하는 경우 Swipe 시작이라 판단합니다.

      조건1) 이동거리가 10(pixel) 이상인 경우

      Tab(Click), LongTab 등의 이벤트와 구분하기 위해 10 pixel 이상의 움직임이 있을 경우 Swipe 이벤트를 발생합니다.

      다음과 같이 TPointF의 거리로 이동 거리를 판단할 수 있습니다. SWIPE_MOVE_MINVALUE는 상수로 10이 선언되어 있습니다.

      if FSwipeData.StartPos.Distance(EventInfo.Location) > SWIPE_MOVE_MINVALUE then

      조건2) 좌우 이동 거리가 상하 이동 거리의 2배 즉 좌우로 많이 이동 시 

      Swipe는 좌우 또는 상하로 이동하는 경우만 발생합니다.(예제에는 좌우로 Swipe 하는 기능만 구현되어 있습니다.)

      좌우로 이동되었다는 판단은 아래의 코드와 같이 X축 이동이 Y축 이동보다 2배 이상. 즉, 좌우로 많이 움지였다는 판단이 있으면 수평으로 Swipe를 한다고 판단합니다.(이 조건은 상하의 폭을 고정하도록 변경도 가능합니다.)

      if (Abs(MovePos.X) > Abs(MovePos.Y) * 2) then

      이후 터치 이동 이벤트 발생 시 수평으로 Swipe를 사용(FSwipeData.Direction = TSwipeDirection.Horizontal)한다면 Swipe 이동 이벤트(DoSwipe)를 발생합니다.


      주의

      Swipe 기능은 폼의 Pan 제스처를 이용하기 때문에 폼위의 컨트롤에서 Pan 제스처를 사용(Touch.InterctiveGestures.Pan = True)할 경우 Swipe 기능이 작동되지 않습니다. 일례로 ListBox는 기본값으로 Pan 제스처 사용으로 되어 있어 ListBox 위에서 Swipe 기능을 구현하려면 Pan Gesture 사용하지 않도록 속성을 변경(Touch.InterctiveGestures.Pan := False) 해야 합니다.


      반대로, ScrollBox를 사용할 경우 ScrollBox에서 Pan 제스처를 허용하면, ScrollBox 이외의 영역에서 Swipe 기능을 이용할 수 있습니다.


      왜? Gesture이벤트를 이용할까? MouseDown, MouseMove, MouseUp 이벤트를 이용해 구현해되 되지 않을까?

      Gesture 이벤트를 이용해 Swipe기능을 구현한 이유는  MouseDown, MouseMove, MouseUp이벤트의 경우 폼위에 컨트롤이 있을 경우 해당 컨트롤에서 마우스 이벤트를 가져갑니다.

      예를 들어 ListView가 폼에 가득차게 구성한 경우 터치 후 드래그 하게되면 폼이 아닌, ListView에서 마우스 이벤트가 발생하게 됩니다.

      하지만, Gesture 이벤트를 이용하면 컨트롤이 Pan 제스처를 사용하지 않는 다면(Touch.InteractiveGesture.Pan := False) 폼위에 컨트롤들이 있어도 폼은 Gesture이벤트를 사용할 수 있어 Swipe 메뉴와 같은 앱 전체적으로 터치 이벤트를 사용이 필요한 경우 사용할 수 있습니다.

      그리고 Pan 제스처를 사용하지 않아도 컨트롤들의 터치(클릭), 스크롤등에 영향을 주지 않습니다.


      사이드바 메뉴와 Swipe 기능 연결하기

      FormGesture 이벤트에서 터치이벤트에 따라 3가지(DoSwipeBegin, DoSwipeMove, DoSwipeEnd)의 Swipe 메소드를 호출합니다. 

      ❑ DoSwipeBegin(Swipe 시작)

      procedure TForm1.DoSwipeBegin(const P: TPointF);
      var
        SwipeEvent: ISupportSwipeEvent;
        Handled: Boolean;
      begin
        Handled := False;
        if Assigned(FCurrentMenu) and Supports(FCurrentMenu.View, ISupportSwipeEvent, SwipeEvent) then
          SwipeEvent.SwipeBegin(P, Handled);
      
        if Handled then
          Exit;
      
        FSwipeBeginStopWatch := TStopWatch.StartNew;
        FMenuHelperDown := False;
      end;

      앞에서 설명한바와 같이 터치 무브 이벤트에서 좌우로 일정거리를 이동한 경우 DoSwipeBegin 이벤트가 발생합니다.

      코드의 아래쪽의 TStopWatch를 사용하는 코드는 Swipe를 빠르게 이동할 경우 예외를 두기 위해 시작시간을 기록하는 코드입니다. DoSwipeEnd 메소드에서 터치 시작 후 뗀 시간과 이동거리를 이용해 사이드바를 열고 닫는 것을 판단합니다.


      ISupportSwipeEvent와 관련된 코드는 프레임과 연동하기위한 코드입니다. 이글의 뒷쪽에서 다시 설명합니다.

      ❑ DoSwipeMove(Swipe 이동)

      procedure TForm1.DoSwipeMove(const P: TPointF);
      var
        MovePos: TPointF;
      
        SwipeEvent: ISupportSwipeEvent;
        Handled: Boolean;
      begin
        Handled := False;
        if Assigned(FCurrentMenu) and Supports(FCurrentMenu.View, ISupportSwipeEvent, SwipeEvent) then
          SwipeEvent.SwipeMove(P, Handled);
      
        if Handled then
          Exit;
      
        MovePos := P - FSwipeData.StartPos;
        SetMenuPosition(MovePos.X);
      end;
      
      procedure TForm1.SetMenuPosition(const Value: Single);
      begin
        if ShowMenu then
        begin
          if Value < 0 then // 왼쪽으로
            lytSidebar.Position.X := Max(Value, -lytSidebar.Width);
        end
        else
        begin
          if Value > 0 then
            lytSidebar.Position.X := Min(-lytSidebar.Width + Value, 0);
        end;
      
        lytMenuHelper.Visible := True;
        lytMenuHelper.Opacity := ((lytSidebar.Width + lytSidebar.Position.X) / lytSidebar.Width) * MENUHELPER_OPACITY;
      end;

      Swipe 이동시에는 사이드바의 위치를 변경하는 작업을 합니다. SetMenuPosition의 Value 인자에는 처음 터치한 좌표와 현재 터치된 좌표간의 수평거리(X값)가 전달됩니다.


      사이드바 위치는 이미 보여진 상태에서는 닫히는 방향으로, 감춰진 상태에서는 보여지는 방향으로 처리됩니다.

      참고로 사이드바가 보여질때 X 좌표가 0이고 감춰져 있을때는 음수 값입니다.

      TIP. Max, Min 함수 소개

      Max(A, B) 함수는 A와 B값 중 큰 값을 반환하는 수학함수입니다. Min 함수는 작은 값을 반환합니다.  System.Math 유닛을 추가(uses) 해야 합니다.


      사이드바 위치를 조정하고 사이드바 이외의 배경의 투명도(Opacity)를 조정해 사이드바가 많이 열릴 수록 진하게 처리합니다.

      ❑ DoSwipeEnd(Swipe 종료)

      procedure TForm1.DoSwipeEnd(const P: TPointF);
      var
        MovePos: TPointF;
        SwipeEvent: ISupportSwipeEvent;
        Handled: Boolean;
      begin
        Handled := False;
        if Assigned(FCurrentMenu) and Supports(FCurrentMenu.View, ISupportSwipeEvent, SwipeEvent) then
          SwipeEvent.SwipeEnd(P, Handled);
      
        if Handled then
          Exit;
      
        MovePos := P - FSwipeData.StartPos;
        SetMenuPosition(MovePos.X);
      
      //  Log.d('DoSwipeEnd : %f > %f(SW: %d)', [lytSidebar.Width + lytSidebar.Position.X, Self.Width * 0.4, FSwipeBeginStopWatch.ElapsedMilliseconds]);
        // 짧게 Swipe하는 경우 방향만 맞으면 메뉴전환
        if FSwipeBeginStopWatch.ElapsedMilliseconds < 300 then
        begin
          if ShowMenu then
            ShowMenu := (MovePos.X > -50)
          else
            ShowMenu := (MovePos.X > 50);
        end
        // 길게 Swipe하는 경우 특정위치 이상 메뉴 이동 시 전환
        else
          ShowMenu := (lytSidebar.Width + lytSidebar.Position.X) >= lytSidebar.Width * 0.5;
      end;

      Swipe가 끝나는 시점에서는 Swipe 시작과 이동한 내용에 따라 결과적으로 사이드바를 열지와 닫을지를 결정해야 합니다.

      Swipe 시작 시 TStopWatch로 기록한 시간을 이용해 빠르게 Swipe 완료된 경우와 천천히 완료한 경우로 분개됩니다.

      빠르게 Swipe를 완료(300 ms 이하)한 경우에는  50 pixel 이상만 움직여도 사이드바를 열고, 닫도록 합니다.

      길게 Swipe를 완료한 경우는 화면의 반이상을 열거나 닫을 경우 처리되도록 합니다.

      기타 내용

      ❑ 다른 컨트롤들 위에서 Swipe 하기

      Pan 제스처 예외 사항

      앞에서 설명드렸지만 Swipe는 메인 폼의 Pan 제스처를 이용합니다. 메인 폼에서 Pan 제스처를 이용하기 위해서는 다른 컨트롤들이 Pan 제스처를 사용하지 않아야 합니다.

      하지만, Pan 제스처를 사용하지 않도록 설정(Touch.InteractiveGesture.Pan := False)해도 일부 리스트(ListView, ListBox)에서 이슈가 있습니다.

      ListView와 ListBox는 Siwpe하기 위해 리스트에서 터치 후 좌우로 움직일 때에 상하로도 움직이게 되는데 이때 리스트의 스크롤이 동작합니다. 기능에 완성도를 높이기 위해 Swipe 중에는 리스트의 동작을 멈추는 예외코드가 필요하고 ListView와 ListBox에는 Swipe 시작과 종료시점에 다음 코드를 사용해 스크롤을 제한할 수 있습니다.


      ListBox의 경우 Swipe 시작 시 HitTest 속성을 False로 종료 시 True로 변경 해 스크롤을 제한할 수 있습니다.

      ListView의 경우 Swipe 시작 시 Enabled 속성을 False로 종료 시 True로 변경 해 스크롤을 제한할 수 있습니다.

      (참고로 ListBox의 경우 마우스 기반으로 스크롤되므로 HitTest를 ListView의 경우 Gesture를 기반으로 스크롤 되므로 Enabled로 스크롤을 제한합니다.)


      이 예제에서는 ListBox와 ListView가 다른 프레임에서 사용하고 있기 때문에  ISupportSwipeEvent 인터페이스를 선언하고 해당 프레임에서 상속받아 처리하도록 구성했습니다.(뒷쪽에서 다시 설명합니다.)


      TIP. HitTest 속성

      HitTest 속성은 마우스 이벤트를 컨트롤이 사용할지를 선택하는 속성입니다.

      컨트롤의 HitTest 속성을 False로 설정하면 해당 컨트롤에서는 마우스 이벤트 발생시키지 않습니다. 즉, 클릭, 드래그 등의 이벤트가 발생하지 않습니다.

      예를들어 ListBox의 경우 MouseDown, MouseMove, MouseUp 이벤트를 기반으로 스크롤 처리를 하기 때문에 HitTest를 False로 선택하면 ListBox의 스크롤 기능이 동작하지 않습니다.


      예를 들면, 이미지가 포함된 버튼을 만들고 싶은 경우 버튼 위에 (자식으로)이미지를 위치시키고 이미지의 HitTest를 False로 변경하면 이미지를 클릭해도 이미지의 클릭이벤트를 통과해 버튼의 클릭이벤트가 발생됩니다.

      HitTest 속성을 이용하면 이와 같이 이미지를 포함한 버튼을 쉽게 만들 수 있습니다.

      프레임과 연동(ISupportSwipeEvent)

      이 예제는 화면과 기능을 별도의 파일로 분리하기 위해 프레임을 사용합니다. 

      프레임에 Swipe Event를 추가하기 위해 ISupportSwipeEvent를 아래와 같이 추가했습니다.

        ISupportSwipeEvent = interface
        ['{152551D7-6455-4439-BEF8-4345811D5900}']
          procedure SwipeBegin(const AStartPos: TPointF; var Handled: Boolean);
          procedure SwipeMove(const AMovePos: TPointF; var Handled: Boolean);
          procedure SwipeEnd(const AEndPos: TPointF; var Handled: Boolean);
        end;

      그리고 Swipe 시작, 이동, 종료 시 프레임에서 작업해야 할 것이 있다면 프레임에서는 아래와 같이 상속해 구현합니다.

      SearchFrame에서 ListView를 제어하기 위해 인터페이스를 상속받고 사용하는 코드입니다.

      type
        TfmSearch = class(TFrame, IFrameView, ISearchFeature, ISupportSwipeEvent)
        private
          ...
          { ISupportSwipeEvent }
          procedure SwipeBegin(const AStartPos: TPointF; var Handled: Boolean);
          procedure SwipeMove(const AMovePos: TPointF; var Handled: Boolean);
          procedure SwipeEnd(const AEndPos: TPointF; var Handled: Boolean);
        end;
      
      implementation
        ...
      procedure TfmSearch.SwipeBegin(const AStartPos: TPointF;
        var Handled: Boolean);
      begin
        Handled := False;
        ListView1.Enabled := False;
      end;
      
      procedure TfmSearch.SwipeMove(const AMovePos: TPointF; var Handled: Boolean);
      begin
        Handled := False;
      end;
      
      procedure TfmSearch.SwipeEnd(const AEndPos: TPointF;
        var Handled: Boolean);
      begin
        Handled := False;
        ListView1.Enabled := True;
      end;

      ListView는 Swipe 시 스크롤이 되지 않아야 하기 때문에 Swipe 시작(DoSwipeBegin)에 Enabled를 False로 끝날때 True로 변경합니다.

      Handeld 메소드의 경우 프레임에서 Swipe동작을 사용했는지 여부입니다. 사용하 경우(Handled := True) 메인 폼에서는 Swipe와 관련된 사이드바를 이동하는 기능을 하지 않습니다.

      예를들어 오른쪽에서 다른 사이드바를 표시하는 작업이 필요하면 프레임에서 구현하고 Handled 값을 True로 변경하는 등의 작업을 구현할 수 있습니다.

      마치며

      이 기능은 많은 코드가 추가되지는 않았지만, 제스처, EnventInfo, 좌표 계산등의 다양한 내용이 포함되어 어렵게 느껴질 수도 있습니다. 하지만, 모바일 앱 개발 시 앱의 완성도를 한단계 높이기 위해 필요한 터치와 터치의 변화에 따른 좌표계산등을 익힐 수 있는 예제라고 생각합니다.이해가지 않는 내용은 관련된 자료와 샘플을 찾아서 꼭 이해하시길 부탁드립니다.)


      예제를 활용하셔서 다양한 기능을 만드시길 바라겠습니다.

      어렵거나 궁금한 내용은 댓글로 남겨주시면 글을 보강하는데 소중히 사용하겠습니다.



      관련글

      참고글



      저작자 표시 비영리 동일 조건 변경 허락
      신고
      크리에이티브 커먼즈 라이선스
      Creative Commons License

      험프리.김현수 험프리.김현수 Firemonkey/모바일 앱 예제

      1. Blog Icon
        개발자

        여기서 SwipeMenuDemo 소스를 다운 받아서 XE7로 소스를 불러 왔더니.. 에러가 나네요..
        에러 메세지는 아래와 같습니다.. ^^

        Unable to load project J:\RAD Studio\Projects\SwipeMenuDemo\SwipeMenuDemo.dproj Error reading
        "J:\RAD Studio\Projects\SwipeMenuDemo\SwipeMenuDemo.dproj" Fatal error - 1072896763. 이름에 잘못된 문자가 포함되어 있습니다. Line 34, column 71.

        이런 에러가 나는데.. 해결이 않되네요.. 혹시 아시나요?

      2. 글쎄요. 저는 본적이 없는 문제인데요.
        아래 방안으로 시도해 보시기 바랍니다.

        1, SwipeMenuDemo.dproj 소스를 열고 포맷팅(Ctrl + D)
        2, (위 방법이 안되면) 새로운 프로젝트 열고 위 프로젝트에서 사용하는 유닛을 추가해 새 프로젝트에서 빌드해 보기 바랍니다.