How to animate a Drawable in Android? - java

I have a Drawable object in my application which is moving on the screen:
public class MyDrawable extends Drawable {
#Override
public void draw(Canvas canvas) {
// ...
}
// ...
}
It worked fine so far but now I have to animate this Drawable. I checked out the documentation and it looks like that the best option for me is using AnimationDrawable.
I did something like this:
public void addAnimation() {
animation = new AnimationDrawable();
animation.addFrame(resources.getDrawable(R.drawable.anim_0), 100);
animation.addFrame(resources.getDrawable(R.drawable.anim_1), 100);
animation.addFrame(resources.getDrawable(R.drawable.anim_2), 100);
animation.addFrame(resources.getDrawable(R.drawable.anim_3), 100);
animation.setOneShot(false);
refreshAnimationBounds();
animation.start();
}
#Override
public void draw(Canvas canvas) {
refreshAnimationBounds();
animation.draw(canvas);
}
What happens is that my original Drawable is moving as intended but I only see the first frame. What could be the problem?
refreshAnimationBounds(); is just refreshing the bounds of my Drawable object (repositions it).
Edit:
The common:
public void onWindowFocusChanged(boolean hasFocus) {
if (hasFocus) {
animation.start();
} else {
animation.stop();
}
}
solution is not working.

In general Animation package is quite buggy. I recall I had same problem with animation not starting and the solution I came with was to start it not in onCreate() as I formerly did but onWindowFocusChanged() once all view stuff was set. I wasn't extending AnimationDrawable myself but it may be the same origin in your case.

Related

Visual bug with native plugin animation in Ionic 4 Capacitor

I'm currently working in a custom android native plugin for Capacitor. The plugin consists in an app's footer which is working fine except for a hiding animation.
The problem is I'm changing the WebView margin everytime the footer is being shown/hide, which makes a black(sometimes is orange, probably because is one of app's main colors) bar appears in the space occupied by the footer. Black bar disappears once the animation ends.
I've tried changing the WebView margin at animation start/end and the result is the same.
Thanks in advance guys, here is some code.
Animation XML:
<translate
android:duration="150"
android:fromYDelta="0"
android:toYDelta="100%" />
WebView margin function:
private void changeWebViewMargin(float pixels) {
CoordinatorLayout.LayoutParams params = (CoordinatorLayout.LayoutParams) wb.getLayoutParams();
params.setMargins(0, 0, 0, (int) dpTopixel(getContext(), pixels));
wb.setLayoutParams(params);
wb.requestLayout();
}
Hide function:
#PluginMethod()
public void hide(PluginCall call) {
Boolean animated = call.getBoolean("animated");
if (animated == null) {
animated = false;
}
final boolean finalAnimated = animated;
this.getBridge().getActivity().runOnUiThread(new Runnable() {
#Override
public void run() {
if (finalAnimated && footer.getVisibility() == View.VISIBLE) {
changeWebViewMargin(0f);
Animation myAnim = AnimationUtils.loadAnimation(getBridge().getContext(), R.anim.hide_footer);
myAnim.setAnimationListener(new Animation.AnimationListener() {
#Override
public void onAnimationStart(Animation animation) {
}
#Override
public void onAnimationEnd(Animation animation) {
footer_img.setVisibility(View.INVISIBLE);
footer.setVisibility(View.INVISIBLE);
btn3.setVisibility(View.INVISIBLE);
}
#Override
public void onAnimationRepeat(Animation animation) {
}
});
footer.startAnimation(myAnim);
footer_img.startAnimation(myAnim);
btn3.startAnimation(myAnim);
} else {
footer_img.setVisibility(View.INVISIBLE);
footer.setVisibility(View.INVISIBLE);
btn3.setVisibility(View.INVISIBLE);
changeWebViewMargin(0f);
}
}
});
}
Instead of working with margins try using WindowInsets:
ViewCompat.setOnApplyWindowInsetsListener(wb, (v, insets) -> {
((ViewGroup.MarginLayoutParams) v.getLayoutParams()).bottomMargin =
insets.getSystemWindowInsetTop() + (int) dpTopixel(getContext(), pixels);
return insets.consumeSystemWindowInsets();
});

How to set an image visibility after animation is complete

