mod: 首页推荐视频骨架屏
This commit is contained in:
191
lib/common/skeleton/skeleton.dart
Normal file
191
lib/common/skeleton/skeleton.dart
Normal 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,
|
||||
);
|
||||
}
|
||||
}
|
74
lib/common/skeleton/video_card_v.dart
Normal file
74
lib/common/skeleton/video_card_v.dart
Normal 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,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user