커스텀 위젯
DESCRIPTION
9. 커스텀 위젯. 학습목표 기존 위젯을 변경하여 실제 프로젝트에 바로 사용할 수 있는 커스텀 위젯을 제작하는 방법을 익힌다 . 위젯이 상위의 뷰 그룹과 통신하는 방법을 연구한다 . 커스텀 속성을 통해 위젯의 활용성을 높이는 방법을 학습한다 . 내용 기존 위젯 변형 새로운 위젯 여러 가지 뷰. 1. 기존 위젯 변형. 위젯 수정 모바일 장비는 통신 수단이며 일종의 액세서리이므로 디자인 역시 중요하다 . - PowerPoint PPT PresentationTRANSCRIPT
안드로이드 프로그래밍 정복 (Android Programming Complete Guide)
커스텀 위젯
2/40
Contents
학습목표 기존 위젯을 변경하여 실제 프로젝트에 바로 사용할 수 있는 커스텀
위젯을 제작하는 방법을 익힌다 . 위젯이 상위의 뷰 그룹과 통신하는 방법을 연구한다 . 커스텀 속성을 통해 위젯의 활용성을 높이는 방법을 학습한다 .
내용 기존 위젯 변형 새로운 위젯 여러 가지 뷰
3/40
위젯 수정 모바일 장비는 통신 수단이며 일종의 액세서리이므로 디자인 역시 중요하다 .
디자인을 결정하는 주요 요소는 위젯이며 , 기능적인 완벽함과 효율만큼 예쁜 외관과 동작의 참신함이 요구되고 , 사용자와의 상호 작용이 직관적이면서도 독창적이어야 한다 .
기능적으로 집합을 표시한다는 면에서 동일하지만 , 항목을 표시하는 방법은 다르다 .
아드로이드 표준 위젯은 디자인이 너무 사무적이며 , 무난한 모양을 지향하고 , 응용 프로그램들의 특수한 목적을 충족시키기에 기능이 너무 일반적이다 .
이에 안드로이드는 여러 가지 방법으로 커스텀 위젯 제작을 지원한다 .• 기존 위젯 클래스를 상속받아 기능을 확장하거나 수정한다 .
- 전통적인 클래스 상속 기법이 그대로 적용 .
- 대부분의 기능을 슈퍼 클래스에서 빌려 쓰고 필요한 부분만 수정 가능 .
• 기존 위젯들을 결합하여 복잡한 동작을 수행하는 위젯 그룹을 정의한다 .
- ViewGroup 또는 그 파생 클래스를 확장하여 만든다 .
- 그룹 내부 위젯끼리의 상호 작용까지 모두 정의해야 한다 .
• 기존의 존재하지 않는 새로운 위젯을 만든다 .
- 기능이나 모양이 특수하여 기존 위젯으로 사용이 불가능할 때 사용하는 방법이다 .
- 최상위 위젯 클래스인 View 로부터 상속 받으며 난이도가 높다 .
※ 만들고자 하는 위젯의 기능과 목적에 따라 적절한 방법을 선택해야 하며 , 방법에 따라 확장 대상 클래스가 달라진다 .
1. 기존 위젯 변형
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); } }}
5/40
파생 클래스의 생성자는 일단 슈퍼 클래스의 생성자를 호출하여 상속받은 멤버를 먼저 초기화해야 한다 .• 세 생성자 모두 super 호출문이 제일 앞에 오고 전달받은 모든 인수를 super 로 넘겨준다 .
그 후 고유의 초기화 작업을 하며 , 매 생성자마다 중복된 코드를 두는 것은 바람직하지 않다 .• init 초기화 메서드를 정의하고 , 각 생성자는 이 메서드를 호출하여 고유의 초기화를 수행하는
것이 일반적이다 .
1. 기존 위젯 변형
6/40
커스텀 위젯은 하나의 클래스이므로 동작에 필요한 객체나 상수들을 내부 멤버로 선언한다 .• 예제 위젯은 소리를 내야 하므로 SoundPool 객체와 사운드 ID 를 멤버로 선언 , init 메서드에서
초기화한다 .
• SoundPool 객체 생성 후 mClick 에 ID 대입 , 사운드 파일은 res/raw 폴더에 mp3 포맷으로 복사해 둔다 .
텍스트가 변경 시 onTextChanged 메서드가 호출된다 .• 문자 입력 시점이므로 onKeyDown 메서드를 재정의 .
• 이 메서드는 키패드의 키를 누를때만 호출 , 화면 키보드에 대해서는 호출되지 않는다 .
에디트가 생성될 때 빈 문자열로 초기화되며 이때도 onTextChanged 메서드가 호출된다 .• 이 시점은 mPool 객체가 아직 초기화되기 전이므로 객체가 null 인지 확인해야 한다 .
커스텀 위젯을 사용하는 방법도 표준 위젯과 동일하다 .• new 연산자로 실행 중 직접 생성 , 레이아웃에 추가 가능하며 XML 파일에 엘리먼트로 배치
가능하다 .
• 각 경우 호출되는 생성자가 다르며 , 대응되는 생성자가 정의되어 있어야 한다 .
1. 기존 위젯 변형
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 예제 실행 결과 ]
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 예제 실행 결과 ]
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") }}
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);
....
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)
• 속성의 개수 , 이름과 값을 조사한다 .
• 속성들은 배열 형태로 저장되어 있으므로 첨자로 읽는다 .
• 속성의 타입을 정확히 알고 있는 경우 정수형 , 실수형 , 진위형 등의 타입으로 바로 읽을 수 있으며 속성이 생략된 경우의 디폴트 값 지정이 가능하다 .
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"); } }}
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 예제 실행 결과 ]
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 문서의 속성을 읽는다 .
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 예제 실행 결과 ]
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); }
17/40
크기 정하기 완전히 새로운 위젯을 만들 때는 최상위 클래스인 View 로부터 상속받는다 .
View 는 화면에 모습을 그리기 위한 메서드들과 사용자와 통신하기 위한 이벤트 핸들러의 기본 원형을 제공한다 .
상속받은 뷰의 기능은 일반적이라 몇 개의 메서드를 반드시 재정의해야 한다 .• onDraw
- 뷰는 화면에 아무것도 그리지 않으므로 선택의 여지없이 재정의해야 한다 . ( 재정의하지 않으면 화면에 아무 것도 나타나지 않는다 .)
• onMeasure
- 부모 레이아웃은 차일드를 배치하기 위해 각 차일드의 크기를 조사하며 이때 차일드의 measure 메서드를 호출한다 .
- measure 는 강제 레이아웃 , 크기 변경 빈도 최소화 , 치명적인 에러 처리 등 중요한 역할을 담당하므로 직접 재정의하는 것은 바람직하지 않다 .
- measure 는 크기 결정 시 onMeasure 를 호출하므로 onMeasure 에서 위젯의 크기를 결정한다 .
void onMeasure (int widthMeasureSpec, int heightMeasureSpec)
- 인수로 전달된 *Spec 은 부모 레이아웃이 차일드에게 제공하는 여유 공간의 폭과 높이에 대한 정보이며 이 안에 공간의 성질을 지정하는 모드와 공간의 크기값이 저장되어 있다 .
- 모드와 크기를 별도의 인수로 전달 시 하나의 정수에 두 값을 묶어서 전달한다 .
2. 새로운 위젯
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)
19/40
2. 새로운 위젯
• 레이아웃을 배치할 때 부모와 차일드는 메서드를 통해 대화하고 타협한다 .
- 부모 : 차일드의 OnMeasure 메서드를 호출하되 남은 여유 공간에 대한 정보를 스펙 인수로 전달
- 차일드 : 전달받는 정보를 참조하여 적절한 크기를 부모에게 응답한다 .
• onMeasure 에서 부모로 응답을 보낼 때는 아래의 메서드로 원하는 크기를 리턴한다 .
void setMeasuredDimension (int measuredWidth, int measuredHeight)
- 두 인수는 차일드가 원하는 폭과 높이이다 .
- 메서드가 두 값을 동시에 리턴할 수 없기 때문에 OnMeasure 는 형식적으로 리턴값이 없는 것으로 선언되어 있지만 실제로는 이 메서드를 통해 두 값을 리턴해야 한다 .
- 만약 이 OnMeasure 가 메서드를 호출하지 않고 리턴해 버리면 부모가 레이아웃 중에 IllegalStateException 예외를 일으키고 다운되어 버린다 .
- 차일드가 크기를 밝히지 않으면 배치가 불가능하다 .
※ OnMeasure 에서 크기를 결정하는 방법은 절대적인 공식이 따로 없고 응용도 가능하여 다소 복잡하고 이론만으로 이해하기 어려우므로 실습이 꼭 필요하다 .
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; }
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; }}
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 예제 실행 결과 ]
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 예제 실행 결과 ]
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 예제 실행 결과 ]
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 예제 실행 결과 ]
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;
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); }
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); }}
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 예제 실행 결과 ]
30/40
ScrollView 모바일 장비는 화면이 작아 많은 내용을 한번에 표시하기 어렵다 .
스크롤이라는 기법을 통해 상하좌우로 영역을 이동하며 가려진 부분을 확인한다 .
ScrollView 는 수직 스크롤 기능을 제공하는 뷰로 , 현재 스크롤 위치의 내용을 보여주되 플리킹 동작이나 방향키 입력을 받아 이동한다 .
이 외에 리스트 뷰나 텍스트 뷰도 고유의 스크롤 기능을 제공한다 .
따라서 두 위젯은 굳이 스크롤 뷰와 함께 사용할 필요가 없으며 , 사용해서는 안된다 .
FrameLayout 의 서브 클래스로 하나의 차일드만 가질 수 있지만 , 리니어 레이아웃 같은 컨테이너를 배치하면 그 안에 많은 뷰를 배치할 수 있다 .
3. 여러 가지 뷰
<ScrollView> <LinearLayout> <Widget> <Widget> <Widget> ... </LinearLayout></ScrollView>
• 수직 리니어 안에 많은 위젯들을 배치해놓고 리니어를 스크롤 뷰로 감싼다 .
• 스크롤 뷰는 리니어의 전체 높이만큼 범위로 인식하고 플리킹 입력을 받아 화면의 위아래로 이동한다 .
• 스크롤은 주로 실행중에 내용물의 길이가 가변적으로 결정되는 동적 레이아웃에 흔히 사용되며 , 정적 레이아웃은 리스트 뷰를 쓰는 것이 더 일반적이다 .
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 예제 실행 결과 ]
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)
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)
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 예제 실행 결과 ]
35/40
WebView 웹뷰는 이름 그대로 웹 페이지를 보여주는 위젯으로 , 네트워크 입출력 , 캐싱 , 링크
클릭 처리 , 확대 , 축소 , 히스토리 관리 등 웹 브라우저가 제공하는 모든 기능을 자체적으로 제공한다 .
안드로이드에 내장된 웹킷 (WebKit) 라이브러리가 모든 것을 처리하며 , 사용자는 원하는 곳에 배치하고 주소를 전달한다 .
웹킷은 오픈 소스이며 사파리 , 크롬 등에 채용되어 성능을 입증 , 높은 신뢰성을 제공한다 .
응용 프로그램이 웹 사이트의 페이지를 읽으려면 인터넷 엑세스를 해야 하므로 특별한 권한이 필요하다 .
모바일 장비에서의 인터넷 엑세스는 요금 문제와 연관이 있으므로 반드시 사용자의 허가나 동의가 필요하다 .
따라서 웹을 사용하는 프로그램은 매니페스트에 INTERNET 퍼미션을 반드시 지정해야 한다 .
Widget 프로젝트의 매니페스트를 열면 아래와 같은 퍼미션 지정문이 작성되어 있다 .
3. 여러 가지 뷰
<user-permission android:name=“android.permission.INTERNET”/>
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 예제• 레이아웃에 에디트와 버튼들을 상단에
배치 , 나머지 영역을 웹 뷰로 가득 채우고 코드에서는 각 버튼이 클릭될 때 웹 뷰에게 명령을 내린다 .
37/40
WebViewTest 예제• 웹 뷰에서 가장 중요한 정보는 연결할 사이트 주소이며 , loadUrl 메서드로 지정한다 .
- 웹 뷰는 URL 을 전달받으면 액티비티 매니저에게 URL 을 전달하며 , 시스템에 등록된 웹 브라우저가 실행된다 .
- 이 경우 액티비티 안의 웹 뷰에 사이트를 열 수 없으며 응용 프로그램이 통제 불가능하다 .
- 따라서 응용 프로그램이 URL 을 처리할 기회를 얻기 위해 웹 클라이언트를 별도로 선언 , 관련 이벤트를 가로채야 하며 , 예제에서는 MyWebClient 라는 이름으로 클래스를 선언 , shouldOverridUrlLoading 메서드를 재정의하여 웹뷰가 url 을 받도록 하였다 .
- 메서드가 true 를 리턴하면 응용 프로그램이 직접 url 을 처리한다는 뜻이다 .
• 준비해 둔 웹 클라이언트 모듈을 setWebViewClient 메서드로 전달 , 이후부터 웹 브라우저 대신 액티비티 안의 웹 뷰가 열린다 .
• 웹 클라이언트는 이 외에도 키보드 입력 , 배율 변경 , 에러 발생시의 이벤트를 처리할 수 있다 .
3. 여러 가지 뷰
[ WebViewTest 예제 실행 결과 ]
38/40
3. 여러 가지 뷰
웹 뷰의 설정 변경 시 setSettings 메서드로 WebSettings 객체를 얻은 후 이 객체의 set* 메서드로 캐시 정책 , 폰트 크기 , 확대 여부 , 스크립트 허용 여부 등의 설정을 변경할 수 있다 .
디폴트가 무난하지만 몇 가지 설정은 꼭 변경해야 한다 .• 예제에서는 자바 스크립트와 내장 확대 기능을 사용하도록 설정하였다 .
• 자바 스크립트를 사용하지 않는 웹 사이트는 거의 없으므로 이 설정을 필수적이다 .
• 확대 , 축소 기능을 사용하면 아래쪽에 확대 , 축소 버튼이 나타나 사용자가 직접 배율을 조정할 수 있어 편리하다 .
웹 클라이언트 지정과 웹 뷰 설정까지 완료하면 loadUrl 메서드로 이동하고자 하는 사이트로 바로 이동 가능하며 , 여러 메서드를 호출하여 웹 뷰를 프로그래밍 가능하다 .
Back, Forward 버튼 : 히스토리의 앞 , 뒤 이동 기능을 제공 , 아래의 메서드를 사용한다 .
void goBack() void goForward() Boolean canGoBack() Boolean cnaGoForward()
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 예제 실행 결과 ]
안드로이드 프로그래밍 정복 (Android Programming Complete Guide)