利用LruCache和DiskLruCache加载网络图片实现图片瀑布流效果(升级版)

MainActivity如下:

package cc.patience7;

import android.os.Bundle;
import android.app.Activity;
/**
 * Demo描述:
 * 采用瀑布流的形式加载大量网络图片
 * 详细分析参见WaterfallScrollView
 *
 * 更新说明:
 * 在原本的的基础上添加了本地缓存DiskLruCache
 *
 * 所以在该示例中对于图片的缓存采用了:LruCache + DiskLruCache 的技术
 *
 * 参考资料:
 * 1 http://blog.csdn.net/guolin_blog/article/details/10470797
 * 2 http://blog.csdn.net/lfdfhl/article/details/18350601
 * 3 http://blog.csdn.net/guolin_blog/article/details/34093441
 *   Thank you very much
 */
public class MainActivity extends Activity {

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

}

LruCacheImageLoader如下:

package cc.patience7;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;

import libcore.io.DiskLruCache;
import libcore.io.DiskLruCache.Snapshot;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.util.LruCache;

public class LruCacheImageLoader {

	private static LruCacheImageLoader mLruCacheImageLoader;

	private static LruCache<String, Bitmap> mLruCache;

	private static DiskLruCache mDiskLruCache;

	private Context mContext;

	 //DiskLruCache中对于图片的最大缓存值.
    private int maxSize=20*1024*1024;

	private LruCacheImageLoader(Context context){
		mContext=context;

		//初始化LruCache.
		//设定LruCache的缓存为可用内存的六分之一
		int maxMemory=(int) Runtime.getRuntime().maxMemory();
		int size=maxMemory/6;
		mLruCache=new LruCache<String, Bitmap>(size){
			@Override
			protected int sizeOf(String key, Bitmap bitmap) {
				return bitmap.getByteCount();
			}

		};

		// 初始化DiskLruCache
		try {
			File dirFile = Utils.getDiskLruCacheDir(context, "image");
			if (!dirFile.exists()) {
				dirFile.mkdirs();
			}
			mDiskLruCache = DiskLruCache.open(dirFile,Utils.getAppVersionCode(mContext), 1, maxSize);
		} catch (Exception e) {
		}

	}

	public static LruCacheImageLoader getLruCacheImageLoaderInstance(Context context){
		if (mLruCacheImageLoader==null) {
			mLruCacheImageLoader=new LruCacheImageLoader(context);
		}
		return mLruCacheImageLoader;
	}

	/**
	 * 从LruCache中获取图片,若不存在返回null
	 */
	public Bitmap getBitmapFromLruCache(String key){
		return mLruCache.get(key);
	}

	/**
	 * 往LruCache中添加图片.
	 * 当然要首先判断LruCache中是否已经存在该图片,若不存在再添加
	 */
	public void addBitmapToLruCache(String key,Bitmap bitmap){
		if (getBitmapFromLruCache(key)==null) {
			mLruCache.put(key, bitmap);
		}
	}

	/**
	 * 依据key获取其对应的Snapshot
	 * @param key
	 * @return
	 */
	public Snapshot getSnapShotByKey(String key){
		Snapshot snapshot=null;
		try {
			snapshot=mDiskLruCache.get(key);
		} catch (Exception e) {
		}

		return snapshot;

	}

	/**
	 * 依据key获取其对应的Editor
	 * @param key
	 * @return
	 */
	public DiskLruCache.Editor getEditorByKey(String key){
		DiskLruCache.Editor editor=null;
		try {
			editor=mDiskLruCache.edit(key);
		} catch (Exception e) {
		}
		return editor;
	}

