Flutter创建一个画布自移动图片

Flutter创建一个画布自移动图片

最近在设计一个很好看的画布显示图片组件,营造一种高级氛围感,组件效果如下

效果

请添加图片描述

可以看到图片缓慢的向上移动

原理

通过绘制一个画布来显示一张图片,文字和按钮通过Stack的组件堆叠来显示

实现步骤

继承CustomPainter

创建一个名为DailyTracksCardPainter的类,继承自CustomPainter,实现其paintshouldRepaint方法,需要注意的是,此Imagedart:ui下的,即
import 'dart:ui' as ui;

// 画布类
class DailyTracksCardPainter extends CustomPainter {
    
    
 ui.Image? image;
 double x;
 double y;

 DailyTracksCardPainter({
    
    this.image, this.x = 0, this.y = 0});

 final painter = Paint();
 
 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, y, image!.width.toDouble() * size.width / imageX,
           image!.height.toDouble() * size.width / imageY),
       painter);
 }

 
 bool shouldRepaint(covariant CustomPainter oldDelegate) {
    
    
   return true;
 }
}

创建StatefulWidget有状态组件

创建一个名为DailyTracksCard的组件

class DailyTracksCard extends StatefulWidget {
    
    
 const DailyTracksCard(
     {
    
    super.key,
     required this.width,
     required this.height,
     this.defaultTracksList});

 final double width;
 final double height;
 final List<String>? defaultTracksList;

 
 State<DailyTracksCard> createState() => _DailyTracksCardState();
}

class _DailyTracksCardState extends State<DailyTracksCard> {
    
    
   ......
}

创建一个异步获取ui.Image的方法

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;
}

在此方法中,我们通过网络请求获取图像二进制数据,然后通过decodeImageFromList方法解码获取图片

创建一个定时器用来定时绘制图片

void timeInit(ui.Image image) {
    
    
// 图片播放定时
timer = Timer.periodic(const Duration(milliseconds: 20), (timer) {
    
    
  double heightImage = image.height * widget.width / image.width;
  // 如果加上容器高度大于图片高度
  if (-currentY + widget.height >= heightImage) {
    
    
    direction = true;
  } else if (currentY >= 0) {
    
    
    direction = false;
  }
  setState(() {
    
    
    direction ? currentY += 0.3 : currentY -= 0.3;
  });
});
}

开启一个定时器,用来不断刷新画布,使其动态显示内容,在判断条件中,如果图片到底部了就让图片向上绘制,反之向下绘制

在初始化中获取图片数据

  
 void initState() {
    
    
   super.initState();
   // 随机从0到3的数,不包括3
   int index = Random().nextInt(3).toInt();
   image =
       loadDailyTracksImage(widget.defaultTracksList![index]).then((value) {
    
    
     timeInit(value);
     return value;
   });
 }

在本代码中,此image类型为late Future<ui.Image> image;,表面他是一个异步变量

通过异步构建组件

FutureBuilder(
 future: image,
 builder: (BuildContext context, AsyncSnapshot<dynamic> snapshot) {
    
    
   if (snapshot.connectionState == ConnectionState.done) {
    
    
     return Stack(
       children: [
         Positioned.fill(
           child: CustomPaint(
             painter: DailyTracksCardPainter(
               image: snapshot.data,
               y: currentY.toDouble(),
             ),
           ),
         ),
         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();
 },
)

自此我们就构建完成

使用组件

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 'dart:developer' as developer;
import 'package:flutter/material.dart';
import 'dart:ui' as ui;
import 'package:flutter/services.dart';

class DailyTracksCard extends StatefulWidget {
    
    
  const DailyTracksCard(
      {
    
    super.key,
      required this.width,
      required this.height,
      this.defaultTracksList});

  final double width;
  final double height;
  final List<String>? defaultTracksList;

  
  State<DailyTracksCard> createState() => _DailyTracksCardState();
}

class _DailyTracksCardState extends State<DailyTracksCard> {
    
    
  // 定时器
  late Timer timer;
  double currentY = 0;
  // 播放方向 false 为正向 true 为反向
  bool direction = false;
  late Future<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;
  }

  void timeInit(ui.Image image) {
    
    
    // 图片播放定时
    timer = Timer.periodic(const Duration(milliseconds: 20), (timer) {
    
    
      double heightImage = image.height * widget.width / image.width;
      // 如果加上容器高度大于图片高度
      if (-currentY + widget.height >= heightImage) {
    
    
        direction = true;
      } else if (currentY >= 0) {
    
    
        direction = false;
      }

      setState(() {
    
    
        direction ? currentY += 0.3 : currentY -= 0.3;
      });
    });
  }

  
  void initState() {
    
    
    super.initState();
    // 随机从0到3的数,不包括3
    int index = Random().nextInt(3).toInt();
    image =
        loadDailyTracksImage(widget.defaultTracksList![index]).then((value) {
    
    
      timeInit(value);
      return value;
    });
  }

  
  void dispose() {
    
    
    super.dispose();
    timer.cancel();
  }

  
  Widget build(BuildContext context) {
    
    
    return ClipRRect(
      borderRadius: BorderRadius.circular(20),
      child: SizedBox(
        width: widget.width,
        height: widget.height,
        child: FutureBuilder(
          future: image,
          builder: (BuildContext context, AsyncSnapshot<dynamic> snapshot) {
    
    
            if (snapshot.connectionState == ConnectionState.done) {
    
    
              return Stack(
                children: [
                  Positioned.fill(
                    child: CustomPaint(
                      painter: DailyTracksCardPainter(
                        image: snapshot.data,
                        y: currentY.toDouble(),
                      ),
                    ),
                  ),
                  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;
  double y;

  DailyTracksCardPainter({
    
    this.image, this.x = 0, this.y = 0});

  final painter = Paint();
  
  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, y, image!.width.toDouble() * size.width / imageX,
            image!.height.toDouble() * size.width / imageY),
        painter);
  }

  
  bool shouldRepaint(covariant CustomPainter oldDelegate) {
    
    
    return true;
  }
}

猜你喜欢

转载自blog.csdn.net/qianxiamuxin/article/details/134484369
今日推荐