Slow response onTouchEvent in Android View with background present - android

all!
I'm trying to figure out have why I have a slow response in Android View in onTouchEvent method. It's a simple TicTacToe game , i removed all the logic, and only things that concern the problem are left.
So far The onTouchEvent method gets coordinates and then calls onDraw method which simply draws a circle. The onDraw method also draws the background, which is 960x1280 jpg file with the size of 366 kB. It works but i have a delay between touching the screen and getting the circle drawn. It's a few seconds on emulator and about 0.5 seconds on my Samsung. I noticed that without background it works perfectly. But at the same time the problem seems to be not in drawing the background. I mean, that if i call invalidate() several times in a row drawing a few more figures, they are all drawn instantly, we have delay only before the first figure. So as I understand somehow onTouchEvent method reacts differently depending if there is a background.
I've been searching but the only thing i found was to add a 16 ms delay (it is in the code, commented) but it didn't help.
If to take a smaller pic , i have a better response, but i need this big picture to suit different phones.
So in conclusion , the problem is that response time depends on the size of background pic, but at the same time it's not a problem of drawing this big picture, only for onTouchEvent to respose. I am confused -) How can that be?
Please, help me if you can.
Here is the code:
public class GameViewBug extends View {
private Paint mCirclePaint;
private Bitmap bitmap;
private float touchX;
private float touchY;
public GameViewBug(Context context) {
super(context);
init();
}
public void init() {
mCirclePaint = new Paint();
mCirclePaint.setColor(Color.RED);
mCirclePaint.setStrokeWidth(10);
mCirclePaint.setStyle(Paint.Style.STROKE);
}
#Override
protected void onDraw(Canvas canvas) {
bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.kletka3);
canvas.drawBitmap(bitmap, 0, 0, null);
//canvas.drawColor(Color.WHITE);
canvas.drawCircle(touchX, touchY, 50, mCirclePaint);
}
public boolean onTouchEvent(MotionEvent event) {
// try {
// Thread.sleep(16);
// }catch (InterruptedException e) {}
if (event.getAction() == MotionEvent.ACTION_DOWN) {
touchX = event.getX();
touchY = event.getY();
}
invalidate();
return true;
}
}

The problem is, that you perform heavy computational operation on main thread in method, which is called very often:
bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.kletka3)
You should load that resource once (suggest using Glide library for displaying images), and do all computations on background thread.

Related

How much priority does scrollBy have over canvas onDraw?

This is a basic code for detecting touch + drawing a custom view :
public boolean onTouchEvent(MotionEvent event) { //Basic onTouch code for scrolling along the Y axis
super.onTouchEvent(event);
if(event.getActionMasked()==MotionEvent.ACTION_DOWN){
mPrevious = event.getY();
}
if(event.getActionMasked()==MotionEvent.ACTION_MOVE){
float distance = mPrevious - event.getY();
scrollBy(0,Math.round(distance));
mPrevious = event.getY();
}
return true;
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//Basic drawing of a circle at (200,200)
canvas.drawCircle(200f,200f,50f, defaultPaint);
//canvas.setMatrix(new Matrix());
Log.d("CANVAS", "("+canvas.getMatrix().toString()+")");
}
I'm working with a custom view, I'm trying to implement user scrolling functionality using scrollBy, but it looks like I'll have to create the logic myself.
I still want to understand how this function works. When I print log the canvas matrix in the last line, it correctly displays the new coordinates, and onDraw is called every time the touch moves.
But if I uncomment canvas.setMatrix, suprisingly nothing changes functionality-wise. The only difference now is that the console log shows that the canvas matrix is always equal to identity mx, even if it's correctly being scrolled. Why? How can scrollBy completely overwrite canvas drawing?

Technique to make a canvas drawLine() clickable?

