본문 바로가기
앱 제작/SSAFY 서명 앱

#4 그림 그리기와 TypeConverter 사용

by Kim Juhwan 2021. 12. 2.

1. 그림 그리기
   1-1. DrawSample.kt
   1-2. fragment_sign.xml

   1-3. SignFragment.kt
2. TypeConverter

 

 

 


 

 

2021/11/18 개발 내용

1. 그림 그리기

1-1. DrawSample.kt

싸인을 할 수 있는 페이지를 제작하였다.

 

안드로이드에서 그림판을 만들어 본 경험이 있어서 싸인 기능을 구현하는 데 도움이 많이 됐다.

싸인 특성상 색도 검은색으로 통일, 굵기도 통일, 모양도 통일이라서 간단했다.

버튼은 간단하게 전체 지우기 버튼 / 저장 버튼만 넣어두었다.

(아이콘이 안 이뻐서 마음에 안 들지만 디자인은 나중에 생각하자)

 

class DrawSample : View {
    constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs) { }

    var list = arrayListOf<Point>()

    var paint: Paint
    init {
        paint = Paint()
        paint.strokeWidth = 10F
        paint.color = Color.BLACK
    }

    override fun onDraw(canvas: Canvas) {
        super.onDraw(canvas)

        list.forEachIndexed { index, point ->
            if(index > 1 && point.isContinue) {
                canvas.drawLine(list[index-1].x, list[index-1].y, point.x, point.y, paint)
            }
        }
    }

    override fun onTouchEvent(event: MotionEvent?): Boolean {
        when(event?.action){
            MotionEvent.ACTION_DOWN -> {
                list.add(Point(event?.x, event?.y, false))
            }
            MotionEvent.ACTION_MOVE -> {
                list.add(Point(event?.x, event?.y, true))
            }
            MotionEvent.ACTION_UP -> {
                list.add(Point(event?.x, event?.y, true))
            }
        }
        invalidate() // onDraw를 호출함
        return true
    }

    fun reset() {
        list.clear()
        invalidate()
    }

    fun getSign() : ArrayList<Point> {
        return list
    }

    fun setSign(sign: List<Point>) {
        list.addAll(sign)
        invalidate()
    }
}

싸인을 하면 터치했던 좌표들이 list에 차곡차곡 들어가게 되는데

이를 내부 DB에 저장할 때 써야 하므로 getSign 함수를 만들어 주었고

나중에 DB에서 꺼내와서 그 좌표값대로 그리는 것도 해야 하므로 setSign 함수도 만들어 주었다.

 

1-2. fragment_sign.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:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".src.main.sign.SignFragment">

    <ImageView
        android:id="@+id/fragment_sign_iv_delete"
        android:layout_width="50dp"
        android:layout_height="50dp"
        android:padding="8dp"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:tint="@color/black"
        android:src="@drawable/ic_sign_delete_btn" />

    <ImageView
        android:id="@+id/fragment_sign_iv_save"
        android:layout_width="50dp"
        android:layout_height="50dp"
        android:padding="8dp"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:tint="@color/black"
        android:src="@drawable/ic_sign_save_btn" />

    <View
        android:id="@+id/fragment_sign_line"
        android:layout_width="match_parent"
        android:layout_height="1dp"
        android:background="@color/black_low_emphasis"
        app:layout_constraintTop_toBottomOf="@+id/fragment_sign_iv_delete"
        app:layout_constraintEnd_toStartOf="@+id/fragment_sign_iv_delete" />

    <com.ssafy.ssaign.src.main.sign.DrawSample
        android:id="@+id/draw"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintTop_toBottomOf="@id/fragment_sign_line" />
</androidx.constraintlayout.widget.ConstraintLayout>

앞서 만든 DrawSample은 View를 상속받았으므로 xml에 추가할 수 있는 커스텀 뷰 형태가 된다.

xml에 추가해주었다.

 

1-3. SignFragment.kt

class SignFragment : BaseFragment<FragmentSignBinding>(FragmentSignBinding::bind, R.layout.fragment_sign) {
    lateinit var draw: DrawSample

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

        // 가로 모드 고정
        activity?.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE)
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        draw = binding.draw

        binding.fragmentSignIvDelete.setOnClickListener {
            draw.reset()
        }

        binding.fragmentSignIvSave.setOnClickListener {
            CoroutineScope(Dispatchers.Main).launch {
                val job = CoroutineScope(Dispatchers.Default).async {
                    db.signDao().insertSign(Sign(0, draw.getSign()))
                }

                job.join()
                showToastMessage("저장 완료")
                (context as MainActivity).onChangeFragement(4)
            }
        }
    }
}

싸인을 편하게 할 수 있도록 가로 모드로 고정해두었고

Default 스레드한테 작업을 맡기고 저장 작업이 완료되면 다음 프래그먼트로 넘어가게 했다.

코루틴을 사용하지 않으면 앱이 죽어버린다 :(

 

2. TypeConverter

class SignTypeConverters {
    @TypeConverter
    fun PointToJson(value: List<Point>): String = Gson().toJson(value)

    @TypeConverter
    fun JsonToPoint(value: String) = Gson().fromJson(value, Array<Point>::class.java).toList()
}

이 부분에서 많이 애를 먹었다.

Room에 대해서 포스팅을 한 적이 있긴 했지만 이번에는 기본 자료형이 아니라 사용자 정의 클래스 형태를 DB에 저장해야 하기 때문이다.

찾아보니까 TypeConverter를 사용하는 방법이 있었다.

쉽게 말하자면 저장할 때는 원하는 데이터 타입을 -> JSON 형태로 바꿔서 넣고

불러올 때는 JSON 형태를 -> 원하는 데이터 타입으로 변환해서 꺼낼 수 있도록 해주는 놈이다.

 

나는 Point 타입을 변환하길 원했으므로 위와 같은 TypeConverter 코드를 작성했다.

코드를 작성하면서 참고한 글은 이 포스팅 맨 아래에 기재해두었다.

 

신기한 점은 내가 만든 저 PointToJson이나 JsonToPoint를 내가 필요할 때 직접 호출해서 사용하는 게 아니라

Dao 파일에서 저장하거나 불러오는 작업을 할 때 저 데이터 타입(여기에서는 Point)을 사용하면 지가 알아서 변환해서 쓴다는 것이다.

나는 코드만 작성해두면, 어노테이션을 보고 찾아와서 알아서 함수를 써가지고 변환하다는 것.

기특하다.

 

테이블에 이런식으로 들어간다.

 

테이블에는 아까 말했듯이 이렇게 JSON 형태로 들어간다.

다소 복잡하게 보이지만 어차피 꺼낼 때 Converter가 변환해주니까 걱정하지 않아도 된다.

 

 


💡 느낀 점

  • 커스텀 뷰 만들어서 사용하는 것도 재밌는 것 같다!
  • TypeConverter의 존재 자체를 모르고 있었는데 프로젝트 덕분에 알게 됐다. 아직 사용법이 익숙치는 않은데 좀 더 이런저런 방향으로 써보고 나중에 포스팅해봐야 할 듯
  • 학기 프로젝트랑 해커톤이 겹치면서 이 프로젝트에 신경을 하나도 못썼다. 이제 다시 시작!!!! 파이팅!!

📘 참고한 자료


 

 

 

 

반응형

댓글