Android手勢ImageView三部曲 第一部
這幾天一直在研究github上的PhotoView跟GestureImageView,發(fā)現(xiàn)寫的都很牛,看了很久的代碼,于是打算把自己所看的一些東西總結(jié)一下,內(nèi)容還是很多的,但是很有含金量哈~~
先附上兩個開源項(xiàng)目的鏈接:
GestureImageView: https://github.com/jasonpolites/gesture-imageview
PhotoView:https://github.com/chrisbanes/PhotoView
這樣說有點(diǎn)乏味哈,先看看我們今天要實(shí)現(xiàn)的效果:
當(dāng)一個手指按住圖片的時候,此時的效果為拖拽的效果。
當(dāng)兩個手指按住圖片的時候,手指旋轉(zhuǎn)則圖片跟著旋轉(zhuǎn),手指縮放則圖片縮放。
效果圖大致為(我模擬器不太好模擬旋轉(zhuǎn)):

好了下面我們來實(shí)現(xiàn)一下這個手勢縮放ImageView:
首先我們創(chuàng)建一個叫MatrixImageView的類去繼承ImageView,然后重寫其構(gòu)造方法(我就不考慮直接new的情況了哈):
public class MatrixImageView2 extends ImageView {
public MatrixImageView2(Context context, AttributeSet attrs) {
super(context, attrs);
initView();
}
}
然后我們需要定義幾種當(dāng)前view的狀態(tài):
MODE_NONE(初始狀態(tài));
MODE_DRAG(拖拽狀態(tài));
MODE_ZOOM(兩個手指縮放狀態(tài))
public class MatrixImageView2 extends ImageView {
private static final int MODE_NONE = 190;
private static final int MODE_DRAG = 468;
private static final int MODE_ZOOM = 685;
.....
}
我們對ImageView做旋轉(zhuǎn)、縮放、位移等操作主要是用到ImageView的這個方法:
/**
* Adds a transformation {@link Matrix} that is applied
* to the view's drawable when it is drawn. Allows custom scaling,
* translation, and perspective distortion.
*
* @param matrix the transformation parameters in matrix form
*/
public void setImageMatrix(Matrix matrix) {
// collapse null and identity to just null
if (matrix != null && matrix.isIdentity()) {
matrix = null;
}
// don't invalidate unless we're actually changing our matrix
if (matrix == null && !mMatrix.isIdentity() ||
matrix != null && !mMatrix.equals(matrix)) {
mMatrix.set(matrix);
configureBounds();
invalidate();
}
}
利用的是Matrix這個類(對這個類不懂的童鞋自己去查資料哈~),然后通過監(jiān)聽我們的onTouchEvent方法獲取當(dāng)前手勢操作,然后對matrix進(jìn)行相應(yīng)操作,改變圖片的狀態(tài)。
代碼比較短,而且我每行都注釋了,我就直接給代碼了:
MatrixImageView.java:
package com.leo.gestureimageview;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Matrix;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.view.MotionEvent;
import android.widget.ImageView;
public class MatrixImageView2 extends ImageView {
private static final int MODE_NONE = 190;
private static final int MODE_DRAG = 468;
private static final int MODE_ZOOM = 685;
//當(dāng)前mode
private int mode;
//手指按下時候的坐標(biāo)
private float startX, startY;
//兩個手指中間點(diǎn)的位置
private float midX, midY;
//當(dāng)前imageview的matirx對象,以前imageview的matrix對象
private Matrix currMatrix, savedMatrix;
//之前圖片的旋轉(zhuǎn)角度
private float preRotate;
//之間兩個手指之間的距離
private float preSpacing;
public MatrixImageView2(Context context, AttributeSet attrs) {
super(context, attrs);
initView();
}
private void initView() {
//初始化模式為初始狀態(tài)
mode = MODE_NONE;
currMatrix = new Matrix();
savedMatrix = new Matrix();
DisplayMetrics dm = getResources().getDisplayMetrics();
//給ImageView設(shè)置一張圖片(此處為了測試直接在imageview里面設(shè)置了一張測試圖片)
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.test);
bitmap = Bitmap.createScaledBitmap(bitmap, dm.widthPixels, dm.heightPixels, true);
setImageBitmap(bitmap);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
//多點(diǎn)觸碰如果需要監(jiān)聽ACTION_POINTER_DOWN等操作的時候,必須用event.getAction() & MotionEvent.ACTION_MASK
//而不是直接的event.getAction();
switch (event.getAction() & MotionEvent.ACTION_MASK) {
//當(dāng)一個手指按下的時候
case MotionEvent.ACTION_DOWN:
//保存當(dāng)前imageview的matrix對象
savedMatrix.set(currMatrix);
//記錄手指開始的坐標(biāo)
startX = event.getX();
startY = event.getY();
//此時的狀態(tài)為拖拽狀態(tài)
mode = MODE_DRAG;
break;
//當(dāng)兩個手指按下的時候(我們先不考慮很多個的情況哈,能力有限~!)
case MotionEvent.ACTION_POINTER_DOWN:
//計(jì)算兩個手指之間的距離并保存起來
preSpacing = calSpacing(event);
//如果兩個手指之間的距離大于我們指定的一個值后(改變狀態(tài)為縮放)
if (preSpacing > 10f) {
savedMatrix.set(currMatrix);
mode = MODE_ZOOM;
//記錄下縮放的中間坐標(biāo)值
midX = (event.getX(0) + event.getX(1)) / 2;
midY = (event.getY(0) + event.getY(1)) / 2;
}
//根據(jù)兩個手指的位置計(jì)算出當(dāng)前角度并保存
preRotate = calRotate(event);
break;
//當(dāng)手指移動的時候
case MotionEvent.ACTION_MOVE:
//如果之前給的狀態(tài)為拖拽狀態(tài)的時候
if (mode == MODE_DRAG) {
//首先把之前的matrix的狀態(tài)賦給當(dāng)前的matrix對象
currMatrix.set(savedMatrix);
//算出手指移動的距離
float dx = event.getX() - startX;
float dy = event.getY() - startY;
//把手指移動的距離設(shè)置給matrix對象
currMatrix.postTranslate(dx, dy);
//當(dāng)狀態(tài)為放大狀態(tài)的時候,并且有兩個手指按下的時候
} else if (mode == MODE_ZOOM && event.getPointerCount() == 2) {
//首先把之前的matrix的狀態(tài)賦給當(dāng)前的matrix對象
currMatrix.set(savedMatrix);
//計(jì)算出此時兩個手指之間的距離
float spacing = calSpacing(event);
//如果此時兩手指之間的距離大于我們給定的值
if (spacing > 10f) {
//此時兩手指距離/第二個手指剛按下時兩手指的距離
float scale = spacing / preSpacing;
//把算出的縮放值給當(dāng)前matrix對象,(縮放中心點(diǎn)為之前算出的mid)
currMatrix.postScale(scale, scale, midX, midY);
}
//根據(jù)兩手指位置算出此時的旋轉(zhuǎn)角度
float rotate = calRotate(event);
if (rotate != preRotate) {
//算出此時需要旋轉(zhuǎn)的角度
rotate = rotate - preRotate;
//開始旋轉(zhuǎn)圖片
currMatrix.postRotate(rotate, getMeasuredWidth() / 2, getMeasuredHeight() / 2);
}
}
break;
}
//最后記得把當(dāng)前的matrix對象給imageview
setImageMatrix(currMatrix);
return true;
}
/**
* 根據(jù)兩手指的位置算出角度
* 勾股定理 tan0=x(兩手指橫坐標(biāo)距離)/y(兩手指縱坐標(biāo)距離);
* @param event
* @return
*/
private float calRotate(MotionEvent event) {
double x = event.getX(0) - event.getX(1);
double y = event.getY(0) - event.getY(1);
double radius = Math.atan2(y, x);
return (float) Math.toDegrees(radius);
}
/**
* 兩個點(diǎn)距離公式為d*d=(x1-x0)的平方+(y1-y0)的平方
* @param event
* @return
*/
private float calSpacing(MotionEvent event) {
float x = event.getX(0) - event.getX(1);
float y = event.getY(0) - event.getY(1);
return (float) Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2));
}
}
然后添加我們的布局文件:
<com.leo.gestureimageview.MatrixImageView android:layout_width="match_parent" android:layout_height="match_parent" android:scaleType="matrix" android:src="@mipmap/test" />
最后運(yùn)行代碼:

好了,雖說我們是實(shí)現(xiàn)了我們的手勢imageview的基本功能,但是如果要處理那種多點(diǎn)(>兩個手指)觸碰,還有一些復(fù)雜的操作的時候,我們的onTouchEvent里面寫的代碼可能就不止這么一點(diǎn)了(還是有點(diǎn)復(fù)雜的,考慮的因素太多),但如果可以把某個事件的處理單獨(dú)拿出去分成很多個分支的話,還會這么復(fù)雜么?? 如果說我們的代碼可以像下面這樣的話,你是不是覺得很爽呢?
package com.leo.gestureimageview;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Matrix;
import android.graphics.PointF;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.view.MotionEvent;
import android.view.ScaleGestureDetector;
import android.widget.ImageView;
import com.leo.gestureimageview.GestureDetectors.MoveGestureDetector;
import com.leo.gestureimageview.GestureDetectors.RotateGestureDetector;
public class MatrixImageView2 extends ImageView {
private Matrix mMatrix = new Matrix();
private float mScaleFactor =1f;
private float mRotationDegrees = 0.f;
private float mFocusX = 0.f;
private float mFocusY = 0.f;
private ScaleGestureDetector mScaleDetector;
private RotateGestureDetector mRotateDetector;
private MoveGestureDetector mMoveDetector;
public MatrixImageView2(Context context, AttributeSet attrs) {
super(context, attrs);
initView();
}
private void initView() {
//初始化模式為初始狀態(tài)
DisplayMetrics dm = getResources().getDisplayMetrics();
//給ImageView設(shè)置一張圖片(此處為了測試直接在imageview里面設(shè)置了一張測試圖片)
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.test);
bitmap = Bitmap.createScaledBitmap(bitmap, dm.widthPixels, dm.heightPixels, true);
setImageBitmap(bitmap);
mScaleDetector = new ScaleGestureDetector(getContext(), new ScaleListener());
mRotateDetector = new RotateGestureDetector(getContext(), new RotateListener());
mMoveDetector = new MoveGestureDetector(getContext(), new MoveListener());
mFocusX = dm.widthPixels/2f;
mFocusY = dm.heightPixels/2f;
}
@Override
public boolean onTouchEvent(MotionEvent event) {
mScaleDetector.onTouchEvent(event);
mRotateDetector.onTouchEvent(event);
mMoveDetector.onTouchEvent(event);
return true;
}
private class ScaleListener extends ScaleGestureDetector.SimpleOnScaleGestureListener {
@Override
public boolean onScale(ScaleGestureDetector detector) {
mScaleFactor *= detector.getScaleFactor(); // scale change since previous event
// Don't let the object get too small or too large.
mScaleFactor = Math.max(0.1f, Math.min(mScaleFactor, 10.0f));
changeMatrix();
return true;
}
}
private class RotateListener extends RotateGestureDetector.SimpleOnRotateGestureListener {
@Override
public boolean onRotate(RotateGestureDetector detector) {
mRotationDegrees -= detector.getRotationDegreesDelta();
changeMatrix();
return true;
}
}
private class MoveListener extends MoveGestureDetector.SimpleOnMoveGestureListener {
@Override
public boolean onMove(MoveGestureDetector detector) {
PointF d = detector.getFocusDelta();
mFocusX += d.x;
mFocusY += d.y;
changeMatrix();
return true;
}
}
private void changeMatrix(){
float scaledImageCenterX = (getDrawable().getIntrinsicWidth()*mScaleFactor)/2;
float scaledImageCenterY = (getDrawable().getIntrinsicHeight()*mScaleFactor)/2;
mMatrix.reset();
mMatrix.postScale(mScaleFactor, mScaleFactor);
mMatrix.postRotate(mRotationDegrees, scaledImageCenterX, scaledImageCenterY);
mMatrix.postTranslate(mFocusX - scaledImageCenterX, mFocusY - scaledImageCenterY);
setImageMatrix(mMatrix);
}
}
我們的ImageView的onTouchEvent就只剩下短短的幾行代碼了,然后各個detector處理完事件后,我們只需要拿到處理好的值就可以了:
@Override
public boolean onTouchEvent(MotionEvent event) {
//把縮放事件給mScaleDetector
mScaleDetector.onTouchEvent(event);
//把旋轉(zhuǎn)事件個mRotateDetector
mRotateDetector.onTouchEvent(event);
//把移動事件給mMoveDetector
mMoveDetector.onTouchEvent(event);
return true;
}
是不是覺得很爽呢? 是的,我也是無意中看到了這個開源項(xiàng)目,先附上這個框架的github鏈接:
https://github.com/Almeros/android-gesture-detectors
下一節(jié)我們將深入了解detector,以及系統(tǒng)自帶的手勢處理工具類GestureDetector,感興趣的小伙伴請繼續(xù)關(guān)注哦。
以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
- Android手勢ImageView三部曲 第二部
- Android自定義GestureDetector實(shí)現(xiàn)手勢ImageView
- Android使用ImageView實(shí)現(xiàn)支持手勢縮放效果
- Android ImageView隨手勢變化動態(tài)縮放圖片
- Android手勢滑動實(shí)現(xiàn)ImageView縮放圖片大小
- Android實(shí)現(xiàn)手勢控制ImageView圖片大小
- Android通過手勢實(shí)現(xiàn)的縮放處理實(shí)例代碼
- android開發(fā)之為activity增加左右手勢識別示例
- android使用gesturedetector手勢識別示例分享
- Android手勢ImageView三部曲 第三部
相關(guān)文章
Android自定義popupwindow實(shí)例代碼
這篇文章主要為大家詳細(xì)介紹了Android自定義popupwindow實(shí)例代碼,popupwindow彈出菜單效果,具有一定的參考價值,感興趣的小伙伴們可以參考一下2016-11-11
Android編程調(diào)用Camera和相冊功能詳解
這篇文章主要介紹了Android編程調(diào)用Camera和相冊功能,結(jié)合實(shí)例形式分析了Android的拍照及相冊調(diào)用功能相關(guān)實(shí)現(xiàn)技巧與操作注意事項(xiàng),需要的朋友可以參考下2017-02-02
PopupWindow自定義位置顯示的實(shí)現(xiàn)代碼
這篇文章主要為大家詳細(xì)介紹了PopupWindow自定義位置顯示,具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-10-10

