Android应用中使用ContentProvider扫描本地图片并显示

之前群里面有朋友问我,有没有关于本地图片选择的Demo,类似微信的效果,他说网上没有这方面的Demo,问我能不能写一篇关于这个效果的Demo,于是我研究了下微信的本地图片选择的Demo,自己仿照的写了下分享给大家,希望对以后有这样子需求的朋友有一点帮助吧,主要使用的是ContentProvider扫描手机中的图片,并用GridView将图片显示出来,关于GridView和ListView显示图片的问题,一直是一个很头疼的问题,因为我们手机的内存有限,手机给每个应用程序分配的内存也有限,所以图片多的情况下很容易伴随着OOM的发生,不过现在也有很多的开源的图片显示框架,对显示很多图片进行了优化,大家有兴趣的可以去了解了解,今天我的这篇文章使用的是LruCache这个类以及对图片进行相对应的裁剪,这样也可以尽量的避免OOM的发生,我们先看下微信的效果吧

接下来我们就来实现这些效果吧,首先我们新建一个项目,取名ImageScan
首先我们先看第一个界面吧,使用将手机中的图片扫描出来,然后根据图片的所在的文件夹将其分类出来,并显示所在文件夹里面的一张图片和文件夹中图片个数,我们根据界面元素(文件夹名, 文件夹图片个数,文件夹中的一张图片)使用一个实体对象ImageBean来封装这三个属性

package com.example.imagescan; 

/**
 * GridView的每个item的数据对象
 *
 * @author len
 *
 */
public class ImageBean{
  /**
   * 文件夹的第一张图片路径
   */
  private String topImagePath;
  /**
   * 文件夹名
   */
  private String folderName;
  /**
   * 文件夹中的图片数
   */
  private int imageCounts; 

  public String getTopImagePath() {
    return topImagePath;
  }
  public void setTopImagePath(String topImagePath) {
    this.topImagePath = topImagePath;
  }
  public String getFolderName() {
    return folderName;
  }
  public void setFolderName(String folderName) {
    this.folderName = folderName;
  }
  public int getImageCounts() {
    return imageCounts;
  }
  public void setImageCounts(int imageCounts) {
    this.imageCounts = imageCounts;
  } 

}

接下来就是主界面的布局啦,上面的导航栏我没有加进去,只有下面的GridView,所以说主界面布局中只有一个GridView

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
  xmlns:tools="http://schemas.android.com/tools"
  android:layout_width="match_parent"
  android:layout_height="match_parent" > 

  <GridView
    android:id="@+id/main_grid"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:listSelector="@android:color/transparent"
    android:cacheColorHint="@android:color/transparent"
    android:stretchMode="columnWidth"
    android:horizontalSpacing="20dip"
    android:gravity="center"
    android:verticalSpacing="20dip"
    android:columnWidth="90dip"
    android:numColumns="2" >
  </GridView> 

</RelativeLayout>

接下来就是GridView的Item的布局,看上面的图也行你会认为他的效果是2张图片添加的效果,其实不是,后面的叠加效果只是一张背景图片而已,代码先贴上来

<?xml version="1.0" encoding="UTF-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
  android:layout_width="fill_parent"
  android:layout_height="wrap_content" > 

  <FrameLayout
    android:id="@+id/framelayout"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content" > 

    <com.example.imagescan.MyImageView
      android:id="@+id/group_image"
      android:background="@drawable/albums_bg"
      android:src="@drawable/friends_sends_pictures_no"
      android:paddingLeft="20dip"
      android:paddingRight="20dip"
      android:paddingTop="18dip"
      android:paddingBottom="30dip"
      android:scaleType="fitXY"
      android:layout_width="fill_parent"
      android:layout_height="150dip" /> 

    <TextView
      android:id="@+id/group_count"
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:background="@drawable/albums_icon_bg"
      android:gravity="center"
      android:layout_marginBottom="10dip"
      android:text="5"
      android:layout_gravity="bottom|center_horizontal" />
  </FrameLayout> 

  <TextView
    android:id="@+id/group_title"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:gravity="center"
    android:layout_below="@id/framelayout"
    android:layout_centerHorizontal="true"
    android:ellipsize="end"
    android:singleLine="true"
    android:text="Camera"
    android:textSize="16sp" /> 

</RelativeLayout>

看到上面的布局代码,也行你已经发现了,上面使用的是自定义的MyImageView,我先不说这个自定义MyImageView的作用,待会再给大家说,我们继续看代码
第一个界面的主要代码

package com.example.imagescan; 

import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map; 

import android.app.Activity;
import android.app.ProgressDialog;
import android.content.ContentResolver;
import android.content.Intent;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.provider.MediaStore;
import android.view.View;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.GridView; 

