RecyclerView的功能扩展(Android图片选择器)
Android 应用程序开发中总会遇到从本地选择照片的操作,本文描述得是一个简单得图片选择器
支持设置图片张数、可以设置屏蔽得图片格式、图片预览。更多的功能正在开发中,项目已放到:码云git上 我也在随时更新这个项目。下面就简单地介绍下这个内容。
效果图片
工程逻辑简述
实现原理则是,使用Android提供的媒体数据库,将图片资源的地址(存在本地的绝对地址(路径))提取出来,再使用Glide图片加载框架,将图片加载到RecyclerView列表中。
对于Glide框架和RecyclerView可以添加如下依赖(若遇到依赖版本问题就自行解决咯)
//Glidecompile 'com.github.bumptech.glide:glide:3.7.0'//RecyclerViewcompile 'com.android.support:recyclerview-v7:26.0.0-alpha1'
以上便是这个工程的基本思路
获取图片地址(路径)
package com.util.photopicker.util;import android.content.Context;import android.database.Cursor;import android.provider.MediaStore;import com.util.photopicker.bean.ImageBean;import java.util.ArrayList;import java.util.List;/** * Created by eric on 2017/12/3. */public class ImageFinder { public static final String NONE = "none"; public static final String TYPE_GIF = "image/gif"; public static final String TYPE_JPEG = "image/jpeg"; public static final String TYPE_jpg = "image/jpg"; public static final String TYPE_PNG = "image/png"; //第二个参数是配置需要屏蔽的图片格式 public static List getImages(Context context, String typeShield){ String shield = typeShield; List list = new ArrayList<>(); Cursor cursor = context.getContentResolver().query( MediaStore.Images.Media.EXTERNAL_CONTENT_URI, null, null, null, null); while (cursor.moveToNext()){ String name = cursor.getString(cursor.getColumnIndex(MediaStore.Images.Media.DISPLAY_NAME)); String path = cursor.getString(cursor.getColumnIndex(MediaStore.Images.Media.DATA)); String type = cursor.getString(cursor.getColumnIndex(MediaStore.Images.Media.MIME_TYPE)); if (type.equals(shield)) continue; list.add(0,new ImageBean(path,name)); } return list; }}
若是以上这个类看的头大,那我们只用知道一下这几行核心代码就OK了
Cursor cursor = context.getContentResolver().query( MediaStore.Images.Media.EXTERNAL_CONTENT_URI, null, null, null, null); while (cursor.moveToNext()){ String name = cursor.getString(cursor.getColumnIndex(MediaStore.Images.Media.DISPLAY_NAME)); String path = cursor.getString(cursor.getColumnIndex(MediaStore.Images.Media.DATA)); String type = cursor.getString(cursor.getColumnIndex(MediaStore.Images.Media.MIME_TYPE)); }
以上便是通过Android为我们提供的媒体数据库来查询图片的name,path,type。当然还有更多的图片属性可以获取,例如文件大小等信息,但我们这儿暂时只需要这些就够了。除此之外,还得有个Bean类来存放我们得到的单个数据,然后放在一个List里,再返回这个list;Bean类如下
package com.util.photopicker.bean;/** * Created by eric on 2017/12/3. */public class ImageBean { private String imagePath; private String imageName; private boolean isChoose = false; public ImageBean(String path, String name){ this.imagePath = path; this.imageName = name; } public String getImagePath() { return imagePath; } public void setImagePath(String imagePath) { this.imagePath = imagePath; } public String getImageName() { return imageName; } public void setImageName(String imageName) { this.imageName = imageName; } public boolean isChoose() { return isChoose; } public void setChoose(boolean choose) { isChoose = choose; }}
当我们拿到了这个list之后就可以用列表的形式展示出来了,这里我采用的是RecyclerView,当然可以用其他的控件来代替,这里就自行考虑了。
RecyclerView
我都知道,RecyclerView简单易用,但是这个还是没法满足我们的需求,以下是对RecyclerView 的 adapter的一点小小的扩展。
package com.util.photopicker.component;import android.support.v7.widget.RecyclerView;import android.view.View;/** * Created by eric on 2017/12/4. */public abstract class BaseViewHolder extends RecyclerView.ViewHolder { public BaseViewHolder(View itemView) { super(itemView); findViewById(itemView); } /** * 转发传入的OnItemChooseCallback与位置 * @param chooseCallback 点击回调 * @param position 位置 */ public void setChooseCallback(OnItemChooseCallback chooseCallback,int position) { if (chooseCallback != null){ intOnItemChooseCallback(chooseCallback,position); } } /** * 传入Item的点击事件的监听器 * @param listener */ public void setOnItemClickListener(OnRecyclerViewItemClickListener listener, int position){ if (listener != null){ initOnItemClickListener(listener, position); } } /** * 传入Item的长按事件的监听器 * @param longClickListener */ public void setOnItemLongClickListener(OnRecyclerViewItemLongClickListener longClickListener, int position){ if (longClickListener != null){ iniOnItemLongClickListener(longClickListener,position); } } /** * 初始化点击事件(开发者自行实现) * @param chooseCallback 单项选择回调 * @param position 当前点击的位置 */ abstract public void intOnItemChooseCallback(OnItemChooseCallback chooseCallback, int position); /** * 初始化Item的点击事件(开发者自行实现) * @param listener 监听器 */ abstract public void initOnItemClickListener(OnRecyclerViewItemClickListener listener, int position); /** * 初始化Item的长按事件(开发者自行实现) * @param longClickListener 监听器 */ abstract public void iniOnItemLongClickListener(OnRecyclerViewItemLongClickListener longClickListener, int position); /** * 通过id匹配控件(开发者自行实现) * @param itemView 父布局 */ abstract protected void findViewById(View itemView); /** * 用于装载数据(开发者自行实现) * @param position 当前位置 */ abstract public void onBind(int position);}
对于一个列表来说,我们必定会用到adapter,RecyclerView也不列外,会用到 adapter 和 ViewHolder。
看上面这个类的名字变知道BaseViewHolder ,是自己写的一个ViewHolder的基类,然后我们的ViewHolder继承这个类便可以得到它的属性和功能。当然只有这个类是不够的,我们都知道 RecyclerView 显示列表,是adapter 和 ViewHolder的结合。可能你已经猜到了,还有一个BaseRecyclerAdapter的基类。
package com.util.photopicker.component;import android.support.v7.widget.RecyclerView;/** * Created by eric on 2017/12/4. */public abstract class BaseRecyclerAdapter extends RecyclerView.Adapter { private OnItemChooseCallback chooseCallback; private OnRecyclerViewItemClickListener onRecyclerViewItemClickListener; private OnRecyclerViewItemLongClickListener onRecyclerViewItemLongClickListener; @Override public void onBindViewHolder(BaseViewHolder holder, int position) { holder.setChooseCallback(chooseCallback,position); holder.setOnItemClickListener(onRecyclerViewItemClickListener,position); holder.setOnItemLongClickListener(onRecyclerViewItemLongClickListener,position); holder.onBind(position); } public void setChooseCallback(OnItemChooseCallback chooseCallback) { this.chooseCallback = chooseCallback; } public void setOnRecyclerViewItemClickListener(OnRecyclerViewItemClickListener onRecyclerViewItemClickListener) { this.onRecyclerViewItemClickListener = onRecyclerViewItemClickListener; } public void setOnRecyclerViewItemLongClickListener(OnRecyclerViewItemLongClickListener onRecyclerViewItemLongClickListener) { this.onRecyclerViewItemLongClickListener = onRecyclerViewItemLongClickListener; }}
以上便是BaseRecyclerAdapter,其中很有灵性的是这几个方法
public void setChooseCallback(OnItemChooseCallback chooseCallback) { this.chooseCallback = chooseCallback; } public void setOnRecyclerViewItemClickListener(OnRecyclerViewItemClickListener onRecyclerViewItemClickListener) { this.onRecyclerViewItemClickListener = onRecyclerViewItemClickListener; } public void setOnRecyclerViewItemLongClickListener(OnRecyclerViewItemLongClickListener onRecyclerViewItemLongClickListener) { this.onRecyclerViewItemLongClickListener = onRecyclerViewItemLongClickListener; }
可以看出,这个便是设置事件的监听,OnItemChooseCallback,OnRecyclerViewItemClickListener ,OnRecyclerViewItemLongClickListener 相信看名字就知道是什么意思了
1、 OnItemChooseCallback
package com.util.photopicker.component;/** * Created by eric on 2017/12/4. */public interface OnItemChooseCallback { /** * 点击单项时 * @param position 位置 * @param isChosen 是否选中 */ void chooseState(int position, boolean isChosen); /** * 现在的值 * @param countNow */ void countNow(int countNow); /** * 警告不能再选了 * @param count */ void countWarning(int count);}
2、 OnRecyclerViewItemClickListener
package com.util.photopicker.component;/** * Created by eric on 2017/12/5. */public interface OnRecyclerViewItemClickListener { void onItemClick(int position);}
3、OnRecyclerViewItemLongClickListener
package com.util.photopicker.component;/** * Created by 肖庆鸿 on 2017/12/5. */public interface OnRecyclerViewItemLongClickListener { void onItemLongClick(int position);}
以上这样写的好处再哪儿呢,下面就可以看出来
ImagesListAdapter
package com.util.photopicker.adapter;import android.content.Context;import android.view.LayoutInflater;import android.view.View;import android.view.ViewGroup;import android.widget.ImageView;import com.bumptech.glide.Glide;import com.util.photopicker.R;import com.util.photopicker.bean.ImageBean;import com.util.photopicker.component.BaseRecyclerAdapter;import com.util.photopicker.component.BaseViewHolder;import com.util.photopicker.component.OnItemChooseCallback;import com.util.photopicker.component.OnRecyclerViewItemClickListener;import com.util.photopicker.component.OnRecyclerViewItemLongClickListener;import java.util.List;/** * Created by eric on 2017/12/3. */public class ImagesListAdapter extends BaseRecyclerAdapter { private int count = 0; private int maxNum = 1; private Context context; private List list; public ImagesListAdapter(Context context, List list){ this.context = context; this.list = list; } @Override public BaseViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_picker,null); return new MyViewHolder(view); } @Override public int getItemCount() { return list.size(); } public void setMaxNum(int maxNum) { if (maxNum < 1) return; this.maxNum = maxNum; } private class MyViewHolder extends BaseViewHolder { private ImageView mImageSrc; private ImageView mImageChoose; private MyViewHolder(View itemView) { super(itemView); } @Override public void intOnItemChooseCallback(final OnItemChooseCallback chooseCallback, final int position) { mImageChoose.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { if (count < maxNum){ if (!list.get(position).isChoose()){ mImageChoose.setImageResource(R.drawable.checked_yes); list.get(position).setChoose(true); chooseCallback.chooseState(position,true); count ++; } else { mImageChoose.setImageResource(R.drawable.checked_null); list.get(position).setChoose(false); chooseCallback.chooseState(position,false); count--; } } else { //count >= maxNum if (!list.get(position).isChoose()){ chooseCallback.countWarning(count); } else { mImageChoose.setImageResource(R.drawable.checked_null); list.get(position).setChoose(false); chooseCallback.chooseState(position,false); count--; } } chooseCallback.countNow(count); } }); } @Override public void initOnItemClickListener(final OnRecyclerViewItemClickListener listener, final int position) { itemView.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { listener.onItemClick(position); } }); } @Override public void iniOnItemLongClickListener(OnRecyclerViewItemLongClickListener longClickListener, int position) { } @Override protected void findViewById(View itemView) { mImageSrc = itemView.findViewById(R.id.image_src); mImageChoose = itemView.findViewById(R.id.image_choose); } @Override public void onBind(int position) { if (list.get(position).isChoose()){ mImageChoose.setImageResource(R.drawable.checked_yes); } else { mImageChoose.setImageResource(R.drawable.checked_null); } Glide.with(context) .load(list.get(position).getImagePath()) .into(mImageSrc); } }}
adapter里面我们只需要重写两个方法就OK了
@Override public BaseViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_picker,null); return new MyViewHolder(view); } @Override public int getItemCount() { return list.size(); }
这个相信已经不用解释了,然后再viewholder中处理逻辑就很清晰了
private MyViewHolder(View itemView) { //这里super不可省略 super(itemView); } @Override public void intOnItemChooseCallback(final OnItemChooseCallback chooseCallback, final int position) { //初始化选择事件的callback } @Override public void initOnItemClickListener(final OnRecyclerViewItemClickListener listener, final int position) { //初始化单击事件 } @Override public void iniOnItemLongClickListener(OnRecyclerViewItemLongClickListener longClickListener, int position) { //初始化长按事件 } @Override protected void findViewById(View itemView) { //通过id来初始化itemView内的控件 } @Override public void onBind(int position) { //装配数据 } }
这个类里是重写继承自抽象类BaseViewHolder的一些方法,看名字就知道这个方法里该写什么。这里放心,若是没有调用adapter对象的set方法这些初始化方法是不会调用的。以上便是图片选择器中对RecyclerView 的 adapter的扩展。
ImagesPickActivity
再下来就是展示图片的ImagesPickActivity了,这里的一系列操作都是些 固定的方式,没什么可说的
package com.util.photopicker.activities;import android.app.Activity;import android.content.Intent;import android.content.pm.PackageManager;import android.net.Uri;import android.os.Build;import android.provider.MediaStore;import android.support.annotation.NonNull;import android.support.v4.app.ActivityCompat;import android.support.v4.content.FileProvider;import android.support.v7.app.AppCompatActivity;import android.os.Bundle;import android.support.v7.widget.GridLayoutManager;import android.support.v7.widget.RecyclerView;import android.support.v7.widget.Toolbar;import android.view.View;import android.widget.Button;import android.widget.ImageButton;import android.widget.TextView;import android.widget.Toast;import com.util.photopicker.R;import com.util.photopicker.bean.ImageBean;import com.util.photopicker.adapter.ImagesListAdapter;import com.util.photopicker.component.OnItemChooseCallback;import com.util.photopicker.component.OnRecyclerViewItemClickListener;import com.util.photopicker.util.ImageFinder;import java.io.File;import java.io.IOException;import java.util.ArrayList;import java.util.List;public class ImagesPickActivity extends AppCompatActivity implements View.OnClickListener{ public final static String RESULT_IMAGES_LIST = "imagesPath"; private final static int REQUEST_CAMERA = 200; private final static String EXTRA_COUNT = "max"; private static final int REQUEST_EXTERNAL_STORAGE = 1; private static String[] PERMISSIONS_STORAGE = { "android.permission.READ_EXTERNAL_STORAGE", "android.permission.WRITE_EXTERNAL_STORAGE" }; private Toolbar mTbPickActivity; private TextView mTvCount; private ImageButton mImageBtnPickerBack; private Button mBtnSure; private RecyclerView mRecyclerViewPickActivity; private List list; private Uri cameraImageUri; private int maxCount; private ArrayList chosenList = new ArrayList<>(); /** * 提供启动活动的方法 * @param activity 起点活动 * @param max 最大图片数 * @param requestCode 请求值 */ public static void startPicker(Activity activity, int max, int requestCode){ Intent intent = new Intent(activity,ImagesPickActivity.class); intent.putExtra(EXTRA_COUNT,max); activity.startActivityForResult(intent,requestCode); } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_pick); Intent intent = getIntent(); maxCount = intent.getIntExtra(EXTRA_COUNT,9); initView(); requestStoragePermission(); } /** * 初始化控件资源 */ private void initView() { mTbPickActivity = (Toolbar) findViewById(R.id.tb_pick_activity); mTvCount = (TextView) findViewById(R.id.tv_count); mImageBtnPickerBack = (ImageButton) findViewById(R.id.imageBtn_picker_back); mBtnSure = (Button) findViewById(R.id.btn_sure); mRecyclerViewPickActivity = (RecyclerView) findViewById(R.id.recyclerView_pick_activity); mTvCount.setText(0+"/"+maxCount); mImageBtnPickerBack.setOnClickListener(this); mBtnSure.setOnClickListener(this); } /** * 初始化图片列表 */ private void initRecyclerView(){ list = ImageFinder.getImages(this,ImageFinder.TYPE_GIF); MyChooseCallback callback = new MyChooseCallback(); MyOnItemClickListener listener = new MyOnItemClickListener(); ImagesListAdapter adapter = new ImagesListAdapter(this,list); RecyclerView.LayoutManager layoutManager = new GridLayoutManager(this,3); adapter.setMaxNum(maxCount); adapter.setChooseCallback(callback); adapter.setOnRecyclerViewItemClickListener(listener); mRecyclerViewPickActivity.setLayoutManager(layoutManager); mRecyclerViewPickActivity.setAdapter(adapter); } /** * 照相机 */ private void takePhoto(){ //文件io流存储图片 File outputImage = new File(getExternalCacheDir(),"outputImage.jpg"); try { if (outputImage.exists()){ outputImage.delete(); } outputImage.createNewFile(); //创建新的对象 } catch (IOException e) { e.printStackTrace(); } if (Build.VERSION.SDK_INT >= 24){ cameraImageUri = FileProvider.getUriForFile(this,"com.eric.photopicker.camera",outputImage); } else { cameraImageUri = Uri.fromFile(outputImage); } //启动相机 Intent intent = new Intent("android.media.action.IMAGE_CAPTURE"); intent.putExtra(MediaStore.EXTRA_OUTPUT,cameraImageUri); startActivityForResult(intent,REQUEST_CAMERA); } /** * 请求读写权限 */ private void requestStoragePermission(){ int permission = ActivityCompat.checkSelfPermission(this, "android.permission.WRITE_EXTERNAL_STORAGE"); if (permission != PackageManager.PERMISSION_GRANTED) { ActivityCompat.requestPermissions(this, PERMISSIONS_STORAGE,REQUEST_EXTERNAL_STORAGE); } else { initRecyclerView(); } } /** * 启动活动返回值处 * @param requestCode 请求码 * @param resultCode 结果码 * @param data 数据 */ @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { switch (requestCode){ case REQUEST_CAMERA: if (resultCode == RESULT_OK){ Toast.makeText(this,"图片获取成功",Toast.LENGTH_SHORT).show(); } break; default: break; } } /** * 权限请求结果处理 * @param requestCode 请求码 * @param permissions 权限数组 * @param grantResults 结果 */ @Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { switch (requestCode){ case REQUEST_EXTERNAL_STORAGE: if (grantResults.length > 1 && grantResults[0] == PackageManager.PERMISSION_GRANTED){ initRecyclerView(); } else { Toast.makeText(this,"您未开启读取储存权限", Toast.LENGTH_SHORT).show(); } break; default: break; } } /** * 系统view点击回调 * @param v view */ @Override public void onClick(View v) { switch (v.getId()){ case R.id.imageBtn_picker_back: finish(); break; case R.id.btn_sure: onPickedDone(); break; default: break; } } /** * 返回一组图片path */ private void onPickedDone(){ ArrayList images = new ArrayList<>(); for (int i : chosenList){ images.add(list.get(i).getImagePath()); } onResult(images); } /** * 返回结果 * @param images */ private void onResult(ArrayList images){ Intent intent = new Intent(); intent.putStringArrayListExtra(RESULT_IMAGES_LIST,images); setResult(RESULT_OK,intent); finish(); } /** * Item点击事件的监听类 */ private class MyOnItemClickListener implements OnRecyclerViewItemClickListener{ @Override public void onItemClick(int position) { ImagesPreviewActivity.startPreView(ImagesPickActivity.this,list.get(position).getImagePath()); } } /** * Item选则事件的监听类 */ private class MyChooseCallback implements OnItemChooseCallback { @Override public void chooseState(int position, boolean isChosen) { if (isChosen){ chosenList.add(position); } else { int index = 0; for (int i : chosenList){ if (i == position){ chosenList.remove(index); } index ++; } } } @Override public void countNow(int countNow) { mTvCount.setText(countNow +"/"+ maxCount); } @Override public void countWarning(int count) { Toast.makeText(ImagesPickActivity.this,"最多选择"+count+"张图片",Toast.LENGTH_SHORT).show(); } }}
当我们单击图片时,想要查看图片
ImagesPreviewActivity
package com.util.photopicker.activities;import android.app.Activity;import android.content.Intent;import android.support.v7.app.AppCompatActivity;import android.os.Bundle;import android.view.View;import android.widget.ImageButton;import android.widget.ImageView;import com.bumptech.glide.Glide;import com.util.photopicker.R;public class ImagesPreviewActivity extends AppCompatActivity { private final static String EXTRA_PREVIEW = "imagePath"; private ImageView mImagePreviewShow; private String imagePath; private ImageButton mImgBtnPreviewBack; public static void startPreView(Activity activity, String imagePath){ Intent intent = new Intent(activity,ImagesPreviewActivity.class); intent.putExtra(EXTRA_PREVIEW,imagePath); activity.startActivity(intent); } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_images_preview); Intent intent = getIntent(); imagePath = intent.getStringExtra(EXTRA_PREVIEW); mImagePreviewShow = (ImageView) findViewById(R.id.image_preview_show); mImgBtnPreviewBack = (ImageButton) findViewById(R.id.imgBtn_preview_back); mImgBtnPreviewBack.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { finish(); } }); Glide.with(this) .load(imagePath) .into(mImagePreviewShow); }}
以上便是一个简单的图片选择器工程,工程源码地址点击:码云(git)
更多相关文章
- Android中保存图片的两种方式
- 【整理】Android中使用XML自定义组件各种状态下的背景图片
- Android对View进行截图并保存到本地相册
- Android事件分发机制以及滑动冲突处理
- Android(安卓)studio 百度地图开发(6)Marker绑定事件、计算两点距
- Android实现再图标右上角显示数字
- android之图片截取
- Android显示从网络下载图片偏小的问题
- Android(安卓)gesture 原理