欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

Android实现本地图片选择及预览缩放效果

程序员文章站 2023-12-15 23:35:28
在做项目时经常会遇到选择本地图片的需求,以前都是懒得写直接调用系统方法来选择图片,但是这样并不能实现多选效果,最近又遇到了,所以还是写一个demo好了,以后也方便使用。还是...

在做项目时经常会遇到选择本地图片的需求,以前都是懒得写直接调用系统方法来选择图片,但是这样并不能实现多选效果,最近又遇到了,所以还是写一个demo好了,以后也方便使用。还是首先来看看效果:

Android实现本地图片选择及预览缩放效果

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

这个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' 

添加插件
file->setting->plugins  搜索zelezny,如下所示

Android实现本地图片选择及预览缩放效果

当需要使用的时候,直接在光标移动到布局文件,点击alt+insert,选择generate butterknife injections

Android实现本地图片选择及预览缩放效果

就出现如下界面,可以自动生成了

Android实现本地图片选择及预览缩放效果

源码地址:android图片选择及预览缩放

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。

上一篇:

下一篇: