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

마인드맵 텍스트 크기 자동화 #66

Merged
merged 21 commits into from
Nov 20, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
9580ebe
feat(#39): Node 클릭 시 텍스트를 감싸는 텍스트 박스 시각화
jaehan4707 Nov 16, 2023
8d33af5
feat(#39): 빈 화면 클릭 시 텍스트 박스 초기화
jaehan4707 Nov 16, 2023
b91c48c
feat(#39): 클릭 이벤트 더블 클릭으로 수정
jaehan4707 Nov 16, 2023
8992e58
rename(#39): Textview 제거
jaehan4707 Nov 16, 2023
cd1d176
feat(#39): NodeClickListener 구현
jaehan4707 Nov 16, 2023
99b16e5
feat(#39): EditDialogInterface 구현
jaehan4707 Nov 16, 2023
13b6143
feat(#39): EditDescriptionDialog 구현
jaehan4707 Nov 16, 2023
9efa952
feat(#39): Node 더블 클릭 이벤트 구현
jaehan4707 Nov 16, 2023
9f1507f
refactor(#39): drawText depth 제거
jaehan4707 Nov 16, 2023
1901ccd
feat(#39): Dialog 애니메이션 등록
jaehan4707 Nov 18, 2023
ed55fa4
feat(#39): Dialog background 추가
jaehan4707 Nov 18, 2023
aead1b0
feat(#39): 다이얼로그 프래그먼트로 변경
jaehan4707 Nov 20, 2023
a252d3e
feat(#39): 노드 크기 조절 자동화
jaehan4707 Nov 20, 2023
aaa86e1
design(#39): root 색상 변경, stroke 색상 추가, 배경색상 추가
jaehan4707 Nov 20, 2023
8fa66ec
feat(#39): 노드 선택 시 stroke 추가
jaehan4707 Nov 20, 2023
8bb9956
feat(#39): 사각형 노드 Padding 추가
jaehan4707 Nov 20, 2023
aac4faf
fix(#39): 사각형 높이 조절 수정
jaehan4707 Nov 20, 2023
b719483
Merge branch 'AOS-feature/mindmap' into AOS-feature/mindmap-text
jaehan4707 Nov 20, 2023
28fcfca
refactor(#39): toFlat() 제거 및 arrgeNode 호출 위치 변경, override invalidate 제거
jaehan4707 Nov 20, 2023
5e724b1
fix(#39): revert mainActivity,xml
jaehan4707 Nov 20, 2023
2b0cbc0
refactor(#39): 코드 리뷰 반영
jaehan4707 Nov 20, 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
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package boostcamp.and07.mindsync.ui.dialog

import android.app.Dialog
import android.content.DialogInterface
import android.content.res.ColorStateList
import android.graphics.Color
import android.graphics.drawable.ColorDrawable
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.DialogFragment
import boostcamp.and07.mindsync.R
import boostcamp.and07.mindsync.databinding.DialogEditDescriptionBinding

class EditDescriptionDialog(private val color: Int) : DialogFragment() {
private var _binding: DialogEditDescriptionBinding? = null
private val binding get() = _binding!!
private lateinit var editDialogInterface: EditDialogInterface
fun setListener(listener: EditDialogInterface) {
editDialogInterface = listener
}

override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val dialog = super.onCreateDialog(savedInstanceState)
dialog.window?.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT))
dialog.window?.attributes?.windowAnimations = R.style.AnimationDialogStyle
isCancelable = true
return dialog
}

override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?,
): View? {
_binding = DialogEditDescriptionBinding.inflate(inflater, container, false)
binding.run {
btnCancel.setOnClickListener {
dismiss()
}
btnSubmit.setOnClickListener {
editDialogInterface.onSubmitClick(binding.etNodeDescription.text.toString())
dismiss()
}
}
return binding.root
}

override fun onStart() {
super.onStart()
binding.root.backgroundTintList = ColorStateList.valueOf(color)
dialog?.window?.setLayout(
1000,
1000,
)
}

override fun onDismiss(dialog: DialogInterface) {
_binding = null
super.onDismiss(dialog)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package boostcamp.and07.mindsync.ui.dialog

interface EditDialogInterface {
fun onSubmitClick(description: String)
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,6 @@ class LineView constructor(
}
}

override fun invalidate() {
arrangeNode()
super.invalidate()
}

private fun arrangeNode() {
head = rightLayoutManager.arrangeNode(head)
}
Expand Down
235 changes: 225 additions & 10 deletions AOS/app/src/main/java/boostcamp/and07/mindsync/ui/view/NodeView.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,22 @@ package boostcamp.and07.mindsync.ui.view

import android.content.Context
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Paint
import android.graphics.Rect
import android.text.TextPaint
import android.util.AttributeSet
import android.view.MotionEvent
import android.view.View
import androidx.core.content.res.ResourcesCompat
import boostcamp.and07.mindsync.R
import boostcamp.and07.mindsync.data.SampleNode
import boostcamp.and07.mindsync.data.model.CircleNode
import boostcamp.and07.mindsync.data.model.Node
import boostcamp.and07.mindsync.data.model.RectangleNode
import boostcamp.and07.mindsync.ui.util.Dp
import boostcamp.and07.mindsync.ui.util.Px
import boostcamp.and07.mindsync.ui.util.toDp
import boostcamp.and07.mindsync.ui.util.toPx
import boostcamp.and07.mindsync.ui.view.layout.MindmapRightLayoutManager

Expand All @@ -27,38 +35,179 @@ class NodeView constructor(context: Context, attrs: AttributeSet?) : View(contex
context.getColor(R.color.mindmap5),
)
private val rightLayoutManager = MindmapRightLayoutManager()
private val textPaint = TextPaint().apply {
color = Color.RED
textSize = Dp(12f).toPx(context)
isAntiAlias = true
typeface = ResourcesCompat.getFont(context, R.font.pretendard_bold)
textAlign = Paint.Align.CENTER
}

override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
arrangeNode()
traverseHead(canvas)
private val strokePaint = Paint().apply {
color = context.getColor(R.color.blue)
style = Paint.Style.STROKE
strokeWidth = Dp(5f).toPx(context)
isAntiAlias = true
}

override fun invalidate() {
private val lineHeight = Dp(15f)
private var touchedNode: Node? = null

override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
traverseTextHead()
arrangeNode()
super.invalidate()
traverseDrawHead(canvas)
touchedNode?.let { touchNode ->
makeStrokeNode(canvas, touchNode)
}
}

private fun arrangeNode() {
head = rightLayoutManager.arrangeNode(head)
}

private fun traverseHead(canvas: Canvas) {
traverseNode(canvas, head, 0)
override fun onTouchEvent(event: MotionEvent): Boolean {
when (event.action) {
MotionEvent.ACTION_DOWN -> {
traverseRangeHead(event.x, event.y)
}
}
return super.onTouchEvent(event)
}

private fun traverseDrawHead(canvas: Canvas) {
traverseDrawNode(canvas, head, 0)
}

private fun traverseNode(canvas: Canvas, node: Node, depth: Int) {
private fun traverseDrawNode(canvas: Canvas, node: Node, depth: Int) {
drawNode(canvas, node, depth)
node.nodes.forEach { node ->
traverseNode(canvas, node, depth + 1)
traverseDrawNode(canvas, node, depth + 1)
}
}

private fun traverseTextHead() {
head = traverseTextNode(head)
}

private fun traverseTextNode(node: Node): Node {
val newNodes = mutableListOf<RectangleNode>()
node.nodes.forEach { child ->
newNodes.add(traverseTextNode(child) as RectangleNode)
traverseTextNode(child)
}
val copyNode =
changeSize(node, sumWidth(node.description), sumTotalHeight(node.description))
val newNode = when (copyNode) {
is CircleNode -> copyNode.copy(nodes = newNodes)
is RectangleNode -> copyNode.copy(nodes = newNodes)
}
return newNode
}

private fun changeSize(node: Node, width: Px, height: Float): Node {
when (node) {
is CircleNode -> {
var newRadius = node.path.radius
if (width.toDp(context) > node.path.radius.dpVal && !node.description.contains("\n")
) {
newRadius = Dp(width.toDp(context) / 2) + lineHeight / 2
}
if (node.description.contains("\n")) {
newRadius = (Dp(height) - lineHeight) / 2
}
return node.copy(node.path.copy(radius = newRadius))
}

is RectangleNode -> {
var newWidth = node.path.width
if (width.toDp(context) > node.path.width.dpVal && !node.description.contains("\n")
) {
newWidth = Dp(width.toDp(context)) + lineHeight
}
val newHeight = Dp(height) / 2 + lineHeight
return node.copy(node.path.copy(width = newWidth, height = newHeight))
}
}
}

private fun traverseRangeHead(x: Float, y: Float) {
val rangeResult = traverseRangeNode(head, x, y, 0)

rangeResult?.let {
touchedNode = it.first
} ?: run {
touchedNode = null
}
invalidate()
}

private fun makeStrokeNode(canvas: Canvas, node: Node) {
when (node) {
is CircleNode -> {
canvas.drawCircle(
node.path.centerX.toPx(context),
node.path.centerY.toPx(context),
node.path.radius.toPx(context),
strokePaint,
)
}

is RectangleNode -> {
canvas.drawRect(
node.path.leftX().toPx(context),
node.path.topY().toPx(context),
node.path.rightX().toPx(context),
node.path.bottomY().toPx(context),
strokePaint,
)
}
}
}

private fun traverseRangeNode(node: Node, x: Float, y: Float, depth: Int): Pair<Node, Int>? {
if (isInsideNode(node, x, y)) {
return Pair(node, depth)
}
for (child in node.nodes) {
return traverseRangeNode(child, x, y, depth + 1) ?: continue
}
return null
}

private fun isInsideNode(node: Node, x: Float, y: Float): Boolean {
when (node) {
is CircleNode -> {
if (x in (node.path.centerX - node.path.radius).toPx(context)..(node.path.centerX + node.path.radius).toPx(
context,
) &&
y in (node.path.centerY - node.path.radius).toPx(context)..(node.path.centerY + node.path.radius).toPx(
context,
)
) {
return true
}
}

is RectangleNode -> {
if (x in node.path.leftX().toPx(context)..node.path.rightX().toPx(context) &&
y in node.path.topY().toPx(context)..node.path.bottomY()
.toPx(context)
) {
return true
}
}
}
return false
}

private fun drawNode(canvas: Canvas, node: Node, depth: Int) {
when (node) {
is CircleNode -> drawCircleNode(canvas, node)
is RectangleNode -> drawRectangleNode(canvas, node, depth)
}
drawText(canvas, node)
}

private fun drawCircleNode(canvas: Canvas, node: CircleNode) {
Expand All @@ -80,4 +229,70 @@ class NodeView constructor(context: Context, attrs: AttributeSet?) : View(contex
rectanglePaint,
)
}

private fun sumTotalHeight(description: String): Float {
val bounds = Rect()
var sum = 0f
description.split("\n").forEach { line ->
textPaint.getTextBounds(line, 0, line.length, bounds)
sum += bounds.height() + lineHeight.dpVal
}
return sum
}

private fun sumWidth(description: String) = Px(textPaint.measureText(description))
private fun drawText(canvas: Canvas, node: Node) {
val lines = node.description.split("\n")
var bounds = Rect()
var totalHeight = sumTotalHeight(node.description)
when (node) {
is CircleNode -> {
textPaint.color = Color.WHITE
if (lines.size > 1) {
var y = node.path.centerY.toPx(context) - totalHeight / 2
for (line in lines) {
textPaint.getTextBounds(line, 0, line.length, bounds)
canvas.drawText(
line,
node.path.centerX.toPx(context),
y + bounds.height(),
textPaint,
)
y += bounds.height() + lineHeight.dpVal
}
} else {
canvas.drawText(
node.description,
node.path.centerX.toPx(context),
node.path.centerY.toPx(context) + lineHeight.dpVal / 2,
textPaint,
)
}
}

is RectangleNode -> {
textPaint.color = Color.BLACK
if (lines.size > 1) {
var y = node.path.centerY.toPx(context) - totalHeight / 2
for (line in lines) {
textPaint.getTextBounds(line, 0, line.length, bounds)
canvas.drawText(
line,
node.path.centerX.toPx(context),
y + bounds.height(),
textPaint,
)
y += bounds.height() + lineHeight.dpVal
}
} else {
canvas.drawText(
node.description,
node.path.centerX.toPx(context),
node.path.centerY.toPx(context) + lineHeight.dpVal / 2,
textPaint,
)
}
}
}
}
}
8 changes: 8 additions & 0 deletions AOS/app/src/main/res/anim/dialog_close.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:interpolator="@android:anim/accelerate_interpolator">
<translate
android:fromYDelta="0%"
android:toYDelta= "100%"
android:duration="200"/>
</set>
8 changes: 8 additions & 0 deletions AOS/app/src/main/res/anim/dialog_open.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:interpolator="@android:anim/accelerate_interpolator">
<translate
android:fromYDelta="100%"
android:toYDelta="0%"
android:duration="200"/>
</set>
8 changes: 8 additions & 0 deletions AOS/app/src/main/res/drawable/bg_round_dialog.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">

<solid android:color="#ffffff"/>
<stroke android:color="@color/black"
android:width="1dp"/>
<corners android:radius="20dp"/>
</shape>
Loading