본문 바로가기

Android/2D 그래픽스

[Android] FrameBuffer 사용하기

앞서 만들었던 View들은 화면이 갱신 될 때마다 모든 요소들을 다시 그려줘야 했기때문에 불편한점이 있었다. 게임이 아닌 정적인 View에서도 전혀 변화가 없는 부분까지 다시 그린다는 것은 분명 비효율적인 일일것이다.

그런 비효율적인 방법을 조금이나마 보완할 수 있는 것이 FrameBuffer이다.

먼저 기존 View들과 다르게 준비 해야 할 두개의 객체가 있다. 바로 FrameBuffer로 사용 할 Bitmap과 FrameBuffer에 그림을 그려 넣을 Canvas이다.

  /** FrameBuffer로 사용 할 비트맵 */

private Bitmap mFrameBuffer;
/** FrameBuffer에 그림을 그릴 캔버스 */
private Canvas mCanvas;



onDraw에서는 기존과 다르게 단지 canvas에 mFrameBuffer를 그려주는 역할만 하면 된다. 단, 아직 mFrameBuffer와 mCanvas는 초기화가 이루어지지 않은 단계이기 때문에 FrameBuffer가 Null Pointer라면 초기화를 진행하도록 한다.

@Override
protected void onDraw(Canvas canvas) {
if (mFrameBuffer == null) {
init();
onBufferedDraw();
} else {
// 화면에 frameBuffer의 내용을 출력한다.
canvas.drawBitmap(mFrameBuffer, 0, 0, null);
}
}


  /** 초기화 메소드 */
private void init() {
// 리소스로부터 아이콘 이미지를 불러온다.
mBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.ic_launcher);
// 프레임버퍼로 사용할 비트맵을 생성한다. 32비트 컬러
mFrameBuffer = Bitmap.createBitmap(getWidth(), getHeight(), Bitmap.Config.ARGB_8888);
// 캔버스에 프레임버퍼를 연결한다.
mCanvas = new Canvas(mFrameBuffer);
}



초기화가 이루어 지면 실제 그림을 그리게 되는데 onDraw대신 그림을 그리는 역할을 수행하는 메소드는 onBufferedDraw라고 이름을 지었다. 이 곳에서 아이콘 이미지를 그린다.

/** 
* 이 곳에서 onDraw의 canvas대신 frameBuffer에 그리기를 수행한다.
* 모든 그리기를 수행하고 나면 invalidate를 실행하여 화면에 frameBuffer의
* 내용이 출력되도록한다.  
*/
private void onBufferedDraw() {
// 현재의 x, y좌표를 기준으로 이미지를 프레임버퍼에 그려준다.
mCanvas.drawBitmap(mBitmap, x, y, null);
// 그리기가 완성되면 화면 갱신!
invalidate();
}



이 전 포스트인 Touch Event와 거의 동일한 코드이지만 달라진 점이 있다면 아이콘이 이동하면서 잔상을 남긴다는 것이다. onDraw에서 인자로 넘어오는 canvas는 항상 새것으로 넘어오지만 mCanvas는 mFrameBuffer와 연결이 되어있기 때문에 모든 그래픽 요소들이 초기화 되지 않았던 것이다.

다음은 전체 소스이다.


package com.cashyalla.graphics;

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

public class FrameBuffer extends View {
	/** 아이콘 비트맵 */
	private Bitmap mBitmap;
	/** 비트맵의 최초 x좌표 */
	private float x = 0.0f;
	/** 비트맵의 최초 y좌표 */
	private float y = 0.0f;
	/** 지난 터치의 x좌표 */
	private float prevX = -1;
	/** 지난 터치의 y좌표 */
	private float prevY = -1;
	/** FrameBuffer로 사용 할 비트맵 */
	private Bitmap mFrameBuffer;
	/** FrameBuffer에 그림을 그릴 캔버스 */
	private Canvas mCanvas;
	
	public FrameBuffer(Context context) {
		super(context);
	}
	
	/** 초기화 메소드 */
	private void init() {
		// 리소스로부터 아이콘 이미지를 불러온다.
		mBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.ic_launcher);
		// 프레임버퍼로 사용할 비트맵을 생성한다. 32비트 컬러
		mFrameBuffer = Bitmap.createBitmap(getWidth(), getHeight(), Bitmap.Config.ARGB_8888);
		// 캔버스에 프레임버퍼를 연결한다.
		mCanvas = new Canvas(mFrameBuffer);
	}
	
	/** 
	 * 이 곳에서 onDraw의 canvas대신 frameBuffer에 그리기를 수행한다.
	 * 모든 그리기를 수행하고 나면 invalidate를 실행하여 화면에 frameBuffer의
	 * 내용이 출력되도록한다.  
	 */
	private void onBufferedDraw() {
		// 현재의 x, y좌표를 기준으로 이미지를 프레임버퍼에 그려준다.
		mCanvas.drawBitmap(mBitmap, x, y, null);
		// 그리기가 완성되면 화면 갱신!
		invalidate();
	}
	
	@Override
	protected void onDraw(Canvas canvas) {
		if (mFrameBuffer == null) {
			init();
			onBufferedDraw();
		} else {
			// 화면에 frameBuffer의 내용을 출력한다.
			canvas.drawBitmap(mFrameBuffer, 0, 0, null);
		}
	}
	
	public boolean onTouchEvent(MotionEvent event) {
		// 현재의 터치 액션의 종류를 받아온다.
		int action = event.getAction();
		// 터치 된 x좌표
		float x = event.getX();
		// 터치 된 y좌표
		float y = event.getY();
		// 액션의 종류에 따른 역할 수행
		switch (action) {
		// 드래그 되었을 때의 이벤트 처리
		case MotionEvent.ACTION_MOVE :
			// 터치 좌표가 이미지 안에 들어와 있다면 드래그 된 만큼 이미지의 좌표도 이동시킨다.
			if (x > this.x && x < this.x + mFrameBuffer.getWidth()
					&& y > this.y && y < this.y + mFrameBuffer.getHeight()) {
				if (prevX > 0 && prevY > 0) {
					this.x += x - prevX;
					this.y += y - prevY;
				}
				// 현재의 좌표들이 지난 좌표가 된다.
				prevX = x;
				prevY = y;
				// 좌표 이동이 끝났으면 아이콘을 옮겨진 좌표에 그린다.
				onBufferedDraw();
			}
			break;
		}
		return true;
	};

}