Skip to content

Slider Visual Update #14901

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

Merged
merged 5 commits into from
Mar 1, 2018
Merged

Slider Visual Update #14901

merged 5 commits into from
Mar 1, 2018

Conversation

gspencergoog
Copy link
Contributor

@gspencergoog gspencergoog commented Feb 27, 2018

This implements an update to the look of the Slider widget.

Specifically, it does the following:

  • Adds the ability to customize the colors of all components of the slider
  • Adds the ability to customize the shape of the slider thumb and value indicator
  • Adds the ability to show the value indicator on continuous sliders
  • Updates the default value indicator to be a "paddle" shape with new animations.
  • Changes the tick marks to be visible all the time on discrete sliders
  • Fixes a memory leak of an animation controller.
  • Removes "thumbOpenAtMin" flag, which is no longer needed, and can be emulated by the
    custom thumb shape support. It was not widely used.

Adds tests for all of the new features.

Copy link
Contributor

@HansMuller HansMuller left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks great! Most of my feedback is just minor edits

@@ -2,46 +2,55 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import 'dart:math' as math;
import 'dart:math';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Within flutter it's conventional to use math. for math package imports.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, done.

/// that use a slider will listen for the [onChanged] callback and rebuild the
/// slider with a new [value] to update the visual appearance of the slider.
///
/// By default, a slider will be as wide as possible, centered vertically. When
/// given unbounded constraints, it will attempt to make the track 144 pixels
/// given unbounded constraints, it will attempt to make the rail 144 pixels
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be helpful to insert a paragraph above this one that identifies the slider's parts (rail, thumb, value indicator, etc). It would be OK to just copy the material from SliderThemeData.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, done.

this.thumbOpenAtMin: false,
}) : assert(value != null),
assert(min != null),
}) : assert(value != null), assert(min != null),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

linebreak between the asserts

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Whoa, I think that was just me accidentally hitting the backspace key...

final String label;

/// The color to use for the portion of the slider that has been selected.
/// The color to use for the portion of the slider rail that has been
/// selected.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In this case "selected" means between the slider's min limit and its value, right? It might be better to just say that than to ask the user to understand what the selected part of the rail is.

Note also: it looks like we're referring to this same part of the rail as "active".

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I've been trying to unify the language to match the spec, but it's a little fragmented. Fixed.

@@ -132,30 +142,30 @@ class Slider extends StatefulWidget {
/// A label to show above the slider when the slider is active.
///
/// Typically used to display the value of a discrete slider.
///
/// The label is rendered using the active [ThemeData]'s
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should you point out that the label is displayed within the value indicator, etc.?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure. It's not guaranteed to be within a custom indicator, but it is drawn by it.

);
}

class DefaultSliderThumbShape extends SliderComponentShape {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Need Dartdoc here

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

}
}

class DefaultSliderValueIndicatorShape extends SliderComponentShape {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See earlier comment about dartdoc, not calling this DefaultFoo

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed to PaddleSliderValueIndicatorShape. Kind of awkward, I know, but descriptive.

@@ -306,6 +326,14 @@ class ThemeData {
/// icons placed on top of the primary color (e.g. toolbar text).
final Brightness primaryColorBrightness;

/// A lighter version of the primary color, used for showing subtle differences
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here and below: I think it would be enough to just say "A lighter/darker version of the primary color".

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK.

@@ -342,7 +370,7 @@ class ThemeData {
///
/// * [InkSplash.splashFactory], which defines the default splash.
/// * [InkRipple.splashFactory], which defines a splash that spreads out
/// more aggresively than the default.
/// more aggressively than the default.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NICE

@@ -418,6 +446,10 @@ class ThemeData {
/// An icon theme that contrasts with the accent color.
final IconThemeData accentIconTheme;

/// A slider theme that describes the colors and shapes used in
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe:

The colors and shapes used to render [Slider].

This is the default value of [SliderTheme.of].

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not really a default value: it's the value, however it got set. But I like your idea, so I did something close.

@gspencergoog gspencergoog force-pushed the m2_slider branch 3 times, most recently from b9fc3b2 to eba21b5 Compare March 1, 2018 20:05
@HansMuller
Copy link
Contributor

LGTM

@gspencergoog gspencergoog merged commit 701eff4 into flutter:master Mar 1, 2018
@Hixie
Copy link
Contributor

Hixie commented Mar 2, 2018

This regressed our "undocumented members" benchmark by infinity percent. :-) (from 0 to 18.)

description.add(new DiagnosticsProperty<IconThemeData>('iconTheme', iconTheme));
description.add(new DiagnosticsProperty<IconThemeData>('primaryIconTheme', primaryIconTheme));
description.add(new DiagnosticsProperty<IconThemeData>('accentIconTheme', accentIconTheme));
description.add(new DiagnosticsProperty<SliderThemeData>('sliderTheme', sliderTheme));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we should probably mark most of these such that they don't show up in the debugDumpApp() output, because it basically makes the output unreadable as it stands today.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(also, minor nit: this would be more readable if all the lines were consistently wrapped or not wrapped)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, in another PR I've reformatted these with dartfmt, but with a line length of 200. That seems to keep the line breaks at bay.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i was more worried about the problem of this all making debugDumpApp useless :-)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I was wondering about that too. How does one mark them so they don't show up there? I didn't see a switch on the DiagnosticsProperty object. I added these because the previous toString was useless for debugging one of my tests.

@@ -732,7 +846,8 @@ class _IdentityThemeDataCacheKey {
// We are explicitly ignoring the possibility that the types might not
// match in the interests of speed.
final _IdentityThemeDataCacheKey otherKey = other;
return identical(baseTheme, otherKey.baseTheme) && identical(localTextGeometry, otherKey.localTextGeometry);
return identical(baseTheme, otherKey.baseTheme) &&
identical(localTextGeometry, otherKey.localTextGeometry);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

does this lack of vertical alignment comes from dartfmt, perchance?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, it is indenting a continued line by 4 spaces. Changing the line length to 200 seems to have solved that problem.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fwiw, i don't mind it being wrapped, i just want it to be more readable by keeping the vertical alignment consistent if it's wrapped

new DiagnosticsProperty<TextStyle>('caption', caption, defaultValue: defaultTheme.caption));
description.add(
new DiagnosticsProperty<TextStyle>('button', button, defaultValue: defaultTheme.button));
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this wrapping is also pretty hard to read

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also fixed in my other PR. #15078

// Test setting only the activeColor.
await tester.pumpWidget(buildApp(activeColor: customColor1));
expect(
sliderBox, paints..rect(color: customColor1)..rect(color: sliderTheme.inactiveRailColor));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

here's another example of dartfmt breaking you

super.debugFillProperties(description);
if (color == null && _opacity == null && size == null) {
return;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we should always add the properties, just give them default values in the description so they get hidden by default in the output

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, I set them all to have default values of 'null'.

opacity: opacity ?? this.opacity,
size: size ?? this.size
);
color: color ?? this.color, opacity: opacity ?? this.opacity, size: size ?? this.size);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

dartfmt broke you here too

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep. Fixed.

accentIconTheme,
platform,
)
brightness,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looks like dartfmt broke you here too

opacity: other.opacity,
size: other.size
);
return copyWith(color: other.color, opacity: other.opacity, size: other.size);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

and here

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

final TestGesture gesture = await tester.startGesture(center);
await tester.pump();
await tester
.pump(const Duration(milliseconds: 500)); // wait for value indicator animation to finish.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

and here

}

Future<Null> expectValueIndicator(
{bool isVisible, SliderThemeData theme, int divisions, bool enabled: true}) async {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

and here

child: new Theme(
data: Theme.of(context).copyWith(
sliderTheme:
Theme.of(context).sliderTheme.copyWith(showValueIndicator: show)),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this looks a bit weird

final Key sliderKey = new UniqueKey();
double value = 0.0;

Widget buildSlider({ double textScaleFactor }) {
Widget buildSlider(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

and here

gesture = await tester.startGesture(center);
await tester.pump();
await tester
.pump(const Duration(milliseconds: 500)); // wait for value indicator animation to finish.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

several times in this test the comment caused dartfmt to wrap a "pump" call inconsistently with the rest of the code

final ColorTween activeTickMarkEnableColor = new ColorTween(
begin: _sliderTheme.disabledActiveTickMarkColor, end: _sliderTheme.activeTickMarkColor);
final ColorTween inactiveTickMarkEnableColor = new ColorTween(
begin: _sliderTheme.disabledInactiveTickMarkColor, end: _sliderTheme.inactiveTickMarkColor);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

these aren't really wrapped in the flutter style either, though here i don't think it impacts readabilty much

/// Defaults to the ambient [ThemeData.sliderTheme] if there is no
/// [SliderTheme] in the given build context.
///
/// Typical usage is as follows:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sample code should be in a ## Sample code section.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done (in another PR)

/// defaults when assigning them to the slider theme component colors.
///
/// This is used to generate the default slider theme for a [ThemeData].
factory SliderThemeData.materialDefaults({
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i would probably call this SliderThemeData.fromPrimaryColors. The name SliderThemeData.materialDefaults sounds like a constant that has material defaults, not a method that computes values from other values.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm. Ok, but the reason I called it that was because the opacities that it overrides the given colors with, and the shapes it assigns for the components are all from the Material spec. This is also the method used to create the default theme for the Material ThemeData.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

being from the material spec is implied, this is the material library. :-)

valueIndicatorColor: Color.lerp(a.valueIndicatorColor, b.valueIndicatorColor, t),
thumbShape: t < 0.5 ? a.thumbShape : b.thumbShape,
valueIndicatorShape: t < 0.5 ? a.valueIndicatorShape : b.valueIndicatorShape,
showValueIndicator: t < 0.5 ? a.showValueIndicator : b.showValueIndicator,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

aw, the shapes can't lerp? :-( :-P

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sadly no. They encompass a lot of behaviors that would have made lerping complex, so I decided to cut my losses and eliminate the lerpTo and lerpFrom. Not that we can't add them, or add a subclass of SliderComponentShape that has them.


@override
bool operator ==(Object other) {
if (other.runtimeType != runtimeType) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you might want to do an identical() test here first since there's so many subproperties and they're so often going to be identical.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, done.

description.add(new DiagnosticsProperty<ShowValueIndicator>(
'showValueIndicator', showValueIndicator,
defaultValue: defaultData.showValueIndicator));
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

these are inconsistently wrapped which is weird

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've already fixed this in my other PR.

DaveShuckerow pushed a commit to DaveShuckerow/flutter that referenced this pull request May 14, 2018
This implements an update to the look of the Slider widget.

Specifically, it does the following:

* Adds the ability to customize the colors of all components of the slider
* Adds the ability to customize the shape of the slider thumb and value indicator
* Adds the ability to show the value indicator on continuous sliders
* Updates the default value indicator to be a "paddle" shape with new animations.
* Changes the tick marks to be visible all the time on discrete sliders
* Fixes a memory leak of an animation controller.
* Removes "thumbOpenAtMin" flag, which is no longer needed, and can be emulated by the
custom thumb shape support. It was not widely used.
* Adds tests for all of the new features.
@github-actions github-actions bot locked as resolved and limited conversation to collaborators Aug 11, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants