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의 존재 자체를 모르고 있었는데 프로젝트 덕분에 알게 됐다. 아직 사용법이 익숙치는 않은데 좀 더 이런저런 방향으로 써보고 나중에 포스팅해봐야 할 듯
- 학기 프로젝트랑 해커톤이 겹치면서 이 프로젝트에 신경을 하나도 못썼다. 이제 다시 시작!!!! 파이팅!!
📘 참고한 자료
- Android 공식문서 - Room을 사용하여 복잡한 데이터 참조
- 버그잡이님 블로그
- 꾸준하게님 블로그
- 한엽님 블로그
- Ready Kim님 블로그 (Moshi 라이브러리를 이용한 Converter)
'앱 제작 > SSAFY 서명 앱' 카테고리의 다른 글
#6 해상도 별 레이아웃 대응하기 (2) | 2021.12.05 |
---|---|
#5 서명 저장하기와 불러오는 방법 고민하기 (10) | 2021.12.04 |
#3 프로젝트 규칙 설정 및 수정사항 (1) | 2021.11.11 |
#2 요구사항 분석과 UI 프레임 구성 (2) | 2021.11.04 |
#1 서명 앱 만들기 (구상하기) (8) | 2021.11.02 |
댓글