-
Notifications
You must be signed in to change notification settings - Fork 45
[하티] 뷰 챌린지 미션 1단계 제출합니다. #10
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
Changes from all commits
e361c2d
88c57b3
808e937
443ab61
d1dbe99
50cfc7a
f8e138e
b5f3f21
f17aab8
14fced3
4316055
24db48d
35f15c4
b9d0b39
b39dbed
12c4868
3f65547
8408934
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,8 @@ | ||
# android-paint | ||
|
||
## 기능 요구 사항 | ||
|
||
- [X] 화면에 자유 곡선을 그릴 수 있다. | ||
- [X] 도구 패널을 추가한다. | ||
- [X] 브러시 색상을 변경할 수 있다. (브러시 색상은 빨, 주, 노, 초, 파로 한정) | ||
- [X] 브러시 굵기를 변경할 수 있다. |
This file was deleted.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
package woowacourse.paint.main | ||
|
||
import android.os.Bundle | ||
import androidx.appcompat.app.AppCompatActivity | ||
import androidx.lifecycle.ViewModelProvider | ||
import woowacourse.paint.databinding.ActivityMainBinding | ||
import woowacourse.paint.main.adapter.ColorAdapter | ||
|
||
class MainActivity : AppCompatActivity() { | ||
private val binding by lazy { ActivityMainBinding.inflate(layoutInflater) } | ||
private val viewModel by lazy { ViewModelProvider(this)[MainViewModel::class.java] } | ||
|
||
override fun onCreate(savedInstanceState: Bundle?) { | ||
super.onCreate(savedInstanceState) | ||
setContentView(binding.root) | ||
setViewModel() | ||
|
||
setColorsRecyclerview() | ||
setBrushSizeListener() | ||
} | ||
|
||
private fun setViewModel() { | ||
binding.viewModel = viewModel | ||
binding.lifecycleOwner = this | ||
} | ||
|
||
private fun setColorsRecyclerview() { | ||
binding.rvColors.apply { | ||
adapter = ColorAdapter { | ||
viewModel.setBrushColor(it) | ||
} | ||
setHasFixedSize(true) | ||
} | ||
} | ||
|
||
private fun setBrushSizeListener() { | ||
binding.rangeSliderBrushSize.addOnChangeListener { _, value, _ -> | ||
viewModel.setBrushSize(value) | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
package woowacourse.paint.main | ||
|
||
import androidx.lifecycle.LiveData | ||
import androidx.lifecycle.MutableLiveData | ||
import androidx.lifecycle.ViewModel | ||
import woowacourse.paint.model.BrushSize | ||
import woowacourse.paint.model.PaintColor | ||
|
||
class MainViewModel : ViewModel() { | ||
private val _brushSize = MutableLiveData<BrushSize>() | ||
val brushSize: LiveData<BrushSize> | ||
get() = _brushSize | ||
|
||
private val _brushColor = MutableLiveData<PaintColor>() | ||
val brushColor: LiveData<PaintColor> | ||
get() = _brushColor | ||
|
||
init { | ||
_brushSize.value = BrushSize(BrushSize.DEFAULT_SIZE) | ||
_brushColor.value = PaintColor.DEFAULT_COLOR | ||
} | ||
|
||
fun setBrushSize(size: Float) { | ||
_brushSize.value = BrushSize(size) | ||
} | ||
|
||
fun setBrushColor(color: PaintColor) { | ||
_brushColor.value = color | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
package woowacourse.paint.main | ||
|
||
import android.annotation.SuppressLint | ||
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.core.content.ContextCompat | ||
import woowacourse.paint.model.BrushSize | ||
import woowacourse.paint.model.DrawablePath | ||
import woowacourse.paint.model.DrawablePathHistory | ||
import woowacourse.paint.model.PaintColor | ||
|
||
class PaintBoard constructor(context: Context, attrs: AttributeSet) : View(context, attrs) { | ||
private val pathHistory = DrawablePathHistory() | ||
private var currentPath = Path() | ||
private val currentPaint = Paint() | ||
|
||
init { | ||
setDefaultPaint() | ||
} | ||
|
||
override fun onDraw(canvas: Canvas) { | ||
super.onDraw(canvas) | ||
|
||
pathHistory.paths.forEach { | ||
canvas.drawPath(it.path, it.paint) | ||
} | ||
canvas.drawPath(currentPath, currentPaint) | ||
} | ||
|
||
@SuppressLint("ClickableViewAccessibility") | ||
override fun onTouchEvent(event: MotionEvent): Boolean { | ||
when (event.action) { | ||
MotionEvent.ACTION_DOWN -> startDrawing(event) | ||
MotionEvent.ACTION_MOVE -> moveDrawing(event) | ||
MotionEvent.ACTION_UP -> endDrawing() | ||
Comment on lines
+38
to
+40
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 함수화를 아주 잘 해주셨군요 굿굿 |
||
else -> return super.onTouchEvent(event) | ||
} | ||
invalidate() | ||
return true | ||
} | ||
|
||
private fun setDefaultPaint() { | ||
currentPaint.apply { | ||
strokeCap = Paint.Cap.ROUND | ||
strokeJoin = Paint.Join.ROUND | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 제 그림판에서는 왜이리 삐쭉빼쭉하지 했는데 여기서 힌트를 얻네요!! 감사합니다 하티😉 |
||
isAntiAlias = true | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 이 친구는 무슨 역할을 해주는 건가요? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 설명드릴게요 핑구! 😆 🌼 strokeCap선의 양 끝 선 처리를 해줍니다.
ref. strokeJoin선의 꼭지점 처리를 해줍니다.
ref. isAntiAlias (setAntiAlias)true로 설정하는 경우, 선 자체에는 영향이 없고, 선의 가장자리를 부드럽게 해줍니다. ref. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 정성스러운 답변 정말 감사해요..❣️ |
||
style = Paint.Style.STROKE | ||
} | ||
} | ||
|
||
private fun startDrawing(event: MotionEvent) { | ||
currentPath = Path() | ||
currentPath.moveTo(event.x, event.y) | ||
currentPath.lineTo(event.x, event.y) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. moveDrawing()에서 lineTo로 그려주니 시작할 때는 그냥 moveTo만 해줘도 되지 않을까요? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 우선 저는 화면 위로 드래그를 하면 선이 그려지고, 그냥 터치만 하는 경우에는 점이 찍히도록 구현하고자 했어요. 혹시 핑구도 이런 문제를 겪으셨나요? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 오 그런 부분에 대해서는 생각을 못했네요..! 하티 덕분에 알아가는게 많아지네요 감사합니다 >< |
||
} | ||
|
||
private fun moveDrawing(event: MotionEvent) { | ||
currentPath.lineTo(event.x, event.y) | ||
} | ||
|
||
private fun endDrawing() { | ||
pathHistory.add(DrawablePath(currentPath, Paint(currentPaint))) | ||
} | ||
|
||
fun setBrushSize(size: BrushSize) { | ||
currentPaint.strokeWidth = size.width | ||
} | ||
|
||
fun setBrushColor(color: PaintColor) { | ||
currentPaint.color = ContextCompat.getColor(context, color.colorRes) | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
package woowacourse.paint.main.adapter | ||
|
||
import android.view.ViewGroup | ||
import androidx.recyclerview.widget.RecyclerView | ||
import woowacourse.paint.main.viewholder.ColorViewHolder | ||
import woowacourse.paint.model.PaintColor | ||
|
||
class ColorAdapter( | ||
private val onColorClickListener: (PaintColor) -> Unit, | ||
) : RecyclerView.Adapter<ColorViewHolder>() { | ||
private val colors = PaintColor.values() | ||
|
||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ColorViewHolder { | ||
return ColorViewHolder(parent) { | ||
onColorClickListener(it) | ||
} | ||
} | ||
|
||
override fun onBindViewHolder(holder: ColorViewHolder, position: Int) { | ||
holder.bind(colors[position]) | ||
} | ||
|
||
override fun getItemCount(): Int { | ||
return colors.size | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
package woowacourse.paint.main.viewholder | ||
|
||
import android.view.LayoutInflater | ||
import android.view.ViewGroup | ||
import androidx.core.content.ContextCompat.getColor | ||
import androidx.recyclerview.widget.RecyclerView | ||
import woowacourse.paint.R | ||
import woowacourse.paint.databinding.ItemColorBinding | ||
import woowacourse.paint.model.PaintColor | ||
|
||
class ColorViewHolder( | ||
parent: ViewGroup, | ||
onColorClickListener: (PaintColor) -> Unit, | ||
) : RecyclerView.ViewHolder( | ||
LayoutInflater.from(parent.context).inflate( | ||
R.layout.item_color, | ||
parent, | ||
false, | ||
), | ||
) { | ||
private val binding = ItemColorBinding.bind(itemView) | ||
|
||
init { | ||
binding.root.setOnClickListener { | ||
onColorClickListener(PaintColor.getColor(adapterPosition)) | ||
} | ||
} | ||
|
||
fun bind(paintColor: PaintColor) { | ||
binding.color = getColor(itemView.context, paintColor.colorRes) | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
package woowacourse.paint.model | ||
|
||
data class BrushSize(val width: Float) { | ||
companion object { | ||
const val DEFAULT_SIZE = 50f | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
package woowacourse.paint.model | ||
|
||
import android.graphics.Paint | ||
import android.graphics.Path | ||
|
||
data class DrawablePath( | ||
val path: Path, | ||
val paint: Paint, | ||
) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
package woowacourse.paint.model | ||
|
||
data class DrawablePathHistory( | ||
private val _paths: MutableList<DrawablePath> = mutableListOf(), | ||
) { | ||
val paths: List<DrawablePath> | ||
get() = _paths.toList() | ||
|
||
fun add(path: DrawablePath) { | ||
_paths.add(path) | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
package woowacourse.paint.model | ||
|
||
import androidx.annotation.ColorRes | ||
import woowacourse.paint.R | ||
|
||
enum class PaintColor(@ColorRes val colorRes: Int) { | ||
RED(R.color.red), | ||
ORANGE(R.color.orange), | ||
YELLOW(R.color.yellow), | ||
GREEN(R.color.green), | ||
BLUE(R.color.blue), | ||
Comment on lines
+7
to
+11
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 저는 그냥 ColorRes값이 없는 enum class로 정의해주고 Custom View에서 색상을 지정해주었는데 이렇게 한 곳에서 관리해주는 것이 더 좋은 것 같아요!👍 |
||
; | ||
|
||
companion object { | ||
val DEFAULT_COLOR: PaintColor | ||
get() = values().first() | ||
|
||
fun getColor(index: Int): PaintColor { | ||
return values()[index] | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,9 +1,55 @@ | ||
<?xml version="1.0" encoding="utf-8"?> | ||
<androidx.constraintlayout.widget.ConstraintLayout | ||
xmlns:android="http://schemas.android.com/apk/res/android" | ||
<layout xmlns:android="http://schemas.android.com/apk/res/android" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 데이터 바인딩을 사용하는 이유가 있을까요? 여기서는 뷰바인딩만 사용해도 괜찮다고 보입니다 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. data binding을 사용할 가능성을 열어두고 싶기도 했고, 습관적으로 적용해준 이유도 없지않아 있는 것 같네요,, 😅 |
||
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"> | ||
xmlns:tools="http://schemas.android.com/tools"> | ||
|
||
</androidx.constraintlayout.widget.ConstraintLayout> | ||
<data> | ||
|
||
<variable | ||
name="viewModel" | ||
type="woowacourse.paint.main.MainViewModel" /> | ||
</data> | ||
|
||
<androidx.constraintlayout.widget.ConstraintLayout | ||
android:layout_width="match_parent" | ||
android:layout_height="match_parent"> | ||
|
||
<woowacourse.paint.main.PaintBoard | ||
android:id="@+id/ctv_paint_board" | ||
android:layout_width="0dp" | ||
android:layout_height="0dp" | ||
app:brushColor="@{viewModel.brushColor}" | ||
app:brushSize="@{viewModel.brushSize}" | ||
app:layout_constraintBottom_toTopOf="@id/range_slider_brush_size" | ||
app:layout_constraintEnd_toEndOf="parent" | ||
app:layout_constraintStart_toStartOf="parent" | ||
app:layout_constraintTop_toTopOf="parent" /> | ||
|
||
<com.google.android.material.slider.Slider | ||
android:id="@+id/range_slider_brush_size" | ||
android:layout_width="0dp" | ||
android:layout_height="wrap_content" | ||
android:valueFrom="0" | ||
android:valueTo="100" | ||
android:value="@{viewModel.brushSize.width}" | ||
app:layout_constraintBottom_toTopOf="@id/rv_colors" | ||
app:layout_constraintEnd_toEndOf="parent" | ||
app:layout_constraintHorizontal_bias="1.0" | ||
app:layout_constraintStart_toStartOf="parent" | ||
app:layout_constraintTop_toBottomOf="@id/ctv_paint_board" /> | ||
|
||
<androidx.recyclerview.widget.RecyclerView | ||
android:id="@+id/rv_colors" | ||
android:layout_width="0dp" | ||
android:layout_height="wrap_content" | ||
android:layout_marginHorizontal="8dp" | ||
android:orientation="horizontal" | ||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" | ||
app:layout_constraintBottom_toBottomOf="parent" | ||
app:layout_constraintEnd_toEndOf="parent" | ||
app:layout_constraintStart_toStartOf="parent" | ||
app:layout_constraintTop_toBottomOf="@id/range_slider_brush_size" | ||
tools:listitem="@layout/item_color" /> | ||
|
||
</androidx.constraintlayout.widget.ConstraintLayout> | ||
</layout> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
현재는 xml 상에서만 PaintBoard를 사용할 수 있어요! 동적 생성을 하려면 어떻게 해야할까요?