Wednesday, April 8, 2015

Implement custom View to display Animated GIF

It's a improved version of the example "Play animated GIF using android.graphics.Movie, with Movie.decodeStream(InputStream)".


- Add function of Run/Stop, and Repeat setting
- Instead of disable hardware acceleration in AndroidManifest.xml, the custom AnimatedGifView disable hardware acceleration by calling setLayerType(), for API Level 11.

Our custom view, AnimatedGifView.gif
package com.example.androidgif;

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 AnimatedGifView extends View {

 private InputStream gifInputStream;
 private Movie gifMovie;
 private int movieWidth, movieHeight;
 private long movieDuration;
 private long movieRunDuration;
 private long lastTick;
 private long nowTick;

 private boolean repeat = true;
 private boolean running = true;

 public void setRepeat(boolean r) {
  repeat = r;
 }

 public void setRunning(boolean r) {
  running = r;
 }

 public AnimatedGifView(Context context) {
  super(context);
  init(context);
 }

 public AnimatedGifView(Context context, AttributeSet attrs) {
  super(context, attrs);
  init(context);
 }

 public AnimatedGifView(Context context, AttributeSet attrs, int defStyleAttr) {
  super(context, attrs, defStyleAttr);
  init(context);
 }

 private void init(Context context) {

  // Turn OFF hardware acceleration
  // API Level 11
  setLayerType(View.LAYER_TYPE_SOFTWARE, null);

  setFocusable(true);
  gifInputStream = context.getResources().openRawResource(
    R.drawable.android_er);

  gifMovie = Movie.decodeStream(gifInputStream);
  movieWidth = gifMovie.width();
  movieHeight = gifMovie.height();
  movieDuration = gifMovie.duration();
 }

 @Override
 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
  setMeasuredDimension(movieWidth, movieHeight);
 }

 public int getMovieWidth() {
  return movieWidth;
 }

 public int getMovieHeight() {
  return movieHeight;
 }

 public long getMovieDuration() {
  return movieDuration;
 }

 @Override
 protected void onDraw(Canvas canvas) {
  
  if(gifMovie == null){
   return;
  }

  nowTick = android.os.SystemClock.uptimeMillis();
  if (lastTick == 0) { // first time
   movieRunDuration = 0;
  }else{
   if(running){
    movieRunDuration += nowTick-lastTick;
    if(movieRunDuration > movieDuration){
     if(repeat){
      movieRunDuration = 0;
     }else{
      movieRunDuration = movieDuration;
     }
    }
   }
  }
  
  gifMovie.setTime((int)movieRunDuration);
  gifMovie.draw(canvas, 0, 0);
  
  lastTick = nowTick;
  invalidate();

 }
}

MainActivity.java
package com.example.androidgif;

import android.support.v7.app.ActionBarActivity;
import android.os.Bundle;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.CompoundButton.OnCheckedChangeListener;
import android.widget.TextView;
import android.widget.ToggleButton;

public class MainActivity extends ActionBarActivity {

 TextView textViewInfo;
 AnimatedGifView gifView;
 
 CheckBox cbRepeat;
 ToggleButton tbRun;

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

  gifView = (AnimatedGifView) findViewById(R.id.gifview);
  textViewInfo = (TextView) findViewById(R.id.textinfo);

  String stringInfo = "";
  stringInfo += "Duration: " + gifView.getMovieDuration() + "\n";
  stringInfo += "W x H: " + gifView.getMovieWidth() + " x "
    + gifView.getMovieHeight() + "\n";

  textViewInfo.setText(stringInfo);
  
  cbRepeat = (CheckBox)findViewById(R.id.repeat);
  cbRepeat.setOnCheckedChangeListener(new OnCheckedChangeListener(){

   @Override
   public void onCheckedChanged(CompoundButton buttonView,
     boolean isChecked) {
    gifView.setRepeat(isChecked);
   }});
  
  tbRun = (ToggleButton)findViewById(R.id.run);
  tbRun.setOnCheckedChangeListener(new OnCheckedChangeListener(){

   @Override
   public void onCheckedChanged(CompoundButton buttonView,
     boolean isChecked) {
    gifView.setRunning(isChecked);
   }});
 }

}

activity_main.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context="com.example.androidgif.MainActivity" >

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:autoLink="web"
        android:text="http://android-er.blogspot.com/"
        android:textStyle="bold" />
    
    <CheckBox
        android:id="@+id/repeat"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Repeat"
        android:checked="true"/>
    <ToggleButton
        android:id="@+id/run"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textOn="Stop"
        android:textOff="Run"
        android:checked="true"/>

    <LinearLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="horizontal" >

        <ImageView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:src="@drawable/android_er"
            />

        <com.example.androidgif.AnimatedGifView
            android:id="@+id/gifview"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" />
    </LinearLayout>

    <TextView
        android:id="@+id/textinfo"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="info..." />

</LinearLayout>



download filesDownload the files.

5 comments:

Unknown said...

Great work. I took just the Animated GIF class, and the example of how to include in the layout XML, added a GIF resource, and it worked. I also modified the code to have a gifResourceId property in the XML, and have the resource loaded that way, rather than in the class itself. Thanks for sharing.

Anonymous said...

Hi Christopher,

Can you please share your code with us?

mail it to 716sasuke@gmail.com

Thanks.

Unknown said...

Thank you very much for the great post. Do you know by the chance how to alter the size of the gif? Lets say I would like to enlarge it a little bit?

Thank you again!

Marc said...

Hi there!

Very useful post. I noticed a lot of people asking for "how to increase the size" - and, having been forced to figure it out, thought I'd share.

Without all the particulars, you need to, before 'invalidate();' in your 'onDraw':


final int savedState = canvas.save();

/* passed into scale are multipliers. So, need to pass in the appropriate factor. Unless you want your gif to be skewed, use the same factor twice (it's a float). */
canvas.scale(scaler, scaler);

gifMovie.draw(canvas, 0, 0);

canvas.restoreToCount(savedState);


Cheers, and thanks again!

Unknown said...

Somebody tried to play gif in reverse order(from end to start)? Performance is terrible. I don't know why bug it seems like something with inner Movie class implementation.