커스텀 위젯

40
안안안안안 안안안안안 안안 (Android Programming Complete Guide) 안안안 안안

Upload: dale

Post on 05-Jan-2016

55 views

Category:

Documents


0 download

DESCRIPTION

9. 커스텀 위젯. 학습목표 기존 위젯을 변경하여 실제 프로젝트에 바로 사용할 수 있는 커스텀 위젯을 제작하는 방법을 익힌다 . 위젯이 상위의 뷰 그룹과 통신하는 방법을 연구한다 . 커스텀 속성을 통해 위젯의 활용성을 높이는 방법을 학습한다 . 내용 기존 위젯 변형 새로운 위젯 여러 가지 뷰. 1. 기존 위젯 변형. 위젯 수정 모바일 장비는 통신 수단이며 일종의 액세서리이므로 디자인 역시 중요하다 . - PowerPoint PPT Presentation

TRANSCRIPT

Page 1: 커스텀 위젯

안드로이드 프로그래밍 정복 (Android Programming Complete Guide)

커스텀 위젯

Page 2: 커스텀 위젯

2/40

Contents

학습목표 기존 위젯을 변경하여 실제 프로젝트에 바로 사용할 수 있는 커스텀

위젯을 제작하는 방법을 익힌다 . 위젯이 상위의 뷰 그룹과 통신하는 방법을 연구한다 . 커스텀 속성을 통해 위젯의 활용성을 높이는 방법을 학습한다 .

내용 기존 위젯 변형 새로운 위젯 여러 가지 뷰

Page 3: 커스텀 위젯

3/40

위젯 수정 모바일 장비는 통신 수단이며 일종의 액세서리이므로 디자인 역시 중요하다 .

디자인을 결정하는 주요 요소는 위젯이며 , 기능적인 완벽함과 효율만큼 예쁜 외관과 동작의 참신함이 요구되고 , 사용자와의 상호 작용이 직관적이면서도 독창적이어야 한다 .

기능적으로 집합을 표시한다는 면에서 동일하지만 , 항목을 표시하는 방법은 다르다 .

아드로이드 표준 위젯은 디자인이 너무 사무적이며 , 무난한 모양을 지향하고 , 응용 프로그램들의 특수한 목적을 충족시키기에 기능이 너무 일반적이다 .

이에 안드로이드는 여러 가지 방법으로 커스텀 위젯 제작을 지원한다 .• 기존 위젯 클래스를 상속받아 기능을 확장하거나 수정한다 .

- 전통적인 클래스 상속 기법이 그대로 적용 .

- 대부분의 기능을 슈퍼 클래스에서 빌려 쓰고 필요한 부분만 수정 가능 .

• 기존 위젯들을 결합하여 복잡한 동작을 수행하는 위젯 그룹을 정의한다 .

- ViewGroup 또는 그 파생 클래스를 확장하여 만든다 .

- 그룹 내부 위젯끼리의 상호 작용까지 모두 정의해야 한다 .

• 기존의 존재하지 않는 새로운 위젯을 만든다 .

- 기능이나 모양이 특수하여 기존 위젯으로 사용이 불가능할 때 사용하는 방법이다 .

- 최상위 위젯 클래스인 View 로부터 상속 받으며 난이도가 높다 .

※ 만들고자 하는 위젯의 기능과 목적에 따라 적절한 방법을 선택해야 하며 , 방법에 따라 확장 대상 클래스가 달라진다 .

1. 기존 위젯 변형

Page 4: 커스텀 위젯

4/40

SoundEdit 예제• 기존 컨트롤 수정 방법이며 , EditText

클래스를 확장하여 키 입력시마다 소리를 내는 입력 위젯을 만든다 .

• 커스텀 위젯 클래스는 별도의 소스 파일에 따로 작성는 것이 관리에 편리하다 .

• EditText 클래스를 확장하여 SoundEditWidget 클래스를 파생 시켰으며 , 생성자는 세 벌을 모두 정의하였다 .

- XML 에서 전개 시 2 개의 인수를 취하는 생성자가 호출

- new 연산자로 생성 시 context 인수만 취하는 첫 번째 생성자가 호출된다 .

• 범용성을 높이기 위해서는 모든 생성자를 다 구비하는 것이 바람직하다 .

- 예제에는 두 번째 생성자만 사용됨 .

1. 기존 위젯 변형

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); } }}

Page 5: 커스텀 위젯

5/40

파생 클래스의 생성자는 일단 슈퍼 클래스의 생성자를 호출하여 상속받은 멤버를 먼저 초기화해야 한다 .• 세 생성자 모두 super 호출문이 제일 앞에 오고 전달받은 모든 인수를 super 로 넘겨준다 .

그 후 고유의 초기화 작업을 하며 , 매 생성자마다 중복된 코드를 두는 것은 바람직하지 않다 .• init 초기화 메서드를 정의하고 , 각 생성자는 이 메서드를 호출하여 고유의 초기화를 수행하는

것이 일반적이다 .

1. 기존 위젯 변형

Page 6: 커스텀 위젯

6/40

커스텀 위젯은 하나의 클래스이므로 동작에 필요한 객체나 상수들을 내부 멤버로 선언한다 .• 예제 위젯은 소리를 내야 하므로 SoundPool 객체와 사운드 ID 를 멤버로 선언 , init 메서드에서

초기화한다 .

• SoundPool 객체 생성 후 mClick 에 ID 대입 , 사운드 파일은 res/raw 폴더에 mp3 포맷으로 복사해 둔다 .

텍스트가 변경 시 onTextChanged 메서드가 호출된다 .• 문자 입력 시점이므로 onKeyDown 메서드를 재정의 .

• 이 메서드는 키패드의 키를 누를때만 호출 , 화면 키보드에 대해서는 호출되지 않는다 .

에디트가 생성될 때 빈 문자열로 초기화되며 이때도 onTextChanged 메서드가 호출된다 .• 이 시점은 mPool 객체가 아직 초기화되기 전이므로 객체가 null 인지 확인해야 한다 .

커스텀 위젯을 사용하는 방법도 표준 위젯과 동일하다 .• new 연산자로 실행 중 직접 생성 , 레이아웃에 추가 가능하며 XML 파일에 엘리먼트로 배치

가능하다 .

• 각 경우 호출되는 생성자가 다르며 , 대응되는 생성자가 정의되어 있어야 한다 .

1. 기존 위젯 변형

Page 7: 커스텀 위젯

7/40

SoundEdit 예제• 리니어 안에 안내 텍스트 뷰 하나와 커스텀 사운드 에디트를 배치한다 .

• XML 파일에 커스텀 위젯을 배치 시 엘리먼트 이름에 위젯의 클래스 명을 적되 커스텀 위젯은 풀패키지 명을 다 적어야 한다 .

• SoundEditWidget 도 EditText 의 파생 클래스이며 View 의 파생 클래스이므로 상위 클래스가 제공하는 모든 속성 사용이 가능하다 .

- onKeyDown : 16 진수 입력 조절 가능

- onDraw : 배경 이미지 삽입 가능

1. 기존 위젯 변형

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 예제 실행 결과 ]

Page 8: 커스텀 위젯

8/40

위젯 조합 개별 위젯들의 기능은 원자적이고 단순하여 복잡한 특정 작업 수행 시 여러 위젯들이

상호 협력적으로 동작해야 한다 .

관련 위젯들을 하나의 그룹으로 묶어 새로운 위젯으로 정의 가능하다 .• 유기적으로 동작하는 위젯 그룹을 하나의 단위로 묶음으로써 통합성과 재사용성이 좋아진다 .

NumEdit 예제• 에디트와 텍스트 뷰를 포함하는 수직 리니어 위젯을 정의 .

• 에디트는 자신의 길이를 텍스트 뷰에 출력하고 , 텍스트 뷰는 에디트의 문자열을 참조하므로 이 둘은 긴밀히 연관된 하나의 묶음이며 항상 같이 사용된다 .

• 레이아웃에는 NumEditWidget 하나만 배치한다 .

1. 기존 위젯 변형

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 예제 실행 결과 ]

Page 9: 커스텀 위젯

9/40

• NumEditWidget 클래스는 LinearLayout 을 상속 받아 차일드를 일렬로 배치 가능하다 .

• 클래스의 멤버로 mEdit, mText 를 선언하고 init 에서 두 객체를 생성하여 리니어에 추가한다 .

• 두 위젯의 상호 동작을 정의한다 .

- 에디트 내용 변경 시 문자열의 길이를 텍스트 뷰에 출력 .

- 위젯 자신이 TextWatcher 리스너 구현 , mEdit 텍스트의 변경 리스너를 this 로 지정 .

- 리스너의 onTextChanged 메서드에서 문자열 변경 시 길이를 새로 조사하여 텍스트 뷰에 출력 .

• 문자열 변경 시점을 구한다는 점에서 이전 예제와 비슷 .

- SoundEdit 예제 : TextView 의 메서드

- NumEdit 예제 : TextWatcher 인터페이스의 메서드

• EditText 를 멤버로 포함할 뿐이므로 onTextChanged 메서드를 재정의할 수 없다 .

• 와처를 등록 , 이를 통해 문자열 변경 시점을 알아내야 한다 .

1. 기존 위젯 변형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") }}

Page 10: 커스텀 위젯

10/40

세 개 이상의 위젯을 묶어 복잡한 기능 수행이 가능하며 , RelativeLayout, TableLayout 을 확장하여원하는 모양으로 배치할 수 있다 .

합쳐진 그룹 위젯에 대해 여백이나 마진 , 배경색 등 조정 가능하며 , 커스텀 속성을 통해 내부 위젯의 속성 역시 조정 가능하다 .

포함되는 위젯의 개수가 많고 배치가 복잡하면 코드에서 생성 , 배치가 번거로우므로 XML 문서로 뷰 그룹을 디자인하여 배치와 속성을 지정해 놓고 코드에서 레이아웃을 전개하여 배치하면 된다 .

numeditwidget 예제

1. 기존 위젯 변형

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);

....

Page 11: 커스텀 위젯

11/40

커스텀 속성 안드로이드에서 레이아웃을 배치 시 주로 XML 문서를 활용한다 .

XML 문서에 배치할 위젯의 클래스 명을 엘리먼트로 작성 , 위젯의 속성을 엘리먼트의 속성으로 작성하며 , 규모가 큰 레이아웃도 문서 작업만으로 생성 가능하다 .

