본문 바로가기

안드로이드

Drawable의 변형

안드로이드에서 drawble은 어플리케이션을 쉽게 작성하는데 매우 유용하다. Drawable 은 View와 매우 연관성이 높은 플러그인 가능한 드로잉 컨테이너이다. 예를 들어 BitmapDrawable 은 이미지를 디스플레이하기 위해 사용되고 ShapeDrawable은 도형과 gradient를 그리기 위해 사용된다. 개발자는 복잡한 렌더링을 작성하기 위해 이것들을 조합할수 있다.

Drawable은 widget을 상속 받지 않고 렌더링을 쉽게 커스터마이징 할수 있도록 해준다. 사실, Drawable은 매우 편리해 안드로이드의 기본 어플리케이션과 widget의 대부분이 사용한다. 핵심 안드로이드 프레임워크에서 사용되는 drawable은 700개 정도나 된다. drawable은 시스템을 통해 매우 극단적으로 사용되기 때문에 안드로이드는 리소스가 로딩되는 과정을 최적화 하고 있다. 예를 들어, 매번 버턴을 만들때 마다 새로운 drawable이 프레임워크 리소스(android.R.drawable.btn_default)로 부터 로딩 되는데 이는 모든 어플리케이션의 모든 버튼이 버튼의 배경으로 다른 drawable 인스턴스를 사용한다는 것을 의미한다. 하지만 이 모든 drawable "상수 상태(constant state)"(역자 주 : 절대 변경 블가한 상태값)라고 불리는 공통의 상태를 공유 한다. 이 상태의 내용은 사용하는 drawable의 유형에 달려 있으나 보통  리소스에 의해 정의될수 있는 모든 속성들을 포함한다. 버튼의 경우 상수 상태는 bitmap 이미지를 포함한다. 이때 모든 어플리케이션의 모든 버튼은 많은 메모리를 절약하기 위해 동일한 bitmap을 공유하게 된다.

아래의 다이어그램은 다른 두 view의 배경으로 동일한 이미지 리소스를 사용할 경우 생성되는 개체들을 보여준다.


이 상태 공유 기능은 많은 메모리 낭비를 훌륭하게 방지할 수 있지만 drawable의 속성을 변경할려고 할때 문제를 발생시킬수 있다. 도서 목록을 가지는 어플리케이션을 상상해보자. 각 도서는 도서명과 별표를 가지며 사용자가 즐겨찾기 도서로 표시할 경우 전체적으로 불투명할수 있고 즐겨찾는 책이 아닐 경우 반투명할 수도 있다. 이런 효과를 구현하기 위해 사용하는 리스트 어댑터의 getView() 메서드(역자 주 : 커스텀 어댑터 작성시 구현해야할 메서드)에서 아래와 같은 코드를 작성해야할 것이다.

Book book = ...;
TextView listItem = ...;

listItem.setText(book.getTitle());

Drawable star = context.getResources().getDrawable(R.drawable.star);
if (book.isFavorite()) {
star.setAlpha(255); // 불투명
} else {
star.setAlpha(70); // 반투명
}

불행하게도 위 코드는 모든 drawable이 같은 투명도를 갖는 엉뚱한 결과를 보여준다.


이 결과는 상수 상태에 대해 설명해주고 있다. 각 아이템별로 별도의 drawable 인스턴스를 갖더라도 같은 상수 상태값을 갖는다. BitmapDrawable의 경우 투명도는 상수 상태값의 한 부분이다. 따라서 drawable 인스턴스중 하나의 투명도 변경은 다른 모든 인스턴스의 투명도를 변경 시킨다. 더 심각한 것은 안드로이드 1.0 과 1.1에서는 이런 이슈를 회피하기가 쉽지 않다는 것이다. (역자 주 : 허걱!.. 그러나 국내 출시된 안드로이드 폰중에 1.0과 1.1은 없다. ㅋㅋ)

안드로이드 1.5 이상에서는 mutate() 메서드를 통해 이 문제를 매우 쉽게 해결할 수 있다. drawable의 이 메서드를 호출할 때 다른 drawable에 영향을 주지 않고 특정 속성이 변경될수 있도록 drawable의 상수 상태를 복사한다. drawable이 mutate된 후에도 bitmap은 여전히 공유된을 주목하라. 아래 다이어그램은 drawable의 mutate()메서드를 호출했을때 무슨일이 일어나는지를 보여준다.


mutate()를 사용하여 이전 코드를 수정해보자.

Drawable star = context.getResources().getDrawable(R.drawable.star);
if (book.isFavorite()) {
star.mutate().setAlpha(255); // 불투명
} else {
star. mutate().setAlpha(70); // 반투명
}

연속된 메서드 호출이 가능하도록 하기 위해 mutate()메서드를 drawable 자신을 리턴한다. 하지만 새로운 drawable 인스턴스를 만들어내지는 않는다. 새로운 코드로 어플리케이션을 실행하면 올바르게 동작하는 것을 볼수 있다.