touch_input.dart 4.59 KB
Newer Older
1 2 3 4
// Copyright 2015 The Chromium Authors. All rights reserved.
// 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 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
import 'package:flutter/rendering.dart';

// Material design colors. :p
List<Color> _kColors = <Color>[
  Colors.teal[500],
  Colors.amber[500],
  Colors.purple[500],
  Colors.lightBlue[500],
  Colors.deepPurple[500],
  Colors.lime[500],
];

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

  final Paint _paint;
  Point position = Point.origin;
  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 51 52 53
  bool get sizedByParent => true;

  /// By selecting the biggest value permitted by the incomming constraints
  /// 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 62 63 64
  bool hitTestSelf(Point position) => true;

  /// Processes pointer events by mutating state and invalidating its previous
  /// painting commands.
65
  @override
66 67 68 69
  void handleEvent(PointerEvent event, BoxHitTestEntry entry) {
    if (event is PointerDownEvent) {
      Color color = _kColors[event.pointer.remainder(_kColors.length)];
      _dots[event.pointer] = new 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 79 80 81 82 83 84
      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.
85
  @override
86 87
  void paint(PaintingContext context, Offset offset) {
    final Canvas canvas = context.canvas;
88 89 90 91 92 93
    // The "size" property indicates the size of that this render box was
    // alotted 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.
94
    canvas.drawRect(offset & size, new Paint()..color = const Color(0xFFFFFFFF));
95 96

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

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

  // Finally, we attach the render tree we've built to the screen.
133 134
  new RenderingFlutterBinding(root: stack);
}