XML 문서는 aapt 툴에 의해 이진 형태로 컴파일되어 실행 파일에 포함 , 전개자(Inflater) 가 정보를 읽고 각 위젯을 생성한다 .

지정된 속성은 생성자의 두 번째 인수인 AttributeSet 객체 형태로 생성자에게 전달 , 이 객체안에 속성의 집합이 저장되어 있다 .

클래스의 주요 메서드는 아래와 같다 .

1. 기존 위젯 변형

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)

• 속성의 개수 , 이름과 값을 조사한다 .

• 속성들은 배열 형태로 저장되어 있으므로 첨자로 읽는다 .

• 속성의 타입을 정확히 알고 있는 경우 정수형 , 실수형 , 진위형 등의 타입으로 바로 읽을 수 있으며 속성이 생략된 경우의 디폴트 값 지정이 가능하다 .

Page 12: 커스텀 위젯

12/40

attribute 예제• 리니어 안에 버튼 위젯을 배치 , 각 속성을 적용하고 Text 속성은 string.xml 에 정의한 후 참조로

지정한다 .

• 각 속성들은 aapt 에 의해 컴파일되며 전개자가 Button 객체를 생성할 때 생성자로 전달한다 .

• attrButton 은 Button 에서 파생 , 생성자에서 attrs 인수를 확인할 뿐 표준 버튼과 동작이 동일 .

• 전개자에 의해 생성되므로 두 번째 생성자만 정의 .

1. 기존 위젯 변형

Widget/attribute.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.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>

Widget/Attribute.java

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"); } }}

Page 13: 커스텀 위젯

13/40

• 생성자 코드는 간단하며 attrs 배열을 전부 순회 , 속성의 이름과 값을 조사하고 조사된 속성값을 mText 문자열에 누적시켜 둔다 .

• 생성자에서 출력이 불가능하므로 문자열 변수에 조사된 결과를 저장해 두고 생성 완료 후 이 변수를 읽는다 .

• onCreate 에서 버튼과 에디트 생성 후 버튼의 mText 를 읽어 아래의 에디트에 출력한다 .

1. 기존 위젯 변형

• 버튼에 지정된 속성들이 문자열로 바뀌어 에디트에 출력된다 .

• 속성값을 버튼 위젯에 실제로 적용하는 주체는 super 의 생성자이며 Button 의 생성자를 거쳐 TextView 의 생성자로 전달 , 위젯에 설정된다 .

• layout 으로 시작되는 레이아웃 파라미터는 객체가 완전히 초기화 된 후 부모 뷰에 추가될 때 적용된다 .

AttributeSet 객체는 XML 문서에 지정된 모든 속성을 포함하지만 문서상에 기록된 값을 액면 그대로 가지고 있으므로 직접 사용하기에 불편하다 .• 문자열의 경우 strings.xml 에 대한 참조로만 나타나므로 실제 문자열 조사를 위해서 리소스를

확인해야 한다 .

• 객체를 직접 사용하지 않고 메서드로 속성 집합을 한번 더 가공한다 .

TypedArray obtainStyledAttributes (AttributeSet set, int[] attrs)

• Resource.Theme 클래스에 정의 , Context 가 래퍼를 제공하므로 컨텍스트로부터 바로 호출이 가능하며 , 참조 리소스는 물론이고 스타일 지정된 값 역시 찾아주므로 최종적으로 적용할 값을 바로 구할 수 있다 .

[ Attribute 예제 실행 결과 ]

Page 14: 커스텀 위젯

14/40

사운드 속성 커스텀 위젯도 자신만의 속성을 정의하여 사용 가능하다 .

SoundEdit 2 예제• SoundEdit 예제를 확장 , 소리의 종류 , 볼륨 , 재생 속도 등을 속성으로 제공한다 .

• 커스텀 위젯의 속성은 attrs.xml 에 정의하고 , res/values 폴더 안에 파일을 작성한다 .( 파일명은 사용자가 원하는 대로 지정 가능하다 .)

1. 기존 위젯 변형

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 문서의 속성을 읽는다 .

Page 15: 커스텀 위젯

15/40

• 커스텀 속성에 대한 네임 스페이스는 표준과 유사하되 마지막 부분을 프로젝트의 패키지명으로 수정하여 사용한다 .

• 세 개의 커스텀 위젯을 배치하되 하나는 sound 를 2 로 지정했고 , 하나는 속도와 볼륨을 절반으로 줄였다 .

1. 기존 위젯 변형

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.SoundEditWidget2android:layout_width="fill_parent"android:layout_height="wrap_content"/><exam.Widget.SoundEditWidget2android:layout_width="fill_parent"android:layout_height="wrap_content"custom:sound="2"/><exam.Widget.SoundEditWidget2android:layout_width="fill_parent"android:layout_height="wrap_content"custom:sound="2"custom:speed="0.5"custom:volume="0.5"/></LinearLayout>

[ SoundEdidt2 예제 실행 결과 ]

Page 16: 커스텀 위젯

16/40

• 생성자는 attrs 배열을 init 메서드로 전달하되 new 연산자로 직접 생성 시 null 이 전달되며 , init 메서드는 전개자로부터 호출 시 attrs 에 저장된 속성값을 읽어 위젯에 적용한다 .

• obtainStyledAttributes 메서드로 R.java 에 정의된 속성의 배열 전달 시 TypedArray 객체로 리턴된다 .

1. 기존 위젯 변형

Widget/soundedit2.java - 2

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(); } }

protected void onTextChanged(CharSequence text, int start, int before, int after) { if (mPool != null) { mPool.play(mSound, mVolume, mVolume, 0, 0, mSpeed); } }}

Widget/soundedit2.java - 1

public class SoundEdit2 extends Activity { public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.soundedit2); }}

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); }

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); }

Page 17: 커스텀 위젯

17/40

크기 정하기 완전히 새로운 위젯을 만들 때는 최상위 클래스인 View 로부터 상속받는다 .

View 는 화면에 모습을 그리기 위한 메서드들과 사용자와 통신하기 위한 이벤트 핸들러의 기본 원형을 제공한다 .

상속받은 뷰의 기능은 일반적이라 몇 개의 메서드를 반드시 재정의해야 한다 .• onDraw

- 뷰는 화면에 아무것도 그리지 않으므로 선택의 여지없이 재정의해야 한다 . ( 재정의하지 않으면 화면에 아무 것도 나타나지 않는다 .)

• onMeasure

- 부모 레이아웃은 차일드를 배치하기 위해 각 차일드의 크기를 조사하며 이때 차일드의 measure 메서드를 호출한다 .

- measure 는 강제 레이아웃 , 크기 변경 빈도 최소화 , 치명적인 에러 처리 등 중요한 역할을 담당하므로 직접 재정의하는 것은 바람직하지 않다 .

- measure 는 크기 결정 시 onMeasure 를 호출하므로 onMeasure 에서 위젯의 크기를 결정한다 .

void onMeasure (int widthMeasureSpec, int heightMeasureSpec)

- 인수로 전달된 *Spec 은 부모 레이아웃이 차일드에게 제공하는 여유 공간의 폭과 높이에 대한 정보이며 이 안에 공간의 성질을 지정하는 모드와 공간의 크기값이 저장되어 있다 .

- 모드와 크기를 별도의 인수로 전달 시 하나의 정수에 두 값을 묶어서 전달한다 .

2. 새로운 위젯

Page 18: 커스텀 위젯

18/40

• 정수에서 두 값을 추출하거나 다시 합칠 때는 View.MeasureSpec 클래스의 메서드를 사용한다 .

• 스펙의 상위 2 비트에 모드가 저장 , 나머지 30 비트가 실제 크기값이며 이들을 비트 연산하여 분리 및 조합하는 메서드이다 .

• 모드는 전달된 크기가 어떤 의미를 가지는지를 지정한다 .

2. 새로운 위젯

모드 설명

AT_MOST차일드가 가질 수 있는 최대 크기이다 .이 값 이하로 크기를 결정해야 하며 이 크기 범위를 넘어서는 안된다 .

EXACTLY차일드가 가져야 하는 정확한 크기이다 .특별한 사유가 없는 한 차일드는 이 크기를 가져야 한다 .

UNSPECIFIED 별다른 제한이 없으므로 원하는 크기를 지정하면 된다 .

- AT_MOST 가 전달된 경우 : 부모가 위젯에게 허용된 최대한의 크기를 알려주며 , 차일드는 자신이 원하는 폭과 부모가 허락한 폭의 최소값을 취해야 한다 .

- EXACTLY 가 전달된 경우 : 부모가 제안한 폭을 사용한다 .

- UNSPECIFIED 가 전달된 경우 : 아무 제약이 없으므로 자신이 원하는 대로 폭을 차지해도 무방하다 .

int getMode (int measureSpec)int getSize (int measureSpec)int makeMeasureSpec (int size, int mode)

Page 19: 커스텀 위젯

19/40

2. 새로운 위젯

• 레이아웃을 배치할 때 부모와 차일드는 메서드를 통해 대화하고 타협한다 .

- 부모 : 차일드의 OnMeasure 메서드를 호출하되 남은 여유 공간에 대한 정보를 스펙 인수로 전달

- 차일드 : 전달받는 정보를 참조하여 적절한 크기를 부모에게 응답한다 .

• onMeasure 에서 부모로 응답을 보낼 때는 아래의 메서드로 원하는 크기를 리턴한다 .

void setMeasuredDimension (int measuredWidth, int measuredHeight)

- 두 인수는 차일드가 원하는 폭과 높이이다 .

- 메서드가 두 값을 동시에 리턴할 수 없기 때문에 OnMeasure 는 형식적으로 리턴값이 없는 것으로 선언되어 있지만 실제로는 이 메서드를 통해 두 값을 리턴해야 한다 .

- 만약 이 OnMeasure 가 메서드를 호출하지 않고 리턴해 버리면 부모가 레이아웃 중에 IllegalStateException 예외를 일으키고 다운되어 버린다 .

- 차일드가 크기를 밝히지 않으면 배치가 불가능하다 .

※ OnMeasure 에서 크기를 결정하는 방법은 절대적인 공식이 따로 없고 응용도 가능하여 다소 복잡하고 이론만으로 이해하기 어려우므로 실습이 꼭 필요하다 .

Page 20: 커스텀 위젯

20/40

