如何在 Kivy 中将图像放入动态文本中

Posted

技术标签:

【中文标题】如何在 Kivy 中将图像放入动态文本中【英文标题】:How to put images into a dynamic text in Kivy 【发布时间】:2018-11-03 18:05:30 【问题描述】:

我正在研究 kivy,并希望将图片嵌入到显示的文本中。 文本被加载一个简单的字符串,然后显示,这很容易做到,但我不知何故找不到如何在所述字符串中显示图像的线索。

这个问题的答案是为这些构建一个新的布局,在这种情况下是 TextWrapper。

示例代码:

from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.button import Label, Button
from kivy.lang import Builder
from kivy.properties import BooleanProperty, StringProperty
from kivy.uix.behaviors import FocusBehavior
from kivy.uix.image import Image
from kivy.uix.recycleboxlayout import RecycleBoxLayout
from kivy.uix.recycleview import RecycleView
from kivy.uix.recycleview.layout import LayoutSelectionBehavior
from kivy.uix.recycleview.views import RecycleDataViewBehavior
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.uix.scrollview import ScrollView

Builder.load_string("""
<ScreenSpecies>:
    BoxLayout:
        orientation: 'vertical'

        Label:
            pos_hint: "x": .45, "top": 1
            size_hint: .1, .1
            text: "The Species"

        GridLayout:
            id: species_layout
            rows: 1
            cols: 2
            padding: dp(10)
            spacing: dp(10)
            orientation: 'horizontal'

            SpeciesView:
                id: species_list_view

            SpeciesLabel:
                id: species_text
                text_selected: "Text" if not species_list_view.text_selected else species_list_view.text_selected
                name_selected: "" if not species_list_view.name_selected else species_list_view.name_selected


<SpeciesView>:
    viewclass: 'SelectableLabel'
    text_selected: ""
    name_selected: ""

    SelectableRecycleBoxLayout:
        orientation: 'vertical'
        default_size: None, dp(32)
        default_size_hint: .6, None
        size_hint: 1, .9
        multiselect: False
        touch_multiselect: False


<SpeciesLabel>:
    size_hint_y: .85
    Label:
        halign: 'left'
        valign: 'middle'
        size_hint_y: None
        height: self.texture_size[1]
        text_size: self.width, None
        text: root.text_selected



<SelectableLabel>:
    canvas.before:
        Color:
            rgba: (.05, 0.5, .9, .8) if self.selected else (.5, .5, .5, 1)
        Rectangle:
            pos: self.pos
            size: self.size
""")


class TextWrapper(BoxLayout):
    def __init__(self, text="", **kwargs):
        super(TextWrapper, self).__init__(**kwargs)
        self.content = self.wrap_text(text)

    def wrap_text(self, source):
        text = source.split("|")

        for i in range(0, len(text)):
            if "img=" in text[i]:
                self.add_widget(Image(source=text[i][4:]))
            else:
                self.add_widget(Label(text=text[i]))
        return text


class SelectableRecycleBoxLayout(FocusBehavior, LayoutSelectionBehavior,
                                 RecycleBoxLayout):
    pass


class SelectableLabel(RecycleDataViewBehavior, Label):
    ''' Add selection support to the Label '''
    index = None
    selected = BooleanProperty(False)
    selectable = BooleanProperty(True)

    def refresh_view_attrs(self, rv, index, data):
        ''' Catch and handle the view changes '''
        self.index = index
        return super(SelectableLabel, self).refresh_view_attrs(
            rv, index, data)

    def on_touch_down(self, touch):
        ''' Add selection on touch down '''
        if super(SelectableLabel, self).on_touch_down(touch):
            return True
        if self.collide_point(*touch.pos) and self.selectable:
            return self.parent.select_with_touch(self.index, touch)

    def apply_selection(self, rv, index, is_selected):
        ''' Respond to the selection of items in the view. '''
        self.selected = is_selected
        if is_selected:
            print("selection changed to 0".format(rv.data[index]))
            rv.name_selected = rv.data[index]['text']
            rv.text_selected = rv.data[index]['description']
        else:
            print("selection removed for 0".format(rv.data[index]))


class ScreenSpecies(Screen):
    pass


class SpeciesView(RecycleView):
    def __init__(self, **kwargs):
        super(SpeciesView, self).__init__(**kwargs)

        self.species_data = [
            "text": "Test1", "description": "Test1.textbf |img=serveimage.png| Test1.textaf",
            "text": "Test2", "description": "Test2.text",
            "text": "Test3", "description": "Test3.text"
        ]

        for species in self.species_data:
            species["description"] = TextWrapper(species["description"])
        # clean keywords out

        self.data = self.species_data


class SpeciesLabel(ScrollView):
    text_selected = StringProperty("")
    name_selected = StringProperty("")


screen_manager = ScreenManager()
screen_manager.add_widget(ScreenSpecies(name="screen_species"))


class TestApp(App):
    def build(self):
        return screen_manager


test_app = TestApp()
test_app.run()

此代码不起作用!因为您不能在不破坏程序的情况下单击标签,这就是问题所在。 text_selected 只接受 str

 ValueError: SpeciesView.text_selected accept only str

我不知道如何显示视图每个元素的描述中的TextWrapper。

这一切的目的是,我可以根据例如更改文本一个可选择的选项列表,并且每个文本中仍然有图像。 结果应该类似于:

同样,问题是如何根据最后按下的按钮动态更改右侧的文本并更改其中的图像。

感谢您的帮助!

【问题讨论】:

