이제 두 번째 Android 11 개발자 프리뷰를 사용할 수 있습니다. 테스트해 보고 의견을 공유하세요.

드로어블 개요

정적 이미지를 앱에 표시해야 할 때 Drawable 클래스와 하위 클래스를 사용하여 도형과 이미지를 그릴 수 있습니다. Drawable그릴 수 있는 항목의 일반 추상화입니다. 다양한 서브클래스는 특정 이미지 시나리오에 유용하며 이 클래스를 확장하여 고유한 방식으로 동작하는 자체 드로어블 객체를 정의할 수 있습니다.

클래스 생성자를 사용하는 방법 이외에 다음 두 방법을 사용하여 Drawable을 정의하고 인스턴스화할 수 있습니다.

  • 프로젝트에 저장된 이미지 리소스(비트맵 파일)를 확장합니다.
  • 드로어블 속성을 정의하는 XML 리소스를 확장합니다.

참고: 연관된 색상 정보와 함께 점, 선 및 곡선 조합을 사용하여 이미지를 정의하는 벡터 드로어블을 사용할 수도 있습니다. 그러면 품질을 저하시키지 않고 벡터 드로어블을 다양한 크기에 맞게 조정할 수 있습니다. 자세한 내용은 벡터 드로어블 개요를 참조하세요.

리소스 이미지에서 드로어블 만들기

프로젝트 리소스에서 이미지 파일을 참조하여 앱에 그래픽을 추가할 수 있습니다. 지원되는 파일 형식은 PNG(권장), JPG(허용) 및 GIF(권장되지 않음)입니다. 게임에 사용되는 앱 아이콘, 로고 및 기타 그래픽은 이 기술에 적합합니다.

이미지 리소스를 사용하려면 프로젝트의 res/drawable/ 디렉터리에 파일을 추가합니다. 프로젝트에서 코드 또는 XML 레이아웃의 이미지 리소스를 참조할 수 있습니다. 두 방식 모두 파일 유형 확장자가 없는 파일 이름인 리소스 ID를 사용하여 참조합니다. 예를 들어 my_image.pngmy_image로 참조하세요.

참고: res/drawable/ 디렉터리에 있는 이미지 리소스는 빌드 프로세스 중에 aapt 도구에서 무손실 이미지 압축을 사용하여 자동으로 최적화할 수 있습니다. 예를 들어 256가지를 초과하는 색상이 필요하지 않은 트루 컬러 PNG를 색상 팔레트를 사용하여 8비트 PNG로 변환할 수 있습니다. 그러면 이미지의 품질은 같지만 필요한 메모리가 적어집니다. 따라서 이 디렉터리에 있는 이미지 바이너리는 빌드 시 변경될 수 있습니다. 이미지를 비트맵으로 변환하기 위해 비트스트림으로 읽으려면 이미지를 res/raw/ 폴더에 둡니다. 그러면 aapt 도구에서 수정하지 않습니다.

다음 코드 스니펫에서는 드로어블 리소스에서 생성된 이미지를 사용하고 레이아웃에 추가하는 ImageView의 빌드 방법을 보여줍니다.

Kotlin

    private lateinit var constraintLayout: ConstraintLayout

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        // Instantiate an ImageView and define its properties
        val i = ImageView(this).apply {
            setImageResource(R.drawable.my_image)
            contentDescription = resources.getString(R.string.my_image_desc)

            // set the ImageView bounds to match the Drawable's dimensions
            adjustViewBounds = true
            layoutParams = ViewGroup.LayoutParams(
                    ViewGroup.LayoutParams.WRAP_CONTENT,
                    ViewGroup.LayoutParams.WRAP_CONTENT)
        }

        // Create a ConstraintLayout in which to add the ImageView
        constraintLayout = ConstraintLayout(this).apply {

            // Add the ImageView to the layout.
            addView(i)
        }

        // Set the layout as the content view.
        setContentView(constraintLayout)
    }
    

자바

    ConstraintLayout constraintLayout;

    protected void onCreate(Bundle savedInstanceState) {
      super.onCreate(savedInstanceState);

      // Create a ConstraintLayout in which to add the ImageView
      constraintLayout = new ConstraintLayout(this);

      // Instantiate an ImageView and define its properties
      ImageView i = new ImageView(this);
      i.setImageResource(R.drawable.my_image);
      i.setContentDescription(getResources().getString(R.string.my_image_desc));

      // set the ImageView bounds to match the Drawable's dimensions
      i.setAdjustViewBounds(true);
      i.setLayoutParams(new ViewGroup.LayoutParams(
              ViewGroup.LayoutParams.WRAP_CONTENT,
              ViewGroup.LayoutParams.WRAP_CONTENT));

      // Add the ImageView to the layout and set the layout as the content view.
      constraintLayout.addView(i);
      setContentView(constraintLayout);
    }
    