public class MainActivity extends Activity {
  private HashMap<String, List<String>> mGruopMap = new HashMap<String, List<String>>();
  private List<ImageBean> list = new ArrayList<ImageBean>();
  private final static int SCAN_OK = 1;
  private ProgressDialog mProgressDialog;
  private GroupAdapter adapter;
  private GridView mGroupGridView; 

  private Handler mHandler = new Handler(){ 

    @Override
    public void handleMessage(Message msg) {
      super.handleMessage(msg);
      switch (msg.what) {
      case SCAN_OK:
        //关闭进度条
        mProgressDialog.dismiss(); 

        adapter = new GroupAdapter(MainActivity.this, list = subGroupOfImage(mGruopMap), mGroupGridView);
        mGroupGridView.setAdapter(adapter);
        break;
      }
    } 

  }; 

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main); 

    mGroupGridView = (GridView) findViewById(R.id.main_grid); 

    getImages(); 

    mGroupGridView.setOnItemClickListener(new OnItemClickListener() { 

      @Override
      public void onItemClick(AdapterView<?> parent, View view,
          int position, long id) {
        List<String> childList = mGruopMap.get(list.get(position).getFolderName()); 

        Intent mIntent = new Intent(MainActivity.this, ShowImageActivity.class);
        mIntent.putStringArrayListExtra("data", (ArrayList<String>)childList);
        startActivity(mIntent); 

      }
    }); 

  }
  /**
   * 利用ContentProvider扫描手机中的图片,此方法在运行在子线程中
   */
  private void getImages() {
    //显示进度条
    mProgressDialog = ProgressDialog.show(this, null, "正在加载..."); 

    new Thread(new Runnable() { 

      @Override
      public void run() {
        Uri mImageUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
        ContentResolver mContentResolver = MainActivity.this.getContentResolver(); 

        //只查询jpeg和png的图片
        Cursor mCursor = mContentResolver.query(mImageUri, null,
            MediaStore.Images.Media.MIME_TYPE + "=? or "
                + MediaStore.Images.Media.MIME_TYPE + "=?",
            new String[] { "image/jpeg", "image/png" }, MediaStore.Images.Media.DATE_MODIFIED); 

        if(mCursor == null){
          return;
        } 

        while (mCursor.moveToNext()) {
          //获取图片的路径
          String path = mCursor.getString(mCursor
              .getColumnIndex(MediaStore.Images.Media.DATA)); 

          //获取该图片的父路径名
          String parentName = new File(path).getParentFile().getName(); 

          //根据父路径名将图片放入到mGruopMap中
          if (!mGruopMap.containsKey(parentName)) {
            List<String> chileList = new ArrayList<String>();
            chileList.add(path);
            mGruopMap.put(parentName, chileList);
          } else {
            mGruopMap.get(parentName).add(path);
          }
        } 

        //通知Handler扫描图片完成
        mHandler.sendEmptyMessage(SCAN_OK);
        mCursor.close();
      }
    }).start(); 

  }
  /**
   * 组装分组界面GridView的数据源,因为我们扫描手机的时候将图片信息放在HashMap中
   * 所以需要遍历HashMap将数据组装成List
   *
   * @param mGruopMap
   * @return
   */
  private List<ImageBean> subGroupOfImage(HashMap<String, List<String>> mGruopMap){
    if(mGruopMap.size() == 0){
      return null;
    }
    List<ImageBean> list = new ArrayList<ImageBean>(); 

    Iterator<Map.Entry<String, List<String>>> it = mGruopMap.entrySet().iterator();
    while (it.hasNext()) {
      Map.Entry<String, List<String>> entry = it.next();
      ImageBean mImageBean = new ImageBean();
      String key = entry.getKey();
      List<String> value = entry.getValue(); 

      mImageBean.setFolderName(key);
      mImageBean.setImageCounts(value.size());
      mImageBean.setTopImagePath(value.get(0));//获取该组的第一张图片 

      list.add(mImageBean);
    } 

    return list; 

  } 

}

首先看getImages()这个方法,该方法是使用ContentProvider将手机中的图片扫描出来,我这里只扫描了手机的外部存储中的图片,由于手机中可能存在很多的图片,扫描图片又比较耗时,所以我们在这里开启了子线程去获取图片,扫描的图片都存放在Cursor中,我们先要将图片按照文件夹进行分类,我们使用了HashMap来进行分类并将结果存储到mGruopMap(Key是文件夹名,Value是文件夹中的图片路径的List)中,分类完了关闭Cursor并利用Handler来通知主线程
然后是subGroupOfImage()方法,改方法是将mGruopMap的数据组装到List中,在List中存放GridView中的每个item的数据对象ImageBean, 遍历HashMap对象,具体的逻辑看代码,之后就是给GridView设置Adapter。
设置item点击事件,点击文件夹跳转到展示文件夹图片的Activity, 我们需要传递每个文件夹中的图片的路径的集合
看GroupAdapter的代码之前,我们先看一个比较重要的类,本地图片加载器NativeImageLoader

package com.example.imagescan; 

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors; 

