欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Android仿QQ、新浪相冊(cè)的實(shí)現(xiàn)

 更新時(shí)間:2016年11月09日 08:51:48   作者:湖廣午王  
這篇文章主要為大家詳細(xì)介紹了Android仿QQ、新浪相冊(cè)的實(shí)現(xiàn)代碼,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下

在移動(dòng)應(yīng)用中,很多時(shí)候都會(huì)用到圖片選擇、圖片裁剪等功能。最近我也在準(zhǔn)備一個(gè)開源的相冊(cè)項(xiàng)目,以方便以后開發(fā)應(yīng)用的時(shí)候使用,也盡可能的方便需要的人。一個(gè)完整的相冊(cè),應(yīng)該包含相冊(cè)列表、圖片列表、圖片的單選和多選、圖片的裁剪、拍照、多選圖片的大圖預(yù)覽等功能。這也是我這個(gè)項(xiàng)目將要包含的功能。在本篇博客中,將會(huì)講述下我在這個(gè)項(xiàng)目中相冊(cè)列表和圖片列表的大致實(shí)現(xiàn)。

實(shí)現(xiàn)效果

結(jié)合幾個(gè)常用的APP中的相冊(cè)效果,當(dāng)前項(xiàng)目中已經(jīng)實(shí)現(xiàn)了一些基本的功能和UI,在后續(xù)完善的過程中還會(huì)有所變動(dòng)。項(xiàng)目在Github上開源,歡迎fork和star。先展示實(shí)現(xiàn)的效果(后面會(huì)增加拍照功能):
單選效果 單選未選擇時(shí)的效果 單選已選擇的效果

功能分析

在實(shí)現(xiàn)相冊(cè)功能之前,我們先需要明確它的邏輯。參照QQ、新浪、微博這中巨頭級(jí)的APP,當(dāng)我們需要用選擇圖片時(shí),會(huì)先打開相冊(cè),獲取到最新的照片列表。然后點(diǎn)擊一個(gè)按鈕可以展開相冊(cè)列表,點(diǎn)擊列表內(nèi)容,可以切換相冊(cè),刷新當(dāng)前照片列表中的內(nèi)容。而且選擇這篇的時(shí)候,會(huì)有單選、多選、單選并裁剪等情況,多選的時(shí)候還要出現(xiàn)選擇效果和指示器等,單選的時(shí)候如果需要裁剪則進(jìn)入裁剪頁,不裁剪則默認(rèn)確定選擇,(拍照功能在后續(xù)博客中再說明)。
這樣,我們就可以明確我們需要實(shí)現(xiàn)的功能有:

1.獲取手機(jī)中的最新圖片
2.獲取手機(jī)中的相冊(cè)列表
3.獲取制定相冊(cè)中的所有圖片
4.展示圖片和相冊(cè)
5.多圖選擇時(shí)需要有選擇效果和指示器
6.單選裁剪時(shí)需要用到裁剪功能

另外,掃描手機(jī)中的圖片也是一個(gè)相對(duì)耗時(shí)的工作,所以這個(gè)工作還需要主要避免放到主線程中。

準(zhǔn)備數(shù)據(jù)

為了使用方便,我們可以將相冊(cè)列表的查詢、制定相冊(cè)的查詢、最新圖片的查詢都放到一個(gè)工具類中,主要工具類代碼如下:

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查詢數(shù)據(jù)庫,找出所有包含圖片的文件夾,保存到相冊(cè)列表中
  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,
    new String[]{
      MediaStore.Images.Media.DATA,
      MediaStore.Images.Media.DISPLAY_NAME,
      MediaStore.Images.Media.DATE_MODIFIED,
    },
    MediaStore.Images.Media.MIME_TYPE + "=? or "
      + MediaStore.Images.Media.MIME_TYPE + "=? or "
      + MediaStore.Images.Media.MIME_TYPE + "=?",
    new String[]{"image/jpeg", "image/png", "image/jpg"},
    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;
 }

 //獲取具體圖片集,確保圖片數(shù)據(jù)已被查詢
 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.DISPLAY_NAME,
       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", "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{

  //文件夾查找完畢
  void onFolderFinish(ImageFolder folder);
  //成功搜索出所有的圖片集
  void onAlbumFinish(ArrayList<ImageFolder> albums);

 }

}

