fix(ui): 骨架屏扫光收窄压淡
This commit is contained in:
1 parent
a5dbae7fff
commit
bd826a9236
2 files changed
+57
-36
No files matched your search
@@ -12,7 +12,7 @@ const double _shimmerTravel = 2.4;
|
|||||||
const double _shimmerStart = -1.2;
|
const double _shimmerStart = -1.2;
|
||||||
const double _subtitleWidthFactor = 0.62;
|
const double _subtitleWidthFactor = 0.62;
|
||||||
const double _shimmerPhaseStep = 0.13;
|
const double _shimmerPhaseStep = 0.13;
|
||||||
const Color _shimmerHighlight = Color(0x12FFFFFF);
|
const Color _shimmerHighlight = Color(0x0DFFFFFF);
|
||||||
|
|
||||||
/// 首页短剧网格的占位。行数至少铺满可视高度,并带一道扫光。
|
/// 首页短剧网格的占位。行数至少铺满可视高度,并带一道扫光。
|
||||||
class DramaGridSkeleton extends StatefulWidget {
|
class DramaGridSkeleton extends StatefulWidget {
|
||||||
@@ -116,41 +116,48 @@ class _SkeletonCard extends StatelessWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final phase = (shimmerValue + index * _shimmerPhaseStep) % 1.0;
|
final phase = (shimmerValue + index * _shimmerPhaseStep) % 1.0;
|
||||||
return ShaderMask(
|
return Stack(
|
||||||
blendMode: BlendMode.srcATop,
|
key: Key('drama-grid-skeleton-$index'),
|
||||||
shaderCallback: (bounds) => _cardShader(bounds, phase),
|
children: [
|
||||||
child: Column(
|
Column(
|
||||||
key: Key('drama-grid-skeleton-$index'),
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
children: [
|
||||||
children: [
|
AspectRatio(
|
||||||
AspectRatio(
|
aspectRatio: AppDimens.cardCoverAspectRatio,
|
||||||
aspectRatio: AppDimens.cardCoverAspectRatio,
|
child: const _SkeletonBlock(),
|
||||||
child: const _SkeletonBlock(),
|
),
|
||||||
|
const SizedBox(height: AppDimens.cardCoverTitleGap),
|
||||||
|
const _SkeletonBar(),
|
||||||
|
const SizedBox(height: AppDimens.skeletonTitleBarGap),
|
||||||
|
const FractionallySizedBox(
|
||||||
|
widthFactor: _subtitleWidthFactor,
|
||||||
|
child: _SkeletonBar(),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
Positioned.fill(
|
||||||
|
child: IgnorePointer(
|
||||||
|
child: ClipRRect(
|
||||||
|
borderRadius: BorderRadius.circular(AppDimens.homeCardRadius),
|
||||||
|
child: DecoratedBox(
|
||||||
|
decoration: BoxDecoration(gradient: _cardHighlight(phase)),
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: AppDimens.cardCoverTitleGap),
|
),
|
||||||
const _SkeletonBar(),
|
],
|
||||||
const SizedBox(height: AppDimens.skeletonTitleBarGap),
|
|
||||||
const FractionallySizedBox(
|
|
||||||
widthFactor: _subtitleWidthFactor,
|
|
||||||
child: _SkeletonBar(),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Shader _cardShader(Rect bounds, double shimmerValue) {
|
LinearGradient _cardHighlight(double shimmerValue) {
|
||||||
final shift = _shimmerStart + _shimmerTravel * shimmerValue;
|
final shift = _shimmerStart + _shimmerTravel * shimmerValue;
|
||||||
return LinearGradient(
|
return LinearGradient(
|
||||||
begin: Alignment(shift, -0.3),
|
begin: Alignment(shift, -0.3),
|
||||||
end: Alignment(shift + 1, 0.3),
|
end: Alignment(shift + 1, 0.3),
|
||||||
colors: const [
|
colors: const [Colors.transparent, _shimmerHighlight, Colors.transparent],
|
||||||
AppColors.skeletonSurface,
|
stops: const [0.42, 0.5, 0.58],
|
||||||
_shimmerHighlight,
|
);
|
||||||
AppColors.skeletonSurface,
|
|
||||||
],
|
|
||||||
).createShader(bounds);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class _SkeletonBlock extends StatelessWidget {
|
class _SkeletonBlock extends StatelessWidget {
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import 'package:flutter_test/flutter_test.dart';
|
|||||||
import 'package:fruitshorts/core/widgets/drama_grid_skeleton.dart';
|
import 'package:fruitshorts/core/widgets/drama_grid_skeleton.dart';
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
testWidgets('renders one local shimmer mask per skeleton card', (
|
testWidgets('renders one narrow highlight overlay per skeleton card', (
|
||||||
tester,
|
tester,
|
||||||
) async {
|
) async {
|
||||||
tester.view.physicalSize = const Size(390, 1000);
|
tester.view.physicalSize = const Size(390, 1000);
|
||||||
@@ -28,20 +28,34 @@ void main() {
|
|||||||
await tester.pump(const Duration(milliseconds: 100));
|
await tester.pump(const Duration(milliseconds: 100));
|
||||||
|
|
||||||
expect(find.byType(GridView), findsOneWidget);
|
expect(find.byType(GridView), findsOneWidget);
|
||||||
expect(
|
expect(find.byType(ShaderMask), findsNothing);
|
||||||
find.ancestor(
|
|
||||||
of: find.byType(GridView),
|
|
||||||
matching: find.byType(ShaderMask),
|
|
||||||
),
|
|
||||||
findsNothing,
|
|
||||||
);
|
|
||||||
for (var index = 0; index < 9; index++) {
|
for (var index = 0; index < 9; index++) {
|
||||||
final card = find.byKey(Key('drama-grid-skeleton-$index'));
|
final card = find.byKey(Key('drama-grid-skeleton-$index'));
|
||||||
expect(card, findsOneWidget);
|
expect(card, findsOneWidget);
|
||||||
expect(
|
expect(
|
||||||
find.ancestor(of: card, matching: find.byType(ShaderMask)),
|
find.descendant(of: card, matching: find.byType(IgnorePointer)),
|
||||||
findsOneWidget,
|
findsOneWidget,
|
||||||
);
|
);
|
||||||
|
final overlay = find.descendant(
|
||||||
|
of: card,
|
||||||
|
matching: find.byWidgetPredicate(
|
||||||
|
(widget) =>
|
||||||
|
widget is DecoratedBox &&
|
||||||
|
widget.decoration is BoxDecoration &&
|
||||||
|
(widget.decoration as BoxDecoration).gradient != null,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
expect(overlay, findsOneWidget);
|
||||||
|
final decoration =
|
||||||
|
tester.widget<DecoratedBox>(overlay).decoration as BoxDecoration;
|
||||||
|
final gradient = decoration.gradient! as LinearGradient;
|
||||||
|
expect(gradient.colors, const [
|
||||||
|
Colors.transparent,
|
||||||
|
Color(0x0DFFFFFF),
|
||||||
|
Colors.transparent,
|
||||||
|
]);
|
||||||
|
expect(gradient.stops, const [0.42, 0.5, 0.58]);
|
||||||
|
expect(gradient.stops![2] - gradient.stops![0], lessThanOrEqualTo(0.25));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
Reference in new issue
Block a user