在 Kivy 中,如何制作位图、可点按和(按比例)调整大小?
In Kivy, how to make a bitmap, point-clickable and (proportionally) resizable?
我试图在 Kivy 中显示一个位图,并赋予它几个属性,其中一些似乎是相互排斥的。应该是:
- 可点击(能够找到点坐标,而不仅仅是点击按钮)
- 可调整大小
- 调整大小时保持比例
这是最后一项似乎有问题。我可以制作一个位图,它基本上是一个按钮类型的对象,并且可以点按 - 但它的大小是固定的。或者我可以制作一个“弹性”位图,它会在 Window 调整大小时调整大小。但是 - 要么是比例偏移,要么是 Widget 的“位图”部分以正确的比例显示,但是 Widget 有一个不成比例的组件,可以点击。
第一套是“按钮”模式,第二套是“拉伸”模式。
在拉伸模式下,实际显示 位图 会调整大小并保持比例大小。但是,显示它的 Image 小部件不按比例调整大小。 (因此 keep_ratio
选项适用于显示的位图,但不适用于实际的 Widget 对象。)这很好,但是整个 Widget 都是可点击的,而且我还没有找到确定位置的方法单击的点是,相对于位图。
我可以想到半解决方案,例如强制 Window 调整大小成比例,根据需要更改 width/height 以匹配,但这相当混乱。似乎应该有某种方法可以使用 Kivy objects/properties,但我还没有找到。
实际代码示例。在“按钮”模式下运行,
KIVY_NO_ARGS=1 python show_bitmap.py -b
import sys
from kivy.app import App
from kivy.uix.label import Label
from kivy.uix.image import Image
from kivy.graphics import Color, Rectangle
from kivy.core.window import Window
from kivy.uix.relativelayout import RelativeLayout
hex_bmp = 'basic_hexes.png'
hex_bmp_size = (456, 292)
stretch_params = {'source': hex_bmp, # Bitmap is stretchable, but click-region is wrong
'allow_stretch': True,
'keep_ratio': True,
'pos': (110, 110),
'size': hex_bmp_size,
'size_hint': (0.5, 0.5),
}
button_params = {'source': hex_bmp, # Behaves like a Button, with regard to click-region
'allow_stretch': True,
'keep_ratio': True,
'pos': (110, 110),
'size': hex_bmp_size,
'size_hint': (None, None),
}
new_params = stretch_params
class VariableImage(Image):
def __init__(self, **kwargs):
super().__init__(**new_params, **kwargs)
Window.bind(on_resize=self.on_window_resize)
with self.canvas.before:
Color(0.9, 0.2, 0.2, 0.5)
self.bg_rect = Rectangle(pos=self.pos, size=self.size)
def on_touch_up(self, touch):
if self.collide_point(*touch.pos):
print(f"*** Clicked VariableImage, pos: {touch.pos}")
def on_window_resize(self, window, width, height):
self.bg_rect.size = self.size
class TopBoxLayout(RelativeLayout):
def __init__(self, initial_window_size, **kwargs):
super().__init__(**kwargs)
self.orientation = 'vertical'
self.bg_rect = None
self.size = initial_window_size
self.label_2 = Label(text='WTF?', pos_hint={'top': 1}, size_hint=(1.0, 0.1))
self.add_widget(self.label_2)
self.back_picture = VariableImage()
self.add_widget(self.back_picture)
self.setup_background()
Window.bind(on_resize=self.on_window_resize)
def setup_background(self):
with self.canvas.before:
Color(0.6, 0.6, 0.6, 0.9)
self.bg_rect = Rectangle(pos=self.pos, size=self.size)
with self.canvas.after:
Color(0.9, 0.9, 0.9, 0.9)
self.target_rect = Rectangle(pos=(100, 100), size=(10, 10))
def on_window_resize(self, window, width, height):
self.bg_rect.pos = self.pos
self.bg_rect.size = self.size
class canvasMain(App):
def build(self):
Window.size = (700, 450)
self.root = TopBoxLayout(Window.size)
return self.root
if __name__ == '__main__':
if len(sys.argv) > 1 and sys.argv[1] == '-b':
new_params = button_params
canvasMain().run()
我认为关键是要确保 VariableImage
实例保持其比例与位图的比例相匹配。这是您的代码的修改版本:
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.image import Image
from kivy.core.window import Window
from kivy.uix.relativelayout import RelativeLayout
kv = '''
<TopBoxLayout>:
canvas.before:
Color:
rgba: (0.6, 0.6, 0.6, 0.9)
Rectangle:
pos: self.pos
size: self.size
Color:
rgba: (0.9, 0.9, 0.9, 0.9)
Rectangle:
pos: 100, 100
size: 10, 10
Label:
text: 'WTF?'
pos_hint: {'top': 1}
size_hint: (1.0, 0.1)
VariableImage:
id: vi
size_hint: None, None
# size: 0.5 * root.width, 0.5 * root.width / self.image_ratio # does the same as the `on_size()` method
<VariableImage>:
source: 'basic_hexes.png'
allow_stretch: True
keep_ratio: True
pos: (110, 110)
canvas.before:
Color:
rgba: (0.9, 0.2, 0.2, 0.5)
Rectangle:
pos: self.pos
size: self.size
'''
class VariableImage(Image):
def on_touch_up(self, touch):
if self.collide_point(*touch.pos):
print(f"*** Clicked VariableImage, pos: {touch.pos}")
class TopBoxLayout(RelativeLayout):
def on_size(self, instance, new_size):
vi = self.ids.vi
# adjust size of VariableImage based on layout width
# a more complicated logic can be used, like keeping size with layout size
vi.size = 0.5 * self.width, 0.5 * self.width / vi.image_ratio
class canvasMain(App):
def build(self):
Window.size = (700, 450)
Builder.load_string(kv)
return TopBoxLayout()
if __name__ == '__main__':
canvasMain().run()
这使用 kivy
语言做了一些简化。 kv
中的行:
size: 0.5 * root.width, 0.5 * root.width / self.image_ratio
保持 VariableImage
的比率与位图比率相同(如果未注释)。 TopBoxLayout
的 on_size()
方法将做完全相同的事情,但更容易用于更复杂的逻辑。
您不需要这两种方法。一个或另一个都可以删除。
我试图在 Kivy 中显示一个位图,并赋予它几个属性,其中一些似乎是相互排斥的。应该是:
- 可点击(能够找到点坐标,而不仅仅是点击按钮)
- 可调整大小
- 调整大小时保持比例
这是最后一项似乎有问题。我可以制作一个位图,它基本上是一个按钮类型的对象,并且可以点按 - 但它的大小是固定的。或者我可以制作一个“弹性”位图,它会在 Window 调整大小时调整大小。但是 - 要么是比例偏移,要么是 Widget 的“位图”部分以正确的比例显示,但是 Widget 有一个不成比例的组件,可以点击。
第一套是“按钮”模式,第二套是“拉伸”模式。
在拉伸模式下,实际显示 位图 会调整大小并保持比例大小。但是,显示它的 Image 小部件不按比例调整大小。 (因此 keep_ratio
选项适用于显示的位图,但不适用于实际的 Widget 对象。)这很好,但是整个 Widget 都是可点击的,而且我还没有找到确定位置的方法单击的点是,相对于位图。
我可以想到半解决方案,例如强制 Window 调整大小成比例,根据需要更改 width/height 以匹配,但这相当混乱。似乎应该有某种方法可以使用 Kivy objects/properties,但我还没有找到。
实际代码示例。在“按钮”模式下运行,
KIVY_NO_ARGS=1 python show_bitmap.py -b
import sys
from kivy.app import App
from kivy.uix.label import Label
from kivy.uix.image import Image
from kivy.graphics import Color, Rectangle
from kivy.core.window import Window
from kivy.uix.relativelayout import RelativeLayout
hex_bmp = 'basic_hexes.png'
hex_bmp_size = (456, 292)
stretch_params = {'source': hex_bmp, # Bitmap is stretchable, but click-region is wrong
'allow_stretch': True,
'keep_ratio': True,
'pos': (110, 110),
'size': hex_bmp_size,
'size_hint': (0.5, 0.5),
}
button_params = {'source': hex_bmp, # Behaves like a Button, with regard to click-region
'allow_stretch': True,
'keep_ratio': True,
'pos': (110, 110),
'size': hex_bmp_size,
'size_hint': (None, None),
}
new_params = stretch_params
class VariableImage(Image):
def __init__(self, **kwargs):
super().__init__(**new_params, **kwargs)
Window.bind(on_resize=self.on_window_resize)
with self.canvas.before:
Color(0.9, 0.2, 0.2, 0.5)
self.bg_rect = Rectangle(pos=self.pos, size=self.size)
def on_touch_up(self, touch):
if self.collide_point(*touch.pos):
print(f"*** Clicked VariableImage, pos: {touch.pos}")
def on_window_resize(self, window, width, height):
self.bg_rect.size = self.size
class TopBoxLayout(RelativeLayout):
def __init__(self, initial_window_size, **kwargs):
super().__init__(**kwargs)
self.orientation = 'vertical'
self.bg_rect = None
self.size = initial_window_size
self.label_2 = Label(text='WTF?', pos_hint={'top': 1}, size_hint=(1.0, 0.1))
self.add_widget(self.label_2)
self.back_picture = VariableImage()
self.add_widget(self.back_picture)
self.setup_background()
Window.bind(on_resize=self.on_window_resize)
def setup_background(self):
with self.canvas.before:
Color(0.6, 0.6, 0.6, 0.9)
self.bg_rect = Rectangle(pos=self.pos, size=self.size)
with self.canvas.after:
Color(0.9, 0.9, 0.9, 0.9)
self.target_rect = Rectangle(pos=(100, 100), size=(10, 10))
def on_window_resize(self, window, width, height):
self.bg_rect.pos = self.pos
self.bg_rect.size = self.size
class canvasMain(App):
def build(self):
Window.size = (700, 450)
self.root = TopBoxLayout(Window.size)
return self.root
if __name__ == '__main__':
if len(sys.argv) > 1 and sys.argv[1] == '-b':
new_params = button_params
canvasMain().run()
我认为关键是要确保 VariableImage
实例保持其比例与位图的比例相匹配。这是您的代码的修改版本:
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.image import Image
from kivy.core.window import Window
from kivy.uix.relativelayout import RelativeLayout
kv = '''
<TopBoxLayout>:
canvas.before:
Color:
rgba: (0.6, 0.6, 0.6, 0.9)
Rectangle:
pos: self.pos
size: self.size
Color:
rgba: (0.9, 0.9, 0.9, 0.9)
Rectangle:
pos: 100, 100
size: 10, 10
Label:
text: 'WTF?'
pos_hint: {'top': 1}
size_hint: (1.0, 0.1)
VariableImage:
id: vi
size_hint: None, None
# size: 0.5 * root.width, 0.5 * root.width / self.image_ratio # does the same as the `on_size()` method
<VariableImage>:
source: 'basic_hexes.png'
allow_stretch: True
keep_ratio: True
pos: (110, 110)
canvas.before:
Color:
rgba: (0.9, 0.2, 0.2, 0.5)
Rectangle:
pos: self.pos
size: self.size
'''
class VariableImage(Image):
def on_touch_up(self, touch):
if self.collide_point(*touch.pos):
print(f"*** Clicked VariableImage, pos: {touch.pos}")
class TopBoxLayout(RelativeLayout):
def on_size(self, instance, new_size):
vi = self.ids.vi
# adjust size of VariableImage based on layout width
# a more complicated logic can be used, like keeping size with layout size
vi.size = 0.5 * self.width, 0.5 * self.width / vi.image_ratio
class canvasMain(App):
def build(self):
Window.size = (700, 450)
Builder.load_string(kv)
return TopBoxLayout()
if __name__ == '__main__':
canvasMain().run()
这使用 kivy
语言做了一些简化。 kv
中的行:
size: 0.5 * root.width, 0.5 * root.width / self.image_ratio
保持 VariableImage
的比率与位图比率相同(如果未注释)。 TopBoxLayout
的 on_size()
方法将做完全相同的事情,但更容易用于更复杂的逻辑。
您不需要这两种方法。一个或另一个都可以删除。