您遇到的问题与此代码有关:
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
)、aPaint
和直径。除了设置一些变量之外,它还具有获取大多数这些变量的方法。
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);
现在,添加了这个,我将 Activity 更改[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 编码的信息会有所帮助。