android进阶3step4:Android 拓展学习——Gif介绍

GIF是什么

GIF(图形交换格式)的原义是“图像互换格式”,是CompuServe公司公司在1987年开发的图像文件格式。

GIF文件的数据,是一种基于LZW算法的连续色调的无损压缩格式。其压缩率一般在50%左右,它不属于任何应用程序。

跨平台

GIF的特点

GIF格式的特点是其在一个GIF文件中可以存多幅彩色图像,如果把存于一个文件中的多幅图像数据逐幅读出并显示到屏幕上,就可构成一种最简单的动画。

多张静态图组成简单动画

GIF的版本

GIF主要分为两个版本,即GIF 89aGIF 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

 

猜你喜欢

转载自blog.csdn.net/qq_17846019/article/details/84851913