让Android自带的Gallery实现多点缩放,拖动和边界回弹效果,效果流畅

http://www.havenliu.com/android/668.html

公司项目要开发一个Android看漫画的软件。看了效果,需要滑动翻页,多点缩放,拖动等。但看每个效果在android上实现都不难,但要全部组合在一起实现就比较麻烦,研究了两天,动手写了几个效果对比,最终还是选择了Gallery来做。但系统自带的Gallery组件不支持对点缩放和拖动【它默认的拖动是翻页,我需要的移动定位图片】,并且当快速滑动时,Gallery是多张连续翻页,而我只需要每次翻页一张。查了Android部分源码。觉得重新Gallery和ImageView来实现。结果还是比较理想的。性能还是不错的,无论是拖动、翻页还是对点缩放都非常流畅。把大致思路放出来共享下,抛砖引玉,如果大家有更好的改进方法,可以共同讨论。文章末尾有完整的Demo源代码,童鞋们看仔细了,就不要再给我留言或发邮件问我要源码了【PS:以前写的文章在末尾都放了源码,但估计很多人都没耐心完整的看完,就发邮件来问我要源码,搞的我屁股都洗白白了,准备上床睡觉,又打开电脑回Email】。

下面是重写的Gallery的代码。注释基本都写上了。
MyGallery.java:

/**
 * MyGallery.java
 * @version 1.0
 * @author Haven http://www.havenliu.com
 * @createTime 2011-12-9 下午03:42:53
 * android.widget.Gallery的子函数。此类很重要。建议仔细看
 */
package com.havenliu.demo;

import android.content.Context;
import android.graphics.Matrix;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.util.Log;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.View;
import android.view.GestureDetector.SimpleOnGestureListener;
import android.widget.Gallery;

public class MyGallery extends Gallery {
	private GestureDetector gestureScanner;
	private MyImageView imageView;

	public MyGallery(Context context) {
		super(context);

	}

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

