2

「setContentView」で、SurfaceView を拡張するクラスのビューを使用するアクティビティを作成しました。問題は次のとおりです: 正常に動作しますが、終了 (BACK キー) するとクラッシュします。コード:

package ro.etrandafir.mate.appCreator;

import android.app.Activity;
import android.os.Bundle;
import android.content.Context;
import android.view.View;
import android.view.SurfaceView;
import android.graphics.Canvas;
import android.view.MotionEvent;
import android.graphics.Color;
import android.graphics.Paint;

public class Sample2 extends Activity implements View.OnTouchListener {

    float x = 0, y = 0;
    SampleTwoView theView;

    public boolean onTouch(View v, MotionEvent event) {
        // TODO: Implement this method
        x = event.getX();
        y = event.getY();
        return true;
    }

    @Override
    protected void onPause() {
        super.onPause();
        finish();
    }

    @Override
    protected void onCreate(Bundle b) {
        super.onCreate(b);
    theView = new SampleTwoView(this);
        theView.setOnTouchListener(this);
        setContentView(theView);
    }

    public class SampleTwoView extends SurfaceView implements Runnable {

        Paint p = new Paint();

        public SampleTwoView(Context context) {
            super(context);
            p.setColor(Color.RED);
            Thread theThread = new Thread(this);
            theThread.start();
        }

        public void run() {
            while (true) {
                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);
            }
        }
    }
}

私に何ができる?onDestroy または何を追加する必要がありますか?

前もってありがとう、マテイ

4

2 に答える 2

2

発生している問題は、次のコードに関連しています。

Canvas canvas;
canvas = getHolder().lockCanvas();
canvas.drawColor(Color.BLUE);

アクティビティが終了すると、スレッドは引き続き実行されますが、カスタムSurfaceViewは使用できなくなるため、null ptr 例外が発生します。onPausefn が呼び出されるとすぐに 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コーディングに関するもう少し情報が役立つと思いました.

于 2013-03-14T19:50:03.193 に答える
2

上記で誰かが言及したように、アクティビティが終了してもスレッドは実行されていますが、カスタム SurfaceView は使用できなくなるため、Null Point Exception が発生します。onPause fn が呼び出されるとすぐに false に設定されるブール値を追加することで、既存のコードに簡単にパッチを適用できます。同じ問題がありました。それを解決するために、SampleTwoView クラスに次の onPause() を追加しました。

// pause method will destroy the Thread
    public void pause() {
        isRunning = false;
        while (true) {
            try {
                myThread.join();
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            break;
        }
        myThread = null;
    }

次に、Sample2 クラスの onPause() メソッドでこの onPause() メソッドを次のように呼び出します。

@Override
protected void onPause() {
    super.onPause();
    SampleTwoView.onPause();
    finish();
}

したがって、メインの Activity クラスの onPause() メソッドが呼び出されるたびに、スレッドが破棄されます。これがお役に立てば幸いです。乾杯!

于 2014-01-08T03:56:04.683 に答える