【问题标题】:How to make Canvas vertex instructions relative to a widget in Kivy如何在 Kivy 中制作相对于小部件的 Canvas 顶点指令
【发布时间】:2021-05-07 09:35:41
【问题描述】:

我刚开始学习 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 指令的大小和位置绑定到正在绘制它的小部件的大小和位置。因此,当调整窗口大小时,大小和位置会按比例变化。

但是,对于使用 pointsBezier 指令之类的指令,我该如何做同样的事情。

我有一个自定义小部件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_'

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

有没有办法可以将此小部件的大小绑定到父小部件的大小,以便即使窗口大小发生变化,它也能正确定位?

【问题讨论】:

    标签: python kivy kivy-language


    【解决方案1】:

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

    当您通过数值对所有位置进行硬编码时,您需要一种方法来相对于小部件的大小来缩放这些值,并且您必须考虑在这种情况下您希望在线条宽度方面发生什么,它是否也应该相对于小部件的大小而增长?线性?还有什么?取决于您想要的各种可能性。

    恕我直言,最简单的起点是使用 Scale 指令,将所有画布指令设置为相对于您的小部件的大小,超过您用于设计当前刽子手的大小。

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

    【讨论】:

    • 非常感谢。,非常简单,效果很好
    【解决方案2】:

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

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

    【讨论】:

    • 非常感谢。我一定会试试这个。顺便说一句,这是绘制此类自定义形状的推荐方法,还是有更好的方法?
    • 不知道有没有“推荐”的方法。另一种方法是在 python 中进行绘图,而不是kv,但这需要将一个方法(进行绘图)绑定到HangManFig1size 属性。
    【解决方案3】:

    我有:

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

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

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

    凭什么,这个帖子比其他帖子好?嗯,这可以使用Bézier curves,而不是points,并且是响应式的。

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

    【讨论】:

    • 非常感谢。我花了一些时间来了解真正发生的事情并正确翻译代码以适合我的应用程序。
    【解决方案4】:

    好吧,我有个坏消息。一旦我想为贝塞尔曲线制作动画,我可以为Bezier 类贝塞尔曲线制作动画,但不能为Line(bezier=(...)) 制作动画。而Bezierwidth 是不能更改的,因为没有这样的属性。最后我只动画了 1px 宽度的贝塞尔曲线。所以,Kivy... 中的动态矢量线并不多(我希望如此)。

    我喜欢那个库的简单性,但我猜它还不成熟。我决定继续前进。

    有很多东西没有 Kivy 的问题,例如 web 和 webframeworks(我选择了 Kivy)。我喜欢 Python,而 Kivy 非常有能力。但是当涉及到一些具体的事情时,Kivy 真的很缺乏:'(

    【讨论】:

    • 感谢您的回答...这意味着我无法将上述小部件相对于其父小部件进行定位
    • 好吧,我通常在编程中从不使用“不能”这个词。而且“如何”比“可以”更重要。无脑的方法是进行抽象,为您希望的窗口获取您的值并为当前窗口生成相对版本。以及从这些相对值中获取当前窗口的绝对值并将其绑定到窗口的函数。清除并重绘
    • 您不能为行的bezier 属性设置动画,是的,但您可以将其绑定到您选择的 ListProperty,然后为该属性设置动画,所以当我需要时,我只需创建小部件上的属性,并在我的画布行中设置bezier: root.the_property_name,动画就像Animation(the_property_name=target_value).start(the_widget)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-09-28
    • 1970-01-01
    • 1970-01-01
    • 2017-09-18
    相关资源
    最近更新 更多