我已经编辑了我的答案来回答你的一些新问题。 【参考方案1】:

包装

我没有在 kivy 中找到任何特定的包装工具来解决您的问题,所以我为此编写了一个临时函数。理想情况下,我认为应该有一个 TextMarkup 选项来允许在文本中嵌入图像,甚至是一个单独的小部件,但现在这总比没有好。

def wrap_text(target, source):
    text = source.split("|")

    for i in range(0, len(text)):
        if "img=" in text[i]:
            target.add_widget(Image(source=text[i][4:]))
        else:
            target.add_widget(Label(text=text[i]))

提供的代码示例在假设您将图像引用按以下格式放入字符串中的情况下工作 - |img=path|。示例:

class Main(App):

    def build(self):
        base = Builder.load_file("Main.kv")

        text_field = "This is text, below it should be the first image|img=example_1.png|" \
                     "Whereas this text should appear in between the images|img=example_2.png|" \
                     "And this text under the second image"

        wrap_text(base, text_field)

        return base

Main.kv 内容

BoxLayout:
    orientation: 'vertical'

当然,如果您愿意,您可以引入一些改进,例如,您可以制作自己的自定义改进,而不是添加一般的Label。或者,您可以使图像始终水平添加,除非 \n 字符位于图像引用之前的字符串中。或者您可以尝试扩展现有的 kivy 工具以包含您想要的功能(就像人们在 kivy 的 Garden 中所做的那样)。

从小部件中检索文本

rv.text_selected = rv.data[index]['description'] 不起作用,因为 rv.data[index]['description']TextWrapper 小部件,而不是字符串。请注意,您将其指定为以下行中的包装器:species["description"] = TextWrapper(species["description"])

如果您想从小部件内检索文本字段(实际上是小部件内标签内的文本),请使用以下代码:

# Initialise a new, empty string, to add to it later
text = ""
        
# Iterate over widget's children (might as well dynamically assign them an id to find them faster)
for widget in rv.data[index]['description'].walk():
            
    # Add text if a label is found
    if isinstance(widget, Label):
        text += widget.text
        
# Assign the text to the field
rv.text_selected = text

输出:

同样,您可以以相同的方式检索图像。但是,请注意,如果您想同时显示文本和图像,则不必手动检索文本或图像,只需显示 TextWrapper 即可。您可以通过调用 add_widget() 来添加它,例如添加到 BoxLayout

【讨论】:

就是这样,我只是通过描述中的文本构建 TextWrapper,效率不高,但效果很好:) 万分感谢!【参考方案2】:

要实现这一点,您必须创建一个自定义小部件,首先创建一个Button,然后将一个FloatLayout 添加到Button。在FloatLayout 中添加一个Image 和一个Label 这是一个例子 .py

from kivy.app import App
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.button import Button
from kivy.properties import ObjectProperty
from kivy.uix.image import Image

#the TextImage control
class TextImage(Button):
   lbl = ObjectProperty()
   img = ObjectProperty()
   box = ObjectProperty()

   def __init__(self, **kwargs):
        super(TextImage, self).__init__(**kwargs)

   #function to change specific widget position
   def orient1(self):
       children = self.ids.box.children[:]
       self.ids.box.clear_widgets()
       for child in sorted(children):
       wid.add_widget(child) 
    .... # you can create your own funtions for arrangement

class Form(FloatLayout):
    def __init__(self, **kwargs):
        super(Form, self).__init__(**kwargs)

        Textimg = TextImage()
        Textimg.orient1() #for index arrangement
        #an image
        self.Img = Image(source='pic.png', size_hint=(None,None),size=(100,100))   
        # change the widget organization
        Textimg.ids.box.orientation='vertical' 
        Textimg.ids.box.add_widget(self.Img) 
        #change your picture
        Textimg.ids.img.source='myexample.png'
        #Change the text
        Textimg.ids.lbl.text='Picture1'
        self.add_widget(Textimg)

class MainApp(App):
    def build(self):
        return Form()

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

.kv 文件

<TextImage>:
    lbl: lbl
    img: img
    box: box

    FloatLayout:
        BoxLayout: # add more Image widget and change there orientation
            id: box
            Image:
                id: img
                source: 'myexample.png'
                pos_hint: 'center_x':.5,'center_y':.5
                size_hint: 1,.9

            Label:
               id: lbl
               size_hint: 1,.1
               pos_hint: 'center_x':.5,'center_y':.5
               halign:'center'

【讨论】:

谢谢!虽然这不允许我在文本和我想要的位置添加可变数量的图像,所以它不是我特定问题的答案,但对于更静态的方法来说这是一个好主意。 如果您想添加可变图像并更改文本相对于图像的位置,那么您将向 FloatLayout 添加一个 BoxLayout(指定 id:名称)并使用添加小部件图像,您可以更改boxlayout 的方向并使用小部件的索引来归档您想要的内容 我发现了一个新问题并再次编辑了我的问题,感谢您的想法(我根据您的回答制作了 TextWrapper)但我不知道如何将 TextWrapper 添加到视图中,谢谢!

以上是关于如何在 Kivy 中将图像放入动态文本中的主要内容,如果未能解决你的问题,请参考以下文章

如何在kivy中将屏幕背景设置为图像

在 Kivy 中将图像对象作为按钮背景传递

如何在java中将文本添加到图像中?

如何在 UILabel 中将文本放入圆圈中

如何使用绑定动态更新 Kivy Label 的文本属性

如何在 Kivy 的 Scrollable GridLayout 中显示图像