在做項目時常常會遇到選擇本地圖片的需求。曾經都是懶得寫直接調用系統方法來選擇圖片。可是這樣並不能實現多選效果。近期又遇到了,所以還是寫一個demo好了。以后也方便使用。還是首先來看看效果

顯示的圖片使用RecyclerView實現的,利用Glide來載入;以下彈出的圖片目錄效果是採用PopupWindow實現,這里比採用PopupWindow更方便,彈出顯示的左邊圖片是這個目錄里的第一張圖片;選中的圖片能夠進行預覽,使用網上一個大神寫的來實現的;至於圖片的獲取是用ContentProvider。
看看主界面的布局文件。上面一欄是一個返回button和一個跳轉預覽界面的button。依據是否有選中的圖片來設置它的點擊和顯示狀態。中間就是一個用於顯示圖片的RecyclerView,左下角是顯示目錄的名字可點擊切換。右下角就是確定button。
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context="com.cdxsc.imageselect_y.ImageSelecteActivity">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="45dp"
android:background="@android:color/white">
<ImageButton
android:id="@+id/ib_back"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_marginLeft="10dp"
android:background="@mipmap/action_bar_back_normal" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_marginLeft="10dp"
android:layout_toRightOf="@id/ib_back"
android:text="選擇圖片"
android:textColor="#000"
android:textSize="16sp" />
<TextView
android:id="@+id/tv_preview"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:layout_centerVertical="true"
android:layout_marginRight="10dp"
android:enabled="false"
android:text="預覽"
android:textColor="#BEBFBF"
android:textSize="16sp" />
</RelativeLayout>
<View
android:layout_width="match_parent"
android:layout_height="0.5dp"
android:background="#eeeeee" />
<android.support.v7.widget.RecyclerView
android:id="@+id/rv"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"></android.support.v7.widget.RecyclerView>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="50dp">
<TextView
android:id="@+id/tv_allPic"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_centerVertical="true"
android:layout_marginLeft="10dp"
android:clickable="true"
android:gravity="center_vertical"
android:text="全部圖片"
android:textColor="@android:color/black"
android:textSize="16sp" />
<Button
android:id="@+id/bt_confirm"
android:layout_width="wrap_content"
android:layout_height="35dp"
android:layout_alignParentRight="true"
android:layout_centerVertical="true"
android:layout_marginRight="10dp"
android:background="@drawable/shape_disable"
android:enabled="false"
android:text="確定"
android:textColor="#676767"
android:textSize="16sp" />
</RelativeLayout>
</LinearLayout>
public class ImageSelecteActivity extends AppCompatActivity {
private static final String TAG = "lzy";
@BindView(R.id.ib_back)
ImageButton mButtonBack;
@BindView(R.id.tv_preview)
TextView mTextViewPreview;
@BindView(R.id.rv)
RecyclerView mRecyclerView;
@BindView(R.id.tv_allPic)
TextView mTextViewAllPic;
@BindView(R.id.bt_confirm)
Button mButtonConfirm;
private GalleryPopupWindow mPopupWindow;
//存儲每一個目錄下的圖片路徑,key是文件名稱
private Map<String, List<String>> mGroupMap = new HashMap<>();
private List<ImageBean> list = new ArrayList<>();
//當前目錄顯示的圖片路徑
private List<String> listPath = new ArrayList<>();
//所選擇的圖片路徑集合
private ArrayList<String> listSelectedPath = new ArrayList<>();
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
//掃描完畢后
getGalleryList();
listPath.clear();
listPath.addAll(mGroupMap.get("全部圖片"));
adapter.update(listPath);
if (mPopupWindow != null)
mPopupWindow.notifyDataChanged();
}
};
private ImageSelectAdapter adapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_image_selecte);
ButterKnife.bind(this);
init();
}
private void init() {
getImages();
mRecyclerView.setLayoutManager(new GridLayoutManager(ImageSelecteActivity.this, 3));
adapter = new ImageSelectAdapter(this, listPath);
mRecyclerView.setAdapter(adapter);
adapter.setOnCheckedChangedListener(onCheckedChangedListener);
}
@OnClick({R.id.ib_back, R.id.tv_preview, R.id.tv_allPic, R.id.bt_confirm})
public void onClick(View view) {
switch (view.getId()) {
case R.id.ib_back:
finish();
break;
case R.id.tv_preview://跳轉預覽界面
Intent intent = new Intent(ImageSelecteActivity.this, ImagePreviewActivity.class);
//把選中的圖片集合傳入預覽界面
intent.putStringArrayListExtra("pic", listSelectedPath);
startActivity(intent);
break;
case R.id.tv_allPic://選擇圖片目錄
if (mPopupWindow == null) {
//把目錄列表的集合傳入顯示
mPopupWindow = new GalleryPopupWindow(this, list);
mPopupWindow.setOnItemClickListener(new GalleryPopupWindow.OnItemClickListener() {
@Override
public void onItemClick(String fileName) {
//切換了目錄。清除之前的選擇的信息
setButtonDisable();
listPath.clear();
listSelectedPath.clear();
//把當前選擇的目錄內圖片的路徑放入listPath,更新界面
listPath.addAll(mGroupMap.get(fileName));
adapter.update(listPath);
mTextViewAllPic.setText(fileName);
}
});
}
mPopupWindow.showAtLocation(mRecyclerView, Gravity.BOTTOM, 0, dp2px(50, ImageSelecteActivity.this));
break;
case R.id.bt_confirm://確定
for (int i = 0; i < listSelectedPath.size(); i++) {
//這里可通過Glide把它轉為Bitmap
Glide.with(this).load("file://" + listSelectedPath.get(i)).asBitmap().into(new SimpleTarget<Bitmap>() {
@Override
public void onResourceReady(Bitmap resource, GlideAnimation<? super Bitmap> glideAnimation) {
Log.i(TAG, "onResourceReady: " + resource);
}
});
}
break;
}
}
/**
* dp轉px
*/
public static int dp2px(int dp, Context context) {
return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp,
context.getResources().getDisplayMetrics());
}
//選擇圖片變化的監聽
private ImageSelectAdapter.OnCheckedChangedListener onCheckedChangedListener = new ImageSelectAdapter.OnCheckedChangedListener() {
@Override
public void onChanged(boolean isChecked, String path, CheckBox cb, int position) {
if (isChecked) {//選中
if (listSelectedPath.size() == 9) {
Toast.makeText(ImageSelecteActivity.this, "最多選擇9張圖片", Toast.LENGTH_SHORT).show();
//把點擊變為checked的圖片變為沒有checked
cb.setChecked(false);
adapter.setCheckedBoxFalse(position);
return;
}
//選中的圖片路徑加入集合
listSelectedPath.add(path);
} else {//取消選中
//從集合中移除
if (listSelectedPath.contains(path))
listSelectedPath.remove(path);
}
//假設沒有選中的button不可點擊
if (listSelectedPath.size() == 0) {
setButtonDisable();
} else {
setButtonEnable();
}
}
};
//選中圖片時的button狀態
private void setButtonEnable() {
mButtonConfirm.setBackgroundResource(R.drawable.selector_bt);
mButtonConfirm.setTextColor(Color.parseColor("#ffffff"));
mButtonConfirm.setEnabled(true);
mTextViewPreview.setEnabled(true);
mTextViewPreview.setTextColor(getResources().getColor(R.color.colorAccent));
mButtonConfirm.setText("確定" + listSelectedPath.size() + "/9");
}
//沒有選擇時button狀態
private void setButtonDisable() {
mButtonConfirm.setBackgroundResource(R.drawable.shape_disable);
mButtonConfirm.setTextColor(Color.parseColor("#676767"));
mButtonConfirm.setEnabled(false);
mTextViewPreview.setEnabled(false);
mTextViewPreview.setTextColor(Color.parseColor("#BEBFBF"));
mButtonConfirm.setText("確定");
}
/**
* 利用ContentProvider掃描手機中的圖片。此方法在執行在子線程中
*/
private void getImages() {
new Thread(new Runnable() {
@Override
public void run() {
Uri mImageUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
ContentResolver mContentResolver = ImageSelecteActivity.this.getContentResolver();
//僅僅查詢jpeg和png的圖片
// Cursor mCursor = mContentResolver.query(mImageUri, null,
// MediaStore.Images.Media.MIME_TYPE + "=? or "
// + MediaStore.Images.Media.MIME_TYPE + "=? or " + MediaStore.Images.Media.MIME_TYPE + "=?",
// new String[]{"image/jpeg", "image/png", "image/jpg"}, MediaStore.Images.Media.DATE_MODIFIED);
Cursor mCursor = mContentResolver.query(mImageUri, null, null, null,
MediaStore.Images.Media.DATE_MODIFIED);
if (mCursor == null) {
return;
}
//存放全部圖片的路徑
List<String> listAllPic = new ArrayList<String>();
while (mCursor.moveToNext()) {
//獲取圖片的路徑
String path = mCursor.getString(mCursor
.getColumnIndex(MediaStore.Images.Media.DATA));
//獲取該圖片的父路徑名
String parentName = new File(path).getParentFile().getName();
listAllPic.add(path);
//依據父路徑名將圖片放入到mGruopMap中
if (!mGroupMap.containsKey(parentName)) {
List<String> chileList = new ArrayList<String>();
chileList.add(path);
mGroupMap.put(parentName, chileList);
} else {
mGroupMap.get(parentName).add(path);
}
}
//加入全部圖片
mGroupMap.put("全部圖片", listAllPic);
//通知Handler掃描圖片完畢
mHandler.sendEmptyMessage(0);
mCursor.close();
}
}).start();
}
//獲取相冊目錄列表
private void getGalleryList() {
Iterator<Map.Entry<String, List<String>>> iterator = mGroupMap.entrySet().iterator();
while (iterator.hasNext()) {
Map.Entry<String, List<String>> next = iterator.next();
ImageBean imageBean = new ImageBean();
imageBean.setFileName(next.getKey());
imageBean.setFirstPicPath(next.getValue().get(0));
imageBean.setCount(next.getValue().size());
if (next.getKey().equals("全部圖片"))
list.add(0, imageBean);
else
list.add(imageBean);
}
}
}
·mGroupMap:這個是以目錄名為key,目錄內的圖片路徑集合為value,也就是依照目錄來分別存儲了全部圖片的路徑。
·listPath:保存的是當前顯示在界面上的目錄內的圖片路徑集合
·listSelectedPath:保存用戶選中的圖片路徑
·list:保存的是ImageBean的集合。ImageBean保存了目錄名、里面首張圖片的路徑以及里面所包括圖片的數量,當切換目錄時用於顯示
·getImages():這種方法就是用來掃描手機里圖片並保存的,這是在子線程中執行的,顯示這可能是一個耗時的任務。通過ContentProvider獲取到一個包括全部圖片的Cursor,然后遍歷這個Cursor把所需的數據就保存在mGroupMap里面,最后利用Handler通知界面更新。
·getGalleryList():這種方法就是mGroupMap里面的數據來給list賦值,也就是產生一個現實目錄列表所需的數據集合。
·GalleryPopupWindow也就是我們用於顯示文件列表的,在67--84行就是一些GalleryPopupWindow的設置,調用showAtLocation方法把PopupWindow顯示在距離底部50dp的位置,並設置了點擊的回調,當切換了一個目錄后要做的相關操作就在這里進行。GalleryPopupWindow再待會再詳細看看
接下來再看看中間RecyclerView的Adapter
public class ImageSelectAdapter extends RecyclerView.Adapter<ImageSelectAdapter.NViewHolder> {
private Context context;
private List<String> list = new ArrayList<>();
private OnCheckedChangedListener onCheckedChangedListener;
private List<Boolean> listChecked = new ArrayList<>();
public ImageSelectAdapter(Context context, List<String> list) {
this.context = context;
this.list.addAll(list);
setListCheched(list);
}
public void update(List<String> list) {
this.list.clear();
this.list.addAll(list);
setListCheched(list);
notifyDataSetChanged();
}
/**
* 設置listChecked的初始值
*
* @param list
*/
private void setListCheched(List<String> list) {
listChecked.clear();
for (int i = 0; i < list.size(); i++) {
listChecked.add(false);
}
}
//當點擊超過了九張圖片,再點擊的設置為false
public void setCheckedBoxFalse(int pos) {
listChecked.set(pos, false);
}
public interface OnCheckedChangedListener {
/**
* @param isChecked 是否選中
* @param path 點擊的圖片路徑
* @param cb 點擊的CheckBox
* @param pos 點擊的位置
*/
void onChanged(boolean isChecked, String path, CheckBox cb, int pos);
}
public void setOnCheckedChangedListener(OnCheckedChangedListener onCheckedChangedListener) {
this.onCheckedChangedListener = onCheckedChangedListener;
}
@Override
public NViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
return new NViewHolder(LayoutInflater.from(context).inflate(R.layout.item_image_select, parent, false));
}
@Override
public void onBindViewHolder(final NViewHolder holder, final int position) {
Glide.with(context).load("file://" + list.get(position)).into(holder.iv);
holder.cb.setChecked(listChecked.get(position));
holder.itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
holder.cb.setChecked(!holder.cb.isChecked());
if (holder.cb.isChecked()) {
listChecked.set(position, true);
} else {
listChecked.set(position, false);
}
if (onCheckedChangedListener != null) {
onCheckedChangedListener.onChanged(holder.cb.isChecked(), list.get(position), holder.cb, position);
}
}
});
}
@Override
public int getItemCount() {
return list.size();
}
public class NViewHolder extends RecyclerView.ViewHolder {
@BindView(R.id.iv_itemImageSelect)
ImageView iv;
@BindView(R.id.cb_itemImageSelect)
CheckBox cb;
public NViewHolder(View itemView) {
super(itemView);
ButterKnife.bind(this, itemView);
}
}
}
這里Item的布局文件就是一個ImageView加一個CheckBox。依據選中狀態改變CheckBox的狀態,這里就不貼出來了。
·listChecked:這個集合是用來存儲每一個位置是否Check的,假設在onBindViewHolder里面不設置CheckBox的狀態的話,由於復用問題會出問題,所以想出了用一個集合來保存它們狀態的方法,不知道大家有沒有其它更好的方法。
·OnCheckedChangedListener:向外暴露的接口,把點擊的位置等參數都傳到Activity中去。
·update():這種方法用來更新界面的,沒有採用直接調notifyDataSetChanged方法是由於,假設數據的數量變化了那么listChecked的數量也要發生變化才行這樣才干相應。所以寫了這種方法。
再接着看看GalleryPopupWindow
/**
* Created by lzy on 2017/2/8.
*/
public class GalleryPopupWindow extends PopupWindow {
private static final String TAG = "lzy";
RecyclerView mRecyclerView;
private Activity activity;
private GalleryPopupWindow.OnItemClickListener onItemClickListener;
private List<ImageBean> list;
private GalleryAdapter adapter;
public GalleryPopupWindow(Activity context, List<ImageBean> list) {
super(context);
this.activity = context;
this.list = list;
LayoutInflater inflater = (LayoutInflater) context
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View contentView = inflater.inflate(R.layout.popu_gallery, null);
initView(contentView);
int h = context.getWindowManager().getDefaultDisplay().getHeight();
int w = context.getWindowManager().getDefaultDisplay().getWidth();
this.setContentView(contentView);
this.setWidth(w);
this.setHeight(ImageSelecteActivity.dp2px(350, context));
this.setFocusable(false);
this.setOutsideTouchable(true);
this.update();
setBackgroundDrawable(new ColorDrawable(000000000));
}
public void notifyDataChanged() {
adapter.notifyDataSetChanged();
}
private void initView(View contentView) {
mRecyclerView = (RecyclerView) contentView.findViewById(R.id.rv_gallery);
mRecyclerView.setLayoutManager(new LinearLayoutManager(activity));
adapter = new GalleryAdapter(list, activity);
adapter.setOnItemClickListener(new GalleryAdapter.OnItemClickListener() {
@Override
public void onItemClick(String fileName) {
if (onItemClickListener != null) {
onItemClickListener.onItemClick(fileName);
dismiss();
}
}
});
mRecyclerView.setAdapter(adapter);
}
//暴露點擊的接口
public interface OnItemClickListener {
/**
* @param keyValue
*/
void onItemClick(String keyValue);
}
public void setOnItemClickListener(OnItemClickListener onItemClickListener) {
this.onItemClickListener = onItemClickListener;
}
}
/**
* Created by lzy on 2017/2/8.
*/
public class GalleryAdapter extends RecyclerView.Adapter<GalleryAdapter.NViewHolder> {
private Context context;
private List<ImageBean> list;
private OnItemClickListener onItemClickListener;
//用於記錄是選中的哪一個目錄
private int selectedPos;
public GalleryAdapter(List<ImageBean> list, Context context) {
this.list = list;
this.context = context;
}
public interface OnItemClickListener {
void onItemClick(String fileName);
}
public void setOnItemClickListener(OnItemClickListener onItemClickListener) {
this.onItemClickListener = onItemClickListener;
}
@Override
public NViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
return new NViewHolder(LayoutInflater.from(context).inflate(R.layout.item_gallery, parent, false));
}
@Override
public void onBindViewHolder(NViewHolder holder, final int position) {
holder.itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
selectedPos = position;
notifyDataSetChanged();
if (onItemClickListener != null) {
onItemClickListener.onItemClick(list.get(position).getFileName());
}
}
});
if (position == selectedPos) {
holder.ivCheck.setVisibility(View.VISIBLE);
} else {
holder.ivCheck.setVisibility(View.GONE);
}
holder.tvCount.setText(list.get(position).getCount() + "張");
holder.tvName.setText(list.get(position).getFileName());
Glide.with(context).load("file://" + list.get(position).getFirstPicPath()).into(holder.iv);
}
@Override
public int getItemCount() {
return list.size();
}
public class NViewHolder extends RecyclerView.ViewHolder {
@BindView(R.id.iv_itemGallery)
ImageView iv;
@BindView(R.id.tv_itemGallery_name)
TextView tvName;
@BindView(R.id.tv_itemGallery_count)
TextView tvCount;
@BindView(R.id.iv_itemGallery_check)
ImageView ivCheck;
public NViewHolder(View itemView) {
super(itemView);
ButterKnife.bind(this, itemView);
}
}
}
這里有個接口是把點擊的文件名稱傳遞給PopupWindow。然后再給Activity。selectedPos是用來記錄選擇的是哪一個目錄。顯示相應的CheckBox。
這里就幾乎相同完畢了,感興趣的能夠下載Demo來看看。
再說一下,這里顯示圖片都是採用的Glide。使用也非常方便,我們獲取的圖片路徑都是文件路徑。假設要轉化為Bitmap也能夠直接調用Glide的方法就能夠輕松實現,例如以下所看到的:
Glide.with(this).load("file://" + listSelectedPath.get(i)).asBitmap().into(new SimpleTarget<Bitmap>() {
@Override
public void onResourceReady(Bitmap resource, GlideAnimation<? super Bitmap> glideAnimation) {
Log.i(TAG, "onResourceReady: " + resource);
}
});
當中找尋控件都沒有使用findViewById,而是採用的ButterKnife。節約了大量的時間,順便說說導入的方法
在app以下的build.gradle中加入以下:
apply plugin: 'com.neenbedankt.android-apt'
apt 'com.jakewharton:butterknife-compiler:8.1.0'
compile 'com.github.bumptech.glide:glide:3.5.2'
項目以下的build.gradle
//加入apt插件
classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
加入插件
File->Setting->Plugins 搜索zelezny。例如以下所看到的

當須要使用的時候。直接在光標移動到布局文件,點擊Alt+Insert。選擇Generate ButterKnife Injections
就出現例如以下界面,能夠自己主動生成了
源代碼地址:http://download.csdn.net/detail/lylodyf/9768761
