How to play GIF in android

Viewed 65908

Hello stackoverflow I'm trying to develop an android application to play my own GIF, here is the code snippet

MainActivity.java

public class MainActivity extends Activity {

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main);
}
}

AnimationView.java

import java.io.ByteArrayOutputStream;
import java.io.InputStream;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Movie;
import android.util.AttributeSet;
import android.view.View;

public class AnimationView extends View {
private Movie mMovie;
private long mMovieStart;
private static final boolean DECODE_STREAM = true;

private int mDrawLeftPos;
private int mHeight, mWidth;

private static byte[] streamToBytes(InputStream is) {
    ByteArrayOutputStream os = new ByteArrayOutputStream(1024);
    byte[] buffer = new byte[1024];
    int len;
    try {
        while ((len = is.read(buffer)) >= 0) {
            os.write(buffer, 0, len);
        }
    } catch (java.io.IOException e) {
    }
    return os.toByteArray();
}

public AnimationView(Context context, AttributeSet attrs) {
    super(context, attrs);
    setFocusable(true);
    java.io.InputStream is;
    is = context.getResources().openRawResource(R.drawable.scanning);
    if (DECODE_STREAM) {
        mMovie = Movie.decodeStream(is);
    } else {
        byte[] array = streamToBytes(is);
        mMovie = Movie.decodeByteArray(array, 0, array.length);
    }
}

@Override
protected void onMeasure( int widthMeasureSpec, int heightMeasureSpec )
{   
    int p_top = this.getPaddingTop(), p_bottom = this.getPaddingBottom();

    mWidth = mMovie.width();
    mHeight = mMovie.height();
    // Calculate new desired height
    final int desiredHSpec = MeasureSpec.makeMeasureSpec( mHeight + p_top + p_bottom , MeasureSpec.EXACTLY );

    setMeasuredDimension( widthMeasureSpec, desiredHSpec );
    super.onMeasure( widthMeasureSpec, desiredHSpec );

    // Update the draw left position
    mDrawLeftPos = Math.max( ( this.getWidth() - mWidth ) / 2, 0) ;
}

@Override
public void onDraw(Canvas canvas) {
    long now = android.os.SystemClock.uptimeMillis();
    if (mMovieStart == 0) { // first time
        mMovieStart = now;
    }
    if (mMovie != null) {
        int dur = mMovie.duration();
        if (dur == 0) {
            dur = 3000;
        }
        int relTime = (int) ((now - mMovieStart) % dur);
        // Log.d("", "real time :: " +relTime);
        mMovie.setTime(relTime);
        mMovie.draw(canvas, mDrawLeftPos, this.getPaddingTop());
        invalidate();
    }
}
}

main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#FFFFFF"
android:orientation="vertical" >

<com.example.androidgifwork.AnimationView
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_centerInParent="true" />
</LinearLayout>

Manifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.androidgifwork"
android:versionCode="1"
android:versionName="1.0" >

<uses-sdk
    android:minSdkVersion="8"
    android:targetSdkVersion="19" />

<application
    android:icon="@drawable/ic_launcher"
    android:label="@string/app_name" >
    <activity
        android:name="com.example.androidgifwork.MainActivity"
        android:label="@string/app_name" >
        <intent-filter>
            <action android:name="android.intent.action.MAIN" />

            <category android:name="android.intent.category.LAUNCHER" />
        </intent-filter>
    </activity>
</application>

When I run the above code snippet GIF is not at all running, but when I remove android:targetSdkVersion="19" GIF running, please help me to solve this riddle.

Thanks

8 Answers

If you can use a WebView, GIFs will play directly over it. But I am not sure, in your case whether you want to put it in a Webview or not.

add this to your dependencies (build.gradle)

 allprojects {
    repositories {
        mavenCentral()
    }
}
dependencies {
    implementation 'pl.droidsonroids.gif:android-gif-drawable:1.2.15'
}

and use this in xml to show your gif

 <pl.droidsonroids.gif.GifTextView
                    android:id="@+id/gifTextView2"
                    android:layout_width="230dp"
                    android:layout_height="200dp"
                    android:layout_weight="1"
                    android:src="@drawable/dev_option_gif"
                    />

More info at: https://github.com/koral--/android-gif-drawable

For devices running API 29(Pie) & above you can use AnimatedImageDrawable to play GIF(s) and WebP animated images.

Here's a sample:

ImageView imgGif = findViewById(R.id.imgGif);

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
        try {
            drawable = ImageDecoder.decodeDrawable(ImageDecoder.createSource(getResources(), R.drawable.giphy));
            imgGif.setImageDrawable(drawable);

            if (drawable instanceof AnimatedImageDrawable) {
                ((AnimatedImageDrawable) drawable).start();
            }

        } catch (IOException e) {
            e.printStackTrace();
        }
    }

Using this provides more control & features you can use on the image such as:

  • Checking whether the gif is running
  • Starting & stopping the playback.
  • Applying color filters
  • Changing image shape

    and much more

You can also use Coil library, which gives you a bit less control over Gif images than Glide does. But still gives simple implementation if you just want to load the GIF once and show. Probably, if you want to repeat that you need to trigger the drawable again or just call the load function once again. Anyway even if calling load again the resources should be cached under the hood(something to be investigated, not 100% sure). Everything you need is adding dependency:

 implementation "io.coil-kt:coil-gif:1.0.0"

And specifying your decoding mechanism:

val imageLoader = ImageLoader.Builder(context)
    .componentRegistry {
        if (SDK_INT >= 28) {
            add(ImageDecoderDecoder())
        } else {
            add(GifDecoder())
        }
    }
    .build()

That's all go-ahead and load images/gifs directly in your image views:

app_logo.load("needed_gif_url")

Don't forget to pass the needed image loader to each request where you need your decoder to be used, as for GIFs. Or just make your custom imageLoader as default for each request:

Coil.setImageLoader(imageLoader)
Related