如何相对于Kivy中的小部件制作画布顶点指令

2024-10-01 22:33:57 发布

您现在位置:Python中文网/ 问答频道 /正文

我刚刚开始学习Kivy,我试图理解Canvas指令在调整kivyApp窗口大小时是如何受到影响的

在Kivy文档中提到——

Note

Kivy drawing instructions are not automatically relative to the position or size of the widget. You, therefore, need to consider these factors when drawing. In order to make your drawing instructions relative to the widget, the instructions need either to be declared in the KvLang or bound to pos and size changes.

example which follows显示了如何将Rectangle指令的大小和位置绑定到正在绘制它的小部件的大小和位置。因此,调整窗口大小时,大小和位置会成比例地改变

但是,对于像Bezier指令这样使用points的东西,我怎么能做同样的事情呢

我有一个自定义小部件HangManFig1,它扩展了Widget类,该类在KVlang中定义如下:

<HangManFig1>:
    canvas:
        Line:
            points: (150, 100, 150, 700)
            width: 10
        Line:
            points: (150, 700, 600, 700)
            width: 10
        Line:
            points: (150, 600, 250, 700)
            width: 10
        Line:
            points: (600, 700, 600, 600)
            width: 3
        Ellipse:
            pos: (550, 500)
        Line:
            bezier: (610, 510, 630, 400, 570, 350)
            width: 10
        Line:
            bezier: (570, 350, 510, 370, 450, 270)
            width: 10
        Line:
            bezier: (570, 350, 600, 300, 550, 200)
            width: 10
        Line:
            bezier: (610, 480, 530, 430, 500, 430)
            width: 10
        Line:
            bezier: (610, 480, 630, 500, 680, 390)
            width: 10

我以Screen的方式使用这个小部件,方式如下:

#:import HangManFig1 figures.hangmanfig1

<MainScreen>:
    name: '_main_screen_'
    BoxLayout:
        RelativeLayout:
            size_hint_x: 0.7
            HangManFig1:

        RelativeLayout:
            size_hint_x: 0.3
            Button:
                text: 'Play'
                pos_hint: {'x': 0.1, 'y': 0.80}
                size_hint: (0.8, 0.1)
                on_release:
                    root.manager.transition.direction = 'left'
                    root.manager.current = '_game_screen_'

            Button:
                text: 'Practice'
                pos_hint: {'x': 0.1, 'y': 0.60}
                size_hint: (0.8, 0.1)
                on_release:
                    root.manager.transition.direction = 'left'
                    root.manager.current = '_game_screen_'

            Button:
                text: 'Share'
                pos_hint: {'x': 0.1, 'y': 0.40}
                size_hint: (0.8, 0.1)
                on_release:
                    root.manager.transition.direction = 'left'
                    root.manager.current = '_share_screen_'

            Button:
                text: 'Credits'
                pos_hint: {'x': 0.1, 'y': 0.20}
                size_hint: (0.8, 0.1)
                on_release:
                    root.manager.transition.direction = 'left'
                    root.manager.current = '_credits_screen_'

KivyApp in full screen

当我调整窗口的大小时,我看到虽然Buttons的位置正确,但没有HangManFig1

Enter image description here

是否有一种方法可以将此小部件的大小绑定到父小部件的大小,以便即使窗口大小发生变化,它也能正确定位


Tags: thetopossize部件linemanagerbutton
3条回答

虽然您使用RelativeLayout使指令的坐标相对于小部件的位置,但它对其大小没有任何影响

当你用数值对所有的位置进行硬编码时,你需要一种方法来缩放这些值相对于你的小部件的大小,并且你必须考虑在这种情况下,你想要在行的宽度上发生什么,如果它相对于控件的大小也增长了呢?线性?还有别的吗?根据您的需要,存在各种可能性

最简单的起点,IMHO,是使用缩放指令,相对于小部件的大小设置所有画布指令,而不是您用来设计当前绞刑架的大小

