티스토리 뷰


 
이 자료들은 팁스소프트에서 제공하는 [ 알짜배기 ] 프로그램을 이용하면 더 편리하게 볼수 있습니다.
* 알짜배기 프로그램 받기 - http://www.tipssoft.com/bulletin/tb.php/QnA/8406
* 안드로이드 강좌 목록 - http://www.tipssoft.com/bulletin/tb.php/old_bbs/501
안드로이드에는 버튼, 텍스트뷰 등과 같이 고유한 기능을 제공하는 정형적인 컨트롤들이 존재합니다.
보통 이런 컨트롤들을 사용하여 어플리케이션을 개발하지만 때때로 안드로이드에서 제공하지 않는
기능을 자신이 사용해야 하는 경우도 있습니다. 이렇게 안드로이드에서 제공하지 않는 기능을 사용
하려면 자신이 직접 그런 기능을 하는 컨트롤을 만들어야 합니다. 예를들어 그림판같은 어플리케이션을
만들어야하는 경우, 해당 기능이 구현된 컨트롤이 존재하지 않기 때문에 자신이 직접 만들어야합니다.
( 그림판 기능을 하는 추가적인 어플리케이션을 설치하고 해당 어플리케이션에 포함된 액티비티를
사용하는 것은 이 상황에 맞지 않습니다. )
자신만의 컨트롤을 만들려면 컨트롤의 기본이 되는 View 클래스에서 새로운 클래스를 상속받고
원하는 기능을 추가해야 합니다. 이 방법에 대한 자세한 사용법은 아래의 링크를 참고하여 주시기
바랍니다.
사용자 정의 뷰 생성 및 배치1 - http://www.tipssoft.com/bulletin/tb.php/FAQ/964
View 클래스에서 상속받은 클래스에 자신만의 기능을 구현하기 위해서는
"자신이 보여주고 싶은 내용을 출력하는 기능""사용자의 조작을 처리해주는 기능"
을 재정의 해야합니다. 이 강좌에서는 "자신이 보여주고 싶은 내용을 출력하는 기능" 을 재정의하는
방법에 대해서 설명할 것입니다.
( "사용자의 조작을 처리해주는 기능" 에 대해서는 별도의 강좌를 통해서 설명할 것입니다. )
사용자 정의 뷰에서 그리기 작업을 정의하려면 onDraw 메소드를 View 클래스에서 오버라이딩 해야하며
onDraw 메소드의 매개 변수로 전달되는 Canvas 객체를 이용하여 그리기 행위를 정의할 수 있습니다.
그러나 Canvas 객체를 그냥 사용하면 기본적으로 흰 바탕에 검정색의 실선으로 그림이 그려지기 때문에
다양한 색상이나 선의 모양을 사용할 수 없게됩니다. 따라서 그림판처럼 다양한 색상과 선의 굵기, 모양
등을 사용하기 위해서는 그려지는 것들의 속성을 설정할 수 있는 Paint 클래스를 사용해야 합니다.
1. View 클래스의 onDraw 메소드에 대하여...
상속된 사용자뷰 클래스에서 그리기 작업을 정의하려면 onDraw 메소드를 오버라이딩 해야하며
onDraw 메소드의 매개 변수로 전달되는 Canvas 객체를 이용하여 그리기 행위를 정의할 수 있습니다.
이 메소드의 원형은 아래와 같습니다.
protected void onDraw(Canvas canvas);
이 메소드의 접근 권한이 protected 인것을 보시면 아시겠지만 이 메소드는 외부의 다른 클래스가
임의로 사용하기 위한 것이 아니라 안드로이드 시스템에 의해서 뷰의 갱신이 필요할때 해당 클래스
내부적으로 적당한 타이밍에 자동으로 호출되어지는 메소드입니다.
이 메소드가 호출되는 시점은 처음 뷰가 생성될 때나 다른 액티비티에 가려졌다가 다시 보여지는
경우 등 뷰의 출력 영역이 다시 그려져야할 때 입니다. 이처럼 시스템의 판단으로 onDraw 메소드가
호출되기도 하지만, invalidate 메소드를 호출하여 프로그래머가 원하는 시점에 onDraw 메소드를
수행시킬수도 있습니다. invalidate 메소드에 대한 설명은 이 강좌의 4 번 항목에서 자세히 다루도록
하겠습니다.
덧붙여 onDraw 메소드가 호출되면 뷰의 기본 배경부터 다시 그리는 것이기때문에 onDraw 메소드가
호출되기 전 뷰에 그려져있던 부분은 모두 지워지고 다시 onDraw 메소드에 정의된 그리기 작업을
수행하게 됩니다. ( onDraw 메소드가 호출되기전에 draw 라는 메소드가 호출되는데 draw 메소드는
재정의가 불가능하며 뷰의 기본 배경을 그리는 역할을 합니다. 따라서 draw 메소드에 의해서 뷰에
그려졌던 그림들이 지워지게 되는것입니다. )
아래의 예제는 onDraw 를 재정의하여 사용하는 방법에 대해서 소개한 것입니다. 단순히 자신이
상속받은 View 클래스의 배경이 빨강색으로 출력되게하는 기능을 구현한 것입니다.
public class TipsView extends View
{
... 생략 ...
protected void onDraw(Canvas canvas)
{
// 뷰의 배경색을 붉은 색으로 지정한다.
canvas.drawColor(Color.RED);
}
... 생략 ...
}