	public void flushDiskLruCache(){
		try {
			mDiskLruCache.flush();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	public static Bitmap getBitmapFromDiskLruCache(String url){
		Bitmap bitmap=null;
		Snapshot snapshot=null;
		String key=null;
		FileInputStream fileInputStream=null;
		try {
			key=Utils.getStringByMD5(url);
			snapshot=mDiskLruCache.get(key);
			fileInputStream = (FileInputStream) snapshot.getInputStream(0);
			bitmap=BitmapFactory.decodeStream(fileInputStream);
		} catch (Exception e) {
			System.out.println(""+e.toString());
		}finally{
			if (fileInputStream!=null) {
				try {
					fileInputStream.close();
				} catch (IOException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
		}

		return bitmap;
	}

}

WaterfallScrollView如下:

package cc.patience7;

import java.io.FileDescriptor;
import java.io.FileInputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.HashSet;
import libcore.io.DiskLruCache;
import libcore.io.DiskLruCache.Snapshot;
import android.content.Context;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.os.AsyncTask;
import android.os.Handler;
import android.os.Message;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnTouchListener;
import android.widget.ImageView;
import android.widget.ImageView.ScaleType;
import android.widget.LinearLayout;
import android.widget.ScrollView;
import android.widget.Toast;
/**
 * Demo功能:
 * 加载网络图片实现图片瀑布流效果(参见截图)
 *
 * Demo流程:
 * 1 为了加载的众多图片可以在屏幕上滑动显示,所以需要一个ScrollView控件.
 *   于是自定义ScrollView
 * 2 将自定义ScrollView作为布局xml文件的根布局.
 *   在根布局下有一个LinearLayout它就是该自定义ScrollView的第一个子孩子.
 *   即代码中waterfallScrollView.getChildAt(0)
 *   将该LinearLayout均分成三个子LinearLayout,它们三的宽度平分屏幕的宽度.
 *   这样我们就可以往这三个LinearLayout中不断添加图片,形成瀑布流
 * 3 将网络图片添加到瀑布流的过程
 *   3.1 当手指在屏幕上停止滑动时(ACTION_UP)加载图片
 *   3.2 从网络中下载图片并将其保存到本地缓存和内存缓存中
 *   3.3 找到三个LinearLayout中当前高度最小的,将图片添加进去
 *   3.4 在添加图片后对ScrollView中所有ImageView进行检查.
 *       对于不在屏幕上显示的ImageView将其所加载的网络图片替换成本地一张小图片.
 * 4 为了加载速度和内存的有效使用,示例中采用了LruCache以及DiskLruCache
 *
 *
 * 错误总结:
 * 在使用ImageView.setTag(key, tag)看到第一个参数为int,于是为其指定一个final的int
 * 运行报错:
 * java.lang.IllegalArgumentException: The key must be an application-specific resource id.
 * 原因是不可自己指定该值,而应该使用系统指定的int值.这么做大概是为了防止自己指定的值与系统某个值冲突吧.
 * 解决办法:在Strings.xml中指定值string值然后使用其在R文件中的int值即可,例如:
 * imageView.setTag(R.string.IMAGE_URL_TAG, imageUrl);其中:
 * R.string.IMAGE_URL_TAG就是字符串IMAGE_URL_TAG在R文件中的int值
 *
 * 在此可见setTag方法的用途:为某个View保存数据.
 * 该方法还是挺有用的,可以把属于该View的某些属性保存到该View里面,而不用单独找个地方来存这些数据
 *
 */
public class WaterfallScrollView extends ScrollView implements OnTouchListener {
	// 每页加载的图片数量
	public final int PAGE_SIZE = 15;
	// 当前页码
	private int currentPage;
	// 每一列的宽度
	private int everyColumnWidth;
	// 第一列的高度
	private int firstColumnHeight;
	// 第一列的布局
	private LinearLayout mFirstLinearLayout;
	// 第二列的高度
	private int secondColumnHeight;
	// 第二列的布局
	private LinearLayout mSecondLinearLayout;
	// 第三列的高度
	private int thirdColumnHeight;
	// 第三列的布局
	private LinearLayout mThirdLinearLayout;
	// 是否已经进入该界面
	private boolean isFirstEnterThisScrollView = false;
	// LruCache
	private LruCacheImageLoader mLruCacheImageLoader;
	// 记录所有正在下载或等待下载的异步任务
	private HashSet<LoadImageAsyncTask> mLoadImageAsyncTaskHashSet;
	// 记录ScrollView中的所有ImageView
	private ArrayList<ImageView> mAllImageViewArrayList;
	// 该WaterfallScrollView控件的高度
	private int waterfallScrollViewHeight;
	// ScrollView顶端已经向上滑出屏幕长度
	private int scrollY=0;
	private int lastScrollY=-1;
	// 处理消息的Handle
	private Handler mHandler;
	// Context
	private Context mContext;
	private final int REFRESH=9527;
	public WaterfallScrollView(Context context) {
		super(context);
		init(context);
	}

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

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

	/**
	 * 判断scrollView是否滑动到底部的三个值:
	 * scrollY:ScrollView顶端已经滑出去的高度
	 * waterfallScrollViewHeight:ScrollView的布局高度
	 * scrollView.getChildAt(0).getMeasuredHeight():ScrollView内容的高度.
	 * 常常有一部分内容要滑动后才可见,这部分的高度也包含在了这里面
	 */
	private void init(Context context){
		mContext=context;
		this.setOnTouchListener(this);
		mAllImageViewArrayList=new ArrayList<ImageView>();
		mLoadImageAsyncTaskHashSet=new HashSet<LoadImageAsyncTask>();
		mLruCacheImageLoader=LruCacheImageLoader.getLruCacheImageLoaderInstance(mContext);
		mHandler=new Handler(){
			@Override
			public void handleMessage(Message msg) {
				super.handleMessage(msg);
				if (msg.what==9527) {
					WaterfallScrollView waterfallScrollView=(WaterfallScrollView) msg.obj;
					scrollY=waterfallScrollView.getScrollY();
					// 如果当前的滚动位置和上次相同,表示已停止滚动
					if (lastScrollY==scrollY) {
						// 当滚动到最底部,并且当前没有正在下载的任务时,开始加载下一页的图片
						int scrollViewMeasuredHeight=waterfallScrollView.getChildAt(0).getMeasuredHeight();
						boolean isAsyncTaskHashSetEmpty=mLoadImageAsyncTaskHashSet.isEmpty();
						if (waterfallScrollViewHeight+scrollY>=scrollViewMeasuredHeight&&isAsyncTaskHashSetEmpty) {
							waterfallScrollView.loadNextPageImages();
						}
						//检查所有ImageView的可见性
						checkAllImageViewVisibility();
					} else {
						lastScrollY=scrollY;
						Message message=new Message();
						message.what=REFRESH;
						message.obj=WaterfallScrollView.this;
						// 5毫秒后再次对滚动位置进行判断
						mHandler.sendMessageDelayed(message, 5);
					}
				}
			}
		};
	}

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

	@Override
	protected void onLayout(boolean changed, int l, int t, int r, int b) {
		super.onLayout(changed, l, t, r, b);
		if (!isFirstEnterThisScrollView) {
			isFirstEnterThisScrollView=true;
			waterfallScrollViewHeight=getHeight();
			mFirstLinearLayout=(LinearLayout) findViewById(R.id.firstLinearLayout);
			mSecondLinearLayout=(LinearLayout) findViewById(R.id.secondLinearLayout);
			mThirdLinearLayout=(LinearLayout) findViewById(R.id.thirdLinearLayout);
			everyColumnWidth=mFirstLinearLayout.getWidth();
			loadNextPageImages();
		}
	}

	@Override
	protected void onDraw(Canvas canvas) {
		super.onDraw(canvas);
	}

	/**
	 * 这里对于手指抬起时(ACTION_UP)时,监听ScrollView是否已经停止滚动的判断的思路不错.
	 * 在ACTION_UP时直接用Handler发送一个消息在handleMessage中处理判断,如果此时还
	 * 没有停止滚动,则延时一定时间再次发送消息判断滚动是否停止.
	 * 这样做避免的在ACTION_UP时去加载图片而是在ScrollView停止滚动时去加载.
	 */
	@Override
	public boolean onTouch(View view, MotionEvent motionEvent) {
		if (motionEvent.getAction()==MotionEvent.ACTION_UP) {
			Message message=new Message();
			message.obj=this;
			message.what=REFRESH;
			mHandler.sendMessageDelayed(message, 5);
		}
		return false;
	}

	private void loadNextPageImages(){
		if (Utils.isExistSDCard()) {
			int start=PAGE_SIZE*currentPage;
			int end=PAGE_SIZE*currentPage+PAGE_SIZE;
			LoadImageAsyncTask loadImageAsyncTask;
			if (end>ImagesUrl.urlStringArray.length) {
				end=ImagesUrl.urlStringArray.length;
			}
			if (start<ImagesUrl.urlStringArray.length) {
				Toast.makeText(mContext, "开始加载", Toast.LENGTH_SHORT).show();
				for (int i = start;i < end; i++) {
					System.out.println("加载"+i);
					loadImageAsyncTask=new LoadImageAsyncTask();
					loadImageAsyncTask.execute(ImagesUrl.urlStringArray[i]);
					mLoadImageAsyncTaskHashSet.add(loadImageAsyncTask);
				}
				currentPage++;
			} else {

			}

		} else {
			Toast.makeText(mContext, "无SD卡", Toast.LENGTH_LONG).show();
		}
	}

	/**
	 * 判断ImageView是否可见
	 * 如果可见:
	 * 1  从LruCache取出图片显示
	 * 2 若不在LruCache中,则从本地缓存中取出
	 * 3 若本地缓存中也不存在那么开启异步任务下载
	 * 4 下载完成后将图片保存至本地和内存缓存中
	 *   第2,3,4步可以参见LoadImageAsyncTask
	 * 若不可见:
	 * 将ImageView显示的图片替换成本地图片
	 */
	private void checkAllImageViewVisibility(){
		ImageView imageView=null;
		for(int i=0;i<mAllImageViewArrayList.size();i++){
			imageView=mAllImageViewArrayList.get(i);
			int top_border=(Integer) imageView.getTag(R.string.TOP_BORDER_TAG);
			int bottom_border=(Integer) imageView.getTag(R.string.BOTTOM_BORDER_TAG);
			if (bottom_border > getScrollY() && top_border < getScrollY() + waterfallScrollViewHeight) {
				String imageUrl=(String) imageView.getTag(R.string.IMAGE_URL_TAG);
				Bitmap bitmap=mLruCacheImageLoader.getBitmapFromLruCache(imageUrl);
				if (bitmap==null) {
					LoadImageAsyncTask loadImageAsyncTask=new LoadImageAsyncTask(imageView);
					loadImageAsyncTask.execute(imageUrl);
				} else {
					System.out.println("---> 从内存缓存中取出图片");
					imageView.setImageBitmap(bitmap);
				}

			} else {
				imageView.setImageResource(R.drawable.empty_photo);
			}
		}
	}

	/**
	 * 该LoadImageAsyncTask是获取图片的入口:
	 *
	 * 注意不管这个图片是在SD卡还是从网络下载,这都是获取图片的入口,这么做的好处
	 * 1   统一了获取图片的入口.
	 *   如果把获取图片分为图片在LruCache,图片在SD卡,图片在网络上这几种不同
	 *   的情况而去分别用对应的函数获取,这样势必会导致该需求的多入口.凌乱,不好优化.
	 *   而且这几种方式放到AsyncTask中都不会出错,尤其是网络请求耗时的情况下.
	 * 2 不管通过哪种方式获取到了图片,我们都要对图片进行保存,或者再次修整,比如缩放.
	 *   我们可以把这些操作又统一放到异步操作的onPostExecute()方法中.
	 *
	 * 为什么这里有需要有两个构造方法呢?因为对应两种不同的情况
	 * 情况一:
	 * 图片第一次显示时利用LoadImageAsyncTask()建立一个异步任务.当图片下载完成时new一个ImageView显示
	 * 该图片即可.
	 * 比如进入应用时开始显示图片,就是这种情况.
	 *
	 * 情况二:
	 * 当某图片再次显示时需要一个ImageView,于是就利用LoadImageAsyncTask传入原来的ImageView;当图片的获取或者下载完成
	 * 就用原来的ImageView显示即可.
	 * 比如把界面拉到最下方再拉回到最上面就是这种情况.
	 *
	 * 这两种情况查看addImageToScrollView()方法即可明白其体现.
	 *
	 */
	private class LoadImageAsyncTask extends AsyncTask<String, Void, Bitmap>{
		private String imageUrl;
		private Bitmap bitmap;
		ImageView imageView=null;

		public LoadImageAsyncTask(){

		}

		public LoadImageAsyncTask(ImageView imageView){
			this.imageView=imageView;
		}

		@Override
		protected Bitmap doInBackground(String... params) {
			FileDescriptor fileDescriptor=null;
			FileInputStream fileInputStream=null;
			Snapshot snapshot=null;
			imageUrl=params[0];
			bitmap=mLruCacheImageLoader.getBitmapFromLruCache(imageUrl);
			if (bitmap == null) {
				try {
					String key = Utils.getStringByMD5(imageUrl);
					snapshot = mLruCacheImageLoader.getSnapShotByKey(key);
					// 从网络下载图片且保存至本地缓存
					if (snapshot == null) {
						DiskLruCache.Editor editor = mLruCacheImageLoader.getEditorByKey(key);
						if (editor != null) {
							OutputStream outputStream = editor.newOutputStream(0);
							if (Utils.getBitmapFromNetWorkAndSaveToDiskLruCache(imageUrl, outputStream)) {
								System.out.println("---> 从网络下载图片且保存至本地缓存");
								editor.commit();
								mLruCacheImageLoader.flushDiskLruCache();
							} else {
								editor.abort();
							}
						}
						// 缓存被写入本地缓存后再次查找key对应的缓存
						snapshot = mLruCacheImageLoader.getSnapShotByKey(key);
					}else{
						System.out.println("---> 图片不在内存中但是在本地缓存中");
					}

					// 将图片再保存至内存缓存
					if (snapshot != null) {
						fileInputStream = (FileInputStream) snapshot.getInputStream(0);
						fileDescriptor = fileInputStream.getFD();
						if (fileDescriptor != null) {
							bitmap = BitmapFactory.decodeFileDescriptor(fileDescriptor);
							if (bitmap != null) {
								System.out.println("---> 从网络下载图片且保存至本地缓存后再缓存到内存");
								mLruCacheImageLoader.addBitmapToLruCache(imageUrl, bitmap);
							}
						}
					}
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
			return bitmap;
		}

		/**
		 * 在onPostExecute()对图片进行修整
		 * 因为在doInBackground()的loadImage()方法中已经把经过scale的图片存到了SD卡和LruCache中
		 * 并且在计算inSampleSize的时候是以宽width为标准的.
		 * 比如inSampleSize=2,那么保存的图的宽和高都是原来的二分之一.
		 * 但是请注意inSampleSize是int类型的,那么缩放出来的比例多半不是我们期望的刚好屏幕宽度的三分之一,它是有偏差的.
		 * 所以在这里进行修正,尤其是对高进行修正.
		 * 这样就保证了宽是一个定值(屏幕的三分之一),高也得到了调整,不至于严重失真.
		 *
		 */
		@Override
		protected void onPostExecute(Bitmap bitmap) {
			super.onPostExecute(bitmap);
			mLoadImageAsyncTaskHashSet.remove(this);
			if (bitmap!=null) {
				double ration=bitmap.getWidth()/(everyColumnWidth*1.0);
				int imageViewHeight=(int) (bitmap.getHeight()/ration);
				int imageViewWidth=everyColumnWidth;
				addImageToScrollView(bitmap,imageView,imageViewWidth,imageViewHeight,imageUrl);
			}
		}
	}

	/**
	 * 将获取到的Bitmap添加到ImageView中.
	 * 这里利用View.setTag()的方式为该ImageView保存了其相关信息.
	 * 比如该ImageView加载的图片的url,它的上下边在ScrollView中的位置信息等.
	 */
	private void addImageToScrollView(Bitmap bitmap,ImageView imageView,int imageViewWidth,int imageViewHeight,final String imageUrl){
		if (imageView != null) {
			imageView.setImageBitmap(bitmap);
		} else {
			ImageView newImageView = new ImageView(mContext);
			LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(imageViewWidth, imageViewHeight);
			newImageView.setImageBitmap(bitmap);
			newImageView.setLayoutParams(layoutParams);
			newImageView.setScaleType(ScaleType.FIT_XY);
			newImageView.setPadding(5, 5, 5, 5);
			newImageView.setTag(R.string.IMAGE_URL_TAG, imageUrl);
			newImageView.setOnClickListener(new OnClickListener() {
				@Override
				public void onClick(View view) {
					Intent intent = new Intent(mContext,ShowImageActivity.class);
					intent.putExtra("imageUrl", imageUrl);
					mContext.startActivity(intent);
				}
			});
			addImageToColumn(newImageView);
			mAllImageViewArrayList.add(newImageView);
		}
	}

	/**
	 * 找到高度最小的LinearLayout并且将ImageView添加进去
	 */
	private void addImageToColumn(ImageView imageView){
		int imageViewHeight=imageView.getLayoutParams().height;
		if (firstColumnHeight <= secondColumnHeight) {
			if (firstColumnHeight <= thirdColumnHeight) {
				imageView.setTag(R.string.TOP_BORDER_TAG, firstColumnHeight);
				firstColumnHeight += imageViewHeight;
				imageView.setTag(R.string.BOTTOM_BORDER_TAG, firstColumnHeight);
				mFirstLinearLayout.addView(imageView);
			}else{
				imageView.setTag(R.string.TOP_BORDER_TAG, thirdColumnHeight);
				thirdColumnHeight += imageViewHeight;
				imageView.setTag(R.string.BOTTOM_BORDER_TAG, thirdColumnHeight);
				mThirdLinearLayout.addView(imageView);
			}
		} else {
			if (secondColumnHeight <= thirdColumnHeight) {
				imageView.setTag(R.string.TOP_BORDER_TAG, secondColumnHeight);
				secondColumnHeight += imageViewHeight;
				imageView.setTag(R.string.BOTTOM_BORDER_TAG, secondColumnHeight);
				mSecondLinearLayout.addView(imageView);
			}else{
				imageView.setTag(R.string.TOP_BORDER_TAG, thirdColumnHeight);
				thirdColumnHeight += imageViewHeight;
				imageView.setTag(R.string.BOTTOM_BORDER_TAG, thirdColumnHeight);
				mThirdLinearLayout.addView(imageView);
			}
		}

	}
}

Utils如下:

package cc.patience7;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileDescriptor;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import org.apache.http.HttpStatus;
import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.BitmapFactory.Options;
import android.os.Environment;

public class Utils {
	/**
	 * 判断SD卡是否存在
	 */
	public static boolean isExistSDCard() {
		boolean isExist = false;
		if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
			isExist = true;
		}
		return isExist;
	}

	/**
	 * 从SD卡中获取图片
	 *
	 * 注意事项:
	 * 这里采用BitmapFactory.decodeFileDescriptor()的方式来避免内存溢出.
	 * 而不是用BitmapFactory.decodeFile()的方式
	 */
	public static Bitmap getBitmapFromSDCard(String filePath,int requestWidth){
		Bitmap bitmap=null;
		try {
			Options options=new Options();
			options.inJustDecodeBounds=true;
			BitmapFactory.decodeFile(filePath, options);
			options.inSampleSize=calculateInSampleSize(options,requestWidth);
			options.inJustDecodeBounds=false;
			FileInputStream fileInputStream=new FileInputStream(filePath);
			FileDescriptor fileDescriptor=fileInputStream.getFD();
			bitmap=BitmapFactory.decodeFileDescriptor(fileDescriptor, null, options);
			fileInputStream.close();
		} catch (Exception e) {

		}
		return bitmap;
	}

	public static Bitmap fixBitmap(){
		return null;
	}

	/**
	 * 计算图片的缩放比例
	 */
	public static int calculateInSampleSize(Options options,int requestWidth){
		int inSampleSize=1;
		//SD卡中图片的宽
		int outWidth=options.outWidth;
		if (outWidth>requestWidth) {
			inSampleSize=Math.round((float) outWidth / (float) requestWidth);
		 }
		return inSampleSize;
	}

	/**
	 * 从网络获取图片且保存至SD卡
	 */
	public static void getBitmapFromNetWorkAndSaveToSDCard(String imageUrl,String filePath){
		URL url=null;
		File imageFile=null;
		HttpURLConnection httpURLConnection=null;
		FileOutputStream fileOutputStream=null;
		BufferedOutputStream bufferedOutputStream=null;
		InputStream inputStream=null;
		BufferedInputStream bufferedInputStream=null;
		try {
			url=new URL(imageUrl);
			httpURLConnection=(HttpURLConnection) url.openConnection();
			httpURLConnection.setConnectTimeout(5*1000);
			httpURLConnection.setReadTimeout(10*1000);
			httpURLConnection.setDoInput(true);
			httpURLConnection.setDoOutput(true);
			if (httpURLConnection.getResponseCode()==HttpStatus.SC_OK) {
				imageFile=new File(filePath);
				if (!imageFile.getParentFile().exists()) {
					imageFile.getParentFile().mkdirs();
				}
				if (!imageFile.exists()) {
					imageFile.createNewFile();
				}
				fileOutputStream=new FileOutputStream(imageFile);
				bufferedOutputStream=new BufferedOutputStream(fileOutputStream);
				inputStream=httpURLConnection.getInputStream();
				bufferedInputStream=new BufferedInputStream(inputStream);
				int len=0;
				byte [] buffer=new byte[1024];
				while((len=bufferedInputStream.read(buffer))!=-1){
					bufferedOutputStream.write(buffer, 0, len);
					bufferedOutputStream.flush();
				}
			} else {
				System.out.println("图片请求失败");
			}
		} catch (Exception e) {
			    System.out.println("e="+e.toString());
		}finally{
			try {
				if (fileOutputStream!=null) {
					fileOutputStream.close();
				}
				if (bufferedOutputStream!=null) {
					bufferedOutputStream.close();
				}
				if (inputStream!=null) {
					inputStream.close();
				}
				if (bufferedInputStream!=null) {
					bufferedInputStream.close();
				}
				if (httpURLConnection!=null) {
					httpURLConnection.disconnect();
				}
			} catch (Exception e) {
				 System.out.println("e="+e.toString());
			}
		}

	}

	/**
	 * 获取DiskLruCache的缓存文件夹
	 * 注意第二个参数dataType
	 * DiskLruCache用一个String类型的唯一值对不同类型的数据进行区分.
	 * 比如bitmap,object等文件夹.其中在bitmap文件夹中缓存了图片.
	 * card/Android/data/<application package>/cache
	 * 如果
	 * 缓存数据的存放位置为:
	 * /sdSD卡不存在时缓存存放位置为:
	 * /data/data/<application package>/cache
	 *
	 */
	public static File getDiskLruCacheDir(Context context, String dataType) {
		String dirPath;
		File cacheFile = null;
		if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState()) ||
			!Environment.isExternalStorageRemovable()) {
			dirPath=context.getExternalCacheDir().getPath();
		} else {
			dirPath=context.getCacheDir().getPath();
		}
		cacheFile=new File(dirPath+File.separator+dataType);
		return cacheFile;
	}

	/**
	 * 获取APP当前版本号
	 * @param context
	 * @return
	 */
	public static int getAppVersionCode(Context context){
		int versionCode=1;
		try {
			String packageName=context.getPackageName();
			PackageManager packageManager=context.getPackageManager();
			PackageInfo packageInfo=packageManager.getPackageInfo(packageName, 0);
			versionCode=packageInfo.versionCode;
		} catch (NameNotFoundException e) {
			e.printStackTrace();
		}
		return versionCode;
	}

	/**
	 * 将字符串用MD5编码.
	 * 比如在该示例中将url进行MD5编码
	 */
	public static String getStringByMD5(String string) {
		String md5String = null;
		try {
			// Create MD5 Hash
			MessageDigest messageDigest = MessageDigest.getInstance("MD5");
			messageDigest.update(string.getBytes());
			byte messageDigestByteArray[] = messageDigest.digest();
			if (messageDigestByteArray == null || messageDigestByteArray.length == 0) {
				return md5String;
			}

			// Create hexadecimal String
			StringBuffer hexadecimalStringBuffer = new StringBuffer();
			int length = messageDigestByteArray.length;
			for (int i = 0; i < length; i++) {
				hexadecimalStringBuffer.append(Integer.toHexString(0xFF & messageDigestByteArray[i]));
			}
			md5String = hexadecimalStringBuffer.toString();
			return md5String;
		} catch (NoSuchAlgorithmException e) {
			e.printStackTrace();
		}
		return md5String;
	}

	/**
	 * 从网络获取图片且保存至SD卡中的缓存
	 */
	public static boolean getBitmapFromNetWorkAndSaveToDiskLruCache(String imageUrl,OutputStream outputStream){
		boolean isSuccessfull=false;
		URL url=null;
		HttpURLConnection httpURLConnection=null;
		BufferedOutputStream bufferedOutputStream=null;
		InputStream inputStream=null;
		BufferedInputStream bufferedInputStream=null;
		try {
			url=new URL(imageUrl);
			httpURLConnection=(HttpURLConnection) url.openConnection();
			httpURLConnection.setConnectTimeout(5*1000);
			httpURLConnection.setReadTimeout(10*1000);
			httpURLConnection.setDoInput(true);
			httpURLConnection.setDoOutput(true);
			if (httpURLConnection.getResponseCode()==HttpStatus.SC_OK) {
				bufferedOutputStream=new BufferedOutputStream(outputStream);
				inputStream=httpURLConnection.getInputStream();
				bufferedInputStream=new BufferedInputStream(inputStream);
				int len=0;
				byte [] buffer=new byte[1024];
				while((len=bufferedInputStream.read(buffer))!=-1){
					bufferedOutputStream.write(buffer, 0, len);
					bufferedOutputStream.flush();
				}
				isSuccessfull=true;
			} else {
				isSuccessfull=false;
				System.out.println("图片请求失败");
			}
		} catch (Exception e) {
			    isSuccessfull=false;
			    System.out.println("e="+e.toString());
		}finally{
			try {
				if (bufferedOutputStream!=null) {
					bufferedOutputStream.close();
				}
				if (inputStream!=null) {
					inputStream.close();
				}
				if (bufferedInputStream!=null) {
					bufferedInputStream.close();
				}
				if (httpURLConnection!=null) {
					httpURLConnection.disconnect();
				}
			} catch (Exception e) {
				 System.out.println("e="+e.toString());
			}
		}

		return isSuccessfull;

	}

}

ZoomImageView如下:

package cc.patience7;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.PointF;
import android.util.AttributeSet;
import android.util.FloatMath;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
/**
 * 流程说明:
 * 在该自定义View中主要是运用到了Matrix对图片进行缩放和位移
 * 1 初始时,将图片绘制到该控件上
 * 2 每当对图片拖动或者缩放时重新绘制图片
 *
 * 核心方法:
 * canvas.drawBitmap(bitmap, matrix, paint)
 * 重点在于去操作一个Matrix.
 * 该处主要用到的是利用Matrix实现缩放(Scale)和位移(Translate)
 *
 *
 * mMatrix和mCurrentMatrix的说明
 * 这是以前写的Demo了,今天在重新整理看到这两个Matrix居然一下子
 * 没有反应过来.所以在此写下笔记,记录下来.
 * 在这个示例中一共涉及到两个Matrix,它们分别有什么用呢?
 * mMatrix.postScale()和mMatrix.postTranslate()起到实际作用的
 * 是mMatrix.但是请注意,这些postScale和postTranslate是基于以往
 * 的matrix的,就是说现在这个mMatrix执行的操作是在原来的矩阵matrix
 * 的基础上进行的.
 * 比如第一次是scale缩放操作,得到的矩阵是matrix1,这个时候停止操作
 * 图片已经改变了原来的样子
 * 然后接着进行第二次的操作,再进行translate位移操作,它就是在第一次
 * 的结果上继续上操作的;从代码上来看,现在的matrix要在上一次的matrix
 * 进行操作.
 * 所以我们需要一个变量来记录上次操作后的矩阵,即此处的mCurrentMatrix
 *
 *
 *
 * 关于CURRENT_MODE == ZOOM_MODE时的说明:
 * 每次的缩放scale都是相对于两指头放在屏幕上的最初状态而言的.
 * 什么意思呢?解释如下:
 * if (CURRENT_MODE == ZOOM_MODE) {
 *    在这段代码中twoFingers_distance_before_move是不变的.
 *    但是twoFingers_distance_after_move在两指操作缩放的过程
 *    中是持续变大或者变小的.
 *    这样导致了计算出来的scale是持续变大或者边小的.
 *    比如在两指慢慢放大的过程中,从输出的Log可以发现这个scale在
 *    一直变大,哪怕是放大的动作很小此时的scale也是1.X,但是图片也只
 *    变大了一点点没有突然变很大.因为每次的缩放都是针对缩放前的状态
 *    而言的,而不是针对上一次缩放而言.举例吧:
 *    status1:两指放在屏幕上的状态
 *    然后两指持续在屏幕上慢慢的MOVE实现放大,每一次微小的放大都构成
 *    了一次新的状态
 *    status2:放大了一点
 *    status3:持续放大了一点
 *    status4:又持续放大了一点
 *    status5:再一次持续放大了一点
 *    .........................
 *    status5,status4的放大都是针对status1而言的,而不是针对它们的上一次
 *    status4或者status3而言.
 *    所以每次都要先复制原来的matrix再进行缩放,代码如下:
 *    * mMatrix.set(mCurrentMatrix);
 *    //依据缩放比例和中心点进行缩放
 *    mMatrix.postScale(scale, scale, mMiddlePointF.x,mMiddlePointF.y);
 *  }
 *
 *
 *
 * 注意事项:
 * 在该Demo中对于ImageView的设置
 * android:layout_width="match_parent"
 * android:layout_height="match_parent"
 * 是不太合理的,在具体项目中应调整
 *
 */
public class ZoomImageView extends View {
	//从SD卡获取的图片
	private Bitmap mRawBitmap;
	//该缩放控件自身的宽
	private int zoomImageViewWidth;
	//该缩放控件自身的高
	private int zoomImageViewHeight;
	//TAG
	private final String TAG="ZoomImageView";
	// 开始点
	private PointF mStartPoinF;
	// 图片位置的变换矩阵
	private Matrix mMatrix;
	// 图片当前矩阵
	private Matrix mCurrentMatrix;
	// 模式参数
	private int CURRENT_MODE = 0;
	// 初始模式
    private static final int INIT_MODE = 1;
	// 拖拉模式
	private static final int DRAG_MODE = 2;
	// 缩放模式
	private static final int ZOOM_MODE = 3;
	// 开启缩放的阈值
	private static final float ZOOM_THRESHOLD = 10.0f;
	// 缩放前两指间的距离
	private float twoFingers_distance_before_move;
	// 缩放后两指间的距离
	private float twoFingers_distance_after_move;
	// 两指间中心点
	private PointF mMiddlePointF;

	public ZoomImageView(Context context) {
		super(context);
	}

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

	public ZoomImageView(Context context, AttributeSet attrs, int defStyleAttr) {
		super(context, attrs, defStyleAttr);
	}

	public void setBitmap(Bitmap bitmap){
		CURRENT_MODE=INIT_MODE;
		mRawBitmap=bitmap;
		mStartPoinF = new PointF();
		mMatrix = new Matrix();
		mCurrentMatrix = new Matrix();
		invalidate();
	}

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

	@Override
	protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
		super.onLayout(changed, left, top, right, bottom);
		if (changed) {
			zoomImageViewWidth=getWidth();
			zoomImageViewHeight=getHeight();
		}
	}

	@Override
	public boolean onTouchEvent(MotionEvent event) {
		switch (event.getAction() & MotionEvent.ACTION_MASK) {
		case MotionEvent.ACTION_DOWN:
			CURRENT_MODE = DRAG_MODE;
			// 记录图片当前matrix
			mCurrentMatrix.set(mMatrix);
			// 记录开始坐标point
			mStartPoinF.set(event.getX(), event.getY());
			break;

		// 当屏幕上已经有触点(手指),再有手指按下时触发该事件
		case MotionEvent.ACTION_POINTER_DOWN:
			CURRENT_MODE = ZOOM_MODE;
			twoFingers_distance_before_move = getTwoPointsDistance(event);
			if (twoFingers_distance_before_move > ZOOM_THRESHOLD) {
				// 计算两触点的中心点
				mMiddlePointF = getMiddlePoint(event);
			}
			break;

		case MotionEvent.ACTION_MOVE:
			//拖动模式下--->处理图片的拖动
			if (CURRENT_MODE == DRAG_MODE) {
				// 获取X轴移动距离
				float distanceX = event.getX() - mStartPoinF.x;
				// 获取Y轴移动距离
				float distanceY = event.getY() - mStartPoinF.y;
				// 在mCurrentMatrix的基础上平移图片,所以将mCurrentMatrix复制到mMatrix
				mMatrix.set(mCurrentMatrix);
				mMatrix.postTranslate(distanceX, distanceY);

			}
			//缩放模式下--->处理图片的缩放
			if (CURRENT_MODE == ZOOM_MODE) {
				twoFingers_distance_after_move = getTwoPointsDistance(event);
				if (twoFingers_distance_after_move > ZOOM_THRESHOLD) {
					// 计算缩放比例
					float scale = twoFingers_distance_after_move / twoFingers_distance_before_move;
					// 在mCurrentMatrix的基础上缩放图片,所以将mCurrentMatrix复制到mMatrix
				    mMatrix.set(mCurrentMatrix);
					// 依据缩放比例和中心点进行缩放
					mMatrix.postScale(scale, scale, mMiddlePointF.x,mMiddlePointF.y);
				}
			}
			break;

		case MotionEvent.ACTION_UP:
		// 当手指离开屏幕,但屏幕上仍有其他触点(手指)时触发该事件
		case MotionEvent.ACTION_POINTER_UP:
			CURRENT_MODE = 0;
			break;
		}
		invalidate();
		return true;
	}

	@Override
	protected void onDraw(Canvas canvas) {
		super.onDraw(canvas);
		switch (CURRENT_MODE) {
		case INIT_MODE:
			initZoomImageView(canvas);
			break;
		default:
			canvas.drawBitmap(mRawBitmap, mMatrix, null);
			break;
		}
	}

    /**
	* 将从SD卡获取的图片显示到该ZoomImageView控件
	* 1  图片的宽或高大于ZoomImageView控件本身的宽或者高则缩放.
	*   1.1 判断以宽为标准或者以高为标准进行压缩,如果:
	*      rawBitmapWidth-zoomImageViewWidth>rawBitmapHeight-zoomImageViewHeight
	*      则说明图片的宽超出控件的宽的程度要大于图片的高超出控件的高的程度.所以必须要满足对于宽的压缩,即以宽为
	*      压缩基准.反之同理,不再赘述.
	*   1.2 在以宽为基准压缩图片后,图片的宽即为ZoomImageView控件的宽,但是图片的高必然小于
	*      ZoomImageView控件的高.所以在Y方向位移,使得图片在控件中心位置绘制.
	*      反之同理,不再赘述
	* 2  图片的宽或高均不大于ZoomImageView控件本身的宽或者高.
	*   则在ZoomImageView控件中心位置绘制图片
	*/
	private void initZoomImageView(Canvas canvas){
		if (mRawBitmap!=null) {
			Matrix matrix=new Matrix();
			int rawBitmapWidth=mRawBitmap.getWidth();
			int rawBitmapHeight=mRawBitmap.getHeight();
			Log.i(TAG, "控件本身宽="+zoomImageViewWidth+",控件本身高="+zoomImageViewHeight);
			Log.i(TAG, "图片宽="+rawBitmapWidth+",图片高="+rawBitmapHeight);
			if (rawBitmapWidth>zoomImageViewWidth||rawBitmapHeight>zoomImageViewHeight) {
				Log.i(TAG, "rawBitmapWidth-zoomImageViewWidth="+(rawBitmapWidth-zoomImageViewWidth));
				Log.i(TAG, "rawBitmapHeight-zoomImageViewHeight="+(rawBitmapHeight-zoomImageViewHeight));
				//以宽为基准压缩
				if (rawBitmapWidth-zoomImageViewWidth>rawBitmapHeight-zoomImageViewHeight) {
					//1 压缩
					float scaleXY=zoomImageViewWidth/(rawBitmapWidth*1.0f);
					matrix.postScale(scaleXY, scaleXY);
					//2在Y方向上平移,使图片居中
					float translateY=(zoomImageViewHeight-rawBitmapHeight*scaleXY)/2.0f;
					matrix.postTranslate(0, translateY);
					Log.i(TAG, "以宽为基准压缩 scaleXY="+scaleXY+",translateY="+translateY);
				//以高为基准压缩
				} else {
					//1 压缩
					float scaleXY=zoomImageViewHeight/(rawBitmapHeight*1.0f);
					matrix.postScale(scaleXY, scaleXY);
					//2在X方向上平移,使图片居中
					float translateX=(zoomImageViewWidth-rawBitmapWidth*scaleXY)/2.0f;
					matrix.postTranslate(translateX, 0);
					Log.i(TAG, "以高为基准压缩 scaleXY="+scaleXY+",translateX="+translateX);
				}
			} else {
				float translateX=(zoomImageViewWidth-rawBitmapWidth)/2.0f;
				float translateY=(zoomImageViewHeight-rawBitmapHeight)/2.0f;
				matrix.postTranslate(translateX, translateY);
				Log.i(TAG, "不压缩,图片居中显示 translateX="+translateX+",translateY="+translateY);
			}
			canvas.drawBitmap(mRawBitmap, matrix, null);
			//将图片初始化完成后的matrix保存到mMatrix.
			//后续进行的操作都是在mMatrix上进行的
			mMatrix.set(matrix);
		}
	}

	// 计算两点之间的距离
	public static float getTwoPointsDistance(MotionEvent event) {
		float disX = event.getX(1) - event.getX(0);
		float disY = event.getY(1) - event.getY(0);
		return FloatMath.sqrt(disX * disX + disY * disY);
	}

	// 计算两点之间的中间点
	public static PointF getMiddlePoint(MotionEvent event) {
		float midX = (event.getX(0) + event.getX(1)) / 2;
		float midY = (event.getY(0) + event.getY(1)) / 2;
		return new PointF(midX, midY);
	}

}

ShowImageActivity如下:

package cc.patience7;

import android.app.Activity;
import android.graphics.Bitmap;
import android.os.Bundle;
import android.view.Window;

public class ShowImageActivity extends Activity {
	private Bitmap mBitmap=null;
	private ZoomImageView mZoomImageView;
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		requestWindowFeature(Window.FEATURE_NO_TITLE);
		setContentView(R.layout.showimage);
	    init();
	}

	private void init(){
		mZoomImageView=(ZoomImageView) findViewById(R.id.zoomImageView);
		String imageUrl=getIntent().getStringExtra("imageUrl");
		mBitmap=LruCacheImageLoader.getBitmapFromDiskLruCache(imageUrl);
		if (mBitmap!=null) {
			mZoomImageView.setBitmap(mBitmap);
		}

	}

	/**
	 * 回收Bitmap避免内存溢出
	 */
	@Override
	protected void onDestroy() {
		super.onDestroy();
		if (mBitmap!=null) {
			mBitmap.recycle();
		}
	}

}

ImagesUrl如下:

package cc.patience7;

public class ImagesUrl {

