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

使用RecycleView无法滚动至底部的技术求助

Kivy RecycleView滚动回弹无法到底部问题排查与修复

问题描述

使用Kivy的RecycleView展示SQL数据库数据时,出现滚动异常:无法正常滚动至列表底部,滚动时会自动回弹至上方位置,仅能通过强制滚动才能查看末尾的第12/13和13/13项,该问题仅在切换为RecycleView后出现。

问题根源分析

从代码逻辑来看,问题集中在RecycleView的高度计算与动态更新逻辑上:

  • CustomCard中手动计算高度后调用parent.refresh_from_data(),触发RecycleView频繁刷新,导致滚动位置被强制重置
  • RecycleBoxLayout设置了default_size: 0,36,但卡片实际高度远大于该值,导致RecycleView整体高度计算错误
  • 卡片高度动态更新时未同步到RecycleView的数据中,key_size指定的cached_size字段未被正确维护

修复方案与代码调整

1. 调整CustomCard类逻辑

移除手动计算高度的冗余代码,通过绑定内部布局自动计算高度,并同步高度到RecycleView数据中:

class MyBoxLayout(BoxLayout):
    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self.orientation = "vertical"
        self.size_hint_y = None
        self.bind(minimum_height=self.setter('height'))


class CustomCard(BoxLayout, RecycleDataViewBehavior):
    fixed_height = NumericProperty(100)
    event_id = NumericProperty()
    widget_count = NumericProperty()
    total_count = NumericProperty()
    event_type = StringProperty()
    reference_number = StringProperty()
    remarks = StringProperty()
    image_count = NumericProperty(0)
    latitude = NumericProperty()
    longitude = NumericProperty()
    image_path = StringProperty(defaultvalue='')
    created_at = StringProperty()
    sent_at = StringProperty(defaultvalue='')
    sent_picture = NumericProperty(0)
    index = 0

    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self.size_hint_y = None
        # 绑定内部布局的最小高度到卡片自身高度
        self.ids.inner_box.bind(minimum_height=self.setter('height'))

    def refresh_view_attrs(self, rv, index, data):
        self.index = index
        self.event_id = int(data.get('event_id', 0))
        self.widget_count = data.get('widget_count', 0)
        self.event_type = data.get('event_type', '')
        self.reference_number = data.get('reference_number', '')
        self.remarks = data.get('remarks', '')
        self.image_count = data.get('image_count', 0)
        self.latitude = float(data.get('latitude', 0.0))
        self.longitude = float(data.get('longitude', 0.0))
        self.image_path = data.get('image_path', '')
        self.created_at = data.get('created_at', '')
        self.sent_at = data.get('sent_at', '') or ''
        self.sent_picture = int(data.get('sent_picture', 0))
        super(CustomCard, self).refresh_view_attrs(rv, index, data)
        # 延迟同步卡片高度到RecycleView数据
        Clock.schedule_once(lambda dt: self.update_cached_size(rv))

    def update_cached_size(self, rv):
        # 将卡片实际高度存入对应数据项,供RecycleView计算总高度
        if rv.data and self.index < len(rv.data):
            rv.data[self.index]['cached_size'] = (0, self.height)
            rv.refresh_from_data()

    def on_touch_up(self, touch):
        if self.collide_point(*touch.pos):
            if touch.is_mouse_scrolling:
                return False
            self.on_release()
            return True
        return super().on_touch_up(touch)

    def on_release(self):
        app = MDApp.get_running_app()
        print(f"Event ID on release: {self.event_id}")
        app.display_event_details(self.event_id)

2. 修正KV文件布局

为内部布局添加ID用于高度绑定,移除RecycleBoxLayout的错误默认高度设置:

