我会尝试:

>将动画图像(可能是.gif文件?)拆分成单独的帧,然后将它们组合成一个AnimationDrawable,然后传递给ImageSpan的构造函数.

> Subclass ImageSpan并覆盖onDraw()方法,添加自己的逻辑,根据某种计时器绘制不同的帧.有一个api演示,演示如何使用Movie类加载可能值得研究的动画gif.

大编辑:

好吧,抱歉没有早点回来,但我不得不留出一些时间来自己调查.我玩过它,因为我可能需要一个解决方案,我自己的未来项目之一.不幸的是,我遇到了使用AnimationDrawable的类似问题,这似乎是由DynamicDrawableSpan(ImageSpan的间接超类)使用的缓存机制引起的.

对我来说另一个问题是,似乎没有一个简单的wat来使Drawable或ImageSpan无效. Drawable实际上有invalidateDrawable(Drawable)和invalidateSelf()方法,但是第一个在我的情况下没有任何效果,而后者仅在附加了一些神奇的Drawable.Callback时才有效.我找不到任何关于如何使用这个的体面文档……

所以,我更进一步向上逻辑树来解决问题.我必须事先添加一个警告,这很可能不是最佳解决方案,但现在它是我唯一能够开展工作的解决方案.如果你偶尔使用我的解决方案,你可能不会遇到问题,但我总是避免用表情符号填满整个屏幕.我不确定会发生什么,但话说回来,我可能甚至都不想知道.

不用多说,这是代码.我添加了一些评论,使其不言自明.它很可能是一个使用不同的Gif解码类/库,但它应该与任何在那里工作.

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();

}

}

用法:

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);

正如您所看到的,我使用Handler来提供“ticks”以前进到下一帧.这样做的好处是,只有在应该渲染新帧时,它才会触发更新.实际的重绘是通过使包含AnimatedImageSpan的TextView无效来完成的.与此同时,缺点是每当你在同一个TextView中有一堆GIF动画(或者就此而言是多个)时,视图可能会像疯了一样更新……明智地使用它. 🙂

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