I'm working on an app that plots nodes on a map, and each node has edges that are represented by a line between them. I've drawn the edges using Canvas and drawLine(), but it would be useful if the lines themselves could be clickable. By that I mean a method of allowing the user to touch the line or think they're touching the line and an event can trigger. (like display edge info, etc...)
I can't rightly attach a touch event to a line I've drawn with Canvas, so I was thinking of placing ImageViews inbetween the ends of each edge line that's drawn. The ImageView could be a dot so it's clear where the touch event triggers.
Does anyone have any other suggestions? I'm mainly looking for ideas that I've missed. Maybe there's something in the Android API that can help with this that I'm unaware of.
Thanks in advance for any tips!
Use a path to draw the line:
Path linePath;
Paint p;
RectF rectF;
float point1X, point1Y, point2X, point2Y;
// initialize components
// draw the line
linePath.moveTo(point1X, point1Y);
linePath.lineTo(point2X, point2Y);
canvas.drawPath(linePath, p);
linePath.computeBounds(rectF, true);
Override onTouchEvent(MotionEvent):
#Override
public boolean onTouchEvent(MotionEvent event) {
float touchX = event.getX();
float touchY = event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
if (rectF.contains(touchX, touchY)) {
// line has been clicked
}
break;
}
return true;
}

How to prevent onClick method on transparent portion of a PNG-loaded ImageView

I am currently developing an Android app that displays multiple images (as ImageView's) stacked on top of each other. Here is how the layers are currently configured:
Background layer: scales the entire screen, must be clickable
Foreground layer: scales the entire screen, must be clickable,
contains transparency which allows the user to see some of the
background layer
The problem I face is with the foreground layer. I am assigning the onClick() method to the imageview, but the method is being called whether they hit the portion of the image which is visible as well as the part which contains transparency. I only want the foreground ImageView onClick() method to be called when the user clicks a portion of that imageview that is not transparent.
This is what the scenario looks like:
The diagonal lines represent the transparent portion of the Foreground image. If a user touches this space, I want it to access the Background image instead of the Foreground image. Thank you for any assistance you can provide.
Here is the solution I implemented (Thanks to answer below):
//ontouchlistener - gets X and Y from event
private void setClick(View view)
{
view.setOnTouchListener(new View.OnTouchListener()
{
public boolean onTouch(View v, MotionEvent event)
{
int imageId = getImageId((int)event.getX(), (int)event.getY());
if (imageId >= 0)
performActions(imageId);
return false;
}
});
}
//get the ID of the first imageview (starting from foreground,
//working backwards) which contains a non-transparent pixel
private int getImageId(int x, int y)
{
ViewGroup parent = (ViewGroup) findViewById(R.id.relative_layout);
for (int a = parent.getChildCount()-1; a >= 0; a--)
{
if (parent.getChildAt(a) instanceof ImageView)
if (!checkPixelTransparent((ImageView)parent.getChildAt(a), x, y))
return parent.getChildAt(a).getId();
}
return -1;
}
//get bitmap from imageview, get pixel from x, y coord
//check if pixel is transparent
private boolean checkPixelTransparent(ImageView iv, int x, int y)
{
Bitmap bitmap = ((BitmapDrawable) iv.getDrawable()).getBitmap();
if (Color.alpha(bitmap.getPixel(x, y)) == 0)
return true;
else
return false;
}
This one sample makes ImageView's transparent area not clickable.
ImageView:
ImageView imgView= (ImageView) findViewById(R.id.color_blue);
imgView.setDrawingCacheEnabled(true);
imgView.setOnTouchListener(changeColorListener);
OnTouchListener:
private final OnTouchListener changeColorListener = new OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
Bitmap bmp = Bitmap.createBitmap(v.getDrawingCache());
int color = bmp.getPixel((int) event.getX(), (int) event.getY());
if (color == Color.TRANSPARENT)
return false;
else {
//code to execute
return true;
}
}
};
If your foreground image is not just a rect but a complex image and you really need that the touch is pixel-precise, you may use
http://developer.android.com/reference/android/view/View.OnTouchListener.html
foregroundImage.setOnTouchListener(new View.OnTouchListener(){...});
The MotionEvent in the callback will contain what kind of action happened (e.g. Touch up) and the exact location.
If you know the exact size of the foreground image as it is displayed, you can figure out which pixel of it was clicked, then check if that pixel's alpha is 0. Or you may need to apply some scaling if the image was scaled. This may get quite tricky since depending on the screen size and proportions the image may have been scaled/positioned differently. This also depends on the layouts your were using.
For the check of the pixel value you'd probably need to keep in memory the Bitmap object containing your foreground's image data as well.
Frankly, I doubt you'd really need all that precision unless your foreground image is really of a very irregular shape.
Bitmap.createBitmap(v.getDrawingCache() and imageView.setDrawingCacheEnabled(true) are depreciated so you can do this by the following snippet code:
imageView.setOnTouchListener { v, event ->
val bmp = convertViewToDrawable(v)
val color: Int = bmp.getPixel(event.x.toInt(), event.y.toInt())
if (color == Color.TRANSPARENT)
return#setOnTouchListener false
else {
Toast.makeText(baseContext, "image clicked", Toast.LENGTH_SHORT).show()
return#setOnTouchListener true
}
}
private fun convertViewToDrawable(view: View): Bitmap {
val b = Bitmap.createBitmap(view.measuredWidth, view.measuredHeight,
Bitmap.Config.ARGB_8888)
val c = Canvas(b)
c.translate((-view.scrollX).toFloat(), (-view.scrollY).toFloat())
view.draw(c)
return b
}

Android: Draggable Toast, Or Alternative?

I'm drawing a number of bitmaps on a canvas, and using MotionEvents to let me drag them around.
When each item is pressed, I'd like to display a Toast, or, Toast-like mini information-panel that tracks the movement of the bitmap being dragged during an ACTION_MOVE. The "Toast" would appear on ACTION_DOWN and vanish on ACTION_UP.
The problem with using Toast is that I have to give it a duration, and, also, I can't change its position once it has been displayed. Unless I can kill the Toast for each ACTION_MOVE, and display a new one right away at the current coordinates? (Sorry, thinking aloud at this point, can't get to my dev machine to test...)
I don't know what other options there might be to achieve this, and I'd very much appreciate suggestions from the community.
Hope this helps, just whipped it up, might even compile!
private boolean mDragging = false;
private float mTouchX = 0, mTouchY = 0;
private Paint mTextPaint = new Paint();//need to set this up in onCreate!
public boolean onTouchEvent(MotionEvent event)
{
mTouchX = event.getX();
mTouchY = event.getY();
if(event.getAction() == ACTION_DOWN)
{
mDragging = true;
}
else if(event.getAction() == ACTION_UP)
{
mDragging = false;
}
return true;
}
protected void onDraw (Canvas canvas)
{
/* Put all your bitmap drawing here. */
/* Draw some info text on top of everything else. */
if(mDragging)
{
String text = mTouchX + ", " + mTouchY;
canvas.drawText(mTouchX, mTouchY + 50, text, mTextPaint);
}
}
A Toast isn't suitable in this case for reasons you already mentioned. It will be better to define a region on the Canvas and draw the message string there using drawText. Put this in the onDraw method and call invalidate whenever you need to update the text or the position of the message board.