這樣,我們就可以利用這個(gè)工具類方便的獲取相冊(cè)列表、獲取制定相冊(cè)的圖片了(最新照片合集當(dāng)做是一個(gè)相冊(cè))。里面主要就是使用ContentResolver來做查詢,Android入門級(jí)問題,四大組件——Activity、Service、ContentProvider和BroadcastReceiver,中的ContentProvider和ContentResolver就是一對(duì)CP了,ContentProvider用來提供數(shù)據(jù),ContentResolver用來獲取數(shù)據(jù)。

展示相冊(cè)和相冊(cè)列表

有了獲取相冊(cè)列表和獲取指定相冊(cè)的方法,展示相冊(cè)和相冊(cè)列表就容易了,按照通常的方式,我們直接使用GridView來展示相冊(cè),用ListView來展示相冊(cè)列表。當(dāng)然,你也可以選擇使用RecyclerView來替代掉GridView和ListView,其實(shí)也都一樣。
顯示圖片直接使用成熟的第三方框架即可,我使用的是Glide。
值得注意的是,在相冊(cè)中,我們展示出來的圖片都是正方塊、并且需要三個(gè)(你也可以設(shè)置四個(gè)或者五個(gè),只要你高興)鋪滿寬度。在這里我使用的是比較懶的方式,直接用一個(gè)自定義的布局作為Item的跟布局,這個(gè)自定義布局繼承RelativeLayout,然后將復(fù)寫它的onMeasure方法:

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
 super.onMeasure(widthMeasureSpec, widthMeasureSpec);
}

心有多懶,人就能有多懶。這樣它的高度就被強(qiáng)制保持為何寬度一致了。

選擇指示器

像QQ中,選擇圖片時(shí),圖片會(huì)根據(jù)選擇的順序,在圖片上的那個(gè)圈圈里面顯示出1234……等數(shù)字,然后取消選擇時(shí),被選的數(shù)字會(huì)順序補(bǔ)位,比如你選了七張圖片、然后取消了顯示數(shù)字3的那張,這時(shí)4就變成3了、5變成了4、6變成了5。
像新浪微博中的圖片選擇,不會(huì)出現(xiàn)數(shù)字,而是出現(xiàn)一個(gè)勾,選中的時(shí)候這個(gè)勾還有動(dòng)畫效果。
這樣的功能怎么實(shí)現(xiàn)呢?
我實(shí)現(xiàn)的方式是,在每個(gè)Item中都有一個(gè)固定大小的View,根據(jù)圖片是否被選中,加載不同的Drawable。當(dāng)然,寫這個(gè)項(xiàng)目既然是為了以后在不同的項(xiàng)目中使用,這個(gè)自然要方便被使用者自行設(shè)置。所以我寫一個(gè)抽象類:

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表示第幾個(gè)被選擇,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;
  }
 }
}

在相冊(cè)的Adapter的構(gòu)造函數(shù)中會(huì)傳入一個(gè)IChooseDrawable實(shí)體,在顯示每個(gè)Item時(shí),會(huì)根據(jù)當(dāng)前狀態(tài)通過drawable.get(int state)取得指定的Drawable,設(shè)置為指示器View的背景。
上面效果圖中的指示器(也可配置為只顯示對(duì)號(hào))實(shí)現(xiàn)為:

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){ //未選擇狀態(tài)
   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{ //選中狀態(tài)
   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){ //顯示數(shù)字
    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{ //顯示一個(gè)√號(hào)
    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);
   }
  }
 }
}

裁剪、單選和多選

單選和多選的區(qū)別在于單選的時(shí)候,沒有選擇指示器,選中直接攜帶數(shù)據(jù)返回。而多選時(shí),有選擇指示器,選擇完成后,需要確定后攜帶數(shù)據(jù)返回,在確定前可以取消之前所選的內(nèi)容。
所以實(shí)現(xiàn)的時(shí)候,只需要判斷用戶傳入的選擇意圖,做出相應(yīng)的處理。如果是裁剪,則選擇一張圖片后,進(jìn)入到裁剪頁面,裁剪結(jié)束后攜帶裁剪結(jié)果返回到進(jìn)入到相冊(cè)前的頁面。如果是單選,則選擇一張圖片后,直接攜帶數(shù)據(jù)返回到進(jìn)入相冊(cè)前的頁面。如果是多選,則要在點(diǎn)擊確認(rèn)按鈕后,攜帶數(shù)據(jù)返回到進(jìn)入相冊(cè)前的頁面。裁剪的實(shí)現(xiàn)見上一篇博客——Android 圖片裁剪。

其他

