| // 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. |
| |
| // This example shows how to use process input events in the underlying render |
| // tree. |
| |
| import 'package:flutter/material.dart'; // Imported just for its color palette. |
| import 'package:flutter/rendering.dart'; |
| |
| // Material design colors. :p |
| List<Color> _kColors = <Color>[ |
| Colors.teal, |
| Colors.amber, |
| Colors.purple, |
| Colors.lightBlue, |
| Colors.deepPurple, |
| Colors.lime, |
| ]; |
| |
| /// A simple model object for a dot that reacts to pointer pressure. |
| class Dot { |
| Dot({ required Color color }) : _paint = Paint()..color = color; |
| |
| final Paint _paint; |
| Offset position = Offset.zero; |
| double radius = 0.0; |
| |
| void update(PointerEvent event) { |
| position = event.position; |
| radius = 5 + (95 * event.pressure); |
| } |
| |
| void paint(Canvas canvas, Offset offset) { |
| canvas.drawCircle(position + offset, radius, _paint); |
| } |
| } |
| |
| /// A render object that draws dots under each pointer. |
| class RenderDots extends RenderBox { |
| RenderDots(); |
| |
| /// State to remember which dots to paint. |
| final Map<int, Dot> _dots = <int, Dot>{}; |
| |
| /// Indicates that the size of this render object depends only on the |
| /// layout constraints provided by the parent. |
| @override |
| bool get sizedByParent => true; |
| |
| /// By selecting the biggest value permitted by the incoming constraints |
| /// during layout, this function makes this render object as large as |
| /// possible (i.e., fills the entire screen). |
| @override |
| void performResize() { |
| size = constraints.biggest; |
| } |
| |
| /// Makes this render object hittable so that it receives pointer events. |
| @override |
| bool hitTestSelf(Offset position) => true; |
| |
| /// Processes pointer events by mutating state and invalidating its previous |
| /// painting commands. |
| @override |
| void handleEvent(PointerEvent event, BoxHitTestEntry entry) { |
| if (event is PointerDownEvent) { |
| final Color color = _kColors[event.pointer.remainder(_kColors.length)]; |
| _dots[event.pointer] = Dot(color: color)..update(event); |
| // We call markNeedsPaint to indicate that our painting commands have |
| // changed and that paint needs to be called before displaying a new frame |
| // to the user. It's harmless to call markNeedsPaint multiple times |
| // because the render tree will ignore redundant calls. |
| markNeedsPaint(); |
| } else if (event is PointerUpEvent || event is PointerCancelEvent) { |
| _dots.remove(event.pointer); |
| markNeedsPaint(); |
| } else if (event is PointerMoveEvent) { |
| _dots[event.pointer]!.update(event); |
| markNeedsPaint(); |
| } |
| } |
| |
| /// Issues new painting commands. |
| @override |
| void paint(PaintingContext context, Offset offset) { |
| final Canvas canvas = context.canvas; |
| // The "size" property indicates the size of that this render box was |
| // allotted during layout. Here we paint our bounds white. Notice that we're |
| // located at "offset" from the origin of the canvas' coordinate system. |
| // Passing offset during the render tree's paint walk is an optimization to |
| // avoid having to change the origin of the canvas's coordinate system too |
| // often. |
| canvas.drawRect(offset & size, Paint()..color = const Color(0xFFFFFFFF)); |
| |
| // We iterate through our model and paint each dot. |
| for (final Dot dot in _dots.values) { |
| dot.paint(canvas, offset); |
| } |
| } |
| } |
| |
| void main() { |
| // Create some styled text to tell the user to interact with the app. |
| final RenderParagraph paragraph = RenderParagraph( |
| const TextSpan( |
| style: TextStyle(color: Colors.black87), |
| text: 'Touch me!', |
| ), |
| textDirection: TextDirection.ltr, |
| ); |
| // A stack is a render object that layers its children on top of each other. |
| // The bottom later is our RenderDots object, and on top of that we show the |
| // text. |
| final RenderStack stack = RenderStack( |
| textDirection: TextDirection.ltr, |
| children: <RenderBox>[ |
| RenderDots(), |
| paragraph, |
| ], |
| ); |
| // The "parentData" field of a render object is controlled by the render |
| // object's parent render object. Now that we've added the paragraph as a |
| // child of the RenderStack, the paragraph's parentData field has been |
| // populated with a StackParentData, which we can use to provide input to the |
| // stack's layout algorithm. |
| // |
| // We use the StackParentData of the paragraph to position the text in the top |
| // left corner of the screen. |
| final StackParentData paragraphParentData = paragraph.parentData! as StackParentData; |
| paragraphParentData |
| ..top = 40.0 |
| ..left = 20.0; |
| |
| // Finally, we attach the render tree we've built to the screen. |
| RenderingFlutterBinding(root: stack).scheduleFrame(); |
| } |