为什么 view.invalidate 不立即在我的 android 游戏中重绘屏幕

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/1458047/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-20 03:06:35  来源:igfitidea点击:

Why isn't view.invalidate immediately redrawing the screen in my android game

android

提问by JaanusSiim

I am trying to make an android game.

我正在尝试制作一个安卓游戏。

I have a game class that extends activity and handles all the user input. Then I have a missionView class that extends view and it draws the level on the screen.

我有一个扩展活动并处理所有用户输入的游戏类。然后我有一个 MissionView 类,它扩展视图并在屏幕上绘制级别。

When the user clicks on a door I want to add some animation.

当用户点击一扇门时,我想添加一些动画。

What happens is: The game calls door.open. Changes the state so the view.onDraw function will draw the door half open. The game calls view.invalidate, which should redraw the screen. Then the game sleeps for half a second. Then it calls door.open again. The second time the function is called it changes the state so the view.onDraw function draws the door completely open. Then the game calls view.invalidate again.

发生的情况是:游戏调用door.open。更改状态,以便 view.onDraw 函数将门绘制为半开状态。游戏调用 view.invalidate,它应该重绘屏幕。然后游戏休眠半秒。然后它再次调用door.open。第二次调用该函数时,它会更改状态,因此 view.onDraw 函数将门完全打开。然后游戏再次调用view.invalidate。

The problem is that it does not redraw the screen when it gets to view.invalidate. If I set a break point on the line and run the debugger and click step into it does not step into my view.onDraw function. It can't even show me the code it is executing.

问题是当它到达 view.invalidate 时它不会重绘屏幕。如果我在行上设置断点并运行调试器并单击 step into 它不会步入我的 view.onDraw 函数。它甚至无法向我展示它正在执行的代码。

What I have is: Door class:

我所拥有的是:门类:

public boolean open()
{
   if (doorState == DoorState.Closed)
   {
       doorState = DoorState.Opening;

       return true;
   }
   else if (doorState == DoorState.Opening)
   {
        doorState = doorState.Open;
        return true;
   }
   else
   {
      return false;
   }
}

Game class:

游戏类:

if (tile instanceof DoorTile)
{
    DoorTile doorTile = (DoorTile) tile;
    Door door = doorTile.getDoor();

    if (door.isClosed())
    {
        door.open();
        selectedEntity.openDoor();
        view.invalidate();    // This line does not work

        try 
        {
            Thread.sleep(500);
        } 
        catch (InterruptedException e1) 
        {
             // TODO Auto-generated catch block
             e1.printStackTrace();
        }
        door.open();

        // Handled touch event so break switch statement
        break;
     }
}

回答by rndmcnlly

View#invalidate tells the system to redraw (via onDraw) the view as soon as the main thread goes idle. That is, calling invalidate schedules your view to be redrawn after all other immediate work has finished. If the code in your Game class is being called from the main thread and it puts that thread to sleep, you aren't just pausing rendering, you are pausing input processing all together (usually a bad idea). As a rule of thumb, never sleep a thread that you didn't spawn yourself unless you know what you are doing.

View#invalidate 告诉系统在主线程空闲时立即重绘(通过 onDraw)视图。也就是说,调用 invalidate 安排您的视图在所有其他直接工作完成后重新绘制。如果您的 Game 类中的代码是从主线程调用的,并且它使该线程进入睡眠状态,那么您不仅在暂停渲染,而且还在一起暂停输入处理(通常是一个坏主意)。根据经验,除非您知道自己在做什么,否则永远不要让不是您自己生成的线程休眠。

If you'd like to have your game logic periodically delay using Thread#sleep then run it in a separate thread and use view.postInvalidate() to signal the main thread to wake up and call onDraw.

如果您希望使用 Thread#sleep 定期延迟游戏逻辑,请在单独的线程中运行它并使用 view.postInvalidate() 通知主线程唤醒并调用 onDraw。

回答by JaanusSiim

For games I would actually use a lower level access to graphics. The LunarLanderfrom Google is an excellent example for that. Basically You need to do two things:

对于游戏,我实际上会使用较低级别的图形访问权限。谷歌的LunarLander就是一个很好的例子。基本上你需要做两件事:

  • instead of View use SurfaceView
  • get handler from the SurfaceView
  • replace view.invalidate() with custom method repaint() that would contain

    private void repaint() {
      Canvas c = null;
      try {
        c = surfaceHolder.lockCanvas();
        paint(c);
      } finally {
        if (c != null) {
          surfaceHolder.unlockCanvasAndPost(c);
        }
      }
    }
    
  • 而不是 View 使用 SurfaceView
  • 从 SurfaceView 获取处理程序
  • 用包含的自定义方法 repaint() 替换 view.invalidate()

    private void repaint() {
      Canvas c = null;
      try {
        c = surfaceHolder.lockCanvas();
        paint(c);
      } finally {
        if (c != null) {
          surfaceHolder.unlockCanvasAndPost(c);
        }
      }
    }
    

Paint method will contain what You have in View.onDraw at the moment

Paint 方法将包含您目前在 View.onDraw 中的内容

回答by Vadim Guzev

I've faced with the same problem in my application.

我在我的应用程序中遇到了同样的问题。

In my case I was trying to call invalidate()from the [doInBackground()][1]method of the [AsyncTask][2].

以我为例,我试图调用invalidate()[doInBackground()][1]的方法[AsyncTask][2]

The problem was solved by moving invalidate()to [onProgressUpdate()][3]method of AsyncTask. I.e. you cannot update UI from the doInBackground()method of AsyncTask. It is mentioned in Android docs, but it is so easy to forget about it... so, don't forget to check it.

通过移动invalidate()到 的[onProgressUpdate()][3]方法解决了该问题AsyncTask。比如,你不能从更新UIdoInBackground()的方法AsyncTask。Android 文档中提到了它,但很容易忘记它......所以,不要忘记检查它。

回答by Hiren Patel

I could be late but I'm sure this would work for you.

我可能会迟到,但我相信这对你有用。

I did this with Kotlin. You can modify according to Java.

我用Kotlin做到了这一点。可以根据Java修改。

Put your code inside runOnUIThread().

把你的代码放在里面runOnUIThread()

Sample code:

示例代码:

activity?.runOnUiThread { 
  // Your code goes here
}

Hope this helps.

希望这可以帮助。