您的位置:首页 > 移动开发 > Android开发

安卓加载视频缩略图,展示于ListView中,完美实现

2016-05-09 17:15 691 查看

安卓获取视频缩略图,展示于ListView中,完美实现

本篇博客为原创,来自于vitamio,转载请注明出处。

应用场景:

获取安卓手机外部存储视频列表,适配器继承至CursorAdapter,利用ViewHolder进行优化;并利用异步加载和缓存机制,在加上一个绑定TAG机制。在ListView中展示视频某一帧的图片,视频名称,视频大小以及视频时长。

分析说明:

在ListView中展示视频某一帧的画面,有以下几种方式。

1.从媒体库中查询

2. android 2.2以后使用ThumbnailUtils类获取

3.调用jni文件,实现MediaMetadataRetriever类

三种方法各有利弊:

第一种方法,新视频增加后需要SDCard重新扫描才能给新增加的文件添加缩略图,灵活性差,而且不是很稳定,适合简单应用

第二种方法,实现简单,但2.2以前的版本不支持

第三种方法,实现复杂,但比较灵活


为了在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;
}

}


其中的布局才用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>


其中的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)


为了解决这个异常,我自定义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");
}
}
}


接下来,需要填充数据到列表展示

设置适配器代码如下:

MyVideoCursorAdapter adapter = new MyVideoCursorAdapter(mContext, null);
lv.setAdapter(adapter);


请求数据:

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


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

}

}


请求数据,然后展示在列表中,UI界面之所以不卡顿,是因为我用到了异步请求视频缩略图,异步加载和缓存,关键点就在MyVideoCursorAdapter类中,下面三行代码:

vh.iv.setTag(vb.path);
mVideoThumbLoader = new MyVideoThumbLoader();
mVideoThumbLoader.showThumbByAsynctack(vb.path, vh.iv);


自定义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);
}
}
}
}


这里一定要注意下面这几行行代码,因为得到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);

}


至此,视频缩略图可以完美的展示于ListView中,不卡顿,不重复,不乱跳,并加入异步加载 和 缓存机制, 在加上一个绑定TAG机制。

本篇博客为原创,来自于vitamio,转载请注明出处http://blog.csdn.net/vitamio

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息