Android自定义View实现多图片选择控件
前言
相信很多朋友在开发中都会遇到图片上传的情况,尤其是多图上传,最经典的莫过于微信的图片选择了。所有很多情况下会使用到多图选择,所以就有了这篇文章,今天抽点时间写了个控件。
•支持自定义选择图片的样式
•支持设置图片选择数量
•支持图片预览,删除
•支持图片拍照
先来看看效果
实现分析
假如不定义控件,我们要实现这样一个功能,无非是写个gridview在item点击的时候去显示图片进行选择,在返回界面的时候进行gridview的数据刷新。我们把这些逻辑写在我们自定义的gridview中,就成了一个新的控件。
1、gridview的效果展示,逻辑实现。
public class imagepickerview extends gridview{ //图片选择数量 int maximagesize = 9; //添加item布局 private int noimgresource; //列选择数量 private int columnnumber = 3; activity context; imagesadapter adapter; list<string> imagelist;//图片选择list private static final int type_show_add = 0; private static final int type_no_show_add = 1; private boolean isshowadd = true; int imagegridsize; public void setnoimgresource(int noimgresource) { this.noimgresource = noimgresource; } public void setcolumnnumber(int columnnumber) { if (columnnumber>5){ columnnumber = 5; } this.columnnumber = columnnumber; this.setnumcolumns(columnnumber); } public void setshowadd(boolean showadd) { isshowadd = showadd; } public void setimagelist(list<string> imagelist) { this.imagelist = imagelist; adapter.setimagelist(imagelist); } public list<string> getimagelist() { return imagelist; } public imagepickerview(context context) { this(context,null); } public imagepickerview(context context, attributeset attrs) { this(context,attrs,0); } /** * 初始化imagepickerview的一些信息 * @param context * @param attrs * @param defstyle */ public imagepickerview(context context, attributeset attrs, int defstyle) { super(context, attrs, defstyle); this.context = (activity) context; adapter = new imagesadapter(); this.setadapter(adapter); if (imagelist==null){ imagelist = new arraylist<>(); } this.setnumcolumns(columnnumber); this.setverticalspacing(10); this.sethorizontalspacing(10); imagegridsize = (this.context.getwindowmanager().getdefaultdisplay().getwidth() - util.dp2px(context, 2) * 2) / columnnumber; } /** * 提供给外部调用用来再activity返回时获取图片信息 * @param requestcode * @param resultcode * @param data */ public void onactivityresult(int requestcode, int resultcode, intent data) { if (data!=null&& !textutils.isempty(data.getstringextra("photopath"))){//拍照 imagelist.add(data.getstringextra("photopath")); }else if (data!=null&&data.getserializableextra("images")!=null){//图片选择 imagelist = (list<string>) data.getserializableextra("images"); }else{ list<imageitem> list = androidimagepicker.getinstance().getselectedimages(); for (int i=0;i<list.size();i++){ imagelist.add(list.get(i).path); } } androidimagepicker.getinstance().setselectlimit(maximagesize-imagelist.size()); adapter.setimagelist(imagelist); } class imagesadapter extends baseadapter { list<string> imagelist; public imagesadapter() { this.imagelist = new arraylist(); } public void setimagelist(list<string> imagelist) { this.imagelist = imagelist; notifydatasetchanged(); } @override public int getcount() { if (isshowadd){ if (imagelist == null || imagelist.isempty()) { return 1; } if (imagelist.size() >= maximagesize) { return maximagesize; } return imagelist.size() + 1; } if (imagelist.size() >= maximagesize) { return maximagesize; } return imagelist.size()+1; } @override public string getitem(int position) { if (isshowadd){ if (position==imagelist.size()){ return null; } return imagelist.get(position-1); } return imagelist.get(position); } @override public long getitemid(int position) { return 0; } @override public int getviewtypecount() { return 2; } @override public int getitemviewtype(int position) { if (isshowadd){ return position==imagelist.size()?type_show_add:type_no_show_add; }else{ return type_no_show_add; } } @override public view getview(final int position, view convertview, viewgroup parent) { int itemviewtype = getitemviewtype(position); if(itemviewtype == type_show_add){//当前item为添加图片item if (noimgresource!=0){//加载用户的添加item布局 convertview = layoutinflater.from(context).inflate(noimgresource, parent, false); }else {//默认的添加item布局 convertview = layoutinflater.from(context).inflate(r.layout.grid_item_camera, parent, false); } convertview.settag(null); convertview.setonclicklistener(new view.onclicklistener() { @override public void onclick(view v) {//点击选择图片 intent intent = new intent(context, imagesgridactivity.class);//图片选择 activity activity = context; activity.startactivityforresult(intent,1001); } }); }else{//普通item,加载图片,并对item设置点击进行预览 final viewholder holder; if(convertview == null){ convertview = layoutinflater.from(context).inflate(r.layout.image_grid_item, null); holder = new viewholder(); holder.ivpic = (simpledraweeview)convertview.findviewbyid(r.id.iv_thumb); holder.cbpanel = convertview.findviewbyid(r.id.thumb_check_panel); convertview.settag(holder); }else{ holder = (viewholder) convertview.gettag(); } convertview.setonclicklistener(new onclicklistener() { @override public void onclick(view v) {//将选择的图片与当前postion传过去。 intent intent = new intent(context, previewdelactivity.class); intent.putextra("images", (serializable) imagelist); intent.putextra("position",position); context.startactivityforresult(intent,1002); } }); imagerequestbuilder requestbuilder = imagerequestbuilder.newbuilderwithsource( uri.parse(string.format("file://%s", imagelist.get(position)))) .setresizeoptions(new resizeoptions(imagegridsize, imagegridsize)) .setautorotateenabled(true); pipelinedraweecontroller controller = (pipelinedraweecontroller) fresco.newdraweecontrollerbuilder() .setoldcontroller(holder.ivpic.getcontroller()) .setimagerequest(requestbuilder.build()) .build(); holder.ivpic.setcontroller(controller); } return convertview; } } class viewholder{ simpledraweeview ivpic; view cbpanel; } }
代码比较简单,都加了注释。在view的初始化方法中获取一些信息和gridview的展示信息,并且设置适配器关联。
在gridview的 item中设置了点击事件,并且提供给外部用来刷新数据的onactivityresult方法。
大家看上面的代码应该就明白了。
2、图片预览和删除
这一步很简单,获取到传过来的imagelist和postion对图片进行展示,在点击删除的时候remove掉imagelist对应的数据,并且刷新viewpager
直接上代码
public class previewdelactivity extends appcompatactivity implements view.onclicklistener { private static final string tag = imagepreviewactivity.class.getsimplename(); textview mtitlecount; textview mbtnok; private imageview backbtn; list<string> mimagelist; int mshowitemposition = 0; viewpager mviewpager; touchimageadapter madapter ; @override protected void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.activity_preview_del); mimagelist = (list<string>) getintent().getserializableextra("images"); mshowitemposition = getintent().getintextra("position",0); mbtnok = (textview) findviewbyid(r.id.btn_del); backbtn = (imageview) findviewbyid(r.id.btn_backpress); mbtnok.setonclicklistener(this); backbtn.setonclicklistener(this); mtitlecount = (textview) findviewbyid(r.id.tv_title_count); mtitlecount.settext(mshowitemposition+1+"/" + mimagelist.size());// 图片数量和当前图片信息展示 initview(); androidimagepicker.getinstance().clearselectedimages(); } private void initview() { mviewpager = (viewpager)findviewbyid(r.id.viewpager); madapter = new touchimageadapter(getsupportfragmentmanager()); mviewpager.setadapter(madapter); mviewpager.setcurrentitem(mshowitemposition, false);//设置显示当前的图片 mviewpager.addonpagechangelistener(new viewpager.onpagechangelistener() { @override public void onpagescrolled(int position, float positionoffset, int positionoffsetpixels) { } @override public void onpageselected(int position) { mtitlecount.settext(position+1+"/" + mimagelist.size());//滑动viewpager时更新显示信息 } @override public void onpagescrollstatechanged(int state) { } }); } @override public void onclick(view v) { int i = v.getid(); if (i == r.id.btn_del) {//删除按钮点击 madapter.remove(mviewpager.getcurrentitem());// mtitlecount.settext(mviewpager.getcurrentitem()+1+"/" + mimagelist.size()); if (mimagelist.size()==0){ intent intent = new intent(); intent.putextra("images", (serializable) mimagelist); setresult(result_ok,intent); finish(); } }else if (i==r.id.btn_backpress){//返回 intent intent = new intent(); intent.putextra("images", (serializable) mimagelist); setresult(result_ok,intent); finish(); } } @override public boolean onkeydown(int keycode, keyevent event) { if (keycode==keyevent.keycode_back){ intent intent = new intent(); intent.putextra("images", (serializable) mimagelist); setresult(result_ok,intent); finish(); return true; } return super.onkeydown(keycode, event); } class touchimageadapter extends fragmentstatepageradapter { public touchimageadapter(fragmentmanager fm) { super(fm); } @override public int getcount() { return mimagelist.size(); } public void remove(int position){ mimagelist.remove(position); notifydatasetchanged(); } @override public int getitemposition(object object) { return position_none; } @override public fragment getitem(int position) { singlepreviewfragment fragment = new singlepreviewfragment(); bundle bundle = new bundle(); bundle.putserializable(singlepreviewfragment.key_url, mimagelist.get(position)); fragment.setarguments(bundle); return fragment; } } @suppresslint("validfragment") private class singlepreviewfragment extends fragment { public static final string key_url = "key_url"; private photodraweeview photodraweeview; private string url; @override public void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); bundle bundle = getarguments(); url = (string) bundle.getserializable(key_url); log.i(tag, "=====current show image path:" + url); photodraweeview = new photodraweeview(getactivity()); photodraweeview.setbackgroundcolor(0xff000000); viewgroup.layoutparams params = new viewgroup.layoutparams(viewgroup.layoutparams.match_parent,viewgroup.layoutparams.match_parent); photodraweeview.setlayoutparams(params); photodraweeview.setonphototaplistener(new onphototaplistener() { @override public void onphototap(view view, float x, float y) { getactivity().finish(); } }); if (!url.startswith("http://") && !url.startswith("https://")) { url = "file://"+url; } imagerequestbuilder requestbuilder = imagerequestbuilder.newbuilderwithsource( uri.parse(url)) .setresizeoptions(new resizeoptions(768,1280)) .setautorotateenabled(true); pipelinedraweecontrollerbuilder controller = fresco.newdraweecontrollerbuilder(); controller.setoldcontroller(photodraweeview.getcontroller()); controller.setimagerequest(requestbuilder.build()); controller.setcontrollerlistener(new basecontrollerlistener<imageinfo>() { @override public void onfinalimageset(string id, imageinfo imageinfo, animatable animatable) { super.onfinalimageset(id, imageinfo, animatable); if (imageinfo == null) { return; } photodraweeview.update(imageinfo.getwidth(), imageinfo.getheight()); } }); photodraweeview.setcontroller(controller.build()); } @override public view oncreateview(layoutinflater inflater, viewgroup container, bundle savedinstancestate) { return photodraweeview; } } }
相信大家都能看懂上面代码,用viewpager对图片进行加载,在点击回退和删除完图片的时候把imagelist传回去。
这样在onactivityresult中获取到imagelist,刷新adapter,gridview就重新渲染了。
使用
1、布局中引入imagepickerview
<?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:paddingbottom="@dimen/activity_vertical_margin" android:paddingleft="@dimen/activity_horizontal_margin" android:paddingright="@dimen/activity_horizontal_margin" android:paddingtop="@dimen/activity_vertical_margin" android:orientation="vertical" tools:context="com.qiangyu.test.imagepickerviewdemo.mainactivity"> <com.redare.imagepicker.widget.imagepickerview android:id="@+id/imagepicker" android:layout_width="match_parent" android:layout_height="wrap_content"> </com.redare.imagepicker.widget.imagepickerview> <button android:id="@+id/commit_btn" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="提交"/> </linearlayout>
2、在activity中获得imagepickerview,并且在onactivityresult方法中调用imagepickerview数据刷新方法
imagepicker.onactivityresult(requestcode,resultcode,data);
3、获取选择图片的路径
调用imagepicker.getimagelist()即返回图片选择的list
下面是一段代码示例
public class mainactivity extends appcompatactivity { private imagepickerview imagepicker; private button commitbtn; @override protected void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.activity_main); fresco.initialize(this); imagepicker = (imagepickerview) findviewbyid(r.id.imagepicker); commitbtn = (button) findviewbyid(r.id.commit_btn); commitbtn.setonclicklistener(new view.onclicklistener() { @override public void onclick(view v) { for (int i=0;i<imagepicker.getimagelist().size();i++){ log.d("yqy",imagepicker.getimagelist().get(i)); } } }); imagepicker.setnoimgresource(r.layout.add_img);//自定义imagepicker add item样式 imagepicker.setcolumnnumber(5);//设置显示5列 } @override protected void onactivityresult(int requestcode, int resultcode, intent data) { imagepicker.onactivityresult(requestcode,resultcode,data); } }
结语
看了imagepickerview的实现,是不是发现一个自定义控件其实也很简单。在我们做自定义控件的时候,其实大部分情况只是在android系统提供的功能上多加一点我们的需求而已。站在巨人的肩膀上,才可以看的更远嘛。
关于图片加载和图片选择本文没有提及,图片加载我参考了文章,感兴趣的朋友可以去github查看,https://github.com/easonline/androidimagepicker。我在自己的demo中对源码做了修改,并统一使用了fresco加载图片。
有需要参考源码的同学请参考:源码下载
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。
下一篇: Android 开源在线音乐播放器