Android通過多點觸控的方式對圖片進行縮放的例項程式碼

Android通過多點觸控的方式對圖片進行縮放的例項程式碼
1 Star2 Stars3 Stars4 Stars5 Stars 給文章打分!
Loading...

在上一篇文章中我帶著大家一起實現了Android瀑布流照片牆的效果,雖然這種效果很炫很酷,但其實還只能算是一個半成品,因為照片牆中所有的圖片都是隻能看不能點的。因此本篇文章中,我們就來對這一功能進行完善,加入點選圖片就能瀏覽大圖的功能,並且在瀏覽大圖的時候還可以通過多點觸控的方式對圖片進行縮放。

如果你還沒有看過Android瀑布流照片牆實現 體驗不規則排列的美感 這篇文章,請儘量先去閱讀完再來看本篇文章,因為這次的程式碼完全是在上次的基礎上進行開發的。

那我們現在就開始動手吧,首先開啟上次的PhotoWallFallsDemo專案,在裡面加入一個ZoomImageView類,這個類就是用於進行大圖展示和多點觸控縮放的,程式碼如下所示:


public class ZoomImageView extends View { 
/** 
* 初始化狀態常量 
*/ 
public static final int STATUS_INIT = 1; 
/** 
* 圖片放大狀態常量 
*/ 
public static final int STATUS_ZOOM_OUT = 2; 
/** 
* 圖片縮小狀態常量 
*/ 
public static final int STATUS_ZOOM_IN = 3; 
/** 
* 圖片拖動狀態常量 
*/ 
public static final int STATUS_MOVE = 4; 
/** 
* 用於對圖片進行移動和縮放變換的矩陣 
*/ 
private Matrix matrix = new Matrix(); 
/** 
* 待展示的Bitmap物件 
*/ 
private Bitmap sourceBitmap; 
/** 
* 記錄當前操作的狀態,可選值為STATUS_INIT、STATUS_ZOOM_OUT、STATUS_ZOOM_IN和STATUS_MOVE 
*/ 
private int currentStatus; 
/** 
* ZoomImageView控制元件的寬度 
*/ 
private int width; 
/** 
* ZoomImageView控制元件的高度 
*/ 
private int height; 
/** 
* 記錄兩指同時放在螢幕上時,中心點的橫座標值 
*/ 
private float centerPointX; 
/** 
* 記錄兩指同時放在螢幕上時,中心點的縱座標值 
*/ 
private float centerPointY; 
/** 
* 記錄當前圖片的寬度,圖片被縮放時,這個值會一起變動 
*/ 
private float currentBitmapWidth; 
/** 
* 記錄當前圖片的高度,圖片被縮放時,這個值會一起變動 
*/ 
private float currentBitmapHeight; 
/** 
* 記錄上次手指移動時的橫座標 
*/ 
private float lastXMove = -1; 
/** 
* 記錄上次手指移動時的縱座標 
*/ 
private float lastYMove = -1; 
/** 
* 記錄手指在橫座標方向上的移動距離 
*/ 
private float movedDistanceX; 
/** 
* 記錄手指在縱座標方向上的移動距離 
*/ 
private float movedDistanceY; 
/** 
* 記錄圖片在矩陣上的橫向偏移值 
*/ 
private float totalTranslateX; 
/** 
* 記錄圖片在矩陣上的縱向偏移值 
*/ 
private float totalTranslateY; 
/** 
* 記錄圖片在矩陣上的總縮放比例 
*/ 
private float totalRatio; 
/** 
* 記錄手指移動的距離所造成的縮放比例 
*/ 
private float scaledRatio; 
/** 
* 記錄圖片初始化時的縮放比例 
*/ 
private float initRatio; 
/** 
* 記錄上次兩指之間的距離 
*/ 
private double lastFingerDis; 
/** 
* ZoomImageView建構函式,將當前操作狀態設為STATUS_INIT。 
* 
* @param context 
* @param attrs 
*/ 
public ZoomImageView(Context context, AttributeSet attrs) { 
super(context, attrs); 
currentStatus = STATUS_INIT; 
} 
/** 
* 將待展示的圖片設定進來。 
* 
* @param bitmap 
*   待展示的Bitmap物件 
*/ 
public void setImageBitmap(Bitmap bitmap) { 
sourceBitmap = bitmap; 
invalidate(); 
} 
@Override 
protected void onLayout(boolean changed, int left, int top, int right, int bottom) { 
super.onLayout(changed, left, top, right, bottom); 
if (changed) { 
// 分別獲取到ZoomImageView的寬度和高度 
width = getWidth(); 
height = getHeight(); 
} 
} 
@Override 
public boolean onTouchEvent(MotionEvent event) { 
switch (event.getActionMasked()) { 
case MotionEvent.ACTION_POINTER_DOWN: 
if (event.getPointerCount() == 2) { 
// 當有兩個手指按在螢幕上時,計算兩指之間的距離 
lastFingerDis = distanceBetweenFingers(event); 
} 
break; 
case MotionEvent.ACTION_MOVE: 
if (event.getPointerCount() == 1) { 
// 只有單指按在螢幕上移動時,為拖動狀態 
float xMove = event.getX(); 
float yMove = event.getY(); 
if (lastXMove == -1 && lastYMove == -1) { 
lastXMove = xMove; 
lastYMove = yMove; 
} 
currentStatus = STATUS_MOVE; 
movedDistanceX = xMove - lastXMove; 
movedDistanceY = yMove - lastYMove; 
// 進行邊界檢查,不允許將圖片拖出邊界 
if (totalTranslateX   movedDistanceX > 0) { 
movedDistanceX = 0; 
} else if (width - (totalTranslateX   movedDistanceX) > currentBitmapWidth) { 
movedDistanceX = 0; 
} 
if (totalTranslateY   movedDistanceY > 0) { 
movedDistanceY = 0; 
} else if (height - (totalTranslateY   movedDistanceY) > currentBitmapHeight) { 
movedDistanceY = 0; 
} 
// 呼叫onDraw()方法繪製圖片 
invalidate(); 
lastXMove = xMove; 
lastYMove = yMove; 
} else if (event.getPointerCount() == 2) { 
// 有兩個手指按在螢幕上移動時,為縮放狀態 
centerPointBetweenFingers(event); 
double fingerDis = distanceBetweenFingers(event); 
if (fingerDis > lastFingerDis) { 
currentStatus = STATUS_ZOOM_OUT; 
} else { 
currentStatus = STATUS_ZOOM_IN; 
} 
// 進行縮放倍數檢查,最大隻允許將圖片放大4倍,最小可以縮小到初始化比例 
if ((currentStatus == STATUS_ZOOM_OUT && totalRatio < 4 * initRatio) 
|| (currentStatus == STATUS_ZOOM_IN && totalRatio > initRatio)) { 
scaledRatio = (float) (fingerDis / lastFingerDis); 
totalRatio = totalRatio * scaledRatio; 
if (totalRatio > 4 * initRatio) { 
totalRatio = 4 * initRatio; 
} else if (totalRatio < initRatio) { 
totalRatio = initRatio; 
} 
// 呼叫onDraw()方法繪製圖片 
invalidate(); 
lastFingerDis = fingerDis; 
} 
} 
break; 
case MotionEvent.ACTION_POINTER_UP: 
if (event.getPointerCount() == 2) { 
// 手指離開螢幕時將臨時值還原 
lastXMove = -1; 
lastYMove = -1; 
} 
break; 
case MotionEvent.ACTION_UP: 
// 手指離開螢幕時將臨時值還原 
lastXMove = -1; 
lastYMove = -1; 
break; 
default: 
break; 
} 
return true; 
} 
/** 
* 根據currentStatus的值來決定對圖片進行什麼樣的繪製操作。 
*/ 
@Override 
protected void onDraw(Canvas canvas) { 
super.onDraw(canvas); 
switch (currentStatus) { 
case STATUS_ZOOM_OUT: 
case STATUS_ZOOM_IN: 
zoom(canvas); 
break; 
case STATUS_MOVE: 
move(canvas); 
break; 
case STATUS_INIT: 
initBitmap(canvas); 
default: 
canvas.drawBitmap(sourceBitmap, matrix, null); 
break; 
} 
} 
/** 
* 對圖片進行縮放處理。 
* 
* @param canvas 
*/ 
private void zoom(Canvas canvas) { 
matrix.reset(); 
// 將圖片按總縮放比例進行縮放 
matrix.postScale(totalRatio, totalRatio); 
float scaledWidth = sourceBitmap.getWidth() * totalRatio; 
float scaledHeight = sourceBitmap.getHeight() * totalRatio; 
float translateX = 0f; 
float translateY = 0f; 
// 如果當前圖片寬度小於螢幕寬度,則按螢幕中心的橫座標進行水平縮放。否則按兩指的中心點的橫座標進行水平縮放 
if (currentBitmapWidth < width) { 
translateX = (width - scaledWidth) / 2f; 
} else { 
translateX = totalTranslateX * scaledRatio   centerPointX * (1 - scaledRatio); 
// 進行邊界檢查,保證圖片縮放後在水平方向上不會偏移出螢幕 
if (translateX > 0) { 
translateX = 0; 
} else if (width - translateX > scaledWidth) { 
translateX = width - scaledWidth; 
} 
} 
// 如果當前圖片高度小於螢幕高度,則按螢幕中心的縱座標進行垂直縮放。否則按兩指的中心點的縱座標進行垂直縮放 
if (currentBitmapHeight < height) { 
translateY = (height - scaledHeight) / 2f; 
} else { 
translateY = totalTranslateY * scaledRatio   centerPointY * (1 - scaledRatio); 
// 進行邊界檢查,保證圖片縮放後在垂直方向上不會偏移出螢幕 
if (translateY > 0) { 
translateY = 0; 
} else if (height - translateY > scaledHeight) { 
translateY = height - scaledHeight; 
} 
} 
// 縮放後對圖片進行偏移,以保證縮放後中心點位置不變 
matrix.postTranslate(translateX, translateY); 
totalTranslateX = translateX; 
totalTranslateY = translateY; 
currentBitmapWidth = scaledWidth; 
currentBitmapHeight = scaledHeight; 
canvas.drawBitmap(sourceBitmap, matrix, null); 
} 
/** 
* 對圖片進行平移處理 
* 
* @param canvas 
*/ 
private void move(Canvas canvas) { 
matrix.reset(); 
// 根據手指移動的距離計算出總偏移值 
float translateX = totalTranslateX   movedDistanceX; 
float translateY = totalTranslateY   movedDistanceY; 
// 先按照已有的縮放比例對圖片進行縮放 
matrix.postScale(totalRatio, totalRatio); 
// 再根據移動距離進行偏移 
matrix.postTranslate(translateX, translateY); 
totalTranslateX = translateX; 
totalTranslateY = translateY; 
canvas.drawBitmap(sourceBitmap, matrix, null); 
} 
/** 
* 對圖片進行初始化操作,包括讓圖片居中,以及當圖片大於螢幕寬高時對圖片進行壓縮。 
* 
* @param canvas 
*/ 
private void initBitmap(Canvas canvas) { 
if (sourceBitmap != null) { 
matrix.reset(); 
int bitmapWidth = sourceBitmap.getWidth(); 
int bitmapHeight = sourceBitmap.getHeight(); 
if (bitmapWidth > width || bitmapHeight > height) { 
if (bitmapWidth - width > bitmapHeight - height) { 
// 當圖片寬度大於螢幕寬度時,將圖片等比例壓縮,使它可以完全顯示出來 
float ratio = width / (bitmapWidth * 1.0f); 
matrix.postScale(ratio, ratio); 
float translateY = (height - (bitmapHeight * ratio)) / 2f; 
// 在縱座標方向上進行偏移,以保證圖片居中顯示 
matrix.postTranslate(0, translateY); 
totalTranslateY = translateY; 
totalRatio = initRatio = ratio; 
} else { 
// 當圖片高度大於螢幕高度時,將圖片等比例壓縮,使它可以完全顯示出來 
float ratio = height / (bitmapHeight * 1.0f); 
matrix.postScale(ratio, ratio); 
float translateX = (width - (bitmapWidth * ratio)) / 2f; 
// 在橫座標方向上進行偏移,以保證圖片居中顯示 
matrix.postTranslate(translateX, 0); 
totalTranslateX = translateX; 
totalRatio = initRatio = ratio; 
} 
currentBitmapWidth = bitmapWidth * initRatio; 
currentBitmapHeight = bitmapHeight * initRatio; 
} else { 
// 當圖片的寬高都小於螢幕寬高時,直接讓圖片居中顯示 
float translateX = (width - sourceBitmap.getWidth()) / 2f; 
float translateY = (height - sourceBitmap.getHeight()) / 2f; 
matrix.postTranslate(translateX, translateY); 
totalTranslateX = translateX; 
totalTranslateY = translateY; 
totalRatio = initRatio = 1f; 
currentBitmapWidth = bitmapWidth; 
currentBitmapHeight = bitmapHeight; 
} 
canvas.drawBitmap(sourceBitmap, matrix, null); 
} 
} 
/** 
* 計算兩個手指之間的距離。 
* 
* @param event 
* @return 兩個手指之間的距離 
*/ 
private double distanceBetweenFingers(MotionEvent event) { 
float disX = Math.abs(event.getX(0) - event.getX(1)); 
float disY = Math.abs(event.getY(0) - event.getY(1)); 
return Math.sqrt(disX * disX   disY * disY); 
} 
/** 
* 計算兩個手指之間中心點的座標。 
* 
* @param event 
*/ 
private void centerPointBetweenFingers(MotionEvent event) { 
float xPoint0 = event.getX(0); 
float yPoint0 = event.getY(0); 
float xPoint1 = event.getX(1); 
float yPoint1 = event.getY(1); 
centerPointX = (xPoint0   xPoint1) / 2; 
centerPointY = (yPoint0   yPoint1) / 2; 
} 
} 

