Android SDKで画像を読み込み、ピンチ操作でズームする

test131_01.png
今回は表示したJPEG画像をピンチ操作によりズームする。ピンチ操作というのは2本の指で画面をタッチして、指間隔を開けば拡大、指間隔を狭めれば縮小というもの。

まずはEasyProjectGenerator for Androidで雛形になるプロジェクトを作成。

test131_02.png そしてjavaのソースコードでアクティビティへImageFileViewクラスを割り当てる。このクラスにJPEG読み込みからピンチ操作、ズーム表示までを実装する。
package com.Test131;

import android.app.Activity;
import android.os.Bundle;
import android.widget.LinearLayout;

public class Test131Act extends Activity
{
	@Override
	public void onCreate(Bundle savedInstanceState)
	{
		super.onCreate(savedInstanceState);

		LinearLayout	layout = new LinearLayout(this);
		setContentView(layout);

		ImageFileView	view = new ImageFileView(this);
		layout.addView(view);
	}
}

test131_03.png
ImageFileView.javaというファイルを作り、実装する。

BitmapFactory.decodeFile()では、大きな画像ファイルを読み込もうとするとメモリが足りなくなってアプリが落ちてしまうので、今回は長辺がnMaxWidthピクセル以下になるように縮小して読み込むことにした。
package com.Test131;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.view.View;



public class ImageFileView extends View
{
	//表示画像
	protected	Bitmap	_bmpImageFile;


	public ImageFileView(Context context)
	{
		super(context);

		{
			String	strFile = "/sdcard/DSC_0670.JPG";

			_bmpImageFile = LoadImageFile(strFile,800*2);
		}
	}


	public	Bitmap	LoadImageFile(String strFile,int nMaxWidth)
	{
		Bitmap	bmpImage;
		
		//画像の解析(大きい画像が開けない対策)
		BitmapFactory.Options	options = new BitmapFactory.Options();
		options.inJustDecodeBounds = true;
		BitmapFactory.decodeFile(strFile,options);
		
		int		nWidth = options.outWidth;
		int		nHeight = options.outHeight;

		if(nWidth == 0 || nHeight == 0)
			return	null;

		if(nWidth <= nMaxWidth && nHeight <= nMaxWidth)
		{
			//リサイズ不要。そのまま読み込んで返す
			options.inJustDecodeBounds = false;
			return	BitmapFactory.decodeFile(strFile,options);
		}



		int		nScale;

		//nScaleは2の乗数にする
		{
			float	fScale;
			float	fScaleX = (float)nWidth / nMaxWidth;
			float	fScaleY = (float)nHeight / nMaxWidth;

			fScale = (fScaleX > fScaleY) ? fScaleX : fScaleY;

			nScale = 1;
			while(fScale >= nScale)
			{
				nScale *= 2;
			}
		}

		//画像の本読み込み
		options.inJustDecodeBounds = false;
		options.inSampleSize = nScale;
		bmpImage = BitmapFactory.decodeFile(strFile,options);

		return	bmpImage;
	}

	@Override
	protected void onDraw(Canvas canvas)
	{
		super.onDraw(canvas);

		if(_bmpImageFile == null)
			return;

		Matrix	matrix = new Matrix();

		//描画
		canvas.drawBitmap(_bmpImageFile, matrix, null);
	}
}


test131_04.png
これで実行すると画像が大きく表示された。

test131_05.png さらに画像のサイズに合わせて縮小&余白を表示する。

	@Override
	protected void onDraw(Canvas canvas)
	{
		super.onDraw(canvas);

		if(_bmpImageFile == null)
			return;

		//画面表示用ズーム率
		float	fImageScale;

		//余白
		float	fMarginX = 0.0f;
		float	fMarginY = 0.0f;
		
		//ズーム率取得
		{
			int		nImageWidth	= _bmpImageFile.getWidth();
			int		nImageHeight= _bmpImageFile.getHeight();
			int		nViewWidth = getWidth();
			int		nViewHeight = getHeight();


			//画像に合わせる
			if((long)nImageWidth * nViewHeight > (long)nViewWidth * nImageHeight)
			{
				fImageScale = (float)nViewWidth / nImageWidth;
				fMarginY = (nViewHeight - fImageScale * nImageHeight) * 0.5f;
			}
			else
			{
				fImageScale = (float)nViewHeight / nImageHeight;
				fMarginX = (nViewWidth - fImageScale * nImageWidth) * 0.5f;
			}
		}


		//ズーム率
		float	fScale = fImageScale;

		//余白移動
		float	fMoveX = fMarginX;
		float	fMoveY = fMarginY;

		Matrix	matrix = new Matrix();

		//ズーム
		matrix.preScale(fScale,fScale);

		//移動
		matrix.postTranslate(fMoveX,fMoveY);

		//描画
		canvas.drawBitmap(_bmpImageFile, matrix, null);
	}

test131_06.png
これで画像に合わせて縮小&余白表示された。ここまではこれまでも紹介してきた流れで、ピンチ操作などは実装していない。

test131_07.png
次にようやくピンチ操作を実装する。

