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

5. 处理选择结果

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

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