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

Android仿微信朋友圈图片查看器

程序员文章站 2024-02-29 22:57:10
再看文章之前,希望大家先打开自己的微信点到朋友圈中去,仔细观察是不是发现朋友圈里的有个“九宫格”的图片区域,点击图片又会跳到图片的详细查看页面,并且支持图片的滑动和缩放?这...

再看文章之前,希望大家先打开自己的微信点到朋友圈中去,仔细观察是不是发现朋友圈里的有个“九宫格”的图片区域,点击图片又会跳到图片的详细查看页面,并且支持图片的滑动和缩放?这个功能是不是很常用呢?!那么我今天正好做了这个demo,下面为大家讲解一下。首先按照惯例先看一下效果图吧,尤其不会录制gif动画(哎~没办法,模拟器不支持多点触控,刚好我的手机又没有root,不能录屏,悲催啊,大家见谅,想要看真实效果的话,烦请移到文章最下方转载文章中进行源码下载,点击下载源码,运行后再看效果哈~~),这里先就拿几张静态的图片顶替一下好了。见谅!

Android仿微信朋友圈图片查看器

        效果嘛,将就着看吧!实在看不明白就想想微信朋友圈,或者拖到下方,点击下载源码!这里,首先分析一下主界面吧,布局都是很简单的,主界面仅仅就是一个listview的控件,listview的item上值得注意的是,item上包含了一个gridview,这个gridview呗用作实现“九宫格”的效果,主界面布局就是一个listview,这里不说了,我们先来看看listview的item的布局吧,以下是item_list.xml

<?xml version="1.0" encoding="utf-8"?> 
<relativelayout xmlns:android="http://schemas.android.com/apk/res/android" 
 android:layout_width="match_parent" 
 android:layout_height="match_parent" 
 android:paddingbottom="5dp" 
 android:paddingtop="5dp" > 
 
 <imageview 
 android:id="@+id/iv_avatar" 
 android:layout_width="50dp" 
 android:layout_height="50dp" 
 android:background="@drawable/ic_launcher" 
 android:scaletype="centercrop" /> 
 
 <textview 
 android:id="@+id/tv_title" 
 android:layout_width="wrap_content" 
 android:layout_height="wrap_content" 
 android:layout_marginleft="5dp" 
 android:layout_torightof="@id/iv_avatar" 
 android:text="爷,今天心情好!" 
 android:textsize="16sp" /> 
 
 <textview 
 android:id="@+id/tv_content" 
 android:layout_width="wrap_content" 
 android:layout_height="wrap_content" 
 android:layout_below="@+id/tv_title" 
 android:layout_marginleft="5dp" 
 android:layout_margintop="3dp" 
 android:layout_torightof="@id/iv_avatar" 
 android:text="今天又是雾霾!" 
 android:textsize="16sp" /> 
 
 <com.example.imagedemo.noscrollgridview 
 android:id="@+id/gridview" 
 android:layout_width="220dp" 
 android:layout_height="wrap_content" 
 android:layout_below="@id/tv_content" 
 android:layout_marginleft="5dp" 
 android:layout_margintop="3dp" 
 android:layout_torightof="@id/iv_avatar" 
 android:columnwidth="70dp" 
 android:gravity="center" 
 android:horizontalspacing="2.5dp" 
 android:numcolumns="3" 
 android:stretchmode="columnwidth" 
 android:verticalspacing="2.5dp" /> 
 
</relativelayout> 

         好了,大家看到了,布局也是极其简单的,但是有个问题就是listview嵌套进了gridview,那么就会出现一个问题,导致gridview显示的不全,那么该怎么解决这个问题呢?其实也简单,就是重写一个gridview,测量一下gridview的高度,再设置上去。具体解决方案请看上篇博文listview嵌套gridview显示不全解决方法或者源码,如下noscrollgridview.java

package com.example.imagedemo; 
 
import android.content.context; 
import android.util.attributeset; 
import android.widget.gridview; 
 
/** 
 * 自定义的“九宫格”——用在显示帖子详情的图片集合 解决的问题:gridview显示不全,只显示了一行的图片,比较奇怪,尝试重写gridview来解决 
 * 
 * @author lichao 
 * @since 2014-10-16 16:41 
 * 
 */ 
public class noscrollgridview extends gridview { 
 
 public noscrollgridview(context context) { 
 super(context); 
 // todo auto-generated constructor stub 
 } 
 
 public noscrollgridview(context context, attributeset attrs) { 
 super(context, attrs); 
 // todo auto-generated constructor stub 
 } 
 
 public noscrollgridview(context context, attributeset attrs, int defstyle) { 
 super(context, attrs, defstyle); 
 // todo auto-generated constructor stub 
 } 
 