import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Point;
import android.os.Handler;
import android.os.Message;
import android.support.v4.util.LruCache; 

/**
 * 本地图片加载器,采用的是异步解析本地图片,单例模式利用getInstance()获取NativeImageLoader实例
 * 调用loadNativeImage()方法加载本地图片,此类可作为一个加载本地图片的工具类
 */
public class NativeImageLoader {
  private LruCache<String, Bitmap> mMemoryCache;
  private static NativeImageLoader mInstance = new NativeImageLoader();
  private ExecutorService mImageThreadPool = Executors.newFixedThreadPool(1); 

  private NativeImageLoader(){
    //获取应用程序的最大内存
    final int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024); 

    //用最大内存的1/4来存储图片
    final int cacheSize = maxMemory / 4;
    mMemoryCache = new LruCache<String, Bitmap>(cacheSize) { 

      //获取每张图片的大小
      @Override
      protected int sizeOf(String key, Bitmap bitmap) {
        return bitmap.getRowBytes() * bitmap.getHeight() / 1024;
      }
    };
  } 

  /**
   * 通过此方法来获取NativeImageLoader的实例
   * @return
   */
  public static NativeImageLoader getInstance(){
    return mInstance;
  }
  /**
   * 加载本地图片,对图片不进行裁剪
   * @param path
   * @param mCallBack
   * @return
   */
  public Bitmap loadNativeImage(final String path, final NativeImageCallBack mCallBack){
    return this.loadNativeImage(path, null, mCallBack);
  } 

  /**
   * 此方法来加载本地图片,这里的mPoint是用来封装ImageView的宽和高,我们会根据ImageView控件的大小来裁剪Bitmap
   * 如果你不想裁剪图片,调用loadNativeImage(final String path, final NativeImageCallBack mCallBack)来加载
   * @param path
   * @param mPoint
   * @param mCallBack
   * @return
   */
  public Bitmap loadNativeImage(final String path, final Point mPoint, final NativeImageCallBack mCallBack){
    //先获取内存中的Bitmap
    Bitmap bitmap = getBitmapFromMemCache(path); 

    final Handler mHander = new Handler(){ 

      @Override
      public void handleMessage(Message msg) {
        super.handleMessage(msg);
        mCallBack.onImageLoader((Bitmap)msg.obj, path);
      } 

    }; 

    //若该Bitmap不在内存缓存中,则启用线程去加载本地的图片,并将Bitmap加入到mMemoryCache中
    if(bitmap == null){
      mImageThreadPool.execute(new Runnable() { 

        @Override
        public void run() {
          //先获取图片的缩略图
          Bitmap mBitmap = decodeThumbBitmapForFile(path, mPoint == null ? 0: mPoint.x, mPoint == null ? 0: mPoint.y);
          Message msg = mHander.obtainMessage();
          msg.obj = mBitmap;
          mHander.sendMessage(msg); 

          //将图片加入到内存缓存
          addBitmapToMemoryCache(path, mBitmap);
        }
      });
    }
    return bitmap; 

  } 

  /**
   * 往内存缓存中添加Bitmap
   *
   * @param key
   * @param bitmap
   */
  private void addBitmapToMemoryCache(String key, Bitmap bitmap) {
    if (getBitmapFromMemCache(key) == null && bitmap != null) {
      mMemoryCache.put(key, bitmap);
    }
  } 

  /**
   * 根据key来获取内存中的图片
   * @param key
   * @return
   */
  private Bitmap getBitmapFromMemCache(String key) {
    return mMemoryCache.get(key);
  } 

  /**
   * 根据View(主要是ImageView)的宽和高来获取图片的缩略图
   * @param path
   * @param viewWidth
   * @param viewHeight
   * @return
   */
  private Bitmap decodeThumbBitmapForFile(String path, int viewWidth, int viewHeight){
    BitmapFactory.Options options = new BitmapFactory.Options();
    //设置为true,表示解析Bitmap对象,该对象不占内存
    options.inJustDecodeBounds = true;
    BitmapFactory.decodeFile(path, options);
    //设置缩放比例
    options.inSampleSize = computeScale(options, viewWidth, viewHeight); 

    //设置为false,解析Bitmap对象加入到内存中
    options.inJustDecodeBounds = false; 

    return BitmapFactory.decodeFile(path, options);
  } 

  /**
   * 根据View(主要是ImageView)的宽和高来计算Bitmap缩放比例。默认不缩放
   * @param options
   * @param width
   * @param height
   */
  private int computeScale(BitmapFactory.Options options, int viewWidth, int viewHeight){
    int inSampleSize = 1;
    if(viewWidth == 0 || viewWidth == 0){
      return inSampleSize;
    }
    int bitmapWidth = options.outWidth;
    int bitmapHeight = options.outHeight; 

    //假如Bitmap的宽度或高度大于我们设定图片的View的宽高,则计算缩放比例
    if(bitmapWidth > viewWidth || bitmapHeight > viewWidth){
      int widthScale = Math.round((float) bitmapWidth / (float) viewWidth);
      int heightScale = Math.round((float) bitmapHeight / (float) viewWidth); 

      //为了保证图片不缩放变形,我们取宽高比例最小的那个
      inSampleSize = widthScale < heightScale ? widthScale : heightScale;
    }
    return inSampleSize;
  } 

  /**
   * 加载本地图片的回调接口
   *
   * @author xiaanming
   *
   */
  public interface NativeImageCallBack{
    /**
     * 当子线程加载完了本地的图片,将Bitmap和图片路径回调在此方法中
     * @param bitmap
     * @param path
     */
    public void onImageLoader(Bitmap bitmap, String path);
  }
}

