1. SurfaceView
SurfaceView를 활용한 바운스 볼(공 튀기기) 구현하기
목표: SurfaceView와 별도의 렌더링 스레드를 사용하여, 우주 배경 위에서 화면 경계에 부딪히면 튕겨 나오는 공 애니메이션을 구현하시오.
[세부 조건]
- 화면 설정 (MainActivity): 기본 XML 레이아웃을 사용하지 않고, 타이틀바(Title Bar)를 제거한 뒤 커스텀 뷰(GameView)를 메인 화면으로 꽉 채워 설정한다.
- 객체 분리 (Ball.java): 공의 상태(위치, 크기, 이동 속도, 이미지)와 동작(그리기, 이동, 벽 충돌 처리)을 관리하는 독립적인 클래스를 작성한다.
- 이동 중 화면 경계(SurfaceFrame)의 상/하/좌/우 끝에 도달하면 이동 방향(delta)에 -1을 곱해 반대로 튕기게 만든다.
- SurfaceView 구현 (GameView.java): SurfaceView를 상속받고 SurfaceHolder.Callback을 구현하여 생명주기를 관리한다.
- 게임 루프(스레드) 구성: 메인 UI 스레드가 아닌 별도의 스레드를 생성해 무한 루프를 돌며 다음 작업을 고속으로 반복한다.
- 공의 좌표 업데이트 (이동 및 충돌 계산)
- Canvas 잠금 (lockCanvas())
- 우주 배경 이미지(bg_space)를 화면 크기에 맞춰 그리고, 그 위에 공(red_ball)을 그림
- Canvas 잠금 해제 및 화면 갱신 (unlockCanvasAndPost())
- 초기 세팅: 공의 크기는 100x100으로 설정하고, 좌표 (0,0)에서 X축으로 15, Y축으로 30의 속도(delta)로 출발시킨다. 앱이 종료되면(surfaceDestroyed) 스레드도 안전하게 종료되도록 플래그(goOnPlay)를 처리한다.
▼결과화면

▼activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/main"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
▼MainActivity.java
package kr.ac.dju.surfaceview;
import android.os.Bundle;
import android.view.Window;
import androidx.activity.EdgeToEdge;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.graphics.Insets;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(new GameView(this));
}
}
▼GameView.java
package kr.ac.dju.surfaceview;
import static android.content.ContentValues.TAG;
//import static androidx.appcompat.graphics.drawable.DrawableContainerCompat.Api21Impl.getResources;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Point;
import android.graphics.drawable.Drawable;
import android.util.Log;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import org.jspecify.annotations.NonNull;
public class GameView extends SurfaceView
implements SurfaceHolder.Callback
{
private static final String TAG = GameView.class.getName();
private final SurfaceHolder holder;
private boolean goOnPlay = true;
private Ball ball;
private Thread renderer = new Thread() {
@Override // 부모 메소드 재정의
public void run() {
super.run();
Drawable bg = getResources().getDrawable(R.drawable.bg_space);
bg.setBounds(holder.getSurfaceFrame());
ball.setDelta(15,30);
while(goOnPlay) {
ball.move(holder.getSurfaceFrame());
Canvas canvas = holder.lockCanvas();
bg.draw(canvas);
ball.draw(canvas);
holder.unlockCanvasAndPost(canvas);
}
}
};
//생성자
public GameView(Context context) {
super(context);
Log.i(TAG,"GameView created");
holder = getHolder();
holder.addCallback(this);
}
@Override
public void surfaceCreated(@NonNull SurfaceHolder holder) {
renderer.start();
ball = new Ball();
ball.setImage(getResources().getDrawable(R.drawable.red_ball));
ball.setSize(new Point(100, 100));
ball.setPoint(new Point(0,0));
}
@Override
public void surfaceChanged(@NonNull SurfaceHolder holder, int format, int width, int height) {
}
@Override
public void surfaceDestroyed(@NonNull SurfaceHolder holder) {
goOnPlay = false; // kill thread
}
}
▼Ball.java
package kr.ac.dju.surfaceview;
import android.graphics.Canvas;
import android.graphics.ColorFilter;
import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
public class Ball {
private Drawable image = null;
private Point point = new Point();
private Point size = new Point();
private Point delta;
public Drawable getImage() {
return image;
}
public void setImage(Drawable image) {
this.image = image;
}
public Point getPoint() {
return point;
}
public void setPoint(Point point) {
this.point = point;
}
public Point getSize() {
return size;
}
public void setSize(Point size) {
this.size = size;
}
public void draw(Canvas canvas) {
image.setBounds(point.x, point.y, point.x+size.x, point.y+size.y);
image.draw(canvas);
}
public void setDelta(int dx, int dy) {
delta = new Point(dx,dy);
}
public void move(Rect surfaceFrame) {
// X axis collision
if(point.x + delta.x <0 ||
point.x + delta.x +size.x>surfaceFrame.right) delta.x*=-1;
else point.x +=delta.x;
// Y axis collision
if(point.y + delta.y <0 ||
point.y + delta.y +size.y>surfaceFrame.bottom) delta.y*=-1;
else point.y +=delta.y;
}
}'[Android] 안드로이드' 카테고리의 다른 글
| [ 안드로이드 기본 ] 터치로 움직이고 중력이 작용하는 CutomView 만들기 (0) | 2026.04.15 |
|---|---|
| [ 안드로이드 기본 ] 간단한 Thread 테스트 (0) | 2026.04.15 |
| [ 안드로이드 기본 ] 간단한 Handler 테스트 (0) | 2026.04.10 |
| [ 안드로이드 기본 ] 간단한 게임화면 구현 _ ScreenChange (1) | 2026.04.10 |
| [ 안드로이드 기본 ] Toast - setOnClickListener() , LogCat - 로그출력 (0) | 2026.04.10 |