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

CustomView and Animation homework #43

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -42,4 +42,5 @@ dependencies {
testImplementation 'junit:junit:4.+'
androidTestImplementation 'androidx.test.ext:junit:1.1.2'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
implementation 'com.google.code.gson:gson:2.9.1'
}
95 changes: 95 additions & 0 deletions app/src/main/java/otus/homework/customview/GraphView.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
package otus.homework.customview

import android.content.Context
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Paint
import android.graphics.RectF
import android.util.AttributeSet
import android.util.Log
import android.view.View

class GraphView @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) {

private var mPaint: Paint = Paint()
var data: Map<Pair<String, Paint>, Map<Int, Point>> = emptyMap()
var height = 0f
var width = 0f

private var rectF = RectF(0f, 0f, height, width)

init{
mPaint.color = Color.BLACK
mPaint.strokeWidth = 2f
}

@JvmName("setData1")
fun setData(list: Map<Pair<String, Paint>, Map<Int, Point>>) {
data = list
invalidate()
}
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
super.onSizeChanged(w, h, oldw, oldh)
Log.d("GW", w.toString())
Log.d("GW", h.toString())
height = h.toFloat()
width = w.toFloat()
}

override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
super.onLayout(changed, left, top, right, bottom)
val widthAddition = (width - measuredWidth) / 2
val heightAddition = (height - measuredHeight) / 2
rectF.let {
it.left = widthAddition + paddingLeft
it.top = heightAddition + paddingTop
it.right = measuredWidth + widthAddition - paddingRight.toFloat()
it.bottom = measuredHeight + heightAddition - paddingBottom.toFloat()
}
}

override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

А ты не хочешь тут описать логику расчетов?

when (MeasureSpec.getMode(widthMeasureSpec)) {
MeasureSpec.UNSPECIFIED,
MeasureSpec.AT_MOST,
MeasureSpec.EXACTLY -> {
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
}
}
}

override fun onDraw(canvas: Canvas?) {
super.onDraw(canvas)
canvas?.drawLine(2f, height, 2f, 0f, mPaint)
canvas?.drawLine(2f, height, width, height, mPaint)

var offset = 30f
data.forEach {cat ->
val catName = cat.key.first
val paint = cat.key.second
paint.strokeWidth = 2f
mPaint.textSize = 16f
mPaint.isAntiAlias = true
canvas?.drawLine(2f,height + offset, 22f, height + offset, paint)
canvas?.drawText(catName, 44f, height + offset, mPaint)
offset += 30f
var i = 1
cat.value[i]?.let {
paint.strokeWidth = 2f
canvas?.drawLine(2f,height, it.coorX+2f, height-it.coorY, paint)
}
cat.value.forEach { point ->
paint.strokeWidth = 10f
canvas?.drawPoint(point.value.coorX+2f, height-point.value.coorY, paint)
cat.value[i+1]?.let {
paint.strokeWidth = 2f
canvas?.drawLine(point.value.coorX+2f, height-point.value.coorY, it.coorX+2f, height-it.coorY, paint)
}
i++
}
}
}

}
90 changes: 89 additions & 1 deletion app/src/main/java/otus/homework/customview/MainActivity.kt
Original file line number Diff line number Diff line change
@@ -1,11 +1,99 @@
package otus.homework.customview

import androidx.appcompat.app.AppCompatActivity
import android.graphics.Paint
import android.os.Bundle
import android.util.Log
import androidx.appcompat.app.AppCompatActivity
import com.google.gson.Gson
import com.google.gson.reflect.TypeToken
import java.io.Reader
import java.lang.reflect.Type

class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val pieChartView = findViewById<PieChartView>(R.id.pieChartView)
val graphView = findViewById<GraphView>(R.id.graphView)

val data = loadJSONFromAsset()
var pie = mapToPie(data)
val points = mapToPoints(data!!)
pieChartView.setData(pie)
graphView.setData(points)

val pieChartClickListener = object : PieChartClickListener {
override fun onClick(category: String) {
pie = pie?.map {
if (it.category == category) it.copy(isClicked = true)
else it.copy(isClicked = false)
}
pieChartView.setData(pie)
}
}