Measuring 예제 소스 코드

2. 새로운 위젯

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; }

Page 21: 커스텀 위젯

21/40

Measuring 예제 소스 코드• MeasView 는 View 로부터 상속받으며

생성자는 디폴트로만 정의 .

• onDraw 는 빨간색으로 전체를 가득 채워 화면에 존재만 표시 .

• onMeasure 에는 위젯의 크기를 결정하는 코드가 작성 .

- Width, Height 는 150 과 80 으로 각각 상수 초기화되며 위젯의 내용물에 근거하여 계산됨 .

- 텍스트 뷰라면 문자열의 길이에 맞게 , 이미지 뷰라면 비트맵의 크기에 맞게 크기가 결정됨 .

- MeasView 는 사실상 내용물이 없으므로 임의의 크기를 가정한 것이다 .

• 위젯이 스스로 크기를 결정했다 해도 주변의 형제 위젯들과 부모의 영역을 나누어 가져야 하므로 항상 설정된 크기대로 배치되지 않는다 .

• 코드는 하나의 예일 뿐이며 위젯의 특성에 따라 고유한 크기 계산 알고리즘을 적용할 수 있다 .

• 최종 결정된 Width, Height 를 setMeasuredDimension 으로 전달한다 .

2. 새로운 위젯

Widget/Measuring.java - 3

/* if (wMode == MeasureSpec.AT_MOST && hMode == MeasureSpec.AT_MOST) { Width = Height = Math.min(Width, Height); } //*/

setMeasuredDimension(Width, Height);

mResult += (Spec2String(widthMeasureSpec) + ", " + Spec2String(heightMeasureSpec) + " -> (" + Width + "," + Height + ")\n"); }

String Spec2String(int Spec) { String str = "";

switch (MeasureSpec.getMode(Spec)) { case MeasureSpec.AT_MOST: str = "AT_MOST"; break; case MeasureSpec.EXACTLY: str = "EXACTLY"; break; default: str = "UNSPECIFIED"; break; }

str += " " + MeasureSpec.getSize(Spec); return str; }}

Page 22: 커스텀 위젯

22/40

• 수직 리니어 안에 수평 리니어와 에디트 배치하였으며 , 에디트는 배치 결과를 문자열로 확인하기 위한 장치이다 .

• 수평 리니어는 높이 100 픽셀의 노란색 배경이 지정되어 있으며 , 이 안에 폭 100 픽셀의 버튼을 양쪽에 배치 , 중간에 MeasView 를 끼워넣었다 .

2. 새로운 위젯

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 예제 실행 결과 ]

Page 23: 커스텀 위젯

23/40

measuring 2 예제• MeasView 의 폭과 높이를 wrap_content 에서 100px, 50px 로 강제 지정한다 .

measuring 3 예제• 양쪽 버튼 2 개를 없애고 MeasView 만 남겨두되 폭과 높이를 fill_parent 로 지정한다 .

measuring 4 예제• 버튼을 남겨두고 MeasView 의 폭과 높이를 fill_parent 로 지정한다 .

2. 새로운 위젯

Widget/measuring2.xml

<exam.Widget.MeasView android:id="@+id/meas" android:layout_width="100px" android:layout_height="50px”/>

Widget/measuring3.xml

<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 2 예제 실행 결과 ] [ Measuring 3 예제 실행 결과 ] [ Measuring 4 예제 실행 결과 ]

Page 24: 커스텀 위젯

24/40

부모가 차일드에게 제안하는 스펙은 어디까지나 크기 결정을 위한 힌트일 뿐이며 , 위젯이 자신의 필요에 따라 부모의 제안을 거절할 수 있고 일부 조정할 수도 있다 .• 최초의 예제 Measuring 에서 주석 처리된 부분을 주석 해제한 후 실행한다 .

2. 새로운 위젯

if (wMode == MeasureSpec.AT_MOST && hMode == MeasureSpec.AT_MOST) {Width = Height = Math.min(Width, Height);}

• 시계처럼 정원을 그려야 한다거나 증명사진처럼 일정한 종횡비를 지켜야 한다면 부모가 제안한 크기를 거부하고 규칙에 맞게 강제로 조정한 크기를 리턴하면 된다 .

• 부모는 220*100 만큼의 공간을 제안했지만 차일드는 높이를 내용물에 맞추어 80 으로 강제 조정했다 .

• 이 경우는 내용물을 기준으로 종횡비를 맞추었으므로 크기가 작은데 부모가 제안한 영역에 맞춘다면 (100,100) 이 되어 초대한 넓은 면적을 차지할 수도 있다 .

• EXACTLY 인 경우는 종횡비를 맞추지 않도록 구현했지만 필요하다면 여백으로 나머지를 채우더라도 내용물의 종횡비를 유지할 수 있다 .

[ Measuring 예제 실행 결과 ]

Page 25: 커스텀 위젯

25/40

차일드가 부모의 제안을 거부할 수 있듯 부모도 차일드의 요청을 다 들어주지 못하는 경우가 있다 .

레이아웃이 복잡하고 차일드 수가 많아져 한번의 대화로 완벽한 배치를 만들어 내기 어려우면 여러 차일드에게 반복적으로 질문을 날려 모두가 OK 할 때까지 조정을 반복 .

measuring 5 예제• 세 위젯 모두 layout_weight 속성을 지정하여 폭을 나누어 가지도록 하였다 .

2. 새로운 위젯

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 예제 실행 결과 ]

Page 26: 커스텀 위젯

26/40

무지개 프로그래스 무지개색으로 작업의 진행 경과를 보여주는 수직 프로그래스 바 위젯이다 .

기존 클래스를 상속받거나 조합해서는 만들 수 없으며 최상위의 View 를 상속받아 처음부터 다시 만들어야 한다 .

표준 프로그래스 바는 수평만 가능하므로 상속받아도 수직으로는 불가능하다 .

RainbowTest 예제

2. 새로운 위젯

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); } } });

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); } } }; }}

class RainbowProgress extends View { int mMax; int mPos; int mProgHeight; LinearGradient mShader;

Page 27: 커스텀 위젯

27/40

RainbowTest 예제

2. 새로운 위젯

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(); }

void init() { mMax = 100; mPos = 0; }

void setMax(int aMax) { if (aMax > 0) { mMax = aMax; invalidate(); } }

int getMax() { return mMax; }

void setPos(int aPos) { if (aPos < 0 || aPos > mMax) { return; } mPos = aPos; invalidate(); }

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); }

RectF rt = new RectF(); rt.left = getPaddingLeft(); rt.right = getWidth() - getPaddingRight(); rt.bottom = getPaddingTop() + mProgHeight; rt.top = rt.bottom - mProgHeight * mPos / mMax;

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); }

Page 28: 커스텀 위젯

28/40

RainbowTest 예제• 일정 범위에서 현재 위치값을 표시하는

것이 위젯의 주요 임무이므로 범위와 현재 위치인 mMax, mPos 를 멤버로 가진다 .

• 범위의 최소값은 무조건 0 으로 고정한다 .

• 두 멤버는 init 에서 각각 100, 0 으로 초기화되며 외부에서 setMax, setPos 메서드로 변경 가능하다 .

• set* 메서드는 규칙에 맞지 않는 값은 거부하여 스스로를 방어한다 .

- setMax : 1 이상 .

- setPos : 0 ~ mMax 범위 내 .

• 프로그래스 막대를 그리는 코드는 모드 onDraw 에 작성되어 있다 .

• 최초 호출 시 패딩을 고려하여 높이를 계산해 놓고 이 높이를 가득 채우는 그러데이션 셰이더를 생성해 놓는다 .

• 나머지는 단순 좌표 계산과 그리기 코드이다 .

2. 새로운 위젯

Widget/RainbowTest.java – 5

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int Width = 26, Height = 100;

switch (MeasureSpec.getMode(widthMeasureSpec)) { case MeasureSpec.AT_MOST: Width = Math.min(MeasureSpec.getSize(widthMeasureSpec), Width); break; case MeasureSpec.EXACTLY: Width = MeasureSpec.getSize(widthMeasureSpec); break; }

switch (MeasureSpec.getMode(heightMeasureSpec)) { case MeasureSpec.AT_MOST: Height = Math.min(MeasureSpec.getSize(heightMeasureSpec), Height); break; case MeasureSpec.EXACTLY: Height = MeasureSpec.getSize(heightMeasureSpec); break; }

setMeasuredDimension(Width, Height); }}

Page 29: 커스텀 위젯

29/40

• 메인 레이아웃에 프로그래스와 버튼을 배치해 놓고 타이머로 작업을 진행시킨다 .

- 프로그래스 위젯에 패딩 속성을 지정했는데 , 코드에서는 패딩만큼 안쪽 여백을 둬야 한다 .

- onDraw 에서 셰이더와 외곽선 좌표를 구하는데 패딩을 고려하고 있다 .

- 마진의 경우 위젯 바깥쪽의 여백이므로 고려하지 않아도 좋다 .

- 액티비티는 버튼을 누를 때 핸드러를 호출하여 0.01 초에 한번씩 프로그래스를 진행 , Max 에 도달하면 작업을 중지한다 .

2. 새로운 위젯

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.RainbowProgressandroid: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"/><Buttonandroid:id="@+id/start"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="Start"/></LinearLayout>

[ RainbowTest 예제 실행 결과 ]

Page 30: 커스텀 위젯

30/40

ScrollView 모바일 장비는 화면이 작아 많은 내용을 한번에 표시하기 어렵다 .

스크롤이라는 기법을 통해 상하좌우로 영역을 이동하며 가려진 부분을 확인한다 .

ScrollView 는 수직 스크롤 기능을 제공하는 뷰로 , 현재 스크롤 위치의 내용을 보여주되 플리킹 동작이나 방향키 입력을 받아 이동한다 .

이 외에 리스트 뷰나 텍스트 뷰도 고유의 스크롤 기능을 제공한다 .

따라서 두 위젯은 굳이 스크롤 뷰와 함께 사용할 필요가 없으며 , 사용해서는 안된다 .

FrameLayout 의 서브 클래스로 하나의 차일드만 가질 수 있지만 , 리니어 레이아웃 같은 컨테이너를 배치하면 그 안에 많은 뷰를 배치할 수 있다 .

3. 여러 가지 뷰

<ScrollView> <LinearLayout> <Widget> <Widget> <Widget> ... </LinearLayout></ScrollView>

