overlay_geometry.dart 6.11 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';
17 18
  Key get key => ObjectKey(this);
  GlobalKey get targetKey => 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
    final Paint paint = Paint()..color = const Color(0x8000FF00);
35
    final double r = size / 2.0;
36
    canvas.drawCircle(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(Offset(r, r), Offset(r + r - 1.0, r), paint);
      canvas.drawLine(Offset(r, r), Offset(r, r + r - 1.0), paint);
45 46
    }
    if (type == MarkerType.bottomRight) {
47 48
      canvas.drawLine(Offset(r, r), Offset(1.0, r), paint);
      canvas.drawLine(Offset(r, r), 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
    this.type = MarkerType.touch,
63
    this.position,
64
    this.size = 40.0,
65 66
  }) : 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 Positioned(
74 75
      left: position.dx - size / 2.0,
      top: position.dy - size / 2.0,
76 77
      width: size,
      height: size,
78 79 80
      child: IgnorePointer(
        child: CustomPaint(
          painter: _MarkerPainter(
81
            size: size,
82 83 84 85
            type: type,
          ),
        ),
      ),
86 87 88 89
    );
  }
}

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

95
typedef CardTapCallback = void Function(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
    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
    return GestureDetector(
112
      key: cardModel.key,
113
      onTapUp: (TapUpDetails details) { onTapUp(cardModel.targetKey, details.globalPosition); },
114
      child: Card(
115 116
        key: cardModel.targetKey,
        color: cardModel.color,
117
        child: Container(
118 119
          height: cardModel.height,
          padding: const EdgeInsets.all(8.0),
120
          child: Center(child: Text(cardModel.label, style: cardLabelStyle)),
121 122
        ),
      ),
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
    ];
148
    cardModels = List<CardModel>.generate(cardHeights.length, (int i) {
149
      final Color color = Color.lerp(Colors.red.shade300, Colors.blue.shade900, i / cardHeights.length);
150
      return CardModel(i, cardHeights[i], color);
151 152 153
    });
  }

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(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 184 185
      Scaffold(
        appBar: AppBar(title: const Text('Tap a Card')),
        body: Container(
186
          padding: const EdgeInsets.symmetric(vertical: 12.0, horizontal: 8.0),
187
          child: NotificationListener<ScrollNotification>(
188
            onNotification: handleScrollNotification,
189 190
            child: ListView.custom(
              childrenDelegate: CardBuilder(
191 192 193 194 195 196 197
                cardModels: cardModels,
                onTapUp: handleTapUp,
              ),
            ),
          ),
        ),
      ),
198 199
    ];
    for (MarkerType type in markers.keys)
200 201
      layers.add(Marker(type: type, position: markers[type]));
    return Stack(children: layers);
202 203 204 205
  }
}

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