How to click block in Google MLKit android vision - android

I am trying to build a realtime text recognization app using google MLKit vision, it's showing text properly but when I am trying to click on a particular line it is only showing the last line text.
Here is Overlay Code :
TextGraphic.kt
class TextGraphic(overlay: GraphicOverlay?,
private val element: Text.Line,
font: Typeface,
fontSize: Float,
color: Int) : Graphic(overlay!!) {
private val rectPaint: Paint = Paint()
private val textPaint: Paint
override fun draw(canvas: Canvas?) {
val rect = RectF(element.boundingBox)
canvas!!.drawRect(rect, rectPaint)
canvas.drawText(element.text, rect.left, rect.bottom, textPaint)
}
companion object {
private const val TAG = "TextGraphic"
private const val TEXT_COLOR = Color.BLACK
private const val STROKE_WIDTH = 2.0f
}
init {
rectPaint.color = color
rectPaint.style = Paint.Style.FILL_AND_STROKE
rectPaint.strokeWidth = STROKE_WIDTH
textPaint = Paint()
textPaint.color = TEXT_COLOR
textPaint.textSize = fontSize
textPaint.typeface = font
postInvalidate()
}}
GraphicOverlay.kt
class GraphicOverlay(context: Context?, attrs: AttributeSet?) : View(context, attrs) {
private val lock = Any()
private var previewWidth = 0
private var widthScaleFactor = 1.0f
private var previewHeight = 0
private var heightScaleFactor = 1.0f
private var facing = CameraCharacteristics.LENS_FACING_BACK
private val graphics: MutableSet<Graphic> = HashSet()
abstract class Graphic(private val overlay: GraphicOverlay) {
abstract fun draw(canvas: Canvas?)
fun scaleX(horizontal: Float): Float {
return horizontal * overlay.widthScaleFactor
}
fun scaleY(vertical: Float): Float {
return vertical * overlay.heightScaleFactor
}
val applicationContext: Context
get() = overlay.context.applicationContext
fun translateX(x: Float): Float {
return if (overlay.facing == CameraCharacteristics.LENS_FACING_FRONT) {
overlay.width - scaleX(x)
} else {
scaleX(x)
}
}
fun translateY(y: Float): Float {
return scaleY(y)
}
fun postInvalidate() {
overlay.postInvalidate()
}
}
fun clear() {
synchronized(lock) { graphics.clear() }
postInvalidate()
}
fun add(graphic: Graphic) {
synchronized(lock) { graphics.add(graphic) }
postInvalidate()
}
fun remove(graphic: Graphic) {
synchronized(lock) { graphics.remove(graphic) }
postInvalidate()
}
fun setCameraInfo(previewWidth: Int, previewHeight: Int, facing: Int) {
synchronized(lock) {
this.previewWidth = previewWidth
this.previewHeight = previewHeight
this.facing = facing
}
postInvalidate()
}
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
synchronized(lock) {
if (previewWidth != 0 && previewHeight != 0) {
widthScaleFactor = width.toFloat() / previewWidth.toFloat()
heightScaleFactor = height.toFloat() / previewHeight.toFloat()
}
for (graphic in graphics) {
graphic.draw(canvas)
}
}
}}
Inside my Fragment where I am clicking :
private fun processTextFromImage(visionText: Text, imageProxy: ImageProxy) {
binding.graphicOverlay.clear()
for (block in visionText.textBlocks) {
for (line in block.lines) {
val textGraphic = TextGraphic(binding.graphicOverlay, line, font, fontSize, color = fontColor)
binding.graphicOverlay.apply {
add(textGraphic)
setOnClickListener {
Toast.makeText(it.context, line.text, Toast.LENGTH_SHORT).show()
}
}
for (element in line.elements) {
textFoundListener(element.text)
}
}
}
}
Is there any better way to to display overlay, this overlay is too fast and my click is only displays the last line text.
If anyone can help me in this, thanks a lot.

Related

make an animation on canvas using specific coordinates sent from the mainactivity

I have a small application that intends to make an animation, consisting of a graphic with 3 elements.
The application consists of:
mainactivity that will instantiate an object of the AnimatedCanvas class and that will send a vector of 3 pointF objects.
the AnimatedCanvas class that inherits from view and manages the canvas
An abstract class called basicItems, which represents the minimum information to draw basic elements of a canvas, such as circles, lines...
I come across two situations. In the first one, if the elements that I want to draw and animate are variables of the AnimatedCanvas class, both the drawing and the animation are done as expected.
The second is when I get the coordinates sent from the mainactivity that either not all animations (or none) are performed or some element is not initialized correctly.
The idea is:
draw a sphere for each of the elements in the graph, to be represented, making an animation that makes the spheres go from alpha 0f to 1f
perform an animation on the y axis, so that these spheres move to a certain point on the axis
at the same time that the spheres are animated, draw a line that animates and moves along the y-axis, along with its sphere
Heres my code...
`
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
>
<RelativeLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content">``your text``
<com.example.prueba_circulo_01.AnimatedCanvasView
android:id="#+id/canvas"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingStart="13dp"
/>
</RelativeLayout>
</layout>
import android.os.Bundle
import android.os.Handler
import android.view.View
import androidx.appcompat.app.AppCompatActivity
import com.example.prueba_circulo_01.databinding.ActivityMainBinding
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
private lateinit var animateC : AnimatedCanvasView
private val p1 = GraphicPoint("enero", 1f, "uno")
private val p2 = GraphicPoint("enero",2f,"dos")
private val p3 = GraphicPoint("enero", 3f, "tres")
private var listAux = listOf<GraphicPoint>(p1,p2,p3)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
binding.apply {
canvas.addDataPoints(listAux,GraphicType.Default)
}
}
}
BasicItem class
abstract class BasicItem (var x : Float,
var y : Float,
var yFinal : Float,
var paint : Paint,
var alpha: Float
){
abstract fun updateY(value: Float)
abstract fun updateYFinal(final : Float)
abstract fun updateAlpha(value: Float)
}
//----------------------------------------------------------
class VerticalLine1 (x : Float,
y : Float,
yFinal : Float,
paint : Paint,
alpha: Float):BasicItem(x,y,yFinal,paint,alpha){
override fun updateY(value: Float) {
y = value
}
override fun updateYFinal(final: Float) {
yFinal = final
}
override fun updateAlpha(value: Float){
alpha = value
paint.alpha = (255*alpha).toInt()
}
}
//----------------------------------------------------------
class Circle1 ( x : Float,
y : Float,
yFinal : Float,
var radius : Float,
paint : Paint,
alpha : Float):BasicItem(x,y,yFinal,paint,alpha) {
override fun updateY(value: Float) {
y = value
}
override fun updateYFinal(final: Float) {
yFinal = final
}
override fun updateAlpha(value: Float){
alpha = value
paint.alpha = (255*alpha).toInt()
}
}
//----------------------------------------------------------
class StringItem(x : Float,
y : Float,
yFinal : Float,
paint : Paint,
var string: String,
alpha : Float):BasicItem(x,y,yFinal,paint,alpha) {
override fun updateY(value: Float) {
y = value
}
override fun updateYFinal(final: Float) {
yFinal = final
}
override fun updateAlpha(value: Float){
alpha = value
paint.alpha = (255*alpha).toInt()
}
}
AnimatedCanvasView class
import android.animation.Animator
import android.animation.AnimatorListenerAdapter
import android.animation.ValueAnimator
import android.content.Context
import android.graphics.*
import android.util.AttributeSet
import android.util.TypedValue
import android.view.View
import androidx.appcompat.content.res.AppCompatResources
import androidx.core.content.ContextCompat
import kotlin.math.abs
import kotlin.math.min
class AnimatedCanvasView(context: Context, attrs: AttributeSet) : View(context, attrs), ValueAnimator.AnimatorUpdateListener {
private val data = mutableListOf<DataPoint>()
private var points = mutableListOf<PointF>()
private val conPoint1 = mutableListOf<PointF>()
private val conPoint2 = mutableListOf<PointF>()
private val graphicPoints = mutableListOf<GraphicPoint>()
private val primaryColorPaint = Paint()
private val borderPathPaint = Paint()
private val blueCircle = Paint()
private val pathPaint = Paint()
private val barPaint = Paint()
private val path = Path()
private val borderPath = Path()
private val barWidth by lazy {
TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP, 1f, resources.displayMetrics
)
}
private val borderPathWidth by lazy {
TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP, 2f, resources.displayMetrics
)
}
private val startX = 0f
private var delay = 500L
private val standardDuration = 3000L
private val standardDelay = 1000L
private val secondDuration = 2000L
private val animatorCircle1 = ValueAnimator.ofFloat(0f,1f)
private val animatorCircle2 = ValueAnimator.ofFloat(0f,1f)
private val animatorCircle3 = ValueAnimator.ofFloat(0f,1f)
private val animatorCircle4 = ValueAnimator.ofFloat(0f,1f)
private val animatorCircle5 = ValueAnimator.ofFloat(0f,1f)
private val animatorCircle6 = ValueAnimator.ofFloat(0f,1f)
private val animatorCircle10 = ValueAnimator.ofFloat(0f,1f)
private lateinit var circle10 : Circle
private lateinit var animatorVerticalLine1 : ValueAnimator
private lateinit var animatorVerticalLine2 : ValueAnimator
private lateinit var animatorVerticalLine3 : ValueAnimator
private var animatorVerticalLine4 : ValueAnimator? = null
private lateinit var animatorVerticalLine5 : ValueAnimator
private lateinit var animatorVerticalLine6 : ValueAnimator
private var curveTopMargin = 32
private val externalRadiusNormal = 30f
private val externalRadiusFinal = 35f
private val internalRadiusNormal = 23f
private val internalRadiusFinal = 33f
private val secondInternalRadius = 15f
private lateinit var circle1 : Circle
private lateinit var circle2 : Circle
private lateinit var circle3 : Circle
private lateinit var circle4 : Circle
private lateinit var circle5 : Circle
private lateinit var circle6 : Circle
private lateinit var internalCircle1 : Circle
private lateinit var internalCircle2 : Circle
private lateinit var internalCircle3 : Circle
private lateinit var secondInternalCircle : Circle
private lateinit var verticalLine1 : VerticalLine
private lateinit var verticalLine2 : VerticalLine
private lateinit var verticalLine3 : VerticalLine
private lateinit var verticalLine4 : VerticalLine
private lateinit var verticalLine5 : VerticalLine
private lateinit var verticalLine6 : VerticalLine
private lateinit var stringCircle1 : StringItem
private lateinit var stringCircle2 : StringItem
private lateinit var stringCircle3 : StringItem
private lateinit var bezierCurve: BezierCurve
private val paintCircleBlack = Paint()
private val paintCircleNormal = Paint()
private val paintCircleFinal = Paint()
private val paintInternal = Paint()
private val paintLine = Paint()
private val paintVerticalLineGray = Paint()
private val paintVerticalLineGreen = Paint()
private val paintSecondInternalRadius = Paint()
private val paintText = Paint()
private val paintBezier = Paint()
private var empezado = false
private val images = mutableListOf<ImageCoordinates>()
val images2 = mutableListOf<imgCoor>()
//lista de animaciones...
private var animatorList = mutableListOf<ValueAnimator>()
private lateinit var animator2 : ValueAnimator
private lateinit var animator3 : ValueAnimator
private lateinit var basicItem: BasicItem
private lateinit var basicItem2: BasicItem
init {
initPaint()
initItemsToDraw()
initAnimators()
}
override fun onAnimationUpdate(animation: ValueAnimator?) {
animation?.apply {
startDelay = standardDelay
addUpdateListener {
basicItem.updateAlpha(it.animatedValue as Float)
invalidate()
}
addListener(object:AnimatorListenerAdapter(){
override fun onAnimationEnd(animation: Animator?) {
super.onAnimationEnd(animation)
ValueAnimator.ofFloat(basicItem.y,basicItem.yFinal).apply {
duration=secondDuration
addUpdateListener {
basicItem.updateY(it.animatedValue as Float)
invalidate()
}
start()
}
}
})
//start()
}
}
fun starAnim(){
animator2 = ValueAnimator.ofFloat(0f,1f)
animator2.duration = 1000
animator2.addUpdateListener(this)
animator2.start()
}
private fun initPaint(){
paintCircleBlack.apply {
isAntiAlias = true
style = Paint.Style.FILL
color = Color.BLACK
alpha = 255
}
paintCircleNormal.apply {
isAntiAlias = true
style = Paint.Style.FILL
color = Color.GREEN
alpha = 0
}
paintCircleFinal.apply {
isAntiAlias = true
style = Paint.Style.FILL
color = Color.DKGRAY
alpha = 0
}
paintInternal.apply {
isAntiAlias = true
style = Paint.Style.FILL
color = Color.WHITE
alpha = 0
}
paintLine.apply {
isAntiAlias = true
style = Paint.Style.FILL
color = Color.GREEN
strokeWidth = 3f
}
paintVerticalLineGray.apply {
isAntiAlias = true
style = Paint.Style.FILL
color = Color.GRAY
strokeWidth = 1f
alpha = 1
}
paintVerticalLineGreen.apply {
isAntiAlias = true
style = Paint.Style.FILL
color = Color.GREEN
strokeWidth = 3f
// alpha = 255
alpha = 255
}
paintSecondInternalRadius.apply {
isAntiAlias = true
style = Paint.Style.FILL
color = Color.DKGRAY
alpha = 0
}
paintText.apply {
isAntiAlias = true
style = Paint.Style.FILL
color = Color.BLACK
textSize = 50f
alpha = 0
}
paintBezier.apply {
isAntiAlias = true
style = Paint.Style.FILL
color = Color.GREEN
strokeWidth = 3f
}
borderPathPaint.apply {
isAntiAlias = true
strokeWidth = borderPathWidth
style = Paint.Style.STROKE
color = ContextCompat.getColor(context, R.color.blue_green_alpha_50)
}
barPaint.apply {
isAntiAlias = true
strokeWidth = barWidth
}
pathPaint.apply {
isAntiAlias = true
style = Paint.Style.FILL
color = Color.WHITE
}
blueCircle.apply {
isAntiAlias = true
color = ContextCompat.getColor(context, R.color.purple_700)
}
primaryColorPaint.apply {
isAntiAlias = true
color = ContextCompat.getColor(context, R.color.blue_green)
}
}
//----------------------------------------------------------
private fun initItemsToDraw(){
circle1=Circle(200f,600f,400f,externalRadiusNormal,paintCircleNormal,0f)
circle2=Circle(400f,600f,500f,externalRadiusNormal,paintCircleNormal,0f)
circle3=Circle(600f,600f,300f,externalRadiusFinal,paintCircleFinal,0f)
//circle4=Circle(400f,1500f,600f,externalRadiusFinal,paintCircleFinal,0f)
internalCircle1=Circle(200f,600f,400f,internalRadiusNormal,paintInternal,0f)
internalCircle2=Circle(400f,600f,500f,internalRadiusNormal,paintInternal,0f)
internalCircle3=Circle(600f,600f,300f,internalRadiusFinal,paintInternal,0f)
secondInternalCircle = Circle(600f,600f,300f,secondInternalRadius,paintSecondInternalRadius, 0f)
verticalLine1 = VerticalLine(
x = 200f,
y = 600f,
yFinal = 400f+circle1.radius,
paint = paintVerticalLineGray,
alpha = 1f
)
verticalLine2 = VerticalLine(
x = 400f,
y = 600f,
yFinal = 500f+circle2.radius,
paint = paintVerticalLineGray,
alpha = 1f
)
verticalLine3 = VerticalLine(
x = 600f,
y = 600f,
yFinal = 300f+circle3.radius,
paint = paintVerticalLineGreen,
alpha = 1f
)
stringCircle1 = StringItem(
x = 200f,
y = 600f,
yFinal = 400f+circle1.radius,
paint = paintText,
string = "hola",
alpha = 1f)
bezierCurve = BezierCurve(200f,400f,600f,paintBezier,1f,400f,500f,600f,300f)
}
private fun initAnimators(){
animatorVerticalLine1 = ValueAnimator.ofFloat(verticalLine1.y, verticalLine1.yFinal)
animatorVerticalLine2 = ValueAnimator.ofFloat(verticalLine2.y, verticalLine2.yFinal)
animatorVerticalLine3 = ValueAnimator.ofFloat(verticalLine3.y, verticalLine3.yFinal)
setAnimator(animatorCircle1, circle1)
setAnimator(animatorCircle1, internalCircle1)
setAnimator(animatorCircle2, circle2)
setAnimator(animatorCircle2, internalCircle2)
setAnimator(animatorCircle3, circle3)
setAnimator(animatorCircle3, internalCircle3)
setAnimator(animatorCircle1, secondInternalCircle)
setAnimatorVerticalLine(animatorVerticalLine1, verticalLine1)
setAnimatorVerticalLine(animatorVerticalLine2, verticalLine2)
setAnimatorVerticalLine(animatorVerticalLine3, verticalLine3)
}
private fun initPrueba(){
//basicItem = Circle(600f,1500f,1100f,40f,paintCircleNormal,1f )
basicItem = Circle(points[1].x,height.toFloat() - 16f.toDP(),1100f,40f,paintCircleNormal,1f )
basicItem2 = Circle(points[2].x,height.toFloat() - 16f.toDP(),1100f,70f,paintCircleNormal,1f )
}
private fun initAnimators2(){
try {
//if (animatorVerticalLine4==null) animatorVerticalLine4 = ValueAnimator.ofFloat(verticalLine4.y, verticalLine4.yFinal)
if (animatorVerticalLine4==null) animatorVerticalLine4 = ValueAnimator.ofFloat(1500f, 1000f)
animatorVerticalLine5 = ValueAnimator.ofFloat(verticalLine5.y, verticalLine5.yFinal)
animatorVerticalLine6 = ValueAnimator.ofFloat(verticalLine6.y, verticalLine6.yFinal)
}catch (ex:Exception){}
}
//----------------------------------------------------------
private fun setElementsToAnimators(){
try {
setAnimatorVerticalLine(animatorVerticalLine4, verticalLine4)
setAnimatorVerticalLine(animatorVerticalLine5, verticalLine5)
setAnimatorVerticalLine(animatorVerticalLine6, verticalLine6)
}catch (ex:Exception){}
}
private fun drawHorizontalLine(canvas: Canvas?, paint: Paint) {
canvas?.drawLine(startX, height.toFloat() - 20f.toDP(), width.toFloat(), height.toFloat() - 20f.toDP(), paint)
}
private fun drawBezierCurve(canvas: Canvas?) {
try {
if (points.isEmpty() && conPoint1.isEmpty() && conPoint2.isEmpty()) return
val pointsToDraw = points.filterIndexed { index, _ -> index != 0 }
path.reset()
path.moveTo(pointsToDraw.first().x, pointsToDraw.first().y)
for (i in 1 until pointsToDraw.size) {
path.cubicTo(
conPoint1[i - 1].x, conPoint1[i - 1].y, conPoint2[i - 1].x, conPoint2[i - 1].y,
pointsToDraw[i].x, pointsToDraw[i].y
)
}
borderPath.set(path)
canvas?.drawPath(path, pathPaint.apply {
})
canvas?.drawPath(borderPath, borderPathPaint)
} catch (e: Exception) {
}
}
private fun setAnimator(animatorToSet: ValueAnimator, basicItem: BasicItem){
animatorToSet.apply {
duration = standardDuration
startDelay = standardDelay
addUpdateListener {
basicItem.updateAlpha(it.animatedValue as Float)
invalidate()
}
addListener(object:AnimatorListenerAdapter(){
override fun onAnimationEnd(animation: Animator?) {
super.onAnimationEnd(animation)
ValueAnimator.ofFloat(basicItem.y,basicItem.yFinal).apply {
duration=secondDuration
addUpdateListener {
basicItem.updateY(it.animatedValue as Float)
invalidate()
}
start()
}
}
})
start()
}
}
private fun setAnimatorVerticalLine(animatorToSet: ValueAnimator?, verticalLine: VerticalLine){
animatorToSet?.apply {
duration = secondDuration
startDelay = standardDelay + standardDuration
repeatCount=0
addUpdateListener {
verticalLine.yFinal = it.animatedValue as Float
verticalLine.paint.alpha = 255
invalidate()
}
start()
}
}
override fun onDraw(canvas: Canvas?) {
super.onDraw(canvas)
try {
canvas?.drawCircle(basicItem.x,basicItem.y,40f,basicItem.paint)
canvas?.drawCircle(basicItem2.x,basicItem2.y,70f,basicItem2.paint)
}catch (ex:Exception){
println(ex)}
drawFirstDraw(canvas)
}
//----------------------------------------------------------
private fun drawFirstDraw(canvas: Canvas?){
canvas?.drawCircle(circle1.x,circle1.y, circle1.radius,paintCircleNormal)
canvas?.drawCircle(circle2.x,circle2.y, circle2.radius,paintCircleNormal)
canvas?.drawCircle(circle3.x,circle3.y, circle3.radius,paintCircleNormal)
canvas?.drawCircle(internalCircle1.x,internalCircle1.y, internalCircle1.radius,paintInternal)
canvas?.drawCircle(internalCircle2.x,internalCircle2.y, internalCircle2.radius,paintInternal)
canvas?.drawCircle(internalCircle3.x,internalCircle3.y, internalCircle3.radius,paintInternal)
canvas?.drawCircle(secondInternalCircle.x,secondInternalCircle.y, secondInternalCircle.radius,paintSecondInternalRadius)
canvas?.drawLine(100f,600f,700f,600f,paintLine)
drawHorizontalLine(canvas,paintLine)
canvas?.drawLine(verticalLine1.x,verticalLine1.y,verticalLine1.x,verticalLine1.yFinal,paintVerticalLineGray)
canvas?.drawLine(verticalLine2.x,verticalLine2.y,verticalLine2.x,verticalLine2.yFinal,paintVerticalLineGray)
canvas?.drawLine(verticalLine3.x,verticalLine3.y,verticalLine3.x,verticalLine3.yFinal,paintVerticalLineGreen)
}
private fun resetDataPoints() {
this.data.clear()
points.clear()
graphicPoints.clear()
conPoint1.clear()
conPoint2.clear()
}
private fun calculatePointsForData() {
if (data.isEmpty()) return
val bottomY = height - 20f.toDP()
val xDiff =
(width.toFloat() / (data.size - 1)) //subtract -1 because we want to include position at right side
val maxData = (data.maxByOrNull { it.amount }?.amount ?: 100f) + 10
for (i in 0 until data.size) {
val y = bottomY - (data[i].amount / maxData * (bottomY - curveTopMargin))
points.add(
PointF(
when (i) {
0 -> xDiff * i + 30
data.size - 1 -> xDiff * i - 30
else ->
xDiff * i
}, y
)
)
}
}
private fun calculateConnectionPointsForBezierCurve() {
try {
val pointsToConnectPoints = points.filterIndexed { index, _ -> index != 0 }
for (i in 1 until pointsToConnectPoints.size) {
val a = pointsToConnectPoints[i - 1]
val b = pointsToConnectPoints[i]
conPoint1.add(
PointF(
a.x + (0.3f * (b.x - a.x)),
a.y + 0.3f * (b.y - a.y) / 6
)
)
conPoint2.add(
PointF(
b.x - (0.3f * (b.x - a.x)),
b.y - 0.3f * (b.y - a.y) / 6
)
)
}
} catch (e: Exception) {
}
}
private fun Float.toDP(): Float =
TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, this, resources.displayMetrics)
fun addDataPoints(data: List<GraphicPoint>, graphicType: GraphicType = GraphicType.Default) {
post {
Thread(Runnable {
resetDataPoints()
primaryColorPaint.apply {
color = ContextCompat.getColor(context, graphicType.primaryColor)
}
borderPathPaint.apply {
color = ContextCompat.getColor(context, graphicType.borderColor)
}
graphicPoints.add(GraphicPoint("", 0f, ""))
graphicPoints.addAll(data)
val max = graphicPoints.maxByOrNull { it.amount }?.amount ?: 30f
val dataPoints = graphicPoints.map { graphicPoint ->
DataPoint(if(graphicPoint.amount<=0) 0f else (graphicPoint.amount * 30f) / max)
}.toList()
graphicPoints.maxByOrNull { it.amount }
val oldPoints = points.toList()
if (oldPoints.isEmpty()) {
this.data.addAll(dataPoints)
calculatePointsForData()
calculateConnectionPointsForBezierCurve()
initPrueba()
postInvalidate()
return#Runnable
}
resetDataPoints()
this.data.addAll(dataPoints)
calculatePointsForData()
calculateConnectionPointsForBezierCurve()
val newPoints = points.toList()
val size = oldPoints.size
var maxDiffY = 0f
for (i in 0 until size) {
val abs = abs(oldPoints[i].y - newPoints[i].y)
if (abs > maxDiffY) maxDiffY = abs
}
val loopCount = maxDiffY / 16
val tempPointsForAnimation = mutableListOf<MutableList<PointF>>()
for (i in 0 until size) {
val old = oldPoints[i]
val new = newPoints[i]
val plusOrMinusAmount = abs(new.y - old.y) / maxDiffY * 16
var tempY = old.y
val tempList = mutableListOf<PointF>()
for (j in 0..loopCount.toInt()) {
if (tempY == new.y) {
tempList.add(PointF(new.x, new.y))
} else {
if (new.y > old.y) {
tempY += plusOrMinusAmount
tempY = min(tempY, new.y)
tempList.add(PointF(new.x, tempY))
} else {
tempY -= plusOrMinusAmount
tempY = kotlin.math.max(tempY, new.y)
tempList.add(PointF(new.x, tempY))
}
}
}
tempPointsForAnimation.add(tempList)
}
if (tempPointsForAnimation.isEmpty()) return#Runnable
val first = tempPointsForAnimation[0]
val length = first.size
for (i in 0 until length) {
conPoint1.clear()
conPoint2.clear()
points.clear()
points.addAll(tempPointsForAnimation.map { it[i] })
calculateConnectionPointsForBezierCurve()
postInvalidate()
Thread.sleep(16)
}
}).start()
}
}
data class ImageCoordinates(
var x: Float,
var y: Float,
var alpha:Float,
var drawable:Int)
data class imgCoor(
var x: Float,
var y:Float,
var finalY:Float
)
data class GraphicPoint(
val month: String,
val amount: Float,
val amountText: String,
)
data class DataPoint(val amount: Float)
sealed class GraphicType(val primaryColor: Int, val borderColor: Int) {
object Credit : GraphicType(R.color.trans_color_4, R.color.trans_color_4_alpha_50)
object Default : GraphicType(R.color.blue_green, R.color.blue_green_alpha_50)
}
`

