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.
Related
I am showing a dots loader in my activity in Android using a Timer() object and passing an object of TimerTask() in the scheduleAtFixedRate() API of Timer.
This is my code snippet:-
private fun scheduleTimer() {
timer = Timer()
timer?.scheduleAtFixedRate(object : TimerTask() {
override fun run() {
if (isSingleDir) {
selectedDotPos++
if (selectedDotPos > noOfDots) {
selectedDotPos = 1
}
} else {
if (isFwdDir) {
selectedDotPos++
if (selectedDotPos == noOfDots) {
isFwdDir = !isFwdDir
}
} else {
selectedDotPos--
if (selectedDotPos == 1) {
isFwdDir = !isFwdDir
}
}
}
(scanForActivity(context))?.runOnUiThread {
invalidate()
}
}
}, 0, animDur.toLong())
}
I am making sure that to cancel the timer by calling Timer::cancel(), when the visibility of the view changes. This is the code:-
override fun onVisibilityChanged(changedView: View, visibility: Int) {
super.onVisibilityChanged(changedView, visibility)
if (visibility != VISIBLE) {
timer?.cancel()
} else if (shouldAnimate) {
scheduleTimer()
}
}
But the TimerTask is leaking some memory. I am using Leak Canary to analyze the memory leak. This is the report:-
Looking for help to resolve this.
Complete code of my class:-
class LinearDotsLoader : DotsLoaderBaseView
{
private var timer: Timer? = null
var isSingleDir = true
private var diffRadius: Int = 0
private var isFwdDir = true
constructor(context: Context) : super(context) {
initCordinates()
initPaints()
}
constructor(context: Context, attrs: AttributeSet) : super(context, attrs) {
initAttributes(attrs)
initCordinates()
initPaints()
initShadowPaints()
}
constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr) {
initAttributes(attrs)
initCordinates()
initPaints()
initShadowPaints()
}
override fun initAttributes(attrs: AttributeSet) {
super.initAttributes(attrs)
val typedArray = context.obtainStyledAttributes(attrs, R.styleable.LinearDotsLoader, 0, 0)
this.noOfDots = typedArray.getInt(R.styleable.LinearDotsLoader_loader_noOfDots, 3)
this.selRadius = typedArray.getDimensionPixelSize(R.styleable.LinearDotsLoader_loader_selectedRadius, radius + 10)
this.dotsDistance = typedArray.getDimensionPixelSize(R.styleable.LinearDotsLoader_loader_dotsDist, 15)
this.isSingleDir = typedArray.getBoolean(R.styleable.LinearDotsLoader_loader_isSingleDir, false)
this.expandOnSelect = typedArray.getBoolean(R.styleable.LinearDotsLoader_loader_expandOnSelect, false)
typedArray.recycle()
}
override fun initCordinates() {
diffRadius = this.selRadius - radius
dotsXCorArr = FloatArray(this.noOfDots)
//init X cordinates for all dots
for (i in 0 until noOfDots) {
dotsXCorArr[i] = (i * dotsDistance + (i * 2 + 1) * radius).toFloat()
}
}
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
val calWidth: Int
val calHeight: Int
if (expandOnSelect) {
calWidth = (2 * this.noOfDots * radius + (this.noOfDots - 1) * dotsDistance + 2 * diffRadius)
calHeight = 2 * this.selRadius
} else {
calHeight = 2 * radius
calWidth = (2 * this.noOfDots * radius + (this.noOfDots - 1) * dotsDistance)
}
setMeasuredDimension(calWidth, calHeight)
}
override fun onVisibilityChanged(changedView: View, visibility: Int) {
super.onVisibilityChanged(changedView, visibility)
if (visibility != VISIBLE) {
timer?.cancel()
timer?.purge()
} else if (shouldAnimate) {
scheduleTimer()
}
}
private fun scheduleTimer() {
timer = Timer()
val dotsTimerTask = DotsTimerTask()
timer?.scheduleAtFixedRate(dotsTimerTask, 0, animDur.toLong())
}
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
drawCircle(canvas)
}
private fun drawCircle(canvas: Canvas) {
for (i in 0 until noOfDots) {
var xCor = dotsXCorArr[i]
if (expandOnSelect) {
if (i + 1 == selectedDotPos) {
xCor += diffRadius.toFloat()
} else if (i + 1 > selectedDotPos) {
xCor += (2 * diffRadius).toFloat()
}
}
var firstShadowPos: Int
var secondShadowPos: Int
if ((isFwdDir && selectedDotPos > 1) || selectedDotPos == noOfDots) {
firstShadowPos = selectedDotPos - 1
secondShadowPos = firstShadowPos - 1
} else {
firstShadowPos = selectedDotPos + 1
secondShadowPos = firstShadowPos + 1
}
if (i + 1 == selectedDotPos) {
selectedCirclePaint?.let {
canvas.drawCircle(
xCor,
(if (expandOnSelect) this.selRadius else radius).toFloat(),
(if (expandOnSelect) this.selRadius else radius).toFloat(),
it
)
}
} else if (showRunningShadow && i + 1 == firstShadowPos) {
canvas.drawCircle(
xCor,
(if (expandOnSelect) this.selRadius else radius).toFloat(),
radius.toFloat(),
firstShadowPaint)
} else if (showRunningShadow && i + 1 == secondShadowPos) {
canvas.drawCircle(
xCor,
(if (expandOnSelect) this.selRadius else radius).toFloat(),
radius.toFloat(),
secondShadowPaint)
} else {
defaultCirclePaint?.let {
canvas.drawCircle(
xCor,
(if (expandOnSelect) this.selRadius else radius).toFloat(),
radius.toFloat(),
it
)
}
}
}
}
var dotsDistance: Int = 15
set(value) {
field = value
initCordinates()
}
var noOfDots: Int = 3
set(noOfDots) {
field = noOfDots
initCordinates()
}
var selRadius: Int = 38
set(selRadius) {
field = selRadius
initCordinates()
}
var expandOnSelect: Boolean = false
set(expandOnSelect) {
field = expandOnSelect
initCordinates()
}
private fun scanForActivity(context: Context?): Activity? {
return when (context) {
null -> null
is Activity -> context
is ContextWrapper -> scanForActivity(context.baseContext)
else -> null
}
}
private fun updateSelectedDot()
{
if (isSingleDir) {
selectedDotPos++
if (selectedDotPos > noOfDots) {
selectedDotPos = 1
}
} else {
if (isFwdDir) {
selectedDotPos++
if (selectedDotPos == noOfDots) {
isFwdDir = !isFwdDir
}
} else {
selectedDotPos--
if (selectedDotPos == 1) {
isFwdDir = !isFwdDir
}
}
}
(scanForActivity(context))?.runOnUiThread {
invalidate()
}
}
private inner class DotsTimerTask: TimerTask(){
override fun run() {
updateSelectedDot()
}
}
}
The answer is to fix racing between cancelling and instantiate/schedule Timer.
To add cancel before each Timer instantiation and starting should be enough.
private fun scheduleTimer() {
timer?.cancel() // fix
timer = Timer()
val dotsTimerTask = DotsTimerTask()
timer?.scheduleAtFixedRate(dotsTimerTask, 0, animDur.toLong())
}
I opened issue on repo of this code:
https://github.com/agrawalsuneet/DotLoadersPack-Android/issues/44
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.
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.
I have to make following Ui, In which when I play this small circle will move in an arc, when I stop it will stop. I can also set the timing of rotation of the small blue circle.
Till now I have implemented the following code: this gives me an arc of the circle, but I am not able to rotate the smaller circler over the bigger one.
public class CustoCustomProgressBar : View{
private var path : Path ? =null
private var mPrimaryPaint: Paint? = null
private var mSecondaryPaint: Paint? = null
private var mBackgroundPaint: Paint? = null
private var mTextPaint: TextPaint? = null
private var mProgressDrawable : Drawable ? = null
private var mRectF: RectF? = null
private var mDrawText = false
private var mTextColor = 0
private var mSecondaryProgressColor = 0
private var mPrimaryProgressColor = 0
private var mBackgroundColor = 0
private var mStrokeWidth = 0
private var mProgress = 0
var secodaryProgress = 0
private set
private var firstArcprogress = 0
private var secondArcProgress = 0
private var thirdArcProgress = 0
private var mFristArcCapSize = 0
private var mSecondArcCapSize = 0
private var mThirdArcCapSize = 0
private var isFristCapVisible = false
private var isSecondCapVisible = false
private var isThirdCapVisible = false
private var capColor = 0
private var mPrimaryCapSize = 0
private var mSecondaryCapSize = 0
var isPrimaryCapVisible = false
var isSecondaryCapVisible = false
private var x = 0
private var y = 0
private var mWidth = 0
private var mHeight = 0
constructor(context: Context) : super(context) {
init(context, null)
}
constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) {
init(context, attrs)
}
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(
context,
attrs,
defStyleAttr
) {
init(context, attrs)
}
fun init(context: Context, attrs: AttributeSet?) {
val a: TypedArray
a = if (attrs != null) {
context.getTheme().obtainStyledAttributes(
attrs,
R.styleable.CustomProgressBar,
0, 0
)
} else {
throw IllegalArgumentException("Must have to pass the attributes")
}
try {
mProgressDrawable = resources.getDrawable(R.drawable.test)
mDrawText = a.getBoolean(R.styleable.CustomProgressBar_showProgressText, false)
mBackgroundColor =
a.getColor(
R.styleable.CustomProgressBar_backgroundColor,
resources.getColor(R.color.white)
)
mPrimaryProgressColor =
a.getColor(
R.styleable.CustomProgressBar_progressColor,
resources.getColor(R.color.white)
)
mSecondaryProgressColor =
a.getColor(
R.styleable.CustomProgressBar_secondaryProgressColor,
resources.getColor(R.color.black)
)
capColor =
a.getColor(
R.styleable.CustomProgressBar_capColor,
resources.getColor(R.color.color_9bc6e6_mind)
)
firstArcprogress =a.getInt(R.styleable.CustomProgressBar_firstArcProgress, 0)
mProgress = a.getInt(R.styleable.CustomProgressBar_progress, 0)
secodaryProgress = a.getInt(R.styleable.CustomProgressBar_secondaryProgress, 0)
mStrokeWidth = a.getDimensionPixelSize(R.styleable.CustomProgressBar_strokeWidth, 10)
mTextColor = a.getColor(
R.styleable.CustomProgressBar_textColor,
resources.getColor(R.color.black)
)
mPrimaryCapSize = a.getInt(R.styleable.CustomProgressBar_primaryCapSize, 20)
mSecondaryCapSize = a.getInt(R.styleable.CustomProgressBar_secodaryCapSize, 20)
isPrimaryCapVisible =
a.getBoolean(R.styleable.CustomProgressBar_primaryCapVisibility, true)
isSecondaryCapVisible =
a.getBoolean(R.styleable.CustomProgressBar_secodaryCapVisibility, true)
isFristCapVisible = a.getBoolean(R.styleable.CustomProgressBar_firstCapVisibility, true)
isSecondCapVisible =
a.getBoolean(R.styleable.CustomProgressBar_secodaryCapVisibility, false)
isThirdCapVisible =
a.getBoolean(R.styleable.CustomProgressBar_thirdCapVisibility, false)
} finally {
a.recycle()
}
mBackgroundPaint = Paint()
mBackgroundPaint!!.setAntiAlias(true)
mBackgroundPaint!!.setStyle(Paint.Style.STROKE)
mBackgroundPaint!!.setStrokeWidth(mStrokeWidth.toFloat())
mBackgroundPaint!!.setColor(mBackgroundColor)
mPrimaryPaint = Paint()
mPrimaryPaint!!.setAntiAlias(true)
mPrimaryPaint!!.setStyle(Paint.Style.STROKE)
mPrimaryPaint!!.setStrokeWidth(mStrokeWidth.toFloat())
mPrimaryPaint!!.setColor(capColor)
mSecondaryPaint = Paint()
mSecondaryPaint!!.setAntiAlias(true)
mSecondaryPaint!!.setStyle(Paint.Style.STROKE)
mSecondaryPaint!!.setStrokeWidth((mStrokeWidth - 2).toFloat())
mSecondaryPaint!!.setColor(mSecondaryProgressColor)
mTextPaint = TextPaint()
mTextPaint!!.color = mTextColor
mRectF = RectF()
}
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
super.onSizeChanged(w, h, oldw, oldh)
mRectF!![paddingLeft.toFloat(), paddingTop.toFloat(), (w - paddingRight).toFloat()] =
(h - paddingBottom).toFloat()
mTextPaint!!.textSize = (w / 5).toFloat()
x = w / 2 - (mTextPaint!!.measureText("$mProgress%") / 2).toInt()
y = (h / 2 - (mTextPaint!!.descent() + mTextPaint!!.ascent()) / 2).toInt()
mWidth = w
mHeight = h
invalidate()
}
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
mPrimaryPaint?.setStyle(Paint.Style.STROKE)
mSecondaryPaint?.setStyle(Paint.Style.STROKE)
// for drawing a full progress .. The background circle
mRectF?.let {
mBackgroundPaint?.let { it1 -> canvas.drawArc(it, 270F, 180F, false, it1) } }
path = Path()
path?.arcTo(mRectF,270F,180F,true)
mRectF?.let {
mBackgroundPaint?.let { it1 ->
canvas.drawArc(it, 95F, 80F, false, it1)
}
}
mRectF?.let {
mBackgroundPaint?.let { it1 ->
canvas.drawArc(it, 180F, 80F, false, it1)
}
}
// // for drawing a secondary progress circle
// val secondarySwipeangle = secodaryProgress * 360 / 100
// mRectF?.let { mSecondaryPaint?.let { it1 ->
// canvas.drawArc(it, 270F, secondarySwipeangle.toFloat(), false,
// it1
// )
// } }
//
val firstArcProgresSwipeAngle = firstArcprogress * 180 / 100
// mRectF?.let {
// mPrimaryPaint?.let { it1 ->
// canvas.drawArc(
// it, 270F, firstArcProgresSwipeAngle.toFloat(), false,
// it1
// )
// }
// }
// // for drawing a main progress circle
// val primarySwipeangle = mProgress * 360 / 100
// mRectF?.let { mPrimaryPaint?.let { it1 ->
// canvas.drawArc(it, 270F, primarySwipeangle.toFloat(), false,
// it1
// )
// } }
// // for cap of secondary progress
// val r = (height - paddingLeft * 2) / 2 // Calculated from canvas width
// var trad = (secondarySwipeangle - 90) * (Math.PI / 180.0) // = 5.1051
// var x = (r * Math.cos(trad)).toInt()
// var y = (r * Math.sin(trad)).toInt()
// mSecondaryPaint?.setStyle(Paint.Style.FILL)
// if (isSecondaryCapVisible) mSecondaryPaint?.let {
// canvas.drawCircle(
// (x + mWidth / 2).toFloat(),
// (y + mHeight / 2).toFloat(),
// mSecondaryCapSize.toFloat(),
// it
// )
// }
val r = (height - paddingLeft * 2) / 2
// for cap of primary progress
var trad = (firstArcProgresSwipeAngle - 90) * (Math.PI / 180.0) // = 5.1051
x = (r * Math.cos(trad)).toInt()
y = (r * Math.sin(trad)).toInt()
mPrimaryPaint?.setStyle(Paint.Style.FILL)
if (isPrimaryCapVisible) mPrimaryPaint?.let {
canvas.drawCircle(
(x + mWidth / 2).toFloat(),
(y + mHeight / 2).toFloat(),
mPrimaryCapSize.toFloat(),
it
)
}
if (mDrawText) mTextPaint?.let {
canvas.drawText(
"$mProgress%", x.toFloat(), y.toFloat(),
it
)
}
}
fun setDrawText(mDrawText: Boolean) {
this.mDrawText = mDrawText
invalidate()
}
override fun setBackgroundColor(mBackgroundColor: Int) {
this.mBackgroundColor = mBackgroundColor
mBackgroundPaint?.setColor(mBackgroundColor)
invalidate()
}
fun setStrokeWidth(mStrokeWidth: Int) {
this.mStrokeWidth = mStrokeWidth
invalidate()
}
fun setSecondaryProgress(mSecondaryProgress: Int) {
secodaryProgress = mSecondaryProgress
invalidate()
}
fun setTextColor(mTextColor: Int) {
this.mTextColor = mTextColor
mTextPaint!!.color = mTextColor
invalidate()
}
var secondaryProgressColor: Int
get() = mSecondaryProgressColor
set(mSecondaryProgressColor) {
this.mSecondaryProgressColor = mSecondaryProgressColor
mSecondaryPaint?.setColor(mSecondaryProgressColor)
invalidate()
}
var primaryProgressColor: Int
get() = mPrimaryProgressColor
set(mPrimaryProgressColor) {
this.mPrimaryProgressColor = mPrimaryProgressColor
mPrimaryPaint?.setColor(mPrimaryProgressColor)
invalidate()
}
var progress: Int
get() = mProgress
set(mProgress) {
while (this.mProgress <= mProgress) {
postInvalidateDelayed(150)
this.mProgress++
}
}
fun getBackgroundColor(): Int {
return mBackgroundColor
}
var primaryCapSize: Int
get() = mPrimaryCapSize
set(mPrimaryCapSize) {
this.mPrimaryCapSize = mPrimaryCapSize
invalidate()
}
var secondaryCapSize: Int
get() = mSecondaryCapSize
set(mSecondaryCapSize) {
this.mSecondaryCapSize = mSecondaryCapSize
invalidate()
}
var arcprogress: Int
get() = firstArcprogress
set(firstArcprogress) {
while (this.firstArcprogress <= firstArcprogress) {
postInvalidateDelayed(150)
this.firstArcprogress = firstArcprogress
}
}
fun getPath(): Path? {
return path
}
fun getXCOORDINTE(): Float{
return x.toFloat()
}
fun getYCoordinate(): Float{
return y.toFloat()
}
}
i'm extended libgdx in fragment my application, and i have problem.
If i open fragment:
activity?.supportFragmentManager?.beginTransaction()?.replace(R.id.objectSchemeView, GenerateBitmap.newInstnace(scheme))?.commit()
and press back from fragment -> and again open fragment, i'll get error:
E/AndroidGraphics: waiting for pause synchronization took too long; assuming deadlock and killing
i search solution this problem, but developers framework deny the problem.
Someone has found a solution to this problem for fragment?
Code Adapter (TestModel - big json)
class Schema(var testModel: TestModel) : ApplicationAdapter() {
#Inject
lateinit var resource: IResourseProvider
internal lateinit var batch: SpriteBatch
private lateinit var texture: Texture
private lateinit var blackTexture: Texture
private lateinit var noneActive: Texture
private var sprites = LinkedHashMap<Sprite, PlaceItem>()
private var noneFreePlaces: ArrayList<PlaceItem> = ArrayList()
private lateinit var camera: OrthographicCamera
private lateinit var controller: CameraController
private lateinit var gestureDetector: GestureDetector
init {
App.graph.inject(this)
}
override fun create() {
batch = SpriteBatch()
camera = OrthographicCamera(Gdx.graphics.width.toFloat(), Gdx.graphics.height.toFloat())
controller = CameraController()
gestureDetector = GestureDetector(20f, 0.5f, 2f, 0.15f, controller)
Gdx.input.inputProcessor = gestureDetector
val pixmap = Pixmap(30, 30, Pixmap.Format.RGBA8888)
pixmap.setColor(Color.BLACK)
pixmap.fillCircle(10, 10, 10)
texture = Texture(pixmap)
pixmap.setColor(Color.BLACK)
pixmap.fillCircle(10, 10, 10)
blackTexture = Texture(pixmap)
pixmap.setColor(Color.GRAY)
pixmap.fillCircle(10, 10, 10)
noneActive = Texture(pixmap)
testModel.places?.forEach {
when (getState(it.state)) {
PlaceState.FREE -> {
sprites.put(Sprite(Texture(pixmap)), it)
}
else -> {
noneFreePlaces.add(it)
}
}
}
}
override fun render() {
Gdx.gl.glClearColor(1f, 1f, 1f, 1f)
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT)
controller.update()
camera.update()
batch.projectionMatrix = camera.combined
batch.begin()
for ((sprite, placeItem) in sprites) {
sprite.setPosition(placeItem.x.toFloat(), placeItem.y.toFloat())
batch.draw(sprite, placeItem.x.toFloat(), placeItem.y.toFloat())
}
noneFreePlaces.forEach {
batch.draw(noneActive, it.x.toFloat(), it.y.toFloat())
}
batch.end()
}
override fun pause() {
super.pause()
for ((sprite, placeItem) in sprites) {
sprite.texture.dispose()
}
}
override fun dispose() {
batch.dispose()
texture.dispose()
blackTexture.dispose()
noneActive.dispose()
for ((sprite, placeItem) in sprites) {
sprite.texture.dispose()
}
}
internal inner class CameraController : GestureDetector.GestureListener {
private var velX: Float = 0.toFloat()
private var velY: Float = 0.toFloat()
private var flinging = false
private var initialScale = 1f
override fun touchDown(x: Float, y: Float, pointer: Int, button: Int): Boolean {
flinging = false
initialScale = camera.zoom
return false
}
override fun tap(x: Float, y: Float, count: Int, button: Int): Boolean {
Gdx.app.log("GestureDetectorTest", "tap at $x, $y, count: $count")
val touch = Vector3()
camera.unproject(touch.set(x, y, 0f))
for ((sprite, placeItem) in sprites) {
if (sprite.boundingRectangle.contains(touch.x, touch.y)) {
if (sprite.texture == blackTexture)
sprite.texture = texture
else
sprite.texture = blackTexture
}
}
return false
}
override fun longPress(x: Float, y: Float): Boolean {
Gdx.app.log("GestureDetectorTest", "long press at $x, $y")
return false
}
override fun fling(velocityX: Float, velocityY: Float, button: Int): Boolean {
Gdx.app.log("GestureDetectorTest", "fling $velocityX, $velocityY")
flinging = true
velX = camera.zoom * velocityX * 0.5f
velY = camera.zoom * velocityY * 0.5f
return false
}
override fun pan(x: Float, y: Float, deltaX: Float, deltaY: Float): Boolean {
// Gdx.app.log("GestureDetectorTest", "pan at " + x + ", " + y);
camera.position.add(-deltaX * camera.zoom, deltaY * camera.zoom, 0f)
return false
}
override fun panStop(x: Float, y: Float, pointer: Int, button: Int): Boolean {
Gdx.app.log("GestureDetectorTest", "pan stop at $x, $y")
return false
}
override fun zoom(originalDistance: Float, currentDistance: Float): Boolean {
val ratio = originalDistance / currentDistance
camera.zoom = initialScale * ratio
println(camera.zoom)
return false
}
override fun pinch(initialFirstPointer: Vector2, initialSecondPointer: Vector2, firstPointer: Vector2, secondPointer: Vector2): Boolean {
return false
}
fun update() {
if (flinging) {
velX *= 0.98f
velY *= 0.98f
camera.position.add(-velX * Gdx.graphics.deltaTime, velY * Gdx.graphics.deltaTime, 0f)
if (Math.abs(velX) < 0.01f) velX = 0f
if (Math.abs(velY) < 0.01f) velY = 0f
}
}
override fun pinchStop() {
}
}
}
Code ApplicationFragment
class GenerateBitmap: AndroidFragmentApplication() {
private var glSurfaceView: GLSurfaceView? = null
private lateinit var model: TestModel
private lateinit var schema: Schema
private val config = AndroidApplicationConfiguration()
override fun onResume() {
Gdx.input.isCatchBackKey = true
Gdx.graphics.isContinuousRendering = true
super.onResume()
}
override fun onCreateView(inflater: LayoutInflater?, container: ViewGroup?, savedInstanceState: Bundle?): View {
val reader = BufferedReader(InputStreamReader(resources.openRawResource((R.raw.example))))
val mModel = Gson().fromJson<TestModel>(reader.readLine().toString(), TestModel::class.java)
schema = Schema(mModel)
return initializeForView(schema, config)
}
companion object {
private val MODEL_KEY = "scheme"
fun newInstnace(model: TestModel): GenerateBitmap {
val args = Bundle()
args.putSerializable(MODEL_KEY, model)
val fragment = GenerateBitmap()
fragment.arguments = args
return fragment
}
}
}