在这里,我对自己的笔记本全屏截图,然后当作自定义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();
- }
- });
- }
- /**
- *初始化资源图片宽高
- */
- privatevoidinitImageViewSize(){
- if(mDrawable==null)
- return;
- //缩放值
- floatscale=1.0f;
- //图片宽度大于控件宽度,图片高度小于控件高度
- if(mDrawableWidth>mWidth&&mDrawableHeight<mheight)
- scale=mWidth*1.0f/mDrawableWidth;
- //图片高度度大于控件宽高,图片宽度小于控件宽度
- elseif(mDrawableHeight>mHeight&&mDrawableWidth<mwidth)
- scale=mHeight*1.0f/mDrawableHeight;
- //图片宽度大于控件宽度,图片高度大于控件高度
- elseif(mDrawableHeight>mHeight&&mDrawableWidth>mWidth)
- scale=Math.min(mHeight*1.0f/mDrawableHeight,mWidth*1.0f/mDrawableWidth);
- //图片宽度小于控件宽度,图片高度小于控件高度
- elseif(mDrawableHeight<mheight&&mdrawablewidth<mwidth)
- 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 plain copy
- @Override
- publicbooleanonScale(ScaleGestureDetectordetector){
- returntrue;
- }
- @Override
- publicbooleanonScaleBegin(ScaleGestureDetectordetector){
- returntrue;
- }
- @Override
- publicvoidonScaleEnd(ScaleGestureDetectordetector){
- }
- setOnTouchListener(this);
- @Override
- publicbooleanonTouch(Viewv,MotionEventevent){
- returnscaleGestureDetector.onTouchEvent(event);
- }
然后手势生效了(呵呵哒 )。
- @Override
- publicbooleanonScaleBegin(ScaleGestureDetectordetector){
- returntrue;
- }
现在重点说一下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);
- }
- /**
- *@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;
- }
在将上面的/。。。补上
- RectFf=getRectf(matrix);
- floatdX=0.0f;
- floatdY=0.0f;
- //图片高度大于控件高度
- if(f.height()>=mHeight){
- //图片顶部出现空白
- if(f.top>0){
- //往上移动
- dY=-f.top;
- }
- //图片底部出现空白
- if(f.bottom<mheight){
- //往下移动
- dY=mHeight-f.bottom;
- }
- }
- //图片宽度大于控件宽度
- if(f.width()>=mWidth){
- //图片左边出现空白
- if(f.left>0){
- //往左边移动
- dX=-f.left;
- }
- //图片右边出现空白
- if(f.right<mwidth){
- //往右边移动
- dX=mWidth-f.right;
- }
- }
- if(f.width()<mwidth){
- dX=mWidth/2-f.right+f.width()/2;
- }
- if(f.height()<mheight){
- dY=mHeight/2-f.bottom+f.height()/2;
- }
- matrix.postTranslate(dX,dY);
- setImageMatrix(matrix);
首先获取矩阵matrix的N维并赋值在f身上。 [java] view plain copy
- //图片高度大于控件高度
- if(f.height()>=mHeight){
- //图片顶部出现空白
- if(f.top>0){
- //往上移动
- dY=-f.top;
- }
- //图片底部出现空白
- if(f.bottom<mheight){
- //往下移动
- dY=mHeight-f.bottom;
- }
- }
大概就是这个意思:当图片高度大于等于控件高度的时候,坚决不让控件高度方向上出现白色位置,此时,假设当图片和控件高度完全相同的时候,是不是图片的纵向刚好和控件完全重叠呢? [java] view plain copy
- if(f.height()<mheight){
- dY=mHeight/2-f.bottom+f.height()/2;
- }
[java] view plain copy
- 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.right<getwidth())
- returnfalse;
- returntrue;
- }
- /**
- *判断y方向上可不可以滑动
- *@return可以滑动返回true
- */
- privatebooleancanSmoothY(){
- RectFrectf=getRectf(matrix);
- if(rectf.top>0||rectf.bottom<getheight())
- returnfalse;
- returntrue;
- }
- /**
- *纠正出界的横和众线
- *@paramdx出界偏移的横线
- *@paramdy出街便宜的众线
- */
- privatevoidremedyXAndY(floatdx,floatdy){
- if(!canSmoothX())
- matrix.postTranslate(-dx,0);
- if(!canSmoothY())
- matrix.postTranslate(0,-dy);
- setImageMatrix(matrix);
- }
完整的自定义控件代码:
- 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&&mDrawableHeight<mheight)
- scale=mWidth*1.0f/mDrawableWidth;
- //图片高度度大于控件宽高,图片宽度小于控件宽度
- elseif(mDrawableHeight>mHeight&&mDrawableWidth<mwidth)
- scale=mHeight*1.0f/mDrawableHeight;
- //图片宽度大于控件宽度,图片高度大于控件高度
- elseif(mDrawableHeight>mHeight&&mDrawableWidth>mWidth)
- scale=Math.min(mHeight*1.0f/mDrawableHeight,mWidth*1.0f/mDrawableWidth);
- //图片宽度小于控件宽度,图片高度小于控件高度
- elseif(mDrawableHeight<mheight&&mdrawablewidth<mwidth)
- scale=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<mheight){
- //往下移动
- dY=mHeight-f.bottom;
- }
- }
- //图片宽度大于控件宽度
- if(f.width()>=mWidth){
- //图片左边出现空白
- if(f.left>0){
- //往左边移动
- dX=-f.left;
- }
- //图片右边出现空白
- if(f.right<mwidth){
- //往右边移动
- dX=mWidth-f.right;
- }
- }
- if(f.width()<mwidth){
- dX=mWidth/2-f.right+f.width()/2;
- }
- if(f.height()<mheight){
- 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(scale<mscale){
- matrix.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.right<getwidth())
- returnfalse;
- returntrue;
- }
- /**
- *判断y方向上可不可以滑动
- *@return可以滑动返回true
- */
- privatebooleancanSmoothY(){
- RectFrectf=getRectf(matrix);
- if(rectf.top>0||rectf.bottom<getheight())
- returnfalse;
- returntrue;
- }
- /**
- *纠正出界的横和众线
- *@paramdx出界偏移的横线
- *@paramdy出街便宜的众线
- */
- privatevoidremedyXAndY(floatdx,floatdy){
- if(!canSmoothX())
- matrix.postTranslate(-dx,0);
- if(!canSmoothY())
- matrix.postTranslate(0,-dy);
- setImageMatrix(matrix);
- }
- }
activity的xml
- <relativelayout
- 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">
- <com.test.gesturedemo.view.myimageview
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:scaleType="matrix"
- android:src="@mipmap/tt1"/>