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,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -26,7 +26,6 @@ class HomeController extends GetxController {
|
|||||||
);
|
);
|
||||||
List<RecVideoItemModel> list = [];
|
List<RecVideoItemModel> list = [];
|
||||||
for (var i in res.data['data']['item']) {
|
for (var i in res.data['data']['item']) {
|
||||||
print(i);
|
|
||||||
list.add(RecVideoItemModel.fromJson(i));
|
list.add(RecVideoItemModel.fromJson(i));
|
||||||
}
|
}
|
||||||
if (type == 'init') {
|
if (type == 'init') {
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
|
import 'package:pilipala/common/skeleton/video_card_v.dart';
|
||||||
import 'package:pilipala/common/widgets/video_card_v.dart';
|
import 'package:pilipala/common/widgets/video_card_v.dart';
|
||||||
import './controller.dart';
|
import './controller.dart';
|
||||||
import 'package:pilipala/common/constants.dart';
|
import 'package:pilipala/common/constants.dart';
|
||||||
@ -84,7 +85,7 @@ class _HomePageState extends State<HomePage>
|
|||||||
(BuildContext context, int index) {
|
(BuildContext context, int index) {
|
||||||
return videoList.isNotEmpty
|
return videoList.isNotEmpty
|
||||||
? VideoCardV(videoItem: videoList[index])
|
? VideoCardV(videoItem: videoList[index])
|
||||||
: const Text('加载中');
|
: const VideoCardVSkeleton();
|
||||||
},
|
},
|
||||||
childCount: videoList.isNotEmpty ? videoList.length : 10,
|
childCount: videoList.isNotEmpty ? videoList.length : 10,
|
||||||
),
|
),
|
||||||
|
Reference in New Issue
Block a user