How to show Animated GIF image in android application?

后端 未结 11 1644
臣服心动
臣服心动 2020-12-28 13:16

I want to show an animated GIF image in an android application like the image below. I have tried the webview but no success. How to show the animated gif in the application

11条回答
  •  长情又很酷
    2020-12-28 14:04

    update:

    There is an up-to-date version on android arsenal and in the GitHub page of GIFView.

    This is something small I did when someone asked me to help him with showing gifs. Most of the things I found online were third-party libraries and solutions which used the UI Thread for processing the gif which didn't go so well on my phone so I decided to do it myself with the help of android's Movie API. I deliberately made it extend ImageView so we can use attributes like scaleType. This supports retrieving gif from url or from the assets directory. I documented everything.

    How to use it:

    Simple example of using it in a xml layout file:

    <[package].GIFView xmlns:gif_view="http://schemas.android.com/apk/res-auto"
            android:id="@+id/gif_view"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:scaleType="center"
            gif_view:gif_src="asset:gif1" />
    

    The code:

    GIF.java:

    /**
     * Class that represents a gif instance.
     */
    public class GIF {
    
        private static final Bitmap.Config DEF_VAL_CONFIG = Bitmap.Config.RGB_565;
    
        private static final int DEF_VAL_DELAY_IN_MILLIS = 33;
    
        // the gif's frames are stored in a movie instance
        private Movie movie;
    
        // the canvas of this gif
        private Canvas canvas;
    
        // the bitmap of this gif
        private Bitmap bitmap;
    
        // the start time of the gif
        private long gifStartTime;
    
        // the executor of the gif's thread
        private ScheduledExecutorService executor;
    
        // the main runnable of the gif
        private Runnable mainRunnable;
    
        // delay in millis between frames
        private int delayInMillis;
    
        private OnFrameReadyListener onFrameReadyListener;
    
        private Handler listenerHandler;
    
        private Runnable listenerRunnable;
    
        /**
         * Creates Gif instance based on the passed InputStream.
         *
         * @param in the InputStream
         * @throws InputStreamIsNull                        if in is null
         * @throws InputStreamIsEmptyOrUnavailableException if in is empty or unavailable
         */
        public GIF(InputStream in) {
            this(in, DEF_VAL_CONFIG);
        }
    
        /**
         * Creates Gif instance based on the passed InputStream and the config.
         *
         * @param in     the InputStream
         * @param config the Config
         * @throws NullPointerException                     if config is null
         * @throws InputStreamIsNull                        if in is null
         * @throws InputStreamIsEmptyOrUnavailableException if in is empty or unavailable
         */
        public GIF(InputStream in, Bitmap.Config config) {
            if (in == null)
                throw new InputStreamIsNull("the input stream is null");
    
            this.movie = Movie.decodeStream(in);
    
            if (movie == null)
                throw new InputStreamIsEmptyOrUnavailableException("the input steam is empty or unavailable");
    
            this.bitmap = Bitmap.createBitmap(movie.width(), movie.height(), config);
    
            // associates the canvas with the bitmap
            this.canvas = new Canvas(bitmap);
    
            this.mainRunnable = new Runnable() {
                @Override
                public void run() {
                    draw();
                    invokeListener();
                }
            };
    
            setDelayInMillis(DEF_VAL_DELAY_IN_MILLIS);
        }
    
        /**
         * Register a callback to be invoked when the gif changed a frame.
         * Invokes methods from a special thread.
         *
         * @param onFrameReadyListener the listener to attach
         */
        public void setOnFrameReadyListener(OnFrameReadyListener onFrameReadyListener) {
            setOnFrameReadyListener(onFrameReadyListener, null);
        }
    
        /**
         * Register a callback to be invoked when the gif changed a frame.
         * Invokes methods from the specified handler.
         *
         * @param onFrameReadyListener the listener to attach
         * @param handler              the handler
         */
        public void setOnFrameReadyListener(OnFrameReadyListener onFrameReadyListener, Handler handler) {
            this.onFrameReadyListener = onFrameReadyListener;
            listenerHandler = handler;
    
            if (listenerHandler != null)
                listenerRunnable = new Runnable() {
                    @Override
                    public void run() {
                        GIF.this.onFrameReadyListener.onFrameReady(bitmap);
                    }
                };
    
            else
                listenerRunnable = null;
        }
    
        /**
         * Sets the delay in millis between every calculation of the next frame to be set.
         *
         * @param delayInMillis the delay in millis
         * @throws IllegalArgumentException if delayInMillis is non-positive
         */
        public void setDelayInMillis(int delayInMillis) {
            if (delayInMillis <= 0)
                throw new IllegalArgumentException("delayInMillis must be positive");
    
            this.delayInMillis = delayInMillis;
        }
    
        /**
         * Starts the gif.
         * If the gif is already running does nothing.
         */
        public void startGif() {
            if (executor != null)
                return;
    
            executor = Executors.newSingleThreadScheduledExecutor();
    
            final int INITIAL_DELAY = 0;
            executor.scheduleWithFixedDelay(mainRunnable, INITIAL_DELAY,
                    delayInMillis, TimeUnit.MILLISECONDS);
        }
    
        /**
         * Stops the gif.
         * If the gif is not running does nothing.
         */
        public void stopGif() {
            if (executor == null)
                return;
    
            executor.shutdown();
    
            // waits until the thread is finished
            while (true) {
                try {
                    executor.awaitTermination(Long.MAX_VALUE, TimeUnit.DAYS);
                    break;
                } catch (InterruptedException ignored) {
                }
            }
    
            executor = null;
        }
    
        // calculates the frame and draws it to the bitmap through the canvas
        private void draw() {
            // if gifStartTime == 0 inits it for the first time
            if (gifStartTime == 0)
                gifStartTime = SystemClock.uptimeMillis();
    
            long timeElapsed = SystemClock.uptimeMillis() - gifStartTime;
    
            int timeInGif = (int) (timeElapsed % movie.duration());
            movie.setTime(timeInGif);
    
            movie.draw(canvas, 0, 0);
        }
    
        // invokes the listener
        private void invokeListener() {
            if (onFrameReadyListener == null)
                return;
    
            // if handler was given invokes from it, otherwise invokes from this thread
            if (listenerHandler != null)
                listenerHandler.post(listenerRunnable);
            else
                onFrameReadyListener.onFrameReady(bitmap);
        }
    
        /**
         * Interface definition for a callback to be invoked when the gif changed a frame.
         */
        public interface OnFrameReadyListener {
            /**
             * Called when the gif changed a frame.
             * 

    * Note: If a handler was given with the listener this method * invokes from the handler, otherwise this method * invokes from a special thread. *

    * Note: This bitmap is mutable and used by the gif instance * thus it is not recommended to mutate it. * * @param bitmap the new bitmap of the gif */ void onFrameReady(Bitmap bitmap); } /** * Definition of a runtime exception class to throw when the inputStream is null. */ public static class InputStreamIsNull extends NullPointerException { /** * Creates a new instance. */ public InputStreamIsNull() { super(); } /** * * Creates a new instance with a message. * * @param message the message */ public InputStreamIsNull(String message) { super(message); } } /** * Definition of a runtime exception class to throw when the inputStream is empty or unavailable. */ public static class InputStreamIsEmptyOrUnavailableException extends RuntimeException { /** * Creates a new instance. */ public InputStreamIsEmptyOrUnavailableException() { super(); } /** * * Creates a new instance with a message. * * @param message the message */ public InputStreamIsEmptyOrUnavailableException(String message) { super(message); } } }

    GIFView.java:

    /**
     * A view that can show gifs.
     * 

    * XML Attributes: *

    * gif_src: * A string that represents the gif's source. *

    * - If you want to get the gif from a url * concatenate the string "url:" with the full url. *

    * - if you want to get the gif from the assets directory * concatenate the string "asset:" with the full path of the gif * within the assets directory. You can exclude the .gif extension. *

    * for example if you have a gif in the path "assets/ex_dir/ex_gif.gif" * the string should be: "asset:ex_dir/ex_gif" *

    * delay_in_millis: * A positive integer that represents how many milliseconds * should pass between every calculation of the next frame to be set. */ public class GIFView extends ImageView { public static final String RESOURCE_PREFIX_URL = "url:"; public static final String RESOURCE_PREFIX_ASSET = "asset:"; private static final int DEF_VAL_DELAY_IN_MILLIS = 33; // the gif instance private GIF gif; // keeps track if the view is in the middle of setting the gif private boolean settingGif; private GIF.OnFrameReadyListener gifOnFrameReadyListener; private OnSettingGifListener onSettingGifListener; // delay in millis between frames private int delayInMillis; /** * Creates a new instance in the passed context. * * @param context the context */ public GIFView(Context context) { super(context); init(null); } /** * Creates a new instance in the passed context with the specified set of attributes. * * @param context the context * @param attrs the attributes */ public GIFView(Context context, AttributeSet attrs) { super(context, attrs); init(attrs); } // inits the view private void init(AttributeSet attrs) { this.gifOnFrameReadyListener = new GIF.OnFrameReadyListener() { @Override public void onFrameReady(Bitmap bitmap) { setImageBitmap(bitmap); } }; setDelayInMillis(DEF_VAL_DELAY_IN_MILLIS); if (attrs != null) initAttrs(attrs); } // inits the view with the specified attributes private void initAttrs(AttributeSet attrs) { TypedArray typedArray = getContext().getTheme().obtainStyledAttributes( attrs, R.styleable.gif_view, 0, 0); try { // gets and sets the delay in millis. int delayInMillis = typedArray.getInt(R.styleable.gif_view_delay_in_millis, DEF_VAL_DELAY_IN_MILLIS); if (delayInMillis != DEF_VAL_DELAY_IN_MILLIS) setDelayInMillis(delayInMillis); // gets the source of the gif and sets it String string = typedArray.getString(R.styleable.gif_view_gif_src); if (string != null) setGifResource(typedArray.getString(R.styleable.gif_view_gif_src)); } finally { typedArray.recycle(); } } /** * Register callbacks to be invoked when the view finished setting a gif. * * @param onSettingGifListener the listener to attach */ public void setOnSettingGifListener(OnSettingGifListener onSettingGifListener) { this.onSettingGifListener = onSettingGifListener; } /** * Sets the delay in millis between every calculation of the next frame to be set. * * @param delayInMillis the delay in millis * @throws IllegalArgumentException if delayInMillis is non-positive */ public void setDelayInMillis(int delayInMillis) { if (delayInMillis <= 0) throw new IllegalArgumentException("delayInMillis must be positive"); this.delayInMillis = delayInMillis; if (gif != null) gif.setDelayInMillis(delayInMillis); } /** * Returns true if the view is in the process of setting the gif, false otherwise. * * @return true if the view is in the process of setting the gif, false otherwise */ public boolean isSettingGif() { return settingGif; } /** * Sets the gif of this view and starts it. *

    * Note that every exception while setting the gif is only sent to the * OnSettingGifListener instance attached to this view. *

    * If the view has already begun setting another gif, does nothing. * You can query this state with isSettingGif(). *

    * The string passed must be in the following format: *

    * - If you want to get the gif from a url * concatenate the string "url:" with the full url. *

    * - if you want to get the gif from the assets directory * concatenate the string "asset:" with the full path of the gif * within the assets directory. You can exclude the .gif extension. *

    * You can use the Constants: *

    * GIFView.RESOURCE_PREFIX_URL = "url:" * GIFView.RESOURCE_PREFIX_ASSET = "asset:" *

    * for example if you have a gif in the path "assets/ex_dir/ex_gif.gif" * invoke the method like this: setGifResource(GIFView.RESOURCE_PREFIX_ASSET + "ex_dir/ex_gif"); * * @param string the string * @throws IllegalArgumentException if the string format is invalid */ public void setGifResource(String string) { if (settingGif) return; // stops the gif if it is running if (gif != null) gif.stopGif(); // defines some finals for readability final int URL_START_INDEX = RESOURCE_PREFIX_URL.length(); final int ASSET_START_INDEX = RESOURCE_PREFIX_ASSET.length(); final String GIF_EXTENSION = ".gif"; if (string.startsWith(RESOURCE_PREFIX_URL)) { // notifies setting gif has started settingGif = true; // gets the url String url = string.substring(URL_START_INDEX); new AsyncSettingOfGif() { @Override protected InputStream getGifInputStream(String url) throws Exception { // gets the input stream from the url return (InputStream) new URL(url).getContent(); } }.execute(url); } else if (string.startsWith(RESOURCE_PREFIX_ASSET)) { // notifies setting gif has started settingGif = true; // gets the asset path String assetPath = string.substring(ASSET_START_INDEX) .replaceAll("[\\\\/]", File.separator); // replacing file separators if (!assetPath.endsWith(GIF_EXTENSION)) assetPath += GIF_EXTENSION; new AsyncSettingOfGif() { @Override protected InputStream getGifInputStream(String assetPath) throws Exception { // gets the input stream from the assets directory return GIFView.this.getResources().getAssets().open(assetPath); } }.execute(assetPath); // if string format is invalid } else { throw new IllegalArgumentException("string format is invalid"); } } /** * Called when the view finished to set the gif * or an exception has occurred. * If there are no exceptions e is null. *

    * Note that the gif can be initialized properly * and one or more exceptions can be caught in the way. * * @param e the Exception */ protected void onFinishSettingGif(Exception e) { // notifies setting the gif has finished settingGif = false; if (gif != null) onSuccess(); else onFailure(e); } // on finish setting the gif private void onSuccess() { gif.setOnFrameReadyListener(gifOnFrameReadyListener, getHandler()); gif.setDelayInMillis(delayInMillis); startGif(); if (onSettingGifListener != null) onSettingGifListener.onSuccess(this); } // when an exception has occurred while trying to set the gif private void onFailure(Exception e) { if (onSettingGifListener != null) onSettingGifListener.onFailure(this, e); } /** * Starts the gif. * If the gif is already running does nothing. * * @throws IllegalStateException if the gif has not been initialized yet */ public void startGif() { if (gif == null || settingGif) throw new IllegalStateException("the gif has not been initialized yet"); gif.startGif(); } /** * Stops the gif. * If the gif is not running does nothing. * * @throws IllegalStateException if the gif has not been initialized yet */ public void stopGif() { if (gif == null || settingGif) throw new IllegalStateException("the gif has not been initialized yet"); gif.stopGif(); } /** * Interface definition for callbacks to be invoked when setting a gif. */ public interface OnSettingGifListener { /** * Called when a gif has successfully set. * * @param view the GIFView */ void onSuccess(GIFView view); /** * Called when a gif cannot be set. * * @param view the GIFView * @param e the Exception */ void onFailure(GIFView view, Exception e); } /** * Definition of an Exception class to throw when the view cannot initialize the gif. */ public static class CannotInitGifException extends Exception { /** * Creates a new instance. */ public CannotInitGifException() { super(); } /** * * Creates a new instance with a message. * * @param message the message */ public CannotInitGifException(String message) { super(message); } } /** * A sub-class of AsyncTask to easily perform an async task of setting a gif. *

    * The default implementation of AsyncSettingOfGif.doInBackground() is to try and init the gif * from the input stream returned from AsyncSettingOfGif.getGifInputStream() and notify * GIFView.onFinishSettingGif() sending to it the exception, if occurred, or null. *

    * Implementations of this class should override AsyncSettingOfGif.getGifInputStream() * to return the right input stream for the gif based on the string argument. * The string argument can be, for example, a url to retrieve the input stream from. */ protected abstract class AsyncSettingOfGif extends AsyncTask { @Override protected Exception doInBackground(String... string) { CannotInitGifException exceptionToSend = null; try (InputStream in = getGifInputStream(string[0])) { // tries to init the gif gif = new GIF(in); } catch (Exception e) { // prepares the message of the exception String message = e.getMessage(); if (e instanceof FileNotFoundException) message = "file not found: " + message; // prepares the exception to send back exceptionToSend = new CannotInitGifException(message); } return exceptionToSend; } /** * Override this method to return the right input stream for the gif based on the string argument. * The string argument can be, for example, a url to retrieve the input stream from. * * @param string the string * @return an InputStream of a gif * @throws Exception if an exception has occurred */ protected abstract InputStream getGifInputStream(String string) throws Exception; @Override protected void onPostExecute(Exception e) { onFinishSettingGif(e); } } }

    res/values/attrs.xml:

    
    
        
            
            
        
    
    

提交回复
热议问题