안녕들 하십니까?

오늘의 강좌 주제는 안드로이드 App Resource(이미지, XML) Hacking 입니다.

정말 잘 만들어진 App을 보면서 이 App은 어떻게 UI를 구성했을까? 고민해 본적은 없습니까? 하하~ 저는 그런 고민을 많이 했습니다. 단순한 UI야 쉽게 만들 수 있지만, 복잡한 UI를 보면서.. UI를 만들기 위해서 어떤 Layout을 사용했으며, 어떤 View를 사용했을까? 고민하다가 찾아본 프로그램에 대한 소개입니다.


전체 강좌 목차

[강좌A01] Moteodev Studio를 이용한 안드로이드 개발 환경 구축 가이드
[강좌A02] 안드로이드 개발 참고 서적 소개
[강좌A03] Android 실전 개발 - 아이디어 / 기획 / Wireframe
[강좌A04] 안드로이드 실전 개발 - 아이콘 제작
[강좌A05] 안드로이드 실전 개발 - 레이아웃 및 리소스 : Part1
[강좌A06] 안드로이드 실전 개발 - 레이아웃 및 리소스 : Part2
[강좌A07] 안드로이드 실전 개발 - 리소스 해킹

참고로 안드로이드 소스 역컴파일은 현재는 불가능합니다. 어셈블리 레벨로 볼 수는 있지만, 그거 분석하는거 공부할려면 짜증납니다. 언젠가는 자바로 역컴파일 해주는 프로그램이 나오지 않을까? ㅋㅋ 안 나올수도 있습니다.

 

기본적으로 Android App을 빌드하면 .apk 파일이 만들어집니다. 이 파일은 압축파일로 압축을 풀면 이미지는 볼 수 있으나, xml 파일의 경우에는 binary로 저장되기 때문에 editor에서 볼 수가 없습니다. xml을 볼 수 있다면.. resource 설계에 상당한 도움이 될 것입니다

~ 시작하겠습니다.

 

1. 먼저 apk 파일을 구해야 겠지요.

핸드폰으로 앱을 다운/설치 하시면.. 기본적으로 apk 파일은 /data/app 디렉토리에 저장됩니다. 근데, 우리가 구매한 핸드폰은 기본적으로 root 계정이 막혀있으며, /data 디렉토리에 퍼미션이 없어서 접근 자체가 불가능합니다. 루팅을 하지 않는 한 /data 디렉토리의 apk를 볼 수 있는 방법이 없습니다. <개발자 폰이 있으면 가능할텐데…> 하지만, 구글 검색어: “android apk rapidshare” 이정도 치면 누군가 apk를 모아서 rapidshare 사이트에 올려둔 게 많습니다. 특정 App 이름을 검색어에 추가하면 그 앱을 구할 수도 있습니다. 최신 App은 거의 없구요. 일종의 불법 app 사용이겠네요. 것도 원래 무료 App이 많습니다.  폰에 설치해서 사용도 가능합니다. 사용하기 위해서 이짓하기는 좀 귀챦습니다. 무료인 경우에는 개발사 홈페이지에서 바로 다운이 가능한 곳도 많습니다. 향후 Google App 인증이 적용된 App들이 나오면 설치는 불가능하게 되겠죠?

저희는 TStore에서 다운받겠습니다. tstore에서 pc manager로 다운 받으시면 local pc apk 파일이 다운됩니다. (참고로 tstore에서는 개발자에게 ARM 관련 library를 제공하기 때문에 ARM이 걸려있는 apk는 폰에 설치해도 실행이 안됩니다. 어플 실행 부분에 체크하는 로직이 들어있기 때문입니다. 저희는 리소스만 볼 것이므로 상관은 없습니다.)

 


앱에서 아무 프로그램이나 pc Manager로 다운받으신 후 우측마우스 클릭 > 파일이 저장된 폴더 열기를 하시면 apk 파일이 저장된 폴더가 열립니다. 저는 R-2 Player라는 라디오 프로그램을 다운 받았습니다. 제가 강좌하고 있는 Hangul To English 앱을 다운 받으셔서 테스트 하셔도 됩니다.

 

2. 압축을 풀자.

