Android手勢(shì)ImageView三部曲 第一部
這幾天一直在研究github上的PhotoView跟GestureImageView,發(fā)現(xiàn)寫(xiě)的都很牛,看了很久的代碼,于是打算把自己所看的一些東西總結(jié)一下,內(nèi)容還是很多的,但是很有含金量哈~~
先附上兩個(gè)開(kāi)源項(xiàng)目的鏈接:
GestureImageView: https://github.com/jasonpolites/gesture-imageview
PhotoView:https://github.com/chrisbanes/PhotoView
這樣說(shuō)有點(diǎn)乏味哈,先看看我們今天要實(shí)現(xiàn)的效果:
當(dāng)一個(gè)手指按住圖片的時(shí)候,此時(shí)的效果為拖拽的效果。
當(dāng)兩個(gè)手指按住圖片的時(shí)候,手指旋轉(zhuǎn)則圖片跟著旋轉(zhuǎn),手指縮放則圖片縮放。
效果圖大致為(我模擬器不太好模擬旋轉(zhuǎn)):
好了下面我們來(lái)實(shí)現(xiàn)一下這個(gè)手勢(shì)縮放ImageView:
首先我們創(chuàng)建一個(gè)叫MatrixImageView的類(lèi)去繼承ImageView,然后重寫(xiě)其構(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(兩個(gè)手指縮放狀態(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; ..... }
我們對(duì)ImageView做旋轉(zhuǎn)、縮放、位移等操作主要是用到ImageView的這個(gè)方法:
/** * 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這個(gè)類(lèi)(對(duì)這個(gè)類(lèi)不懂的童鞋自己去查資料哈~),然后通過(guò)監(jiān)聽(tīng)我們的onTouchEvent方法獲取當(dāng)前手勢(shì)操作,然后對(duì)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; //手指按下時(shí)候的坐標(biāo) private float startX, startY; //兩個(gè)手指中間點(diǎn)的位置 private float midX, midY; //當(dāng)前imageview的matirx對(duì)象,以前imageview的matrix對(duì)象 private Matrix currMatrix, savedMatrix; //之前圖片的旋轉(zhuǎn)角度 private float preRotate; //之間兩個(gè)手指之間的距離 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è)置一張圖片(此處為了測(cè)試直接在imageview里面設(shè)置了一張測(cè)試圖片) 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)聽(tīng)ACTION_POINTER_DOWN等操作的時(shí)候,必須用event.getAction() & MotionEvent.ACTION_MASK //而不是直接的event.getAction(); switch (event.getAction() & MotionEvent.ACTION_MASK) { //當(dāng)一個(gè)手指按下的時(shí)候 case MotionEvent.ACTION_DOWN: //保存當(dāng)前imageview的matrix對(duì)象 savedMatrix.set(currMatrix); //記錄手指開(kāi)始的坐標(biāo) startX = event.getX(); startY = event.getY(); //此時(shí)的狀態(tài)為拖拽狀態(tài) mode = MODE_DRAG; break; //當(dāng)兩個(gè)手指按下的時(shí)候(我們先不考慮很多個(gè)的情況哈,能力有限~!) case MotionEvent.ACTION_POINTER_DOWN: //計(jì)算兩個(gè)手指之間的距離并保存起來(lái) preSpacing = calSpacing(event); //如果兩個(gè)手指之間的距離大于我們指定的一個(gè)值后(改變狀態(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ù)兩個(gè)手指的位置計(jì)算出當(dāng)前角度并保存 preRotate = calRotate(event); break; //當(dāng)手指移動(dòng)的時(shí)候 case MotionEvent.ACTION_MOVE: //如果之前給的狀態(tài)為拖拽狀態(tài)的時(shí)候 if (mode == MODE_DRAG) { //首先把之前的matrix的狀態(tài)賦給當(dāng)前的matrix對(duì)象 currMatrix.set(savedMatrix); //算出手指移動(dòng)的距離 float dx = event.getX() - startX; float dy = event.getY() - startY; //把手指移動(dòng)的距離設(shè)置給matrix對(duì)象 currMatrix.postTranslate(dx, dy); //當(dāng)狀態(tài)為放大狀態(tài)的時(shí)候,并且有兩個(gè)手指按下的時(shí)候 } else if (mode == MODE_ZOOM && event.getPointerCount() == 2) { //首先把之前的matrix的狀態(tài)賦給當(dāng)前的matrix對(duì)象 currMatrix.set(savedMatrix); //計(jì)算出此時(shí)兩個(gè)手指之間的距離 float spacing = calSpacing(event); //如果此時(shí)兩手指之間的距離大于我們給定的值 if (spacing > 10f) { //此時(shí)兩手指距離/第二個(gè)手指剛按下時(shí)兩手指的距離 float scale = spacing / preSpacing; //把算出的縮放值給當(dāng)前matrix對(duì)象,(縮放中心點(diǎn)為之前算出的mid) currMatrix.postScale(scale, scale, midX, midY); } //根據(jù)兩手指位置算出此時(shí)的旋轉(zhuǎn)角度 float rotate = calRotate(event); if (rotate != preRotate) { //算出此時(shí)需要旋轉(zhuǎn)的角度 rotate = rotate - preRotate; //開(kāi)始旋轉(zhuǎn)圖片 currMatrix.postRotate(rotate, getMeasuredWidth() / 2, getMeasuredHeight() / 2); } } break; } //最后記得把當(dāng)前的matrix對(duì)象給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); } /** * 兩個(gè)點(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)行代碼:
好了,雖說(shuō)我們是實(shí)現(xiàn)了我們的手勢(shì)imageview的基本功能,但是如果要處理那種多點(diǎn)(>兩個(gè)手指)觸碰,還有一些復(fù)雜的操作的時(shí)候,我們的onTouchEvent里面寫(xiě)的代碼可能就不止這么一點(diǎn)了(還是有點(diǎn)復(fù)雜的,考慮的因素太多),但如果可以把某個(gè)事件的處理單獨(dú)拿出去分成很多個(gè)分支的話,還會(huì)這么復(fù)雜么?? 如果說(shuō)我們的代碼可以像下面這樣的話,你是不是覺(jué)得很爽呢?
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è)置一張圖片(此處為了測(cè)試直接在imageview里面設(shè)置了一張測(cè)試圖片) 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就只剩下短短的幾行代碼了,然后各個(gè)detector處理完事件后,我們只需要拿到處理好的值就可以了:
@Override public boolean onTouchEvent(MotionEvent event) { //把縮放事件給mScaleDetector mScaleDetector.onTouchEvent(event); //把旋轉(zhuǎn)事件個(gè)mRotateDetector mRotateDetector.onTouchEvent(event); //把移動(dòng)事件給mMoveDetector mMoveDetector.onTouchEvent(event); return true; }
是不是覺(jué)得很爽呢? 是的,我也是無(wú)意中看到了這個(gè)開(kāi)源項(xiàng)目,先附上這個(gè)框架的github鏈接:
https://github.com/Almeros/android-gesture-detectors
下一節(jié)我們將深入了解detector,以及系統(tǒng)自帶的手勢(shì)處理工具類(lèi)GestureDetector,感興趣的小伙伴請(qǐng)繼續(xù)關(guān)注哦。
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
- Android手勢(shì)ImageView三部曲 第二部
- Android自定義GestureDetector實(shí)現(xiàn)手勢(shì)ImageView
- Android使用ImageView實(shí)現(xiàn)支持手勢(shì)縮放效果
- Android ImageView隨手勢(shì)變化動(dòng)態(tài)縮放圖片
- Android手勢(shì)滑動(dòng)實(shí)現(xiàn)ImageView縮放圖片大小
- Android實(shí)現(xiàn)手勢(shì)控制ImageView圖片大小
- Android通過(guò)手勢(shì)實(shí)現(xiàn)的縮放處理實(shí)例代碼
- android開(kāi)發(fā)之為activity增加左右手勢(shì)識(shí)別示例
- android使用gesturedetector手勢(shì)識(shí)別示例分享
- Android手勢(shì)ImageView三部曲 第三部
相關(guān)文章
Bootstrap 下拉菜單.dropdown的具體使用方法
這篇文章主要介紹了Bootstrap 下拉菜單.dropdown的具體使用方法,詳細(xì)講解下拉菜單的交互,有興趣的可以了解一下2017-10-10Android巧用XListView實(shí)現(xiàn)萬(wàn)能下拉刷新控件
這篇文章主要為大家詳細(xì)介紹了Android巧用XListView實(shí)現(xiàn)萬(wàn)能下拉刷新控件,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-01-01Android編程動(dòng)態(tài)按鈕實(shí)現(xiàn)方法
這篇文章主要介紹了Android編程動(dòng)態(tài)按鈕實(shí)現(xiàn)方法,分享了onTouch方法及xml調(diào)用兩種實(shí)現(xiàn)技巧,需要的朋友可以參考下2016-10-10深入理解與運(yùn)行Android Jetpack組件之ViewModel
ViewModel是Android Jetpack組件之一,是一種用于管理UI相關(guān)數(shù)據(jù)的架構(gòu)組件,它能夠幫助開(kāi)發(fā)者實(shí)現(xiàn)優(yōu)雅的數(shù)據(jù)驅(qū)動(dòng)和生命周期管理,本文將深入淺出地介紹ViewModel的使用和原理,帶你一步步掌握這個(gè)強(qiáng)大的組件2023-08-08Android編程中TextView字體屬性設(shè)置方法(大小、字體、下劃線、背景色)
這篇文章主要介紹了Android編程中TextView字體屬性設(shè)置方法,包括大小、字體、下劃線、背景色等設(shè)置技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-10-10Android開(kāi)發(fā)中總結(jié)的Adapter工具類(lèi)【附完整源碼下載】
這篇文章主要介紹了Android開(kāi)發(fā)中總結(jié)的Adapter工具類(lèi),簡(jiǎn)單說(shuō)明了Adapter的功能,并結(jié)合實(shí)例形式分析了Adapter工具類(lèi)的相關(guān)使用方法,并附帶完整源碼供讀者下載參考,需要的朋友可以參考下2017-11-11Android 實(shí)現(xiàn)全屏和無(wú)標(biāo)題欄的顯示
本篇文章主要介紹了Android 全屏顯示和無(wú)標(biāo)題欄的方法,并附上代碼實(shí)例,和運(yùn)行效果圖,有需要的朋友可以參考下2016-07-07Android編程實(shí)現(xiàn)檢測(cè)當(dāng)前電源狀態(tài)的方法
這篇文章主要介紹了Android編程實(shí)現(xiàn)檢測(cè)當(dāng)前電源狀態(tài)的方法,涉及Android針對(duì)當(dāng)前電源的電量、容量、伏數(shù)、溫度等的檢測(cè)技巧,非常簡(jiǎn)單實(shí)用,需要的朋友可以參考下2015-11-11