@Google #Flutter Tutorial for Beginners #127: Fun with Scratcher in Flutter


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,
          ),
        ),
      ),
    );
  }
}

Leave a Reply

Your email address will not be published.