Flutter画布自移动改进版
由于之前的版本采用的是定时器的写法,会造成性能上的不足,此次采用AnimationController的动画方案,解决之前设计的不足
效果
使用方法
DailyTracksCard(
width: 550,
height: 250,
defaultTracksList: const [
'https://p2.music.126.net/0-Ybpa8FrDfRgKYCTJD8Xg==/109951164796696795.jpg',
'https://p2.music.126.net/QxJA2mr4hhb9DZyucIOIQw==/109951165422200291.jpg',
'https://p1.music.126.net/AhYP9TET8l-VSGOpWAKZXw==/109951165134386387.jpg',
],
)
源码
import 'dart:async';
import 'dart:math';
import 'package:flutter/material.dart';
import 'dart:ui' as ui;
import 'package:flutter/services.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import '../common/utils/screenadaptor.dart';
class DailyTracksCard extends StatefulWidget {
const DailyTracksCard(
{super.key,
required this.width,
required this.height,
this.dailyTracksList,
this.defaultTracksList});
final double width;
final double height;
final List<dynamic>? dailyTracksList;
final List<String>? defaultTracksList;
@override
State<DailyTracksCard> createState() => _DailyTracksCardState();
}
class _DailyTracksCardState extends State<DailyTracksCard>
with SingleTickerProviderStateMixin {
// 平移动画控制器
late Future<AnimationController> animationController;
late Animation<double> animation;
// 播放方向 false 为正向 true 为反向
bool direction = false;
late ui.Image image;
// 获取For You 每日推荐图片
Future<ui.Image> loadDailyTracksImage(String path) async {
final data = await NetworkAssetBundle(Uri.parse(path)).load(path);
final bytes = data.buffer.asUint8List();
final image = await decodeImageFromList(bytes);
return image;
}
// 异步获取动画构建器
Future<AnimationController> loadAnimationController() async {
if (widget.dailyTracksList!.isNotEmpty) {
image =
await loadDailyTracksImage(widget.dailyTracksList![0]["al"]["picUrl"])
.then((value) {
return value;
});
} else {
// 随机从0到3的数,不包括3
int index = Random().nextInt(3).toInt();
image = await loadDailyTracksImage(widget.defaultTracksList![index])
.then((value) {
return value;
});
}
AnimationController controller = AnimationController(
duration: const Duration(seconds: 28),
vsync: this,
);
animation = controller.drive(Tween(
begin: 0,
end: (image.height * widget.width / image.width - widget.height),
));
controller.repeat(reverse: true);
return controller;
}
@override
void initState() {
super.initState();
// 异步获取动画构建器
animationController = loadAnimationController();
}
@override
void dispose() {
super.dispose();
// 释放动画资源
animationController.then((value) => value.dispose());
}
@override
Widget build(BuildContext context) {
return ClipRRect(
borderRadius: BorderRadius.circular(
screenAdaptor.getLengthByOrientation(20.w, 12.w),
),
child: SizedBox(
width: widget.width,
height: widget.height,
child: FutureBuilder(
future: animationController,
builder: (BuildContext context, AsyncSnapshot<dynamic> snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
return Stack(
children: [
Positioned.fill(
child: CustomPaint(
painter: DailyTracksCardPainter(
image: image,
currentY: animation,
repaint: snapshot.data,
),
),
),
const Positioned(
top: 38,
left: 50,
child: Text(
"每日\n推荐",
style: TextStyle(
fontSize: 60,
fontWeight: FontWeight.bold,
color: Colors.white,
letterSpacing: 12,
),
),
),
Positioned(
right: 30,
bottom: 30,
child: IconButton(
style: ButtonStyle(
// 半透明背景
backgroundColor: MaterialStateProperty.all(
Colors.white.withOpacity(0.15),
),
overlayColor: MaterialStateProperty.all(
Colors.white.withOpacity(0.3),
),
),
// 播放按钮
icon: const Icon(
Icons.play_arrow_rounded,
color: Colors.white,
size: 60,
),
onPressed: () {},
),
)
],
);
}
return const SizedBox();
},
),
),
);
}
}
// 画布类
class DailyTracksCardPainter extends CustomPainter {
ui.Image? image;
double x;
// 当前y轴位置
final Animation<double> currentY;
final Animation<double> repaint;
DailyTracksCardPainter({this.image, this.x = 0, required this.currentY, required this.repaint})
: super(repaint: repaint);
final painter = Paint();
@override
void paint(Canvas canvas, Size size) {
double imageX = image!.width.toDouble();
double imageY = image!.height.toDouble();
// 要绘制的Rect,即原图片的大小
Rect src = Rect.fromLTWH(0, 0, imageX, imageY);
// 要绘制成的Rect,即绘制后的图片大小
canvas.drawImageRect(
image!,
src,
Rect.fromLTWH(
x,
-currentY.value,
image!.width.toDouble() * size.width / imageX,
image!.height.toDouble() * size.width / imageY),
painter);
}
@override
bool shouldRepaint(covariant DailyTracksCardPainter oldDelegate) {
return oldDelegate.repaint != repaint;
}
}