Android圖片查看支持雙擊放大縮小、多點觸摸

        前一段時間寫的,現在分享出來供大家參考。

        研究過圖庫的源碼,但着實太複雜,我都看不懂!也參考過網上的一些源碼,但很多功能都不全,都不是我想要的! 結合網上的代碼以及圖庫的部分源碼自己寫了一個類。

        未實現的功能——逐級放大,這個矩陣變換把我弄暈了,而且項目中也用不到,所以就沒再研究。

        該模塊主要實現了放大和原大兩個級別的縮放。

 功能有:

  1. 以觸摸點爲中心放大(這個是網上其他的代碼沒有的)
  2. 邊界控制(這個是網上其他的代碼沒有的)
  3. 雙擊放大或縮小(主要考慮到電阻屏)
  4. 多點觸摸放大和縮小

        這個模塊已經通過了測試,並且用戶也使用有一段時間了,是屬於比較穩定的了。

下面貼上代碼及使用方法(沒有寫測試項目,大家見諒):

 ImageControl.cs   類似一個用戶自定義的ImageView控件。用法將在下面的代碼中貼出。

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Matrix;
import android.util.AttributeSet;
import android.util.FloatMath;
import android.view.MotionEvent;
import android.widget.ImageView;

public class ImageControl extends ImageView {
	public ImageControl(Context context) {
		super(context);
		// TODO Auto-generated constructor stub
	}

	public ImageControl(Context context, AttributeSet attrs) {
		super(context, attrs);
		// TODO Auto-generated constructor stub
	}

	public ImageControl(Context context, AttributeSet attrs, int defStyle) {
		super(context, attrs, defStyle);
		// TODO Auto-generated constructor stub
	}

	// ImageView img;
	Matrix imgMatrix = null; // 定義圖片的變換矩陣

	static final int DOUBLE_CLICK_TIME_SPACE = 300; // 雙擊時間間隔
	static final int DOUBLE_POINT_DISTANCE = 10; // 兩點放大兩點間最小間距
	static final int NONE = 0;
	static final int DRAG = 1; // 拖動操作
	static final int ZOOM = 2; // 放大縮小操作
	private int mode = NONE; // 當前模式

	float bigScale = 3f; // 默認放大倍數
	Boolean isBig = false; // 是否是放大狀態
	long lastClickTime = 0; // 單擊時間
	float startDistance; // 多點觸摸兩點距離
	float endDistance; // 多點觸摸兩點距離

	float topHeight; // 狀態欄高度和標題欄高度
	Bitmap primaryBitmap = null;

	float contentW; // 屏幕內容區寬度
	float contentH; // 屏幕內容區高度

	float primaryW; // 原圖寬度
	float primaryH; // 原圖高度

	float scale; // 適合屏幕縮放倍數
	Boolean isMoveX = true; // 是否允許在X軸拖動
	Boolean isMoveY = true; // 是否允許在Y軸拖動
	float startX;
	float startY;
	float endX;
	float endY;
	float subX;
	float subY;
	float limitX1;
	float limitX2;
	float limitY1;
	float limitY2;
	ICustomMethod mCustomMethod = null;

	/**
	 * 初始化圖片
	 * 
	 * @param bitmap
	 *            要顯示的圖片
	 * @param contentW
	 *            內容區域寬度
	 * @param contentH
	 *            內容區域高度
	 * @param topHeight
	 *            狀態欄高度和標題欄高度之和
	 */
	public void imageInit(Bitmap bitmap, int contentW, int contentH,
			int topHeight, ICustomMethod iCustomMethod) {
		this.primaryBitmap = bitmap;
		this.contentW = contentW;
		this.contentH = contentH;
		this.topHeight = topHeight;
		mCustomMethod = iCustomMethod;
		primaryW = primaryBitmap.getWidth();
		primaryH = primaryBitmap.getHeight();
		float scaleX = (float) contentW / primaryW;
		float scaleY = (float) contentH / primaryH;
		scale = scaleX < scaleY ? scaleX : scaleY;
		if (scale < 1 && 1 / scale < bigScale) {
			bigScale = (float) (1 / scale + 0.5);
		}

		imgMatrix = new Matrix();
		subX = (contentW - primaryW * scale) / 2;
		subY = (contentH - primaryH * scale) / 2;
		this.setImageBitmap(primaryBitmap);
		this.setScaleType(ScaleType.MATRIX);
		imgMatrix.postScale(scale, scale);
		imgMatrix.postTranslate(subX, subY);
		this.setImageMatrix(imgMatrix);
	}

	/**
	 * 按下操作
	 * 
	 * @param event
	 */
	public void mouseDown(MotionEvent event) {
		mode = NONE;
		startX = event.getRawX();
		startY = event.getRawY();
		if (event.getPointerCount() == 1) {
			// 如果兩次點擊時間間隔小於一定值,則默認爲雙擊事件
			if (event.getEventTime() - lastClickTime < DOUBLE_CLICK_TIME_SPACE) {
				changeSize(startX, startY);
			} else if (isBig) {
				mode = DRAG;
			}
		}

		lastClickTime = event.getEventTime();
	}

