Android應(yīng)用中使用ContentProvider掃描本地圖片并顯示
之前群里面有朋友問我,有沒有關(guān)于本地圖片選擇的Demo,類似微信的效果,他說網(wǎng)上沒有這方面的Demo,問我能不能寫一篇關(guān)于這個(gè)效果的Demo,于是我研究了下微信的本地圖片選擇的Demo,自己仿照的寫了下分享給大家,希望對(duì)以后有這樣子需求的朋友有一點(diǎn)幫助吧,主要使用的是ContentProvider掃描手機(jī)中的圖片,并用GridView將圖片顯示出來,關(guān)于GridView和ListView顯示圖片的問題,一直是一個(gè)很頭疼的問題,因?yàn)槲覀兪謾C(jī)的內(nèi)存有限,手機(jī)給每個(gè)應(yīng)用程序分配的內(nèi)存也有限,所以圖片多的情況下很容易伴隨著OOM的發(fā)生,不過現(xiàn)在也有很多的開源的圖片顯示框架,對(duì)顯示很多圖片進(jìn)行了優(yōu)化,大家有興趣的可以去了解了解,今天我的這篇文章使用的是LruCache這個(gè)類以及對(duì)圖片進(jìn)行相對(duì)應(yīng)的裁剪,這樣也可以盡量的避免OOM的發(fā)生,我們先看下微信的效果吧


接下來我們就來實(shí)現(xiàn)這些效果吧,首先我們新建一個(gè)項(xiàng)目,取名ImageScan
首先我們先看第一個(gè)界面吧,使用將手機(jī)中的圖片掃描出來,然后根據(jù)圖片的所在的文件夾將其分類出來,并顯示所在文件夾里面的一張圖片和文件夾中圖片個(gè)數(shù),我們根據(jù)界面元素(文件夾名, 文件夾圖片個(gè)數(shù),文件夾中的一張圖片)使用一個(gè)實(shí)體對(duì)象ImageBean來封裝這三個(gè)屬性
package com.example.imagescan;
/**
* GridView的每個(gè)item的數(shù)據(jù)對(duì)象
*
* @author len
*
*/
public class ImageBean{
/**
* 文件夾的第一張圖片路徑
*/
private String topImagePath;
/**
* 文件夾名
*/
private String folderName;
/**
* 文件夾中的圖片數(shù)
*/
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;
}
}
接下來就是主界面的布局啦,上面的導(dǎo)航欄我沒有加進(jìn)去,只有下面的GridView,所以說主界面布局中只有一個(gè)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的布局,看上面的圖也行你會(huì)認(rèn)為他的效果是2張圖片添加的效果,其實(shí)不是,后面的疊加效果只是一張背景圖片而已,代碼先貼上來
<?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>
看到上面的布局代碼,也行你已經(jīng)發(fā)現(xiàn)了,上面使用的是自定義的MyImageView,我先不說這個(gè)自定義MyImageView的作用,待會(huì)再給大家說,我們繼續(xù)看代碼
第一個(gè)界面的主要代碼
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:
//關(guān)閉進(jìn)度條
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掃描手機(jī)中的圖片,此方法在運(yùn)行在子線程中
*/
private void getImages() {
//顯示進(jìn)度條
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();
//根據(jù)父路徑名將圖片放入到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的數(shù)據(jù)源,因?yàn)槲覀儝呙枋謾C(jī)的時(shí)候?qū)D片信息放在HashMap中
* 所以需要遍歷HashMap將數(shù)據(jù)組裝成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;
}
}
然后是subGroupOfImage()方法,改方法是將mGruopMap的數(shù)據(jù)組裝到List中,在List中存放GridView中的每個(gè)item的數(shù)據(jù)對(duì)象ImageBean, 遍歷HashMap對(duì)象,具體的邏輯看代碼,之后就是給GridView設(shè)置Adapter。
設(shè)置item點(diǎn)擊事件,點(diǎn)擊文件夾跳轉(zhuǎn)到展示文件夾圖片的Activity, 我們需要傳遞每個(gè)文件夾中的圖片的路徑的集合
看GroupAdapter的代碼之前,我們先看一個(gè)比較重要的類,本地圖片加載器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實(shí)例
* 調(diào)用loadNativeImage()方法加載本地圖片,此類可作為一個(gè)加載本地圖片的工具類
*/
public class NativeImageLoader {
private LruCache<String, Bitmap> mMemoryCache;
private static NativeImageLoader mInstance = new NativeImageLoader();
private ExecutorService mImageThreadPool = Executors.newFixedThreadPool(1);
private NativeImageLoader(){
//獲取應(yīng)用程序的最大內(nèi)存
final int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
//用最大內(nèi)存的1/4來存儲(chǔ)圖片
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的實(shí)例
* @return
*/
public static NativeImageLoader getInstance(){
return mInstance;
}
/**
* 加載本地圖片,對(duì)圖片不進(jìn)行裁剪
* @param path
* @param mCallBack
* @return
*/
public Bitmap loadNativeImage(final String path, final NativeImageCallBack mCallBack){
return this.loadNativeImage(path, null, mCallBack);
}
/**
* 此方法來加載本地圖片,這里的mPoint是用來封裝ImageView的寬和高,我們會(huì)根據(jù)ImageView控件的大小來裁剪Bitmap
* 如果你不想裁剪圖片,調(diào)用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){
//先獲取內(nèi)存中的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不在內(nèi)存緩存中,則啟用線程去加載本地的圖片,并將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);
//將圖片加入到內(nèi)存緩存
addBitmapToMemoryCache(path, mBitmap);
}
});
}
return bitmap;
}
/**
* 往內(nèi)存緩存中添加Bitmap
*
* @param key
* @param bitmap
*/
private void addBitmapToMemoryCache(String key, Bitmap bitmap) {
if (getBitmapFromMemCache(key) == null && bitmap != null) {
mMemoryCache.put(key, bitmap);
}
}
/**
* 根據(jù)key來獲取內(nèi)存中的圖片
* @param key
* @return
*/
private Bitmap getBitmapFromMemCache(String key) {
return mMemoryCache.get(key);
}
/**
* 根據(jù)View(主要是ImageView)的寬和高來獲取圖片的縮略圖
* @param path
* @param viewWidth
* @param viewHeight
* @return
*/
private Bitmap decodeThumbBitmapForFile(String path, int viewWidth, int viewHeight){
BitmapFactory.Options options = new BitmapFactory.Options();
//設(shè)置為true,表示解析Bitmap對(duì)象,該對(duì)象不占內(nèi)存
options.inJustDecodeBounds = true;
BitmapFactory.decodeFile(path, options);
//設(shè)置縮放比例
options.inSampleSize = computeScale(options, viewWidth, viewHeight);
//設(shè)置為false,解析Bitmap對(duì)象加入到內(nèi)存中
options.inJustDecodeBounds = false;
return BitmapFactory.decodeFile(path, options);
}
/**
* 根據(jù)View(主要是ImageView)的寬和高來計(jì)算Bitmap縮放比例。默認(rèn)不縮放
* @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的寬度或高度大于我們?cè)O(shè)定圖片的View的寬高,則計(jì)算縮放比例
if(bitmapWidth > viewWidth || bitmapHeight > viewWidth){
int widthScale = Math.round((float) bitmapWidth / (float) viewWidth);
int heightScale = Math.round((float) bitmapHeight / (float) viewWidth);
//為了保證圖片不縮放變形,我們?nèi)捀弑壤钚〉哪莻€(gè)
inSampleSize = widthScale < heightScale ? widthScale : heightScale;
}
return inSampleSize;
}
/**
* 加載本地圖片的回調(diào)接口
*
* @author xiaanming
*
*/
public interface NativeImageCallBack{
/**
* 當(dāng)子線程加載完了本地的圖片,將Bitmap和圖片路徑回調(diào)在此方法中
* @param bitmap
* @param path
*/
public void onImageLoader(Bitmap bitmap, String path);
}
}
computeScale()計(jì)算圖片需要裁剪的比例,根據(jù)控件的大小和圖片的大小確定比例,如果圖片比控件大,我們就進(jìn)行裁剪,否則不需要。
decodeThumbBitmapForFile()方法是根據(jù)計(jì)算好了圖片裁剪的比例之后從文件中加載圖片,我們先設(shè)置options.inJustDecodeBounds = true表示解析不占用內(nèi)存,但是我們能獲取圖片的具體大小,利用computeScale()計(jì)算好比例,在將options.inJustDecodeBounds=false,再次解析Bitmap,這樣子就對(duì)圖片進(jìn)行了裁剪。
loadNativeImage(final String path, final Point mPoint, final NativeImageCallBack mCallBack)我們?cè)诳蛻舳酥恍枰{(diào)用該方法就能獲取到Bitmap對(duì)象,里面的具體邏輯是先判斷內(nèi)存緩存LruCache中是否存在該Bitmap,不存在就開啟子線程去讀取,為了方便管理加載本地圖片線程,這里使用了線程池,池中只能容納一個(gè)線程,讀取完了本地圖片先將Bitmap加入到LruCache中,保存的Key為圖片路徑,然后再使用Handler通知主線程圖片加載好了,之后將Bitmap和路徑回調(diào)到方法onImageLoader(Bitmap bitmap, String path)中,該方法的mPoint是用來封裝控件的寬和高的對(duì)象
如果不對(duì)圖片進(jìn)行裁剪直接這個(gè)方法的重載方法loadNativeImage(final String path, final NativeImageCallBack mCallBack) 就行了,邏輯是一樣的,只是這個(gè)方法不對(duì)圖片進(jìn)行裁剪
接下來就是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的寬和高的對(duì)象
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);
//用來監(jiān)聽I(yíng)mageView的寬和高
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設(shè)置路徑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;
}
}
但是我們想在getView()中獲取ImageView的寬和高存在問題,在getView()里面剛開始顯示item的時(shí)候利用ImageView.getWidth() 獲取的都是0,為什么剛開始獲取不到寬和高呢,因?yàn)槲覀兪褂肔ayoutInflater來將XML布局文件Inflater()成View的時(shí)候,View并沒有顯示在界面上面,表明并沒有對(duì)View進(jìn)行onMeasure(), onLayout(), onDraw()等操作,必須等到retrue convertView的時(shí)候,表示該item對(duì)應(yīng)的View已經(jīng)繪制在ListView的位置上了, 此時(shí)才對(duì)item對(duì)應(yīng)的View進(jìn)行onMeasure(), onLayout(), onDraw()等操作,這時(shí)候才能獲取到Item的寬和高,于是我想到了自定義ImageView,在onMeasure()中利用回調(diào)的模式主動(dòng)通知我ImageView測(cè)量的寬和高,但是這有一個(gè)小小的問題,就是顯示GridView的第一個(gè)item的時(shí)候,獲取的寬和高還是0,第二個(gè)就能正常獲取了,第一個(gè)寬和高為0,表示我們不對(duì)第一張圖片進(jìn)行裁剪而已,在效率上也沒啥問題,不知道大家有沒有好的方法,可以在getView()中獲取Item中某個(gè)控件的寬和高。
自定義MyImageView的代碼,我們只需要設(shè)置OnMeasureListener監(jiān)聽,當(dāng)MyImageView測(cè)量完畢之后,就會(huì)將測(cè)量的寬和高回調(diào)到onMeasureSize()中,然后我們可以根據(jù)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);
//將圖片測(cè)量的大小回調(diào)到onMeasureSize()方法中
if(onMeasureListener != null){
onMeasureListener.onMeasureSize(getMeasuredWidth(), getMeasuredHeight());
}
}
public interface OnMeasureListener{
public void onMeasureSize(int width, int height);
}
}
上面這些代碼就完成了第一個(gè)界面的功能了,接下來就是點(diǎn)擊GridView的item跳轉(zhuǎn)另一個(gè)界面來顯示該文件夾下面的所有圖片,功能跟第一個(gè)界面差不多,也是使用GridView來顯示圖片,第二個(gè)界面的布局代碼我就不貼了,直接貼上界面的代碼
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上面一個(gè)我們自定義的MyImageView用來顯示圖片,另外還有一個(gè)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的寬和高的對(duì)象
/**
* 用來存儲(chǔ)圖片的選中情況
*/
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);
//用來監(jiān)聽I(yíng)mageView的寬和高
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,則添加動(dòng)畫
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加點(diǎn)擊動(dòng)畫,利用開源庫(kù)nineoldandroids設(shè)置動(dòng)畫
* @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;
}
}
第二個(gè)界面的Adapter跟第一個(gè)界面差不多,無非多了一個(gè)CheckBox用來記錄圖片選擇情況,我們只需要對(duì)CheckBox設(shè)置setOnCheckedChangeListener監(jiān)聽,微信的選中之后CheckBox有一個(gè)動(dòng)畫效果,所以我利用nineoldandroids動(dòng)畫庫(kù)也給CheckBox加了一個(gè)動(dòng)畫效果,直接調(diào)用addAnimation()方法就能添加了,getSelectItems()方法就能獲取我們選中的item的position了,知道了選中的position,其他的信息就都知道了,微信有對(duì)圖片進(jìn)行預(yù)覽的功能,我這里就不添加了,如果有這個(gè)需求可以自行添加,給大家推薦一個(gè)https://github.com/chrisbanes/PhotoView
運(yùn)行項(xiàng)目,效果如下

