前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Android自定义View播放Gif动画的示例

Android自定义View播放Gif动画的示例

作者头像
砸漏
发布2020-10-23 23:16:17
2K0
发布2020-10-23 23:16:17
举报
文章被收录于专栏:恩蓝脚本

前言

GIF是一种很常见的动态图片格式,在Android中它的使用场景非常多,大到启动页动画、小到一个Loading展示,都可以用GIF动画来完成,使用也很方便,直接从美工那边拿过来用就成。如果项目赶时间或者自定义原生动画太麻烦,GIF都是一个很好的选择,相比于最新的WEBP格式的动画,也有更好的兼容性(毕竟已经出现很多年了)。

关于图片加载我一直用的是Google推荐的 Glide ,图片加载和缓存都做的很好,同样也支持GIF动画。不过Glide默认就是循环播放Gif,没有开放相关的接口来控制Gif。这就使的我们不能很好地控制Gif的播放,比如控制播放开始时间、播放次数,播放暂停、播放开始、结束事件的监听,虽然用Glide可能做到(网上说可以,但我没找到方法),但操作也会很麻烦。

分析

除了第三方的库,Android自带的类 android.graphics.Movie 也可以用来加载播放Gif动画,而且实现起来很简单。

  • Movie decodeStream(InputStream is)
  • Movie decodeFile(String pathName)
  • Movie decodeByteArray(byte[] data, int offset,int length)

按来源分别可以从Gif文件的输入流,文件路径,字节数组中得到Movie的实列。然后我们可以通过操作Movie对象来操作Gif文件。

下面介绍下几个方法:

int width() movie的宽,值等于gif图片的宽,单位:px。

int height() movie的高,值等于gif图片的高,单位:px。

int duration() movie播放一次的时长,也就是gif播放一次的时长,单位:毫秒。

boolean isOpaque() Gif图片是否带透明

boolean setTime(int relativeMilliseconds) 设置movie当前处在什么时间,然后找到对应时间的图片帧,范围0 ~ duration。返回是否成功找到那一帧。

代码语言:javascript
复制
draw(Canvas canvas, float , float y) 
draw(Canvas canvas, float x, float y, Paint paint) 

在Canves中画出当前帧对应的图像。x,y对应Movie左上角在Canves中的坐标。

以上就是Movie平常会用到大部分方法,下面就利用这些自定义VIew实现播放Gif动画。

实现

首先定义一些需要的属性,用于在布局文件中设置gif

代码语言:javascript
复制
<declare-styleable name="GIFVIEW" 
    <!--gif文件引用-- 
    <attr name="gifSrc" format="reference" / 
    <!--是否加载完自动播放-- 
    <attr name="authPlay" format="boolean" / 
    <!--播放次放,默认永远播放-- 
    <attr name="playCount" format="integer" / 
  </declare-styleable 

然后定义Gifde的播放监听器,来监听各个时段的事件,都很简单就不再介绍了:

代码语言:javascript
复制
public interface OnPlayListener {
    void onPlayStart();

    void onPlaying(int percent);

    void onPlayPause(boolean pauseSuccess);

    void onPlayRestart();

    void onPlayEnd();
  }

声明类,直接继承ImageView,这样我们不仅可以显示Gif动画,也可以显示普通图片:

代码语言:javascript
复制
public class GifImageView extends AppCompatImageView

然后加载Gif图片资源

代码语言:javascript
复制
public void setGifResource(int movieResourceId, OnPlayListener onPlayListener) {
    mOnPlayListener = onPlayListener;
    movie = Movie.decodeStream(getResources().openRawResource(movieResourceId));
    if (movie == null) {
      //如果movie为空,那么就不是gif文件,尝试转换为bitmap显示
      Bitmap bitmap = BitmapFactory.decodeResource(getResources(), movieResourceId);
      if (bitmap != null) {
        setImageBitmap(bitmap);
        return;
      }
    }
    movieDuration = movie.duration() == 0 ? DEFAULT_DURATION : movie.duration();
    requestLayout();
  }

调用requestLayout重新计算View大小,并重新绘制。

