GIF是什么
GIF(图形交换格式)的原义是“图像互换格式”,是CompuServe公司公司在1987年开发的图像文件格式。
GIF文件的数据,是一种基于LZW算法的连续色调的无损压缩格式。其压缩率一般在50%左右,它不属于任何应用程序。
跨平台
GIF的特点
GIF格式的特点是其在一个GIF文件中可以存多幅彩色图像,如果把存于一个文件中的多幅图像数据逐幅读出并显示到屏幕上,就可构成一种最简单的动画。
多张静态图组成简单动画
GIF的版本
GIF主要分为两个版本,即GIF 89a和GIF 87a
GIF 87a:是在1987年制定的版本
GIF 89a:是1989年制定的版本。在这个版本中,为GIF文档扩充了图形控制区块,备注,说明,应用程序编程接口等四个区块,并提供了对透明色和多帧动画的支持
如何在安卓上实现GIF
方法1:电影类
安卓我们提供了一个方便的工具:android.graphics.Movie
package android.graphics;直接继承自Object,直接继承自Object的基本上都是工具类。
Android的ImageView无法直接加载Gif图片,通过Android中的电影类把一个gif图片当作一个原始的资源加载到电影,然后电影将其解析为电影帧进行加载。电影其实管理着GIF动画中的多个帧,只需要通过setTime()一下就可以让它在draw()的时候绘出相应的那帧图像,通过当前时间与duration之间的换算关系,便可实现GIF动起来的效果。对于比较小的GIF图片使用此方法还是可以的,要是大的话,建议还是把GIF图片转换成一帧一帧的图片为png,然后通过动画播放。
简单的利用电影播放GIF图的控件
GifView.java自定义gif类
import java.io.File;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Movie;
import android.os.Build;
import android.os.SystemClock;
import android.view.View;
/**
* 自定义可以循环播放gif动画的View,可以像使用其他控件一样使用
**/
public class GifView extends View {
private int mResId;
private Movie mMovie;
private long mStartTime;
public GifView(Context context) {
super(context);
////android sdk>16 必须关闭硬件加速
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
setLayerType(View.LAYER_TYPE_SOFTWARE, null);
}
}
//设置展示这个gif的viewgroup的id 在使用的使用调用
public void setMovieResource(int resId) {
mResId = resId;
//创建Movie对象
mMovie = Movie.decodeStream(getResources().openRawResource(resId));
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if (mMovie != null) {
//设置viewgroup的宽高为gif图片的宽高
setMeasuredDimension(mMovie.width(), mMovie.height());
} else {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (mMovie != null) {
//动画开始的时间
long now = SystemClock.uptimeMillis();
//第一次播放
if (mStartTime == 0) {
mStartTime = now;
}
//动画持续的时间,也就是完成一次动画的时间
int dur = mMovie.duration();
if (dur == 0) {
dur = 1000;
}
//注意这是取余操作,这才能算出当前这次重复播放的第一帧的时间
//假设有10帧的gif,第一帧为100ms now=100 mStartTime=100 则取余播放第0帧图片
//第二帧now=200 mStartTime=100 取余则播放第1帧的图片
int time = (int)((now - mStartTime) % dur);
//设置相对本次播放第一帧时间,根据这个时间来决定显示第几帧
mMovie.setTime(time);
mMovie.draw(canvas, 0, 0);
//会驱动View的刷新,view一刷新就好调用此方法
invalidate();
}
}
}
ManiActivity.java中调用 注意要在res文件夹下创建raw文件来放置你的gif图片
import android.app.Activity;
import android.view.ViewGroup;
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
GifView gifView = new GifView(this);
//这是放gif的ViewGroup控件 可以说relativelayout等
((ViewGroup)findViewById(R.id.layout_holder)).addView(gifView);
gifView.setMovieResource(R.raw.ppt);
}
这是activity_main.xml中中
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/layout_holder"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context=".MainActivity" >
</RelativeLayout>
完成。
加载网络的GIF图片
1.添加网络权限
<uses-permission android:name="android.permission.INTERNET" />
2.(android 6.0之后不提供org.apache.http。*)在项目的build.gradle中添加否则导不了包
android {
compileSdkVersion 28
defaultConfig {
...
useLibrary 'org.apache.http.legacy'
...
}
3.创建GifDownload.java类下载gif回调给主界面
import android.util.Log;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.DefaultHttpClient;
import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
/**
* <p>文件描述:<p>
* <p>作者:Mr-Donkey<p>
* <p>创建时间:2018/12/6 15:04<p>
* <p>更改时间:2018/12/6 15:04<p>
* <p>版本号:1<p>
*/
public class GifDownload extends Thread {
private String mUrl;
private DownloadListener mListener;
//传入回调对象的构造
public GifDownload(String url, DownloadListener listener) {
mUrl = url;
mListener = listener;
}
@Override
public void run() {
//新建文件夹存放下载后的图片
File file = new File("/data/data/com.example.mygif/cache/", "test.gif");
//多级目录上的父目录不存在则创建
if (!file.getParentFile().exists()) {
file.getParentFile().mkdirs();
}
//如果已经存在该文件则删除
if (file.exists()) {
file.delete();
}
/***
* 网络获取gif文件
* 传入url
*/
try {
HttpClient httpClient = new DefaultHttpClient();
HttpGet httpGet = new HttpGet(mUrl);
HttpResponse httpResponse = httpClient.execute(httpGet);
if (httpResponse.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
//如果访问成功
//创建文件输出流
FileOutputStream fileOutputStream = new FileOutputStream(file);
//拿到文件输入流读取数据
InputStream inputStream = httpResponse.getEntity().getContent();
//每次读的大小
byte[] buffer = new byte[1024 * 8];
int len = 0;
//读到-1为空
while ((len = inputStream.read(buffer)) != -1) {
//文件输出流写人数据
fileOutputStream.write(buffer, 0, len);
}
//清空缓冲区
fileOutputStream.flush();
//关闭流
fileOutputStream.close();
inputStream.close();
}
//关闭网络连接
httpClient.getConnectionManager().shutdown();
} catch (Exception exception) {
Log.e("GifDownload", "run", exception);
}
//下载完成之后回调给主界面
mListener.onFinish(file);
}
//回调接口
public static interface DownloadListener {
void onFinish(File file);
}
}
4.GiifView.java中多加了一个setFile方法
//直接设置文件来构造电影
public void setFile(File file) { mMovie = Movie.decodeFile(file.getAbsolutePath()); }
import java.io.File;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Movie;
import android.os.Build;
import android.os.SystemClock;
import android.view.View;
/**
* 自定义可以循环播放gif动画的View,可以像使用其他控件一样使用
**/
public class GifView extends View {
private int mResId;
private Movie mMovie;
private long mStartTime;
public GifView(Context context) {
super(context);
////android sdk>16 必须关闭硬件加速
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
setLayerType(View.LAYER_TYPE_SOFTWARE, null);
}
}
//设置展示这个gif的viewgroup的id 在使用的使用调用
public void setMovieResource(int resId) {
mResId = resId;
//创建Movie对象
mMovie = Movie.decodeStream(getResources().openRawResource(resId));
}
//直接设置file来构造Movie
public void setFile(File file) {
mMovie = Movie.decodeFile(file.getAbsolutePath());
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if (mMovie != null) {
//设置viewgroup的宽高为gif图片的宽高
setMeasuredDimension(mMovie.width(), mMovie.height());
} else {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (mMovie != null) {
//动画开始的时间
long now = SystemClock.uptimeMillis();
//第一次播放
if (mStartTime == 0) {
mStartTime = now;
}
//动画持续的时间,也就是完成一次动画的时间
int dur = mMovie.duration();
if (dur == 0) {
dur = 1000;
}
//注意这是取余操作,这才能算出当前这次重复播放的第一帧的时间
//假设有10帧的gif,第一帧为100ms now=100 mStartTime=100 则取余播放第0帧图片
//第二帧now=200 mStartTime=100 取余则播放第1帧的图片
int time = (int)((now - mStartTime) % dur);
//设置相对本次播放第一帧时间,根据这个时间来决定显示第几帧
mMovie.setTime(time);
mMovie.draw(canvas, 0, 0);
//会驱动View的刷新,view一刷新就好调用此方法
invalidate();
}
}
}
5.MainActivity.java接受下载返回的文件设置给GifView进行加载显示
import java.io.File;
import android.app.Activity;
import android.os.Bundle;
import android.view.Menu;
import android.view.ViewGroup;
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
final GifView gifView = new GifView(this);
//放gifview的容器 可以是relativelayout等
((ViewGroup)findViewById(R.id.layout_holder)).addView(gifView);
//url文件地址
new
GifDownload("https://cdn.duitang.com/uploads/item/201411/16/20141116232126_yXFLS.gif",
new GifDownload.DownloadListener() {
@Override
public void onFinish(File file) {
if (file.exists()) {
//设置资源给gifview,来展示
gifView.setFile(file);
}
}
}).start();
}
}
多张静态图实现轮放(实现GIF效果)
GIF的原理也是多张静态图显示,来实现一种动画的效果
1.添加SD卡读取权限(图片是放在手机上,具体需求具体加权限)
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
2.布局文件activity_main.xml装gifView的容器
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/layout_holder"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context=".MainActivity" >
</RelativeLayout>
3.GifView的实现
控制每一帧每一帧显示,实现轮播
/**
* <p>文件描述:<p>
* <p>作者:Mr-Donkey<p>
* <p>创建时间:2018/12/6 15:24<p>
* <p>更改时间:2018/12/6 15:24<p>
* <p>版本号:1<p>
*/
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Handler;
import android.os.Message;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.ImageView.ScaleType;
public class GifView extends FrameLayout {
private ImageView mIvImageView; //放在ImageView上显示,轮放
private String[] mImages; //图片路径集合
private int mEndIndex;//记录最后一张图片的下标
private int mRate = 1000;//设置图片播放的速度
public GifView(Context context) {
super(context);
//新建ImageView
mIvImageView = new ImageView(context);
mIvImageView.setScaleType(ScaleType.FIT_XY);
addView(mIvImageView);
}
//对外提供设置每一张图片播放的时间(速度)
public void setRate(int rate) {
mRate = rate;
}
//对外设置图片数组的资源
public void setImages(String[] images) {
mImages = images;
//最后一张图片的下标
mEndIndex = mImages.length - 1;
//先加载第一张图片
Bitmap bitmap = BitmapFactory.decodeFile(mImages[0]);
//设置父布局的宽高为图片的宽高
FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(bitmap.getWidth(),
bitmap.getHeight());
mIvImageView.setLayoutParams(params);
//把bitmap释放掉
bitmap.recycle();
}
/**对外设置播放的方法
* 一帧一帧加载图片
* 在线程中执行
*/
public void play() {
new Thread(new Runnable() {
@Override
public void run() {
int index = 0;
while (index < mEndIndex) {
//记录开始的时间
long stime = java.lang.System.currentTimeMillis();
Message message = mHandler.obtainMessage(1);
message.obj = BitmapFactory.decodeFile(mImages[index]);
mHandler.sendMessage(message);
//拿到加载完这张图片的时间间隔
//末尾-开始=间隔
long interval = java.lang.System.currentTimeMillis() - stime;
//设置需要延迟的时间(由传入的时间决定了)
long offset = mRate - interval;
if (offset > 0) {
try {
Thread.sleep(offset);
} catch (InterruptedException e) {
}
}
index++;
//如果下标等于最后一张,又重新赋值为0一直循环加载
if (index == mEndIndex) {
index = 0;
}
}
}
}).start();
}
//通过Handler实现主线程的UI更新,接受子线程发来的数据,进行显示
private Handler mHandler = new Handler() {
public void handleMessage(android.os.Message msg) {
mIvImageView.setImageBitmap((Bitmap)msg.obj);
}
};
}
4.MainActivity.java中
import java.io.File;
import android.app.Activity;
import android.os.Bundle;
import android.view.Menu;
import android.view.ViewGroup;
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
GifView gifView = new GifView(this);
((ViewGroup)findViewById(R.id.layout_holder)).addView(gifView);
//图片所在的路径
File dir = new File("/storage/emulated/0/images/");
File[] files = dir.listFiles();
String[] images = new String[files.length];
for (int i = 0; i < images.length; i++) {
//拿到图片的绝对路径,存放到images中来
images[i] = files[i].getAbsolutePath();
}
//gifview设置image数据源(路径)
gifView.setImages(images);
//每一图片显示的时间
gifView.setRate(200);
//开启图片轮播
gifView.play();
}
}
思考:单个gif文件和多张静态图的gif优缺点?
单个gif优点:
- 优点:单文件直接播放使用,逻辑简单;
- 缺点: 文件大,图片质量低
多张静态图优点:
- 优点:整体文件小,图片质量高;
- 缺点:代码逻辑复杂
方法2:使用的GitHub上的框架的Android的GIF抽拉
以下内容来自官方:https://github.com/koral--/android-gif-drawable
通过JNI捆绑的GIFLib用于渲染帧。这种方式比应该类WebView
或更高效Movie
。
如何使用?
1,添加依赖
以下将项依赖插入build.gradle
项目的文件中。或者直接在项目结构中添加依赖
dependencies {
implementation 'pl.droidsonroids.gif:android-gif-drawable:1.2.16'
}
请注意,在整个工程的build.gradle
,添加: mavenCentral()
buildscript {
repositories {
mavenCentral()
}
}
allprojects {
repositories {
mavenCentral()
}
要求(要求)
- Android 4.2+(API级别17+)
- 用于
GifTextureView
硬件加速渲染 - 适用于
GifTexImage2D
OpenGL ES 2.0+
然后就可以在XML中使用控件了
最简单的方法是使用GifImageView
(或GifImageButton
)像普通法一样ImageView
<pl.droidsonroids.gif.GifImageView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:src="@drawable/src_anim"
android:background="@drawable/bg_anim"
/>
如果由android:src和/
或android:background
GIF文件声明的绘制,它们则自动将识别为GifDrawable
小号动画状语从句:。
如果给定绘制不是GIF,那么提到浏览普通就像ImageView
状语从句ImageButton
。
更多的实现详看官方GitHub: https ://github.com/koral--/android-gif-drawable