overlay_geometry.dart 6.05 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
import 'package:flutter/material.dart';
6 7 8

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

10 11 12
  int value;
  double height;
  Color color;
13 14

  String get label => 'Card $value';
15 16
  Key get key => ObjectKey(this);
  GlobalKey get targetKey => GlobalObjectKey(this);
17 18 19 20
}

enum MarkerType { topLeft, bottomRight, touch }

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

  final double size;
  final MarkerType type;

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

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

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

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

65
  final Offset position;
66 67 68
  final double size;
  final MarkerType type;

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

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

93
typedef CardTapCallback = void Function(GlobalKey targetKey, Offset globalPosition);
94

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

  final List<CardModel> cardModels;
  final CardTapCallback onTapUp;
100 101

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

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

  @override
125
  int get estimatedChildCount => cardModels.length;
126 127

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

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

138
  @override
139
  void initState() {
140
    super.initState();
141
    final List<double> cardHeights = <double>[
142 143
      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,
144
      48.0, 63.0, 82.0, 146.0, 60.0, 55.0, 84.0, 96.0, 50.0,
145
    ];
146
    cardModels = List<CardModel>.generate(cardHeights.length, (int i) {
147
      final Color color = Color.lerp(Colors.red.shade300, Colors.blue.shade900, i / cardHeights.length);
148
      return CardModel(i, cardHeights[i], color);
149 150 151
    });
  }

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

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

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

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