2011-12-26 17 views
8

como la pregunta, uso ImageSpan para agregar una imagen en TextView. pero no puede animar. ¿Tiene algún consejo?
Intento extender AnimationDrawable para agregar drawable en ImageSpan. pero no funcionaCómo agregar un emoticón animado en TextView o EditText en Android

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

Respuesta

22

me gustaría probar a cualquiera:

  • Dividir la imagen animada (presumiblemente un archivo .gif?) en marcos separados y combinarlos en un AnimationDrawable que luego pasa al constructor de ImageSpan.
  • Subeclade ImageSpan e invalide el método onDraw() para agregar su propia lógica para dibujar los diferentes cuadros basándose en algún tipo de temporizador. Hay una demostración de API que ilustra cómo usar la clase Película para cargar un archivo .gif animado que podría valer la pena examinar.

Gran Editar: bien, lo siento por no volver antes, pero tuve que dejar de lado algún tiempo para investigar esto por mí mismo. He tenido una jugada porque probablemente necesitaré una solución para esto para uno de mis proyectos futuros. Lamentablemente, me encontré con problemas similares con el uso de un AnimationDrawable, que parece ser causado por el mecanismo de almacenamiento en caché que utiliza DynamicDrawableSpan (una superclase indirecta de ImageSpan).

Otro problema para mí es que no parece haber una vaguedad directa para invalidar un Drawable o ImageSpan. Drawable en realidad tiene los métodos invalidateDrawable(Drawable) y invalidateSelf(), pero el primero no tuvo ningún efecto en mi caso, mientras que el último solo funciona si se adjunta algún Drawable.Callback mágico. No pude encontrar ninguna documentación decente sobre cómo usar esto ...

Así que fui un paso más allá en el árbol de la lógica para resolver el problema. Debo añadir una advertencia de antemano de que probablemente esta no sea una solución óptima, pero por ahora es la única a la que pude poner manos a la obra. Probablemente no tenga problemas si usa mi solución esporádicamente, pero evitaría llenar toda la pantalla con emoticones por todos los medios. No estoy seguro de qué pasaría, pero, de nuevo, probablemente ni siquiera quiera saberlo.

Sin más preámbulos, aquí está el código. Agregué algunos comentarios para que sean fáciles de entender. Es bastante probable que use una clase/biblioteca de descodificación de Gif diferente, pero debería funcionar con cualquier otra.

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

    } 

} 

de uso:

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

Como se puede ver que usé un controlador para proporcionar los 'tics' para avanzar al siguiente fotograma.La ventaja de esto es que solo lanzará una actualización cada vez que se genere un nuevo fotograma. El redibujado real se realiza al invalidar el TextView que contiene el AnimatedImageSpan. Al mismo tiempo, el inconveniente es que cada vez que tenga un montón de gifs animados en el mismo TextView (o múltiples para el caso), las vistas podrían actualizarse como locas ... Úselo sabiamente. :)

+0

tks por su consejo, lo intentaré. – Stay

+0

eh, ¿qué es StateDrawable? – Stay

+0

Lo siento, debo haber tenido una crisis cerebral temporal. Quise escribir "[' AnimationDrawable'] (http://developer.android.com/reference/android/graphics/drawable/AnimationDrawable.html) ". Lo corregí en mi respuesta también. –