mirror of
https://github.com/dzeiocom/OpenHealth.git
synced 2025-04-23 03:12:14 +00:00
fix: Value higher than max being slow
WIP: Better scrolling
This commit is contained in:
parent
3aa0861b1f
commit
4bec653f75
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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">
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
package com.dzeio.charts
|
||||
|
||||
import kotlin.math.abs
|
||||
import kotlin.math.max
|
||||
|
||||
data class Animation(
|
||||
/**
|
||||
@ -27,12 +28,22 @@ 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) {
|
||||
@ -41,7 +52,11 @@ data class Animation(
|
||||
result = currentValue - moveValue
|
||||
}
|
||||
|
||||
if (abs(targetValue - currentValue) < moveValue) {
|
||||
if (
|
||||
abs(targetValue - currentValue) <= moveValue ||
|
||||
result < minValue ||
|
||||
result > maxValue
|
||||
) {
|
||||
return targetValue
|
||||
}
|
||||
return result
|
||||
|
@ -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()
|
||||
}
|
||||
|
||||
}
|
@ -1,4 +0,0 @@
|
||||
package com.dzeio.charts.builders
|
||||
|
||||
class ChartBuilder {
|
||||
}
|
@ -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,19 +103,9 @@ 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
|
||||
@ -115,18 +121,20 @@ 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()) {
|
||||
for (item in list.subList(getXOffset(), getXOffset() + getDisplayedEntries())) {
|
||||
// // Process bottom texts
|
||||
val text = xAxis.onValueFormat(item.x)
|
||||
bottomTexts.add(text)
|
||||
@ -148,10 +156,20 @@ class BarChartView @JvmOverloads constructor(context: Context?, attrs: Attribute
|
||||
// // 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()))
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user