先放上一張效果圖:
在這裡,我對自己的筆記本全屏截圖,然後當作自定義ImageView的src內容放在真機上運行。
可以看到這裡的圖片是可以移動和縮放的。
在這裡先說清一點,如果在xml的控件上設置src,則需要在代碼上通過getDrawable();獲取,如果是通過setBackGround的,則通過getBackground();獲取即可。
- publicclassMyImageViewextendsImageViewimplementsScaleGestureDetector.OnScaleGestureListener,
- View.OnTouchListener{ 這個是我自定義ImageView的類名。
我在這裡實現了一些待會會用到的接口。
/**
*控件寬度
*/
privateintmWidth;
/**
*控件高度
*/
privateintmHeight;
/**
*拿到src的圖片
*/
privateDrawablemDrawable;
/**
*圖片寬度(使用前判斷mDrawable是否null)
*/
privateintmDrawableWidth;
/**
*圖片高度(使用前判斷mDrawable是否null)
*/
privateintmDrawableHeight;
/**
*初始化縮放值
*/
privatefloatmScale;
/**
*雙擊圖片的縮放值
*/
privatefloatmDoubleClickScale;
/**
*最大的縮放值
*/
privatefloatmMaxScale;
/**
*最小的縮放值
*/
privatefloatmMinScale;
privateScaleGestureDetectorscaleGestureDetector;
/**
*當前有著縮放值、平移值的矩陣。
*/
privateMatrixmatrix;
這些是我定義出來的一些成員變量,每個變量我都寫上了作用。
publicMyImageView(Contextcontext){
this(context,null);
}
publicMyImageView(Contextcontext,AttributeSetattrs){
this(context,attrs,0);
}
publicMyImageView(Contextcontext,AttributeSetattrs,intdefStyleAttr){
super(context,attrs,defStyleAttr);
setOnTouchListener(this);
scaleGestureDetector=newScaleGestureDetector(context,this);
initListener();
}
這裡是三個標准的構造器,直接用短的引用長的就是了。
先看一看initListener();干了什麼事情。
- /**
- *初始化事件監聽
- */
- privatevoidinitListener(){
- //強制設置模式
- setScaleType(ScaleType.MATRIX);
- //添加觀察者
- getViewTreeObserver().addOnGlobalLayoutListener(newViewTreeObserver.OnGlobalLayoutListener(){
- @Override
- publicvoidonGlobalLayout(){
- //移除觀察者
- getViewTreeObserver().removeOnGlobalLayoutListener(this);
- //獲取控件大小
- mWidth=getWidth();
- mHeight=getHeight();
-
- //通過getDrawable獲得Src的圖片
- mDrawable=getDrawable();
- if(mDrawable==null)
- return;
- mDrawableWidth=mDrawable.getIntrinsicWidth();
- mDrawableHeight=mDrawable.getIntrinsicHeight();
- initImageViewSize();
- moveToCenter();
- }
- });
- }
這裡唯一要注意的是我在初始化監聽這個方法內,強制了ImageView的scaleType
[java]view plaincopy
- /**
- *初始化資源圖片寬高
- */
- privatevoidinitImageViewSize(){
- if(mDrawable==null)
- return;
-
- //縮放值
- floatscale=1.0f;
- //圖片寬度大於控件寬度,圖片高度小於控件高度
- if(mDrawableWidth>mWidth&&mDrawableHeight scale=mWidth*1.0f/mDrawableWidth;
- //圖片高度度大於控件寬高,圖片寬度小於控件寬度
- elseif(mDrawableHeight>mHeight&&mDrawableWidth scale=mHeight*1.0f/mDrawableHeight;
- //圖片寬度大於控件寬度,圖片高度大於控件高度
- elseif(mDrawableHeight>mHeight&&mDrawableWidth>mWidth)
- scale=Math.min(mHeight*1.0f/mDrawableHeight,mWidth*1.0f/mDrawableWidth);
- //圖片寬度小於控件寬度,圖片高度小於控件高度
- elseif(mDrawableHeight scale=Math.min(mHeight*1.0f/mDrawableHeight,mWidth*1.0f/mDrawableWidth);
- mScale=scale;
- mMaxScale=mScale*8.0f;
- mMinScale=mScale*0.5f;
- }
先判斷一下有沒有src資源,沒有的話,這個方法調用也沒意義了。
- /**
- *移動控件中間位置
- */
- privatevoidmoveToCenter(){
- finalfloatdx=mWidth/2-mDrawableWidth/2;
- finalfloatdy=mHeight/2-mDrawableHeight/2;
- matrix=newMatrix();
- //平移至中心
- matrix.postTranslate(dx,dy);
- //以控件中心作為縮放
- matrix.postScale(mScale,mScale,mWidth/2,mHeight/2);
- setImageMatrix(matrix);
- }
看注釋的意思是要把它(圖片)移動到屏幕的正中心,
dx的意思是取橫方向上,控件中心到圖片中心的值,如果大於0就向右移動,
反之向左移動相應的絕對值。
dy則換成縱向方向就是了。
在這裡實例化了matrix對象(初始化一次就行),至於為什麼只需要初始化一次,
因為圖片的縮放值和平移值,都是通過matrix保存的,如果再一次初始化,縮放值
和平移值等等數據都會被清空。
我是先讓它平時到控件正中心,然後以控件中心縮放mScale,mScale在initImageViewSize();的時候已經賦值了。
至於先縮放後平移,應該也是可以得,但可能計算公式相對麻煩些,
在這裡本著方便為主的原則,就不再作計算了。
接下來會說到這個東西scaleGestureDetector = new ScaleGestureDetector(context, this);
通過這個方法,實現了監聽事件,是手勢滑動的監聽事件。
[java]view plaincopy
- @Override
- publicbooleanonScale(ScaleGestureDetectordetector){
-
- returntrue;
- }
-
-
- @Override
- publicbooleanonScaleBegin(ScaleGestureDetectordetector){
- returntrue;
- }
-
- @Override
- publicvoidonScaleEnd(ScaleGestureDetectordetector){
-
- }
但是,雖然實現了監聽,但是然並卵,因為onTouch事件中沒有它(scaleGestureDetector),在這裡 ,重寫onTouchEvent是沒用的,因為onTouchEventListener的優先級比onTouchEvent要高,所以我們只能這樣子。
- setOnTouchListener(this);
- @Override
- publicbooleanonTouch(Viewv,MotionEventevent){
-
- returnscaleGestureDetector.onTouchEvent(event);
- }
在最後調用了scaleGestureDetector.onTouchEvent(event);這個方法。
然後手勢生效了(呵呵哒
)。
- @Override
- publicbooleanonScaleBegin(ScaleGestureDetectordetector){
- returntrue;
- }
這個方法是手勢執行前生效,必須return ture,不然onScale必定失效!
現在重點說一下onScale,因為這個方法是處理手勢的縮放,
- @Override
- publicbooleanonScale(ScaleGestureDetectordetector){
- if(mDrawable==null){
- returntrue;
- }
- //系統定義的縮放值
- floatscaleFactor=detector.getScaleFactor();
- //獲取已經縮放的值
- floatscale=getmScale();
- floatscaleResult=scale*scaleFactor;
- if(scaleResult>=mMaxScale&&scaleFactor>1.0f)
- scaleFactor=mMaxScale/scale;
- if(scaleResult<=mMinScale&&scaleFactor<1.0f)
- scaleFactor=mMinScale/scale;
-
-
- matrix.postScale(scaleFactor,scaleFactor,detector.getFocusX(),detector.getFocusY());
-
- /
- setImageMatrix(matrix);
- }
其中,scaleFactor是獲得手勢縮放的值(具體怎麼獲取的不知道),當值>1.0f時,說明兩個手指的滑動距離是不斷增加(相對於兩個手指都down了的那一瞬間),同理<1.0f說明兩個手指的滑動距離不斷減少,也是相對於那一瞬間,
- /**
- *@return當前縮放的值
- */
- privatefloatgetmScale(){
- float[]floats=newfloat[9];
- matrix.getValues(floats);
- returnfloats[Matrix.MSCALE_X];
- }
通過這個方法 ,拿到了之前matrix對象的scaleX值(X和Y都沒所謂,因為在這裡都是一個值),然後將當前的scale*手勢滑動的縮放值,得到最新的縮放值scaleResult,在這裡做了一個最大放大值和最小縮小值得處理,如果scaleResult大於等於最大縮放值和手指滑動為放大手勢,則讓手勢縮放為一個恆定的最大放大值(反之同理)。
看效果圖後,會覺得比較奇葩,因為縮小的時候,位置好像偏了!(原本是在控件正中心)。
- /**
- *@parammatrix矩陣
- *@returnmatrix的ltbr和width,height
- */
- privateRectFgetRectf(Matrixmatrix){
- RectFf=newRectF();
- if(mDrawable==null)
- returnnull;
- f.set(0,0,mDrawableWidth,mDrawableHeight);
- matrix.mapRect(f);
- returnf;
- }
首先看一下這個方法,通過這個方法,可以得到矩陣matrix的N維屬性,並把這N維屬性賦值到一個float類型的矩形上。
在將上面的/。。。補上
- RectFf=getRectf(matrix);
- floatdX=0.0f;
- floatdY=0.0f;
- //圖片高度大於控件高度
- if(f.height()>=mHeight){
- //圖片頂部出現空白
- if(f.top>0){
- //往上移動
- dY=-f.top;
- }
- //圖片底部出現空白
- if(f.bottom//往下移動
- dY=mHeight-f.bottom;
- }
- }
- //圖片寬度大於控件寬度
- if(f.width()>=mWidth){
- //圖片左邊出現空白
- if(f.left>0){
- //往左邊移動
- dX=-f.left;
- }
- //圖片右邊出現空白
- if(f.right//往右邊移動
- dX=mWidth-f.right;
- }
- }
-
- if(f.width()dX=mWidth/2-f.right+f.width()/2;
- }
-
- if(f.height()dY=mHeight/2-f.bottom+f.height()/2;
- }
- matrix.postTranslate(dX,dY);
- setImageMatrix(matrix);
首先獲取矩陣matrix的N維並賦值在f身上。
[java]view plaincopy
- //圖片高度大於控件高度
- if(f.height()>=mHeight){
- //圖片頂部出現空白
- if(f.top>0){
- //往上移動
- dY=-f.top;
- }
- //圖片底部出現空白
- if(f.bottom//往下移動
- dY=mHeight-f.bottom;
- }
- }
大概就是這個意思:當圖片高度大於等於控件高度的時候,堅決不讓控件高度方向上出現白色位置,此時,假設當圖片和控件高度完全相同的時候,是不是圖片的縱向剛好和控件完全重疊呢?
[java]view plaincopy
- if(f.height()dY=mHeight/2-f.bottom+f.height()/2;
- }
[java]view plaincopy
- privatefloatdownX;
- privatefloatdownY;
- privatefloatnowMovingX;
- privatefloatnowMovingY;
- privatefloatlastMovedX;
- privatefloatlastMovedY;
- privatebooleanisFirstMoved=false;
-
- @Override
- publicbooleanonTouch(Viewv,MotionEventevent){
- switch(event.getAction()&MotionEvent.ACTION_MASK){
- caseMotionEvent.ACTION_DOWN:
- isFirstMoved=false;
- downX=event.getX();
- downY=event.getY();
- break;
- caseMotionEvent.ACTION_POINTER_DOWN:
- isFirstMoved=false;
- break;
- caseMotionEvent.ACTION_MOVE:
- nowMovingX=event.getX();
- nowMovingY=event.getY();
- if(!isFirstMoved){
- isFirstMoved=true;
- lastMovedX=nowMovingX;
- lastMovedY=nowMovingY;
- }
- floatdX=0.0f;
- floatdY=0.0f;
- RectFrectf=getRectf(matrix);
- //判斷滑動方向
- finalfloatscrollX=nowMovingX-lastMovedX;
- //判斷滑動方向
- finalfloatscrollY=nowMovingY-lastMovedY;
- //圖片高度大於控件高度
- if(rectf.height()>mHeight&&canSmoothY()){
- dY=nowMovingY-lastMovedY;
- }
-
- //圖片寬度大於控件寬度
- if(rectf.width()>mWidth&&canSmoothX()){
- dX=nowMovingX-lastMovedX;
- }
- matrix.postTranslate(dX,dY);
-
- remedyXAndY(dX,dY);
-
- lastMovedX=nowMovingX;
- lastMovedY=nowMovingY;
- break;
- caseMotionEvent.ACTION_UP:
- break;
- caseMotionEvent.ACTION_POINTER_UP:
- isFirstMoved=false;
- break;
- }
- returnscaleGestureDetector.onTouchEvent(event);
- }
MotionEvent.ACTION_POINTER_DOWN;這個也是壓下的時候,區別在於只有不是第一根手指壓下的時候才執行,
所以,我在壓下的動作都初始化isFirstMoved=false;
當移動的時候,ACTION_MOVE也會執行。
由於移動的時候處理邏輯少的問題,出現屏幕越界後明顯的白邊反彈,因此在這裡編輯了一部分代碼。。。
滑動前,先判斷能否滑動,滑動後,再次判斷是否越界,因此,有效解決了白邊反彈現象。
- /**
- *判斷x方向上能不能滑動
- *@return可以滑動返回true
- */
- privatebooleancanSmoothX(){
- RectFrectf=getRectf(matrix);
- if(rectf.left>0||rectf.rightreturnfalse;
- returntrue;
- }
-
- /**
- *判斷y方向上可不可以滑動
- *@return可以滑動返回true
- */
- privatebooleancanSmoothY(){
- RectFrectf=getRectf(matrix);
- if(rectf.top>0||rectf.bottomreturnfalse;
- returntrue;
- }
以上是x和y方向上,滑動前判斷可不可以滑動的片段代碼。
- /**
- *糾正出界的橫和眾線
- *@paramdx出界偏移的橫線
- *@paramdy出街便宜的眾線
- */
- privatevoidremedyXAndY(floatdx,floatdy){
- if(!canSmoothX())
- matrix.postTranslate(-dx,0);
- if(!canSmoothY())
- matrix.postTranslate(0,-dy);
- setImageMatrix(matrix);
- }
這段是用於滑動之後判斷是否越界的,如果越界,把多余的dx和dy滑動回去。
完整的自定義控件代碼:
- packagecom.test.gesturedemo.view;
-
- importandroid.content.Context;
- importandroid.graphics.Matrix;
- importandroid.graphics.RectF;
- importandroid.graphics.drawable.Drawable;
- importandroid.util.AttributeSet;
- importandroid.view.MotionEvent;
- importandroid.view.ScaleGestureDetector;
- importandroid.view.View;
- importandroid.view.ViewTreeObserver;
- importandroid.widget.ImageView;
-
- /**
- *Createdby13798on2016/6/3.
- */
- publicclassMyImageViewextendsImageViewimplementsScaleGestureDetector.OnScaleGestureListener,View.OnTouchListener{
- /**
- *控件寬度
- */
- privateintmWidth;
- /**
- *控件高度
- */
- privateintmHeight;
- /**
- *拿到src的圖片
- */
- privateDrawablemDrawable;
- /**
- *圖片寬度(使用前判斷mDrawable是否null)
- */
- privateintmDrawableWidth;
- /**
- *圖片高度(使用前判斷mDrawable是否null)
- */
- privateintmDrawableHeight;
-
- /**
- *初始化縮放值
- */
- privatefloatmScale;
-
- /**
- *雙擊圖片的縮放值
- */
- privatefloatmDoubleClickScale;
-
- /**
- *最大的縮放值
- */
- privatefloatmMaxScale;
-
- /**
- *最小的縮放值
- */
- privatefloatmMinScale;
-
- privateScaleGestureDetectorscaleGestureDetector;
- /**
- *當前有著縮放值、平移值的矩陣。
- */
- privateMatrixmatrix;
-
- publicMyImageView(Contextcontext){
- this(context,null);
- }
-
- publicMyImageView(Contextcontext,AttributeSetattrs){
- this(context,attrs,0);
- }
-
- publicMyImageView(Contextcontext,AttributeSetattrs,intdefStyleAttr){
- super(context,attrs,defStyleAttr);
- setOnTouchListener(this);
- scaleGestureDetector=newScaleGestureDetector(context,this);
- initListener();
- }
-
-
- /**
- *初始化事件監聽
- */
- privatevoidinitListener(){
- //強制設置模式
- setScaleType(ScaleType.MATRIX);
- //添加觀察者
- getViewTreeObserver().addOnGlobalLayoutListener(newViewTreeObserver.OnGlobalLayoutListener(){
- @Override
- publicvoidonGlobalLayout(){
- //移除觀察者
- getViewTreeObserver().removeGlobalOnLayoutListener(this);
- //獲取控件大小
- mWidth=getWidth();
- mHeight=getHeight();
-
- //通過getDrawable獲得Src的圖片
- mDrawable=getDrawable();
- if(mDrawable==null)
- return;
- mDrawableWidth=mDrawable.getIntrinsicWidth();
- mDrawableHeight=mDrawable.getIntrinsicHeight();
- initImageViewSize();
- moveToCenter();
- }
- });
- }
-
- /**
- *初始化資源圖片寬高
- */
- privatevoidinitImageViewSize(){
- if(mDrawable==null)
- return;
-
- //縮放值
- floatscale=1.0f;
- //圖片寬度大於控件寬度,圖片高度小於控件高度
- if(mDrawableWidth>mWidth&&mDrawableHeightscale=mWidth*1.0f/mDrawableWidth;
- //圖片高度度大於控件寬高,圖片寬度小於控件寬度
- elseif(mDrawableHeight>mHeight&&mDrawableWidthscale=mHeight*1.0f/mDrawableHeight;
- //圖片寬度大於控件寬度,圖片高度大於控件高度
- elseif(mDrawableHeight>mHeight&&mDrawableWidth>mWidth)
- scale=Math.min(mHeight*1.0f/mDrawableHeight,mWidth*1.0f/mDrawableWidth);
- //圖片寬度小於控件寬度,圖片高度小於控件高度
- elseif(mDrawableHeightscale=Math.min(mHeight*1.0f/mDrawableHeight,mWidth*1.0f/mDrawableWidth);
- mScale=scale;
- mMaxScale=mScale*8.0f;
- mMinScale=mScale*0.5f;
- }
-
- /**
- *移動控件中間位置
- */
- privatevoidmoveToCenter(){
- finalfloatdx=mWidth/2-mDrawableWidth/2;
- finalfloatdy=mHeight/2-mDrawableHeight/2;
- matrix=newMatrix();
- //平移至中心
- matrix.postTranslate(dx,dy);
- //以控件中心作為縮放
- matrix.postScale(mScale,mScale,mWidth/2,mHeight/2);
- setImageMatrix(matrix);
- }
-
- /**
- *@return當前縮放的值
- */
- privatefloatgetmScale(){
- float[]floats=newfloat[9];
- matrix.getValues(floats);
- returnfloats[Matrix.MSCALE_X];
- }
-
- /**
- *@parammatrix矩陣
- *@returnmatrix的ltbr和width,height
- */
- privateRectFgetRectf(Matrixmatrix){
- RectFf=newRectF();
- if(mDrawable==null)
- returnnull;
- f.set(0,0,mDrawableWidth,mDrawableHeight);
- matrix.mapRect(f);
- returnf;
- }
-
- @Override
- publicbooleanonScale(ScaleGestureDetectordetector){
- if(mDrawable==null){
- returntrue;
- }
- //系統定義的縮放值
- floatscaleFactor=detector.getScaleFactor();
- //獲取已經縮放的值
- floatscale=getmScale();
- floatscaleResult=scale*scaleFactor;
- if(scaleResult>=mMaxScale&&scaleFactor>1.0f)
- scaleFactor=mMaxScale/scale;
- if(scaleResult<=mMinScale&&scaleFactor<1.0f)
- scaleFactor=mMinScale/scale;
-
- matrix.postScale(scaleFactor,scaleFactor,detector.getFocusX(),detector.getFocusY());
-
- RectFf=getRectf(matrix);
- floatdX=0.0f;
- floatdY=0.0f;
- //圖片高度大於控件高度
- if(f.height()>=mHeight){
- //圖片頂部出現空白
- if(f.top>0){
- //往上移動
- dY=-f.top;
- }
- //圖片底部出現空白
- if(f.bottom//往下移動
- dY=mHeight-f.bottom;
- }
- }
- //圖片寬度大於控件寬度
- if(f.width()>=mWidth){
- //圖片左邊出現空白
- if(f.left>0){
- //往左邊移動
- dX=-f.left;
- }
- //圖片右邊出現空白
- if(f.right//往右邊移動
- dX=mWidth-f.right;
- }
- }
-
- if(f.width()dX=mWidth/2-f.right+f.width()/2;
- }
-
- if(f.height()dY=mHeight/2-f.bottom+f.height()/2;
- }
- matrix.postTranslate(dX,dY);
- setImageMatrix(matrix);
- returntrue;
- }
-
-
- @Override
- publicbooleanonScaleBegin(ScaleGestureDetectordetector){
- returntrue;
- }
-
- @Override
- publicvoidonScaleEnd(ScaleGestureDetectordetector){
- floatscale=getmScale();
- if(scalematrix.postScale(mScale/scale,mScale/scale,mWidth/2,mHeight/2);
- setImageMatrix(matrix);
- }
- }
-
-
- privatefloatdownX;
- privatefloatdownY;
- privatefloatnowMovingX;
- privatefloatnowMovingY;
- privatefloatlastMovedX;
- privatefloatlastMovedY;
- privatebooleanisFirstMoved=false;
-
- @Override
- publicbooleanonTouch(Viewv,MotionEventevent){
- switch(event.getAction()&MotionEvent.ACTION_MASK){
- caseMotionEvent.ACTION_DOWN:
- isFirstMoved=false;
- downX=event.getX();
- downY=event.getY();
- break;
- caseMotionEvent.ACTION_POINTER_DOWN:
- isFirstMoved=false;
- break;
- caseMotionEvent.ACTION_MOVE:
- nowMovingX=event.getX();
- nowMovingY=event.getY();
- if(!isFirstMoved){
- isFirstMoved=true;
- lastMovedX=nowMovingX;
- lastMovedY=nowMovingY;
- }
- floatdX=0.0f;
- floatdY=0.0f;
- RectFrectf=getRectf(matrix);
- //判斷滑動方向
- finalfloatscrollX=nowMovingX-lastMovedX;
- //判斷滑動方向
- finalfloatscrollY=nowMovingY-lastMovedY;
- //圖片高度大於控件高度
- if(rectf.height()>mHeight&&canSmoothY()){
- dY=nowMovingY-lastMovedY;
- }
-
- //圖片寬度大於控件寬度
- if(rectf.width()>mWidth&&canSmoothX()){
- dX=nowMovingX-lastMovedX;
- }
- matrix.postTranslate(dX,dY);
-
- remedyXAndY(dX,dY);
-
- lastMovedX=nowMovingX;
- lastMovedY=nowMovingY;
- break;
- caseMotionEvent.ACTION_UP:
- break;
- caseMotionEvent.ACTION_POINTER_UP:
- isFirstMoved=false;
- break;
- }
- returnscaleGestureDetector.onTouchEvent(event);
- }
-
- /**
- *判斷x方向上能不能滑動
- *@return可以滑動返回true
- */
- privatebooleancanSmoothX(){
- RectFrectf=getRectf(matrix);
- if(rectf.left>0||rectf.rightreturnfalse;
- returntrue;
- }
-
- /**
- *判斷y方向上可不可以滑動
- *@return可以滑動返回true
- */
- privatebooleancanSmoothY(){
- RectFrectf=getRectf(matrix);
- if(rectf.top>0||rectf.bottomreturnfalse;
- returntrue;
- }
-
- /**
- *糾正出界的橫和眾線
- *@paramdx出界偏移的橫線
- *@paramdy出街便宜的眾線
- */
- privatevoidremedyXAndY(floatdx,floatdy){
- if(!canSmoothX())
- matrix.postTranslate(-dx,0);
- if(!canSmoothY())
- matrix.postTranslate(0,-dy);
- setImageMatrix(matrix);
- }
- }
activity的xml
-
- xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:tools="http://schemas.android.com/tools"
- android:layout_width="match_parent"
- android:layout_height="match_parent">
-
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:scaleType="matrix"
- android:src="@mipmap/tt1"/>
-