Хорошо, обо всем по порядку.Это может быть опечатка в вашем вопросе, но в соответствии с вашим кодом класс Game
создает экземпляр объекта Ball
, а класс Ball
создает экземпляр объекта Game
.Это называется циклическая зависимость , и вам это не нужно, потому что это приведет к ошибке StackOverFlow
(не так ли?).Поэтому удалите объект Game
из класса Ball
.
Во-вторых, поскольку вы вызываете ball1.update()
внутри onDraw()
, поэтому вызов invalidate()
внутри ball1.update()
ничем не отличается от вызова его после ball1.update()
.В обоих случаях вы звоните invalidate()
из onDraw()
.
Цель invalidate()
- указать представлению перерисовывать себя всякий раз, когда мы меняем какие-либо данные, связанные с представлением.Поскольку вы изменяете данные, относящиеся к просмотру внутри onDraw()
, вызывая ball1.update()
, поэтому вызов invalidate()
сразу после этого будет логичным шагом.Вот так и будет работать в вашем случае.
class Game(context: Context, attributes: AttributeSet): View(context,attributes) {
private val paint :Paint = Paint(ANTI_ALIAS_FLAG)
private val ball1 = Ball(width/2 - 50.toDouble(),150.0,20.0)
init{
paint.color = Color.CYAN
}
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
canvas.apply {
drawOval(
(width / 2) - 50f,
ball1.posy.toFloat() - 50f,
(width / 2) + 50f,
ball1.posy.toFloat() + 50f,
paint)
ball1.update()
invalidate()
}
}
}
Но onDraw()
- не лучшее место для звонка invalidate()
.Есть много проблем, связанных с производительностью, поэтому лучше оставить onDraw()
в покое.
Читать здесь:
Полагаю, вы хотите оживить свой вид.Так что будет лучше читать документы по animation .
Но все равно давайте продолжим.Если вы хотите избежать звонка invalidate()
с onDraw()
, но все же хотите добиться того же результата.Лучше всего создать в классе Game
еще одну функцию, которая начнет процесс обновления объекта Ball
и непрерывного вызова invalidate()
.Я называю этот метод как startAnimation()
Вот как мы можем это сделать.Поскольку ваше представление выполняется в потоке пользовательского интерфейса, нам нужно получить в него Handler
и попросить пользовательский интерфейс непрерывно запускать Runnable с помощью Handler.post()
.Этот Runnable
будет содержать код, который обновит ваш Ball
объект и вызовет invalidate()
.К счастью, сам класс View
содержит метод post()
, который совпадает с Handler.post()
, поэтому мы можем безопасно вызывать этот метод внутри нашего Game
класса, потому что Game
наследует View
.
Здеськод:
class Game(context: Context, attributes: AttributeSet): View(context,attributes) {
private lateinit var runnable : Runnable // reference to the runnable object
private val ball1 = Ball(width/2 - 50.toDouble(),150.0,20.0)
private val paint :Paint = Paint(ANTI_ALIAS_FLAG)
//This is the constructor of the class
init{
paint.color = Color.CYAN
//Here we initialize our Runnable and put the code that we want to run once we call startAnimation()
runnable = Runnable {
ball1.update()
invalidate()
//Calling post() inside here will loop the above code and you will see a smooth animation
post(runnable)
}
}
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
canvas.apply {
drawOval(
(width / 2) - 50f,
ball1.posy.toFloat() - 50f,
(width / 2) + 50f,
ball1.posy.toFloat() + 50f,
paint)
}
}
//This is the new function I am talking about
fun startAnimation()
{
post(runnable)
}
fun stopAnimation()
{
removeCallbacks(runnable)
}
}
И мы запускаем анимацию, вызывая startAnimation()
из MainActivity
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val game = findViewById<Game>(R.id.view)
game.startAnimation()
}
}
Чтобы остановить анимацию, просто позвоните stopAnimation()
Узнайте больше о процессах, потоках и обработчике здесь: