-
Notifications
You must be signed in to change notification settings - Fork 132
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
base: master
Are you sure you want to change the base?
Changes from all commits
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 |
---|---|---|
@@ -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) { | ||
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++ | ||
} | ||
} | ||
} | ||
|
||
} |
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) | ||
} | ||
} |
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 | ||
) |
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( | ||
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. Реализуй, пожалуйста, сохранение/восстановление стейта |
||
context: Context, | ||
attrs: AttributeSet? = null, | ||
defStyleAttr: Int = 0 | ||
) : View(context, attrs, defStyleAttr) { | ||
private var animator: ValueAnimator = ValueAnimator.ofFloat() | ||
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. Он разве нужен на уровне класса? |
||
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() | ||
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 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() { | ||
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. Мне кажется что у тебя тут ошибка, как то странно выглядит выбранный сектор на первой картинке |
||
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() | ||
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. а зачем нужен вызов requestLayout? |
||
invalidate() | ||
} | ||
animatorSet.play(animator) | ||
} | ||
} |
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 | ||
} |
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 | ||
) |
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.
А ты не хочешь тут описать логику расчетов?