	public MyGallery(Context context, AttributeSet attrs) {
		super(context, attrs);

		gestureScanner = new GestureDetector(new MySimpleGesture());
		this.setOnTouchListener(new OnTouchListener() {

			float baseValue;
			float originalScale;

			@Override
			public boolean onTouch(View v, MotionEvent event) {
				View view = MyGallery.this.getSelectedView();
				if (view instanceof MyImageView) {
					imageView = (MyImageView) view;

					if (event.getAction() == MotionEvent.ACTION_DOWN) {
						baseValue = 0;
						originalScale = imageView.getScale();
					}
					if (event.getAction() == MotionEvent.ACTION_MOVE) {
						if (event.getPointerCount() == 2) {
							float x = event.getX(0) - event.getX(1);
							float y = event.getY(0) - event.getY(1);
							float value = (float) Math.sqrt(x * x + y * y);// 计算两点的距离
							// System.out.println("value:" + value);
							if (baseValue == 0) {
								baseValue = value;
							} else {
								float scale = value / baseValue;// 当前两点间的距离除以手指落下时两点间的距离就是需要缩放的比例。
								// scale the image
								imageView.zoomTo(originalScale * scale, x + event.getX(1), y + event.getY(1));

							}
						}
					}
				}
				return false;
			}

		});
	}
	@Override
	public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
		View view = MyGallery.this.getSelectedView();
		if (view instanceof MyImageView) {
			imageView = (MyImageView) view;

			float v[] = new float[9];
			Matrix m = imageView.getImageMatrix();
			m.getValues(v);
			// 图片实时的上下左右坐标
			float left, right;
			// 图片的实时宽,高
			float width, height;
			width = imageView.getScale() * imageView.getImageWidth();
			height = imageView.getScale() * imageView.getImageHeight();
			// 一下逻辑为移动图片和滑动gallery换屏的逻辑。如果没对整个框架了解的非常清晰,改动以下的代码前请三思!!!!!!
			if ((int) width <= Main.screenWidth && (int) height <= Main.screenHeight)// 如果图片当前大小<屏幕大小,直接处理滑屏事件 			{ 				super.onScroll(e1, e2, distanceX, distanceY); 			} else { 				left = v[Matrix.MTRANS_X]; 				right = left + width; 				Rect r = new Rect(); 				imageView.getGlobalVisibleRect(r); 				if (distanceX > 0)// 向左滑动
				{
					if (r.left > 0) {// 判断当前ImageView是否显示完全
						super.onScroll(e1, e2, distanceX, distanceY);
					} else if (right < Main.screenWidth) {
						super.onScroll(e1, e2, distanceX, distanceY);
					} else {
						imageView.postTranslate(-distanceX, -distanceY);
					}
				} else if (distanceX < 0)// 向右滑动
				{
					if (r.right < Main.screenWidth) { 						super.onScroll(e1, e2, distanceX, distanceY); 					} else if (left > 0) {
						super.onScroll(e1, e2, distanceX, distanceY);
					} else {
						imageView.postTranslate(-distanceX, -distanceY);
					}
				}

			}

		} else {
			super.onScroll(e1, e2, distanceX, distanceY);
		}
		return false;
	}
	@Override
	public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
		return false;
	}

	@Override
	public boolean onTouchEvent(MotionEvent event) {
		gestureScanner.onTouchEvent(event);
		switch (event.getAction()) {
		case MotionEvent.ACTION_UP:
			// 判断上下边界是否越界
			View view = MyGallery.this.getSelectedView();
			if (view instanceof MyImageView) {
				imageView = (MyImageView) view;
				float width = imageView.getScale() * imageView.getImageWidth();
				float height = imageView.getScale() * imageView.getImageHeight();
				if ((int) width <= Main.screenWidth && (int) height <= Main.screenHeight)// 如果图片当前大小<屏幕大小,判断边界 				{ 					break; 				} 				float v[] = new float[9]; 				Matrix m = imageView.getImageMatrix(); 				m.getValues(v); 				float top = v[Matrix.MTRANS_Y]; 				float bottom = top + height; 				if (top > 0) {
					imageView.postTranslateDur(-top, 200f);
				}
				Log.i("manga", "bottom:" + bottom);
				if (bottom < Main.screenHeight) { 					imageView.postTranslateDur(Main.screenHeight - bottom, 200f); 				} 			} 			break; 		} 		return super.onTouchEvent(event); 	} 	private class MySimpleGesture extends SimpleOnGestureListener { 		// 按两下的第二下Touch down时触发 		public boolean onDoubleTap(MotionEvent e) { 			View view = MyGallery.this.getSelectedView(); 			if (view instanceof MyImageView) { 				imageView = (MyImageView) view; 				if (imageView.getScale() > imageView.getScaleRate()) {
					imageView.zoomTo(imageView.getScaleRate(), Main.screenWidth / 2, Main.screenHeight / 2, 200f);
					// imageView.layoutToCenter();
				} else {
					imageView.zoomTo(1.0f, Main.screenWidth / 2, Main.screenHeight / 2, 200f);
				}

			} else {

			}
			// return super.onDoubleTap(e);
			return true;
		}
	}
}

MyImageView.java:

/**
 * MyImageView.java
 * @version 1.0
 * @author Haven  http://www.havenliu.com
 * @createTime 2011-12-9 下午03:12:30
 * 此类代码是根据android系统自带的ImageViewTouchBase代码修改
 */
package com.havenliu.demo;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Matrix;
import android.graphics.RectF;
import android.os.Handler;
import android.util.AttributeSet;
import android.view.KeyEvent;
import android.widget.ImageView;

public class MyImageView extends ImageView {
	@SuppressWarnings("unused")
	private static final String TAG = "ImageViewTouchBase";

	// This is the base transformation which is used to show the image
	// initially. The current computation for this shows the image in
	// it's entirety, letterboxing as needed. One could choose to
	// show the image as cropped instead.
	//
	// This matrix is recomputed when we go from the thumbnail image to
	// the full size image.
	protected Matrix mBaseMatrix = new Matrix();

