2010년 11월 21일 일요일

코드요소 - User Interface의 기본 개념

안드로이드 애플리케이션에서 사용자 인터페이스는 View와 ViewGroup 객체를 사용해서 만든다. 많은 뷰와 뷰 그룹이 있지만 이들은 모두 View 클래스의 서브클래스이다.
View객체는 사용자 인터페이스에 나타나는 기본 단위이다. View 클래스는 텍스트필드, 버튼 등 "widget"이라고 불리는 클래스들의 베이스 클래스이다. ViewGroup 클래스는 linear, tabular, relative 등 레이아웃 클래스들의 베이스 클래스이다.
View 객체는 레이아웃 파라메터나 화면의 사각영역 등의 속성을 저장하는 데이터 구조이다. 그리고 측정치, 레이아웃, 그리기, 포커스 변경, 스크롤, 제스쳐 등을 관리하고 처리한다. 또한 뷰는 사용자와의 상호작용 이벤트의 수신 지점이다.

뷰 계층도

안드로이드 플랫폼에서 개발자는 다음 다이어그램에서 보이는 것과 같은 ViewGroup과 View 노드의 계층도를 사용하여 액티비티의 UI를 정의한다. 이 뷰 계층도 내에 자원요소에서 설명한 레이아웃, 위젯들이 배치되게 된다.

이 계층도는 간단할 수도 있고 필요한 만큼 복잡하게 정의할 수도 있으며, 안드로이드의 미리 정의된 위젯들을 사용하여 만들 수도 있고, 자신이 제작한 뷰들을 이용하여 만들 수도 있다.
이 뷰 계층도를 화면에 나타나도록 하려면, 액티비티가 setContentView() 메쏘드를 이용하여 루트 노드 객체에 대한 레퍼런스를 전달해야 한다. 안드로이드 시스템은 이 레퍼런스를 받아서 뷰 트리를 갱신,측정,생성한다. 계층도상의 루트 노드가 차일드 노드 자신을 그리도록 요청한다.--바꾸어 말하면, 각 뷰그룹 노드는 각 차일드 노드들에게 자신을 그리도록 요청하는 책임을 맡고 있다. 차일드는 부모 내에서 크기나 위치를 요청할 수 있지만, 각 차일드가 어디에 얼마나 크게 그려질지는 부모 노드가 최종적으로 결정한다. 안드로이드는 레이아웃의 엘리먼트들을 순차적으로 파싱(계층 트리의 꼭대기에서부터)하여 뷰를 인스턴스화하고 부모 뷰에 추가한다. 이 그리기가 순차적으로 이루어지기 때문에, 위치가 겹치는 엘리먼트가 있을 경우, 마지막에 그려진 뷰가 이전에 그려진 뷰의 위에 그려지게 된다.

UI 이벤트

UI에 뷰/위젯을 배치했으면, 사용자와의 상호작용에 의해 액션을 취할 필요가 있다. UI 이벤트를 인식하려면 다음 둘 중 한 가지를 해야 한다:
• 이벤트 리스터를 정의하고 그것을 뷰에 등록한다. 대부분, 이 방식으로 이벤트를 취한다. View 클래스에는 On<something>Listener라는 이름의 네스티드 인터페이스 콜렉션이 있고, 각 인터페이스에는 On<somethind>()라는 콜백 메쏘드가 들어 있다. 예를 들면, View.OnClickListerer(View의 클릭을 처리), View.OnTouchListener, View.OnKeyListener 등이 있다. 따라서 뷰가 클릭되었을 때 알려주기를 원한다면, OnClickListener를 구현하고 그 리스너의 onClick() 콜백 메쏘드(여기서 클릭에 대한 반응 코드를 입력)를 정의하고, 그것을 View에 setOnClickListener()로 등록해야 한다.
• 그 View의 기존 콜백 메쏘드를 오버라이드한다. 자신만의 View 클래스를 구현하고 그 안에서 발생하는 특정 이벤트를 취하려면 이 방식으로 구현한다. 처리할 수 있는 이벤트의 예로는 onTouchEvent(),onTrackballEvent(),onKeyDown() 등이 있다.

메뉴

메뉴는 어플리케이션의 UI에서 중요한 요소 중 하나이다. 메뉴를 통해 어플리케이션의 기능과 설정을 표시하는 안정적인 인터페이스를 만들 수 있다. 가장 일반적은 어플리케이션 메뉴는 기기에 있는 MENU 키를 누름으로써 나타나는 메뉴이다. 하지만, 사용자가 항목을 꾹 눌렀을 때 나타나는 컨텍스트 메뉴를 설정할 수도 있다.
메뉴 또한 뷰 계층도를 사용하여 구조화되어 있지만, 이 구조를 개발자가 정의하지는 않는다. 대신에, 개발자는 액티비티의 onCreateOptionsMenu() 나 onCreateContextMenu() 콜백 메쏘드를 통해 정의하고 메뉴에 포함시킬 항목들을 선언한다. 그러면 안드로이드 시스템이 필요한 때에 필요한 뷰 계층도를 자동으로 그리고 각 메뉴 항목을 표시한다.
메뉴는 자신의 이벤트를 처리하기 때문에 메뉴의 항목에 대해 이벤트 리스터를 등록할 필요가 없다. 메뉴의 항목이 선택되면, 프레임웍이 자동으로 onOptionsItemSelected() 나 onContextItemSelected() 메쏘드를 호출한다.
그리고 어플리케이션 레이아웃과 똑같이, 메뉴 항목을 XML 파일에 선언할 수도 있다.

어댑터

하드코딩된 정보를 표시하는 것이 아니라, 외부 데이터 소스에서 동적인 정보를 뷰 그룹에 표시하고 싶을 때가 있다. 이런 작업을 하려면 뷰 그룹으로 AdapterView를 사용하고 각 차일드 View는 그 Adapter로부터 데이터를 초기화하고 나타내도록 한다.
AdapterView 객체는 특정 Adapter 객체에 기반하여 그 차일드 뷰를 결정하는 ViewGroup을 구현한 것이다. Adapter는 데이터소스와 AdapterView(데이터를 화면에 표시) 사이의 통로 역할을 한다. Adapter 클래스를 구현한 것은 유형별로 여러 가지가 있는데, 예를 들면 Cursor로부터 데이터베이스 데이터를 읽어오는 CursorAdapter, 임의의 배열로부터 데이터를 읽어오는 ArrayAdapter 등이 있다.

스타일 및 테마

표준 위젯의 모양이 만족스럽지 않을 경우가 있다. 이것을 개선하기 위해, 자신의 스타일과 테마를 만들어 적용할 수 있다.
• 스타일은 레이아웃 상의 개별적인 엘리먼트에 개별적으로 적용시킬 수 있는 하나 이상의 구성 가능한 속성들이다. 예를 들어, 문자 크기와 색상을 지정하는 스타일을 정의하고, 그것을 특정 뷰 엘리먼트에 적용할 수 있다.
• 테마는 어플리케이션의 모든 액티비티나 특정 액티비티에 적용시킬 수 있는 하나 이상의 구성 가능한 속성들이다. 예를 들어, 윈도우 프레임과 패널 배경에 대한 색상 및 문자 크기와 색상을 지정한 테마를 정의할 수 있다. 그리고 이 테마를 특정 액티비티나 전체 어플리케이션에 적용되도록 할 수 있다.

스타일과 테마는 자원 중 하나이다. 안드로이드는 개발자가 적용시킬 수 있는 여러 스타일과 테마를 제공하며, 개발자만의 임의 스타일 및 테마 자원을 정의해서 사용할 수도 있다.

2010년 11월 15일 월요일

코드요소

지난호까지 안드로이드의 자원요소를 알아봤고, 지금부터는 코드요소에 대해 연재하려고 합니다.

