Kivy月度费用追踪APP:ScrollView内GridLayout的FloatLayout对齐问题
Hey there! Let's work through your Kivy layout problems step by step, and replace those wonky pos_hint hacks with clean, maintainable layouts.
1. Why those weird y values "worked" (and why they're a bad idea)
The odd y values (0 for the image, 1.15 for the category label, 1.35 for the date) are a symptom of misconfigured layout sizes:
- When you set
FL1 = FloatLayout(size=self.size),selfrefers toMyGrid—which hasn't finished laying out yet, so its size is Kivy's default (not the final 260x480 you set in Config). - Later,
FL1gets placed into aGridLayoutcell with a fixedrow_default_height=40, but its internal size is still stuck on the initialMyGridsize. - FloatLayout's
pos_hintuses the parent's coordinate system:y=0sticks to the bottom,y=1sticks to the top. When your parent's size is wrong, settingy=1.15shoves the label above the parent's actual top—just enough to visually align with the image, but this is a fragile hack that'll break if you change any layout dimensions.
2. Why size_hint messes up positioning, and better GridLayout alternatives
size_hint defines a widget's size as a percentage of its parent's size, so changing it directly alters how much space the widget takes up—and throws off any manual pos_hint values you set.
Your current nested FloatLayout mess is totally unnecessary! Kivy's built-in layouts (GridLayout, BoxLayout, AnchorLayout) are designed to handle this kind of structured layout automatically. Here's a better approach:
- For each expense row, use a 2-column GridLayout (left for icon/info, right for amount).
- On the left, use a horizontal BoxLayout to hold the image and a vertical BoxLayout for the category/date labels.
- On the right, use an AnchorLayout to pin the amount label to the top-right.
This way, Kivy handles all sizing and positioning—no more manual pos_hint guesswork.
3. Why "BOOOOOOOOOOOKS" wraps when there's space
You set text_size=self.size on the category label, where self is MyGrid (full 260px width). But the label is actually inside a GridLayout cell that's only half the ScrollView width (since GL has cols=2). The label thinks it has 260px of space, but in reality it's confined to a narrower cell—so the text wraps once it hits the cell's edge, even though there's empty space in the full window.
The fix? Set text_size to match the label's actual available width, or let Kivy handle it with text_size=(None, None) and size_hint_x=1.
Optimized Code Example
Here's your refactored code that fixes all three issues, with clean, maintainable layouts:
from kivy.app import App from kivy.uix.gridlayout import GridLayout from kivy.uix.boxlayout import BoxLayout from kivy.uix.scrollview import ScrollView from kivy.uix.label import Label from kivy.uix.image import Image from kivy.uix.button import Button from kivy.config import Config Config.set('graphics', 'width', '260') Config.set('graphics', 'height', '480') class MyGrid(GridLayout): def __init__(self, **kwargs): super(MyGrid, self).__init__(**kwargs) self.cols = 1 # ScrollView and inner GridLayout for expenses SV = ScrollView() # Main expense grid: 2 columns, dynamic height expense_grid = GridLayout( cols=2, size_hint_y=None, height=self.minimum_height, spacing=(20, 20), row_default_height=40, row_force_default=True ) expense_grid.bind(minimum_height=expense_grid.setter('height')) name = ['PET', 'GROCERIES', 'CLOTHES', 'BOOOOOOOOOOOKS', 'COSMETICS', 'TOILETRIES', 'GAMES'] numa = ['$ 526.00', '$ 33.00', '$ 1008.28', '$ 10.85', '$ 25.20', '$ 100.00', '$ 66.85'] for category, amount in zip(name[:6], numa[:6]): # Left side: Icon + category/date left_layout = BoxLayout(orientation='horizontal', spacing=10) # Icon (fixed size to avoid stretching) left_layout.add_widget(Image(source="pet.png", size_hint=(None, 1), width=30)) # Category and date stacked vertically text_layout = BoxLayout(orientation='vertical', spacing=2) text_layout.add_widget(Label( text=category, size_hint_y=0.7, halign='left', valign='middle', text_size=(None, None) )) text_layout.add_widget(Label( text='Jan 20', size_hint_y=0.3, halign='left', font_size=10, text_size=(None, None) )) left_layout.add_widget(text_layout) # Right side: Amount aligned to top-right right_layout = BoxLayout() right_layout.add_widget(Label( text=amount, halign='right', valign='top', size_hint=(1, 1), text_size=(None, None) )) # Add to main expense grid expense_grid.add_widget(left_layout) expense_grid.add_widget(right_layout) SV.add_widget(expense_grid) self.add_widget(Button(text='Arbitrary Button 1', background_color=(.7, .5, .9, 1))) self.add_widget(SV) self.add_widget(Button(text='Arbitrary Button 2', background_color=(.7, .5, .9, 1))) class MainApp(App): def build(self): return MyGrid() MainApp().run()
Key improvements:
- No more FloatLayout hacks—uses BoxLayout and GridLayout to handle automatic positioning
- Fixed text wrapping by removing the incorrect
text_size=self.size - Icon uses fixed width (
size_hint=(None,1)) to maintain aspect ratio - All sizing is relative to parent containers, so layout stays consistent if you adjust window size
内容的提问来源于stack exchange,提问作者MACNEI61