	// This is the supplementary transformation which reflects what
	// the user has done in terms of zooming and panning.
	//
	// This matrix remains the same when we go from the thumbnail image
	// to the full size image.
	protected Matrix mSuppMatrix = new Matrix();

	// This is the final matrix which is computed as the concatentation
	// of the base matrix and the supplementary matrix.
	private final Matrix mDisplayMatrix = new Matrix();

	// Temporary buffer used for getting the values out of a matrix.
	private final float[] mMatrixValues = new float[9];

	// The current bitmap being displayed.
	// protected final RotateBitmap mBitmapDisplayed = new RotateBitmap(null);
	protected Bitmap image = null;

	int mThisWidth = -1, mThisHeight = -1;

	float mMaxZoom = 2.0f;// 最大缩放比例
	float mMinZoom ;// 最小缩放比例

	private int imageWidth;// 图片的原始宽度
	private int imageHeight;// 图片的原始高度

	private float scaleRate;// 图片适应屏幕的缩放比例

	public MyImageView(Context context, int imageWidth, int imageHeight) {
		super(context);
		this.imageHeight = imageHeight;
		this.imageWidth = imageWidth;
		init();
	}

	public MyImageView(Context context, AttributeSet attrs, int imageWidth, int imageHeight) {
		super(context, attrs);
		this.imageHeight = imageHeight;
		this.imageWidth = imageWidth;
		init();
	}

	/**
	 * 计算图片要适应屏幕需要缩放的比例
	 */
	private void arithScaleRate() {
		float scaleWidth = Main.screenWidth / (float) imageWidth;
		float scaleHeight = Main.screenHeight / (float) imageHeight;
		scaleRate = Math.min(scaleWidth, scaleHeight);
	}

	public float getScaleRate() {
		return scaleRate;
	}

	public int getImageWidth() {
		return imageWidth;
	}

	public void setImageWidth(int imageWidth) {
		this.imageWidth = imageWidth;
	}

	public int getImageHeight() {
		return imageHeight;
	}

	public void setImageHeight(int imageHeight) {
		this.imageHeight = imageHeight;
	}

	@Override
	public boolean onKeyDown(int keyCode, KeyEvent event) {
		if (keyCode == KeyEvent.KEYCODE_BACK && event.getRepeatCount() == 0) {
			event.startTracking();
			return true;
		}
		return super.onKeyDown(keyCode, event);
	}

	@Override
	public boolean onKeyUp(int keyCode, KeyEvent event) {
		if (keyCode == KeyEvent.KEYCODE_BACK && event.isTracking() && !event.isCanceled()) {
			if (getScale() > 1.0f) {
				// If we're zoomed in, pressing Back jumps out to show the
				// entire image, otherwise Back returns the user to the gallery.
				zoomTo(1.0f);
				return true;
			}
		}
		return super.onKeyUp(keyCode, event);
	}

	protected Handler mHandler = new Handler();

	@Override
	public void setImageBitmap(Bitmap bitmap) {
		super.setImageBitmap(bitmap);
		image = bitmap;
		// 计算适应屏幕的比例
		arithScaleRate();
		//缩放到屏幕大小
		zoomTo(scaleRate,Main.screenWidth / 2f, Main.screenHeight / 2f);

		//居中
		layoutToCenter();

//		imageView.zoomTo(scaleRate, Main.screenWidth / 2, Main.screenHeight / 2
//		center(true, true);
	}