	/**
	 * 非第一個點按下操作
	 * 
	 * @param event
	 */
	public void mousePointDown(MotionEvent event) {
		startDistance = getDistance(event);
		if (startDistance > DOUBLE_POINT_DISTANCE) {
			mode = ZOOM;
		} else {
			mode = NONE;
		}
	}

	/**
	 * 移動操作
	 * 
	 * @param event
	 */
	public void mouseMove(MotionEvent event) {
		if ((mode == DRAG) && (isMoveX || isMoveY)) {
			float[] XY = getTranslateXY(imgMatrix);
			float transX = 0;
			float transY = 0;
			if (isMoveX) {
				endX = event.getRawX();
				transX = endX - startX;
				if ((XY[0] + transX) <= limitX1) {
					transX = limitX1 - XY[0];
				}
				if ((XY[0] + transX) >= limitX2) {
					transX = limitX2 - XY[0];
				}
			}
			if (isMoveY) {
				endY = event.getRawY();
				transY = endY - startY;
				if ((XY[1] + transY) <= limitY1) {
					transY = limitY1 - XY[1];
				}
				if ((XY[1] + transY) >= limitY2) {
					transY = limitY2 - XY[1];
				}
			}

			imgMatrix.postTranslate(transX, transY);
			startX = endX;
			startY = endY;
			this.setImageMatrix(imgMatrix);
		} else if (mode == ZOOM && event.getPointerCount() > 1) {
			endDistance = getDistance(event);
			float dif = endDistance - startDistance;
			if (Math.abs(endDistance - startDistance) > DOUBLE_POINT_DISTANCE) {
				if (isBig) {
					if (dif < 0) {
						changeSize(0, 0);
						mode = NONE;
					}
				} else if (dif > 0) {
					float x = event.getX(0) / 2 + event.getX(1) / 2;
					float y = event.getY(0) / 2 + event.getY(1) / 2;
					changeSize(x, y);
					mode = NONE;
				}
			}
		}
	}

	/**
	 * 鼠標擡起事件
	 */
	public void mouseUp() {
		mode = NONE;
	}

	/**
	 * 圖片放大縮小
	 * 
	 * @param x
	 *            點擊點X座標
	 * @param y
	 *            點擊點Y座標
	 */
	private void changeSize(float x, float y) {
		if (isBig) {
			// 如果處於最大狀態,則還原
			imgMatrix.reset();
			imgMatrix.postScale(scale, scale);
			imgMatrix.postTranslate(subX, subY);
			isBig = false;
		} else {
			imgMatrix.postScale(bigScale, bigScale); // 在原有矩陣後乘放大倍數
			float transX = -((bigScale - 1) * x);
			float transY = -((bigScale - 1) * (y - topHeight)); // (bigScale-1)(y-statusBarHeight-subY)+2*subY;
			float currentWidth = primaryW * scale * bigScale; // 放大後圖片大小
			float currentHeight = primaryH * scale * bigScale;
			// 如果圖片放大後超出屏幕範圍處理
			if (currentHeight > contentH) {
				limitY1 = -(currentHeight - contentH); // 平移限制
				limitY2 = 0;
				isMoveY = true; // 允許在Y軸上拖動
				float currentSubY = bigScale * subY; // 當前平移距離
				// 平移後,內容區域上部有空白處理辦法
				if (-transY < currentSubY) {
					transY = -currentSubY;
				}
				// 平移後,內容區域下部有空白處理辦法
				if (currentSubY + transY < limitY1) {
					transY = -(currentHeight + currentSubY - contentH);
				}
			} else {
				// 如果圖片放大後沒有超出屏幕範圍處理,則不允許拖動
				isMoveY = false;
			}

			if (currentWidth > contentW) {
				limitX1 = -(currentWidth - contentW);
				limitX2 = 0;
				isMoveX = true;
				float currentSubX = bigScale * subX;
				if (-transX < currentSubX) {
					transX = -currentSubX;
				}
				if (currentSubX + transX < limitX1) {
					transX = -(currentWidth + currentSubX - contentW);
				}
			} else {
				isMoveX = false;
			}

			imgMatrix.postTranslate(transX, transY);
			isBig = true;
		}

		this.setImageMatrix(imgMatrix);
		if (mCustomMethod != null) {
			mCustomMethod.customMethod(isBig);
		}
	}

	/**
	 * 獲取變換矩陣中X軸偏移量和Y軸偏移量
	 * 
	 * @param matrix
	 *            變換矩陣
	 * @return
	 */
	private float[] getTranslateXY(Matrix matrix) {
		float[] values = new float[9];
		matrix.getValues(values);
		float[] floats = new float[2];
		floats[0] = values[Matrix.MTRANS_X];
		floats[1] = values[Matrix.MTRANS_Y];
		return floats;
	}

