発生している問題は、次のコードに関連しています。
Canvas canvas;
canvas = getHolder().lockCanvas();
canvas.drawColor(Color.BLUE);
アクティビティが終了すると、スレッドは引き続き実行されますが、カスタムSurfaceView
は使用できなくなるため、null ptr 例外が発生します。onPause
fn が呼び出されるとすぐに false に設定されるブール値を追加することで、既存のコードに簡単にパッチを適用できます。
public void run() {
while (booleanThatGetsSetToFalseWhenActivityPauses) {
if (!getHolder().getSurface().isValid()) continue;
Canvas canvas;
canvas = getHolder().lockCanvas();
canvas.drawColor(Color.BLUE);
if ((x != 0) && (y != 0)) canvas.drawCircle(x, y, 40, p);
getHolder().unlockCanvasAndPost(canvas);
}
}
ただし、アプリケーション全体の構造を変更することをお勧めします。これは単なる練習用かもしれませんが、目標を達成するためのより効率的でバグのない方法は、単に標準SurfaceView
を使用し、描画ロジックをカスタム ビューから完全に分離することだと思います。
再設計されたアクティビティを以下に示しますがBall
、ボールのロジックを維持するために使用されるクラスを利用しています。現在のコードでは、アクティビティ (座標) とビュー ( ) の両方に個別に結合されていますPaint
。この新しいボール クラスでは、ボールは位置 ( a で指定PointF
)、 a Paint
、および直径を持ちます。一部の設定に加えて、これらの変数のほとんどを取得するメソッドもあります。
public class Ball {
private Paint mPaint;
private PointF mCoordinates;
private int mDiameter;
public Ball (int color, int diameter) {
mPaint = new Paint();
mPaint.setColor(color);
mCoordinates = new PointF();
mCoordinates.x = 0;
mCoordinates.y = 0;
mDiameter = diameter;
}
public void setCoordinates (float x, float y) {
mCoordinates.x = x;
mCoordinates.y = y;
}
public PointF getCoordinates() {
return mCoordinates;
}
public Paint getPaint() {
return mPaint;
}
public int getDiameter() {
return mDiameter;
}
/* You did not want to draw the uninitialized ball, so this method checks that */
public boolean hasNonZeroLocation () {
return (mCoordinates.x != 0 && mCoordinates.y != 0);
}
}
Ball
以下に示すように、アクティビティでクラスを使用します。キャンバスへの再描画は、無限 while ループではなく、ユーザーがキャンバスに触れたときにのみ発生することに注意してください。Handler
これは、実行するアクションを UI スレッドにポストするクラスの使用によるものです。さらに、カスタム ビューが不要になり、ボールのロジックがアクティビティとビューから分離されました。
public class RedBallActivity extends Activity {
Handler mDrawingHandler;
SurfaceView mDrawingSurfaceView;
Ball mBall;
private final Runnable drawRedBallOnBlueSurface = new Runnable() {
@Override
public void run() {
if (!mDrawingSurfaceView.getHolder().getSurface().isValid()) return;
Canvas canvas = mDrawingSurfaceView.getHolder().lockCanvas();
canvas.drawColor(Color.BLUE);
if (mBall.hasNonZeroLocation())
canvas.drawCircle(mBall.getCoordinates().x, mBall.getCoordinates().y, mBall.getDiameter(), mBall.getPaint());
mDrawingSurfaceView.getHolder().unlockCanvasAndPost(canvas);
}
};
private final OnTouchListener mCanvasTouchListener = new OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
mBall.setCoordinates(event.getX(), event.getY());
mDrawingHandler.post(drawRedBallOnBlueSurface);
return true;
}
};
@Override
protected void onCreate(Bundle b) {
super.onCreate(b);
mDrawingSurfaceView = new SurfaceView(this);
mDrawingSurfaceView.setOnTouchListener(mCanvasTouchListener);
setContentView(mDrawingSurfaceView);
mBall = new Ball(Color.RED, 40);
mDrawingHandler = new Handler();
}
}
実際にこのコードを実行すると、最初は画面が青色の背景で描画されていないことに気付くでしょう。mDrawingHandler.post(drawRedBallOnBlueSurface);
メソッドの最後で単純に呼び出したいと思うかもしれませんonCreate
が、SurfaceView を描画する準備が整っているとは限りません (この lockCanvas メソッドに関するドキュメントを参照してください)。表面を最初に青色にしたい場合は[SurfaceHolder.Callback][2]
、SurfaceView の SurfaceHolder に接続する必要がある を実装する必要があります。surfaceCreated
呼び出されるメソッドで、表面の準備ができていることがわかっているので、呼び出すことができます。mDrawingHandler.post(drawRedBallOnBlueSurface);
これを追加して、アクティビティを[SurfaceHolder.Callback][2]
次のように実装するように変更します。
public class FriendManagerActivity extends Activity implements SurfaceHolder.Callback {
次の行をコンストラクターに追加します。
mDrawingSurfaceView.getHolder().addCallback(this);
インターフェースを実装します。
@Override
public void surfaceCreated(SurfaceHolder holder) {
mDrawingHandler.post(drawRedBallOnBlueSurface);
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width,
int height) {
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
}
私のちょっとしたリデザインについて質問があればお気軽にどうぞ!あなたの問題は簡単にパッチを当てることができますが、ロジックをビューと結合する方法に少し欠陥があるように感じました.SurfaceViewコーディングに関するもう少し情報が役立つと思いました.