자원요소를 뼈대라고 한다면 코드요소는 살과 기능들이라고 할 수 있고, 자원요소를 정적인 부분이라고 하면, 코드요소는 동적인 부분이라고 할 수 있습니다. 그만큼 자원요소는 좀 재미 없고 지루한 느낌이 들고, 코드요소는 재미가 좀 나죠.. 하지만 코드요소를 또 열심히 하다 보면, 나중엔 또다시 자원요소를 들여다봐야 하는 일이 있는 만큼 자원요소는 골격의 개념으로서 중요한 부분을 차지합니다.

간단한 어플인 경우 자원요소 없이 코드요소만으로 안드로이드 프로그램을 작성하는 일이 가능하며 그만큼 안드로이드는 유연한 개발환경을 제공합니다. 하지만 화면이 좀 늘어나면 코드요소만으로 화면을 제어하기가 매우 어려우며 따라서 자원요소와 코드요소를 적절히 배합하여 어플을 만드는 것이 바람직합니다.

코드요소는 Java 코드로 작성하며, 안드로이드 자체가 Java 위의 어플리케이션 프레임워크로 작성되어 있기 때문에 기본적인 Java 개념에 대한 이해를 갖고 시작하는 것이 좋습니다. Java를 모르는 상태로 안드로이드를 배우려 한다면 사상누각이라는 것이 어떤 것인가를 실감할 수 있을 것이라 생각됩니다. ^^

코드요소에 관해 연재하는 순서는
- User Interface의 기본개념
- 메뉴 설정
- 화면 구성
- 자원에 접근
- 컨텐츠 제공자
- 알림 기능
- 어댑터
- 서비스
의 순서로 다룰 예정입니다.

2010년 11월 7일 일요일

자원요소 - Android Manifest.xml

모든 어플리케이션은 루트 디렉토리에 AndroidManifest.xml 파일을 갖고 있어야 한다. 이 파일은 어플리케이션에 대한 기본적인 정보를 안드로이드 시스템에 제공함으로써, 시스템이 어플리케이션 코드를 실행하기 전에 필요한 정보를 갖고 있도록 한다. 이 파일이 수행하는 주요한 일은 다음과 같다:

• 어플리케이션의 자바 패키지 이름을 지정한다. 이 패키지 이름은 어플리케이션의 고유한 식별자로써 작용한다.
• 어플리케이션의 컴포넌트들에 대한 사항을 지정한다-어플리케이션을 구성하는 액티비티, 서비스, 브로드캐스트 리시버, 컨텐트 프로바이더 등을 지정한다. 그리고 각 컴포넌트를 실행시키고 외부에 알리는(예를 들어, 어떤 인텐트 메시지를 처리할 수 있는지) 클래스의 이름을 지정한다. 이렇게 지정함으로써 안드로이드 시스템이 어떤 컴포넌트를 어떤 조건 하에서 실행시킬지 알 수 있도록 한다.
• 어떤 프로세스가 어플리케이션들의 작동을 주관하는지 결정한다.
• 어플리케이션이 다른 API의 보호된 부분에 액세스하고 다른 어플리케이션들과 상호작용하기 위해 가지고 있어야 하는 퍼미션을 선언한다.
• 위와 반대로 다른 어플리케이션이 이 어플리케이션과 상호작용하기 위해 필요한 퍼미션을 정의한다.
• 어플리케이션이 실행될 때 프로필 및 기타 정보를 제공하는 도구 클래스를 선언한다. 이 선언은 어플리케이션이 개발 및 테스트될 때만 존재하며, 어플리케이션이 발매되면 제거된다.
• 어플리케이션에 요구되는 최소 API 레벨을 지정한다.
• 어플리케이션과 연결되어야 하는 라이브러리를 지정한다.

* AndroidManifest 파일의 구조 틀

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

