Android Imitation QQ, the realization of Sina album _android

Source: Internet
Author: User
Tags abstract semaphore

In mobile applications, many times will use the image selection, image cutting and other functions. Recently I am also preparing an open source Photo album project to facilitate the development of applications later use, but also as convenient as possible to the needs of the people. A complete photo album should include a list of albums, a list of pictures, a selection of pictures and multiple selections, the cutting of pictures, photos, pictures, and many other features of the big picture preview. This is also the function that I will include in this project. In this blog, I'll tell you about the general implementation of my album and picture lists in this project.

Implementation effect

Combined with the album Effects of several commonly used apps, some basic features and UI have been implemented in the current project, and there will be changes in the process of subsequent refinement. The project is open source on GitHub and welcome to fork and star. First show the effect of the implementation (the following will increase the camera function):
Effect of a single selection when not selected effect radio selected effect

Functional analysis

Before we realize the album feature, we need to be clear about its logic. Referring to QQ, Sina, Weibo, the Giant-class app, when we need to select pictures, we will first open the album, get to the latest photo list. Then click on a button to expand the album list, click on the list content, you can switch albums, refresh the current photo list of content. And when you choose this one, there will be radio, multiple selection, radio and cutting and so on, the choice of time to appear select effects and indicators, and so on, if you need to crop to cut into the crop page, do not crop the default to determine the choice, (Photo function in the follow-up blog to explain).
In this way, we can make it clear that the functions we need to implement are:

1. Get the latest picture in mobile phone
2. Get a list of albums in the phone
3. Get all the pictures in the album
4. Show pictures and albums
5. Multi-image selection needs to have selection effects and indicators
6. Cutting function is required for single cropping

In addition, scanning the image of the phone is also a relatively time-consuming work, so the work also needs to be largely avoided in the main thread.

Preparing data

For ease of use, we can put the album list query, the development of the album query, the latest picture of the query into a tool class, the main tool class code as follows:

public class Albumtool {private Handler Handler;
 Private semaphore semaphore;
 Private Callback Callback;

 private context;
 private final int type_folder=1;

