Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[둘리] 뷰 챌린지 미션 1단계 제출합니다. #14

Merged
merged 24 commits into from
Oct 1, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
ed6898e
feat: 레이아웃 그리기
hyemdooly Sep 29, 2023
0e4dbd0
feat: 선을 그릴 수 있는 기능 추가
hyemdooly Sep 29, 2023
4b31375
feat: button listener 설정
hyemdooly Sep 29, 2023
970025b
feat: UI State 추가
hyemdooly Sep 29, 2023
56ddb01
feat: RecyclerView 어댑터 연결
hyemdooly Sep 29, 2023
14a1889
feat: PaintView 연결
hyemdooly Sep 29, 2023
cacf0fd
feat: Point 생성
hyemdooly Sep 29, 2023
2a97a99
refactor: 네이밍 수정 및 함수 리팩터링
hyemdooly Sep 29, 2023
37d0ad4
feat: Line 클래스 생성
hyemdooly Sep 29, 2023
7a8823e
refactor: 아이템 높이, 너비 변경
hyemdooly Sep 29, 2023
9b6da38
refactor: 패키지 이동
hyemdooly Sep 29, 2023
71214e0
feat: 각각 다른 paint에 대한 line 그리기 지원
hyemdooly Sep 29, 2023
100f930
feat: colors 뷰 margin 적용
hyemdooly Sep 29, 2023
93ab0a8
refactor: 필요없는 플러그인 삭제, 네이밍 변경
hyemdooly Oct 1, 2023
68e1f78
refactor: DataBindingUtil 대신 다른 코드로 변경
hyemdooly Oct 1, 2023
cc8c325
refactor: 변수 네이밍 변경
hyemdooly Oct 1, 2023
8783cab
refactor: 구성 변경 대응 및 CanvasView에서 CustomColor를 사용하도록 수정
hyemdooly Oct 1, 2023
ed6ecda
refactor: 코드 줄임
hyemdooly Oct 1, 2023
6852594
refactor: CustomColor 네이밍 수정
hyemdooly Oct 1, 2023
89deedc
refactor: 함수 네이밍 수정
hyemdooly Oct 1, 2023
2c91a84
refactor: 설정이 바뀌었을 때만 line을 추가하도록 수정
hyemdooly Oct 1, 2023
894e464
refactor: 네이밍 수정
hyemdooly Oct 1, 2023
e0fc87e
refactor: ktlint 재적용
hyemdooly Oct 1, 2023
345cc70
refactor: default 값 이동
hyemdooly Oct 1, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ android {
isMinifyEnabled = false
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
"proguard-rules.pro",
)
}
}
Expand All @@ -33,6 +33,10 @@ android {
kotlinOptions {
jvmTarget = "11"
}

buildFeatures {
dataBinding = true
}
}

dependencies {
Expand All @@ -43,4 +47,5 @@ dependencies {
testImplementation("junit:junit:4.13.2")
androidTestImplementation("androidx.test.ext:junit:1.1.5")
androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")
implementation("androidx.activity:activity-ktx:1.1.0")
}
26 changes: 26 additions & 0 deletions app/src/main/java/woowacourse/paint/ColorViewHolder.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package woowacourse.paint

import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import woowacourse.paint.databinding.ItemColorPaletteBinding
import woowacourse.paint.model.ColorUiModel

class ColorViewHolder(private val binding: ItemColorPaletteBinding, onItemClick: (ColorUiModel) -> Unit) :
RecyclerView.ViewHolder(binding.root) {
init {
binding.onItemClick = onItemClick
}

fun bind(item: ColorUiModel) {
binding.colorModel = item
}

companion object {
fun create(parent: ViewGroup, onItemClick: (ColorUiModel) -> Unit): ColorViewHolder {
val layoutInflater = LayoutInflater.from(parent.context)
val binding = ItemColorPaletteBinding.inflate(layoutInflater, parent, false)
return ColorViewHolder(binding, onItemClick)
}
}
}
33 changes: 33 additions & 0 deletions app/src/main/java/woowacourse/paint/ColorsAdapter.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package woowacourse.paint

import android.view.ViewGroup
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import woowacourse.paint.model.ColorUiModel

class ColorsAdapter(private val onItemClick: (ColorUiModel) -> Unit) :
ListAdapter<ColorUiModel, ColorViewHolder>(
diffUtil,
) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ColorViewHolder {
return ColorViewHolder.create(parent, onItemClick)
}

override fun getItemCount(): Int = currentList.size

override fun onBindViewHolder(holder: ColorViewHolder, position: Int) {
holder.bind(currentList[position])
}

companion object {
private val diffUtil = object : DiffUtil.ItemCallback<ColorUiModel>() {
override fun areItemsTheSame(oldItem: ColorUiModel, newItem: ColorUiModel): Boolean {
return oldItem.color == newItem.color
}

override fun areContentsTheSame(oldItem: ColorUiModel, newItem: ColorUiModel): Boolean {
return oldItem == newItem
}
}
}
}
73 changes: 71 additions & 2 deletions app/src/main/java/woowacourse/paint/MainActivity.kt
Original file line number Diff line number Diff line change
@@ -1,14 +1,83 @@
package woowacourse.paint

import android.os.Bundle
import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity

import com.google.android.material.slider.RangeSlider
import woowacourse.paint.databinding.ActivityMainBinding

class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
private val viewModel by viewModels<MainViewModel>()
private val canvasView by lazy { binding.cvCanvas }
private val adapter = ColorsAdapter { model ->
viewModel.pickColor(model)
}

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
setupBinding()
setupViewModel()
setupCanvas()
setupColors()
setupWidthSlider()
}

