Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

I updated bubbles to act more like the apple watch UI #8

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# Bubble Lens

# A Fork from Bubble Lens
[![pub package](https://img.shields.io/pub/v/dart_jsonwebtoken.svg)](https://pub.dev/packages/bubble_lens)

A reproduction of the Apple Watch UI animation
Expand Down
335 changes: 188 additions & 147 deletions lib/bubble_lens.dart
Original file line number Diff line number Diff line change
@@ -1,148 +1,189 @@
import 'package:flutter/material.dart';
import 'dart:math';

class BubbleLens extends StatefulWidget {
final double width;
final double height;
final List<Widget> widgets;
final double size;
final double paddingX;
final double paddingY;
final Duration duration;
final Radius radius;
final double highRatio;
final double lowRatio;

const BubbleLens({
Key? key,
required this.width,
required this.height,
required this.widgets,
this.size = 100,
this.paddingX = 10,
this.paddingY = 0,
this.duration = const Duration(milliseconds: 100),
this.radius = const Radius.circular(999),
this.highRatio = 0,
this.lowRatio = 0
}) : super(key: key);

@override
BubbleLensState createState() => BubbleLensState();
}

class BubbleLensState extends State<BubbleLens> {
double _middleX = 0;
double _middleY = 0;
double _offsetX = 0;
double _offsetY = 0;
double _lastX = 0;
double _lastY = 0;
List _steps = [];
int _counter = 0;
int _total = 0;
int _lastTotal = 0;

double _minLeft = double.infinity;
double _maxLeft = double.negativeInfinity;
double _minTop = double.infinity;
double _maxTop = double.negativeInfinity;

@override
void initState() {
super.initState();
_middleX = widget.width / 2;
_middleY = widget.height / 2;
_offsetX = _middleX - widget.size / 2;
_offsetY = _middleY - widget.size / 2;
_lastX = 0;
_lastY = 0;
_steps = [
[-(widget.size / 2) + -(widget.paddingX / 2), -widget.size + -widget.paddingY],
[-widget.size + -widget.paddingX, 0],
[-(widget.size / 2) + -(widget.paddingX / 2), widget.size + widget.paddingY],
[(widget.size / 2) + (widget.paddingX / 2), widget.size + widget.paddingY],
[widget.size + widget.paddingX, 0],
[(widget.size / 2) + (widget.paddingX / 2), -widget.size + -widget.paddingY],
];
}

@override
void dispose() {
super.dispose();
}

@override
Widget build(BuildContext context) {
_counter = 0;
_total = 0;
return Container(
width: widget.width,
height: widget.height,
child: GestureDetector(
behavior: HitTestBehavior.opaque,
onPanUpdate: (details) {
double newOffsetX = max(_minLeft, min(_maxLeft, _offsetX + details.delta.dx));
double newOffsetY = max(_minTop, min(_maxTop, _offsetY + details.delta.dy));
if (newOffsetX != _offsetX || newOffsetY != _offsetY) {
setState(() {
_offsetX = newOffsetX;
_offsetY = newOffsetY;
});
}
},
child: Stack(
children: widget.widgets.map((item) {
int index = widget.widgets.indexOf(item);
double left;
double top;
if (index == 0) {
left = _offsetX;
top = _offsetY;
} else if (index - 1 == _total) {
left = (_counter + 1) * (widget.size + widget.paddingX) + _offsetX;
top = _offsetY;
_lastTotal = _total;
_counter++;
_total += _counter * 6;
} else {
List step = _steps[((index - _lastTotal - 2) / _counter % _steps.length).floor()];
left = _lastX + step[0];
top = _lastY + step[1];
}
_minLeft = min(_minLeft, -(left - _offsetX) + _middleX - (widget.size / 2));
_maxLeft = max(_maxLeft, left - _offsetX + _middleX - (widget.size / 2));
_minTop = min(_minTop, -(top - _offsetY) + _middleY - (widget.size / 2));
_maxTop = max(_maxTop, top - _offsetY + _middleY - (widget.size / 2));
_lastX = left;
_lastY = top;
double distance = sqrt(pow(_middleX - (left + widget.size / 2), 2) + pow(_middleY - (top + widget.size / 2), 2));
double size = widget.size / max(distance / widget.size, 1);
double scale = max(0, min(1, (size / widget.size * (1 + widget.highRatio + widget.lowRatio)) - widget.lowRatio));
return AnimatedPositioned(
duration: widget.duration,
top: top,
left: left,
child: Container(
alignment: Alignment.center,
width: widget.size,
height: widget.size,
child: Transform.scale(
scale: scale,
child: ClipRRect(
borderRadius: BorderRadius.all(widget.radius),
child: Container(
width: widget.size,
height: widget.size,
child: item,
),
),
)
)
);
}).toList()
),
),
);
}
import 'package:flutter/material.dart';
import 'dart:math';

class BubbleLens extends StatefulWidget {
final double width;
final double height;
final List<Widget> widgets;
final double size;
final double paddingX;
final double paddingY;
final Duration duration;
final Radius radius;
final bool fadeEdge;

BubbleLens({
Key? key,
required this.width,
required this.height,
required this.widgets,
this.fadeEdge = true,
this.size = 100,
this.paddingX = 10,
this.paddingY = 0,
this.duration = const Duration(milliseconds: 100),
this.radius = const Radius.circular(999),
}) : super(key: key);

@override
BubbleLensState createState() => BubbleLensState();
}

class BubbleLensState extends State<BubbleLens> {
double _middleX = 0;
double _middleY = 0;
double _offsetX = 0;
double _offsetY = 0;
double _lastX = 0;
double _lastY = 0;
List _steps = [];
int _counter = 0;
int _total = 0;
int _lastTotal = 0;

double _minLeft = double.infinity;
double _maxLeft = double.negativeInfinity;
double _minTop = double.infinity;
double _maxTop = double.negativeInfinity;

double get longerSide => max(widget.width, widget.height);

@override
void initState() {
super.initState();

_middleX = widget.width / 2;
_middleY = widget.height / 2;
_offsetX = _middleX - widget.size / 2;
_offsetY = _middleY - widget.size / 2;
_lastX = 0;
_lastY = 0;
_steps = [
[
-(widget.size / 2) + -(widget.paddingX / 2),
-widget.size + -widget.paddingY
],
[-widget.size + -widget.paddingX, 0],
[
-(widget.size / 2) + -(widget.paddingX / 2),
widget.size + widget.paddingY
],
[
(widget.size / 2) + (widget.paddingX / 2),
widget.size + widget.paddingY
],
[widget.size + widget.paddingX, 0],
[
(widget.size / 2) + (widget.paddingX / 2),
-widget.size + -widget.paddingY
],
];
}

@override
void dispose() {
super.dispose();
}

@override
Widget build(BuildContext context) {
_counter = 0;
_total = 0;
Widget bubbles = Container(
width: widget.width,
height: widget.height,
child: GestureDetector(
behavior: HitTestBehavior.opaque,
onPanUpdate: (details) {
double newOffsetX = max(
_minLeft, min(_maxLeft, _offsetX + details.delta.dx));
double newOffsetY = max(
_minTop, min(_maxTop, _offsetY + details.delta.dy));
if (newOffsetX != _offsetX || newOffsetY != _offsetY) {
setState(() {
_offsetX = newOffsetX;
_offsetY = newOffsetY;
});
}
},
child: Stack(
children: widget.widgets.map((item) {
int index = widget.widgets.indexOf(item);
double left;
double top;
if (index == 0) {
left = _offsetX;
top = _offsetY;
} else if (index - 1 == _total) {
left =
(_counter + 1) * (widget.size + widget.paddingX) + _offsetX;
top = _offsetY;
_lastTotal = _total;
_counter++;
_total += _counter * 6;
} else {
List step = _steps[((index - _lastTotal - 2) / _counter %
_steps.length).floor()];
left = _lastX + step[0];
top = _lastY + step[1];
}
_minLeft = min(
_minLeft, -(left - _offsetX) + _middleX - (widget.size / 2));
_maxLeft =
max(_maxLeft, left - _offsetX + _middleX - (widget.size / 2));
_minTop = min(
_minTop, -(top - _offsetY) + _middleY - (widget.size / 2));
_maxTop =
max(_maxTop, top - _offsetY + _middleY - (widget.size / 2));
_lastX = left;
_lastY = top;
double distance = sqrt(
pow(_middleX - (left + widget.size / 2), 2) +
pow(_middleY - (top + widget.size / 2), 2));
double distPercent = distance / (longerSide / 2);

double scale = .3 * log(distPercent * -1 + 1.2) + .93;
scale = max(.3, min(1, (scale)));
if (scale.toString() == double.nan.toString()) scale = .3;


double originX = min(
widget.size * 1, max(widget.size * -1, left - _middleX)) / -2;
double originY = min(
widget.size * 1, max(widget.size * -1, top - _middleY)) / -2;

double fadePercent = distPercent*.9;
double fadeValue = .9;
return AnimatedPositioned(
duration: widget.duration,
top: top,
left: left,
child: Container(
alignment: Alignment.center,
width: widget.size,
height: widget.size,
child: Transform.scale(
scale: scale,
origin: Offset(originX, originY),
child: ClipRRect(
borderRadius: BorderRadius.all(widget.radius),
child: Opacity(
opacity: (widget.fadeEdge && fadePercent>fadeValue) ? max(0, min(1, 1-(fadePercent-fadeValue)/(1-fadeValue))) : 1,
child: Container(
width: widget.size,
height: widget.size,
child: item,
),
),
),
)
)
);
}).toList()
),
),
);

return bubbles;
}
}