由於這個類是整個多點觸控縮放功能最核心的一個類,我在這裡給大家詳細的講解一下。首先在ZoomImageView裡我們定義了四種狀態,STATUS_INIT、STATUS_ZOOM_OUT、STATUS_ZOOM_IN和STATUS_MOVE,這四個狀態分別代表初始化、放大、縮小和移動這幾個動作,然後在建構函式裡我們將當前狀態置為初始化狀態。接著我們可以呼叫setImageBitmap()方法把要顯示的圖片物件傳進去,這個方法會invalidate一下當前的View,因此onDraw()方法就會得到執行。然後在onDraw()方法裡判斷出當前的狀態是初始化狀態,所以就會呼叫initBitmap()方法進行初始化操作。

那我們就來看一下initBitmap()方法,在這個方法中首先對圖片的大小進行了判斷,如果圖片的寬和高都是小於螢幕的寬和高的,則直接將這張圖片進行偏移,讓它能夠居中顯示在螢幕上。如果圖片的寬度大於螢幕的寬度,或者圖片的高度大於螢幕的高度,則將圖片進行等比例壓縮,讓圖片的的寬或高正好等同於螢幕的寬或高,保證在初始化狀態下圖片一定能完整地顯示出來。這裡所有的偏移和縮放操作都是通過矩陣來完成的,我們把要縮放和偏移的值都存放在矩陣中,然後在繪製圖片的時候傳入這個矩陣物件就可以了。