MotionEvent.ACTION_POINTER_DOWNやMotionEvent.ACTION_POINTER_MOVEでピンチ操作を簡単に検知できる。しかしどうも簡単にそれを使ってズームする仕組みは用意されていない模様。今回は簡易的なピンチズームを実装したものの、きちんとしたものを実装しようと思ったらMatrixは使わずに自分で領域計算して描画した方がよさそうです。

	@Override
	protected void onDraw(Canvas canvas)
	{
		super.onDraw(canvas);

		if(_bmpImageFile == null)
			return;

		//画面表示用ズーム率
		float	fImageScale;

		//余白
		float	fMarginX = 0.0f;
		float	fMarginY = 0.0f;
		
		//ズーム率取得
		{
			int		nImageWidth	= _bmpImageFile.getWidth();
			int		nImageHeight= _bmpImageFile.getHeight();
			int		nViewWidth = getWidth();
			int		nViewHeight = getHeight();


			//画像に合わせる
			if((long)nImageWidth * nViewHeight > (long)nViewWidth * nImageHeight)
			{
				fImageScale = (float)nViewWidth / nImageWidth;
				fMarginY = (nViewHeight - fImageScale * nImageHeight) * 0.5f;
			}
			else
			{
				fImageScale = (float)nViewHeight / nImageHeight;
				fMarginX = (nViewWidth - fImageScale * nImageWidth) * 0.5f;
			}
		}


		//ピンチを含めた総合ズーム率
		float	fScale = _fPinchScale * fImageScale;

		//余白を含めた移動量
		float	fMoveX = _fPinchMoveX + fMarginX;
		float	fMoveY = _fPinchMoveY + fMarginY;
		
		
		//ズーム原点指定
		fMoveX += _ptPinchStart.x - _ptPinchStart.x * _fPinchScale;
		fMoveY += _ptPinchStart.y - _ptPinchStart.y * _fPinchScale;

		Matrix	matrix = new Matrix();

		//ズーム
		matrix.preScale(fScale,fScale);

		//移動
		matrix.postTranslate(fMoveX,fMoveY);

		//描画
		canvas.drawBitmap(_bmpImageFile, matrix, null);
	}


	//ピンチによる現在のズーム率(拡大  > 1.0f > 縮小)
	private	float	_fPinchScale	= 1.0f;

	private	PointF	_ptPinchStart	= new PointF();
	private float	_fPinchStartDistance	= 0.0f;
	private	float	_fPinchMoveX	= 0.0f;
	private	float	_fPinchMoveY	= 0.0f;

	//タッチ操作内部処理用
	private static final int TOUCH_NONE = 0;
	private static final int TOUCH_DRAG = 1;
	private static final int TOUCH_ZOOM = 2;
	private int		_nTouchMode	= TOUCH_NONE;

	
	@Override
	public boolean onTouchEvent(MotionEvent event)
	{

		switch(event.getAction() & MotionEvent.ACTION_MASK)
		{
			//ピンチ開始 
		case MotionEvent.ACTION_POINTER_DOWN:
			if(event.getPointerCount() >= 2)
			{
				_fPinchStartDistance = GetDistance(event);
				if(_fPinchStartDistance > 50f)
				{
					GetCenterPoint(event,_ptPinchStart);
					_nTouchMode = TOUCH_ZOOM;
				}
			}
		    break;

		    //ピンチ中
		case MotionEvent.ACTION_MOVE:
		    if(_nTouchMode == TOUCH_ZOOM && _fPinchStartDistance > 0)
		    {
		    	PointF	pt = new PointF();

		    	GetCenterPoint(event,pt);
		    	_fPinchMoveX	= pt.x - _ptPinchStart.x;
		    	_fPinchMoveY	= pt.y - _ptPinchStart.y;

		    	_fPinchScale = GetDistance(event) / _fPinchStartDistance;
				invalidate();
		    }
		    break;

		    //ピンチ終了
		case MotionEvent.ACTION_UP:
		case MotionEvent.ACTION_POINTER_UP:
			if(_nTouchMode == TOUCH_ZOOM)
			{
				//ピンチ終了処理
				_nTouchMode = TOUCH_NONE;

				_fPinchMoveX	= 0.0f;
				_fPinchMoveY	= 0.0f;
				_fPinchScale	= 1.0f;
				_ptPinchStart.x	= 0.0f;
				_ptPinchStart.y	= 0.0f;
				invalidate();
			}
			break;
		}
		
		
		switch(event.getAction() & MotionEvent.ACTION_MASK)
		{
			//ドラッグ開始
		case	MotionEvent.ACTION_DOWN:
			if(_nTouchMode == TOUCH_NONE && event.getPointerCount() == 1)
			{
				_nTouchMode = TOUCH_DRAG;
			}
			break;

		case MotionEvent.ACTION_MOVE:
			if(_nTouchMode == TOUCH_DRAG)
			{
			}
			break;
			
			//ドラッグ終了
		case	MotionEvent.ACTION_UP:
			if(_nTouchMode == TOUCH_DRAG)
			{
				//ドラッグ終了処理
				_nTouchMode = TOUCH_NONE;
				break;
			}
		}

		return true;
	}	

	//ピンチ距離取得用
	private	float	GetDistance(MotionEvent event)
	{
		float x = event.getX(0) - event.getX(1);
		float y = event.getY(0) - event.getY(1);
		return android.util.FloatMath.sqrt(x * x + y * y);
	}

	//ピンチ開始座標取得用
	private	void	GetCenterPoint(MotionEvent event,PointF pt)
	{
		pt.x = (event.getX(0) + event.getX(1)) * 0.5f;
		pt.y = (event.getY(0) + event.getY(1)) * 0.5f;
	}



プロジェクトファイルをダウンロード

カテゴリー「android」 のエントリー