flutter 抽獎(jiǎng)旋轉(zhuǎn)大轉(zhuǎn)盤

Untitled.gif

使用 CustomPainter 繪制好內(nèi)容潜的,然后再使用動(dòng)畫對(duì)整個(gè)對(duì)內(nèi)容旋轉(zhuǎn)固定的角度 卒密。以下效果圖由于是gif錄制的不太流暢 看效果可以運(yùn)行源碼在最下面雏节。

配置數(shù)據(jù)源

 void _loadImage() async {
    ui.Image? img = await loadImageFromAssets('images/ionc_rank_list.png');
    for (int x = 0 ; x < 12 ; x ++) {
      titles.add('T_$x');
      prices.add('P_$x');
      _images.add(img!);
    }
    setState(() {});
  }

開始旋轉(zhuǎn) 隨機(jī)0 到12個(gè) 指定停止到哪一個(gè)

  void drawClick() async {
    int res = Random().nextInt(12);
    print(res);
    angle = res * 360 / _images.length;
    _controller.forward();
    setState(() {});
  }

以下是 全部代碼 可以直接在main 里面運(yùn)行 圖片自行換一下

import 'dart:async';

import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'dart:math';
import 'dart:ui' as ui;
import 'package:flutter/services.dart' show rootBundle;
import 'package:scale_button/scale_button.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: const Text('扇形轉(zhuǎn)盤'),
        ),
        body: SpinWheel(),
      ),
    );
  }
}

class SpinWheel extends StatefulWidget {
  const SpinWheel({Key? key}) : super(key: key);
  @override
  _SpinWheelState createState() => _SpinWheelState();
}

class _SpinWheelState extends State<SpinWheel> with SingleTickerProviderStateMixin {
  late AnimationController _controller;
  late Animation<double> _animation;

  final List<ui.Image> _images = [];
  List<String> titles = [];
  List<String> prices = [];

  double angle = 0;

  @override
  void initState() {
    super.initState();

    initConfig();
    _loadImage();
  }

  @override
  void dispose() {
    // TODO: implement dispose
    _controller.dispose(); // 釋放動(dòng)畫資源
    super.dispose();

  }

  void initConfig() {
    _controller = AnimationController(
      duration: const Duration(seconds: 3), // 定義動(dòng)畫持續(xù)時(shí)間
      vsync: this,
    );

    _animation = CurvedAnimation(parent: _controller, curve: Curves.decelerate);

    _animation.addStatusListener((status) {
      if (status == AnimationStatus.completed) {
        _controller.reset(); // 重置動(dòng)畫
      }
    });
  }

  void _loadImage() async {
    ui.Image? img = await loadImageFromAssets('images/ionc_rank_list.png');
    for (int x = 0 ; x < 12 ; x ++) {
      titles.add('T_$x');
      prices.add('P_$x');
      _images.add(img!);
    }
    setState(() {});
  }


  //讀取 assets 中的圖片
  Future<ui.Image>? loadImageFromAssets(String path) async {
    ByteData data = await rootBundle.load(path);
    return decodeImageFromList(data.buffer.asUint8List());
  }
  

  void drawClick() async {
    int res = Random().nextInt(12);
    print(res);
    angle = res * 360 / _images.length;
    _controller.forward();
    setState(() {});
  }

  @override
  Widget build(BuildContext context) {
    if (_images.isEmpty) return Container();
    return Material(
      color: Colors.transparent,
      child: Container(
        width: 400,
        height: 400,
        padding: const EdgeInsets.only(top: 0),
        child: Stack(
          alignment: Alignment.center,
          children: [
            _rotationTransition(),
            ScaleButton(
              child: Image.asset(
                'images/icon_pointer.png',
                width: 160,
              ),
              onTap: (){
                drawClick();
              },
            ),
          ],
        ),
      ),
    );
  }

  Widget _rotationTransition(){
    return RotationTransition(
      turns: Tween(begin: 0.0, end: 5.0).animate(
        CurvedAnimation(
          parent: _controller,
          curve: Curves.ease,
        ),
      ),
      // turns: _animation,
      child: Transform.rotate(
        angle: angle * -(3.141592653589793 / 180), // 將角度轉(zhuǎn)換為弧度
        child: Center(
          child: CustomPaint(
            size: const Size(360, 360),
            painter: SpinWheelPainter(titles: titles, images: _images, prices: prices),
          ),
        ),
      ),
    );
  }
}

class SpinWheelPainter extends CustomPainter {
  List <String> titles;
  List <ui.Image> images;
  List <String> prices ;
  SpinWheelPainter({required this.titles ,required this.images,required this.prices});

  @override
  void paint(Canvas canvas, Size size) {
    final Paint paint = Paint()
      ..style = PaintingStyle.fill;

    final double radius = size.width / 2;
    final double centerX = size.width / 2;
    final double centerY = size.height / 2;

    final int numSectors = titles.length; // 扇形數(shù)量
    final double sectorAngle = 2 * pi / numSectors; // 每個(gè)扇形的角度

    TextStyle textStyle = const TextStyle(
      color: Colors.red,
      fontSize: 12,
    );

    const double startAngle = 197 * pi / 180; // 15度的弧度值

    for (int i = 0; i < titles.length; i++) {
      final double currentStartAngle = startAngle + i * sectorAngle +1;
      final double currentSweepAngle = sectorAngle;
      paint.color = i % 2 == 0
          ? Colors.blue
          : const Color.fromRGBO( 251,232, 192,1); // 循環(huán)使用顏色數(shù)組

      canvas.drawArc(
        Rect.fromCircle(center: Offset(centerX, centerY), radius: radius),
        currentStartAngle,
        currentSweepAngle,
        true,
        paint,
      );

      // 旋轉(zhuǎn)畫布
      canvas.save();
      canvas.translate(centerX, centerY);
      canvas.rotate(currentStartAngle + currentSweepAngle / 2 + pi / 2); // 旋轉(zhuǎn)45度
      canvas.translate(-centerX, -centerY);
      double img_width = 40;
      if(images.isNotEmpty){
        final imageRect = Rect.fromLTWH(
          centerX - img_width / 2,
          centerY - radius * 0.7 - img_width,
          img_width,
          img_width,
        );

        paintImage(
          canvas: canvas,
          rect: imageRect,
          image: images[i],
          fit: BoxFit.fill,
          alignment: Alignment.center,
          repeat: ImageRepeat.noRepeat,
          flipHorizontally: false,
          filterQuality: FilterQuality.low,
        );

      }


      // 添加文字
      final String text = titles[i];
      final TextPainter textPainter = TextPainter(
        text: TextSpan(
          text: text,
          style: textStyle,
        ),
        textAlign: TextAlign.center,
        textDirection: TextDirection.ltr,
      );

      textPainter.layout();

      // 計(jì)算文字位置
      final double textX = centerX - textPainter.width / 2;
      final double textY = centerY - radius * 0.6;

      // 繪制文字
      textPainter.paint(canvas, Offset(textX, textY));

      final String text1 = prices[i];

      final TextPainter priceTextPainter = TextPainter(
        text: TextSpan(
          text: text1,
          style: textStyle,
        ),
        textAlign: TextAlign.center,
        textDirection: TextDirection.ltr,
      );

      priceTextPainter.layout();

      // 計(jì)算文字位置
      final double priceTextX = centerX - priceTextPainter.width / 2;
      final double priceTextY = centerY - radius * 0.5;

      // 繪制文字
      priceTextPainter.paint(canvas, Offset(priceTextX, priceTextY));

      canvas.restore(); // 恢復(fù)畫布狀態(tài)
    }
  }

  @override
  bool shouldRepaint(covariant CustomPainter oldDelegate) {
    return false;
  }
}


最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末肠鲫,一起剝皮案震驚了整個(gè)濱河市鸟顺,隨后出現(xiàn)的幾起案子酷宵,更是在濱河造成了極大的恐慌亥贸,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,490評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件浇垦,死亡現(xiàn)場(chǎng)離奇詭異炕置,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)男韧,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,581評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門朴摊,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人此虑,你說(shuō)我怎么就攤上這事甚纲。” “怎么了朦前?”我有些...
    開封第一講書人閱讀 165,830評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵介杆,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我韭寸,道長(zhǎng)春哨,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,957評(píng)論 1 295
  • 正文 為了忘掉前任棒仍,我火速辦了婚禮,結(jié)果婚禮上臭胜,老公的妹妹穿的比我還像新娘莫其。我一直安慰自己癞尚,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,974評(píng)論 6 393
  • 文/花漫 我一把揭開白布乱陡。 她就那樣靜靜地躺著浇揩,像睡著了一般。 火紅的嫁衣襯著肌膚如雪憨颠。 梳的紋絲不亂的頭發(fā)上胳徽,一...
    開封第一講書人閱讀 51,754評(píng)論 1 307
  • 那天,我揣著相機(jī)與錄音爽彤,去河邊找鬼养盗。 笑死,一個(gè)胖子當(dāng)著我的面吹牛适篙,可吹牛的內(nèi)容都是我干的往核。 我是一名探鬼主播,決...
    沈念sama閱讀 40,464評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼嚷节,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼聂儒!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起硫痰,我...
    開封第一講書人閱讀 39,357評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤衩婚,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后效斑,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體非春,經(jīng)...
    沈念sama閱讀 45,847評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,995評(píng)論 3 338
  • 正文 我和宋清朗相戀三年鳍悠,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了税娜。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,137評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡藏研,死狀恐怖敬矩,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情蠢挡,我是刑警寧澤弧岳,帶...
    沈念sama閱讀 35,819評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站业踏,受9級(jí)特大地震影響禽炬,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜勤家,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,482評(píng)論 3 331
  • 文/蒙蒙 一腹尖、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧伐脖,春花似錦热幔、人聲如沸乐设。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,023評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)近尚。三九已至,卻和暖如春场勤,著一層夾襖步出監(jiān)牢的瞬間戈锻,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,149評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工和媳, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留格遭,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,409評(píng)論 3 373
  • 正文 我出身青樓窗价,卻偏偏與公主長(zhǎng)得像如庭,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子撼港,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,086評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容