When I add ImageView using animation it works smoothly fine. But when I set up the player (astronaut) to be moved with interaction, the ImageView keeps trembling when not moving.
Problem
I have attached all the related codes in Kotlin below
MyGameObject class
open class MyGameObject(var x:Int, var y:Int, var dx:Int, var dy:Int, var image:Drawable) {
var width:Int = 300
var height:Int = 300
open fun move(canvas:Canvas)
{
x += dx
y += dy
if(x > (canvas.width - width) || x < 0)
dx = -dx
if(y > (canvas.height - height) || y < 0)
dy = -dy
image.setBounds(x, y, x+width, y+width)
image.draw(canvas)
}
}
Astronaut class (player)
import android.graphics.Canvas
import android.graphics.drawable.Drawable
class Astronaut(x:Int, y:Int, dx:Int, dy:Int, image: Drawable) : MyGameObject(x, y,dx, dy, image) {
var px:Int=0
var py:Int=0
override fun move(canvas: Canvas) {
if (px > x)
x += 5
else
x -= 5
if (py > y)
y += 5
else
y -= 5
image.setBounds(x, y, x + width, y + height)
image.draw(canvas)
}
}
MySurfaceView class
import android.content.Context
import android.graphics.Canvas
import android.graphics.Paint
import android.util.AttributeSet
import android.view.*
class MySurfaceView(context: Context?, attrs: AttributeSet?) : SurfaceView(context, attrs), Runnable {
var paint = Paint()
var isRunning = true
lateinit var myThread: Thread
lateinit var myHolder: SurfaceHolder
var myGameObjects = ArrayList<MyGameObject>()
val astronautImage = context!!.resources.getDrawable(R.drawable.astronaut, null)
val astronaut = Astronaut(100, 100, 0, 0, astronautImage)
init {
val asteroid = context!!.resources.getDrawable(R.drawable.asteroid, null)
myGameObjects.add(MyGameObject(100, 100, 10, 10, asteroid))
myGameObjects.add(astronaut)
myThread = Thread(this)
myThread.start()
myHolder = holder // controls access to canvas
}
override fun onTouchEvent(event: MotionEvent?): Boolean {
//return super.onTouchEvent(event)
astronaut.px = event!!.x.toInt()
astronaut.py = event!!.y.toInt()
return true
}
override fun run() {
while(isRunning)
{
if(!myHolder.surface.isValid)
{
continue
}
val canvas: Canvas = myHolder.lockCanvas() // prevent other threads using this section
canvas.drawRect(0f, 0f, canvas.width.toFloat(), canvas.height.toFloat(), paint)
for(gameObject in myGameObjects)
{
gameObject.move(canvas)
}
myHolder.unlockCanvasAndPost(canvas)
}
}
}
I have already tried to change values in astronaut class and MySurfaceView class but that did not work.
I think it is because you haven't set a fallback option in case astronaut is not moving. When px is equal to astronaut's x, astronaut goes into the else block because the px > x is false, then it moves 5 pixels to the left, then it goes back to initial position because now px is greater than x.
So, based by your logic, the condition should be:
x = x + when {
px > x -> 5
px == x -> 0
px < x -> -5
}
same with the y coordinate, you need a case when the astronaut is NOT moving... That's why the bitmap *trembles* in a diagonal path.
Hope this helps!
Related
My program now draws an oval at the touch of a finger and moves it along with the finger.
I want to add a multi-tap so that without releasing the first oval, pressing with the second finger will add the next one.
How my code works now:
Here I create 20 ovals:
var ovalsViews = Array(20) { OvalsView(this) }
In the function MotionEvent.ACTION_DOWN, MotionEvent.ACTION_POINTER_DOWN (here I put my finger on the screen) I add ovals from the ovals table to the layout (ConstraintLayout):
layout.addView(ovalsViews[ID])
In the MotionEvent.ACTION_MOVE function (here I move my finger on the screen) I change the coordinates of each oval that I hold with my finger:
ovalsViews[ID]._top = motionEvent.getY(ID)
ovalsViews[ID]._left = motionEvent.getX(ID)
And finally, in the function MotionEvent.ACTION_UP, MotionEvent.ACTION_POINTER_UP (here I release my finger from the screen) I want to remove the oval that I am holding with my finger:
layout.removeView(ovalsViews[ID])
Now my program is working strangely, when I click once everything works ok, but the next clicks do not add new ovals. Also, when I release the first finger, the oval disappears and in order to get an oval for the first time, now you will need to hit it twice, then three, and so on. What am I doing wrong?
Here is the whole code:
package com.example.lab10
import android.annotation.SuppressLint
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.content.Context
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Paint
import android.util.DisplayMetrics
import android.util.Log
import android.view.MotionEvent
import android.view.SurfaceHolder
import android.view.SurfaceView
import android.view.View
import android.view.WindowManager
import androidx.constraintlayout.widget.ConstraintLayout
class TaskSevenNEW : AppCompatActivity() {
#SuppressLint("ClickableViewAccessibility")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_task_seven_new)
title = "Task#7 NEW"
val layout = findViewById<ConstraintLayout>(R.id.layout_7_NEW)
var ovalsViews = Array(20) { OvalsView(this) }
layout.setOnTouchListener(View.OnTouchListener { view, motionEvent ->
when (motionEvent.getActionMasked()) {
MotionEvent.ACTION_DOWN, MotionEvent.ACTION_POINTER_DOWN -> {
Log.d("touchInfo", "down")
val ID: Int = motionEvent.getPointerId(motionEvent.getActionIndex())
Log.d("touchInfo", "down ID: " + ID)
layout.addView(ovalsViews[ID])
}
MotionEvent.ACTION_MOVE -> {
Log.d("touchInfo", "move")
var idx = 0
while (idx < motionEvent.pointerCount) {
val ID: Int = motionEvent.getPointerId(idx) // pobranie unikalnego id dla każdego dotyku
idx++
Log.d("touchInfo", "move ID: " + ID)
ovalsViews[ID]._top = motionEvent.getY(ID)
ovalsViews[ID]._left = motionEvent.getX(ID)
}
}
MotionEvent.ACTION_UP, MotionEvent.ACTION_POINTER_UP -> {
Log.d("touchInfo", "up")
val ID: Int = motionEvent.getPointerId(motionEvent.getActionIndex())
Log.d("touchInfo", "up ID: " + ID)
layout.removeView(ovalsViews[ID])
}
else -> Log.d("touchInfo", "unhandled")
}
return#OnTouchListener true
})
}
private class OvalsView(context: Context) : SurfaceView(context), SurfaceHolder.Callback {
private val mSurfaceHolder: SurfaceHolder
private val mPainter = Paint()
private var mDrawingThread: Thread? = null
private val mDisplay = DisplayMetrics()
private var mDisplayWidth: Int
private var mDisplayHeight: Int
private var mRotation = 0f
private var running = true
var _top: Float = 0f
var _left: Float = 0f
init {
val wm = context.getSystemService(Context.WINDOW_SERVICE) as WindowManager
wm.defaultDisplay.getMetrics(mDisplay)
mDisplayWidth = mDisplay.widthPixels
mDisplayHeight = mDisplay.heightPixels
mPainter.isAntiAlias = true
mPainter.color = Color.RED
mSurfaceHolder = holder
mSurfaceHolder.addCallback(this)
}
private fun animateOvals(): Boolean {
mRotation += 1
return true
}
private fun drawWheel(canvas: Canvas) {
canvas.drawColor(Color.WHITE)
// canvas.rotate(mRotation, mDisplayWidth / 2f, mDisplayHeight / 2f)
//drawOval(float left, float top, float right, float bottom, #NonNull Paint paint)
// canvas.drawOval(mDisplayWidth/2f - 100f/2f - 100f, mDisplayHeight/2 + 100f/2f,
// mDisplayWidth/2f + 100f/2f + 100f, mDisplayHeight/2f - 100/2f, mPainter)
if (_top > 0f && _left > 0f)
canvas.drawOval(_left, _top, _left + 200f, _top + 350f, mPainter)
}
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
mDisplayWidth = w
mDisplayHeight = h
super.onSizeChanged(w, h, oldw, oldh)
}
override fun surfaceChanged(holder: SurfaceHolder, format: Int, width: Int, height: Int) {
}
override fun surfaceDestroyed(holder: SurfaceHolder) {
if (mDrawingThread != null) {
mDrawingThread!!.interrupt()
running = false
mDrawingThread!!.join()
Log.e("qqq", "Thread done")
}
}
override fun surfaceCreated(holder: SurfaceHolder) {
mDrawingThread = Thread(Runnable {
var frameStartTime = System.nanoTime();
var frameTime: Long = 0
var canvas: Canvas? = null
while (!Thread.currentThread().isInterrupted && animateOvals() && running) {
canvas = mSurfaceHolder.lockCanvas()
if (canvas != null) {
drawWheel(canvas)
mSurfaceHolder.unlockCanvasAndPost(canvas)
}
frameTime = (System.nanoTime() - frameStartTime) / 1000000
if (frameTime < MAX_FRAME_TIME) // faster than the max fps - limit the FPS
{
try {
Thread.sleep(MAX_FRAME_TIME - frameTime)
} catch (e: InterruptedException) {
// ignore
}
}
}
})
mDrawingThread!!.start()
}
}
}
I need to compare, for example, two straight lines. One of these lines will be set by me initially (or drawn), will have certain coordinates and thickness. The second one will be drawn by the user. Finally, I need to see the result of comparing these lines as a percentage. Can it be implement like in the following code below? I had an idea to draw my own line, put its coordinates from Path into an array somehow, and then compare it with the new one. But i don't know.
And, and yet, can you tell me how to make the background transparent? I am not satisfied with the replacement of a white background with a static picture, the animation will take place in the far background
Thank you!
enter image description here
import android.content.Context
import android.graphics.*
import android.util.Log
import android.view.View
import android.view.MotionEvent
import android.view.ViewConfiguration
import androidx.core.content.res.ResourcesCompat
class SomeDraw(context: Context) : View(context) {
// Holds the path you are currently drawing.
private var path = Path()
private val drawColor = ResourcesCompat.getColor(resources, R.color.purple_700, null)
private val backgroundColor = ResourcesCompat.getColor(resources, R.color.transparent, null)
private lateinit var extraCanvas: Canvas
private lateinit var extraBitmap: Bitmap
private lateinit var frame: Rect
// Set up the paint with which to draw.
private val paint = Paint().apply {
color = drawColor
// Smooths out edges of what is drawn without affecting shape.
isAntiAlias = true
// Dithering affects how colors with higher-precision than the device are down-sampled.
isDither = true
style = Paint.Style.STROKE // default: FILL
strokeJoin = Paint.Join.ROUND // default: MITER
strokeCap = Paint.Cap.ROUND // default: BUTT
strokeWidth = 70.0F // default: Hairline-width (really thin)
}
/**
* Don't draw every single pixel.
* If the finger has has moved less than this distance, don't draw. scaledTouchSlop, returns
* the distance in pixels a touch can wander before we think the user is scrolling.
*/
private val touchTolerance = ViewConfiguration.get(context).scaledTouchSlop
private var currentX = 0f
private var currentY = 0f
private var motionTouchEventX = 0f
private var motionTouchEventY = 0f
/**
* Called whenever the view changes size.
* Since the view starts out with no size, this is also called after
* the view has been inflated and has a valid size.
*/
override fun onSizeChanged(width: Int, height: Int, oldWidth: Int, oldHeight: Int) {
super.onSizeChanged(width, height, oldWidth, oldHeight)
if (::extraBitmap.isInitialized) extraBitmap.recycle()
extraBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
extraCanvas = Canvas(extraBitmap)
extraCanvas.drawColor(backgroundColor)
// Calculate a rectangular frame around the picture.
val inset = 0
frame = Rect(inset, inset, width - inset, height - inset)
}
override fun onDraw(canvas: Canvas) {
// Draw the bitmap that has the saved path.
canvas.drawBitmap(extraBitmap, 0f, 0f, null)
// Draw a frame around the canvas.
extraCanvas.drawRect(frame, paint)
}
/**
* No need to call and implement MyCanvasView#performClick, because MyCanvasView custom view
* does not handle click actions.
*/
override fun onTouchEvent(event: MotionEvent): Boolean {
motionTouchEventX = event.x
motionTouchEventY = event.y
when (event.action) {
MotionEvent.ACTION_DOWN -> touchStart()
MotionEvent.ACTION_MOVE -> touchMove()
MotionEvent.ACTION_UP -> touchUp()
}
return true
}
/**
* The following methods factor out what happens for different touch events,
* as determined by the onTouchEvent() when statement.
* This keeps the when conditional block
* concise and makes it easier to change what happens for each event.
* No need to call invalidate because we are not drawing anything.
*/
private fun touchStart() {
path.reset()
path.moveTo(motionTouchEventX, motionTouchEventY)
currentX = motionTouchEventX
currentY = motionTouchEventY
}
private fun touchMove() {
val dx = Math.abs(motionTouchEventX - currentX)
val dy = Math.abs(motionTouchEventY - currentY)
if (dx >= touchTolerance || dy >= touchTolerance) {
// QuadTo() adds a quadratic bezier from the last point,
// approaching control point (x1,y1), and ending at (x2,y2).
path.quadTo(currentX, currentY, (motionTouchEventX + currentX) / 2, (motionTouchEventY + currentY) / 2)
currentX = motionTouchEventX
currentY = motionTouchEventY
// Draw the path in the extra bitmap to save it.
extraCanvas.drawPath(path, paint)
val pathTrue = path
Log.d("AAA", ""+pathTrue.equals(path))
}
// Invalidate() is inside the touchMove() under ACTION_MOVE because there are many other
// types of motion events passed into this listener, and we don't want to invalidate the
// view for those.
invalidate()
}
private fun touchUp() {
// Reset the path so it doesn't get drawn again.
path.reset()
}
}
I tried to work with Path but nothing worked. Tried various ways to set transparency to the background (color, apply alpha, delete) - nothing helped
I am using a chart library called MPAndroidChart and it responds to most of my needs. However, I need to customize some parts.
I want to draw some indicator lines for labels on xAxis like this :
As I dug in, I could write a CustomAxisRenderer but it seems I need to copy most of the super class codes.
I want the min value to be drawn exactly on xAxis. This min value could be 0 or any other number as well.
How can this be done? Is it even possible to do it?
Any help or hint would be appreciated.
I solved the first issue:
internal class IndicatorAxisRenderer(
viewPortHandler: ViewPortHandler,
xAxis: XAxis,
trans: Transformer
) : XAxisRenderer(viewPortHandler, xAxis, trans) {
private var indicatorWidth = 1f
private var indicatorHeight = 1f
private fun getXLabelPositions(): FloatArray {
var i = 0
val positions = FloatArray(mXAxis.mEntryCount * 2)
val centeringEnabled = mXAxis.isCenterAxisLabelsEnabled
while (i < positions.size) {
if (centeringEnabled) {
positions[i] = mXAxis.mCenteredEntries[i / 2]
} else {
positions[i] = mXAxis.mEntries[i / 2]
}
positions[i + 1] = 0f
i += 2
}
mTrans.pointValuesToPixel(positions)
return positions
}
override fun renderAxisLine(c: Canvas?) {
super.renderAxisLine(c)
val positions = getXLabelPositions()
var i = 0
while (i < positions.size) {
val x = positions[i]
if (mViewPortHandler.isInBoundsX(x)) {
val y = mViewPortHandler.contentBottom()
c?.drawLine(
x, y,
x, y + indicatorHeight,
mAxisLinePaint
)
}
i += 2
}
}
fun setIndicatorSize(width: Float, height: Float) {
this.indicatorWidth = width
this.indicatorHeight = height
}
}
This code renders indicator lines on top of the xAxis.
I want to move "bitmap" from top to down and then from down to top.
I use "invalidate()" to refresh "onDraw()" after "y1" increases or decreases, everything working fine but "bitmap" movement is very slow.
I can increases or decreases y1 like "y1 += 15" but "bitmap" will move not smooth
I want refresh "onDraw()" every 100ms or less.
How to do it?
This screenshot
package com.example.duc25.runningman
import android.content.Context
import android.graphics.BitmapFactory
import android.graphics.Canvas
import android.graphics.Paint
import android.util.Log
import android.view.MotionEvent
import android.view.View
import android.widget.Toast
import java.util.*
class Man(contex: Context, var screenW: Float, var screenH: Float): View(contex){
var bitmap1 = BitmapFactory.decodeResource(resources, R.drawable.step1)
var x1: Float = 0F
var y1: Float = 0F
var boolean1: Boolean = true
override fun onTouchEvent(event: MotionEvent): Boolean{
if(event.action === MotionEvent.ACTION_DOWN){
if(boolean1 == true){
boolean1 = false
}else{
boolean1 = true
}
}
return true
}
override fun onDraw(canvas: Canvas){
set_X(canvas)
if (boolean1) {
if (y1 <= (screenH / 100) * 70){
y1 += 1F
}else{
boolean1 = false
}
}else{
if(y1 >= 0) {
y1 -= 1F
}else{
boolean1 = true
}
}
invalidate()
}
fun set_X(canvas: Canvas) {
val paint = Paint(Paint.ANTI_ALIAS_FLAG)
canvas.drawBitmap(bitmap1, x1, y1, paint)
}
}
Instead of using invalidate(), you probably want to use postInvalidateOnAnimation(). This will trigger an animation at 60 fps, or ~ every 16 millis.
You could also use some sort of postInvalidateDelayed() if you want a slower fps (you probably want 60 fps).
My guess you are getting lag because you are spamming the main thread with invalidate() repetitively. If you are still getting lag when using postInvalidateOnAnimation(), then what you are doing on each animation frame is too heavy, and you'll have to find a way to make it cheaper / faster.
EDIT: A few other tips.
1. You are creating the Paint every animation frame. That's bad. I recommend you move the val paint = Paint(Paint.ANTI_ALIAS_FLAG) into a property at the top of the class, i.e. with the y1 and x1 properties.
2. bitmap1 can be val instead of var.
At this class i draw simple ground for Tic-Tae-Toe. It consists of intercepted lines and "X" in the center of the cell.
So when User touches the cell, then textColor in it should be changed.
I use invalidate(rect) to redraw concrete cell, but in this case every cell changes it's textColor.
According to Romain Guy words, the canvas with whole view Rect comes
for drawing. The DisplayList will find interceptions between drawing commands and your dirty Rect, and only those commands will be drawn. But seems, that it doesn't work so way.
Partial invalidation in custom Android view with hardware acceleration
And also i found strange code change between 4.4 - 5.0 Android. So you can see, that mCurrentDirty disappeared from code at all.
Android View.invalidate(Rect) different behavior between two devices
P.S for SA this logic works correctly, and only dirty Rect is changed.
package com.eugeneshapovalov.fizmigclient
import android.content.Context
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Paint
import android.graphics.Rect
import android.util.AttributeSet
import android.view.MotionEvent
import android.view.View
import timber.log.Timber
class TicTacToeView : View, View.OnTouchListener {
constructor(context: Context) : this(context, null)
constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, 0)
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)
companion object {
const val CELL_SIZE_RATIO = 1 / 3f
const val LINE_SIZE = 2f
const val CELL_COUNT = 3
}
val linePaint = Paint()
val textPaint = Paint()
val dirtyCell = Rect()
var colorNumber: Int
init {
setOnTouchListener(this)
colorNumber = 0
linePaint.strokeWidth = resources.displayMetrics.density * LINE_SIZE
textPaint.textSize = 60f
}
private lateinit var cells: Array<Array<Rect>>
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
super.onSizeChanged(w, h, oldw, oldh)
initCells()
}
private fun initCells() {
cells = Array(CELL_COUNT, { Array(CELL_COUNT, { Rect() }) })
val xCell = (width * CELL_SIZE_RATIO).toInt()
val yCell = (height * CELL_SIZE_RATIO).toInt()
for (i in 0 until CELL_COUNT) {
for (j in 0 until CELL_COUNT) {
cells[i][j].left = (x + j * xCell).toInt()
cells[i][j].top = (y + i * yCell).toInt()
cells[i][j].right = (x + (j + 1) * xCell).toInt()
cells[i][j].bottom = (y + (i + 1) * yCell).toInt()
}
}
}
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
drawLines(canvas)
drawText(canvas)
}
private fun drawLines(canvas: Canvas) {
// Vertical lines
canvas.drawLine(x + width * CELL_SIZE_RATIO, y, x + width * CELL_SIZE_RATIO, y + height, linePaint)
canvas.drawLine(x + width * 2 * CELL_SIZE_RATIO, y, x + width * 2 * CELL_SIZE_RATIO, y + height, linePaint)
// Horizontal lines
canvas.drawLine(x, y + height * CELL_SIZE_RATIO, x + width, y + height * CELL_SIZE_RATIO, linePaint)
canvas.drawLine(x, y + height * 2 * CELL_SIZE_RATIO, x + width, y + height * 2 * CELL_SIZE_RATIO, linePaint)
}
private fun drawText(canvas: Canvas) {
textPaint.color = when (colorNumber % 5) {
0 -> Color.BLACK
1 -> Color.BLUE
2 -> Color.RED
3 -> Color.GRAY
4 -> Color.YELLOW
else -> Color.GREEN
}
for (i in 0 until CELL_COUNT) {
for (j in 0 until CELL_COUNT) {
val rect = cells[i][j]
canvas.drawText("X", rect.exactCenterX(), rect.exactCenterY(), textPaint)
}
}
}
override fun onTouch(v: View?, event: MotionEvent?): Boolean {
when (event?.action) {
MotionEvent.ACTION_UP -> {
for (i in 0 until CELL_COUNT) {
for (j in 0 until CELL_COUNT) {
val rect = cells[i][j]
if (rect.contains(event.x.toInt(), event.y.toInt())) {
colorNumber += (j + 7)
Timber.d("Rect: ${rect.flattenToString()}")
invalidate(rect)
}
}
}
}
}
return true
}
}