简体   繁体   English

在不使用Builder / .kv文件和NumericProperty的情况下在Kivy中旋转图像

[英]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. 因此,基于许多不同的示例,我将示例Kivy应用程序拼凑在一起,该应用程序使用Kivy Animation旋转图像。

I want to know how to achieve the same result without using the .kv file (or Builder.load_string ). 我想知道如何在不使用.kv文件(或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. 但是无法获取self.rot.angle来更新动画。 It worked fine if I handled the animation manually, but I want to use the Kivy Animation object. 如果我手动处理动画,效果很好,但是我想使用Kivy Animation对象。

Is there a plain-python method of doing this? 是否有执行此操作的纯Python方法?

示例输出

Yes, it's possible, here is your Sprite class that rotates using only python code. 是的,有可能,这是您的Sprite类,仅使用python代码进行旋转。 The only thing here is - there is a reason the kv lang was made! 这里唯一的是-制作kv lang是有原因的! 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). 我强烈建议保留kv代码。无论如何,要回答您的问题:关键是1)在self.rot小部件而不是self小部件上启动动画2)从self.center开始重置self.rot.origin变量是(0,0)初始化小部件的时间(在将其实际放置在屏幕上之前,为其赋予适当的self.center)。

Alternatively, you could use the bind method. 另外,您可以使用bind方法。 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. 例如,设置self.rot.angle = self.angle ,然后设置self.rot.angle = self.angle self.bind(angle=on_angle)on_angle self.angle更改,它将调用on_angle函数。 You would have to define a self.on_angle function which should reset self.rot.angle = self.angle . 您将必须定义一个self.on_angle函数,该函数应重置self.rot.angle = self.angle KV lang automatically does this for you, saving you tons of code. KV lang自动为您执行此操作,从而节省了大量代码。 In kv, angle: self.angle automatically updates the angle variable anytime the self.angle variable changes, meaning no playing with the bind function. 在kv中, angle: self.angle每当self.angle变量更改时angle: self.angle自动更新angle变量,这意味着不使用bind函数。

Here is your sprite class. 这是您的Sprite类别。 Note the two lines self.rot.origin = self.center and self.anim.start(self.rot) : 注意两行self.rot.origin = self.centerself.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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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