• 수직 리니어 안에 많은 위젯들을 배치해놓고 리니어를 스크롤 뷰로 감싼다 .

• 스크롤 뷰는 리니어의 전체 높이만큼 범위로 인식하고 플리킹 입력을 받아 화면의 위아래로 이동한다 .

• 스크롤은 주로 실행중에 내용물의 길이가 가변적으로 결정되는 동적 레이아웃에 흔히 사용되며 , 정적 레이아웃은 리스트 뷰를 쓰는 것이 더 일반적이다 .

Page 31: 커스텀 위젯

31/40

ScrollViewTest 예제• 수직으로 긴 커스텀 뷰를 스크롤한다 .

• 메인 레이아웃에는 스크롤 뷰 하나만 배치 , 커스텀 뷰는 실행중에 생성하여 스크롤 뷰의 차일드로 추가한다 .

• ColorView 는 특별한 기능 없이 점점 진해지는 그러데이션을 보여준다 .

• 뷰의 크기는 onMeasure 에서 폭 500, 높이 1024 로 강제 지정했다 .

3. 여러 가지 뷰

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); }}

[ ScrollViewTest 예제 실행 결과 ]

Page 32: 커스텀 위젯

32/40

아래의 메서드는 스크롤 뷰의 동작 , 모양을 지정하는 메서드로 , ScrollView 의 메서드가 아니라 View 의 메서드이다 .

즉 , 모든 뷰는 스크롤 여부에 상관없이 이 메서드들을 호출할 수 있다 .

• 스크롤 바의 스타일을 지정하며 , 아래 4 가지 스타일 중 하나를 지정한다 .

3. 여러 가지 뷰

스타일 설명

SCROLLBARS_INSIDE_OVERLAY패딩 추가 없이 내용물의 안쪽에 배치한다 .내용물 위에 투명하게 얹히며 디폴트 값이다 .

SCROLLBARS_INSIDE_INSET패딩을 추가하고 패딩에 스크롤바를 배치한다 .내용물 위에 오버랩되지 않는다 .

SCROLLBARS_OUTSIDE_OVERLAY패딩 추가없이 가장자리에 배치한다 .내용물 위에 투명하게 얹힌다 .

SCROLLBARS_OUTSIDE_INSET 패딩을 추가하고 가장자리에 배치한다 .

• INSIDE 와 OUTSIDE 는 스크롤 바를 패딩에 놓을 것인가 , 가장자리에 놓을 것인가의 차이이다 .

• INSET 과 OVERLAY 는 자리를 차지할 것인가 아니면 배경 위에 얹힐 것인가를 지정한다 .

void setScrollBarStyle (int style)

Page 33: 커스텀 위젯

33/40

스크롤의 가장자리가 흐릿해지는 효과를 적용할 것인가 아닌가와 음영의 길이를 지정 .

• 중간쯤으로 스크롤하면 윗 부분에 검정색 음영이 깔린다 .

• XML 문서에서는 fadingEdge, fadingEdgeLength 속성으로 이 값을 지정한다 .

스크롤 바를 사용할 것인가 아닌가를 지정한다 .

• View 에서는 기본적으로 표시되지 않지만 ScrollView 는 표시한다 .

• false 로 변경 시 스크롤 바는 표시되지 않지만 숨겨진 상태이므로 스크롤은 사용할 수 있다 .

ScrollView 는 수직 스크롤만 지원하며 수평 스크롤은 지원하지 않는다 .• 모바일 장비는 보통 세로로 길쭉하고 항목들을 아래위로 배치하며 , 사람들의 손가락도 좌우

플리킹보다는 상하 플리킹에 더 익숙하기 때문이다 .

3. 여러 가지 뷰

void setVerticalFadingEdgeEnabled (boolean verticalFadingEdgeEnabled)void setHorizontalFadingEdgeEnabled (boolean horizontalFadingEdgeEnabled)void setFadingEdgeLength (int length)

void setVerticalScrollBarEnabled (boolean verticalScrollBarEnabled)void setHorizontalScrollBarEnabled (boolean horizontalScrollBarEnabled)

Page 34: 커스텀 위젯

34/40

1.5 버전부터 수평 스크롤 가능한 클래스가 추가되었으며 클래스 이름 앞에 Horizontal이 추가되었다 .

HScrollViewTest 예제• 레이아웃에는 ScrollView 대신 HorizontalScrollView 를 배치 , 수직으로 그러데이션하는 대신

수평으로 그러데이션한다 .

3. 여러 가지 뷰

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 예제 실행 결과 ]

Page 35: 커스텀 위젯

35/40

WebView 웹뷰는 이름 그대로 웹 페이지를 보여주는 위젯으로 , 네트워크 입출력 , 캐싱 , 링크

클릭 처리 , 확대 , 축소 , 히스토리 관리 등 웹 브라우저가 제공하는 모든 기능을 자체적으로 제공한다 .

안드로이드에 내장된 웹킷 (WebKit) 라이브러리가 모든 것을 처리하며 , 사용자는 원하는 곳에 배치하고 주소를 전달한다 .

웹킷은 오픈 소스이며 사파리 , 크롬 등에 채용되어 성능을 입증 , 높은 신뢰성을 제공한다 .

