Rotating an object on a touch event in kivy

前端 未结 2 889
天命终不由人
天命终不由人 2021-01-02 07:43

I\'m working on making a circle that will spin kind of like a large dial. Currently, I have an arrow at the top to show which direction the dial is facing. I\'d like its b

相关标签:
2条回答
  • 2021-01-02 07:54

    You can bind angle of canvas to NumericProperty, to change it from inside your code. All you need to do is to compute those angles correctly. After playing a bit with it I created following code:

    from kivy.app import App
    from kivy.uix.widget import Widget
    from kivy.lang import Builder
    from kivy.animation import Animation
    from kivy.properties import NumericProperty
    
    import math 
    
    kv = '''
    <Dial>:
        canvas:
            Rotate:
                angle: root.angle
                origin: self.center
            Color:
                rgb: 1, 0, 0
            Ellipse:    
                size: min(self.size), min(self.size)
                pos: 0.5*self.size[0] - 0.5*min(self.size), 0.5*self.size[1] - 0.5*min(self.size)
            Color:
                rgb: 0, 0, 0
            Ellipse:    
                size: 50, 50
                pos: 0.5*root.size[0]-25, 0.9*root.size[1]-25
    '''
    Builder.load_string(kv)
    
    class Dial(Widget):
        angle = NumericProperty(0)
    
        def on_touch_down(self, touch):
            y = (touch.y - self.center[1])
            x = (touch.x - self.center[0])
            calc = math.degrees(math.atan2(y, x))
            self.prev_angle = calc if calc > 0 else 360+calc
            self.tmp = self.angle
    
        def on_touch_move(self, touch):
            y = (touch.y - self.center[1])
            x = (touch.x - self.center[0])
            calc = math.degrees(math.atan2(y, x))
            new_angle = calc if calc > 0 else 360+calc
    
            self.angle = self.tmp + (new_angle-self.prev_angle)%360
    
        def on_touch_up(self, touch):
            Animation(angle=0).start(self)
    
    class DialApp(App):
        def build(self):
            return Dial()
    
    if __name__ == "__main__":
        DialApp().run()
    

    I'm calculating difference between initial (after pressing mouse) and later angle in on_touch_move. Since angle is a property I can also modify it using kivy.animation to make dial spin back after releasing mouse button.

    EDIT

    on_touch_down event for child circle:

    from kivy.app import App
    from kivy.uix.widget import Widget
    from kivy.uix.floatlayout import FloatLayout
    from kivy.lang import Builder
    from kivy.animation import Animation
    from kivy.properties import NumericProperty
    
    import math 
    
    kv = '''
    <Dial>:
        circle_id: circle_id
        size: root.size
        pos: 0, 0
        canvas:
            Rotate:
                angle: self.angle
                origin: self.center
            Color:
                rgb: 1, 0, 0
            Ellipse:    
                size: min(self.size), min(self.size)
                pos: 0.5*self.size[0] - 0.5*min(self.size), 0.5*self.size[1] - 0.5*min(self.size)
        Circle:
            id: circle_id   
            size_hint: 0, 0
            size: 50, 50
            pos: 0.5*root.size[0]-25, 0.9*root.size[1]-25
            canvas:
                Color:
                    rgb: 0, 1, 0
                Ellipse:    
                    size: 50, 50
                    pos: self.pos              
    '''
    Builder.load_string(kv)
    
    class Circle(Widget):
        def on_touch_down(self, touch):
            if self.collide_point(*touch.pos):
                print "small circle clicked"
    
    class Dial(Widget):
        angle = NumericProperty(0)
    
        def on_touch_down(self, touch):
            if not self.circle_id.collide_point(*touch.pos):
                print "big circle clicked"
    
            y = (touch.y - self.center[1])
            x = (touch.x - self.center[0])
            calc = math.degrees(math.atan2(y, x))
            self.prev_angle = calc if calc > 0 else 360+calc
            self.tmp = self.angle
    
            return super(Dial, self).on_touch_down(touch) # dispatch touch event futher
    
        def on_touch_move(self, touch):
            y = (touch.y - self.center[1])
            x = (touch.x - self.center[0])
            calc = math.degrees(math.atan2(y, x))
            new_angle = calc if calc > 0 else 360+calc
    
            self.angle = self.tmp + (new_angle-self.prev_angle)%360
    
        def on_touch_up(self, touch):
            Animation(angle=0).start(self)
    
    
    class DialApp(App):
        def build(self):
            return Dial()
    
    if __name__ == "__main__":
        DialApp().run()
    
    0 讨论(0)
  • 2021-01-02 08:02

    You can use GearTick from garden which is a rotating slider. It's not exactly what you need but can be adapted for your needs. "By default it allows rotation anti-clockwise you probably would need it to go clockwise"(Update: The widget now has a orientation property that can be set to 'clockwise' or 'anti-clockwise').

    You would need to manage the spring back and stopping at the "finger stop".

    The example at the ends manage spring back using animation, however you still need to manage/implement the finger stop functionality.

    https://github.com/kivy-garden/garden.geartick

    Usage::

    Python::

    from kivy.garden.geartick import GearTick
    parent.add_widget(GearTick(range=(0, 100)))
    

    kv::

    BoxLayout:
        orientation: 'vertical'
        GearTick:
            id: gear_tick
            zoom_factor: 1.1
            # uncomment the following to use non default values
            #max: 100
            #background_image: 'background.png'
            #overlay_image: 'gear.png'
            #orientation: 'anti-clockwise'
            on_release:
                Animation.stop_all(self)
                Animation(value=0).start(self)
        Label:
            size_hint: 1, None
            height: '22dp'
            color: 0, 1, 0, 1
            text: ('value: {}').format(gear_tick.value)
    

    enter image description here

    To install::

    pip install kivy-garden
    garden install geartick
    

    Working Example that you can copy paste::

    from kivy.lang import Builder
    from kivy.app import runTouchApp
    from kivy.garden.geartick import GearTick
    runTouchApp(Builder.load_string('''
    #:import Animation kivy.animation.Animation
    GridLayout:
        cols: 2
        canvas.before:
            Color:
                rgba: 1, 1, 1, 1
            Rectangle:
                size: self.size
                pos: self.pos
        BoxLayout:
            orientation: 'vertical'
            GearTick:
                id: gear_tick
                zoom_factor: 1.1
                # uncomment the following to use non default values
                #max: 100
                #background_image: 'background.png'
                #overlay_image: 'gear.png'
                #orientation: 'anti-clockwise'
                on_release:
                    Animation.stop_all(self)
                    Animation(value=0).start(self)
            Label:
                size_hint: 1, None
                height: '22dp'
                color: 0, 1, 0, 1
                text: ('value: {}').format(gear_tick.value)
    '''))
    
    0 讨论(0)
提交回复
热议问题