overlay_geometry.dart 6.03 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 11 12 13 14

class CardModel {
  CardModel(this.value, this.height, this.color);
  int value;
  double height;
  Color color;
  String get label => "Card $value";
Hixie's avatar
Hixie committed
15
  Key get key => new ObjectKey(this);
16
  GlobalKey get targetKey => new GlobalObjectKey(this);
17 18 19 20
}

enum MarkerType { topLeft, bottomRight, touch }

21 22 23 24 25
class _MarkerPainter extends CustomPainter {
  const _MarkerPainter({
    this.size,
    this.type
  });
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 33 34 35
    Paint paint = new Paint()..color = const Color(0x8000FF00);
    double r = size / 2.0;
    canvas.drawCircle(new Point(r, r), r, paint);

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

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 59 60 61 62 63 64 65 66 67 68
  Marker({
    this.type: MarkerType.touch,
    this.position,
    this.size: 40.0,
    Key key
  }) : super(key: key);

  final Point position;
  final double size;
  final MarkerType type;

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

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

93 94 95 96 97 98 99
typedef void CardTapCallback(Key targetKey, Point globalPosition);

class CardBuilder extends LazyBlockDelegate {
  CardBuilder({ this.cardModels, this.onTapUp });

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

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

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

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

  @override
  double estimateTotalExtent(int firstIndex, int lastIndex, double minOffset, double firstStartOffset, double lastEndOffset) {
    return (lastEndOffset - minOffset) * cardModels.length / (lastIndex + 1);
  }
133 134 135
}

class OverlayGeometryAppState extends State<OverlayGeometryApp> {
136 137
  List<CardModel> cardModels;
  Map<MarkerType, Point> markers = new Map<MarkerType, Point>();
138
  double markersScrollOffset = 0.0;
139 140
  ScrollListener scrollListener;

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

155
  void handleScroll(double offset) {
156
    setState(() {
157 158
      double dy = markersScrollOffset - offset;
      markersScrollOffset = offset;
159 160 161 162 163 164 165
      for (MarkerType type in markers.keys) {
        Point oldPosition = markers[type];
        markers[type] = new Point(oldPosition.x, oldPosition.y + dy);
      }
    });
  }

166
  void handleTapUp(GlobalKey target, Point globalPosition) {
167
    setState(() {
168
      markers[MarkerType.touch] = globalPosition;
169 170 171 172
      final RenderBox box = target.currentContext.findRenderObject();
      markers[MarkerType.topLeft] = box.localToGlobal(new Point(0.0, 0.0));
      final Size size = box.size;
      markers[MarkerType.bottomRight] = box.localToGlobal(new Point(size.width, size.height));
173
      final ScrollableState scrollable = Scrollable.of(target.currentContext);
174 175 176 177
      markersScrollOffset = scrollable.scrollOffset;
    });
  }

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

void main() {
Adam Barth's avatar
Adam Barth committed
202
  runApp(new MaterialApp(
203
    theme: new ThemeData(
204
      brightness: Brightness.light,
205 206 207 208
      primarySwatch: Colors.blue,
      accentColor: Colors.redAccent[200]
    ),
    title: 'Cards',
209
    home: new OverlayGeometryApp()
210
  ));
211
}