overlay_geometry.dart 6.25 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.

Ian Hickson's avatar
Ian Hickson committed
5
import 'package:flutter/gestures.dart';
6 7
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
8 9 10

class CardModel {
  CardModel(this.value, this.height, this.color);
11

12 13 14
  int value;
  double height;
  Color color;
15 16

  String get label => 'Card $value';
Hixie's avatar
Hixie committed
17
  Key get key => new ObjectKey(this);
18
  GlobalKey get targetKey => new GlobalObjectKey(this);
19 20 21 22
}

enum MarkerType { topLeft, bottomRight, touch }

23 24 25
class _MarkerPainter extends CustomPainter {
  const _MarkerPainter({
    this.size,
26
    this.type,
27
  });
28 29 30 31

  final double size;
  final MarkerType type;

32
  @override
Adam Barth's avatar
Adam Barth committed
33
  void paint(Canvas canvas, _) {
34 35
    final Paint paint = new Paint()..color = const Color(0x8000FF00);
    final double r = size / 2.0;
36
    canvas.drawCircle(new Offset(r, r), r, paint);
37

38 39
    paint
      ..color = const Color(0xFFFFFFFF)
40
      ..style = PaintingStyle.stroke
41
      ..strokeWidth = 1.0;
42
    if (type == MarkerType.topLeft) {
43 44
      canvas.drawLine(new Offset(r, r), new Offset(r + r - 1.0, r), paint);
      canvas.drawLine(new Offset(r, r), new Offset(r, r + r - 1.0), paint);
45 46
    }
    if (type == MarkerType.bottomRight) {
47 48
      canvas.drawLine(new Offset(r, r), new Offset(1.0, r), paint);
      canvas.drawLine(new Offset(r, r), new Offset(r, 1.0), paint);
49 50 51
    }
  }

52
  @override
53 54 55 56 57 58
  bool shouldRepaint(_MarkerPainter oldPainter) {
    return oldPainter.size != size
        || oldPainter.type != type;
  }
}

59
class Marker extends StatelessWidget {
60
  const Marker({
61
    Key key,
62 63 64 65 66
    this.type: MarkerType.touch,
    this.position,
    this.size: 40.0,
  }) : super(key: key);

67
  final Offset position;
68 69 70
  final double size;
  final MarkerType type;

71
  @override
72
  Widget build(BuildContext context) {
73
    return new Positioned(
74 75
      left: position.dx - size / 2.0,
      top: position.dy - size / 2.0,
76 77
      width: size,
      height: size,
78
      child: new IgnorePointer(
79 80 81
        child: new CustomPaint(
          painter: new _MarkerPainter(
            size: size,
82 83 84 85
            type: type,
          ),
        ),
      ),
86 87 88 89
    );
  }
}

90
class OverlayGeometryApp extends StatefulWidget {
91
  @override
92 93 94
  OverlayGeometryAppState createState() => new OverlayGeometryAppState();
}

95
typedef void CardTapCallback(GlobalKey targetKey, Offset globalPosition);
96

97
class CardBuilder extends SliverChildDelegate {
98 99 100 101
  CardBuilder({ this.cardModels, this.onTapUp });

  final List<CardModel> cardModels;
  final CardTapCallback onTapUp;
102 103

  static const TextStyle cardLabelStyle =
104
    const TextStyle(color: Colors.white, fontSize: 18.0, fontWeight: FontWeight.bold);
105

106
  @override
107
  Widget build(BuildContext context, int index) {
108 109
    if (index >= cardModels.length)
      return null;
110
    final CardModel cardModel = cardModels[index];
111 112
    return new GestureDetector(
      key: cardModel.key,
113
      onTapUp: (TapUpDetails details) { onTapUp(cardModel.targetKey, details.globalPosition); },
114 115 116 117 118 119
      child: new Card(
        key: cardModel.targetKey,
        color: cardModel.color,
        child: new Container(
          height: cardModel.height,
          padding: const EdgeInsets.all(8.0),
120 121 122
          child: new Center(child: new Text(cardModel.label, style: cardLabelStyle)),
        ),
      ),
123 124 125 126
    );
  }

  @override
127
  int get estimatedChildCount => cardModels.length;
128 129

  @override
130 131
  bool shouldRebuild(CardBuilder oldDelegate) {
    return oldDelegate.cardModels != cardModels;
132
  }
133 134 135
}

class OverlayGeometryAppState extends State<OverlayGeometryApp> {
136
  List<CardModel> cardModels;
137
  Map<MarkerType, Offset> markers = <MarkerType, Offset>{};
138
  double markersScrollOffset = 0.0;
139

140
  @override
141
  void initState() {
142
    super.initState();
143
    final List<double> cardHeights = <double>[
144 145
      48.0, 63.0, 82.0, 146.0, 60.0, 55.0, 84.0, 96.0, 50.0,
      48.0, 63.0, 82.0, 146.0, 60.0, 55.0, 84.0, 96.0, 50.0,
146
      48.0, 63.0, 82.0, 146.0, 60.0, 55.0, 84.0, 96.0, 50.0,
147
    ];
Hixie's avatar
Hixie committed
148
    cardModels = new List<CardModel>.generate(cardHeights.length, (int i) {
149
      final Color color = Color.lerp(Colors.red.shade300, Colors.blue.shade900, i / cardHeights.length);
150 151 152 153
      return new CardModel(i, cardHeights[i], color);
    });
  }

Adam Barth's avatar
Adam Barth committed
154
  bool handleScrollNotification(ScrollNotification notification) {
155
    if (notification is ScrollUpdateNotification && notification.depth == 0) {
156
      setState(() {
157
        final double dy = markersScrollOffset - notification.metrics.extentBefore;
158 159
        markersScrollOffset = notification.metrics.extentBefore;
        for (MarkerType type in markers.keys) {
160 161
          final Offset oldPosition = markers[type];
          markers[type] = oldPosition.translate(0.0, dy);
162 163 164 165
        }
      });
    }
    return false;
166 167
  }

168
  void handleTapUp(GlobalKey target, Offset globalPosition) {
169
    setState(() {
170
      markers[MarkerType.touch] = globalPosition;
171
      final RenderBox box = target.currentContext.findRenderObject();
172
      markers[MarkerType.topLeft] = box.localToGlobal(const Offset(0.0, 0.0));
173
      final Size size = box.size;
174
      markers[MarkerType.bottomRight] = box.localToGlobal(new Offset(size.width, size.height));
Adam Barth's avatar
Adam Barth committed
175
      final ScrollableState scrollable = Scrollable.of(target.currentContext);
176
      markersScrollOffset = scrollable.position.pixels;
177 178 179
    });
  }

180
  @override
181
  Widget build(BuildContext context) {
182
    final List<Widget> layers = <Widget>[
183
      new Scaffold(
184
        appBar: new AppBar(title: const Text('Tap a Card')),
185
        body: new Container(
186
          padding: const EdgeInsets.symmetric(vertical: 12.0, horizontal: 8.0),
Adam Barth's avatar
Adam Barth committed
187
          child: new NotificationListener<ScrollNotification>(
188 189 190 191 192 193 194 195 196 197
            onNotification: handleScrollNotification,
            child: new ListView.custom(
              childrenDelegate: new CardBuilder(
                cardModels: cardModels,
                onTapUp: handleTapUp,
              ),
            ),
          ),
        ),
      ),
198 199 200
    ];
    for (MarkerType type in markers.keys)
      layers.add(new Marker(type: type, position: markers[type]));
201
    return new Stack(children: layers);
202 203 204 205
  }
}

void main() {
Adam Barth's avatar
Adam Barth committed
206
  runApp(new MaterialApp(
207
    theme: new ThemeData(
208
      brightness: Brightness.light,
209
      primarySwatch: Colors.blue,
210
      accentColor: Colors.redAccent,
211 212
    ),
    title: 'Cards',
213
    home: new OverlayGeometryApp(),
214
  ));
215
}