<manifest> 

    <uses-permission /> 
    <permission /> 
    <permission-tree /> 
    <permission-group /> 
    <instrumentation /> 
    <uses-sdk /> 
    <uses-configuration />   
    <uses-feature />   
    <supports-screens />   

    <application> 

        <activity> 
            <intent-filter> 
                <action /> 
                <category /> 
                <data /> 
            </intent-filter> 
            <meta-data /> 
        </activity> 

        <activity-alias> 
            <intent-filter> . . . </intent-filter> 
            <meta-data /> 
        </activity-alias> 

        <service> 
            <intent-filter> . . . </intent-filter> 
            <meta-data/> 
        </service> 

        <receiver> 
            <intent-filter> . . . </intent-filter> 
            <meta-data /> 
        </receiver> 

        <provider> 
            <grant-uri-permission /> 
            <path-permission /> 
            <meta-data /> 
        </provider> 

        <uses-library /> 

    </application> 

</manifest>


ex) NotePad 예의 AndroidManifest.xml 파일

<manifest xmlns:android="http://schemas.android.com/apk/res/android" 
          package="com.example.android.notepad"> 
    <application android:icon="@drawable/app_notes" 
                 android:label="@string/app_name" > 

        <provider android:name="NotePadProvider" 
                  android:authorities="com.google.provider.NotePad" /> 

        <activity android:name="NotesList" android:label="@string/title_notes_list"> 
            <intent-filter> 
                <action android:name="android.intent.action.MAIN" /> 
                <category android:name="android.intent.category.LAUNCHER" /> 
            </intent-filter> 
            <intent-filter> 
                <action android:name="android.intent.action.VIEW" /> 
                <action android:name="android.intent.action.EDIT" /> 
                <action android:name="android.intent.action.PICK" /> 
                <category android:name="android.intent.category.DEFAULT" /> 
                <data android:mimeType="vnd.android.cursor.dir/vnd.google.note" /> 
            </intent-filter> 
            <intent-filter> 
                <action android:name="android.intent.action.GET_CONTENT" /> 
                <category android:name="android.intent.category.DEFAULT" /> 
                <data android:mimeType="vnd.android.cursor.item/vnd.google.note" /> 
            </intent-filter> 
        </activity> 
         
        <activity android:name="NoteEditor" 
                  android:theme="@android:style/Theme.Light" 
                  android:label="@string/title_note" > 
            <intent-filter android:label="@string/resolve_edit"> 
                <action android:name="android.intent.action.VIEW" /> 
                <action android:name="android.intent.action.EDIT" /> 
                <action android:name="com.android.notepad.action.EDIT_NOTE" /> 
                <category android:name="android.intent.category.DEFAULT" /> 
                <data android:mimeType="vnd.android.cursor.item/vnd.google.note" /> 
            </intent-filter> 
            <intent-filter> 
                <action android:name="android.intent.action.INSERT" /> 
                <category android:name="android.intent.category.DEFAULT" /> 
                <data android:mimeType="vnd.android.cursor.dir/vnd.google.note" /> 
            </intent-filter> 
        </activity> 
         
        <activity android:name="TitleEditor"  
                  android:label="@string/title_edit_title" 
                  android:theme="@android:style/Theme.Dialog"> 
            <intent-filter android:label="@string/resolve_title"> 
                <action android:name="com.android.notepad.action.EDIT_TITLE" /> 
                <category android:name="android.intent.category.DEFAULT" /> 
                <category android:name="android.intent.category.ALTERNATIVE" /> 
                <category android:name="android.intent.category.SELECTED_ALTERNATIVE" /> 
                <data android:mimeType="vnd.android.cursor.item/vnd.google.note" /> 
            </intent-filter> 
        </activity> 
         
    </application> 
</manifest>


Intent의 개념

안드로이드 애플이케이션의 세 개 핵심 컴포넌트-액티비티,서비스,브로드캐스트 리시버-는 인텐트라는 메시지를 통해 실행된다. 인텐트 메시징은 같거나 다른 애플리케이션에 있는 컴포넌트들간의 런타임 바인딩을 제공하는 장치이다.
Intent 클래스의 객체인 intent 자체는 수행할 오퍼레이션의 추상적인 설명을 담고 있는 수동적인 데이터 구조이다.

