while 循环导致 UI 变为空白

While loop causes UI to go blank

我正在编写一个简单的 Whack a Mole 克隆,并且我在 layout.xml 的 GridLayout 中声明了我的 UI 元素,然后以编程方式分配给数组中的 ImageView 变量。我有一个 startGame() 方法,它只需要一个 运行dom int,将其从数组中拉出并使其显示一秒钟,然后重复。出于某种原因,当我将此代码放入 while() 循环时,它会导致我的 UI 在启动后立即变为空白。

我知道这是 while() 循环,因为我尝试将代码从 while() 循环中取出,并且它 运行 正确(一次),但是当放在 while 循环中时一切都变白了。

这是导致问题的方法:

public void startGame() {
    gameStarted = true;
    while(gameStarted) {
        randomInt = rand.nextInt(11);
        mole[randomInt].setVisibility(View.VISIBLE);

        handler.postDelayed(new Runnable() {
            @Override
            public void run() {
                mole[randomInt].setVisibility(View.INVISIBLE);
            }
        }, 5000);
    }
}

所有其他相关代码都在 onCreate 中,否则它只是骨架 Activity 子类。

public class WAM_Activity extends Activity {

    private ImageView[] mole = new ImageView[11];
    private int[] moleId = {R.id.mole1, R.id.mole3, R.id.mole4, R.id.mole5, R.id.mole6, R.id.mole7, R.id.mole8, R.id.mole9, R.id.mole10, R.id.mole11, R.id.mole12};
    private boolean gameStarted;
    private int randomInt = 0;
    private Random rand = new Random();
    Handler handler = new Handler();

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

        for (int i = 0; i < 11; i++) {
            mole[i] = (ImageView) findViewById(moleId[i]);
            mole[i].setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View view) {
                    //do stuff eventually
                }
            });
        }
        gameStarted = true;
        startGame();

    }

知道为什么这不起作用吗?我已经盯着它看了几个小时了,我很困惑。

Android 不能那样工作,当 onCreate 被调用时,它需要完成才能让应用程序继续响应,我很惊讶你没有得到任何 "App not respopnding"错误。

如果您想创建一个 "game loop",您只需创建一个新的 Thread 并将 while 放在那里即可。

Activity 的生命周期必须在不阻塞它们的情况下执行,应用程序才能正常运行,有关更多信息,请查看 here.

你知道线程吗?如果您愿意,我可以 post 一个如何使用线程执行此操作的示例,但它可能很长,如果您不知道 Thread 是什么,它会让您感到困惑。

编辑:好的,我将举一个 Thread

的例子

当我创建我的游戏时,我通常只有一个 Activity,它唯一做的就是创建自定义 SurfaceView,没有别的。

public class GameActivity extends Activity
{
    //This is a custom class that extends SurfaceView - I will write it below
    private GameSurface game;

    @Override
    protected void onCreate(Bundle bundle)
    {
        super.onCreate(bundle);

        //Create a new instance of the game
        game = new GameSurface(this);

        //Set the View of the game to the activity
        setContentView(game);
    }
}

您还可以添加额外的东西,例如 onSaveInstanceState 来保存游戏数据并稍后恢复它们,但我现在不想添加它们,所以代码看起来很简单。

这个class很简单,让我们继续我们的SurfaceView。我选择 SurfaceView 的原因是因为它允许在其上绘制自定义图形——这正是我们在视频游戏中想要的。我会尽量使 class 尽可能简单:

/*SurfaceHolder.Callback will run some functions in our class when 
our surface is completed - at that point we can initialize data 
that have to do with the View's width/height.

I don't know if you've noticed that on a View's onCreate() 
when you call getWidth() or getHeight() you get 0, that's because 
the surface is not initialized yet, this is a way to fix that.

Also we need a Runnable to run the Thread inside this class, 
no need to make more classes and make it more complicated*/
public class GameSurface extends SurfaceView
    implements SurfaceHolder.Callback, Runnable
{
    //This is our thread - we need the "running" variable to be 
    //able to stop the Thread manually, this will go inside our "while" loop
    private Thread thread;
    private boolean running;

    //Right here you can add more variables that draw graphics
    //For example you can create a new class that has a function that
    //takes Canvas as a parameter and draws stuff into it, I will add
    //a Rect in this case which is a class already made by android 
    //but you can create your own class that draws images or more
    //complicated stuff
    private Rect myRect;

    //Rect needs a paint to give it color
    private Paint myPaint;

    //Constructor
    public GameSurface(Context context)
    {
        super(context);

        //This is the callback to let us know when surface is completed
        getHolder().addCallback(this);
    }

    //When a class implements SurfaceHolder.Callback you are forced to
    //create three functions "surfaceCreated", "surfaceChanged" and 
    //"surfaceDestroyed" these are called when the surface is created,
    //when some settings are changed (like the orientation) and when 
    //it is about to be destroyed

    @Override
    public void surfaceCreated(Surface holder)
    {
        //Let's initialize our Rect, lets assume we want it to have 40
        //pixels height and fill the screen's width
        myRect = new Rect(0, 0, getWidth(), 40);

        //Give color to the rect
        myPaint = new Paint();
        myPaint.setARGB(0, 255, 0, 0);

        //In case you are not familiar with the Rect class, as 
        //parameters it gets Rect(left, top, right, bottom)

        //Time to start our Thread - nothing much to explain here if 
        //you know how threads work, remember this class implements
        //Runnable so the Thread's constructor gets "this" as parameter
        running = true;
        thread = new Thread(this);
        thread.start();
    }

    //We won't use this one for now, but we are forced to type it
    //Even if we leave it empty
    @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {}

    //When the surface is destroyed we just want the Thread to
    //terminate - we don't want threads running when our app is not visible!
    @Override
    public void surfaceDestroyed(SurfaceHolder holder)
    //We will type this function later
    {destroyThread();}

    //Time for the interesting stuff! let's start with input
    @Override
    public boolean onTouchEvent(MotionEvent event)
    {
        //The logic is as follows: when our Rect is touched, we want
        //it to become smaller
        if (event.getAction() == MotionEvent.ACTION_DOWN)
        {
            if (myRect.contains((int) event.getX(), (int) event.getY())
            {
                myRect.right -= 5;

                //Return true - we did something with the input
                return true;
            }
        }

        return super.onTouchEvent(event);
    }

    //This is our update, it will run once per frame
    private void update()
    {
        //Let's assume we want our rect to move 1 pixel downwards
        //on every frame
        myRect.offset(0, 1);
    }

    //Now for our draw function
    public void draw(Canvas canvas)
    {
        //Here we want to draw a background and our rect
        canvas.drawARGB(0, 0, 0, 255);
        canvas.drawRect(myRect, myPaint);
    }

    //The only thing left is our run() function for the Thread
    @Override
    public void run()
    {
        //Screen
        Canvas canvas;

        //Our game cycle (the famous while)
        while(running)
        {
            //Count start time so we can calculate frames
            int startTime = System.currentTimeMillis();

            //Update our game
            update();

            //Empty screen so it can obtain new instance
            canvas = null;
            //Try locking the canvas for pixel editing on surface
            try
            {
                //Try getting screen
                canvas = getHolder().lockCanvas();

                //Succeeded
                if (canvas != null) synchronized (getHolder())
                {
                    //Actual drawing - our draw function
                    draw(canvas);
                }
            } finally
            {
                //Draw changes
                if (canvas != null) getHolder().unlockCanvasAndPost(canvas);
            }

        //End Frame - 1000/30 means 30 frames per second
        int frameTime = System.currentTimeMillis() -startTime;
        if (frameTime < 1000/30)
            try { Thread.sleep(1000/30 -frameTime); } catch (InterruptedException e){}
        }
    }

    //Last but not least, our function for closing the thread
    private void destroyThread()
    {
        //Stop thread's loop
        running = false;

        //Try to join thread with UI thread
        boolean retry = true;
        while (retry)
        {
            try {thread.join(); retry = false;}
            catch (InterruptedException e) {}
        }
    }
}

我可能犯了一些小错误(可能是区分大小写的字母),所以请随时更正这些错误,我立即编写了代码,所以我没有时间测试它,但它应该可以完美运行。

如果您还有其他问题,需要更多解释或有什么不对,请告诉我!