android: how to add button or text box on canvas

I am making a canvas and setting its background which is an image
I am adding text on it by canvas. Drawtext method which works perfectly alright
now I want these text to be clickable but i couldn't find any method
The other method I could think of was to add text box on canvas add write on click event of these text box but could not find any example related to this can anybody suggest what to do.
Canvas is a space where you can just draw some graphics, thus the only way to do what you want is detecting when the user click the surface the canvas is drawn on (e.g. a SurfaceView), and using the coordenates you just fire an event. Of course, you need to verify whether the click was done on the specific part you want (e.g. the area where you drew a button or something).
Use the onTouchEvent method. Here is an example I used for finding out if the user's click coordinates are in a List of rectangles (aka buttons):
#Override
public boolean onTouchEvent( MotionEvent event) {
super.onTouchEvent(event);
int x = (int)event.getX();
int y = (int)event.getY();
xStored = x; yStored=y;
if (event.getAction()==MotionEvent.ACTION_UP){
}else if(event.getAction()==MotionEvent.ACTION_DOWN){
System.out.println("Touching down!");
for(Rect rect : rectangles){
if(rect.contains(x,y)){
System.out.println("Touched Rectangle, start activity."+x+","+y);
invalidate();
}else{
}
}
}else if(event.getAction()==MotionEvent.ACTION_MOVE){
}
this.postInvalidate();
return true;
}

Categories

Resources