I find this interaction very interesting and more consistent with the user experience than other validations.

It only implements the front-end action logic and verifies that it is dragging to the correct area.

Real man-machine recognition has not been achieved

The logic is simple

1: randomly generate the position of slider button and answer area.

2: Drag the process to verify whether in the answer area, if in the answer area turns green.

3: Drag end, verify whether in the answer area; In: return successful not: perform slider return animation.

(Jigsaw slider verification code)

Here is the github address. If you think it will help you, please don’t be too cheap

The main code is just over 100 lines

import 'dart:math'; import 'dart:ui'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:shack/widget/dotted_border/r_dotted_line_border.dart'; Class DemoVerity extends StatefulWidget {final Function lister; class DemoVerity extends StatefulWidget {final Function lister; DemoVerity({required this.lister}); @override _DemoVerityState createState() => _DemoVerityState(); } class _DemoVerityState extends State<DemoVerity> with TickerProviderStateMixin {final double radius = 32.0; Offset offsetCtrInit = Offset. Zero; OffsetCtr = offset.zero; offsetCtr = offset.zero; /// Offset offsetAwe = Offset. Zero; late AnimationController anwerAnimationController; late AnimationController animationController; /// late final Animation<double> moveAnimation; /// Late final Animation<double> scaleAnimation; // Double get distance => (offsetawe-offsetctr).distance; Bool success = false; @override void initState() { super.initState(); animationController = AnimationController(vsync: this, duration: Duration(milliseconds: 200)); animationController.addStatusListener((status) { if (status == AnimationStatus.completed) { animationController.reset();  setState(() { offsetCtr = offsetCtrInit; }); }}); MoveAnimation = Tween<double>(begin: 0.0, end: 1.0). Animate (Parent: animationController, curve: Const Cubic(0.68, 0, 0, 1.5),); anwerAnimationController = AnimationController( vsync: this, duration: const Duration(milliseconds: 800)); ScaleAnimation = Tween < double > (the begin: 1.0, end: 1.3). The animate (CurvedAnimation (parent: anwerAnimationController, curve: Curves.fastOutSlowIn, ), ); WidgetsBinding.instance! .addPostFrameCallback((timeStamp) { final size = context.size ?? Size.zero; Final x1 = radius + Random().nextint ((sie.width - radius * 2.2).toint ()); final x1 = radius + Random().nextint ((sie.width - radius * 2.2).toint ()); final y1 = radius + Random().nextInt(30); Final x2 = radius + Random().nextint ((sie.width - radius * 2.6).toint ()); Final y2 = sie.height * 0.7 + Random().nextint ((sie.height * 0.3).toint ()) - radius * 1.2 - MediaQuery.of(context).padding.bottom; setState(() { offsetCtr = Offset(x1, y1); offsetCtrInit = offsetCtr; offsetAwe = Offset(x2, y2); }); anwerAnimationController.repeat(reverse: true); }); } @override void dispose() { animationController.dispose(); anwerAnimationController.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return SizedBox( width: double.infinity, child: Stack( clipBehavior: Clip. None, children: [/// / Answer area of c-space (top: offsetawe. dy-radius, left: offsetawe. dx-radius, child: ScaleTransition( scale: scaleAnimation, child: Container( width: radius * 2, height: radius * 2, alignment: const Alignment(0, 0), decoration: BoxDecoration( border: RDottedLineBorder.all( width: 1, color: success ? Colors.green : Colors.blue), color: success ? const Color(0xffe9faef) : const Color(0xffe9f5fe), shape: BoxShape.circle, ), child: success ? const SizedBox() : const Icon(Icons.add, size: 20, color: Color.blue),),),),), /// Slider AnimatedBuilder(Animation: animationController, Builder: (_, child) { return Positioned( left: offsetCtr.dx - ((offsetCtr.dx - offsetCtrInit.dx) * moveAnimation.value) - radius, top: offsetCtr.dy - ((offsetCtr.dy - offsetCtrInit.dy) * moveAnimation.value) - radius, child: child!, ); }, child: GestureDetector( onPanUpdate: (DragUpdateDetails details) {/// Answer radius * 0.9 final rDistance = radius * 0.9; /// Answer area if ((distance < rDistance) && ! Success) {success = true; / / vibration HapticFeedback mediumImpact (); anwerAnimationController. Stop (); AnwerAnimationController. AnimateTo (1.0, duration: If ((distance >= rDistance) && success) {success = false; if (distance >= rDistance) {success = false (!anwerAnimationController.isAnimating) anwerAnimationController.repeat(reverse: true); } if (!mounted) return; setState(() { offsetCtr += Offset(details.delta.dx, details.delta.dy); }); }, onPanEnd: (DragEndDetails details) {// debugPrint('longer >>> $SUCCESS '); if (! Success) { animationController.forward(); } widget.lister(success); }, child: Container( width: radius * 2, height: radius * 2, padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: const Color(0xff016df3), shape: BoxShape.circle, boxShadow: const <BoxShadow>[ BoxShadow( color: Color(0xFF616161), offset: Offset (4.0, 4.0), blurRadius: 8.0),),), the child: Image. The asset (' assets/img/safe_icon. JPG),),),),,,); }}Copy the code