Commit 8cf44b6bca41627770b80193f44827b27159ba40

Authored by 刘宏哲
1 parent 3f8522dc

feat(app): update ui

... ... @@ -395,7 +395,7 @@ class _MonthGrid extends StatelessWidget {
dayBuilder: (context, day) => Column(
mainAxisSize: MainAxisSize.min,
children: [
ActivityBurnRing(report: day, size: 32),
ActivityBurnRing(report: day, size: 32, animate: true),
const SizedBox(height: 4),
Text(
day.date.day.toString(),
... ...
... ... @@ -51,11 +51,27 @@ class _ActivityBurnRingState extends State<ActivityBurnRing>
_animation = _RingProgressTween(begin: from, end: target).animate(
CurvedAnimation(parent: _controller, curve: Curves.easeInOutQuad),
);
_controller.duration =
widget.animate ? const Duration(seconds: 1) : Duration.zero;
_controller.duration = _animationDuration(from: from, target: target);
_controller.forward(from: 0);
}
Duration _animationDuration({
required _RingProgress from,
required _RingProgress target,
}) {
if (!widget.animate) return Duration.zero;
final visualDelta = _RingProgressTween.maximumVisualDelta(from, target);
if (visualDelta == 0) return Duration.zero;
// A tiny drawable change should not spend a full second creeping forward.
final milliseconds = (1000 * visualDelta.clamp(0, 1))
.round()
.clamp(150, 1000)
.toInt();
return Duration(milliseconds: milliseconds);
}
@override
void dispose() {
_controller.dispose();
... ... @@ -116,12 +132,101 @@ class _RingProgress {
class _RingProgressTween extends Tween<_RingProgress> {
_RingProgressTween({required super.begin, required super.end});
static final _activeMinimumProgress = _minimumProgress(
radius: _ActivityBurnRingPainter._outerRadius,
width: _ActivityBurnRingPainter._ringWidth,
);
static final _exerciseMinimumProgress = _minimumProgress(
radius: _ActivityBurnRingPainter._middleRadius,
width: _ActivityBurnRingPainter._ringWidth,
);
static final _standMinimumProgress = _minimumProgress(
radius: _ActivityBurnRingPainter._innerRadius,
width: _ActivityBurnRingPainter._ringWidth,
);
@override
_RingProgress lerp(double t) => _RingProgress(
active: begin!.active + (end!.active - begin!.active) * t,
exercise: begin!.exercise + (end!.exercise - begin!.exercise) * t,
stand: begin!.stand + (end!.stand - begin!.stand) * t,
active: _lerpProgress(
begin!.active,
end!.active,
t,
minimumProgress: _activeMinimumProgress,
),
exercise: _lerpProgress(
begin!.exercise,
end!.exercise,
t,
minimumProgress: _exerciseMinimumProgress,
),
stand: _lerpProgress(
begin!.stand,
end!.stand,
t,
minimumProgress: _standMinimumProgress,
),
);
static double _minimumProgress({
required double radius,
required double width,
}) =>
math.asin((width / 2) / radius) / math.pi;
static double maximumVisualDelta(
_RingProgress from,
_RingProgress target,
) =>
math.max(
_visualDelta(
from.active,
target.active,
_activeMinimumProgress,
),
math.max(
_visualDelta(
from.exercise,
target.exercise,
_exerciseMinimumProgress,
),
_visualDelta(
from.stand,
target.stand,
_standMinimumProgress,
),
),
);
static double _visualDelta(
double begin,
double end,
double minimumProgress,
) {
if (begin == 0 && end > 0) {
return end - math.min(end, minimumProgress);
}
return (_visibleProgress(begin, minimumProgress) -
_visibleProgress(end, minimumProgress))
.abs();
}
static double _visibleProgress(double value, double minimumProgress) {
if (value <= 0 || value >= 1) return value;
return math.max(value, minimumProgress).toDouble();
}
double _lerpProgress(
double begin,
double end,
double t, {
required double minimumProgress,
}) {
if (begin == 0 && end > 0) {
final visualStart = math.min(end, minimumProgress);
return visualStart + (end - visualStart) * t;
}
return begin + (end - begin) * t;
}
}
class _ActivityBurnRingPainter extends CustomPainter {
... ... @@ -163,7 +268,6 @@ class _ActivityBurnRingPainter extends CustomPainter {
center: center,
radius: _outerRadius * scale,
width: _ringWidth * scale,
trackColor: _track,
progressColor: _active,
progress: progress.active,
startAngle: startAngle,
... ... @@ -173,7 +277,6 @@ class _ActivityBurnRingPainter extends CustomPainter {
center: center,
radius: _middleRadius * scale,
width: _ringWidth * scale,
trackColor: _innerTrack,
progressColor: _exercise,
progress: progress.exercise,
startAngle: startAngle,
... ... @@ -183,7 +286,6 @@ class _ActivityBurnRingPainter extends CustomPainter {
center: center,
radius: _innerRadius * scale,
width: _ringWidth * scale,
trackColor: _center,
progressColor: _stand,
progress: progress.stand,
startAngle: startAngle,
... ... @@ -248,7 +350,6 @@ class _ActivityBurnRingPainter extends CustomPainter {
required Offset center,
required double radius,
required double width,
required Color trackColor,
required Color progressColor,
required double progress,
required double startAngle,
... ... @@ -329,6 +430,10 @@ class _ActivityBurnRingPainter extends CustomPainter {
/// Matches the home-page ring: shrink the butt-capped arc, then add two
/// circular caps so that rounded ends never consume the remaining gap.
///
/// A non-zero arc shorter than the diameter of its caps is rendered at the
/// minimum visible length. This avoids the merged cap's center drifting
/// clockwise while the value is animated from zero.
void _drawPartialRingWithRoundedCaps(
Canvas canvas, {
required Rect rect,
... ... @@ -337,7 +442,12 @@ class _ActivityBurnRingPainter extends CustomPainter {
required double progress,
required Paint paint,
}) {
final sweep = (math.pi * 2 * progress.clamp(0, 1)).toDouble();
final visibleProgress = _minimumVisibleProgress(
progress: progress,
radius: radius,
width: paint.strokeWidth,
);
final sweep = math.pi * 2 * visibleProgress;
if (sweep <= 0) return;
final capAngle = math.asin((paint.strokeWidth / 2) / radius);
... ... @@ -371,11 +481,29 @@ class _ActivityBurnRingPainter extends CustomPainter {
required double width,
required double progress,
}) {
final sweep = (math.pi * 2 * progress.clamp(0, 1)).toDouble();
final visibleProgress = _minimumVisibleProgress(
progress: progress,
radius: radius,
width: width,
);
final sweep = math.pi * 2 * visibleProgress;
final capAngle = math.asin((width / 2) / radius);
return startAngle + sweep - math.min(capAngle, sweep / 2).toDouble();
}
double _minimumVisibleProgress({
required double progress,
required double radius,
required double width,
}) {
final clampedProgress = progress.clamp(0, 1).toDouble();
if (clampedProgress <= 0) return 0;
final capAngle = math.asin((width / 2) / radius);
final minimumProgress = capAngle / math.pi;
return math.max(clampedProgress, minimumProgress).toDouble();
}
Offset _pointOnRing(Offset center, double radius, double angle) => Offset(
center.dx + math.cos(angle) * radius,
center.dy + math.sin(angle) * radius,
... ...
... ... @@ -372,7 +372,7 @@ class _DayRing extends StatelessWidget {
),
),
const SizedBox(height: 4),
ActivityBurnRing(report: report, size: 32),
ActivityBurnRing(report: report, size: 32, animate: true),
const SizedBox(height: 3),
Text(
report.date.day.toString(),
... ...