在kivy中的触摸事件上旋转对象
Posted
技术标签:
【中文标题】在kivy中的触摸事件上旋转对象【英文标题】:Rotating an object on a touch event in kivy 【发布时间】:2013-12-26 00:20:21 【问题描述】:我正在制作一个会像大表盘一样旋转的圆圈。目前,我在顶部有一个箭头来显示表盘的朝向。我希望它的行为有点像老式的旋转电话,这样当你的手指/光标向下时你可以旋转它,但在你放手后它会(慢慢地)拉回顶部。
这是我的对象的样子:
这是我的代码:
#!/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 演示和from this SO question。
您可以看到我的计算正确地打印了圆的原点和触摸事件之间的角度(不确定这将如何响应多个手指,还没有考虑那么远),但不确定如何将其集成到界面中的“旋转”反馈事件中。
【问题讨论】:
【参考方案1】:您可以将画布的角度绑定到NumericProperty
,以从您的代码内部进行更改。您需要做的就是正确计算这些角度。在玩了一下之后,我创建了以下代码:
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
对其进行修改,以便在释放鼠标按钮后使表盘回旋。
编辑
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()
【讨论】:
这可能超出了这个问题的范围,但是你知道我如何在顶部的小圆圈中添加一个触摸事件吗?我为它分配了一个 on_touch 声音事件,以确保我知道自己在做什么,但似乎我可以进行旋转/或/我可以为子圈子设置 on_touch ---我似乎无法同时启用同时。 您必须使用super
进一步调度on_touch_down
事件。我添加了示例。
这似乎是双重打印两个事件?
你想要的行为是什么?
哦,对不起,我想象它只在点击在小圆圈而不是大圆圈时打印“点击的小圆圈”,当它在零件中时打印“点击大圆圈”不包含小圆圈的大圆圈。无论我在哪里单击,它都会打印两个语句。【参考方案2】:
您可以使用 Garden 中的 GearTick,它是一个旋转滑块。它不完全是您所需要的,但可以根据您的需要进行调整。 “默认情况下,它允许逆时针旋转,您可能需要它顺时针旋转”(更新:小部件现在有一个 orientation
属性,可以设置为“顺时针”或“逆时针”)。
您需要管理回弹并在“手指停止”处停止。
最后的示例使用动画管理回弹,但是您仍然需要管理/实现手指停止功能。
https://github.com/kivy-garden/garden.geartick
用法::
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)
安装::
pip install kivy-garden
garden install geartick
可以复制粘贴的工作示例::
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)
'''))
【讨论】:
看起来超级酷,但是当我尝试运行时,即使在安装了模块之后,我也得到了 ``` from kivy.uix.behaviors import ButtonBehavior ImportError: No module named behavior ``` 你需要master最新的kivy。看看这里的安装说明kivy.org/docs/installation/… 嗯,很奇怪,是的,我确实从 master 安装了,但仍然没有运气。 如果您从 master 安装了 kivy,那么它应该可以工作,请务必事先删除任何以前安装的 kivy,行为是在 1.8 中引入的。因此,如果您正确安装了它,那么它应该可以工作。在您的控制台上查找第一行,它应该提到运行任何 kivy 应用程序时使用的 kivy 版本。 我从 master 安装,我重新拉源,我编译到位,我运行 make---我得到了同样的错误。以上是关于在kivy中的触摸事件上旋转对象的主要内容,如果未能解决你的问题,请参考以下文章
iOS 8.x 中的 UIAlertController 无法响应触摸事件,触摸事件是不是发送到键窗口对象?