Skip to content

Commit 763bb62

Browse files
committed
add pinned position support
1 parent fa2f67d commit 763bb62

File tree

4 files changed

+217
-5
lines changed

4 files changed

+217
-5
lines changed

example/home.dart

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import 'guideline.dart';
1313
import 'horizontal_list.dart';
1414
import 'margin.dart';
1515
import 'percentage_layout.dart';
16+
import 'pinned_position.dart';
1617
import 'relative_id.dart';
1718
import 'self_wrap_content.dart';
1819
import 'staggered_grid.dart';
@@ -38,6 +39,7 @@ class ExampleHome extends StatelessWidget {
3839
'Vertical List': const VerticalListExample(),
3940
'Staggered Grid': const StaggeredGridExample(),
4041
'Circle Position': const CirclePositionExample(),
42+
'Pinned Position': const PinnedPositionExample(),
4143
'Self wrapContent': const SelfWrapContentExample(),
4244
'Margin': const MarginExample(),
4345
'Charts': const ChartsExample(),

example/pinned_position.dart

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import 'package:flutter/material.dart';
2+
import 'package:flutter_constraintlayout/src/constraint_layout.dart';
3+
4+
import 'custom_app_bar.dart';
5+
6+
class PinnedPositionExample extends StatelessWidget {
7+
const PinnedPositionExample({Key? key}) : super(key: key);
8+
9+
@override
10+
Widget build(BuildContext context) {
11+
ConstraintId anchor = ConstraintId('anchor');
12+
return Scaffold(
13+
appBar: const CustomAppBar(
14+
title: 'Pinned Position',
15+
codePath: 'example/pinned_position.dart',
16+
),
17+
body: ConstraintLayout(
18+
children: [
19+
Container(
20+
color: Colors.yellow,
21+
).applyConstraint(
22+
id: anchor,
23+
size: 200,
24+
centerTo: parent,
25+
),
26+
Container(
27+
color: Colors.cyan,
28+
).applyConstraint(
29+
size: 40,
30+
pinnedInfo: PinnedInfo(
31+
anchor,
32+
PinnedPos(0, PinnedType.absolute, 0.5, PinnedType.percent),
33+
PinnedPos(0.5, PinnedType.percent, 0.5, PinnedType.percent),
34+
),
35+
)
36+
],
37+
),
38+
);
39+
}
40+
}

lib/src/constraint_layout.dart

Lines changed: 173 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -459,6 +459,7 @@ extension ConstrainedWidgetsExt on Widget {
459459
double? widthHeightRatio,
460460
bool? ratioBaseOnWidth,
461461
int? eIndex,
462+
PinnedInfo? pinnedInfo,
462463
}) {
463464
return Constrained(
464465
key: key,
@@ -524,6 +525,7 @@ extension ConstrainedWidgetsExt on Widget {
524525
centerBottomCenterTo: centerBottomCenterTo,
525526
centerBottomRightTo: centerBottomRightTo,
526527
eIndex: eIndex,
528+
pinnedInfo: pinnedInfo,
527529
),
528530
child: this,
529531
);
@@ -934,6 +936,95 @@ class ConstraintDefine {
934936
int get hashCode => id.hashCode;
935937
}
936938

939+
enum PinnedType {
940+
absolute,
941+
percent,
942+
}
943+
944+
class PinnedPos {
945+
double xOffset;
946+
PinnedType xType;
947+
double yOffset;
948+
PinnedType yType;
949+
950+
PinnedPos(this.xOffset, this.xType, this.yOffset, this.yType);
951+
952+
void checkBounds(double value, PinnedType pinnedType, double base) {
953+
if (pinnedType == PinnedType.absolute) {
954+
assert(value >= 0 && value <= base);
955+
} else {
956+
assert(value >= 0 && value <= 1);
957+
}
958+
}
959+
960+
Offset resolve(Size size) {
961+
assert(() {
962+
checkBounds(xOffset, xType, size.width);
963+
checkBounds(yOffset, yType, size.height);
964+
return true;
965+
}());
966+
double x;
967+
double y;
968+
if (xType == PinnedType.absolute) {
969+
x = xOffset;
970+
} else {
971+
x = xOffset * size.width;
972+
}
973+
if (yType == PinnedType.absolute) {
974+
y = yOffset;
975+
} else {
976+
y = yOffset * size.height;
977+
}
978+
return Offset(x, y);
979+
}
980+
981+
@override
982+
bool operator ==(Object other) =>
983+
identical(this, other) ||
984+
other is PinnedPos &&
985+
runtimeType == other.runtimeType &&
986+
xOffset == other.xOffset &&
987+
xType == other.xType &&
988+
yOffset == other.yOffset &&
989+
yType == other.yType;
990+
991+
@override
992+
int get hashCode =>
993+
xOffset.hashCode ^ xType.hashCode ^ yOffset.hashCode ^ yType.hashCode;
994+
}
995+
996+
class PinnedInfo {
997+
/// [0,360]
998+
int rotateDegree;
999+
ConstraintId anchorId;
1000+
PinnedPos selfPos;
1001+
PinnedPos targetPos;
1002+
1003+
PinnedInfo(
1004+
this.anchorId,
1005+
this.selfPos,
1006+
this.targetPos, {
1007+
this.rotateDegree = 0,
1008+
});
1009+
1010+
@override
1011+
bool operator ==(Object other) =>
1012+
identical(this, other) ||
1013+
other is PinnedInfo &&
1014+
runtimeType == other.runtimeType &&
1015+
rotateDegree == other.rotateDegree &&
1016+
anchorId == other.anchorId &&
1017+
selfPos == other.selfPos &&
1018+
targetPos == other.targetPos;
1019+
1020+
@override
1021+
int get hashCode =>
1022+
rotateDegree.hashCode ^
1023+
anchorId.hashCode ^
1024+
selfPos.hashCode ^
1025+
targetPos.hashCode;
1026+
}
1027+
9371028
class Constraint extends ConstraintDefine {
9381029
/// 'wrap_content'、'match_parent'、'match_constraint'、'48, etc'
9391030
/// 'match_parent' will be converted to the base constraints
@@ -1073,6 +1164,8 @@ class Constraint extends ConstraintDefine {
10731164

10741165
final int? eIndex;
10751166

1167+
final PinnedInfo? pinnedInfo;
1168+
10761169
Constraint({
10771170
ConstraintId? id,
10781171
this.width = wrapContent,
@@ -1135,6 +1228,7 @@ class Constraint extends ConstraintDefine {
11351228
this.widthHeightRatio,
11361229
this.ratioBaseOnWidth,
11371230
this.eIndex,
1231+
this.pinnedInfo,
11381232
}) : super(id);
11391233

11401234
@override
@@ -1201,7 +1295,8 @@ class Constraint extends ConstraintDefine {
12011295
maxHeight == other.maxHeight &&
12021296
widthHeightRatio == other.widthHeightRatio &&
12031297
ratioBaseOnWidth == other.ratioBaseOnWidth &&
1204-
eIndex == other.eIndex;
1298+
eIndex == other.eIndex &&
1299+
pinnedInfo == other.pinnedInfo;
12051300

12061301
@override
12071302
int get hashCode =>
@@ -1264,7 +1359,8 @@ class Constraint extends ConstraintDefine {
12641359
maxHeight.hashCode ^
12651360
widthHeightRatio.hashCode ^
12661361
ratioBaseOnWidth.hashCode ^
1267-
eIndex.hashCode;
1362+
eIndex.hashCode ^
1363+
pinnedInfo.hashCode;
12681364

12691365
bool checkSize(double size) {
12701366
if (size == matchParent || size == wrapContent || size == matchConstraint) {
@@ -1801,6 +1897,25 @@ class Constraint extends ConstraintDefine {
18011897
needsReorderEventOrder = true;
18021898
}
18031899

1900+
if (parentData.pinnedInfo != pinnedInfo) {
1901+
if (parentData.pinnedInfo == null || pinnedInfo == null) {
1902+
needsRecalculateConstraints = true;
1903+
needsLayout = true;
1904+
} else {
1905+
if (parentData.pinnedInfo!.anchorId != pinnedInfo!.anchorId) {
1906+
needsRecalculateConstraints = true;
1907+
needsLayout = true;
1908+
} else if (parentData.pinnedInfo!.selfPos != pinnedInfo!.selfPos ||
1909+
parentData.pinnedInfo!.targetPos != pinnedInfo!.targetPos) {
1910+
needsLayout = true;
1911+
} else if (parentData.pinnedInfo!.rotateDegree !=
1912+
pinnedInfo!.rotateDegree) {
1913+
needsPaint = true;
1914+
}
1915+
}
1916+
parentData.pinnedInfo = pinnedInfo;
1917+
}
1918+
18041919
if (needsLayout) {
18051920
AbstractNode? targetParent = renderObject.parent;
18061921
if (needsRecalculateConstraints) {
@@ -1875,6 +1990,7 @@ class _ConstraintBoxData extends ContainerBoxParentData<RenderBox> {
18751990
double? widthHeightRatio;
18761991
bool? ratioBaseOnWidth;
18771992
int? eIndex;
1993+
PinnedInfo? pinnedInfo;
18781994

18791995
// for internal use
18801996
late Map<ConstraintId, _ConstrainedNode> _constrainedNodeMap;
@@ -2227,6 +2343,9 @@ class _ConstraintRenderBox extends RenderBox
22272343
if (child is _BarrierRenderBox) {
22282344
constraintsIdSet.addAll(childParentData._referencedIds!);
22292345
}
2346+
if (childParentData.pinnedInfo != null) {
2347+
constraintsIdSet.add(childParentData.pinnedInfo!.anchorId);
2348+
}
22302349
child = childParentData.nextSibling;
22312350
}
22322351

@@ -2265,6 +2384,18 @@ class _ConstraintRenderBox extends RenderBox
22652384
/// Each child element must have complete constraints both horizontally and vertically
22662385
static void _debugCheckConstraintsIntegrity(List<_ConstrainedNode> nodeList) {
22672386
for (final element in nodeList) {
2387+
if (element.pinnedInfo != null) {
2388+
if (element.width != wrapContent && element.width < 0) {
2389+
throw ConstraintLayoutException(
2390+
'When setting pinnedInfo, width and height must be wrapContent or fixed size.');
2391+
}
2392+
if (element.height != wrapContent && element.height < 0) {
2393+
throw ConstraintLayoutException(
2394+
'When setting pinnedInfo, width and height must be wrapContent or fixed size.');
2395+
}
2396+
continue;
2397+
}
2398+
22682399
/// Check constraint integrity in the horizontal direction
22692400
if (element.width == wrapContent || element.width >= 0) {
22702401
if (element.leftConstraint == null && element.rightConstraint == null) {
@@ -2492,6 +2623,11 @@ class _ConstraintRenderBox extends RenderBox
24922623
currentNode.baselineAlignType = childParentData.baseline!.type;
24932624
}
24942625

2626+
if (childParentData.pinnedInfo != null) {
2627+
currentNode.pinnedConstraint = _getConstrainedNodeForChild(
2628+
childParentData.pinnedInfo!.anchorId, childIndex);
2629+
}
2630+
24952631
child = childParentData.nextSibling;
24962632
}
24972633

@@ -2814,7 +2950,7 @@ class _ConstraintRenderBox extends RenderBox
28142950
} else if (sizeConfirmedChild.rightConstraint != null) {
28152951
childSpanWidth += size.width - sizeConfirmedChild.getRight();
28162952
} else {
2817-
/// It is not possible to execute this branch
2953+
childSpanWidth += sizeConfirmedChild.getX();
28182954
}
28192955

28202956
if (sizeConfirmedChild.topConstraint != null &&
@@ -2824,7 +2960,7 @@ class _ConstraintRenderBox extends RenderBox
28242960
} else if (sizeConfirmedChild.bottomConstraint != null) {
28252961
childSpanHeight += size.height - sizeConfirmedChild.getBottom();
28262962
} else {
2827-
/// It is not possible to execute this branch
2963+
childSpanHeight += sizeConfirmedChild.getY();
28282964
}
28292965

28302966
if (childSpanWidth > contentWidth) {
@@ -3190,6 +3326,18 @@ class _ConstraintRenderBox extends RenderBox
31903326
}
31913327

31923328
Offset calculateChildOffset(_ConstrainedNode node) {
3329+
if (node.pinnedInfo != null) {
3330+
PinnedInfo pinnedInfo = node.pinnedInfo!;
3331+
Offset selfOffset = pinnedInfo.selfPos.resolve(node.renderBox!.size);
3332+
Offset targetOffset =
3333+
pinnedInfo.targetPos.resolve(node.pinnedConstraint!.getSize(this));
3334+
double offsetX =
3335+
node.pinnedConstraint!.getX() + targetOffset.dx - selfOffset.dx;
3336+
double offsetY =
3337+
node.pinnedConstraint!.getY() + targetOffset.dy - selfOffset.dy;
3338+
return Offset(offsetX, offsetY);
3339+
}
3340+
31933341
EdgeInsets margin = node.margin;
31943342
EdgeInsets goneMargin = node.goneMargin;
31953343
double offsetX = 0;
@@ -3730,6 +3878,7 @@ class _ConstrainedNode {
37303878
_ConstrainedNode? rightConstraint;
37313879
_ConstrainedNode? bottomConstraint;
37323880
_ConstrainedNode? baselineConstraint;
3881+
_ConstrainedNode? pinnedConstraint;
37333882
_AlignType? leftAlignType;
37343883
_AlignType? topAlignType;
37353884
_AlignType? rightAlignType;
@@ -3823,6 +3972,8 @@ class _ConstrainedNode {
38233972

38243973
Size? get helperSize => parentData._helperSize;
38253974

3975+
PinnedInfo? get pinnedInfo => parentData.pinnedInfo;
3976+
38263977
set helperSize(Size? size) {
38273978
parentData._helperSize = size;
38283979
}
@@ -3886,6 +4037,13 @@ class _ConstrainedNode {
38864037
return getY() + getMeasuredHeight();
38874038
}
38884039

4040+
Size getSize([RenderBox? parent]) {
4041+
if (isParent()) {
4042+
return parent!.size;
4043+
}
4044+
return renderBox!.size;
4045+
}
4046+
38894047
double getMeasuredWidth() {
38904048
if (isGuideline || isBarrier) {
38914049
return helperSize!.width;
@@ -3975,6 +4133,9 @@ class _ConstrainedNode {
39754133
if (baselineConstraint != null)
39764134
getDepthFor(baselineConstraint!, parentSizeConfirmed, resolvedWidth,
39774135
resolvedHeight),
4136+
if (pinnedConstraint != null)
4137+
getDepthFor(pinnedConstraint!, parentSizeConfirmed, resolvedWidth,
4138+
resolvedHeight),
39784139
];
39794140
depth = getMaxDepth(list) + 1;
39804141
}
@@ -4051,6 +4212,13 @@ class _ConstrainedNode {
40514212
map['baselineConstraint'] = baselineConstraint!.toJson();
40524213
}
40534214
}
4215+
if (pinnedConstraint != null) {
4216+
if (pinnedConstraint!.isParent()) {
4217+
map['pinnedConstraint'] = 'parent';
4218+
} else {
4219+
map['pinnedConstraint'] = pinnedConstraint!.toJson();
4220+
}
4221+
}
40544222
}
40554223
map['depth'] = getDepth(null, null, null);
40564224
return map;
@@ -4090,6 +4258,7 @@ class _HelperBox extends RenderBox {
40904258
constraintBoxData.widthHeightRatio = null;
40914259
constraintBoxData.ratioBaseOnWidth = null;
40924260
constraintBoxData.eIndex = null;
4261+
constraintBoxData.pinnedInfo = null;
40934262
constraintBoxData._direction = null;
40944263
constraintBoxData._referencedIds = null;
40954264
constraintBoxData._isGuideline = false;

pubspec.yaml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,4 +38,5 @@ flutter:
3838
- example/circle_position.dart
3939
- example/self_wrap_content.dart
4040
- example/margin.dart
41-
- example/charts.dart
41+
- example/charts.dart
42+
- example/pinned_position.dart

0 commit comments

Comments
 (0)