mod: 首页推荐视频骨架屏

This commit is contained in:
guozhigq
2023-04-19 10:15:56 +08:00
parent 3a344843f9
commit 385cc4dad9
4 changed files with 267 additions and 2 deletions

View File

@ -0,0 +1,191 @@
import 'package:flutter/material.dart';
class Skeleton extends StatelessWidget {
final Widget child;
const Skeleton({
required this.child,
super.key,
});
@override
Widget build(BuildContext context) {
var shimmerGradient = LinearGradient(
colors: [
Colors.transparent,
Theme.of(context).colorScheme.background.withAlpha(10),
Theme.of(context).colorScheme.background.withAlpha(10),
Colors.transparent,
],
stops: const [
0.1,
0.3,
0.5,
0.7,
],
begin: const Alignment(-1.0, -0.3),
end: const Alignment(1.0, 0.9),
tileMode: TileMode.clamp,
);
return Shimmer(
linearGradient: shimmerGradient,
child: ShimmerLoading(
isLoading: true,
child: child,
),
);
}
}
class Shimmer extends StatefulWidget {
static ShimmerState? of(BuildContext context) {
return context.findAncestorStateOfType<ShimmerState>();
}
const Shimmer({
super.key,
required this.linearGradient,
this.child,
});
final LinearGradient linearGradient;
final Widget? child;
@override
ShimmerState createState() => ShimmerState();
}
class ShimmerState extends State<Shimmer> with SingleTickerProviderStateMixin {
late AnimationController _shimmerController;
@override
void initState() {
super.initState();
_shimmerController = AnimationController.unbounded(vsync: this)
..repeat(min: -0.5, max: 1.5, period: const Duration(milliseconds: 1000));
}
@override
void dispose() {
_shimmerController.dispose();
super.dispose();
}
LinearGradient get gradient => LinearGradient(
colors: widget.linearGradient.colors,
stops: widget.linearGradient.stops,
begin: widget.linearGradient.begin,
end: widget.linearGradient.end,
transform: _SlidingGradientTransform(
slidePercent: _shimmerController.value,
),
);
bool get isSized =>
(context.findRenderObject() as RenderBox?)?.hasSize ?? false;
Size get size => (context.findRenderObject() as RenderBox).size;
Offset getDescendantOffset({
required RenderBox descendant,
Offset offset = Offset.zero,
}) {
final shimmerBox = context.findRenderObject() as RenderBox;
return descendant.localToGlobal(offset, ancestor: shimmerBox);
}
Listenable get shimmerChanges => _shimmerController;
@override
Widget build(BuildContext context) {
return widget.child ?? const SizedBox();
}
}
class _SlidingGradientTransform extends GradientTransform {
const _SlidingGradientTransform({
required this.slidePercent,
});
final double slidePercent;
@override
Matrix4? transform(Rect bounds, {TextDirection? textDirection}) {
return Matrix4.translationValues(bounds.width * slidePercent, 0.0, 0.0);
}
}
class ShimmerLoading extends StatefulWidget {
const ShimmerLoading({
super.key,
required this.isLoading,
required this.child,
});
final bool isLoading;
final Widget child;
@override
State<ShimmerLoading> createState() => _ShimmerLoadingState();
}
class _ShimmerLoadingState extends State<ShimmerLoading> {
Listenable? _shimmerChanges;
@override
void didChangeDependencies() {
super.didChangeDependencies();
if (_shimmerChanges != null) {
_shimmerChanges!.removeListener(_onShimmerChange);
}
_shimmerChanges = Shimmer.of(context)?.shimmerChanges;
if (_shimmerChanges != null) {
_shimmerChanges!.addListener(_onShimmerChange);
}
}
@override
void dispose() {
_shimmerChanges?.removeListener(_onShimmerChange);
super.dispose();
}
void _onShimmerChange() {
if (widget.isLoading) {
setState(() {});
}
}
@override
Widget build(BuildContext context) {
if (!widget.isLoading) {
return widget.child;
}
final shimmer = Shimmer.of(context)!;
if (!shimmer.isSized) {
return const SizedBox();
}
final shimmerSize = shimmer.size;
final gradient = shimmer.gradient;
final offsetWithinShimmer = shimmer.getDescendantOffset(
descendant: context.findRenderObject() as RenderBox,
);
return ShaderMask(
blendMode: BlendMode.srcATop,
shaderCallback: (bounds) {
return gradient.createShader(
Rect.fromLTWH(
-offsetWithinShimmer.dx,
-offsetWithinShimmer.dy,
shimmerSize.width,
shimmerSize.height,
),
);
},
child: widget.child,
);
}
}

View File

@ -0,0 +1,74 @@
import 'package:pilipala/common/constants.dart';
import 'package:flutter/material.dart';
import 'skeleton.dart';
class VideoCardVSkeleton extends StatelessWidget {
const VideoCardVSkeleton({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Skeleton(
child: Card(
elevation: 0.8,
shape: RoundedRectangleBorder(
borderRadius: StyleString.mdRadius,
),
margin: EdgeInsets.zero,
child: Column(
children: [
AspectRatio(
aspectRatio: StyleString.aspectRatio,
child: LayoutBuilder(
builder: (context, boxConstraints) {
return Container(
clipBehavior: Clip.hardEdge,
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.background,
borderRadius: BorderRadius.circular(6),
border: Border.all(
color: Theme.of(context)
.colorScheme
.outline
.withOpacity(0.1),
),
),
);
},
),
),
Padding(
// 多列
padding: const EdgeInsets.fromLTRB(8, 8, 6, 7),
// 单列
// padding: const EdgeInsets.fromLTRB(14, 10, 4, 8),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
children: [
// const SizedBox(height: 6),
Container(
width: 200,
height: 13,
color: Theme.of(context).colorScheme.background,
),
const SizedBox(height: 5),
Container(
width: 150,
height: 13,
color: Theme.of(context).colorScheme.background,
),
const SizedBox(height: 12),
Container(
width: 80,
height: 13,
color: Theme.of(context).colorScheme.background,
),
],
),
),
],
),
),
);
}
}