pieChartView.pieChartClickListener = pieChartClickListener
}

private fun mapToPie(data: List<Metka>?): List<PiePiece>? {

val group = data?.groupBy { it.category }
var startPoint = 0f
var finishPoint = 0f
var count = 0f
data?.forEach {
count += it.amount
}
return group?.map { map ->
startPoint += finishPoint
var countElem = 0f
map.value.forEach {
countElem += it.amount
}
finishPoint = countElem / count * 360f
PiePiece(
category = map.key,
start = startPoint,
end = finishPoint,
paint = createPaint(),
data = map.value,
)
}
}

private fun mapToPoints(data: List<Metka>): Map<Pair<String, Paint>, Map<Int, Point>> {
val minAmount = data.minOf { it.amount }.toFloat()
val maxAmount = ((data.maxOf { it.amount }) - minAmount).toFloat()
val minTime = data.minOf { it.time }.toFloat()
val maxTime = ((data.maxOf { it.time }) - minTime).toFloat()
val group = data.groupBy { it.category }

val outMap: MutableMap<Pair<String, Paint>, Map<Int, Point>> = mutableMapOf()
group.forEach { map ->
var i = 0

val pointMap = mutableMapOf<Int, Point>()

map.value.forEach {
pointMap[++i] =
Point(
coorX = ((it.time - minTime) / maxTime) * 600f,
coorY = ((it.amount - minAmount) / maxAmount) * 400f,
)
}
outMap[Pair(map.key, createPaint())] = pointMap
}

return outMap
}

private fun loadJSONFromAsset(): List<Metka>? =
resources.openRawResource(R.raw.payload).bufferedReader().use {
return getList(it, Metka::class.java)
}

private fun <T> getList(jsonArray: Reader?, clazz: Class<T>?): List<T>? {
val typeOfT: Type = TypeToken.getParameterized(MutableList::class.java, clazz).type
return Gson().fromJson(jsonArray, typeOfT)
}
}
16 changes: 16 additions & 0 deletions app/src/main/java/otus/homework/customview/Metka.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package otus.homework.customview

import com.google.gson.annotations.SerializedName

data class Metka(
@SerializedName("amount")
val amount: Int,
@SerializedName("category")
val category: String,
@SerializedName("id")
val id: Int,
@SerializedName("name")
val name: String,
@SerializedName("time")
val time: Long
)
136 changes: 136 additions & 0 deletions app/src/main/java/otus/homework/customview/PieChartView.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
package otus.homework.customview

import android.animation.AnimatorSet
import android.animation.ValueAnimator
import android.annotation.SuppressLint
import android.content.Context
import android.graphics.*
import android.util.AttributeSet
import android.view.MotionEvent
import android.view.View
import android.view.animation.OvershootInterpolator

interface PieChartClickListener {
fun onClick(category: String)
}

class PieChartView @JvmOverloads constructor(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Реализуй, пожалуйста, сохранение/восстановление стейта

context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) {
private var animator: ValueAnimator = ValueAnimator.ofFloat()
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Он разве нужен на уровне класса?

var pieChartClickListener: PieChartClickListener? = null
private var mPaint: Paint = Paint()
private var bitmap: Bitmap? = null
private var data: List<PiePiece>? = emptyList()
private var oval = RectF()
private var bigOval = RectF()
private var center_x = 0f
private var center_y = 0f
private var offset = 0f
private var bOffset = 0f
private var animWidth = 0f
private var clickedPie: PiePiece? = null
private val animatorSet = AnimatorSet()
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

а зачем сет?

private var paths = mutableListOf<Path>()

init {
mPaint.color = Color.BLUE
mPaint.strokeWidth = 40f
}

@JvmName("setData1")
fun setData(list: List<PiePiece>?) {
data = list
invalidate()
}

override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
super.onSizeChanged(w, h, oldw, oldh)
animWidth = w.toFloat() / 10f
center_x = layoutParams.height.toFloat() / 2
center_y = layoutParams.height.toFloat() / 2
offset = layoutParams.height.toFloat() / 2
bOffset = (layoutParams.height.toFloat() / 2) - 20f
oval[center_x - offset, center_y - offset, center_x + offset] = center_y + offset
bigOval[center_x - bOffset, center_y - bOffset, center_x + bOffset] = center_y + bOffset
animation()
}

