Android實現本地圖片選擇及預覽縮放效果仿春雨醫生
在做專案時經常會遇到選擇本地圖片的需求,以前都是懶得寫直接呼叫系統方法來選擇圖片,但是這樣並不能實現多選效果,最近又遇到了,所以還是寫一個demo好了,以後也方便使用。還是首先來看看效果
顯示的圖片使用RecyclerView實現的,利用Glide來載入;下面彈出的圖片資料夾效果是採用PopupWindow實現,這裡比採用PopupWindow更方便,彈出顯示的左邊圖片是這個資料夾裡的第一張圖片;選中的圖片可以進行預覽,使用網上一個大神寫的來實現的;至於圖片的獲取是用ContentProvider。
看看主介面的佈局檔案,上面一欄是一個返回按鈕和一個跳轉預覽介面的按鈕,根據是否有選中的圖片來設定它的點選和顯示狀態;中間就是一個用於顯示圖片的RecyclerView,左下角是顯示資料夾的名字可點選切換,右下角就是確定按鈕。
<?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); } //如果沒有選中的按鈕不可點選 if (listSelectedPath.size() == 0) { setButtonDisable(); } else { setButtonEnable(); } } }; //選中圖片時的按鈕狀態 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"); } //沒有選擇時按鈕狀態 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