Skip to content

Commit 3ada502

Browse files
authored
Pan and zoom gallery demo (flutter#25164)
Adds the "2D Transformations" demo to the gallery, which shows how to do things such as navigate around a map a la Google Maps, or show a full screen zoomable photo. The idea is to abstract this code into a first class widget soon.
1 parent 423cf22 commit 3ada502

8 files changed

+1191
-0
lines changed

examples/flutter_gallery/lib/demo/all.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,5 +12,6 @@ export 'images_demo.dart';
1212
export 'material/material.dart';
1313
export 'pesto_demo.dart';
1414
export 'shrine_demo.dart';
15+
export 'transformations/transformations_demo.dart';
1516
export 'typography_demo.dart';
1617
export 'video_demo.dart';
Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
import 'dart:ui' show Vertices;
2+
import 'package:flutter/material.dart';
3+
import 'transformations_demo_board.dart';
4+
import 'transformations_demo_edit_board_point.dart';
5+
import 'transformations_demo_gesture_transformable.dart';
6+
7+
class TransformationsDemo extends StatefulWidget {
8+
const TransformationsDemo({ Key key }) : super(key: key);
9+
10+
static const String routeName = '/transformations';
11+
12+
@override _TransformationsDemoState createState() => _TransformationsDemoState();
13+
}
14+
class _TransformationsDemoState extends State<TransformationsDemo> {
15+
// The radius of a hexagon tile in pixels.
16+
static const double _kHexagonRadius = 32.0;
17+
// The margin between hexagons.
18+
static const double _kHexagonMargin = 1.0;
19+
// The radius of the entire board in hexagons, not including the center.
20+
static const int _kBoardRadius = 8;
21+
22+
bool _reset = false;
23+
Board _board = Board(
24+
boardRadius: _kBoardRadius,
25+
hexagonRadius: _kHexagonRadius,
26+
hexagonMargin: _kHexagonMargin,
27+
);
28+
29+
@override
30+
Widget build (BuildContext context) {
31+
final BoardPainter painter = BoardPainter(
32+
board: _board,
33+
);
34+
35+
// The scene is drawn by a CustomPaint, but user interaction is handled by
36+
// the GestureTransformable parent widget.
37+
return Scaffold(
38+
appBar: AppBar(),
39+
body: LayoutBuilder(
40+
builder: (BuildContext context, BoxConstraints constraints) {
41+
// Draw the scene as big as is available, but allow the user to
42+
// translate beyond that to a visibleSize that's a bit bigger.
43+
final Size size = Size(constraints.maxWidth, constraints.maxHeight);
44+
final Size visibleSize = Size(size.width * 3, size.height * 2);
45+
return GestureTransformable(
46+
reset: _reset,
47+
onResetEnd: () {
48+
setState(() {
49+
_reset = false;
50+
});
51+
},
52+
child: CustomPaint(
53+
painter: painter,
54+
),
55+
boundaryRect: Rect.fromLTWH(
56+
-visibleSize.width / 2,
57+
-visibleSize.height / 2,
58+
visibleSize.width,
59+
visibleSize.height,
60+
),
61+
// Center the board in the middle of the screen. It's drawn centered
62+
// at the origin, which is the top left corner of the
63+
// GestureTransformable.
64+
initialTranslation: Offset(size.width / 2, size.height / 2),
65+
onTapUp: _onTapUp,
66+
size: size,
67+
);
68+
},
69+
),
70+
floatingActionButton: _board.selected == null ? resetButton : editButton,
71+
);
72+
}
73+
74+
FloatingActionButton get resetButton {
75+
return FloatingActionButton(
76+
onPressed: () {
77+
setState(() {
78+
_reset = true;
79+
});
80+
},
81+
tooltip: 'Reset Transform',
82+
backgroundColor: Theme.of(context).primaryColor,
83+
child: const Icon(Icons.home),
84+
);
85+
}
86+
87+
FloatingActionButton get editButton {
88+
return FloatingActionButton(
89+
onPressed: () {
90+
if (_board.selected == null) {
91+
return;
92+
}
93+
showModalBottomSheet<Widget>(context: context, builder: (BuildContext context) {
94+
return Container(
95+
width: double.infinity,
96+
height: 150,
97+
padding: const EdgeInsets.all(12.0),
98+
child: EditBoardPoint(
99+
boardPoint: _board.selected,
100+
onColorSelection: (Color color) {
101+
setState(() {
102+
_board = _board.copyWithBoardPointColor(_board.selected, color);
103+
Navigator.pop(context);
104+
});
105+
},
106+
),
107+
);
108+
});
109+
},
110+
tooltip: 'Edit Tile',
111+
child: const Icon(Icons.edit),
112+
);
113+
}
114+
115+
void _onTapUp(TapUpDetails details) {
116+
final Offset scenePoint = details.globalPosition;
117+
final BoardPoint boardPoint = _board.pointToBoardPoint(scenePoint);
118+
setState(() {
119+
_board = _board.copyWithSelected(boardPoint);
120+
});
121+
}
122+
}
123+
124+
// CustomPainter is what is passed to CustomPaint and actually draws the scene
125+
// when its `paint` method is called.
126+
class BoardPainter extends CustomPainter {
127+
const BoardPainter({
128+
this.board,
129+
});
130+
131+
final Board board;
132+
133+
@override
134+
void paint(Canvas canvas, Size size) {
135+
void drawBoardPoint(BoardPoint boardPoint) {
136+
final Color color = boardPoint.color.withOpacity(
137+
board.selected == boardPoint ? 0.2 : 1.0,
138+
);
139+
final Vertices vertices = board.getVerticesForBoardPoint(boardPoint, color);
140+
canvas.drawVertices(vertices, BlendMode.color, Paint());
141+
}
142+
143+
board.forEach(drawBoardPoint);
144+
}
145+
146+
// We should repaint whenever the board changes, such as board.selected.
147+
@override
148+
bool shouldRepaint(BoardPainter oldDelegate) {
149+
return oldDelegate.board != board;
150+
}
151+
}

0 commit comments

Comments
 (0)