该类是一个单例类,提供了本地图片加载,内存缓存,裁剪等逻辑,该类在加载本地图片的时候采用的是异步加载的方式,对于大图片的加载也是比较耗时的,所以采用子线程的方式去加载,对于图片的缓存机制使用的是LruCache,使用手机分配给应用程序内存的1/4用来缓存图片,除了使用LruCache缓存图片之外,还对图片进行了裁剪,举个很简单的例子,假如我们的控件大小是100 * 100, 而我们的图片是400*400,我们加载这么大的图片需要很多的内存,所以我们采用了图片裁剪,根据控件的大小来确定图片的裁剪比例,从而减小内存的消耗,提高GridView滑动的流畅度,介绍里面几个比较重要的方法
computeScale()计算图片需要裁剪的比例,根据控件的大小和图片的大小确定比例,如果图片比控件大,我们就进行裁剪,否则不需要。
decodeThumbBitmapForFile()方法是根据计算好了图片裁剪的比例之后从文件中加载图片,我们先设置options.inJustDecodeBounds = true表示解析不占用内存,但是我们能获取图片的具体大小,利用computeScale()计算好比例,在将options.inJustDecodeBounds=false,再次解析Bitmap,这样子就对图片进行了裁剪。
loadNativeImage(final String path, final Point mPoint, final NativeImageCallBack mCallBack)我们在客户端只需要调用该方法就能获取到Bitmap对象,里面的具体逻辑是先判断内存缓存LruCache中是否存在该Bitmap,不存在就开启子线程去读取,为了方便管理加载本地图片线程,这里使用了线程池,池中只能容纳一个线程,读取完了本地图片先将Bitmap加入到LruCache中,保存的Key为图片路径,然后再使用Handler通知主线程图片加载好了,之后将Bitmap和路径回调到方法onImageLoader(Bitmap bitmap, String path)中,该方法的mPoint是用来封装控件的宽和高的对象
如果不对图片进行裁剪直接这个方法的重载方法loadNativeImage(final String path, final NativeImageCallBack mCallBack) 就行了,逻辑是一样的,只是这个方法不对图片进行裁剪
接下来就是GridView的Adapter类的代码

package com.example.imagescan; 

import java.util.List; 

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Point;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.GridView;
import android.widget.ImageView;
import android.widget.TextView; 

import com.example.imagescan.MyImageView.OnMeasureListener;
import com.example.imagescan.NativeImageLoader.NativeImageCallBack; 

public class GroupAdapter extends BaseAdapter{
  private List<ImageBean> list;
  private Point mPoint = new Point(0, 0);//用来封装ImageView的宽和高的对象
  private GridView mGridView;
  protected LayoutInflater mInflater; 

  @Override
  public int getCount() {
    return list.size();
  } 

  @Override
  public Object getItem(int position) {
    return list.get(position);
  } 

  @Override
  public long getItemId(int position) {
    return position;
  } 

  public GroupAdapter(Context context, List<ImageBean> list, GridView mGridView){
    this.list = list;
    this.mGridView = mGridView;
    mInflater = LayoutInflater.from(context);
  } 