HTTP의 동사(GET,POST 등)와 자원(URL)의 구조처럼, 안드로이드의 인텐트도 Action + Context 의 형태를 갖는다. 안드로이드가 훨씬 더 많은 action과 context component를 갖고 있지만 그 개념을 똑같다.
웹 브라우저가 동사+URL 구조를 어떻게 처리해야 하는지 아는 것처럼, 안드로이드도 어떻게 주어진 인텐트를 처리할 액티비티를 찾거나  다른 프로그램의 로직을 찾는지 알고 있다.

각 컴포넌트에 대해 인텐트를 전달하는 메카니즘은 다음과 같다:
•액티비티를 새로 시작하거나 기존 액티비티가 새로운 일을 하도록 할 경우, Context.startActivity() 나 Activity.startActivityForResult() 에 인텐트 객체를 전달한다.
•서비스를 시작하거나 실행중인 서비스에 새로운 명령을 전달할 때, Context.startService()에 인텐트 객체를 전달한다. 유사한 형태로, 호출하는 컴포넌트와 대상 서비스간에 연결을 만들 경우, Context.bindService()에 인텐트 객체를 전달한다. 만약 서비스가 실행 중이 아닐 경우 그 서비스를 시작시킬 수도 있다.
•브로드캐스트 메쏘드(Context.sendBroadcast(), Context.sendOrderedBroadcast(), Context.sendStickyBroadcast() 등과 같은)에 전달된 인텐트 객체는 모든 관심 있는 브로드캐스트 리시버에게 전달된다. 대부분의 브로드캐스트는 시스템 코드에서 시작된다.

대상컴포넌트가 지정되어 있는 Explicit intent와 지정된 대상 컴포넌트가 없는 Implicit intent로 구분할 수 있다.

Intent Filter

대상 컴포넌트를 지정하지 않은 인텐트의 경우, 대상 컴포넌트가 될 가능성이 있는 컴포넌트(잠재 컴포넌트)에 대해 실행 대상이 되는지 테스트해야 한다.

이 테스트의 기준으로 정해 놓은 것이 intent filter이다. 필터에는 그 컴포넌트가 수행하는 일이나 처리할 수 있는 인텐트 등이 명시된다. 대상 컴포넌트가 되는지를 테스트할 때는 인텐트 필터의 다음 세 가지 항목을 점검한다:
action, data (both URI and data type), category

extras 나 flags는 아무 역할도 하지 않는다.

인텐트 필터는 IntentFilter 클래스의 인스턴스이다. 하지만, 그 컴포넌트를 실행하기 전에 안드로이드 시스템이 그 능력에 대해 알고 있어야 하기 때문에, 인텐트 필터는 보통 Java 코드에 설정되지 않고 애플리케이션의 매니페이스 파일(AndroidManifest.xml)에 <intent-filter> 요소로 정의된다. (한 가지 예외는 브로드캐스트 리시버인데 이것은 Context.registerReceiver()를 호출함으로써 동적으로 등록되며, 그러면 바로 IntentFilter 객체로 생성된다.)
필터는 인텐트 객체의 action, data, category 필드에 해당하는 필드를 갖고 있다. implicit intent는 이 세 영역에 대해 테스트된다. 필터를 정의한 컴포넌트가 인텐트를 갖고 있는 컴포넌트에게 전달되려면 이 세 가지 테스트를 통과해야 한다.

2010년 11월 1일 월요일

자원요소-위젯

화면에 배치하는 사용자 인터페이스 요소로서 Java의 프레임, 컨트롤 등과 유사하다.
레이아웃 내에 위젯들이 배치되는 형태로 대부분의 안드로이드 프로그램이 구성되며, 이들 요소가 단말기 화면에 나타난다.

TextView

화면에 텍스트를 그린다.

속성은 다음과 같다.
id 텍스트뷰의 이름으로서, 코드요소에서 텍스트뷰를 지칭할 때 이 태그에 지정된 이름을 사용한다.
text 텍스트뷰에 표시될 글자
layout_width
layout_height
lines 높이를 줄 수로 지정
ems 글꼴의 크기와 무관하게 텍스트뷰에 표시될 글자의 수를 지정
autoLink 링크형태로 표시. web,email,phone,map 사용 가능.

EditText