	/**
	 * 獲取兩點間的距離
	 * 
	 * @param event
	 * @return
	 */
	private float getDistance(MotionEvent event) {
		float x = event.getX(0) - event.getX(1);
		float y = event.getY(0) - event.getY(1);
		return FloatMath.sqrt(x * x + y * y);
	}

	/**
	 * @author Administrator 用戶自定義方法
	 */
	public interface ICustomMethod {
		public void customMethod(Boolean currentStatus);
	}
}

 

ImageVewActivity.cs   這個用於測試的Activity

import android.app.Activity;
import android.graphics.Bitmap;
import android.graphics.Rect;
import android.graphics.drawable.BitmapDrawable;
import android.os.Bundle;
import android.view.MotionEvent;
import android.view.View;
import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.Toast;
import ejiang.boiler.ImageControl.ICustomMethod;
import ejiang.boiler.R.id;

public class ImageViewActivity extends Activity {

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		// TODO Auto-generated method stub
		super.onCreate(savedInstanceState);
		setContentView(R.layout.common_image_view);
		findView();
	}

	public void onWindowFocusChanged(boolean hasFocus) {
		super.onWindowFocusChanged(hasFocus);
		init();
	}

	ImageControl imgControl;
	LinearLayout llTitle;
	TextView tvTitle;

	private void findView() {
		imgControl = (ImageControl) findViewById(id.common_imageview_imageControl1);
		llTitle = (LinearLayout) findViewById(id.common_imageview_llTitle);
		tvTitle = (TextView) findViewById(id.common_imageview_title);
	}

	private void init() {
		tvTitle.setText("圖片測試");
		// 這裏可以爲imgcontrol的圖片路徑動態賦值
		// ............
		
		Bitmap bmp;
		if (imgControl.getDrawingCache() != null) {
			bmp = Bitmap.createBitmap(imgControl.getDrawingCache());
		} else {
			bmp = ((BitmapDrawable) imgControl.getDrawable()).getBitmap();
		}
		Rect frame = new Rect();
		getWindow().getDecorView().getWindowVisibleDisplayFrame(frame);
		int statusBarHeight = frame.top;
		int screenW = this.getWindowManager().getDefaultDisplay().getWidth();
		int screenH = this.getWindowManager().getDefaultDisplay().getHeight()
				- statusBarHeight;
		if (bmp != null) {
			imgControl.imageInit(bmp, screenW, screenH, statusBarHeight,
					new ICustomMethod() {
                      
						@Override
						public void customMethod(Boolean currentStatus) {
							// 當圖片處於放大或縮小狀態時,控制標題是否顯示
							if (currentStatus) {
								llTitle.setVisibility(View.GONE);
							} else {
								llTitle.setVisibility(View.VISIBLE);
							}
						}
					});
		}
		else
		{
			Toast.makeText(ImageViewActivity.this, "圖片加載失敗,請稍候再試!", Toast.LENGTH_SHORT)
					.show();
		}

	}

	@Override
	public boolean onTouchEvent(MotionEvent event) {
		switch (event.getAction() & MotionEvent.ACTION_MASK) {
		case MotionEvent.ACTION_DOWN:
			imgControl.mouseDown(event);			
			break;

		/**
		 * 非第一個點按下
		 */
		case MotionEvent.ACTION_POINTER_DOWN:
		
				imgControl.mousePointDown(event);
		
			break;
		case MotionEvent.ACTION_MOVE:
				imgControl.mouseMove(event);
			
			break;

		case MotionEvent.ACTION_UP:
			imgControl.mouseUp();
			break;

		}

		return super.onTouchEvent(event);
	}
}

        在上面的代碼中,需要注意兩點。一Activity中要重寫onTouchEvent方法,將觸摸事件傳遞到ImageControl,這點類似於WPF中的路由事件機制。二初始化imgControl即imgControl.imageInit,注意其中的參數。最後一個參數類似於C#中的委託,我這裏使用接口來實現,在放大縮小的切換時要執行的操作都卸載這個方法中。


common_image_view.xml  佈局文件

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/rl"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent" >

    <ejiang.boiler.ImageControl
        android:id="@+id/common_imageview_imageControl1"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:src="@drawable/ic_launcher" />

    <LinearLayout
        android:id="@+id/common_imageview_llTitle"
        style="@style/reportTitle1"
        android:layout_alignParentLeft="true"
        android:layout_alignParentTop="true" >

        <TextView
            android:id="@+id/common_imageview_title"
            style="@style/title2"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="報告" />
    </LinearLayout>

</RelativeLayout>


        我學習android的時間也不長,因此有什麼紕漏或錯誤,歡迎大家指出!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章