Android 高仿微信朋友圈动态支持双击手势放大并滑动查看图片效果
最近参与了开发一款旅行app,其中包含实时聊天和动态评论功能,终于耗时几个月几个伙伴完成了,今天就小结一下至于实时聊天功能如果用户不多的情况可以scoket实现,如果用户万级就可以采用开源的smack + opnefile实现,也可以用mina开源+xmmp,至于怎么搭建和实现,估计目前github上一搜一大把,至于即时通讯怕误人子弟,暂且不做介绍,现就把实现的一个微信朋友圈的小功能介绍一下。
先上效果图:
一拿到主流的ui需求,大致分析下,需要我listview嵌套gridview,而gridview的行数也和图片总数有关系,因此通过个数我们可以动态设置条目的宽高,而点击图片放大我们可一跳转到另一界面,图片左右滑动可以用viewpager实现,双击图片放大和手指缩放图片也可以用就监听手势进行不断放大,对于安卓事件不熟悉的朋友可以直接使用一个著名的photoview开源项目,支持手势缩放图片和滑动图片实现画廊功能,也很好的解决了内存溢出问题。
一 配置imageloader
本项目中加载网络图片我就直接使用imageloader,但建议还是去看下源码,因为开源项目本身自带缓存机制,有很好的缓存技巧,有很多东西值得我们借鉴。其不仅可以加载本地图片(文件path),也支持加载网络图片(url),并且自带防止内存溢出功能。
public class myapplication extends application { @override public void oncreate() { super.oncreate(); displayimageoptions defaultoptions = new displayimageoptions .builder() .showimageforemptyuri(r.drawable.empty_photo) .showimageonfail(r.drawable.empty_photo) .cacheinmemory(true) .cacheondisc(true) .build(); imageloaderconfiguration config = new imageloaderconfiguration .builder(getapplicationcontext()) .defaultdisplayimageoptions(defaultoptions) .disccachesize(50 * 1024 * 1024)// .disccachefilecount(100)//缓存一百张图片 .writedebuglogs() .build(); imageloader.getinstance().init(config); } }
二 准备主界面和需要的基础类
1 listadapter
public class fridlistadapter extends baseadapter{ private arraylist<mybean> mlist; private layoutinflater minflater; private context mcontext; public fridlistadapter(context context,arraylist<mybean> list) { minflater = layoutinflater.from(context); mcontext=context; this.mlist=list; } @override public int getcount() { return mlist==null?0:mlist.size(); } @override public mybean getitem(int position) { return mlist.get(position); } @override public long getitemid(int position) { return getitem(position).id; } @override public view getview(int position, view convertview, viewgroup parent) { viewholder holder; if (convertview == null) { holder = new viewholder(); convertview = minflater.inflate(r.layout.list_item, null); holder.avator=(imageview)convertview.findviewbyid(r.id.avator); holder.name=(textview)convertview.findviewbyid(r.id.name); holder.content = (textview) convertview.findviewbyid(r.id.content); holder.gridview=(noscrollgridview)convertview.findviewbyid(r.id.gridview); convertview.settag(holder); } else { holder = (viewholder) convertview.gettag(); } final mybean bean = getitem(position); //加载网络图片 imageloader.getinstance().displayimage(bean.avator, holder.avator); holder.name.settext(bean.name); holder.content.settext(bean.content); if(bean.urls!=null&&bean.urls.length>0){ holder.gridview.setvisibility(view.visible); holder.gridview.setadapter(new dynamicgridadapter(bean.urls, mcontext)); holder.gridview.setonitemclicklistener(new adapterview.onitemclicklistener() { @override public void onitemclick(adapterview<?> parent, view view, int position, long id) { imagebrower(position,bean.urls); } }); }else{ holder.gridview.setvisibility(view.gone); } return convertview; } private void imagebrower(int position, string[] urls) { intent intent = new intent(mcontext, imagepageractivity.class); // 图片url,为了演示这里使用常量,一般从数据库中或网络中获取 intent.putextra(imagepageractivity.extra_image_urls, urls); intent.putextra(imagepageractivity.extra_image_index, position); mcontext.startactivity(intent); } // 优化listview private static class viewholder { public textview name; public imageview avator; textview content; noscrollgridview gridview; } }
2 主界面
实际项目中数据是数据是从服务器获取的,本次就只将图片从网络获取,
public class mainactivity extends listactivity { public static final string tag = "mainactivity"; private fridlistadapter madapter; @override public void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.activity_main); new loderdatatask().execute(); } class loderdatatask extends asynctask<void, void, messagemodle> { @override protected messagemodle doinbackground(void... params) { gson gson = new gson(); messagemodle msg = gson.fromjson(getdata(), messagemodle.class); return msg; } @override protected void onpostexecute(messagemodle result) { madapter = new fridlistadapter(mainactivity.this, result.list); setlistadapter(madapter); } } private string getdata() { // 模拟网络获取数据 string json = "{\"code\":200,\"msg\":\"ok\",list:[" + "{\"id\":110,\"avator\":\"http://img0.bdstatic.com/img/image/shouye/leimu/mingxing.jpg\",\"name\":\"赵薇\",\"content\":\"今天不开心!\",\"urls\":[]}," + "{\"id\":111,\"avator\":\"http://image.cnwest.com/attachement/jpg/site1/20110507/001372d8a36f0f2f4c953a.jpg\",\"name\":\"李晨\",\"content\":\"我们\"," + " \"urls\":[\"http://guangdong.sinaimg.cn/2015/0530/u11307p693dt20150530094310.jpg\"]}," + "{\"id\":114,\"avator\":\"http://img.hexun.com/2009-05-01/117287830.jpg\",\"name\":\"小马哥\",\"content\":\"今天淘宝了吗\",\"urls\":[" + "\"http://g.hiphotos.bdimg.com/album/s%3d680%3bq%3d90/sign=ccd33b46d53f8794d7ff4b26e2207fc9/0d338744ebf81a4c0f993437d62a6059242da6a1.jpg\"," + "\"http://f.hiphotos.bdimg.com/album/s%3d680%3bq%3d90/sign=6b62f61bac6eddc422e7b7f309e0c7c0/6159252dd42a2834510deef55ab5c9ea14cebfa1.jpg\"," + "\"http://g.hiphotos.bdimg.com/album/s%3d680%3bq%3d90/sign=e58fb67bc8ea15ce45eee301863b4bce/a5c27d1ed21b0ef4fd6140a0dcc451da80cb3e47.jpg\"," + "\"http://c.hiphotos.bdimg.com/album/s%3d680%3bq%3d90/sign=cdab1512d000baa1be2c44b3772bc82f/91529822720e0cf3855c96050b46f21fbf09aaa1.jpg\"]}," + "{\"id\":112,\"avator\":\"http://img3.yxlady.com/yl/uploadfiles_5361/20150528/20150528050208705.jpg\",\"name\":\"邓超\",\"content\":\"奔跑吧兄弟! 欢迎收看!\",\"urls\":[\"http://upload.cbg.cn/2015/0305/1425518659246.jpg\"," + "\"http://www.people.com.cn/mediafile/pic/20150619/30/4179219540177204330.jpg\"]}," + "{\"id\":113,\"avator\":\"http://img4.imgtn.bdimg.com/it/u=945108765,1070109457&fm=21&gp=0.jpg\",\"name\":\"奥巴马\",\"content\":\"holle\",\"urls\":[\"http://f.hiphotos.bdimg.com/album/s%3d680%3bq%3d90/sign=6b62f61bac6eddc422e7b7f309e0c7c0/6159252dd42a2834510deef55ab5c9ea14cebfa1.jpg\",\"http://g.hiphotos.bdimg.com/album/s%3d680%3bq%3d90/sign=e58fb67bc8ea15ce45eee301863b4bce/a5c27d1ed21b0ef4fd6140a0dcc451da80cb3e47.jpg\",\"http://c.hiphotos.bdimg.com/album/s%3d680%3bq%3d90/sign=cdab1512d000baa1be2c44b3772bc82f/91529822720e0cf3855c96050b46f21fbf09aaa1.jpg\"]}]}"; return json; }
3 gridview的adapter
因为listview的条目中包含gridview,在这里还需要为它创建atapter
由于adapter没太多技术含量,因此重点部分列出,在这里我们需要判断下适配的数据眼总数,微信最大数是9张,显示一张的时候,图片比较大,两张的时候稍微减少,四张的时候两列两行和两张的大小一致,其他张数的时候都是三行三列的九宫格。
@override public view getview(int position, view convertview, viewgroup parent) { mygridviewholder viewholder; if (convertview == null) { viewholder = new mygridviewholder(); convertview = mlayoutinflater.inflate(r.layout.gridview_item, parent, false); viewholder.imageview = (imageview) convertview .findviewbyid(r.id.album_image); convertview.settag(viewholder); } else { viewholder = (mygridviewholder) convertview.gettag(); } string url = getitem(position); if (getcount() == 1) { viewholder.imageview.setlayoutparams(new android.widget.abslistview.layoutparams(300, 250)); } if (getcount() == 2 ||getcount() == 4) { viewholder.imageview.setlayoutparams(new android.widget.abslistview.layoutparams(200, 200)); } imageloader.getinstance().displayimage(url, viewholder.imageview); return convertview; }
4 新建用于支持九宫格自定义的gridview
public class noscrollgridview extends gridview { public noscrollgridview(context context) { super(context); } public noscrollgridview(context context, attributeset attrs) { super(context, attrs); } @override protected void onmeasure(int widthmeasurespec, int heightmeasurespec) { int expandspec = 0; int size = getadapter().getcount(); if (size == 1) { setnumcolumns(1); } if ( size==2 || size == 4 ) { setnumcolumns(2); } else { setnumcolumns(3); } expandspec = measurespec.makemeasurespec(integer.max_value >> 2,measurespec.at_most); super.onmeasure(widthmeasurespec,expandspec ); } }
三 点击图片后的基础类
1 建立大图查看器viewpaer
public class imagepageractivity extends fragmentactivity { private static final string state_position = "state_position"; public static final string extra_image_index = "image_index"; public static final string extra_image_urls = "image_urls"; private hackyviewpager mpager; private int pagerposition; private textview indicator; @override public void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.image_detail_pager); pagerposition = getintent().getintextra(extra_image_index, 0); string[] urls = getintent().getstringarrayextra(extra_image_urls); mpager = (hackyviewpager) findviewbyid(r.id.pager); imagepageradapter madapter = new imagepageradapter( getsupportfragmentmanager(), urls); mpager.setadapter(madapter); indicator = (textview) findviewbyid(r.id.indicator); charsequence text = getstring(r.string.viewpager_indicator, 1, mpager .getadapter().getcount()); indicator.settext(text); // 更新下标 mpager.setonpagechangelistener(new onpagechangelistener() { @override public void onpagescrollstatechanged(int arg0) { } @override public void onpagescrolled(int arg0, float arg1, int arg2) { } @override public void onpageselected(int arg0) { charsequence text = getstring(r.string.viewpager_indicator, arg0 + 1, mpager.getadapter().getcount()); indicator.settext(text); } }); if (savedinstancestate != null) { pagerposition = savedinstancestate.getint(state_position); } mpager.setcurrentitem(pagerposition); } @override public void onsaveinstancestate(bundle outstate) { outstate.putint(state_position, mpager.getcurrentitem()); } private class imagepageradapter extends fragmentstatepageradapter { public string[] filelist; public imagepageradapter(fragmentmanager fm, string[] filelist) { super(fm); this.filelist = filelist; } @override public int getcount() { return filelist == null ? 0 : filelist.length; } @override public fragment getitem(int position) { string url = filelist[position]; return imagedetailfragment.newinstance(url); } }
2 查看大图界面
public class imagedetailfragment extends fragment { private string mimageurl; private imageview mimageview; private progressbar progressbar; private photoviewattacher mattacher; public static imagedetailfragment newinstance(string imageurl) { final imagedetailfragment f = new imagedetailfragment(); final bundle args = new bundle(); args.putstring("url", imageurl); f.setarguments(args); return f; } @override public void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); mimageurl = getarguments() != null ? getarguments().getstring("url") : null; } @override public view oncreateview(layoutinflater inflater, viewgroup container, bundle savedinstancestate) { final view v = inflater.inflate(r.layout.image_detail_fragment, container, false); mimageview = (imageview) v.findviewbyid(r.id.image); mattacher = new photoviewattacher(mimageview); mattacher.setonphototaplistener(new onphototaplistener() { @override public void onphototap(view arg0, float arg1, float arg2) { getactivity().finish(); } }); progressbar = (progressbar) v.findviewbyid(r.id.loading); return v; } @override public void onactivitycreated(bundle savedinstancestate) { super.onactivitycreated(savedinstancestate); imageloader.getinstance().displayimage(mimageurl, mimageview, new simpleimageloadinglistener() { @override public void onloadingstarted(string imageuri, view view) { progressbar.setvisibility(view.visible); } @override public void onloadingfailed(string imageuri, view view, failreason failreason) { string message = null; switch (failreason.gettype()) { case io_error: message = "下载错误"; break; case decoding_error: message = "图片无法显示"; break; case network_denied: message = "网络有问题,无法下载"; break; case out_of_memory: message = "图片太大无法显示"; break; case unknown: message = "未知的错误"; break; } toast.maketext(getactivity(), message, toast.length_short).show(); progressbar.setvisibility(view.gone); } @override public void onloadingcomplete(string imageuri, view view, bitmap loadedimage) { progressbar.setvisibility(view.gone); mattacher.update(); } }); }
四 界面的头像圆形
圆形头像用主流的circleimageview.jar的框架,但是有兴趣的朋友也可以自定义imagview采用重写ondrawi()画圆形的方式将bitmap画上去,由于此demo整体功能较复杂,因此使用第三方的东西,listview条目布局如下:
<relativelayout 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:padding="6dp" > <de.hdodenhof.circleimageview.circleimageview android:id="@+id/avator" android:layout_width="48dp" android:layout_height="48dp" android:src="@drawable/empty_photo" /> <textview android:id="@+id/name" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginleft="10dp" android:layout_torightof="@id/avator" android:textcolor="#576b95" android:textsize="16sp" android:text="name" /> <textview android:id="@+id/content" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_below="@+id/name" android:layout_marginleft="10dp" android:textsize="12sp" android:layout_torightof="@id/avator" android:text="content" /> <com.loveplusplus.demo.image.noscrollgridview android:id="@+id/gridview" android:layout_width="wrap_content" android:layout_height="wrap_content" android:paddingtop="5dp" android:layout_below="@id/content" android:layout_marginleft="10dp" android:layout_torightof="@id/avator" android:horizontalspacing="1dp" android:numcolumns="3" android:visibility="gone" android:verticalspacing="1dp" /> </relativelayout>
接下来我们还需要将主流的photoview.jar加入到工程中,
总结一下实现以上功能我们使用了第三的imagloader,支持手势缩放的photoview,圆形图像的circleimageview,熟悉安卓view绘制机制加载过程,事件传递和分发的朋友是不需要第三方开源项目的支持的,但是对于入门不久的同学,学会怎样使用开源框架就可以,但是想要提高开源项目的的核心还是需要了解的,欢迎阅读
运行效果图:
有兴趣的朋友建议阅读下:
安卓事件机制(一)和上篇关于view的博文。谢谢交流和分享。
demo源码下载地址:https://github.com/tamicer/chatmomentdemo
以上所述是小编给大家介绍的android 高仿微信朋友圈动态支持双击手势放大并滑动查看图片效果,希望对大家有所帮助