touch_input.dart 4.69 KB
Newer Older
Ian Hickson's avatar
Ian Hickson committed
1
// Copyright 2014 The Flutter Authors. All rights reserved.
2 3 4
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

5 6 7 8
// 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.
9 10 11 12
import 'package:flutter/rendering.dart';

// Material design colors. :p
List<Color> _kColors = <Color>[
13 14 15 16 17 18
  Colors.teal,
  Colors.amber,
  Colors.purple,
  Colors.lightBlue,
  Colors.deepPurple,
  Colors.lime,
19 20 21 22
];

/// A simple model object for a dot that reacts to pointer pressure.
class Dot {
23
  Dot({ required Color color }) : _paint = Paint()..color = color;
24 25

  final Paint _paint;
26
  Offset position = Offset.zero;
27 28 29 30 31 32 33 34 35 36 37 38 39
  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.
40 41
class RenderDots extends RenderBox {
  RenderDots();
42 43 44 45

  /// State to remember which dots to paint.
  final Map<int, Dot> _dots = <int, Dot>{};

46 47
  /// Indicates that the size of this render object depends only on the
  /// layout constraints provided by the parent.
48
  @override
49 50
  bool get sizedByParent => true;

51
  /// By selecting the biggest value permitted by the incoming constraints
52 53
  /// during layout, this function makes this render object as large as
  /// possible (i.e., fills the entire screen).
54
  @override
55 56 57 58
  void performResize() {
    size = constraints.biggest;
  }

59
  /// Makes this render object hittable so that it receives pointer events.
60
  @override
61
  bool hitTestSelf(Offset position) => true;
62 63 64

  /// Processes pointer events by mutating state and invalidating its previous
  /// painting commands.
65
  @override
66 67
  void handleEvent(PointerEvent event, BoxHitTestEntry entry) {
    if (event is PointerDownEvent) {
68
      final Color color = _kColors[event.pointer.remainder(_kColors.length)];
69
      _dots[event.pointer] = Dot(color: color)..update(event);
70 71 72 73
      // 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.
74 75 76 77 78
      markNeedsPaint();
    } else if (event is PointerUpEvent || event is PointerCancelEvent) {
      _dots.remove(event.pointer);
      markNeedsPaint();
    } else if (event is PointerMoveEvent) {
79
      _dots[event.pointer]!.update(event);
80 81 82 83 84
      markNeedsPaint();
    }
  }

  /// Issues new painting commands.
85
  @override
86 87
  void paint(PaintingContext context, Offset offset) {
    final Canvas canvas = context.canvas;
88
    // The "size" property indicates the size of that this render box was
Josh Soref's avatar
Josh Soref committed
89
    // allotted during layout. Here we paint our bounds white. Notice that we're
90 91 92 93
    // 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.
94
    canvas.drawRect(offset & size, Paint()..color = const Color(0xFFFFFFFF));
95 96

    // We iterate through our model and paint each dot.
97
    for (final Dot dot in _dots.values) {
98
      dot.paint(canvas, offset);
99
    }
100 101 102 103
  }
}

void main() {
104
  // Create some styled text to tell the user to interact with the app.
105
  final RenderParagraph paragraph = RenderParagraph(
106
    const TextSpan(
107
      style: TextStyle(color: Colors.black87),
108
      text: 'Touch me!',
Ian Hickson's avatar
Ian Hickson committed
109 110
    ),
    textDirection: TextDirection.ltr,
111
  );
112 113 114
  // 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.
115
  final RenderStack stack = RenderStack(
116
    textDirection: TextDirection.ltr,
117
    children: <RenderBox>[
118
      RenderDots(),
119
      paragraph,
Ian Hickson's avatar
Ian Hickson committed
120
    ],
121
  );
122 123 124 125 126 127 128 129
  // 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.
130
  final StackParentData paragraphParentData = paragraph.parentData! as StackParentData;
131 132 133
  paragraphParentData
    ..top = 40.0
    ..left = 20.0;
134 135

  // Finally, we attach the render tree we've built to the screen.
136
  RenderingFlutterBinding(root: stack).scheduleFrame();
137
}