You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

KivyMD GridView布局多单元格滚动条失效问题求助

问题描述

我需要创建一个包含10个单元格的GridView布局,要求:

  • 单元格保持固定高度,宽度随窗口尺寸自适应调整
  • 单屏至少仅显示4个单元格,剩余单元格通过滚动条查看

目前遇到以下问题:

  1. 添加滚动条后无法滚动单元格
  2. 增加单元格数量时,单元格高度被自动压缩
  3. 设置单元格高度的代码无响应
现有代码
from kivy.app import App
from kivy.uix.scrollview import ScrollView
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.label import Label
from kivy.uix.image import Image
from kivy.core.window import Window
from kivy.graphics import Color, Rectangle
from kivymd.app import MDApp
from kivymd.uix.gridlayout import MDGridLayout
from kivymd.uix.button import MDRectangleFlatButton, MDFloatingActionButton
from kivymd.uix.boxlayout import MDBoxLayout
from kivymd.uix.textfield import MDTextField

class MyBoxLayout(BoxLayout):
    background_color = [0.678, 0.847, 0.902, 1]  # Light blue color
    spacing = 50

class MyGridCell(MDRectangleFlatButton):
    def __init__(self, cell_data, **kwargs):
        super(MyGridCell, self).__init__(**kwargs)
        self.text = cell_data['name']

class MyGridLayout(MDGridLayout):
    def __init__(self, **kwargs):
        super(MyGridLayout, self).__init__(**kwargs)
        self.padding = [200, 0]
        self.cols = 2
        self.spacing = 10

    def on_padding(self, instance, value):
        self.update_padding()

    def update_padding(self):
        self.padding_left = self.padding[0]
        self.padding_right = self.padding[0]

    def create_widgets(self):
        cell_data_list = [
            {'name': 'Cell 1', 'purpose': 'Purpose 1'},
            {'name': 'Cell 2', 'purpose': 'Purpose 2'},
            {'name': 'Cell 3', 'purpose': 'Purpose 1'},
            {'name': 'Cell 4', 'purpose': 'Purpose 2'},
            # Add more dictionaries for additional cells
        ]
        
        for cell_data in cell_data_list:
            button = MyGridCell(cell_data=cell_data)
            self.add_widget(button)

    def adjust_cell_sizes(self):
        for child in self.children:
            child.size_hint = (1, 1)


class MyApp(MDApp):
    def build(self):
        Window.clearcolor = (1, 1, 1, 1)
        Window.maximize()  # Maximize the window
        main_layout = MyBoxLayout(orientation='vertical')

        header_layout = MyBoxLayout(size_hint=(1, None), height=70, padding=10, spacing=800)
        logo_image = Image(source='LogoFull.png', size_hint=(None, 1), width=200)
        search_layout = MDBoxLayout(orientation='horizontal', size_hint=(None, 1))
        search_input = MDTextField(hint_text='Search..', size_hint=(None, None), width=300, height=30)
        search_icon = MDFloatingActionButton(icon='magnify')

        search_layout.add_widget(search_input)
        search_layout.add_widget(search_icon)
        header_layout.add_widget(logo_image)
        header_layout.add_widget(search_layout)

        footer_layout = MyBoxLayout(size_hint=(1, None), height=50)
        footer_label = Label(text='Footer')

        scrollview = ScrollView(do_scroll_x=False)
        layout = MyGridLayout()
        layout.create_widgets()
        scrollview.add_widget(layout)
        main_layout.add_widget(header_layout)
        main_layout.add_widget(scrollview)
        main_layout.add_widget(footer_layout)

        # Bind the pos and size properties of the layouts to update the background rectangles
        header_layout.bind(pos=self.update_header_background, size=self.update_header_background)
        footer_layout.bind(pos=self.update_footer_background, size=self.update_footer_background)

        return main_layout

    def update_header_background(self, instance, value):
        instance.canvas.before.clear()
        with instance.canvas.before:
            Color(*instance.background_color)
            Rectangle(pos=instance.pos, size=instance.size)

    def update_footer_background(self, instance, value):
        instance.canvas.before.clear()
        with instance.canvas.before:
            Color(*instance.background_color)
            Rectangle(pos=instance.pos, size=instance.size)

    def on_start(self):
        self.root.children[1].children[0].adjust_cell_sizes()


if __name__ == '__main__':
    MyApp().run()
已尝试操作
  • 添加滚动条,但滚动时单元格无响应
  • 添加更多单元格,单元格高度被缩减
  • 尝试设置单元格高度,但代码未生效
解决方案

核心问题分析

  1. 滚动失效:ScrollView的子布局(MyGridLayout)默认size_hint_y=1,高度会被限制在ScrollView的可视区域内,无法超出自然无法滚动
  2. 高度压缩:GridLayout在size_hint_y=1时,会自动拉伸/压缩子元素以填满可用空间,导致固定高度失效

修改后的完整代码

