Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Android - Fade out bitmap image on canvas

I am drawing a scaled bitmap onto a canvas and would like to fade my image out at a specified time.

Basically, when my character image goes over a certain section of the canvas, I require the character image to slowly fade away (3 seconds), before the page automatically re-directs to the next java class.

Currently, my image simply redirects to the new java class, please see below some code to how I am creating my image.

Resources res = getResources();
float px = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 20, res.getDisplayMetrics());
imgSpacing = (int) px / 2;
int size = (int) ((PhoneWidth / 5) - px);

chrImg = BitmapFactory.decodeResource(getResources(), R.drawable.character);
chrImg = Bitmap.createScaledBitmap(chrImg, size, size, true);

Then within the canvas onDraw:

if(indexX == mazeFinishX && indexY == mazeFinishY)
{
    canvas.drawBitmap(finish, j * totalCellWidth, i * totalCellHeight, null);
    // As soon as the character moves over this square they are automatically re-directed to new page
    // This is where I want to fade the character image out before the re-direct
}

I have looked online, however cannot quite work out how to get the fading to work for my drawable image fetched from my games resources drawable folder. Thanks

like image 782
Raj Avatar asked Aug 14 '13 10:08

Raj


3 Answers

If you think there's a possibility you will want to change the fade animation down the road, such as scaling and/or rotating, then you should go with an animation XML.

But for just a quick bitmap fade, you can repeatedly post delayed invalidation messages. You probably want to limit your invalidated area to just where your character bitmap is:

private static final int FADE_MILLISECONDS = 3000; // 3 second fade effect
private static final int FADE_STEP = 120;          // 120ms refresh

// Calculate our alpha step from our fade parameters
private static final int ALPHA_STEP = 255 / (FADE_MILLISECONDS / FADE_STEP);

// Initializes the alpha to 255
private Paint alphaPaint = new Paint();

// Need to keep track of the current alpha value
private int currentAlpha = 255;

@Override
protected void onDraw(Canvas canvas) {
    ...
    if(indexX == mazeFinishX && indexY == mazeFinishY) {

        // Drawing your wormhole?
        int x = j * totalCellWidth;
        int y = i * totalCellHeight;
        canvas.drawBitmap(finish, x, y, null);

        if (currentAlpha > 0) {

           // Draw your character at the current alpha value
           canvas.drawBitmap(chrImg, x, y, alphaPaint);

           // Update your alpha by a step
           alphaPaint.setAlpha(currentAlpha);
           currentAlpha -= ALPHA_STEP;

           // Assuming you hold on to the size from your createScaledBitmap call
           postInvalidateDelayed(FADE_STEP, x, y, x + size, y + size);

        } else {
           // No character draw, just reset your alpha paint
           currentAlpha = 255;
           alphaPaint.setAlpha(currentAlpha);

           // Now do your redirect
        }
    }
    ...
}

I'd recommend putting the constants FADE_MILLISECONDS and FADE_STEP into res/integers.xml just so they aren't hard-coded.

like image 117
Dane White Avatar answered Sep 29 '22 17:09

Dane White


The best way for me to explain this is provide a full custom view example that you can pull the pieces from that you need. Below is a view that accomplishes this.

public class CharacterView extends View {

    private Paint mCharacterPaint;
    private Bitmap mCharacterBitmap;
    private Transformation mTransformation;
    private AlphaAnimation mFadeOut;

    public CharacterView(Context context) {
        super(context);
        init(context);
    }