  @Override
  public View getView(int position, View convertView, ViewGroup parent) {
    final ViewHolder viewHolder;
    ImageBean mImageBean = list.get(position);
    String path = mImageBean.getTopImagePath();
    if(convertView == null){
      viewHolder = new ViewHolder();
      convertView = mInflater.inflate(R.layout.grid_group_item, null);
      viewHolder.mImageView = (MyImageView) convertView.findViewById(R.id.group_image);
      viewHolder.mTextViewTitle = (TextView) convertView.findViewById(R.id.group_title);
      viewHolder.mTextViewCounts = (TextView) convertView.findViewById(R.id.group_count); 

      //用来监听ImageView的宽和高
      viewHolder.mImageView.setOnMeasureListener(new OnMeasureListener() { 

        @Override
        public void onMeasureSize(int width, int height) {
          mPoint.set(width, height);
        }
      }); 

      convertView.setTag(viewHolder);
    }else{
      viewHolder = (ViewHolder) convertView.getTag();
      viewHolder.mImageView.setImageResource(R.drawable.friends_sends_pictures_no);
    } 

    viewHolder.mTextViewTitle.setText(mImageBean.getFolderName());
    viewHolder.mTextViewCounts.setText(Integer.toString(mImageBean.getImageCounts()));
    //给ImageView设置路径Tag,这是异步加载图片的小技巧
    viewHolder.mImageView.setTag(path); 

    //利用NativeImageLoader类加载本地图片
    Bitmap bitmap = NativeImageLoader.getInstance().loadNativeImage(path, mPoint, new NativeImageCallBack() { 

      @Override
      public void onImageLoader(Bitmap bitmap, String path) {
        ImageView mImageView = (ImageView) mGridView.findViewWithTag(path);
        if(bitmap != null && mImageView != null){
          mImageView.setImageBitmap(bitmap);
        }
      }
    }); 

    if(bitmap != null){
      viewHolder.mImageView.setImageBitmap(bitmap);
    }else{
      viewHolder.mImageView.setImageResource(R.drawable.friends_sends_pictures_no);
    } 

    return convertView;
  } 

  public static class ViewHolder{
    public MyImageView mImageView;
    public TextView mTextViewTitle;
    public TextView mTextViewCounts;
  } 

}

首先我们将每个item的图片路径设置Tag到该ImageView上面,然后利用NativeImageLoader来加载本地图片,但是我们显示的图片的宽和高可能远大于GirdView item中ImageView的大小,于是为了节省内存,我们需要对图片进行裁剪,需要对图片裁剪我们利用loadNativeImage(final String path, final Point mPoint, final NativeImageCallBack mCallBack)方法,我们就必须要获取ImageView的宽和高了
但是我们想在getView()中获取ImageView的宽和高存在问题,在getView()里面刚开始显示item的时候利用ImageView.getWidth() 获取的都是0,为什么刚开始获取不到宽和高呢,因为我们使用LayoutInflater来将XML布局文件Inflater()成View的时候,View并没有显示在界面上面,表明并没有对View进行onMeasure(), onLayout(), onDraw()等操作,必须等到retrue convertView的时候,表示该item对应的View已经绘制在ListView的位置上了, 此时才对item对应的View进行onMeasure(), onLayout(), onDraw()等操作,这时候才能获取到Item的宽和高,于是我想到了自定义ImageView,在onMeasure()中利用回调的模式主动通知我ImageView测量的宽和高,但是这有一个小小的问题,就是显示GridView的第一个item的时候,获取的宽和高还是0,第二个就能正常获取了,第一个宽和高为0,表示我们不对第一张图片进行裁剪而已,在效率上也没啥问题,不知道大家有没有好的方法,可以在getView()中获取Item中某个控件的宽和高。

自定义MyImageView的代码,我们只需要设置OnMeasureListener监听,当MyImageView测量完毕之后,就会将测量的宽和高回调到onMeasureSize()中,然后我们可以根据MyImageView的大小来裁剪图片

package com.example.imagescan; 

import android.content.Context;
import android.util.AttributeSet;
import android.widget.ImageView; 

public class MyImageView extends ImageView {
  private OnMeasureListener onMeasureListener; 

  public void setOnMeasureListener(OnMeasureListener onMeasureListener) {
    this.onMeasureListener = onMeasureListener;
  } 

  public MyImageView(Context context, AttributeSet attrs) {
    super(context, attrs);
  } 

  public MyImageView(Context context, AttributeSet attrs, int defStyle) {
    super(context, attrs, defStyle);
  } 

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

    //将图片测量的大小回调到onMeasureSize()方法中
    if(onMeasureListener != null){
      onMeasureListener.onMeasureSize(getMeasuredWidth(), getMeasuredHeight());
    }
  } 

  public interface OnMeasureListener{
    public void onMeasureSize(int width, int height);
  } 

}

上面这些代码就完成了第一个界面的功能了,接下来就是点击GridView的item跳转另一个界面来显示该文件夹下面的所有图片,功能跟第一个界面差不多,也是使用GridView来显示图片,第二个界面的布局代码我就不贴了,直接贴上界面的代码

package com.example.imagescan; 

import java.util.List; 

import android.app.Activity;
import android.os.Bundle;
import android.widget.GridView;
import android.widget.Toast; 

public class ShowImageActivity extends Activity {
  private GridView mGridView;
  private List<String> list;
  private ChildAdapter adapter; 

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.show_image_activity); 

    mGridView = (GridView) findViewById(R.id.child_grid);
    list = getIntent().getStringArrayListExtra("data"); 

    adapter = new ChildAdapter(this, list, mGridView);
    mGridView.setAdapter(adapter); 

  } 

  @Override
  public void onBackPressed() {
    Toast.makeText(this, "选中 " + adapter.getSelectItems().size() + " item", Toast.LENGTH_LONG).show();
    super.onBackPressed();
  } 

}

