개요.
ApplusForm에서 제공하는 Templete 중 SlideMenuA01~05에 대한 강좌입니다.
홈페이지에서 소개하는 내용입니다.
Slide Menu Template A01 Type
Slide Menu Template은 서비스 메뉴가 많은 경우에 적합한 유형입니다.
* 주요기능 : 슬라이드 메뉴이동, local html page로드, 아이콘 애니메이션, 지도연동 등
* 활용분야 : 모바일 오피스, 기업 및 회사 소개 등
* 특징 : 메뉴 영역이 주 화면 영역 좌측에 노출되는 형식(주 화면 밑에 메뉴 영역 배치 효과)
최종 업데이트 일자 : 2013.08.01 / 지원 OS : Android 2.3 ~ , iOS 5.0 ~
SlideMenuA01은 최근 대중적으로 사용되고 있는 메뉴 형태를 제공합니다. 왼쪽 혹은 오른쪽에 메뉴가 있고 메뉴를 선택하면 슬라이드되면서 메뉴가 사라지는 기능입니다. 단순하지만 실제로 구현하기에는 까다로울 수 있지만 AppForm을 사용하면 간단하게 해결됩니다.
메뉴만 만들고 메뉴를 선택했을 때 보여주는 화면은 mobile web page로 구성하여 앱을 개발해도 썩 괜찮게 보여질 것입니다.
홈페이지에서도 스크린샷을 볼 수 있지만 어떤 화면을 제공하는지 잠시 보겠습니다. A01~A05까지 다섯가지의 형태를 제공하지만 구성된 컨텐츠가 동일하고 애니메이션 효과를 스크린샷으로는 잘 구분되지 않죠..
A01~A05까지의 메뉴 애니메이션이 어떻게 동작하는지 그림으로 표현해보았습니다.
각각의 메뉴가 나타나는 애니메이션이 조금씩 다르게 구성되어 있습니다. 원하는 애니메이션을 갖는 메뉴를 가져다가 사용할 수 있습니다.
소스도 다른 부분은 동일하고, 메뉴가 출현하는 애니메이션 부분만 다르게 구성되어 있으므로 이부분에 집중해서 설명하겠습니다.
Theme.
언제나 그렇듯.. 처음 로드되는 소스는 /moml/ui/start.xml입니다. UI를 먼저 보겠습니다.
<UI>
<UILAYOUT portrait="320,460" landscape="320,460" theme="themeMenu" >
UILAYOUT을 보면 theme=“themeMenu”가 보입니다.
MOML에서는 Theme를 지원하는데 UI Attribute를 지정할 때 공통되는 Attribute를 모아서 코드를 간결하게 하거나, theme를 따로 묶어 변경했을 때 전체 앱의 분위기를 바꿔줄 때 유용하게 사용됩니다.
Theme를 사용하여 앱의 분위기를 바꾸는 방법은 Profile 템플릿에서 확인할 수 있으니 여기서는 코드를 간결하게 하는 용도로 우선 이해하면 되겠습니다.
themeMenu는 파일의 상단에 정의되어 있습니다.
<THEMES id="themeMenu" >
<THEME id="thLeftButton" layout="140,30" align="linear:horizontal|center" defaultImg="leftMenuUnSel.png" checkedImg="leftMenuSelected.png"/>
<THEME id="thLabel1" layout="18,18" margin="5,8,5,8" />
<THEME id="thLabel2" layout="110,30" fontSize="12" textAlign="left|vCenter" fontColor="#ffffff" margin="0,2,0,0"/>
</THEMES>
하나의 XML을 document라는 예약어로 정의되어 있습니다. document내에서만 유효한 Theme는 XML내에 UI와 함께 정의하여 사용할 수 있습니다.
<LABEL themeId="thLeftButton" layout="140,20" text=" Information" fontSize="10" textAlign="left|vCenter" fontColor="#9CA4B3"/>
이 LABEL은 themeMenu의 thLeftButton의 attribute 값을 모두 수용하고 layout은 재정의한 값으로 대체하게 됩니다. 그래서 실제로는
<LABEL align="linear:horizontal|center" defaultImg="leftMenuUnSel.png" checkedImg="leftMenuSelected.png" layout="140,20" text=" Information" fontSize="10" textAlign="left|vCenter" fontColor="#9CA4B3" />
과 동일하게 attribute를 가지게 됩니다.
RADIO Menu.
<WINDOW id="id_leftMenu" layout="0,0,140,460" align="linear:vertical" defaultImg="#000000" onShow="function.onShow()">
<LABEL layout="140,40" defaultImg="leftMenuTop.png" align="linear:vertical|center">
<IMAGE layout="140,30" defaultImg="logo.png"/>
</LABEL>
<LABEL themeId="thLeftButton" layout="140,20" text=" Information" fontSize="10" textAlign="left|vCenter" fontColor="#9CA4B3"/>
<RADIO id="leftMenuInfo" style="button" groupId="leftMenu" themeId="thLeftButton" onClick="function.navigation('info')" selected="true">
<LABEL id="infoIcon" themeId="thLabel1" defaultImg="mainMenuInfoBtn.png"/>
<LABEL themeId="thLabel2" text="About MoSPI"/>
</RADIO>
... 중략 ...
<LABEL themeId="thLeftButton" layout="140,260" defaultImg="leftMenuSelected.png" align="linear:vertical|top|center">
<LABEL themeId="thLeftButton" layout="60,60" defaultImg="menuAll.png" onClick="function.menuAllMenu" />
</LABEL>
</WINDOW>
메뉴는 id를 id_leftMenu로 하고 WINDOW로 묶었습니다. 자세히 들여다 보면, LABEL과 RADIO로 만들어져 있는데 실제 만들어진 화면에는 RADIO가 보이지 않습니다. BUTTON을 사용하면 메뉴가 선택된 상태를 표시하기 위해 defaultImg와 pressedImg를 교체해줘야 하고, onClick시에도 상태값을 가지고 처리해야 하는 등 번거로운 일이 많습니다. RADIO에는 button style이 있어 RADIO의 속성을 그대로 가지면서 형태만 버튼 형태를 갖도록 하는 기능을 제공하고 있습니다. 즉, 동일한 groupId를 갖는 RADIO들은 하나가 선택되었을 때 다른 RADIO들은 선택되지 않는 상태로 자동으로 변경됩니다.
LABEL안에 IMAGE가 있고, RADIO안에 LABEL이 있는 형태를 볼 수 있는데요, 모든 UI element는 WINDOW를 상속받기 때문에 스스로 container가 될 수 있기 때문입니다. 이를 이용하여 보다 편리하게 UI를 구성할 수 있습니다.
button style의 RADIO의 다른 예는 /moml/ui/citrine에서도 볼 수 있습니다.
<WINDOW id="tabMenu" themeId="themeMainTabMenu">
<RADIO themeId="radio1" selected="true" onClick="con1.src='overview.xml'">
<LABEL text="Overview" fontColor="#ffffff"/>
</RADIO>
<RADIO themeId="radio1" onClick="con1.src='feature.xml'">
<LABEL text="Feature" fontColor="#ffffff"/>
</RADIO>
<RADIO themeId="radio1" onClick="con1.src='effect.xml'">
<LABEL text="Effect" fontColor="#ffffff"/>
</RADIO>
</WINDOW>
상단 메뉴를 탭메뉴와 동일하게 구성하여 활용할 수 있습니다.
화면의 오른쪽 영역인 컨텐츠영역의 구성을 보겠습니다.
<WINDOW id="id_rightArea" layout="140,0,320,460" align="linear:vertical" defaultImg="#E5E5E5" onClick="'none'">
<WINDOW layout="640,885" align="relative">
<NAVIGATIONCONTAINER themeId="bg" id="navigation" layout="0,0,320,460" selectedItem="info">
<VIEWITEM id="info" src="../info/infoMain.xml" transitionInEffect="none" transitionOutEffect="none" />
<VIEWITEM id="contact" src="../contact/contact.xml" transitionInEffect="none" transitionOutEffect="none" />
<VIEWITEM id="citrine" src="../citrine/citrine.xml" transitionInEffect="none" transitionOutEffect="none" />
<VIEWITEM id="license" src="../license/license.xml" transitionInEffect="none" transitionOutEffect="none" />
<VIEWITEM id="allMenu" src="mainAllMenu.xml" transitionInEffect="tornado" transitionOutEffect="tornado"/>
</NAVIGATIONCONTAINER>
</WINDOW>
</WINDOW>
오른쪽에 보이는 영역은 id가 id_rightArea인 WINDOW로 감싸고 NAVIGATIONCONTAINER를 배치했습니다. 여기에선 메뉴를 눌렀을 때 해당하는 VIEWITEM이 로드될 것입니다. 컨텐츠 영역은 html페이지를 보여주는 WEBVIEW로 구성되어 있어 따로 설명하지는 않겠습니다.
<RADIO id="leftMenuInfo" ---중략--- onClick="function.navigation('info')" >
을 보면 메뉴를 눌렀을 때 navigation function이 호출됩니다.
<FUNCTION id="navigation(item)">
<FUNCTIONITEM cmd="function.fn_slide" />
<FUNCTIONITEM cmd="navigation.selectedItem=item"/>
</FUNCTION>
navigation function은 fn_slide function을 호출하여 메뉴 애니메이션을 실행하고, 인자로 받은 item을 NAVIGATIONCONTAINER의 selectedItem으로 설정하여 메뉴에 해당하는 화면을 로드하도록 하고 있습니다.
Menu Animation.
<FUNCTION id="fn_slide">
<FUNCTIONITEM condition="userVariable.slideAnimationEnd == 'true'" cmd="function.fn_slideDo"/>
</FUNCTION>
<FUNCTION id="fn_slideDo">
<FUNCTIONITEM cmd="userVariable.slideAnimationEnd = 'false'"/>
<FUNCTIONITEM condition="userVariable.menuView=='0'"
cmd="id_rightArea.left=0, userVariable.menuView='1', animation.flyIn('id_rightArea', 140, 0, 320, 460, 'easeOut4', 300, '')"
elseCmd="id_rightArea.left=140, userVariable.menuView='0', function.updateRadio, animation.flyIn('id_rightArea', -140, 0, 320, 460, 'easeOut4', 300, '')"/>
<FUNCTIONITEM cmd="userVariable.slideAnimationEnd = 'true'" delay="300"/>
</FUNCTION>
fn_slide function에서는 애니메이션 도중인지를 체크하여 중복해서 애니메이션이 실행되는 것을 방지하고 있습니다. fn_slideDo에서 animation.flyIn function으로 id가 id_rightArea인 WINDOW를 이동시킵니다..
animation.flyIn은 아래와 같습니다.
void flyIn(uiId, x, y, scale, timeCurve, millisec, endFunction)
void flyIn(uiId, x, y, width, height, timeCurve, millisec, endFunction) //deprecated
x : id_rightArea의 x로부터 이동이 시작될 상대 위치(140 이라면 현재 위치로부터 id_rightArea.x + 140부터 animation이 시작됨)
y : id_rightArea의 y로부터 이동이 시작될 상대 위치(0 이므로 변동 없음)
width, height : 시작할 때의 크기 (0,0이라면 크기가 0부터 현재의 크기로 점점 커집니다.)
timeCurve : http://cafe.naver.com/citrineframework/212 에 잘 설명되어 있습니다. (easeOut4이면 처음에는 빠르게 시작하고 천천히 끝납니다.)
millisec : 애니메이션이 동작하는 시간
endFunction : 애니메이션이 끝났을 때 실행할 명령입니다.
메뉴 애니메이션은 위 내용은 간단하게 구현됩니다.
Icon Animation.
애니메이션은 여러 UI element가 모인 WINDOW에 적용할 수도 있고, 개별 UI element에도 적용할 수도 있습니다. 모든 UI Element에 적용할 수 있다고 보는 것이 맞겠습니다.
개별 UI element에 적용한 부분은 메뉴에 있는 아이콘이 움직이는 부분입니다.
Function.
UI
<LABEL id="infoIcon" themeId="thLabel1" defaultImg="mainMenuInfoBtn.png"/>
<LABEL id="mapIcon" themeId="thLabel1" defaultImg="mainMenuMapBtn.png"/>
<LABEL id="peopleIcon" themeId="thLabel1" defaultImg="mainMenuPeopleBtn.png"/>
<LABEL id="reserveIcon" themeId="thLabel1" defaultImg="mainMenuReserveBtn.png"/>
각 메뉴에는 icon이 하나씩 붙어 있습니다. buttonAni, buttonAni2, buttonAni3 function은 이 아이콘을 각기 다른 형태의 애니메이션이 적용되어 동작하도록 했습니다. animation이 끝났을 때 다시 function을 호출하므로 재귀적으로 반복해서 애니메이션이 수행됩니다. 여기에서 사용된 flyIn, show, rotate animation은 MOMLAPIReference.pdf에 잘 소개되어 있으니 참고하시기 바랍니다.
All menu.
다른 앱에서는 찾아볼 수 없는 특별한 기능을 소개하겠습니다. 모든 화면을 모아서 보여주는 메뉴입니다.
대개 스크린샷을 찍어 이미지로 보여주고 선택했을 때 메뉴로 이동하는 기능을 제공할 수 있겠지만 여기서는 직접 해당 페이지를 한 화면에 모두 로드하여 보여줄 수 있습니다. 물론 각 화면내에서 동작도 가능합니다. 메뉴에서 아이콘을 선택하면 실행됩니다.
아래는/moml/ui/main/mainAllMenu.xml의 일부입니다.
<WINDOW id="allMenu" layout="0,0,320,460" align="relative" defaultImg="#000000" ... 중략... >
<CONTAINER id="info" layout="5,45,150,170" src="../info/infoMain.xml" enabled="false"/>
<BUTTON layout="5,45,150,170" text="About MoSPI" ... 중략... onClick="function.hideAni('info')"/>
<CONTAINER layout="160,45,150,170" src="../contact/contact.xml" enabled="false"/>
<BUTTON layout="160,45,150,170" text="Contact us" ... 중략... onClick="function.hideAni('contact')"/>
<CONTAINER id="map" layout="5,220,150,170" src="../citrine/citrine.xml" enabled="false"/>
<BUTTON layout="5,220,150,170" text="About Citrine" ... 중략... onClick="function.hideAni('citrine')"/>
<CONTAINER layout="160,220,150,170" src="../license/license.xml" enabled="false"/>
<BUTTON layout="160,220,150,170" text="License" ... 중략... onClick="function.hideAni('license')"/>
</WINDOW>
소스를 보면 CONTAINER에 src가 지정되어 있습니다.
CONTAINER에 필요한 xml을 로드하여 layout에 정해준 크기만큼 화면에 출력하도록 했습니다.
About Citrine의 BUTTON을 삭제하고
<CONTAINER id="map" layout="5,220,150,170" src="../citrine/citrine.xml" enabled="true"/>
로 변경해서 실행하면, 썸네일 화면이 출력됩니다.
다른 BUTTON도 삭제하고 실행해보면 동작하는 모습을 확인할 수 있습니다.
MOML은 CONTAINER안에 moml ui를 담을 수 있고 moml ui의 크기는 CONTAINER의 크기에 따라 상대적으로 자체 좌표체계를 통하여 다시 계산되어 출력되므로 한 화면에 여러 러 UI를 모아서 볼 수 있는 기능을 무리없이 제공할 수 있습니다.
SlideMenuA01~A05 템플릿 소스분석 및 소개를 마칩니다.
'Mobile > ApplusForm' 카테고리의 다른 글
Video Clip B01 소스 분석_앱플러스폼(ApplusForm, AppForm) (0) | 2013.10.25 |
---|---|
SlideMenuA02를 활용한 앱 만들기_앱플러스폼(ApplusForm, AppForm) (1) | 2013.10.08 |
MagazineA01을 활용한 만화책 뷰어_앱플러스폼(ApplusForm, AppForm) (1) | 2013.10.05 |
MagazineA01 소스 분석_앱플러스폼(ApplusForm, AppForm) (0) | 2013.10.05 |
앱플러스폼(ApplusForm) 서비스를 소개합니다.(AppForm,앱개발,스마트폰 앱개발,센차터치,폰갭,웹앱,하이브리드앱) (0) | 2013.10.05 |