0
# Layout and Animation
1
2
Advanced layout managers, smooth scrolling, animated containers, and transition effects for fluid user experiences. These components provide modern animation patterns and flexible layout options.
3
4
## Capabilities
5
6
### Advanced Layout Managers
7
8
Enhanced layout managers that provide flexible positioning and automatic resizing with fluent design principles.
9
10
```python { .api }
11
class FlowLayout(QLayout):
12
def __init__(self, parent=None, needAni: bool = False, isTight: bool = False): ...
13
def addItem(self, item: QLayoutItem): ...
14
def addWidget(self, widget: QWidget): ...
15
def setAnimation(self, needAni: bool): ...
16
def setTightMode(self, isTight: bool): ...
17
def setSpacing(self, spacing: int): ...
18
def setContentsMargins(self, left: int, top: int, right: int, bottom: int): ...
19
20
class ExpandLayout(QVBoxLayout):
21
def __init__(self, parent=None): ...
22
def addWidget(self, widget: QWidget): ...
23
def setExpandingWidget(self, widget: QWidget): ...
24
25
class VBoxLayout(QVBoxLayout):
26
def __init__(self, parent=None): ...
27
def addWidget(self, widget: QWidget, stretch: int = 0, alignment: Qt.Alignment = Qt.Alignment()): ...
28
```
29
30
**Usage Example:**
31
```python
32
from qfluentwidgets import FlowLayout, ExpandLayout, VBoxLayout
33
34
# Flow layout with animation
35
flow_layout = FlowLayout(self, needAni=True, isTight=False)
36
flow_layout.setSpacing(10)
37
38
# Add widgets that flow horizontally then wrap
39
for i in range(10):
40
button = PushButton(f"Button {i+1}", self)
41
flow_layout.addWidget(button)
42
43
container = QWidget()
44
container.setLayout(flow_layout)
45
46
# Expanding layout for resizable content
47
expand_layout = ExpandLayout(self)
48
49
header = TitleLabel("Header Section")
50
content = QTextEdit()
51
footer = BodyLabel("Footer Section")
52
53
expand_layout.addWidget(header)
54
expand_layout.addWidget(content) # This will expand
55
expand_layout.addWidget(footer)
56
57
expand_layout.setExpandingWidget(content)
58
59
# Enhanced VBox layout
60
vbox = VBoxLayout(self)
61
vbox.addWidget(TitleLabel("Title"), 0, Qt.AlignCenter)
62
vbox.addWidget(content_widget, 1) # Stretch factor 1
63
vbox.addWidget(button_widget, 0, Qt.AlignRight)
64
```
65
66
### Scroll Areas
67
68
Enhanced scrolling components with smooth animations and fluent design integration.
69
70
```python { .api }
71
class ScrollArea(QScrollArea):
72
def __init__(self, parent=None): ...
73
def setScrollAnimation(self, enabled: bool): ...
74
def setBorderVisible(self, visible: bool): ...
75
def setViewportMargins(self, left: int, top: int, right: int, bottom: int): ...
76
77
class SmoothScrollArea(ScrollArea):
78
def __init__(self, parent=None): ...
79
def setSmoothMode(self, mode: SmoothMode): ...
80
def smoothMode(self) -> SmoothMode: ...
81
def setScrollSpeed(self, speed: float): ...
82
83
class SingleDirectionScrollArea(ScrollArea):
84
def __init__(self, orient: Qt.Orientation = Qt.Vertical, parent=None): ...
85
def setOrientation(self, orient: Qt.Orientation): ...
86
87
class SmoothScrollBar(QScrollBar):
88
def __init__(self, parent=None): ...
89
def setSmoothMode(self, mode: SmoothMode): ...
90
def setScrollSpeed(self, speed: float): ...
91
```
92
93
**Usage Example:**
94
```python
95
from qfluentwidgets import SmoothScrollArea, SingleDirectionScrollArea, SmoothMode
96
97
# Smooth scrolling area
98
scroll_area = SmoothScrollArea(self)
99
scroll_area.setSmoothMode(SmoothMode.COSINE)
100
scroll_area.setScrollSpeed(1.2)
101
scroll_area.setBorderVisible(False)
102
103
# Create scrollable content
104
content_widget = QWidget()
105
content_layout = QVBoxLayout(content_widget)
106
107
for i in range(50):
108
item = CardWidget()
109
item.setFixedHeight(80)
110
content_layout.addWidget(item)
111
112
scroll_area.setWidget(content_widget)
113
scroll_area.setWidgetResizable(True)
114
115
# Single direction scrolling
116
horizontal_scroll = SingleDirectionScrollArea(Qt.Horizontal, self)
117
horizontal_scroll.setFixedHeight(120)
118
119
# Horizontal content
120
h_content = QWidget()
121
h_layout = QHBoxLayout(h_content)
122
123
for i in range(20):
124
card = CardWidget()
125
card.setFixedSize(100, 80)
126
h_layout.addWidget(card)
127
128
horizontal_scroll.setWidget(h_content)
129
```
130
131
### Animated Stacked Widgets
132
133
Container widgets with smooth transitions between pages and content areas.
134
135
```python { .api }
136
class PopUpAniStackedWidget(QStackedWidget):
137
def __init__(self, parent=None): ...
138
def addWidget(self, widget: QWidget) -> int: ...
139
def setCurrentIndex(self, index: int): ...
140
def setCurrentWidget(self, widget: QWidget): ...
141
def setPopUpAni(self, isPopUp: bool): ...
142
def setAniDirection(self, direction: PopUpAniDirection): ...
143
144
class OpacityAniStackedWidget(QStackedWidget):
145
def __init__(self, parent=None): ...
146
def addWidget(self, widget: QWidget) -> int: ...
147
def setCurrentIndex(self, index: int): ...
148
def setDuration(self, duration: int): ...
149
150
class PopUpAniDirection(Enum):
151
BOTTOM_TO_TOP = 0
152
TOP_TO_BOTTOM = 1
153
LEFT_TO_RIGHT = 2
154
RIGHT_TO_LEFT = 3
155
```
156
157
**Usage Example:**
158
```python
159
from qfluentwidgets import PopUpAniStackedWidget, OpacityAniStackedWidget, PopUpAniDirection
160
161
# Pop-up animation stacked widget
162
popup_stack = PopUpAniStackedWidget(self)
163
popup_stack.setAniDirection(PopUpAniDirection.BOTTOM_TO_TOP)
164
165
# Add pages
166
home_page = QWidget()
167
settings_page = QWidget()
168
about_page = QWidget()
169
170
popup_stack.addWidget(home_page)
171
popup_stack.addWidget(settings_page)
172
popup_stack.addWidget(about_page)
173
174
# Smooth transitions between pages
175
def switch_to_settings():
176
popup_stack.setCurrentWidget(settings_page)
177
178
def switch_to_home():
179
popup_stack.setCurrentWidget(home_page)
180
181
# Opacity animation stacked widget
182
fade_stack = OpacityAniStackedWidget(self)
183
fade_stack.setDuration(300) # 300ms fade duration
184
185
# Add content with fade transitions
186
for i in range(3):
187
page = QWidget()
188
label = TitleLabel(f"Page {i+1}")
189
layout = QVBoxLayout(page)
190
layout.addWidget(label)
191
fade_stack.addWidget(page)
192
193
# Navigate with fade effects
194
fade_stack.setCurrentIndex(1)
195
```
196
197
### Progress Indicators
198
199
Visual progress indicators with smooth animations and modern styling.
200
201
```python { .api }
202
class ProgressBar(QProgressBar):
203
def __init__(self, parent=None): ...
204
def setRange(self, min: int, max: int): ...
205
def setValue(self, value: int): ...
206
def setCustomBarColor(self, color: QColor): ...
207
208
class IndeterminateProgressBar(ProgressBar):
209
def __init__(self, parent=None): ...
210
def start(self): ...
211
def stop(self): ...
212
def pause(self): ...
213
def resume(self): ...
214
def setSpeed(self, speed: float): ...
215
216
class ProgressRing(QWidget):
217
def __init__(self, parent=None): ...
218
def setRange(self, min: int, max: int): ...
219
def setValue(self, value: int): ...
220
def setStrokeWidth(self, width: int): ...
221
def setCustomRingColor(self, color: QColor): ...
222
223
class IndeterminateProgressRing(ProgressRing):
224
def __init__(self, parent=None): ...
225
def start(self): ...
226
def stop(self): ...
227
def setSpeed(self, speed: float): ...
228
```
229
230
**Usage Example:**
231
```python
232
from qfluentwidgets import ProgressBar, IndeterminateProgressBar, ProgressRing
233
from PyQt5.QtCore import QTimer
234
235
# Standard progress bar
236
progress = ProgressBar(self)
237
progress.setRange(0, 100)
238
progress.setValue(0)
239
progress.setFixedSize(300, 6)
240
241
# Simulate progress
242
timer = QTimer()
243
current_value = 0
244
245
def update_progress():
246
global current_value
247
current_value += 1
248
progress.setValue(current_value)
249
if current_value >= 100:
250
timer.stop()
251
252
timer.timeout.connect(update_progress)
253
timer.start(50) # Update every 50ms
254
255
# Indeterminate progress for unknown duration
256
loading_bar = IndeterminateProgressBar(self)
257
loading_bar.setFixedSize(200, 4)
258
loading_bar.start()
259
260
# Progress ring
261
ring = ProgressRing(self)
262
ring.setRange(0, 100)
263
ring.setValue(75)
264
ring.setStrokeWidth(6)
265
ring.setFixedSize(50, 50)
266
267
# Spinning progress ring
268
spinner = IndeterminateProgressRing(self)
269
spinner.setFixedSize(40, 40)
270
spinner.setStrokeWidth(4)
271
spinner.start()
272
```
273
274
## Animation System
275
276
### Smooth Scrolling Configuration
277
278
```python { .api }
279
class SmoothMode(Enum):
280
NO_SMOOTH = 0
281
CONSTANT = 1
282
LINEAR = 2
283
QUADRATIC = 3
284
COSINE = 4
285
286
class SmoothScroll:
287
def __init__(self, widget: QAbstractScrollArea, orient: Qt.Orientation = Qt.Vertical): ...
288
def setSmoothMode(self, mode: SmoothMode): ...
289
def setScrollSpeed(self, speed: float): ...
290
def smoothMode(self) -> SmoothMode: ...
291
```
292
293
**Usage Example:**
294
```python
295
from qfluentwidgets import SmoothScroll, SmoothMode
296
297
# Apply smooth scrolling to any scroll area
298
list_widget = QListWidget(self)
299
smooth_scroll = SmoothScroll(list_widget, Qt.Vertical)
300
smooth_scroll.setSmoothMode(SmoothMode.COSINE)
301
smooth_scroll.setScrollSpeed(1.0)
302
303
# Different smooth modes:
304
# - NO_SMOOTH: Instant scrolling
305
# - CONSTANT: Constant speed
306
# - LINEAR: Linear acceleration
307
# - QUADRATIC: Quadratic easing
308
# - COSINE: Smooth cosine curve (recommended)
309
310
# Apply to table widget
311
table = QTableWidget(self)
312
table_smooth = SmoothScroll(table)
313
table_smooth.setSmoothMode(SmoothMode.LINEAR)
314
```
315
316
### Custom Animations
317
318
```python
319
from PyQt5.QtCore import QPropertyAnimation, QEasingCurve, QParallelAnimationGroup
320
321
class AnimatedWidget(QWidget):
322
def __init__(self, parent=None):
323
super().__init__(parent)
324
self.setupAnimations()
325
326
def setupAnimations(self):
327
# Fade animation
328
self.fade_animation = QPropertyAnimation(self, b"windowOpacity")
329
self.fade_animation.setDuration(300)
330
self.fade_animation.setEasingCurve(QEasingCurve.OutCubic)
331
332
# Scale animation
333
self.scale_animation = QPropertyAnimation(self, b"geometry")
334
self.scale_animation.setDuration(250)
335
self.scale_animation.setEasingCurve(QEasingCurve.OutBack)
336
337
# Parallel animation group
338
self.animation_group = QParallelAnimationGroup()
339
self.animation_group.addAnimation(self.fade_animation)
340
self.animation_group.addAnimation(self.scale_animation)
341
342
def show_animated(self):
343
# Fade in
344
self.fade_animation.setStartValue(0.0)
345
self.fade_animation.setEndValue(1.0)
346
347
# Scale from smaller
348
start_rect = self.geometry()
349
start_rect.setSize(start_rect.size() * 0.8)
350
end_rect = self.geometry()
351
352
self.scale_animation.setStartValue(start_rect)
353
self.scale_animation.setEndValue(end_rect)
354
355
self.show()
356
self.animation_group.start()
357
```
358
359
## Layout Utilities
360
361
### Responsive Layout Helpers
362
363
```python
364
class ResponsiveLayout:
365
def __init__(self, widget: QWidget):
366
self.widget = widget
367
self.breakpoints = {
368
'mobile': 480,
369
'tablet': 768,
370
'desktop': 1024,
371
'large': 1440
372
}
373
374
def get_current_breakpoint(self):
375
width = self.widget.width()
376
if width < self.breakpoints['mobile']:
377
return 'mobile'
378
elif width < self.breakpoints['tablet']:
379
return 'tablet'
380
elif width < self.breakpoints['desktop']:
381
return 'desktop'
382
else:
383
return 'large'
384
385
def apply_responsive_layout(self):
386
breakpoint = self.get_current_breakpoint()
387
388
if breakpoint == 'mobile':
389
self.apply_mobile_layout()
390
elif breakpoint == 'tablet':
391
self.apply_tablet_layout()
392
else:
393
self.apply_desktop_layout()
394
395
# Usage
396
responsive = ResponsiveLayout(self)
397
self.resizeEvent = lambda event: responsive.apply_responsive_layout()
398
```
399
400
### Dynamic Layout Management
401
402
```python
403
class DynamicLayoutManager:
404
def __init__(self, container: QWidget):
405
self.container = container
406
self.widgets = []
407
self.current_layout = None
408
409
def add_widget(self, widget: QWidget):
410
self.widgets.append(widget)
411
self.rebuild_layout()
412
413
def remove_widget(self, widget: QWidget):
414
if widget in self.widgets:
415
self.widgets.remove(widget)
416
widget.setParent(None)
417
self.rebuild_layout()
418
419
def rebuild_layout(self):
420
# Clean up old layout
421
if self.current_layout:
422
while self.current_layout.count():
423
item = self.current_layout.takeAt(0)
424
if item.widget():
425
item.widget().setParent(None)
426
427
# Create new layout based on widget count
428
if len(self.widgets) <= 3:
429
self.current_layout = QHBoxLayout()
430
else:
431
self.current_layout = FlowLayout()
432
433
# Add widgets to new layout
434
for widget in self.widgets:
435
self.current_layout.addWidget(widget)
436
437
self.container.setLayout(self.current_layout)
438
```
439
440
## Performance Optimization
441
442
### Layout Performance Tips
443
444
```python
445
# 1. Use setUpdatesEnabled to batch layout changes
446
widget.setUpdatesEnabled(False)
447
for i in range(100):
448
layout.addWidget(create_widget())
449
widget.setUpdatesEnabled(True)
450
451
# 2. Pre-calculate sizes when possible
452
def optimize_flow_layout():
453
flow_layout.setTightMode(True) # Reduce spacing calculations
454
455
# Set fixed sizes when content is known
456
for widget in widgets:
457
if widget.hasFixedSize():
458
widget.setFixedSize(widget.sizeHint())
459
460
# 3. Use appropriate smooth modes
461
if is_low_end_device():
462
smooth_scroll.setSmoothMode(SmoothMode.LINEAR) # Faster
463
else:
464
smooth_scroll.setSmoothMode(SmoothMode.COSINE) # Smoother
465
```
466
467
### Animation Performance
468
469
```python
470
# Optimize animations for performance
471
class OptimizedAnimation:
472
def __init__(self, target: QWidget):
473
self.target = target
474
self.animation = QPropertyAnimation(target, b"geometry")
475
476
# Performance optimizations
477
self.animation.setDuration(200) # Shorter duration
478
self.animation.setEasingCurve(QEasingCurve.OutCubic) # Efficient curve
479
480
# Reduce updates during animation
481
self.animation.started.connect(lambda: target.setUpdatesEnabled(True))
482
self.animation.finished.connect(lambda: target.setUpdatesEnabled(True))
483
484
def animate_to_position(self, end_rect: QRect):
485
self.animation.setStartValue(self.target.geometry())
486
self.animation.setEndValue(end_rect)
487
self.animation.start()
488
```
489
490
## Best Practices
491
492
### Layout Guidelines
493
494
1. **Use FlowLayout** for responsive, wrapping content
495
2. **Use SmoothScrollArea** for better user experience
496
3. **Limit animation complexity** on lower-end devices
497
4. **Batch layout updates** when adding multiple widgets
498
5. **Test with different window sizes** for responsive behavior
499
500
### Animation Guidelines
501
502
1. **Keep animations under 300ms** for responsiveness
503
2. **Use appropriate easing curves** (OutCubic, OutQuart recommended)
504
3. **Avoid simultaneous complex animations**
505
4. **Provide animation disable options** for accessibility
506
5. **Test on various hardware** for performance