	public static String urlStringArray []=new String []{
		"http://img.my.csdn.net/uploads/201309/01/1378037235_3453.jpg",
		"http://img.my.csdn.net/uploads/201309/01/1378037235_7476.jpg",
		"http://img.my.csdn.net/uploads/201309/01/1378037235_9280.jpg",
		"http://img.my.csdn.net/uploads/201309/01/1378037234_3539.jpg",
		"http://img.my.csdn.net/uploads/201309/01/1378037234_6318.jpg",
		"http://img.my.csdn.net/uploads/201309/01/1378037194_2965.jpg",
		"http://img.my.csdn.net/uploads/201309/01/1378037193_1687.jpg",
		"http://img.my.csdn.net/uploads/201309/01/1378037193_1286.jpg",
		"http://img.my.csdn.net/uploads/201309/01/1378037192_8379.jpg",
		"http://img.my.csdn.net/uploads/201309/01/1378037178_9374.jpg",
		"http://img.my.csdn.net/uploads/201309/01/1378037177_1254.jpg",
		"http://img.my.csdn.net/uploads/201309/01/1378037177_6203.jpg",
		"http://img.my.csdn.net/uploads/201309/01/1378037152_6352.jpg",
		"http://img.my.csdn.net/uploads/201309/01/1378037151_9565.jpg",
		"http://img.my.csdn.net/uploads/201309/01/1378037151_7904.jpg",
		"http://img.my.csdn.net/uploads/201309/01/1378037148_7104.jpg",
		"http://img.my.csdn.net/uploads/201309/01/1378037129_8825.jpg",
		"http://img.my.csdn.net/uploads/201309/01/1378037128_5291.jpg",
		"http://img.my.csdn.net/uploads/201309/01/1378037128_3531.jpg",
		"http://img.my.csdn.net/uploads/201309/01/1378037127_1085.jpg",
		"http://img.my.csdn.net/uploads/201309/01/1378037095_7515.jpg",
		"http://img.my.csdn.net/uploads/201309/01/1378037094_8001.jpg",
		"http://img.my.csdn.net/uploads/201309/01/1378037093_7168.jpg",
		"http://img.my.csdn.net/uploads/201309/01/1378037091_4950.jpg",
		"http://img.my.csdn.net/uploads/201308/31/1377949643_6410.jpg",
		"http://img.my.csdn.net/uploads/201308/31/1377949642_6939.jpg",
		"http://img.my.csdn.net/uploads/201308/31/1377949630_4505.jpg",
		"http://img.my.csdn.net/uploads/201308/31/1377949630_4593.jpg",
		"http://img.my.csdn.net/uploads/201308/31/1377949629_7309.jpg",
		"http://img.my.csdn.net/uploads/201308/31/1377949629_8247.jpg",
		"http://img.my.csdn.net/uploads/201308/31/1377949615_1986.jpg",
		"http://img.my.csdn.net/uploads/201308/31/1377949614_8482.jpg",
		"http://img.my.csdn.net/uploads/201308/31/1377949614_3743.jpg",
		"http://img.my.csdn.net/uploads/201308/31/1377949614_4199.jpg",
		"http://img.my.csdn.net/uploads/201308/31/1377949599_3416.jpg",
		"http://img.my.csdn.net/uploads/201308/31/1377949599_5269.jpg",
		"http://img.my.csdn.net/uploads/201308/31/1377949598_7858.jpg",
		"http://img.my.csdn.net/uploads/201308/31/1377949598_9982.jpg",
		"http://img.my.csdn.net/uploads/201308/31/1377949578_2770.jpg",
		"http://img.my.csdn.net/uploads/201308/31/1377949578_8744.jpg",
		"http://img.my.csdn.net/uploads/201308/31/1377949577_5210.jpg",
		"http://img.my.csdn.net/uploads/201308/31/1377949577_1998.jpg",
		"http://img.my.csdn.net/uploads/201308/31/1377949482_8813.jpg",
		"http://img.my.csdn.net/uploads/201308/31/1377949481_6577.jpg",
		"http://img.my.csdn.net/uploads/201308/31/1377949480_4490.jpg",
		"http://img.my.csdn.net/uploads/201308/31/1377949455_6792.jpg",
		"http://img.my.csdn.net/uploads/201308/31/1377949455_6345.jpg",
		"http://img.my.csdn.net/uploads/201308/31/1377949442_4553.jpg",
		"http://img.my.csdn.net/uploads/201308/31/1377949441_8987.jpg",
		"http://img.my.csdn.net/uploads/201308/31/1377949441_5454.jpg",
		"http://img.my.csdn.net/uploads/201308/31/1377949454_6367.jpg",
		"http://img.my.csdn.net/uploads/201308/31/1377949442_4562.jpg"
	};

}

main.xml如下:

<cc.patience7.WaterfallScrollView 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" >

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="horizontal" >