다른 경우에는 다음 예제에 표시된 대로 이미지 리소스를 Drawable 객체로 처리할 수 있습니다.

Kotlin

    val myImage: Drawable = ResourcesCompat.getDrawable(context.resources, R.drawable.my_image, null)
    

자바

    Resources res = context.getResources();
    Drawable myImage = ResourcesCompat.getDrawable(res, R.drawable.my_image, null);
    

참고: 프로젝트에 있는 각각의 고유 리소스에서는 인스턴스화할 객체 수에 관계없이 하나의 상태만 유지할 수 있습니다. 예를 들어 같은 이미지 리소스에서 두 개의 Drawable 객체를 인스턴스화하고 한 객체의 속성(예: 알파)을 변경하면 다른 객체에도 영향을 미칩니다. 이미지 리소스의 여러 인스턴스를 처리할 때 Drawable 객체를 직접 변환하지 않고 트윈 애니메이션을 실행해야 합니다.

아래 XML 스니펫에서는 XML 레이아웃에서 ImageView에 드로어블 리소스를 추가하는 방법을 보여줍니다.

    <ImageView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:src="@drawable/my_image"
            android:contentDescription="@string/my_image_desc" />
    

프로젝트 리소스 사용에 관한 자세한 내용은 리소스 및 애셋을 참조하세요.

참고: 이미지 리소스를 드로어블 소스로 사용할 때 이미지가 다양한 픽셀 밀도에 적합한 크기인지 확인하세요. 이미지가 정확하지 않으면 크기에 맞춰 조정되어 드로어블에 아티팩트가 생길 수 있습니다. 자세한 내용은 다양한 픽셀 밀도 지원을 참조하세요.

XML 리소스에서 드로어블 만들기

처음에는 사용자 코드나 사용자 상호작용에 따라 정의된 변수에 종속되지 않는 Drawable 객체를 만들려면 XML에서 Drawable을 정의하는 것이 좋은 방법입니다. 사용자가 앱과 상호작용하는 동안 Drawable에서 속성을 변경하는 경우에도 XML에서 객체를 정의하는 것이 좋습니다. 객체를 인스턴스화한 후에 속성을 수정할 수 있기 때문입니다.

XML로 Drawable을 정의한 다음 프로젝트의 res/drawable/ 디렉터리에 파일을 저장합니다. 다음 예제에서는 Drawable에서 상속되는 TransitionDrawable 리소스를 정의하는 XML을 보여줍니다.

    <!-- res/drawable/expand_collapse.xml -->
    <transition xmlns:android="http://schemas.android.com/apk/res/android">
        <item android:drawable="@drawable/image_expand">
        <item android:drawable="@drawable/image_collapse">
    </transition>
    

그런 다음 Resources.getDrawable()을 호출하고 XML 파일의 리소스 ID를 전달하여 객체를 검색하고 인스턴스화합니다. inflate() 메서드를 지원하는 모든 Drawable 서브클래스는 XML에 정의하고 앱에서 인스턴스화할 수 있습니다. XML 확장을 지원하는 각 드로어블 클래스에서 객체 속성을 정의하는 데 유용한 특정 XML 속성을 활용합니다. 다음 코드에서는 TransitionDrawable을 인스턴스화하고 ImageView 객체의 콘텐츠로 설정합니다.

Kotlin

    val transition= ResourcesCompat.getDrawable(
            context.resources,
            R.drawable.expand_collapse,
            null
    ) as TransitionDrawable

    val image: ImageView = findViewById(R.id.toggle_image)
    image.setImageDrawable(transition)

    // Description of the initial state that the drawable represents.
    image.contentDescription = resources.getString(R.string.collapsed)

    // Then you can call the TransitionDrawable object's methods.
    transition.startTransition(1000)

    // After the transition is complete, change the image's content description
    // to reflect the new state.
    

자바

    Resources res = context.getResources();
    TransitionDrawable transition =
        (TransitionDrawable) ResourcesCompat.getDrawable(res, R.drawable.expand_collapse, null);

    ImageView image = (ImageView) findViewById(R.id.toggle_image);
    image.setImageDrawable(transition);

    // Description of the initial state that the drawable represents.
    image.setContentDescription(getResources().getString(R.string.collapsed));

    // Then you can call the TransitionDrawable object's methods.
    transition.startTransition(1000);

    // After the transition is complete, change the image's content description
    // to reflect the new state.
    

