Android App 에서 SurfaceView 를 사용 하여 다 중 스 레 드 애니메이션 을 만 드 는 실례 설명

16339 단어 AndroidSurfaceView
1.Surface View 의 정의
일반적인 상황 프로그램의 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 가 실제 프로젝트 에서 사용 하 는 것 을 배우 고 요정 을 그립 니 다.이 요정 은 네 방향 으로 걷 는 애니메이션 을 가지 고 요정 이 화면 주 위 를 계속 걸 을 수 있 도록 합 니 다.게임 에서 엘 프 소재 와 최종 구현 효과 도:
2016428160517972.png (330×250)
2016428160544089.png (338×177)
먼저 핵심 클래스 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);
 }
현재 안 드 로 이 드 프로젝트 를 실행 하면 보검 을 든 무사 가 화면 을 따라 끊임없이 가 는 것 을 볼 수 있 을 것 이다.

좋은 웹페이지 즐겨찾기