<HangManFig1>:
    h: 600
    w: 800 # just guessing the size you used to design it, adjust as needed
    canvas:
        PushMatrix:
        Scale:
           xy: self.width / self.w, self.height / self.h

        Line:
            points: (150, 100, 150, 700)
            width: 10

        Ellipse:
            pos: (550, 500)
        ... # etc, all the other instructions remain unchanged

        PopMatrix: # restore the initial canvas so it doesn't affect other instructions after your drawing

如果这对您来说还不够,例如,因为您希望保持行的宽度不变,您可以不使用Scale指令,而是使用一个函数,将小部件的大小和一组坐标作为输入,并返回相对于该大小的值:

def rscale(size, *args):
    w, h = size
    ws = w / 800  # adjust accordingly, as in the first example
    hs = h / 600
    return (x / (ws if i % 2 else hs) for i, x in enumerate(args))

这个函数可以这样使用

        Line:
           points: rscale(self.size, 150, 100, 150, 700)

如果你想要更复杂的东西,比如保持绞刑架的长宽比,同时保持在你的尺寸范围内,你可以相应地调整如下:


def rscale(size, *args):
    w, h = size
    scale = min(w / 800, h / 600) # pick the smallest scale of the two
    return (x / s for x in args)

是的,你可以。这需要做一些工作,但不是为Canvas{}使用显式坐标,而是使用基于HangManFig1对象大小的值。例如,图形的第一个Line可能类似于:

<HangManFig1>:
    canvas:
        Line:
            points: (root.width*0.1, root.height * 0.1, root.width * 0.1, root.height * 0.8)
            width: 10

我有:

class ourLine(Line):
    widget=None
    vLines=[]
    firstime=True

    def __init__(self,relto=Window,**kwargs):
        self.kwargs=kwargs
        if not self.widget:
            print('you should inherit a new class with \'widget\' attr')
        super(ourLine,self).__init__(**kwargs)
        W, H = Window.width, Window.height
        self.rBezier=kwargs['bezier']
        self.vBezier=[]
        c=0
        for p in self.rBezier:
            c+=1
            if not c%2:
                self.vBezier.append(p/H)
            else:
                self.vBezier.append(p/W)
        self.__class__.vLines.append(self)
        del self.kwargs['bezier']

    def deAbstractor(self):
        W, H = self.__class__.widget.width, self.__class__.widget.height
        _vBezier=[]
        c=0
        with self.__class__.widget.canvas:
            for p in self.vBezier:
                c+=1
                if not c%2:
                    _vBezier.append(p*H)
                else:
                    _vBezier.append(p*W)
            Line(bezier=_vBezier, **self.kwargs)

    def dyna(c,w,s):
        print(w)
        for l in c.vLines:
            l.__class__.widget.canvas.clear()
        for l in c.vLines:
            l.deAbstractor()
    def activate(c):
        c.widget.bind(size=lambda w,s: myLine.dyna(myLine,w,s))

这可能有点混乱,可能包含一些不必要的内容。那是因为,首先我想让它更先进。但后来有点疯狂了。但这仍然是一个做矢量线的好方法。它支持宽度和您在行中提供的其他属性(我希望如此)。颜色不会改变,但可以实现。让我们看看它的实际行动:

import kivy
from kivy.app import App
from kivy.graphics import *
from kivy.core.window import Window
from kivy.uix.floatlayout import FloatLayout
from guy.who.helped import ourLine
root=FloatLayout()

#tell us your widget so we can refer
class myLine(ourLine):
    widget=root

#you may want to start listening later, so it is manual.
ourLine.activate(myLine)

#you can start using like a normal Line
with root.canvas:
    Color(1,1,0,1)
    myLine(bezier=[2,70,90,80,Window.width,Window.height])
    myLine(bezier=[200,170,309,80,Window.width/2,Window.height*0.8], width=12)

class MyApp(App):
    def build(self):
        return root

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

Enter image description here

这篇文章比其他的好吗?嗯,这可以使用Bézier curves,而不是,并且是响应性的

奇怪的是,它的性能并没有那么差。 当您调整窗口大小时,它将触发。所以,剩下的时间,就像基本行+几个字节的RAM来保存相对值和附加值一样

相关问题 更多 >

    热门问题