搜索所有视频和图片并混合显示,视频在右下角显示时长,点击时右上角显示当前为第几个选中的图片/视频,取消时补位。
这个选择器是基于RecyclerView来实现的
下面介绍关键类
QueryProcessor是用于异步查询所有图片/视频的类
public class QueryProcessor {
//弱引用,防止内存泄漏
private WeakReference context;
private ContentResolver resolver;
private Cursor cursor = null;
public QueryProcessor(Context context) {
this.context = new WeakReference<>(context);
resolver = context.getContentResolver();
}
/**
* 查询所有图片和视频
* @param callback 查询完毕后的回调接口
*/
public void queryAll(final QueryCallback callback) {
if (context == null) {
return;
}
//开辟一个新线程执行耗时查询
new Thread(new Runnable() {
@Override
public void run() {
ArrayList multiModels = new ArrayList<>();
queryImages(multiModels, callback);
queryVideos(multiModels, callback);
callback.querySuccess(multiModels);
cursor.close();
}
}).start();
}
/**
* 查询所有图片
* @param multiModels 存放查询到的图片和视频的集合
* @param callback 查询完毕后的回调接口
*/
private void queryImages(ArrayList multiModels, final QueryCallback callback) {
cursor = resolver.query(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, null, null
, null, null);
if (cursor == null) {
callback.queryFailed();
return;
}
while (cursor.moveToNext()) {
MultiModel multiModel = new MultiModel();
multiModel.setPath(cursor.getString(cursor.getColumnIndex(MediaStore.Images.Media.DATA)));
multiModel.setDate(cursor.getString(cursor.getColumnIndex(MediaStore.Images.Media.DATE_ADDED)));
multiModel.setType(0);
multiModels.add(multiModel);
}
}
/**
* 查询所有视频
* @param multiModels 存放查询到的图片和视频的集合
* @param callback 查询完毕后的回调接口
*/
private void queryVideos(ArrayList multiModels, final QueryCallback callback) {
cursor = resolver.query(MediaStore.Video.Media.EXTERNAL_CONTENT_URI, null, null
, null, null);
if (cursor == null) {
callback.queryFailed();
return;
}
while (cursor.moveToNext()) {
MultiModel multiModel = new MultiModel();
multiModel.setPath(cursor.getString(cursor.getColumnIndex(MediaStore.Video.Media.DATA)));
multiModel.setDate(cursor.getString(cursor.getColumnIndex(MediaStore.Video.Media.DATE_ADDED)));
multiModel.setDuration(cursor.getLong(cursor.getColumnIndex(MediaStore.Video.Media.DURATION)));
multiModel.setType(1);
multiModels.add(multiModel);
}
}
/**
* 查询照片和视频结束后的回调接口
*/
public interface QueryCallback {
void querySuccess(ArrayList multiModels);
void queryFailed();
}
}
在Adapter中比较麻烦的一个是子View的点击事件,另一个就是点击已经选择过的处于中间位置的图片/视频时,其后面的图片/视频右上角的编号要依次补位,而这里又涉及到局部刷新,局部刷新的关键点就是notifyItemChanged(postion, payload)
方法以及其回调onBindViewHolder(GalleryViewHolder holder, int position, List
public class GalleryAdapter extends RecyclerView.Adapter<GalleryAdapter.GalleryViewHolder> {
private Context context;
//数据源,所有的图片/视频
private List data;
//已选中的图片/视频
private List selectedModels;
//记录已选中的图片在数据源中的索引位置
private List selectedPosList;
//素材导入界面最上层显示的文字
private TextView tvSelectedModelsNum;
public GalleryAdapter(Context context, List data, TextView tvSelectedModelsNum) {
this.context = context;
this.data = data;
this.tvSelectedModelsNum = tvSelectedModelsNum;
this.selectedModels = new ArrayList<>();
this.selectedPosList = new ArrayList<>();
}
@NonNull
@Override
public GalleryViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(context).inflate(R.layout.gallery_item, null);
return new GalleryViewHolder(view);
}
@Override
public void onBindViewHolder(@NonNull final GalleryViewHolder holder, int position) {
MultiModel model = data.get(position);
//使用Fresco加载图片
FrescoHelper.INSTANCE.bindImage(holder.importPreview, "file://" + model.getPath()
, new ResizeOptions((int)UIUtils.dip2Px(context, 88.5f)
, (int)UIUtils.dip2Px(context, 88.5f))
, Priority.HIGH, null);
//判断是图片还是视频,若是视频需要在右下角显示时长
if (model.getType() == 1) {
holder.importVideoDuration.setVisibility(View.VISIBLE);
holder.importVideoDuration.setText(timeParse(model.getDuration()));
} else {
holder.importVideoDuration.setVisibility(View.GONE);
}
//当子项出现(或重新出现)到屏幕内时,需要判断其是否已经被选择,若已经被选择则需要将其置为对应位置;若不是被选择的图片/视频,需要取消其右上角的数字显示(在这里有坑,不判断的话在新刷出来的子项里会有之前选择过的图片的数字在新的图片的右上角,原因尚未明确)
if (selectedModels.contains(model)) {
int index = selectedModels.indexOf(model);
holder.iconContainer.setBackgroundResource(R.drawable.bg_import_icon_container);
holder.importSelectNumber.setText(context.getString(R.string.import_selected_icon_num
, index + 1));
holder.importSelectNumber.setVisibility(View.VISIBLE);
} else {
holder.importSelectNumber.setVisibility(View.GONE);
holder.importSelectNumber.setText("");
holder.iconContainer.setBackgroundResource(R.drawable.ic_import_unselected);
}
}
/**
* 三个参数的onBind方法,实际上onBind方法首先都是回调三参的onBind,若payloads里面没有数据,则再回调两个参数的onBind。notifyItemChanged方法也是如此,首先回调的是三参的onBind。
**/
@Override
public void onBindViewHolder(@NonNull GalleryViewHolder holder, int position, @NonNull List
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:gravity="center"
android:padding="2dp">
<FrameLayout
android:layout_width="88.5dp"
android:layout_height="88.5dp">
<com.facebook.drawee.view.SimpleDraweeView
android:id="@+id/iv_import_preview"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerCrop"/>
<FrameLayout
android:id="@+id/import_icon_container"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_gravity="end"
android:layout_marginTop="4dp"
android:layout_marginRight="4dp"
android:layout_marginEnd="4dp"
android:background="@drawable/ic_import_unselected">
<TextView
android:id="@+id/iv_import_select_number"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="16sp"
android:textColor="#ffffff"
android:layout_gravity="center"
android:visibility="gone"/>
FrameLayout>
<TextView
android:id="@+id/tv_import_video_duration"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="12sp"
android:textColor="#ededed"
android:gravity="end"
android:layout_gravity="end|bottom"
android:layout_marginRight="4dp"
android:layout_marginEnd="4dp"
android:layout_marginBottom="4dp"
android:visibility="gone"/>
FrameLayout>
LinearLayout>
//查询图片和视频并显示在RecyclerView上
new QueryProcessor(this).queryAll(new QueryProcessor.QueryCallback() {
@Override
public void querySuccess(ArrayList multiModels) {
ImportActivity.this.galleryDataSource.addAll(multiModels);
//将所有视频和图片按照创建时间进行一次排序,由最新的到最老的
Collections.sort(ImportActivity.this.galleryDataSource);
mAdapter = new GalleryAdapter(ImportActivity.this, ImportActivity.this.galleryDataSource,
tvSelectedModelsNum);
runOnUiThread(new Runnable() {
@Override
public void run() {
mGallery.setAdapter(mAdapter);
}
});
}
@Override
public void queryFailed() {
Toast.makeText(ImportActivity.this, "视频或图片查询失败", Toast.LENGTH_SHORT).show();
}
});
效果基本和知乎开源的Matisse一致,就不放效果图了。