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

如何让KivyMD的MDList与ScrollView滚动至列表底部

KivyMD ScrollView 无法滚动至底部的修复方案

问题

原本MDList和ScrollView可正常滚动,在列表下方添加包含TextInput、Label的BoxLayout组件后,列表无法滚动至底部,仅能滚动到第一条不可见行就回弹。

相关代码

main.py

import itertools
from ctypes import windll, c_int64
windll.user32.SetProcessDpiAwarenessContext(c_int64(-4))    # Has to be here before kivy import. 4k fix
from kivy.core.window import Window
from kivy.metrics import sp
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.textinput import TextInput
from kivymd.app import MDApp
from kivymd.uix.list import OneLineListItem
from skimage.io import imread
import os
import struct


# C:\WPy64-31040\python-3.10.4.amd64\python.exe main.py
import kivy; print(kivy.__version__)


def clickme(list_item):
    print(list_item.text)


class MyLayout(BoxLayout):
    pass


class MyTextInput(TextInput):
    def __init__(self, **kwargs):
        super(MyTextInput, self).__init__(**kwargs)
        self.size_hint = (None, None)
        self.foreground_color = (0, 0, 0, 1)    # Text is black.
        self.background_color = (0.6, 0.6, 0.6, 0.3)    # Background is transparent and therefore not visible.
        self.cursor_color = (0, 0, 0, 0)    # Hide the cursor by making it transparent.
        self.font_size = sp(24)
        # Left, right center.
        self.halign = 'center'
        # self.padding_x = [self.center[0] - self._get_text_width(max(self._lines, key=len), self.tab_width, self._label_cached) / 2.0,
        #     0] if self.text else [self.center[0], 0]
        # Top, bottom center. Moved to kv file.
        # self.padding_y = [self.height / 2.0 - (self.line_height / 2.0) * len(self._lines), 0]    # Deprecated. Use padding instead.
        # self.padding = [self.padding[0], self.height / 2.0 - (self.line_height / 2.0) * len(self._lines), self.padding[2], 0]

    def keyboard_on_textinput(self, window, text):
        """Upper case and always one letter only."""
        self.text = text.upper()


class CrosswordApp(MDApp):
    def build(self):
        return MyLayout()

    def on_start(self):
        # List of possible crossword candidates.
        for i in range(20):
            self.root.ids.lst_matches.add_widget(
                OneLineListItem(text=f"Single-line item {i} very very very very very long string that hopefully is long enough", on_release=clickme,
                                divider="Full", font_style="Caption"))

        # Get black and white blocks.
        # tex = self.root.ids.img_crossword.texture
        # for y in range(0, tex.size[1], 5):
        #     for x in range(0, tex.size[0], 5):
        #         print(self.get_color_value(x, y, tex))

        # Read crossword image using path relative to this file's path.
        img = imread(os.path.dirname(os.path.realpath(__file__)) + r"\images\Huisgenoot20220605P.png")
        dcol = {}
        ysize = int(img.shape[0] / 23)    # Also len(img)
        xsize = int(img.shape[1] / 18)    # Also len(img[0])
        for y, x in itertools.product(range(23), range(18)):
            cnt = r = g = b = 0
            for y2, x2 in itertools.product(range(y * ysize, y * ysize + ysize - 1), range(x * xsize, x * xsize + xsize - 1)):
                cnt = cnt + 1
                r = r + img[y2][x2][0]
                g = g + img[y2][x2][1]
                b = b + img[y2][x2][2]
            dcol[(x, y)] = [int(r / cnt), int(g / cnt), int(b / cnt)]
        for row in range(23):
            for col in range(18):
                v = dcol[col, row]
                if v[0] > 230 and v[1] > 230 and v[2] > 230:
                    print(" ", end="")
                elif 167 <= v[0] <= 187 and 203 <= v[1] <= 223 and 203 <= v[2] <= 223:
                    print("B", end="")
                else:
                    print("#", end="")
            print("")

        print(dcol[0, 0])
        print(dcol[1, 0])
        print(dcol[2, 0])
        print(dcol[3, 0])
        print(dcol[6, 1])
        print(dcol[17, 0])

    def get_color_value(self, x, y, texture):
        """Get the rgba (0..255) value at x and y of an image texture."""
        pixel = texture.get_region(x, y, 1, 1)
        bp = pixel.pixels
        return struct.unpack('4B', bp)     # tuple [c for c in struct.unpack('4B', bp)]    # / 255.0


