1. 程式人生 > >android進階3step4:Android 拓展學習——Gif介紹

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