Skip to content
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
13 changes: 10 additions & 3 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@ plugins {
}

android {
compileSdkVersion 30
compileSdkVersion 34
buildToolsVersion "30.0.3"

defaultConfig {
applicationId "otus.homework.customview"
minSdkVersion 23
targetSdkVersion 30
minSdkVersion 24
targetSdkVersion 34
versionCode 1
versionName "1.0"

Expand Down Expand Up @@ -42,4 +42,11 @@ dependencies {
testImplementation 'junit:junit:4.+'
androidTestImplementation 'androidx.test.ext:junit:1.1.2'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
implementation 'androidx.core:core-ktx:1.12.0'
implementation 'androidx.appcompat:appcompat:1.6.1'
implementation 'com.google.android.material:material:1.11.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
testImplementation 'junit:junit:'
androidTestImplementation 'androidx.test.ext:junit:1.1.5'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
}
5 changes: 3 additions & 2 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.CustomView">
<activity android:name=".MainActivity">
<activity android:name=".MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />

Expand All @@ -19,4 +20,4 @@
</activity>
</application>

</manifest>
</manifest>
105 changes: 105 additions & 0 deletions app/src/main/java/otus/homework/customview/GraphChart.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
package otus.homework.customview

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

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

private var points: List<Float> = listOf()

private val axisPaint = Paint()
private val linePaint = Paint()
private val path = Path()

private var xStep = (width / 10).toFloat()
private var yStep = (height / 20).toFloat()
private var originY = 0f
private var originX = 0f

fun setData(points: List<Float>) {
this.points = points
}

private fun drawAxisLines(canvas: Canvas) {
canvas.drawLine(xStep, yStep, xStep, height - 10f, axisPaint)
canvas.drawLine(
10f, height - yStep,
width - xStep, height - yStep, axisPaint
)
}

private fun drawChartLines(canvas: Canvas) {
path.moveTo(originX, originY)
xStep = width / points.size.toFloat()
yStep = height / (points.max() - points.min())
val min = points.min()
points.onEach {
path.lineTo(originX, (it - min) * yStep)

originX += xStep
}
canvas.drawPath(path, linePaint)
}

private fun drawChart(canvas: Canvas) {
drawAxisLines(canvas)
drawChartLines(canvas)
}

override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
val widthMode = MeasureSpec.getMode(widthMeasureSpec)
var width = MeasureSpec.getSize(widthMeasureSpec)
val heightMode = MeasureSpec.getMode(heightMeasureSpec)
var height = MeasureSpec.getSize(heightMeasureSpec)

when (widthMode) {
MeasureSpec.AT_MOST, MeasureSpec.UNSPECIFIED -> if (width > height) {
width = (points.size) * convertToDp(50)
}
}
when (heightMode) {
MeasureSpec.AT_MOST, MeasureSpec.UNSPECIFIED -> if (height > width) {
height = (points.size) * convertToDp(50)
}
}

setMeasuredDimension(width, height)
}

override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
linePaint.style = Paint.Style.STROKE
linePaint.strokeWidth = 12f
linePaint.color = Color.RED

axisPaint.style = Paint.Style.STROKE
axisPaint.strokeWidth = 4f
axisPaint.color = Color.GREEN
drawChart(canvas)
}

// override fun onSaveInstanceState(): Parcelable {
// return ExpenseState(super.onSaveInstanceState()).apply {
// this.points = this.points
// }
// }
//
// override fun onRestoreInstanceState(state: Parcelable?) {
// if (state is ExpenseState) {
// points = state.points
// }
// super.onRestoreInstanceState(state)
// }

private fun convertToDp(number: Int) = (context.resources.displayMetrics.density * number).toInt()
}
28 changes: 27 additions & 1 deletion app/src/main/java/otus/homework/customview/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,36 @@ package otus.homework.customview

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.widget.Toast
import org.json.JSONArray

class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)

// findViewById<PieChart>(R.id.pie).apply {
// setData(getExpenses())
// setOnClickListenerItem {
// Toast.makeText(applicationContext, "${it.name}:${it.amount}", Toast.LENGTH_SHORT)
// .show()
// }
// }

findViewById<GraphChart>(R.id.pie).setData(listOf(10f, 20f, 40f, 30f, 20f, 10f, 20f, 40f, 30f, 20f))
}

private fun getExpenses(): List<Playload> {
val jsonArray = JSONArray(
applicationContext.resources.openRawResource(R.raw.payload).reader().readText()
)
return (0 until jsonArray.length()).map {
val jsonObj = jsonArray.getJSONObject(it)
Playload(
jsonObj.optInt("id"),
jsonObj.optString("name", ""),
jsonObj.optInt("amount"),
)
}
}
}
}
147 changes: 147 additions & 0 deletions app/src/main/java/otus/homework/customview/PieChart.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
package otus.homework.customview

