首页 > 代码库 > android项目 之 记事本(14) ----- 手势缩放与拖拽图片

android项目 之 记事本(14) ----- 手势缩放与拖拽图片

本文是自己学习所做笔记,欢迎转载,但请注明出处: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);

         如果大家有更好的办法,能使图片初始时居中,请分享下。

       

android项目 之 记事本(14) ----- 手势缩放与拖拽图片