        <LinearLayout
            android:id="@+id/firstLinearLayout"
            android:layout_width="0dip"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:orientation="vertical" />

         <LinearLayout
            android:id="@+id/secondLinearLayout"
            android:layout_width="0dip"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:orientation="vertical" />

          <LinearLayout
            android:id="@+id/thirdLinearLayout"
            android:layout_width="0dip"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:orientation="vertical" />

    </LinearLayout>

</cc.patience7.WaterfallScrollView>

showimage.xml如下:

<cc.patience7.ZoomImageView
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/zoomImageView"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#000000" >

</cc.patience7.ZoomImageView>
时间: 2024-10-12 19:04:25

利用LruCache和DiskLruCache加载网络图片实现图片瀑布流效果(升级版)的相关文章

利用LruCache加载网络图片实现图片瀑布流效果(改进版)

MainActivity如下: package cc.patience4; import cc.patience4.R; import android.os.Bundle; import android.app.Activity; /** * Demo描述: * 采用瀑布流的形式加载大量网络图片 * 详细分析参见WaterfallScrollView * * 更新说明: * 该示例在基础版的基础上加入了图片查看功能. * 点击瀑布流中一张图片后可欣赏图片并能对图片进行单指拖动和两指缩放. * 对

利用LruCache加载网络图片实现图片瀑布流效果(基础版)

MainActivity如下: package cc.patience3; import android.os.Bundle; import android.app.Activity; /** * Demo描述: * 采用瀑布流的形式加载大量网络图片 * 详细分析参见WaterfallScrollView * * 参考资料: * 1 http://blog.csdn.net/guolin_blog/article/details/10470797 * 2 http://blog.csdn.net

利用LruCache为GridView加载大量本地图片完整示例

MainActivity如下: package cc.testlrucache; import android.os.Bundle; import android.widget.GridView; import android.app.Activity; public class MainActivity extends Activity { private GridView mGridView; private GridViewAdapter mGridViewAdapter; @Overri

Android利用Volley框架加载网络图片

Volley框架是在Google I/O 2013上Volley发布的,目的是使Android平台上的网络通信库,能使网络通信更快,更简单,更健壮.我们也简单了解下Volley引入的背景,在Volley出现以前,我们可能面临如下的问题,比如要在ListView或是GridView中加载数量较多的图片时:先在ListAdapter#getView()里开始图像的读取,再通过AsyncTask等机制使用HttpURLConnection从服务器去的图片资源,然后在AsyncTask#onPostEx

Android中用双缓存技术,加载网络图片

最近在学校参加一个比赛,写的一个Android应用,里面要加载大量的网络图片,可是用传统的方法图片一多就会造成程序出现内存溢出而崩溃.因为自己也在学习中,所以看了很多博客和视频,然后参照这些大神的写源码,自己写了一个加载网络图片工具类. 里面要用到一个经典的图片缓存库DiskLruCache 下载地址为:  DiskLruCache下载 下面是使用这个类实现的 双缓存网络图片加载 [java] view plain copy public class DiskLruCacheUtils { pr

Android Volley入门到精通:使用Volley加载网络图片

在上一篇文章中,我们了解了Volley到底是什么,以及它的基本用法.本篇文章中我们即将学习关于Volley更加高级的用法,如何你还没有看过我的上一篇文章的话,建议先去阅读Android Volley完全解析(一),初识Volley的基本用法. 在上篇文章中有提到过,Volley是将AsyncHttpClient和Universal-Image-Loader的优点集成于一身的一个框架.我们都知道,Universal-Image-Loader具备非常强大的加载网络图片的功能,而使用Volley,我们

Android--使用Volley加载网络图片

1. ImageRequest的用法 前面我们已经学习过了StringRequest和JsonRequest的用法,并且总结出了它们的用法都是非常类似的,基本就是进行以下三步操作即可: 1. 创建一个RequestQueue对象. 2. 创建一个Request对象. 3. 将Request对象添加到RequestQueue里面. 其中,StringRequest和JsonRequest都是继承自Request的,所以它们的用法才会如此类似.那么不用多说,今天我们要学习的ImageRequest,

Android三种基本的加载网络图片方式(转)

Android三种基本的加载网络图片方式,包括普通加载网络方式.用ImageLoader加载图片.用Volley加载图片. 1. [代码]普通加载网络方式 ? 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60

(BUG已修改,最优化)安卓ListView异步加载网络图片与缓存软引用图片,线程池,只加载当前屏之说明

原文:http://blog.csdn.net/java_jh/article/details/20068915 迟点出更新的.这个还有BUG.因为软引应不给力了.2.3之后 前几天的原文有一个线程管理与加载源过多,造成浪费流量的问题.下面对这进下改进的一些说明(红色为新加) 这两天一直在优化这个问题.google也很多种做法.但发现都是比较不全面. 比如: 一些只实现了异步加载,却没有线程池与软引用. 一些是用AsynTast的, 一些有了线程池但加载所有的图片,这样造成具大资源浪费 一些是用