【问题标题】:How to add animated emoticon in TextView or EditText in Android如何在 Android 的 TextView 或 EditText 中添加动画表情
【发布时间】:2012-01-27 19:14:53
【问题描述】:

作为问题,我使用 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();
    }
}

【问题讨论】:

    标签: android gif


    【解决方案1】:

    我会尝试:

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

    大编辑: 好吧,很抱歉没有早点回来,但我不得不自己抽出一些时间来调查这件事。我玩过它,因为我自己可能需要一个解决方案来解决我未来的一个项目。不幸的是,我在使用AnimationDrawable 时遇到了类似的问题,这似乎是由DynamicDrawableSpanImageSpan 的间接超类)使用的缓存机制引起的。

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

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

    事不宜迟,代码如下。我添加了一些 cmets 以使其不言自明。它很可能使用了不同的 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 时,视图可能会像疯了一样更新......明智地使用它。 :)

    【讨论】:

    • 谢谢你的建议,我试试看。
    • 对不起,我一定是暂时性的大脑崩溃了。我的意思是写“AnimationDrawable”。我也在我的回答中更正了它。
    • 我也解决了我的问题,你可能会看到我扩展了 AnimationDrawable,但它不起作用..
    • 好吧,我有一些时间自己经历了一遍。请在我更新的答案中找到结果 - 希望对您有所帮助!
    • whatis Gif 解码类/库?能分享一下链接吗
    【解决方案2】:

    您可以使用 ObjectAnimator 为 ImageSpan 中的 drawable 设置动画

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

    【讨论】:

    • 酷,我以前没有注意到这个类。看起来很有趣,虽然它只支持从 API 级别 11 开始。
    • 谢谢你的建议,我试试看。
    • 您能举个例子吗?谢谢!
    猜你喜欢
    • 1970-01-01
    • 2021-01-28
    • 1970-01-01
    • 1970-01-01
    • 2011-08-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多