1
0
mirror of https://github.com/dzeiocom/OpenHealth.git synced 2025-04-22 19:02:16 +00:00

fix: Value higher than max being slow

WIP: Better scrolling
This commit is contained in:
Florian Bouillon 2022-07-26 23:32:37 +02:00
parent 3aa0861b1f
commit 4bec653f75
Signed by: Florian Bouillon
GPG Key ID: 0A288052C94BD2C8
10 changed files with 121 additions and 56 deletions

View File

@ -1,11 +1,15 @@
package com.dzeio.openhealth.ui.steps
import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.core.widget.NestedScrollView
import androidx.recyclerview.widget.LinearLayoutManager
import com.dzeio.charts.Entry
import com.dzeio.openhealth.Application
import com.dzeio.openhealth.R
import com.dzeio.openhealth.adapters.StepsAdapter
import com.dzeio.openhealth.core.BaseFragment
import com.dzeio.openhealth.databinding.FragmentStepsHomeBinding
@ -19,6 +23,10 @@ import java.util.Locale
class StepsHomeFragment :
BaseFragment<StepsHomeViewModel, FragmentStepsHomeBinding>(StepsHomeViewModel::class.java) {
companion object {
const val TAG = "${Application.TAG}/SHFragment"
}
override val bindingInflater: (LayoutInflater, ViewGroup?, Boolean) -> FragmentStepsHomeBinding =
FragmentStepsHomeBinding::inflate
@ -46,7 +54,7 @@ class StepsHomeFragment :
viewModel.items.observe(viewLifecycleOwner) { list ->
adapter.set(list)
// chart.numberOfEntries = list.size / 2
chart.xAxis.entriesDisplayed = 30
chart.numberOfLabels = 2
// chart.animation.enabled = false
@ -80,5 +88,21 @@ class StepsHomeFragment :
chart.refresh()
}
val scrollView = requireActivity().findViewById<NestedScrollView>(R.id.scrollView)
var scrollEnabled = false
scrollView.setOnTouchListener { view, _ ->
view.performClick()
if (scrollEnabled) {
} else {
return@setOnTouchListener !scrollEnabled
}
return@setOnTouchListener true
}
binding.chart.setOnToggleScroll {
Log.d(TAG, it.toString())
scrollEnabled = it
}
}
}

View File

@ -35,6 +35,7 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/scrollView"
app:layout_behavior="@string/appbar_scrolling_view_behavior">

View File

@ -1,6 +1,7 @@
package com.dzeio.charts
import kotlin.math.abs
import kotlin.math.max
data class Animation(
/**
@ -27,25 +28,39 @@ data class Animation(
*
* @return the new updated value
*/
fun updateValue(maxValue: Float, targetValue: Float, currentValue: Float): Float {
fun updateValue(
maxValue: Float,
targetValue: Float,
currentValue: Float,
minValue: Float,
minStep: Float
): Float {
if (!enabled) {
return targetValue
}
val moveValue = (maxValue - targetValue) / refreshRate
if (currentValue < minValue) {
return minValue
}
val moveValue = max(minStep, (maxValue - targetValue) / refreshRate)
var result = targetValue
if (currentValue < targetValue) {
result = currentValue + moveValue
result = currentValue + moveValue
} else if (currentValue > targetValue) {
result = currentValue - moveValue
}
if (abs(targetValue - currentValue) < moveValue) {
if (
abs(targetValue - currentValue) <= moveValue ||
result < minValue ||
result > maxValue
) {
return targetValue
}
return result
}
fun getDelay() = this.duration / this.refreshRate
}
}

View File

@ -3,4 +3,4 @@ package com.dzeio.charts
data class Entry(
val x: Double,
val y: Float
)
)

View File

@ -19,4 +19,4 @@ class XAxisLabels {
it.textAlign = Paint.Align.CENTER
}
}
}
}

View File

@ -16,13 +16,10 @@ class XAxis<T> {
/**
* Offset in the list
*
* WILL CHANGE AS SOON AS SCROLLING IS AVAILABLE
*/
var baseOffset = 0
var onValueFormat: (it: T) -> String = onValueFormat@{
return@onValueFormat it.toString()
}
}
}

View File

@ -9,4 +9,4 @@ class YAxis<T> {
@ColorInt
var color = Color.parseColor("#FC496D")
}
}

View File

@ -1,4 +0,0 @@
package com.dzeio.charts.builders
class ChartBuilder {
}

View File