I'm trying to set an image visibility after an animation ends, but i'm have difficulty.
I have created few images and set the tag for each. Whenever the user clicks the image the visibility gets set to invisible. I have also created a method that animates the images. I would like when the animation finishes for the visibility of the images to reset to Visible. Apologies,if this sounds a bit unclear i'm not entirely sure how to word this question. Any help would be appreciated.
public void popBubbles(View view) {
final String tag = String.valueOf(view.getTag());
if(tag=="0"){
bubble.setVisibility(View.INVISIBLE);
}else if(tag=="1"){
bubble1.setVisibility(View.INVISIBLE);
}else if(tag=="2"){
bubble2.setVisibility(View.INVISIBLE);
}else if(tag=="3"){
bubble3.setVisibility(View.INVISIBLE);
}else if(tag=="4"){
bubble4.setVisibility(View.INVISIBLE);
}else if(tag=="5"){
bubble5.setVisibility(View.INVISIBLE);
}else if(tag=="6"){
bubble6.setVisibility(View.INVISIBLE);
}else if(tag=="7"){
bubble7.setVisibility(View.INVISIBLE);
}else if(tag=="8"){
bubble8.setVisibility(View.INVISIBLE);
}
}
public void animateBubbles() {
for (final ImageView img : IMGS) {
animation = ObjectAnimator.ofFloat(img, "translationY", 0f, -deviceHeight);
animation.setDuration(4000);
animation.start();
animation.setRepeatCount(ValueAnimator.INFINITE);
}
}
This code is in kotlin but you can add a listener to the animation use the on AnimationEnd() method like this.
animation.addListener(object : Animator.AnimatorListener {
override fun onAnimationEnd(animation: Animator?) {
}
})
Hope this helps.
set Animation listener and override methods. You can write your logic in onAnimationEnd method
animation.setAnimationListener(new Animation.AnimationListener() {
#Override
public void onAnimationStart(Animation animation) {
}
#Override
public void onAnimationEnd(Animation animation) {
//Do operation on ImageView
}
#Override
public void onAnimationRepeat(Animation animation) {
}
});

Restore canvas from previous call of onDraw for ImageView

Wow, this is frustrating. I'm really just trying to clear my ImageView before each time I draw on it.
I'm trying to extend Android's ImageView class into a custom StatusBarImageView. I provide a public method setPercentComplete(float percent) so that my activity can update the image accordingly. But every time I try to update the image by invalidating it and triggering it's onDraw(Canvas canvas) method, for some reason it tries to draw the new status bar and it's text on top of the existing image rather than starting fresh. So the captions I draw using canvas.drawText(...) bleed over each other.
Can anyone please tell me why this is happening / how to fix it?
I've tried several different approaches and none of them have worked.
I tried saving before the first time I draw and restoring before each subsequent time, but apparently it's a new canvas each time the onDraw() method fires, so restore() throws an exception that there hasn't been a save().
I've tried using canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR), but not only does that not work, it changes the background to be solid black for some reason.
Here's my code:
class StatusBarImageView extends ImageView {
private float status = 100.0f;
private Canvas lastCanvas;
private Paint imagePainter = new Paint();
private Paint textPainter = new Paint();
public StatusBarImageView(Context context) {
super(context);
initializePainters();
}
public StatusBarImageView(Context context, AttributeSet attributes) {
super(context, attributes);
initializePainters();
}
public StatusBarImageView(Context context, AttributeSet attributes, int defStyle) {
super(context, attributes, defStyle);
initializePainters();
}
private void initializePainters() {
this.status = 100.0f;
this.imagePainter.setStyle(Style.FILL_AND_STROKE);
this.textPainter.setStyle(Style.FILL);
this.textPainter.setFakeBoldText(true);
}
public void setPercentComplete(float status) {
this.status = status;
this.invalidate();
}
#Override
public void onDraw(Canvas canvas) {
super.onDraw(canvas);
//Erase previous status bar and its text captions before drawing new one
if(this.lastCanvas != null) {
this.lastCanvas.restore(); //throws exception
//this.lastCanvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR); // Doesn't work and makes background black
}
canvas.save();
this.lastCanvas = canvas;
//this.canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR); // Doesn't work and makes the background black
//this.canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.MULTIPLY); // Same problem
this.drawStatusBarAndCaptions(canvas); //Uses canvas.drawRect and canvas.drawText
}
//...
}
And the xml just looks like:
<my.package.name.then.StatusBarImageView
android:id="#+id/status_bar"
android:layout_width="match_parent"
android:layout_height="0dp"
android:padding="0dp"
android:layout_margin="0dp"
android:layout_weight="3"/>
But every time I try to update the image by invalidating it and triggering it's onDraw(Canvas canvas) method.
Well, that's the way it should work. Any invalidate() would eventually cause onDraw() to be called.
In order to achieve the requirement you have, you may keep a boolean value, which will indicate you whether current onDraw() is initiated after setPercentComplete() call:
private boolean flag = false;
public void setPercentComplete(float status) {
this.status = status;
this.flag = true;
this.invalidate();
}
#Override
public void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (flag) {
flag = false;
// perform actions for showing percent status
} else {
// draw other stuff
}
}

Using onSizeChanged in Drawable class

I have created a custom class which extends Drawable. I'm trying to get the dimensions of the Drawable by using onSizeChanged() as follow
public class Circle extends Drawable {
...
#Override
protected void onSizeChanged(int xNew, int yNew, int xOld, int yOld) {
super.onSizeChanged(xNew, yNew, xOld, yOld);
}
}
But I'm getting an error saying that "Method does not override from the superclass". What do I do to fix it?
Thanks a lot to Michael Spitsin for this answer. To get the dimensions of the layout which the drawable is attached to, use the following code
#Override
protected void onBoundsChange(Rect bounds) {
mHeight = bounds.height();
mWidth = bounds.width();
}
If we go to View source code and in Drawable source code, we will see next things:
In View.setBackgroundDrawable(Drawable) if we pass not null, than field mBackground (is is responsible to store background drawable) is updated.
If we will try to find usages of method Drawable.onBoundsChanged() then we will see that it is mainly used in Drawable.setBounds method. And if we will find usages of it, we will see next snippet in View.class:
private void drawBackground(Canvas canvas) {
final Drawable background = mBackground;
if (background == null) {
return;
}
if (mBackgroundSizeChanged) {
background.setBounds(0, 0, mRight - mLeft, mBottom - mTop);
mBackgroundSizeChanged = false;
rebuildOutline();
}
//draw background through background.draw(canvas)
}
Thus, using of onBoundsChanged callback is accepted for your task.

Rotate video SurfaceView inside a ViewPage

I'm using a SurfaceView as display to a MediaPlayer, but the video is sideways and I need to rotate it. I tried using SurfaceView.setRotation(90) but this gave me a black View.
If I set the rotation to any degree lower than 90, the video translates to the right (being pushed behind the content of the other page in the ViewPage) by a relative amount.
Here is the code I'm using to create the view and play the video:
final SurfaceView surface = new SurfaceView(getActivity());
surface.setRotation(90);
surface.getHolder().addCallback(new SurfaceHolder.Callback(){
#Override
public void surfaceChanged(SurfaceHolder holder, int format, int width,int height) {}
#Override
public void surfaceCreated(SurfaceHolder holder) {
holder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
mPlayer = new MediaPlayer();
mPlayer.setDisplay(surface);
try {
mPlayer.setDataSource(url);
mPlayer.setLooping(true);
mPlayer.setVideoScalingMode(MediaPlayer.VIDEO_SCALING_MODE_SCALE_TO_FIT_WITH_CROPPING);
} catch (IOException e) {
e.printStackTrace();
}
mPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
#Override
public void onPrepared(MediaPlayer mp) {
mp.start();
}
});
mPlayer.prepareAsync();
mPlayer.setScreenOnWhilePlaying(true);
}
#Override
public void surfaceDestroyed(SurfaceHolder holder) {
mPlayer.stop();
mPlayer.release();
}
});
As said, I'm adding the surface to a ViewPage. The video plays, and, aside from rotation, everything else works.
I tried mPlayer.setSurface(holder.getSurface()) with the same results.
I tried using TextureView with limited success. It rotated, but I couldn't scale it and keep aspect ratio with cropping.
Tried locking the canvas and rotating the canvas of the holder, but all I've got was a black View, no matter what degree.

Categories