2. Canvas
Canvas 는 그림을 그리는 행위를 정의한 클래스입니다. 예를들어 선, 원, 면 과 같은 도형을 그리는
행위가 이에 해당합니다. 따라서 뷰에서 어떤 것을 그리고자 한다면 반드시 Canvas 클래스가 필요
하기 때문에 뷰의 onDraw 메소드의 매개변수로 Canvas 클래스 객체가 전달되는 것입니다.
실제로 Canvas 클래스를 살펴보면 점, 선, 도형, 문자열, 이미지 등 여러가지 형태의 그리기 작업을
수행할 수 있는 drawXXX 계열의 메소드들이 존재하고 이 메소드들을 조합하여 자신이 원하는
그림을 그리면 됩니다.
예를 들어 선과 원을 그리는 메소드를 사용한다고하면 다음과 같은 코드를 구성해야합니다.
protected void onDraw(Canvas canvas)
{
// 도형의 속성을 지정해주는 Paint 객체를 생성합니다.
// Paint 클래스는 이 강좌의 3 번 항목에서 다룰 것입니다.
Paint paint = new Paint();
// 생성된 Paint 객체의 속성을 빨강색으로 지정합니다.
paint.setColor(Color.RED);
// 뷰의 배경색을 흰색으로 채운다.
canvas.drawColor(Color.WHITE);
// (100, 100) 좌표에 지름 10 인 paint 속성이 적용된(빨강색) 원을 그린다.
canvas.drawCircle(100, 100, 10, paint);
// 시작점이 (100, 100) 이고, 끝점이 (110, 150) 인 paint 속성이 적용된(빨간색) 선을 그린다.
canvas.drawLine(100, 100, 110, 150, paint)
}
다른 시스템에서와 마찬가지로 안드로이드 시스템도 픽셀단위의 그림을 저장하기 위해서 "비트맵"
이라는 개념을 사용합니다. 비트맵은 독자적인 클래스로 존재하며 Canvas 객체를 이용해서 그림을
그리면 내부적으로는 Canvas 객체가 관리하는 Bitmap 객체에 그림이 저장되는 구조입니다.
즉, 그림을 그리는 행위는 Canvas 객체가 담당하고 실제로 그림이 저장되는 곳은 Bitmap 객체입니다.
따라서 Canvas 객체를 사용하기 위해서는 해당 Canvas 객체가 사용할 Bitmap 객체를 지정해주어야
하는게 일반적인데 위 예제에서는 Bitmap 객체를 지정하는 부분이 없습니다. 이것은 View 객체가
자신에게 출력된 그림을 유지하기 위해서 내부적으로 비트맵을 가지고 있는데 이 Bitmap 객체를
Canvas 객체에 사용하기 때문입니다. 즉, View 객체는 onDraw 메소드가 호출되기전에 내부적으로
관리하는 Bitmap 객체를 Canvas 객체에 적용한 후, 이 Canvas 객체를 onDraw 메소드의 매개변수로
전달하는 것입니다.
3. Paint
Canvas 클래스의 drawXXXX 메소드를 사용하여 그림을 그릴때 해당 메소드들은 Paint 객체를
요구합니다. 왜냐하면 Canvas 객체가 그림을 그릴때, 어떤 속성을 사용할지에 대한 정보가 필요하기
때문입니다. 즉, 그림을 표현할때는 "원을 그린다" 와 같은 행위만 존재하는것이 아니라 "빨강색 원을
그린다"와 같이 속성을 포함한 구체적 표현을 요구하기 때문입니다. 이렇게 그릴때 사용하는 속성에
대해서 정의한 클래스가 Paint 입니다.
안드로이드 시스템은 그리는 작업을 "행위"와 "속성"으로 구분하며 행위는 Canvas 클래스에 정의하고
속성은 Paint 클래스에 정의하고 있습니다. 따라서 이 두개의 클래스는 별개의 관계가 아니라 Canvas
객체를 사용할때 Paint 객체를 함께 사용하는 형태로 구성되는게 일반적입니다.
Paint 클래스에 정의되는 속성을 살펴보면 선 또는 면의 색상, 선의 형식, 폰트의 종류, 폰트의 형식,
여러가지 이미지필터, 디더링(dithering), 안티알리아스(antialias), 투명화, 각종 정렬 등이 있습니다.
Canvas 클래스의 drawXXXX 메소드를 사용할때, 잘못된 Paint 객체를 넘기거나 null 을 명시하면
오류가 나기 때문에 정확하게 Paint 객체를 생성하여 전달해야 합니다. 사용하는 형식은 2번 항목의
예제를 참고하시면 됩니다.
4. invalidate 메소드에 대하여...
View 클래스에서 onDraw 메소드는 적당한 시점에 자동적으로 호출되지만 사용자가 어떤 목적을
가지고 특정 시점에서 강제적으로 onDraw 메소드를 호출하려면 자신이 onDraw 메소드에 전달할
Canvas 객체를 직접 구성해야 합니다. 하지만, Canvas 객체를 구성하려면 뷰가 사용하는 Bitmap
객체를 참조할수 있어야 하는데 이 Bitmap 객체가 효율적인 수행을 위해서 시스템 자원으로 분류되어
있기 때문에 복사하여 참조는 가능하지만 직접적인 참조는 불가능합니다. 따라서 Bitmap 객체를 복사
하여 Canvas 객체에 지정한후 onDraw에 넘겨도 수행은 가능하겠지만 실제 뷰에 적용되지는 않습니다.
따라서 onDraw 메소드를 직접 호출하는 것은 사실상 어려운 일입니다. 하지만, onDraw 메소드를
직접 호출하지 않더라도 onDraw 메소드가 호출되도록 유도하는 것은 가능합니다. 즉, onDraw
메소드는 적당한 상황에 자동으로 호출되는 방식이기 때문에 이것을 역으로 이용해서 그 상황이 지금
발생한 것처럼 지시하면 됩니다. 이렇게 하면 UI 스레드가 현재 뷰가 갱신이 필요한것으로 간주하여
onDraw 메소드를 호출하게 되므로 결국 직접 호출한것과 같은 효과를 낼 수 있습니다.
이런 상황을 만들어주는 메소드가 invalidate 입니다. 따라서 원하는 시점에서 onDraw 메소드가 호출
되기를 바란다면 invalidate 메소드를 사용하시면 됩니다. 하지만, invalidate 메소드는 상황을 지정하는
방법을 사용하기 때문에 여러번 반복적으로 호출되어도 그만큼을 수행해 주지는 않습니다. 즉, 현재
작업하는 시점에서 invalidate 메소드를 반복적으로 10번 호출하더라도 같은 상황변수에 10번 동일한
값을 기록하는것이기 때문에 onDraw 메소드는 결국 1번만 수행하게 됩니다.
아래의 예제에서 startTest 메소드가 호출되면 m_data 값이 0 -> 10 -> 20 의 순으로 바뀌고
바뀔때마다 invalidate 가 호출되기 때문에 흰색에서 빨강색으로 바뀐후 파란색이 되기를 기대하지만
실제로는 흰색에서 파란색으로 바뀌게 됩니다.
이것은 단일 스레드를 기반으로 하는 상황에서 메인 스레드가 startTest 메소드를 수행하는 동안
갱신여부를 판단할수 없기 때문에 startTest 메소드의 호출이 끝난후에 갱신여부를 판단하고
onDraw 메소드를 호출하여 발생하는 현상입니다.

public class TipsView extends View
{
int m_data = 0;
... 생략 ...
protected void onDraw(Canvas canvas)
{
// m_data 의 값에 따라 뷰의 배경색을 지정한다.
if(m_data == 10) {
canvas.drawColor(Color.RED);
} else fi(m_data == 20) {
canvas.drawColor(Color.BLUE);
} else {
canvas.drawColor(Color.WHITE);
}
}
public void startTest()
{
m_data = 10;
invalidate();
// 수 초가 걸리는 작업을 수행한다.
m_data = 20;
invalidate();
}
... 생략 ...
}
만약 멀티 스레드를 기반으로 하는 상황에서 UI 스레드가 아닌 스레드가 뷰의 갱신을 UI 스레드로
요청할 경우 invalidate 메소드가 아닌 postInvalidate 메소드를 사용해야 합니다.

 

댓글