춤추는 개발자

[Android Studio] 미니 그림판 제작하기 본문

Android/study_til

[Android Studio] 미니 그림판 제작하기

Heon_9u 2020. 12. 1. 11:59
728x90
반응형

미니 그림판 제작 Project

 

 이번에는 xml파일과 View 클래스를 함꼐 활용해서 미니 그림판을 제작하겠습니다.

xml파일 윗 부분에는 붓의 크기 조절 및 색상 선택 버튼, 초기화 버튼을 배치하고, 아래 부분은 커스텀 뷰가 오도록 하겠습니다. 이를 위해 View 클래스를 상속받는 클래스를 내부 클래스가 아닌 독립된 클래스로 제작하겠습니다.

 

 처음부터 activity_main.xmlMainActivity.java를 코딩하면 에러가 뜨기 때문에 MyView 클래스를 먼저 만드는 것을 추천드립니다.

 

package com.example.mathgraphic;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;

public class MyView extends View {
    Paint p1 = new Paint();
    Paint p2 = new Paint();
    Paint p3 = new Paint();
    Paint p4 = new Paint();
    Paint p5 = new Paint();

    static int myData_x[] = new int[30000];
    static int myData_y[] = new int[30000];
    static int myData_color[] = new int[30000];

    static int radius = 20;
    static int whatColor = 0;

    static int dataNumber = 0;
    int mx, my;

    public MyView(Context context, AttributeSet attr) {
        super(context);
        p1.setColor(Color.RED);
        p2.setColor(Color.BLUE);
        p3.setColor(Color.YELLOW);
        p4.setColor(Color.GREEN);
        p5.setColor(Color.BLACK);

        myData_x[0] = 0;
        myData_y[0] = 0;
        myData_color[0] = 5;
    }

    @Override
    public void onDraw(Canvas canvas) {
        for(int i=1; i<=dataNumber; i++) {
            if(myData_color[i] == 1) {
                canvas.drawCircle(myData_x[i], myData_y[i], radius, p1);
            }

            if(myData_color[i] == 2) {
                canvas.drawCircle(myData_x[i], myData_y[i], radius, p2);
            }

            if(myData_color[i] == 3) {
                canvas.drawCircle(myData_x[i], myData_y[i], radius, p3);
            }

            if(myData_color[i] == 4) {
                canvas.drawCircle(myData_x[i], myData_y[i], radius, p4);
            }

            if(myData_color[i] == 5) {
                canvas.drawCircle(myData_x[i], myData_y[i], radius, p5);
            }
        }
        invalidate();
    }

    public void saveData() {
        myData_x[dataNumber] = mx;
        myData_y[dataNumber] = my;
        myData_color[dataNumber] = whatColor;
    }

    public boolean onTouchEvent(MotionEvent event) {
        mx = (int) event.getX();
        my = (int) event.getY();

        dataNumber += 1;
        saveData();

        return true;
    }

    static public void clearPaint() {
        for(int i=1; i<myData_x.length; i++) {
            myData_x[i] = 0;
            myData_y[i] = 0;
            myData_color[i] = 0;
        }
    }
}

<MyView.java>

 

static 변수는 MainActivity를 다룰 때 설명하겠습니다. myData_xmyDaya_y에 지금까지 그렸던 그림의 위치(x, y)값을 저장합니다. 이때 그림판을 터치하면 발생하는 onTouchEvent로 마우스 커서의 x, y값을 저장하고 saveData() 메서드를 호출해 배열에 다시 저장합니다.

 onDraw 메소드를 보면 for문을 통해 dataNumber만큼 반복해 그림을 그려줍니다. 이때 if문으로 분기하지 않고 if, elseif나 switch문을 사용해도 좋습니다.

 onDraw 메소드는 앱을 실행할 때와 화면에 다시 그려줄 때, 실행됩니다. 하지만 onDraw가 호출된 이후 화면은 더이상 갱신되지 않을 상태로 남아있게 되는데 이떄 화면을 다시 그려주기위해 invalidate 메서드를 호출합니다.

 invalidate란 화면을 모두 지우고 새로 그리는 것을 말합니다. 즉, 모든 뷰를 무효화하고 다시 onDraw를 호출해 화면을 실시간으로 갱신해주는 역할을 합니다.

 

추가) invalidate는 thread내부에서 작동할 수 없어서 타이머를 이용한 애니메이션을 구현할 수 없습니다. 이때 사용되는 것이 postInvalidate입니다.

 

이제 MainActivity를 만들겠습니다.

 

package com.example.mathgraphic;

import androidx.appcompat.app.AppCompatActivity;

import android.content.Context;
import android.os.Bundle;
import android.view.View;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }

    public void increaseValue(View v) {
        MyView.radius += 2;
        System.out.println(MyView.radius);
    }

    public void decreaseValue(View v) {
        MyView.radius -= 2;
        System.out.println(MyView.radius);
    }

    public void setRed(View v) {
        MyView.whatColor = 1;
        System.out.println(MyView.whatColor);
    }

    public void setBlue(View v) {
        MyView.whatColor = 2; System.out.println(MyView.whatColor);
    }

    public void setYellow(View v) {
        MyView.whatColor = 3; System.out.println(MyView.whatColor);
    }

    public void setGreen(View v) {
        MyView.whatColor = 4; System.out.println(MyView.whatColor);
    }

    public void setBlack(View v) {
        MyView.whatColor = 5; System.out.println(MyView.whatColor);
    }

    public void clearPaint(View v) {
        MyView.clearPaint();
    }
}

 <MainActivity.java>

 

 여기서는 MyView 클래스의 객체 생성없이 변수와 함수를 조절하고 있습니다. 이는 radius, whatColor, clearPaint()를 static으로 선언했기 때문입니다. static 변수는 클래스 내에서 같은 주소값을 가지고 공유하기 때문에 따로 객체 생성없이 활용할 수 있습니다. 그래서 클래스명.변수명 or 클래스명.함수명으로 사용할 수 있습니다.

 

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    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:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".MainActivity">

    <LinearLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="horizontal">

        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="+"
            android:layout_weight="1"
            android:onClick="increaseValue"/>

        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="-"
            android:layout_weight="1"
            android:onClick="decreaseValue"/>

        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="빨강"
            android:layout_weight="1"
            android:onClick="setRed"/>

        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="파랑"
            android:layout_weight="1"
            android:onClick="setBlue"/>

        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="노랑"
            android:layout_weight="1"
            android:onClick="setYellow"/>

        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="초록"
            android:layout_weight="1"
            android:onClick="setGreen"/>

        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="검정"
            android:layout_weight="1"
            android:onClick="setBlack"/>

        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="초기화"
            android:layout_weight="1"
            android:onClick="clearPaint"/>

    </LinearLayout>

    <com.example.mathgraphic.MyView
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>

</LinearLayout>

<activity_main.xml>

 

마지막은 xml문입니다. LinearLayout으로 Button을 정렬하고 마지막에 com.example.mathgraphic.MyView으로 그림판을 구성했습니다. xml문에서 새로운 View로 레이아웃을 나눌 때는 위처럼 full name을 작성해줘야 합니다.

 

 

실행해서 직접 사용한 결과, drawCircle대신 drawLine을 사용하면 더 깔끔하게 그림판을 사용할 수 있을 것 같습니다. 또한, 붓 크기를 결정하는 +, -가 기존 그림에도 적용되기 때문에 myData_radius에 저장해서 사용한다면 해결할 수 있습니다.

 

 

 

이상으로 미니 그림판 제작 프로젝트를 마치겠습니다. 다음 포스팅은 DB를 활용한 영어 단어 앱 만들기와 공공 데이터를 활용한 버스 노선 앱 만들기를 진행해보겠습니다. 최종적으로 만들고자 하는 앱에 필요한 두 가지 기능으로 모두 완성한 후, 저만의 앱을 만들 계획입니다. 

728x90
반응형