Skip to content

Commit

Permalink
[둘리] 뷰 챌린지 미션 2단계 제출합니다. (#38)
Browse files Browse the repository at this point in the history
* feat: button bindingAdapter로 isSelected 생성

* feat: 선택된 버튼 색깔 생성

* feat: 브러쉬 요소 추가

* feat: 브러쉬 선택 기능 UI 추가

* feat: 함수 일부 네이밍 변경 및 Circle, Rectangle 그리기 기능 추가

* refactor: 함수 분리 및 중복 코드 삭제

* feat: 지우개 기능 구현

* refactor: Drawing 클래스에서 Path, Paint 복사하도록 수정

* feat: 전체 삭제, undo, redo 구현

* refactor: 함수 순서 수정

* refactor: SettingState sealed class -> enum class로 변경, 상수 위치 이동

* refactor: width로 굵기 네이밍 통일

* refactor: Brush -> Tool로 네이밍 변경

* refactor: enum class 상수 네이밍 변경

* refactor: Drawing 클래스 세분화

* refactor: tool이 drawing 객체를 리턴하도록 수정

* refactor: CanvasView가 하던 일을 Drawing 클래스로 이동

* refactor: undo, redo 코드 줄 수 줄이기

* design: 도구에 따른 설정 화면 변경

* refactor: 도구에 따라 버튼 동적 생성

* refactor: brushes -> tools로 네이밍 수정

* refactor: 함수명 수정

* refactor: tool 바꿀 때 설정창 닫도록 수정

* fix: Rectangle 버그 수정

* refactor: 도구 모음 일치

* refactor: tool -> drawingTool로 네이밍 변경

* refactor: Drawings 일급컬렉션 생성

* refactor: 팩토리 함수 삭제
  • Loading branch information
hyemdooly committed Oct 11, 2023
1 parent a7edc74 commit 38b960c
Show file tree
Hide file tree
Showing 23 changed files with 438 additions and 97 deletions.
1 change: 1 addition & 0 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
plugins {
id("com.android.application")
id("org.jetbrains.kotlin.android")
id("kotlin-kapt")
}

android {
Expand Down
54 changes: 46 additions & 8 deletions app/src/main/java/woowacourse/paint/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@ import android.os.Bundle
import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
import com.google.android.material.slider.RangeSlider
import woowacourse.paint.canvas.DrawingTool
import woowacourse.paint.databinding.ActivityMainBinding
import woowacourse.paint.databinding.ItemToolBinding
import woowacourse.paint.utils.toUiModel

class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
Expand All @@ -13,12 +16,15 @@ class MainActivity : AppCompatActivity() {
private val adapter = ColorsAdapter { model ->
viewModel.pickColor(model)
}
private val toolButtons = mutableListOf<ItemToolBinding>()

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setupBinding()
setupViewModel()
setupToolbar()
setupCanvas()
setupTools()
setupColors()
setupWidthSlider()
}
Expand All @@ -40,6 +46,42 @@ class MainActivity : AppCompatActivity() {
adapter.submitList(colors)
}
}
viewModel.selectedDrawingTool.observe(this) { tool ->
canvasView.setupTools(tool)
changeButtonSelectedStatus(tool)
}
}

private fun changeButtonSelectedStatus(drawingTool: DrawingTool) {
toolButtons.forEach { button ->
button.root.isSelected = button.drawingTool == drawingTool
}
viewModel.setSettingState(PaintChangingState.NOTHING)
}

private fun setupToolbar() {
setSupportActionBar(binding.tbPaint)
binding.ivRedo.setOnClickListener {
canvasView.redo()
}
binding.ivUndo.setOnClickListener {
canvasView.undo()
}
binding.ivClear.setOnClickListener {
canvasView.eraseAll()
}
}

private fun setupTools() {
viewModel.drawingTools.forEach { toolAssigned ->
val toolButtonBinding = ItemToolBinding.inflate(layoutInflater, binding.llTools, true)
with(toolButtonBinding) {
onClick = viewModel::pickTool
drawingTool = toolAssigned
name = getString(toolAssigned.toUiModel().toolNameId)
}
toolButtons.add(toolButtonBinding)
}
}

