You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

Android SurfaceView仅绘制两次:如何让onDraw方法持续调用?

解决SurfaceView仅渲染两次的问题

嘿,我来帮你搞定这个SurfaceView的绘图问题!首先得明确一点:SurfaceView的onDraw()方法本来就不是用来实现持续渲染的,这也是你设置setWillNotDraw(false)后依然只触发两次的核心原因。

为什么你的代码会只渲染两次?

SurfaceView的设计和普通View完全不同:它的绘图区域是一个独立的Surface,和UI线程的View层级是分离的。onDraw()在这里只是用来绘制Surface上方的View内容(比如一些叠加层),而不是更新Surface本身的画面。默认情况下,SurfaceView的onDraw()只会在View初始化和布局变化时被调用,所以你看到的两次渲染就是这个原因。

正确的持续渲染方式:用SurfaceHolder+子线程循环绘图

要实现持续调用绘图逻辑,你需要利用SurfaceHolder的API,在子线程中循环获取Canvas、绘制内容、提交画布。具体步骤如下:

  • 定义一个绘图线程类,负责循环绘制
  • surfaceCreated()回调中启动线程
  • surfaceDestroyed()回调中停止线程,避免内存泄漏
  • 在线程中通过lockCanvas()获取画布,绘制完成后用unlockCanvasAndPost()提交

修改后的完整代码示例

public class MainActivity extends Activity implements SurfaceHolder.Callback {
    private static final String TAG = "SurfaceView";
    private SurfaceView surfaceView;
    private SurfaceHolder surfaceHolder;
    private DrawThread drawThread;
    private boolean isDrawing = false;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
        surfaceView = findViewById(R.id.surface_view);
        surfaceHolder = surfaceView.getHolder();
        surfaceHolder.addCallback(this);
    }

    @Override
    public void surfaceCreated(SurfaceHolder holder) {
        // 启动绘图线程
        isDrawing = true;
        drawThread = new DrawThread();
        drawThread.start();
    }

    @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
        // 当Surface尺寸变化时,可以在这里调整绘图参数
    }

    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {
        // 停止线程,避免内存泄漏
        isDrawing = false;
        try {
            drawThread.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    // 自定义绘图线程
    private class DrawThread extends Thread {
        @Override
        public void run() {
            super.run();
            while (isDrawing) {
                Canvas canvas = null;
                try {
                    // 锁定画布,获取绘图权限
                    canvas = surfaceHolder.lockCanvas();
                    if (canvas != null) {
                        // 在这里写你的绘图逻辑,比如绘制图形、文字等
                        drawContent(canvas);
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    // 必须释放画布,否则会导致Surface卡顿或崩溃
                    if (canvas != null) {
                        surfaceHolder.unlockCanvasAndPost(canvas);
                    }
                }
                
                // 控制帧率,比如每秒30帧
                try {
                    Thread.sleep(33);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    // 你的绘图逻辑方法
    private void drawContent(Canvas canvas) {
        // 示例:绘制背景和一个移动的矩形
        canvas.drawColor(Color.WHITE);
        long currentTime = System.currentTimeMillis();
        float x = (currentTime % 1000) / 1000f * surfaceView.getWidth();
        canvas.drawRect(x, 100, x + 100, 200, new Paint(Color.RED));
    }
}

关键注意事项

  • 必须在finally块中释放画布:如果不调用unlockCanvasAndPost(),Surface会被一直锁定,导致无法继续绘图甚至ANR。
  • 控制帧率:添加Thread.sleep()避免CPU占用过高,根据需求调整睡眠时间(比如33ms对应约30帧,16ms对应约60帧)。
  • 线程安全:确保isDrawing变量的可见性,或者用volatile修饰,避免线程间的状态不一致。

内容的提问来源于stack exchange,提问作者likaa

火山引擎 最新活动