main.dart
import 'package:flutter/material.dart';
import 'advanced.dart';
import 'basic.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
debugShowCheckedModeBanner: false,
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: DefaultTabController(
length: 2,
child: Scaffold(
bottomNavigationBar: const SafeArea(
child: TabBar(
labelColor: Colors.blueAccent,
unselectedLabelColor: Colors.blueGrey,
indicatorColor: Colors.blueAccent,
indicatorSize: TabBarIndicatorSize.label,
tabs: [
Tab(icon: Icon(Icons.looks_one)),
Tab(icon: Icon(Icons.looks_two)),
],
),
),
body: TabBarView(
physics: const NeverScrollableScrollPhysics(),
children: [
AdvancedScreen(),
const BasicScreen(),
],
),
),
),
);
}
}
advanced.dart
import 'package:flutter/material.dart';
import 'scratch_box.dart';
const _googleIcon = 'assets/google.png';
const _dartIcon = 'assets/dart.png';
const _flutterIcon = 'assets/flutter.png';
class AdvancedScreen extends StatefulWidget {
@override
_AdvancedScreenState createState() => _AdvancedScreenState();
}
class _AdvancedScreenState extends State<AdvancedScreen>
with SingleTickerProviderStateMixin {
double validScratches = 0;
late AnimationController _animationController;
late Animation<double> _animation;
@override
void initState() {
_animationController = AnimationController(
vsync: this,
duration: const Duration(milliseconds: 1200),
)..addStatusListener(
(listener) {
if (listener == AnimationStatus.completed) {
_animationController.reverse();
}
},
);
_animation = Tween(begin: 1.0, end: 1.25).animate(
CurvedAnimation(
parent: _animationController,
curve: Curves.elasticIn,
),
);
super.initState();
}
@override
void dispose() {
_animationController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
const Text(
'Scratch To Win',
style: TextStyle(
fontFamily: 'The unseen',
color: Colors.blueAccent,
fontSize: 50,
fontWeight: FontWeight.bold,
),
),
const Text(
'Get it!',
style: TextStyle(
fontFamily: 'The unseen',
color: Colors.black,
fontSize: 20,
),
),
Container(
margin: const EdgeInsets.only(top: 10),
height: 1,
width: 300,
color: Colors.black12,
)
],
),
buildRow(_googleIcon, _flutterIcon, _googleIcon),
buildRow(_dartIcon, _flutterIcon, _googleIcon),
buildRow(_dartIcon, _flutterIcon, _dartIcon),
],
),
),
);
}
Widget buildRow(String left, String center, String right) {
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ScratchBox(image: left),
ScratchBox(
image: center,
animation: _animation,
onScratch: () {
setState(() {
validScratches++;
if (validScratches == 3) {
_animationController.forward();
}
});
},
),
ScratchBox(image: right),
],
);
}
}
basics.dart
import 'package:flutter/material.dart';
import 'package:scratcher/scratcher.dart';
class BasicScreen extends StatefulWidget {
const BasicScreen({super.key});
@override
_BasicScreenState createState() => _BasicScreenState();
}
class _BasicScreenState extends State<BasicScreen> {
double brushSize = 30;
double progress = 0;
bool thresholdReached = false;
bool enabled = true;
double? size;
final key = GlobalKey<ScratcherState>();
@override
Widget build(BuildContext context) {
return SafeArea(
child: Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
ElevatedButton(
child: const Text('Reset'),
onPressed: () {
key.currentState?.reset(
duration: const Duration(milliseconds: 2000),
);
setState(() => thresholdReached = false);
},
),
ElevatedButton(
child: const Text('Change size'),
onPressed: () {
setState(() {
if (size == null) {
size = 200;
} else if (size == 200) {
size = 0;
} else {
size = null;
}
});
},
),
ElevatedButton(
child: const Text('Reveal'),
onPressed: () {
key.currentState?.reveal(
duration: const Duration(milliseconds: 2000),
);
},
),
],
),
Column(
children: [
Text('Brush size (${brushSize.round()})'),
Slider(
value: brushSize,
onChanged: (v) => setState(() => brushSize = v),
min: 5,
max: 100,
),
],
),
CheckboxListTile(
value: enabled,
title: const Text('Scratcher enabled'),
onChanged: (e) => setState(() {
enabled = e ?? false;
}),
),
Expanded(
child: Stack(
children: [
SizedBox(
height: size,
width: size,
child: Scratcher(
key: key,
enabled: enabled,
brushSize: brushSize,
threshold: 30,
image: Image.asset('assets/background.jpg'),
onThreshold: () => setState(() => thresholdReached = true),
onChange: (value) {
setState(() {
progress = value;
});
},
onScratchStart: () {
print("Scratching has started");
},
onScratchUpdate: () {
print("Scratching in progress");
},
onScratchEnd: () {
print("Scratching has finished");
},
child: Container(
color: Colors.black,
alignment: Alignment.center,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: const[
Text(
'Scratch the screen!',
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 36,
fontWeight: FontWeight.bold,
color: Colors.amber,
),
),
SizedBox(height: 8),
Text(
'Photo by Fabian Wiktor from Pexels',
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.bold,
color: Colors.amber,
),
)
],
),
),
),
),
Positioned(
bottom: 0,
right: 0,
child: Container(
padding: const EdgeInsets.symmetric(
horizontal: 8,
vertical: 12,
),
color: Colors.black,
child: Text(
'${progress.floor().toString()}% '
'(${thresholdReached ? 'done' : 'pending'})',
textAlign: TextAlign.right,
style: const TextStyle(
color: Colors.white,
),
),
),
),
],
),
),
],
),
);
}
}
scratch_box.dart
import 'package:flutter/material.dart';
import 'package:scratcher/scratcher.dart';
class ScratchBox extends StatefulWidget {
ScratchBox({
required this.image,
this.onScratch,
this.animation,
});
final String image;
final VoidCallback? onScratch;
final Animation<double>? animation;
@override
_ScratchBoxState createState() => _ScratchBoxState();
}
class _ScratchBoxState extends State<ScratchBox> {
bool isScratched = false;
double opacity = 0.5;
@override
Widget build(BuildContext context) {
var icon = AnimatedOpacity(
opacity: opacity,
duration: const Duration(milliseconds: 750),
child: Image.asset(
widget.image,
width: 70,
height: 70,
fit: BoxFit.contain,
),
);
return Container(
width: 80,
height: 80,
margin: const EdgeInsets.all(10),
child: Scratcher(
accuracy: ScratchAccuracy.low,
color: Colors.blueGrey,
image: Image.asset('assets/scratch.png'),
brushSize: 15,
threshold: 60,
onThreshold: () {
setState(() {
opacity = 1;
isScratched = true;
});
widget.onScratch?.call();
},
child: Container(
child: widget.animation == null
? icon
: ScaleTransition(
scale: widget.animation!,
child: icon,
),
),
),
);
}
}