How to add animated emoticon to TextView or EditText on Android

as a question, I use ImageSpan to add an image to a TextView. but he cannot revive. Do you have any tips?
I am trying to extend AnimationDrawable to add drawable to ImageSpan. but it does not work

public class EmoticonDrawalbe extends AnimationDrawable { private Bitmap bitmap; private GifDecode decode; private int gifCount; public EmoticonDrawalbe(Context context, String source) { decode = new GifDecode(); decode.read(context, source); gifCount = decode.getFrameCount(); if (gifCount <= 0) { return; } for (int i = 0; i < gifCount; i++) { bitmap = decode.getFrame(i); addFrame(new BitmapDrawable(bitmap), decode.getDelay(i)); } setOneShot(false); } @Override public void draw(Canvas canvas) { super.draw(canvas); start(); } } 
+8
android gif
source share
2 answers

I would try either:

  • Separate the animated image (presumably a .gif? File) into separate frames and combine them into an AnimationDrawable , then go to the ImageSpan constructor.
  • Subclass ImageSpan and override the onDraw() method to add your own logic to draw different frames based on some kind of timer. There's a demo version of the api that illustrates how to use the Movie class to load an animated gif, which might be worth a look.

Big change: Okay, sorry I didn't come back earlier, but I had to set aside some time to research it myself. I had a game with him, as I will probably need a solution for this for one of my future projects. Unfortunately, I ran into similar issues using AnimationDrawable , which seems to be caused by a caching mechanism that uses DynamicDrawableSpan (an indirect superclass from ImageSpan ).

Another problem for me is that there seems to be no direct access to the invalidity of Drawable or ImageSpan. Actually Drawable has invalidateDrawable(Drawable) and invalidateSelf() methods, but the former had no effect in my case, while the latter only works if the magic Drawable.Callback attached. I could not find decent documentation on how to use this ...

So, I went further through the logical tree to solve the problem. I must add a warning in advance that this is most likely not the best solution, but at the moment it is the only one I could work with. You probably will not run into problems if you use your solution sporadically, but I would not fill the entire screen with emoticons. I'm not sure what will happen, but then again, I probably don't even want to know.

No noise, here is the code. I added a few comments to make it clear. He probably used a different Gif / libary decoding class, but he should work with any of them.

AnimatedGifDrawable.java

 public class AnimatedGifDrawable extends AnimationDrawable { private int mCurrentIndex = 0; private UpdateListener mListener; public AnimatedGifDrawable(InputStream source, UpdateListener listener) { mListener = listener; GifDecoder decoder = new GifDecoder(); decoder.read(source); // Iterate through the gif frames, add each as animation frame for (int i = 0; i < decoder.getFrameCount(); i++) { Bitmap bitmap = decoder.getFrame(i); BitmapDrawable drawable = new BitmapDrawable(bitmap); // Explicitly set the bounds in order for the frames to display drawable.setBounds(0, 0, bitmap.getWidth(), bitmap.getHeight()); addFrame(drawable, decoder.getDelay(i)); if (i == 0) { // Also set the bounds for this container drawable setBounds(0, 0, bitmap.getWidth(), bitmap.getHeight()); } } } /** * Naive method to proceed to next frame. Also notifies listener. */ public void nextFrame() { mCurrentIndex = (mCurrentIndex + 1) % getNumberOfFrames(); if (mListener != null) mListener.update(); } /** * Return display duration for current frame */ public int getFrameDuration() { return getDuration(mCurrentIndex); } /** * Return drawable for current frame */ public Drawable getDrawable() { return getFrame(mCurrentIndex); } /** * Interface to notify listener to update/redraw * Can't figure out how to invalidate the drawable (or span in which it sits) itself to force redraw */ public interface UpdateListener { void update(); } } 

AnimatedImageSpan.java

 public class AnimatedImageSpan extends DynamicDrawableSpan { private Drawable mDrawable; public AnimatedImageSpan(Drawable d) { super(); mDrawable = d; // Use handler for 'ticks' to proceed to next frame final Handler mHandler = new Handler(); mHandler.post(new Runnable() { public void run() { ((AnimatedGifDrawable)mDrawable).nextFrame(); // Set next with a delay depending on the duration for this frame mHandler.postDelayed(this, ((AnimatedGifDrawable)mDrawable).getFrameDuration()); } }); } /* * Return current frame from animated drawable. Also acts as replacement for super.getCachedDrawable(), * since we can't cache the 'image' of an animated image. */ @Override public Drawable getDrawable() { return ((AnimatedGifDrawable)mDrawable).getDrawable(); } /* * Copy-paste of super.getSize(...) but use getDrawable() to get the image/frame to calculate the size, * in stead of the cached drawable. */ @Override public int getSize(Paint paint, CharSequence text, int start, int end, Paint.FontMetricsInt fm) { Drawable d = getDrawable(); Rect rect = d.getBounds(); if (fm != null) { fm.ascent = -rect.bottom; fm.descent = 0; fm.top = fm.ascent; fm.bottom = 0; } return rect.right; } /* * Copy-paste of super.draw(...) but use getDrawable() to get the image/frame to draw, in stead of * the cached drawable. */ @Override public void draw(Canvas canvas, CharSequence text, int start, int end, float x, int top, int y, int bottom, Paint paint) { Drawable b = getDrawable(); canvas.save(); int transY = bottom - b.getBounds().bottom; if (mVerticalAlignment == ALIGN_BASELINE) { transY -= paint.getFontMetricsInt().descent; } canvas.translate(x, transY); b.draw(canvas); canvas.restore(); } } 

Application:

 final TextView gifTextView = (TextView) findViewById(R.id.gif_textview); SpannableStringBuilder sb = new SpannableStringBuilder(); sb.append("Text followed by animated gif: "); String dummyText = "dummy"; sb.append(dummyText); sb.setSpan(new AnimatedImageSpan(new AnimatedGifDrawable(getAssets().open("agif.gif"), new AnimatedGifDrawable.UpdateListener() { @Override public void update() { gifTextView.postInvalidate(); } })), sb.length() - dummyText.length(), sb.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); gifTextView.setText(sb); 

As you can see, I used a handler to provide β€œticks” for moving to the next frame. The advantage of this is that it will only disable the update whenever a new frame needs to be rendered. Actual redrawing is done by invalidating the TextView that contains the AnimatedImageSpan. At the same time, the disadvantage is that whenever you have a bunch of animated gifs in the same TextView (or a few, for that matter), the views can be updated as crazy ... Use it wisely. :)

+22
source share

You can use ObjectAnimator to animate selection in ImageSpan

http://developer.android.com/reference/android/animation/ObjectAnimator.html

+2
source share

All Articles