圖片初始化完成之後,就可以對圖片進行縮放處理了。這裡在onTouchEvent()方法來對點選事件進行判斷,如果發現有兩個手指同時按在螢幕上(使用event.getPointerCount()判斷)就將當前狀態置為縮放狀態,並呼叫distanceBetweenFingers()來得到兩指之間的距離,以計算出縮放比例。然後invalidate一下,就會在onDraw()方法中就會呼叫zoom()方法。之後就在這個方法里根據當前的縮放比例以及中心點的位置對圖片進行縮放和偏移,具體的邏輯大家請仔細閱讀程式碼,註釋已經寫得非常清楚。

然後當只有一個手指按在螢幕上時,就把當前狀態置為移動狀態,之後會對手指的移動距離進行計算,並處理了邊界檢查的工作,以防止圖片偏移出螢幕。然後invalidate一下當前的view,又會進入到onDraw()方法中,這裡判斷出當前是移動狀態,於是會呼叫move()方法。move()方法中的程式碼非常簡單,就是根據手指移動的距離對圖片進行偏移就可以了。

介紹完了ZoomImageView,然後我們新建一個佈局image_details.xml,在佈局中直接引用建立好的ZoomImageView:


<?xml version="1.0" encoding="utf-8"?> 
<com.example.photowallfallsdemo.ZoomImageView xmlns:android="http://schemas.android.com/apk/res/android" 
android:id="@ id/zoom_image_view" 
android:layout_width="match_parent" 
android:layout_height="match_parent" 
android:background="#000000" > 
</com.example.photowallfallsdemo.ZoomImageView> 

