| // Copyright 2014 The Flutter Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| import 'package:flutter/material.dart'; |
| |
| class _GesturePainter extends CustomPainter { |
| const _GesturePainter({ |
| required this.zoom, |
| required this.offset, |
| required this.swatch, |
| required this.forward, |
| required this.scaleEnabled, |
| required this.tapEnabled, |
| required this.doubleTapEnabled, |
| required this.longPressEnabled, |
| }); |
| |
| final double zoom; |
| final Offset offset; |
| final MaterialColor swatch; |
| final bool forward; |
| final bool scaleEnabled; |
| final bool tapEnabled; |
| final bool doubleTapEnabled; |
| final bool longPressEnabled; |
| |
| @override |
| void paint(Canvas canvas, Size size) { |
| final Offset center = size.center(Offset.zero) * zoom + offset; |
| final double radius = size.width / 2.0 * zoom; |
| final Gradient gradient = RadialGradient( |
| colors: forward |
| ? <Color>[swatch.shade50, swatch.shade900] |
| : <Color>[swatch.shade900, swatch.shade50], |
| ); |
| final Paint paint = Paint() |
| ..shader = gradient.createShader(Rect.fromCircle( |
| center: center, |
| radius: radius, |
| )); |
| canvas.drawCircle(center, radius, paint); |
| } |
| |
| @override |
| bool shouldRepaint(_GesturePainter oldPainter) { |
| return oldPainter.zoom != zoom |
| || oldPainter.offset != offset |
| || oldPainter.swatch != swatch |
| || oldPainter.forward != forward |
| || oldPainter.scaleEnabled != scaleEnabled |
| || oldPainter.tapEnabled != tapEnabled |
| || oldPainter.doubleTapEnabled != doubleTapEnabled |
| || oldPainter.longPressEnabled != longPressEnabled; |
| } |
| } |
| |
| class GestureDemo extends StatefulWidget { |
| const GestureDemo({super.key}); |
| |
| @override |
| GestureDemoState createState() => GestureDemoState(); |
| } |
| |
| class GestureDemoState extends State<GestureDemo> { |
| |
| late Offset _startingFocalPoint; |
| |
| late Offset _previousOffset; |
| Offset _offset = Offset.zero; |
| |
| late double _previousZoom; |
| double _zoom = 1.0; |
| |
| static const List<MaterialColor> kSwatches = <MaterialColor>[ |
| Colors.red, |
| Colors.pink, |
| Colors.purple, |
| Colors.deepPurple, |
| Colors.indigo, |
| Colors.blue, |
| Colors.lightBlue, |
| Colors.cyan, |
| Colors.green, |
| Colors.lightGreen, |
| Colors.lime, |
| Colors.yellow, |
| Colors.amber, |
| Colors.orange, |
| Colors.deepOrange, |
| Colors.brown, |
| Colors.grey, |
| Colors.blueGrey, |
| ]; |
| int _swatchIndex = 0; |
| MaterialColor _swatch = kSwatches.first; |
| MaterialColor get swatch => _swatch; |
| |
| bool _forward = true; |
| bool _scaleEnabled = true; |
| bool _tapEnabled = true; |
| bool _doubleTapEnabled = true; |
| bool _longPressEnabled = true; |
| |
| void _handleScaleStart(ScaleStartDetails details) { |
| setState(() { |
| _startingFocalPoint = details.focalPoint; |
| _previousOffset = _offset; |
| _previousZoom = _zoom; |
| }); |
| } |
| |
| void _handleScaleUpdate(ScaleUpdateDetails details) { |
| setState(() { |
| _zoom = _previousZoom * details.scale; |
| |
| // Ensure that item under the focal point stays in the same place despite zooming |
| final Offset normalizedOffset = (_startingFocalPoint - _previousOffset) / _previousZoom; |
| _offset = details.focalPoint - normalizedOffset * _zoom; |
| }); |
| } |
| |
| void _handleScaleReset() { |
| setState(() { |
| _zoom = 1.0; |
| _offset = Offset.zero; |
| }); |
| } |
| |
| void _handleColorChange() { |
| setState(() { |
| _swatchIndex += 1; |
| if (_swatchIndex == kSwatches.length) { |
| _swatchIndex = 0; |
| } |
| _swatch = kSwatches[_swatchIndex]; |
| }); |
| } |
| |
| void _handleDirectionChange() { |
| setState(() { |
| _forward = !_forward; |
| }); |
| } |
| |
| @override |
| Widget build(BuildContext context) { |
| return Stack( |
| fit: StackFit.expand, |
| children: <Widget>[ |
| GestureDetector( |
| onScaleStart: _scaleEnabled ? _handleScaleStart : null, |
| onScaleUpdate: _scaleEnabled ? _handleScaleUpdate : null, |
| onTap: _tapEnabled ? _handleColorChange : null, |
| onDoubleTap: _doubleTapEnabled ? _handleScaleReset : null, |
| onLongPress: _longPressEnabled ? _handleDirectionChange : null, |
| child: CustomPaint( |
| painter: _GesturePainter( |
| zoom: _zoom, |
| offset: _offset, |
| swatch: swatch, |
| forward: _forward, |
| scaleEnabled: _scaleEnabled, |
| tapEnabled: _tapEnabled, |
| doubleTapEnabled: _doubleTapEnabled, |
| longPressEnabled: _longPressEnabled, |
| ), |
| ), |
| ), |
| Positioned( |
| bottom: 0.0, |
| left: 0.0, |
| child: Card( |
| child: Container( |
| padding: const EdgeInsets.all(4.0), |
| child: Column( |
| crossAxisAlignment: CrossAxisAlignment.start, |
| children: <Widget>[ |
| Row( |
| children: <Widget>[ |
| Checkbox( |
| value: _scaleEnabled, |
| onChanged: (bool? value) { setState(() { _scaleEnabled = value!; }); }, |
| ), |
| const Text('Scale'), |
| ], |
| ), |
| Row( |
| children: <Widget>[ |
| Checkbox( |
| value: _tapEnabled, |
| onChanged: (bool? value) { setState(() { _tapEnabled = value!; }); }, |
| ), |
| const Text('Tap'), |
| ], |
| ), |
| Row( |
| children: <Widget>[ |
| Checkbox( |
| value: _doubleTapEnabled, |
| onChanged: (bool? value) { setState(() { _doubleTapEnabled = value!; }); }, |
| ), |
| const Text('Double Tap'), |
| ], |
| ), |
| Row( |
| children: <Widget>[ |
| Checkbox( |
| value: _longPressEnabled, |
| onChanged: (bool? value) { setState(() { _longPressEnabled = value!; }); }, |
| ), |
| const Text('Long Press'), |
| ], |
| ), |
| ], |
| ), |
| ), |
| ), |
| ), |
| ], |
| ); |
| } |
| } |
| |
| void main() { |
| runApp(MaterialApp( |
| theme: ThemeData.dark(), |
| home: Scaffold( |
| appBar: AppBar(title: const Text('Gestures Demo')), |
| body: const GestureDemo(), |
| ), |
| )); |
| } |