一个文本输入框和一个按钮在水平盒布局中,文本输入框几乎占满了屏幕空间,按钮只占很小的空间

移动开发 2026-07-10

问题在于 MDTextField 占据大部分宽度,而 MDButton 在右侧几乎没有空间。尝试了 size_hint_x = .5 在两者中都不工作,.7 .3 也不工作。1:1 也不工作。尝试给按钮设定固定宽度,但也不起作用。并且每行之间的垂直间距太大,显得不紧凑。

使用 pydroid 3kivymd2android

可复现问题的代码在这里:

from kivymd.app import MDApp
from kivymd.uix.screen import MDScreen
from kivymd.uix.boxlayout import MDBoxLayout
from kivymd.uix.appbar import MDTopAppBar, MDTopAppBarTitle
from kivymd.uix.label import MDLabel
from kivymd.toast import toast
from kivymd.uix.button import MDButton, MDButtonIcon, MDButtonText
from kivymd.uix.widget import MDWidget
from kivymd.icon_definitions import md_icons
from kivymd.uix.textfield import (
    MDTextField, 
    MDTextFieldHintText,
    MDTextFieldMaxLengthText
)
from kivymd.uix.segmentedbutton import (
    MDSegmentedButton,
    MDSegmentedButtonItem,
    MDSegmentButtonLabel
)
from kivymd.uix.dialog import (
    MDDialog,
    MDDialogHeadlineText,
    MDDialogSupportingText,
    MDDialogButtonContainer
)
from kivy.uix.widget import Widget
from kivy.core.window import Window
from kivy.core.text import Label as CoreLabel
from kivy.metrics import dp

class TxtField(MDTextField):
    def __init__(self, ilen = 7, htxt="", shintx=0.7, **kwargs):
        super().__init__(**kwargs)
        self.mode = "outlined" #"filled"
        self.size_hint_x = .5
        self.pos_hint={"center_x": .5}
        self.input_filter = lambda s, u: s if (
            len(self.text) < ilen and s.isdigit()
        ) else ""
        if not htxt: htxt = "input:"
        self.add_widget(
            MDTextFieldHintText(
                text = htxt
            )
        )

class mkbtn(MDButton):
    def __init__(
        self,
        text,
        style="elevated", #
        radius=[20,20,20,20],
        icon=None,
        func=lambda x: dummy_func(x), **kwargs):

        super().__init__(**kwargs)
        self.style = style
        self.radius = radius
        self.size_hint_x = None
        self.pos_hint={"center_x": .5}
        #padding=[5, 5, 5, 5]
        #self.adaptive_width=True
        #self.size_hint_min_x = 0.3
        self.wide = dp(120)
        if icon: self.add_widget(MDButtonIcon(icon))
        self.add_widget(MDButtonText(text=text))
        self.bind(on_release=func)


def dummy_func(x="", y=""):
    pass