其他的一些功能,主要是拍照的功能、和大圖切換預(yù)覽現(xiàn)在還未添加進(jìn)項(xiàng)目中,目前準(zhǔn)備是利用OpenGl做拍照預(yù)覽和拍照(也許會(huì)添加些許常用濾鏡),實(shí)現(xiàn)的相關(guān)細(xì)節(jié)也會(huì)在后續(xù)單獨(dú)寫博客來介紹。

以上就是本文的全部內(nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。

相關(guān)文章

  • android和js的交互之jsbridge使用教程

    android和js的交互之jsbridge使用教程

    這篇文章主要給大家介紹了關(guān)于android和js的交互之jsbridge使用的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧。
    2018-04-04
  • Android kotlin使用注解實(shí)現(xiàn)防按鈕連點(diǎn)功能的示例

    Android kotlin使用注解實(shí)現(xiàn)防按鈕連點(diǎn)功能的示例

    這篇文章主要介紹了Android kotlin使用注解實(shí)現(xiàn)防按鈕連點(diǎn)功能的示例,幫助大家更好的理解和學(xué)習(xí)使用Android,感興趣的朋友可以了解下
    2021-03-03
  • 淺析Android文件存儲(chǔ)

    淺析Android文件存儲(chǔ)

    本文詳細(xì)介紹了android的外部存儲(chǔ)和私有存儲(chǔ)。大家在有保存文件的需求的時(shí)候,根據(jù)自己的需要,選擇到底是存在哪里比較合適。內(nèi)部存儲(chǔ)相對(duì)較小,不介意把一些大文件存在其中。應(yīng)該存在外部存儲(chǔ)會(huì)更好。對(duì)于可以給其他文件訪問的,可以存在外部存儲(chǔ)的公有文件里面
    2021-06-06
  • flutter Bloc 更新后事件同步與異步詳解

    flutter Bloc 更新后事件同步與異步詳解

    這篇文章主要為大家介紹了flutter Bloc 更新后事件同步與異步詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-11-11
  • Android網(wǎng)絡(luò)連接判斷與相關(guān)處理

    Android網(wǎng)絡(luò)連接判斷與相關(guān)處理

    這篇文章主要為大家詳細(xì)介紹了Android網(wǎng)絡(luò)連接判斷操作,幫助大家判斷WIFI網(wǎng)絡(luò)是否可用,判斷MOBILE網(wǎng)絡(luò)是否可用,感興趣的小伙伴們可以參考一下
    2016-08-08
  • 安卓(Android)實(shí)現(xiàn)選擇時(shí)間功能

    安卓(Android)實(shí)現(xiàn)選擇時(shí)間功能

    安卓開發(fā)過程中難免會(huì)碰到需要選擇日期時(shí)間的情況,當(dāng)然不可能讓用戶自己輸入日期時(shí)間,小編收集整理了一些資料,總結(jié)了一下如何實(shí)現(xiàn)android選擇時(shí)間的功能,方便后來者參考
    2016-08-08
  • Android Force Close 出現(xiàn)的異常原因分析及解決方法

    Android Force Close 出現(xiàn)的異常原因分析及解決方法

    本文給大家講解Android Force Close 出現(xiàn)的異常原因分析及解決方法,forceclose意為強(qiáng)行關(guān)閉,當(dāng)前應(yīng)用程序發(fā)生了沖突。對(duì)android force close異常分析感興趣的朋友一起通過本文學(xué)習(xí)吧
    2016-08-08
  • Android中實(shí)現(xiàn)GPS定位的簡單例子

    Android中實(shí)現(xiàn)GPS定位的簡單例子

    這篇文章主要介紹了Android中實(shí)現(xiàn)GPS定位的簡單例子,例子邏輯清晰,但相對(duì)簡單了些,需要的朋友可以參考下
    2014-07-07
  • Android自定義雙向滑動(dòng)控件

    Android自定義雙向滑動(dòng)控件

    這篇文章主要為大家詳細(xì)介紹了Android自定義雙向滑動(dòng)控件,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2022-04-04
  • Android IPC機(jī)制利用Messenger實(shí)現(xiàn)跨進(jìn)程通信

    Android IPC機(jī)制利用Messenger實(shí)現(xiàn)跨進(jìn)程通信

    這篇文章主要介紹了Android IPC機(jī)制中 Messager 實(shí)現(xiàn)跨進(jìn)程通信的知識(shí),對(duì)Android學(xué)習(xí)通信知識(shí)非常重要,需要的同學(xué)可以參考下
    2016-07-07

最新評(píng)論