Spider Cursor Animation in Rive and Implemented in Flutter

Flutter Queen✨
4 min readJul 20, 2023

--

As technology advances, user interfaces and user experience play a crucial role in the success of any application. Developers constantly seek innovative ways to engage users and create captivating interactions. One such impressive element is the Spider Cursor Animation, which can add a touch of creativity and fun to your Flutter application. In this article, we will explore how to create a Spider Cursor Animation using Rive and implement it in a Flutter app.

Introducing Rive

Rive is a popular design and animation tool that allows developers and designers to create and export animations to be used in various platforms and frameworks. It offers a simple yet powerful interface for creating complex animations that can be integrated seamlessly into applications. Rive supports a wide range of platforms, including Flutter, making it a perfect choice for our Spider Cursor Animation.

Implementing the Spider Cursor Animation in Flutter

Assuming you have Flutter and the necessary development tools set up, follow the steps below to integrate the Spider Cursor Animation into your Flutter app:

  • Adding the Rive Package: In your Flutter project’s pubspec.yaml file, add the Rive package to the dependencies:
dependencies:
flutter:
sdk: flutter
rive: ^0.7.0 # Check for the latest version on pub.dev
  • Importing the Rive Package: Run flutter pub get in your terminal to fetch the package. Next, import the Rive package in your Dart file:
import 'package:flutter/material.dart';
import 'package:rive/rive.dart';
  • Loading the Spider Animation: Place the exported animation file (e.g., spider_animation.riv) in your Flutter project's assets folder. Ensure it is correctly defined in your pubspec.yaml file.
flutter:
assets:
- assets/spider_animation.riv

Here’s the complete source code:

// ignore_for_file: public_member_api_docs, sort_constructors_first
import 'dart:math';

import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';
import 'package:flutter_app/spider_controller.dart';
import 'package:flutter_app/ui_page.dart';
import 'package:rive/rive.dart';


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

class MyApp extends StatefulWidget {
const MyApp({super.key});

@override
State<MyApp> createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
ThemeMode _themeMode = ThemeMode.light;

void _switchTheme() {
if (_themeMode == ThemeMode.dark) {
_themeMode = ThemeMode.light;
} else {
_themeMode = ThemeMode.dark;
}
setState(() {});
}

@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Spider Mouse',
debugShowCheckedModeBanner: false,
theme: ThemeData(
colorScheme: const ColorScheme.light(
primary: Color(0xFFEE5E3E),
secondary: Color(0xFFEE5E3E),
),
),
darkTheme: ThemeData.dark().copyWith(
colorScheme: const ColorScheme.dark(
primary: Color(0xFFEE5E3E),
secondary: Color(0xFFEE5E3E),
),
),
themeMode: _themeMode,
home: SpiderMouse(
child: CounterPage(
onThemeChanged: _switchTheme,
),
),
);
}
}

class SpiderMouse extends StatefulWidget {
const SpiderMouse({
Key? key,
required this.child,
}) : super(key: key);

final Widget child;

@override
State<SpiderMouse> createState() => _SpiderMouseState();
}

class _SpiderMouseState extends State<SpiderMouse> {
late Ticker ticker;

final Size _cursorSize = const Size(125, 125);

late final Artboard _artboard;
late final StateMachineController _stateMachineController;

late final SpiderController _spider;

final _indicatorPainter = IndicatorPainter();

var _previuosDuration = Duration.zero;

bool _isLoading = true;

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

@override
void dispose() {
ticker.stop();
ticker.dispose();
_stateMachineController.dispose();
super.dispose();
}


//? sets up the spider animation. It loads the Rive file, initializes the spider controller, and starts the animation
Future<void> _setup() async {
ticker = Ticker(_onTick);
await _setupRiveFile();
_spider = SpiderController(_stateMachineController);
ticker.start();
setState(() {
_isLoading = false;
});
}

//method is the callback for the ticker, which updates the spider animation and repaints the widget.
void _onTick(Duration elapsed) {
_spider.update((elapsed.inMicroseconds.toDouble() -
_previuosDuration.inMicroseconds.toDouble()) /
1000000.0);
_previuosDuration = elapsed;
setState(() {});
}


//?method loads the Rive file containing the spider animation. It gets the artboard, state machine controller, and attaches the controller to the artboard.
Future<void> _setupRiveFile() async {
// Load file
final file = await RiveFile.asset('assets/spider.riv');

// Get artboard
final artboard = file.artboardByName('Spider');
if (artboard == null) {
throw Exception('Failed to load artboard');
}
_artboard = artboard.instance();

// Get State Machine controller and attach to artboard
final controller =
StateMachineController.fromArtboard(_artboard, 'spider-machine');
if (controller == null) {
throw Exception('Failed to load state machine');
}
_stateMachineController = controller;
_artboard.addController(_stateMachineController);
}
//spider cursor's position is updated using _setMousePosition() method, which is passed the user's mouse position.

void _setMousePosition(Offset pos) {
_indicatorPainter.position = pos;
_spider.targetPosition = pos;
}

@override
Widget build(BuildContext context) {
if (_isLoading) return const SizedBox.shrink();

final pointerOffset = _cursorSize.height / 5;
final dxPointer = _spider.dx -
(_cursorSize.width / 2) -
(pointerOffset * sin(_spider.rotation));
final dyPointer = _spider.dy -
(_cursorSize.height / 2) +
(pointerOffset * cos(_spider.rotation));

final transform = Matrix4.identity()
..translate(
dxPointer,
dyPointer,
)
..rotateZ(_spider.rotation);

//? handle user mouse events.
return Listener(
onPointerMove: (event) => _setMousePosition(event.position),
onPointerHover: (event) => _setMousePosition(event.position),
onPointerDown: (event) {
if (event.buttons == kSecondaryMouseButton) {
_spider.rightClick();
} else {
_spider.leftClick();
}
},
child: MouseRegion(
cursor: SystemMouseCursors.none,
child: Stack(
children: [
RepaintBoundary(child: widget.child),
IgnorePointer(
child: Transform(
alignment: Alignment.center,
transform: transform,
child: SizedBox(
width: _cursorSize.width,
height: _cursorSize.height,
child: Rive(
artboard: _artboard,
),
),
),
),
CustomPaint(
painter: _indicatorPainter,
),
],
),
),
);
}
}

class IndicatorPainter extends CustomPainter {
Offset position = Offset.zero;

final _indicatorPaint = Paint()
..color = Colors.black12
..style = PaintingStyle.fill;

@override
void paint(Canvas canvas, Size size) {
canvas.drawCircle(position, 5, _indicatorPaint);
}

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

Overall, the code creates a Flutter app that utilizes the Rive animation package to implement a spider cursor animation. The spider cursor follows the user’s mouse position and performs different actions based on left-click or right-click events. Additionally, the app allows users to toggle between light and dark themes using a toggle button.

I hope this article was helpful to you. Thank you for taking the time to read it. Your feedback and suggestions are always welcome.

Support Me:

--

--