private fun setupBinding() {
binding = ActivityMainBinding.inflate(layoutInflater)
binding.lifecycleOwner = this
setContentView(binding.root)
}

private fun setupViewModel() {
binding.viewModel = viewModel
viewModel.width.observe(this) { width ->
canvasView.setupWidth(width)
}
viewModel.selectedColor.observe(this) { color ->
canvasView.setupColor(color)
viewModel.colors.value?.let { colors ->
adapter.submitList(colors)
}
}
}

private fun setupCanvas() {
binding.cvCanvas.initPaint(
viewModel.width.value ?: MainViewModel.DEFAULT_WIDTH,
viewModel.selectedColor.value ?: MainViewModel.DEFAULT_SELECTED_COLOR,
)
}

private fun setupColors() {
binding.rvColors.adapter = adapter
binding.rvColors.setHasFixedSize(true)
binding.rvColors.addItemDecoration(SpaceItemDecoration(getSpace()))
}

private fun getSpace(): Int {
val colorWidth = resources.getDimensionPixelSize(R.dimen.color_item_size)
val display = this.applicationContext?.resources?.displayMetrics
val deviceWidth = display?.widthPixels

deviceWidth?.let {
return (deviceWidth - (colorWidth * 5)) / 4
}
return 10
}

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

companion object {
private const val minWidth = 0f
private const val maxWidth = 100f
}
}
59 changes: 59 additions & 0 deletions app/src/main/java/woowacourse/paint/MainViewModel.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package woowacourse.paint

import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.Transformations
import androidx.lifecycle.ViewModel
import woowacourse.paint.canvas.PaletteColor
import woowacourse.paint.model.ColorUiModel

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

private val _colors =
MutableLiveData(PaletteColor.getAllColors().map { ColorUiModel(it, it.ordinal == 0) })
val colors: LiveData<List<ColorUiModel>>
get() = _colors

val selectedColor: LiveData<PaletteColor>
get() = Transformations.map(_colors) { colors ->
colors.firstOrNull { it.isPicked }?.color ?: DEFAULT_SELECTED_COLOR
}

private val _width = MutableLiveData(DEFAULT_WIDTH)
val width: LiveData<Float>
get() = _width

fun setColorSettingState() {
if (_paintChangingState.value == PaintChangingState.ColorChanging) {
_paintChangingState.value = PaintChangingState.Nothing
return
}
_paintChangingState.value = PaintChangingState.ColorChanging
}

fun setWidthSettingState() {
if (_paintChangingState.value == PaintChangingState.WidthChanging) {
_paintChangingState.value = PaintChangingState.Nothing
return
}
_paintChangingState.value = PaintChangingState.WidthChanging
}

fun pickColor(model: ColorUiModel) {
val colors = _colors.value ?: return
_colors.value = colors.map { it.copy(isPicked = it.color == model.color) }
}

fun pickWidth(selectedWidth: Float) {
_width.value = selectedWidth
}

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

sealed class PaintChangingState {
object Nothing : PaintChangingState()
object ColorChanging : PaintChangingState()
object WidthChanging : PaintChangingState()
}
19 changes: 19 additions & 0 deletions app/src/main/java/woowacourse/paint/SpaceItemDecoration.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package woowacourse.paint

import android.graphics.Rect
import android.view.View
import androidx.recyclerview.widget.RecyclerView

class SpaceItemDecoration(private val space: Int) : RecyclerView.ItemDecoration() {
override fun getItemOffsets(
outRect: Rect,
view: View,
parent: RecyclerView,
state: RecyclerView.State,
) {
val position = parent.getChildAdapterPosition(view)
if (position != 0) {
outRect.left = space
}
}
}
85 changes: 85 additions & 0 deletions app/src/main/java/woowacourse/paint/canvas/CanvasView.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
package woowacourse.paint.canvas

import android.content.Context
import android.graphics.Canvas
import android.graphics.Paint
import android.graphics.Path
import android.util.AttributeSet
import android.view.MotionEvent
import android.view.View
import androidx.annotation.ColorInt

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>()

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

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

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)
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()
}

private fun drawLine(x: Float, y: Float) {
path.moveTo(startPoint.x, startPoint.y)
path.lineTo(x, y)
startPoint = Point(x, y)
invalidate()
}

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

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

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

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
}
}
}
6 changes: 6 additions & 0 deletions app/src/main/java/woowacourse/paint/canvas/Line.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package woowacourse.paint.canvas

import android.graphics.Paint
import android.graphics.Path

class Line(val path: Path, val paint: Paint)
11 changes: 11 additions & 0 deletions app/src/main/java/woowacourse/paint/canvas/PaletteColor.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package woowacourse.paint.canvas

import android.graphics.Color

enum class PaletteColor(val colorCode: Int) {
RED(Color.RED), ORANGE(Color.parseColor("#FF9802")), YELLOW(Color.YELLOW), GREEN(Color.GREEN), BLUE(Color.BLUE);

companion object {
fun getAllColors(): List<PaletteColor> = PaletteColor.values().toList()
}
}
3 changes: 3 additions & 0 deletions app/src/main/java/woowacourse/paint/canvas/Point.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package woowacourse.paint.canvas

data class Point(val x: Float, val y: Float)
5 changes: 5 additions & 0 deletions app/src/main/java/woowacourse/paint/model/ColorUiModel.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package woowacourse.paint.model

import woowacourse.paint.canvas.PaletteColor

data class ColorUiModel(val color: PaletteColor, val isPicked: Boolean = false)
5 changes: 5 additions & 0 deletions app/src/main/res/drawable/ic_check_24.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<vector android:height="24dp" android:tint="#C67101"
android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http:https://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M9,16.17L4.83,12l-1.42,1.41L9,19 21,7l-1.41,-1.41z"/>
</vector>
Loading