Flutter旋转选择器插件flutter_spinning_wheel的使用

Flutter旋转选择器插件flutter_spinning_wheel的使用

A customizable widget to use as a spinning wheel in Flutter.

开始使用

安装

在你的 pubspec.yaml 文件中添加以下内容:

flutter_spinning_wheel : ^latest_version

然后在项目根目录运行以下命令来安装依赖:

flutter packages get

基本用法

创建一个新的 Flutter 项目:

flutter create myapp

编辑 lib/main.dart 文件如下:

import 'package:flutter/material.dart';

import 'package:flutter_spinning_wheel/flutter_spinning_wheel.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  [@override](/user/override)
  Widget build(BuildContext context) {
    return MaterialApp(
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(),
    );
  }
}

class MyHomePage extends StatelessWidget {
  [@override](/user/override)
  Widget build(BuildContext context) {
    return Scaffold(
        backgroundColor: Colors.blueGrey,
        body: Center(
          child: Container(
            child: SpinningWheel(
              Image.asset('assets/images/wheel-6-300.png'), // 使用自定义图片作为轮盘
              width: 310, // 轮盘宽度
              height: 310, // 轮盘高度
              dividers: 6, // 分割份数
              onEnd: _dividerController.add, // 动画结束时触发回调
            ),
          ),
        ));
  }
}

你可以将 wheel-6-300.png 替换为你自己的图片。

效果图:

基本轮盘 游戏轮盘

构造函数参数

以下是 SpinningWheel 的构造函数参数及其描述:

参数名称 默认值 描述
image 无 用于轮盘的图片
dividers 无 图片的分割份数,所有分割必须相等
height 无 显示轮盘的容器高度
width 无 显示轮盘的容器宽度
initialSpinAngle 0.0 轮盘初始旋转角度
spinResistance 0.5 从 >0.0 到 1.0 用于计算轮盘的速度和减速
canInteractWhileSpinning true 如果设置为 false,则动画开始后用户无法停止它
secondaryImage 无 渲染在轮盘顶部的次级图片,不会受动画影响
secondaryImageHeight 无 次级图片的高度
secondaryImageWidth 无 次级图片的宽度
secondaryImageTop 无 用于微调次级图片的位置
secondaryImageLeft 无 用于微调次级图片的位置
onUpdate void onUpdate(int value) 在选中的分割变化时执行的回调函数
onEnd void onEnd(int value) 动画停止时执行的回调函数
shouldStartOrStop 无 控制是否开始或停止动画的流

使用案例

游戏轮盘

import 'dart:async';
import 'dart:math';

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_spinning_wheel/flutter_spinning_wheel.dart';

void main() {
  SystemChrome.setEnabledSystemUIOverlays([]);
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  [@override](/user/override)
  Widget build(BuildContext context) {
    return MaterialApp(
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(),
    );
  }
}

class MyHomePage extends StatelessWidget {
  final StreamController _dividerController = StreamController<int>();

  dispose() {
    _dividerController.close();
  }

  [@override](/user/override)
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(backgroundColor: Color(0xffDDC3FF), elevation: 0.0),
      backgroundColor: Color(0xffDDC3FF),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            SpinningWheel(
              Image.asset('assets/images/roulette-8-300.png'), // 轮盘图片
              width: 310,
              height: 310,
              initialSpinAngle: _generateRandomAngle(), // 随机初始角度
              spinResistance: 0.6,
              canInteractWhileSpinning: false, // 动画期间不可交互
              dividers: 8, // 分割份数
              onUpdate: _dividerController.add, // 更新时的回调
              onEnd: _dividerController.add, // 动画结束时的回调
              secondaryImage: Image.asset('assets/images/roulette-center-300.png'), // 次级图片
              secondaryImageHeight: 110,
              secondaryImageWidth: 110,
            ),
            SizedBox(height: 30),
            StreamBuilder(
              stream: _dividerController.stream,
              builder: (context, snapshot) =>
                  snapshot.hasData ? RouletteScore(snapshot.data) : Container(),
            )
          ],
        ),
      ),
    );
  }

  double _generateRandomAngle() => Random().nextDouble() * pi * 2;
}

class RouletteScore extends StatelessWidget {
  final int selected;

  final Map<int, String> labels = {
    1: '1000\$',
    2: '400\$',
    3: '800\$',
    4: '7000\$',
    5: '5000\$',
    6: '300\$',
    7: '2000\$',
    8: '100\$',
  };

  RouletteScore(this.selected);

  [@override](/user/override)
  Widget build(BuildContext context) {
    return Text('${labels[selected]}',
        style: TextStyle(fontStyle: FontStyle.italic, fontSize: 24.0));
  }
}

效果图:

示例代码

以下是完整的示例代码:

import 'dart:async';
import 'dart:math';

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_spinning_wheel/flutter_spinning_wheel.dart';

void main() {
  SystemChrome.setEnabledSystemUIOverlays([]);
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  [@override](/user/override)
  Widget build(BuildContext context) {
    return MaterialApp(
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(),
    );
  }
}

class MyHomePage extends StatelessWidget {
  [@override](/user/override)
  Widget build(BuildContext context) {
    return Scaffold(
      body: Column(
        crossAxisAlignment: CrossAxisAlignment.stretch,
        children: [
          Expanded(
            child: Container(
              color: Color(0xffB0F9D2),
              child: InkWell(
                  child: Center(child: Text('B A S I C')),
                  onTap: () {
                    Navigator.push(
                      context,
                      MaterialPageRoute(builder: (context) => Basic()),
                    );
                  }),
            ),
          ),
          Expanded(
            child: Container(
              color: Color(0xffDDC3FF),
              child: InkWell(
                  child: Center(child: Text('R O U L E T T E')),
                  onTap: () {
                    Navigator.push(
                      context,
                      MaterialPageRoute(builder: (context) => Roulette()),
                    );
                  }),
            ),
          ),
        ],
      ),
    );
  }

  Widget buildNavigationButton({String text, Function onPressedFn}) {
    return FlatButton(
      color: Color.fromRGBO(255, 255, 255, 0.3),
      textColor: Colors.white,
      shape: RoundedRectangleBorder(
        borderRadius: BorderRadius.circular(50.0),
      ),
      onPressed: onPressedFn,
      child: Text(
        text,
        style: TextStyle(color: Colors.white, fontSize: 18.0),
      ),
    );
  }
}

class Basic extends StatelessWidget {
  final StreamController _dividerController = StreamController<int>();

  dispose() {
    _dividerController.close();
  }

  [@override](/user/override)
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(backgroundColor: Color(0xffB0F9D2), elevation: 0.0),
      backgroundColor: Color(0xffB0F9D2),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            SpinningWheel(
              Image.asset('assets/images/wheel-6-300.png'),
              width: 310,
              height: 310,
              initialSpinAngle: _generateRandomAngle(),
              spinResistance: 0.2,
              dividers: 6,
              onUpdate: _dividerController.add,
              onEnd: _dividerController.add,
            ),
            StreamBuilder(
              stream: _dividerController.stream,
              builder: (context, snapshot) =>
                  snapshot.hasData ? BasicScore(snapshot.data) : Container(),
            )
          ],
        ),
      ),
    );
  }

  double _generateRandomAngle() => Random().nextDouble() * pi * 2;
}

class BasicScore extends StatelessWidget {
  final int selected;

  final Map<int, String> labels = {
    1: 'Purple',
    2: 'Magenta',
    3: 'Red',
    4: 'Dark Orange',
    5: 'Light Orange',
    6: 'Yellow',
  };

  BasicScore(this.selected);

  [@override](/user/override)
  Widget build(BuildContext context) {
    return Text('${labels[selected]}',
        style: TextStyle(fontStyle: FontStyle.italic));
  }
}

class Roulette extends StatelessWidget {
  final StreamController _dividerController = StreamController<int>();

  final _wheelNotifier = StreamController<double>();

  dispose() {
    _dividerController.close();
    _wheelNotifier.close();
  }

  [@override](/user/override)
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(backgroundColor: Color(0xffDDC3FF), elevation: 0.0),
      backgroundColor: Color(0xffDDC3FF),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            SpinningWheel(
              Image.asset('assets/images/roulette-8-300.png'),
              width: 310,
              height: 310,
              initialSpinAngle: _generateRandomAngle(),
              spinResistance: 0.6,
              canInteractWhileSpinning: false,
              dividers: 8,
              onUpdate: _dividerController.add,
              onEnd: _dividerController.add,
              secondaryImage: Image.asset('assets/images/roulette-center-300.png'),
              secondaryImageHeight: 110,
              secondaryImageWidth: 110,
              shouldStartOrStop: _wheelNotifier.stream,
            ),
            SizedBox(height: 30),
            StreamBuilder(
              stream: _dividerController.stream,
              builder: (context, snapshot) =>
                  snapshot.hasData ? RouletteScore(snapshot.data) : Container(),
            ),
            SizedBox(height: 30),
            new RaisedButton(
              child: new Text("Start"),
              onPressed: () =>
                  _wheelNotifier.sink.add(_generateRandomVelocity()),
            )
          ],
        ),
      ),
    );
  }

  double _generateRandomVelocity() => (Random().nextDouble() * 6000) + 2000;

  double _generateRandomAngle() => Random().nextDouble() * pi * 2;
}

class RouletteScore extends StatelessWidget {
  final int selected;

  final Map<int, String> labels = {
    1: '1000\$',
    2: '400\$',
    3: '800\$',
    4: '7000\$',
    5: '5000\$',
    6: '300\$',
    7: '2000\$',
    8: '100\$',
  };

  RouletteScore(this.selected);

  [@override](/user/override)
  Widget build(BuildContext context) {
    return Text('${labels[selected]}',
        style: TextStyle(fontStyle: FontStyle.italic, fontSize: 24.0));
  }
}

更多关于Flutter旋转选择器插件flutter_spinning_wheel的使用的实战教程也可以访问 https://www.itying.com/category-92-b0.html

1 回复

更多关于Flutter旋转选择器插件flutter_spinning_wheel的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html


flutter_spinning_wheel 是一个用于创建旋转选择器(类似于幸运大转盘)的 Flutter 插件。它允许你自定义旋转器的外观、旋转行为以及触发事件。以下是如何使用 flutter_spinning_wheel 的基本指南。

1. 添加依赖

首先,在你的 pubspec.yaml 文件中添加 flutter_spinning_wheel 依赖:

dependencies:
  flutter:
    sdk: flutter
  flutter_spinning_wheel: ^1.0.0  # 请检查最新版本

然后运行 flutter pub get 来获取依赖。

2. 导入库

在你的 Dart 文件中导入 flutter_spinning_wheel:

import 'package:flutter_spinning_wheel/flutter_spinning_wheel.dart';

3. 创建旋转选择器

你可以使用 SpinningWheel 小部件来创建一个旋转选择器。以下是一个简单的示例:

import 'package:flutter/material.dart';
import 'package:flutter_spinning_wheel/flutter_spinning_wheel.dart';

class SpinningWheelExample extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Spinning Wheel Example'),
      ),
      body: Center(
        child: SpinningWheel(
          Image.asset('assets/wheel.png'),  // 轮盘的图片
          width: 300,
          height: 300,
          initialSpinAngle: 0,
          spinResistance: 0.6,
          canInteractWhileSpinning: false,
          dividers: 8,  // 轮盘的分割数量
          onUpdate: (double angle) {
            print('Current Angle: $angle');
          },
          onEnd: (double angle) {
            print('Final Angle: $angle');
            int selected = (angle / (360 / 8)).floor() % 8;
            print('Selected: $selected');
          },
        ),
      ),
    );
  }
}

4. 自定义旋转选择器

你可以通过以下参数来自定义旋转选择器:

  • Image: 轮盘的图片。
  • width 和 height: 轮盘的宽度和高度。
  • initialSpinAngle: 初始旋转角度。
  • spinResistance: 旋转阻力,值越大旋转越慢。
  • canInteractWhileSpinning: 是否允许在旋转时进行交互。
  • dividers: 轮盘的分割数量。
  • onUpdate: 旋转时的回调,返回当前角度。
  • onEnd: 旋转结束时的回调,返回最终角度。

5. 处理选择结果

在 onEnd 回调中,你可以根据最终角度计算出选择的选项。例如,如果轮盘有 8 个分割,每个分割的角度为 45 度,你可以通过以下方式计算选择的选项:

int selected = (angle / (360 / 8)).floor() % 8;
回到顶部