1. 程式人生 > >安卓載入視訊縮圖,展示於ListView中,完美實現

安卓載入視訊縮圖,展示於ListView中,完美實現

安卓獲取視訊縮圖,展示於ListView中,完美實現

應用場景:

獲取安卓手機外部儲存視訊列表,介面卡繼承至CursorAdapter,利用ViewHolder進行優化;並利用非同步載入和快取機制,在加上一個繫結TAG機制。在ListView中展示視訊某一幀的圖片,視訊名稱,視訊大小以及視訊時長。

分析說明:

在ListView中展示視訊某一幀的畫面,有以下幾種方式。

1.從媒體庫中查詢

2. android 2.2以後使用ThumbnailUtils類獲取

3.呼叫jni檔案,實現MediaMetadataRetriever類

三種方法各有利弊:

第一種方法,新視訊增加後需要SDCard重新掃描才能給新增加的檔案新增縮圖,靈活性差,而且不是很穩定,適合簡單應用

第二種方法,實現簡單,但2.2以前的版本不支援

第三種方法,實現複雜,但比較靈活
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

為了在UI介面中展示視訊縮圖不卡頓,不亂跳,不重複載入,簡單方便的前提下,我選擇第二種方式實現。

先定義一個MyVideoCursorAdapter類繼承至CursorAdapter

package cn.lsj.mypalyer.adapter;

import android.content.Context;
import android.database.Cursor;
import android.support.v4.widget.CursorAdapter;
import android.text.format.Formatter;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import cn.lsj.mypalyer.R;
import cn.lsj.mypalyer.bean.VideoBean;
import cn.lsj.mypalyer.utils.MyUtils;
import cn.lsj.mypalyer.utils.MyVideoThumbLoader;
import cn.lsj.mypalyer.view.MyImageView;

public class MyVideoCursorAdapter extends CursorAdapter {

private MyVideoThumbLoader mVideoThumbLoader;

public MyVideoCursorAdapter(Context context, Cursor c) {
    super(context, c);
    mVideoThumbLoader = new MyVideoThumbLoader();// 初始化縮圖載入方法
}

@Override
public View newView(Context context, Cursor cursor, ViewGroup parent) {

    final VideoBean vb = VideoBean.getInstance(cursor);

    View view = View.inflate(mContext, R.layout.video_list_item, null);

    ViewHolder vh = new ViewHolder();

    vh.title = (TextView) view.findViewById(R.id.video_list_item_tv_title);
    vh.duration = (TextView) view
            .findViewById(R.id.video_list_item_tv_duration);
    vh.size = (TextView) view.findViewById(R.id.video_list_item_tv_size);
    vh.iv = (MyImageView) view.findViewById(R.id.iv);

    view.setTag(vh);

    vh.iv.setTag(vb.path);
    return view;
}

@Override
public void bindView(View view, final Context context, Cursor cursor) {

    final ViewHolder vh = (ViewHolder) view.getTag();
    final VideoBean vb = VideoBean.getInstance(cursor);

    vh.title.setText(vb.title);
    vh.duration.setText(MyUtils.DurationByMs(vb.duration));
    vh.size.setText(Formatter.formatFileSize(context, vb.size));

    if (vb.duration == 0) {
        vh.iv.setImageResource(R.drawable.btn_audio_play);
    } else {

        mVideoThumbLoader.showThumbByAsynctack(vb.path, vh.iv);
    }

}

    private class ViewHolder {
        private TextView title;
        private TextView duration;
        private TextView size;
        private MyImageView iv;
    }

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74

其中的佈局才用LinearLayout佈局,左邊展示視訊縮圖,右邊展示視訊大小,中間展示視訊名稱和時長

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@drawable/main_list_selector"
android:orientation="horizontal"
android:padding="6dp" >

<!-- icon -->

<cn.lsj.mypalyer.view.MyImageView
    android:id="@+id/iv"
    android:layout_width="45dp"
    android:layout_height="45dp"
    android:src="@drawable/btn_audio_play" />
<!-- 視訊資訊 -->

<LinearLayout
    android:layout_width="0dp"
    android:layout_height="wrap_content"
    android:layout_marginLeft="10dp"
    android:layout_marginRight="6dp"
    android:layout_marginTop="6dp"
    android:layout_weight="1"
    android:orientation="vertical" >

    <!-- 檔名 -->

    <TextView
        android:id="@+id/video_list_item_tv_title"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:singleLine="true"
        android:text="檔名"
        android:textColor="@color/white"
        android:textSize="16sp" />
    <!-- 視訊時長 -->