지원되는 XML 속성에 관한 자세한 내용은 위에 나열된 클래스를 참조하세요.

도형 드로어블

ShapeDrawable 객체는 2D 그래픽을 동적으로 그릴 때 선택하면 좋습니다. ShapeDrawable 객체에 기본 도형을 프로그래밍 방식으로 그리고 앱에 필요한 스타일을 적용할 수 있습니다.

ShapeDrawableDrawable의 서브클래스입니다. 따라서 Drawable이 필요할 때마다 ShapeDrawable을 사용할 수 있습니다. 예를 들어 보기의 setBackgroundDrawable() 메서드에 전달하여 보기의 배경을 설정하는 데 ShapeDrawable 객체를 사용할 수 있습니다. 또한 고유 맞춤 보기로 도형을 그려서 앱의 레이아웃에 추가할 수도 있습니다.

ShapeDrawable에 고유 draw() 메서드가 있으므로 다음 코드 예제에 표시된 대로 onDraw() 이벤트 중에 ShapeDrawable 객체를 그리는 View의 서브클래스를 만들 수 있습니다.

Kotlin

    class CustomDrawableView(context: Context) : View(context) {
        private val drawable: ShapeDrawable = run {
            val x = 10
            val y = 10
            val width = 300
            val height = 50
            contentDescription = context.resources.getString(R.string.my_view_desc)

            ShapeDrawable(OvalShape()).apply {
                // If the color isn't set, the shape uses black as the default.
                paint.color = 0xff74AC23.toInt()
                // If the bounds aren't set, the shape can't be drawn.
                setBounds(x, y, x + width, y + height)
            }
        }

        override fun onDraw(canvas: Canvas) {
            drawable.draw(canvas)
        }
    }
    

자바

    public class CustomDrawableView extends View {
      private ShapeDrawable drawable;

      public CustomDrawableView(Context context) {
        super(context);

        int x = 10;
        int y = 10;
        int width = 300;
        int height = 50;
        setContentDescription(context.getResources().getString(
                R.string.my_view_desc));

        drawable = new ShapeDrawable(new OvalShape());
        // If the color isn't set, the shape uses black as the default.
        drawable.getPaint().setColor(0xff74AC23);
        // If the bounds aren't set, the shape can't be drawn.
        drawable.setBounds(x, y, x + width, y + height);
      }

      protected void onDraw(Canvas canvas) {
        drawable.draw(canvas);
      }
    }
    

다른 맞춤 보기를 사용할 때와 마찬가지로 위의 코드 샘플에 있는 CustomDrawableView 클래스를 사용할 수 있습니다. 예를 들어, 다음 예제에 표시된 대로 앱의 활동에 프로그래밍 방식으로 추가할 수 있습니다.

Kotlin

    private lateinit var customDrawableView: CustomDrawableView

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        customDrawableView = CustomDrawableView(this)

        setContentView(customDrawableView)
    }
    

자바

    CustomDrawableView customDrawableView;

    protected void onCreate(Bundle savedInstanceState) {
      super.onCreate(savedInstanceState);
      customDrawableView = new CustomDrawableView(this);

      setContentView(customDrawableView);
    }
    

대신 XML 레이아웃에서 맞춤 보기를 사용하려면 CustomDrawableView 클래스를 통해 View(Context, AttributeSet) 생성자를 재정의해야 합니다. 이 생성자는 XML에서 클래스를 확장할 때 호출됩니다. 다음 예에서는 XML 레이아웃에서 CustomDrawableView를 선언하는 방법을 보여줍니다.

    <com.example.shapedrawable.CustomDrawableView
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            />
    

android.graphics.drawable 패키지의 다른 많은 드로어블 유형과 마찬가지로 ShapeDrawable 클래스를 사용하면 공용 메서드를 사용하여 객체의 다양한 속성을 정의할 수 있습니다. 예를 들어 알파 투명도, 색상 필터, 디더, 불투명도 및 색상 등의 속성을 조정할 수 있습니다.

XML 리소스를 사용하여 기본 드로어블 도형도 정의할 수 있습니다. 자세한 내용은 드로어블 리소스 유형도형 드로어블을 참조하세요.

NinePatch 드로어블

NinePatchDrawable 그래픽은 보기의 배경으로 사용할 수 있는 확장 가능한 비트맵 이미지입니다. Android에서는 보기의 콘텐츠를 반영하도록 그래픽 크기를 자동으로 조정합니다. NinePatch 이미지의 사용 예로는 표준 Android 버튼에서 사용되는 배경이 있습니다. 이 버튼은 다양한 길이의 문자열을 수용하기 위해 확장되어야 합니다. NinePatch 그래픽은 추가 1픽셀 테두리가 포함된 표준 PNG 이미지입니다. 프로젝트의 res/drawable/ 디렉터리에 9.png 확장자로 저장해야 합니다.