from kivy.app import App
from kivy.uix.scrollview import ScrollView
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.label import Label
from kivy.uix.image import Image
from kivy.core.window import Window
from kivy.graphics import Color, Rectangle
from kivymd.app import MDApp
from kivymd.uix.gridlayout import MDGridLayout
from kivymd.uix.button import MDRectangleFlatButton, MDFloatingActionButton
from kivymd.uix.boxlayout import MDBoxLayout
from kivymd.uix.textfield import MDTextField

class MyBoxLayout(BoxLayout):
    background_color = [0.678, 0.847, 0.902, 1]  # Light blue color
    spacing = 50

class MyGridCell(MDRectangleFlatButton):
    def __init__(self, cell_data, **kwargs):
        super(MyGridCell, self).__init__(**kwargs)
        self.text = cell_data['name']
        # 固定单元格高度,宽度自适应
        self.size_hint_y = None
        self.height = 180  # 可根据需求调整,确保单屏显示4个的话,可视高度/4即可

class MyGridLayout(MDGridLayout):
    def __init__(self, **kwargs):
        super(MyGridLayout, self).__init__(**kwargs)
        self.padding = [200, 0]
        self.cols = 2
        self.spacing = 10
        # 关闭垂直方向的size_hint,手动计算高度
        self.size_hint_y = None
        # 绑定子元素变化事件,自动更新总高度
        self.bind(children=self.update_total_height)

    def on_padding(self, instance, value):
        self.update_padding()

    def update_padding(self):
        self.padding_left = self.padding[0]
        self.padding_right = self.padding[0]

    def create_widgets(self):
        cell_data_list = [
            {'name': 'Cell 1', 'purpose': 'Purpose 1'},
            {'name': 'Cell 2', 'purpose': 'Purpose 2'},
            {'name': 'Cell 3', 'purpose': 'Purpose 1'},
            {'name': 'Cell 4', 'purpose': 'Purpose 2'},
            {'name': 'Cell 5', 'purpose': 'Purpose 1'},
            {'name': 'Cell 6', 'purpose': 'Purpose 2'},
            {'name': 'Cell 7', 'purpose': 'Purpose 1'},
            {'name': 'Cell 8', 'purpose': 'Purpose 2'},
            {'name': 'Cell 9', 'purpose': 'Purpose 1'},
            {'name': 'Cell 10', 'purpose': 'Purpose 2'},
        ]
        
        for cell_data in cell_data_list:
            button = MyGridCell(cell_data=cell_data)
            self.add_widget(button)

    def update_total_height(self, instance, value):
        # 计算GridLayout总高度:(单元格高度 + 垂直间距) * 行数 + 上下padding
        if not self.children:
            return
        cell_height = self.children[0].height
        row_count = (len(self.children) + self.cols - 1) // self.cols
        self.height = (cell_height + self.spacing[1]) * row_count + self.padding[1] + self.padding[3]

class MyApp(MDApp):
    def build(self):
        Window.clearcolor = (1, 1, 1, 1)
        Window.maximize()  # Maximize the window
        main_layout = MyBoxLayout(orientation='vertical')

        header_layout = MyBoxLayout(size_hint=(1, None), height=70, padding=10, spacing=800)
        logo_image = Image(source='LogoFull.png', size_hint=(None, 1), width=200)
        search_layout = MDBoxLayout(orientation='horizontal', size_hint=(None, 1))
        search_input = MDTextField(hint_text='Search..', size_hint=(None, None), width=300, height=30)
        search_icon = MDFloatingActionButton(icon='magnify')

        search_layout.add_widget(search_input)
        search_layout.add_widget(search_icon)
        header_layout.add_widget(logo_image)
        header_layout.add_widget(search_layout)

        footer_layout = MyBoxLayout(size_hint=(1, None), height=50)
        footer_label = Label(text='Footer')
        footer_layout.add_widget(footer_label)

        scrollview = ScrollView(do_scroll_x=False)
        layout = MyGridLayout()
        layout.create_widgets()
        scrollview.add_widget(layout)
        main_layout.add_widget(header_layout)
        main_layout.add_widget(scrollview)
        main_layout.add_widget(footer_layout)

        # Bind the pos and size properties of the layouts to update the background rectangles
        header_layout.bind(pos=self.update_header_background, size=self.update_header_background)
        footer_layout.bind(pos=self.update_footer_background, size=self.update_footer_background)

        return main_layout

    def update_header_background(self, instance, value):
        instance.canvas.before.clear()
        with instance.canvas.before:
            Color(*instance.background_color)
            Rectangle(pos=instance.pos, size=instance.size)

    def update_footer_background(self, instance, value):
        instance.canvas.before.clear()
        with instance.canvas.before:
            Color(*instance.background_color)
            Rectangle(pos=instance.pos, size=instance.size)


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

关键修改说明

  • MyGridCell:设置size_hint_y=None和固定height,确保单元格高度不被布局压缩
  • MyGridLayout:设置size_hint_y=None,并通过update_total_height方法根据单元格数量自动计算总高度,让布局高度超出ScrollView可视区域,从而触发滚动
  • 移除了无效的adjust_cell_sizes方法,避免覆盖固定高度设置
  • 在cell_data_list中添加了10个单元格,满足需求

内容的提问来源于stack exchange,提问作者nobodyelse

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.18 16:54:59