import android.annotation.SuppressLint
import android.content.Context
import android.graphics.Canvas
import android.graphics.Paint
import android.graphics.Path
import android.graphics.RectF
import android.graphics.Region
import android.os.Parcelable
import android.util.AttributeSet
import android.view.MotionEvent
import android.view.View
import androidx.core.content.ContextCompat.getColor

class PieChart @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0,
defStyleRes: Int = 0
) : View(context, attrs, defStyleAttr, defStyleRes) {
private val colors = listOf(
getColor(context, R.color.abc),
getColor(context, R.color.rigla),
getColor(context, R.color.five),
getColor(context, R.color.truffo),
getColor(context, R.color.simple_wine),
getColor(context, R.color.abc_express),
getColor(context, R.color.uber),
getColor(context, R.color.metro),
getColor(context, R.color.dentist),
getColor(context, R.color.five_2),
getColor(context, R.color.pool),
getColor(context, R.color.uber_2),
)
private var paths = listOf<Path>()
private val paint = Paint()
private var path = Path()

private val rectF = RectF()
private var centerX = 0f
private var centerY = 0f

private var playloads = listOf<Playload>()
private var playloadsAmountSum = 0

private var index = 0
private var section = 0f
private var start = 0f
var touchListener : ((Playload) -> Unit)? = null

fun setData(playloads: List<Playload>) {
this.playloads = playloads
paths = playloads.map { Path() }
playloadsAmountSum = playloads.sumOf { it.amount }
}

fun setOnClickListenerItem(action: ((Playload) -> Unit)?) {
touchListener = action
}

override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec)

val widthMode = MeasureSpec.getMode(widthMeasureSpec)
var width = MeasureSpec.getSize(widthMeasureSpec)
val heightMode = MeasureSpec.getMode(heightMeasureSpec)
var height = MeasureSpec.getSize(heightMeasureSpec)

when (widthMode) {
MeasureSpec.AT_MOST, MeasureSpec.UNSPECIFIED -> if (width > height) {
width = height
}
}
when (heightMode) {
MeasureSpec.AT_MOST, MeasureSpec.UNSPECIFIED -> if (height > width) {
height = width
}
}

val horizontalOffset = if (height > width) 0f else (width - height).toFloat() / 2
val verticalOffset = if (width > height) 0f else (height - width).toFloat() / 2

rectF.left = horizontalOffset + paddingLeft
rectF.top = verticalOffset + paddingTop
rectF.right = width - horizontalOffset - paddingRight
rectF.bottom = height - verticalOffset - paddingBottom

centerX = width.toFloat() / 2
centerY = height.toFloat() / 2

setMeasuredDimension(width, height)
}

override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)

playloads.forEach() { item ->
index = item.id - 1
section = (item.amount.toFloat() / playloadsAmountSum) * 360
paint.color = colors[index % colors.size]
paths[index].arcTo(
rectF,
start,
section,
true
)
paths[index].lineTo(centerX, centerY)
paths[index].close()

canvas.drawPath(paths[index], paint)
start += section
}
}

@SuppressLint("ClickableViewAccessibility")
override fun onTouchEvent(event: MotionEvent?): Boolean {

if(event?.action == MotionEvent.ACTION_DOWN){
val rect = RectF()
val region = Region()

playloads.forEachIndexed { index, outlay ->
paths[index].computeBounds(rect, false)
region.setPath(paths[index], Region(rect.left.toInt(), rect.top.toInt(), rect.right.toInt(), rect.bottom.toInt())
)
if (region.contains(event.x.toInt(), event.y.toInt())) {
touchListener?.invoke(outlay)
}
}
}
return super.onTouchEvent(event)
}

override fun onSaveInstanceState(): Parcelable {
return PlayloadState(super.onSaveInstanceState()).apply {
this.playloads = playloads
}
}

override fun onRestoreInstanceState(state: Parcelable?) {
if (state is PlayloadState) {
playloads = state.playloads.toMutableList()
}
super.onRestoreInstanceState(state)
}
}
7 changes: 7 additions & 0 deletions app/src/main/java/otus/homework/customview/Playload.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package otus.homework.customview

data class Playload(
val id: Int,
val name: String,
val amount: Int,
)
15 changes: 15 additions & 0 deletions app/src/main/java/otus/homework/customview/PlayloadState.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package otus.homework.customview

import android.os.Parcel
import android.os.Parcelable
import android.view.View

class PlayloadState(superState: Parcelable?) : View.BaseSavedState(superState) {

var playloads: List<Playload> = listOf()

override fun writeToParcel(out: Parcel, flags: Int) {
super.writeToParcel(out, flags)
out.writeList(playloads)
}
}
17 changes: 8 additions & 9 deletions app/src/main/res/layout/activity_main.xml
Original file line number Diff line number Diff line change
@@ -1,19 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
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"
tools:context=".MainActivity">

<TextView
android:layout_width="wrap_content"
<otus.homework.customview.GraphChart
android:id="@+id/pie"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Hello World!"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:layout_gravity="center"/>

</androidx.constraintlayout.widget.ConstraintLayout>
</FrameLayout>
Loading