在kivy中的触摸事件上旋转对象

2022-01-15 00:00:00 python kivy user-interface layout

问题描述

我正在制作一个会像大表盘一样旋转的圆圈.目前,我在顶部有一个箭头来显示表盘的朝向.我希望它的行为有点像老式的旋转电话,这样当你的手指/光标向下时你可以旋转它,但在你放手后它会(慢慢地)拉回顶部.

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 behavior to be kind of like an old timey rotary phone, such that while your finger/cursor is down you can rotate it, but it'll (slowly) yank back to top after you let go.

这是我的对象的样子:

这是我的代码:

#!/usr/bin/kivy
import kivy
kivy.require('1.7.2')
import math

from random import random
from kivy.app import App
from kivy.uix.widget import Widget
from kivy.uix.gridlayout import GridLayout
from kivy.uix.anchorlayout import AnchorLayout
from kivy.uix.relativelayout import RelativeLayout
from kivy.graphics import Color, Ellipse, Rectangle

class MinimalApp(App):
    title = 'My App'
    def build(self):
        root = RootLayout()
        return(root)

class RootLayout(AnchorLayout):
    pass

class Circley(RelativeLayout):
    angle = 0
    def on_touch_down(self, touch):
        ud = touch.ud
        ud['group'] = g = str(touch.uid)
        return True
    def on_touch_move(self, touch):
        ud = touch.ud
#        print(touch.x, 0)
#        print(self.center)
#        print(0, touch.y)
#        print(touch.x - self.center[0], touch.y - self.center[1])
        y = (touch.y - self.center[1])
        x = (touch.x - self.center[0])
        calc = math.degrees(math.atan2(y,x))
        angle = calc if calc > 0 else (360 + calc)
        print(angle)
    def on_touch_up(self, touch):
        touch.ungrab(self)
        ud = touch.ud
        return True

if __name__ == '__main__':
    MinimalApp().run()

还有kv:

#:kivy 1.7.2
#:import kivy kivy

<RootLayout>:
    anchor_x: 'center'                              # I think this /is/ centered
    anchor_y: 'center' 
    canvas.before:
        Color:
            rgba: 0.4, 0.4, 0.4, 1
        Rectangle:
            pos: self.pos
            size: self.size
    Circley:
        anchor_x: 'center'                          # this is /not/ centered.
        anchor_y: 'center' 
        canvas.before:
            PushMatrix
            Color:
                rgba: 0.94, 0.94, 0.94, 1
            Rotate:
                angle: self.angle
                axis: 0, 0, 1
                origin: self.center
            Ellipse:
                source: 'arrow.png'
                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)
                Label:
                    text: unicode(self.size)    # this is /not/ appearing
                    color: 1,0,0,1
        canvas.after:
            PopMatrix

部分内容来自 kivy touchtracer 演示,来自这个 SO question.

Parts of that are borrowed from the kivy touchtracer demo, and from this SO question.

您可以看到我的计算正确地打印了圆的原点和触摸事件之间的角度(不确定这将如何响应多个手指,还没有考虑那么远),但不确定如何将其集成到界面中的旋转"反馈事件中.

You can see I have a calculation that is correctly printing the angle between the origin of the circle and the touch event (not sure how this'll respond to multiple fingers, haven't thought that far through), but not sure how to integrate this into a "spinning" feedback event in the interface.


解决方案

您可以将画布的角度绑定到 NumericProperty,以便从代码内部进行更改.您需要做的就是正确计算这些角度.在玩了一下之后,我创建了以下代码:

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()

我正在计算 on_touch_move 中初始角度(按下鼠标后)和后期角度之间的差异.由于角度是一个属性,我还可以使用 kivy.animation 对其进行修改,以在释放鼠标按钮后使表盘回旋.

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.

编辑

on_touch_down 子圈子事件:

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()

相关文章