zoukankan      html  css  js  c++  java
  • 利用LruCache载入网络图片实现图片瀑布流效果(改进版)

    PS:
    2015年1月20日21:37:27
    关于LoadImageAsyncTask和checkAllImageViewVisibility可能有点小bug
    改动后的代码请參见升级版本号的代码
    http://blog.csdn.net/lfdfhl/article/details/42925193


    MainActivity例如以下:

    package cc.patience4;
    
    import cc.patience4.R;
    import android.os.Bundle;
    import android.app.Activity;
    /**
     * Demo描写叙述:
     * 採用瀑布流的形式载入大量网络图片
     * 具体分析參见WaterfallScrollView
     * 
     * 更新说明:
     * 该演示样例在基础版的基础上增加了图片查看功能.
     * 点击瀑布流中一张图片后可赞赏图片并能对图片进行单指拖动和两指缩放.
     * 对于图片的操作主要用到了自己定义的View,具体代码和凝视请參见
     * ZoomImageView.
     * 
     * 
     * 參考资料:
     * 1 http://blog.csdn.net/guolin_blog/article/details/10470797
     * 2 http://blog.csdn.net/lfdfhl/article/details/18350601
     *   Thank you very much
     *   
     */
    public class MainActivity extends Activity {
    
    	@Override
    	protected void onCreate(Bundle savedInstanceState) {
    		super.onCreate(savedInstanceState);
    		setContentView(R.layout.main);
    	}
    
    }


    效果图例如以下:


    WaterfallScrollView例如以下:

    package cc.patience4;
    
    import java.io.File;
    import java.util.ArrayList;
    import java.util.HashSet;
    import cc.patience4.R;
    import android.content.Context;
    import android.content.Intent;
    import android.graphics.Bitmap;
    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.
     * 
     * 
     * 错误总结:
     * 在使用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();
    		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 isAsyncTashHashSetEmpty=mLoadImageAsyncTaskHashSet.isEmpty();
    						if (waterfallScrollViewHeight+scrollY>=scrollViewMeasuredHeight&&isAsyncTashHashSetEmpty) {
    							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 (start<ImagesUrl.urlStringArray.length) {
    				if (end>ImagesUrl.urlStringArray.length) {
    					end=ImagesUrl.urlStringArray.length;
    				}
    				Toast.makeText(mContext, "開始载入", Toast.LENGTH_SHORT).show();
    				for (int i = start;i < end; 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中,则开启异步任务下载
    	 * 若不可见:
    	 * 将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();
    					loadImageAsyncTask.execute(imageUrl);
    				} else {
    					imageView.setImageBitmap(bitmap);
    				}
    				
    			} else {
    				imageView.setImageResource(R.drawable.empty_photo);
    			}
    		}
    	}
    	
    	/**
    	 * 该LoadImageAsyncTask是获取网络图片的入口:
    	 * 1 从LruCache中获取,取到则停止
    	 * 2 若不在LruCache,则从SD卡获取
    	 * 3 若在则从SD卡获取
    	 * 4 若不在SD卡,则从网络获取且保持至SD卡
    	 * 5 从SD卡获取下载的图片
    	 * 6 加入到LruCache中
    	 * 
    	 * 注意无论这个图片是在SD卡还是从网络下载,这都是获取图片的入口,这么做的优点
    	 * 1   统一了获取图片的入口.
    	 *   假设把获取图片分为图片在LruCache,图片在SD卡,图片在网络上这几种不同
    	 *   的情况而去分别用相应的函数获取,这样势必会导致该需求的多入口.凌乱,不好优化.
    	 *   而且这几种方式放到AsyncTask中都不会出错,尤其是网络请求耗时的情况下.
    	 * 2 无论通过哪种方式获取到了图片,我们都要对图片再次修整,比方缩放.
    	 *   我们能够把这些操作又统一放到异步操作的onPostExecute()方法中.
    	 */
    	private class LoadImageAsyncTask extends AsyncTask<String, Void, Bitmap>{
    		private String imageUrl;
    		private Bitmap bitmap;
    		@Override
    		protected Bitmap doInBackground(String... params) {
    			imageUrl=params[0];
    			bitmap=mLruCacheImageLoader.getBitmapFromLruCache(imageUrl);
    			if (bitmap==null) {
    				String filePath=Utils.getImageFilePath(imageUrl);
    				File imageFile=new File(filePath);
    				if (!imageFile.exists()) {
    					Utils.getBitmapFromNetWorkAndSaveToSDCard(imageUrl, filePath);
    				}
    				if (filePath!=null) {
    					bitmap=Utils.getBitmapFromSDCard(filePath, everyColumnWidth);
    					if (bitmap!=null) {
    						mLruCacheImageLoader.addBitmapToLruCache(imageUrl, bitmap);
    					}
    				}
    			} else {
    
    			}
    			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,imageViewWidth,imageViewHeight,imageUrl);
    			}
    		}
    	}
    	
    	/**
    	 * 将获取到的Bitmap加入到ImageView中.
    	 * 这里利用View.setTag()的方式为该ImageView保存了其相关信息.
    	 * 比方该ImageView载入的图片的url,它的上下边在ScrollView中的位置信息等.
    	 */
    	private void addImageToScrollView(Bitmap bitmap,int imageViewWidth,int imageViewHeight,final String imageUrl){
    		ImageView imageView=new ImageView(mContext);
    		LinearLayout.LayoutParams layoutParams=new LinearLayout.LayoutParams(imageViewWidth, imageViewHeight);
    		imageView.setImageBitmap(bitmap);
    		imageView.setLayoutParams(layoutParams);
    		imageView.setScaleType(ScaleType.FIT_XY);
    		imageView.setPadding(5, 5, 5, 5);
    		imageView.setTag(R.string.IMAGE_URL_TAG, imageUrl);
    		imageView.setOnClickListener(new OnClickListener() {
    			@Override
    			public void onClick(View view) {
    				Intent intent=new Intent(mContext, ShowImageActivity.class);
    				intent.putExtra("imageUrl", imageUrl);
    				mContext.startActivity(intent);
    			}
    		});
    		addImageToColumn(imageView);
    		mAllImageViewArrayList.add(imageView);
    	}
    	
    	
    	/**
    	 * 找到高度最小的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);
    			}
    		}
    		
    	}
    }
    

    ZoomImageView例如以下:
    package cc.patience4;
    
    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);
    	}
    
    }
    

    LruCacheImageLoader例如以下:
    package cc.patience4;
    
    import android.graphics.Bitmap;
    import android.util.LruCache;
    
    public class LruCacheImageLoader {
    	
    	private static LruCacheImageLoader mLruCacheImageLoader;
    	
    	private static LruCache<String, Bitmap> mLruCache;
    	
    	private LruCacheImageLoader(){
    		int maxMemory=(int) Runtime.getRuntime().maxMemory();
    		int size=maxMemory/6;
    		//设定LruCache的缓存为可用内存的六分之中的一个
    		mLruCache=new LruCache<String, Bitmap>(size){
    			@Override
    			protected int sizeOf(String key, Bitmap bitmap) {
    				return bitmap.getByteCount();
    			}
    			
    		};
    	}
    	
    	public static LruCacheImageLoader getLruCacheImageLoaderInstance(){
    		if (mLruCacheImageLoader==null) {
    			mLruCacheImageLoader=new LruCacheImageLoader();
    		}
    		return mLruCacheImageLoader;
    	}
    	
    	/**
    	 * 从LruCache中获取图片,若不存在返回null
    	 */
    	public static Bitmap getBitmapFromLruCache(String key){
    		return mLruCache.get(key);
    	}
    	
    	/**
    	 * 往LruCache中加入图片.
    	 * 当然要首先推断LruCache中是否已经存在该图片,若不存在再加入
    	 */
    	public static void addBitmapToLruCache(String key,Bitmap bitmap){
    		if (getBitmapFromLruCache(key)==null) {
    			mLruCache.put(key, bitmap);
    		}
    	}
    
    }
    

    ShowImageActivity例如以下:
    package cc.patience4;
    
    import android.app.Activity;
    import android.graphics.Bitmap;
    import android.graphics.BitmapFactory;
    import android.os.Bundle;
    import android.view.Window;
    
    public class ShowImageActivity extends Activity {
    	private Bitmap mBitmap;
    	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(){
    		String imageUrl=getIntent().getStringExtra("imageUrl");
    		String filePath=Utils.getImageFilePath(imageUrl);
    		mZoomImageView=(ZoomImageView) findViewById(R.id.zoomImageView);
    		mBitmap=BitmapFactory.decodeFile(filePath);
    		mZoomImageView.setBitmap(mBitmap);
    	}
    	
    	
    	/**
    	 * 回收Bitmap避免内存溢出
    	 */
    	@Override
    	protected void onDestroy() {
    		super.onDestroy();
    		if (mBitmap!=null) {
    			mBitmap.recycle();
    		}
    	}
    
    }
    

    Utils例如以下:

    package cc.patience4;
    
    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.net.HttpURLConnection;
    import java.net.URL;
    import org.apache.http.HttpStatus;
    import android.graphics.Bitmap;
    import android.graphics.BitmapFactory;
    import android.graphics.BitmapFactory.Options;
    import android.os.Environment;
    
    public class Utils {
    	public final static String IMAGES_DIR_NAME="waterfallImages";
    	public final static String IMAGES_DIR_PATH=Environment.getExternalStorageDirectory()+File.separator+IMAGES_DIR_NAME;
    
    	/**
    	 * 推断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;
    	}
    	
    	/**
    	 * 根据图片的Url获取其在SDCard的存储路径
    	 */
    	public static String getImageFilePath(String imageUrl){
    		File dir=new File(IMAGES_DIR_PATH);
    		if (!dir.exists()) {
    			dir.mkdirs();
    		}
    		String imageFilePath=null;
    		String imageName=null;
    		int start=imageUrl.lastIndexOf("/");
    		int end=imageUrl.lastIndexOf(".");
    		imageName=imageUrl.substring(start+1, end);
    		imageFilePath=IMAGES_DIR_PATH+File.separator+imageName;
    		return imageFilePath;
    	}
    	
    	
    	/**
    	 * 从网络获取图片且保存至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());
    			}
    		}
    		
    	}
    
    
    }
    



    ImagesUrl例如以下:
    package cc.patience4;
    
    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.patience4.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.patience4.WaterfallScrollView>

    showimage.xml例如以下:
    <cc.patience4.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.patience4.ZoomImageView>


  • 相关阅读:
    B.Icebound and Sequence
    Educational Codeforces Round 65 (Rated for Div. 2) D. Bicolored RBS
    Educational Codeforces Round 65 (Rated for Div. 2) C. News Distribution
    Educational Codeforces Round 65 (Rated for Div. 2) B. Lost Numbers
    Educational Codeforces Round 65 (Rated for Div. 2) A. Telephone Number
    Codeforces Round #561 (Div. 2) C. A Tale of Two Lands
    Codeforces Round #561 (Div. 2) B. All the Vowels Please
    Codeforces Round #561 (Div. 2) A. Silent Classroom
    HDU-2119-Matrix(最大匹配)
    读书的感想!
  • 原文地址:https://www.cnblogs.com/jzssuanfa/p/7130214.html
Copyright © 2011-2022 走看看