override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
when (MeasureSpec.getMode(widthMeasureSpec)) {
MeasureSpec.UNSPECIFIED,
MeasureSpec.AT_MOST,
MeasureSpec.EXACTLY -> {
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
}
}
}

override fun onDraw(canvas: Canvas?) {
super.onDraw(canvas)
bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
val newCanvas = bitmap?.let { Canvas(it) }
val paint = Paint()
paint.color = Color.BLACK
paint.isAntiAlias = true
data?.forEach {
// if (it.isClicked) {
// canvas?.drawArc(oval, it.start, it.end, true, it.paint)
// newCanvas?.drawArc(oval, it.start, it.end, true, it.paint)
// } else {
// canvas?.drawArc(bigOval, it.start, it.end, true, it.paint)
// newCanvas?.drawArc(bigOval, it.start, it.end, true, it.paint)
// }

canvas?.drawArc(bigOval, it.start, it.end, true, it.paint)
newCanvas?.drawArc(bigOval, it.start, it.end, true, it.paint)

paint.strokeWidth = 20f
paint.textSize = 16f
}
paths.forEach {
canvas?.drawPath(it, paint)
}
paint.color = Color.WHITE
canvas?.drawCircle(center_x, center_y, bOffset - 40f, paint) // рисуем круг
paint.color = Color.BLACK
paint.textSize = 30f
paint.textAlign = Paint.Align.CENTER
val text = data?.find { it.isClicked }?.category
canvas?.drawText(text ?: "", center_x, center_y, paint)
}

@SuppressLint("ClickableViewAccessibility")
override fun onTouchEvent(event: MotionEvent?): Boolean {
val color = event?.let { bitmap?.getPixel(event.x.toInt(), event.y.toInt()) }
if (event?.action == MotionEvent.ACTION_DOWN) return true
if (event?.action == MotionEvent.ACTION_UP) {
data?.forEach {
if (it.paint.color == color) {
pieChartClickListener?.onClick(it.category)
clickedPie = it
animatorSet.start()
}
}
}
return super.onTouchEvent(event)
}

private fun animation() {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Screenshot 2022-12-25 at 18 16 52

Screenshot 2022-12-25 at 18 16 50

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Мне кажется что у тебя тут ошибка, как то странно выглядит выбранный сектор на первой картинке

animator = ValueAnimator.ofFloat(animWidth, animWidth * 2f)
animator.duration = 200
animator.interpolator = OvershootInterpolator()
animator.addUpdateListener {
clickedPie?.let { clickedPie ->
data?.forEach { pie ->
pie.paint.strokeWidth = if(pie.isClicked) 20f else 0f
}
}
requestLayout()
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

а зачем нужен вызов requestLayout?

invalidate()
}
animatorSet.play(animator)
}
}
25 changes: 25 additions & 0 deletions app/src/main/java/otus/homework/customview/PiePiece.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package otus.homework.customview

import android.graphics.Color
import android.graphics.Paint
import kotlin.random.Random

data class PiePiece(
val category: String,
val start: Float,
val end: Float,
val paint: Paint,
val data: List<Metka>,
val isClicked: Boolean = false
)

fun createPaint(): Paint {
val paint = Paint()
paint.color = Color.argb(
255, Random.nextInt(255),Random.nextInt(255), Random.nextInt(255)
)
paint.isAntiAlias = true
paint.style = Paint.Style.FILL_AND_STROKE
paint.strokeCap = Paint.Cap.BUTT
return paint
}
6 changes: 6 additions & 0 deletions app/src/main/java/otus/homework/customview/Point.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package otus.homework.customview

data class Point(
val coorX: Float,
val coorY: Float
)
Loading