Please Enable JavaScript!
Gon[ Enable JavaScript ]

반응형

안드로이드(Android) SurfaceView 를 이용하여 터치로 이미지 이동시키기 


이 예제는 애니메이션 효과를 보여주며 터치 이벤트가 발생했을 때 클릭 지점이 그림 위라면
누른상태에서 이미지가 이동할수 있도록 구현한것이다.

SurfaceView 를 이용하였으며 스레드를 구현하기 위해 Runnable Implements 하였다.

SurfaceView 를 상속받게 되면 3가지 추상함수를 구현해야되는데 쓰임새는 주석의 설명과 같다.


/** surface 가 변경될때 */
public void surfaceChanged(SurfaceHolder holder, int format,int width,int height){

}
/** surface 가 생성될때 */
public void surfaceCreated(SurfaceHolder holder) {

}
/** surface 가 종료될때 */
public void surfaceDestroyed(SurfaceHolder holder) {

}

이제 그림을 그리기 위한 화면 객체를 생성할 함수가 준비 되었으니 이곳에 그림을 그리는

역할을 할 스레드가 만들어져야한다. 스레드는 Runnable 인터페이스를 이용하여 구현되었다.

public void run() 함수를 override 하여 내부에 필요한 기능을 구현하였다.

 

처음에 객체가 생성될 SurfaceHolder 객체를 리턴받게 된다. 여기에서 이미지를 그릴

도화지에 해당하는 Canvas 가져온다. 가져올때 SurfaceHolder 에서 Canvas 작업이

끝나기 전까지 lock 시키고 끝나고 나면 unlock 시키는데 소스는 다음과 같다

Canvas c = null;
try {
	c = holder.lockCanvas(null);
	synchronized (holder) {
		doDraw(c);
		Thread.sleep(50);
	}
} catch (InterruptedException e) {
	e.printStackTrace();
} finally {
	if (c != null){
		holder.unlockCanvasAndPost(c);
	}
}

그리고 위의 기능은 run 스레드를 수행하는 함수에 들어 있으며, 실질적인 그림작업은

doDraw 라는 함수를 하나 만들어 구현하였다. 내용은 Canvas 객체를 이용해 비트맵이미지를

그리며 text를 쓰는 작업이다.

이미지의 이동은 터치 이벤트를 캡쳐한것중에 MotionEvent.ACTION_MOVE 일때 이미지를

그릴 좌표를 변경시켜준다. 변경된 좌표는 스레드에서 이미지를 그릴 때 적용이 되어

이미지를 터치 지점으로 이동하게 만들어 준다. 그리고 마우스를 Up 했을떄 이동을 멈추게

하였다
@Override
public boolean onTouchEvent(MotionEvent event) {
	int keyAction = event.getAction();
	int x = (int)event.getX();
	int y = (int)event.getY();
	mouseX = x;
	mouseY = y;
	switch (keyAction){
	case MotionEvent.ACTION_MOVE:
		if (bMove){
			moveX = x;
			moveY = y;
		}
		break;
	case MotionEvent.ACTION_UP:
		bMove = false;
		break;
	case MotionEvent.ACTION_DOWN:
		this.checkImageMove(x, y);
		break;
	}
	// 함수 override 해서 사용하게 되면  return  값이  super.onTouchEvent(event) 되므로
	// MOVE, UP 관련 이벤트가 연이어 발생하게 할려면 true 를 반환해주어야 한다.
	return true;
}

이것을 이용해 애니메이션 관련 프로그램에 적용을 하고자 한다. 좀더 개선을 해야겠다는 생각이

팍팍 들긴 하는데 당장은 2가지 밖에 생각이 나질 않는다. 좀더 부드럽게 이미지가 따라 올수

있도록 해야겠다는 것과 애니메이션 효과를 넣어 실제로 물건을 들어 옮기는 것과 같은

느낌이 들도록 구현 하고 싶다.

 