接著建立一個Activity,在這個Activity中來載入image_details佈局。新建ImageDetailsActivity,程式碼如下所示:


public class ImageDetailsActivity extends Activity { 
private ZoomImageView zoomImageView; 
@Override 
protected void onCreate(Bundle savedInstanceState) { 
super.onCreate(savedInstanceState); 
requestWindowFeature(Window.FEATURE_NO_TITLE); 
setContentView(R.layout.image_details); 
zoomImageView = (ZoomImageView) findViewById(R.id.zoom_image_view); 
String imagePath = getIntent().getStringExtra("image_path"); 
Bitmap bitmap = BitmapFactory.decodeFile(imagePath); 
zoomImageView.setImageBitmap(bitmap); 
} 
} 

可以看到,首先我們獲取到了ZoomImageView的例項,然後又通過Intent得到了需要展示的圖片路徑,接著使用BitmapFactory將路徑下的圖片載入到記憶體中,然後呼叫ZoomImageView的setImageBitmap()方法將圖片傳入,就可以讓這張圖片展示出來了。

接下來我們需要考慮的,就是如何在照片牆上給圖片增加點選事件,讓它能夠啟動ImageDetailsActivity了。其實這也很簡單,只需要在動態新增圖片的時候給每個ImageView的例項註冊一下點選事件就好了,修改MyScrollView中addImage()方法的程式碼,如下所示:


private void addImage(Bitmap bitmap, int imageWidth, int imageHeight) { 
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(imageWidth, 
imageHeight); 
if (mImageView != null) { 
mImageView.setImageBitmap(bitmap); 
} else { 
ImageView imageView = new ImageView(getContext()); 
imageView.setLayoutParams(params); 
imageView.setImageBitmap(bitmap); 
imageView.setScaleType(ScaleType.FIT_XY); 
imageView.setPadding(5, 5, 5, 5); 
imageView.setTag(R.string.image_url, mImageUrl); 
imageView.setOnClickListener(new OnClickListener() { 
@Override 
public void onClick(View v) { 
Intent intent = new Intent(getContext(), ImageDetailsActivity.class); 
intent.putExtra("image_path", getImagePath(mImageUrl)); 
getContext().startActivity(intent); 
} 
}); 
findColumnToAdd(imageView, imageHeight).addView(imageView); 
imageViewList.add(imageView); 
} 
}

可以看到,這裡我們呼叫了ImageView的setOnClickListener()方法來給圖片增加點選事件,當使用者點選了照片牆中的任意圖片時,就會啟動ImageDetailsActivity,並將圖片的路徑傳遞過去。

由於我們新增了一個新的Activity,別忘了在AndroidManifest.xml檔案裡註冊一下:


<?xml version="1.0" encoding="utf-8"?> 
<manifest xmlns:android="http://schemas.android.com/apk/res/android" 
package="com.example.photowallfallsdemo" 
android:versionCode="1" 
android:versionName="1.0" > 
<uses-sdk 
android:minSdkVersion="14" 
android:targetSdkVersion="17" /> 
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> 
<uses-permission android:name="android.permission.INTERNET" /> 
<application 
android:allowBackup="true" 
android:icon="@drawable/ic_launcher" 
android:label="@string/app_name" 
android:theme="@style/AppTheme" > 
<activity 
android:name="com.example.photowallfallsdemo.MainActivity" 
android:label="@string/app_name" > 
<intent-filter> 
<action android:name="android.intent.action.MAIN" /> 
<category android:name="android.intent.category.LAUNCHER" /> 
</intent-filter> 
</activity> 
<activity android:name="com.example.photowallfallsdemo.ImageDetailsActivity" > 
</activity> 
</application> 
</manifest> 

這樣所有的編碼工作就已經完成了,現在我們執行一下程式,又會看到熟悉的照片牆介面,點選任意一張圖片會進入到相應的大圖介面,並且可以通過多點觸控的方式對圖片進行縮放,放大後還可以通過單指來移動圖片,如下圖所示。

好了,今天的講解到此結束,有疑問的朋友請在下面留言。

原始碼下載,請點選這裡
以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支援指令碼之家。

您可能感興趣的文章:

android 多點觸控圖片縮放的具體實現方法Android 圖片縮放與旋轉的實現詳解Android應用中實現手勢控制圖片縮放的完全攻略Android實現ImageView圖片縮放和拖動Android實現多點觸控,自由縮放圖片的例項程式碼Android多點觸控實現圖片自由縮放Android多點觸控技術實戰 針對圖片自由縮放和移動Android開發例項之多點觸控程式Android多點觸控實現對圖片放大縮小平移,慣性滑動等功能

相關文章

Android 開發 最新文章