how to save android.graphics.path in database and redraw them from database

I'm developing an android app with drawing functionality. I want to save those drawing Paths, so i can redraw them when I reopen the activity from history. I just want to save everything either its a path or custom shape on canvas.
private val drawShapes = Stack<ShapeAndPaint?>()
private val redoShapes = Stack<ShapeAndPaint?>()
private var currentShape: ShapeAndPaint? = null
var isDrawingEnabled = false
private set
private var viewChangeListener: BrushViewChangeListener? = null
var currentShapeBuilder: ShapeBuilder? = null
// eraser parameters
private var isErasing = false
var serializablePath = SerializablePath()
var customPath = CustomPath()
// endregion
#SuppressLint("Range")
private fun createPaint(): Paint {
val paint = Paint()
paint.isAntiAlias = true
paint.isDither = true
paint.style = Paint.Style.STROKE
paint.strokeJoin = Paint.Join.ROUND
paint.strokeCap = Paint.Cap.ROUND
paint.xfermode = PorterDuffXfermode(PorterDuff.Mode.SRC_OVER)
// apply shape builder parameters
currentShapeBuilder?.apply {
paint.strokeWidth = this.shapeSize
paint.alpha = this.shapeOpacity
paint.color = this.shapeColor
}
if (WhiteBoardActivity.dullPenSelected){
paint.alpha = 40
}
return paint
}
private fun createEraserPaint(): Paint {
val paint = createPaint()
paint.xfermode = PorterDuffXfermode(PorterDuff.Mode.CLEAR)
return paint
}
private fun setupBrushDrawing() {
//Caution: This line is to disable hardware acceleration to make eraser feature work properly
setLayerType(LAYER_TYPE_HARDWARE, null)
visibility = GONE
currentShapeBuilder = ShapeBuilder()
}
fun clearAll() {
drawShapes.clear()
redoShapes.clear()
invalidate()
}
fun setBrushViewChangeListener(brushViewChangeListener: BrushViewChangeListener?) {
viewChangeListener = brushViewChangeListener
}
public override fun onDraw(canvas: Canvas) {
for (shape in drawShapes) {
shape?.shape?.draw(canvas, shape.paint)
}
}
#SuppressLint("ClickableViewAccessibility")
override fun onTouchEvent(event: MotionEvent): Boolean {
return if (isDrawingEnabled) {
val touchX = event.x
val touchY = event.y
when (event.action) {
MotionEvent.ACTION_DOWN -> onTouchEventDown(touchX, touchY)
MotionEvent.ACTION_MOVE -> onTouchEventMove(touchX, touchY)
MotionEvent.ACTION_UP -> onTouchEventUp(touchX, touchY)
}
invalidate()
true
} else {
false
}
}
private fun onTouchEventDown(touchX: Float, touchY: Float) {
createShape()
currentShape?.shape?.startShape(touchX, touchY)
customPath.ActionLine(touchX,touchY)
}
private fun onTouchEventMove(touchX: Float, touchY: Float) {
currentShape?.shape?.moveShape(touchX, touchY)
customPath.ActionMove(touchX,touchY)
}
private fun onTouchEventUp(touchX: Float, touchY: Float) {
currentShape?.apply {
shape.stopShape()
endShape(touchX, touchY)
}
}
private fun createShape() {
var paint = createPaint()
var shape: AbstractShape = BrushShape()
if (isErasing) {
paint = createEraserPaint()
} else {
when(currentShapeBuilder?.shapeType){
ShapeType.BRUSH -> {
shape = BrushShape()
}
else -> {}
}
}
currentShape = ShapeAndPaint(shape, paint)
drawShapes.push(currentShape)
viewChangeListener?.onStartDrawing()
}
private fun endShape(touchX: Float, touchY: Float) {
if (currentShape?.shape?.hasBeenTapped() == true) {
// just a tap, this is not a shape, so remove it
drawShapes.remove(currentShape)
//handleTap(touchX, touchY);
}
viewChangeListener?.apply {
onStopDrawing()
onViewAdd(this#DrawingView)
}
}
fun undo(): Boolean {
if (!drawShapes.empty()) {
redoShapes.push(drawShapes.pop())
invalidate()
}
viewChangeListener?.onViewRemoved(this)
return !drawShapes.empty()
}
fun redo(): Boolean {
if (!redoShapes.empty()) {
drawShapes.push(redoShapes.pop())
invalidate()
}
viewChangeListener?.onViewAdd(this)
return !redoShapes.empty()
}
// region eraser
fun brushEraser() {
isDrawingEnabled = true
isErasing = true
}
// endregion
// region Setters/Getters
fun enableDrawing(brushDrawMode: Boolean) {
isDrawingEnabled = brushDrawMode
isErasing = !brushDrawMode
if (brushDrawMode) {
visibility = VISIBLE
}
}
fun getBitmap(): Bitmap? {
destroyDrawingCache()
this.buildDrawingCache()
return this.drawingCache
}
fun setPaintAlpha(i: Int) {
createPaint().alpha = i
}
// endregion
val drawingPath: Pair<Stack<ShapeAndPaint?>, Stack<ShapeAndPaint?>>
get() = Pair(drawShapes, redoShapes)
companion object {
private const val DEFAULT_ERASER_SIZE = 50.0f
var eraserSize = DEFAULT_ERASER_SIZE
}
// region constructors
init {
setupBrushDrawing()
}
}
this is my DrawingView class.
is there any possible solution? Thanks in advance.