	// Center as much as possible in one or both axis. Centering is
	// defined as follows: if the image is scaled down below the
	// view's dimensions then center it (literally). If the image
	// is scaled larger than the view and is translated out of view
	// then translate it back into view (i.e. eliminate black bars).
	protected void center(boolean horizontal, boolean vertical) {
		// if (mBitmapDisplayed.getBitmap() == null) {
		// return;
		// }
		if (image == null) {
			return;
		}

		Matrix m = getImageViewMatrix();

		RectF rect = new RectF(0, 0, image.getWidth(), image.getHeight());
//		RectF rect = new RectF(0, 0, imageWidth*getScale(), imageHeight*getScale());

		m.mapRect(rect);

		float height = rect.height();
		float width = rect.width();

		float deltaX = 0, deltaY = 0;

		if (vertical) {
			int viewHeight = getHeight();
			if (height < viewHeight) { 				deltaY = (viewHeight - height) / 2 - rect.top; 			} else if (rect.top > 0) {
				deltaY = -rect.top;
			} else if (rect.bottom < viewHeight) {
				deltaY = getHeight() - rect.bottom;
			}
		}

		if (horizontal) {
			int viewWidth = getWidth();
			if (width < viewWidth) { 				deltaX = (viewWidth - width) / 2 - rect.left; 			} else if (rect.left > 0) {
				deltaX = -rect.left;
			} else if (rect.right < viewWidth) { 				deltaX = viewWidth - rect.right; 			} 		} 		postTranslate(deltaX, deltaY); 		setImageMatrix(getImageViewMatrix()); 	} 	private void init() { 		setScaleType(ImageView.ScaleType.MATRIX); 	} 	/** 	 * 设置图片居中显示 	 */ 	public void layoutToCenter() 	{ 		//正在显示的图片实际宽高 		float width = imageWidth*getScale(); 		float height = imageHeight*getScale(); 		//空白区域宽高 		float fill_width = Main.screenWidth - width; 		float fill_height = Main.screenHeight - height; 		//需要移动的距离 		float tran_width = 0f; 		float tran_height = 0f; 		if(fill_width>0)
			tran_width = fill_width/2;
		if(fill_height>0)
			tran_height = fill_height/2;

		postTranslate(tran_width, tran_height);
		setImageMatrix(getImageViewMatrix());
	}

	protected float getValue(Matrix matrix, int whichValue) {
		matrix.getValues(mMatrixValues);
		mMinZoom =( Main.screenWidth/2f)/imageWidth;

		return mMatrixValues[whichValue];
	}

	// Get the scale factor out of the matrix.
	protected float getScale(Matrix matrix) {
		return getValue(matrix, Matrix.MSCALE_X);
	}

	protected float getScale() {
		return getScale(mSuppMatrix);
	}

	// Combine the base matrix and the supp matrix to make the final matrix.
	protected Matrix getImageViewMatrix() {
		// The final matrix is computed as the concatentation of the base matrix
		// and the supplementary matrix.
		mDisplayMatrix.set(mBaseMatrix);
		mDisplayMatrix.postConcat(mSuppMatrix);
		return mDisplayMatrix;
	}

	static final float SCALE_RATE = 1.25F;

	// Sets the maximum zoom, which is a scale relative to the base matrix. It
	// is calculated to show the image at 400% zoom regardless of screen or
	// image orientation. If in the future we decode the full 3 megapixel image,
	// rather than the current 1024x768, this should be changed down to 200%.
	protected float maxZoom() {
		if (image == null) {
			return 1F;
		}

		float fw = (float) image.getWidth() / (float) mThisWidth;
		float fh = (float) image.getHeight() / (float) mThisHeight;
		float max = Math.max(fw, fh) * 4;
		return max;
	}

	protected void zoomTo(float scale, float centerX, float centerY) {
		if (scale > mMaxZoom) {
			scale = mMaxZoom;
		} else if (scale < mMinZoom) {
			scale = mMinZoom;
		}

		float oldScale = getScale();
		float deltaScale = scale / oldScale;

		mSuppMatrix.postScale(deltaScale, deltaScale, centerX, centerY);
		setImageMatrix(getImageViewMatrix());
		center(true, true);
	}

	protected void zoomTo(final float scale, final float centerX, final float centerY, final float durationMs) {
		final float incrementPerMs = (scale - getScale()) / durationMs;
		final float oldScale = getScale();
		final long startTime = System.currentTimeMillis();

		mHandler.post(new Runnable() {
			public void run() {
				long now = System.currentTimeMillis();
				float currentMs = Math.min(durationMs, now - startTime);
				float target = oldScale + (incrementPerMs * currentMs);
				zoomTo(target, centerX, centerY);
				if (currentMs < durationMs) { 					mHandler.post(this); 				} 			} 		}); 	} 	protected void zoomTo(float scale) { 		float cx = getWidth() / 2F; 		float cy = getHeight() / 2F; 		zoomTo(scale, cx, cy); 	} 	protected void zoomToPoint(float scale, float pointX, float pointY) { 		float cx = getWidth() / 2F; 		float cy = getHeight() / 2F; 		panBy(cx - pointX, cy - pointY); 		zoomTo(scale, cx, cy); 	} 	protected void zoomIn() { 		zoomIn(SCALE_RATE); 	} 	protected void zoomOut() { 		zoomOut(SCALE_RATE); 	} 	protected void zoomIn(float rate) { 		if (getScale() >= mMaxZoom) {
			return; // Don't let the user zoom into the molecular level.
		} else if (getScale() <= mMinZoom) {
			return;
		}
		if (image == null) {
			return;
		}

		float cx = getWidth() / 2F;
		float cy = getHeight() / 2F;

		mSuppMatrix.postScale(rate, rate, cx, cy);
		setImageMatrix(getImageViewMatrix());
	}

	protected void zoomOut(float rate) {
		if (image == null) {
			return;
		}

		float cx = getWidth() / 2F;
		float cy = getHeight() / 2F;

		// Zoom out to at most 1x.
		Matrix tmp = new Matrix(mSuppMatrix);
		tmp.postScale(1F / rate, 1F / rate, cx, cy);

		if (getScale(tmp) < 1F) {
			mSuppMatrix.setScale(1F, 1F, cx, cy);
		} else {
			mSuppMatrix.postScale(1F / rate, 1F / rate, cx, cy);
		}
		setImageMatrix(getImageViewMatrix());
		center(true, true);
	}

	public void postTranslate(float dx, float dy) {
		mSuppMatrix.postTranslate(dx, dy);
		setImageMatrix(getImageViewMatrix());
	}
	float _dy=0.0f;
	protected void postTranslateDur( final float dy, final float durationMs) {
		_dy=0.0f;
		final float incrementPerMs = dy / durationMs;
		final long startTime = System.currentTimeMillis();
		mHandler.post(new Runnable() {
			public void run() {
				long now = System.currentTimeMillis();
				float currentMs = Math.min(durationMs, now - startTime);

				postTranslate(0, incrementPerMs*currentMs-_dy);
				_dy=incrementPerMs*currentMs;

				if (currentMs < durationMs) {
					mHandler.post(this);
				}
			}
		});
	}
	protected void panBy(float dx, float dy) {
		postTranslate(dx, dy);
		setImageMatrix(getImageViewMatrix());
	}
}

源码:狂击这里

时间: 2024-12-02 12:15:59

让Android自带的Gallery实现多点缩放,拖动和边界回弹效果,效果流畅的相关文章

Android的GridView和Gallery结合Demo

Demo介绍:首页是一个GridView加载图片,竖屏时显示3列图片,横屏时显示4列图片;并且对图片进行大小限制和加灰色边框处理. 点击某一张图片,会链接到Gallery页面,由于Android自带的Gallery控件滑动效果很不好(滑动一次会加载好多张图片),这里对Gallery进行了扩展,滑动一次只加载一张图片. Demo效果如下:   1.首页Activity页面,GridViewActivity.java介绍: public class GridViewActivity extends

Android开发学习笔记 Gallery和GridView浅析_Android

