安卓加载视频缩略图,展示于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
相关文章推荐
- 使用C++实现JNI接口需要注意的事项
- Android IPC进程间通讯机制
- Android Manifest 用法
- [转载]Activity中ConfigChanges属性的用法
- Android之获取手机上的图片和视频缩略图thumbnails
- Android之使用Http协议实现文件上传功能
- Android学习笔记(二九):嵌入浏览器
- android string.xml文件中的整型和string型代替
- i-jetty环境搭配与编译
- android之定时器AlarmManager
- android wifi 无线调试
- Android Native 绘图方法
- Android java 与 javascript互访(相互调用)的方法例子
- android 代码实现控件之间的间距
- android FragmentPagerAdapter的“标准”配置
- Android"解决"onTouch和onClick的冲突问题
- android:installLocation简析
- android searchView的关闭事件
- SourceProvider.getJniDirectories