Android:Canvas没有被重绘

时间:2013-09-01 18:26:45

标签: android multithreading android-canvas game-engine

有很多像我这样的类似问题,但这些问题对我没有帮助。

我正在制作游戏。游戏线程,SurfaceView和Activity已经完成并且到目前为止工作。问题是画布没有重绘。在启动时,它会在背景上绘制图标,但在每个刻度线上,图标都不会移动(它应该每秒移动一次)。我想提一下,我从来不需要打电话给 postInvalidate 。我有一个工作示例,我从来没有调用它,但它在我当前的例子中不起作用(我不想更深入,因为我实际上不需要调用它)。我从我的工作示例中复制了当前代码,概念和实现方式完全相同,但我当前的代码不刷新画布。当我在 onDraw 方法中记录绘图位置时,我看到它的坐标每秒都按预期更新,所以我可以确定这是一个画布绘图问题。我已经搜索了几个小时,但我没有找到与我的工作示例有什么不同(除了我正在使用另一个Android版本而且我没有扩展线程但是实现了Runnable,因为扩展线程是一种糟糕的风格。但是,我还扩展线程,看是否有任何差异,但它没有帮助)。我已经尝试使用 canvas.drawColor(Color.BLACK)来清理画布,但这也没有帮助。我已经尝试使用背景颜色而不是每个刻度随机变化的背景图像,但它没有改变但总是保持不变。 我发现在第一次调用时画布的密度为(例如)240。在第二次调整后,画布密度始终为0.我知道密度在这里对我没有帮助,但也许它是一个重要的信息对于某人。

以下是重要的课程......

游戏布局,R.layout.game

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/gameContainer"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >

    <com.mydomain.mygame.base.game.GameSurface
        android:id="@+id/gameSurface"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1.0"
        android:background="@drawable/background_game" >
    </com.mydomain.mygame.base.game.GameSurface>

<!-- ...more definitions -->

</LinearLayout>

GameActivity(包含布局)

public class GameActivity extends Activity
{
    @SuppressWarnings("unused")
    private GameSurface gameSurface;

    @Override
    protected void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.game);

        gameSurface = (GameSurface)findViewById(R.id.gameSurface);
        //TODO on button click -> execute methods
    }
}

GameSurface(登录onDraw显示每个滴答的更新坐标)

public class GameSurface extends SurfaceView implements SurfaceHolder.Callback
{
    private GameThread thread;
    protected final static int TICK_FREQUENCY = 100;// ms, stays always the same. It's a technical constant which doesn't change

    private static final String TAG = GameSurface.class.getSimpleName();

    public GameSurface(Context context, AttributeSet attrs)
    {
        super(context, attrs);

        ShapeManager.INSTANCE.init(context);

        SurfaceHolder holder = getHolder();
        holder.addCallback(this);

        setFocusable(true); // make sure we get key events

        thread = new GameThread(holder, this);
    }

    public void updateStatus()
    {
        GameProcessor.INSTANCE.updateShapes();
    }

    @Override
    protected void onDraw(Canvas canvas)
    {
        for (Shape shape : GameProcessor.INSTANCE.getShapes())
        {
            Log.i(TAG, "getX()=" + shape.getX() + ", getY()=" + shape.getY());
            canvas.drawBitmap(shape.getBitmap(), shape.getX(), shape.getY(), null);
        }
    }

    @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height)
    {
        //will never invoked since we only operate in landscape
    }

    @Override
    public void surfaceCreated(SurfaceHolder holder)
    {
        // start the thread here so we don't busy-wait in run
        thread.setRunning(true);
        new Thread(thread).start();
    }

    @Override
    public void surfaceDestroyed(SurfaceHolder holder)
    {
        Log.i(TAG, "executing surfaceDestroyed()...");  
        thread.setRunning(false);
    }
}

GameThread

public class GameThread implements Runnable
{
    private SurfaceHolder surfaceHolder;
    private boolean running = false;
    private GameSurface gameSurface;
    private long lastTick;

    public GameThread(SurfaceHolder surfaceHolder, GameSurface gameSurface)
    {
        this.surfaceHolder = surfaceHolder;
        this.gameSurface = gameSurface;

        lastTick = System.currentTimeMillis();
    }

    @Override
    public void run()
    {
        Canvas canvas;
        while (running)
        {
            canvas = null;
            if (System.currentTimeMillis() > lastTick + GameSurface.TICK_FREQUENCY)
            {
                long timeDifference = System.currentTimeMillis() - (lastTick + GameSurface.TICK_FREQUENCY);

                try
                {
                    canvas = surfaceHolder.lockCanvas(null);
                    synchronized (surfaceHolder)
                    {
                        gameSurface.updateStatus();
                        gameSurface.draw(canvas);
                    }
                }
                finally
                {
                    if (canvas != null)
                    {
                        surfaceHolder.unlockCanvasAndPost(canvas);
                    }
                }
                lastTick = System.currentTimeMillis() - timeDifference;
            }
        }
    }

    public void setRunning(boolean running)
    {
        this.running = running;
    }
}

为什么此代码不更新我的画布的任何想法?我无法解释。我不发布 ShapeManager GameProcessor ,因为它们与问题没有任何关系(它们只加载和控制游戏的当前状态和速度)。

[UPDATE]

我发现在游戏线程开始之前调用onDraw()。这意味着在线程使用它之前会将 canvas 传递给此方法。有趣的是,在线程启动后,它总是使用相同的画布,但是它不是第一次传递的画布引用。虽然每次打勾都会分配 canvas = surfaceHolder.lockCanvas(null); ,但它始终是相同的引用,但它不是原始引用。 在我的一个工作示例中,引用始终是相同的,因为我在构造函数初始化时创建位图。我不能在我当前的实现中这样做,因为我必须使用onMeasure()获得的值进行计算,该值比构造函数晚得多。

我试图以某种方式将原始画布传递给线程,但引用仍然会更改。同时我认为这是问题,但我不知道如何解决它。

1 个答案:

答案 0 :(得分:2)

经常发生的事情,我自己找到了解决方案。 显然,它吸引到不同的画布实例是一个真正的问题。我还不确定为什么会这样。以前没有遇到过这个问题。不过,我可以通过在我的GameSurface构造函数中设置 setWillNotDraw(true); 并且我不能在我的线程中调用gameSurface.draw(canvas)来避免绘制到画布,但是gameSurface.postInvalidate()代替。