MainActivity如下:
ZoomImageView如下:
ImagesUrl如下:
main.xml如下:
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; } }
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>