-
Notifications
You must be signed in to change notification settings - Fork 168
Canvas homework #138
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
base: master
Are you sure you want to change the base?
Canvas homework #138
Changes from 1 commit
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,9 @@ | ||
| package otus.homework.customview | ||
|
|
||
| data class CategoryModel( | ||
| val id: Int, | ||
| val name: String, | ||
| val amount: Int, | ||
| val category: String, | ||
| val color: Int, | ||
| ) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,133 @@ | ||
| package otus.homework.customview | ||
|
|
||
| import android.annotation.SuppressLint | ||
| import android.content.Context | ||
| import android.graphics.Canvas | ||
| import android.graphics.Paint | ||
| import android.graphics.RectF | ||
| import android.util.AttributeSet | ||
| import android.view.MotionEvent | ||
| import android.view.View | ||
| import kotlin.math.atan2 | ||
| import kotlin.math.min | ||
| import kotlin.math.sqrt | ||
|
|
||
| class PieChart @JvmOverloads constructor( | ||
| context: Context, | ||
| attrs: AttributeSet? = null, | ||
| var onSliceClick: ((String) -> Unit)? = null | ||
| ): View(context, attrs) { | ||
|
|
||
| val Int.dp: Float | ||
| get() = this * resources.displayMetrics.density | ||
|
|
||
| val pieWith = 200.dp | ||
| val pieHeight = 200.dp | ||
|
|
||
| private var data: List<CategoryModel> = emptyList() | ||
| private val sectors = mutableListOf<Sector>() | ||
|
|
||
| fun setData(items: List<CategoryModel>) { | ||
| data = items | ||
| invalidate() | ||
| } | ||
|
|
||
| init{ | ||
| if (isInEditMode) { | ||
| //setValues(listOf(1,2,3,4,5)) | ||
| } | ||
| } | ||
|
|
||
| override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { | ||
| val wMode = MeasureSpec.getMode(widthMeasureSpec) | ||
| val hMode = MeasureSpec.getMode(heightMeasureSpec) | ||
| val wSize = MeasureSpec.getSize(widthMeasureSpec) | ||
| val hSize = MeasureSpec.getSize(heightMeasureSpec) | ||
|
|
||
| when (wMode) { | ||
|
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. не нужно вызывать desiredWidth и desiredHeight желаемые размеры
Author
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. поправил |
||
| MeasureSpec.EXACTLY -> { | ||
| setMeasuredDimension(wSize, hSize) | ||
| } | ||
| MeasureSpec.AT_MOST -> { | ||
| val newW = min(pieWith.toInt(), wSize) | ||
| setMeasuredDimension(newW, hSize) | ||
| } | ||
| MeasureSpec.UNSPECIFIED -> { | ||
| setMeasuredDimension(pieWith.toInt(), hSize) | ||
| } | ||
| } | ||
|
|
||
| when (hMode) { | ||
| MeasureSpec.EXACTLY -> { | ||
| setMeasuredDimension(wSize, hSize) | ||
| } | ||
| MeasureSpec.AT_MOST -> { | ||
| val newH = min(pieHeight.toInt(), wSize) | ||
| setMeasuredDimension(wSize, newH) | ||
| } | ||
| MeasureSpec.UNSPECIFIED -> { | ||
| setMeasuredDimension(wSize, pieHeight.toInt()) | ||
| } | ||
| } | ||
| super.onMeasure(widthMeasureSpec, heightMeasureSpec) | ||
|
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. это не надо вызывать
Author
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. поправил |
||
| } | ||
|
|
||
| val paint = Paint().apply { | ||
| style = Paint.Style.FILL | ||
| } | ||
|
|
||
| val rect = RectF(0f, 0f, pieWith, pieHeight) | ||
|
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. график всегда 200 на 200 и не подстраивается под размеры вью, нужно реализовать
Author
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. поправил |
||
|
|
||
| @SuppressLint("DrawAllocation") | ||
| override fun onDraw(canvas: Canvas) { | ||
| super.onDraw(canvas) | ||
|
|
||
| sectors.clear() | ||
|
|
||
| if(data.isEmpty()) return | ||
|
|
||
| val total = data.sumOf { it.amount.toDouble() }.toFloat() | ||
| var startAngle = 0f | ||
|
|
||
| data.forEach {item -> | ||
| paint.color = item.color | ||
| val swipeAngle = (item.amount/total) * 360f | ||
|
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. тут при нулевом
Author
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. поправил |
||
|
|
||
| canvas.drawArc(rect, startAngle, swipeAngle, true, paint) | ||
|
|
||
| sectors.add( | ||
|
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. в
Author
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. поправил |
||
| Sector( | ||
| category = item.category, | ||
| start = startAngle, | ||
| end = startAngle + swipeAngle | ||
| ) | ||
| ) | ||
|
|
||
| startAngle += swipeAngle | ||
| } | ||
| } | ||
|
|
||
| override fun onTouchEvent(event: MotionEvent): Boolean { | ||
|
|
||
| if (event.action != MotionEvent.ACTION_DOWN) return true | ||
|
|
||
| val dx = event.x - rect.centerX() | ||
| val dy = event.y - rect.centerY() | ||
|
|
||
| val distance = sqrt(dx * dx + dy * dy) | ||
|
|
||
| if (distance > rect.width() / 2f) return false | ||
|
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. тут лучше возвращать
Author
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. поправил |
||
|
|
||
| val angle = ((Math.toDegrees( | ||
| atan2(dy.toDouble(), dx.toDouble()) | ||
| ) + 360) % 360).toFloat() | ||
|
|
||
| sectors.forEach { sector -> | ||
| if (angle in sector.start..sector.end) { | ||
| onSliceClick?.invoke(sector.category) | ||
| return true | ||
| } | ||
| } | ||
| return true | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,40 @@ | ||
| package otus.homework.customview | ||
|
|
||
| import android.content.Context | ||
| import android.graphics.Color | ||
| import androidx.lifecycle.ViewModel | ||
| import androidx.lifecycle.ViewModelProvider | ||
| import otus.homework.customview.data.JsonMapper | ||
|
|
||
| class PieViewModel( | ||
| private val jsonMapper: JsonMapper | ||
| ) : ViewModel() { | ||
|
|
||
| val data = jsonMapper.mapJson() | ||
|
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. тут каждый payload превращается в отдельный сектор, а данные должны агрегироваться по категориям
Author
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. поправил |
||
| .map { | ||
| CategoryModel( | ||
| id = it.id, | ||
| name = it.name, | ||
| amount = it.amount, | ||
| category = it.category, | ||
| color = randomColor() | ||
| ) | ||
| } | ||
| } | ||
|
|
||
| private fun randomColor(): Int { | ||
| return Color.rgb( | ||
| (0..255).random(), | ||
| (0..255).random(), | ||
| (0..255).random(), | ||
| ) | ||
| } | ||
|
|
||
| class PieViewModelFactory( | ||
| private val context: Context | ||
| ) : ViewModelProvider.NewInstanceFactory() { | ||
| override fun <T : ViewModel> create(modelClass: Class<T>): T { | ||
| val jsonMapper = JsonMapper(context.applicationContext) | ||
| return PieViewModel(jsonMapper) as T | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| package otus.homework.customview | ||
|
|
||
| data class Sector( | ||
| val category: String, | ||
| val start: Float, | ||
| val end: Float | ||
| ) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,20 @@ | ||
| package otus.homework.customview.data | ||
|
|
||
| import android.content.Context | ||
| import com.google.gson.Gson | ||
| import com.google.gson.reflect.TypeToken | ||
| import otus.homework.customview.R | ||
| import otus.homework.customview.dto.CategoryDTO | ||
|
|
||
| class JsonMapper(private val context: Context) { | ||
|
|
||
| fun mapJson(): List<CategoryDTO> { | ||
| val json = context.resources | ||
| .openRawResource(R.raw.payload) | ||
| .bufferedReader() | ||
| .use { it.readText() } | ||
|
|
||
| val type = object : TypeToken<List<CategoryDTO>>() {}.type | ||
| return Gson().fromJson(json, type) | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| package otus.homework.customview.dto | ||
|
|
||
| data class CategoryDTO( | ||
| val id: Int, | ||
| val name: String, | ||
| val amount: Int, | ||
| val category: String, | ||
| ) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,19 +1,21 @@ | ||
| <?xml version="1.0" encoding="utf-8"?> | ||
| <androidx.constraintlayout.widget.ConstraintLayout | ||
| <LinearLayout | ||
| 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" | ||
| android:orientation="vertical" | ||
| tools:context=".MainActivity"> | ||
|
|
||
| <otus.homework.customview.PieChart | ||
| android:id="@+id/pieChartView" | ||
| android:layout_width="match_parent" | ||
| android:layout_height="0dp" | ||
| android:layout_weight="1"/> | ||
|
|
||
| <TextView | ||
| android:layout_width="wrap_content" | ||
| 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:id="@+id/categoryName" | ||
| android:layout_width="match_parent" | ||
| android:layout_height="0dp" | ||
| android:layout_weight="1"/> | ||
|
|
||
| </androidx.constraintlayout.widget.ConstraintLayout> | ||
| </LinearLayout> |
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.
не реализован механизм сохранения состояния, нужно сделать
onSaveInstanceStateиonRestoreInstanceStateThere 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.
поправил, спасибо