Android Canvas.drawText() double decimal number not rendering correctly

I get strange behaviour when trying to render some text along with a number. It is likely that the problem is related to threading. My problem is that when I draw with Canvas.drawText() the number is always displayed as 0.0 but if I Log.d() the number is not 0.0 but rather around ~60.
Here is my code:
GameView:
class GameView(
context: Context?,
attrs: AttributeSet
) : SurfaceView(context, attrs), SurfaceHolder.Callback {
private var gameLoop: GameLoop = GameLoop(this, holder)
private val paint: Paint = Paint()
init {
holder.addCallback(this)
isFocusable = true
}
fun update() {
// TODO: update game state
}
override fun surfaceCreated(surfaceHolder: SurfaceHolder) {
gameLoop.startLoop()
}
override fun surfaceChanged(surfaceHolder: SurfaceHolder, p1: Int, p2: Int, p3: Int) {
// do nothing
}
override fun surfaceDestroyed(surfaceHolder: SurfaceHolder) {
gameLoop.stopLoop()
}
override fun draw(drawCanvas: Canvas) {
super.draw(drawCanvas)
drawInfo(drawCanvas)
}
private fun drawInfo(canvas: Canvas) {
val averageUps = gameLoop.getAverageUps()
val averageFps = gameLoop.getAverageFps()
val color = ContextCompat.getColor(context, DEBUG_TEXT_COLOR)
paint.color = color
paint.textSize = DEBUG_TEXT_SIZE
canvas.drawText("UPS: $averageUps", 40f, DEBUG_TEXT_SIZE, paint)
canvas.drawText("FPS: $averageFps", 40f, DEBUG_TEXT_SIZE*2 + 10f, paint)
}
companion object {
const val DEBUG_TEXT_SIZE = 40f
const val DEBUG_TEXT_COLOR = R.color.light_blue_A200
}
}
GameLoop:
class GameLoop(
private val gameView: GameView,
private val surfaceHolder: SurfaceHolder
) : Thread() {
private var isRunning: Boolean = false
private var averageUps: Double = 0.0
private var averageFps: Double = 0.0
fun getAverageUps(): Double {
return averageUps
}
fun getAverageFps(): Double {
return averageFps
}
fun startLoop() {
isRunning = true
start()
}
override fun run() {
super.run()
var updateCount = 0
var frameCount = 0
var startTime = 0L
var elapsedTime = 0L
var sleepTime = 0L
var canvas: Canvas? = null
startTime = System.currentTimeMillis()
while (isRunning) {
try {
canvas = surfaceHolder.lockCanvas()
synchronized(canvas) {
gameView.update()
updateCount++
gameView.draw(canvas)
}
} catch (iae: IllegalArgumentException) {
iae.printStackTrace()
} finally {
if (canvas != null) {
surfaceHolder.unlockCanvasAndPost(canvas)
frameCount++
}
}
elapsedTime = System.currentTimeMillis() - startTime
sleepTime = (updateCount*UPS_PERIOD - elapsedTime)
if (sleepTime > 0) {
try {
sleep(sleepTime)
} catch (ie: InterruptedException) {
ie.printStackTrace()
}
}
while (sleepTime < 0 && updateCount < MAX_UPS-1) {
gameView.update()
updateCount++
elapsedTime = System.currentTimeMillis() - startTime
sleepTime = (updateCount*UPS_PERIOD - elapsedTime)
}
elapsedTime = System.currentTimeMillis() - startTime
if (elapsedTime >= 1000.0) {
averageUps = updateCount.toDouble() / (elapsedTime/1000.0)
averageFps = frameCount.toDouble() / (elapsedTime/1000.0)
updateCount = 0
frameCount = 0
startTime = System.currentTimeMillis()
}
}
}
fun stopLoop() {
// TODO: finish me...
}
companion object {
private const val MAX_UPS: Long = 60
const val UPS_PERIOD: Long = 1000/MAX_UPS
}
}
The problem is in GameView.drawInfo(canvas: Canvas). Output is always "UPS: 0.0 FPS: 0.0" but when I log out averageUps and averageFps they are both around ~60.
I hope that you can help me resolve this issue :-)
To answer my own question:
The problem was in the activity that contains GameView.
I had GameView in the xml.
When i instantiated the GameView manually and sat it with setContentView(gameView) all rendering started to work.