TextView에서 파생된 클래스이다.
사용자로부터 텍스트를 입력받을 수 있다.
TextView의 모든 속성을 상속한다.

유용한 속성들은 다음과 같다.
• hint 사용자가 입력을 시작하기 전에 표시되고 입력을 시작하면 사라진다.
• completionHint: 자동완성기능
• completionThreshold: 자동완성기능. 몇 개 글자가 표시되었을 때 목록을 표시할 것인지 지정.

Button
화면에 버튼 형태로 표시되는 컨트롤로, 클릭 후 액션을 수행하도록 하는데 기본적으로 사용된다.

CheckBox
체크 컨트롤을 보여줌. Java의 체크박스와 유사.

ex) 기본 XML 파일 및 Java 파일

<?xml version="1.0" encoding="utf-8"?>
<CheckBox xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/check"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="This checkbox is: unchecked" />


public class CheckBoxDemo extends Activity
implements CompoundButton.OnCheckedChangeListener {
CheckBox cb;
@Override
public void onCreate(Bundle icicle) {
  super.onCreate(icicle);
  setContentView(R.layout.main);
  cb=(CheckBox)findViewById(R.id.check);
  cb.setOnCheckedChangeListener(this);
}
public void onCheckedChanged(CompoundButton buttonView,
  boolean isChecked) {
  if (isChecked) {
  cb.setText("This checkbox is: checked");
  }
  else {
  cb.setText("This checkbox is: unchecked");
  }
}
}

RadioButton
체크 컨트롤을 보여줌. Java의 체크박스와 유사.

ex) 라디오 그룹 정의 XML 코드

<?xml version="1.0" encoding="utf-8"?>
<RadioGroup
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
<RadioButton android:id="@+id/radio1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Rock" />
<RadioButton android:id="@+id/radio2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Scissors" />
<RadioButton android:id="@+id/radio3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Paper" />
</RadioGroup>

Spinner
지정된 여러 항목 중에서 하나를 선택하도록 한다.

ex) ArrayAdapter를 이용하여 Spinner에 데이터 표시하기

//spinner.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">
 
    <TextView
android:id="@+id/selection"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
/>
<Spinner android:id="@+id/spinner"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:drawSelectorOnTop="true"
/>

</LinearLayout>

//SpinnerDemo.java
import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.widget.ArrayAdapter;
import android.widget.Spinner;
import android.widget.TextView;
import android.widget.AdapterView;

public class SpinnerDemo extends Activity implements AdapterView.OnItemSelectedListener {
TextView selection;
String[] items={"lorem", "ipsum", "dolor", "sit", "amet",
"consectetuer", "adipiscing", "elit", "morbi", "vel",
"ligula", "vitae", "arcu", "aliquet", "mollis",
"etiam", "vel", "erat", "placerat", "ante",
"porttitor", "sodales", "pellentesque", "augue", "purus"};

@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
setContentView(R.layout.spinner);

selection=(TextView)findViewById(R.id.selection);
Spinner spin = (Spinner) findViewById(R.id.spinner);

spin.setOnItemSelectedListener(this);
ArrayAdapter<String> aa=new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1,items);
aa.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
spin.setAdapter(aa);
}
public void onItemSelected(AdapterView parent, View v,
int position, long id) {
selection.setText(items[position]);
}
public void onNothingSelected(AdapterView parent) {
selection.setText("");
}
}



ToggleButton
On/Off 스위치

DatePicker/TimePicker
날자/시간 선택

ProgressBar
진행상태 표시

SeekBar
사용자지정 진행상태 표시

RatingBar
별을 이용한 등급표시

Chronometer
시간흐름 표시

AnalogClock
아날로그 시계 표시

DigitalClock
디지털 시계 표시

ImageView/ImageButton

TextView, Button의 이미지용 컨트롤이라고 보면 된다.
xml에서 참조할 때는 android:src 속성을 사용한다.

ex) ImageView를 적용한 XML 파일

<?xml version="1.0" encoding="utf-8"?>
<ImageView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/icon"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:adjustViewBounds="true"
android:src="@drawable/molecule"
/>