安卓載入視訊縮圖,展示於ListView中,完美實現
阿新 • • 發佈:2019-01-08
安卓獲取視訊縮圖,展示於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機制。