    <TextView
        android:id="@+id/video_list_item_tv_duration"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:singleLine="true"
        android:text="時長"
        android:textColor="@color/halfwhite"
        android:textSize="14sp" />
</LinearLayout>
<!-- 檔案大小 -->

<TextView
    android:id="@+id/video_list_item_tv_size"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_marginTop="6dp"
    android:singleLine="true"
    android:text="檔案大小"
    android:textColor="@color/halfwhite"
    android:textSize="16sp" />

</LinearLayout>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61

其中的cn.lsj.mypalyer.view.MyImageView是繼承至ImageView,之所以要自定義,是因為在展示列表時,會報異常(這點很重要,後面會講)

java.lang.IllegalArgumentException: Cannot draw recycled bitmapsat 
Android.view.GLES20Canvas.drawBitmap(GLES20Canvas.java:778)at 
android.view.GLES20RecordingCanvas.drawBitmap
(GLES20RecordingCanvas.java:117)at 
android.graphics.drawable.BitmapDrawable.draw(BitmapDrawable.java:393)at 
android.widget.ImageView.onDraw(ImageView.java:979)at 
android.view.View.draw(View.java:13458)at 
android.view.View.getDisplayList(View.java:12409)at     
android.view.View.getDisplayList(View.java:12453)at 
android.view.View.draw(View.java:13182)at 
android.view.ViewGroup.drawChild(ViewGroup.java:2929)at 
android.view.ViewGroup.dispatchDraw(ViewGroup.java:2799)at 
android.view.View.draw(View.java:13461)at 
android.view.View.getDisplayList(View.java:12409)at 
android.view.View.getDisplayList(View.java:12453)at 
android.view.View.draw(View.java:13182)at   
android.view.ViewGroup.drawChild(ViewGroup.java:2929)at 
android.widget.ListView.drawChild(ListView.java:3226)at 
android.view.ViewGroup.dispatchDraw(ViewGroup.java:2799)at  
android.widget.AbsListView.dispatchDraw(AbsListView.java:2433)at 
android.widget.ListView.dispatchDraw(ListView.java:3221)at  
android.view.View.draw(View.java:13461)at android.widget.AbsListView.draw
(AbsListView.java:3759)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

為了解決這個異常,我自定義MyImageView類繼承至ImageView

package cn.lsj.mypalyer.view;

import android.content.Context;
import android.graphics.Canvas;
import android.util.AttributeSet;
import android.widget.ImageView;

public class MyImageView extends ImageView {

public MyImageView(Context context, AttributeSet attrs, int defStyle) {
    super(context, attrs, defStyle);
}

public MyImageView(Context context, AttributeSet attrs) {
    super(context, attrs);
}

public MyImageView(Context context) {
    super(context);
}

@Override
protected void onDraw(Canvas canvas) {
    try {
        super.onDraw(canvas);
    } catch (Exception e) {
        System.out.println("trying to use a recycled bitmap");
    }
}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31

接下來,需要填充資料到列表展示

設定介面卡程式碼如下:
MyVideoCursorAdapter adapter = new MyVideoCursorAdapter(mContext, null);
lv.setAdapter(adapter);
  • 1
  • 2
  • 3
請求資料:
MyQueryHandler myQueryHandler = new MyQueryHandler(
            mContext.getContentResolver());

myQueryHandler.startQuery(100, adapter, Media.EXTERNAL_CONTENT_URI,
            new String[] { Media._ID, Media.TITLE, Media.DATA,
                    Media.DURATION, Media.SIZE }, null, null, null);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
MyQueryHandler類繼承至AsyncQueryHandler,用於非同步請求資料
package cn.lsj.mypalyer.utils;

import android.content.AsyncQueryHandler;
import android.content.ContentResolver;
import android.database.Cursor;
import android.support.v4.widget.CursorAdapter;

public class MyQueryHandler extends AsyncQueryHandler {

public MyQueryHandler(ContentResolver cr) {
    super(cr);
}

@Override
protected void onQueryComplete(int token, Object cookie, Cursor cursor) {
    super.onQueryComplete(token, cookie, cursor);

    if (token == 100 && cookie instanceof CursorAdapter) {
        CursorAdapter adapter = (CursorAdapter) cookie;
        adapter.swapCursor(cursor);
    }

}

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
請求資料,然後展示在列表中,UI介面之所以不卡頓,是因為我用到了非同步請求視訊縮圖,非同步載入和快取,關鍵點就在MyVideoCursorAdapter類中,下面三行程式碼:
vh.iv.setTag(vb.path);
mVideoThumbLoader = new MyVideoThumbLoader();
mVideoThumbLoader.showThumbByAsynctack(vb.path, vh.iv);
  • 1
  • 2
  • 3
  • 4

自定義MyVideoThumbLoader類,主要的實現機制就是 非同步載入 和 快取機制 在加上一個繫結TAG機制

package cn.lsj.mypalyer.utils;

import android.graphics.Bitmap;
import android.media.ThumbnailUtils;
import android.os.AsyncTask;
import android.provider.MediaStore.Video.Thumbnails;
import android.support.v4.util.LruCache;
import cn.lsj.mypalyer.R;
import cn.lsj.mypalyer.activity.MainActivity;
import cn.lsj.mypalyer.view.MyImageView;

public class MyVideoThumbLoader {

// 建立cache
private LruCache<String, Bitmap> lruCache;

public MyVideoThumbLoader() {
    int maxMemory = (int) Runtime.getRuntime().maxMemory();// 獲取最大的執行記憶體
    int maxSize = maxMemory / 4;
    // 拿到快取的記憶體大小 
    lruCache = new LruCache<String, Bitmap>(maxSize) {
        @Override
        protected int sizeOf(String key, Bitmap value) {
            // 這個方法會在每次存入快取的時候呼叫
            return value.getByteCount();
        }
    };
}

public void addVideoThumbToCache(String path, Bitmap bitmap) {
    if (getVideoThumbToCache(path) == null) {
        // 當前地址沒有快取時,就新增

        lruCache.put(path, bitmap);
    }
}

public Bitmap getVideoThumbToCache(String path) {

    return lruCache.get(path);

}

public void showThumbByAsynctack(String path, MyImageView imgview) {

    if (getVideoThumbToCache(path) == null) {
        // 非同步載入
        new MyBobAsynctack(imgview, path).execute(path);
    } else {
        imgview.setImageBitmap(getVideoThumbToCache(path));
    }

}

class MyBobAsynctack extends AsyncTask<String, Void, Bitmap> {
    private MyImageView imgView;
    private String path;

    public MyBobAsynctack(MyImageView imageView, String path) {
        this.imgView = imageView;
        this.path = path;
    }

    @Override
    protected Bitmap doInBackground(String... params) {

        Bitmap bitmap = null;

        try {

            ThumbnailUtils tu = new ThumbnailUtils();

            bitmap = tu.createVideoThumbnail(params[0],
                    Thumbnails.MICRO_KIND);

            System.out.println("111111path: " + path + "  bitmap: "
                    + bitmap);

            if (bitmap == null) {

                bitmap = android.graphics.BitmapFactory.decodeResource(
                        MainActivity.mainActivity.getResources(),
                        R.drawable.btn_audio_play);

                System.out.println("5555555path: " + path + "  bitmap: "
                        + bitmap);

            }

            // //直接對Bitmap進行縮略操作,最後一個引數定義為OPTIONS_RECYCLE_INPUT ,來回收資源

            Bitmap bitmap2 = tu.extractThumbnail(bitmap, 50, 50,
                    ThumbnailUtils.OPTIONS_RECYCLE_INPUT);
            System.out.println("path: " + path + "bitmap2: " + bitmap2);

            // 加入快取中
            if (getVideoThumbToCache(params[0]) == null) {
                addVideoThumbToCache(path, bitmap2);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

        return bitmap;
    }

    @Override
    protected void onPostExecute(Bitmap bitmap) {
        if (imgView.getTag().equals(path)) {// 通過 Tag可以繫結 圖片地址和
                                            // imageView,這是解決Listview載入圖片錯位的解決辦法之一
            imgView.setImageBitmap(bitmap);
        }
    }
}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
這裡一定要注意下面這幾行行程式碼,因為得到bitmap有可能為空,當視訊是mkv格式時,就會為null,所以加個判斷,設定預設的圖片。而且,ImageView要用自定義的MyImageView,否則會報異常java.lang.IllegalArgumentException: Cannot draw recycled bitmapsat
bitmap = tu.createVideoThumbnail(params[0],Thumbnails.MICRO_KIND);
if (bitmap == null) {

                bitmap = android.graphics.BitmapFactory.decodeResource(
                        MainActivity.mainActivity.getResources(),
                        R.drawable.btn_audio_play);

                System.out.println("5555555path: " + path + "  bitmap: "
                        + bitmap);

            }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

至此,視訊縮圖可以完美的展示於ListView中,不卡頓,不重複,不亂跳,並加入非同步載入 和 快取機制, 在加上一個繫結TAG機制。