一.Gallery的简介 Gallery(画廊)是一个锁定中心条目并且拥有水平滚动列表的视图,一般用来浏览图片,并且可以响应事件显示信息.Gallery还可以和ImageSwitcher组件结合使用来实现一个通过缩略图来浏览图片的效果. Gallery常用的XML属性 属性名称 描述 android:animationDuration 设置布局变化时动画的转换所需的时间(毫秒级).仅在动画开始时计时.该值必须是整数,比如:100. android:gravity 指定在对象的X和Y轴上如何放置内

Android开发学习笔记 Gallery和GridView浅析

一.Gallery的简介 Gallery(画廊)是一个锁定中心条目并且拥有水平滚动列表的视图,一般用来浏览图片,并且可以响应事件显示信息.Gallery还可以和ImageSwitcher组件结合使用来实现一个通过缩略图来浏览图片的效果. Gallery常用的XML属性 属性名称 描述 android:animationDuration 设置布局变化时动画的转换所需的时间(毫秒级).仅在动画开始时计时.该值必须是整数,比如:100. android:gravity 指定在对象的X和Y轴上如何放置内

api-开发是使用位置服务(android自带API),无法上传实时坐标,只能上传第一个点的坐标?

问题描述 开发是使用位置服务(android自带API),无法上传实时坐标,只能上传第一个点的坐标? 开发是使用位置服务(android自带API),无法上传实时坐标,只能上传第一个点的坐标?有大神知道这是为什么吗?急急急!!! 解决方案 http://blog.csdn.net/qiannuo/article/details/6791873

android自带相机删除预览照片后怎么回到下一张预览而不是回到相机

问题描述 android自带相机删除预览照片后怎么回到下一张预览而不是回到相机 打开照相机----随便照几张照片----照完后照相按钮上方会有刚才照的照片的预览(一个小方框)----点击小方框预览照片----随便删除一张----此后将回到照相界面--怎么改代码让它回到下一张的预览照片? 解决方案 你打开图片是跳转到另一个activity了吗?如果是的话,把 activity的启动方式 可以改为 singleTop,不然你每次打开一张图片就会新建一个activity,这样浪费内存,且会造成你说的后

调用Android自带日历功能(日历列表单、添加一个日历事件)

调用Android自带日历功能  觉得这篇文章不错,转载过来. 转载:http://blog.csdn.net/djy1992/article/details/9948393 Android手机配备有一个内置的日历应用程序.第三方应用程序可以利用日历内容提供商接口读取用户的日历信息和安排在日历新的事件.这个日历可以直接同步用户的谷歌日历.  不幸的是,没有文档和Android手机的日历应用集成,因为有另外一个联系人应用程序.相反,本文所提供的所有信息,将会通过逆向工程的谷歌日历内容提供商.该接口

android 自带webview 加载html5游戏

问题描述 android 自带webview 加载html5游戏 android 自带webview加载html5游戏不流畅,但在微信内置浏览或是UC等浏览器中玩的就很流畅,该怎么样优化webview能像在微信中玩h5游戏一样流畅? 解决方案 自己写webview就算了,看看有没有开源的吧,应该有很多

Android自带的webview中加载支付宝成功后没有回调,只停留在了支付宝支付成功页面

问题描述 Android自带的webview中加载支付宝成功后没有回调,只停留在了支付宝支付成功页面 我用Android自带的webview中加载支付宝页面去支付,在js里也配置了回调的return_url,为啥收不着回调呢?只是返回支付宝支付成功的链接,然后就停在支付宝支付完成的页面了. 程序里也设置了这个setJavaScriptEnabled(true); public boolean shouldOverrideUrlLoading(WebView view, String url),在

android中文api(80)——Gallery.LayoutParams

前言 本章内容是 android.widget.Gallery.LayoutParams,版本为Android 2.3 r1,翻译来自"我是谁",欢迎大家访问他的博客:http://blog.sina.com.cn/u/1744311365,再次感谢"我是谁" !期待你加入Android 中文API的翻译,联系我over140@gmail.com.   声明 欢迎转载,但请保留文章原始出处:)  博客园:http://www.cnblogs.com/ Android