mirror of
https://github.com/dzeiocom/OpenHealth.git
synced 2025-04-24 11:52:15 +00:00
Merge branch 'master' of github.com:dzeiocom/OpenHealth
This commit is contained in:
commit
80e614b988
@ -5,10 +5,15 @@ import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import com.dzeio.charts.Entry
|
||||
import com.dzeio.openhealth.adapters.StepsAdapter
|
||||
import com.dzeio.openhealth.core.BaseFragment
|
||||
import com.dzeio.openhealth.databinding.FragmentStepsHomeBinding
|
||||
import com.google.android.material.color.MaterialColors
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import java.text.DateFormat
|
||||
import java.util.Date
|
||||
import java.util.Locale
|
||||
|
||||
@AndroidEntryPoint
|
||||
class StepsHomeFragment :
|
||||
@ -41,19 +46,39 @@ class StepsHomeFragment :
|
||||
|
||||
viewModel.items.observe(viewLifecycleOwner) { list ->
|
||||
adapter.set(list)
|
||||
// chart.numberOfEntries = list.size / 2
|
||||
chart.numberOfLabels = 2
|
||||
|
||||
val strings = ArrayList<String>()
|
||||
val values = ArrayList<Int>()
|
||||
// chart.animation.enabled = false
|
||||
chart.animation.refreshRate = 60
|
||||
chart.animation.duration = 500
|
||||
|
||||
list.forEach {
|
||||
strings.add(it.formatTimestamp())
|
||||
values.add(it.value)
|
||||
}
|
||||
|
||||
chart.setBottomTextList(strings)
|
||||
chart.setDataList(
|
||||
values
|
||||
chart.xAxis.labels.color = MaterialColors.getColor(
|
||||
requireView(),
|
||||
com.google.android.material.R.attr.colorOnBackground
|
||||
)
|
||||
chart.xAxis.labels.size = 32f
|
||||
chart.yAxis.color = MaterialColors.getColor(
|
||||
requireView(),
|
||||
com.google.android.material.R.attr.colorPrimary
|
||||
)
|
||||
|
||||
chart.list = list.reversed().map {
|
||||
return@map Entry(it.timestamp.toDouble(), it.value.toFloat())
|
||||
} as ArrayList<Entry>
|
||||
|
||||
chart.xAxis.onValueFormat = onValueFormat@{
|
||||
val formatter = DateFormat.getDateTimeInstance(
|
||||
DateFormat.SHORT,
|
||||
DateFormat.SHORT,
|
||||
Locale.getDefault()
|
||||
)
|
||||
return@onValueFormat formatter.format(Date(it.toLong()))
|
||||
}
|
||||
|
||||
// chart.yAxis.max = (total / list.size).toInt()
|
||||
|
||||
chart.refresh()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
51
charts/src/main/java/com/dzeio/charts/Animation.kt
Normal file
51
charts/src/main/java/com/dzeio/charts/Animation.kt
Normal file
@ -0,0 +1,51 @@
|
||||
package com.dzeio.charts
|
||||
|
||||
import kotlin.math.abs
|
||||
|
||||
data class Animation(
|
||||
/**
|
||||
* Enable / Disable the Chart Animations
|
||||
*/
|
||||
var enabled: Boolean = true,
|
||||
|
||||
/**
|
||||
* Number of milliseconds the animation is running before it ends
|
||||
*/
|
||||
var duration: Int = 1000,
|
||||
|
||||
/**
|
||||
* Number of updates per seconds
|
||||
*/
|
||||
var refreshRate: Int = 50
|
||||
) {
|
||||
/**
|
||||
* Update the value depending on the maximum obtainable value
|
||||
*
|
||||
* @param maxValue the maximum value the item can obtain
|
||||
* @param targetValue the value you want to obtain at the end of the animation
|
||||
* @param currentValue the current value
|
||||
*
|
||||
* @return the new updated value
|
||||
*/
|
||||
fun updateValue(maxValue: Float, targetValue: Float, currentValue: Float): Float {
|
||||
if (!enabled) {
|
||||
return targetValue
|
||||
}
|
||||
|
||||
val moveValue = (maxValue - targetValue) / refreshRate
|
||||
|
||||
var result = targetValue
|
||||
if (currentValue < targetValue) {
|
||||
result = currentValue + moveValue
|
||||
} else if (currentValue > targetValue) {
|
||||
result = currentValue - moveValue
|
||||
}
|
||||
|
||||
if (abs(targetValue - currentValue) < moveValue) {
|
||||
return targetValue
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
fun getDelay() = this.duration / this.refreshRate
|
||||
}
|
6
charts/src/main/java/com/dzeio/charts/Entry.kt
Normal file
6
charts/src/main/java/com/dzeio/charts/Entry.kt
Normal file
@ -0,0 +1,6 @@
|
||||
package com.dzeio.charts
|
||||
|
||||
data class Entry(
|
||||
val x: Double,
|
||||
val y: Float
|
||||
)
|
22
charts/src/main/java/com/dzeio/charts/Labels.kt
Normal file
22
charts/src/main/java/com/dzeio/charts/Labels.kt
Normal file
@ -0,0 +1,22 @@
|
||||
package com.dzeio.charts
|
||||
|
||||
import android.graphics.Color
|
||||
import android.graphics.Paint
|
||||
import androidx.annotation.ColorInt
|
||||
|
||||
class XAxisLabels {
|
||||
|
||||
var size = 25f
|
||||
|
||||
@ColorInt
|
||||
var color: Int = Color.parseColor("#9B9A9B")
|
||||
|
||||
fun build(): Paint {
|
||||
return Paint().also {
|
||||
it.isAntiAlias = true
|
||||
it.color = color
|
||||
it.textSize = size
|
||||
it.textAlign = Paint.Align.CENTER
|
||||
}
|
||||
}
|
||||
}
|
28
charts/src/main/java/com/dzeio/charts/axis/XAxis.kt
Normal file
28
charts/src/main/java/com/dzeio/charts/axis/XAxis.kt
Normal file
@ -0,0 +1,28 @@
|
||||
package com.dzeio.charts.axis
|
||||
|
||||
import com.dzeio.charts.XAxisLabels
|
||||
|
||||
class XAxis<T> {
|
||||
|
||||
var max: T? = null
|
||||
var min: T? = null
|
||||
|
||||
val labels = XAxisLabels()
|
||||
|
||||
/**
|
||||
* Number of entries displayed in the chart at the same time
|
||||
*/
|
||||
var entriesDisplayed = 5
|
||||
|
||||
/**
|
||||
* 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()
|
||||
}
|
||||
|
||||
}
|
12
charts/src/main/java/com/dzeio/charts/axis/YAxis.kt
Normal file
12
charts/src/main/java/com/dzeio/charts/axis/YAxis.kt
Normal file
@ -0,0 +1,12 @@
|
||||
package com.dzeio.charts.axis
|
||||
|
||||
import android.graphics.Color
|
||||
import androidx.annotation.ColorInt
|
||||
|
||||
class YAxis<T> {
|
||||
var max: T? = null
|
||||
var min: T? = null
|
||||
|
||||
@ColorInt
|
||||
var color = Color.parseColor("#FC496D")
|
||||
}
|
@ -2,128 +2,156 @@ package com.dzeio.charts.views
|
||||
|
||||
import android.content.Context
|
||||
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.util.Log
|
||||
import android.view.View
|
||||
import com.dzeio.charts.Animation
|
||||
import com.dzeio.charts.Entry
|
||||
import com.dzeio.charts.axis.XAxis
|
||||
import com.dzeio.charts.axis.YAxis
|
||||
import kotlin.math.abs
|
||||
import kotlin.math.max
|
||||
import kotlin.math.min
|
||||
|
||||
class BarChartView @JvmOverloads constructor(context: Context?, attrs: AttributeSet? = null) :
|
||||
View(context, attrs) {
|
||||
BaseChart(context, attrs) {
|
||||
|
||||
companion object {
|
||||
const val TAG = "DzeioCharts/BarView"
|
||||
}
|
||||
|
||||
/**
|
||||
* Nunber of entries displayed at the same time
|
||||
* Number of entries displayed at the same time
|
||||
*/
|
||||
val numberOfEntries = 5
|
||||
private var zoom = 100f
|
||||
|
||||
/**
|
||||
* Number of labels displayed at the same time
|
||||
*/
|
||||
val numberOfLabels = 3
|
||||
var numberOfLabels = 3
|
||||
|
||||
/**
|
||||
* Spacing between entries
|
||||
*/
|
||||
val spacing = 22
|
||||
var spacing = 22
|
||||
|
||||
/**
|
||||
* top margin from the canvas
|
||||
*/
|
||||
@Deprecated("Not needed anymore, Use the parent Padding/Margin")
|
||||
private val topMargin: Int = 5
|
||||
val xAxis: XAxis<Double> = XAxis()
|
||||
|
||||
val yAxis: YAxis<Float> = YAxis()
|
||||
|
||||
val animation = Animation()
|
||||
|
||||
private val textTopMargin = 5
|
||||
|
||||
private var barWidth: Int = 0
|
||||
|
||||
private val textColor = Color.parseColor("#9B9A9B")
|
||||
|
||||
private val foregroundColor = Color.parseColor("#FC496D")
|
||||
private val percentList: ArrayList<Float> = ArrayList()
|
||||
private var targetPercentList: ArrayList<Float> = ArrayList()
|
||||
private val textPaint: Paint = Paint().also {
|
||||
it.isAntiAlias = true
|
||||
it.color = textColor
|
||||
it.textSize = 25f
|
||||
it.textAlign = Paint.Align.CENTER
|
||||
}
|
||||
|
||||
private val fgPaint: Paint = Paint().also {
|
||||
/**
|
||||
* value goes from 1 to 0 (1 at bottom, 0 at top)
|
||||
*/
|
||||
private var targetPercentList: ArrayList<Float> = ArrayList()
|
||||
|
||||
private var fgPaint: Paint = Paint().also {
|
||||
it.isAntiAlias = true
|
||||
it.color = foregroundColor
|
||||
it.color = yAxis.color
|
||||
}
|
||||
private val rect: Rect = Rect()
|
||||
private val rect: RectF = RectF()
|
||||
|
||||
private var bottomTextDescent = 0
|
||||
private var bottomTextHeight = 0
|
||||
private var bottomTextList: ArrayList<String>? = ArrayList()
|
||||
|
||||
private var movementOffset: Int = 0
|
||||
|
||||
private val animator: Runnable = object : Runnable {
|
||||
override fun run() {
|
||||
var needNewFrame = false
|
||||
for (i in targetPercentList.indices) {
|
||||
if (percentList[i] < targetPercentList[i]) {
|
||||
percentList[i] = percentList[i] + 0.02f
|
||||
val value = animation.updateValue(1f, targetPercentList[i], percentList[i])
|
||||
|
||||
if (value != percentList[i]) {
|
||||
needNewFrame = true
|
||||
} else if (percentList[i] > targetPercentList[i]) {
|
||||
percentList[i] = percentList[i] - 0.02f
|
||||
needNewFrame = true
|
||||
}
|
||||
if (abs(targetPercentList[i] - percentList[i]) < 0.02f) {
|
||||
percentList[i] = targetPercentList[i]
|
||||
percentList[i] = value
|
||||
}
|
||||
}
|
||||
if (needNewFrame) {
|
||||
postDelayed(this, 20)
|
||||
postDelayed(this, animation.getDelay().toLong())
|
||||
}
|
||||
invalidate()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* dataList will be reset when called is method.
|
||||
*
|
||||
* @param bottomStringList The String ArrayList in the bottom.
|
||||
*/
|
||||
fun setBottomTextList(bottomStringList: ArrayList<String>?) {
|
||||
barWidth = measuredWidth / numberOfEntries - spacing
|
||||
bottomTextList = bottomStringList
|
||||
var list: ArrayList<Entry> = arrayListOf()
|
||||
|
||||
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()
|
||||
// }
|
||||
|
||||
fun refresh() {
|
||||
|
||||
val r = Rect()
|
||||
|
||||
//// prepare bottom texts
|
||||
bottomTextDescent = 0
|
||||
for (s in bottomTextList!!) {
|
||||
textPaint.getTextBounds(s, 0, s.length, r)
|
||||
bottomTextHeight = 0
|
||||
bottomTexts = arrayListOf()
|
||||
|
||||
//// prepare values
|
||||
|
||||
// set the bar Width (also handle div by 0)
|
||||
barWidth = measuredWidth / max(min(list.size, getDisplayedEntries()), 1) - spacing
|
||||
|
||||
// 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())) {
|
||||
if (entry.y > calculatedMax) calculatedMax = entry.y
|
||||
}
|
||||
calculatedMax
|
||||
}
|
||||
|
||||
// make sure the target list
|
||||
// Log.d(TAG, list.size.toString())
|
||||
targetPercentList = arrayListOf()
|
||||
|
||||
|
||||
for ((i, item) in list.withIndex()) {
|
||||
//// Process bottom texts
|
||||
val text = xAxis.onValueFormat(item.x)
|
||||
bottomTexts.add(text)
|
||||
|
||||
// get Text boundaries
|
||||
xAxis.labels.build().getTextBounds(text, 0, text.length, r)
|
||||
|
||||
// get height of text
|
||||
if (bottomTextHeight < r.height()) {
|
||||
bottomTextHeight = r.height()
|
||||
}
|
||||
Log.d(TAG, measuredWidth.toString())
|
||||
// if (autoSetWidth && barWidth < r.width()) {
|
||||
// barWidth = r.width()
|
||||
// }
|
||||
if (bottomTextDescent < abs(r.bottom)) {
|
||||
bottomTextDescent = abs(r.bottom)
|
||||
}
|
||||
}
|
||||
postInvalidate()
|
||||
|
||||
// get text descent
|
||||
val descent = abs(r.bottom)
|
||||
if (bottomTextDescent < descent) {
|
||||
bottomTextDescent = descent
|
||||
}
|
||||
|
||||
/**
|
||||
* @param list The ArrayList of Integer with the range of [0-max].
|
||||
*/
|
||||
fun setDataList(list: ArrayList<Int>) {
|
||||
barWidth = measuredWidth / numberOfEntries - spacing
|
||||
// Calculate max
|
||||
val max = list.reduce { acc, i -> if (acc > i) return@reduce acc else return@reduce i }
|
||||
//// process values
|
||||
|
||||
targetPercentList = ArrayList()
|
||||
for (integer in list) {
|
||||
targetPercentList.add(1 - integer.toFloat() / max.toFloat())
|
||||
// add to animations the values
|
||||
targetPercentList.add(min(1 - item.y / max, 1f))
|
||||
}
|
||||
|
||||
// Make sure percentList.size() == targetPercentList.size()
|
||||
// post list
|
||||
if (percentList.isEmpty() || percentList.size < targetPercentList.size) {
|
||||
val temp = targetPercentList.size - percentList.size
|
||||
for (i in 0 until temp) {
|
||||
@ -136,64 +164,99 @@ class BarChartView @JvmOverloads constructor(context: Context?, attrs: Attribute
|
||||
}
|
||||
}
|
||||
|
||||
// Misc operations
|
||||
|
||||
fgPaint = Paint().apply {
|
||||
isAntiAlias = true
|
||||
color = yAxis.color
|
||||
}
|
||||
|
||||
removeCallbacks(animator)
|
||||
post(animator)
|
||||
}
|
||||
|
||||
override fun onDraw(canvas: Canvas) {
|
||||
if (percentList.isNotEmpty()) {
|
||||
for (i in 1 until percentList.size) {
|
||||
val left = spacing * i + barWidth * (i - 1)
|
||||
Log.d(TAG, "$spacing, $i, $barWidth = $left")
|
||||
val right = (spacing + barWidth) * i
|
||||
val bottom = height - bottomTextHeight - textTopMargin
|
||||
val top = topMargin + ((bottom - topMargin) * percentList[i - 1])
|
||||
// draw each rectangles
|
||||
for (i in 1..getDisplayedEntries()) {
|
||||
// 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]
|
||||
|
||||
rect.set(left, top.toInt(), right, bottom)
|
||||
|
||||
canvas.drawRect(rect, fgPaint)
|
||||
// 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 (bottomTextList != null && !bottomTextList!!.isEmpty()) {
|
||||
if (bottomTexts.isNotEmpty() && numberOfLabels > 0) {
|
||||
val size = bottomTexts.size
|
||||
var i = 1
|
||||
for (s in bottomTextList!!) {
|
||||
var items = size / max(2, (numberOfLabels - 2))
|
||||
|
||||
// handle cases where size is even and numberOfLabels is 3
|
||||
if (size % 2 != 0) {
|
||||
items += 1
|
||||
}
|
||||
|
||||
val rect = Rect()
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// Log.i(TAG, "Drawing $i")
|
||||
|
||||
xAxis.labels.build().getTextBounds(s, 0, s.length, rect)
|
||||
|
||||
canvas.drawText(
|
||||
s,
|
||||
// handle last entry overflowing
|
||||
min(
|
||||
// handle first entry overflowing
|
||||
max(
|
||||
(spacing * i + barWidth * (i - 1) + barWidth / 2).toFloat(),
|
||||
rect.width() / 2f
|
||||
),
|
||||
measuredWidth - rect.width() / 2f
|
||||
),
|
||||
|
||||
(height - bottomTextDescent).toFloat(),
|
||||
textPaint
|
||||
xAxis.labels.build()
|
||||
)
|
||||
i++
|
||||
if (numberOfLabels == 1) {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
|
||||
val mViewWidth = measureWidth(widthMeasureSpec)
|
||||
val mViewHeight = measureHeight(heightMeasureSpec)
|
||||
setMeasuredDimension(mViewWidth, mViewHeight)
|
||||
override fun onChartMoved(movementX: Float, movementY: Float) {
|
||||
movementOffset = (movementX / 100).toInt()
|
||||
refresh()
|
||||
}
|
||||
|
||||
private fun measureWidth(measureSpec: Int): Int {
|
||||
var preferred = 0
|
||||
if (bottomTextList != null) {
|
||||
preferred = bottomTextList!!.size * (barWidth + spacing)
|
||||
}
|
||||
return getMeasurement(measureSpec, preferred)
|
||||
override fun onZoomChanged(scale: Float) {
|
||||
Log.d(TAG, "New Zoom: $scale")
|
||||
zoom = scale
|
||||
refresh()
|
||||
}
|
||||
|
||||
private fun measureHeight(measureSpec: Int): Int {
|
||||
val preferred = 222
|
||||
return getMeasurement(measureSpec, preferred)
|
||||
private fun getXOffset(): Int {
|
||||
return min(max(0, xAxis.baseOffset + movementOffset), list.size - 1 - getDisplayedEntries())
|
||||
}
|
||||
|
||||
private fun getMeasurement(measureSpec: Int, preferred: Int): Int {
|
||||
val specSize = MeasureSpec.getSize(measureSpec)
|
||||
val measurement = when (MeasureSpec.getMode(measureSpec)) {
|
||||
MeasureSpec.EXACTLY -> specSize
|
||||
MeasureSpec.AT_MOST -> Math.min(preferred, specSize)
|
||||
else -> preferred
|
||||
}
|
||||
return measurement
|
||||
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()))
|
||||
}
|
||||
|
||||
}
|
||||
|
115
charts/src/main/java/com/dzeio/charts/views/BaseChart.kt
Normal file
115
charts/src/main/java/com/dzeio/charts/views/BaseChart.kt
Normal file
@ -0,0 +1,115 @@
|
||||
package com.dzeio.charts.views
|
||||
|
||||
import android.content.Context
|
||||
import android.util.AttributeSet
|
||||
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) {
|
||||
|
||||
// The ‘active pointer’ is the one currently moving our object.
|
||||
private var activePointerId = INVALID_POINTER_ID
|
||||
|
||||
private var lastTouchX: Float = 0f
|
||||
private var lastTouchY: Float = 0f
|
||||
|
||||
private var posX: Float = 0f
|
||||
private var posY: Float = 0f
|
||||
|
||||
private var lastZoom: Float = 100f
|
||||
private var currentZoom: Float = 0f
|
||||
|
||||
open fun onChartMoved(movementX: Float, movementY: Float) {}
|
||||
|
||||
/**
|
||||
* @param scale Float starting from 100%
|
||||
*
|
||||
* 99-% zoom out,
|
||||
* 101+% zoom in
|
||||
*/
|
||||
open fun onZoomChanged(scale: Float) {}
|
||||
|
||||
private val scaleGestureDetector = ScaleGestureDetector(
|
||||
context,
|
||||
object : ScaleGestureDetector.SimpleOnScaleGestureListener() {
|
||||
override fun onScale(detector: ScaleGestureDetector): Boolean {
|
||||
|
||||
if (currentZoom != detector.scaleFactor) {
|
||||
currentZoom = detector.scaleFactor
|
||||
onZoomChanged(lastZoom + -currentZoom + 1)
|
||||
}
|
||||
|
||||
return super.onScale(detector)
|
||||
}
|
||||
|
||||
override fun onScaleEnd(detector: ScaleGestureDetector?) {
|
||||
super.onScaleEnd(detector)
|
||||
|
||||
lastZoom += -currentZoom + 1
|
||||
}
|
||||
})
|
||||
|
||||
/**
|
||||
* Code mostly stolen from https://developer.android.com/training/gestures/scale#drag
|
||||
*/
|
||||
override fun onTouchEvent(ev: MotionEvent): Boolean {
|
||||
super.onTouchEvent(ev)
|
||||
|
||||
scaleGestureDetector.onTouchEvent(ev)
|
||||
|
||||
when (ev.actionMasked) {
|
||||
MotionEvent.ACTION_DOWN -> {
|
||||
ev.actionIndex.also { pointerIndex ->
|
||||
// Remember where we started (for dragging)
|
||||
lastTouchX = ev.getX(pointerIndex)
|
||||
lastTouchY = ev.getY(pointerIndex)
|
||||
}
|
||||
|
||||
// Save the ID of this pointer (for dragging)
|
||||
activePointerId = ev.getPointerId(0)
|
||||
}
|
||||
|
||||
MotionEvent.ACTION_MOVE -> {
|
||||
// Find the index of the active pointer and fetch its position
|
||||
val (x: Float, y: Float) =
|
||||
ev.findPointerIndex(activePointerId).let { pointerIndex ->
|
||||
// Calculate the distance moved
|
||||
ev.getX(pointerIndex) to
|
||||
ev.getY(pointerIndex)
|
||||
}
|
||||
|
||||
posX += x - lastTouchX
|
||||
posY += y - lastTouchY
|
||||
|
||||
onChartMoved(-posX, posY)
|
||||
|
||||
// Remember this touch position for the next move event
|
||||
lastTouchX = x
|
||||
lastTouchY = y
|
||||
}
|
||||
MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> {
|
||||
activePointerId = INVALID_POINTER_ID
|
||||
}
|
||||
MotionEvent.ACTION_POINTER_UP -> {
|
||||
|
||||
ev.actionIndex.also { pointerIndex ->
|
||||
ev.getPointerId(pointerIndex)
|
||||
.takeIf { it == activePointerId }
|
||||
?.run {
|
||||
// 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user