테두리를 사용하여 이미지의 확장 가능 영역 및 정적 영역을 정의합니다. 1픽셀 너비의 검정색 선 하나 이상을 테두리의 왼쪽 맨 위에 그려 확장 가능 섹션을 나타냅니다(다른 테두리 픽셀은 완전히 투명하거나 흰색이어야 함). 원하는 만큼 많은 수의 확장 가능 섹션을 보유할 수 있습니다. 확장 가능 섹션의 상대 크기는 동일하므로 가장 큰 섹션의 크기가 항상 가장 큽니다.

오른쪽과 맨 아래에 각각 하나씩 선을 그려 이미지의 선택적 드로어블 섹션도 적용할 수 있습니다(패딩 선을 사용하면 효과적). View 객체에서 NinePatch 그래픽을 배경으로 설정한 다음 보기의 텍스트를 지정하면, 오른쪽과 맨 아래 선이 포함된 경우 해당 선으로 지정된 영역에만 모든 텍스트가 입력되도록 확장됩니다. 패딩 선이 포함되지 않은 경우 Android에서는 왼쪽과 맨 위 선을 사용하여 이 드로어블 영역을 정의합니다.

선의 차이를 명확하게 구분하기 위해 왼쪽과 위쪽 선은 이미지를 확장하기 위해 복제될 수 있는 이미지의 픽셀을 정의합니다. 맨 아래 선과 오른쪽 선은 이미지 내에서 보기의 콘텐츠로 채울 수 있는 상대적 영역을 정의합니다.

그림 1에서는 버튼을 정의하는 데 사용되는 NinePatch 그래픽의 예를 보여줍니다.

확장 가능한 영역 및 패딩 상자 이미지

그림 1: 버튼을 정의하는 NinePatch 그래픽의 예

이 NinePatch 그래픽을 통해서는 왼쪽 및 위쪽 선이 있는 확장 가능한 영역과 맨 아래 및 오른쪽 선이 있는 드로어블 영역을 정의합니다. 상단 이미지에서 점선으로 표시된 회색 선은 이미지를 확장하기 위해 복제되는 이미지의 영역을 나타냅니다. 맨 아래 이미지의 분홍색 직사각형은 보기의 콘텐츠가 허용되는 영역을 식별합니다. 콘텐츠가 이 영역에 맞지 않으면 콘텐츠가 들어가도록 이미지가 확장됩니다.

9-패치 그리기 도구에서는 WYSIWYG 그래픽 편집기를 사용하여 NinePatch 이미지를 만드는 매우 편리한 방법을 제공합니다. 확장 가능한 영역에 맞게 정의한 영역이 픽셀을 복제한 결과 드로잉 아티팩트를 생성하는 위험에 처한 경우 경고도 표시합니다.

다음 샘플 레이아웃 XML에서는 NinePatch 그래픽을 몇 개의 버튼에 추가하는 방법을 보여줍니다. NinePatch 이미지는 res/drawable/my_button_background.9.png에 저장됩니다.

    <Button android:id="@+id/tiny"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignParentTop="true"
            android:layout_centerInParent="true"
            android:text="Tiny"
            android:textSize="8sp"
            android:background="@drawable/my_button_background"/>

    <Button android:id="@+id/big"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignParentBottom="true"
            android:layout_centerInParent="true"
            android:text="Biiiiiiig text!"
            android:textSize="30sp"
            android:background="@drawable/my_button_background"/>
    

버튼이 텍스트 주변에 딱 맞도록 layout_widthlayout_height 속성은 wrap_content로 설정됩니다.

그림 2에서는 위에 표시된 XML 및 NinePatch 이미지에서 렌더링된 두 개의 버튼을 보여줍니다. 버튼의 너비와 높이가 텍스트에 따라 변하는 방식 및 이에 맞게 배경 이미지가 확장되는 방식을 확인하세요.

크기가 작은 버튼과 보통 크기의 버튼 이미지

그림 2: XML 리소스와 NinePatch 그래픽을 사용하여 렌더링된 버튼

맞춤 드로어블

맞춤형 그림을 만들려면 Drawable 클래스(또는 서브클래스)를 확장하여 만들 수 있습니다.

가장 중요한 구현 메서드는 draw(Canvas)입니다. 이 메서드에서 그리기 안내를 제공하는 데 사용해야 하는 Canvas 객체를 제공하기 때문입니다.

다음 코드에서는 원을 그리는 Drawable의 간단한 서브클래스를 보여줍니다.

Kotlin

    class MyDrawable : Drawable() {
        private val redPaint: Paint = Paint().apply { setARGB(255, 255, 0, 0) }

        override fun draw(canvas: Canvas) {
            // Get the drawable's bounds
            val width: Int = bounds.width()
            val height: Int = bounds.height()
            val radius: Float = Math.min(width, height).toFloat() / 2f

            // Draw a red circle in the center
            canvas.drawCircle((width / 2).toFloat(), (height / 2).toFloat(), radius, redPaint)
        }

        override fun setAlpha(alpha: Int) {
            // This method is required
        }

        override fun setColorFilter(colorFilter: ColorFilter?) {
            // This method is required
        }

        override fun getOpacity(): Int =
            // Must be PixelFormat.UNKNOWN, TRANSLUCENT, TRANSPARENT, or OPAQUE
            PixelFormat.OPAQUE
    }
    

자바

    public class MyDrawable extends Drawable {
        private final Paint redPaint;

        public MyDrawable() {
            // Set up color and text size
            redPaint = new Paint();
            redPaint.setARGB(255, 255, 0, 0);
        }

        @Override
        public void draw(Canvas canvas) {
            // Get the drawable's bounds
            int width = getBounds().width();
            int height = getBounds().height();
            float radius = Math.min(width, height) / 2;

            // Draw a red circle in the center
            canvas.drawCircle(width/2, height/2, radius, redPaint);
        }

        @Override
        public void setAlpha(int alpha) {
            // This method is required
        }

        @Override
        public void setColorFilter(ColorFilter colorFilter) {
            // This method is required
        }

        @Override
        public int getOpacity() {
            // Must be PixelFormat.UNKNOWN, TRANSLUCENT, TRANSPARENT, or OPAQUE
            return PixelFormat.OPAQUE;
        }
    }
    

그런 다음 여기에 표시된 대로 ImageView와 같이 원하는 모든 위치에 드로어블을 추가할 수 있습니다.

Kotlin

    val myDrawing = MyDrawable()
    val image: ImageView = findViewById(R.id.imageView)
    image.setImageDrawable(myDrawing)
    image.contentDescription = resources.getString(R.string.my_image_desc)
    

자바

    MyDrawable mydrawing = new MyDrawable();
    ImageView image = findViewById(R.id.imageView);
    image.setImageDrawable(mydrawing);
    image.setContentDescription(getResources().getString(R.string.my_image_desc));
    

Android 7.0(API 레벨 24) 이상에서는 XML을 사용하여 다음과 같이 맞춤 드로어블의 인스턴스도 정의할 수 있습니다.

  • 완전한 클래스 이름을 XML 요소 이름으로 사용합니다. 이 접근 방식의 경우 맞춤 드로어블 클래스는 공용 최상위 레벨 클래스여야 합니다.
        <com.myapp.MyDrawable xmlns:android="http://schemas.android.com/apk/res/android"
            android:color="#ffff0000" />
        
  • drawable을 XML 태그 이름으로 사용하고 클래스 속성에서 완전한 클래스 이름을 지정합니다. 이 접근 방식은 공용 최상위 레벨 클래스와 공용 정적 내부 클래스 모두에 사용할 수 있습니다.
        <drawable xmlns:android="http://schemas.android.com/apk/res/android"
            class="com.myapp.MyTopLevelClass$MyDrawable"
            android:color="#ffff0000" />
        

드로어블에 색조 추가

Android 5.0(API 레벨 21) 이상에서는 알파 마스크로 정의된 비트맵 및 9-패치의 색조를 조정할 수 있습니다. 색상 리소스 또는 색상 리소스로 확인되는 테마 속성을 사용하여 색조를 조정할 수 있습니다(예: ?android:attr/colorPrimary). 일반적으로 이러한 애셋은 한 번만 만들고 테마에 맞게 자동으로 색상을 지정합니다.

setTint() 메서드를 사용하여 BitmapDrawable, NinePatchDrawable 또는 VectorDrawable 객체에 색조를 적용할 수 있습니다. 레이아웃에서 android:tintandroid:tintMode 속성을 사용하여 색조 색상 및 모드도 설정할 수 있습니다.

이미지의 주요 색상 추출

Android 지원 라이브러리에는 이미지에서 주요 색상을 추출하는 데 사용할 수 있는 Palette 클래스가 포함되어 있습니다. 색상에 액세스하도록 드로어블을 Bitmap으로 로드하여 Palette에 전달할 수 있습니다. 자세한 내용은 Palette API로 색상 선택을 참조하세요.