전체소스와 구현 이미지


실행 Activity Main.java

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

public class Main extends Activity {
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(new AnEventMove(this));
    }

}
AnEventMove.java
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Point;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.SurfaceHolder.Callback;

public class AnEventMove extends SurfaceView implements Callback, Runnable {
	private Context mContext;
	private SurfaceHolder holder;
	private Bitmap imgMove;
	private int moveX = 0;
	private int moveY = 0;
	private int imgWidth = 0;
	private int imgHeight = 0;
	private int moveLength = 20;
	private Thread thread = null;
	private Point pImage;
	private Point pWindow;
	private boolean bMove = false;
	private int mouseX = 0;
	private int mouseY = 0;
	
	public AnEventMove(Context context) {
		super(context);
		mContext = context;
		
		// SurfaceHolder Create
		holder = getHolder();
		holder.addCallback(this);
		setFocusable(true);
	}
	/** surface 가 변경될때 */
	public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
		
	}
	/** surface 가 생성될때 */
	public void surfaceCreated(SurfaceHolder holder) {
		// window 크기
		pWindow = new Point();
		pWindow.x = 320;
		pWindow.y = 480;
		
		// 이미지 위치
		pImage = new Point(0, 0);
		Resources res = getResources();
		Bitmap tempBitmap = BitmapFactory.decodeResource(res, R.drawable.excavator);
		imgWidth = 80;
		imgHeight = 70;
		
		// 표시할 위치
		moveX = pWindow.x / 2;
		moveY = pWindow.y / 2;
		
		// 이미지 그리기
		imgMove = Bitmap.createScaledBitmap(tempBitmap, imgWidth, imgHeight, true);
		setClickable(true);
		thread = new Thread(this);
		thread.start();
	}

	public void surfaceDestroyed(SurfaceHolder holder) {
		try { 
			thread.interrupt(); 
		} catch (Exception e) { 
			Log.e(this.getClass().getName(), e.getMessage()); 
		} 
	}

	public void run() {
		// Canvas 의 사이즈
		pWindow.x = getWidth();
		pWindow.y = getHeight();
		
		while (!Thread.currentThread().isInterrupted()) {
			Canvas c = null;
			
			try {
				c = holder.lockCanvas(null);
				synchronized (holder) {
					doDraw(c);
					Thread.sleep(50);
				}
			} catch (InterruptedException e) {
				e.printStackTrace();
			} finally {
				if (c != null){
					holder.unlockCanvasAndPost(c);
				}
			}
		}
	}
	
	private void doDraw(Canvas cv){
		
		pImage.x = moveX;
		pImage.y = moveY;
		cv.drawColor(Color.LTGRAY); // 새로그림
		cv.drawBitmap(imgMove, moveX - 40, moveY - 30, null);
		
		// Paint 표시, 그리기 개체
		Paint paint = new Paint();
		paint.setAntiAlias(true);
		paint.setTextSize(16);
		paint.setColor(Color.RED);
		cv.drawText("Move Enable : "+bMove, 0, 400, paint);
		cv.drawText("IMAGE Point : X="+pImage.x+", Y="+pImage.y, 0, 425, paint);
		cv.drawText("Mouse Point : X="+mouseX+", Y="+mouseY, 0, 450, paint);
	}
	@Override
	public boolean onKeyDown(int keyCode, KeyEvent event) {
		switch (keyCode){
		case KeyEvent.KEYCODE_DPAD_LEFT:
			if (moveX > 0){
				moveX -= moveLength;
			}
			break;
		case KeyEvent.KEYCODE_DPAD_RIGHT:
			if ((moveX + imgWidth) < pWindow.x){
				moveX += moveLength;
			}
			break;
		case KeyEvent.KEYCODE_DPAD_UP:
			if (moveX > 0){
				moveY -= moveLength;
			}
			break;
		case KeyEvent.KEYCODE_DPAD_DOWN:
			if ((moveY + imgHeight) < pWindow.y){
				moveY += moveLength;
			}
			break;
		case KeyEvent.KEYCODE_DPAD_CENTER:
			break;
		}
		return  super.onKeyDown(keyCode, event);
	}
	@Override
	public boolean onTouchEvent(MotionEvent event) {
		int keyAction = event.getAction();
		int x = (int)event.getX();
		int y = (int)event.getY();
		mouseX = x;
		mouseY = y;
		switch (keyAction){
		case MotionEvent.ACTION_MOVE:
			if (bMove){
				moveX = x;
				moveY = y;
			}
			break;
		case MotionEvent.ACTION_UP:
			bMove = false;
			break;
		case MotionEvent.ACTION_DOWN:
			this.checkImageMove(x, y);
			break;
		}
		// 함수 override 해서 사용하게 되면  return  값이  super.onTouchEvent(event) 되므로
		// MOVE, UP 관련 이벤트가 연이어 발생하게 할려면 true 를 반환해주어야 한다.
		return true;
	}
	/**
	 * 현재 이미지위에 마우스가 위치하는지 판단한다.
	 * @param x
	 * @param y
	 */
	private void checkImageMove(int x, int y){
		int inWidth = 30;
		int inHeight = 20;
		if ((pImage.x - inWidth < x) && (x < pImage.x + inWidth)){
			if ((pImage.y - inHeight < y) && (y < pImage.y + inHeight)){
				bMove = true;
			}
		}
	}
}
반응형
Posted by 녹두장군