<CustomCard>:
    spacing: dp(8)
    padding: dp(8)

    canvas.before:
        Color:
            rgba: (228/255, 225/255, 232/255, 1)
        Rectangle:
            size: self.size
            pos: self.pos

    MDFloatLayout:
        size_hint: .01, 1
        pos_hint: {"center_x": .02, "center_y": .5}
        md_bg_color: "green" if root.sent_at else ("yellow" if root.latitude and root.longitude else "#EC1F26")

    MyBoxLayout:
        id: inner_box  # 添加ID用于高度绑定
        spacing: dp(1)
        Label:
            text: f"[{root.widget_count} of {root.total_count}]: {root.reference_number}"
            font_size: "15sp"
            color:"black"
            bold: True
            size_hint_y: None
            height: self.texture_size[1]
            text_size: self.width, None

        Label:
            text: "Remarks:"
            bold: True
            font_size:"14sp"
            color:"#666666"
            size_hint_y: None
            height: self.texture_size[1] if root.remarks else 0
            text_size: self.width, None
            opacity: 1 if root.remarks else 0

        Label:
            text: root.remarks if root.remarks else "&lt;None&gt;"
            font_size:"14sp"
            color: (0, 0, 0, 1)
            size_hint_y: None
            height: self.texture_size[1] if root.remarks else 0
            text_size: self.width, None
            opacity: 1 if root.remarks else 0

        MDButton:
            style: 'text'
            height: "15dp" if root.image_count > 0 else 0
            size_hint_x: .3
            opacity: 1 if root.image_count > 0 else 0
            MDButtonIcon:
                icon: 'image'
                x: 1
                theme_icon_color: "Custom"
                icon_color: "black"
            MDButtonText:
                text: f"{root.image_count} Image(s) sent" if root.sent_picture == 1 else (f"{root.image_count} Image(s)" if root.image_count > 0 else "0 Image")
                theme_text_color: "Custom"
                text_color: "black"
                font_style: "Body"
                role: "medium"

        Label:
            text: f"Transmitted successfully: [color=#008000]{root.sent_at}[/color]" if root.sent_at else ("Status: Ready to transmit" if (root.latitude and root.longitude) else "Status: Location not found")
            bold: True
            font_size: "12sp"
            color: "#666666"
            size_hint_y: None
            height: self.texture_size[1]
            text_size: self.width, None
            markup: True

    AnchorLayout:
        anchor_x: 'right'
        anchor_y: 'top'
        size_hint: None, None
        size: 100, 50
        pos_hint: {'right': 0.9, 'top': 1}
        padding: [0, 5]
        Label:
            text: root.event_type
            font_name: "f2icons/status.ttf"
            color: "#ffff00"
            font_size: "15sp"

MDScreen:
    on_enter: app.set_bars_colors("#FFF200", "#FFFFFF", "Dark")
    name: 'main'
    md_bg_color: "#FFF200"
    MDNavigationLayout:
        MDScreenManager:
            MDScreen:
                MDBoxLayout:
                    orientation: 'vertical'
                    pos_hint: {"center_y": 0.36}
                    padding: [7, 10, 0, 0]
                    TextInput:
                        id: search_bar
                        hint_text: "Search..."
                        height: '40dp'
                        size_hint_y: None
                        pos_hint: {"center_x": .5, "center_y": .5}
                        on_text: app.filter_cards(self.text)
                        width: '376dp'
                        size_hint_x: None

                    RecycleView:
                        id: card_list
                        pos_hint: {"center_y": 0.3}
                        size_hint_y: 1
                        viewclass: "CustomCard"
                        do_scroll_x: False
                        RecycleBoxLayout:
                            orientation: 'vertical'
                            padding:dp(15)
                            spacing: dp(15)
                            size_hint_y: None
                            height: self.minimum_height
                            default_size_hint: 1, None
                            key_size: 'cached_size'

3. 关键修复点说明

  • 移除手动计算高度的update_card_height方法,通过绑定内部布局minimum_height自动计算卡片高度,避免计算误差
  • 添加update_cached_size方法,将卡片实际高度同步到RecycleView的数据项中,让RecycleView能准确计算列表总高度
  • 修正refresh_view_attrs中的笔误(原代码中self.created_at = self.remarks = ...的错误赋值)
  • 删除RecycleBoxLayout中错误的default_size: 0,36设置,避免与实际卡片高度冲突

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.19 10:37:33