我对Kivy完全陌生,我正在尝试找出最好的方法来整理我的意思。我希望能够在屏幕上绘制一些图形(例如,由3个圆组成的三角形和一条连接所有这些图形的线),其中我可以单击一个圆并将其拖动到另一个位置,然后重新绘制圆的新位置的线。最好是,在拖动时,我希望圆圈在光标/触摸输入上“粘住”。
我要制作这些小部件吗?我知道Kivy提供的图形工具可以绘制所需的形状,但是我不确定在绘制之后如何再次与它们进行交互。另外,我不确定如何用光标进行拖动的“粘”部分,因为看来Kivy只会在光标旁边重复绘制圆,这将导致它反复覆盖图形。 / p>
答案 0 :(得分:0)
您可能能够从Bezier示例中获得启发,因为它对引导线并允许拖动它们的点进行了类似的操作。
https://github.com/kivy/kivy/blob/master/examples/canvas/bezier.py
我在这里也做了一个更复杂的例子 https://gist.github.com/tshirtman/78669a514f390bf246627b190e2eba1a 允许创建多行。
基本上,如果在一个小部件中有多个交互点,则该想法是在属性中跟踪这些点的位置,并使用此属性绘制画布,因此说明在以下情况时会自动更新属性更改,还可以使用on_touch_down
方法中的属性来检查与他们的触摸距离,从而确定要与之交互的点(如果有),一旦确定,您只需要以某种方式将该接触链接到该点,这样就可以进行进一步的交互(on_touch_move
和on_touch_up
)(touch.ud
对此很有用),并抓住接触,这样您就不会错过任何更新(父级小部件始终可以确定这种接触实际上不再传播了。)
要点中的代码可供参考(并且因为SO不太喜欢指向外部资源的答案)。
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.widget import Widget
from kivy.properties import ListProperty, NumericProperty
from kivy.metrics import dp
KV = '''
#:import chain itertools.chain
FloatLayout:
Label:
size_hint_y: None
text_size: self.width, None
height: self.texture_size[1]
pos_hint: {'top': 1}
color: 0, 0, 0, 1
padding: 10, 10
text:
'\\n'.join((
'click to create line',
'click near a point to drag it',
'click near a line to create a new point in it',
'double click a point to delete it'
))
canvas.before:
Color:
rgba: 1, 1, 1, .8
Rectangle:
pos: self.pos
size: self.size
BezierCanvas:
<BezierLine>:
_points: list(chain(*self.points))
canvas:
Color:
rgba: 1, 1, 1, .2
SmoothLine:
points: self._points or []
Color:
rgba: 1, 1, 1, 1
Line:
bezier: self._points or []
width: 2
Color:
rgba: 1, 1, 1, .5
Point:
points: self._points or []
pointsize: 5
'''
def dist(a, b):
return ((a[0] - b[0]) ** 2 + (a[1] - b[1]) ** 2) ** .5
class BezierLine(Widget):
points = ListProperty()
select_dist = NumericProperty(10)
delete_dist = NumericProperty(5)
def on_touch_down(self, touch):
if super(BezierLine, self).on_touch_down(touch):
return True
max_dist = dp(self.select_dist)
l = len(self.points)
for i, p in enumerate(self.points):
if dist(touch.pos, p) < max_dist:
touch.ud['selected'] = i
touch.grab(self)
return True
for i, p in enumerate(self.points[:-1]):
if (
dist(touch.pos, p)
+ dist(touch.pos, self.points[i + 1])
- dist(p, self.points[i + 1])
< max_dist
):
self.points = (
self.points[:i + 1]
+ [list(touch.pos)]
+ self.points[i + 1:]
)
touch.ud['selected'] = i + 1
touch.grab(self)
return True
def on_touch_move(self, touch):
if touch.grab_current is not self:
return super(BezierLine, self).on_touch_move(touch)
point = touch.ud['selected']
self.points[point] = touch.pos
def on_touch_up(self, touch):
if touch.grab_current is not self:
return super(BezierLine, self).on_touch_up(touch)
touch.ungrab(self)
i = touch.ud['selected']
if touch.is_double_tap:
if len(self.points) < 3:
self.parent.remove_widget(self)
else:
self.points = (
self.points[:i] + self.points[i + 1:]
)
class BezierCanvas(Widget):
def on_touch_down(self, touch):
if super(BezierCanvas, self).on_touch_down(touch):
return True
bezierline = BezierLine()
bezierline.points = [(touch.pos), (touch.pos)]
touch.ud['selected'] = 1
touch.grab(bezierline)
self.add_widget(bezierline)
return True
class BezierApp(App):
def build(self):
return Builder.load_string(KV)
if __name__ == '__main__':
try:
BezierApp().run()
except:
import pudb; pudb.post_mortem()