feat: Animation run when the chart get in view (#39)

This commit is contained in:
Florian Bouillon 2023-02-14 17:09:47 +01:00 committed by GitHub
parent dd023f96cc
commit 1a812acb9e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
33 changed files with 163 additions and 217 deletions

BIN
.github/screenshot.jpg vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 158 KiB

BIN
.github/screenshot2.jpg vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 214 KiB

BIN
.github/screenshot3.jpg vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 216 KiB

BIN
.github/usage-example.jpg vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 223 KiB

View File

@ -1,4 +1,8 @@
# Crash Handler
<p align="center">
<img alt="Dzeio Charts logo" width="30%" src="sample/src/main/ic_launcher-playstore.png">
</p>
# Dzeio Charts
Highly customizable and easy to use Chart library for android
@ -10,6 +14,52 @@ Add to you dependencies (check the latest release for the version):
- (Gradle Kotlin DSL) Add `implementation("com.dzeio:charts:1.0.0")`
- (Gradle Groovy DSL) Add `implementation "com.dzeio:charts:1.0.0" `
## Usage
_note: full featured examples in the `sample` app_
Add this to your views
```xml
<com.dzeio.charts.ChartView
android:id="@+id/chart_line"
android:layout_width="match_parent"
android:layout_height="200dp" />
```
```kotlin
val chart = binding.chart // get the chart from the view
// setup the Serie
val serie = LineSerie(chart)
// give the serie its entries
serie.entries = // fill this with com.dzeio.charts.Entry
serie.entries = arrayListOf(
Entry(
1,
53f
)
)
// refresh the Chart
chart.refresh()
```
| Basic charts | Fully customized chart | Grouped/Stacked charts |
|:-----------------------------------------:|:-------------------------------------------:|:-------------------------------------------:|
| ![screenshot.jpg](.github/screenshot.jpg) | ![screenshot2.jpg](.github/screenshot2.jpg) | ![screenshot3.jpg](.github/screenshot3.jpg) |
<p align="center">
<b>Example Usage</b>
</p>
<p align="center">in a health oriented step counter with a daily goal</p>
<p align="center">
<img width="40%" src=".github/usage-example.jpg" />
</p>
_note: Every charts used above used a helper function to have Material 3 colors [See MainFragment.kt the materialTheme function](./sample/src/main/java/com/dzeio/chartstest/ui/MainFragment.kt)_
## Build
- Install Android Studio

View File

@ -2,15 +2,18 @@ package com.dzeio.charts
import android.annotation.SuppressLint
import android.content.Context
import android.content.res.Resources
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Paint
import android.graphics.Rect
import android.graphics.RectF
import android.util.AttributeSet
import android.view.MotionEvent
import android.view.View
import com.dzeio.charts.axis.XAxis
import com.dzeio.charts.axis.YAxis
import com.dzeio.charts.components.Animation
import com.dzeio.charts.components.Annotation
import com.dzeio.charts.components.ChartScroll
import com.dzeio.charts.series.SerieInterface
@ -38,6 +41,39 @@ class ChartView @JvmOverloads constructor(context: Context?, attrs: AttributeSet
override var padding: Float = 8f
private var runUpdates = true
init {
viewTreeObserver.addOnScrollChangedListener {
val actualPosition = Rect()
val isGlobalVisible = getGlobalVisibleRect(actualPosition)
val screen = Rect(
0,
0,
Resources.getSystem().displayMetrics.widthPixels,
Resources.getSystem().displayMetrics.heightPixels
)
val displayed = isShown && isGlobalVisible && Rect.intersects(actualPosition, screen)
if (!displayed) {
if (!runUpdates) {
return@addOnScrollChangedListener
}
for (serie in series) {
serie.resetAnimation()
refresh()
runUpdates = false
}
} else if (!runUpdates) {
runUpdates = true
refresh()
} else if (annotator.entry != null && annotator.hideOnScroll) {
annotator.entry = null
refresh()
}
}
}
private val scroller = ChartScroll(this).apply {
var lastMovementX = 0.0
var lastMovementY = 0f
@ -111,6 +147,9 @@ class ChartView @JvmOverloads constructor(context: Context?, attrs: AttributeSet
}
override fun refresh() {
if (!runUpdates) {
return
}
// run Axis logics
xAxis.refresh()
yAxis.refresh()
@ -126,37 +165,46 @@ class ChartView @JvmOverloads constructor(context: Context?, attrs: AttributeSet
// post(animator)
}
override fun onDraw(canvas: Canvas) {
private var lastRun = runUpdates
override fun onDraw(canvas: Canvas) {
// don't draw anything if everything is empty
if (series.isEmpty() || series.maxOf { it.entries.size } == 0) {
if (!runUpdates && lastRun == runUpdates && series.isEmpty() || series.maxOf { it.entries.size } == 0) {
super.onDraw(canvas)
return
}
if (debug) {
// draw corners
canvas.drawRect(rect.apply {
canvas.drawRect(
rect.apply {
set(
padding / 2,
padding / 2,
width.toFloat() - padding / 2,
height.toFloat() - padding / 2
)
}, debugStrokePaint)
},
debugStrokePaint
)
}
var bottom = xAxis.getHeight() ?: 0f
// right distance from the yAxis
val rightDistance = yAxis.onDraw(canvas, rect.apply {
val rightDistance = yAxis.onDraw(
canvas,
rect.apply {
set(padding, padding, width.toFloat() - padding, height.toFloat() - bottom - padding)
})
}
)
bottom = xAxis.onDraw(canvas, rect.apply {
bottom = xAxis.onDraw(
canvas,
rect.apply {
set(padding, 0f, width.toFloat() - rightDistance - padding, height.toFloat() - padding)
})
}
)
// chart draw rectangle
seriesRect.apply {
@ -187,7 +235,9 @@ class ChartView @JvmOverloads constructor(context: Context?, attrs: AttributeSet
annotator.onDraw(canvas, seriesRect)
if (needRedraw && runUpdates) {
postDelayed({ this.invalidate() }, animator.getDelay().toLong())
}
super.onDraw(canvas)
}

View File

@ -2,6 +2,7 @@ package com.dzeio.charts
import com.dzeio.charts.axis.XAxisInterface
import com.dzeio.charts.axis.YAxisInterface
import com.dzeio.charts.components.Animation
import com.dzeio.charts.components.Annotation
import com.dzeio.charts.series.SerieInterface

View File

@ -1,4 +1,4 @@
package com.dzeio.charts
package com.dzeio.charts.components
import kotlin.math.abs

View File

@ -175,4 +175,8 @@ class BarSerie(
override fun refresh() {
// TODO("Not yet implemented")
}
override fun resetAnimation() {
entriesCurrentY.clear()
}
}

View File

@ -177,4 +177,8 @@ class LineSerie(
override fun refresh() {
// TODO("Not yet implemented")
}
override fun resetAnimation() {
entriesCurrentY.clear()
}
}

View File

@ -45,4 +45,6 @@ sealed interface SerieInterface {
* this is where the pre-logic is handled to make [onDraw] quicker
*/
fun refresh()
fun resetAnimation()
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View File

@ -1,30 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<path android:pathData="M31,63.928c0,0 6.4,-11 12.1,-13.1c7.2,-2.6 26,-1.4 26,-1.4l38.1,38.1L107,108.928l-32,-1L31,63.928z">
<aapt:attr name="android:fillColor">
<gradient
android:endX="85.84757"
android:endY="92.4963"
android:startX="42.9492"
android:startY="49.59793"
android:type="linear">
<item
android:color="#44000000"
android:offset="0.0" />
<item
android:color="#00000000"
android:offset="1.0" />
</gradient>
</aapt:attr>
</path>
<path
android:fillColor="#FFFFFF"
android:fillType="nonZero"
android:pathData="M65.3,45.828l3.8,-6.6c0.2,-0.4 0.1,-0.9 -0.3,-1.1c-0.4,-0.2 -0.9,-0.1 -1.1,0.3l-3.9,6.7c-6.3,-2.8 -13.4,-2.8 -19.7,0l-3.9,-6.7c-0.2,-0.4 -0.7,-0.5 -1.1,-0.3C38.8,38.328 38.7,38.828 38.9,39.228l3.8,6.6C36.2,49.428 31.7,56.028 31,63.928h46C76.3,56.028 71.8,49.428 65.3,45.828zM43.4,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2c-0.3,-0.7 -0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C45.3,56.528 44.5,57.328 43.4,57.328L43.4,57.328zM64.6,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2s-0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C66.5,56.528 65.6,57.328 64.6,57.328L64.6,57.328z"
android:strokeWidth="1"
android:strokeColor="#00000000" />
</vector>

View File

@ -2,169 +2,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
android:viewportWidth="512"
android:viewportHeight="512"
android:tint="#FFFFFF">
<path
android:fillColor="#3DDC84"
android:pathData="M0,0h108v108h-108z" />
<path
android:fillColor="#00000000"
android:pathData="M9,0L9,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,0L19,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M29,0L29,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M39,0L39,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M49,0L49,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M59,0L59,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M69,0L69,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M79,0L79,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M89,0L89,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M99,0L99,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,9L108,9"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,19L108,19"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,29L108,29"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,39L108,39"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,49L108,49"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,59L108,59"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,69L108,69"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,79L108,79"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,89L108,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,99L108,99"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,29L89,29"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,39L89,39"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,49L89,49"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,59L89,59"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,69L89,69"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,79L89,79"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M29,19L29,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M39,19L39,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M49,19L49,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M59,19L59,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M69,19L69,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M79,19L79,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
android:pathData="M0,0h512v512h-512z"
android:fillColor="#FF000000"/>
</vector>

View File

@ -0,0 +1,13 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
android:viewportWidth="48"
android:viewportHeight="48"
android:autoMirrored="true">
<group android:scaleX="0.43"
android:scaleY="0.43"
android:translateX="13.68"
android:translateY="13.68">
<path android:fillColor="#FF4CAF50" android:pathData="M5.5,40.5q-1.45,0 -2.475,-1.025Q2,38.45 2,37q0,-1.45 1.025,-2.475Q4.05,33.5 5.5,33.5q0.25,0 0.5,0.025t0.65,0.125l10,-10q-0.1,-0.4 -0.125,-0.65 -0.025,-0.25 -0.025,-0.5 0,-1.45 1.025,-2.475Q18.55,19 20,19q1.45,0 2.475,1.025Q23.5,21.05 23.5,22.5q0,0.1 -0.15,1.15l5.5,5.5q0.4,-0.1 0.65,-0.125 0.25,-0.025 0.5,-0.025t0.5,0.025q0.25,0.025 0.65,0.125l8,-8q-0.1,-0.4 -0.125,-0.65Q39,20.25 39,20q0,-1.45 1.025,-2.475Q41.05,16.5 42.5,16.5q1.45,0 2.475,1.025Q46,18.55 46,20q0,1.45 -1.025,2.475Q43.95,23.5 42.5,23.5q-0.25,0 -0.5,-0.025t-0.65,-0.125l-8,8q0.1,0.4 0.125,0.65 0.025,0.25 0.025,0.5 0,1.45 -1.025,2.475Q31.45,36 30,36q-1.45,0 -2.475,-1.025Q26.5,33.95 26.5,32.5q0,-0.25 0.025,-0.5t0.125,-0.65l-5.5,-5.5q-0.4,0.1 -0.65,0.125 -0.25,0.025 -0.5,0.025 -0.1,0 -1.15,-0.15l-10,10q0.1,0.4 0.125,0.65 0.025,0.25 0.025,0.5 0,1.45 -1.025,2.475Q6.95,40.5 5.5,40.5ZM8,18.4l-1,-2.2 -2.2,-1 2.2,-1L8,12l1,2.2 2.2,1 -2.2,1ZM30,15.85 L28.45,12.55 25.15,11 28.45,9.45L30,6.15l1.55,3.3 3.3,1.55 -3.3,1.55Z"/>
</group>
</vector>

View File

@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background" />
<foreground android:drawable="@drawable/ic_launcher_foreground" />
<background android:drawable="@drawable/ic_launcher_background"/>
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
<monochrome android:drawable="@drawable/ic_launcher_foreground"/>
</adaptive-icon>

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background"/>
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
<monochrome android:drawable="@drawable/ic_launcher_foreground"/>
</adaptive-icon>

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 982 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="ic_launcher_background">#FFFFFF</color>
</resources>