ImageView图片拖拽缩放控件
1、在父控件为Viewpager的背景实现,所以会处理图片左右滑动和Viewpager滑动 的冲突
package com.example.widget; 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.widget.ImageView; public class ImageControl extends ImageView { private String tag = getClass().getSimpleName(); /** 用于记录开始时候的坐标位置 */ private PointF startPoint = new PointF(); private static final int MODE_DRAG = 1; /** 放大缩小照片模式 */ private static final int MODE_ZOOM = 2; /** 不支持Matrix */ private static final int MODE_UNABLE = 3; private static final int MODE_EXZOOM= 4; private int mMode = 0;// /** 缩放开始时的手指间距 */ private float mStartDis; /** 当前Matrix */ private Matrix mCurrentMatrix = new Matrix(); private float[] mCurrentValues=new float[9]; private Matrix mMatrix = new Matrix(); private float MAX_ZOOM; // 这里希望双指控制不要缩小,但是Min_ZOOM!=1,因为图片可能本身就被放大过以填充控件 private float MIN_ZOOM; private float DEFAULT_ZOOM; /** 位图对象 */ private Bitmap mBitmap = null; /** 图片长度 */ private float mImageWidth; /** 图片高度 */ private float mImageHeight; private ImageListener listener; private float mDobleClickScale; private float[] mValues; PointF mid = new PointF(); private XViewPager pager; public ImageControl(Context context) { super(context); init(); } public ImageControl(Context context, AttributeSet attrs) { super(context, attrs); init(); } public ImageControl(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); // TODO Auto-generated constructor stub init(); } GestureDetector mGestureDetector; private void init() { // 背景设置为balck setBackgroundColor(Color.BLACK); // 将缩放类型设置为MATRIX,表示控件内按原图原始大小在左上角(0,0)置放 mGestureDetector = new GestureDetector(getContext(), new GestureListener()); } @Override public void setImageBitmap(Bitmap bm) { // TODO Auto-generated method stub // setimageBitmap在2次onMeasure之后 setScaleType(ScaleType.MATRIX); super.setImageBitmap(bm); // 这个判断很有必要,在activiy destroy时会setImageBitmap(null) if (bm == null) { return; } mBitmap = bm; // 图片原生尺寸 mImageWidth = mBitmap.getWidth(); mImageHeight = mBitmap.getHeight(); fitScreen(); // matrixA=getImageMatrix();表示matrixA指向当前的matrix对象,当getImageMatrix变化时matrixA也变了 // matrixA.set(getImageMatrix());表示对此时的matrix的状态copy一份保存下来,无论getImageMatrix怎么变 mMatrix.set(getImageMatrix()); mValues = new float[9]; mMatrix.getValues(mValues); MIN_ZOOM = mValues[Matrix.MSCALE_X]*2/3; DEFAULT_ZOOM=mValues[Matrix.MSCALE_X]; mDobleClickScale = mValues[Matrix.MSCALE_X] * 2; MAX_ZOOM = mValues[Matrix.MSCALE_X] * 3; } @Override public void setImageResource(int resId) { // TODO Auto-generated method stub Log.i(tag, "setImageResource"); setScaleType(ScaleType.FIT_CENTER); super.setImageResource(resId); } private void fitScreen() { // TODO Auto-generated method stub float scaleX = (float) getWidth() / (float) mImageWidth; mCurrentMatrix.set(getImageMatrix()); mCurrentMatrix.postScale(scaleX, scaleX); // 图片与下边界 的距离 float marginY = getHeight() - scaleX * mImageHeight; Log.i(tag, "空余纵向边界marginY=" + marginY); mCurrentMatrix.getValues(mCurrentValues); if (marginY > 0) { // 说明纵向铺不满 mCurrentMatrix.postTranslate(0, (marginY-mCurrentValues[Matrix.MTRANS_Y]) / 2f); }else { mCurrentMatrix.postTranslate(0, marginY); } // Matrix是从(0,0)左上角开始做的变换 setImageMatrix(mCurrentMatrix); } @Override public boolean onTouchEvent(MotionEvent event) { /** 处理单点、多点触摸 **/ switch (event.getAction() & MotionEvent.ACTION_MASK) { // 按下事件 case MotionEvent.ACTION_DOWN: // 设置拖动模式 mMode = MODE_DRAG; startPoint.set(event.getX(), event.getY()); mid.set(getWidth() / 2, getHeight() / 2); break; // 多点触摸 case MotionEvent.ACTION_POINTER_DOWN: if (mMode == MODE_UNABLE) return true; mMode = MODE_ZOOM; pager.willIntercept=false; mStartDis = getDistance(event); midPoint(mid, event); break; case MotionEvent.ACTION_MOVE: if (mMode == MODE_ZOOM) { float endDis = getDistance(event); if (endDis > 10f) { // 两个手指并拢在一起的时候像素大于10 float scale = endDis / mStartDis;// 得到缩放倍数 setZoom(scale); mStartDis = endDis;// 重置距离 } } else if (mMode == MODE_DRAG) { float dx = event.getX() - startPoint.x; // 得到x轴的移动距离 float dy = event.getY() - startPoint.y; // 得到y轴的移动距离 // 避免和双击冲突,大于10f才算是拖动 if (Math.sqrt(dx * dx + dy * dy) > 10f) { startPoint.set(event.getX(), event.getY()); // 在当前基础上移动 mCurrentMatrix.set(getImageMatrix()); mCurrentMatrix.getValues(mCurrentValues); Log.i(tag, "dx="+dx); dx = checkDxBound(mCurrentValues, dx); dy = checkDyBound(mCurrentValues, dy); mCurrentMatrix.postTranslate(dx, dy); setImageMatrix(mCurrentMatrix); Log.i(tag, "dx="+dx); if(dx==0){ pager.willIntercept=true; }else { pager.willIntercept=false; } } } break; case MotionEvent.ACTION_CANCEL: // break; case MotionEvent.ACTION_UP: if(mMode==MODE_EXZOOM&&mCurrentValues[Matrix.MSCALE_X]<DEFAULT_ZOOM){ pager.willIntercept=true; } //至此前缩放模式结束,重置 mMode = 0; break; // 多点松开 case MotionEvent.ACTION_POINTER_UP: getImageMatrix().getValues(mCurrentValues); //至此缩放模式结束,进入“前缩放模式“,用于后面的ACTION_MOVE、UP mMode=MODE_EXZOOM; default: break; } return mGestureDetector.onTouchEvent(event); } // scale是相对于当前(不是最初)的大小来进行变化的倍率 private void setZoom(float scale) { // TODO Auto-generated method stub float[] values = new float[9]; // 不要getImageMatrix().getValues(values) mCurrentMatrix.set(getImageMatrix()); mCurrentMatrix.getValues(values); // 限制放大的最大倍数 if (scale * values[Matrix.MSCALE_X] > MAX_ZOOM) { scale = MAX_ZOOM / values[Matrix.MSCALE_X]; } // 限制缩小的最小倍数 if (scale * values[Matrix.MSCALE_X] <MIN_ZOOM) { scale = MIN_ZOOM / values[Matrix.MSCALE_X]; } if (scale == 1) { return; } mCurrentMatrix.postScale(scale, scale, mid.x, mid.y); setImageMatrix(mCurrentMatrix); restrainXYBound(); } @Override public void setImageMatrix(Matrix matrix) { // TODO Auto-generated method stub super.setImageMatrix(matrix); //双指缩放viewpager决不响应 if(mMode==MODE_ZOOM){ pager.willIntercept=false; return; } } // 取手势中心点 private void midPoint(PointF point, MotionEvent event) { float x = event.getX(0) + event.getX(1); float y = event.getY(0) + event.getY(1); point.set(x / 2, y / 2); } private void restrainXYBound() { // TODO Auto-generated method stub float[] values = new float[9]; mCurrentMatrix.set(getImageMatrix()); mCurrentMatrix.getValues(values); float dx = 0, dy = 0; dx = checkDxBound(values, dx); dy = checkDyBound(values, dy); mCurrentMatrix.postTranslate(dx, dy); Log.i(tag, "dx="+dx); Log.i(tag, "dy="+dy); setImageMatrix(mCurrentMatrix); // float x1=(values[Matrix.MTRANS_X] + values[Matrix.MSCALE_X] // * mImageWidth - getWidth()); // float y1=(values[Matrix.MTRANS_Y] + values[Matrix.MSCALE_Y] // * mImageHeight - getHeight()); // // Log.i(tag, "x1="+x1+"y1="+y1); // float dx=0,dy=0; // if(x1>mValues[Matrix.MTRANS_X]){ // dx=-x1-mValues[Matrix.MTRANS_X]; // } // // else if(x1>mValues[Matrix.MTRANS_X]){ // // dx=x1-mValues[Matrix.MTRANS_X]; // // dx=-dx; // // } // if(y1>mValues[Matrix.MTRANS_Y]){ // dy=-y1-mValues[Matrix.MTRANS_Y]; // } // // else if(y1>mValues[Matrix.MTRANS_Y]){ // // dy=y1-mValues[Matrix.MTRANS_Y]; // // dy=-dy; // // } // mCurrentMatrix.postTranslate(dx,dy); // setImageMatrix(mCurrentMatrix); // mCurrentMatrix.set(getImageMatrix()); // mCurrentMatrix.getValues(values); // x1=(values[Matrix.MTRANS_X] + values[Matrix.MSCALE_X] // * mImageWidth - getWidth()); // y1=(values[Matrix.MTRANS_Y] + values[Matrix.MSCALE_Y] // * mImageHeight - getHeight()); // Log.i(tag, "x1="+x1+"y1="+y1); } /** * 计算两个手指间的距离 * * @param event * @return */ private float getDistance(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); } /** * 和当前矩阵对比,检验dx,使图像移动后不会超出ImageView边界 * * @param values * @param dx * @return */ private float checkDxBound(float[] values, float dx) { float width = getWidth(); if (mImageWidth * values[Matrix.MSCALE_X] < width){ //当控件能容纳整张图片时,让图片保持正中 dx=(getWidth()-mImageWidth*values[Matrix.MSCALE_X])/2-values[Matrix.MTRANS_X]; }else 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) { // 获取ImageView控件高度 float height = getHeight(); // mImageHeight=sitImageBitmap方法中获取的图片高度 // values[Matrix.MSCALE_Y]当前Y轴缩放级别计算出当前Y轴的显示高度 if (mImageHeight * values[Matrix.MSCALE_Y] < height) { // 图片的纵向全部显示了 dy=(getHeight()-mImageHeight*values[Matrix.MSCALE_Y])/2-values[Matrix.MTRANS_Y]; }else 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; } public void onDoubleClick() { float[] values = new float[9]; getImageMatrix().getValues(values); // 获取当前X轴缩放级别 float scale = values[Matrix.MSCALE_X]; float tmp; if (scale > DEFAULT_ZOOM) { tmp = DEFAULT_ZOOM / scale; } else { tmp = mDobleClickScale / scale; } setZoom(tmp); } private class GestureListener extends SimpleOnGestureListener { @Override public boolean onDown(MotionEvent e) { // 捕获Down事件 return true; } @Override public boolean onDoubleTap(MotionEvent e) { // 触发双击事件 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); if (listener != null) { listener.onLongPress(); } } @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); } } public interface ImageListener { void onLongPress(); } public ImageListener getListener() { return listener; } public void setListener(ImageListener listener) { this.listener = listener; } public XViewPager getPager() { return pager; } public void setPager(XViewPager pager) { this.pager = pager; } }
Viewpager
package com.example.widget; import android.content.Context; import android.graphics.Matrix; import android.support.v4.view.ViewPager; import android.util.AttributeSet; import android.util.Log; import android.view.MotionEvent; public class XViewPager extends ViewPager { public boolean willIntercept = true; private String tag = "XViewPager"; public XViewPager(Context context) { super(context); } public XViewPager(Context context, AttributeSet attrs) { super(context, attrs); // TODO Auto-generated constructor stub } @Override public boolean onInterceptTouchEvent(MotionEvent event) { Log.i(tag, "willIntercept---" + willIntercept); /** 处理单点、多点触摸 **/ switch (event.getAction() & MotionEvent.ACTION_MASK) { // 按下事件 case MotionEvent.ACTION_DOWN: Log.i(tag, "ACTION_DOWN"); case MotionEvent.ACTION_POINTER_DOWN: Log.i(tag, "ACTION_POINTER_DOWN"); case MotionEvent.ACTION_MOVE: Log.i(tag, "ACTION_MOVE"); break; case MotionEvent.ACTION_CANCEL: Log.i(tag, "ACTION_CANCEL"); break; case MotionEvent.ACTION_UP: Log.i(tag, "ACTION_UP"); break; // 多点松开 case MotionEvent.ACTION_POINTER_UP: Log.i(tag, "ACTION_POINTER_UP"); default: break; } if (willIntercept) { // 这个地方直接返回true会很卡 return super.onInterceptTouchEvent(event); } else { // 不让viewpager响应outouch return false; } } /** * 设置ViewPager是否拦截点击事件 * * @param value * if true, ViewPager拦截点击事件 if false, * ViewPager将不能滑动,ViewPager的子View可以获得点击事件 主要受影响的点击事件为横向滑动 * */ public void setTouchIntercept(boolean value) { willIntercept = value; } }
Done!