当前位置: 移动技术网 > IT编程>移动开发>Android > Android仿微信朋友圈图片查看器

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

2019年07月24日  | 移动技术网IT编程  | 我要评论

国内网上商城排名,windowsce软件,遂平教体局

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

        效果嘛,将就着看吧!实在看不明白就想想微信朋友圈,或者拖到下方,点击下载源码!这里,首先分析一下主界面吧,布局都是很简单的,主界面仅仅就是一个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了解的不深刻吧,希望以后加强学习,也希望以后跟你们交流学习,共同进步!

本文转载:

如对本文有疑问,请在下面进行留言讨论,广大热心网友会与你互动!! 点击进行留言回复

相关文章:

验证码:
移动技术网