private fun setupCanvas() {
Expand Down Expand Up @@ -67,17 +109,13 @@ class MainActivity : AppCompatActivity() {
}

private fun setupWidthSlider() {
binding.rsThicknessChanger.valueFrom = minWidth
binding.rsThicknessChanger.valueTo = maxWidth
binding.rsThicknessChanger.addOnChangeListener(
binding.rsWidthChanger.valueFrom = MainViewModel.MIN_WIDTH
binding.rsWidthChanger.valueTo = MainViewModel.MAX_WIDTH
binding.rsWidthChanger.setValues(viewModel.width.value)
binding.rsWidthChanger.addOnChangeListener(
RangeSlider.OnChangeListener { _, value, _ ->
viewModel.pickWidth(value)
},
)
}

companion object {
private const val minWidth = 0f
private const val maxWidth = 100f
}
}
29 changes: 17 additions & 12 deletions app/src/main/java/woowacourse/paint/MainViewModel.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,13 @@ import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.Transformations
import androidx.lifecycle.ViewModel
import woowacourse.paint.canvas.DrawingTool
import woowacourse.paint.canvas.PaletteColor
import woowacourse.paint.model.ColorUiModel

class MainViewModel : ViewModel() {
private val _paintChangingState =
MutableLiveData<PaintChangingState>(PaintChangingState.Nothing)
MutableLiveData(PaintChangingState.NOTHING)
val paintChangingState: LiveData<PaintChangingState>
get() = _paintChangingState

Expand All @@ -18,6 +19,12 @@ class MainViewModel : ViewModel() {
val colors: LiveData<List<ColorUiModel>>
get() = _colors

val drawingTools = DrawingTool.values().toList()

private var _selectedTool = MutableLiveData(drawingTools.first())
val selectedDrawingTool: LiveData<DrawingTool>
get() = _selectedTool

val selectedColor: LiveData<PaletteColor>
get() = Transformations.map(_colors) { colors ->
colors.firstOrNull { it.isPicked }?.color ?: DEFAULT_SELECTED_COLOR
Expand All @@ -27,20 +34,16 @@ class MainViewModel : ViewModel() {
val width: LiveData<Float>
get() = _width

fun setColorSettingState() {
if (_paintChangingState.value == PaintChangingState.ColorChanging) {
_paintChangingState.value = PaintChangingState.Nothing
fun setSettingState(state: PaintChangingState) {
if (_paintChangingState.value == state) {
_paintChangingState.value = PaintChangingState.NOTHING
return
}
_paintChangingState.value = PaintChangingState.ColorChanging
_paintChangingState.value = state
}

fun setWidthSettingState() {
if (_paintChangingState.value == PaintChangingState.WidthChanging) {
_paintChangingState.value = PaintChangingState.Nothing
return
}
_paintChangingState.value = PaintChangingState.WidthChanging
fun pickTool(drawingTool: DrawingTool) {
_selectedTool.value = drawingTool
}

fun pickColor(model: ColorUiModel) {
Expand All @@ -53,7 +56,9 @@ class MainViewModel : ViewModel() {
}

companion object {
const val DEFAULT_WIDTH = 0F
const val DEFAULT_WIDTH = 1F
const val MIN_WIDTH = 1f
const val MAX_WIDTH = 100f
val DEFAULT_SELECTED_COLOR = PaletteColor.RED
}
}
6 changes: 2 additions & 4 deletions app/src/main/java/woowacourse/paint/PaintChangingState.kt
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
package woowacourse.paint

sealed class PaintChangingState {
object Nothing : PaintChangingState()
object ColorChanging : PaintChangingState()
object WidthChanging : PaintChangingState()
enum class PaintChangingState {
NOTHING, COLOR_CHANGING, WIDTH_CHANGING
}
93 changes: 46 additions & 47 deletions app/src/main/java/woowacourse/paint/canvas/CanvasView.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,83 +3,82 @@ package woowacourse.paint.canvas
import android.content.Context
import android.graphics.Canvas
import android.graphics.Paint
import android.graphics.Path
import android.graphics.PorterDuff
import android.graphics.PorterDuffXfermode
import android.util.AttributeSet
import android.view.MotionEvent
import android.view.View
import androidx.annotation.ColorInt
import woowacourse.paint.canvas.drawing.Drawings

class CanvasView(context: Context, attr: AttributeSet) : View(
context,
attr,
) {
private var path = Path()
private var paint = Paint()
private var startPoint: Point = Point(0f, 0f)
private val lines = mutableListOf<Line>()
private var drawingTool = DrawingTool.PEN
private val paint = Paint().apply {
isAntiAlias = true
style = Paint.Style.FILL_AND_STROKE
strokeCap = Paint.Cap.ROUND
xfermode = if (drawingTool == DrawingTool.ERASER) {
PorterDuffXfermode(PorterDuff.Mode.CLEAR)
} else {
null
}
}
private val drawings = Drawings()

fun initPaint(width: Float, color: PaletteColor) {
paint = getPaint(width, color.colorCode)
init {
setLayerType(LAYER_TYPE_HARDWARE, null)
}

fun initPaint(width: Float, selectedColor: PaletteColor) {
setupWidth(width)
setupColor(selectedColor)
}

override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
lines.forEach { line ->
canvas.drawPath(line.path, line.paint)
}
canvas.drawPath(path, paint)
drawings.drawAll(canvas)
}

override fun onTouchEvent(event: MotionEvent): Boolean {
val x = event.x
val y = event.y

when (event.action) {
MotionEvent.ACTION_DOWN -> drawDot(x, y)
MotionEvent.ACTION_MOVE -> drawLine(x, y)
MotionEvent.ACTION_DOWN -> drawings.add(drawingTool.draw(paint, ::invalidate))
MotionEvent.ACTION_UP -> drawings.checkLastDrawingEmpty()
else -> super.onTouchEvent(event)
}
return true
}

private fun drawDot(x: Float, y: Float) {
startPoint = Point(x, y)
path.moveTo(startPoint.x, startPoint.y)
path.lineTo(x, y)
invalidate()
return drawings.onDrawingTouchEvent(event)
}

private fun drawLine(x: Float, y: Float) {
path.moveTo(startPoint.x, startPoint.y)
path.lineTo(x, y)
startPoint = Point(x, y)
invalidate()
fun setupTools(selectedDrawingTool: DrawingTool) {
drawingTool = selectedDrawingTool
if (drawingTool == DrawingTool.ERASER) {
paint.xfermode = PorterDuffXfermode(PorterDuff.Mode.CLEAR)
return
}
paint.xfermode = null
}

fun setupWidth(width: Float) {
addLine()
paint = getPaint(width, paint.color)
paint.strokeWidth = width
}

fun setupColor(color: PaletteColor) {
addLine()
paint = getPaint(paint.strokeWidth, color.colorCode)
paint.color = color.colorCode
}

private fun addLine() {
if (path.isEmpty) return
lines.add(Line(path, paint))
path = Path()
fun eraseAll() {
drawings.clear()
invalidate()
}

private fun getPaint(width: Float, @ColorInt selectedColor: Int): Paint {
return Paint().apply {
isAntiAlias = true
style = Paint.Style.STROKE
strokeJoin = Paint.Join.ROUND
color = selectedColor
strokeWidth = width
strokeCap = Paint.Cap.ROUND
}
fun undo() {
drawings.undo()
invalidate()
}

fun redo() {
drawings.redo()
invalidate()
}
}
28 changes: 28 additions & 0 deletions app/src/main/java/woowacourse/paint/canvas/DrawingTool.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package woowacourse.paint.canvas

import android.graphics.Paint
import woowacourse.paint.canvas.drawing.Circle
import woowacourse.paint.canvas.drawing.Drawing
import woowacourse.paint.canvas.drawing.Line
import woowacourse.paint.canvas.drawing.Rectangle

enum class DrawingTool(val hasWidth: Boolean, val hasColor: Boolean) {
PEN(true, true) {
override fun draw(paint: Paint, invalidate: () -> Unit): Drawing =
Line(paint, invalidate)
},
RECTANGLE(false, true) {
override fun draw(paint: Paint, invalidate: () -> Unit): Drawing =
Rectangle(paint, invalidate)
},
CIRCLE(false, true) {
override fun draw(paint: Paint, invalidate: () -> Unit): Drawing =
Circle(paint, invalidate)
},
ERASER(true, false) {
override fun draw(paint: Paint, invalidate: () -> Unit): Drawing =
Line(paint, invalidate)
}, ;

abstract fun draw(paint: Paint, invalidate: () -> Unit): Drawing
}
6 changes: 0 additions & 6 deletions app/src/main/java/woowacourse/paint/canvas/Line.kt

This file was deleted.

28 changes: 28 additions & 0 deletions app/src/main/java/woowacourse/paint/canvas/drawing/Circle.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package woowacourse.paint.canvas.drawing

import android.graphics.Paint
import android.graphics.Path
import android.view.MotionEvent
import woowacourse.paint.canvas.Point

class Circle(paint: Paint, private val invalidate: () -> Unit) :
Drawing(Paint(paint)) {
private lateinit var startPoint: Point

override fun onTouchEvent(event: MotionEvent): Boolean {
val x = event.x
val y = event.y

when (event.action) {
MotionEvent.ACTION_DOWN -> startPoint = Point(x, y)
MotionEvent.ACTION_MOVE -> {
path.reset()
path.addOval(startPoint.x, startPoint.y, x, y, Path.Direction.CW)
invalidate()
}

else -> return true
}
return true
}
}
15 changes: 15 additions & 0 deletions app/src/main/java/woowacourse/paint/canvas/drawing/Drawing.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package woowacourse.paint.canvas.drawing

import android.graphics.Canvas
import android.graphics.Paint
import android.graphics.Path
import android.view.MotionEvent

abstract class Drawing(val paint: Paint) {
val path = Path()
fun onDraw(canvas: Canvas) {
canvas.drawPath(path, paint)
}

abstract fun onTouchEvent(event: MotionEvent): Boolean
}
Loading

0 comments on commit 38b960c

Please sign in to comment.