응용 프로그램이 웹 사이트의 페이지를 읽으려면 인터넷 엑세스를 해야 하므로 특별한 권한이 필요하다 .

모바일 장비에서의 인터넷 엑세스는 요금 문제와 연관이 있으므로 반드시 사용자의 허가나 동의가 필요하다 .

따라서 웹을 사용하는 프로그램은 매니페스트에 INTERNET 퍼미션을 반드시 지정해야 한다 .

Widget 프로젝트의 매니페스트를 열면 아래와 같은 퍼미션 지정문이 작성되어 있다 .

3. 여러 가지 뷰

<user-permission android:name=“android.permission.INTERNET”/>

Page 36: 커스텀 위젯

36/40

웹뷰를 사용하면 간단한 웹 브라우저는 만들 수 있으며 , 액티비티의 일부에 웹 페이지를 표시할 수 있다 .

3. 여러 가지 뷰

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); }

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; } } };

class MyWebClient extends WebViewClient { public boolean shouldOverrideUrlLoading(WebView view, String url) { view.loadUrl(url); return true; } }}

WebViewTest 예제• 레이아웃에 에디트와 버튼들을 상단에

배치 , 나머지 영역을 웹 뷰로 가득 채우고 코드에서는 각 버튼이 클릭될 때 웹 뷰에게 명령을 내린다 .

Page 37: 커스텀 위젯

37/40

WebViewTest 예제• 웹 뷰에서 가장 중요한 정보는 연결할 사이트 주소이며 , loadUrl 메서드로 지정한다 .

- 웹 뷰는 URL 을 전달받으면 액티비티 매니저에게 URL 을 전달하며 , 시스템에 등록된 웹 브라우저가 실행된다 .

- 이 경우 액티비티 안의 웹 뷰에 사이트를 열 수 없으며 응용 프로그램이 통제 불가능하다 .

- 따라서 응용 프로그램이 URL 을 처리할 기회를 얻기 위해 웹 클라이언트를 별도로 선언 , 관련 이벤트를 가로채야 하며 , 예제에서는 MyWebClient 라는 이름으로 클래스를 선언 , shouldOverridUrlLoading 메서드를 재정의하여 웹뷰가 url 을 받도록 하였다 .

- 메서드가 true 를 리턴하면 응용 프로그램이 직접 url 을 처리한다는 뜻이다 .

• 준비해 둔 웹 클라이언트 모듈을 setWebViewClient 메서드로 전달 , 이후부터 웹 브라우저 대신 액티비티 안의 웹 뷰가 열린다 .

• 웹 클라이언트는 이 외에도 키보드 입력 , 배율 변경 , 에러 발생시의 이벤트를 처리할 수 있다 .

3. 여러 가지 뷰

[ WebViewTest 예제 실행 결과 ]

Page 38: 커스텀 위젯

38/40

3. 여러 가지 뷰

웹 뷰의 설정 변경 시 setSettings 메서드로 WebSettings 객체를 얻은 후 이 객체의 set* 메서드로 캐시 정책 , 폰트 크기 , 확대 여부 , 스크립트 허용 여부 등의 설정을 변경할 수 있다 .

디폴트가 무난하지만 몇 가지 설정은 꼭 변경해야 한다 .• 예제에서는 자바 스크립트와 내장 확대 기능을 사용하도록 설정하였다 .

• 자바 스크립트를 사용하지 않는 웹 사이트는 거의 없으므로 이 설정을 필수적이다 .

• 확대 , 축소 기능을 사용하면 아래쪽에 확대 , 축소 버튼이 나타나 사용자가 직접 배율을 조정할 수 있어 편리하다 .

웹 클라이언트 지정과 웹 뷰 설정까지 완료하면 loadUrl 메서드로 이동하고자 하는 사이트로 바로 이동 가능하며 , 여러 메서드를 호출하여 웹 뷰를 프로그래밍 가능하다 .

Back, Forward 버튼 : 히스토리의 앞 , 뒤 이동 기능을 제공 , 아래의 메서드를 사용한다 .

void goBack() void goForward() Boolean canGoBack() Boolean cnaGoForward()

Page 39: 커스텀 위젯

39/40

웹은 원격지의 페이지를 가져와 보여주지만 로컬의 HTML 파일도 표시할 수 있다 .• 로컬의 HTML 파일은 asset 폴더에 저장되며 HTML 파일과 관련 이미지 파일을 넣어두고

file:///android_asset/파일명 식으로 읽는다 .

• HTML 은 간단한 서식을 표현 가능하며 이미지나 도표도 포함할 수 있어 다양한 서식이 있는 복잡한 문서를 출력하는 용도로도 사용 가능하다 .

WebViewTest 예제• 아래의 HTML 파일을 test.html 이라는 이름으로 작성해 둔다 .

• 예제의 Local 버튼을 누르면 작성한 페이지가 나타난다 .

3. 여러 가지 뷰

<body><h2>Test Web Page</h2><span style=“COLOR:#ff0000;”> 빨간색 글자 </span><br/><a href=http://www.microsoft.com>Micorsoft.com</a></body>

[ WebViewTest 예제 실행 결과 ]

Page 40: 커스텀 위젯

안드로이드 프로그래밍 정복 (Android Programming Complete Guide)