댓글을 달아 주세요

  1. 블루 2010.02.08 17:22  댓글주소  수정/삭제  댓글쓰기

    윈도우용 2D 게임은 많이 만들어 봤는데 안드로이드에 관심이 있던중에 자바를 잘 하지 못하여...

    이미지 출력, 키입력(터치등..), 사운드중첩 처리만 되면 게임을 만들수 있기에 그런곳을 찾아 다니던중

    많은 도움이 되었습니다. 감사합니다. ^ㅡ^

  2. Favicon of https://mainia.tistory.com 녹두장군 2010.02.09 00:07 신고  댓글주소  수정/삭제  댓글쓰기

    저도 게임에 관심이 많아 개발 해볼려고 했는데 여전히 업무에 쫓겨
    하지 못하고 있네요.. 지금은 안드로이드 어플 학습과 챌린저를 위해
    짬을 내서 개발을 하고 있습니다..

    서로 정보를 주고 받으며 좋은 인연이 되었으면 좋겠네요 ...

  3. 2010.07.21 21:13  댓글주소  수정/삭제  댓글쓰기

    비밀댓글입니다

    • Favicon of https://mainia.tistory.com 녹두장군 2010.07.21 23:06 신고  댓글주소  수정/삭제

      말로 해서는 제가 이해력이 떨어져서 모르겠네요..
      샘플로 볼수 있는 프로젝트를 통재로 주시면 한번
      봐드릴수 있습니다 ..

      메일은 gonhaha@naver.com 입니다

  4. 콩새마 2010.08.17 21:06  댓글주소  수정/삭제  댓글쓰기

    유용한 자료 감사합니다. ^^

    종료시 exception이 발생하여서, 다음과 같이 수정하였는데, 자바를 잘 몰라서
    제대로 수정한지는 모르겠읍니다.
    public void surfaceDestroyed(SurfaceHolder holder)
    {
    mbRun = false; <-- thread while 조건 변수 추가
    try {
    mThread.join(); <-- thread 종료 대기
    } catch (InterruptedException e) {
    // TODO Auto-generated catch block
    e.printStackTrace();
    }
    }

    @Override
    public void run() {
    // TODO Auto-generated method stub
    mpWindow.x = getWidth();
    mpWindow.y = getHeight();

    while(mbRun) {
    ......
    }
    }

    • 녹두장군 2010.08.18 10:16  댓글주소  수정/삭제

      그 부분을 세심하게 못봤네요..
      변수를 넣어서 값을 바꾸고 run 을 종료하는 것도 방법이구요. 전 이렇게 바꿨습니다 .
      thread 함수중 currentThread 가 있는데 현재 스레드를 얻어오는거죠. 거기서 isInterrupted 로 종료 되었는지 여부를 판단해서 interrupt() 함수를 사용해 종료하
      는 거죠

      그리고 interrupt() 함수로 종료하게 되면
      InterruptedException 은 발생하는 것이므로
      에러라든지 따로 처리가 필요한것은 아닙니다.

      public void surfaceDestroyed(SurfaceHolder holder) {
      try {
      thread.interrupt();
      } catch (Exception e) {
      Log.e(this.getClass().getName(), e.getMessage());
      }
      }

      public void run() {
      // Canvas 의 사이즈
      pWindow.x = getWidth();
      pWindow.y = getHeight();

      while (!Thread.currentThread().isInterrupted()) {
      Canvas c = null;

  5. 2010.08.20 01:31  댓글주소  수정/삭제  댓글쓰기

    비밀댓글입니다

  6. IronAnt 2010.09.28 08:09  댓글주소  수정/삭제  댓글쓰기

    좋은정보 감사드립니다. 어떻게 해야하나 곰곰히 생각해봤는데
    surfaceView를 사용하는것이 맞나보네용.~!! ^- ^

    지하도 알려주는 기본 프로그램처럼 더 큰 이미지도 한번 시도해봐야겠네요
    유용한 정보 감사드려요~! !

  7. maya 2010.10.07 10:37  댓글주소  수정/삭제  댓글쓰기

    좋은 정보 감사합니다.
    막연히 이렇게 만들고 싶다라는 생각만 갖고 시작했으나, 구현 방법에 대해 막연한 생각만 들어 답답하던 중 많은 정보를 얻었습니다.

  8. BaeraL 2010.10.12 15:18  댓글주소  수정/삭제  댓글쓰기

    항상 좋은 정보 잘 보고 갑니다..
    자주 애용하겠습니다!

    녹두장군님도 화이팅하세요!

    • 녹두장군 2010.10.14 09:29  댓글주소  수정/삭제

      네 감사합니다..이제 점점 날씨가 추워지고 있네요
      건강한 하루 되시길 바랍니다 ^^

  9. starling 2010.11.25 13:43  댓글주소  수정/삭제  댓글쓰기

    안녕하세요 강좌 잘봤습니다 ^ㅡ^

    혹시 카메라 프리뷰 위에 이미지를 띄우고 그 이미지와 프리뷰를 함께 저장되는 방법 알수있을까요..?

    어떤경로로 찾아봐야 하는지 ... 궁금합니다.

  10. 궁금녀 2011.03.10 16:17  댓글주소  수정/삭제  댓글쓰기

    질문드릴게있습니다! 제가 이번에, 졸업작품으로 안드로이드를 이용해서 낱말카드 맞추기 게임을 만드려고 하는데요ㅠㅠ 위에 소스설명 참고해서 낱말카드 한장을 불러와서 드래그앤드롭하는것 까지는 성공했습니다..

    그런데 문제는, 9개의 낱말카드를 불러와서 각기 따로따로 이동해야 하는데.. 잘 안되네요ㅠㅠ

    이미지를 비트맵으로 읽어와서 하나하나 출력하는데까지는 했습니다만, 이미지 모두가 함께움직여요

    이경우, 사용자가 지금 터치한 이미지가 어떤 것인지 판별을 어떻게 해야 하는지 모르겠습니다ㅜㅜ
    도와주세요!!!!

    • 녹두장군 2011.03.11 09:20  댓글주소  수정/삭제

      자바 함수중에 좌표갑인 Rect 값 즉 left,top,width,height 를 넣게 되면 영역에 포함되는지에 대한 여부를 알려주는 것이 있을 겁니다.
      아니면 배열로 영역의 값을 셋팅하고 for 문을 돌면서 어디에 포함되는지 찾아 그 영역의 번호를 리턴하는 방식으로 짜면 될것 같은데요.
      고민해 보시고 정안되시면 다시 연락주세요

      봄이네요 .. 만끽합시다~~

  11. 에레보스 2011.03.28 16:43  댓글주소  수정/삭제  댓글쓰기

    저도 질문이 있는데요 지금 한개 사물이 지정해서 움직이는 걸 하셨는데
    한 화면에 두가지 사물을 놓고 예제처럼 이동을 시킬 수 있나요?
    아직 감이 잘 안 와서요.. ㅎ.. 좀 가르쳐주셨으면 합니다.

    • 녹두장군 2011.03.29 21:05  댓글주소  수정/삭제

      두 손가락으로 동시에 움직이신다는 것인지? 아니면 하나씩
      따로 움직이신다는 건지 ? 제가 질문에 대해서 명확히
      알수가 없지만 두개 동시에 움직이는건 멀티터치로 이용하면 될것같고
      하나씩 움직인다면 그림만 추가될뿐 지금 소스에서 약간만 수정하면 될것 같아요..

      그러니까 그림의 현위치와 Bitmap 객체를 저장할 자료구조클래스가
      필요하겠죠. 여기에 그림을 추가해 for 문을 돌면서 그림을 하나씩
      꺼내 저장된 위치를 검사해서 터치 이벤트에 그속에 포함되는지
      검사한후 이동 시키면 될것 같아요

  12. 성이 2011.05.17 16:39  댓글주소  수정/삭제  댓글쓰기

    질문이 있는데요... 제가 지금 키이벤트를 받아서 이미지 하나는 움직일수 있게 하고 다른이미지는 중간에
    그려놓기만 했거든요?? 중간에 가만히 있는 이미지를 움직이게 하려면 어떻게 하나요.. 쓰레드로 움직일수 있다고 하던데... 감이 잘 안잡히내요... 혹시... 키이벤트 받은 이미지랑 서있는이미지랑 충돌판정도 하실수 있으신가요...

  13. 학생 2011.05.17 20:32  댓글주소  수정/삭제  댓글쓰기

    좋은 코드 감사합니다 ^^

    다만, 화면에 이미지를 2개 를 띄웠을때 따로 움직이게 할 수 있는방법좀 알려주세요~

    이미지 2개를 띄워서 마우스를 드래그할때마다 2개의 이미지들이 같이 움직이네요 ㅜㅜ

    따로 움직이는 방법좀 알려주시면 감사하겠습니다

    위에 이 문제와 관련된 댓글을 봤는데요 도저히 어떻게 코딩을 해야할지 모르겠어요 ㅜㅜ
    감이 않잡히네요

  14. Sinclair 2012.06.18 20:27  댓글주소  수정/삭제  댓글쓰기

    좋은 정보 감사드립니다^^ 많이 배우고 갑니다.

  15. 박병춘 2013.08.22 21:59  댓글주소  수정/삭제  댓글쓰기

    고맙습니다.

  16. 덜덜해 2014.11.24 10:20  댓글주소  수정/삭제  댓글쓰기

    좋은 글 잘보고 갑니다.
    궁금한게 하나 있어 질문드립니다.
    메인엑티비티를 하나 만들어서 해당 액티비티를 인텐트해서 호출하여 도형일 이리저리 움직인 후
    뒤로가기 버튼을 누루니 오류가 나면서 종료가 되더군요
    왜그럴까요?

    • 녹두장군 2014.11.25 18:39  댓글주소  수정/삭제

      현재 페이지에 나와있는 전체소스를 적용했을때
      그렇게 나온다는 말이시죠?