From 3a0ce961cac3555e4d45f481b1db9fd825d95132 Mon Sep 17 00:00:00 2001 From: xster Date: Thu, 27 Apr 2017 15:52:41 -0700 Subject: [PATCH] lerp LinearGradient (#9628) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Implement * Don’t scale the fractional offsets since it’s easier to use * review notes * fix nit --- .../flutter/lib/src/painting/box_painter.dart | 53 +++++++++++++++- .../test/painting/box_painter_test.dart | 60 ++++++++++++++++++- 2 files changed, 109 insertions(+), 4 deletions(-) diff --git a/packages/flutter/lib/src/painting/box_painter.dart b/packages/flutter/lib/src/painting/box_painter.dart index c5bb2a2969..8ceb81a4e2 100644 --- a/packages/flutter/lib/src/painting/box_painter.dart +++ b/packages/flutter/lib/src/painting/box_painter.dart @@ -637,7 +637,10 @@ class LinearGradient extends Gradient { this.colors, this.stops, this.tileMode: TileMode.clamp - }); + }) : assert(begin != null), + assert(end != null), + assert(colors != null), + assert(tileMode != null); /// The offset from coordinate (0.0,0.0) at which stop 0.0 of the /// gradient is placed, in a coordinate space that maps the top left @@ -679,6 +682,54 @@ class LinearGradient extends Gradient { ); } + /// Returns a new [LinearGradient] with its properties scaled by the given factor. + LinearGradient scale(double factor) { + return new LinearGradient( + begin: begin, + end: end, + colors: colors.map((Color color) => Color.lerp(null, color, factor)).toList(), + stops: stops, + tileMode: tileMode, + ); + } + + /// Linearly interpolate between two [LinearGradient]s. + /// + /// If either gradient is null, this function linearly interpolates from a + /// a gradient that matches the other gradient in begin, end, stops and + /// tileMode and with the same colors but transparent. + static LinearGradient lerp(LinearGradient a, LinearGradient b, double t) { + if (a == null && b == null) + return null; + if (a == null) + return b.scale(t); + if (b == null) + return a.scale(1.0 - t); + // Interpolation is only possible when the lengths of colors and stops are + // the same or stops is null for one. + // TODO(xster): lerp unsimilar LinearGradients in the future by scaling + // lists of LinearGradients. + assert(a.colors.length == b.colors.length); + assert(a.stops == null || b.stops == null || a.stops.length == b.stops.length); + final List interpolatedColors = []; + for (int i = 0; i < a.colors.length; i++) + interpolatedColors.add(Color.lerp(a.colors[i], b.colors[i], t)); + List interpolatedStops; + if (a.stops != null && b.stops != null) { + for (int i = 0; i < a.stops.length; i++) + interpolatedStops.add(ui.lerpDouble(a.stops[i], b.stops[i], t)); + } else { + interpolatedStops = a.stops ?? b.stops; + } + return new LinearGradient( + begin: FractionalOffset.lerp(a.begin, b.begin, t), + end: FractionalOffset.lerp(a.end, b.end, t), + colors: interpolatedColors, + stops: interpolatedStops, + tileMode: t < 0.5 ? a.tileMode : b.tileMode, + ); + } + @override bool operator ==(dynamic other) { if (identical(this, other)) diff --git a/packages/flutter/test/painting/box_painter_test.dart b/packages/flutter/test/painting/box_painter_test.dart index bfba545ae7..4373036d3a 100644 --- a/packages/flutter/test/painting/box_painter_test.dart +++ b/packages/flutter/test/painting/box_painter_test.dart @@ -6,7 +6,7 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:flutter/painting.dart'; void main() { - test("BorderSide control test", () { + test('BorderSide control test', () { final BorderSide side1 = const BorderSide(); final BorderSide side2 = side1.copyWith( color: const Color(0xFF00FFFF), @@ -39,7 +39,7 @@ void main() { expect(interpolated.color, equals(side2.color.withOpacity(0.2))); }); - test("Border control test", () { + test('Border control test', () { final Border border1 = new Border.all(width: 4.0); final Border border2 = Border.lerp(null, border1, 0.25); final Border border3 = Border.lerp(border1, null, 0.25); @@ -54,7 +54,7 @@ void main() { expect(border4.left.width, equals(2.0)); }); - test("BoxShadow control test", () { + test('BoxShadow control test', () { final BoxShadow shadow1 = const BoxShadow(blurRadius: 4.0); final BoxShadow shadow2 = BoxShadow.lerp(null, shadow1, 0.25); final BoxShadow shadow3 = BoxShadow.lerp(shadow1, null, 0.25); @@ -76,4 +76,58 @@ void main() { [shadow2], [shadow3, shadow1], 0.5); expect(shadowList, equals([shadow4, shadow1.scale(0.5)])); }); + + test('LinearGradient scale test', () { + final LinearGradient testGradient = const LinearGradient( + begin: FractionalOffset.bottomRight, + end: const FractionalOffset(0.7, 1.0), + colors: const [ + const Color(0x00FFFFFF), + const Color(0x11777777), + const Color(0x44444444), + ], + ); + final LinearGradient actual = LinearGradient.lerp(null, testGradient, 0.25); + + expect(actual, const LinearGradient( + begin: FractionalOffset.bottomRight, + end: const FractionalOffset(0.7, 1.0), + colors: const [ + const Color(0x00FFFFFF), + const Color(0x04777777), + const Color(0x11444444), + ], + )); + }); + + test('LinearGradient lerp test', () { + final LinearGradient testGradient1 = const LinearGradient( + begin: FractionalOffset.topLeft, + end: FractionalOffset.bottomLeft, + colors: const [ + const Color(0x33333333), + const Color(0x66666666), + ], + ); + + final LinearGradient testGradient2 = const LinearGradient( + begin: FractionalOffset.topRight, + end: FractionalOffset.topLeft, + colors: const [ + const Color(0x44444444), + const Color(0x88888888), + ], + ); + final LinearGradient actual = + LinearGradient.lerp(testGradient1, testGradient2, 0.5); + + expect(actual, const LinearGradient( + begin: const FractionalOffset(0.5, 0.0), + end: const FractionalOffset(0.0, 0.5), + colors: const [ + const Color(0x3B3B3B3B), + const Color(0x77777777), + ], + )); + }); }