 private final int type_album=2;
  Public Albumtool {this.context=context; Handler=new handler (Looper.getmainlooper ()) {@Override public void Handlemessage (msg) {if (Callback!=nul
       L) {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 the public void Run () {Getalbums
   );
 }). Start ();
   The public void Findfolderasync (final Imagefolder folder) {New Thread (new Runnable () {@Overridepublic void Run () {GetFolder (Context,folder);
 }). Start ();  //Get all picture sets private arraylist<imagefolder> getalbums (context context) {arraylist<imagefolder> albums=new
  Arraylist<> ();
  Albums.add (Getnewestphotos (context));
  Use Contentresolver to query the database, find all the folders that contain pictures, and save them to the album list Contentresolver resolver = Context.getcontentresolver (); Cursor Cursor = Resolver.query (MediaStore.Images.Media.EXTERNAL_CONTENT_URI, new string[]{MediaStore.Images.Med Ia. DATA, MediaStore.Images.ImageColumns.BUCKET_ID, MediaStore.Images.Media.DATE_MODIFIED, "count (*) as Coun T "}, 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 = 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;
  //get "latest pictures" Set private Imagefolder Getnewestphotos (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, new string[]{MediaStore.Images.Med Ia. DATA, MediaStore.Images.Media.DISPLAY_NAME, MediaStore.Images.Media.DATE_MODIFIED,}, Mediastore.image S.media.mime_type + "=? or "+ MediaStore.Images.Media.MIME_TYPE +" =? or "+ MediaStore.Images.Media.MIME_TYPE +" =? ", New string[]{" Image/jpeg "," Image/png "," Image/jpg "}, Media Store.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;  //Get a specific picture set to ensure that the picture data has been queried 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.ima Ges. Media.data, MediaStore.Images.Media.DISPLAY_NAME, MediaStore.Images.Media.DATE_MODIFIED}, Mediast Ore. 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", "Image/png", "Image/jpg"}, 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{//folder lookup completed void Onfolderfinish (Imagefolder folder);

 Successfully searched out all the picture set void Onalbumfinish (arraylist<imagefolder> albums);

 }

}

In this way, we can take advantage of this tool class to easily get the album list, get the picture of the album (the latest photo collection as an album). The main thing is to use Contentresolver to do the query, Android entry-level issues, four components--activity, Service, ContentProvider and Broadcastreceiver, ContentProvider and Contentresolver are a pair of CP, and ContentProvider is used to provide data that contentresolver use to get data.

Show albums and albums list

With the way to get a list of albums and get a specific album, it's easy to show a list of albums and albums, and in the usual way, we use the GridView directly to show albums, and ListView to show albums lists. Of course, you can also choose to use Recyclerview to replace the GridView and ListView, in fact, are the same.
Show pictures directly using a mature third-party framework, I'm using glide.
It is worth noting that in the album, we show the pictures are square blocks, and need three (you can also set four or five, as long as you are happy) covered width. Here I am using a lazy way, directly using a custom layout as item layout, this custom layout inherits Relativelayout and then copies its Onmeasure method:

@Override
protected void onmeasure (int widthmeasurespec, int heightmeasurespec) {
 super.onmeasure ( Widthmeasurespec, Widthmeasurespec);

How lazy a man is, how lazy he can be. So its height is forced to keep the width consistent.

Select indicator

Like QQ, choose the picture, the picture will be according to the order of choice, in the picture of that circle inside show 1234 ... And so on, and then deselect, the selected number will be the order complement, such as you selected seven pictures, and then canceled the display number 3, when 4 became 3, 5 into 4, 6 into 5.
Like Sina Weibo in the selection of pictures, there will be no number, but a tick, selected when the tick and animation effect.
How to achieve this function?
The way I do this is to have a fixed size view in each item, and load different drawable depending on whether the picture is selected. Of course, writing this project is for later use in different projects, this nature should be convenient for users to set up themselves. So I write an abstract class:

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) >=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);

 The//state represents the number selected, and 0 indicates that the public abstract void Draw (Canvas canvas,paint paint,int State) is not selected;

  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;

 }
 }
}

A ichoosedrawable entity is passed in the adapter constructor of the album, and when each item is displayed, it is set to the background of the indicator view, based on the current state, with the specified drawable through Drawable.get (int.).
The indicators in the above effect graph (which can also be configured to display only the check mark) are:

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, Paint Paint, int state) {width=canvas.getwidth ();
  Height=canvas.getheight ();
   if (state==0) {//Not selected state 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,height/2,width/2-2,paint);
   }else{//Selected state Paint.setcolor (Choosebgcolor);
   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,height/2,width/2-2,paint);
   Paint.setcolor (0xDDFFFFFF);
    if (isshownum) {//Display digital Paint.setstyle (Paint.Style.FILL);
    Paint.settextalign (Paint.Align.CENTER);
    Paint.settextsize (width*0.53f);
   Canvas.drawtext (state+ "", Width/2,getbaseline (paint,0,height), paint);
    }else{//Display a √ 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);

 }
  }
 }
}

Cropping, radio and multiple selection

The difference between a radio and a multiple selection is that when a single election, there is no indicator, select the direct carry data back. When multiple selections, there is a selection indicator, after the selection is completed, you need to be sure to bring the data back, before you can determine the cancellation of the previously selected content.
Therefore, the realization of the time, only need to judge the user incoming choice intention, make corresponding processing. If it is cropped, then select a picture, go to the cropping page, after the crop is finished, bring the crop results back to the page before entering the album. If it is a radio, then select a picture, directly carry the data back to the page before entering the album. If it is more than one choice, then after clicking on the confirmation button, bring the data back to the page before entering the album. The implementation of the crop is seen on a blog--android picture cropping.

Other

Some of the other features, mainly camera features, and large graphics switch preview is not yet added to the project, is currently ready to use OpenGL to do photo preview and take photos (perhaps add a few commonly used filters), the implementation of the relevant details will be in the subsequent separate blog to introduce.

The above is the entire content of this article, I hope to help you learn, but also hope that we support the cloud habitat community.

Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.