overlay_geometry.dart 6.47 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
import 'dart:io';

import 'package:flutter/foundation.dart';
Ian Hickson's avatar
Ian Hickson committed
8
import 'package:flutter/gestures.dart';
9 10
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
11 12 13

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

15 16 17
  int value;
  double height;
  Color color;
18 19

  String get label => 'Card $value';
20 21
  Key get key => ObjectKey(this);
  GlobalKey get targetKey => GlobalObjectKey(this);
22 23 24 25
}

enum MarkerType { topLeft, bottomRight, touch }

26 27 28
class _MarkerPainter extends CustomPainter {
  const _MarkerPainter({
    this.size,
29
    this.type,
30
  });
31 32 33 34

  final double size;
  final MarkerType type;

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

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

55
  @override
56 57 58 59 60 61
  bool shouldRepaint(_MarkerPainter oldPainter) {
    return oldPainter.size != size
        || oldPainter.type != type;
  }
}

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

70
  final Offset position;
71 72 73
  final double size;
  final MarkerType type;

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

93
class OverlayGeometryApp extends StatefulWidget {
94
  @override
95
  OverlayGeometryAppState createState() => OverlayGeometryAppState();
96 97
}

98
typedef CardTapCallback = void Function(GlobalKey targetKey, Offset globalPosition);
99

100
class CardBuilder extends SliverChildDelegate {
101 102 103 104
  CardBuilder({ this.cardModels, this.onTapUp });

  final List<CardModel> cardModels;
  final CardTapCallback onTapUp;
105 106

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

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

  @override
130
  int get estimatedChildCount => cardModels.length;
131 132

  @override
133 134
  bool shouldRebuild(CardBuilder oldDelegate) {
    return oldDelegate.cardModels != cardModels;
135
  }
136 137 138
}

class OverlayGeometryAppState extends State<OverlayGeometryApp> {
139
  List<CardModel> cardModels;
140
  Map<MarkerType, Offset> markers = <MarkerType, Offset>{};
141
  double markersScrollOffset = 0.0;
142

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

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

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

183
  @override
184
  Widget build(BuildContext context) {
185 186 187 188 189 190 191 192 193 194 195 196 197
    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,
                ),
198 199 200 201
              ),
            ),
          ),
        ),
202 203 204 205
        for (MarkerType type in markers.keys)
          Marker(type: type, position: markers[type]),
      ],
    );
206 207 208 209
  }
}

void main() {
210 211 212 213 214 215
  if (Platform.isMacOS) {
    // TODO(gspencergoog): Update this when TargetPlatform includes macOS. https://github.com/flutter/flutter/issues/31366
    // See https://github.com/flutter/flutter/wiki/Desktop-shells#target-platform-override
    debugDefaultTargetPlatformOverride = TargetPlatform.fuchsia;
  }

216 217
  runApp(MaterialApp(
    theme: ThemeData(
218
      brightness: Brightness.light,
219
      primarySwatch: Colors.blue,
220
      accentColor: Colors.redAccent,
221 222
    ),
    title: 'Cards',
223
    home: OverlayGeometryApp(),
224
  ));
225
}