class MainScreen(MDScreen):
    def __init__(self, **kwargs):
        super().__init__(**kwargs)

        self.md_bg_color = [1, 1, 1, 1]
        #md_bg_color: self.theme_cls.secondaryContainerColor

        self.main_layout = MDBoxLayout(orientation="vertical")

        self.app_bar = MDTopAppBar(type="small")
        self.abtitle = MDTopAppBarTitle(
            text="WOS Squad Ratio (Bear/CJ)",
            halign="center"
        )
        self.app_bar.add_widget(self.abtitle)


        self.content_layout = MDBoxLayout(orientation="vertical", padding=[5, 5, 5, 5], spacing=dp(10),)

        self.hlay1 = MDBoxLayout(orientation="horizontal", padding=[5, 5, 5, 5], spacing=dp(10))
        self.bdctf = TxtField(htxt="Bear DC", ilen=6)
        self.prof_btn = mkbtn("Profiles", icon="triangle-small-down")
        self.hlay1.add_widget(self.bdctf)
        self.hlay1.add_widget(self.prof_btn)
        #self.hlay1.add_widget(MDWidget())
        ###
        self.hlay2 = MDBoxLayout(orientation="horizontal", padding=[5, 5, 5, 5], spacing=dp(10),)
        self.cdctf = TxtField(htxt="CJ DC", ilen=6)
        self.aprof_btn = mkbtn("Add Profiles")
        self.hlay2.add_widget(self.cdctf)
        self.hlay2.add_widget(self.aprof_btn)
        ###
        self.hlay3 = MDBoxLayout(orientation="horizontal", padding=[5, 5, 5, 5], spacing=dp(10),)
        self.titf = TxtField(htxt="Total Infantry", ilen=7)
        self.cb_btn = mkbtn("Calc Bear")
        self.hlay3.add_widget(self.titf)
        self.hlay3.add_widget(self.cb_btn)
        ###
        self.hlay4 = MDBoxLayout(orientation="horizontal", padding=[5, 5, 5, 5], spacing=dp(10),)
        self.tltf = TxtField(htxt="Total Lancer", ilen=7)
        self.cc_btn = mkbtn("Calc CJ")
        self.hlay4.add_widget(self.tltf)
        self.hlay4.add_widget(self.cc_btn)
        ###
        self.hlay5 = MDBoxLayout(orientation="horizontal", padding=[5, 5, 5, 5], spacing=dp(10),)
        self.tmtf = TxtField(htxt="Total Lancer", ilen=7)
        self.sd_btn = mkbtn("Save Data")
        self.hlay5.add_widget(self.tmtf)
        self.hlay5.add_widget(self.sd_btn)
        ###
        self.tlbl = MDLabel(halign="left",role="large",padding=[5, 0, 0, 500],text = "")
        def cng_lbl(txt): self.tlbl.text = txt
        ###
        self.content_layout.add_widget(self.hlay1)
        self.content_layout.add_widget(self.hlay2)
        self.content_layout.add_widget(self.hlay3)
        self.content_layout.add_widget(self.hlay4)
        self.content_layout.add_widget(self.hlay5)
        self.content_layout.add_widget(MDWidget())
        ###
        self.main_layout.add_widget(self.app_bar)
        self.main_layout.add_widget(self.content_layout)
        ###
        self.add_widget(self.main_layout)

class MyApp(MDApp):
    def build(self):
        root = MainScreen()
        return root

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

解决方案

我不知道是否解决了所有问题,但我会描述我发现的情况。

似乎 MDButton 的工作方式与其他控件不同。

这是原始的 MDButton.init

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        Clock.schedule_once(self.adjust_width, 0.2)
        Clock.schedule_once(self.adjust_pos, 0.2)

它使用 Clock 在0.2秒后执行 self.adjust_width,以创建具有最小宽度的按钮——因此它不会在自己的 __init__ 中一次性使用 self.width,因为它将被 self.adjust_width 计算出的宽度所替换。

(顺便说一句:据我记得 __init__ 不能直接计算,因为 Kivy 稍后才创建真实对象,从文件 .kv 读取并设置值,之后代码还可能改变大小——这就是它必须使用 Clock 来延迟运行代码的原因)

我找到了两种实现方式:

  1. 也使用 Clock 来运行自己的函数,稍后再修改宽度
from kivy.clock import Clock

class mkbtn(MDButton):
    def __init__(...):

        # ... code ...

        Clock.schedule_once(self.update_later, 0.2)

    def update_later(self, args):
        self.width = dp(120)
  1. adjust_width 替换为自己的函数——空代码——这样就不会计算自己的 width
class mkbtn(MDButton):
    def __init__(...):

        # ... code ...

        self.width = dp(120)

    def adjust_width(self, args):
        pass

在桌面Linux上的结果

enter image description here

我还把按钮上的文本居中(但文本与图标可能需要不同的方法)

#from kivy.clock import Clock

# if you put this before class mkbtn then you can use `func=dummy_func`
def dummy_func(x="", y=""):
    pass


class mkbtn(MDButton):
    def __init__(
        self,
        text,
        style="elevated",  #
        radius=[20, 20, 20, 20],
        icon=None,
        # func=lambda x: dummy_func(x),
        func=dummy_func,
        **kwargs
    ):

        super().__init__(**kwargs)
        self.style = style
        self.radius = radius
        self.size_hint_x = None

        # self.adaptive_width=True
        # self.size_hint_min_x = 0.3
        # self.wide = dp(120)
        self.width = dp(120)

        if icon:
            self.add_widget(MDButtonIcon(icon))
        self.add_widget(MDButtonText(text=text))
        self.bind(on_release=func)

        # center text on button - has to be after `add_widget(MDButtonText(..>))`
        self._button_text.pos_hint = {"center_x": 0.5, "center_y": 0.5}

        # center button 
        # self.pos_hint = {"center_x": 0.5, "center_y": 0.5}  # center Y

        # Clock.schedule_once(self.update_later, 0.2)

    # def update_later(self, args):
    #    self.width = dp(120)

    def adjust_width(self, args):
        pass

如果在Button和 TextField中使用 pos_hint = {"cente_y": 0.5},那么这两个控件看起来会在同一行。

enter image description here

#from kivy.clock import Clock


class TxtField(MDTextField):
    def __init__(self, ilen=7, htxt="", shintx=0.7, **kwargs):
        super().__init__(**kwargs)
        self.mode = "outlined"  # "filled"
        self.size_hint_x = 0.5

        # center textfield in layout (Box)
        self.pos_hint = {"center_x": 0.5, "center_y": 0.5}  # center Y

        self.input_filter = lambda s, u: (
            s if (len(self.text) < ilen and s.isdigit()) else ""
        )

        if not htxt:
            htxt = "input:"

        self.add_widget(MDTextFieldHintText(text=htxt))


# if you put this before class mkbtn then you can use `func=dummy_func`
def dummy_func(x="", y=""):
    pass


class mkbtn(MDButton):
    def __init__(
        self,
        text,
        style="elevated",  #
        radius=[20, 20, 20, 20],
        icon=None,
        # func=lambda x: dummy_func(x),
        func=dummy_func,
        **kwargs
    ):

        super().__init__(**kwargs)
        self.style = style
        self.radius = radius
        self.size_hint_x = None

        # self.adaptive_width=True
        # self.size_hint_min_x = 0.3
        # self.wide = dp(120)
        self.width = dp(120)

        if icon:
            self.add_widget(MDButtonIcon(icon))
        self.add_widget(MDButtonText(text=text))
        self.bind(on_release=func)

        # center text on button - has to be after `add_widget(MDButtonText(..>))`
        self._button_text.pos_hint = {"center_x": 0.5, "center_y": 0.5}

        # center button in layout (Box)
        self.pos_hint = {"center_x": 0.5, "center_y": 0.5}  # center Y

        # Clock.schedule_once(self.update_later, 0.2)

    # def update_later(self, args):
    #    self.width = dp(120)

    def adjust_width(self, args):
        pass

如果在 MDBoxLayout 中添加背景颜色,你就能看到它是如何居中的

        self.hlay5 = MDBoxLayout(
            orientation="horizontal",
            padding=[5, 5, 5, 5],
            spacing=dp(10),
            md_bg_color=[1, 0, 0, 1],
        )

居中前:

enter image description here

居中后:

enter image description here


要降低行高,我必须在每个 hlay* 中使用 size_hint_y=None,并绑定一个会计算每个 hlay* 的最小高度的函数。

self.hlay4.bind(minimum_height=self.hlay4.setter("height"))

enter image description here

class MainScreen(MDScreen):
    def __init__(self, **kwargs):
        super().__init__(**kwargs)

        self.md_bg_color = [1, 1, 1, 1]
        # md_bg_color: self.theme_cls.secondaryContainerColor

        self.main_layout = MDBoxLayout(orientation="vertical")

        self.app_bar = MDTopAppBar(type="small")
        self.abtitle = MDTopAppBarTitle(
            text="WOS Squad Ratio (Bear/CJ)", halign="center"
        )
        self.app_bar.add_widget(self.abtitle)

        self.content_layout = MDBoxLayout(
            orientation="vertical",
            padding=[5, 5, 5, 5],
            spacing=dp(10),
        )

        self.hlay1 = MDBoxLayout(
            orientation="horizontal",
            padding=[5, 5, 5, 5],
            spacing=dp(10),
            size_hint_y=None,
        )
        self.bdctf = TxtField(htxt="Bear DC", ilen=6)
        self.prof_btn = mkbtn("Profiles", icon="triangle-small-down")
        self.hlay1.add_widget(self.bdctf)
        self.hlay1.add_widget(self.prof_btn)
        self.hlay1.bind(minimum_height=self.hlay1.setter("height"))

        ###
        self.hlay2 = MDBoxLayout(
            orientation="horizontal",
            padding=[5, 5, 5, 5],
            spacing=dp(10),
            size_hint_y=None,
        )
        self.cdctf = TxtField(htxt="CJ DC", ilen=6)
        self.aprof_btn = mkbtn("Add Profiles")
        self.hlay2.add_widget(self.cdctf)
        self.hlay2.add_widget(self.aprof_btn)
        self.hlay2.bind(minimum_height=self.hlay2.setter("height"))

        ###
        self.hlay3 = MDBoxLayout(
            orientation="horizontal",
            padding=[5, 5, 5, 5],
            spacing=dp(10),
            size_hint_y=None,
        )
        self.titf = TxtField(htxt="Total Infantry", ilen=7)
        self.cb_btn = mkbtn("Calc Bear")
        self.hlay3.add_widget(self.titf)
        self.hlay3.add_widget(self.cb_btn)
        self.hlay3.bind(minimum_height=self.hlay3.setter("height"))

        ###
        self.hlay4 = MDBoxLayout(
            orientation="horizontal",
            padding=[5, 5, 5, 5],
            spacing=dp(10),
            size_hint_y=None,
        )
        self.tltf = TxtField(htxt="Total Lancer", ilen=7)
        self.cc_btn = mkbtn("Calc CJ")
        self.hlay4.add_widget(self.tltf)
        self.hlay4.add_widget(self.cc_btn)
        self.hlay4.bind(minimum_height=self.hlay4.setter("height"))

        ###
        self.hlay5 = MDBoxLayout(
            orientation="horizontal",
            padding=[5, 5, 5, 5],
            spacing=dp(10),
            md_bg_color=[1, 0, 0, 1],
            size_hint_y=None,
        )
        self.tmtf = TxtField(htxt="Total Lancer", ilen=7)
        self.sd_btn = mkbtn("Save Data")
        self.hlay5.add_widget(self.tmtf)
        self.hlay5.add_widget(self.sd_btn)
        self.hlay5.bind(minimum_height=self.hlay5.setter("height"))

        ###
        self.tlbl = MDLabel(
            halign="left",
            role="large",
            padding=[5, 0, 0, 500],
            text="",
        )

        def cng_lbl(txt):
            self.tlbl.text = txt

        ###
        self.content_layout.add_widget(self.hlay1)
        self.content_layout.add_widget(self.hlay2)
        self.content_layout.add_widget(self.hlay3)
        self.content_layout.add_widget(self.hlay4)
        self.content_layout.add_widget(self.hlay5)
        self.content_layout.add_widget(
            MDWidget(
                # size_hint_y=1,  # doesn't need this
                md_bg_color=[0, 1, 0, 1],
            )
        )

        ###
        self.main_layout.add_widget(self.app_bar)
        self.main_layout.add_widget(self.content_layout)

        ###
        self.add_widget(self.main_layout)

调整窗口大小后尺寸仍然保持。

但问题在于当窗口太小,无法容纳所有元素时会出现问题

enter image description here

备选方案

另一种可能是使用kv构建主屏幕。下面是一段应该能实现该功能的kv脚本:

kv = '''
MDScreen:
    md_bg_color: [1, 1, 1, 1]
    MDBoxLayout:
        id: main_layout
        orientation: "vertical"
        MDTopAppBar:
            id: app_bar
            type: "small"
            MDTopAppBarTitle:
                text: "WOS Squad Ratio (Bear/CJ)"
                halign: "center"
        MDBoxLayout:
            orientation: "vertical"
            padding: [5, 5, 5, 5]
            spacing: dp(10)
            MDBoxLayout:
                id: hlay1
                orientation: "horizontal"
                padding: [5, 5, 5, 5]
                spacing: dp(10)
                MDTextField:
                    id: bdctf
                    mode: "outlined"
                    size_hint_x: 0.5
                    pos_hint: {"center_x": 0.5}
                    MDTextFieldHintText:
                        text: "Bear DC"
                AnchorLayout:
                    size_hint_x: 0.5
                    MDButton:
                        id: prof_btn
                        style: "elevated"
                        radius: [20, 20, 20, 20]
                        on_release: print("prof_btn release")
                        MDButtonText:
                            text: "Profiles"
                            pos_hint: {"center_x": 0.5}
                        MDButtonIcon:
                            icon: "triangle-small-down"
            MDBoxLayout:
                id: hlay2
                orientation: "horizontal"
                padding: [5, 5, 5, 5]
                spacing: dp(10)
                MDTextField:
                    id: cdctf
                    mode: "outlined"
                    size_hint_x: 0.5
                    pos_hint: {"center_x": 0.5}
                    MDTextFieldHintText:
                        text: "CJ DC"
                AnchorLayout:
                    size_hint_x: 0.5
                    MDButton:
                        id: aprof_btn
                        style: "elevated"
                        radius: [20, 20, 20, 20]
                        on_release: print("aprof_btn release")
                        MDButtonText:
                            text: "Add Profiles"
                            pos_hint: {"center_x": 0.5}
            MDBoxLayout:
                id: hlay3
                orientation: "horizontal"
                padding: [5, 5, 5, 5]
                spacing: dp(10)
                MDTextField:
                    id: titf
                    mode: "outlined"
                    size_hint_x: 0.5
                    pos_hint: {"center_x": 0.5}
                    MDTextFieldHintText:
                        text: "Total Infantry"
                AnchorLayout:
                    size_hint_x: 0.5
                    MDButton:
                        id: cb_btn
                        style: "elevated"
                        radius: [20, 20, 20, 20]
                        on_release: print("cb_btn release")
                        MDButtonText:
                            text: "Calc Bear"
                            pos_hint: {"center_x": 0.5}
            MDBoxLayout:
                id: hlay4
                orientation: "horizontal"
                padding: [5, 5, 5, 5]
                spacing: dp(10)
                MDTextField:
                    id: tltf
                    mode: "outlined"
                    size_hint_x: 0.5
                    pos_hint: {"center_x": 0.5}
                    MDTextFieldHintText:
                        text: "Total Lancer"
                AnchorLayout:
                    size_hint_x: 0.5
                    MDButton:
                        id: cc_btn
                        style: "elevated"
                        radius: [20, 20, 20, 20]
                        on_release: print("cc_btn release")
                        MDButtonText:
                            text: "Calc CJ"
                            pos_hint: {"center_x": 0.5}
            MDBoxLayout:
                id: hlay5
                orientation: "horizontal"
                padding: [5, 5, 5, 5]
                spacing: dp(10)
                MDTextField:
                    id: tmtf
                    mode: "outlined"
                    size_hint_x: 0.5
                    pos_hint: {"center_x": 0.5}
                    MDTextFieldHintText:
                        text: "Total Lancer"
                AnchorLayout:
                    size_hint_x: 0.5
                    MDButton:
                        id: sd_btn
                        style: "elevated"
                        radius: [20, 20, 20, 20]
                        on_release: print("sd_btn release")
                        MDButtonText:
                            text: "Save Data"
                            pos_hint: {"center_x": 0.5}

'''

以及在 build() 方法中:

class MyApp(MDApp):
    def build(self):
        root = Builder.load_string(kv)
        return root

请注意,MDButton 组件现在包含在具有 size_hint_x: 0.5AnchorLayout 容器中。你可以按任意方式调整那些 size_hint_x 属性。

站内所有文章版权归属LeftHeroAI导航站,无授权禁止任何主体转载、抄袭、复制内容,亦不得私自架设镜像站点。一经侵权,本站将通过法律途径追责。

相关文章