This is my code where I am loading a gif from URL. I am using Glide Library. This code is not working, blank activity display
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_background_gif);
ImageView imageView = (ImageView) findViewById(R.id.gifImageView);
//Glide.with(this).load(getResources().getDrawable(R.drawable.sample_img)).into(imageView);
Glide.with(this)
.load("http://more-sky.com/data/out/6/IMG_105566.gif")
.into(new GlideDrawableImageViewTarget(imageView));
}
Try using this
Glide.with(context)
.load(imageUrl)
.asGif()
.placeholder(R.drawable.loading2)
.crossFade()
.into(imageView);
you can programatically achieve it,
follow this link
http://www.geeks.gallery/how-to-display-the-animated-gif-image-in-android/
OR use library,
https://github.com/koral--/android-gif-drawable
https://github.com/felipecsl/GifImageView
You can also use Picasso
Picasso.with(context)
.load(url)
.resize(50, 50)
.centerCrop()
.into(imageView)
You can use WebView to load GIF image. Implementation is very easy and works like a charm. Create html page. Here's the code:
<html style="margin: 0;">
<body style="margin: 0;">
<img src="imagename.gif" style="width: 100%; height: 100%" />
</body>
</html>
Create assets folder and create html folder in assets. Copy Gif image and webpage in html folder. Add WebView in your xml.
MainActivity
webView = (WebView)findViewById(R.id.webView);
webView.setBackgroundColor(Color.TRANSPARENT); //for gif without background
webView.loadUrl("file:///android_asset/html/HTML_PAGE_NAME.html");
From P OS gif is now directly supported by Android and it is easy to load.
Java Code (loading from file):
ImageDecoder.Source source = ImageDecoder.createSource(new File(fileName));
AnimatedImageDrawable drawable = (AnimatedImageDrawable) ImageDecoder.decodeDrawable(source);
imageView.setImageDrawable(drawable);
drawable.start();
Kotlin code(loading from assets):
val source = ImageDecoder.createSource(assets, assetFileName)
val drawable = ImageDecoder.decodeDrawable(source)
imageView.setImageDrawable(drawable)
if (drawable is AnimatedImageDrawable) {
drawable.start()
}
I used Movie class to display animated GIF according to this page. Below is the class I created to diaplay the gif and method invoking it.
It is deprecated from SDK 28 and AnimatedImageView is recommended. I used both and it turned out to me that old, unofficial way (with Movie) is faster and more reliable (AnimatedImageView hangs freezes sometimes).
Class to display animiated GIF with Movie
public class ShowGifView extends View {
private Movie movie;
private int gifImageDrawableId;
private final Context ctx;
private long gifStart = 0;
public ShowGifView(Context context) {
super(context);
// Make the custom view focus.
setFocusable(true);
ctx = context;
}
#Override
protected void onDraw(Canvas canvas) {
long now = System.currentTimeMillis();
if (gifStart == 0) {
gifStart = now;
}
if (movie != null) {
// Get gif movie duration time.
int duration = movie.duration();
if (duration == 0) {
duration = 1000;
}
// Get played frame percentage.
int relTime = (int)((now - gifStart) % duration);
// Set current gif frame time.
movie.setTime(relTime);
// Get custom view width and height.
int width = this.getWidth();
int height = this.getHeight();
// Get gif image width and height.
int movieWidth = movie.width();
int movieHeight = movie.height();
// Scale canvas size to fit the custom view.
canvas.scale((float)width / movieWidth, (float)height / movieHeight);
// Draw the gif image frame to custom view canvas.
movie.draw(canvas, 1, 1);
// This method will invoke onDraw method.
invalidate();
}
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if(movie != null){
int scale = heightMeasureSpec / movie.height();
setMeasuredDimension(movie.width() * scale, movie.height() * scale);
}else{
setMeasuredDimension(getSuggestedMinimumWidth(), getSuggestedMinimumHeight());
}
}
public int getGifImageDrawableId() {
return gifImageDrawableId;
}
public void setGifImageDrawableId(int gifImageDrawableId) {
this.gifImageDrawableId = gifImageDrawableId;
}
// Call this method to read the drawable gif image to create movie object.
public void drawGif() {
Resources resources = ctx.getResources();
InputStream inputStream = resources.openRawResource(gifImageDrawableId);
movie = Movie.decodeStream(inputStream);
// Invalidate the view and invoke onDraw method.
invalidate();
}
}
Method invoking this:
private View addAnimatedGif(ConstraintLayout lout, int animatedGif) {
ShowGifView resultView = new ShowGifView(getApplicationContext());
// Set Layer type to display animated GIF on all APIs
resultView.setLayerType(View.LAYER_TYPE_SOFTWARE, new Paint());
resultView.setGifImageDrawableId(animatedGif);
resultView.drawGif();
ConstraintSet cSet = new ConstraintSet();
lout.addView(resultView);
resultView.setId(View.generateViewId());
int id = resultView.getId();
cSet.clone(lout);
cSet.connect(id, ConstraintSet.START, ConstraintSet.PARENT_ID, ConstraintSet.START, 0);
cSet.connect(id, ConstraintSet.TOP, ConstraintSet.PARENT_ID, ConstraintSet.TOP, 0);
cSet.connect(id, ConstraintSet.END, ConstraintSet.PARENT_ID, ConstraintSet.END, 0);
cSet.connect(id, ConstraintSet.BOTTOM, ConstraintSet.PARENT_ID, ConstraintSet.BOTTOM, 0);
cSet.applyTo(lout);
return resultView;
}
I am trying to show frame by frame animation by changing images in a imageview. I tried animation drawable in xml and also changing the bitmap of the imageview inside a Handler. I also tried to store only three bitmaps inside a arraylist(to avoid out of memory) as a caching mechanism but really low improvement. I need to iterate 36 images for a full animation. The problem i am facing is that in all the methods I used I cannot complete the animation in the given timeframe of 50ms. The images range from 250 kb smallest to 540 kb maximum. The fps of animation is really low. As the ios version of the app is ready I am constrained to show animation consistent to the ios version. I am a noob in renderscript and opengl. Is there any way to show a smooth animation for large images in 50-60ms. Any hints or suggestions is highly appreciated. Heres a snapshot of the animation:
Here's the link to my images for any one intrested.
I wrote a simple activity that does the most basic thing:
Loads all bitmaps in a Thread, then posts a change to an ImageView every 40ms.
package mk.testanimation;
import android.app.Activity;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Bundle;
import android.os.Handler;
import android.util.Log;
import android.widget.ImageView;
import java.util.ArrayList;
public class MainActivity extends Activity {
ImageView mImageView;
private int mImageRes[] = new int[]{
R.drawable.s0,
R.drawable.s1,
R.drawable.s2,
R.drawable.s3,
R.drawable.s4,
R.drawable.s5,
R.drawable.s6,
R.drawable.s7,
R.drawable.s8,
R.drawable.s9,
R.drawable.s10,
R.drawable.s11,
R.drawable.s12,
R.drawable.s13,
R.drawable.s14,
R.drawable.s15,
R.drawable.s16,
R.drawable.s17,
R.drawable.s18,
R.drawable.s19,
R.drawable.s20,
R.drawable.s21,
R.drawable.s22,
R.drawable.s23,
R.drawable.s24,
R.drawable.s25,
R.drawable.s26,
R.drawable.s27,
R.drawable.s28,
R.drawable.s29,
R.drawable.s30,
R.drawable.s31,
R.drawable.s32,
R.drawable.s33,
R.drawable.s34,
R.drawable.s35,
};
private ArrayList<Bitmap> mBitmaps = new ArrayList<Bitmap>(mImageRes.length);
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
final Handler handler = new Handler();
mImageView = new ImageView(this);
setContentView(mImageView);
Thread important = new Thread() {
#Override
public void run() {
long timestamp = System.currentTimeMillis();
for (int i = 0; i < mImageRes.length; i++) {
mBitmaps.add(BitmapFactory.decodeResource(getResources(), mImageRes[i]));
}
Log.d("ANIM-TAG", "Loading all bitmaps took " + (System.currentTimeMillis() - timestamp) + "ms");
for (int i = 0; i < mBitmaps.size(); i++) {
final int idx = i;
handler.postDelayed(new Runnable() {
#Override
public void run() {
mImageView.setImageBitmap(mBitmaps.get(idx));
}
}, i * 40);
}
}
};
important.setPriority(Thread.MAX_PRIORITY);
important.start();
}
}
This looked pretty decent on my Nexus 7, but it did take a little over 4s to load all the bitmaps.
Can you load the bitmaps in advance?
Also, it won't save a ton, but your pngs have a bunch of padding around the transparent space. You can crop them and reduce the memory a bit. Otherwise compressing the images will also help (like limiting the number of colors used).
Ideally, in the above solution, you'd recycle the bitmaps immediately after they're no longer used.
Also, if that's too memory-heavy, you could do as you mentioned, and have a Bitmap buffer, but I'm pretty sure it'll need to be more than 3 images large.
Good luck.
EDIT: Attempt 2.
First, I cropped all the images to 590x590. This shaved about 1mb off the images. Then I created a new class, which is a bit "busy" and doesn't have a fixed frame rate but renders the images as soon as they are ready:
package mk.testanimation;
import android.app.Activity;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Bundle;
import android.os.Handler;
import android.util.Log;
import android.widget.ImageView;
import java.util.ArrayList;
public class MainActivity extends Activity {
ImageView mImageView;
private int mImageRes[] = new int[]{R.drawable.s0, R.drawable.s1, R.drawable.s2, R.drawable.s3, R.drawable.s4, R.drawable.s5, R.drawable.s6, R.drawable.s7, R.drawable.s8, R.drawable.s9, R.drawable.s10, R.drawable.s11, R.drawable.s12, R.drawable.s13, R.drawable.s14, R.drawable.s15, R.drawable.s16, R.drawable.s17, R.drawable.s18, R.drawable.s19, R.drawable.s20, R.drawable.s21, R.drawable.s22, R.drawable.s23, R.drawable.s24, R.drawable.s25, R.drawable.s26, R.drawable.s27, R.drawable.s28, R.drawable.s29, R.drawable.s30, R.drawable.s31, R.drawable.s32, R.drawable.s33, R.drawable.s34, R.drawable.s35};
private ArrayList<Bitmap> mBitmaps = new ArrayList<Bitmap>(mImageRes.length);
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
final long timestamp = System.currentTimeMillis();
final Handler handler = new Handler();
mImageView = new ImageView(this);
setContentView(mImageView);
Thread important = new Thread() {
#Override
public void run() {
for (int i = 0; i < mImageRes.length; i++) {
mBitmaps.add(BitmapFactory.decodeResource(getResources(), mImageRes[i]));
}
}
};
important.setPriority(Thread.MAX_PRIORITY);
important.start();
Thread drawing = new Thread() {
#Override
public void run() {
int i = 0;
while (i < mImageRes.length) {
if (i >= mBitmaps.size()) {
Thread.yield();
} else {
final Bitmap bitmap = mBitmaps.get(i);
handler.post(new Runnable() {
#Override
public void run() {
mImageView.setImageBitmap(bitmap);
}
});
i++;
}
}
Log.d("ANIM-TAG", "Time to render all frames:" + (System.currentTimeMillis() - timestamp) + "ms");
}
};
drawing.setPriority(Thread.MAX_PRIORITY);
drawing.start();
}
}
The above got the rendering to start nearly immediately, and it took less than 4s on my 2012 Nexus 7.
As a last effort, I converted all the images to 8-bit PNGs instead of 32-bit. This brought the rendering to under 2 seconds!
I'll bet any solution you end up with will benefit from making the images as small as possible.
Again -- Good Luck!
I'm facing the same problem and I have solved it by overriding an AnimationDrawable.
So, if the problem is you that can't load all images in array because it is too big for the memory to hold it, then load the image when you need it.
My AnimationDrawable is this:
public abstract class MyAnimationDrawable extends AnimationDrawable {
private Context context;
private int current;
private int reqWidth;
private int reqHeight;
private int totalTime;
public MyAnimationDrawable(Context context, int reqWidth, int reqHeight) {
this.context = context;
this.current = 0;
//In my case size of screen to scale Drawable
this.reqWidth = reqWidth;
this.reqHeight = reqHeight;
this.totalTime = 0;
}
#Override
public void addFrame(Drawable frame, int duration) {
super.addFrame(frame, duration);
totalTime += duration;
}
#Override
public void start() {
super.start();
new Handler().postDelayed(new Runnable() {
public void run() {
onAnimationFinish();
}
}, totalTime);
}
public int getTotalTime() {
return totalTime;
}
#Override
public void draw(Canvas canvas) {
try {
//Loading image from assets, you could make it from resources
Bitmap bmp = BitmapFactory.decodeStream(context.getAssets().open("presentation/intro_000"+(current < 10 ? "0"+current : current)+".jpg"));
//Scaling image to fitCenter
Matrix m = new Matrix();
m.setRectToRect(new RectF(0, 0, bmp.getWidth(), bmp.getHeight()), new RectF(0, 0, reqWidth, reqHeight), Matrix.ScaleToFit.CENTER);
bmp = Bitmap.createBitmap(bmp, 0, 0, bmp.getWidth(), bmp.getHeight(), m, true);
//Calculating the start 'x' and 'y' to paint the Bitmap
int x = (reqWidth - bmp.getWidth()) / 2;
int y = (reqHeight - bmp.getHeight()) / 2;
//Painting Bitmap in canvas
canvas.drawBitmap(bmp, x, y, null);
//Jump to next item
current++;
} catch (IOException e) {
e.printStackTrace();
}
}
abstract void onAnimationFinish();
}
Now to play animation you need to do what next
//Get your ImageView
View image = MainActivity.this.findViewById(R.id.presentation);
//Create AnimationDrawable
final AnimationDrawable animation = new MyAnimationDrawable(this, displayMetrics.widthPixels, displayMetrics.heightPixels) {
#Override
void onAnimationFinish() {
//Do something when finish animation
}
};
animation.setOneShot(true); //dont repeat animation
//This is just to say that my AnimationDrawable has 72 frames with 50 milliseconds interval
try {
//Always load same bitmap, anyway you load the right one in draw() method in MyAnimationDrawable
Bitmap bmp = BitmapFactory.decodeStream(MainActivity.this.getAssets().open("presentation/intro_00000.jpg"));
for (int i = 0; i < 72; i++) {
animation.addFrame(new BitmapDrawable(getResources(), bmp), 50);
}
} catch (IOException e) {
e.printStackTrace();
}
//Set AnimationDrawable to ImageView
if (Build.VERSION.SDK_INT < 16) {
image.setBackgroundDrawable(animation);
} else {
image.setBackground(animation);
}
//Start animation
image.post(new Runnable() {
#Override
public void run() {
animation.start();
}
});
That is all, and works OK form me!!!
Try using an AnimationDrawable for your ImageView. See my answer here for example.
The most efficient way to show fram by frame animation in android is using OpenGL with NDK.
Create an imageView using programming and then rotate it with an angle and then make another one and then rotate it.. do it for number of imageView you want to show. you only have to add only one image for it.
you can rotate an image like this..
Matrix matrix=new Matrix();
imageView.setScaleType(ScaleType.MATRIX); //required
matrix.postRotate((float) angle, pivX, pivY);
imageView.setImageMatrix(matrix);
Maybe stupid answer but it may help you. Make the animation using another tool and save it as a high quality video and then just play the video.
Try exporting your images to a GIF? Android does support decoding GIF files.
Have a look : Supported Media Formats.
Loading the bitmaps from assets instead of resources saved 40% decoding time for me. You might want to try that.
So this went from 5 seconds with resources to 3 seconds on your pictures on my 2012 Nexus 7:
long time = System.currentTimeMillis();
try {
for (int i = 0; i < 35; i++) {
InputStream assetStream = getAssets().open(
"_step" + (i + 1) + ".png");
try {
Bitmap bitmap = BitmapFactory.decodeStream(assetStream);
if (bitmap == null) {
throw new RuntimeException("Could not load bitmap");
}
mBitmaps.add(bitmap);
} finally {
assetStream.close();
}
}
} catch (Exception e) {
throw new RuntimeException(e);
}
Log.d("ANIM", "Loading bitmaps elapsed "+(System.currentTimeMillis() - time)+"ms");
I want to display animated GIF images in my aplication.
As I found out the hard way Android doesn't support animated GIF natively.
However it can display animations using AnimationDrawable:
Develop > Guides > Images & Graphics > Drawables Overview
The example uses animation saved as frames in application resources but what I need is to display animated gif directly.
My plan is to break animated GIF to frames and add each frame as drawable to AnimationDrawable.
Does anyone know how to extract frames from animated GIF and convert each of them into Drawable?
Android actually can decode and display animated GIFs, using android.graphics.Movie class.
This is not too much documented, but is in SDK Reference. Moreover, it is used in Samples in ApiDemos in BitmapDecode example with some animated flag.
UPDATE:
Use glide:
dependencies {
implementation 'com.github.bumptech.glide:glide:4.9.0'
}
usage:
Glide.with(context).load(GIF_URI).into(new DrawableImageViewTarget(IMAGE_VIEW));
see docs
also put (main/assets/htmls/name.gif) [with this html adjust to the size]
<html style="margin: 0;">
<body style="margin: 0;">
<img src="name.gif" style="width: 100%; height: 100%" />
</body>
</html>
declare in your Xml for example like this (main/res/layout/name.xml): [you define the size, for example]
<WebView
android:layout_width="70dp"
android:layout_height="70dp"
android:id="#+id/webView"
android:layout_gravity="center_horizontal" />
in your Activity put the next code inside of onCreate
web = (WebView) findViewById(R.id.webView);
web.setBackgroundColor(Color.TRANSPARENT); //for gif without background
web.loadUrl("file:///android_asset/htmls/name.html");
if you want load dynamically you have to load the webview with data:
// or "[path]/name.gif" (e.g: file:///android_asset/name.gif for resources in asset folder), and in loadDataWithBaseURL(), you don't need to set base URL, on the other hand, it's similar to loadData() method.
String gifName = "name.gif";
String yourData = "<html style=\"margin: 0;\">\n" +
" <body style=\"margin: 0;\">\n" +
" <img src=" + gifName + " style=\"width: 100%; height: 100%\" />\n" +
" </body>\n" +
" </html>";
// Important to add this attribute to webView to get resource from outside.
webView.getSettings().setAllowFileAccess(true);
// Notice: should use loadDataWithBaseURL. BaseUrl could be the base url such as the path to asset folder, or SDCard or any other path, where your images or the other media resides related to your html
webView.loadDataWithBaseURL("file:///android_asset/", yourData, "text/html", "utf-8", null);
// Or if you want to load image from SD card or where else, here is the idea.
String base = Environment.getExternalStorageDirectory().getAbsolutePath().toString();
webView.loadDataWithBaseURL(base + '/', yourData, "text/html", "utf-8", null);
suggestion: is better load gif with static images for more information check https://developer.android.com/reference/android/graphics/drawable/AnimationDrawable.html
That's it, I hope you help.
Currently we can use Glide https://github.com/bumptech/glide
I solved the problem by splitting gif animations into frames before saving it to phone, so I would not have to deal with it in Android.
Then I download every frame onto phone, create Drawable from it and then create AnimationDrawable - very similar to example from my question
i found a very easy way, with a nice and simple working example here
display animated widget
Before getting it working there are some chages to do do in the code
IN THE FOLLOWING
#Override
public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceStated);
setContentView(new MYGIFView());
}
}
just replace
setContentView(new MYGIFView());
in
setContentView(new MYGIFView(this));
AND IN
public GIFView(Context context) {
super(context);
Provide your own gif animation file
is = context.getResources().openRawResource(R.drawable.earth);
movie = Movie.decodeStream(is);
}
REPLACE THE FIRST LINE IN
public MYGIFView(Context context) {
according to the name of the class...
after done this little changes it should work as for me...
hope this help
Glide 4.6
1. To Load gif
GlideApp.with(context)
.load(R.raw.gif) // or url
.into(imageview);
2. To get the file object
GlideApp.with(context)
.asGif()
.load(R.raw.gif) //or url
.into(new SimpleTarget<GifDrawable>() {
#Override
public void onResourceReady(#NonNull GifDrawable resource, #Nullable Transition<? super GifDrawable> transition) {
resource.start();
//resource.setLoopCount(1);
imageView.setImageDrawable(resource);
}
});
Ways to show animated GIF on Android:
Movie class. As mentioned above, it's fairly buggy.
WebView. It's very simple to use and usually works. But sometimes it starts to misbehave, and it's always on some obscure devices you don't have. Plus, you can’t use multiple instances in any kind of list views, because it does things to your memory. Still, you might consider it as a primary approach.
Custom code to decode gifs into bitmaps and show them as Drawable or ImageView. I'll mention two libraries:
https://github.com/koral--/android-gif-drawable - decoder is implemented in C, so it's very efficient.
https://code.google.com/p/giffiledecoder - decoder is implemented in Java, so it's easier to work with. Still reasonably efficient, even with large files.
You'll also find many libraries based on GifDecoder class. That's also a Java-based decoder, but it works by loading the entire file into memory, so it's only applicable to small files.
I had a really hard time to have animated gif working in Android. I only had following two working:
WebView
Ion
WebView works OK and really easy, but the problem is it makes the view loads slower and the app would be unresponsive for a second or so. I did not like that. So I have tried different approaches (DID NOT WORK):
ImageViewEx is deprecated!
picasso did not load animated gif
android-gif-drawable looks great, but it caused some wired NDK issues in my project. It caused my local NDK library stop working, and I was not able to fix it
I had some back and forth with Ion; Finally, I have it working, and it is really fast :-)
Ion.with(imgView)
.error(R.drawable.default_image)
.animateGif(AnimateGifMode.ANIMATE)
.load("file:///android_asset/animated.gif");
Glide
Image Loader Library for Android, recommended by Google.
Glide is quite similar to Picasso but this is much faster than Picasso.
Glide consumes less memory than Picasso.
What that Glide has but Picasso doesn't
An ability to load GIF Animation to a simple ImageView might be the most interesting feature of Glide. And yes, you can't do that with Picasso.
Some important links-
https://github.com/bumptech/glide
http://inthecheesefactory.com/blog/get-to-know-glide-recommended-by-google/en
Use ImageViewEx, a library that makes using a gif as easy as using an ImageView.
Try this, bellow code display gif file in progressbar
loading_activity.xml(in Layout folder)
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#ffffff" >
<ProgressBar
android:id="#+id/progressBar"
style="?android:attr/progressBarStyleLarge"
android:layout_width="70dp"
android:layout_height="70dp"
android:layout_centerHorizontal="true"
android:layout_centerVertical="true"
android:indeterminate="true"
android:indeterminateDrawable="#drawable/custom_loading"
android:visibility="gone" />
</RelativeLayout>
custom_loading.xml(in drawable folder)
here i put black_gif.gif(in drawable folder), you can put your own gif here
<?xml version="1.0" encoding="utf-8"?>
<animated-rotate xmlns:android="http://schemas.android.com/apk/res/android"
android:drawable="#drawable/black_gif"
android:pivotX="50%"
android:pivotY="50%" />
LoadingActivity.java(in res folder)
public class LoadingActivity extends Activity {
ProgressBar bar;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_loading);
bar = (ProgressBar) findViewById(R.id.progressBar);
bar.setVisibility(View.VISIBLE);
}
}
Nobody has mentioned the Ion or Glide library. they work very well.
It's easier to handle compared to a WebView.
I have had success with the solution proposed within this article, a class called GifMovieView, which renders a View which can then be displayed or added to a specific ViewGroup. Check out the other methods presented in parts 2 and 3 of the specified article.
The only drawback to this method is that the antialiasing on the movie is not that good (must be a side-effect of using the "shady" Android Movie Class). You are then better off setting the background to a solid color within your animated GIF.
Some thoughts on the BitmapDecode example... Basically it uses the ancient, but rather featureless Movie class from android.graphics.
On recent API versions you need to turn off hardware acceleration, as described here. It was segfaulting for me otherwise.
<activity
android:hardwareAccelerated="false"
android:name="foo.GifActivity"
android:label="The state of computer animation 2014">
</activity>
Here is the BitmapDecode example shortened with only the GIF part. You have to make your own Widget (View) and draw it by yourself. Not quite as powerful as an ImageView.
import android.app.Activity;
import android.content.Context;
import android.graphics.*;
import android.os.*;
import android.view.View;
public class GifActivity extends Activity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(new GifView(this));
}
static class GifView extends View {
Movie movie;
GifView(Context context) {
super(context);
movie = Movie.decodeStream(
context.getResources().openRawResource(
R.drawable.some_gif));
}
#Override
protected void onDraw(Canvas canvas) {
if (movie != null) {
movie.setTime(
(int) SystemClock.uptimeMillis() % movie.duration());
movie.draw(canvas, 0, 0);
invalidate();
}
}
}
}
2 other methods, one with ImageView another with WebView can be found in this fine tutorial. The ImageView method uses the Apache licensed android-gifview from Google Code.
#PointerNull gave good solution, but it is not perfect. It doesn't work on some devices with big files and show buggy Gif animation with delta frames on pre ICS version.
I found solution without this bugs. It is library with native decoding to drawable: koral's android-gif-drawable.
For only android API (Android Pie)28 and + use AnimatedImageDrawable as
// ImageView from layout
val ima : ImageView = findViewById(R.id.img_gif)
// create AnimatedDrawable
val decodedAnimation = ImageDecoder.decodeDrawable(
// create ImageDecoder.Source object
ImageDecoder.createSource(resources, R.drawable.tenor))
// set the drawble as image source of ImageView
ima.setImageDrawable(decodedAnimation)
// play the animation
(decodedAnimation as? AnimatedImageDrawable)?.start()
XML code, add a ImageView
<ImageView
android:id="#+id/img_gif"
android:background="#drawable/ic_launcher_background" <!--Default background-->
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
android:layout_width="200dp"
android:layout_height="200dp" />
AnimatedImageDrawable is a child of Drawable and created by ImageDecoder.decodeDrawable
ImageDecoder.decodeDrawable which further required the instance of ImageDecoder.Source created by ImageDecoder.createSource.
ImageDecoder.createSource can only take source as a name, ByteBuffer, File, resourceId, URI, ContentResolver to create source object and uses it to create AnimatedImageDrawable as Drawable (polymorphic call)
static ImageDecoder.Source createSource(AssetManager assets, String fileName)
static ImageDecoder.Source createSource(ByteBuffer buffer)
static ImageDecoder.Source createSource(File file)
static ImageDecoder.Source createSource(Resources res, int resId)
static ImageDecoder.Source createSource(ContentResolver cr, Uri uri)
Note: You can also create Bitmap using ImageDecoder#decodeBitmap.
Output:
AnimatedDrawable also supports resizing, frame and color manipulation
Put it into a WebView, it has to be able to display it correctly, since the default browser supports gif files. (Froyo+, if i am not mistaken)
There are two options to load animated gifs into our Android apps
1)Using Glide to load the gif into an ImageView.
String urlGif = "https://cdn.dribbble.com/users/263558/screenshots/1337078/dvsd.gif";
//add Glide implementation into the build.gradle file.
ImageView imageView = (ImageView)findViewById(R.id.imageView);
Uri uri = Uri.parse(urlGif);
Glide.with(getApplicationContext()).load(uri).into(imageView);
2) Using an html to load the gif into a WebView
Create the html with the address to the .gif file:
<html style="margin: 0;">
<body style="margin: 0;">
<img src="https://..../myimage.gif" style="width: 100%; height: 100%" />
</body>
</html>
store this file into the assets directory:
The load this html into the WebView of your application:
WebView webView = (WebView)findViewById(R.id.webView);
webView = (WebView) findViewById(R.id.webView);
webView.loadUrl("file:///android_asset/html/webpage_gif.html");
Heres is a complete example of this two options.
I think the better library to handle gif files is this one: by koral
Used it and i'm successful and this library is dedicated to GIF'S; but where as the picasso and glide are general purpose image framework; so i think the developers of this library have entirely concentrated on gif files
Use fresco. Here's how to do it:
http://frescolib.org/docs/animations.html
Here's the repo with the sample:
https://github.com/facebook/fresco/tree/master/samples/animation
Beware fresco does not support wrap content!
Just wanted to add that the Movie class is now deprecated.
This class was deprecated in API level P.
It is recommended to use this
AnimatedImageDrawable
Drawable for drawing animated images (like GIF).
Similar to what #Leonti said, but with a little more depth:
What I did to solve the same problem was open up GIMP, hide all layers except for one, export it as its own image, and then hide that layer and unhide the next one, etc., until I had individual resource files for each one. Then I could use them as frames in the AnimationDrawable XML file.
Something I did for showing gifs in apps. I extended ImageView so people can use its attributes freely. It can show gifs from url or from the assets directory.
The library also makes it easy for extending classes to inherit from it and extend it to support different methods to initialize the gif.
https://github.com/Gavras/GIFView
There's a little guide on the github page.
It was also published on Android Arsenal:
https://android-arsenal.com/details/1/4947
Use example:
From XML:
<com.whygraphics.gifview.gif.GIFView xmlns:gif_view="http://schemas.android.com/apk/res-auto"
android:id="#+id/main_activity_gif_vie"
android:layout_width="200dp"
android:layout_height="200dp"
android:scaleType="center"
gif_view:gif_src="url:http://pop.h-cdn.co/assets/16/33/480x264/gallery-1471381857-gif-season-2.gif" />
In the activity:
GIFView mGifView = (GIFView) findViewById(R.id.main_activity_gif_vie);
mGifView.setOnSettingGifListener(new GIFView.OnSettingGifListener() {
#Override
public void onSuccess(GIFView view, Exception e) {
Toast.makeText(MainActivity.this, "onSuccess()", Toast.LENGTH_SHORT).show();
}
#Override
public void onFailure(GIFView view, Exception e) {
}
});
Setting the gif programmatically:
mGifView.setGifResource("asset:gif1");
Easiest way - Can be consider the below code
We can take advantage of Imageview setImageResource , refer below code for the same.
The below code can be used to show the image like gif incase if you have the multiple split image of gif. Just split the gif into individual png from a online tool and put image in the drawable like the below order
image_1.png, image_2.png, etc.
Have the handler to change the image dynamically.
int imagePosition = 1;
Handler handler = new Handler();
Runnable runnable = new Runnable() {
public void run() {
updateImage();
}
};
public void updateImage() {
appInstance.runOnUiThread(new Runnable() {
#Override
public void run() {
int resId = getResources().getIdentifier("image_" + imagePosition, "drawable", appInstance.getPackageName());
gifImageViewDummy.setImageResource(resId);
imagePosition++;
//Consider you have 30 image for the anim
if (imagePosition == 30) {
//this make animation play only once
handler.removeCallbacks(runnable);
} else {
//You can define your own time based on the animation
handler.postDelayed(runnable, 50);
}
//to make animation to continue use below code and remove above if else
// if (imagePosition == 30)
//imagePosition = 1;
// handler.postDelayed(runnable, 50);
//
}
});
}
The easy way to display animated GIF directly from URL to your app layout is to use WebView class.
Step 1:
In your layout XML
<WebView
android:id="#+id/webView"
android:layout_width="50dp"
android:layout_height="50dp"
/>
Step 2: In your Activity
WebView wb;
wb = (WebView) findViewById(R.id.webView);
wb.loadUrl("https://.......);
Step 3: In your Manifest.XML make Internet permission
<uses-permission android:name="android.permission.INTERNET" />
Step 4: In case you want to make your GIF background transparent and make GIF fit to your Layout
wb.setBackgroundColor(Color.TRANSPARENT);
wb.getSettings().setLoadWithOverviewMode(true);
wb.getSettings().setUseWideViewPort(true);
If you want to use Glide for loading gif:
Glide.with(this)
.asGif()
.load(R.raw.onboarding_layers) //Your gif resource
.apply(RequestOptions.diskCacheStrategyOf(DiskCacheStrategy.NONE))
.listener(new RequestListener<GifDrawable>() {
#Override
public boolean onLoadFailed(#Nullable #org.jetbrains.annotations.Nullable GlideException e, Object model, Target<GifDrawable> target, boolean isFirstResource) {
return false;
}
#Override
public boolean onResourceReady(GifDrawable resource, Object model, Target<GifDrawable> target, DataSource dataSource, boolean isFirstResource) {
resource.setLoopCount(1);
return false;
}
})
.into((ImageView) view.findViewById(R.id.layer_icons));
To save resources there is glide library for.
Have no idea why to use anything else, especialy webview to show image only.
Glide is perfect and easy library that prepares animated drawable from gif and put it directly to imageview.
The logic of gifdrawable handle animation itself.
Gif have lzw ziped raw rgb data of an animation inside.
There is no reason for complicated usage of webview and manage more files to show just a gif file in app.
First of all the Android browser should support Animated GIFs. If it doesn't then it's a bug! Have a look at the issue trackers.
If you're displaying these animated GIFs outside of a browser it might be a different story. To do what you're asking would require external library that supports the decoding of Animated GIFs.
The first port of call would be to look at Java2D or JAI (Java Advanced Imaging) API, although I would be very surprised if Android Dalvik would support those libraries in your App.
public class Test extends GraphicsActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(new SampleView(this));
}
private static class SampleView extends View {
private Bitmap mBitmap;
private Bitmap mBitmap2;
private Bitmap mBitmap3;
private Bitmap mBitmap4;
private Drawable mDrawable;
private Movie mMovie;
private long mMovieStart;
// Set to false to use decodeByteArray
private static final boolean DECODE_STREAM = true;
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 SampleView(Context context) {
super(context);
setFocusable(true);
java.io.InputStream is;
is = context.getResources().openRawResource(R.drawable.icon);
BitmapFactory.Options opts = new BitmapFactory.Options();
Bitmap bm;
opts.inJustDecodeBounds = true;
bm = BitmapFactory.decodeStream(is, null, opts);
// now opts.outWidth and opts.outHeight are the dimension of the
// bitmap, even though bm is null
opts.inJustDecodeBounds = false; // this will request the bm
opts.inSampleSize = 4; // scaled down by 4
bm = BitmapFactory.decodeStream(is, null, opts);
mBitmap = bm;
// decode an image with transparency
is = context.getResources().openRawResource(R.drawable.icon);
mBitmap2 = BitmapFactory.decodeStream(is);
// create a deep copy of it using getPixels() into different configs
int w = mBitmap2.getWidth();
int h = mBitmap2.getHeight();
int[] pixels = new int[w * h];
mBitmap2.getPixels(pixels, 0, w, 0, 0, w, h);
mBitmap3 = Bitmap.createBitmap(pixels, 0, w, w, h,
Bitmap.Config.ARGB_8888);
mBitmap4 = Bitmap.createBitmap(pixels, 0, w, w, h,
Bitmap.Config.ARGB_4444);
mDrawable = context.getResources().getDrawable(R.drawable.icon);
mDrawable.setBounds(150, 20, 300, 100);
is = context.getResources().openRawResource(R.drawable.animated_gif);
if (DECODE_STREAM) {
mMovie = Movie.decodeStream(is);
} else {
byte[] array = streamToBytes(is);
mMovie = Movie.decodeByteArray(array, 0, array.length);
}
}
#Override
protected void onDraw(Canvas canvas) {
canvas.drawColor(0xFFCCCCCC);
Paint p = new Paint();
p.setAntiAlias(true);
canvas.drawBitmap(mBitmap, 10, 10, null);
canvas.drawBitmap(mBitmap2, 10, 170, null);
canvas.drawBitmap(mBitmap3, 110, 170, null);
canvas.drawBitmap(mBitmap4, 210, 170, null);
mDrawable.draw(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 = 1000;
}
int relTime = (int) ((now - mMovieStart) % dur);
mMovie.setTime(relTime);
mMovie.draw(canvas, getWidth() - mMovie.width(), getHeight()
- mMovie.height());
invalidate();
}
}
}
}
class GraphicsActivity extends Activity {
// set to true to test Picture
private static final boolean TEST_PICTURE = false;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
#Override
public void setContentView(View view) {
if (TEST_PICTURE) {
ViewGroup vg = new PictureLayout(this);
vg.addView(view);
view = vg;
}
super.setContentView(view);
}
}
class PictureLayout extends ViewGroup {
private final Picture mPicture = new Picture();
public PictureLayout(Context context) {
super(context);
}
public PictureLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
#Override
public void addView(View child) {
if (getChildCount() > 1) {
throw new IllegalStateException(
"PictureLayout can host only one direct child");
}
super.addView(child);
}
#Override
public void addView(View child, int index) {
if (getChildCount() > 1) {
throw new IllegalStateException(
"PictureLayout can host only one direct child");
}
super.addView(child, index);
}
#Override
public void addView(View child, LayoutParams params) {
if (getChildCount() > 1) {
throw new IllegalStateException(
"PictureLayout can host only one direct child");
}
super.addView(child, params);
}
#Override
public void addView(View child, int index, LayoutParams params) {
if (getChildCount() > 1) {
throw new IllegalStateException(
"PictureLayout can host only one direct child");
}
super.addView(child, index, params);
}
#Override
protected LayoutParams generateDefaultLayoutParams() {
return new LayoutParams(LayoutParams.MATCH_PARENT,
LayoutParams.MATCH_PARENT);
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
final int count = getChildCount();
int maxHeight = 0;
int maxWidth = 0;
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
if (child.getVisibility() != GONE) {
measureChild(child, widthMeasureSpec, heightMeasureSpec);
}
}
maxWidth += getPaddingLeft() + getPaddingRight();
maxHeight += getPaddingTop() + getPaddingBottom();
Drawable drawable = getBackground();
if (drawable != null) {
maxHeight = Math.max(maxHeight, drawable.getMinimumHeight());
maxWidth = Math.max(maxWidth, drawable.getMinimumWidth());
}
setMeasuredDimension(resolveSize(maxWidth, widthMeasureSpec),
resolveSize(maxHeight, heightMeasureSpec));
}
private void drawPict(Canvas canvas, int x, int y, int w, int h, float sx,
float sy) {
canvas.save();
canvas.translate(x, y);
canvas.clipRect(0, 0, w, h);
canvas.scale(0.5f, 0.5f);
canvas.scale(sx, sy, w, h);
canvas.drawPicture(mPicture);
canvas.restore();
}
#Override
protected void dispatchDraw(Canvas canvas) {
super.dispatchDraw(mPicture.beginRecording(getWidth(), getHeight()));
mPicture.endRecording();
int x = getWidth() / 2;
int y = getHeight() / 2;
if (false) {
canvas.drawPicture(mPicture);
} else {
drawPict(canvas, 0, 0, x, y, 1, 1);
drawPict(canvas, x, 0, x, y, -1, 1);
drawPict(canvas, 0, y, x, y, 1, -1);
drawPict(canvas, x, y, x, y, -1, -1);
}
}
#Override
public ViewParent invalidateChildInParent(int[] location, Rect dirty) {
location[0] = getLeft();
location[1] = getTop();
dirty.set(0, 0, getWidth(), getHeight());
return getParent();
}
#Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
final int count = super.getChildCount();
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
if (child.getVisibility() != GONE) {
final int childLeft = getPaddingLeft();
final int childTop = getPaddingTop();
child.layout(childLeft, childTop,
childLeft + child.getMeasuredWidth(),
childTop + child.getMeasuredHeight());
}
}
}
}
as the question, I use ImageSpan to add a image into TextView. but it can't animate.Do you have any advise?
I try to extend AnimationDrawable to add drawable into ImageSpan. but it doesn't work
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();
}
}
I would try to either:
Split the animated image (presumably a .gif file?) into separate frames and combine those into an AnimationDrawable that you then pass to the ImageSpan's constructor.
Subclass ImageSpan and override the onDraw() method to add your own logic to draw the different frames based on some sort of timer. There's an api demo that illustrates how to use the Movie class to load up an animated gif that might be worth looking into.
Big Edit:
Alright, sorry for not getting back earlier, but I had to set aside some time to investigate this myself. I've had a play with it since I'll probably be needing a solution for this myself for one of my future projects. Unfortunately, I ran into similar problems with using an AnimationDrawable, which seems to be caused by the caching mechanism that DynamicDrawableSpan (an indirect superclass of ImageSpan) uses.
Another issue for me is that there does not appear to be a straightforward wat to invalidate a Drawable, or ImageSpan. Drawable actually has invalidateDrawable(Drawable) and invalidateSelf() methods, but the first did not have any effect in my case, whereas the latter only works if some magical Drawable.Callback is attached. I couldn't find any decent documentation on how to use this...
So, I went a step further up the logic tree to solve the problem. I have to add a warning in advance that this is most likely not an optimal solution, but for now it's the only one I was able to get to work. You probably won't run into problems if you use my solution sporadically, but I'd avoid filling the whole screen with emoticons by all means. I'm not sure what would happen, but then again, I probably don't even want to know.
Without further ado, here's the code. I added some comments to make it self-explanatory. It's quite likely a used a different Gif decoding class/libary, but it should work with about any out there.
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();
}
}
Usage:
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);
As you can see I used a Handler to provide the 'ticks' to advance to the next frame. The advantage of this is that it will only fire off an update whenever a new frame should be rendered. The actual redrawing is done by invalidating the TextView which contains the AnimatedImageSpan. At the same time the drawback is that whenever you have a bunch of animated gifs in the same TextView (or multiple for that matter), the views might be updated like crazy... Use it wisely. :)
You can use ObjectAnimator to animate the drawable in the ImageSpan
http://developer.android.com/reference/android/animation/ObjectAnimator.html