if __name__ == '__main__':
    Window.maximize()
    CrosswordApp().run()

crossword.kv(原代码)

#:kivy 2.1.0

<MyTextInput>

<MyLayout>
    BoxLayout:
        orientation: 'horizontal'
        size: root.width, root.height
        padding: 0
        spacing: 0
        Image:
            id: img_crossword
            source: 'images/Huisgenoot20220605P.png'
            size_hint: None, None
            size: img_crossword.image_ratio * root.height, root.height
            allow_stretch: True
            keep_ratio: True
        BoxLayout:
            orientation: 'vertical'
            size_hint: None, None
            size: root.width - (img_crossword.image_ratio * root.height), root.height
            padding: 0
            spacing: 0
            ScrollView:
                # size_hint: None, 1
                width: root.width - (img_crossword.image_ratio * root.height)
                do_scroll_x: False
                do_scroll_y: True
                bar_color: 0, 0, 255, 1
                bar_width: 12
                MDList:
                    id: lst_matches
                    size_hint: None, 0.8
                    width: root.width - (img_crossword.image_ratio * root.height)
                    height: self.height
                    spacing: dp(0)
                    padding: dp(0)
            BoxLayout:
                orientation: 'horizontal'
                size_hint: None, 0.2
                width: root.width - (img_crossword.image_ratio * root.height)
                padding: 0
                spacing: 0
                TextInput:
                    id: txt_clue
                    size_hint: 0.4, 0.2
                    text: 'Clue'
                Label:
                    id: lbl_clue
                    size_hint: 0.4, 0.2
                    color: 0, 0, 0, 1
                    text: 'Clue'
                TextInput:
                    id: txt_word
                    size_hint: 0.2, 0.2
                    text: 'Word'

    FloatLayout:
        MyTextInput:
            text: 'A'
            pos: img_crossword.pos[0] + img_crossword.width / 18 * 16, img_crossword.pos[1] + img_crossword.height / 23 * 5
            size: img_crossword.width / 18, img_crossword.height / 23
            padding: [self.padding[0], self.height / 2.0 - (self.line_height / 2.0) * len(self._lines), self.padding[2], 0]

修复方案

问题根源在于ScrollView和MDList的尺寸配置错误,导致ScrollView无法正确识别可滚动区域。修改crossword.kv中的以下部分:

  1. ScrollView:设置size_hint_y: 0.8,移除手动指定的width(继承父容器宽度即可),让它在垂直方向占据80%的空间。
  2. MDList:设置size_hint_y: None,并将height: self.height改为height: self.minimum_height,这样列表会根据内部项的总高度自动计算自身高度,确保ScrollView能滚动到所有内容。
  3. 底部BoxLayout:将size_hint: None, 0.2改为size_hint_y: 0.2,保持垂直方向占据20%的空间,和上方ScrollView的占比匹配。

修改后的crossword.kv相关部分代码:

<MyLayout>
    BoxLayout:
        # ... 其他代码保持不变 ...
        BoxLayout:
            orientation: 'vertical'
            size_hint: None, None
            size: root.width - (img_crossword.image_ratio * root.height), root.height
            padding: 0
            spacing: 0
            ScrollView:
                size_hint_y: 0.8
                do_scroll_x: False
                do_scroll_y: True
                bar_color: 0, 0, 255, 1
                bar_width: 12
                MDList:
                    id: lst_matches
                    size_hint_y: None
                    width: root.width - (img_crossword.image_ratio * root.height)
                    height: self.minimum_height
                    spacing: dp(0)
                    padding: dp(0)
            BoxLayout:
                orientation: 'horizontal'
                size_hint_y: 0.2
                width: root.width - (img_crossword.image_ratio * root.height)
                padding: 0
                spacing: 0
                # ... 内部TextInput、Label代码不变 ...
    # ... FloatLayout部分代码不变 ...

修改后,ScrollView会正确识别MDList的完整高度,允许滚动至底部。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.22 12:39:24