@@ -79,8 +79,7 @@ class Checkbox extends StatefulWidget {
79
79
///
80
80
/// When the checkbox is tapped, if [tristate] is false (the default) then
81
81
/// the [onChanged] callback will be applied to `!value` . If [tristate] is
82
- /// true this callback will be applied to false if the current [value]
83
- /// is true, false otherwise.
82
+ /// true this callback cycle from false to true to null.
84
83
///
85
84
/// The callback provided to [onChanged] should update the state of the parent
86
85
/// [StatefulWidget] using the [State.setState] method, so that the parent
@@ -195,7 +194,7 @@ class _RenderCheckbox extends RenderToggleable {
195
194
Color inactiveColor,
196
195
ValueChanged <bool > onChanged,
197
196
@required TickerProvider vsync,
198
- }): _showDash = value == null ,
197
+ }): _oldValue = value,
199
198
super (
200
199
value: value,
201
200
tristate: tristate,
@@ -206,18 +205,54 @@ class _RenderCheckbox extends RenderToggleable {
206
205
vsync: vsync,
207
206
);
208
207
209
- bool _showDash ;
208
+ bool _oldValue ;
210
209
211
210
@override
212
211
set value (bool newValue) {
213
- final bool oldValue = value;
214
- if (newValue == oldValue)
212
+ if (newValue == value)
215
213
return ;
216
- _showDash = newValue == null || newValue == false && oldValue == null ;
214
+ _oldValue = value ;
217
215
super .value = newValue;
218
216
}
219
217
218
+ // The square outer bounds of the checkbox at t, with the specified origin.
219
+ // At t == 0.0, the outer rect's size is _kEdgeSize (Checkbox.width)
220
+ // At t == 0.5, .. is _kEdgeSize - _kStrokeWidth
221
+ // At t == 1.0, .. is _kEdgeSize
222
+ RRect _outerRectAt (Offset origin, double t) {
223
+ final double inset = 1.0 - (t - 0.5 ).abs () * 2.0 ;
224
+ final double size = _kEdgeSize - inset * _kStrokeWidth;
225
+ final Rect rect = new Rect .fromLTWH (origin.dx + inset, origin.dy + inset, size, size);
226
+ return new RRect .fromRectAndRadius (rect, _kEdgeRadius);
227
+ }
228
+
229
+ // The checkbox's border color if value == false, or its fill color when
230
+ // value == true or null.
231
+ Color _colorAt (double t) {
232
+ // As t goes from 0.0 to 0.25, animate from the inactiveColor to activeColor.
233
+ return onChanged == null
234
+ ? inactiveColor
235
+ : (t >= 0.25 ? activeColor : Color .lerp (inactiveColor, activeColor, t * 4.0 ));
236
+ }
237
+
238
+ // White stroke used to paint the check and dash.
239
+ void _initStrokePaint (Paint paint) {
240
+ paint
241
+ ..color = const Color (0xFFFFFFFF )
242
+ ..style = PaintingStyle .stroke
243
+ ..strokeWidth = _kStrokeWidth;
244
+ }
245
+
246
+ void _drawBorder (Canvas canvas, RRect outer, double t, Paint paint) {
247
+ assert (t >= 0.0 && t <= 0.5 );
248
+ final double size = outer.width;
249
+ // As t goes from 0.0 to 1.0, gradually fill the outer RRect.
250
+ final RRect inner = outer.deflate (math.min (size / 2.0 , _kStrokeWidth + size * t));
251
+ canvas.drawDRRect (outer, inner, paint);
252
+ }
253
+
220
254
void _drawCheck (Canvas canvas, Offset origin, double t, Paint paint) {
255
+ assert (t >= 0.0 && t <= 1.0 );
221
256
// As t goes from 0.0 to 1.0, animate the two checkmark strokes from the
222
257
// mid point outwards.
223
258
final Path path = new Path ();
@@ -233,6 +268,7 @@ class _RenderCheckbox extends RenderToggleable {
233
268
}
234
269
235
270
void _drawDash (Canvas canvas, Offset origin, double t, Paint paint) {
271
+ assert (t >= 0.0 && t <= 1.0 );
236
272
// As t goes from 0.0 to 1.0, animate the horizontal line from the
237
273
// mid point outwards.
238
274
const Offset start = const Offset (_kEdgeSize * 0.2 , _kEdgeSize * 0.5 );
@@ -249,33 +285,52 @@ class _RenderCheckbox extends RenderToggleable {
249
285
paintRadialReaction (canvas, offset, size.center (Offset .zero));
250
286
251
287
final Offset origin = offset + (size / 2.0 - const Size .square (_kEdgeSize) / 2.0 );
252
- final double t = position.value ;
253
- final Color borderColor = (onChanged == null )
254
- ? inactiveColor
255
- : (t >= 0.25 ? activeColor : Color . lerp (inactiveColor, activeColor, t * 4.0 )) ;
288
+ final AnimationStatus status = position.status ;
289
+ final double tNormalized = status == AnimationStatus .forward || status == AnimationStatus .completed
290
+ ? position.value
291
+ : 1.0 - position.value ;
256
292
257
- final double inset = 1.0 - (t - 0.5 ).abs () * 2.0 ;
258
- final double rectSize = _kEdgeSize - inset * _kStrokeWidth;
259
- final Rect rect = new Rect .fromLTWH (origin.dx + inset, origin.dy + inset, rectSize, rectSize);
260
- final RRect outer = new RRect .fromRectAndRadius (rect, _kEdgeRadius);
293
+ // Four cases: false to null, false to true, null to false, true to false
294
+ if (_oldValue == false || value == false ) {
295
+ final double t = value == false ? 1.0 - tNormalized : tNormalized;
296
+ final RRect outer = _outerRectAt (origin, t);
297
+ final Paint paint = new Paint ()..color = _colorAt (t);
261
298
262
- final Paint paint = new Paint ()
263
- ..color = borderColor;
299
+ if (t <= 0.5 ) {
300
+ _drawBorder (canvas, outer, t, paint);
301
+ } else {
302
+ canvas.drawRRect (outer, paint);
264
303
265
- if (t <= 0.5 ) {
266
- final RRect inner = outer.deflate (math.min (rectSize / 2.0 , _kStrokeWidth + rectSize * t));
267
- canvas.drawDRRect (outer, inner, paint);
268
- } else {
304
+ _initStrokePaint (paint);
305
+ final double tShrink = (t - 0.5 ) * 2.0 ;
306
+ if (_oldValue == null )
307
+ _drawDash (canvas, origin, tShrink, paint);
308
+ else
309
+ _drawCheck (canvas, origin, tShrink, paint);
310
+ }
311
+ } else { // Two cases: null to true, true to null
312
+ final RRect outer = _outerRectAt (origin, 1.0 );
313
+ final Paint paint = new Paint () ..color = _colorAt (1.0 );
269
314
canvas.drawRRect (outer, paint);
270
- final double t = (position.value - 0.5 ) * 2.0 ;
271
- paint
272
- ..color = const Color (0xFFFFFFFF )
273
- ..style = PaintingStyle .stroke
274
- ..strokeWidth = _kStrokeWidth;
275
- if (_showDash)
276
- _drawDash (canvas, origin, t, paint);
277
- else
278
- _drawCheck (canvas, origin, t, paint);
315
+
316
+ _initStrokePaint (paint);
317
+ if (tNormalized <= 0.5 ) {
318
+ final double tShrink = 1.0 - tNormalized * 2.0 ;
319
+ if (_oldValue == true )
320
+ _drawCheck (canvas, origin, tShrink, paint);
321
+ else
322
+ _drawDash (canvas, origin, tShrink, paint);
323
+ } else {
324
+ final double tExpand = (tNormalized - 0.5 ) * 2.0 ;
325
+ if (value == true )
326
+ _drawCheck (canvas, origin, tExpand, paint);
327
+ else
328
+ _drawDash (canvas, origin, tExpand, paint);
329
+ }
279
330
}
280
331
}
332
+
333
+ // TODO(hmuller): smooth segues for cases where the value changes
334
+ // in the middle of position's animation cycle.
335
+ // https://github.com/flutter/flutter/issues/14674
281
336
}
0 commit comments