如何每秒运行一个Runnable来更新UI

4

我正在尝试使用Kotlin Android编写每秒移动一张图片的代码,但无法使其正常工作。目前,我正在使用Timer来安排每秒钟执行一次Timer Task,但它并没有按照预期工作。

这是我的代码:

class Actvt_Image<float> : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_actvt__image)

        val pict_mario = findViewById<ImageView>(R.id.img_Mario)
        val bt_down = findViewById<Button>(R.id.bt_down)
        val frame = findViewById<LinearLayout>(R.id.frame)
        val txt1=findViewById<TextView>(R.id.txt1)

        var i =100
        val timer = Timer()
        val myTask = object : TimerTask() {
            override fun run() {

                txt1.text = (i+1).toString()
                img_Mario.rotation=180f
                img_Mario.translationX +=100
                img_Mario.translationY +=20
            }
        }

        bt_down.setOnClickListener {

            i=0
            timer.schedule(myTask, 1000, 1000)

        }
    }

}

尝试在您的clicklistener中调用timer.run()。 - Robin Vinzenz
它无法按照我的预期移动。 - Dang Hoang
它是否移动?你想如何移动? - Syed Ahmed Jamil
我想移动图像马里奥,但是它无法移动。 - Dang Hoang
分享使用了 R.id.img_Marioxml - Ferran
显示剩余2条评论
2个回答

2
您正在尝试在后台线程上更新UI,这是不可能的。UI只能在UI线程上更新。此外,使用TimerTimerTask每1秒创建和销毁一个线程不是使用线程的正确方式,因为创建线程是一种消耗内存的操作。
您需要做的是使用Handler并告诉UI线程在每个所需间隔之后运行Runnable。删除TimerTimerTask并使用以下内容
val handler = Handler(Looper.getMainLooper())
handler.post(object : Runnable {
            override fun run() {
                txt1.text = (i+1).toString()
                img_Mario.rotation=180f
                img_Mario.translationX +=100
                img_Mario.translationY +=20

                handler.postDelayed(this, 1000)
            }
        })

上面的代码使用了一个处理程序并将任务发布到UI线程消息队列。 任务本身正在更新UI并再次使用相同的处理程序将自己发布到UI线程消息队列,但这次使用延迟1秒后的handler.postDelayed()方法。
编辑:如何停止可运行项
如果要停止特定的runnable,您可以使用以下方法并传递相同的runnable对象,该对象已传递给handler.post()。 当然,您必须始终保留对runnable的引用以停止它。 上面的代码没有保留引用。 请参见下面的完整代码
handler.removeCallbacks(runnable) //stops a specific runnable

要停止UI线程消息队列中所有剩余的回调或runnable,请使用以下方法

handler.removeCallbacksAndMessages(null) //stops any pending callback in message queue

完整代码

注意:我添加了一个停止按钮的点击监听器作为额外的内容

class Actvt_Image<float> : AppCompatActivity() {

        private lateinit var handler : Handler
        private lateinit var runnable : Runnable // reference to the runnable object
        private var i = 0

        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(R.layout.activity_actvt__image)

            val pict_mario = findViewById<ImageView>(R.id.img_Mario)
            val bt_down = findViewById<Button>(R.id.bt_down)
            val bt_stop = findViewById<Button>(R.id.bt_stop)
            val frame = findViewById<LinearLayout>(R.id.frame)
            val txt1=findViewById<TextView>(R.id.txt1)

            handler = Handler(Looper.getMainLooper())
            runnable = Runnable {
                i++
                txt1.text = i.toString()
                img_Mario.rotation=180f
                img_Mario.translationX +=100
                img_Mario.translationY +=20
                handler.postDelayed(runnable, 1000)
            }

            bt_down.setOnClickListener {

                handler.post(runnable)

            }

            bt_stop.setOnClickListener {
                //Use this to stop all callbacks
                //handler.removeCallbacksAndMessages(null)

                handler.removeCallbacks(runnable) 

            }
        }

    }

在这里阅读有关进程、线程和处理程序的更多信息: https://developer.android.com/guide/components/processes-and-threads https://developer.android.com/reference/android/os/Handler


你的代码是正确的。但我如何控制它何时可运行?例如:当我点击一个按钮时,它会运行吗? - Dang Hoang
@DangHoang 在 bt_down.setOnClickListener 之前初始化处理程序,然后将其余代码放在 bt_down.setOnClickListener 内部。我编辑了我的答案,请在那里查看完整的代码。 - Syed Ahmed Jamil
@DangHoang 抱歉,我忘记添加了。请检查我的更新答案。这次,我没有直接在handler.post()方法中创建runnable对象。我将其作为单独的runnable对象保留了引用,并使用它来启动和停止runnable - Syed Ahmed Jamil
@DangHoang 很高兴听到这个消息。如果这个解决方案对您有用,请通过单击复选标记图标将答案标记为已接受。祝你好运。 - Syed Ahmed Jamil

0

我有一段代码,它的运行结果和我预期的一样。

     val t = object : Thread() {
        override fun run() {
            while (!isInterrupted) {
                try {
                    Thread.sleep(1000)  //1000ms = 1 sec
                    runOnUiThread {
                        i++

                        txt1.text = i.toString()
                        img_Mario.rotation=180f
                        img_Mario.translationX +=20

                    }

                } catch (e: InterruptedException) {
                    e.printStackTrace()
                }

            }
        }
    }

    bt_down.setOnClickListener {
        i=0
        t.start()
    }

这也可以工作,但问题是您正在创建一个新线程,并要求该线程在UI线程上运行一段代码,而本身不执行任何操作。简而言之,如果该新线程没有做其他任何事情,则无需创建新线程。相反,您可以使用UI线程本身通过使用“handler.post()”运行一段代码,就像我在我的答案中提到的那样。 - Syed Ahmed Jamil

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接