So based on a bunch of different examples, I cobbled together an example Kivy app which uses Kivy Animation
to rotate an image.
I want to know how to achieve the same result without using the .kv
file (or Builder.load_string
).
#! /usr/bin/env python3
import kivy
kivy.require('1.9.1')
from kivy.app import App
from kivy.uix.widget import Widget
from kivy.uix.label import Label
from kivy.uix.image import Image
from kivy.uix.floatlayout import FloatLayout
from kivy.graphics import Rectangle, Color, Rotate, PushMatrix, PopMatrix
from kivy.core.window import Window
from kivy.clock import Clock
from kivy.graphics.svg import Svg
from kivy.animation import Animation
from kivy.lang import Builder
from kivy.properties import NumericProperty
import random
WINDOW_WIDTH, WINDOW_HEIGHT = Window.size
Builder.load_string('''
<Sprite>:
canvas.before:
PushMatrix
Rotate:
angle: self.angle
axis: (0, 0, 1)
origin: self.center
canvas.after:
PopMatrix
''')
class Sprite( Image ):
angle = NumericProperty(0)
def __init__( self, x=0, y=0, **kwargs ):
super( Sprite, self ).__init__( **kwargs )
self.size_hint = (None, None) # tell the layout not to size me
self.angle = 0
self.source = 'alien.png'
self.size = self.texture.size
if ( x == 0 and y == 0 ):
self.pos = ( random.randrange(0,WINDOW_WIDTH) , random.randrange(0,WINDOW_HEIGHT) )
else:
self.pos = ( x,y )
self.animate() # start moving animation
def animateComplete( self, *kargs ):
Animation.cancel_all( self ) # is this needed?
self.angle = 0
self.animate()
def animate( self ):
self.anim = Animation( angle=360, duration=1 )
self.anim.bind( on_complete=self.animateComplete )
self.anim.repeat = True
self.anim.start( self )
class FPSText( Label ):
def __init__( self, **kwargs ):
super( FPSText, self ).__init__( **kwargs )
self.size_hint = (None, None) # tell the layout not to size me
self.pos_hint = { 'right':1, 'top':1 }
def update( self, count ):
self.text = "%d aliens / %3.1f FPS" % ( count, Clock.get_fps() )
self.size = self.texture_size
class AlienGame( FloatLayout ):
def __init__(self, **kwargs):
super( AlienGame, self).__init__(**kwargs)
self.aliens = []
self.fps_text = FPSText()
self.add_widget( self.fps_text )
self.addAlien( WINDOW_WIDTH//2, WINDOW_HEIGHT//2 )
def addAlien( self, x=0, y=0 ):
new_alien = Sprite( x, y )
self.aliens.append( new_alien )
self.add_widget( new_alien )
def update( self, dt ):
self.fps_text.text = '--'
self.fps_text.update( len( self.aliens ) )
def on_touch_down( self, touch ):
if ( touch.is_double_tap ):
for i in range( 7 ):
self.addAlien()
else: #if ( touch.is_single_tap ): (no single tap property)
self.addAlien( touch.pos[0], touch.pos[1] )
class RotApp( App ):
def build( self ):
game = AlienGame()
Clock.schedule_interval(game.update, 1.0 / 60.0)
return game
if ( __name__ == '__main__' ):
RotApp().run()
I tried something like:
class Sprite( Image ):
def __init__( self, x=0, y=0, **kwargs ):
super( Sprite, self ).__init__( **kwargs )
self.size_hint = (None, None) # tell the layout not to size me
self.source = 'alien.png'
self.size = self.texture.size
# define the rotation
with self.canvas.before:
PushMatrix()
self.rot = Rotate()
self.rot.angle = 0
self.rot.origin = self.center
self.rot.axis = (0, 0, 1)
with self.canvas.after:
PopMatrix()
But was unable to get the self.rot.angle
to update with the animation. It worked fine if I handled the animation manually, but I want to use the Kivy Animation
object.
Is there a plain-python method of doing this?
Yes, it's possible, here is your Sprite class that rotates using only python code. The only thing here is - there is a reason the kv lang was made! It makes this stuff much easier. I highly suggest keeping the kv code in. Anyway, to answer your question: the key was to 1) Start the animation on the self.rot widget rather than the self widget 2) Reset the self.rot.origin variable since self.center is (0,0) the moment a widget is initialized (and before it's actually placed on the screen, giving it a proper self.center).
Alternatively, you could use the bind
method. for example, set self.rot.angle = self.angle
and then later on, self.bind(angle=on_angle)
which would call your on_angle
function anytime your self.angle
changes. You would have to define a self.on_angle
function which should reset self.rot.angle = self.angle
. KV lang automatically does this for you, saving you tons of code. In kv, angle: self.angle
automatically updates the angle
variable anytime the self.angle
variable changes, meaning no playing with the bind
function.
Here is your sprite class. Note the two lines self.rot.origin = self.center
and self.anim.start(self.rot)
:
class Sprite( Image ):
angle = NumericProperty(0)
def __init__( self, x=0, y=0, **kwargs ):
super( Sprite, self ).__init__( **kwargs )
# define the rotation
with self.canvas.before:
PushMatrix()
self.rot = Rotate()
self.rot.angle = 0
self.rot.origin = self.center
self.rot.axis = (0, 0, 1)
with self.canvas.after:
PopMatrix()
self.size_hint = (None, None) # tell the layout not to size me
self.angle = 0
self.source = 'alien.png'
self.size = self.texture.size
if ( x == 0 and y == 0 ):
self.pos = ( random.randrange(0,WINDOW_WIDTH) , random.randrange(0,WINDOW_HEIGHT) )
else:
self.pos = ( x,y )
self.rot.origin = self.center # Reset the center of the Rotate canvas instruction
self.animate() # start moving animation
def animateComplete( self, *kargs ):
Animation.cancel_all( self ) # is this needed?
self.rot.angle = 0
self.animate()
def animate( self ):
self.anim = Animation( angle=360, duration=1 )
self.anim.bind( on_complete=self.animateComplete )
self.anim.repeat = True
self.anim.start( self.rot ) # Start rotating the self.rot widget instead of the self widget
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.