When running the app on android, the first time scrolling down for about 1000 widgets in RecycleView, I can see the height changes real time (btw i make the height of parent widgets to adjust dynamically) because the text input by user are sometimes too long. It makes the app laggy while adjusting it’s height specially on low end mobile devices.
KV FILE:
<CustomCard>:
spacing: dp(10)
padding: dp(10)
# Add a canvas for background color
canvas.before:
Color:
rgba: (230/255, 227/255, 235/255, 1) # Set your desired background color here
RoundedRectangle:
size: self.size
pos: self.pos
radius: [15]
MDFloatLayout:
size_hint_x: .01
size_hint_y: 1
pos_hint: {"center_x": .02, "center_y": .5}
canvas.before:
Color:
rgba: (2/255, 120/255, 3/255, 1) if root.sent_at else (255/255, 242/255, 0, 1) if root.latitude and root.longitude else (0.925, 0.122, 0.149, 1)
RoundedRectangle:
pos: self.pos
size: self.size
radius: [5, 5, 5, 5]
MyBoxLayout:
id: boxer
spacing: dp(2)
size_hint_y: None
height: self.minimum_height
pos_hint: {"center_x": 0.5, "center_y": 0.5}
orientation: "vertical"
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
on_size:
self.texture_update()
self.height = self.texture_size[1]
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
on_size:
self.texture_update()
self.height = self.texture_size[1] if root.remarks else 0
Label:
text: root.remarks if root.remarks else "<None>"
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
on_size:
self.texture_update()
self.height = self.texture_size[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) transmitted" 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: "13sp"
color: "#666666"
size_hint_y: None
height: self.texture_size[1]
text_size: self.width, None
markup: True
on_size:
self.texture_update()
self.height = self.texture_size[1]
Label:
text: root.event_type
pos_hint: {"center_x": 0.5, "center_y": 0.8}
font_name: "f2icons/status.ttf"
color: "#FFF200"
font_size: "19sp"
size_hint_x: None
size: self.texture_size
MDScreen:
on_enter: app.apply_colors_based_on_mode()
name: 'main'
md_bg_color: "#FFF200"
MDNavigationLayout:
MDScreenManager:
MDScreen:
# RecycleView section
BoxLayout:
orientation: 'vertical'
padding: "10dp"
RecycleView:
id: card_list
viewclass: "CustomCard"
do_scroll_x: False
effect_cls: "ScrollEffect"
RecycleBoxLayout:
orientation: 'vertical'
spacing: dp(15)
size_hint_y: None
size: self.minimum_size
default_size_hint: 1, None
# magic value for the default height of the message
default_size: 0, 38
# Bottom navigation bar
MDBottomAppBar:
id: bottom_nav
theme_bg_color: "Custom"
#allow_hidden: True
#scroll_cls: card_list
action_items:[]
MDFabBottomAppBarButton:
id: fab
icon: "plus"
theme_bg_color: "Custom"
md_bg_color: "#FFF200"
theme_text_color: "Custom"
text_color: "black"
on_release: app.new_event()
PY FILE:
class CustomCard(BoxLayout, RecycleDataViewBehavior):
# Your properties and methods
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='') # Add this line
sent_picture = NumericProperty(0)
index = 0
def update_card_height(self, *args):
total_height = 0
for child in self.children:
if child.size_hint_y is None:
total_height += child.height
self.height = total_height + dp(50)
# Force the RecycleView to refresh the view
if self.parent and hasattr(self.parent, 'refresh_from_data'):
self.parent.refresh_from_data()
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 = self.remarks = data.get('created_at', '')
self.sent_at = data.get('sent_at', '') or ''
self.sent_picture = int(data.get('event_id', 0))
super(CustomCard, self).refresh_view_attrs(rv, index, data)
Clock.schedule_once(self.update_card_height)
def on_touch_up(self, touch):
if self.collide_point(*touch.pos):
# Only call on_release if the touch was actually within the card
if touch.is_mouse_scrolling:
return False # Ignore scroll events
app = MDApp.get_running_app()
print(f"Event ID on release: {self.event_id}")
app.display_event_details(self.event_id)
return True
return super().on_touch_up(touch)
class MainFunction(MDApp):
def add_main_widgets(self):
def fetch_data_thread():
events_data = self.fetch_data_from_db1()
self.update_ui_with_data(events_data)
self.cleanup_expired_events()
Thread(target=fetch_data_thread).start()
@mainthread
def update_ui_with_data(self, events_data):
card_list = self.sm.get_screen('main').ids.card_list
current_data = {card['event_id']: card for card in card_list.data}
self.original_data = [] # Store original data here
for event in events_data:
event_id = event.id
# Prepare the data for each card
card_data = {
'event_id': event_id,
'widget_count': 0,
'total_count': 0,
'event_type': event.event_type,
'reference_number': event.reference_number,
'remarks': event.remarks,
'image_count': event.image_count,
'latitude': event.latitude,
'longitude': event.longitude,
'created_at': event.created_at,
'sent_at': event.sent_at or '',
'sent_picture': event.sent_picture
}
# Add or update card data
if event_id in self.cards_dict:
self.cards_dict[event_id].update(card_data)
else:
self.cards_dict[event_id] = card_data
card_list.data.insert(0, card_data) # Add to card_list data
# Store the original data as the initial state
self.original_data = list(card_list.data)
# Calculate total count and update each card
total_count = len(card_list.data)
for index, card_data in enumerate(card_list.data):
card_data['widget_count'] = index + 1
card_data['total_count'] = total_count
I was doing some research and here’s what i found:
“if you can pre-compute a sufficiently good approximation of the size of the item before displaying it, and put that value in the key_size key of your dict (that you can then update when the actual size is computed), then you can significantly improve the experience link: https://github.com/kivy/kivy/issues/6582”
I don’t know how to implement it or if that suggestion would work. I’ll appreciate any help you can give to me. Thanks
1