本文是自己学习所做笔记,欢迎转载,但请注明出处:http://blog.csdn.net/jesson20121020
上节实现了查看图片及录音的功能,其中查看图片,可以调用系统的图库来查看图片,也可以自定义Activity来查看图片,今天就在上节的基础上,实现手势缩放与拖拽图片。
想必大家都用过系统的图库,浏览图片时,可以通过手势放大或缩小图片,旋转图片,拖拽图片等功能,我们也为自已定义的查看图片的Activity增加手势缩放与拖拽图片的功能,效果如下图:
上面四幅图中,演示了通过手势(多点触控)来缩小,放大,拖拽图片。
这里主要是用到了多点触控,所以我们首先要知道多点和单点的区别。
单手指操作过程: ACTION_DOWN-ACTION_MOVE-ACTIOIN_UP
多手指操作过程:ACTION_DOWN-ACTION_POINTER_DOWN-ACTION_MOVE-ACTION_POINTER_UP-ACTION_UP
一般实现图片的缩放都是用Matrix的postScale方法,那么通过手势(多点)来缩放图片当然也不例外,区别就是通过手指的滑动来判断缩放的比例及中心位置,具体做法如下:
手势缩放图片的步骤:
1. 设置ImageView的scaleType属性为matrix。
因为实现图片的缩放要用到Matrix,所以这个属性是前提,可以在xml里设置ImageView设置,android:scaleType="matrix",或者在代码里设置imageView.setScaleType(ScaleType.matrix)
2. 给ImageView绑定触摸监听器
//触摸事件 img.setOnTouchListener(new TouchEvent());
3. 在触摸事件中实现多手指缩放及拖拽图片
这是本节的核心,主要是要先判断MotionEvent的类型,是单手指还是多手指,可以通过event.getActionMasked()来获得,并设立三个标志,分别用于判断当前操作是拖拽,缩放还是无操作,如果是拖拽,则需要记录手指的起始位置及终点位置,然后利用Matrix的postTranslate方法来实现图片的移动。如果是缩放,则需要先计算图片缩放的比例及位置,在计算缩放比例时,又需要先知道多手指移动的直径,通过多手指移动前后的比例来得到缩放的比例;而要计算图片缩放的位置,只需要计算出手指移动前后的中点即可。
4. 控制缩放比例
其实,完成前3步就已经能实现通过手势来控制图片的缩放和移动,但是你会发现,这时,图片可以放大的无限大,也可以缩小到无限小,而且,不管图片是在放大状态,还是缩小状态,都会随你的手指的移动而移动到任何地方。这显然是不符合实际使用的,所以这就需要控制图片的缩放的比例,主要代码如下:
//控制缩放比例 private void controlScale(){ float values[] = new float[9]; matrix.getValues(values); if(mode == ZOOM){ if(values[0] < MINSCALER) matrix.setScale(MINSCALER, MINSCALER); else if(values[0] > MAXSCALER) matrix.setScale(MAXSCALER, MAXSCALER); } }
5. 设置图片居中显示
通过第4步,可以控制图片的缩放比例,这样,图片就会有一个最大的和最小的绽放比例,当计算出的缩放比例小于最小的缩放比例时,就会设置当前的缩放比例为最小的缩放比例,当大于最大的缩放比例时,就设置当前图片的缩放比例为最大的缩放比例。
但是,还有一个问题,就是,这时不管图片的放大还是缩小状态,都会随着你的手指移动,但在实际过程中,我们往往需要图片的缩放后都是在控件的中心位置,即,设置图片居中显示,代码如下:
//自动居中 左右及上下都居中 protected void center() { center(true,true); } private void center(boolean horizontal, boolean vertical) { Matrix m = new Matrix(); m.set(matrix); RectF rect = new RectF(0, 0, bm.getWidth(), bm.getHeight()); m.mapRect(rect); float height = rect.height(); float width = rect.width(); float deltaX = 0, deltaY = 0; if (vertical) { int screenHeight = dm.heightPixels; //手机屏幕分辨率的高度 //int screenHeight = 400; if (height < screenHeight) { deltaY = (screenHeight - height)/2 - rect.top; }else if (rect.top > 0) { deltaY = -rect.top; }else if (rect.bottom < screenHeight) { deltaY = screenHeight - rect.bottom; } } if (horizontal) { int screenWidth = dm.widthPixels; //手机屏幕分辨率的宽度 //int screenWidth = 400; if (width < screenWidth) { deltaX = (screenWidth - width)/2 - rect.left; }else if (rect.left > 0) { deltaX = -rect.left; }else if (rect.right < screenWidth) { deltaX = screenWidth - rect.right; } } matrix.postTranslate(deltaX, deltaY); }
通过居中设置后,这样图片在未占满屏幕时,是不能进行将其移动到其他位置的,只有在图片大于屏幕时,也可以移动图片从而查看图片的不同位置。
基本上,通过这5步,就已经可以实现图片手势(多点)缩放的功能,而且也可以控制图片的缩放比例及居中显示。下面给出整个代码:
public class ShowPicture extends Activity { private ImageView img; private Bitmap bm; private DisplayMetrics dm; private Matrix matrix = new Matrix(); private Matrix savedMatrix = new Matrix(); private PointF mid = new PointF(); private PointF start = new PointF(); private static int DRAG = 2; private static int ZOOM = 1; private static int NONE = 0; private int mode = 0; private float oldDist = 1f; private static float MINSCALER = 0.3f; private static float MAXSCALER = 3.0f; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); requestWindowFeature(Window.FEATURE_CUSTOM_TITLE); setContentView(R.layout.activity_show_picture); getWindow().setFeatureInt(Window.FEATURE_CUSTOM_TITLE, R.layout.title_add); //设置标题 TextView tv_title = (TextView)findViewById(R.id.tv_title); tv_title.setText("查看图片"); Button bt_back = (Button)findViewById(R.id.bt_back); bt_back.setOnClickListener(new OnClickListener() { @Override public void onClick(View arg0) { ShowPicture.this.finish(); } }); Button bt_del = (Button)findViewById(R.id.bt_save); bt_del.setBackgroundResource(R.drawable.paint_icon_delete); dm = new DisplayMetrics(); getWindowManager().getDefaultDisplay().getMetrics(dm); //获取分辨率 img = (ImageView)findViewById(R.id.iv_showPic); Intent intent = this.getIntent(); String imgPath = intent.getStringExtra("imgPath"); bm = BitmapFactory.decodeFile(imgPath); //设置居中显示 savedMatrix.setTranslate((dm.widthPixels - bm.getWidth())/2 , (dm.heightPixels - bm.getHeight()) / 2); img.setImageMatrix(savedMatrix); //绑定图片 img.setImageBitmap(bm); //触摸事件 img.setOnTouchListener(new TouchEvent()); } //添加触摸事件,实现图片的手势缩放 class TouchEvent implements OnTouchListener{ @Override public boolean onTouch(View view, MotionEvent event) { switch(event.getActionMasked()){ //单击触控,用于拖动 case MotionEvent.ACTION_DOWN : matrix.set(img.getImageMatrix()); savedMatrix.set(matrix); start.set(event.getX(), event.getY()); mode = DRAG; break; //多点触控,按下时 case MotionEvent.ACTION_POINTER_DOWN : oldDist = getSpacing(event); savedMatrix.set(matrix); getMidPoint(mid,event); mode = ZOOM; break; //多点触控,抬起时 case MotionEvent.ACTION_POINTER_UP : mode = NONE; break; case MotionEvent.ACTION_MOVE : if(mode == DRAG){ matrix.set(savedMatrix); matrix.postTranslate(event.getX() - start.x, event.getY() - start.y); } //缩放 else if(mode == ZOOM){ //取得多指移动的直径,如果大于10,则认为是缩放手势 float newDist = getSpacing(event); if(newDist > 10){ matrix.set(savedMatrix); float scale = newDist / oldDist; matrix.postScale(scale, scale,mid.x,mid.y); } } break; } img.setImageMatrix(matrix); controlScale(); //setCenter(); center(); return true; } } //求距离 private float getSpacing(MotionEvent event){ float x = event.getX(0) - event.getX(1); float y = event.getY(0) - event.getY(1); return FloatMath.sqrt(x * x + y * y); } //求中点 private void getMidPoint(PointF mid,MotionEvent event){ float x = event.getX(0) + event.getX(1); float y = event.getY(0) + event.getY(1); mid.set(x / 2, y / 2); } //控制缩放比例 private void controlScale(){ float values[] = new float[9]; matrix.getValues(values); if(mode == ZOOM){ if(values[0] < MINSCALER) matrix.setScale(MINSCALER, MINSCALER); else if(values[0] > MAXSCALER) matrix.setScale(MAXSCALER, MAXSCALER); } } //自动居中 左右及上下都居中 protected void center() { center(true,true); } private void center(boolean horizontal, boolean vertical) { Matrix m = new Matrix(); m.set(matrix); RectF rect = new RectF(0, 0, bm.getWidth(), bm.getHeight()); m.mapRect(rect); float height = rect.height(); float width = rect.width(); float deltaX = 0, deltaY = 0; if (vertical) { int screenHeight = dm.heightPixels; //手机屏幕分辨率的高度 //int screenHeight = 400; if (height < screenHeight) { deltaY = (screenHeight - height)/2 - rect.top; }else if (rect.top > 0) { deltaY = -rect.top; }else if (rect.bottom < screenHeight) { deltaY = screenHeight - rect.bottom; } } if (horizontal) { int screenWidth = dm.widthPixels; //手机屏幕分辨率的宽度 //int screenWidth = 400; if (width < screenWidth) { deltaX = (screenWidth - width)/2 - rect.left; }else if (rect.left > 0) { deltaX = -rect.left; }else if (rect.right < screenWidth) { deltaX = screenWidth - rect.right; } } matrix.postTranslate(deltaX, deltaY); } }
注:
另外还有一个问题就是,图片的初始居中问题,在ImageView中可以设置属性android:scaleType="fitCenter"来实现图片的居中显示,但是这里是要实现手势缩放图片,所以需要将该属性设置为android:scaleType="matrix",但同时,这也带来了一个问题,就是在初始时,图片不能居中。
在网上找过解决办法,第一种方法,就是在XML文件里先设置ImageVIew的scaleType属性为fitCenter,然后在ImageVIew的setOnTouchListener()方法之前设置setScaleType(ScaleType.MATRIX); 但是这种方法我没有成功,图片初始还是在左上角;另一种方法,就是先计算手机屏幕的高和宽,求出使图片居中的点(x,y),然后通过Matrix的平移到该位置,最后通过setImageMatrix()为ImageView绑定该Matrix,以实现图片初始居中显示。而我最后采用的也就是第二种方法,代码如下:
private DisplayMetrics dm; ... ... dm = new DisplayMetrics(); getWindowManager().getDefaultDisplay().getMetrics(dm); //获取分辨率 ... ... //设置居中显示 savedMatrix.setTranslate((dm.widthPixels - bm.getWidth())/2 , (dm.heightPixels - bm.getHeight()) / 2); img.setImageMatrix(savedMatrix);
如果大家有更好的办法,能使图片初始时居中,请分享下。