作为问题,我使用ImageSpan将图像添加到TextView中。但是它不能动起来。你有什么建议吗?
我尝试扩展AnimationDrawable以将drawable添加到ImageSpan中。但这不起作用

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

最佳答案

我会尝试:

  • 将动画图像(大概是.gif文件?)分割为单独的帧,然后将它们组合为AnimationDrawable,然后将其传递给ImageSpan的构造函数。
  • 子类化ImageSpan并重写onDraw()方法以添加您自己的逻辑以基于某种计时器绘制不同的帧。有一个api演示,说明了如何使用Movie类加载可能值得研究的gif动画。

  • 大修改:
    好吧,很抱歉没有早点回来,但是我不得不抽出一些时间自己调查一下。我玩过这个游戏,因为我可能需要为自己的 future 项目之一提供解决方案。不幸的是,我在使用AnimationDrawable时遇到了类似的问题,这似乎是由DynamicDrawableSpan(ImageSpan的间接父类(super class))使用的缓存机制引起的。

    对我来说,另一个问题是,似乎没有简单的方法可以使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);
    

    如您所见,我使用处理程序来提供“滴答声”以前进到下一帧。这样做的好处是,只有在应渲染新帧时,它才会触发更新。通过使包含AnimatedImageSpan的TextView无效来完成实际的重绘。同时缺点是,只要您在同一TextView中拥有一堆动画gif(或与此相关的多个动画),这些 View 就可能像疯了一样更新...明智地使用它。 :)

    10-04 21:56