Cannot not get the right value of custom attribute in custom transition

I'm trying to make custom image view that have rounded corners and a custom transition to change a border radius smoothly.
In CircleTransition, I try to get imageCornerRadius but it's always return 0 which ruined the transaction. But in activity, when I get imageCornerRadius, it returns the value in xml file. So how i can get the imageCornerRadius to perform the transition.
This is declare of my custom view
RoundedImageView
custom attribute
<declare-styleable name="RoundedImageView">
<attr name="imageCornerRadius" format="dimension" />
</declare-styleable>
class RoundedImageView : AppCompatImageView {
constructor(context: Context) : super(context) {
Log.d("debug", "first constructor")
}
constructor(context: Context, attrSet: AttributeSet) : super(context, attrSet) {
Log.d("debug", "second constructor")
init(attrSet)
}
constructor(context: Context, attrSet: AttributeSet, defStyleAttr: Int) : super(
context,
attrSet,
defStyleAttr
) {
Log.d("debug", "third constructor")
init(attrSet)
}
private fun init(attrSet: AttributeSet){
context.theme.obtainStyledAttributes(
attrSet,
R.styleable.RoundedImageView,
0,
0
).apply {
try {
imageCornerRadius = getDimensionPixelSize(
R.styleable.RoundedImageView_imageCornerRadius,
0
).toFloat()
} finally {
recycle()
}
}
}
// Custom attr
var imageCornerRadius: Float = 0F
//Attr for drawing
private lateinit var bitmapRect: RectF
val rect = RectF(drawable.bounds)
val holePath = Path()
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
super.onSizeChanged(w, h, oldw, oldh)
Log.d("size changed", "w = $w h = $h")
bitmapRect = RectF(0f, 0f, w.toFloat(), h.toFloat())
}
override fun onDraw(canvas: Canvas?) {
val drawableWidth = this.width
val drawableHeight = this.height
/* Clip */
holePath.apply {
reset()
addRoundRect(
0F,
0F,
rect.right + drawableWidth,
rect.bottom + drawableHeight,
imageCornerRadius,
imageCornerRadius,
Path.Direction.CW
)
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
canvas?.clipPath(holePath)
} else {
#Suppress("DEPRECATION")
canvas?.clipPath(holePath, Region.Op.REPLACE)
}
// Draw image
super.onDraw(canvas)
}
}
My custom transition change Size, Coordinate, imageCornerRadius
CircleTransition.kt
class CircleTransition() : Transition() {
private val TAG = CircleTransition::class.java.simpleName
private val BOUNDS = TAG + ":viewBounds"
private val CORNER_RADIUS = TAG + ":imageCornerRadius"
private val PROPS = arrayOf(BOUNDS, CORNER_RADIUS)
init {
Log.d("debug", "Circle Transition called")
}
override fun captureStartValues(transitionValues: TransitionValues?) {
captureValues(transitionValues)
}
override fun captureEndValues(transitionValues: TransitionValues?) {
captureValues(transitionValues)
}
fun captureValues(transitionValues: TransitionValues?) {
val view = transitionValues?.view
//get View Bound
val bound = RectF()
bound.left = view?.left?.toFloat() ?: return
bound.top = view.top.toFloat()
bound.right = view.right.toFloat()
bound.bottom = view.bottom.toFloat()
transitionValues.values.put(BOUNDS, bound)
//get view Corner radius
if(view is RoundedImageView){
val cornerRadius = view.imageCornerRadius
transitionValues.values.put(CORNER_RADIUS, cornerRadius)
}
}
override fun getTransitionProperties(): Array<String> {
return PROPS
}
override fun createAnimator(
sceneRoot: ViewGroup?,
startValues: TransitionValues?,
endValues: TransitionValues?
): Animator? {
if (startValues == null || endValues == null) {
return null
}
val view = endValues.view as RoundedImageView
//startScene
val sBound = startValues.values[BOUNDS] as RectF? ?: return null
//How I get imageCornerRadius
val sCornerRadius = startValues.values[CORNER_RADIUS] as Float? ?: return null
val sWidth = sBound.right - sBound.left
val sHeight = sBound.top - sBound.bottom
//endScene
val eBound = endValues.values[BOUNDS] as RectF? ?: return null
//How I get imageCornerRadius
val eCornerRadius = endValues.values[CORNER_RADIUS] as Float? ?: return null
val eWidth = eBound.right - eBound.left
val eHeight = eBound.top - eBound.bottom
if (sBound == eBound && sCornerRadius == eCornerRadius) {
return null
}
val widthAnimator: ValueAnimator =
ValueAnimator.ofInt(sWidth.toInt(), eWidth.toInt()).apply {
addUpdateListener {
val layoutParams = view.layoutParams
layoutParams.width = it.animatedValue as Int
view.layoutParams = layoutParams
}
}
val heightAnimator: ValueAnimator =
ValueAnimator.ofInt(sHeight.toInt() * -1, eHeight.toInt() * -1).apply {
interpolator = AccelerateInterpolator()
addUpdateListener {
val layoutParams = view.layoutParams
layoutParams.height = it.animatedValue as Int
view.layoutParams = layoutParams
}
}
val cornerRadiusAnimator = ValueAnimator.ofFloat(96F, 0F).apply {
addUpdateListener {
view.imageCornerRadius = it.animatedValue as Float
}
}
// set endView have the same size, coorinate like startScene
view.x = sBound.left
view.y = sBound.top
// view.layoutParams = ViewGroup.LayoutParams(sBound.width().toInt(), sBound.height().toInt())
// move view
val startX = sBound.left
val startY = sBound.top
val moveXTo = eBound.left
val moveYTo = eBound.top
val moveXAnimator: Animator =
ObjectAnimator.ofFloat(view, "x", startX, moveXTo.toFloat())
val moveYAnimator: Animator =
ObjectAnimator.ofFloat(view, "y", startY, moveYTo.toFloat()).apply {
addUpdateListener {
view.invalidate()
}
}
val animatorSet = AnimatorSet()
animatorSet.playTogether(
widthAnimator,
heightAnimator,
cornerRadiusAnimator,
moveXAnimator,
moveYAnimator
)
return animatorSet
}
}