    public CharacterView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init(context);
    }

    public CharacterView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        init(context);
    }

    private void init(Context context) {
        //This would be your character image instead
        mCharacterBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.ic_launcher);

        //We need a paint to efficiently modify the alpha
        mCharacterPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        //This is needed to house the current value during the animation
        mTransformation = new Transformation();
        //Construct an animation that will do all the timing math for you
        mFadeOut = new AlphaAnimation(1f, 0f);
        mFadeOut.setDuration(500);
        //Use a listener to trigger the end action
        mFadeOut.setAnimationListener(new Animation.AnimationListener() {
            @Override
            public void onAnimationStart(Animation animation) { }

            @Override
            public void onAnimationEnd(Animation animation) {
                //Trigger your action to change screens here.
            }

            @Override
            public void onAnimationRepeat(Animation animation) { }
        });
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        if (event.getAction() == MotionEvent.ACTION_DOWN) {
            //In this example, touching the view triggers the animation
            // your code would run this sequence when the character reaches the
            // appropriate coordinates (P.S. I would not advocate putting this code
            // inside of onDraw()
            mFadeOut.start();
            mFadeOut.getTransformation(System.currentTimeMillis(), mTransformation);
            invalidate();
        }
        return true;
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        //...existing drawing code...

        canvas.drawBitmap(mCharacterBitmap, 0, 0, mCharacterPaint);
        if (mFadeOut.hasStarted() && !mFadeOut.hasEnded()) {
            mFadeOut.getTransformation(System.currentTimeMillis(), mTransformation);
            //Keep drawing until we are done
            mCharacterPaint.setAlpha((int)(255 * mTransformation.getAlpha()));
            invalidate();
        } else {
            //Reset the alpha if animation is canceled
            mCharacterPaint.setAlpha(255);
        }
    }
}

The most efficient way to dynamically modify the transparency of what you draw is to apply it using a Paint drawing to your Canvas. You will need to continually invalidate() the view with each frame transition to get the animation to display until the character is gone. The example uses an Animation object and a Transformation object to handle all of the timing math to make the animation actually look good.

The gist here is that you start the animation via some external trigger (I wouldn't recommend doing the check for when to fade out in onDraw(), probably better in the place where those character locations are updated. In my example, for simplicity sake, I started this when the view to touched.

This trigger starts the animation and gets the initial transformation value, then invalidate() triggers a new onDraw(). While the animation is running, onDraw() is repeated due to the invalidate() and each iteration the alpha on the paint decreases slightly.

When the animation is finished, it will call the AnimationListener for you, so you can trigger the screen transition inside of onAnimationEnd().

like image 45
devunwired Avatar answered Sep 29 '22 19:09

devunwired


        if(indexX == mazeFinishX && indexY == mazeFinishY)
        {
            canvas.drawBitmap(finish, j * totalCellWidth, i * totalCellHeight, null);
            // As soon as the character moves over this square they are automtically re-directs to new page
            new CountDownTimer(0500, 1000) {
                @Override
                public void onTick(long millisUntilFinished) {}
                @Override
                public void onFinish() {
Paint paint = new Paint();
paint.setAlpha(25);
canvas.drawBitmap(chrImg, 0, 0, paint);
                }
            }.start();
            new CountDownTimer(0500, 1000) {
                @Override
                public void onTick(long millisUntilFinished) {}
                @Override
                public void onFinish() {
Paint paint = new Paint();
paint.setAlpha(45);
canvas.drawBitmap(chrImg, 0, 0, paint);             }
            }.start();
            new CountDownTimer(0500, 1000) {
                @Override
                public void onTick(long millisUntilFinished) {}
                @Override
                public void onFinish() {
Paint paint = new Paint();
paint.setAlpha(70);
canvas.drawBitmap(chrImg, 0, 0, paint);             }
            }.start();
            new CountDownTimer(0500, 1000) {
                @Override
                public void onTick(long millisUntilFinished) {}
                @Override
                public void onFinish() {
Paint paint = new Paint();
paint.setAlpha(100);
canvas.drawBitmap(chrImg, 0, 0, paint);             }
            }.start();
            // This is where I want to fade the character image out before the re-direct
            new CountDownTimer(3000, 1000) {

            @Override
            public void onTick(long millisUntilFinished) {
                // TODO Auto-generated method stub

            }

            @Override
            public void onFinish() {
                // TODO Auto-generated method stub

                    Intent i = new Intent(currentclass.this, nextClass.class);
                    startActivity(i);
            }
        }.start();

    }

this will make your bitmap becomes transparent ( the alpha will be 100 ) .. and then redirect to the next class after 3 seconds.

Update: for the bitmap you can't use .setAlpha so we must use a paint, so we created a new paint each time then set the bitmap with the paint. but if you have an imageView you can use .setalpha

like image 29
Ahmed Ekri Avatar answered Sep 29 '22 19:09

Ahmed Ekri