Android 应用程序开发中总会遇到从本地选择照片的操作,本文描述得是一个简单得图片选择器
支持设置图片张数、可以设置屏蔽得图片格式、图片预览。更多的功能正在开发中,项目已放到:码云git上 我也在随时更新这个项目。下面就简单地介绍下这个内容。
实现原理则是,使用Android提供的媒体数据库,将图片资源的地址(存在本地的绝对地址(路径))提取出来,再使用Glide图片加载框架,将图片加载到RecyclerView列表中。
对于Glide框架和RecyclerView可以添加如下依赖(若遇到依赖版本问题就自行解决咯)
//Glide
compile 'com.github.bumptech.glide:glide:3.7.0'
//RecyclerView
compile '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)