상세 컨텐츠

본문 제목

[안드로이드] View 터치 시 선 그리기 (Canvas, Paint, View)

Android

by choiDev 2020. 11. 24. 03:36

본문

터치 시 canvas에 선을 그리는 예제 코드를 만들었습니다.
실제 동작은 아래와 같이 진행됩니다.

터치 시 선을 그리는 예제

 

예제 코드가 아래쪽에 기재되어있으며, 혹시 전체 프로젝트를 보고싶으신 분들을 위해

git 주소를 남기니 git에서 확인하시면 전체적으로 간결하게 보실 수 있습니다.

Git repository URL : github.com/OreoChoi/SimpleDrawApplication

예제 코드 [layout.xml]

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/lo_canvas"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <com.google.android.material.floatingactionbutton.FloatingActionButton
        android:id="@+id/fb_open"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:background="@drawable/ic_floating_background"
        android:elevation="8dp"
        android:layout_marginBottom="15dp"
        android:src="@drawable/ic_dir"
        app:fabSize="mini"
        app:layout_constraintBottom_toTopOf="@id/fb_save"
        app:layout_constraintHorizontal_bias="0.9"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"/>

    <com.google.android.material.floatingactionbutton.FloatingActionButton
        android:id="@+id/fb_save"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:background="@drawable/ic_floating_background"
        android:elevation="8dp"
        android:layout_marginBottom="15dp"
        android:src="@drawable/ic_save"
        app:fabSize="mini"
        app:layout_constraintBottom_toTopOf="@id/fb_eraser"
        app:layout_constraintHorizontal_bias="0.9"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"/>

    <com.google.android.material.floatingactionbutton.FloatingActionButton
        android:id="@+id/fb_eraser"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:background="@drawable/ic_floating_background"
        android:elevation="8dp"
        android:layout_marginBottom="15dp"
        android:src="@drawable/ic_eraser"
        app:fabSize="mini"
        app:layout_constraintBottom_toTopOf="@id/fb_pen"
        app:layout_constraintHorizontal_bias="0.9"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintVertical_bias="0.76" />

    <com.google.android.material.floatingactionbutton.FloatingActionButton
        android:id="@+id/fb_pen"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:background="@drawable/ic_floating_background"
        android:elevation="8dp"
        android:src="@drawable/ic_pen"
        app:fabSize="mini"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintVertical_bias="0.9"
        app:layout_constraintHorizontal_bias="0.9"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent" />


</androidx.constraintlayout.widget.ConstraintLayout>

 

예제 코드 [Acitivity]

/**
 * jhChoi - 201123
 * 그리기 화면입니다.
 * (그리기, 지우기, 저장, 호출)등이 가능합니다.
 */
public class DrawActivity extends AppCompatActivity {
    private DrawCanvas drawCanvas;
    private FloatingActionButton fbPen;             //펜 모드 버튼
    private FloatingActionButton fbEraser;          //지우개 모드 버튼
    private FloatingActionButton fbSave;            //그림 저장 버튼
    private FloatingActionButton fbOpen;            //그림 호출 버튼
    private ConstraintLayout canvasContainer;       //캔버스 root view

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_draw);
        findId();
        canvasContainer.addView(drawCanvas);
        setOnClickListener();
    }

    /**
     * jhChoi - 201124
     * View Id를 셋팅합니다.
     */
    private void findId() {
        canvasContainer = findViewById(R.id.lo_canvas);
        fbPen = findViewById(R.id.fb_pen);
        fbEraser = findViewById(R.id.fb_eraser);
        fbSave = findViewById(R.id.fb_save);
        fbOpen = findViewById(R.id.fb_open);
        drawCanvas = new DrawCanvas(this);
    }

    /**
     * jhChoi - 201124
     * OnClickListener Setting
     */
    private void setOnClickListener() {
        fbPen.setOnClickListener((v)->{
            drawCanvas.changeTool(DrawCanvas.MODE_PEN);
        });

        fbEraser.setOnClickListener((v)->{
            drawCanvas.changeTool(DrawCanvas.MODE_ERASER);
        });

        fbSave.setOnClickListener((v)->{
            drawCanvas.invalidate();
            Bitmap saveBitmap = drawCanvas.getCurrentCanvas();
            CanvasIO.saveBitmap(this, saveBitmap);
        });

        fbOpen.setOnClickListener((v)->{
            drawCanvas.init();
            drawCanvas.loadDrawImage = CanvasIO.openBitmap(this);
            drawCanvas.invalidate();
        });
    }

    /**
     * jhChoi - 201124
     * Pen을 표현할 class입니다.
     */
    class Pen {
        public static final int STATE_START = 0;        //펜의 상태(움직임 시작)
        public static final int STATE_MOVE = 1;         //펜의 상태(움직이는 중)
        float x, y;                                     //펜의 좌표
        int moveStatus;                                 //현재 움직임 여부
        int color;                                      //펜 색
        int size;                                       //펜 두께

        public Pen(float x, float y, int moveStatus, int color, int size) {
            this.x = x;
            this.y = y;
            this.moveStatus = moveStatus;
            this.color = color;
            this.size = size;
        }

        /**
         * jhChoi - 201124
         * 현재 pen의 상태가 움직이는 상태인지 반환합니다.
         */
        public boolean isMove() {
            return moveStatus == STATE_MOVE;
        }
    }

    /**
     * jhChoi - 201124
     * 그림이 그려질 canvas view
     */
    class DrawCanvas extends View {
        public static final int MODE_PEN = 1;                     //모드 (펜)
        public static final int MODE_ERASER = 0;                  //모드 (지우개)
        final int PEN_SIZE = 3;                                   //펜 사이즈
        final int ERASER_SIZE = 30;                               //지우개 사이즈

        ArrayList<Pen> drawCommandList;                           //그리기 경로가 기록된 리스트
        Paint paint;                                              //펜
        Bitmap loadDrawImage;                                     //호출된 이전 그림
        int color;                                                //현재 펜 색상
        int size;                                                 //현재 펜 크기

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

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

        public DrawCanvas(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
            super(context, attrs, defStyleAttr);
            init();
        }

        /**
         * jhChoi - 201124
         * 그리기에 필요한 요소를 초기화 합니다.
         */
        private void init() {
            paint = new Paint(Paint.ANTI_ALIAS_FLAG);
            drawCommandList = new ArrayList<>();
            loadDrawImage = null;
            color = Color.BLACK;
            size = PEN_SIZE;
        }

        /**
         * jhChoi - 201124
         * 현재까지 그린 그림을 Bitmap으로 반환합니다.
         */
        public Bitmap getCurrentCanvas() {
            Bitmap bitmap = Bitmap.createBitmap(this.getWidth(), this.getHeight(), Bitmap.Config.ARGB_8888);
            Canvas canvas = new Canvas(bitmap);
            this.draw(canvas);
            return bitmap;
        }

        /**
         * jhChoi - 201124
         * Tool type을 (펜 or 지우개)로 변경합니다.
         * */
        private void changeTool(int toolMode) {
            if (toolMode == MODE_PEN) {
                this.color = Color.BLACK;
                size = PEN_SIZE;
            } else {
                this.color = Color.WHITE;
                size = ERASER_SIZE;
            }
            paint.setColor(color);
        }

        @Override
        protected void onDraw(Canvas canvas) {
            canvas.drawColor(Color.WHITE);

            if (loadDrawImage != null) {
                canvas.drawBitmap(loadDrawImage, 0, 0, null);
            }

            for (int i = 0; i < drawCommandList.size(); i++) {
                Pen p = drawCommandList.get(i);
                paint.setColor(p.color);
                paint.setStrokeWidth(p.size);

                if (p.isMove()) {
                    Pen prevP = drawCommandList.get(i - 1);
                    canvas.drawLine(prevP.x, prevP.y, p.x, p.y, paint);
                }
            }
        }

        @Override
        public boolean onTouchEvent(MotionEvent e) {
            int action = e.getAction();
            int state = action == MotionEvent.ACTION_DOWN ? Pen.STATE_START : Pen.STATE_MOVE;
            drawCommandList.add(new Pen(e.getX(), e.getY(), state, color, size));
            invalidate();
            return true;
        }
    }
}

관련글 더보기