 @override 
 protected void onmeasure(int widthmeasurespec, int heightmeasurespec) { 
 // todo auto-generated method stub 
 int expandspec = measurespec.makemeasurespec(integer.max_value >> 2, 
  measurespec.at_most); 
 super.onmeasure(widthmeasurespec, expandspec); 
 } 
 
} 

        接下来看看listview上面item的实体是什么样的数据结构,这就显得非常简单了。

public class itementity { 
 private string avatar; // 用户头像url 
 private string title; // 标题 
 private string content; // 内容 
 private arraylist<string> imageurls; // 九宫格图片的url集合 
 
 public itementity(string avatar, string title, string content, 
  arraylist<string> imageurls) { 
 super(); 
 this.avatar = avatar; 
 this.title = title; 
 this.content = content; 
 this.imageurls = imageurls; 
 } 
 ... 
} 

        好了,有了listview,那么不可避免的就是做item上的数据适配了。继承一个baseadapter,代码如下,都比较简单:

/** 
 * 首页listview的数据适配器 
 * 
 * @author administrator 
 * 
 */ 
public class listitemadapter extends baseadapter { 
 
 private context mcontext; 
 private arraylist<itementity> items; 
 
 public listitemadapter(context ctx, arraylist<itementity> items) { 
 this.mcontext = ctx; 
 this.items = items; 
 } 
 
 @override 
 public int getcount() { 
 return items == null ? 0 : items.size(); 
 } 
 
 @override 
 public object getitem(int position) { 
 return items.get(position); 
 } 
 
 @override 
 public long getitemid(int position) { 
 return position; 
 } 
 
 @override 
 public view getview(int position, view convertview, viewgroup parent) { 
 viewholder holder; 
 if (convertview == null) { 
  holder = new viewholder(); 
  convertview = view.inflate(mcontext, r.layout.item_list, null); 
  holder.iv_avatar = (imageview) convertview 
   .findviewbyid(r.id.iv_avatar); 
  holder.tv_title = (textview) convertview 
   .findviewbyid(r.id.tv_title); 
  holder.tv_content = (textview) convertview 
   .findviewbyid(r.id.tv_content); 
  holder.gridview = (noscrollgridview) convertview 
   .findviewbyid(r.id.gridview); 
  convertview.settag(holder); 
 } else { 
  holder = (viewholder) convertview.gettag(); 
 } 
 itementity itementity = items.get(position); 
 holder.tv_title.settext(itementity.gettitle()); 
 holder.tv_content.settext(itementity.getcontent()); 
 // 使用imageloader加载网络图片 
 displayimageoptions options = new displayimageoptions.builder()// 
  .showimageonloading(r.drawable.ic_launcher) // 加载中显示的默认图片 
  .showimageonfail(r.drawable.ic_launcher) // 设置加载失败的默认图片 
  .cacheinmemory(true) // 内存缓存 
  .cacheondisk(true) // sdcard缓存 
  .bitmapconfig(config.rgb_565)// 设置最低配置 
  .build();// 
 imageloader.getinstance().displayimage(itementity.getavatar(), 
  holder.iv_avatar, options); 
 final arraylist<string> imageurls = itementity.getimageurls(); 
 if (imageurls == null || imageurls.size() == 0) { // 没有图片资源就隐藏gridview 
  holder.gridview.setvisibility(view.gone); 
 } else { 
  holder.gridview.setadapter(new noscrollgridadapter(mcontext, 
   imageurls)); 
 } 
 // 点击回帖九宫格,查看大图 
 holder.gridview.setonitemclicklistener(new onitemclicklistener() { 
 
  @override 
  public void onitemclick(adapterview<?> parent, view view, 
   int position, long id) { 
  // todo auto-generated method stub 
  imagebrower(position, imageurls); 
  } 
 }); 
 return convertview; 
 } 
 
 /** 
 * 打开图片查看器 
 * 
 * @param position 
 * @param urls2 
 */ 
 protected void imagebrower(int position, arraylist<string> urls2) { 
 intent intent = new intent(mcontext, imagepageractivity.class); 
 // 图片url,为了演示这里使用常量,一般从数据库中或网络中获取 
 intent.putextra(imagepageractivity.extra_image_urls, urls2); 
 intent.putextra(imagepageractivity.extra_image_index, position); 
 mcontext.startactivity(intent); 
 } 
 
