Speed up bitmap animation in custom View - android

In my custom view i have 1 animation that i need to run at demand (on tile click). Currently i am using this method:
public boolean onTouchEvent(MotionEvent event) {
switch(event.getAction()){
case MotionEvent.ACTION_DOWN:
//check what tile for clicked
getHandler().removeCallbacks(explosionThread);
getHandler().post(explosionThread);
}
}
break;
}
return super.onTouchEvent(event);
}
So i am calling (or sending to view thread, to be specific) a Runnable that calls it self until it comes to an end of an image...
private Runnable explosionThread=new Runnable(){
#Override
public void run() {
invalidate();
if(expCount<15){
getHandler().postDelayed(this, 10);
}
}
};
In my onDraw() method i implemented logic to go threw bitmap and draw it on screen ( using cnavas.drawBitmap(bitmap,srcRect,destRect,paint)....
Now, I want to avoid using SurfaceView (i have only 1 animation and View uses less resources).
I think the animation is slow because onDraw needs to draw whole screen each time invalidate() is called witch can be slow ( drawing 64 tiles with png images). I know that there is an overload of invalidate method, invalidate(Rect dirty) but i don't really know how to use it. If u think that that is the answer please write how to avoid drawing whole onDraw method ( or what method can I overwrite that is used by invalidate(Rect) method, if there is any).
If you have any other better way to speed up animation post it plz.
Thanks in advance....

That's right. One of the way to speed up rendering through canvas is to use invalidate(Rect). Rect passed to invalidate method defines area which will be redrawn. Your onDraw will be called after invalidate with clipping region being set up on canvas. So all your "drawBitmap" will be clipped by the rect.

for running the animation are using a .gif file or you are using a sequence of images run on a thread to show as an animation ?

Related

Why validate on View does not work but postInvalidate works

I have a card a game in which the person chooses the card and drags (done through translate animation) and then when let go it moves to the center of the screen where have an imageView place holder. At the animationEnd I set the image view to that card and can invalidate on each view on the screen. What I dont understand is that when I call invalidate then the card looks like it left some trails (shadows from the translate animation). When I call postInvalidate, it gets drawn properly. Why is that??
AnimationEnd code:
#Override
public void onAnimationEnd(Animation arg0) {
if(card == null){
MyApplication.SWERR(null);
busy = false;
}
card.getCardImage().setVisibility(View.INVISIBLE);
Drawable drawable = card.getCardSuitNumber().getDrawable(mContext);
finalDestIV.setBackgroundDrawable(drawable);
finalDestIV.setVisibility(View.VISIBLE);
invalidateAll();
PlayerStatus p = mGameMonitor.getPlayerStatusFromPlayerEnum(GameConstants.PLAYERS.P1);
p.handleCardChosen(card);
cardReleased = false;
p.setPlayerLock(false);
card = null;
}
InvalidateAll method
private void invalidateAll(){
rootLayout.postInvalidate();
for (int i=0; i<rootLayout.getChildCount();i++){
View v = rootLayout.getChildAt(i);
v.postInvalidate();
}
}
Typical problem of novice Android developers.
Please look into the description of the invalidate method.
According to the Google documentation:
Invalidate the whole view. If the view is visible, onDraw(android.graphics.Canvas) will be called at some point in the future. This must be called from a UI thread. To call from a non-UI thread, call postInvalidate().
So, I suppose onAnimationEnd is calling from non-UI thread (probably animation thread).
I think if you avoid the API policy it may lead to unexpected behavior and may differs between different Android versions.
Calling invalidate from non-UI thread can interrupt some UI operations (or brake some logic) in Android system which makes some misbehavior.
Look also here: What does postInvalidate() do?

using canvas.drawBitmap() with canvas.translate()

My code here receives a value for bmpX and bmpY and uses them to draw a bitmap at that location on the screen. This works exactly as I want it to, but I would rather use the canvas.translate() function to handle the movement of the image, rather than just canvas.drawBitmap(), because I will be applying other draw elements to the canvas and I want them all to move the same amount and direction.
My question is: where can I move the canvas.drawBitmap() in the code so that it just draws onto the canvas at the start, but it is moved any time after that using canvas.translate() instead? Anywhere I place the code still freezes the image at that point where I draw it, regardless of how much I change the position of canvas.translate(). I'm imaging its possible to "stick" the image to the canvas, so to speak, and then move the canvas, which also moves the image with it.
public void run() {
// TODO Auto-generated method stub
while(isRunning) {
if(!surfaceHolder.getSurface().isValid()) {
continue;
}
canvas = surfaceHolder.lockCanvas();
canvas.drawRGB(255, 255, 255);
canvas.drawBitmap(bmp, bmpX-(bmp.getWidth()/2),
bmpY-(bmp.getHeight()/2), null);
//canvas.translate(bmpX, bmpY);
surfaceHolder.unlockCanvasAndPost(canvas);
}
}
I hope I explained my problem clearly enough. Thanks!
Answering my own question here for clarity, afetr info I got offline. UI cannot be done on a thread like this, I needed to be using an android UI thread instead. runOnUIUpdatethread and async tasks

Canvas draw : not able to render animation with runtime generated Bitmap images

Need a little help here : I'm out of ideas now...
Here's what I need to do :
Render "base" image [ it is created from an ARGB.8888 byte array ]
user clicks 2 points on the screen; I need to perform the the pixel manipulation on a region of pixels around the path taken from one point to another... [ I need to calculate a squre block of pixel for each pixel on the whole path ].
display the modification of the image as the code progresses in animation form.
I am able to display the whole path; I am able to calculate & manipulate the pixels properly .. But what I am unable to do is show the animation as my code is progressing on the path... with the current implementation I am able to display the whole calculated path at the end ...
public void onDraw(Canvas canvas){
canvas.drawColor(Color.WHITE);
Paint p = new Paint();
canvas.drawBitmap(base,0,0,p);
traverse clickEvent1.x -> clickEvent2.x
traverse for clickEvent1.Y -> clickEvent2.Y
{
newBitMap = calculateNewBitMap(base)
// I nee to redraw Canvas with (newBitMap)
// canvas.drawBitMap(newBitMap);
//Doesn't work
//postInvalidate();
//invalidate()
//AnimationDrawable.addFrame(newBitMap)
// I am not calling start here
//but just wanted to let you know that I do call animation start to display the frames stored in it
// animation.start();
}
// obviously wouldn't work here As it is already out of the loop
//invalidate();
}
Please NOTE :
The newBitMap image is generated at runtime, it would not be available to me beforehand...
I tried invalidate() in the loop as well ; but it would only draw the cumulative result after the whole loop has traversed and not the intermediate states of the newBitMaps.
Performance is of critical importance + I am dealing with HUGE image sizes .. so please keep that in mind as well ... if I create multiple bitmaps for temporary storing the JVM crashes due to "OutofMemory" ..
I tried storing the new Images in "AnimationDrawable" form as well; but tht's not solving the problem as well....
AnimationDrawable animDrawable = new AnimationDrawable();
Drawable frame1 = new BitmapDrawable(newCaclBitMap);
animDrawable.addFrame(frame1, 250);
Thanks for any pointers / suggestions ..
It seems like what you are doing is running all of your drawing logic in a single call to onDraw() when I think what you want is to have onDraw() called once for each frame of your animation.
So, instead of something like this in your trace:
onDraw()
drawFrame()
drawFrame()
drawFrame()
...
You would have this:
onDraw()
drawFrame()
onDraw()
drawFrame()
onDraw()
drawFrame()
...
The CubeLiveWallpaper example has an example of this type of thing.
Animation objects are used to animate View objects. What you want to do is animate a canvas. This is more complicated but potentially more powerful. Essentially you need to derive an equation that will govern the movement of you drawing as a function of time. When the animation starts you get the current time stamp and then in you onDraw method you draw what the canvas should look like at that point in time. Basically you have to draw every step.

view.invalidate() not working to redraw imageview

Ok guys, this may sound dumb, but I have been banging my head against the keyboard for some time now trying to figure out why this will not refresh. the basics: I have a little sample app that i am testing to see if i can rotate an image around a point a X amount of degrees, and show it one degree at a time to make a smooth animation. So I have a great sample i found that works great with a slider bar, basically setting the images rotation to a point on the slider bar, great! but.... when i try and create a for loop with a random number and use my for variable updating the image along the way every degree... it does nothing... and all i get is the updated image at the end... but when i drag my finger on he slider bar the graphic is updated instant as me spinning it... I cant figure out what i am doing wrong here... here is the code with the slider... i don't have my piece that creates the random number and draws it but essentially i did it behind a button click
essentially if you look at this piece i did the same behind a button again but it doesnt do it "real time". i called view.invalidate() and view.postinvalidate() to try to force it but no go...
#Override
public void onProgressChanged(SeekBar seekBar, int progress,
boolean fromUser) {
// TODO Auto-generated method stub
curRotate = (float)progress;
drawMatrix();
}
private void drawMatrix(){
Matrix matrix = new Matrix();
matrix.postScale(curScale, curScale);
matrix.postRotate(curRotate);
Bitmap resizedBitmap = Bitmap.createBitmap(bitmap, 0, 0, bmpWidth, bmpHeight, matrix, true);
myImageView.setImageBitmap(resizedBitmap);
}
I think what you did was something like:
for (int degrees = 0 ; degrees < maxRotation ; i++) {
// perform the rotation by matrix
myImageView.invalidate();
}
This wouldn't work because invalidate() only schedules a redraw in the main thread event queue. This means that the redraw will be performed only when the current code has all been executed (in this case, the for cycle).
For a simple rotation a Tween Animation would be better suited. For more advanced stuff (like game animations) you might need to create a custom view or use SurfaceView.
Sounds like you're blocking the UI thread with your code to rotate the image.
I don't have any code to show you right now (reply back and when I'm home tonight I can post something that should help), but yuo will probably get better results placing your rotate code in an AsyncTask, see the Painless Threading area of the dev site for more info.
I was having the same problem, i used:
runOnUiThread(new Runnable() {
public void run() {
myImageView.setImageBitmap(image);
imageView.invalidate();
}
});

