본문 바로가기

안드로이드 개발 팁/일반

시퀀스를 이루는 이미지 리소스를 편리하게 불러오기

비슷한 속성을 가지면서 이미지 내의 일부만 변하는 이미지들의 이름은 대부분 [이미지이름]_[이미지번호].[확장자] 형식으로 구성됩니다. 이러한 이미지를 프로젝트 폴더의 리소스 폴더에 넣게 되면 자동으로 R.drawable 클래스에 [이미지이름]_[이미지번호] 라는 이름으로 필드가 생성되고, 각 리소스에 접근할 수 있는 주소가 할당됩니다. 다음은  R.drawable 클래스 내에 선언된 필드의 예를 보여줍니다.

public static final class drawable {
 
        public static final int ic_launcher=0x7f020000;
        public static final int img_0=0x7f020001;
        public static final int img_1=0x7f020002;
        public static final int img_2=0x7f020003;
        public static final int img_3=0x7f020004;
        public static final int img_4=0x7f020005;
        public static final int img_5=0x7f020006;
    }


애플리케이션 내에서 이미지 리소스에 접근하려면 R.drawable.[리소스명] 으로 접근하는데, 위의 예시와 같이 이미지 시퀀스가 리소스에 있을 경우 각 이미지에 접근하기 위해 R.drawable.img_0, R.drawable.img_1 ... 등 각 리소스의 이름으로 접근해야 하므로 이미지 번호가 있음에도 불구하고 전체 이미지 이름으로 접근해야 하므로 매우 불편합니다.

이미지가 10개 이내라면 일일이 리소스 이름을 적어도 별 문제가 없겠지만, 이 이상으로 늘어나게 되면 각 리소스를 다루기가 매우 어려워집니다. 리소스가 100개라면 100개의 이름을 하나하나 입력해야 하니, 보통 짜증나는 일이 아니죠.. -_-;;

이러한 불편함을 해결하기 위해, Reflection을 사용하여 R.drawable 클래스 내의 필드 값을 불러오는 방식으로 [이미지이름]+[시퀀스번호] 형태로 구성된 리소스에 접근하는 방법을 알아보겠습니다.

[애플리케이션 정보]

액티비티
  • Main (Main.java)

레이아웃
  • main.xml (Main)

예제 프로젝트:

MultipleImageHandling.zip


예제 애플리케이션의 레이아웃은 다음과 같습니다. 이미지 번호를 선택할 수 있는 SeekBar, 현재 시퀀스 번호를 보여주는 TextView, 선택된 이미지를 보여주는 ImageView로 구성되어 있습니다.

[main.xml]
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="vertical" android:padding="10dp">
    <TextView        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Image sequence:"
        android:textAppearance="?android:attr/textAppearanceMedium" />
    <SeekBar        android:id="@+id/imgSeqSeekBar"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" android:max="5"/>
    <TextView        android:id="@+id/imgSeqNum"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:text="0" android:gravity="right"/>
    <TextView        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Selected image:"
        android:textAppearance="?android:attr/textAppearanceMedium" />
    <ImageView        android:id="@+id/imgView"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:src="@drawable/img_0" />
</LinearLayout>

다음, Main 액티비티의 코드를 다음과 같이 작성합니다.

[Main.java]
package com.androidhuman.example.MultipleImageHandling;import java.lang.reflect.Field;import android.app.Activity;import android.os.Bundle;import android.widget.ImageView;import android.widget.SeekBar;import android.widget.SeekBar.OnSeekBarChangeListener;import android.widget.TextView;public class Main extends Activity {    	private SeekBar sbImgSequence;	private TextView tvImgSequence;	private ImageView ivSequenceImage;		private final String IMG_NAME_FORMAT = "img_%s";	    @Override    public void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.main);                sbImgSequence = (SeekBar)findViewById(R.id.imgSeqSeekBar);        tvImgSequence = (TextView)findViewById(R.id.imgSeqNum);        ivSequenceImage = (ImageView)findViewById(R.id.imgView);                sbImgSequence.setOnSeekBarChangeListener(new OnSeekBarChangeListener(){			@Override			public void onProgressChanged(SeekBar seekBar, int progress,					boolean fromUser) {				try {					// Get resources' Field from R.drawable class					Field drawableRes = R.drawable.class.getField(String.format(IMG_NAME_FORMAT, progress));					// Get field's value and set as resource address to be displayed on ImageView					ivSequenceImage.setImageResource(drawableRes.getInt(R.drawable.class));					tvImgSequence.setText(String.valueOf(progress));				} catch (SecurityException e) {					e.printStackTrace();				} catch (NoSuchFieldException e) {					e.printStackTrace();				} catch (IllegalArgumentException e) {					e.printStackTrace();				} catch (IllegalAccessException e) {					e.printStackTrace();				}			}			@Override			public void onStartTrackingTouch(SeekBar seekBar) {			}			@Override			public void onStopTrackingTouch(SeekBar seekBar) {			}        	        });    }}
IMG_NAME_FORMAT에는 이미지 파일의 포맷을 지정합니다. 여기에서는 리소스 이름으로 img_0, img_1 .. 이므로 img_%s로 지정하여 format()메서드를 통해 이미지 번호를 입력받아 최종 이미지 리소스 이름을 만들 수 있도록 하였습니다.

사용자가 SeekBar를 움직였을 때 SeekBar의 위치를 이미지 번호로 받고, 이를 사용하여 이미지 이름을 완성하여 리소스에서 불러오기 위해 format()메서드를 사용하여 완성된 이미지 리소스 이름을 사용하여 해당 필드를 Field 형태로 받고, 필드의 값에는 이미지 리소스에 접근할 주소가 있으므로 setImageResource() 의 인자로 이 값을 넘겨주어 이미지뷰에 해당 리소스를 표시하도록 할 수 있습니다.

// Get resources' Field from R.drawable classField drawableRes = R.drawable.class.getField(String.format(IMG_NAME_FORMAT, progress));// Get field's value and set as resource address to be displayed on ImageViewivSequenceImage.setImageResource(drawableRes.getInt(R.drawable.class));tvImgSequence.setText(String.valueOf(progress));

애플리케이션을 실행한 모습은 다음과 같습니다. SeekBar의 슬라이더를 움직여 이미지 번호를 선택하면 선택한 이미지가 아래의 ImageView에 표시되는 것을 확인할 수 있습니다.



추가--
'침묵' 님께서 댓글로 Resources.getIdentifier()를 사용하면 리플렉션을 사용하지 않고도 리소스의 id를 가져올 수 있다고 알려주셨습니다. :)

참고: http://developer.android.com/reference/android/content/res/Resources.html#getIdentifier(java.lang.String, java.lang.String, java.lang.String)

위의 예제를 getIdentifier()를 사용하도록 변경하면 다음과 같은 형태가 됩니다.

ivSequenceImage.setImageResource(Resources.getIdentifier(String.format(IMG_NAME_FORMAT, progress), "drawable", "com.androidhuman.example.MultipleResourceHandling");