代码语言:javascript
复制
@Override
  protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    if (movie != null) {
      int movieWidth = movie.width();
      int movieHeight = movie.height();
      setMeasuredDimension(movieWidth, movieHeight);
    } else {
      super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }
  }

开始播放:

代码语言:javascript
复制
public void play(int counts) {
    this.counts = counts;
    reset();
    if (mOnPlayListener != null) {
      mOnPlayListener.onPlayStart();
    }
    invalidate();
  }

不断调用onDraw方法来绘制Gif当前时间的图片帧:

代码语言:javascript
复制
@Override
  protected void onDraw(Canvas canvas) {
    if (movie != null) {
      if (!mPaused && hasStart) {
        drawMovieFrame(canvas);
        invalidateView();
      } else {
        drawMovieFrame(canvas);
      }
    } else {
      super.onDraw(canvas);
    }
  }
  /**
   * 画出gif帧
   */
  private void drawMovieFrame(Canvas canvas) {
    movie.setTime(getCurrentFrameTime());
    movie.draw(canvas, 0.0f, 0.0f);
  }

最核心的方法就是计算当前时间需要播放处于movie中的哪个时间段。

代码语言:javascript
复制
private int getCurrentFrameTime() {
    if (movieDuration == 0)
      return 0;
      //因为有暂停,所以需要减去暂停时间
    long now = SystemClock.uptimeMillis() - dealyTime;
    int nowCount = (int) ((now - mMovieStart) / movieDuration);
    if (counts != -1 && nowCount  = counts) {
      hasStart = false;
      if (mOnPlayListener != null) {
        mOnPlayListener.onPlayEnd();
      }
    }
    int currentTime = (int) ((now - mMovieStart) % movieDuration);
    int percent = currentTime * 100 / movieDuration;
    if (mOnPlayListener != null && hasStart) {
      mOnPlayListener.onPlaying(percent);
    }
    return currentTime;
  }

暂停Gif播放:

代码语言:javascript
复制
public void pause() {
    if (movie != null && !mPaused && hasStart) {
      mPaused = true;
      invalidate();
      mMoviePauseTime = SystemClock.uptimeMillis();
      if (mOnPlayListener != null) {
        mOnPlayListener.onPlayPause(true);
      }
    } else {
      if (mOnPlayListener != null) {
        mOnPlayListener.onPlayPause(false);
      }
    }
  }

继续Gif播放:

代码语言:javascript
复制
if (mPaused && mMoviePauseTime   0) {
        mPaused = false;
        dealyTime = dealyTime + SystemClock.uptimeMillis() - mMoviePauseTime;
        invalidate();
        if (mOnPlayListener != null) {
          mOnPlayListener.onPlayRestart();
        }
      }
经过这些处理,我们就

能更好地控制Gif的播放流程了。下面简单看下成品图:

进阶

倒叙播放

相信看了上面GifImageView的实现原理后,倒叙播放的实现也是很容易的。

代码语言:javascript
复制
public void playReserver() {
    if (movie != null) {
      reset();
      reverse = true;
      if (mOnPlayListener != null) {
        mOnPlayListener.onPlayStart();
      }
      invalidate();
    }
  }
代码语言:javascript
复制
if (reverse) {
          movie.setTime(movieDuration - getCurrentFrameTime());
        } else {
          movie.setTime(getCurrentFrameTime());
        }

如下图,狗子的头已经从原来的左边转到右边变成了现在的右边转到左边(ಠᴗಠ)。

像播放视频一样播放Gif动画

这部分是我在写完GifView后想到的一点进阶功能,既然我们已经实现了播放和暂停,即能控制在某个时间点播放指定的Gif图片帧,如果再加入进度条,快进等功能,那么不就能做到和视频播放器一样的功能了吗?限于篇幅,我只简单实现了进度条功能,更多功能实现请移步Github,地址: GifView 。

以上就是本文的全部内容,希望对大家的学习有所帮助。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2020-09-11 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • 分析
  • 实现
  • 进阶
相关产品与服务
云点播
面向音视频、图片等媒体,提供制作上传、存储、转码、媒体处理、媒体 AI、加速分发播放、版权保护等一体化的高品质媒体服务。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档