定义ImageView,实现功能如下:

1.初始化时图片垂直居中显示,拉伸图片宽度至ImageView宽度。

2.使用两根手指放大缩小图片,可设置最大放大倍数,当图片小于ImageView宽度时,在手指离开屏幕时恢复到ImageView宽度。

3.支持双击放大缩小。当图片处于未放大状态时,双击放大至指定倍数,当图片处于放大状态时,双击恢复至未放大状态。

4.图片拖动效果。当图片处于未放大状态时,不可拖动。

5.图片拖动效果。当放大后的高度不超过ImageView时,不可垂直拖动。(由于默认设置拉伸宽度至ImageView宽度,水平方向可不判断)。

6.图片拖动效果。当图片向右拖动时,若左边缘超出左边界,则停止水平拖动。同理上下右边缘,即拖动后不会看到背景留白。

首先讲一下原理,图片放大缩小无非就是使用Matrix类,而这里通过手势控制,那自然是需要监听onTouch事件,所以原理简单来说,就是通过监听onTouch的各种事件来控制Matrix类了。其中具体控制方式如下:

onTouch Matrix 辅助操作
ACTION_DOWN 记录初始点,设置本次模式为拖动模式,ScaleType设置成Matrix
ACTION_POINTER_DOWN 设置本次模式为缩放模式
ACTION_MOVE 根据模式执行postScale或postTranslate  
ACTION_UP 根据当前缩放级别决定是否重置Matrix  
双击 postScale  
ACTION_CANCEL 同UP  

现在开始,按功能点一一列代码说明:

首先自定义ImageView,由于和Matrix有关,取名为MatrixImageView.java,继承于ImageView.java

public class MatrixImageView extends ImageView{
private final static String TAG="MatrixImageView";
private GestureDetector mGestureDetector;
/** 模板Matrix,用以初始化 */
private Matrix mMatrix=new Matrix();
/** 图片长度*/
private float mImageWidth;
/** 图片高度 */
private float mImageHeight; public MatrixImageView(Context context, AttributeSet attrs) {
super(context, attrs);
MatrixTouchListener mListener=new MatrixTouchListener();
setOnTouchListener(mListener);
mGestureDetector=new GestureDetector(getContext(), new GestureListener(mListener));
//背景设置为balck
setBackgroundColor(Color.BLACK);
//将缩放类型设置为FIT_CENTER,表示把图片按比例扩大/缩小到View的宽度,居中显示
setScaleType(ScaleType.FIT_CENTER);
}

由于用到了双击缩放效果,在此引用了手势类 GestureDetector,GestureListener就是继承与SimpleOnGestureListener的手势监听类了。MatrixTouchListener继承与onTouchListner,是Touch事件监听的主体。在构造函数最后一句setScaleType(ScaleType.FIT_CENTER),便是满足功能一的要点。

接下去,重写setImageBitmap方法。

@Override
public void setImageBitmap(Bitmap bm) {
// TODO Auto-generated method stub
super.setImageBitmap(bm);
//设置完图片后,获取该图片的坐标变换矩阵
mMatrix.set(getImageMatrix());
float[] values=new float[9];
mMatrix.getValues(values);
//图片宽度为屏幕宽度除缩放倍数
mImageWidth=getWidth()/values[Matrix.MSCALE_X];
mImageHeight=(getHeight()-values[Matrix.MTRANS_Y]*2)/values[Matrix.MSCALE_Y];
}

在此是为了初始化几个重要的变量:

mMatrix:图片的原始Matrix,记录下来作为模板,之后的变化都是在这个Matrix的基础上进行
mImageWidth:图片的真实宽度,注意这个宽度是指图片在ImageView中的真实宽度,非显示宽度也非文件宽度。当我们把图片放入ImageView中时,会根据ImageView的宽高进行一个转换,转换结果记录在Matrix中。我们根据显示宽度与Matrix进行计算获得真实宽度。
mImageHeight:同宽度。

三种宽度的区别(非官方叫法,只为理解)

显示宽度:ImageView中的图片(Bitmap、Drawable)在ImageView中显示的高度,是通过Matrix计算之后的宽度。当放大图片时,这个宽度可能超过ImageView的宽度。
真实宽度:ImageView中的图片在Matrix计算之前的宽度。当ImageView宽为390,图片X轴缩放级别为0.5,一个填充满ImageView的X轴的图片的真实宽度为780。这个宽度和ImageView的ScaleType相关。
文件宽度:文件X轴分辨率,不一定等于真实宽度。

之所以在sitImageView中进行设置,是因为每次ImageView更换图片后这些变量都会跟着改变,我们只需要和当前图片相关的这些变量。若希望在layout中设置图片而不是代码中设置,请把上述代码移至构造函数。

接下来是重点的onTouch事件:

public class MatrixTouchListener implements OnTouchListener{
/** 拖拉照片模式 */
private static final int MODE_DRAG = 1;
/** 放大缩小照片模式 */
private static final int MODE_ZOOM = 2;
/** 不支持Matrix */
private static final int MODE_UNABLE=3;
/** 最大缩放级别*/
float mMaxScale=6;
/** 双击时的缩放级别*/
float mDobleClickScale=2;
private int mMode = 0;//
/** 缩放开始时的手指间距 */
private float mStartDis;
/** 当前Matrix*/
private Matrix mCurrentMatrix = new Matrix(); /** 用于记录开始时候的坐标位置 */
private PointF startPoint = new PointF();
@Override
public boolean onTouch(View v, MotionEvent event) {
// TODO Auto-generated method stub
switch (event.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
//设置拖动模式
mMode=MODE_DRAG;
startPoint.set(event.getX(), event.getY());
// isMatrixEnable();
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
reSetMatrix();
break;
case MotionEvent.ACTION_MOVE:
if (mMode == MODE_ZOOM) {
setZoomMatrix(event);
}else if (mMode==MODE_DRAG) {
setDragMatrix(event);
}
break;
case MotionEvent.ACTION_POINTER_DOWN:
if(mMode==MODE_UNABLE) return true;
mMode=MODE_ZOOM;
mStartDis = distance(event);
break;
default:
break;
} return mGestureDetector.onTouchEvent(event);
}
/**
* 计算两个手指间的距离
* @param event
* @return
*/
private float distance(MotionEvent event) {
float dx = event.getX(1) - event.getX(0);
float dy = event.getY(1) - event.getY(0);
/** 使用勾股定理返回两点之间的距离 */
return (float) Math.sqrt(dx * dx + dy * dy);
}

首先可以看到,在ACTION_DOWN和ACTION_POINTER_DOWN中主要是进行当前事件模式的确定。当我们按下一个点时,会触发Down事件,而按下第二个点后,又会触发Action_Pointer_Down事件,在此我们把按下一个点标记为拖动事件,按下两个点标记为缩放事件。先跟踪缩放事件方法setZoomMatrix(event);

/**
* 设置缩放Matrix
* @param event
*/
private void setZoomMatrix(MotionEvent event) {
//只有同时触屏两个点的时候才执行
if(event.getPointerCount()<2) return;
float endDis = distance(event);// 结束距离
if (endDis > 10f) { // 两个手指并拢在一起的时候像素大于10
float scale = endDis / mStartDis;// 得到缩放倍数
mStartDis=endDis;//重置距离
mCurrentMatrix.set(getImageMatrix());//初始化Matrix
float[] values=new float[9];
mCurrentMatrix.getValues(values); scale = checkMaxScale(scale, values);
setImageMatrix(mCurrentMatrix);
}
}

首先我们判断是否点击了两个点,如果不是直接返回。接着,使用distance方法计算出两个点之间的距离。之前在Action_Pointer_Down中已经计算出了初始距离,这里计算出的是移动后的两个点的距离。通过这两个距离我们可以得出本次移动的缩放倍数,但这还没完,我们需要验证这个缩放倍数是否越界了:

/**
* 检验scale,使图像缩放后不会超出最大倍数
* @param scale
* @param values
* @return
*/
private float checkMaxScale(float scale, float[] values) {
if(scale*values[Matrix.MSCALE_X]>mMaxScale)
scale=mMaxScale/values[Matrix.MSCALE_X];
mCurrentMatrix.postScale(scale, scale,getWidth()/2,getHeight()/2);
return scale;
}

我们获取了Matrix矩阵中保存的数组,在这个数组中,values[Matrix.MSCALE_X](事实上就是数组的第0个)代表了X轴的缩放级别,判断一下图片当前的缩放级别再乘以刚得到的scale后是否回去越界,会的话就将其控制在边界值。之后,以ImageView的中心点为原点,在当前Matrix的基础上进行指定倍数的缩放。

在Move事件中缩放时我们只会阻止超越最大值的缩放,在UP事件中我们会对小于原始缩放值的缩放进行重置。方法reSetMatrix如下。

/**
* 重置Matrix
*/
private void reSetMatrix() {
if(checkRest()){
mCurrentMatrix.set(mMatrix);
setImageMatrix(mCurrentMatrix);
}
}
/**
* 判断是否需要重置
* @return 当前缩放级别小于模板缩放级别时,重置
*/
private boolean checkRest() {
// TODO Auto-generated method stub
float[] values=new float[9];
getImageMatrix().getValues(values);
//获取当前X轴缩放级别
float scale=values[Matrix.MSCALE_X];
//获取模板的X轴缩放级别,两者做比较
mMatrix.getValues(values);
return scale<values[Matrix.MSCALE_X];
}

首先获取当前X轴缩放级别(由于默认拉伸宽度至ImageView宽度,缩放级别以X轴为准),再通过模板Matrix得到原始的X轴缩放级别,判断当前缩放级别是否小于模板缩放级别,若小于,则重置成模板缩放级别。

接下去是双击放大缩小图片效果,该功能在手势接口GestureListener中完成,主要代码如下:

    private class  GestureListener extends SimpleOnGestureListener{
private final MatrixTouchListener listener;
public GestureListener(MatrixTouchListener listener) {
this.listener=listener;
}
@Override
public boolean onDown(MotionEvent e) {
//捕获Down事件
return true;
}
@Override
public boolean onDoubleTap(MotionEvent e) {
//触发双击事件
listener.onDoubleClick();
return true;
}
/**
* 双击时触发
*/
public void onDoubleClick(){
float scale=isZoomChanged()?1:mDobleClickScale;
mCurrentMatrix.set(mMatrix);//初始化Matrix
mCurrentMatrix.postScale(scale, scale,getWidth()/2,getHeight()/2);
setImageMatrix(mCurrentMatrix);
}
/**
* 判断缩放级别是否是改变过
* @return true表示非初始值,false表示初始值
*/
private boolean isZoomChanged() {
float[] values=new float[9];
getImageMatrix().getValues(values);
//获取当前X轴缩放级别
float scale=values[Matrix.MSCALE_X];
//获取模板的X轴缩放级别,两者做比较
mMatrix.getValues(values);
return scale!=values[Matrix.MSCALE_X];
}

在构造函数中将onTouchListner传递来进来。在此只重写两个方法:Down和onDoubleTap,只有在Down事件中返回true,onDoubleTap才能正常触发。

在onDoubleClick事件中,首先通过isZoomChanged方法判断当前的缩放级别是否是模板Matrix的缩放级别,是的话将缩放倍数设置为2倍,否的话设置成1倍。在载入模板Matrix,在此基础上做缩放。

最后是图片拖动效果setDragMatrix()

public void setDragMatrix(MotionEvent event) {
if(isZoomChanged()){
float dx = event.getX() - startPoint.x; // 得到x轴的移动距离
float dy = event.getY() - startPoint.y; // 得到x轴的移动距离
//避免和双击冲突,大于10f才算是拖动
if(Math.sqrt(dx*dx+dy*dy)>10f){
startPoint.set(event.getX(), event.getY());
//在当前基础上移动
mCurrentMatrix.set(getImageMatrix());
float[] values=new float[9];
mCurrentMatrix.getValues(values);
dx=checkDxBound(values,dx);
dy=checkDyBound(values,dy);
mCurrentMatrix.postTranslate(dx, dy);
setImageMatrix(mCurrentMatrix);
}
}
}

首先是通过isZoomChanged方法判断是否缩放过,若未缩放过则不可拖动(这种情况下图片全貌都可以看到,不需要拖动)。接着,拿当前坐标和按下时记录的startPoint坐标进行计算,得出拖动的距离。需要注意的是,在此需要对拖动距离做一个判断,当其小于10f时不进行拖动,否则会和双击事件冲突(在双击事件前同样会触发Move事件,两者一同执行的话,双击的缩放无法正常工作)。当确定开始拖动的之后,先重置startPoint的坐标,接着,开始验证当前移动的位移量是否合法。

/**
*和当前矩阵对比,检验dx,使图像移动后不会超出ImageView边界
* @param values
* @param dx
* @return
*/
private float checkDxBound(float[] values,float dx) {
float width=getWidth();
if(mImageWidth*values[Matrix.MSCALE_X]<width)
return 0;
if(values[Matrix.MTRANS_X]+dx>0)
dx=-values[Matrix.MTRANS_X];
else if(values[Matrix.MTRANS_X]+dx<-(mImageWidth*values[Matrix.MSCALE_X]-width))
dx=-(mImageWidth*values[Matrix.MSCALE_X]-width)-values[Matrix.MTRANS_X];
return dx;
} /**
* 和当前矩阵对比,检验dy,使图像移动后不会超出ImageView边界
* @param values
* @param dy
* @return
*/
private float checkDyBound(float[] values, float dy) {
float height=getHeight();
if(mImageHeight*values[Matrix.MSCALE_Y]<height)
return 0;
if(values[Matrix.MTRANS_Y]+dy>0)
dy=-values[Matrix.MTRANS_Y];
else if(values[Matrix.MTRANS_Y]+dy<-(mImageHeight*values[Matrix.MSCALE_Y]-height))
dy=-(mImageHeight*values[Matrix.MSCALE_Y]-height)-values[Matrix.MTRANS_Y];
return dy;
}

以Y轴为例,首先获取ImageView高度,再通过sitImageBitmap方法中获取的图片真实高度和当前Y轴缩放级别计算出当前Y轴的显示高度。如果显示高度小于ImageView高度,表示当前显示的图片还没有ImageView高,在Y轴不需要移动都可看清全貌,Y轴位移量直接返回0。

假如显示高度超过了ImageView高度,获取图片当前在Y轴的位移量(values[Matrix.MTRANS_Y]值),将其加上计算出的位移量后是否大于0,若大于0,表示图片上边缘将会离开ImageView上边缘,需要重新计算位移量。若上述条件不成立,判断当前位移量加上计算后的位移量,是否小于图片显示高度-屏幕高度,若小于表示图片下边缘将离开ImageView下边缘,同样需要重新计算。最后返回计算的Y轴偏移量。X轴同理。最后使用验证过的X、Y轴偏移量,在当前图片Matrix的基础上行进行偏移。

最后贴下完整代码,Demo的话,可以到我的照相机Demo里查看,该功能为相册功能的一部分。

package com.linj.album.view;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.PointF;
import android.util.AttributeSet;
import android.util.Log;
import android.view.GestureDetector;
import android.view.GestureDetector.SimpleOnGestureListener;
import android.view.MotionEvent;
import android.view.View;
import android.widget.ImageView; /**
* @ClassName: MatrixImageView
* @Description: 带放大、缩小、移动效果的ImageView
* @author LinJ
* @date 2015-1-7 上午11:15:07
*
*/
public class MatrixImageView extends ImageView{
private final static String TAG="MatrixImageView";
private GestureDetector mGestureDetector;
/** 模板Matrix,用以初始化 */
private Matrix mMatrix=new Matrix();
/** 图片长度*/
private float mImageWidth;
/** 图片高度 */
private float mImageHeight; public MatrixImageView(Context context, AttributeSet attrs) {
super(context, attrs);
MatrixTouchListener mListener=new MatrixTouchListener();
setOnTouchListener(mListener);
mGestureDetector=new GestureDetector(getContext(), new GestureListener(mListener));
//背景设置为balck
setBackgroundColor(Color.BLACK);
//将缩放类型设置为FIT_CENTER,表示把图片按比例扩大/缩小到View的宽度,居中显示
setScaleType(ScaleType.FIT_CENTER);
} @Override
public void setImageBitmap(Bitmap bm) {
// TODO Auto-generated method stub
super.setImageBitmap(bm);
//设置完图片后,获取该图片的坐标变换矩阵
mMatrix.set(getImageMatrix());
float[] values=new float[9];
mMatrix.getValues(values);
//图片宽度为屏幕宽度除缩放倍数
mImageWidth=getWidth()/values[Matrix.MSCALE_X];
mImageHeight=(getHeight()-values[Matrix.MTRANS_Y]*2)/values[Matrix.MSCALE_Y];
} public class MatrixTouchListener implements OnTouchListener{
/** 拖拉照片模式 */
private static final int MODE_DRAG = 1;
/** 放大缩小照片模式 */
private static final int MODE_ZOOM = 2;
/** 不支持Matrix */
private static final int MODE_UNABLE=3;
/** 最大缩放级别*/
float mMaxScale=6;
/** 双击时的缩放级别*/
float mDobleClickScale=2;
private int mMode = 0;//
/** 缩放开始时的手指间距 */
private float mStartDis;
/** 当前Matrix*/
private Matrix mCurrentMatrix = new Matrix(); /** 用于记录开始时候的坐标位置 */
private PointF startPoint = new PointF();
@Override
public boolean onTouch(View v, MotionEvent event) {
// TODO Auto-generated method stub
switch (event.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
//设置拖动模式
mMode=MODE_DRAG;
startPoint.set(event.getX(), event.getY());
isMatrixEnable();
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
reSetMatrix();
break;
case MotionEvent.ACTION_MOVE:
if (mMode == MODE_ZOOM) {
setZoomMatrix(event);
}else if (mMode==MODE_DRAG) {
setDragMatrix(event);
}
break;
case MotionEvent.ACTION_POINTER_DOWN:
if(mMode==MODE_UNABLE) return true;
mMode=MODE_ZOOM;
mStartDis = distance(event);
break;
default:
break;
} return mGestureDetector.onTouchEvent(event);
} public void setDragMatrix(MotionEvent event) {
if(isZoomChanged()){
float dx = event.getX() - startPoint.x; // 得到x轴的移动距离
float dy = event.getY() - startPoint.y; // 得到x轴的移动距离
//避免和双击冲突,大于10f才算是拖动
if(Math.sqrt(dx*dx+dy*dy)>10f){
startPoint.set(event.getX(), event.getY());
//在当前基础上移动
mCurrentMatrix.set(getImageMatrix());
float[] values=new float[9];
mCurrentMatrix.getValues(values);
dx=checkDxBound(values,dx);
dy=checkDyBound(values,dy);
mCurrentMatrix.postTranslate(dx, dy);
setImageMatrix(mCurrentMatrix);
}
}
} /**
* 判断缩放级别是否是改变过
* @return true表示非初始值,false表示初始值
*/
private boolean isZoomChanged() {
float[] values=new float[9];
getImageMatrix().getValues(values);
//获取当前X轴缩放级别
float scale=values[Matrix.MSCALE_X];
//获取模板的X轴缩放级别,两者做比较
mMatrix.getValues(values);
return scale!=values[Matrix.MSCALE_X];
} /**
* 和当前矩阵对比,检验dy,使图像移动后不会超出ImageView边界
* @param values
* @param dy
* @return
*/
private float checkDyBound(float[] values, float dy) {
float height=getHeight();
if(mImageHeight*values[Matrix.MSCALE_Y]<height)
return 0;
if(values[Matrix.MTRANS_Y]+dy>0)
dy=-values[Matrix.MTRANS_Y];
else if(values[Matrix.MTRANS_Y]+dy<-(mImageHeight*values[Matrix.MSCALE_Y]-height))
dy=-(mImageHeight*values[Matrix.MSCALE_Y]-height)-values[Matrix.MTRANS_Y];
return dy;
} /**
*和当前矩阵对比,检验dx,使图像移动后不会超出ImageView边界
* @param values
* @param dx
* @return
*/
private float checkDxBound(float[] values,float dx) {
float width=getWidth();
if(mImageWidth*values[Matrix.MSCALE_X]<width)
return 0;
if(values[Matrix.MTRANS_X]+dx>0)
dx=-values[Matrix.MTRANS_X];
else if(values[Matrix.MTRANS_X]+dx<-(mImageWidth*values[Matrix.MSCALE_X]-width))
dx=-(mImageWidth*values[Matrix.MSCALE_X]-width)-values[Matrix.MTRANS_X];
return dx;
} /**
* 设置缩放Matrix
* @param event
*/
private void setZoomMatrix(MotionEvent event) {
//只有同时触屏两个点的时候才执行
if(event.getPointerCount()<2) return;
float endDis = distance(event);// 结束距离
if (endDis > 10f) { // 两个手指并拢在一起的时候像素大于10
float scale = endDis / mStartDis;// 得到缩放倍数
mStartDis=endDis;//重置距离
mCurrentMatrix.set(getImageMatrix());//初始化Matrix
float[] values=new float[9];
mCurrentMatrix.getValues(values); scale = checkMaxScale(scale, values);
setImageMatrix(mCurrentMatrix);
}
} /**
* 检验scale,使图像缩放后不会超出最大倍数
* @param scale
* @param values
* @return
*/
private float checkMaxScale(float scale, float[] values) {
if(scale*values[Matrix.MSCALE_X]>mMaxScale)
scale=mMaxScale/values[Matrix.MSCALE_X];
mCurrentMatrix.postScale(scale, scale,getWidth()/2,getHeight()/2);
return scale;
} /**
* 重置Matrix
*/
private void reSetMatrix() {
if(checkRest()){
mCurrentMatrix.set(mMatrix);
setImageMatrix(mCurrentMatrix);
}
} /**
* 判断是否需要重置
* @return 当前缩放级别小于模板缩放级别时,重置
*/
private boolean checkRest() {
// TODO Auto-generated method stub
float[] values=new float[9];
getImageMatrix().getValues(values);
//获取当前X轴缩放级别
float scale=values[Matrix.MSCALE_X];
//获取模板的X轴缩放级别,两者做比较
mMatrix.getValues(values);
return scale<values[Matrix.MSCALE_X];
} /**
* 判断是否支持Matrix
*/
private void isMatrixEnable() {
//当加载出错时,不可缩放
if(getScaleType()!=ScaleType.CENTER){
setScaleType(ScaleType.MATRIX);
}else {
mMode=MODE_UNABLE;//设置为不支持手势
}
} /**
* 计算两个手指间的距离
* @param event
* @return
*/
private float distance(MotionEvent event) {
float dx = event.getX(1) - event.getX(0);
float dy = event.getY(1) - event.getY(0);
/** 使用勾股定理返回两点之间的距离 */
return (float) Math.sqrt(dx * dx + dy * dy);
} /**
* 双击时触发
*/
public void onDoubleClick(){
float scale=isZoomChanged()?1:mDobleClickScale;
mCurrentMatrix.set(mMatrix);//初始化Matrix
mCurrentMatrix.postScale(scale, scale,getWidth()/2,getHeight()/2);
setImageMatrix(mCurrentMatrix);
}
} private class GestureListener extends SimpleOnGestureListener{
private final MatrixTouchListener listener;
public GestureListener(MatrixTouchListener listener) {
this.listener=listener;
}
@Override
public boolean onDown(MotionEvent e) {
//捕获Down事件
return true;
}
@Override
public boolean onDoubleTap(MotionEvent e) {
//触发双击事件
listener.onDoubleClick();
return true;
}
@Override
public boolean onSingleTapUp(MotionEvent e) {
// TODO Auto-generated method stub
return super.onSingleTapUp(e);
} @Override
public void onLongPress(MotionEvent e) {
// TODO Auto-generated method stub
super.onLongPress(e);
} @Override
public boolean onScroll(MotionEvent e1, MotionEvent e2,
float distanceX, float distanceY) {
return super.onScroll(e1, e2, distanceX, distanceY);
} @Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
float velocityY) {
// TODO Auto-generated method stub return super.onFling(e1, e2, velocityX, velocityY);
} @Override
public void onShowPress(MotionEvent e) {
// TODO Auto-generated method stub
super.onShowPress(e);
} @Override
public boolean onDoubleTapEvent(MotionEvent e) {
// TODO Auto-generated method stub
return super.onDoubleTapEvent(e);
} @Override
public boolean onSingleTapConfirmed(MotionEvent e) {
// TODO Auto-generated method stub
return super.onSingleTapConfirmed(e);
} } }

注:当该ImageView在其他ViewGroup中,如常用的ViewPager中时,Move事件会和ViewGroup事件冲突。在划屏进行ViewPager的Item切换时,Viewpager将会通过onInterceptTouchEvent方法拦截掉,返回给ImageView一个Cancel事件,这种情况下需要重写ViewGroup的onInterceptTouchEvent方法。

Android:手把手教你打造可缩放移动的ImageView(上)的更多相关文章

  1. Android:手把手教你打造可缩放移动的ImageView(下)

    在上一篇Android:手把手教你打造可缩放移动的ImageView最后提出了一个注意点:当自定义的MatrixImageView如ViewPager.ListView等带有滑动效果的ViewGrou ...

  2. Android 手势检测实战 打造支持缩放平移的图片预览效果(下)

    转载请标明出处:http://blog.csdn.net/lmj623565791/article/details/39480503,本文出自:[张鸿洋的博客] 上一篇已经带大家实现了自由的放大缩小图 ...

  3. Android 进阶 教你打造 Android 中的 IOC 框架 【ViewInject】 (下)

    上一篇博客我们已经带大家简单的吹了一下IoC,实现了Activity中View的布局以及控件的注入,如果你不了解,请参考:Android 进阶 教你打造 Android 中的 IOC 框架 [View ...

  4. 手把手教你打造一个心电图效果View Android自定义View

    大家好,看我像不像蘑菇-因为我在学校呆的发霉了. 思而不学则殆 丽丽说得对,我有奇怪的疑问,大都是思而不学造成的,在我书读不够的情况下想太多,大多等于白想,所以革命没成功,同志仍需努力. 好了废话不说 ...

  5. Android 进阶 教你打造 Android 中的 IOC 框架 【ViewInject】 (上)

    转载请标明出处:http://blog.csdn.net/lmj623565791/article/details/39269193,本文出自:[张鸿洋的博客] 1.概述 首先我们来吹吹牛,什么叫Io ...

  6. 手把手教你打造ImageView支持手势放大缩小

    写在前面 最近有了新的任务,学习的时间比以前少了不少,Java回炉的文估计是得缓缓了,不过每周一篇尽量保质保量.最近感觉我文写的有点不好,因为我写东西除非必要,不然概念性的东西我基本上都是一笔带过…… ...

  7. 手把手教你打造一个纯CSS图标库

    来,干了这碗安利 写这篇文章的目的其实就是为了安利一下我的图标库:iconoo,所以,开门见山,star吧少年少妇们!(这样的我是不是应该要加个github互粉的团伙了?) 主题说完了,下面进入正题. ...

  8. android 手把手教您自定义ViewGroup(一)

    1.概述 在写代码之前,我必须得问几个问题: 1.ViewGroup的职责是啥? ViewGroup相当于一个放置View的容器,并且我们在写布局xml的时候,会告诉容器(凡是以layout为开头的属 ...

  9. 手把手教你打造高效的 Kubernetes 命令行终端

    Kubernetes 作为云原生时代的操作系统,熟悉和使用它是每名用户的必备技能.本文将介绍一些提高操作 Kubernetes 效率的技巧以及如何打造一个高效的 Kubernetes 命令行终端的方法 ...

随机推荐

  1. java.lang.AbstractMethodError: com.microsoft.jdbc.base.BaseDatabaseMetaData.supportsGetGeneratedKeys()Z

    解决:问谷老师得知是microsoft提供的数据库驱动存在bug.需要换一种驱动连接,使用jtds(下载地址:http://sourceforge.net/projects/jtds/files/)下 ...

  2. js中声明函数的方法

    <!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8" ...

  3. moco入门

    前提:moco是什么?有什么用 Moco是针对HTTP集成而生的,不过,现在也有人把它用在其它需要一个模拟服务器的场景中.比如,在移动开发中,有人开发一个移动应用,需要有一个远端服务,但在开发时,这个 ...

  4. Linux下tar.gz 安装

    将安装文件拷贝至你的目录中 如果是以root身份登录上的,就将软件拷贝至/root中. cp xxx.tar.gz /root 解压缩包 tar xvzf xxx.tar.gz 切换到安装目录下 cd ...

  5. Control(拆点+最大流)

    Control http://acm.hdu.edu.cn/showproblem.php?pid=4289 Time Limit: 2000/1000 MS (Java/Others)    Mem ...

  6. 最小重组缓冲区和路径MTU发现

    概括: 主要来源于unp,可参考:http://blog.csdn.net/ysu108/article/details/7764461 疑惑: 1. 最小重组缓冲区大小: ipv4为576,ipv6 ...

  7. mybatis sql语句符号问题

    写sql语句<或>不能直接写,而应写作<或>,不然项目不能正常编译启动

  8. 【JDK1.8】JUC——ReentrantLock

    一.前言 在之前的几篇中,我们回顾了锁框架中比较重要的几个类,他们为实现同步提供了基础支持,从现在开始到后面,就开始利用之前的几个类来进行各种锁的具体实现.今天来一起看下ReentrantLock,首 ...

  9. 观察者模式 DataObserver

    DatasetObserver是Observer的一个子类 针对于adapter设计的 当调用notifydatasetchanged的时候就会触发回调的方法 adapter.registerObse ...

  10. 我对于UI设计这个领域的理解

    User Interface(UI),包括三部分用户.界面以及用户与界面之间的交互关系.UI设计则是指对软件的人机交互.操作逻辑.界面美观的整体设计. 如何看待UI设计这个领域? 任何一个行业的出现都 ...