Transcript XML - Daum

커스텀 위젯
안드로이드 프로그래밍 정복(Android Programming Complete
Guide)
Contents
 학습목표
 기존 위젯을 변경하여 실제 프로젝트에 바로 사용할 수 있는 커스텀 위젯
을 제작하는 방법을 익힌다.
 위젯이 상위의 뷰 그룹과 통신하는 방법을 연구한다.
 커스텀 속성을 통해 위젯의 활용성을 높이는 방법을 학습한다.
 내용
 기존 위젯 변형
 새로운 위젯
 여러 가지 뷰
2/40
1. 기존 위젯 변형
 위젯 수정
 모바일 장비는 통신 수단이며 일종의 액세서리이므로 디자인 역시 중요하다.
 디자인을 결정하는 주요 요소는 위젯이며, 기능적인 완벽함과 효율만큼 예쁜 외관과 동
작의 참신함이 요구되고, 사용자와의 상호 작용이 직관적이면서도 독창적이어야 한다.
 기능적으로 집합을 표시한다는 면에서 동일하지만, 항목을 표시하는 방법은 다르다.
 아드로이드 표준 위젯은 디자인이 너무 사무적이며, 무난한 모양을 지향하고, 응용 프로
그램들의 특수한 목적을 충족시키기에 기능이 너무 일반적이다.
 이에 안드로이드는 여러 가지 방법으로 커스텀 위젯 제작을 지원한다.
• 기존 위젯 클래스를 상속받아 기능을 확장하거나 수정한다.
- 전통적인 클래스 상속 기법이 그대로 적용.
- 대부분의 기능을 슈퍼 클래스에서 빌려 쓰고 필요한 부분만 수정 가능.
• 기존 위젯들을 결합하여 복잡한 동작을 수행하는 위젯 그룹을 정의한다.
- ViewGroup 또는 그 파생 클래스를 확장하여 만든다.
- 그룹 내부 위젯끼리의 상호 작용까지 모두 정의해야 한다.
• 기존의 존재하지 않는 새로운 위젯을 만든다.
- 기능이나 모양이 특수하여 기존 위젯으로 사용이 불가능할 때 사용하는 방법이다.
- 최상위 위젯 클래스인 View로부터 상속 받으며 난이도가 높다.
※ 만들고자 하는 위젯의 기능과 목적에 따라 적절한 방법을 선택해야 하며, 방법에 따라 확장 대상
클래스가 달라진다.
3/40
1. 기존 위젯 변형
 SoundEdit 예제
• 기존 컨트롤 수정 방법이며, EditText 클
래스를 확장하여 키 입력시마다 소리를
내는 입력 위젯을 만든다.
• 커스텀 위젯 클래스는 별도의 소스 파일
에 따로 작성는 것이 관리에 편리하다.
• EditText 클래스를 확장하여
SoundEditWidget 클래스를 파생 시켰으
며, 생성자는 세 벌을 모두 정의하였다.
- XML에서 전개 시 2개의 인수를 취하는 생
성자가 호출
- new 연산자로 생성 시 context 인수만 취하는
첫 번째 생성자가 호출된다.
• 범용성을 높이기 위해서는 모든 생성자
를 다 구비하는 것이 바람직하다.
- 예제에는 두 번째 생성자만 사용됨.
Widget/SoundEdit.java
public class SoundEdit extends Activity {
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.soundedit);
}
}
class SoundEditWidget extends EditText {
SoundPool mPool = null;
int mClick;
public SoundEditWidget(Context context) {
super(context);
init(context);
}
public SoundEditWidget(Context context, AttributeSet attrs) {
super(context, attrs);
init(context);
}
public SoundEditWidget(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init(context);
}
void init(Context context) {
mPool = new SoundPool(1, AudioManager.STREAM_MUSIC, 0);
mClick = mPool.load(context, R.raw.click, 1);
}
protected void onTextChanged(CharSequence text,int start,int before,int after) {
if (mPool != null) {
mPool.play(mClick, 1, 1, 0, 0, 1);
}
}
}
4/40
1. 기존 위젯 변형
 파생 클래스의 생성자는 일단 슈퍼 클래스의 생성자를 호출하여 상속받은 멤버를 먼저
초기화해야 한다.
• 세 생성자 모두 super 호출문이 제일 앞에 오고 전달받은 모든 인수를 super로 넘겨준다.
 그 후 고유의 초기화 작업을 하며, 매 생성자마다 중복된 코드를 두는 것은 바람직하지
않다.
• init 초기화 메서드를 정의하고, 각 생성자는 이 메서드를 호출하여 고유의 초기화를 수행하는 것
이 일반적이다.
5/40
1. 기존 위젯 변형
 커스텀 위젯은 하나의 클래스이므로 동작에 필요한 객체나 상수들을 내부 멤버로 선언
한다.
• 예제 위젯은 소리를 내야 하므로 SoundPool 객체와 사운드 ID를 멤버로 선언, init 메서드에서 초
기화한다.
• SoundPool 객체 생성 후 mClick에 ID 대입, 사운드 파일은 res/raw 폴더에 mp3 포맷으로 복사해
둔다.
 텍스트가 변경 시 onTextChanged 메서드가 호출된다.
• 문자 입력 시점이므로 onKeyDown 메서드를 재정의.
• 이 메서드는 키패드의 키를 누를때만 호출, 화면 키보드에 대해서는 호출되지 않는다.
 에디트가 생성될 때 빈 문자열로 초기화되며 이때도 onTextChanged 메서드가 호출된다.
• 이 시점은 mPool 객체가 아직 초기화되기 전이므로 객체가 null인지 확인해야 한다.
 커스텀 위젯을 사용하는 방법도 표준 위젯과 동일하다.
• new 연산자로 실행 중 직접 생성, 레이아웃에 추가 가능하며 XML 파일에 엘리먼트로 배치 가능
하다.
• 각 경우 호출되는 생성자가 다르며, 대응되는 생성자가 정의되어 있어야 한다.
6/40
1. 기존 위젯 변형
 SoundEdit 예제
• 리니어 안에 안내 텍스트 뷰 하나와 커스텀 사운드 에디트를 배치한다.
• XML 파일에 커스텀 위젯을 배치 시 엘리먼트 이름에 위젯의 클래스 명을 적되 커스텀 위젯은 풀
패키지 명을 다 적어야 한다.
• SoundEditWidget도 EditText의 파생 클래스이며 View의 파생 클래스이므로 상위 클래스가 제공
하는 모든 속성 사용이 가능하다.
- onKeyDown : 16진수 입력 조절 가능
- onDraw : 배경 이미지 삽입 가능
Widget/soundedit.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“ >
<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text=“문자를 입력하시면 소리가 납니다.” />
<exam.Widget.SoundEditWidget
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:textSize=“12pt” />
</LinearLayout>
[ SoundEdit 예제 실행 결과 ]
7/40
1. 기존 위젯 변형
 위젯 조합
 개별 위젯들의 기능은 원자적이고 단순하여 복잡한 특정 작업 수행 시 여러 위젯들이 상
호 협력적으로 동작해야 한다.
 관련 위젯들을 하나의 그룹으로 묶어 새로운 위젯으로 정의 가능하다.
• 유기적으로 동작하는 위젯 그룹을 하나의 단위로 묶음으로써 통합성과 재사용성이 좋아진다.
 NumEdit 예제
• 에디트와 텍스트 뷰를 포함하는 수직 리니어 위젯을 정의.
• 에디트는 자신의 길이를 텍스트 뷰에 출력하고, 텍스트 뷰는 에디트의 문자열을 참조하므로 이 둘
은 긴밀히 연관된 하나의 묶음이며 항상 같이 사용된다.
• 레이아웃에는 NumEditWidget 하나만 배치한다.
Widget/numedit.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent” >
<exam.Widget.NumEditWidget
android:layout_width="fill_parent"
android:layout_height="wrap_content” />
</LinearLayout>
[ NumEdit 예제 실행 결과 ]
8/40
1. 기존 위젯 변형
• NumEditWidget 클래스는
LinearLayout을 상속 받아 차일드를 일
렬로 배치 가능하다.
• 클래스의 멤버로 mEdit, mText를 선언
하고 init에서 두 객체를 생성하여 리니
어에 추가한다.
• 두 위젯의 상호 동작을 정의한다.
- 에디트 내용 변경 시 문자열의 길이를 텍스
트 뷰에 출력.
- 위젯 자신이 TextWatcher 리스너 구현,
mEdit 텍스트의 변경 리스너를 this로 지정.
- 리스너의 onTextChanged 메서드에서 문자
열 변경 시 길이를 새로 조사하여 텍스트 뷰에
출력.
• 문자열 변경 시점을 구한다는 점에서
이전 예제와 비슷.
- SoundEdit 예제 : TextView의 메서드
- NumEdit 예제 : TextWatcher 인터페이스의
메서드
• EditText를 멤버로 포함할 뿐이므로
onTextChanged 메서드를 재정의할 수
없다.
• 와처를 등록, 이를 통해 문자열 변경 시
점을 알아내야 한다.
Widget/NumEdit.java
public class NumEdit extends Activity {
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.numedit);
}
}
class NumEditWidget extends LinearLayout implements TextWatcher {
EditText mEdit;
TextView mText;
public NumEditWidget(Context context) {
super(context);
init();
}
public NumEditWidget(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
void init() {
setOrientation(LinearLayout.VERTICAL);
mEdit = new EditText(getContext());
mText = new TextView(getContext());
mText.setText("Now Length : 0 Characters");
LinearLayout.LayoutParams param = new LinearLayout.LayoutParams(
LayoutParams.FILL_PARENT, LayoutParams.WRAP_CONTENT);
addView(mEdit, param);
addView(mText, param);
mEdit.addTextChangedListener(this);
}
public void afterTextChanged(Editable s) {
}
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
public void onTextChanged(CharSequence s, int start, int before, int count) {
mText.setText("Now Length : " + s.length() + " Characters")
}
}
9/40
1. 기존 위젯 변형
 세 개 이상의 위젯을 묶어 복잡한 기능 수행이 가능하며, RelativeLayout, TableLayout을
확장하여원하는 모양으로 배치할 수 있다.
 합쳐진 그룹 위젯에 대해 여백이나 마진, 배경색 등 조정 가능하며, 커스텀 속성을 통해
내부 위젯의 속성 역시 조정 가능하다.
 포함되는 위젯의 개수가 많고 배치가 복잡하면 코드에서 생성, 배치가 번거로우므로
XML 문서로 뷰 그룹을 디자인하여 배치와 속성을 지정해 놓고 코드에서 레이아웃을 전
개하여 배치하면 된다.
 numeditwidget 예제
Widget/numeditwidget.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” >
<EditText
android:id="@+id/limedit_edit"
android:layout_width="fill_parent"
android:layout_height="wrap_content” />
<TextView
android:id="@+id/limedit_text"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="Now Length : 0 Characters” />
</LinearLayout>
void init() {
LayoutInflater inflater =
(LayoutInflater)getContext().getSystemService(
Context.LAYOUT_INFLATER_SERVICE);
inflater.inflate(R.layout.numeditwidget, this, true);
mEdit = (EditText)findViewById(R.id.limedit_edit);
mText = (TextView)findViewById(R.id.limedit_text);
....
10/40
1. 기존 위젯 변형
 커스텀 속성
 안드로이드에서 레이아웃을 배치 시 주로 XML 문서를 활용한다.
 XML 문서에 배치할 위젯의 클래스 명을 엘리먼트로 작성, 위젯의 속성을 엘리먼트의
속성으로 작성하며, 규모가 큰 레이아웃도 문서 작업만으로 생성 가능하다.
 XML 문서는 aapt 툴에 의해 이진 형태로 컴파일되어 실행 파일에 포함, 전개자
(Inflater)가 정보를 읽고 각 위젯을 생성한다.
 지정된 속성은 생성자의 두 번째 인수인 AttributeSet 객체 형태로 생성자에게 전달, 이
객체안에 속성의 집합이 저장되어 있다.
 클래스의 주요 메서드는 아래와 같다.
int getAttributeCount ()
String getAttributeName (int index)
String getAttributeValue (int index)
int getAttributeIntValue (int index, int defaultValue)
boolean getAttributeBooleanValue (int index, boolean defaultValue)
float getAttributeFloatValue (int index, float defaultValue)
• 속성의 개수, 이름과 값을 조사한다.
• 속성들은 배열 형태로 저장되어 있으므로 첨자로 읽는다.
• 속성의 타입을 정확히 알고 있는 경우 정수형, 실수형, 진위형 등의 타입으로 바로 읽을 수 있으며
속성이 생략된 경우의 디폴트 값 지정이 가능하다.
11/40
1. 기존 위젯 변형
 attribute 예제
• 리니어 안에 버튼 위젯을 배치, 각 속성을 적용하고 Text 속성은 string.xml에 정의한 후 참조로 지
정한다.
• 각 속성들은 aapt에 의해 컴파일되며 전개자가 Button 객체를 생성할 때 생성자로 전달한다.
• attrButton은 Button에서 파생, 생성자에서 attrs 인수를 확인할 뿐 표준 버튼과 동작이 동일.
• 전개자에 의해 생성되므로 두 번째 생성자만 정의.
Widget/attribute.xml
Widget/Attribute.java
<?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">
<exam.Widget.AttrButton
android:id="@+id/attrbtn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/attrbtn"
android:textSize="15pt"
android:textColor="#ff0000"/>
<EditText
android:id="@+id/attredit"
android:layout_width="fill_parent"
android:layout_height="wrap_content"/>
</LinearLayout>
public class Attribute extends Activity {
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.attribute);
AttrButton btn = (AttrButton)findViewById(R.id.attrbtn);
EditText edit = (EditText)findViewById(R.id.attredit);
edit.setText(btn.mText);
}
}
class AttrButton extends Button {
String mText = "";
public AttrButton(Context context, AttributeSet attrs) {
super(context, attrs);
int i;
String Name;
String Value;
for (i=0;i<attrs.getAttributeCount();i++) {
Name = attrs.getAttributeName(i);
Value = attrs.getAttributeValue(i);
mText += (Name + " = " + Value + "\n");
}
}
}
12/40
1. 기존 위젯 변형
• 생성자 코드는 간단하며 attrs 배열을 전부 순회, 속성의 이름과 값을 조사하고 조사된 속성값을
mText 문자열에 누적시켜 둔다.
• 생성자에서 출력이 불가능하므로 문자열 변수에 조사된 결과를 저장해 두고 생성 완료 후 이 변
수를 읽는다.
• onCreate에서 버튼과 에디트 생성 후 버튼의 mText를 읽어 아래의 에디트에 출력한다.
• 버튼에 지정된 속성들이 문자열로 바뀌어 에디트에
출력된다.
• 속성값을 버튼 위젯에 실제로 적용하는 주체는
super의 생성자이며 Button의 생성자를 거쳐
TextView의 생성자로 전달, 위젯에 설정된다.
• layout으로 시작되는 레이아웃 파라미터는 객체가
완전히 초기화 된 후 부모 뷰에 추가될 때 적용된다.
[ Attribute 예제 실행 결과 ]
 AttributeSet 객체는 XML 문서에 지정된 모든 속성을 포함하지만 문서상에 기록된 값을
액면 그대로 가지고 있으므로 직접 사용하기에 불편하다.
• 문자열의 경우 strings.xml에 대한 참조로만 나타나므로 실제 문자열 조사를 위해서 리소스를 확
인해야 한다.
• 객체를 직접 사용하지 않고 메서드로 속성 집합을 한번 더 가공한다.
TypedArray obtainStyledAttributes (AttributeSet set, int[] attrs)
• Resource.Theme 클래스에 정의, Context가 래퍼를 제공하므로 컨텍스트로부터 바로 호출이 가능
하며, 참조 리소스는 물론이고 스타일 지정된 값 역시 찾아주므로 최종적으로 적용할 값을 바로
구할 수 있다.
13/40
1. 기존 위젯 변형
 사운드 속성
 커스텀 위젯도 자신만의 속성을 정의하여 사용 가능하다.
 SoundEdit 2 예제
• SoundEdit 예제를 확장, 소리의 종류, 볼륨, 재생 속도 등을 속성으로 제공한다.
• 커스텀 위젯의 속성은 attrs.xml에 정의하고, res/values 폴더 안에 파일을 작성한다.
(파일명은 사용자가 원하는 대로 지정 가능하다.)
Widget/values/attrs.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="SoundEditWidget2">
<attr name="sound" format="integer" />
<attr name="volume" format="float" />
<attr name="speed" format="float" />
</declare-styleable>
</resources>
public static final class styleable {
public static final int[] SoundEditWidget2 = {
0x7f010000, 0x7f010001, 0x7f010002
};
public static final int SoundEditWidget2_sound = 0;
public static final int SoundEditWidget2_speed = 2;
public static final int SoundEditWidget2_volume = 1;
};
• declare-styleable 엘리먼트의 name 속성에 위젯의 이름 작성.
• attr 엘리먼트의 name에 속성의 이름, format에 속성의 타입 지정.
- ingeger, float, string, dimension, color 등의 타입 지정
- enum 열거형으로 속성값에 개별 이름 설정 가능.
• 문서에 속성을 정의해 놓으면 R.java에 styleable 클래스가 자동으로 생성된다.
• 클래스 명과 같은 이름으로 속성의 배열이 작성되며 각 속성의 첨자가 0부터 순서대로 부여된다.
• 생성자에서는 이 값들을 참조하여 XML 문서의 속성을 읽는다.
14/40
1. 기존 위젯 변형
• 커스텀 속성에 대한 네임 스페이스는 표준과 유사하되 마지막 부분을 프로젝트의 패키지명으로
수정하여 사용한다.
• 세 개의 커스텀 위젯을 배치하되 하나는 sound를 2로 지정했고, 하나는 속도와 볼륨을 절반으로
줄였다.
Widget/soundedit2.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:custom="http://schemas.android.com/apk/res/exam.Widget"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
<exam.Widget.SoundEditWidget2
android:layout_width="fill_parent"
android:layout_height="wrap_content"
/>
<exam.Widget.SoundEditWidget2
android:layout_width="fill_parent"
android:layout_height="wrap_content"
custom:sound="2"
/>
<exam.Widget.SoundEditWidget2
android:layout_width="fill_parent"
android:layout_height="wrap_content"
custom:sound="2"
custom:speed="0.5"
custom:volume="0.5"
/>
</LinearLayout>
[ SoundEdidt2 예제 실행 결과 ]
15/40
1. 기존 위젯 변형
• 생성자는 attrs 배열을 init 메서드로 전달하되 new 연산자로 직접 생성 시 null이 전달되며, init 메
서드는 전개자로부터 호출 시 attrs에 저장된 속성값을 읽어 위젯에 적용한다.
• obtainStyledAttributes 메서드로 R.java에 정의된 속성의 배열 전달 시 TypedArray 객체로 리턴된
다.
Widget/soundedit2.java - 1
Widget/soundedit2.java - 2
public class SoundEdit2 extends Activity {
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.soundedit2);
}
}
void init(Context context, AttributeSet attrs) {
mPool = new SoundPool(1, AudioManager.STREAM_MUSIC, 0);
mClick1 = mPool.load(context, R.raw.click, 1);
mClick2 = mPool.load(context, R.raw.click2, 1);
mSound = mClick1;
if (attrs != null) {
TypedArray ar = context.obtainStyledAttributes(attrs,
R.styleable.SoundEditWidget2);
mVolume = ar.getFloat(R.styleable.SoundEditWidget2_volume,
1.0f);
mSpeed = ar.getFloat(R.styleable.SoundEditWidget2_speed,
1.0f);
mSound = ar.getInt(R.styleable.SoundEditWidget2_sound,
mClick1);
ar.recycle();
}
}
class SoundEditWidget2 extends EditText {
SoundPool mPool = null;
int mClick1, mClick2;
int mSound;
float mVolume = 1.0f;
float mSpeed = 1.0f;
public SoundEditWidget2(Context context) {
super(context);
init(context, null);
}
protected void onTextChanged(CharSequence text, int start, int before,
int after) {
if (mPool != null) {
mPool.play(mSound, mVolume, mVolume, 0, 0, mSpeed);
}
}
public SoundEditWidget2(Context context, AttributeSet attrs) {
super(context, attrs);
init(context, attrs);
}
public SoundEditWidget2(Context context, AttributeSet attrs,
int defStyle) {
super(context, attrs, defStyle);
init(context, attrs);
}
}
16/40
2. 새로운 위젯
 크기 정하기
 완전히 새로운 위젯을 만들 때는 최상위 클래스인 View로부터 상속받는다.
 View는 화면에 모습을 그리기 위한 메서드들과 사용자와 통신하기 위한 이벤트 핸들러
의 기본 원형을 제공한다.
 상속받은 뷰의 기능은 일반적이라 몇 개의 메서드를 반드시 재정의해야 한다.
• onDraw
- 뷰는 화면에 아무것도 그리지 않으므로 선택의 여지없이 재정의해야 한다.
(재정의하지 않으면 화면에 아무 것도 나타나지 않는다.)
• onMeasure
- 부모 레이아웃은 차일드를 배치하기 위해 각 차일드의 크기를 조사하며 이때 차일드의 measure 메서드를 호출
한다.
- measure는 강제 레이아웃, 크기 변경 빈도 최소화, 치명적인 에러 처리 등 중요한 역할을 담당하므로 직접 재정
의하는 것은 바람직하지 않다.
- measure는 크기 결정 시 onMeasure를 호출하므로 onMeasure에서 위젯의 크기를 결정한다.
void onMeasure (int widthMeasureSpec, int heightMeasureSpec)
- 인수로 전달된 *Spec은 부모 레이아웃이 차일드에게 제공하는 여유 공간의 폭과 높이에 대한 정보이며 이 안에
공간의 성질을 지정하는 모드와 공간의 크기값이 저장되어 있다.
- 모드와 크기를 별도의 인수로 전달 시 하나의 정수에 두 값을 묶어서 전달한다.
17/40
2. 새로운 위젯
• 정수에서 두 값을 추출하거나 다시 합칠 때는 View.MeasureSpec 클래스의 메서드를 사용한다.
int getMode (int measureSpec)
int getSize (int measureSpec)
int makeMeasureSpec (int size, int mode)
• 스펙의 상위 2비트에 모드가 저장, 나머지 30비트가 실제 크기값이며 이들을 비트 연산하여 분리
및 조합하는 메서드이다.
• 모드는 전달된 크기가 어떤 의미를 가지는지를 지정한다.
모드
설명
AT_MOST
차일드가 가질 수 있는 최대 크기이다.
이 값 이하로 크기를 결정해야 하며 이 크기 범위를 넘어서는 안된다.
EXACTLY
차일드가 가져야 하는 정확한 크기이다.
특별한 사유가 없는 한 차일드는 이 크기를 가져야 한다.
UNSPECIFIED
별다른 제한이 없으므로 원하는 크기를 지정하면 된다.
- AT_MOST가 전달된 경우
: 부모가 위젯에게 허용된 최대한의 크기를 알려주며, 차일드는 자신이 원하는 폭과 부모가 허락한 폭의 최소값을
취해야 한다.
- EXACTLY가 전달된 경우
: 부모가 제안한 폭을 사용한다.
- UNSPECIFIED가 전달된 경우
: 아무 제약이 없으므로 자신이 원하는 대로 폭을 차지해도 무방하다.
18/40
2. 새로운 위젯
• 레이아웃을 배치할 때 부모와 차일드는 메서드를 통해 대화하고 타협한다.
- 부모 : 차일드의 OnMeasure 메서드를 호출하되 남은 여유 공간에 대한 정보를 스펙 인수로 전달
- 차일드 : 전달받는 정보를 참조하여 적절한 크기를 부모에게 응답한다.
• onMeasure에서 부모로 응답을 보낼 때는 아래의 메서드로 원하는 크기를 리턴한다.
void setMeasuredDimension (int measuredWidth, int measuredHeight)
- 두 인수는 차일드가 원하는 폭과 높이이다.
- 메서드가 두 값을 동시에 리턴할 수 없기 때문에 OnMeasure는 형식적으로 리턴값이 없는 것으로 선언되어 있
지만 실제로는 이 메서드를 통해 두 값을 리턴해야 한다.
- 만약 이 OnMeasure가 메서드를 호출하지 않고 리턴해 버리면 부모가 레이아웃 중에 IllegalStateException 예외
를 일으키고 다운되어 버린다.
- 차일드가 크기를 밝히지 않으면 배치가 불가능하다.
※ OnMeasure에서 크기를 결정하는 방법은 절대적인 공식이 따로 없고 응용도 가능하여 다소 복잡
하고 이론만으로 이해하기 어려우므로 실습이 꼭 필요하다.
19/40
2. 새로운 위젯
 Measuring 예제 소스 코드
Widget/Measuring.java - 1
public class Measuring extends Activity {
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.measuring);
final MeasView meas = (MeasView)findViewById(R.id.meas);
final EditText text = (EditText)findViewById(R.id.text);
text.postDelayed(new Runnable() {
public void run() {
text.setText(meas.mResult);
}
}, 1000);
}
}
class MeasView extends View {
String mResult = "";
public MeasView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
public MeasView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public MeasView(Context context) {
super(context);
}
protected void onDraw(Canvas canvas) {
canvas.drawColor(Color.RED);
}
Widget/Measuring.java - 2
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int wMode, hMode;
int wSpec, hSpec;
int Width, Height;
Width = 150;
Height = 80;
wMode = MeasureSpec.getMode(widthMeasureSpec);
wSpec = MeasureSpec.getSize(widthMeasureSpec);
hMode = MeasureSpec.getMode(heightMeasureSpec);
hSpec = MeasureSpec.getSize(heightMeasureSpec);
switch (wMode) {
case MeasureSpec.AT_MOST:
Width = Math.min(wSpec, Width);
break;
case MeasureSpec.EXACTLY:
Width = wSpec;
break;
case MeasureSpec.UNSPECIFIED:
break;
}
switch (hMode) {
case MeasureSpec.AT_MOST:
Height = Math.min(hSpec, Height);
break;
case MeasureSpec.EXACTLY:
Height = hSpec;;
break;
case MeasureSpec.UNSPECIFIED:
break;
}
20/40
2. 새로운 위젯
 Measuring 예제 소스 코드
Widget/Measuring.java - 3
/*
if (wMode == MeasureSpec.AT_MOST && hMode ==
MeasureSpec.AT_MOST) {
Width = Height = Math.min(Width, Height);
}
//*/
• MeasView는 View로부터 상속받으며 생성
자는 디폴트로만 정의.
• onDraw는 빨간색으로 전체를 가득 채워
화면에 존재만 표시.
• onMeasure에는 위젯의 크기를 결정하는
코드가 작성.
setMeasuredDimension(Width, Height);
mResult += (Spec2String(widthMeasureSpec) + ", "
+ Spec2String(heightMeasureSpec) +
" -> (" + Width + "," + Height + ")\n");
- Width, Height는 150과 80으로 각각 상수 초기
화되며 위젯의 내용물에 근거하여 계산됨.
}
- 텍스트 뷰라면 문자열의 길이에 맞게, 이미지
뷰라면 비트맵의 크기에 맞게 크기가 결정됨.
String Spec2String(int Spec) {
String str = "";
- MeasView는 사실상 내용물이 없으므로 임의의
크기를 가정한 것이다.
switch (MeasureSpec.getMode(Spec)) {
case MeasureSpec.AT_MOST:
str = "AT_MOST";
break;
case MeasureSpec.EXACTLY:
str = "EXACTLY";
break;
default:
str = "UNSPECIFIED";
break;
}
• 위젯이 스스로 크기를 결정했다 해도 주변
의 형제 위젯들과 부모의 영역을 나누어
가져야 하므로 항상 설정된 크기대로 배치
되지 않는다.
• 코드는 하나의 예일 뿐이며 위젯의 특성에
따라 고유한 크기 계산 알고리즘을 적용할
수 있다.
• 최종 결정된 Width, Height를
setMeasuredDimension으로 전달한다.
str += " " + MeasureSpec.getSize(Spec);
return str;
}
}
21/40
2. 새로운 위젯
• 수직 리니어 안에 수평 리니어와 에디트 배치하였으며, 에디트는 배치 결과를 문자열로 확인하기
위한 장치이다.
• 수평 리니어는 높이 100 픽셀의 노란색 배경이 지정되어 있으며, 이 안에 폭 100 픽셀의 버튼을
양쪽에 배치, 중간에 MeasView를 끼워넣었다.
Widget/measuring.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:orientation="horizontal"
android:layout_width="fill_parent"
android:layout_height="100px"
android:background="#ffff00">
<Button
android:layout_width="100px"
android:layout_height="wrap_content"
android:text="Left"/>
<exam.Widget.MeasView
android:id="@+id/meas"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<Button
android:layout_width="100px"
android:layout_height="wrap_content"
android:text="Right"/>
</LinearLayout>
<EditText
android:id="@+id/text"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:textSize="7pt"/>
</LinearLayout>
[ Measuring 예제 실행 결과 ]
[ Measuring 예제 실행 결과 ]
22/40
2. 새로운 위젯
 measuring 2 예제
• MeasView의 폭과 높이를 wrap_content에서 100px, 50px로 강제 지정한다.
 measuring 3 예제
• 양쪽 버튼 2개를 없애고 MeasView만 남겨두되 폭과 높이를 fill_parent로 지정한다.
 measuring 4 예제
• 버튼을 남겨두고 MeasView의 폭과 높이를 fill_parent로 지정한다.
Widget/measuring3.xml
Widget/measuring2.xml
<exam.Widget.MeasView
android:id="@+id/meas"
android:layout_width="100px"
android:layout_height="50px”/>
[ Measuring 2 예제 실행 결과 ]
<LinearLayout
android:orientation="horizontal"
android:layout_width="fill_parent"
android:layout_height="100px"
android:background="#ffff00">
<exam.Widget.MeasView
android:id="@+id/meas"
android:layout_width="fill_parent"
android:layout_height="fill_parent"/>
[ Measuring 3 예제 실행 결과 ]
[ Measuring 4 예제 실행 결과 ]
23/40
2. 새로운 위젯
 부모가 차일드에게 제안하는 스펙은 어디까지나 크기 결정을 위한 힌트일 뿐이며, 위젯
이 자신의 필요에 따라 부모의 제안을 거절할 수 있고 일부 조정할 수도 있다.
• 최초의 예제 Measuring에서 주석 처리된 부분을 주석 해제한 후 실행한다.
if (wMode == MeasureSpec.AT_MOST && hMode == MeasureSpec.AT_MOST) {
Width = Height = Math.min(Width, Height);
}
• 시계처럼 정원을 그려야 한다거나 증명사진처럼 일정한 종횡비를 지켜야 한다면 부모가 제안한
크기를 거부하고 규칙에 맞게 강제로 조정한 크기를 리턴하면 된다.
• 부모는 220*100만큼의 공간을 제안했지만 차일드
는 높이를 내용물에 맞추어 80으로 강제 조정했다.
• 이 경우는 내용물을 기준으로 종횡비를 맞추었으므
로 크기가 작은데 부모가 제안한 영역에 맞춘다면
(100,100)이 되어 초대한 넓은 면적을 차지할 수도
있다.
• EXACTLY인 경우는 종횡비를 맞추지 않도록 구현
했지만 필요하다면 여백으로 나머지를 채우더라도
내용물의 종횡비를 유지할 수 있다.
[ Measuring 예제 실행 결과 ]
24/40
2. 새로운 위젯
 차일드가 부모의 제안을 거부할 수 있듯 부모도 차일드의 요청을 다 들어주지 못하는 경
우가 있다.
 레이아웃이 복잡하고 차일드 수가 많아져 한번의 대화로 완벽한 배치를 만들어 내기 어
려우면 여러 차일드에게 반복적으로 질문을 날려 모두가 OK할 때까지 조정을 반복.
 measuring 5 예제
• 세 위젯 모두 layout_weight 속성을 지정하여 폭을 나누어 가지도록 하였다.
Widget/measuring5.xml
<LinearLayout
android:orientation="horizontal"
android:layout_width="fill_parent"
android:layout_height="100px"
android:background="#ffff00">
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Left"
android:layout_weight="1"/>
<exam.Widget.MeasView
android:id="@+id/meas"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Right"
android:layout_weight="1"/>
[ Measuring 5 예제 실행 결과 ]
25/40
2. 새로운 위젯
 무지개 프로그래스
 무지개색으로 작업의 진행 경과를 보여주는 수직 프로그래스 바 위젯이다.
 기존 클래스를 상속받거나 조합해서는 만들 수 없으며 최상위의 View를 상속받아 처음
부터 다시 만들어야 한다.
 표준 프로그래스 바는 수평만 가능하므로 상속받아도 수직으로는 불가능하다.
 RainbowTest 예제
Widget/RainbowTest.java - 2
mHandler = new Handler() {
public void handleMessage(Message msg) {
int Pos;
Pos = mProgress.getPos();
if (Pos < mProgress.getMax()) {
mProgress.setPos(Pos+1);
mHandler.sendEmptyMessageDelayed(0,100);
} else {
Toast.makeText(RainbowTest.this, "Completed",
0).show();
mProgress.setPos(0);
}
}
};
Widget/RainbowTest.java - 1
public class RainbowTest extends Activity {
RainbowProgress mProgress;
Handler mHandler;
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.rainbowtest);
mProgress = (RainbowProgress)findViewById(R.id.progress);
Button btn = (Button)findViewById(R.id.start);
btn.setOnClickListener(new Button.OnClickListener() {
public void onClick(View v) {
if (mProgress.getPos() == 0) {
mProgress.setPos(0);
mHandler.sendEmptyMessage(0);
}
}
});
}
}
class RainbowProgress extends View {
int mMax;
int mPos;
int mProgHeight;
LinearGradient mShader;
26/40
2. 새로운 위젯
 RainbowTest 예제
Widget/RainbowTest.java - 3
public RainbowProgress(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init(); }
public RainbowProgress(Context context, AttributeSet attrs) {
super(context, attrs);
init(); }
public RainbowProgress(Context context) {
super(context);
init(); }
Widget/RainbowTest.java – 4
int getPos() { return mPos; }
protected void onDraw(Canvas canvas) {
if (mShader == null) {
mProgHeight = getHeight() - getPaddingTop()
– getPaddingBottom();
int[] colors = { Color.RED, Color.YELLOW, Color.GREEN,
Color.BLUE };
mShader = new LinearGradient(0,0,0,mProgHeight,
colors, null, TileMode.CLAMP);
}
void init() {
mMax = 100;
mPos = 0; }
RectF rt = new RectF();
rt.left = getPaddingLeft();
rt.right = getWidth() - getPaddingRight();
rt.bottom = getPaddingTop() + mProgHeight;
rt.top = rt.bottom - mProgHeight * mPos / mMax;
void setMax(int aMax) {
if (aMax > 0) {
mMax = aMax;
invalidate(); }
}
Paint fillpnt = new Paint();
fillpnt.setShader(mShader);
canvas.drawRect(rt, fillpnt);
rt.top = getPaddingTop();
Paint outpnt = new Paint();
outpnt.setColor(Color.WHITE);
outpnt.setStyle(Paint.Style.STROKE);
canvas.drawRect(rt, outpnt);
int getMax() { return mMax; }
void setPos(int aPos) {
if (aPos < 0 || aPos > mMax) { return; }
mPos = aPos;
invalidate();
}
}
27/40
2. 새로운 위젯
 RainbowTest 예제
Widget/RainbowTest.java – 5
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
{
int Width = 26, Height = 100;
• 일정 범위에서 현재 위치값을 표시하는 것
이 위젯의 주요 임무이므로 범위와 현재
위치인 mMax, mPos를 멤버로 가진다.
switch (MeasureSpec.getMode(widthMeasureSpec)) {
case MeasureSpec.AT_MOST:
Width = Math.min(MeasureSpec.getSize(widthMeasureSpec),
Width);
break;
case MeasureSpec.EXACTLY:
Width = MeasureSpec.getSize(widthMeasureSpec);
break;
}
• 범위의 최소값은 무조건 0으로 고정한다.
• 두 멤버는 init에서 각각 100, 0으로 초기
화되며 외부에서 setMax, setPos 메서드로
변경 가능하다.
• set* 메서드는 규칙에 맞지 않는 값은 거부
하여 스스로를 방어한다.
switch (MeasureSpec.getMode(heightMeasureSpec)) {
case MeasureSpec.AT_MOST:
Height = Math.min(MeasureSpec.getSize(heightMeasureSpec),
Height);
break;
case MeasureSpec.EXACTLY:
Height = MeasureSpec.getSize(heightMeasureSpec);
break;
}
- setMax : 1이상.
- setPos : 0 ~ mMax 범위 내.
• 프로그래스 막대를 그리는 코드는 모드
onDraw에 작성되어 있다.
• 최초 호출 시 패딩을 고려하여 높이를 계
산해 놓고 이 높이를 가득 채우는 그러데
이션 셰이더를 생성해 놓는다.
setMeasuredDimension(Width, Height);
}
}
• 나머지는 단순 좌표 계산과 그리기 코드이
다.
28/40
2. 새로운 위젯
• 메인 레이아웃에 프로그래스와 버튼을 배치해 놓고 타이머로 작업을 진행시킨다.
- 프로그래스 위젯에 패딩 속성을 지정했는데, 코드에서는 패딩만큼 안쪽 여백을 둬야 한다.
- onDraw에서 셰이더와 외곽선 좌표를 구하는데 패딩을 고려하고 있다.
- 마진의 경우 위젯 바깥쪽의 여백이므로 고려하지 않아도 좋다.
- 액티비티는 버튼을 누를 때 핸드러를 호출하여 0.01초에 한번씩 프로그래스를 진행, Max에 도달하면 작업을
중지한다.
Widget/rainbowtest.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"
>
<exam.Widget.RainbowProgress
android:id="@+id/progress"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingLeft="5px"
android:paddingTop="10px"
android:paddingRight="5px"
android:paddingBottom="10px"
/>
<Button
android:id="@+id/start"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Start"
/>
</LinearLayout>
[ RainbowTest 예제 실행 결과 ]
29/40
3. 여러 가지 뷰
 ScrollView
 모바일 장비는 화면이 작아 많은 내용을 한번에 표시하기 어렵다.
 스크롤이라는 기법을 통해 상하좌우로 영역을 이동하며 가려진 부분을 확인한다.
 ScrollView는 수직 스크롤 기능을 제공하는 뷰로, 현재 스크롤 위치의 내용을 보여주되
플리킹 동작이나 방향키 입력을 받아 이동한다.
 이 외에 리스트 뷰나 텍스트 뷰도 고유의 스크롤 기능을 제공한다.
 따라서 두 위젯은 굳이 스크롤 뷰와 함께 사용할 필요가 없으며, 사용해서는 안된다.
 FrameLayout의 서브 클래스로 하나의 차일드만 가질 수 있지만, 리니어 레이아웃 같은
컨테이너를 배치하면 그 안에 많은 뷰를 배치할 수 있다.
<ScrollView>
<LinearLayout>
<Widget>
<Widget>
<Widget>
...
</LinearLayout>
</ScrollView>
• 수직 리니어 안에 많은 위젯들을 배치해놓고 리니어를 스크롤 뷰로 감싼다.
• 스크롤 뷰는 리니어의 전체 높이만큼 범위로 인식하고 플리킹 입력을 받아 화면의 위아래로 이동
한다.
• 스크롤은 주로 실행중에 내용물의 길이가 가변적으로 결정되는 동적 레이아웃에 흔히 사용되며,
정적 레이아웃은 리스트 뷰를 쓰는 것이 더 일반적이다.
30/40
3. 여러 가지 뷰
 ScrollViewTest 예제
• 수직으로 긴 커스텀 뷰를 스크롤한다.
• 메인 레이아웃에는 스크롤 뷰 하나만 배치
, 커스텀 뷰는 실행중에 생성하여 스크롤
뷰의 차일드로 추가한다.
• ColorView는 특별한 기능 없이 점점 진해
지는 그러데이션을 보여준다.
• 뷰의 크기는 onMeasure에서 폭 500, 높이
1024로 강제 지정했다.
[ ScrollViewTest 예제 실행 결과 ]
Widget/scrollviewtest.xml
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/scr"
android:layout_width="fill_parent"
android:layout_height="wrap_content“ >
</ScrollView>
Widget/ScrollViewTest.java
public class ScrollViewTest extends Activity {
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.scrollviewtest);
ScrollView svw = (ScrollView)findViewById(R.id.scr);
//svw.setScrollBarStyle(View.SCROLLBARS_INSIDE_INSET);
//svw.setVerticalFadingEdgeEnabled(false);
//svw.setVerticalScrollBarEnabled(false);
svw.addView(new ColorView(this));
}
}
class ColorView extends View {
public ColorView(Context context) {
super(context);
}
public void onDraw(Canvas canvas) {
Paint Pnt = new Paint();
for (int y=0;y<1024;y+=4) {
Pnt.setARGB(255,255-y/4,255-y/4,255);
canvas.drawRect(0,y,500,y+4,Pnt);
}
}
protected void onMeasure (int widthMeasureSpec, int heightMeasureSpec)
{
setMeasuredDimension(500,1024);
}
}
31/40
3. 여러 가지 뷰
 아래의 메서드는 스크롤 뷰의 동작, 모양을 지정하는 메서드로, ScrollView의 메서드가
아니라 View의 메서드이다.
 즉, 모든 뷰는 스크롤 여부에 상관없이 이 메서드들을 호출할 수 있다.
void setScrollBarStyle (int style)
• 스크롤 바의 스타일을 지정하며, 아래 4가지 스타일 중 하나를 지정한다.
스타일
설명
SCROLLBARS_INSIDE_OVERLAY
패딩 추가 없이 내용물의 안쪽에 배치한다.
내용물 위에 투명하게 얹히며 디폴트 값이다.
SCROLLBARS_INSIDE_INSET
패딩을 추가하고 패딩에 스크롤바를 배치한다.
내용물 위에 오버랩되지 않는다.
SCROLLBARS_OUTSIDE_OVERLAY
패딩 추가없이 가장자리에 배치한다.
내용물 위에 투명하게 얹힌다.
SCROLLBARS_OUTSIDE_INSET
패딩을 추가하고 가장자리에 배치한다.
• INSIDE와 OUTSIDE는 스크롤 바를 패딩에 놓을 것인가, 가장자리에 놓을 것인가의 차이이다.
• INSET과 OVERLAY는 자리를 차지할 것인가 아니면 배경 위에 얹힐 것인가를 지정한다.
32/40
3. 여러 가지 뷰
 스크롤의 가장자리가 흐릿해지는 효과를 적용할 것인가 아닌가와 음영의 길이를 지정.
void setVerticalFadingEdgeEnabled (boolean verticalFadingEdgeEnabled)
void setHorizontalFadingEdgeEnabled (boolean horizontalFadingEdgeEnabled)
void setFadingEdgeLength (int length)
• 중간쯤으로 스크롤하면 윗 부분에 검정색 음영이 깔린다.
• XML 문서에서는 fadingEdge, fadingEdgeLength 속성으로 이 값을 지정한다.
 스크롤 바를 사용할 것인가 아닌가를 지정한다.
void setVerticalScrollBarEnabled (boolean verticalScrollBarEnabled)
void setHorizontalScrollBarEnabled (boolean horizontalScrollBarEnabled)
• View에서는 기본적으로 표시되지 않지만 ScrollView는 표시한다.
• false로 변경 시 스크롤 바는 표시되지 않지만 숨겨진 상태이므로 스크롤은 사용할 수 있다.
 ScrollView는 수직 스크롤만 지원하며 수평 스크롤은 지원하지 않는다.
• 모바일 장비는 보통 세로로 길쭉하고 항목들을 아래위로 배치하며, 사람들의 손가락도 좌우 플리
킹보다는 상하 플리킹에 더 익숙하기 때문이다.
33/40
3. 여러 가지 뷰
 1.5버전부터 수평 스크롤 가능한 클래스가 추가되었으며 클래스 이름 앞에 Horizontal이
추가되었다.
 HScrollViewTest 예제
• 레이아웃에는 ScrollView 대신 HorizontalScrollView를 배치, 수직으로 그러데이션하는 대신 수평
으로 그러데이션한다.
Widget/HScrollViewTest.java
public class HScrollViewTest extends Activity {
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.hscrollviewtest);
HorizontalScrollView svw =
(HorizontalScrollView)findViewById(R.id.scr);
svw.addView(new HColorView(this));
}
}
class HColorView extends View {
public HColorView(Context context) {
super(context); }
public void onDraw(Canvas canvas) {
Paint Pnt = new Paint();
for (int x=0;x<1024;x+=4) {
Pnt.setARGB(255,255-x/4,255-x/4,255);
canvas.drawRect(x,0,x+4,500,Pnt); }
}
protected void onMeasure (int widthMeasureSpec, int heightMeasureSpec)
{
setMeasuredDimension(1024, 500);
}
}
[ HScrollViewTest 예제 실행 결과 ]
34/40
3. 여러 가지 뷰
 WebView
 웹뷰는 이름 그대로 웹 페이지를 보여주는 위젯으로, 네트워크 입출력, 캐싱, 링크 클릭
처리, 확대, 축소, 히스토리 관리 등 웹 브라우저가 제공하는 모든 기능을 자체적으로 제
공한다.
 안드로이드에 내장된 웹킷(WebKit) 라이브러리가 모든 것을 처리하며, 사용자는 원하는
곳에 배치하고 주소를 전달한다.
 웹킷은 오픈 소스이며 사파리, 크롬 등에 채용되어 성능을 입증, 높은 신뢰성을 제공한
다.
 응용 프로그램이 웹 사이트의 페이지를 읽으려면 인터넷 엑세스를 해야 하므로 특별한
권한이 필요하다.
 모바일 장비에서의 인터넷 엑세스는 요금 문제와 연관이 있으므로 반드시 사용자의 허
가나 동의가 필요하다.
 따라서 웹을 사용하는 프로그램은 매니페스트에 INTERNET 퍼미션을 반드시 지정해야
한다.
 Widget 프로젝트의 매니페스트를 열면 아래와 같은 퍼미션 지정문이 작성되어 있다.
<user-permission android:name=“android.permission.INTERNET”/>
35/40
3. 여러 가지 뷰
 웹뷰를 사용하면 간단한 웹 브라우저는 만들 수 있으며, 액티비티의 일부에 웹 페이지를
표시할 수 있다.
 WebViewTest 예제
Widget/WebViewTest.java - 2
Button.OnClickListener mClickListener = new View.OnClickListener() {
public void onClick(View v) {
switch (v.getId()) {
case R.id.btngo:
String url;
EditText addr = (EditText)findViewById(R.id.address);
url = addr.getText().toString();
mWeb.loadUrl(url);
break;
case R.id.btnback:
if (mWeb.canGoBack()) {
mWeb.goBack(); }
break;
case R.id.btnforward:
if (mWeb.canGoForward()) {
mWeb.goForward(); }
break;
case R.id.btnlocal:
mWeb.loadUrl("file:///android_asset/test.html");
break;
}
}
};
• 레이아웃에 에디트와 버튼들을 상단에 배
치, 나머지 영역을 웹 뷰로 가득 채우고 코
드에서는 각 버튼이 클릭될 때 웹 뷰에게
명령을 내린다.
Widget/WebViewTest.java - 1
public class WebViewTest extends Activity {
WebView mWeb;
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.webviewtest);
mWeb = (WebView)findViewById(R.id.web);
mWeb.setWebViewClient(new MyWebClient());
WebSettings set = mWeb.getSettings();
set.setJavaScriptEnabled(true);
set.setBuiltInZoomControls(true);
mWeb.loadUrl("http://www.google.com");
findViewById(R.id.btngo).setOnClickListener(mClickListener);
findViewById(R.id.btnback).setOnClickListener(mClickListener);
findViewById(R.id.btnforward).setOnClickListener(mClickListener);
findViewById(R.id.btnlocal).setOnClickListener(mClickListener);
class MyWebClient extends WebViewClient {
public boolean shouldOverrideUrlLoading(WebView view, String url)
{
view.loadUrl(url);
return true; }
}
}
}
36/40
3. 여러 가지 뷰
 WebViewTest 예제
• 웹 뷰에서 가장 중요한 정보는 연결할 사이트 주소이며, loadUrl 메서드로 지정한다.
- 웹 뷰는 URL을 전달받으면 액티비티 매니저에게 URL을 전달하며, 시스템에 등록된 웹 브라우저가 실행된다.
- 이 경우 액티비티 안의 웹 뷰에 사이트를 열 수 없으며 응용 프로그램이 통제 불가능하다.
- 따라서 응용 프로그램이 URL을 처리할 기회를 얻기 위해 웹 클라이언트를 별도로 선언, 관련 이벤트를 가로채
야 하며, 예제에서는 MyWebClient라는 이름으로 클래스를 선언, shouldOverridUrlLoading 메서드를 재정의하여
웹뷰가 url을 받도록 하였다.
- 메서드가 true를 리턴하면 응용 프로그램이 직접 url을 처리한다는 뜻이다.
• 준비해 둔 웹 클라이언트 모듈을 setWebViewClient 메서드로 전달, 이후부터 웹 브라우저 대신 액
티비티 안의 웹 뷰가 열린다.
• 웹 클라이언트는 이 외에도 키보드 입력, 배율 변경, 에러 발생시의 이벤트를 처리할 수 있다.
[ WebViewTest 예제 실행 결과 ]
37/40
3. 여러 가지 뷰
 웹 뷰의 설정 변경 시 setSettings 메서드로 WebSettings 객체를 얻은 후 이 객체의 set*
메서드로 캐시 정책, 폰트 크기, 확대 여부, 스크립트 허용 여부 등의 설정을 변경할 수
있다.
 디폴트가 무난하지만 몇 가지 설정은 꼭 변경해야 한다.
• 예제에서는 자바 스크립트와 내장 확대 기능을 사용하도록 설정하였다.
• 자바 스크립트를 사용하지 않는 웹 사이트는 거의 없으므로 이 설정을 필수적이다.
• 확대, 축소 기능을 사용하면 아래쪽에 확대, 축소 버튼이 나타나 사용자가 직접 배율을 조정할 수
있어 편리하다.
 웹 클라이언트 지정과 웹 뷰 설정까지 완료하면 loadUrl 메서드로 이동하고자 하는 사이
트로 바로 이동 가능하며, 여러 메서드를 호출하여 웹 뷰를 프로그래밍 가능하다.
 Back, Forward 버튼 : 히스토리의 앞, 뒤 이동 기능을 제공, 아래의 메서드를 사용한다.
void goBack()
void goForward()
Boolean canGoBack()
Boolean cnaGoForward()
38/40
3. 여러 가지 뷰
 웹은 원격지의 페이지를 가져와 보여주지만 로컬의 HTML 파일도 표시할 수 있다.
• 로컬의 HTML 파일은 asset 폴더에 저장되며 HTML 파일과 관련 이미지 파일을 넣어두고
file:///android_asset/파일명 식으로 읽는다.
• HTML은 간단한 서식을 표현 가능하며 이미지나 도표도 포함할 수 있어 다양한 서식이 있는 복
잡한 문서를 출력하는 용도로도 사용 가능하다.
 WebViewTest 예제
• 아래의 HTML 파일을 test.html이라는 이름으로 작성해 둔다.
• 예제의 Local 버튼을 누르면 작성한 페이지가 나타난다.
<body>
<h2>Test Web Page</h2>
<span style=“COLOR:#ff0000;”>빨간색 글자</span><br/>
<a href=http://www.microsoft.com>Micorsoft.com</a>
</body>
[ WebViewTest 예제 실행 결과 ]
39/40
안드로이드 프로그래밍 정복(Android Programming Complete
Guide)