我想嘗試之一:
- 斯普利特動畫形象(大概是一個.gif文件?)分成不同的框架,然後將它們合併成一個AnimationDrawable,然後傳遞給ImageSpan的構造函數。
- 子類ImageSpan並重寫
onDraw()
方法來添加您自己的邏輯以基於某種計時器繪製不同的幀。有一個api演示,演示如何使用Movie類加載可能值得研究的動畫gif。
大編輯: 好吧,抱歉沒有找回較早,但我不得不留出一些時間來調查這個自己。我已經玩過了,因爲我可能需要爲我的未來項目中的一個解決方案。不幸的是,我遇到了類似的問題,使用AnimationDrawable
,這似乎是由DynamicDrawableSpan
(ImageSpan
的間接超類)使用的緩存機制引起的。
對我來說,另一個問題是,看起來並不是一個簡單的掃描儀,無效Drawable或ImageSpan。可繪製的方法實際上有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(或多個GIF),視圖可能會像瘋了一樣更新......明智地使用它。 :)
很酷,我沒有注意過這個類。看起來很有趣,雖然它只支持開始API Level 11。 – 2011-12-26 04:26:36
tks您的建議,我會嘗試。 – Stay 2011-12-26 06:17:19
你能否提供一個例子?謝謝! – shiami 2012-03-20 10:40:55