Scrolling a Canvas smoothly in Android

I'm new to Android.
I am drawing bitmaps, lines and shapes onto a Canvas inside the OnDraw(Canvas canvas) method of my view. I am looking for help on how to implement smooth scrolling in response to a drag by the user. I have searched but not found any tutorials to help me with this.
The reference for Canvas seems to say that if a Canvas is constructed from a Bitmap (called bmpBuffer, say) then anything drawn on the Canvas is also drawn on bmpBuffer. Would it be possible to use bmpBuffer to implement a scroll ... perhaps copy it back to the Canvas shifted by a few pixels at a time? But if I use Canvas.drawBitmap to draw bmpBuffer back to Canvas shifted by a few pixels, won't bmpBuffer be corrupted? Perhaps, therefore, I should copy bmpBuffer to bmpBuffer2 then draw bmpBuffer2 back to the Canvas.
A more straightforward approach would be to draw the lines, shapes, etc. straight into a buffer Bitmap then draw that buffer (with a shift) onto the Canvas but so far as I can see the various methods: drawLine(), drawShape() and so on are not available for drawing to a Bitmap ... only to a Canvas.
Could I have 2 Canvases? One of which would be constructed from the buffer bitmap and used simply for plotting the lines, shapes, etc. and then the buffer bitmap would be drawn onto the other Canvas for display in the View?
I should welcome any advice!
Answers to similar questions here (and on other websites) refer to "blitting". I understand the concept but can't find anything about "blit" or "bitblt" in the Android documentation. Are Canvas.drawBitmap and Bitmap.Copy Android's equivalents?
I seem to have found an answer. I have put the bulk of the drawing code (which was previously in onDraw()) in a new doDrawing() method. This method starts by creating a new bitmap larger than the screen (large enough to hold the complete drawing). It then creates a second Canvas on which to do the detailed drawing:
BufferBitmap = Bitmap.createBitmap(1000, 1000, Bitmap.Config.ARGB_8888);
Canvas BufferCanvas = new Canvas(BufferBitmap);
The rest of the doDrawing() method is taken up with detailed drawing to BufferCanvas.
The entire onDraw() method now reads as follows:
#Override protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawBitmap(BufferBitmap, (float) -posX, (float) -posY, null);
}
The position variables, posX and posY, are initialised at 0 in the application's onCreate()method. The application implements OnGestureListener and uses the distanceX and distanceY arguments returned in the OnScroll notification to increment posX and posY.
That seems to be about all that's needed to implement smooth scrolling. Or am I over-looking something!?
I had this problem too,
I did the drawing like this:
Canvas BigCanvas = new Canvas();
Bitmap BigBitmap = new Bitmap(width,height);
int ScrollPosX , ScrollPosY // (calculate these with the onScrollEvent handler)
void onCreate()
{
BigCanvas.SetBitmap(BigBitmap);
}
onDraw(Canvas TargetCanvas)
{
// do drawing stuff
// ie. BigCanvas.Draw.... line/bitmap/anything
//draw to the screen with the scrolloffset
//drawBitmap (Bitmap bitmap, Rect src, Rect dst, Paint paint)
TargetCanvas.DrawBitmap(BigBitmap(new Rect(ScrollPosX,ScrollPosY,ScrollPosX + BigBitmap.getWidth(),ScrollPosY + BigBitmap.getHeight(),new Rect(0,0,ScreenWidth,ScreenHeight),null);
}
for smooth scrolling you'd need to make some sort of method that takes a few points after scrolling (i.e the first scroll point and the 10th) , subtract those and scroll by that number in a for each loop that makes it gradually slower ( ScrollAmount - turns - Friction ).
I Hope this gives some more insight.
Continuation of reply to Viktor ...
In fact, the situation is more complicated. Because the doDrawing process is quite slow (taking 2-3 seconds on my slow old HTC Hero phone) I found it desirable to pop up a Toast message to advise the user that it was happening and to indicate the reason. The obvious way to do this was to create a new method containing just 2 lines:
public void redrawBuffer(String strReason) {
Toaster.Toast(strReason, "Short");`
doDrawing();
}
and to call this method from other places in my program instead of doDrawing().
However, I found that the Toaster either never appeared or flashed up so briefly that it could not be read. My workaround has been to use a time check Handler to force the program to sleep for 200 milliseconds between displaying the Toast and calling doDrawing(). Although this slightly delays the start of a redraw I feel this is a price worth paying in terms of the program's usability because the user knows what is going on.
reDrawBuffer() now reads:
public void redrawBuffer(String strReason) {
Toaster.Toast(strReason, "Short");
mTimeCheckHandler.sleep(200);
}`
and the Handler code (which is nested within my View class) is:
private timeCheckHandler mTimeCheckHandler = new timeCheckHandler();
class timeCheckHandler extends Handler {
#Override
public void handleMessage(Message msg) {
doDrawing();
}
public void sleep(long delayMillis) {
this.removeMessages(0);
sendMessageDelayed(obtainMessage(0), delayMillis);
}
}`
No need for the activity to be restarted! (Per prepbgg's Jan 27 10 reply to his Jan 17 10 'answer') Rather than recycling the bitmap and incurring the overhead of having the activity reloaded, you can avoid having the application loaded by putting the 'android:configChanges' attribute shown below, in the 'activity' element of the AndroidManifest.xml file for the app. This tells the system the the app will handle orientation changes and that it doesn't need to restart the app.
<activity android:name=".ANote"
android:label="#string/app_name"
android:configChanges="orientation|screenLayout">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
This method can be used to get a notification when the orienation is changed:
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
prt("onConfigurationChanged: "+newConfig);
if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT) {
prt(" PORTRAIT");
} else {
prt(" LANDSCAPE");
}
} // end of onConfigurationChanged
prepbgg: I don't think the code will work because canvas.drawBitmap does not draw into the bitmap but draws the bitmap on-to the canvas.
Correct me if I am wrong!

Categories

Resources