 /** 
 * listview组件复用,防止“卡顿” 
 * 
 * @author administrator 
 * 
 */ 
 class viewholder { 
 private imageview iv_avatar; 
 private textview tv_title; 
 private textview tv_content; 
 private noscrollgridview gridview; 
 } 
} 

        这里有需要解释的地方了,看看listview上的图片处理,由于图片都是从网络获取的,为了避免图片过多造成oom,那么这里加载图片的时候必不可少的需要做内存优化,图片的优化方式有很多,我这里采取了最简单最直接得方式,使用了开源的imageloader这个图片加载框架,这个框架简直是太优秀了,减少了开发者一系列不必要而且时常会出现的麻烦,关于imageloader并不是本篇博文需要讲解的知识,关于imageloader,欢迎在github主页上下载,地址是https://github.com/nostra13/android-universal-image-loader,既然使用了imageloader这个框架,就不得不在程序上做一些初始化的操作,首先需要自定义一个全局的上下文application类,将imageloader的相关属性初始化上去,直接看代码好了,见名知意:myapplication.java

public class myapplication extends application { 
 @override 
 public void oncreate() { 
 super.oncreate(); 
 displayimageoptions defaultoptions = new displayimageoptions.builder() // 
  .showimageforemptyuri(r.drawable.ic_launcher) // 
  .showimageonfail(r.drawable.ic_launcher) // 
  .cacheinmemory(true) // 
  .cacheondisk(true) // 
  .build();// 
 imageloaderconfiguration config = new imageloaderconfiguration// 
 .builder(getapplicationcontext())// 
  .defaultdisplayimageoptions(defaultoptions)// 
  .disccachesize(50 * 1024 * 1024)// 
  .disccachefilecount(100)// 缓存一百张图片 
  .writedebuglogs()// 
  .build();// 
 imageloader.getinstance().init(config); 
 } 
} 

       定义这个application之后,需要在清单文件中配置一下,在manifest.xml中的application节点上添加:
android:name="com.example.imagedemo.myapplication"  
        此外由于imageloader是网络获取图片,又需要本地sdcard缓存图片,所以需要加上一下的权限,这是imageloader标准权限:

<uses-permission android:name="android.permission.internet" /> 
<uses-permission android:name="android.permission.write_external_storage" /> 
<uses-permission android:name="android.permission.access_network_state" /> 

       再看看上面的item上数据,里面有个gridview,显然这个gridview也是需要做数据适配的,这个数据反应的是从网络加载图片,比较简单,看代码noscrollgridadapter.java

 ...... 
override 
public view getview(int position, view convertview, viewgroup parent) { 
 view view = view.inflate(ctx, r.layout.item_gridview, null); 
 imageview imageview = (imageview) view.findviewbyid(r.id.iv_image); 
 displayimageoptions options = new displayimageoptions.builder()// 
  .cacheinmemory(true)// 
  .cacheondisk(true)// 
  .bitmapconfig(config.rgb_565)// 
  .build(); 
 imageloader.getinstance().displayimage(imageurls.get(position), 
  imageview, options); 
 return view; 
} 
 ...... 

         这样,所有的数据适配就做好了,接下来就需要做图片查看器了,当我们点击listview上item里的“九宫格”——noscrollgridview的某张图片的时候,需要把这个图片的url传给一个图片查看器,图片查看器里会根据传递进来的url去网络加载这张图片,那么其实图片查看器就是一个新的单独的activity,这个activity会包含一个viewpager,用来管理多张图片的查看。image_detail_pager.xml

<?xml version="1.0" encoding="utf-8"?> 
<framelayout xmlns:android="http://schemas.android.com/apk/res/android" 
 android:layout_width="match_parent" 
 android:layout_height="match_parent" > 
 
 <com.example.imagedemo.hackyviewpager 
 android:id="@+id/pager" 
 android:layout_width="match_parent" 
 android:layout_height="match_parent" 
 android:background="@android:color/black" /> 
 
 <textview 
 android:id="@+id/indicator" 
 android:layout_width="match_parent" 
 android:layout_height="wrap_content" 
 android:layout_gravity="bottom" 
 android:background="@android:color/transparent" 
 android:gravity="center" 
 android:text="@string/viewpager_indicator" 
 android:textcolor="@android:color/white" 
 android:textsize="18sp" /> 
 
</framelayout> 

hackyviewpager.java

public class hackyviewpager extends viewpager { 
 
 private static final string tag = "hackyviewpager"; 
 
 public hackyviewpager(context context) { 
 super(context); 
 } 
 
 public hackyviewpager(context context, attributeset attrs) { 
 super(context, attrs); 
 } 
 
 @override 
 public boolean onintercepttouchevent(motionevent ev) { 
 try { 
  return super.onintercepttouchevent(ev); 
 } catch (illegalargumentexception e) { 
  // 不理会 
  log.e(tag, "hacky viewpager error1"); 
  return false; 
 } catch (arrayindexoutofboundsexception e) { 
  // 不理会 
  log.e(tag, "hacky viewpager error2"); 
  return false; 
 } 
 } 
 
} 

imagepageractivity.java

