简体   繁体   中英

Rotate image in Kivy without using Builder / .kv file and NumericProperty

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM