티스토리 뷰

Graphic 그리기가 가능한 요소들을 Drawable 객체로 만들어 그릴 수도 있다. Drawable 객체는 그릴 수 있는 모든 것을 의미하는데, 대표적으로 Shape, Bitmap, Picture, LayerDrawable 등이 있다.

 

그리기 객체의 형태로는 PNG, JPEG 이미지 등을 표현하는 Bitmap, 이미지가 자동으로 늘어나는 부분을 설정하여 사용하는 NinePatch, 도형 그리기가 가능한 Shape, 세로축의 순서에 따라 그리는 Layer 등이 있다.

 

그리기 메서드를 사용하면 다양한 그래픽을 그릴 수 있다는 것은 직전 포스팅에서 실습했다. 그런데 왜 굳이 Drawable 객체를 사용하여 그래픽을 그릴 필요가 있는 걸까? 

 

그래픽을 그리는 하나의 단위를 그리기 객체로 만들어 두면 각각의 그래픽 그리기 작업을 독립적인 객체로 나누어 관리할 수 있는 장점이 생긴다. 또한 해당 객체에 애니메이션을 적용하기도 매우 편하기 때문에 사용한다.

 

이러한 Drawable 객체를 사용하는 방법은 크게 세 가지로 나눌 수 있다. 

 

구분 설명
리소스 파일의 사용 프로젝트 리소스에 이미지와 같은 파일을 포함시킨 후 읽어 사용
XML로 정의하여 사용 그리기 객체의 속성을 정의한 XML 파일을 정의하여 사용한다.
소스 코드에서 객체를 만들어 사용 소스 코드에서 new 연산자를 이용하여 그리기 객체를 만든 후 사용한다.

Drawalbe 객체를 만들고 다양한 효과와 Path를 적용하는 예제를 확인해보자.

 

우선 res/values/colors.xml 파일에 사용할 생상 정보를 작성해주자.

◎colors.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <color name="purple_200">#FFBB86FC</color>
    <color name="purple_500">#FF6200EE</color>
    <color name="purple_700">#FF3700B3</color>
    <color name="teal_200">#FF03DAC5</color>
    <color name="teal_700">#FF018786</color>
    <color name="black">#FF000000</color>
    <color name="white">#FFFFFFFF</color>

    <color name="color01">#FF000000</color>
    <color name="color02">#FF888888</color>
    <color name="color03">#FF333333</color>
</resources>

 

다음으로 아래와 같이 Drawable 객체를 생성한다. 딱히 어려운 코드는 없으나 API 29 버전 이후부터는

Activity.getWindowManager().getDefaultDisplay();

 가 deprecated 되었기 때문에 해당 버전을 분기하는 코드를 추가했다.

 

◎CustomViewDrawalbe.java

package com.example.customviewdrawable.custom.drawable;

import android.app.Activity;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.*;
import android.graphics.drawable.ShapeDrawable;
import android.graphics.drawable.shapes.RectShape;
import android.os.Build;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.view.*;
import androidx.annotation.Nullable;
import com.example.customviewdrawable.R;

public class CustomViewDrawable extends View {

    private ShapeDrawable upperDrawable;
    private ShapeDrawable lowerDrawable;

    public CustomViewDrawable(Context context) {
        super(context);
        init(context);
    }

    public CustomViewDrawable(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        init(context);
    }

    private void init(Context context) {
        Activity activity = (Activity) context;        // 빌드 버전에 따른 View의 폭과 높이 확인
        int width = 0;
        int height = 0;

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
            WindowMetrics windowMetrics = activity.getWindowManager().getCurrentWindowMetrics();
            Insets insets = windowMetrics.getWindowInsets()
                    .getInsetsIgnoringVisibility(WindowInsets.Type.systemBars());
            width = windowMetrics.getBounds().width();
            height = windowMetrics.getBounds().height();
        } else {
            DisplayMetrics displayMetrics = new DisplayMetrics();
            Display display = activity.getWindowManager().getDefaultDisplay();
            width = display.getWidth();
            height = display.getHeight();
        }

        Resources curRes = getResources();
        int blackColor = curRes.getColor(R.color.color01 ,null);
        int grayColor = curRes.getColor(R.color.color02 ,null);
        int darkGrayColor = curRes.getColor(R.color.color03 ,null);

        upperDrawable = new ShapeDrawable();

        RectShape rectangle = new RectShape();
        rectangle.resize(width, height*2/3);
        upperDrawable.setShape(rectangle);
        upperDrawable.setBounds(0, 0, width, height*2/3);

        // LinearGradient 객체 생성
        LinearGradient gradient = new LinearGradient(0, 0, 0, height * 2 / 3,
                grayColor, blackColor, Shader.TileMode.CLAMP);

        Paint paint = upperDrawable.getPaint();

        // paint 객체에 새로 생성한 LinearGradient 객체를 shape로 설정
        paint.setShader(gradient);

        lowerDrawable = new ShapeDrawable();

        RectShape rectangle2 = new RectShape();
        rectangle2.resize(width, height*1/3);
        lowerDrawable.setShape(rectangle2);
        lowerDrawable.setBounds(0, height*2/3, width, height);

        LinearGradient gradient2 = new LinearGradient(0, 0, 0, height*1/3,
                blackColor, darkGrayColor, Shader.TileMode.CLAMP);

        Paint paint2 = lowerDrawable.getPaint();
        paint2.setShader(gradient2);

    }

    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        // Drawable 객체를 Canvas에 그리기
        upperDrawable.draw(canvas);
        lowerDrawable.draw(canvas);

        // Cap.BUTT와 Join.MITER를 페인트 객체에 적용
        Paint pathPaint = new Paint();
        pathPaint.setAntiAlias(true);
        pathPaint.setColor(Color.YELLOW);
        pathPaint.setStyle(Paint.Style.STROKE);
        pathPaint.setStrokeWidth(16.0F);
        pathPaint.setStrokeCap(Paint.Cap.BUTT);
        pathPaint.setStrokeJoin(Paint.Join.MITER);

        // Path 객체 생성
        Path path = new Path();
        path.moveTo(20, 20);
        path.lineTo(120, 20);
        path.lineTo(160, 90);
        path.lineTo(180, 80);
        path.lineTo(200, 120);

        // Path 객체 그리기
        canvas.drawPath(path, pathPaint);

        // Cap.ROUND와 Join.ROUND를 페인트 객체에 적용
        pathPaint.setColor(Color.WHITE);
        pathPaint.setStrokeCap(Paint.Cap.ROUND);
        pathPaint.setStrokeJoin(Paint.Join.ROUND);

        // offset을 주어 이동한 뒤 다시 그리기
        path.offset(30, 120);
        canvas.drawPath(path, pathPaint);

        // Cap.SQUARE와 Join.BEVEL 속성을 적용
        pathPaint.setColor(Color.CYAN);
        pathPaint.setStrokeCap(Paint.Cap.SQUARE);
        pathPaint.setStrokeJoin(Paint.Join.BEVEL);

        path.offset(30, 120);
        canvas.drawPath(path, pathPaint);

    }

}

위 코드에서 사용한 LinearGradient 와 Path 객체의 속성 정보는 LinearGradientPath  에서 확인할 수 있다.

 

이제, main_activity.xml 파일에서 해당 drawable 객체를 삽입하고, MainActivity 에서 생성자를 호출해주면 끝이 난다.

 

◎activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".MainActivity">
    <com.example.customviewdrawable.custom.drawable.CustomViewDrawable
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" android:id="@+id/customViewDrawable"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent" app:layout_constraintHorizontal_bias="0.0"
            app:layout_constraintTop_toTopOf="parent" app:layout_constraintBottom_toBottomOf="parent"/>

</androidx.constraintlayout.widget.ConstraintLayout>

 

◎MainActivity.java

package com.example.customviewdrawable;

import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import com.example.customviewdrawable.custom.drawable.CustomViewDrawable;

public class MainActivity extends AppCompatActivity {

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

        CustomViewDrawable view = new CustomViewDrawable(this);
        setContentView(R.layout.activity_main);
    }
}

Comments