GridView的item上面一个我们自定义的MyImageView用来显示图片,另外还有一个CheckBox来记录我们选中情况,Adapter的代码如下

package com.example.imagescan; 

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map; 

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Point;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.ImageView;
import android.widget.CompoundButton.OnCheckedChangeListener;
import android.widget.GridView; 

import com.example.imagescan.MyImageView.OnMeasureListener;
import com.example.imagescan.NativeImageLoader.NativeImageCallBack;
import com.nineoldandroids.animation.AnimatorSet;
import com.nineoldandroids.animation.ObjectAnimator; 

public class ChildAdapter extends BaseAdapter {
  private Point mPoint = new Point(0, 0);//用来封装ImageView的宽和高的对象
  /**
   * 用来存储图片的选中情况
   */
  private HashMap<Integer, Boolean> mSelectMap = new HashMap<Integer, Boolean>();
  private GridView mGridView;
  private List<String> list;
  protected LayoutInflater mInflater; 

  public ChildAdapter(Context context, List<String> list, GridView mGridView) {
    this.list = list;
    this.mGridView = mGridView;
    mInflater = LayoutInflater.from(context);
  } 

  @Override
  public int getCount() {
    return list.size();
  } 

  @Override
  public Object getItem(int position) {
    return list.get(position);
  } 

  @Override
  public long getItemId(int position) {
    return position;
  } 

  @Override
  public View getView(final int position, View convertView, ViewGroup parent) {
    final ViewHolder viewHolder;
    String path = list.get(position); 

    if(convertView == null){
      convertView = mInflater.inflate(R.layout.grid_child_item, null);
      viewHolder = new ViewHolder();
      viewHolder.mImageView = (MyImageView) convertView.findViewById(R.id.child_image);
      viewHolder.mCheckBox = (CheckBox) convertView.findViewById(R.id.child_checkbox); 

      //用来监听ImageView的宽和高
      viewHolder.mImageView.setOnMeasureListener(new OnMeasureListener() { 

        @Override
        public void onMeasureSize(int width, int height) {
          mPoint.set(width, height);
        }
      }); 

      convertView.setTag(viewHolder);
    }else{
      viewHolder = (ViewHolder) convertView.getTag();
      viewHolder.mImageView.setImageResource(R.drawable.friends_sends_pictures_no);
    }
    viewHolder.mImageView.setTag(path);
    viewHolder.mCheckBox.setOnCheckedChangeListener(new OnCheckedChangeListener() { 

      @Override
      public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
        //如果是未选中的CheckBox,则添加动画
        if(!mSelectMap.containsKey(position) || !mSelectMap.get(position)){
          addAnimation(viewHolder.mCheckBox);
        }
        mSelectMap.put(position, isChecked);
      }
    }); 

    viewHolder.mCheckBox.setChecked(mSelectMap.containsKey(position) ? mSelectMap.get(position) : false); 

    //利用NativeImageLoader类加载本地图片
    Bitmap bitmap = NativeImageLoader.getInstance().loadNativeImage(path, mPoint, new NativeImageCallBack() { 

      @Override
      public void onImageLoader(Bitmap bitmap, String path) {
        ImageView mImageView = (ImageView) mGridView.findViewWithTag(path);
        if(bitmap != null && mImageView != null){
          mImageView.setImageBitmap(bitmap);
        }
      }
    }); 

    if(bitmap != null){
      viewHolder.mImageView.setImageBitmap(bitmap);
    }else{
      viewHolder.mImageView.setImageResource(R.drawable.friends_sends_pictures_no);
    } 

    return convertView;
  } 

  /**
   * 给CheckBox加点击动画,利用开源库nineoldandroids设置动画
   * @param view
   */
  private void addAnimation(View view){
    float [] vaules = new float[]{0.5f, 0.6f, 0.7f, 0.8f, 0.9f, 1.0f, 1.1f, 1.2f, 1.3f, 1.25f, 1.2f, 1.15f, 1.1f, 1.0f};
    AnimatorSet set = new AnimatorSet();
    set.playTogether(ObjectAnimator.ofFloat(view, "scaleX", vaules),
        ObjectAnimator.ofFloat(view, "scaleY", vaules));
        set.setDuration(150);
    set.start();
  } 

  /**
   * 获取选中的Item的position
   * @return
   */
  public List<Integer> getSelectItems(){
    List<Integer> list = new ArrayList<Integer>();
    for(Iterator<Map.Entry<Integer, Boolean>> it = mSelectMap.entrySet().iterator(); it.hasNext();){
      Map.Entry<Integer, Boolean> entry = it.next();
      if(entry.getValue()){
        list.add(entry.getKey());
      }
    } 

    return list;
  } 

  public static class ViewHolder{
    public MyImageView mImageView;
    public CheckBox mCheckBox;
  } 

}