/** 
 * 图片查看器 
 */ 
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); 
 arraylist<string> urls = getintent().getstringarraylistextra( 
  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 arraylist<string> filelist; 
 
 public imagepageradapter(fragmentmanager fm, arraylist<string> filelist) { 
  super(fm); 
  this.filelist = filelist; 
 } 
 
 @override 
 public int getcount() { 
  return filelist == null ? 0 : filelist.size(); 
 } 
 
 @override 
 public fragment getitem(int position) { 
  string url = filelist.get(position); 
  return imagedetailfragment.newinstance(url); 
 } 
 
 } 
} 

          已知图片查看的界面是继承自fragmentactivity的,所以支持显示的界面必须需要fragment来实现,那么就自定义个frangment吧,用这个fragment来从url中获取图片资源,显示图片。image_detail_fragment.xml

<?xml version="1.0" encoding="utf-8"?> 
<framelayout xmlns:android="http://schemas.android.com/apk/res/android" 
 android:layout_width="match_parent" 
 android:layout_height="match_parent" 
 android:background="@android:color/black" > 
 
 <imageview 
 android:id="@+id/image" 
 android:layout_width="match_parent" 
 android:layout_height="match_parent" 
 android:adjustviewbounds="true" 
 android:contentdescription="@string/app_name" 
 android:scaletype="centercrop" /> 
 
 <progressbar 
 android:id="@+id/loading" 
 android:layout_width="wrap_content" 
 android:layout_height="wrap_content" 
 android:layout_gravity="center" 
 android:visibility="gone" /> 
 
</framelayout> 

imagedetailfragment.java

/** 
 * 单张图片显示fragment 
 */ 
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(); 
   } 
  }); 
 } 
} 

         写到这里,此篇博文也宣告结束了。需要提出的是,我这里的图片查看器实现的图片的缩放效果使用的是开源组件photoview,关于photoview的github项目地址在这里,https://github.com/chrisbanes/photoview 需要点进去这个项目的网址,去下载源码,将源码全部拷贝到项目中来,使用也是相当方便的,demo如下:

imageview mimageview; 
photoviewattacher mattacher; 
 
@override 
public void oncreate(bundle savedinstancestate) { 
 super.oncreate(savedinstancestate); 
 setcontentview(r.layout.activity_main); 
 
 // any implementation of imageview can be used! 
 mimageview = (imageview) findviewbyid(r.id.iv_photo); 
 
 // set the drawable displayed 
 drawable bitmap = getresources().getdrawable(r.drawable.wallpaper); 
 mimageview.setimagedrawable(bitmap); 
 
 // attach a photoviewattacher, which takes care of all of the zooming functionality. 
 mattacher = new photoviewattacher(mimageview); 
} 
 
 
// if you later call mimageview.setimagedrawable/setimagebitmap/setimageresource/etc then you just need to call 
attacher.update(); 

         刚开始这个图片查看器是我自己自定义view来实现的,其实需要实现图片的手势识别+多点触控+缩放,是可以使用矩阵matrix来实现的,只不过这样显得特别的麻烦不说,而且极易出现bug,这对于某些“急功近利”的项目来说,是个不好的兆头。所以,我这里摒弃了我用matrix自定义的效果,改用github大牛为我们写好的开源组件,这样效率就上去了,大家也可以用matrix自己去实现一下图片的多点触摸缩放的效果,关于matrix的学习,请参加我以前的博文,android自定义控件——3d画廊和图像矩阵。其实关于android上的图片缩放真没什么其它的方式,唯一能使用的还是matrix这个类,不信先来瞧瞧github大牛写的开源组件photoview是怎么实现的,查看以下部分源码:

// these are set so we don't keep allocating them on the heap 
 private final matrix mbasematrix = new matrix(); 
 private final matrix mdrawmatrix = new matrix(); 
 private final matrix msuppmatrix = new matrix(); 
 private final rectf mdisplayrect = new rectf(); 
 private final float[] mmatrixvalues = new float[9]; 
/** 
 * set's the imageview's scaletype to matrix. 
 */ 
 private static void setimageviewscaletypematrix(imageview imageview) { 
 /** 
 * photoview sets it's own scaletype to matrix, then diverts all calls 
 * setscaletype to this.setscaletype automatically. 
 */ 
 if (null != imageview && !(imageview instanceof iphotoview)) { 
  if (!scaletype.matrix.equals(imageview.getscaletype())) { 
  imageview.setscaletype(scaletype.matrix); 
  } 
 } 
 } 

        以上只是photoview的部分源码,一目了然的发现它的实现也是基于matrix的,时间与篇幅的局限性,大家需要更好的了解photoview的实现的话,就下载它的源码查看吧,要理解大神的想法是需要一些扎实的基础,关于photoview的具体实现细节,我也弄不太明白,可能是我对matrix了解的不深刻吧,希望以后加强学习,也希望以后跟你们交流学习,共同进步!

本文转载: