8

I need to replace phrases of text in a with images and then append that to a TextView. For regular Drawables this is no problem, but when the Drawable is an AnimationDrawable I don't know where and when to call .start();.

This is how I append text to the TextView:

textview.append(Html.fromHtml(textWithHtmlImgTags, imagegetter, null));

The image tags in the textWithHtmlImgTags are replaced using imagegetter:

new ImageGetter()
{
    @Override
    public Drawable getDrawable(String source) {

        if(source.endsWith("_ani"))
        {
            Log.i("cmv", "This is an animated drawable.");

            AnimationDrawable dra = (AnimationDrawable)res.getDrawable(sRes.get(source));
            dra.setBounds(0, 0, dra.getIntrinsicWidth(), dra.getIntrinsicHeight());
            dra.start(); // This doesn't work..

            return dra;
        }

        Drawable dr = res.getDrawable(sRes.get(source));
        dr.setBounds(0, 0, dr.getIntrinsicWidth(), dr.getIntrinsicHeight());
        return dr;
    }

};

My AnimationDrawables are added, but they aren't animated (they're stuck on frame 1).

In the documentation it says:

It's important to note that the start() method called on the AnimationDrawable cannot be called during the onCreate() method of your Activity, because the AnimationDrawable is not yet fully attached to the window. If you want to play the animation immediately, without requiring interaction, then you might want to call it from the onWindowFocusChanged() method in your Activity, which will get called when Android brings your window into focus.

Since the images are added dynamically I don't think it has anything to do with onCreate(). So I guess I call my .start() when my drawable is not yet fully attached to the window, but where/when/how should I call it?

Thanks in advance!

T.S.
  • 1,242
  • 13
  • 22

3 Answers3

6

I came out with the solution. In your custom TextView:

(1) First, you have to decide the timing of the animation start and stop.

@Override
protected void onAttachedToWindow() {
    super.onAttachedToWindow();

    handleAnimationDrawable(true);
}

@Override
protected void onDetachedFromWindow() {
    super.onDetachedFromWindow();

    handleAnimationDrawable(false);
}

private void handleAnimationDrawable(boolean isPlay) {
    CharSequence text = getText();
    if (text instanceof Spanned) {
        Spanned span = (Spanned) text;
        ImageSpan[] spans = span.getSpans(0, span.length() - 1,
                ImageSpan.class);
        for (ImageSpan s : spans) {
            Drawable d = s.getDrawable();
            if (d instanceof AnimationDrawable) {
                AnimationDrawable animationDrawable = (AnimationDrawable) d;
                if (isPlay) {
                    animationDrawable.setCallback(this);
                    animationDrawable.start();
                } else {
                    animationDrawable.stop();
                    animationDrawable.setCallback(null);
                }
            }
        }
    }
}

(2) And then implement your own Drawable.Callback to trigger the redraw.

@Override
public void invalidateDrawable(Drawable dr) {
    invalidate();
}

@Override
public void scheduleDrawable(Drawable who, Runnable what, long when) {
    if (who != null && what != null) {
        mHandler.postAtTime(what, when);
    }
}

@Override
public void unscheduleDrawable(Drawable who, Runnable what) {
    if (who != null && what != null) {
        mHandler.removeCallbacks(what);
    }
}
shiami
  • 7,174
  • 16
  • 53
  • 68
1

I guess the important question is: when do you make the call ?

textview.append(Html.fromHtml(textWithHtmlImgTags...

If you are calling this after onCreate then you can call the animation.start() . If it is called in onCreate() method, I would:

  • keep the list of the drawables generated in ImageGetter;
  • call start() on all of the drawables in onAttachedToWindow or onStart (not sure if they work in the onStart function)
Siyamed
  • 495
  • 2
  • 11
  • I make the call multiple times, the first call is in onCreate. After that it is called a several times more (each time when the application gets a response from a server). I tried keeping a list of all AnimationDrawables and start them onAttachedToWindow, but still they remain stuck on the first frame. – T.S. Apr 29 '12 at 19:37
1

View already implements Drawable.Callback so you only need the following TextView overrides to display AnimationDrawable objects in an ImageSpan:

//  comparing against drawables found in the spans is probably safer
@Override
protected boolean verifyDrawable(Drawable who) {
    return (super.verifyDrawable(who) || who instanceof AnimationDrawable);
}

//  again comparing against drawables found in the spans is probably safer
@Override
public void invalidateDrawable(Drawable drawable) {
    if (drawable instanceof AnimationDrawable) {
        onLayout(true, getLeft(), getTop(), getRight(), getBottom());
        invalidate();
        return;
    }
    super.invalidateDrawable(drawable);
}

Overriding verifyDrawable is necessary so that the super class implementations of Drawable.Callback will accept unexpected Drawable objects and handle scheduling.

Calling onLayout forces the TextView to invalidate its cached display list otherwise the updated frames aren't drawn.

I also found that for some reason if I created the ImageSpan referring to the animation resource id the resource wasn't cached and seemed to be reloaded every time ImageSpan.getDrawable() was called. By loading the resource, setting its bounds, and passing it to the ImageSpan constructor I avoided that problem.

use onAttachedToWindow/onDetachedFromWindow as otherwise suggested.

Greg
  • 61
  • 4