第二个界面的Adapter跟第一个界面差不多,无非多了一个CheckBox用来记录图片选择情况,我们只需要对CheckBox设置setOnCheckedChangeListener监听,微信的选中之后CheckBox有一个动画效果,所以我利用nineoldandroids动画库也给CheckBox加了一个动画效果,直接调用addAnimation()方法就能添加了,getSelectItems()方法就能获取我们选中的item的position了,知道了选中的position,其他的信息就都知道了,微信有对图片进行预览的功能,我这里就不添加了,如果有这个需求可以自行添加,给大家推荐一个https://github.com/chrisbanes/PhotoView

运行项目,效果如下

看起来还不错吧,采用的是异步读取图片,对图片进行了缓存和裁剪,使得在显示本地图片方面比较流畅,GridView滑动也挺流畅的,也有效的避免OOM的产生。

(0)

相关推荐

  • Android数据持久化之ContentProvider机制详解

    本文实例讲述了Android数据持久化之ContentProvider机制.分享给大家供大家参考,具体如下: 一般而言,android操作系统的应用程序所建立的数据只允许自己使用,应用程序彼此间无法借助公用存储器来共享数据,android系统提供了一个机制,即内容提供器(ContentProvider),来公开自己私有的数据到数据内容器,通过该机制,可以供其他应用程序来读取自己内部的数据,当然也可以访问其他应用程序的数据.通常,内容提供器背后都有SQLite数据库的支持,用以存储内容提供内部数据

  • Android ContentProvider获取手机联系人实例

    在做项目的时候,因为要用到我们自动获取联系人的姓名和电话,就想到了ContentProvider分享数据的功能,这样做既节省了时间,也减少了我们输入错误号码的几率,所以,想在这里把小demo分享给大家,方便以后要用的时候可以看看 我们先看下获取所有联系人的方式,把所有联系人展示在listView上 public void getLinkMan(View view){ //获取联系人 Uri uri=Uri.parse("content://com.android.contacts/raw_con

  • Android 自定义ContentProvider简单实例

    Android 自定义ContentProvider简单实例 Android允许我们定义自己的的ContentProvider对象来共享数据,练练手,简单来实现一下. 要使用ContentProvider来操作数据,必须要有保存数据的场所.可以使用文件或SQLite数据库的方式来保存数据,通常使用SQLite数据库. 1,创建一个数据库帮助类,归根结底都是它在操作数据库.代码如下: package com.njue; import android.content.Context; import

  • Android ContentProvider实现获取手机联系人功能

    在之前项目中有用到关于获取手机联系人的部分,闲置就想和大家分享一下,话不多说,上代码: java部分: package com.example.content; import android.content.ContentResolver; import android.database.Cursor; import android.net.Uri; import android.support.v7.app.AppCompatActivity; import android.os.Bundle

  • Android 中自定义ContentProvider与ContentObserver的使用简单实例

    Android 中自定义ContentProvider与ContentObserver的使用简单实例 示例说明: 该示例中一共包含两个工程.其中一个工程完成了自定义ContentProvider,另外一个工程用于测试该自定义ContentProvider且在该工程中使用了ContentObserver监听自定义ContentProvider的数据变化 以下代码为工程TestContentProvider ContentProviderTest如下: package cn.testcontentp

  • Android 中ContentProvider的实例详解

    Android 中ContentProvider的实例详解 Content Provider 的简单介绍: * Android中的Content Provider 机制可支持在多个应用中存储和读取数据.这也是跨应用 共享数据的唯一方式.在Android系统中,没有一个公共的内存区域,供多个应用共享存储数据: * Android 提供了一些主要数据类型的ContentProvider ,比如:音频.视频.图片和私人通讯录等: 在android.provider 包下面找到一些android提供的C

  • android基础总结篇之八:创建及调用自己的ContentProvider

    今天我们来讲解一下如何创建及调用自己的ContentProvider. 在前面两篇文章中我们分别讲了如何读写联系人和短消息,相信大家对于ContentProvider的操作方法已经有了一定程度的了解.在有些场合,除了操作ContentProvider之外,我们还有可能需要创建自己的ContentProvider,来提供信息共享的服务,这就要求我们很好的掌握ContentProvider的创建及使用技巧.下面我们就由表及里的逐步讲解每个步骤. 在正式开始实例演示之前,我们先来了解以下两个知识点:

  • 实例讲解Android中ContentProvider组件的使用方法

    ContentProvider基本使用 为了在应用程序之间交换数据,android提供了ContentProvider,ContentProvider是不同应用程序之间进行数据交换的标准API,当一个应用程序需要把自己的数据暴露给其他程序使用时,该应用程序就可以通过提供ContentPRovider来实现,其他应用程序就可以通过ContentResolver来操作ContentProvider暴露的数据. 实现ContentProvider的步骤: 1)编写一个类,继承ContentProvid

  • Android应用中使用ContentProvider扫描本地图片并显示

    之前群里面有朋友问我,有没有关于本地图片选择的Demo,类似微信的效果,他说网上没有这方面的Demo,问我能不能写一篇关于这个效果的Demo,于是我研究了下微信的本地图片选择的Demo,自己仿照的写了下分享给大家,希望对以后有这样子需求的朋友有一点帮助吧,主要使用的是ContentProvider扫描手机中的图片,并用GridView将图片显示出来,关于GridView和ListView显示图片的问题,一直是一个很头疼的问题,因为我们手机的内存有限,手机给每个应用程序分配的内存也有限,所以图片多

  • Android用RecyclerView实现动态添加本地图片

    本文介绍了Android用RecyclerView实现动态添加本地图片,分享给大家,具体如下: 本文所用的多图选择的library来自:https://github.com/lovetuzitong/MultiImageSelector 简单介绍一下用法: 1.跳转到图片选择页面: Intent intent = new Intent(PassengerSetActivity.this, MultiImageSelectorActivity.class); intent.putExtra(Mul

  • Android开发中使用颜色矩阵改变图片颜色,透明度及亮度的方法

    本文实例讲述了Android开发中使用颜色矩阵改变图片颜色,透明度及亮度的方法.分享给大家供大家参考,具体如下: 一.如图 二.代码实现 public class ColorImageActivity extends Activity { private ImageView mImageView; private SeekBar mSBRed,mSBGreen,mSBBlue,mSBAlpha,mSBLight; //修改后的图片 private Bitmap mModBitmap; //画布

  • Python button选取本地图片并显示的实例

    从本地文件夹中选取一张图片并在canvas上显示 from tkinter import * from tkinter import filedialog from PIL import Image, ImageTk if __name__ == "__main__": root = Tk() #setting up a tkinter canvas with scrollbars frame = Frame(root, bd=2, relief=SUNKEN) frame.grid_

  • 解决Android studio中关于模拟器的/data目录不能显示的问题

    当我们在Android studio中打开Android device  monitor时,发现data目录不能打开,如图: 当我们去点击/data/目录时,发现什么都没有,这是怎么回事呢? 原因是我们权限不够,当前的用户没有权限访问data目录. 1.改变/data目录的权限 (1).在Android studio的sdk目录中找到platform-tools目录,在这个目录下同时按住shift和鼠标右键,然后选择命令窗口打开 (2).输入adb shell命令,然后输入su,使得用户切换到r

  • Android App中使用Glide加载图片的教程

    与其他图片加载库相同,Glide除了可以加载网络图片之外,也可以加载本地图片.甚至还可以从各种各样奇葩的数据源中加载图片. 加载网络图片 很多情况下,我们使用图片加载库就是为了加载网络图片.网络操作是一个很复杂的东西.试想一下,如果没有图片加载库,我们就要手动去下载图片,缓存图片,最后再从文件里面读取bitmap并设置到Imageview里面.这还算好的,要是在Listview里面你会更头疼的.原因我就不说了,你懂的~~再加上各种各样的Bitmap操作,保准你再也不想撸代码了.而且Bitmap这

  • Android音乐播放器制作 扫描本地音乐显示在手机(一)

    思路 首先是扫描本地所有的音频文件,然后全部装进集合当中,接下来就是用ListView展示在屏幕上,大概就是这几个步骤了,接下来细讲 创建一个容器 进行过数据解析的朋友都应该知道JavaBean吧,用来装载解析出来的数据,我们这里同样也要创建一个JavaBean,用来装载扫描到的音频文件,具体的代码是: package com.duanlian.mymusicplayerdemo.bean; /** * Created by user on 2016/6/24. * 放置音乐 */ public

  • vue中img或元素背景图片无法显示或路径错误的解决

    目录 img或元素背景图片无法显示或路径错误 背景图片打包后出现的路径引用错误问题 img或元素背景图片无法显示或路径错误 1.在给vue中img元素动态绑定图片路径时会显示不出来图片: <span>普通:</span><img src="./video.png" alt="" srcset=""> <span>动态绑定:</span><img :src="'./vide

  • Android中ImageView实现选择本地图片并显示功能

    运行结果: 模拟器图库就三张 没办法~画质挺感人~ 一个隐式意图 布局文件: <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height=&qu

  • Android设置拍照或者上传本地图片的示例

    前几天,我们客户端这边收到了市场部的一个需求,需要在我们订单成交后,我们的客户端有一个上传交易凭证的功能,那么如何在Android实现上传图片的这个功能呢?在我进行编码之前,我先问自己几个问题. 第一, 图片是直接选择图库里的,还是需要拍照和选择图片两个选项? 因为在选择图片的时候,会有一个拍照的按钮,也可以实现拍照的功能. 第二, 需不需要本地缓存? 本地缓存值得是,在我们的图片上传后,是否在下次直接显示,而不是从服务器读取. 第三,图片是否需要压缩? 众所周知,图片这种资源,因为体积较大,在

随机推荐