欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

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

程序员文章站 2023-12-04 09:02:16
前言 gif是一种很常见的动态图片格式,在android中它的使用场景非常多,大到启动页动画、小到一个loading展示,都可以用gif动画来完成,使用也很方便,直接从美...

前言

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。返回是否成功找到那一帧。

draw(canvas canvas, float , float y) 
draw(canvas canvas, float x, float y, paint paint) 

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

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

实现

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

<declare-styleable name="gifview">
    <!--gif文件引用-->
    <attr name="gifsrc" format="reference" />
    <!--是否加载完自动播放-->
    <attr name="authplay" format="boolean" />
    <!--播放次放,默认永远播放-->
    <attr name="playcount" format="integer" />
  </declare-styleable>

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

public interface onplaylistener {
    void onplaystart();

    void onplaying(int percent);

    void onplaypause(boolean pausesuccess);

    void onplayrestart();

    void onplayend();
  }

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

public class gifimageview extends appcompatimageview

然后加载gif图片资源

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大小,并重新绘制。

@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);
    }
  }

开始播放:

public void play(int counts) {
    this.counts = counts;
    reset();
    if (monplaylistener != null) {
      monplaylistener.onplaystart();
    }
    invalidate();
  }

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

@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中的哪个时间段。

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播放:

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播放:

if (mpaused && mmoviepausetime > 0) {
        mpaused = false;
        dealytime = dealytime + systemclock.uptimemillis() - mmoviepausetime;
        invalidate();
        if (monplaylistener != null) {
          monplaylistener.onplayrestart();
        }
      }
经过这些处理,我们就

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

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

进阶

倒叙播放

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

public void playreserver() {
    if (movie != null) {
      reset();
      reverse = true;
      if (monplaylistener != null) {
        monplaylistener.onplaystart();
      }
      invalidate();
    }
  }
if (reverse) {
          movie.settime(movieduration - getcurrentframetime());
        } else {
          movie.settime(getcurrentframetime());
        }

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

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

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

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

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

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