Android仿QQ、新浪相册的实现

Android仿QQ、新浪相册的实现,第1张

概述在移动应用中,很多时候都会用到图片选择、图片裁剪等功能。最近我也在准备一个开源的相册项目,以方便以后开发应用的时候使用,也尽可能的方便需要的人。一个完整的相册,应该包含相册列表、图片列表、图片的单选和

在移动应用中,很多时候都会用到图片选择、图片裁剪等功能。最近我也在准备一个开源的相册项目,以方便以后开发应用的时候使用,也尽可能的方便需要的人。一个完整的相册,应该包含相册列表、图片列表、图片的单选和多选、图片的裁剪、拍照、多选图片的大图预览等功能。这也是我这个项目将要包含的功能。在本篇博客中,将会讲述下我在这个项目中相册列表和图片列表的大致实现。

实现效果

结合几个常用的APP中的相册效果,当前项目中已经实现了一些基本的功能和UI,在后续完善的过程中还会有所变动。项目在Github上开源,欢迎fork和star。先展示实现的效果(后面会增加拍照功能):
单选效果 单选未选择时的效果 单选已选择的效果

功能分析

在实现相册功能之前,我们先需要明确它的逻辑。参照QQ、新浪、微博这中巨头级的APP,当我们需要用选择图片时,会先打开相册,获取到最新的照片列表。然后点击一个按钮可以展开相册列表,点击列表内容,可以切换相册,刷新当前照片列表中的内容。而且选择这篇的时候,会有单选、多选、单选并裁剪等情况,多选的时候还要出现选择效果和指示器等,单选的时候如果需要裁剪则进入裁剪页,不裁剪则默认确定选择,(拍照功能在后续博客中再说明)。
这样,我们就可以明确我们需要实现的功能有:

1.获取手机中的最新图片
2.获取手机中的相册列表
3.获取制定相册中的所有图片
4.展示图片和相册
5.多图选择时需要有选择效果和指示器
6.单选裁剪时需要用到裁剪功能

另外,扫描手机中的图片也是一个相对耗时的工作,所以这个工作还需要主要避免放到主线程中。

准备数据

为了使用方便,我们可以将相册列表的查询、制定相册的查询、最新图片的查询都放到一个工具类中,主要工具类代码如下:

public class AlbumTool { private Handler handler; //private Semaphore semaphore; private Callback callback; private Context context; private final int TYPE_FolDER=1; private final int TYPE_ALBUM=2; public AlbumTool(Context context){  this.context=context;  handler=new Handler(Looper.getMainLooper()){   @OverrIDe   public voID handleMessage(Message msg) {    if(callback!=null){     switch (msg.what){      case TYPE_FolDER:       callback.onFolderFinish((ImageFolder) msg.obj);       break;      case TYPE_ALBUM:       callback.onAlbumFinish((ArrayList<ImageFolder>) msg.obj);       break;     }    }    super.handleMessage(msg);   }  }; } public voID setCallback(Callback callback){  this.callback=callback; } public voID findAlbumsAsync(){  new Thread(new Runnable() {   @OverrIDe   public voID run() {    getAlbums(context);   }  }).start(); } public voID findFolderAsync(final ImageFolder folder){  new Thread(new Runnable() {   @OverrIDe   public voID run() {    getFolder(context,folder);   }  }).start(); } //获取所有图片集 private ArrayList<ImageFolder> getAlbums(Context context) {  ArrayList<ImageFolder> albums=new ArrayList<>();  albums.add(getNewestPhotos(context));  //利用ContentResolver查询数据库,找出所有包含图片的文件夹,保存到相册列表中  ContentResolver resolver = context.getContentResolver();  Cursor cursor = resolver.query(MediaStore.Images.Media.EXTERNAL_CONTENT_URI,new String[]{      MediaStore.Images.Media.DATA,MediaStore.Images.ImageColumns.BUCKET_ID,MediaStore.Images.Media.DATE_MODIFIED,"count(*) as count"    },MediaStore.Images.Media.MIME_TYPE + "=? or " +      MediaStore.Images.Media.MIME_TYPE + "=? or " +      MediaStore.Images.Media.MIME_TYPE + "=?) " +      "group by (" + MediaStore.Images.ImageColumns.BUCKET_ID,new String[]{"image/jpeg","image/png","image/jpg"},MediaStore.Images.Media.DATE_MODIFIED + " desc");  if (cursor != null) {   while (cursor.movetoNext()) {    final file file = new file(cursor.getString(0));    ImageFolder imageFolder = new ImageFolder();    imageFolder.setDir(file.getParent());    imageFolder.setID(cursor.getString(1));    imageFolder.setFirstimagePath(cursor.getString(0));    String[] all=file.getParentfile().List(new filenameFilter() {     private boolean e(String filename,String ends){      return filename.tolowerCase().endsWith(ends);     }     @OverrIDe     public boolean accept(file dir,String filename) {      return e(filename,".png") || e(filename,".jpg") || e(filename,"jpeg");     }    });    if(all!=null&&all.length>0){     imageFolder.setCount(all.length);     albums.add(imageFolder);    }   }   cursor.close();  }  sendMessage(TYPE_ALBUM,albums);  return albums; } //获取《最新图片》集 private ImageFolder getNewestPhotos(Context context) {  ImageFolder newestFolder=new ImageFolder();  newestFolder.setname(ChooserSetting.newestAlbumname);  ArrayList<ImageInfo> imageBeans = new ArrayList<>();  ContentResolver resolver = context.getContentResolver();  Cursor cursor = resolver.query(MediaStore.Images.Media.EXTERNAL_CONTENT_URI,MediaStore.Images.Media.disPLAY_name,},MediaStore.Images.Media.MIME_TYPE + "=? or "      + MediaStore.Images.Media.MIME_TYPE + "=? or "      + MediaStore.Images.Media.MIME_TYPE + "=?",MediaStore.Images.Media.DATE_MODIFIED + " desc"      + (ChooserSetting.newestAlbumSize < 0 ? ""      : (" limit " + ChooserSetting.newestAlbumSize)));  if (cursor != null){   while (cursor.movetoNext()) {    ImageInfo info=new ImageInfo();    info.path=cursor.getString(0);    info.displayname=cursor.getString(1);    info.time=cursor.getLong(2);    imageBeans.add(info);   }   cursor.close();   newestFolder.setFirstimagePath(imageBeans.get(0).path);   newestFolder.setDatas(imageBeans);   newestFolder.setCount(imageBeans.size());  }  sendMessage(TYPE_FolDER,newestFolder);  return newestFolder; } //获取具体图片集,确保图片数据已被查询 private ImageFolder getFolder(Context context,ImageFolder folder) {  ContentResolver resolver = context.getContentResolver();  Cursor cursor;  if(folder!=null&&folder.getDatas()!=null&&folder.getDatas().size()>0){   sendMessage(TYPE_FolDER,folder);   return folder;  }  if (folder == null) {   return getNewestPhotos(context);  } else {   cursor = resolver.query(MediaStore.Images.Media.EXTERNAL_CONTENT_URI,new String[]{       MediaStore.Images.Media.DATA,MediaStore.Images.Media.DATE_MODIFIED     },MediaStore.Images.ImageColumns.BUCKET_ID + "=? and (" +       MediaStore.Images.Media.MIME_TYPE + "=? or "       + MediaStore.Images.Media.MIME_TYPE + "=? or "       + MediaStore.Images.Media.MIME_TYPE + "=?) ",new String[]{folder.getID(),"image/jpeg",MediaStore.Images.Media.DATE_MODIFIED + " desc");  }  ArrayList<ImageInfo> datas=new ArrayList<>();  folder.setDatas(datas);  if (cursor != null){   while (cursor.movetoNext()) {    ImageInfo info=new ImageInfo();    info.path=cursor.getString(0);    info.displayname=cursor.getString(1);    info.time=cursor.getLong(2);    datas.add(info);   }   cursor.close();  }  sendMessage(TYPE_FolDER,folder);  return folder; } private voID sendMessage(int what,Object obj){  Message msg=new Message();  msg.what=what;  msg.obj=obj;  handler.sendMessage(msg); } public interface Callback{  //文件夹查找完毕  voID onFolderFinish(ImageFolder folder);  //成功搜索出所有的图片集  voID onAlbumFinish(ArrayList<ImageFolder> albums); }}

这样,我们就可以利用这个工具类方便的获取相册列表、获取制定相册的图片了(最新照片合集当做是一个相册)。里面主要就是使用ContentResolver来做查询,AndroID入门级问题,四大组件――Activity、Service、ContentProvIDer和broadcastReceiver,中的ContentProvIDer和ContentResolver就是一对CP了,ContentProvIDer用来提供数据,ContentResolver用来获取数据。

展示相册和相册列表

有了获取相册列表和获取指定相册的方法,展示相册和相册列表就容易了,按照通常的方式,我们直接使用GrIDVIEw来展示相册,用ListVIEw来展示相册列表。当然,你也可以选择使用RecyclerVIEw来替代掉GrIDVIEw和ListVIEw,其实也都一样。
显示图片直接使用成熟的第三方框架即可,我使用的是GlIDe。
值得注意的是,在相册中,我们展示出来的图片都是正方块、并且需要三个(你也可以设置四个或者五个,只要你高兴)铺满宽度。在这里我使用的是比较懒的方式,直接用一个自定义的布局作为Item的跟布局,这个自定义布局继承relativeLayout,然后将复写它的onMeasure方法:

@OverrIDeprotected voID onMeasure(int wIDthMeasureSpec,int heightmeasureSpec) { super.onMeasure(wIDthMeasureSpec,wIDthMeasureSpec);}

心有多懒,人就能有多懒。这样它的高度就被强制保持为何宽度一致了。

选择指示器

像QQ中,选择图片时,图片会根据选择的顺序,在图片上的那个圈圈里面显示出1234……等数字,然后取消选择时,被选的数字会顺序补位,比如你选了七张图片、然后取消了显示数字3的那张,这时4就变成3了、5变成了4、6变成了5。
像新浪微博中的图片选择,不会出现数字,而是出现一个勾,选中的时候这个勾还有动画效果。
这样的功能怎么实现呢?
我实现的方式是,在每个Item中都有一个固定大小的VIEw,根据图片是否被选中,加载不同的Drawable。当然,写这个项目既然是为了以后在不同的项目中使用,这个自然要方便被使用者自行设置。所以我写一个抽象类:

public abstract class IChooseDrawable{ private Paint paint; protected int wIDth=0; protected int height=0; private SparseArray<Drawable> drawables; public IChooseDrawable(){  paint=new Paint();  paint.setAntiAlias(true);  paint.setcolor(0x88000000);  drawables=new SparseArray<>(); } public Drawable get(int state){  if(drawables.indexOfKey(state)>=0){   return drawables.get(state);  }else{   InDrawable drawable=new InDrawable(state);   drawables.put(state,drawable);   return drawable;  } } public voID clear(){  drawables.clear(); } public int getBaseline(Paint paint,int top,int bottom){  Paint.FontMetrics i=paint.getFontMetrics();  return (int) ((bottom+top-i.top-i.bottom)/2); } //state表示第几个被选择,0表示未选中 public abstract voID draw(Canvas canvas,Paint paint,int state); private class InDrawable extends Drawable{  private int state=0;  InDrawable(int state){   this.state=state;  }  @OverrIDe  public voID draw(@NonNull Canvas canvas) {   IChooseDrawable.this.draw(canvas,paint,state);  }  @OverrIDe  public voID setAlpha(int Alpha) {  }  @OverrIDe  public voID setcolorFilter(colorFilter colorFilter) {  }  @OverrIDe  public int getopacity() {   return PixelFormat.transparent;  } }}

在相册的Adapter的构造函数中会传入一个IChooseDrawable实体,在显示每个Item时,会根据当前状态通过drawable.get(int state)取得指定的Drawable,设置为指示器VIEw的背景。
上面效果图中的指示器(也可配置为只显示对号)实现为:

public class CircleChooseDrawable extends IChooseDrawable { private boolean isShowNum=true; private int chooseBgcolor=0xFFFF6600; private Path path; public CircleChooseDrawable(){  super(); } public CircleChooseDrawable(boolean isShowNum,int chooseBgcolor){  super();  this.isShowNum=isShowNum;  this.chooseBgcolor=chooseBgcolor; } @OverrIDe public voID draw(Canvas canvas,int state) {  wIDth=canvas.getWIDth();  height=canvas.getHeight();  if(state==0){ //未选择状态   paint.setcolor(0x55000000);   paint.setStyle(Paint.Style.FILL);   canvas.drawCircle(wIDth/2,height/2,wIDth/2-2,paint);   paint.setcolor(0xDDFFFFFF);   paint.setstrokeWIDth(2);   paint.setStyle(Paint.Style.stroke);   canvas.drawCircle(wIDth/2,paint);  }else{ //选中状态   paint.setcolor(chooseBgcolor);   paint.setStyle(Paint.Style.FILL);   canvas.drawCircle(wIDth/2,paint);   paint.setcolor(0xDDFFFFFF);   if(isShowNum){ //显示数字    paint.setStyle(Paint.Style.FILL);    paint.setTextAlign(Paint.Align.CENTER);    paint.setTextSize(wIDth*0.53f);    canvas.drawText(state+"",wIDth/2,getBaseline(paint,height),paint);   }else{ //显示一个√号    paint.setStyle(Paint.Style.stroke);    paint.setstrokeWIDth(3);    paint.setstrokeCap(Paint.Cap.ROUND);    if(path==null){     path=new Path();     path.moveto(wIDth/4f,height/2f);     path.lineto(wIDth*2/5f,height*5/7f);     path.lineto(wIDth*3/4f,height/3f);    }    canvas.drawPath(path,paint);   }  } }}

裁剪、单选和多选

单选和多选的区别在于单选的时候,没有选择指示器,选中直接携带数据返回。而多选时,有选择指示器,选择完成后,需要确定后携带数据返回,在确定前可以取消之前所选的内容。
所以实现的时候,只需要判断用户传入的选择意图,做出相应的处理。如果是裁剪,则选择一张图片后,进入到裁剪页面,裁剪结束后携带裁剪结果返回到进入到相册前的页面。如果是单选,则选择一张图片后,直接携带数据返回到进入相册前的页面。如果是多选,则要在点击确认按钮后,携带数据返回到进入相册前的页面。裁剪的实现见上一篇博客――AndroID 图片裁剪。

其他

其他的一些功能,主要是拍照的功能、和大图切换预览现在还未添加进项目中,目前准备是利用OpenGl做拍照预览和拍照(也许会添加些许常用滤镜),实现的相关细节也会在后续单独写博客来介绍。

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

总结

以上是内存溢出为你收集整理的Android仿QQ、新浪相册的实现全部内容,希望文章能够帮你解决Android仿QQ、新浪相册的实现所遇到的程序开发问题。

如果觉得内存溢出网站内容还不错,欢迎将内存溢出网站推荐给程序员好友。

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/web/1147719.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-05-31
下一篇 2022-05-31

发表评论

登录后才能评论

评论列表(0条)

保存