一 、设计目标
在进行开发的时候有现成的图片选择器,通过内容提供器可以直接打开系统相册进行选择,得到要选择的图片,界面比较简陋,但可以实现简单的图片选择功能。要设计一个自己的图片选择器,要明确设计的目标:
- 访问手机的媒体库,获得有图片的文件夹
- 为文件夹列表设置一个下拉选择框,下拉选择要访问的文件夹
- 选择目标文件夹,遍历文件夹所有图片,利用Glide进行图片加载最后在RecyclerView中进行显示
- 设置一个ImageView对要选择的图片进行预览,点击预览图进入图片编辑页面
以上大概就是我要实现的图片选择器的设计目标,对设计目标进行分析后确定使用的工具有
- MediaStore媒体库
- Handler异步加载
- Glide图片加载
- RecyclerView列表显示
- Spinner下拉选择框
二、 相关内容学习
1. MediaStore
MediaStore这个类是android系统提供的一个多媒体数据库,android 中多媒体信息都可以从这里提取。这个MediaStore包括了多媒体数据库的所有信息,包括音频,视频和图像,android把所有的多媒体数据库接口 进行了封装,所有的数据库不用自己进行创建,直接调用利用ContentResolver去掉用那些封装好的接口就可以进行数据库的操作了。今天我就介绍 一些这些接口的用法。在进行图片选择的时候,就是通过MediaStore进行查找图片路径等操作。
MediaStore详细字段参考链接: https://blog.csdn.net/lemon_blue/article/details/52353851
2. Handler异步加载
Handler主要用于异步消息的处理: 有点类似辅助类,封装了消息投递、消息处理等接口。当发出一个消息之后,首先进入一个消息队列,发送消息的函数即刻返回,而另外一个部分在消息队列中逐一将消息取出,然后对消息进行处理,也就是发送消息和接收消息不是同步的处理。 这种机制通常用来处理相对耗时比较长的操作。
3. Glide图片加载
一行代码
Glide.with(context).load(url).into(imageView);
4. RecyclerView
参考之前的内容
5. Spinner控件
参考代码,spinner的适配器是ArrayAdapter
三、 代码分析
1. 图片工具类
定义一个工具类对媒体库进行查询,返回所以包含图片的文件夹列表,注释比较详细。
public class ImageUtils {
/**
* 图片文件夹名列表
*/
public static List folderNameList = new ArrayList<> ();
/**
* 图片文件夹路径列表
*/
public static List folderPathList = new ArrayList<> ();
/**
* 文件夹列表,用于判断当前文件夹是否遍历
*/
public static HashSet mFolderList = new HashSet<> ();
/**
* 得到图片文件夹,利用ContentResolver进行遍历
* @param context
* @param handler
*/
public static void getFolder(final Context context, final Handler handler) {
new Thread (new Runnable () {
@Override
public void run() {
Uri imageUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
ContentResolver contentResolver = context.getContentResolver ();
//文件夹选择条件,MIME_TYPE媒体类型为JPEG和png的文件
String selection = MediaStore.Images.Media.MIME_TYPE + "=? or " + MediaStore.Images.Media.MIME_TYPE + "=?";
String[] selectionArgs = new String[]{"image/jpeg", "image/png"};
Cursor cursor = contentResolver.query (imageUri, null, selection, selectionArgs, MediaStore.Images.Media.DATE_MODIFIED);
while (cursor.moveToNext ()) {
String imagePath = cursor.getString (cursor.getColumnIndex (MediaStore.Images.Media.DATA));
//获得当前文件夹路径
File imageFolder = new File (imagePath).getParentFile ();
if(imageFolder == null) {
continue;
}
String folderPath = imageFolder.getAbsolutePath ();
//利用HashSet进行判断当前文件夹是否被选择
if(mFolderList.contains (folderPath)) {
continue;
} else {
mFolderList.add (folderPath);
folderPathList.add (folderPath);
//截取文件夹的最后一个"/"后面的字符串作为文件夹名称
int indexOfPath = folderPath.lastIndexOf ("/");
String folderName = folderPath.substring (indexOfPath);
folderNameList.add (folderName);
}
}
cursor.close ();
mFolderList.clear ();
Message msg = new Message ();
msg.what = 1;
handler.sendMessage (msg);
}
}).start ();
}
}
2. 图片选择器主界面
public class CreateFragment extends Fragment {
/**
* 图片选择界面的预览图
*/
private ImageView iv_selectedImage;
/**
* 显示当前文件夹的图片列表
*/
private RecyclerView recyclerView;
/**
* 图片列表适配器
*/
private CreateAdapter createAdapter;
/**
* 列表网格布局管理器
*/
private GridLayoutManager gridLayoutManager;
/**
* 文件夹下拉选择栏
*/
private Spinner spinner;
/**
* 下拉选择栏适配器
*/
private ArrayAdapter spinnerAdapter;
/**
* 文件夹下拉选择栏显示列表
*/
private List nameList = new ArrayList<> ();
/**
* 所有图片文件夹路径
*/
private List imageList = new ArrayList<> ();
/**
* 当前文件夹下所有图片路径
*/
private static List selectedImageList = new ArrayList<> ();
/**
* 当前预览图片路径
*/
private static String selectedImagePath;
public CreateFragment() {
}
/**
* 获取当前fragment实例
* @return
*/
public static Fragment newInstance() {
Bundle args = new Bundle ();
Fragment fragment = new CreateFragment ();
fragment.setArguments (args);
return fragment;
}
/**
* Handler处理图片工具类得到的文件夹列表和更新UI
*/
private Handler handler = new Handler () {
@Override
public void handleMessage(Message msg) {
super.handleMessage (msg);
if(msg.what == 1) {
nameList = ImageUtils.folderNameList;
imageList = ImageUtils.folderPathList;
spinnerAdapter = new ArrayAdapter (getActivity (), R.layout.support_simple_spinner_dropdown_item, nameList);
spinner.setAdapter (spinnerAdapter);
}
}
};
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate (R.layout.fragment_create, container, false);
initView (view);
ImageUtils.getFolder (getActivity (), handler);
return view;
}
/**
* 初始化界面
* @param view
*/
public void initView(View view) {
nameList.clear ();
iv_selectedImage = view.findViewById (R.id.iv_create_selected_image);
//预览图点击事件
iv_selectedImage.setOnClickListener (new View.OnClickListener () {
@Override
public void onClick(View view) {
Intent intent = new Intent (getActivity (), SelectedImageActivity.class);
intent.putExtra ("path", selectedImagePath);
startActivity (intent);
}
});
spinner = view.findViewById (R.id.create_spinner);
recyclerView = view.findViewById (R.id.create_select_recycler_view);
onSpinnerSelectedListener ();
}
/**
* 设置下拉选择栏的选择事件
*/
public void onSpinnerSelectedListener() {
spinner.setOnItemSelectedListener (new AdapterView.OnItemSelectedListener () {
@Override
public void onItemSelected(AdapterView> adapterView, View view, int i, long l) {
selectedImageList.clear ();
//利用排序工具类进行排序返回当前文件夹图片路径列表
selectedImageList = SortUtils.sortImage (imageList, i);
setRecyclerView ();
}
@Override
public void onNothingSelected(AdapterView> adapterView) {
}
});
}
/**
* 得到当前文件夹的图片路径,为RecyclerView设置适配器,进行显示
*/
private void setRecyclerView() {
//布局管理器一定要设置
gridLayoutManager = new GridLayoutManager (getActivity (), 4, GridLayoutManager.VERTICAL, false);
recyclerView.setLayoutManager (gridLayoutManager);
createAdapter = new CreateAdapter (getActivity (), selectedImageList);
recyclerView.setAdapter (createAdapter);
/**
* 为RecyclerView的item设置点击事件
*/
createAdapter.setOnItemClickListener (new CreateAdapter.OnItemClickListener () {
@Override
public void onClick(int position) {
selectedImagePath = selectedImageList.get (position);
Glide.with (getActivity ()).load (selectedImagePath).into (iv_selectedImage);
}
});
//加载每个文件夹的第一张图片
selectedImagePath = selectedImageList.get (0);
Glide.with (getActivity ()).load (selectedImagePath).into (iv_selectedImage);
}
}
3. 按图片修改时间排序工具类
public class SortUtils {
private static List selectedImageList = new ArrayList<> ();
public static List sortImage(List imageList,int position) {
String imageFolder = imageList.get (position);
//获取当前文件夹
File imageFile = new File (imageFolder).getAbsoluteFile ();
//获取当前文件夹下的文件
File[] files = imageFile.listFiles (new FileFilter () {
@Override
public boolean accept(File file) {
String imageName = file.getName ().toString ();
if(imageName.endsWith (".jpeg") || imageName.endsWith (".jpg") || imageName.endsWith (".png")) {
return true;
}
return false;
}
});
//
List fileList = new ArrayList<> ();
for (int j = 0; j < files.length; j++) {
fileList.add (files[j]);
}
Collections.sort (fileList, new FileComparator ());
for (int j = 0; j < fileList.size (); j++) {
selectedImageList.add (fileList.get (j).getAbsolutePath ());
}
return selectedImageList;
}
/**
* 文件比较类,实现Comparator接口,重写compare方法
* 传入File泛型
* public int compare(Object o1, Object o2) 返回一个基本类型的整型
* 如果要按照升序排序,则o1< o2,返回-1(负数),相等返回0,01大于02返回1(正数)
* 如果要按照降序排序,则o1< o2,返回1(正数),相等返回0,01大于02返回-1(负数)
*/
public static class FileComparator implements Comparator {
/**
* 按修改时间进行排序,lastModified()返回此抽象路径名表示的文件最后一次被修改的时间。
* @param file1
* @param file2
* @return
*/
@Override
public int compare(File file1, File file2) {
if(file1.lastModified () < file2.lastModified ()) {
return 1;
} else {
return -1;
}
}
}
}
4. RecyclerView适配器
public class CreateAdapter extends RecyclerView.Adapter {
/**
* 使用适配器的上下文
*/
private Context context;
/**
* 传入适配器的显示列表
*/
private List list;
/**
* 实例化一个接口
*/
private OnItemClickListener onItemClickListener;
public CreateAdapter(Context context, List list) {
this.context = context;
this.list = list;
}
class CreateHolder extends RecyclerView.ViewHolder {
RelativeLayout rlItemLayout;
ImageView ivItemImage;
public CreateHolder(View itemView) {
super (itemView);
rlItemLayout = itemView.findViewById (R.id.create_list_item_layout_rl);
ivItemImage = itemView.findViewById (R.id.create_list_item_iv);
}
}
@NonNull
@Override
public CreateHolder onCreateViewHolder(@NonNull ViewGroup parent, final int viewType) {
View view = LayoutInflater.from (parent.getContext ()).inflate (R.layout.create_list_item_layout, parent, false);
final CreateHolder holder = new CreateHolder (view);
view.setOnClickListener (new View.OnClickListener () {
@Override
public void onClick(View view) {
onItemClickListener.onClick ((int) view.getTag ());
}
});
return holder;
}
@Override
public void onBindViewHolder(@NonNull CreateHolder holder, int position) {
Glide.with (context).load (list.get (position)).into (holder.ivItemImage);
holder.itemView.setTag (position);
}
@Override
public int getItemCount() {
return list.size ();
}
@Override
public void onAttachedToRecyclerView(@NonNull RecyclerView recyclerView) {
super.onAttachedToRecyclerView (recyclerView);
RecyclerView.LayoutManager manager = recyclerView.getLayoutManager ();
if(manager instanceof GridLayoutManager) {
GridLayoutManager gridLayoutManager = (GridLayoutManager) manager;
gridLayoutManager.setSpanSizeLookup (new GridLayoutManager.SpanSizeLookup () {
@Override
public int getSpanSize(int position) {
return 1;
}
});
}
}
/**
* 定义一个item的点击事件接口
*/
public interface OnItemClickListener {
/**
* 为item添加点击事件,传入一个点击的item位置
*
* @param position
*/
void onClick(int position);
}
/**
* 为Activity提供一个监听点击事件的方法,实现自定义的接口
*
* @param onItemClickListener
*/
public void setOnItemClickListener(OnItemClickListener onItemClickListener) {
this.onItemClickListener = onItemClickListener;
}
}