@ -22,6 +22,8 @@ class BarChartView @JvmOverloads constructor(context: Context?, attrs: Attribute
const val TAG = "DzeioCharts/BarView"
}
var debug: Boolean = false
/**
* Number of entries displayed at the same time
*/
@ -47,7 +49,7 @@ class BarChartView @JvmOverloads constructor(context: Context?, attrs: Attribute
private var barWidth: Int = 0
private val percentList: ArrayList<Float> = ArrayList()
private var percentList: ArrayList<Float> = ArrayList()
/**
* value goes from 1 to 0 (1 at bottom, 0 at top)
@ -68,10 +70,24 @@ class BarChartView @JvmOverloads constructor(context: Context?, attrs: Attribute
private val animator: Runnable = object : Runnable {
override fun run() {
var needNewFrame = false
// var txt = ""
// for (tpl in targetPercentList) {
// txt += "$tpl, "
// }
// Log.d(TAG, txt)
for (i in targetPercentList.indices) {
val value = animation.updateValue(1f, targetPercentList[i], percentList[i])
val value = animation.updateValue(
1f,
targetPercentList[i],
percentList[i],
0f,
0.01f
)
if (value != percentList[i]) {
// if (!needNewFrame) {
// Log.d(TAG, "$i, $value, ${percentList[i]}")
// }
needNewFrame = true
percentList[i] = value
}
@ -87,27 +103,17 @@ class BarChartView @JvmOverloads constructor(context: Context?, attrs: Attribute
private var bottomTexts: ArrayList<String> = arrayListOf()
// init {
// val mockList = ArrayList<Entry>()
// for (i in 0 until 25) {
// mockList.add(Entry(i.toDouble(), i.toFloat()))
// }
//
// list = mockList
//
// this.refresh()
// }
var previousRefresh = movementOffset
fun refresh() {
val r = Rect()
//// prepare bottom texts
// // prepare bottom texts
bottomTextDescent = 0
bottomTextHeight = 0
bottomTexts = arrayListOf()
//// prepare values
// // prepare values
// set the bar Width (also handle div by 0)
barWidth = measuredWidth / max(min(list.size, getDisplayedEntries()), 1) - spacing
@ -115,19 +121,21 @@ class BarChartView @JvmOverloads constructor(context: Context?, attrs: Attribute
// calculate max depending on the maximum value displayed or set in the yAxis params
val max: Float = if (yAxis.max != null) yAxis.max!! else {
var calculatedMax = 0f
for (entry in list.subList(this.getXOffset(), getDisplayedEntries() + this.getXOffset())) {
for (entry in list.subList(
this.getXOffset(),
getDisplayedEntries() + this.getXOffset()
)) {
if (entry.y > calculatedMax) calculatedMax = entry.y
}
calculatedMax
if (calculatedMax < 0) 0f else calculatedMax
}
// make sure the target list
// Log.d(TAG, list.size.toString())
targetPercentList = arrayListOf()
for ((i, item) in list.withIndex()) {
//// Process bottom texts
for (item in list.subList(getXOffset(), getXOffset() + getDisplayedEntries())) {
// // Process bottom texts
val text = xAxis.onValueFormat(item.x)
bottomTexts.add(text)
@ -145,13 +153,23 @@ class BarChartView @JvmOverloads constructor(context: Context?, attrs: Attribute
bottomTextDescent = descent
}
//// process values
// // process values
// add to animations the values
targetPercentList.add(min(1 - item.y / max, 1f))
targetPercentList.add(1 - item.y / max)
}
// post list
val movement = movementOffset - previousRefresh
previousRefresh = movementOffset
// Log.d(TAG, movement.toString())
if (movement >= 1) {
percentList = percentList.subList(1, percentList.size).toCollection(ArrayList())
percentList.add(1f)
} else if (movement <= -1) {
percentList = percentList.subList(0, percentList.size - 1).toCollection(ArrayList())
percentList.add(0, 1f)
}
if (percentList.isEmpty() || percentList.size < targetPercentList.size) {
val temp = targetPercentList.size - percentList.size
for (i in 0 until temp) {
@ -165,7 +183,6 @@ class BarChartView @JvmOverloads constructor(context: Context?, attrs: Attribute
}
// Misc operations
fgPaint = Paint().apply {
isAntiAlias = true
color = yAxis.color
@ -178,18 +195,26 @@ class BarChartView @JvmOverloads constructor(context: Context?, attrs: Attribute
override fun onDraw(canvas: Canvas) {
if (percentList.isNotEmpty()) {
// draw each rectangles
for (i in 1..getDisplayedEntries()) {
for (i in 1..percentList.size) {
// Log.d(TAG, percentList[i - 1].toString())
val left = spacing * i + barWidth * (i - 1).toFloat()
// Log.d(TAG, "$spacing, $i, $barWidth = $left")
val right = (spacing + barWidth) * i.toFloat()
val bottom = height - bottomTextHeight - textTopMargin.toFloat()
val top = bottom * percentList[this.getXOffset() + i - 1]
val top = bottom * percentList[i - 1]
// create rounded rect
canvas.drawRoundRect(left, top, right, bottom, 8f, 8f, fgPaint)
// remove the bottom corners DUH
canvas.drawRect(left, max(top, bottom - 8f), right, bottom, fgPaint)
if (debug) {
canvas.drawText(
bottomTexts[i - 1].toString(),
left + (right - left) / 2,
top + (bottom - top) / 2,
xAxis.labels.build()
)
}
}
}
if (bottomTexts.isNotEmpty() && numberOfLabels > 0) {
@ -206,7 +231,6 @@ class BarChartView @JvmOverloads constructor(context: Context?, attrs: Attribute
// Log.i(TAG, "$size / max($numberOfLabels - 2, 2) = $items")
for (s in bottomTexts) {
if ((numberOfLabels <= 2 || i % items != 0) && i != 1 && i != size) {
i++
continue
@ -246,17 +270,16 @@ class BarChartView @JvmOverloads constructor(context: Context?, attrs: Attribute
override fun onZoomChanged(scale: Float) {
Log.d(TAG, "New Zoom: $scale")
zoom = scale
zoom = (scale * 1.2).toFloat()
refresh()
}
private fun getXOffset(): Int {
return min(max(0, xAxis.baseOffset + movementOffset), list.size - 1 - getDisplayedEntries())
return min(max(0, xAxis.baseOffset + movementOffset), list.size - getDisplayedEntries())
}
private fun getDisplayedEntries(): Int {
// Log.d(TAG, "Number of entries displayed ${list.size}, ${xAxis.entriesDisplayed} + (($zoom - 100) * 10) = ${xAxis.entriesDisplayed + ((zoom - 100) * 10).toInt()}")
return max(1, min(list.size, xAxis.entriesDisplayed + ((zoom - 100) * 10).toInt()))
}
}

View File

@ -6,7 +6,6 @@ import android.view.MotionEvent
import android.view.MotionEvent.INVALID_POINTER_ID
import android.view.ScaleGestureDetector
import android.view.View
import androidx.core.view.MotionEventCompat
abstract class BaseChart @JvmOverloads constructor(context: Context?, attrs: AttributeSet? = null) :
View(context, attrs) {
@ -37,7 +36,6 @@ abstract class BaseChart @JvmOverloads constructor(context: Context?, attrs: Att
context,
object : ScaleGestureDetector.SimpleOnScaleGestureListener() {
override fun onScale(detector: ScaleGestureDetector): Boolean {
if (currentZoom != detector.scaleFactor) {
currentZoom = detector.scaleFactor
onZoomChanged(lastZoom + -currentZoom + 1)
@ -51,7 +49,8 @@ abstract class BaseChart @JvmOverloads constructor(context: Context?, attrs: Att
lastZoom += -currentZoom + 1
}
})
}
)
/**
* Code mostly stolen from https://developer.android.com/training/gestures/scale#drag
@ -63,6 +62,7 @@ abstract class BaseChart @JvmOverloads constructor(context: Context?, attrs: Att
when (ev.actionMasked) {
MotionEvent.ACTION_DOWN -> {
onToggleScroll?.invoke(false)
ev.actionIndex.also { pointerIndex ->
// Remember where we started (for dragging)
lastTouchX = ev.getX(pointerIndex)
@ -78,8 +78,7 @@ abstract class BaseChart @JvmOverloads constructor(context: Context?, attrs: Att
val (x: Float, y: Float) =
ev.findPointerIndex(activePointerId).let { pointerIndex ->
// Calculate the distance moved
ev.getX(pointerIndex) to
ev.getY(pointerIndex)
ev.getX(pointerIndex) to ev.getY(pointerIndex)
}
posX += x - lastTouchX
@ -92,10 +91,11 @@ abstract class BaseChart @JvmOverloads constructor(context: Context?, attrs: Att
lastTouchY = y
}
MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> {
onToggleScroll?.invoke(true)
activePointerId = INVALID_POINTER_ID
}
MotionEvent.ACTION_POINTER_UP -> {
onToggleScroll?.invoke(true)
ev.actionIndex.also { pointerIndex ->
ev.getPointerId(pointerIndex)
.takeIf { it == activePointerId }
@ -103,13 +103,22 @@ abstract class BaseChart @JvmOverloads constructor(context: Context?, attrs: Att
// This was our active pointer going up. Choose a new
// active pointer and adjust accordingly.
val newPointerIndex = if (pointerIndex == 0) 1 else 0
lastTouchX = MotionEventCompat.getX(ev, newPointerIndex)
lastTouchY = MotionEventCompat.getY(ev, newPointerIndex)
activePointerId = MotionEventCompat.getPointerId(ev, newPointerIndex)
lastTouchX = ev.getX(newPointerIndex)
lastTouchY = ev.getY(newPointerIndex)
activePointerId = ev.getPointerId(newPointerIndex)
}
}
}
}
return true
}
}
private var onToggleScroll: ((Boolean) -> Unit)? = null
/**
* @param ev if input is false disable scroll
*/
fun setOnToggleScroll(ev: (Boolean) -> Unit) {
onToggleScroll = ev
}
}