apk
파일은 압축 파일이므로 압축을 풀 수가 있습니다. 다만, 알집으로는 못 풀고 오픈소스 진영에서 만든 압축 프로그램인 7-zip으로 푸셔야 합니다. 7-zip은 홈페이지
(http://www.7-zip.org/) 에서 다운받으시면 됩니다.


위 그림을 보시면 /res/drawable 디렉토리에 이미지들이 보이네요. 보통 개발할 때 drawable 디렉토리에는 xml만 넣고, drawable-hdpi 폴더에 큰이미지, drawable-mdpi에 작은 이미지를 넣는데, 이 앱은 drawable에 이미지랑 xml 모두 다 들어있군요.

3. xml 파일 보기.

압축 푼 파일에서 xml 파일을 열어 보겠습니다.


~ binary 파일로 되어 있어서 xml 파일 자체를 볼 방법이 없군요.

이제 이 xml을 볼 수 있는 프로그램을 다운 받아 봅시다.

오픈소스로 만든 AXMLPrinter2 라는 자바로 개발된 프로그램입니다.


http://code.google.com/p/android4me/downloads/list  

에서 AXMLPrinter2.jar 파일을 다운 받아서 아무 디렉토리에 넣습니다. 저는 d:\test 디렉토리에 apk 압축을푼 파일(apk 디렉토리명도 r2_player로 변경했습니다) AXMLPrinter2.jar 파일을 넣어두겠습니다.

이제 준비가 끝났습니다. 위 압축 푼 파일 중에서 D:\test\r2_player\res\layout-hdpi\player_fwvga.xml 파일을 변환한다고 할 때..

cmd 창에서 다음과 같이 입력하시면 작업이 끝납니다.

java –jar [AXMLPrinter2.jar위치] [binary 포맷 xml 파일 위치] > [변환될 파일 위치]


java –jar D:\test\AXMLPrinter2.jar D:\test\r2_player\res\layout-hdpi\player_fwvga.xml > D:\test\r2_ player\res\layout-hdpi\player_fwvga.xml.txt



변환 작업이 완료되었습니다.

변환된 파일 위치에 가보시면 변환된 파일이 존재합니다. 열어보도록 하겠습니다.



잘 변환이 되었습니다.
그런데 이 작업에는 두가지 문제가 존재합니다.

하나는 변환 해야 될 파일이 많은 경우 엄청난 노가다를 해야 합니다.
두번째는, 현재 변환된 XML을 보시면 android:layout_width=”-2”로 실제 상수값이 들어가 있습니다. -2developer.android.com 에서 찾아보니
WRAP_CONTENT를 말합니다. 이것을 우리가 보기좋게 변환하려면 AXMLPrinter2 프로그램을 소스를 수정하거나 혹은 XML -> XML로 변환하는 XSLT 하나 만들면 될 것 같은데만들까 했는데.. 도저히 시간이 나지 않는군요. 누군가 해 주시면 감사하겠습니다. 아무도 안 해주심..하하 내년쯤에나 만들어 볼까 합니다.

 

첫번째 이슈인 변환해야 될 파일이 많은거..이것은 제가 도저히 참을 수가 없어서 간단히 VB스크립트로 조금 일을 간단하게 처리할 수 있도록 만들었습니다.

 

처음에는 vbsWindow Shell을 이용하여 프로그램을 직접 실행하도록 만들었으나 cmd 창이 개수만큼 떴다 사라지고 해서 오히려 불편해서 그냥 batch 파일을 만들어 주는 스크립트를 만들었습니다.

스크립트
소스입니다.



Const AXMLPRINTER_FILE = "D:\test\AXMLPrinter2.jar"

Const APP_ROOT_DIR = "D:\test\r2_player"

Const SAVE_FILE = "D:\test\print_xml_r2_player.bat"

 

Dim fso, tf

Set fso = CreateObject("Scripting.FileSystemObject")

 

 

If Not fso.FolderExists(APP_ROOT_DIR) Then

             Print("App Root Folder not exist. Check APP_ROOT_DIR Constants")

             WScript.Quit 0

End If

 

If Not fso.FileExists(AXMLPRINTER_FILE) Then

             Print("AXMLPrinter2.jar file not exist. please check AXMLPRINTER_FILE Constants")

             WScript.Quit 0

End If

 

Set tf = fso.CreateTextFile(SAVE_FILE, True)

 

call ShowSubfolders(APP_ROOT_DIR)

 

tf.Close

 

 

Sub ShowSubFolders(Folder)

             Set folder = fso.GetFolder(Folder)

    Set files = folder.Files

 

             For each file In files

                            If InStr(file.Name, ".") <> 0 Then

            fext = Right(file.Name, Len(file.Name) - InStrRev(file.Name, "."))

                  End If

 

                            If UCase(fext) = "XML" Then

                                        runCmd = "java -jar " & AXMLPRINTER_FILE & " " & file.Path & " > " & file.Path & ".txt"

                                        Print(runCmd)

                                        tf.WriteLine(runCmd)

 

                            End If

    Next

 

    For Each Subfolder in Folder.SubFolders

        ShowSubFolders Subfolder

    Next

End Sub

 

Sub Print(x)

   WScript.Echo x

End Sub

 


소스는 간단합니다.
디렉토리를 뒤져서 xml 파일인 경우 우리가 실행할 명령어를 텍스트 파일로 만들어 주는 스크립트입니다. vb 스크립트 아시면 금방 이해가 가실겁니다.

 

위 스크립트를 test 디렉토리에 저장한 후 처음 세줄인 상수값을 자신에게 맞게 수정합니다.

 

Const AXMLPRINTER_FILE = "D:\test\AXMLPrinter2.jar" 'AXMLPrinter2.jar 파일 경로 및 파일명

Const APP_ROOT_DIR = "D:\test\r2_player"                  '압축푼 apk 파일 루트 경로 (이 경로 하위 디렉토리를 뒤집니다.)

Const SAVE_FILE = "D:\test\print_xml_r2_player.bat"   '스크립트가 생성할 파일명

 

자 이제 실행은 cmd 창에서 cscript d:\test\print_xml.vbs 라고 명령 내리시면 apk 루트 하위의 모든 xml를 뒤져서 필요한 배치 스크립트를 만들어 줍니다. wscript d:\test\print_xml.vbs 라고 입력하셔도 되나, 이 경우 스크립트에서 메시지가 출력될 때 팝업창으로 나와서 조금 불편합니다.


위 그림과 같이 입력하면 배치파일이 만들어 집니다.

이제 만들어진 배치 파일을 실행하시면 모든 xml 파일을 텍스트 파일로 변환해 줍니다. 일이 한결 편해졌습니다.

이로써 7번째 안드로이드 실전 강좌를 마치겠습니다.

다음 강좌에서는 DB 부분을 살펴볼 것입니다. DB 관련 툴도 살펴보고 저희 Hangul to English 앱의 DB 부분 소스도 개발하겠습니다. 제 강좌를 구독하시면서 아직 Hangul To English App을 설치해 보지 않으신 분은.. 한번 설치해보시기 바랍니다. 그래도 직접 해보면 개발하는 앱에 대한 이해를 높일 수 있으니 ..

 

끝으로 스크립트 소스 첨부합니다. 필요하신분은 다운받으시기 바랍니다.

 

 

 

  1. 지니님 2010.08.20 19:33 신고

    안녕하세요 강좌 잘 보고 있습니다. 서핑하다 우연히 들어왔는데 이렇게 좋은 강좌가 있었네요. 앞으로도 좋은 강좌 많이 부탁드립니다.^^

  2. onjo 2010.08.21 18:39 신고

    좋은 정보 감사합니다.. ^^

  3. 나그네 2010.08.25 11:36 신고

    좋은 강좌 감사합니다.

    너무나 많은 도움이 된거 같네요!

    앞으로도 잘 부탁드립니다 (__)

  4. seso 2010.12.19 16:04 신고

    좋은 정보 감사드립니다~!!

  5. 지니파파 2010.12.21 17:00 신고

    좋은 정보 감사합니다.

  6. tony 2011.05.04 19:28 신고

    최고에요0.0

  7. 빛나는 야옹이 2011.06.29 12:10 신고

    열심히 따라했는데요... 안되네요... xml파일 내용이 텅~ 비어있어요,,, 왜 그런건가요?

  8. 빛나는 야옹이 2011.06.29 12:12 신고

    꼭 해보고 싶은데 ㅠㅠ *

  9. 뽀잉뽀잉 2011.10.26 21:48 신고

    와우! 이런정보가 있다니!!
    정말 많은 도움이 되었어요!!

  10. eeaa3 2012.02.27 21:56 신고

    소스구하기에 좋습니다. 책에서는 이부분을 왜 그냥지나쳣는지 ㅎㅎ


Part2 가 시작되었습니다.

전체 강좌 목차
[강좌A01] Moteodev Studio를 이용한 안드로이드 개발 환경 구축 가이드
[강좌A02] 안드로이드 개발 참고 서적 소개
[강좌A03] Android 실전 개발 - 아이디어 / 기획 / Wireframe
[강좌A04] 안드로이드 실전 개발 - 아이콘 제작
[강좌A05] 안드로이드 실전 개발 - 레이아웃 및 리소스 : Part1
[강좌A05] 안드로이드 실전 개발 - 레이아웃 및 리소스 : Part2

 

이제부터는 버튼 관련 리소스 xml을 정의해 보도록 하겠습니다.

Info_grayed.png
info_clicked.png  

1번 이미지는 앱의 정보를 볼 수 있는 이미지 버튼 스타일의 ImageView 입니다. (디퐅트 이미지:info.png, 클릭 이미지:info_clicked.png) 클릭시 팝업 대화상자가 나타날 겁니다. 디폴트로 회색 이미지였다가 클릭시 배경색이 블루로 변경됩니다. . 이 이미지는 iconWorkshop에 있는 이미지를 32*32로 맞추고, grayscale로 변환하여 회색 배경의 이미지로 만들었습니다.

 

Android에서 클릭시 이미지를 변경되는 효과를 주기 위해서 물론 코딩으로도 처리할 수 있지만, xml로 간단히 구현할 수 있습니다. 아래 코드처럼 xml 문서를 만드신 후 drawable 디렉토리에 저장합니다.


button_info.xml


<?xml version="1.0" encoding="utf-8"?>

<selector xmlns:android="http://schemas.android.com/apk/res/android">

    <item android:state_focused="true"

android:state_pressed="false"

android:drawable="@drawable/info_clicked" />

    <item android:state_focused="true"

android:state_pressed="true"

android:drawable="@drawable/info_clicked" />

    <item android:state_focused="false"

android:state_pressed="true"

android:drawable="@drawable/info_clicked" />

    <item android:drawable="@drawable/info"/>

</selector>


Selector element 하위에 item element가 들어갑니다. Item element의 attribute를 보시면 다음의 4가지로 나누어져 있습니다.
android:state_focused="true" android:state_pressed="false" 포커스를 가지고 있으면서 누르지 않은 상태.(보통 클릭 후 상태)
android:state_focused="true" android:state_pressed="true" 포커스를 가지고 있으면서 누른 상태 android:state_focused="false" android:state_pressed="true" 포커스는 없으면서 누른 상태 아무런 속성 설정이 없는 것은 디폴트 상태. 입니다.

Custom EditText 같은 경우에는 위 4가지 속성에 따라서 배경이미지를 다르게 줄 수 있으나, 저희는 클릭시에만 다른 이미지를 보여 줄 것이므로, 디폴트 상태값에는 info.png를 할당하고, 나머지 클릭 상태값에는 info_clicked.png를 설정했습니다.
그림에서 4번 이미지와 5번 이미지는 clipboard copy 및 record delete를 위한 버튼(이미지)입니다. 버튼 클릭시 배경색을 주황색으로 바꿔 주도록 효과를 주기 위해서 이미지를 제작하신 후 button_info.xml과 동일한 스타일로 xml 문서를 만들어 drawable 디렉토리에 저장합니다.

button_copy_clipboard.xml


<?xml version="1.0" encoding="utf-8"?>

<selector xmlns:android="http://schemas.android.com/apk/res/android">

    <item android:state_focused="true"

android:state_pressed="false"

android:drawable="@drawable/copy_clipboard_clicked" />

    <item android:state_focused="true"

android:state_pressed="true"

android:drawable="@drawable/copy_clipboard_clicked" />

    <item android:state_focused="false"

android:state_pressed="true"

android:drawable="@drawable/copy_clipboard_clicked" />

    <item android:drawable="@drawable/copy_clipboard" />

</selector>


button_delete_record.xml

<?xml version="1.0" encoding="utf-8"?>

<selector xmlns:android="http://schemas.android.com/apk/res/android">

    <item android:state_focused="true"

android:state_pressed="false"

android:drawable="@drawable/delete_record_clicked" />

    <item android:state_focused="true"

android:state_pressed="true"

android:drawable="@drawable/delete_record_clicked" />

    <item android:state_focused="false"

android:state_pressed="true"

android:drawable="@drawable/delete_record_clicked" />

    <item android:drawable="@drawable/delete_record" />

</selector>


다음은 6번 이미지를 위한 xml을 보도록 하겠습니다. 저희 앱에 구지 필요 없는 항목이긴 하지만, UI를 좀 더 미려하게 보이게 하기 위해서 추가했습니다. 코드는 다음과 같습니다.

shape_title_indicator.xml


<?xml version="1.0" encoding="UTF-8"?> 

<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle"> 

    <stroke android:width="2dip"  android:color="#19CD00" />

    <corners android:bottomRightRadius="1dip" android:bottomLeftRadius="1dip" 

             android:topLeftRadius="1dip" android:topRightRadius="1dip"/> 

    <padding android:left="1dip" android:top="1dip"

             android:right="1dip" android:bottom="1dip" />

</shape>



이 코드는 기본 도형들을 xml로 정의하는 방법입니다. Shape를 line으로 지정하여 선을 만들수도 있으며 oval 로 지정하여 타원형을 만들수도 있습니다.

<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="line">

   <stroke android:width="1dp" android:color="#FF000000"

           android:dashWidth="1dp" android:dashGap="2dp" />

</shape>



<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="oval">

    <solid android:color="#00000000"/>

    <stroke android:width="4dp" android:color="#99000000"

            android:dashWidth="4dp" android:dashGap="2dp" />

    <padding android:left="7dp" android:top="7dp"

             android:right="7dp" android:bottom="7dp" />

    <corners android:radius="4dp" />

</shape>


Stroke 는 테두리를 그리는 것을 말합니다. dashWidth와 dashGap은 점선을 그릴 때 사용합니다.
Solid는 단색으로 채워 넣는 것을 말하며, gradient는 단색대신 그라디언트 효과를 줄 수도 있습니다

<gradient android:startColor="#FFFF0000" android:endColor="#80FF00FF"

.        android:angle="270"/>


Corners는 가장자리를 둥글게 처리하는 것을 말합니다. Padding은 안쪽 여백을 주는 것입니다. 저희 코드가 이해가 가십니까? 저희 코드는 두께가 2dip인 둥근 사각형입니다. 여백을 주었기 때문에 ImageView 사이즈보다 약간 작게 나오게 됩니다. 그래야 우측 텍스트와 크기를 비슷하게 맞출수 있어서 패딩으로 조정했습니다. 실제 ImageView에 xml을 할당할 때 width를 4dip밖에 주지 않을 것이므로 두꺼운 모서리가 둥근 직선으로 보이게 됩니다.


다음은 문자열을 편집하도록 하겠습니다. Motodev에서 res/values/string.xml 파일을 더블 클릭하여 엽니다. 아래 그림과 같이 편집합니다.


Motodev를 사용하기 때문에 여러 언어를 표 형태의 편집창에서 바로 편집할 수 있어서 편리합니다. 문자열 입력값은 XML 문서 안에 정의되므로 XML 문법에 허용되지 않는 문자열은 입력하시면 에러가 납니다. 저희 앱의 Info 팝업창을 만들 때(뒤쪽 편에 있습니다.) 엔터가 들어간 긴 문장을 TextView에 표시하기 위해서 xml에 문자열 값을 CDATA 섹션에 넣어서 처리를 해 보려고 했으나 제대로 처리가 되지 않았습니다. 특수문자들은 CDATA 안에 넣어서 처리할 수 있습니다. 엔터가 들어간 여러줄의 텍스트를 표현하려면 아직 제가 해본 바로는 각각의 문장을 문자열로 만들어서 프로그램에서 Html.fromHtml() 메소드 처리시 문장마다 <br/>태그를 넣어주거나, 레이아웃에서 여러 개의 TextView를 만들어 사용하시는 것도 한가지 방법입니다.

그리고 label_title_main의 값처럼 <u><b>태그를 추가하여 타이틀 텍스트에 밑줄과 볼드체효과를 줄 수 있습니다. <u><b><i>태그값은 string의 value값으로 바로 정의할 수 있습니다.

이제 drawable에 들어갈 항목을 모두 정의했습니다. 이제부터 메인 레이아웃을 작성하도록 하겠습니다. 프로젝트 생성시 자동으로 만들어진 res/layout/main.xml 파일을 열어서 다음과 같이 편집합니다.

Main.xml

<?xml version="1.0" encoding="utf-8"?>

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"

    android:orientation="vertical"

    android:layout_width="fill_parent"

    android:layout_height="fill_parent"

    >

         <!-- 상단 타이틀바 -->                     

         <LinearLayout android:layout_width="fill_parent"

                       android:layout_height="wrap_content"

                       android:orientation="horizontal"

                       android:background="@drawable/layout_box_bg">

                  <ImageView android:layout_width="4dip"              

                             android:layout_height="20dip"

                             android:layout_marginLeft="3dip"

                             android:layout_gravity="center_vertical"

                             android:src="@drawable/shape_title_indicator"/>

                                                       

                  <TextView android:layout_width="fill_parent"

                           android:layout_height="wrap_content"

                           android:layout_marginLeft="6dip"

                           android:layout_gravity="center_vertical"

                           android:layout_weight="1"

                           android:paddingTop="4dip"

                           android:paddingBottom="2dip"

                           android:textColor="#FFFFFF"

                           android:textSize="16sp"

                           android:text="@string/label_title_main"/>  

                                  

                  <ImageView android:id="@+id/button_info"

                           android:layout_height="32dip"

                           android:layout_width="32dip"

                           android:layout_gravity="right|center_vertical"

                           android:background="@null"

                           android:src="@drawable/button_info"/>                                 

                                                          

         </LinearLayout>

        

         <!-- 상단 한글/영문 입력창  -->

         <LinearLayout android:layout_width="fill_parent"

                       android:layout_height="wrap_content"

                       android:orientation="vertical"

                       android:background="@drawable/layout_box_bg_large">

                       

                  <LinearLayout android:layout_width="fill_parent"

                                android:layout_height="wrap_content"

                                android:orientation="horizontal"  >

                           <TextView android:layout_width="55dip"

                                     android:layout_height="wrap_content"

                                      android:layout_gravity="center_vertical"

                                     android:layout_marginLeft="10dip"

                                     android:textColor="#FFFFFF"

                                     android:textSize="16sp"

                                     android:text="@string/label_password"/>

                           <EditText android:id="@+id/text_password"

                                     android:layout_width="wrap_content"

                                     android:layout_height="38dip"

                                     android:textSize="12sp"

                                     android:layout_weight="1"/>

                           <ImageButton android:id="@+id/button_remove"

                                   android:layout_height="38dip"

                                   android:layout_width="38dip"

                                    android:scaleType="centerCrop"

                                   android:layout_marginLeft="3dip"

                                   android:layout_gravity="right"

                                 android:src="@drawable/remove"/>                                                                  

             </LinearLayout>           

             

             <LinearLayout android:layout_width="fill_parent"

                           android:layout_height="wrap_content"

                           android:orientation="horizontal" >

                          

<TextView android:layout_width="55dip"

                                      android:layout_height="wrap_content"

                                      android:layout_gravity="center_vertical"

                                      android:layout_marginLeft="10dip"

                                      android:textColor="#FFFFFF"

                                      android:textSize="16sp"

                                      android:text="@string/label_english"/>

 

                           <EditText android:id="@+id/text_english"

                                      android:layout_width="wrap_content"

                                      android:layout_height="38dip"

                                     android:textSize="12sp"

                                     android:layout_weight="1"

                                              />

                      <ImageButton android:id="@+id/button_copy"

                                   android:layout_height="38dip"

                                   android:layout_width="38dip"

                                   android:scaleType="centerCrop"

                                   android:layout_marginLeft="3dip"

                                   android:layout_gravity="right"

                                   android:src="@drawable/copy"/>             

             </LinearLayout>                     

         </LinearLayout>

        

         <Button android:id="@+id/button_save"

                 android:layout_width="fill_parent"

                 android:layout_height="wrap_content"

                 android:layout_marginTop="4dip"

                 android:drawableLeft="@drawable/save"

                 android:gravity="center"

                 android:text="@string/save"/>       

                

         <!-- list Layout -->

         <LinearLayout android:orientation="vertical"

                  android:layout_width="fill_parent"

                  android:layout_height="fill_parent"

                  android:background="@null"

                  android:layout_weight="1">

                          

                  <ListView android:id="@+id/android:list"

                           android:layout_width="fill_parent"

                           android:layout_height="fill_parent"

                           android:drawSelectorOnTop="false"/>

        

                  <TextView android:id="@+id/list_empty"

                           android:layout_width="fill_parent"

                           android:layout_height="fill_parent"

                           android:gravity="center_horizontal|center_vertical"

                           android:textSize="20sp"

                           android:paddingTop="50dip"

                           android:text="" />

        

         </LinearLayout>           

</LinearLayout>




LinearLayout만을 사용하여 UI 요소들을 배치했습니다.

상단 타이틀바 영역을 보시면 가운데 TextView 항목만 android:layout_weight="1" 로 주어졌습니다.  화면이 커지고 작아졌을 때 가운데 TextView만 가중치를 주어 늘어나고 줄어들도록 처리했습니다. 다른 부분들도 마찬가지입니다.

 

하단의 리스트뷰 정의 부분을 보시면 <ListView android:id="@+id/android:list" 아이디를 설정하는 부분이 다른 것과 좀 다릅니다. 저희는 메인 화면에 리스트가 들어갈 것이고, 코딩을 좀 더 간단히 처리하기 위해서 ListActivity에서 상속받을 것입니다. ListActivity에서 상속을 받아서 리스트 처리를 할 때는 반드시 아이디를 위와 같이 기술하셔야 합니다.

 

나머지 레이아웃 소스는 그 동안 책을 보셨다면 대부분 아실 내용입니다. 책을 안 보셨더라도 속성명만 가지고도 대충 알 수 있을 겁니다. 저는 여기서 상세한 설정 정보에 대해서 설명하기 보다는 ImageView ImageButton의 사이즈 설정에 대해서 애기를 좀 하겠습니다.

 

먼저 레이아웃 소스 중 타이틀영역의 Info 버튼 이미지 부분 입니다.

<ImageView android:id="@+id/button_info"

           android:layout_height="32dip"

           android:layout_width="32dip"

           android:layout_gravity="right|center_vertical"

           android:background="@null"

           android:src="@drawable/button_info"/>


ImageView
사이즈가 가로, 세로 모두 32dip 입니다.
저희 이 info 이미지를 hdpi 폴더에 48*48 크기로 만들어 두었습니다. ImageView src 속성에 지정되는 이미지는 로딩시에 pre-scaling이 됩니다. hdpi 폰 에서는 저희가 만들어둔 hdpi 폴더의 이미지 사이즈 그대로 48*48 크기의 이미지가 로딩되며, mdpi에서는 48/1.5 = 32 , 32*32 크기 이미지로 축소되어서 로딩됩니다. 그리고 ImageView가 화면에 그려질 때 scaleType 속성값에 지정된 값에 따라서 다시 스케일이 조정될 수도 있지만, scaleType을 지정하지 않으면 디폴트값이 center 속성값이 지정됩니다. center는 따로 스케일 조정을 하지 않는 옵션이므로 ImageView에는 저희가 계획한대로 hdpi에서는 48*48 크기로, mdpi에서는 32*32 크기로 이미지가 화면에 그려집니다.


다음은 ImageButton 부분 최종 소스입니다.

<ImageButton android:id="@+id/button_copy"

             android:layout_height="38dip"

             android:layout_width="38dip"

             android:scaleType="centerCrop"

             android:layout_marginLeft="3dip"

             android:layout_gravity="right"

             android:src="@drawable/copy"/>


 

ImageButton 이게 골 때리는 겁니다. ImageView는 우리가 원하는 대로 정확하게 동작합니다. 하지만 이미지 버튼은 그렇지가 않습니다.

 

먼저 아래의 소스를 보겠습니다. 최종 소스와 다른 점은 ImageButton View의 가로 세로사이즈가 32dip이며 scaleType을 지정하지 않았기 때문에 center 값으로 동작합니다. , 이미지를 draw할 때 별다른 scale 작업을 하지 않습니다. 저희가 이 ImageButton에 사용할 아이콘을 Info icon과 동일하게 hdpi 폴더에 48*48 사이즈로 만들어 두었습니다.

 

32*32 size ImageButton의 레이아웃을 설정했을 때.

 <ImageButton android:id="@+id/button_copy"

             android:layout_height="32dip"

             android:layout_width="32dip"

             android:layout_marginLeft="3dip"

             android:layout_gravity="right"

             android:src="@drawable/copy"/>


ImageView
와 동일한 방식으로 쉽게 생각하여 hdpi 화면에서 48*48 사이즈로 이미지를 로딩하면

~ 이 이미지처럼 버튼 형태의 디자인보다 이미지가 더 커져 버립니다.

 

왜 이럴까요? 버튼의 경우에는 버튼처럼 보이게 하기 위해서 버튼 배경 이미지가 뒤에 깔립니다. 그래서 이런 모습으로 보입니다. 버튼을 나타내는 ImageButton View의 크기를 우리가 제작한 이미지 보다 크게 그려야 합니다. 참고로 이미지 버튼 xml 설정값에서 android:background="@null" 이라고 설정을 추가하면 ImageButton의 버튼 배경이미지가 없어지고, ImageView와 동일한 형태가 되어 버립니다.

 

아래 코드는 위의 단점을 보완하기 위해서 scaleType은 설정하지 않고 ImageButton의 크기만 키웠습니다. 어찌 될까요?

<ImageButton android:id="@+id/button_copy"

             android:layout_height="38dip"

             android:layout_width="38dip"

             android:layout_marginLeft="3dip"

             android:layout_gravity="right"

             android:src="@drawable/copy"/>

좀 낫습니다. 그래도 너무 꽉 차 보이는 군요. 제가 원하는 바는 아닙니다. 그렇다면 ImageButton의 경우 내부에 그림이 그려질 정확한 영역은 얼마일까요? 아래 그림은 WVGA800 화면의 내용을 Android SDK Tools 폴더 하위에 있는 hierarchyviewer.bat 툴로 살펴본 모습니다. 해당 ImageButton을 클릭해보니 width, height값이 57(우리가 Layout에서 38dip를 지정했고, hdpi 모드에서는 38 * 1.5 = 57이 됩니다)이며, padding 값이 mPaddingBottom=17, mPaddingTop=10, mPaddingLeft=16, mPaddingRight=17 값으로 나타납니다.

좀 알기쉽게 그림으로 나타내면 다음과 같습니다.


물론, 이것도 저희가 원하는 바는 아닙니다. 다음은 scaleType=”centerCrop” 로 설정한 소스입니다.


<ImageButton android:id="@+id/button_copy"

             android:layout_height="38dip"

             android:layout_width="38dip"

             android:scaleType="centerCrop"

             android:layout_marginLeft="3dip"

             android:layout_gravity="right"

             android:src="@drawable/copy"/>


scaleType=”centerCrop”은 이미지(아이콘)를 중앙에 그릴영역 (30 * 24) 에 맞춘 후 원 이미지의 비율에 맞게 가로(24px) 30px로 늘립니다.(minus padding). 아래 그림과 같이 57 * 57 ImageButton 30*30 아이콘이 생성됩니다. 거의 우리가 원하는 디자인이 된 것 같습니다.

이밖에 scaleType에 대한 다른 옵션에 대한 상세 내용들은 레퍼런스들을 참고하시기 바랍니다.

 

결론적으로 ImageButton에 아이콘을 올릴 경우 다음과 같은 전략을 사용하실 수 있습니다.

 

1. ImageButton View의 사이즈를 38*38 mdpi 기준 레이아웃에서 설정했을 때 res/drawable-hdpi/ 폴더에 30*30 아이콘을 제작해서 넣어둔 후, scaleType를 지정하지 않고(Default : center) 아이콘을 로드하면 pre-scaling 만으로 ImageButton을 그려 넣기 때문에 편리합니다. (32*32로 만들어서 넣어 두어도 보기에 괜챦습니다.)

 

2. 우리 소스처럼 scaleType=”centerCrop”으로 설정하여 보기 좋게 만듭니다. 이 방식은 pre-scaling 이후 draw 시점에 auto-scaling을 한 번 더 하므로 약간의 성능에서 손실을 보지만, 이미지도 몇 개 안되고..크게 무리는 없을 것 같습니다. 저희는 이미 30*30 아이콘이 아닌 48*48로 만들었기 때문에 2번 방식을 사용하도록 하겠습니다.

 

List row Layout

 

다음은 리스트 중 하나의 Item에 대한 레이아웃에 대한 이미지 입니다


위 그림에서 보듯이 list가 단 하나의 항목만 가진게 아니라 여러 항목을 가지고 있다면 별도의 layout을 만들어 adapter에서 처리하는 게 편리합니다. 위 그림을 표현하기 위해 간단하게 레이아아웃을 만들면 다음과 같습니다.

 

아래와 같이 소스를 작성하여 res/layout/ 폴더에 넣습니다.


list_item.xml

 

<?xml version="1.0" encoding="utf-8"?>

<LinearLayout

           xmlns:android="http://schemas.android.com/apk/res/android"

           android:layout_width="wrap_content"

           android:layout_height="wrap_content">

         <LinearLayout android:layout_width="wrap_content"

                    android:layout_height="wrap_content"

                    android:orientation="vertical"

                    android:gravity="left"

                    android:layout_weight="1">

                  <TextView android:id="@+id/korText"

                           android:layout_width="fill_parent"

                           android:layout_height="wrap_content"

                           android:textColor="#FFFFFF"

                           android:textSize="18sp"

                           android:gravity="center_vertical"

                           android:paddingLeft="5dip"

                           android:singleLine="true"/>

         <TextView android:id="@+id/engText"

                           android:layout_width="fill_parent"

                           android:layout_height="wrap_content"

                           android:textColor="#FFF123"

                           android:textSize="20sp"

                           android:gravity="center_vertical"

                           android:paddingLeft="5dip"

                           android:singleLine="true"/>                

                 

    </LinearLayout>

   

    <ImageView android:id="@+id/copyClipboard"

                              android:layout_width="48dip"

                              android:layout_height="48dip"

                              android:layout_marginLeft="5dip"

                              android:layout_marginRight="0dip"

                              android:background="@null"

                              android:src="@drawable/button_copy_clipboard"

                              android:gravity="center_vertical"/>

                             

    <ImageView android:id="@+id/deleteImage"

                              android:layout_width="48dip"

                              android:layout_height="48dip"

                              android:layout_marginLeft="0dip"

                              android:layout_marginRight="0dip"

                              android:background="@null"

                              android:src="@drawable/button_delete_record"

                              android:gravity="center_vertical"/>

</LinearLayout>


 

모 특별한 내용은 없는 것 같아서 소스에 대한 설명은 생략합니다.

 

끝으로 첫번째 레이아웃 이미지에서 Info(1) 버튼 클릭시 아래 그림과 같이 AlertDialog 를 띄울 예정입니다.


alertDialog 화면을 구성할 때 message(단순문자열)말고 view 자체를 add 할 수가 있습니다. Information 화면은 다른 App을 만들 때도 거의 유사한 형태로 사용될 수 있는 재사용성이 높은 UI라서 Layout 뿐만 아니라 string 정보도 별도로 xml을 구성하도록 하겠습니다. 일부 개발자 분들 중 문자열 리소스는 res/values/strings.xml 파일에만 작성해야만 되는 줄로 알고 계신 분들도 많은 것 같은데..꼭 그렇지는 않습니다. stings.xml 과 동일한 형식으로 xml 문서를 만드신 후 /res/values/폴더에 다른 이름으로 저장하시면 됩니다. 앱의 복잡도가 올라가면서 문자열 리소스에 등록한 문자열이 많은 경우에 한 파일로만 작성하시면 관리하기 힘이 듭니다. 모듈별로 만드는 것도 나쁘지 않다고 봅니다.

 

저희 앱의 Info 는 앞에서 얘기했듯이 재사용성이 높으므로 stings-info.xml 문서를 만들어 values/ 폴더와 values-ko/ 폴더에 넣습니다.

 

res/values/strings_info.xml

 <?xml version="1.0" encoding="utf-8" standalone="no"?>

<resources>

         <string name="msg_info_desc">Hangul to English is utility app for converting fro hangul keyboard to english keyboard.</string>

    <string name="msg_info_support_email_desc">Support Email </string>

    <string name="msg_info_support_email">jinook@paran.com</string>

    <string name="msg_info_thanks">Thanks.</string>

    <string name="msg_info_support_url">http://overoid.tistory.com</string>

    <string name="msg_info_support_url_desc">Support Site</string>

    <string name="msg_info_close_button">close</string>

</resources>


res/values-ko/strings_info.xml

<?xml version="1.0" encoding="utf-8" standalone="no"?>

<resources>

         <string name="msg_info_desc">Hangul To English 프로그램(App)은 사용자가 보다 한글로 입력된 패스워드에 해당되는 영문자를 쉽게 찾아주는 앱입니다.</string>

    <string name="msg_info_support_email_desc">문의사항, 건의사항은 이메일로 보내주세요.</string>

    <string name="msg_info_support_email">jinook@paran.com</string>

    <string name="msg_info_thanks">감사합니다.</string>

    <string name="msg_info_support_url">http://overoid.tistory.com</string>

    <string name="msg_info_support_url_desc">상세 메뉴얼 및 프로그램 관련 정보는 아래 사이트를 참고해 주시기 바랍니다.</string>

    <string name="msg_info_close_button">닫기</string>

</resources>

 
그리고 AlertDialog에 넣을 View의 내용을 Layout 파일로 만들어 res/layout/ 폴더에 넣어둡니다.
코딩은 차차 하게 될 것입니다.

res/layout/info.xml


<?xml version="1.0" encoding="utf-8"?>

<LinearLayout

           xmlns:android="http://schemas.android.com/apk/res/android"

           android:layout_width="fill_parent"

           android:layout_height="fill_parent"

           android:padding="10dip"

           android:orientation="vertical">

          

           <TextView android:layout_width="fill_parent"

              android:layout_height="wrap_content" 

              android:textSize="12sp"

              android:text="@string/msg_info_desc"/>

             

          <TextView android:layout_width="fill_parent"

              android:layout_height="wrap_content" 

              android:layout_marginTop="20dip"

              android:textSize="12sp"

              android:text="@string/msg_info_support_url_desc"/>

          <TextView android:layout_width="fill_parent"

              android:layout_height="wrap_content" 

              android:textSize="12sp"

              android:linksClickable="true"

              android:autoLink="web"

              android:clickable="true"

              android:text="@string/msg_info_support_url"/>

             

          <TextView android:layout_width="fill_parent"

              android:layout_height="wrap_content" 

              android:layout_marginTop="20dip"

              android:textSize="12sp"

              android:text="@string/msg_info_support_email_desc"/>

          <TextView android:layout_width="fill_parent"

              android:layout_height="wrap_content" 

              android:textSize="12sp"

              android:linksClickable="true"

              android:autoLink="email"

              android:clickable="true"

              android:text="@string/msg_info_support_email"/> 

             

          <TextView android:layout_width="fill_parent"

              android:layout_height="wrap_content" 

              android:layout_marginTop="20dip"

              android:textSize="12sp"

              android:text="@string/msg_info_thanks"/>                  

          

</LinearLayout>


LinearLayout 하위에 TextView만 잔뜩 가지고 있는 Simple View입니다. 중간에 제 블로그 및 email 링크를 위해 몇가지 속성이 추가되었습니다. Android:autoLink email 혹은 web으로 주면 자동으로 링크가 만들어집니다. 또한, android:linksClickable=”true”로 설정하시면 앱 화면에서 링크를 클릭하면 자동으로 웹사이트로 이동하거나 이메일 어플이 실행됩니다. 별다른 코드도 필요없습니다.

 

아래 그림은 최종적으로 만든 layout을 별도의 코딩없이 애뮬에 띄운 모습입니다.
                               [HVGA , 영문 모드에서 실행한 화면]


                              [WVGA800, 한글 모드에서 실행한 화면]


드디어 장문의 레이아웃편이 끝이 났습니다. 파트2에 Syntax Highlight 적용하는데.. 이상하게 먹어서 결국은 다시 빼 버렸네요. 에구..시간만 허비했습니다. 어차피 소스 파일로 제공할 건데...왜 적용하느라 애 먹었는지 ...

리소스편까지 작업된 소스 파일입니다. 필요하신 분 다운받아 가시기 바랍니다.


 

다음번 강좌에서는 리소스 XML 을 들여다 보는 방법에 대해서 간단히 알려드린 후 DB(sqlite)로 넘어가도록 하겠습니다.


  1. 신단수 2010.08.17 17:17 신고

    안드로이드 개발에 대한 막연함만 가지고 있었는데..
    강좌 처음부터 보니 감이 잡힙니다.
    단비와 같은 자료 제공과 강좌입니다.
    좋은 정보 감사드려요...

  2. 이유식 2010.08.26 12:03 신고

    안드로이드를 공부하고 있는 학생입니다.
    졸작으로 어플을 만들고 있는데 제가 원하는 리스트 뷰에 대한 내용이 여기에
    딱 있어서 많은 도움이 되었습니다.
    죄송하지만,, 리스트 아이템 추가 이벤트와 리스트에서 삭제 이벤트에 대한
    팁만 조금 알려주시면 안되나요?
    위에 save text에 대한 이벤트는 버튼이 있는 곳에서 이벤트 처리를 하면 될것 같고요,,
    그 밑에 리스트 아이템으로 오는 것에서 삭제 이벤트는 어떻게 지정해야 되나요?

    • 보고픈 2010.08.26 12:56 신고

      리스트의 내용을 보통 Adapter에서 처리하는데요.
      삭제 이벤트는 Adapter내의 getView() 메소드 내에서 이벤트 핸들러를 추가하시면 됩니다.
      상세한 소스 코드는 다음 다음번 포스트 정도에서 나올것 같습니다.

  3. AnzLove 2010.09.05 03:30 신고

    이미지버튼 때문에 골치아파하고 있는데, 도움이 많이 되네요. ^^
    감사합니다.


오늘의 안드로이드 실전 개발 강좌는 전 시간에 이어 저희가 개발할 앱의 레이아웃 및 리소스들을 만들어 보도록 하겠습니다

전체 강좌 목차
[강좌A01] Moteodev Studio를 이용한 안드로이드 개발 환경 구축 가이드
[강좌A02] 안드로이드 개발 참고 서적 소개
[강좌A03] Android 실전 개발 - 아이디어 / 기획 / Wireframe
[강좌A04] 안드로이드 실전 개발 - 아이콘 제작
[강좌A05] 안드로이드 실전 개발 - 레이아웃 및 리소스 : Part1

추천해 드린 책들 중 레이아웃 및 리소스 부분은 먼저 공부하셨는지 모르겠습니다. 최소한 그 부분은 먼저 공부하시고 제 강좌를 보시기 바랍니다. 대충 훑어 보시면 됩니다. 블로그에서 대부분의 안드로이드 책에 있는 내용까지 모두 다룰려면 거의 책을 써야 되는 상황이라 아주 초보적인 내용은 다루지 않기 때문에 책을 보신 후 블로그 포스트를 보시면 설명을 이해하는데 도움이 될 것입니다.

먼저 Motodev 실행하시고 (eclipse에 SDK 설치하신 분도 상관없습니다) Motodev > New > New Android Project 실행하시고, 아래 그림에 보이듯이 



프로젝트 정보를 설정후 생성합니다.

프로젝트 명은 “Hangul2English”

Target SDK : Android 2.0 (국내 출시용이므로 2.0 으로 해도 무방합니다. Google Market에 해외 사용자들도 포함해서 Release 하실려면 적어도 1.6으로 만드셔야 합니다. 아직 1.5 사용자도 많긴 하지만, 1.5와 1.6사이에 변경된 게 많아서.. 1.6정도로 타협하시면 될 것 같습니다.)

Package Name : com.overoid.hangul2english (제 블로그의 앞자리를 땄습니다.)

프로젝트를 생성하셨으면 아래 그림과 같이 패키지 및 디렉토리를 추가하여 구성합니다.

com.overoid.hangul2english.data – sqlite 관련된 소스가 들어갈 예정입니다.

com.overoid.hangul2english.util – utility class

 

화면이 많아서 소스가 복잡해지면 저는 activity / adapter / view 용 패키지를 따로 만들어서 사용하기도 합니다. 구글에서 만든 패키지들를 보면 아무리 복잡해도 별도의 패키지를 안 만들고 메인 폴더 하위에 수십개의 클래스 파일이 다 들어가 있습니다. 머리가 좋은 모양입니다. 저는 파일이 너무 많아지면 이게 그건지 저게 그건지 너무 헷갈립니다. 본 프로젝트는 워낙 화면이 간단한지라 나눌 필요는 없을 것 같습니다.

 

만일, 개발하시려는 앱이 많은 화면과 뷰를 가지고 있다면 아래와 같이 패키지를 나누셔도 좋을것 같습니다. (그냥 제 취향입니다.)

 

com.overoid.hangul2english.activity

com.overoid.hangul2english.activity.adapter

com.overoid.hangul2english.activity.view

 

res\ 하위에 drawable 디렉토리를 추가합니다. drawable 에는 xml로 된 리소스(이미지 등) 파일이 들어갈 예정입니다.

 

values-ko 디렉토리도 추가한 후 values/strings.xml 파일을 복사해서 붙여넣습니다. 한글 문자열을 저장할 파일입니다. values-ko 라고 디렉토리를 만들어서 사용하시면 android os가 폰 설정된 언어셋을 읽어서 언어에 맞게 strings.xml 파일을 로드해서 처리합니다.

 

기본 작업이 끝났습니다.

이제 처음으로 작업할 내용은 이미지나 버튼을 위한 리소스(이미지 등) 작업을 먼저 하겠습니다. 나중에 해도 상관없습니다. 제 개발 스타일입니다.

.

저희가 개발할 앱의 UI 기획서를 다시 보겠습니다.


이 화면 요소 중 1, 2, 3, 4, 5, 7 bitmap(png) 이미지 파일을 로드 한 것 입니다. 6번도 이미지이긴 하지만 xml로 정의해서 로딩시에 그린것입니다.

먼저 UI 구성에 필요한 이미지를 제작 혹은 구하셔서 res\drawable-hdpi 디렉토리에만 저장합니다. (아이콘 제작은 이전 포스트(http://overoid.tistory.com/7) 을 참고하세요)

여기서 잠깐..

안드로이드가 여러 크기의 화면을 어떻게 지원하는지 잠깐 돌아보겠습니다.

원문은 http://www.kandroid.org/guide/practices/screens_support.html 여기에 있습니다. 이 문서는 반드시 읽어 보시기 바랍니다. 아주 중요한 문서입니다. 그렇지 않으면 나중에 복잡한 UI를 여러 화면 사이즈에 대응해야 하는 앱을 만들 때 고생하십니다.

 

제가 소개해 드린 알짜만 골라 배우는 안드로이드 프로그래밍 2” 에도 원문 문서를 기반으로 상세히 관련 내용이 추가되어 있습니다. 오호.. 오늘 찾아보니 번역하신 분도 있군요. http://materer.tistory.com/archive/201005 이 문서입니다.

 

여기에서는 위 내용 중 꼭 필요한 일부분만 간략하게 설명하도록 하겠습니다.

먼저 용어 설명입니다.


스크린사이즈(Screen Size) : 스크린 사이즈는 스크린의 대각선 크기 값으로 물리적인 크기를 나타냅니다. 안드로이드는 스크린 사이즈를 large, normal, small 로 나눕니다.

가로세로비(Aspect ratio) : 가로세로비는 스크린의 물리적인 넓이와 높이 비율을 말합니다. 안드로이드에서는 리소스 제한자인 long, notlong을 이용하여 화면 비율에 대한 layout 리소스를 제공합니다.

해상도(Resolution) : 스크린이 가지고 있는 전체 픽셀수를 나타냅니다. 해상도가 보통 넓이 * 높이로 표현되기는 하지만 해상도가 특정 가로세로비(Aspect ratio)를 의미하지는 않습니다. 안드로이드에서는 해상도를 직접 처리하지는 않습니다.

밀도(density) : 스크린 해상도를 기반으로 물리적 넓이와 높이안에 얼마나 많은 픽셀이 들어있는가를 나타냅니다. Lower density의 스크린에서는 같은 넓이와 높이안에 더 적은 수의 픽셀이있고, Higher density의 스크린에서는 같은 넓이와 높이안에 더 많은 수의 픽셀이 있습니다.  안드로이드에서 density는 아주 중요한 개념입니다. 만일, UI 요소들을 pixel 단위로 크기를 지정하면 낮은 density 화면에서는 더 크게 보이고, 높은 density 화면에서는 더 작게 보입니다. Androiddensity high, medium, low 로 나누며, 플랫폼에서는 실제 스크린밀도에 맞게 리소스들의 사이즈를 조정합니다.

Density Independent Pixel(dip) : density와 상관없이 레이아웃의 위치를 표현할 때 사용하는 가상의 pixel 단위입니다.. Density-independent pixel default density 160dip 에서의 물리적 pixel과 같다. Mediaum Density(160), mdpi 화면에서는 1pixel = 1dip이며, 다른 dip에서 픽셀변환공식은 pixels = dips * (density / 160) 로 처리됩니다.



다음 표는 Android에서 지원되는 Screen Size Density 간의 관계를 나타낸 내용입니다.

   Low density (120), ldpi  Medium density (160), mdpi  High density (240), hdpi
 Small screen  QVGA (240x320),
 2.6"-3.0" diagonal
   
 Normal screen  

W QVGA (240x400),
  3.2"-3.5" diagonal

 

 FWQVGA (240x432),
 3.5"-3.8" diagonal
 HVGA (320x480),
3.0"-3.5" diagonal
 

  WVGA (480x800),
  3.3"-4.0" diagonal

 

FWVGA (480x854),
3.5"-4.0" diagonal
 Large screen    

  WVGA (480x800),
  4.8"-5.5" diagonal

 

 FWVGA (480x854), 
 5.0"-5.8" diagonal
 


이 표에서 BaselineHVGA, Normal Screen, Medium density 이며, dip pixel 1:1로 매칭되는 조건입니다. 대부분의 국내 폰들은 WVGA(400*800) hdpi 입니다.

 

실제 개발시 장치 종류마다 리소스 한정자를 사용하여 별도의 리소스를 지정할 수도 있습니다.
밀도(ldpi, mdpi, hdpi, nodip)

가로세로비(long, notlong)

스크린사이즈(small, normal, large)

 

리소스를 디렉토리명으로 구분한 샘플입니다.

Res/layout/layout1..xml -> Noraml 스크린 사이즈 레이아웃

Res/layout-small/layout1.xml -> Small 스크린 사이즈 레이아웃

Res/layout-large/layout1.xml -> Large 스크린 사이즈 레이아웃

Res/drawable-ldpi/icon.png -> Low density를 위한 아이콘

Res/drawable-mdpi/icon.png -> Medium density를 위한 아이콘

Res/drawable-hdpi/icon.png -> High density를 위한 아이콘

Res/drawable-nodpi/res.xml -> density와 무관한 리소스

 

무슨 말들인지 어렵습니다. 하하~ 쉽다구요. 여러분들은 머리가 저보다 훨씬 좋으신 겁니다. 위에서 얘기한 것중 이것만 기억하십시요. 안드로이드는 폰이 다양해서 여러 UI를 동시 처리하기 위해서 신경써야 한다. 그중 제일 중요한 것은 density이며, dpi라는 단위를 사용해야 한다.

 

이처럼 다양한 크기 및 density를 지원해야 하기 때문에 안드로이드 UI 개발은 iPhone UI 개발보다 훨씬 복잡하고 귀챦습니다. 그렇기 때문에 안드로이드가 내부적으로 UI를 처리하는 방식을 이해하고, 개발할 앱의 타겟을 잘 잡아서 만들어야 합니다.


하지만 국내 TStore에 올라오는 어플들을 보면 국내 출시된 폰이 거의 큰 화면 위주라 이런거 신경안쓰고 큰 화면 기준으로 개발된 앱들도 더러 있는 것 같습니다. 어떻게 아냐구요? 다음 강좌에 나올 리소스 해킹편을 보시면 다른 앱들의 리소스를 들여다 보시면 이해가 되실 겁니다.

 

How Android Supports Multiple Screens

 

안드로이드에서는 런타임시에 아래 3가지 방식 중 하나로 다양한 화면을 지원합니다. 아주 중요한 내용입니다.

 

1.  Pre-Scaling (보통 bitmap 이미지 처리시에..)


Pre-Scaling
은 로딩시점에 크기를 조절합니다. CPU에 이득이 있다고 알려져 있습니다.

Pre-Scaling은 폰의 Density를 기준으로 동일한 dpi 디렉토리에 지정된 리소스를 로딩하며 이때는아무런 크기 변환없이 보여줍니다. 즉 폰이 hdpi density라면 res/drawable-hdpi 디렉토리 하위의 리소스를 먼저 찾아서 있다면 아무런 크기 변환없이 그대로 보여주게 됩니다. 만일, 매칭되는 리소스가 없다면 디폴트 리소스(basline)를 로딩하고 로딩시에 적합한 density로 크기 변환을 합니다. 예를들어 res/drawable-mdpi/ 100*100 아이콘만 존재한다고 할 때 만일 폰의 사양이 hdpi라면 안드로이드는 drawable-mdpi 하위의 아이콘을 읽을때 자동으로 크기를 확대해서 150 * 150 bitmap을 만듭니다. 반대로 drawable-hdpi/ 150*150 아이콘만 존재할 때 mdpi 폰에서 읽으면 자동으로 100*100 아이콘으로 크기를 변환합니다. drawable-hdpi drawable-mdpi에 모두 이미지가 존재한다면 별도의 스케일 변환 작업이 필요없으니 좀 더 성능에 유리합니다.

 

2. Auto-scaling (pixel dimensions and coordinates)


Auto-scaling
은 그리는 시점에 크기를 조절합니다. 메모리에 이득이 있다고 알려져 있습니다. 주로 Pixel좌표, Pixel Dimesion, Application에서 사용된 Pixel 수식 등에 적용되며, 리소스가 아닌 웹이나 SD카드에서 Bitmap 데이터를 가져왔을 때도 적용됩니다. 쉽게 얘기하면 App에서 (10,10)에서 (100,100) 좌표로 4각형을 그리도록 구현했다면 High-Density(240dpi) 화면을 가진 Device에서는 그리는 시점에 자동으로 스케일을 변환해서 (15,15) (150,150) 좌표에 사각형을 그리게 된다.

 

3. Compatibility Mode(호환모드)


Large
스크린을 지원 안하는 앱을 Large 스크린에서 실행하면 검은 배경에 원래 크기만큼만 표시합니다. 보기에는 별로입니다. 앱이 싼티 납니다.


.. 여전히 복잡하고 어렵군요. 원문 보시면 좀 더 상세하지만 복잡한 내용들로 가득 차 있습니다.

중요한 점은 안드로이드가 여러 화면사이즈를 지원하기 위해서 런타임시에 자동으로 크기를 바꾼다는 것을 기억하십시요.

 

제가 사용하는 기본 레이아웃 디자인 룰은 다음과 같습니다.

 

1. 레이아웃 디자인시에는 HVGA 기본 스크린 사이즈를 중심으로 DIP 단위만을 사용해서 디자인합니다. (px dip 1:1 이라서 화면 크기에 대응하여 사이즈 결정하기가 좋습니다.) 320 * 480 화면 기준으로 들어갈 이미지나 UI 요소들 각각의 가로 사이즈를 px로 계산한 후 코딩시에는 그 값의 단위를 dip로만 입력하면 됩니다.

 

2. AbsoluteLayout을 사용하지 않습니다. , 화면의 절대 좌표 보다는 상대 좌표를 사용해야 합니다. 이거 사용해서 디자인하면..나중에 감당 안됩니다.

3. Bitmap HDPI 기준으로 만듭니다. 그래야 자동으로 크기가 조정 되더라도 보기에 좋습니다. , HVGA를 기준으로 계산했을 때 100 * 100 이미지가 필요하다면 HDPI 기준으로 크기의 1.5배인 150 *150 크기로 실 이미지를 제작하시면 됩니다. 만일, 여력이 된다면 hdpi mdpi 기준으로 각각 만드는 것도 나쁘지는 않습니다. 위에서 언급했듯이 성능에 유리합니다.

 

4. 각 장치별로 레이아웃 및 이미지를 모두 별도로 만들어서 세밀하게 조정할 수도 있지만, 그런 경우 관리가 힘듭니다. 가급적 레이아웃과 이미지를 적게 사용해서 통일된 UI를 구성하는 것이 좋습니다.

 
자 이제 저희 앱 개발로 돌아가겠습니다.

기획서 상의 1, 2, 3, 7 번 이미지는 HVGA상에서 32 * 32 크기로 랜더링 될 것입니다. 즉 코딩시에 가로 세로 사이즈 값에 32dip라고 입력할 것입니다. 그래서 실제 이미지는 위 가이드에 따라서 48 * 48 (32 * 1.5 = 48) 사이즈로 제작해서 drawable-hdpi 에 저장합니다.

4, 5번 이미지는 리스트에 들어갈 이미지 버튼입니다. 약간 큰 리스트로 구성할 계획이라서 코딩시 크기값은 48dip 가 되며, 실 이미지 제작은 hdpi 기준으로 64 * 64 크기로 제작합니다.

 

그리고 1, 4, 5번은 클릭시에 이미지 버튼에 효과를 주기 위해서 이미지를 쌍으로 준비합니다. iconWorkshop에서 배경에 색칠을 한다던지, GrayScale로 변경하여 만드시면 됩니다.

 

6번 이미지는 이미지로 제작한 게 아니라 xml로 정의하여 런타임시에 그린 이미지입니다.

8, 9번은 nine-patch 이미지입니다. Nine-patch 이미지는 이미지를 9분할하여 이미지 사이즈 변경시에 이쁘게 형태를 유지할 수 있도록 하는 이미지 입니다. 파일명.9.png 이런식으로 파일명이 구성되어 있습니다. 레이아웃에 지정할 때는 파일명 부분만(9.png를 제외한) 지정하시면 됩니다.  이 두 이미지는 제가 이것저것 보다가 찾은 이미지인데, 안드로이드 블랙 배경에 잘 어울리는 듯 합니다.

 

참고로 Android SDK 하위 폴더를 보시면 data/res/drawable-hdpi/ 애뮬레이터에서 사용하는 모든 이미지들이 있습니다. 좋은 것들도 많고 도움이 많이 됩니다. 구글에서 제공되는 기본 앱 디자인 스타일과 유사한 인터페이스를 제공하려면 이 이미지들을 이용하시면 됩니다.

 

이제 이미지가 모두 준비되었습니다.

다음 화면은 hdpi 폴더에 저장된 이미지 내용입니다. 위에서 언급한 내용과 이전 포스트에서 제작한 아이콘이 저장되어 있습니다.



내용이 너무 길어 읽으시는 분들이 지루할까? 여기서 레이아웃 Part1을 마치고, 잠시 후에 Part2로 찾아뵙겠습니다.


  1. 소영 2010.09.26 12:02 신고

    아 정말 정말 정말 많은 도움이 되었어요!!
    에뮬레이터에서 기본적으로 제공되는 버튼 같은 것만 사용하다가

    이제 한번 직접 만든 이미지를 사용해서 상용화되는 앱을 만들려다보니까

    가장먼저 골치 아픈 것이 기종에 따라 다른 스크린 사이즈에서 이미지를 리사이즈 하는 것이더라구요..ㅠㅠ

    사실 아직도 -_-.. 그렇지만 여기서 정말 좋은 정보 많이 얻고 가요! 도전해봐야겠어요! ㅎㅎ

  2. 희망안드 2011.01.25 06:56 신고

    성격이 깔끔하시고 세심한 면이 많으신듯 해요
    하나하나 글쓰시는게 많은 도움이 되고
    집중력을 높이네요
    대단히 감사합니다
    강좌 계속 하시면서 책도 쓰심 대박 나실듯
    계속 강좌 부탁드려요

  3. 강민 2011.11.20 12:36 신고

    당신은 최고입니다 ㅋ
    너무 강좌가 좋네요
    교육쪽에서 일하시나? ㅋㅋㅋ
    계속 강좌 읽겠습니다 ㅠㅠ

+ Recent posts