Gestaffelte Enter-Animation für Songlisten (EinblendItem)
Items in song_list.dart und artist_list.dart faden/rutschen beim ersten Erscheinen gestaffelt statt abrupt ein — angelehnt an HyperOS' fließende Motion-Sprache. Läuft einmal pro Element, respektiert Reduce-Motion und deckelt die Verzögerung ab Index 12.
This commit is contained in:
@@ -0,0 +1,76 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:melo/shared/einblend_item.dart';
|
||||
|
||||
void main() {
|
||||
Widget wrap(Widget child, {bool ruhig = false}) => MaterialApp(
|
||||
home: MediaQuery(
|
||||
data: MediaQueryData(disableAnimations: ruhig),
|
||||
child: Scaffold(body: child),
|
||||
),
|
||||
);
|
||||
|
||||
double opacityVon(WidgetTester tester, Finder einblendItem) => tester
|
||||
.widget<Opacity>(
|
||||
find.descendant(of: einblendItem, matching: find.byType(Opacity)))
|
||||
.opacity;
|
||||
|
||||
double versatzYVon(WidgetTester tester, Finder einblendItem) => tester
|
||||
.widget<Transform>(
|
||||
find.descendant(of: einblendItem, matching: find.byType(Transform)))
|
||||
.transform
|
||||
.getTranslation()
|
||||
.y;
|
||||
|
||||
testWidgets('Opacity startet bei 0 und erreicht 1,0 nach dem Einblenden',
|
||||
(tester) async {
|
||||
await tester.pumpWidget(wrap(
|
||||
const EinblendItem(index: 0, child: Text('Titel')),
|
||||
));
|
||||
final item = find.byType(EinblendItem);
|
||||
|
||||
expect(opacityVon(tester, item), 0);
|
||||
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(opacityVon(tester, item), 1);
|
||||
expect(versatzYVon(tester, item), 0);
|
||||
});
|
||||
|
||||
testWidgets(
|
||||
'bei abgeschalteten Systemanimationen erscheint das Element sofort',
|
||||
(tester) async {
|
||||
await tester.pumpWidget(wrap(
|
||||
const EinblendItem(index: 3, child: Text('Titel')),
|
||||
ruhig: true,
|
||||
));
|
||||
final item = find.byType(EinblendItem);
|
||||
|
||||
// Bewusst kein weiterer pump() davor: die Reduce-Motion-Einstellung
|
||||
// darf nicht erst nach einer Zeitverzögerung greifen.
|
||||
expect(opacityVon(tester, item), 1);
|
||||
expect(versatzYVon(tester, item), 0);
|
||||
});
|
||||
|
||||
testWidgets('der Deckel bremst die Verzögerung ab Index 12',
|
||||
(tester) async {
|
||||
await tester.pumpWidget(wrap(
|
||||
const Column(
|
||||
children: [
|
||||
EinblendItem(index: 12, child: Text('Zwölf')),
|
||||
EinblendItem(index: 50, child: Text('Fünfzig')),
|
||||
],
|
||||
),
|
||||
));
|
||||
|
||||
// Deckel: min(index, 12) * 25ms → beide starten spätestens nach 300ms.
|
||||
// Ohne Deckel bräuchte Index 50 1250ms Verzögerung und wäre hier noch
|
||||
// bei Opacity 0.
|
||||
await tester.pump(const Duration(milliseconds: 300));
|
||||
await tester.pump(const Duration(milliseconds: 220));
|
||||
|
||||
for (final item in find.byType(EinblendItem).evaluate()) {
|
||||
expect(opacityVon(tester, find.byWidget(item.widget)), 1);
|
||||
}
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user