Canvas does not draw in SurfaceView

I create an override method called draw in SurfaceView. I want to see the paint I set in my SurfaceView but nothing shows up when I touched the screen and trying to draw a line. What should I do to make this work?
private var mPaint: Paint
private val mPaths: ArrayList<Path> = ArrayList<Path>()
private val mEraserPath: Path = Path()
init {
mPaint = Paint()
mPaint.isAntiAlias = true
mPaint.isDither = true
mPaint.style = Paint.Style.STROKE
mPaint.strokeJoin = Paint.Join.ROUND
mPaint.strokeCap = Paint.Cap.ROUND
mPaint.strokeWidth = 3f
mPaint.alpha = 255
mPaint.color = android.graphics.Color.BLACK
mPaint.xfermode = PorterDuffXfermode(PorterDuff.Mode.CLEAR)
}
override fun draw(canvas: Canvas) {
canvas.drawPaint(mPaint)
val action: EditAction? = this.getEditAction()
for (path: Path in mPaths) {
when (action) {
EditAction.COLOR -> {
setPaintColor(this.getStrokeColor()) // android.graphics.Color.BLACK
setPaintSize(this.getStrokeSize()) // 5f
canvas.drawPath(path, mPaint)
}
EditAction.SIZE -> {
setPaintColor(this.getStrokeColor()) // android.graphics.Color.BLACK
setPaintSize(this.getStrokeSize()) // 5f
canvas.drawPath(path, mPaint)
}
EditAction.ERASER -> {
}
}
}
canvas.drawPath(mEraserPath, mPaint)
super.draw(canvas)
}
Instead of using draw, use the SurfaceHolder.Callback functions instead, as shown below. I have mof
class SlowSurfaceView #JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0)
: SurfaceView(context, attrs, defStyleAttr), SurfaceHolder.Callback {
private var mPaint: Paint = Paint()
init {
holder.addCallback(this)
mPaint.isAntiAlias = true
mPaint.isDither = true
mPaint.style = Paint.Style.STROKE
mPaint.strokeJoin = Paint.Join.ROUND
mPaint.strokeCap = Paint.Cap.ROUND
mPaint.strokeWidth = 3f
mPaint.alpha = 255
mPaint.color = android.graphics.Color.RED
mPaint.xfermode = PorterDuffXfermode(PorterDuff.Mode.CLEAR)
}
override fun surfaceChanged(holder: SurfaceHolder, format: Int, width: Int, height: Int) {
// Do nothing for now
}
override fun surfaceDestroyed(holder: SurfaceHolder) {
}
override fun surfaceCreated(holder: SurfaceHolder) {
if (isAttachedToWindow) {
val canvas = holder.lockCanvas()
canvas?.let {
it.drawRect(Rect(100, 100, 200, 200), mPaint)
holder.unlockCanvasAndPost(it)
}
}
}
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
val desiredWidth = suggestedMinimumWidth + paddingLeft + paddingRight
val desiredHeight = suggestedMinimumHeight + paddingTop + paddingBottom
setMeasuredDimension(View.resolveSize(desiredWidth, widthMeasureSpec),
View.resolveSize(desiredHeight, heightMeasureSpec))
}
}
Refer to the above modify the code, and hopefully you should get what you want.

Categories

Resources