Android实现本地图片选择及预览缩放效果仿春雨医生

知识为进步之母,而进步又为富强之源泉。这篇文章主要讲述Android实现本地图片选择及预览缩放效果仿春雨医生相关的知识,希望能为你提供帮助。
在做项目时常常会遇到选择本地图片的需求。曾经都是懒得写直接调用系统方法来选择图片。可是这样并不能实现多选效果。近期又遇到了,所以还是写一个demo好了。以后也方便使用。还是首先来看看效果

Android实现本地图片选择及预览缩放效果仿春雨医生

文章图片


显示的图片使用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; } }




这个PopupWindow的布局文件就是一个RecyclerView,所以这里面也没什么。也就是设置RecyclerView。然后向外暴露一个点击的接口,用于Activity接收是点击了哪个目录,所以接口參数也就是目录名,再看看这个PopupWindow的Adapter
/** * 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‘


加入插件【Android实现本地图片选择及预览缩放效果仿春雨医生】
File-> Setting-> Plugins ?搜索zelezny。例如以下所看到的
Android实现本地图片选择及预览缩放效果仿春雨医生

文章图片



当须要使用的时候。直接在光标移动到布局文件,点击Alt+Insert。选择Generate ButterKnife Injections
Android实现本地图片选择及预览缩放效果仿春雨医生

文章图片

就出现例如以下界面,能够自己主动生成了
Android实现本地图片选择及预览缩放效果仿春雨医生

文章图片





源代码地址:http://download.csdn.net/detail/lylodyf/9768761


    推荐阅读