看起來還不錯(cuò)吧,采用的是異步讀取圖片,對(duì)圖片進(jìn)行了緩存和裁剪,使得在顯示本地圖片方面比較流暢,GridView滑動(dòng)也挺流暢的,也有效的避免OOM的產(chǎn)生。
- 實(shí)例講解Android中ContentProvider組件的使用方法
- Android中自定義ContentProvider實(shí)例
- Android開發(fā)之ContentProvider的使用詳解
- Android 自定義ContentProvider簡(jiǎn)單實(shí)例
- Android數(shù)據(jù)持久化之ContentProvider機(jī)制詳解
- Android ContentProvider的實(shí)現(xiàn)及簡(jiǎn)單實(shí)例代碼
- Android開發(fā)教程之ContentProvider數(shù)據(jù)存儲(chǔ)
- android基礎(chǔ)總結(jié)篇之八:創(chuàng)建及調(diào)用自己的ContentProvider
- Android學(xué)習(xí)筆記之ContentProvider和Uri詳解
- 詳解Android ContentProvider的基本原理和使用
相關(guān)文章
Android中實(shí)現(xiàn)淘寶購(gòu)物車RecyclerView或LIstView的嵌套選擇的邏輯
這篇文章主要介紹了Android中實(shí)現(xiàn)淘寶購(gòu)物車RecyclerView或LIstView的嵌套選擇的邏輯,非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友可以參考下2016-12-12
Android使用MediaRecorder類進(jìn)行錄制視頻
這篇文章主要介紹了Android使用MediaRecorder類進(jìn)行錄制視頻的相關(guān)資料,需要的朋友可以參考下2015-10-10
Android實(shí)現(xiàn)滑動(dòng)側(cè)邊欄
這篇文章主要為大家詳細(xì)介紹了Android實(shí)現(xiàn)滑動(dòng)側(cè)邊欄效果,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-12-12
Retrofit Rxjava實(shí)現(xiàn)圖片下載、保存并展示實(shí)例
本篇文章主要介紹了Retrofit Rxjava實(shí)現(xiàn)圖片下載、保存并展示實(shí)例,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-06-06
Android開發(fā)之無痕過渡下拉刷新控件的實(shí)現(xiàn)思路詳解
下拉刷新效果功能在程序開發(fā)中經(jīng)常會(huì)見到,今天小編抽時(shí)間給大家分享Android開發(fā)之無痕過渡下拉刷新控件的實(shí)現(xiàn)思路詳解,需要的朋友參考下吧2016-11-11
使用Android Studio檢測(cè)內(nèi)存泄露(LeakCanary)
本篇文章主要介紹了用Android Studio檢測(cè)內(nèi)存泄露的問題的解決方法,Android Studio在為我們提供了良好的編碼體驗(yàn)的同時(shí),也提供了許多對(duì)App性能分析的工具,下面我們一起來了解一下。2016-12-12

