Android App 에서 SurfaceView 를 사용 하여 다 중 스 레 드 애니메이션 을 만 드 는 실례 설명
16339 단어 AndroidSurfaceView
일반적인 상황 프로그램의 View 와 사용자 응답 은 같은 스 레 드 에서 처 리 됩 니 다.이것 은 장시간 이벤트(예 를 들 어 네트워크 방문)를 처리 할 때 다른 스 레 드 에 넣 어야 하 는 이유 입 니 다(현재 UI 스 레 드 의 조작 과 그리 기 를 막 는 것 을 방지 합 니 다).그러나 다른 스 레 드 에 서 는 UI 요 소 를 수정 할 수 없습니다.예 를 들 어 배경 스 레 드 로 사용자 정의 View 를 업데이트 하 는 것 은 허용 되 지 않 습 니 다.
다른 라인 에서 인 터 페 이 스 를 그 릴 필요 가 있 거나,신속 한 업데이트 인터페이스 가 필요 하거나,UI 인 터 페 이 스 를 렌 더 링 하 는 데 시간 이 오래 걸 릴 경우 SurfaceView 를 사용 해 야 한다.Surface View 에는 Surface 대상 이 포함 되 어 있 으 며 Surface 는 배경 스 레 드 에서 그 릴 수 있 습 니 다.Surface View 의 특성 은 일부 장면 에 비교적 적합 하 다 는 것 을 결정 한다.인터페이스 가 신속하게 업데이트 되 고 프레임 율 에 대한 요구 가 높 은 상황 이 필요 하 다.Surface View 를 사용 하려 면 다음 과 같은 몇 가지 상황 에 주의해 야 합 니 다.
Surface View 와 Surface Holder.Callback 함 수 는 현재 Surface View 창 스 레 드 에서 호출 됩 니 다(일반적으로 프로그램의 메 인 스 레 드 입 니 다).자원 상태 에 대해 서 는 스 레 드 간 의 동기 화 에 주의해 야 합 니 다.
스 레 드 를 그 릴 때 Surface 를 합 법 적 으로 가 져 와 야 내용 을 그 릴 수 있 습 니 다.Surface Holder.Callback.surfaceCreated()와 Surface Holder.Callback.surfaceDestroyed()사이 의 상 태 는 합 법 적 입 니 다.또한 Surface 형식 은 SURFACE 입 니 다.TYPE_PUSH_BUFFERS 는 비합법적 입 니 다.
스 레 드 를 추가 로 그리 면 시스템 자원 이 소모 되 므 로 Surface View 를 사용 할 때 주의해 야 합 니 다.
2.Surface View 사용
먼저 Surface View 를 계승 하고 Surface Holder.Callback 인 터 페 이 스 를 실현 하 며 세 가지 방법 을 실현 합 니 다.surfaceCreated,surfaceChanged,surfaceDestroyed.
(1)surfaceCreated(SurfaceHolder holder):surface 를 만 들 때 호출 합 니 다.보통 이 방법 에서 그림 을 그 리 는 스 레 드 를 시작 합 니 다.
(2)surfaceChanged(SurfaceHolder holder,int format,int width,int height):surface 크기 가 바 뀌 었 을 때 호출 합 니 다.예 를 들 어 가로 세로 화면 전환 등 입 니 다.
(3)surfaceDestroyed(SurfaceHolder holder):surface 가 삭 제 될 때 호출 됩 니 다.게임 화면 을 종료 할 때 이 방법 에서 그림 그리 기 스 레 드 를 중단 합 니 다.
Surface Holder 를 얻 고 리 셋 함 수 를 추가 해 야 이 세 가지 방법 이 실 행 됩 니 다.
Surface View 클래스 를 계승 하여 Surface Holder.Callback 인 터 페 이 스 를 실현 하면 사용자 정의 Surface View 를 실현 할 수 있 습 니 다.Surface Holder.Callback 은 바 텀 Surface 상태 가 변화 할 때 View 에 알 립 니 다.Surface Holder.Callback 은 다음 과 같은 인 터 페 이 스 를 가지 고 있 습 니 다.
(1)surfaceCreated(SurfaceHolder holder):Surface 가 처음 생 성 되면 바로 이 함 수 를 호출 합 니 다.프로그램 은 이 함수 에서 화면 그리 기 와 관련 된 초기 화 작업 을 할 수 있 습 니 다.일반적으로 다른 스 레 드 에서 화면 을 그립 니 다.따라서 이 함수 에서 Surface 를 그리 지 마 십시오.
(2)surfaceChanged(SurfaceHolder holder,int format,int width,int height):Surface 의 상태(크기 와 형식)가 변 할 때 이 함 수 를 호출 합 니 다.surfaceCreated 호출 후 이 함 수 는 적어도 한 번 호출 됩 니 다.
(3)surfaceDestroyed(Surface Holder holder):Surface 가 파괴 되 기 전에 이 함 수 를 호출 합 니 다.이 함수 가 호출 되면 Surface 를 계속 사용 할 수 없습니다.보통 이 함수 에서 사용 하 는 자원 을 정리 합 니 다.
Surface View 의 getHolder()함 수 를 통 해 Surface Holder 대상 을 가 져 올 수 있 으 며 Surface 는 Surface Holder 대상 안에 있 습 니 다.서 피 스 는 현재 창의 픽 셀 데 이 터 를 저장 하고 있 지만 사용 중 서 피 스 와 직접 접촉 하지 않 고 서 피 스 홀더 의 Canvas lockCanvas()또는 Canvas lockCanvas(Rect dirty)함수 로 Canvas 대상 을 가 져 오고 Canvas 에 내용 을 그 려 서 피 스 의 데 이 터 를 수정 합 니 다.서 피 스 를 편집 할 수 없 거나 호출 함 수 를 만 들 지 않 으 면 null 로 돌아 갑 니 다.unlockCanvas()와 lockCanvas()에서 서 피 스 의 내용 은 캐 시 되 지 않 기 때문에 서 피 스 의 내용 을 완전히 다시 그 려 야 합 니 다.효율 을 높이 기 위해 변 화 된 부분 만 다시 그 리 려 면 lockCanvas(Rect dirty)함수 로 dirty 영역 을 지정 하면 이 영역 밖의 내용 이 캐 시 됩 니 다.lockCanvas 함 수 를 호출 하여 Canvas 를 가 져 온 후 Surface View 는 unlockCanvas AndPost(Canvas canvas)함 수 를 호출 할 때 까지 Surface 의 동기 화 자 물 쇠 를 가 져 옵 니 다.이 동기 화 체 제 는 Surface 그리 기 과정 에서 변경 되 지 않도록 합 니 다(파괴,수정).
Canvas 에서 그리 기 가 완료 되면 함수 unlockCanvas AndPost(Canvas canvas)를 호출 하여 시스템 Surface 가 그 려 졌 음 을 알 립 니 다.그러면 시스템 은 그 려 진 내용 을 표시 합 니 다.서로 다른 플랫폼 의 자원 을 충분히 이용 하기 위해 플랫폼 의 가장 좋 은 효 과 를 발휘 하기 위해 Surface Holder 의 setType 함 수 를 통 해 그 려 진 유형 을 설정 할 수 있 습 니 다.현 재 는 다음 과 같은 인 자 를 받 습 니 다.
(1)SURFACE_TYPE_NORMAL:네 이 티 브 데 이 터 를 RAM 으로 캐 시 하 는 일반 Surface
(2)SURFACE_TYPE_HARDWARE:DMA(Direct memory access)엔진 과 하드웨어 가속 에 적용 되 는 Surface
(3)SURFACE_TYPE_GPU:GPU 가속 에 적용 되 는 Surface
(4)SURFACE_TYPE_PUSH_BUFFERS:이 Surface 는 네 이 티 브 데 이 터 를 포함 하지 않 음 을 나타 내 며 Surface 에서 사용 하 는 데 이 터 는 다른 대상 이 제공 합 니 다.Camera 이미지 미리 보기 에 서 는 이 유형의 Surface 를 사용 합 니 다.Camera 가 Surface 데 이 터 를 미리 보기 위해 제공 합 니 다.그러면 이미지 미리 보기 가 원활 합 니 다.이런 종 류 를 설정 하면 lockCanvas 를 호출 하여 Canvas 대상 을 가 져 올 수 없습니다.
Surface View 를 방문 하 는 바 텀 그래 픽 은 Surface Holder 인 터 페 이 스 를 통 해 이 루어 지 며 getHolder()방법 으로 이 Surface Holder 대상 을 얻 을 수 있 습 니 다.surfaceCreated(SurfaceHolder)와 surfaceDestroyed(SurfaceHolder)방법 을 실현 하여 이 Surface 가 창의 표시 와 숨 기 는 과정 에서 언제 만 들 고 소각 되 었 는 지 알 아야 합 니 다.
메모:하나의 Surface View 는 Surface Holder.Callback.surfaceCreated()와 Surface Holder.Callback.surfaceDestroyed()호출 사이 에 만 사용 할 수 있 으 며,다른 시간 에는 Canvas 대상(null)을 얻 을 수 없습니다.
3.SurfaceView 실전
다음은 하나의 작은 demo 를 통 해 Surface View 가 실제 프로젝트 에서 사용 하 는 것 을 배우 고 요정 을 그립 니 다.이 요정 은 네 방향 으로 걷 는 애니메이션 을 가지 고 요정 이 화면 주 위 를 계속 걸 을 수 있 도록 합 니 다.게임 에서 엘 프 소재 와 최종 구현 효과 도:
먼저 핵심 클래스 GameView 를 만 듭 니 다.자바,원본 코드 는 다음 과 같 습 니 다.
public class GameView extends SurfaceView implements
SurfaceHolder.Callback {
//
public static int SCREEN_WIDTH;
public static int SCREEN_HEIGHT;
private Context mContext;
private SurfaceHolder mHolder;
// (1000 / 30)
private static final int DRAW_INTERVAL = 30;
private DrawThread mDrawThread;
private FrameAnimation []spriteAnimations;
private Sprite mSprite;
private int spriteWidth = 0;
private int spriteHeight = 0;
private float spriteSpeed = (float)((500 * SCREEN_WIDTH / 480) * 0.001);
private int row = 4;
private int col = 4;
public GameSurfaceView(Context context) {
super(context);
this.mContext = context;
mHolder = this.getHolder();
mHolder.addCallback(this);
initResources();
mSprite = new Sprite(spriteAnimations,0,0,spriteWidth,spriteHeight,spriteSpeed);
}
private void initResources() {
Bitmap[][] spriteImgs = generateBitmapArray(mContext, R.drawable.sprite, row, col);
spriteAnimations = new FrameAnimation[row];
for(int i = 0; i < row; i ++) {
Bitmap []spriteImg = spriteImgs[i];
FrameAnimation spriteAnimation = new FrameAnimation(spriteImg,new int[]{150,150,150,150},true);
spriteAnimations[i] = spriteAnimation;
}
}
public Bitmap decodeBitmapFromRes(Context context, int resourseId) {
BitmapFactory.Options opt = new BitmapFactory.Options();
opt.inPreferredConfig = Bitmap.Config.RGB_565;
opt.inPurgeable = true;
opt.inInputShareable = true;
InputStream is = context.getResources().openRawResource(resourseId);
return BitmapFactory.decodeStream(is, null, opt);
}
public Bitmap createBitmap(Context context, Bitmap source, int row,
int col, int rowTotal, int colTotal) {
Bitmap bitmap = Bitmap.createBitmap(source,
(col - 1) * source.getWidth() / colTotal,
(row - 1) * source.getHeight() / rowTotal, source.getWidth()
/ colTotal, source.getHeight() / rowTotal);
return bitmap;
}
public Bitmap[][] generateBitmapArray(Context context, int resourseId,
int row, int col) {
Bitmap bitmaps[][] = new Bitmap[row][col];
Bitmap source = decodeBitmapFromRes(context, resourseId);
this.spriteWidth = source.getWidth() / col;
this.spriteHeight = source.getHeight() / row;
for (int i = 1; i <= row; i++) {
for (int j = 1; j <= col; j++) {
bitmaps[i - 1][j - 1] = createBitmap(context, source, i, j,
row, col);
}
}
if (source != null && !source.isRecycled()) {
source.recycle();
source = null;
}
return bitmaps;
}
public void surfaceChanged(SurfaceHolder holder, int format, int width,
int height) {
}
public void surfaceCreated(SurfaceHolder holder) {
if(null == mDrawThread) {
mDrawThread = new DrawThread();
mDrawThread.start();
}
}
public void surfaceDestroyed(SurfaceHolder holder) {
if(null != mDrawThread) {
mDrawThread.stopThread();
}
}
private class DrawThread extends Thread {
public boolean isRunning = false;
public DrawThread() {
isRunning = true;
}
public void stopThread() {
isRunning = false;
boolean workIsNotFinish = true;
while (workIsNotFinish) {
try {
this.join();// run
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
workIsNotFinish = false;
}
}
public void run() {
long deltaTime = 0;
long tickTime = 0;
tickTime = System.currentTimeMillis();
while (isRunning) {
Canvas canvas = null;
try {
synchronized (mHolder) {
canvas = mHolder.lockCanvas();
//
mSprite.setDirection();
//
mSprite.updatePosition(deltaTime);
drawSprite(canvas);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if (null != mHolder) {
mHolder.unlockCanvasAndPost(canvas);
}
}
deltaTime = System.currentTimeMillis() - tickTime;
if(deltaTime < DRAW_INTERVAL) {
try {
Thread.sleep(DRAW_INTERVAL - deltaTime);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
tickTime = System.currentTimeMillis();
}
}
}
private void drawSprite(Canvas canvas) {
//
canvas.drawColor(Color.BLACK);
mSprite.draw(canvas);
}
}
GameView.java 에는 그림 스 레 드 DrawThread 가 포함 되 어 있 습 니 다.스 레 드 의 run 방법 에서 Canvas 를 잠 그 고 요정 을 그립 니 다.요정 의 위 치 를 업데이트 하고 Canvas 를 풀 어 줍 니 다.엘 프 소 재 는 큰 그림 이기 때문에 2 차원 배열 을 만 들 었 습 니 다.이 2 차원 배열 을 사용 하여 요정 의 네 방향 애니메이션 을 초기 화 했 습 니 다.다음은 Sprite.java 의 소스 코드 를 보 겠 습 니 다.
public class Sprite {
public static final int DOWN = 0;
public static final int LEFT = 1;
public static final int RIGHT = 2;
public static final int UP = 3;
public float x;
public float y;
public int width;
public int height;
//
public double speed;
//
public int direction;
//
public FrameAnimation[] frameAnimations;
public Sprite(FrameAnimation[] frameAnimations, int positionX,
int positionY, int width, int height, float speed) {
this.frameAnimations = frameAnimations;
this.x = positionX;
this.y = positionY;
this.width = width;
this.height = height;
this.speed = speed;
}
public void updatePosition(long deltaTime) {
switch (direction) {
case LEFT:
// , : *
this.x = this.x - (float) (this.speed * deltaTime);
break;
case DOWN:
this.y = this.y + (float) (this.speed * deltaTime);
break;
case RIGHT:
this.x = this.x + (float) (this.speed * deltaTime);
break;
case UP:
this.y = this.y - (float) (this.speed * deltaTime);
break;
}
}
/**
*
*/
public void setDirection() {
if (this.x <= 0
&& (this.y + this.height) < GameSurfaceView.SCREEN_HEIGHT) {
if (this.x < 0)
this.x = 0;
this.direction = Sprite.DOWN;
} else if ((this.y + this.height) >= GameSurfaceView.SCREEN_HEIGHT
&& (this.x + this.width) < GameSurfaceView.SCREEN_WIDTH) {
if ((this.y + this.height) > GameSurfaceView.SCREEN_HEIGHT)
this.y = GameSurfaceView.SCREEN_HEIGHT - this.height;
this.direction = Sprite.RIGHT;
} else if ((this.x + this.width) >= GameSurfaceView.SCREEN_WIDTH
&& this.y > 0) {
if ((this.x + this.width) > GameSurfaceView.SCREEN_WIDTH)
this.x = GameSurfaceView.SCREEN_WIDTH - this.width;
this.direction = Sprite.UP;
} else {
if (this.y < 0)
this.y = 0;
this.direction = Sprite.LEFT;
}
}
public void draw(Canvas canvas) {
FrameAnimation frameAnimation = frameAnimations[this.direction];
Bitmap bitmap = frameAnimation.nextFrame();
if (null != bitmap) {
canvas.drawBitmap(bitmap, x, y, null);
}
}
}
엘 프 류 는 현재 위치 에 따라 걷 는 방향 을 판단 한 다음 걷 는 방향 에 따라 엘 프 의 위 치 를 업데이트 하고 자신의 애니메이션 을 그립 니 다.요정 의 애니메이션 은 한 프레임 한 프레임 재생 그림 이기 때문에 여 기 는 FrameAnimation.java 를 봉 인 했 습 니 다.원본 코드 는 다음 과 같 습 니 다.
public class FrameAnimation{
/** */
private Bitmap[] bitmaps;
/** */
private int[] duration;
/** */
protected Long lastBitmapTime;
/** , */
protected int step;
/** */
protected boolean repeat;
/** */
protected int repeatCount;
/**
* @param bitmap: <br/>
* @param duration: <br/>
* @param repeat: <br/>
*/
public FrameAnimation(Bitmap[] bitmaps, int duration[], boolean repeat) {
this.bitmaps = bitmaps;
this.duration = duration;
this.repeat = repeat;
lastBitmapTime = null;
step = 0;
}
public Bitmap nextFrame() {
// step
if (step >= bitmaps.length) {
//
if( !repeat ) {
return null;
} else {
lastBitmapTime = null;
}
}
if (null == lastBitmapTime) {
//
lastBitmapTime = System.currentTimeMillis();
return bitmaps[step = 0];
}
// X
long nowTime = System.currentTimeMillis();
if (nowTime - lastBitmapTime <= duration[step]) {
// duration , Bitmap
// duration 0, ,
return bitmaps[step];
}
lastBitmapTime = nowTime;
return bitmaps[step++];// Bitmap
}
}
FrameAnimation 은 각 프레임 의 표시 시간 에 따라 현재 그림 프레임 을 되 돌려 줍 니 다.지정 한 시간 을 초과 하지 않 으 면 현재 프레임 을 계속 되 돌려 줍 니 다.그렇지 않 으 면 다음 프레임 으로 돌아 갑 니 다.다음 에 해 야 할 일 은 Activty 에 표 시 된 View 가 이전에 만 든 GameView 를 위해 전체 화면 디 스 플레이 를 설정 하 는 것 입 니 다.
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
WindowManager.LayoutParams.FLAG_FULLSCREEN);
requestWindowFeature(Window.FEATURE_NO_TITLE);
getWindow().setFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON,
WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
DisplayMetrics outMetrics = new DisplayMetrics();
this.getWindowManager().getDefaultDisplay().getMetrics(outMetrics);
GameSurfaceView.SCREEN_WIDTH = outMetrics.widthPixels;
GameSurfaceView.SCREEN_HEIGHT = outMetrics.heightPixels;
GameSurfaceView gameView = new GameSurfaceView(this);
setContentView(gameView);
}
현재 안 드 로 이 드 프로젝트 를 실행 하면 보검 을 든 무사 가 화면 을 따라 끊임없이 가 는 것 을 볼 수 있 을 것 이다.
이 내용에 흥미가 있습니까?
현재 기사가 여러분의 문제를 해결하지 못하는 경우 AI 엔진은 머신러닝 분석(스마트 모델이 방금 만들어져 부정확한 경우가 있을 수 있음)을 통해 가장 유사한 기사를 추천합니다:
Bitrise에서 배포 어플리케이션 설정 테스트하기이 글은 Bitrise 광고 달력의 23일째 글입니다. 자체 또는 당사 등에서 Bitrise 구축 서비스를 사용합니다. 그나저나 며칠 전 Bitrise User Group Meetup #3에서 아래 슬라이드를 발표했...
텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
CC BY-SA 2.5, CC BY-SA 3.0 및 CC BY-SA 4.0에 따라 라이센스가 부여됩니다.