Android App中實現(xiàn)可以雙擊放大和縮小圖片功能的實例
先來看一個很簡單的核心圖片縮放方法:
public static Bitmap scale(Bitmap bitmap, float scaleWidth, float scaleHeight) { int width = bitmap.getWidth(); int height = bitmap.getHeight(); Matrix matrix = new Matrix(); matrix.postScale(scaleWidth, scaleHeight); Log.i(TAG, "scaleWidth:"+ scaleWidth +", scaleHeight:"+ scaleHeight); return Bitmap.createBitmap(bitmap, 0, 0, width, height, matrix, true); }
注意要比例設(shè)置正確否則可能會內(nèi)存溢出,比如曾經(jīng)使用圖片縮放時遇到這么個問題:
java.lang.IllegalArgumentException: bitmap size exceeds 32bits
后來一行行查代碼,發(fā)現(xiàn)原來是 scale 的比例計算錯誤,將原圖給放大了 20 多倍,導(dǎo)致內(nèi)存溢出所致,重新修改比例值后就正常了。
好了,下面真正來看一下這個實現(xiàn)了放大和原大兩個級別的縮放的模塊。
功能有:
- 以觸摸點為中心放大(這個是網(wǎng)上其他的代碼沒有的)
- 邊界控制(這個是網(wǎng)上其他的代碼沒有的)
- 雙擊放大或縮?。ㄖ饕紤]到電阻屏)
- 多點觸摸放大和縮小
這個模塊已經(jīng)通過了測試,并且用戶也使用有一段時間了,是屬于比較穩(wěn)定的了。
下面貼上代碼及使用方法(沒有寫測試項目,大家見諒):
ImageControl 類似一個用戶自定義的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; // 當(dāng)前模式 float bigScale = 3f; // 默認(rèn)放大倍數(shù) Boolean isBig = false; // 是否是放大狀態(tài) long lastClickTime = 0; // 單擊時間 float startDistance; // 多點觸摸兩點距離 float endDistance; // 多點觸摸兩點距離 float topHeight; // 狀態(tài)欄高度和標(biāo)題欄高度 Bitmap primaryBitmap = null; float contentW; // 屏幕內(nèi)容區(qū)寬度 float contentH; // 屏幕內(nèi)容區(qū)高度 float primaryW; // 原圖寬度 float primaryH; // 原圖高度 float scale; // 適合屏幕縮放倍數(shù) 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 * 內(nèi)容區(qū)域?qū)挾? * @param contentH * 內(nèi)容區(qū)域高度 * @param topHeight * 狀態(tài)欄高度和標(biāo)題欄高度之和 */ 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) { // 如果兩次點擊時間間隔小于一定值,則默認(rèn)為雙擊事件 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; } } } } /** * 鼠標(biāo)抬起事件 */ public void mouseUp() { mode = NONE; } /** * 圖片放大縮小 * * @param x * 點擊點X坐標(biāo) * @param y * 點擊點Y坐標(biāo) */ private void changeSize(float x, float y) { if (isBig) { // 如果處于最大狀態(tài),則還原 imgMatrix.reset(); imgMatrix.postScale(scale, scale); imgMatrix.postTranslate(subX, subY); isBig = false; } else { imgMatrix.postScale(bigScale, bigScale); // 在原有矩陣后乘放大倍數(shù) 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; // 當(dāng)前平移距離 // 平移后,內(nèi)容區(qū)域上部有空白處理辦法 if (-transY < currentSubY) { transY = -currentSubY; } // 平移后,內(nèi)容區(qū)域下部有空白處理辦法 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 這個用于測試的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的圖片路徑動態(tài)賦值 // ............ 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) { // 當(dāng)圖片處于放大或縮小狀態(tài)時,控制標(biāo)題是否顯示 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,注意其中的參數(shù)。最后一個參數(shù)類似于C#中的委托,我這里使用接口來實現(xiàn),在放大縮小的切換時要執(zhí)行的操作都卸載這個方法中。
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>
相關(guān)文章
Android編程實現(xiàn)獲取當(dāng)前連接wifi名字的方法
這篇文章主要介紹了Android編程實現(xiàn)獲取當(dāng)前連接wifi名字的方法,涉及Android針對WiFi屬性操作的相關(guān)技巧,需要的朋友可以參考下2015-11-11android播放視頻時在立體聲與單聲道之間切換無變化原因分析及解決
使用第三方視頻播放器,有立體聲與單聲道之間切換,發(fā)現(xiàn)切換后無作用,原因是由于在HAL層默認(rèn)沒有處理上層發(fā)的stereo 轉(zhuǎn)mono的命令,具體的解決方法如下2013-06-06Android 實現(xiàn)不同字體顏色的TextView實現(xiàn)代碼
這篇文章主要介紹了Android 實現(xiàn)不同字體顏色的TextView實現(xiàn)代碼的相關(guān)資料,需要的朋友可以參考下2017-05-05Android實現(xiàn)下載zip壓縮文件并解壓的方法(附源碼)
這篇文章主要給大家介紹了利用Android實現(xiàn)下載zip壓縮文件并解壓的方法,文中給出了示例代碼并提供了源碼下載,需要的朋友可以參考借鑒,下面來一起看看吧。2017-02-02