mirror of
https://github.com/flutter/flutter
synced 2024-10-13 11:42:54 +00:00
Add ScrollbarTheme/ScrollbarThemeData (#72308)
This commit is contained in:
parent
cbe72db135
commit
09008e6f71
|
@ -115,6 +115,7 @@ export 'src/material/refresh_indicator.dart';
|
|||
export 'src/material/reorderable_list.dart';
|
||||
export 'src/material/scaffold.dart';
|
||||
export 'src/material/scrollbar.dart';
|
||||
export 'src/material/scrollbar_theme.dart';
|
||||
export 'src/material/search.dart';
|
||||
export 'src/material/selectable_text.dart';
|
||||
export 'src/material/shadows.dart';
|
||||
|
|
|
@ -8,6 +8,7 @@ import 'package:flutter/widgets.dart';
|
|||
|
||||
import 'color_scheme.dart';
|
||||
import 'material_state.dart';
|
||||
import 'scrollbar_theme.dart';
|
||||
import 'theme.dart';
|
||||
|
||||
const double _kScrollbarThickness = 8.0;
|
||||
|
@ -37,6 +38,7 @@ const Duration _kScrollbarTimeToFade = Duration(milliseconds: 600);
|
|||
///
|
||||
/// * [RawScrollbar], a basic scrollbar that fades in and out, extended
|
||||
/// by this class to add more animations and behaviors.
|
||||
/// * [ScrollbarTheme], which configures the Scrollbar's appearance.
|
||||
/// * [CupertinoScrollbar], an iOS style scrollbar.
|
||||
/// * [ListView], which displays a linear, scrollable list of children.
|
||||
/// * [GridView], which displays a 2 dimensional, scrollable array of children.
|
||||
|
@ -59,8 +61,8 @@ class Scrollbar extends RawScrollbar {
|
|||
Key? key,
|
||||
required Widget child,
|
||||
ScrollController? controller,
|
||||
bool isAlwaysShown = false,
|
||||
this.showTrackOnHover = false,
|
||||
bool? isAlwaysShown,
|
||||
this.showTrackOnHover,
|
||||
this.hoverThickness,
|
||||
double? thickness,
|
||||
Radius? radius,
|
||||
|
@ -78,13 +80,17 @@ class Scrollbar extends RawScrollbar {
|
|||
|
||||
/// Controls if the track will show on hover and remain, including during drag.
|
||||
///
|
||||
/// Defaults to false, cannot be null.
|
||||
final bool showTrackOnHover;
|
||||
/// If this property is null, then [ScrollbarThemeData.showTrackOnHover] of
|
||||
/// [ThemeData.scrollbarTheme] is used. If that is also null, the default value
|
||||
/// is false.
|
||||
final bool? showTrackOnHover;
|
||||
|
||||
/// The thickness of the scrollbar when a hover state is active and
|
||||
/// [showTrackOnHover] is true.
|
||||
///
|
||||
/// Defaults to 12.0 dp when null.
|
||||
/// If this property is null, then [ScrollbarThemeData.thickness] of
|
||||
/// [ThemeData.scrollbarTheme] is used to resolve a thickness. If that is also
|
||||
/// null, the default value is 12.0 pixels.
|
||||
final double? hoverThickness;
|
||||
|
||||
@override
|
||||
|
@ -96,9 +102,15 @@ class _ScrollbarState extends RawScrollbarState<Scrollbar> {
|
|||
bool _dragIsActive = false;
|
||||
bool _hoverIsActive = false;
|
||||
late ColorScheme _colorScheme;
|
||||
late ScrollbarThemeData _scrollbarTheme;
|
||||
// On Android, scrollbars should match native appearance.
|
||||
late bool _useAndroidScrollbar;
|
||||
|
||||
@override
|
||||
bool get showScrollbar => widget.isAlwaysShown ?? _scrollbarTheme.isAlwaysShown ?? false;
|
||||
|
||||
bool get _showTrackOnHover => widget.showTrackOnHover ?? _scrollbarTheme.showTrackOnHover ?? false;
|
||||
|
||||
Set<MaterialState> get _states => <MaterialState>{
|
||||
if (_dragIsActive) MaterialState.dragged,
|
||||
if (_hoverIsActive) MaterialState.hovered,
|
||||
|
@ -125,16 +137,16 @@ class _ScrollbarState extends RawScrollbarState<Scrollbar> {
|
|||
|
||||
return MaterialStateProperty.resolveWith((Set<MaterialState> states) {
|
||||
if (states.contains(MaterialState.dragged))
|
||||
return dragColor;
|
||||
return _scrollbarTheme.thumbColor?.resolve(states) ?? dragColor;
|
||||
|
||||
// If the track is visible, the thumb color hover animation is ignored and
|
||||
// changes immediately.
|
||||
if (states.contains(MaterialState.hovered) && widget.showTrackOnHover)
|
||||
return hoverColor;
|
||||
if (states.contains(MaterialState.hovered) && _showTrackOnHover)
|
||||
return _scrollbarTheme.thumbColor?.resolve(states) ?? hoverColor;
|
||||
|
||||
return Color.lerp(
|
||||
idleColor,
|
||||
hoverColor,
|
||||
_scrollbarTheme.thumbColor?.resolve(states) ?? idleColor,
|
||||
_scrollbarTheme.thumbColor?.resolve(states) ?? hoverColor,
|
||||
_hoverAnimationController.value,
|
||||
)!;
|
||||
});
|
||||
|
@ -144,10 +156,11 @@ class _ScrollbarState extends RawScrollbarState<Scrollbar> {
|
|||
final Color onSurface = _colorScheme.onSurface;
|
||||
final Brightness brightness = _colorScheme.brightness;
|
||||
return MaterialStateProperty.resolveWith((Set<MaterialState> states) {
|
||||
if (states.contains(MaterialState.hovered) && widget.showTrackOnHover) {
|
||||
return brightness == Brightness.light
|
||||
? onSurface.withOpacity(0.03)
|
||||
: onSurface.withOpacity(0.05);
|
||||
if (states.contains(MaterialState.hovered) && _showTrackOnHover) {
|
||||
return _scrollbarTheme.trackColor?.resolve(states)
|
||||
?? (brightness == Brightness.light
|
||||
? onSurface.withOpacity(0.03)
|
||||
: onSurface.withOpacity(0.05));
|
||||
}
|
||||
return const Color(0x00000000);
|
||||
});
|
||||
|
@ -157,10 +170,11 @@ class _ScrollbarState extends RawScrollbarState<Scrollbar> {
|
|||
final Color onSurface = _colorScheme.onSurface;
|
||||
final Brightness brightness = _colorScheme.brightness;
|
||||
return MaterialStateProperty.resolveWith((Set<MaterialState> states) {
|
||||
if (states.contains(MaterialState.hovered) && widget.showTrackOnHover) {
|
||||
return brightness == Brightness.light
|
||||
? onSurface.withOpacity(0.1)
|
||||
: onSurface.withOpacity(0.25);
|
||||
if (states.contains(MaterialState.hovered) && _showTrackOnHover) {
|
||||
return _scrollbarTheme.trackBorderColor?.resolve(states)
|
||||
?? (brightness == Brightness.light
|
||||
? onSurface.withOpacity(0.1)
|
||||
: onSurface.withOpacity(0.25));
|
||||
}
|
||||
return const Color(0x00000000);
|
||||
});
|
||||
|
@ -168,10 +182,14 @@ class _ScrollbarState extends RawScrollbarState<Scrollbar> {
|
|||
|
||||
MaterialStateProperty<double> get _thickness {
|
||||
return MaterialStateProperty.resolveWith((Set<MaterialState> states) {
|
||||
if (states.contains(MaterialState.hovered) && widget.showTrackOnHover)
|
||||
return widget.hoverThickness ?? _kScrollbarThicknessWithTrack;
|
||||
if (states.contains(MaterialState.hovered) && _showTrackOnHover)
|
||||
return widget.hoverThickness
|
||||
?? _scrollbarTheme.thickness?.resolve(states)
|
||||
?? _kScrollbarThicknessWithTrack;
|
||||
// The default scrollbar thickness is smaller on mobile.
|
||||
return widget.thickness ?? (_kScrollbarThickness / (_useAndroidScrollbar ? 2 : 1));
|
||||
return widget.thickness
|
||||
?? _scrollbarTheme.thickness?.resolve(states)
|
||||
?? (_kScrollbarThickness / (_useAndroidScrollbar ? 2 : 1));
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -190,6 +208,8 @@ class _ScrollbarState extends RawScrollbarState<Scrollbar> {
|
|||
@override
|
||||
void didChangeDependencies() {
|
||||
final ThemeData theme = Theme.of(context);
|
||||
_colorScheme = theme.colorScheme;
|
||||
_scrollbarTheme = theme.scrollbarTheme;
|
||||
switch (theme.platform) {
|
||||
case TargetPlatform.android:
|
||||
_useAndroidScrollbar = true;
|
||||
|
@ -207,16 +227,16 @@ class _ScrollbarState extends RawScrollbarState<Scrollbar> {
|
|||
|
||||
@override
|
||||
void updateScrollbarPainter() {
|
||||
_colorScheme = Theme.of(context).colorScheme;
|
||||
scrollbarPainter
|
||||
..color = _thumbColor.resolve(_states)
|
||||
..trackColor = _trackColor.resolve(_states)
|
||||
..trackBorderColor = _trackBorderColor.resolve(_states)
|
||||
..textDirection = Directionality.of(context)
|
||||
..thickness = _thickness.resolve(_states)
|
||||
..radius = widget.radius ?? (_useAndroidScrollbar ? null : _kScrollbarRadius)
|
||||
..crossAxisMargin = (_useAndroidScrollbar ? 0.0 : _kScrollbarMargin)
|
||||
..minLength = _kScrollbarMinLength
|
||||
..radius = widget.radius ?? _scrollbarTheme.radius ?? (_useAndroidScrollbar ? null : _kScrollbarRadius)
|
||||
..crossAxisMargin = _scrollbarTheme.crossAxisMargin ?? (_useAndroidScrollbar ? 0.0 : _kScrollbarMargin)
|
||||
..mainAxisMargin = _scrollbarTheme.mainAxisMargin ?? 0.0
|
||||
..minLength = _scrollbarTheme.minThumbLength ?? _kScrollbarMinLength
|
||||
..padding = MediaQuery.of(context).padding;
|
||||
}
|
||||
|
||||
|
|
284
packages/flutter/lib/src/material/scrollbar_theme.dart
Normal file
284
packages/flutter/lib/src/material/scrollbar_theme.dart
Normal file
|
@ -0,0 +1,284 @@
|
|||
// Copyright 2014 The Flutter Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'dart:ui' show lerpDouble;
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
import 'material_state.dart';
|
||||
import 'theme.dart';
|
||||
|
||||
/// Defines default property values for descendant [Scrollbar] widgets.
|
||||
///
|
||||
/// Descendant widgets obtain the current [ScrollbarThemeData] object with
|
||||
/// `ScrollbarTheme.of(context)`. Instances of [ScrollbarThemeData] can be customized
|
||||
/// with [ScrollbarThemeData.copyWith].
|
||||
///
|
||||
/// Typically the [ScrollbarThemeData] of a [ScrollbarTheme] is specified as part of the overall
|
||||
/// [Theme] with [ThemeData.scrollbarTheme].
|
||||
///
|
||||
/// All [ScrollbarThemeData] properties are `null` by default. When null, the [Scrollbar]
|
||||
/// computes its own default values, typically based on the overall theme's
|
||||
/// [ThemeData.colorScheme].
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [ThemeData], which describes the overall theme information for the
|
||||
/// application.
|
||||
@immutable
|
||||
class ScrollbarThemeData with Diagnosticable {
|
||||
/// Creates a theme that can be used for [ThemeData.scrollbarTheme].
|
||||
const ScrollbarThemeData({
|
||||
this.thickness,
|
||||
this.showTrackOnHover,
|
||||
this.isAlwaysShown,
|
||||
this.radius,
|
||||
this.thumbColor,
|
||||
this.trackColor,
|
||||
this.trackBorderColor,
|
||||
this.crossAxisMargin,
|
||||
this.mainAxisMargin,
|
||||
this.minThumbLength,
|
||||
});
|
||||
|
||||
/// Overrides the default value of [Scrollbar.thickness] in all
|
||||
/// descendant [Scrollbar] widgets.
|
||||
///
|
||||
/// Resolves in the following states:
|
||||
/// * [MaterialState.hovered] on web and desktop platforms.
|
||||
final MaterialStateProperty<double?>? thickness;
|
||||
|
||||
/// Overrides the default value of [Scrollbar.showTrackOnHover] in all
|
||||
/// descendant [Scrollbar] widgets.
|
||||
final bool? showTrackOnHover;
|
||||
|
||||
/// Overrides the default value of [Scrollbar.isAlwaysShown] in all
|
||||
/// descendant [Scrollbar] widgets.
|
||||
final bool? isAlwaysShown;
|
||||
|
||||
/// Overrides the default value of [Scrollbar.radius] in all
|
||||
/// descendant widgets.
|
||||
final Radius? radius;
|
||||
|
||||
/// Overrides the default [Color] of the [Scrollbar] thumb in all descendant
|
||||
/// [Scrollbar] widgets.
|
||||
///
|
||||
/// Resolves in the following states:
|
||||
/// * [MaterialState.dragged].
|
||||
/// * [MaterialState.hovered] on web and desktop platforms.
|
||||
final MaterialStateProperty<Color?>? thumbColor;
|
||||
|
||||
/// Overrides the default [Color] of the [Scrollbar] track when
|
||||
/// [showTrackOnHover] is true in all descendant [Scrollbar] widgets.
|
||||
///
|
||||
/// Resolves in the following states:
|
||||
/// * [MaterialState.hovered] on web and desktop platforms.
|
||||
final MaterialStateProperty<Color?>? trackColor;
|
||||
|
||||
/// Overrides the default [Color] of the [Scrollbar] track border when
|
||||
/// [showTrackOnHover] is true in all descendant [Scrollbar] widgets.
|
||||
///
|
||||
/// Resolves in the following states:
|
||||
/// * [MaterialState.hovered] on web and desktop platforms.
|
||||
final MaterialStateProperty<Color?>? trackBorderColor;
|
||||
|
||||
/// Overrides the default value of the [ScrollbarPainter.crossAxisMargin]
|
||||
/// property in all descendant [Scrollbar] widgets.
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [ScrollbarPainter.crossAxisMargin], which sets the distance from the
|
||||
/// scrollbar's side to the nearest edge in logical pixels.
|
||||
final double? crossAxisMargin;
|
||||
|
||||
/// Overrides the default value of the [ScrollbarPainter.mainAxisMargin]
|
||||
/// property in all descendant [Scrollbar] widgets.
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [ScrollbarPainter.mainAxisMargin], which sets the distance from the
|
||||
/// scrollbar's start and end to the edge of the viewport in logical pixels.
|
||||
final double? mainAxisMargin;
|
||||
|
||||
/// Overrides the default value of the [ScrollbarPainter.minLength]
|
||||
/// property in all descendant [Scrollbar] widgets.
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [ScrollbarPainter.minLength], which sets the preferred smallest size
|
||||
/// the scrollbar can shrink to when the total scrollable extent is large,
|
||||
/// the current visible viewport is small, and the viewport is not
|
||||
/// overscrolled.
|
||||
final double? minThumbLength;
|
||||
|
||||
/// Creates a copy of this object with the given fields replaced with the
|
||||
/// new values.
|
||||
ScrollbarThemeData copyWith({
|
||||
MaterialStateProperty<double?>? thickness,
|
||||
bool? showTrackOnHover,
|
||||
bool? isAlwaysShown,
|
||||
Radius? radius,
|
||||
MaterialStateProperty<Color?>? thumbColor,
|
||||
MaterialStateProperty<Color?>? trackColor,
|
||||
MaterialStateProperty<Color?>? trackBorderColor,
|
||||
double? crossAxisMargin,
|
||||
double? mainAxisMargin,
|
||||
double? minThumbLength,
|
||||
}) {
|
||||
return ScrollbarThemeData(
|
||||
thickness: thickness ?? this.thickness,
|
||||
showTrackOnHover: showTrackOnHover ?? this.showTrackOnHover,
|
||||
isAlwaysShown: isAlwaysShown ?? this.isAlwaysShown,
|
||||
radius: radius ?? this.radius,
|
||||
thumbColor: thumbColor ?? this.thumbColor,
|
||||
trackColor: trackColor ?? this.trackColor,
|
||||
trackBorderColor: trackBorderColor ?? this.trackBorderColor,
|
||||
crossAxisMargin: crossAxisMargin ?? this.crossAxisMargin,
|
||||
mainAxisMargin: mainAxisMargin ?? this.mainAxisMargin,
|
||||
minThumbLength: minThumbLength ?? this.minThumbLength,
|
||||
);
|
||||
}
|
||||
|
||||
/// Linearly interpolate between two Scrollbar themes.
|
||||
///
|
||||
/// The argument `t` must not be null.
|
||||
///
|
||||
/// {@macro dart.ui.shadow.lerp}
|
||||
static ScrollbarThemeData lerp(ScrollbarThemeData? a, ScrollbarThemeData? b, double t) {
|
||||
assert(t != null);
|
||||
return ScrollbarThemeData(
|
||||
thickness: _lerpProperties<double?>(a?.thickness, b?.thickness, t, lerpDouble),
|
||||
showTrackOnHover: t < 0.5 ? a?.showTrackOnHover : b?.showTrackOnHover,
|
||||
isAlwaysShown: t < 0.5 ? a?.isAlwaysShown : b?.isAlwaysShown,
|
||||
radius: Radius.lerp(a?.radius, b?.radius, t),
|
||||
thumbColor: _lerpProperties<Color?>(a?.thumbColor, b?.thumbColor, t, Color.lerp),
|
||||
trackColor: _lerpProperties<Color?>(a?.trackColor, b?.trackColor, t, Color.lerp),
|
||||
trackBorderColor: _lerpProperties<Color?>(a?.trackBorderColor, b?.trackBorderColor, t, Color.lerp),
|
||||
crossAxisMargin: lerpDouble(a?.crossAxisMargin, b?.crossAxisMargin, t),
|
||||
mainAxisMargin: lerpDouble(a?.mainAxisMargin, b?.mainAxisMargin, t),
|
||||
minThumbLength: lerpDouble(a?.minThumbLength, b?.minThumbLength, t),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode {
|
||||
return hashValues(
|
||||
thickness,
|
||||
showTrackOnHover,
|
||||
isAlwaysShown,
|
||||
radius,
|
||||
thumbColor,
|
||||
trackColor,
|
||||
trackBorderColor,
|
||||
crossAxisMargin,
|
||||
mainAxisMargin,
|
||||
minThumbLength,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
if (identical(this, other))
|
||||
return true;
|
||||
if (other.runtimeType != runtimeType)
|
||||
return false;
|
||||
return other is ScrollbarThemeData
|
||||
&& other.thickness == thickness
|
||||
&& other.showTrackOnHover == showTrackOnHover
|
||||
&& other.isAlwaysShown == isAlwaysShown
|
||||
&& other.radius == radius
|
||||
&& other.thumbColor == thumbColor
|
||||
&& other.trackColor == trackColor
|
||||
&& other.trackBorderColor == trackBorderColor
|
||||
&& other.crossAxisMargin == crossAxisMargin
|
||||
&& other.mainAxisMargin == mainAxisMargin
|
||||
&& other.minThumbLength == minThumbLength;
|
||||
}
|
||||
|
||||
@override
|
||||
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
|
||||
super.debugFillProperties(properties);
|
||||
properties.add(DiagnosticsProperty<MaterialStateProperty<double?>>('thickness', thickness, defaultValue: null));
|
||||
properties.add(DiagnosticsProperty<bool>('showTrackOnHover', showTrackOnHover, defaultValue: null));
|
||||
properties.add(DiagnosticsProperty<bool>('isAlwaysShown', isAlwaysShown, defaultValue: null));
|
||||
properties.add(DiagnosticsProperty<Radius>('radius', radius, defaultValue: null));
|
||||
properties.add(DiagnosticsProperty<MaterialStateProperty<Color?>>('thumbColor', thumbColor, defaultValue: null));
|
||||
properties.add(DiagnosticsProperty<MaterialStateProperty<Color?>>('trackColor', trackColor, defaultValue: null));
|
||||
properties.add(DiagnosticsProperty<MaterialStateProperty<Color?>>('trackBorderColor', trackBorderColor, defaultValue: null));
|
||||
properties.add(DiagnosticsProperty<double>('crossAxisMargin', crossAxisMargin, defaultValue: null));
|
||||
properties.add(DiagnosticsProperty<double>('mainAxisMargin', mainAxisMargin, defaultValue: null));
|
||||
properties.add(DiagnosticsProperty<double>('minThumbLength', minThumbLength, defaultValue: null));
|
||||
}
|
||||
|
||||
static MaterialStateProperty<T>? _lerpProperties<T>(
|
||||
MaterialStateProperty<T>? a,
|
||||
MaterialStateProperty<T>? b,
|
||||
double t,
|
||||
T Function(T?, T?, double) lerpFunction,
|
||||
) {
|
||||
// Avoid creating a _LerpProperties object for a common case.
|
||||
if (a == null && b == null)
|
||||
return null;
|
||||
return _LerpProperties<T>(a, b, t, lerpFunction);
|
||||
}
|
||||
}
|
||||
|
||||
class _LerpProperties<T> implements MaterialStateProperty<T> {
|
||||
const _LerpProperties(this.a, this.b, this.t, this.lerpFunction);
|
||||
|
||||
final MaterialStateProperty<T>? a;
|
||||
final MaterialStateProperty<T>? b;
|
||||
final double t;
|
||||
final T Function(T?, T?, double) lerpFunction;
|
||||
|
||||
@override
|
||||
T resolve(Set<MaterialState> states) {
|
||||
final T? resolvedA = a?.resolve(states);
|
||||
final T? resolvedB = b?.resolve(states);
|
||||
return lerpFunction(resolvedA, resolvedB, t);
|
||||
}
|
||||
}
|
||||
|
||||
/// Applies a scrollbar theme to descendant [Scrollbar] widgets.
|
||||
///
|
||||
/// Descendant widgets obtain the current theme's [ScrollbarThemeData] using
|
||||
/// [ScrollbarTheme.of]. When a widget uses [ScrollbarTheme.of], it is
|
||||
/// automatically rebuilt if the theme later changes.
|
||||
///
|
||||
/// A scrollbar theme can be specified as part of the overall Material theme
|
||||
/// using [ThemeData.scrollbarTheme].
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [ScrollbarThemeData], which describes the configuration of a
|
||||
/// scrollbar theme.
|
||||
class ScrollbarTheme extends InheritedWidget {
|
||||
/// Constructs a scrollbar theme that configures all descendant [Scrollbar]
|
||||
/// widgets.
|
||||
const ScrollbarTheme({
|
||||
Key? key,
|
||||
required this.data,
|
||||
required Widget child,
|
||||
}) : super(key: key, child: child);
|
||||
|
||||
/// The properties used for all descendant [Scrollbar] widgets.
|
||||
final ScrollbarThemeData data;
|
||||
|
||||
/// Returns the configuration [data] from the closest [ScrollbarTheme]
|
||||
/// ancestor. If there is no ancestor, it returns [ThemeData.scrollbarTheme].
|
||||
///
|
||||
/// Typical usage is as follows:
|
||||
///
|
||||
/// ```dart
|
||||
/// ScrollbarThemeData theme = ScrollbarTheme.of(context);
|
||||
/// ```
|
||||
static ScrollbarThemeData of(BuildContext context) {
|
||||
final ScrollbarTheme? scrollbarTheme = context.dependOnInheritedWidgetOfExactType<ScrollbarTheme>();
|
||||
return scrollbarTheme?.data ?? Theme.of(context).scrollbarTheme;
|
||||
}
|
||||
|
||||
@override
|
||||
bool updateShouldNotify(ScrollbarTheme oldWidget) => data != oldWidget.data;
|
||||
}
|
|
@ -34,6 +34,7 @@ import 'outlined_button_theme.dart';
|
|||
import 'page_transitions_theme.dart';
|
||||
import 'popup_menu_theme.dart';
|
||||
import 'radio_theme.dart';
|
||||
import 'scrollbar_theme.dart';
|
||||
import 'slider_theme.dart';
|
||||
import 'snack_bar_theme.dart';
|
||||
import 'switch_theme.dart';
|
||||
|
@ -278,6 +279,7 @@ class ThemeData with Diagnosticable {
|
|||
bool? applyElevationOverlayColor,
|
||||
PageTransitionsTheme? pageTransitionsTheme,
|
||||
AppBarTheme? appBarTheme,
|
||||
ScrollbarThemeData? scrollbarTheme,
|
||||
BottomAppBarTheme? bottomAppBarTheme,
|
||||
ColorScheme? colorScheme,
|
||||
DialogTheme? dialogTheme,
|
||||
|
@ -410,6 +412,7 @@ class ThemeData with Diagnosticable {
|
|||
tabBarTheme ??= const TabBarTheme();
|
||||
tooltipTheme ??= const TooltipThemeData();
|
||||
appBarTheme ??= const AppBarTheme();
|
||||
scrollbarTheme ??= const ScrollbarThemeData();
|
||||
bottomAppBarTheme ??= const BottomAppBarTheme();
|
||||
cardTheme ??= const CardTheme();
|
||||
chipTheme ??= ChipThemeData.fromDefaults(
|
||||
|
@ -493,6 +496,7 @@ class ThemeData with Diagnosticable {
|
|||
applyElevationOverlayColor: applyElevationOverlayColor,
|
||||
pageTransitionsTheme: pageTransitionsTheme,
|
||||
appBarTheme: appBarTheme,
|
||||
scrollbarTheme: scrollbarTheme,
|
||||
bottomAppBarTheme: bottomAppBarTheme,
|
||||
colorScheme: colorScheme,
|
||||
dialogTheme: dialogTheme,
|
||||
|
@ -583,6 +587,7 @@ class ThemeData with Diagnosticable {
|
|||
required this.applyElevationOverlayColor,
|
||||
required this.pageTransitionsTheme,
|
||||
required this.appBarTheme,
|
||||
required this.scrollbarTheme,
|
||||
required this.bottomAppBarTheme,
|
||||
required this.colorScheme,
|
||||
required this.dialogTheme,
|
||||
|
@ -657,6 +662,7 @@ class ThemeData with Diagnosticable {
|
|||
assert(materialTapTargetSize != null),
|
||||
assert(pageTransitionsTheme != null),
|
||||
assert(appBarTheme != null),
|
||||
assert(scrollbarTheme != null),
|
||||
assert(bottomAppBarTheme != null),
|
||||
assert(colorScheme != null),
|
||||
assert(dialogTheme != null),
|
||||
|
@ -1085,6 +1091,9 @@ class ThemeData with Diagnosticable {
|
|||
/// textTheme of [AppBar]s.
|
||||
final AppBarTheme appBarTheme;
|
||||
|
||||
/// A theme for customizing the colors, thickness, and shape of [Scrollbar]s.
|
||||
final ScrollbarThemeData scrollbarTheme;
|
||||
|
||||
/// A theme for customizing the shape, elevation, and color of a [BottomAppBar].
|
||||
final BottomAppBarTheme bottomAppBarTheme;
|
||||
|
||||
|
@ -1270,6 +1279,7 @@ class ThemeData with Diagnosticable {
|
|||
bool? applyElevationOverlayColor,
|
||||
PageTransitionsTheme? pageTransitionsTheme,
|
||||
AppBarTheme? appBarTheme,
|
||||
ScrollbarThemeData? scrollbarTheme,
|
||||
BottomAppBarTheme? bottomAppBarTheme,
|
||||
ColorScheme? colorScheme,
|
||||
DialogTheme? dialogTheme,
|
||||
|
@ -1353,6 +1363,7 @@ class ThemeData with Diagnosticable {
|
|||
applyElevationOverlayColor: applyElevationOverlayColor ?? this.applyElevationOverlayColor,
|
||||
pageTransitionsTheme: pageTransitionsTheme ?? this.pageTransitionsTheme,
|
||||
appBarTheme: appBarTheme ?? this.appBarTheme,
|
||||
scrollbarTheme: scrollbarTheme ?? this.scrollbarTheme,
|
||||
bottomAppBarTheme: bottomAppBarTheme ?? this.bottomAppBarTheme,
|
||||
colorScheme: (colorScheme ?? this.colorScheme).copyWith(brightness: brightness),
|
||||
dialogTheme: dialogTheme ?? this.dialogTheme,
|
||||
|
@ -1510,6 +1521,7 @@ class ThemeData with Diagnosticable {
|
|||
applyElevationOverlayColor: t < 0.5 ? a.applyElevationOverlayColor : b.applyElevationOverlayColor,
|
||||
pageTransitionsTheme: t < 0.5 ? a.pageTransitionsTheme : b.pageTransitionsTheme,
|
||||
appBarTheme: AppBarTheme.lerp(a.appBarTheme, b.appBarTheme, t),
|
||||
scrollbarTheme: ScrollbarThemeData.lerp(a.scrollbarTheme, b.scrollbarTheme, t),
|
||||
bottomAppBarTheme: BottomAppBarTheme.lerp(a.bottomAppBarTheme, b.bottomAppBarTheme, t),
|
||||
colorScheme: ColorScheme.lerp(a.colorScheme, b.colorScheme, t),
|
||||
dialogTheme: DialogTheme.lerp(a.dialogTheme, b.dialogTheme, t),
|
||||
|
@ -1595,6 +1607,7 @@ class ThemeData with Diagnosticable {
|
|||
&& other.applyElevationOverlayColor == applyElevationOverlayColor
|
||||
&& other.pageTransitionsTheme == pageTransitionsTheme
|
||||
&& other.appBarTheme == appBarTheme
|
||||
&& other.scrollbarTheme == scrollbarTheme
|
||||
&& other.bottomAppBarTheme == bottomAppBarTheme
|
||||
&& other.colorScheme == colorScheme
|
||||
&& other.dialogTheme == dialogTheme
|
||||
|
@ -1679,6 +1692,7 @@ class ThemeData with Diagnosticable {
|
|||
applyElevationOverlayColor,
|
||||
pageTransitionsTheme,
|
||||
appBarTheme,
|
||||
scrollbarTheme,
|
||||
bottomAppBarTheme,
|
||||
colorScheme,
|
||||
dialogTheme,
|
||||
|
@ -1760,6 +1774,7 @@ class ThemeData with Diagnosticable {
|
|||
properties.add(DiagnosticsProperty<bool>('applyElevationOverlayColor', applyElevationOverlayColor, level: DiagnosticLevel.debug));
|
||||
properties.add(DiagnosticsProperty<PageTransitionsTheme>('pageTransitionsTheme', pageTransitionsTheme, level: DiagnosticLevel.debug));
|
||||
properties.add(DiagnosticsProperty<AppBarTheme>('appBarTheme', appBarTheme, defaultValue: defaultData.appBarTheme, level: DiagnosticLevel.debug));
|
||||
properties.add(DiagnosticsProperty<ScrollbarThemeData>('ScrollbarTheme', scrollbarTheme, defaultValue: defaultData.scrollbarTheme, level: DiagnosticLevel.debug));
|
||||
properties.add(DiagnosticsProperty<BottomAppBarTheme>('bottomAppBarTheme', bottomAppBarTheme, defaultValue: defaultData.bottomAppBarTheme, level: DiagnosticLevel.debug));
|
||||
properties.add(DiagnosticsProperty<ColorScheme>('colorScheme', colorScheme, defaultValue: defaultData.colorScheme, level: DiagnosticLevel.debug));
|
||||
properties.add(DiagnosticsProperty<DialogTheme>('dialogTheme', dialogTheme, defaultValue: defaultData.dialogTheme, level: DiagnosticLevel.debug));
|
||||
|
|
|
@ -600,15 +600,14 @@ class RawScrollbar extends StatefulWidget {
|
|||
Key? key,
|
||||
required this.child,
|
||||
this.controller,
|
||||
this.isAlwaysShown = false,
|
||||
this.isAlwaysShown,
|
||||
this.radius,
|
||||
this.thickness,
|
||||
this.thumbColor,
|
||||
this.fadeDuration = _kScrollbarFadeDuration,
|
||||
this.timeToFade = _kScrollbarTimeToFade,
|
||||
this.pressDuration = Duration.zero,
|
||||
}) : assert(isAlwaysShown != null),
|
||||
assert(child != null),
|
||||
}) : assert(child != null),
|
||||
assert(fadeDuration != null),
|
||||
assert(timeToFade != null),
|
||||
assert(pressDuration != null),
|
||||
|
@ -683,7 +682,7 @@ class RawScrollbar extends StatefulWidget {
|
|||
/// [controller] property has not been set, the [PrimaryScrollController] will
|
||||
/// be used.
|
||||
///
|
||||
/// Defaults to false.
|
||||
/// Defaults to false when null.
|
||||
///
|
||||
/// {@tool snippet}
|
||||
///
|
||||
|
@ -728,7 +727,7 @@ class RawScrollbar extends StatefulWidget {
|
|||
/// }
|
||||
/// ```
|
||||
/// {@end-tool}
|
||||
final bool isAlwaysShown;
|
||||
final bool? isAlwaysShown;
|
||||
|
||||
/// The [Radius] of the scrollbar thumb's rounded rectangle corners.
|
||||
///
|
||||
|
@ -790,6 +789,14 @@ class RawScrollbarState<T extends RawScrollbar> extends State<T> with TickerProv
|
|||
@protected
|
||||
late final ScrollbarPainter scrollbarPainter;
|
||||
|
||||
/// Overridable getter to indicate that the scrollbar should be visible, even
|
||||
/// when a scroll is not underway.
|
||||
///
|
||||
/// Subclasses can override this getter to make its value depend on an inherited
|
||||
/// theme.
|
||||
@protected
|
||||
bool get showScrollbar => widget.isAlwaysShown ?? false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
@ -820,7 +827,7 @@ class RawScrollbarState<T extends RawScrollbar> extends State<T> with TickerProv
|
|||
// A scroll event is required in order to paint the thumb.
|
||||
void _maybeTriggerScrollbar() {
|
||||
WidgetsBinding.instance!.addPostFrameCallback((Duration duration) {
|
||||
if (widget.isAlwaysShown) {
|
||||
if (showScrollbar) {
|
||||
_fadeoutTimer?.cancel();
|
||||
// Wait one frame and cause an empty scroll event. This allows the
|
||||
// thumb to show immediately when isAlwaysShown is true. A scroll
|
||||
|
@ -881,7 +888,7 @@ class RawScrollbarState<T extends RawScrollbar> extends State<T> with TickerProv
|
|||
}
|
||||
|
||||
void _maybeStartFadeoutTimer() {
|
||||
if (!widget.isAlwaysShown) {
|
||||
if (!showScrollbar) {
|
||||
_fadeoutTimer?.cancel();
|
||||
_fadeoutTimer = Timer(widget.timeToFade, () {
|
||||
_fadeoutAnimationController.reverse();
|
||||
|
|
557
packages/flutter/test/material/scrollbar_theme_test.dart
Normal file
557
packages/flutter/test/material/scrollbar_theme_test.dart
Normal file
|
@ -0,0 +1,557 @@
|
|||
// Copyright 2014 The Flutter Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'dart:ui' as ui;
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/rendering.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
import '../rendering/mock_canvas.dart';
|
||||
|
||||
void main() {
|
||||
test('ScrollbarThemeData copyWith, ==, hashCode basics', () {
|
||||
expect(const ScrollbarThemeData(), const ScrollbarThemeData().copyWith());
|
||||
expect(const ScrollbarThemeData().hashCode, const ScrollbarThemeData().copyWith().hashCode);
|
||||
});
|
||||
|
||||
testWidgets('Passing no ScrollbarTheme returns defaults', (WidgetTester tester) async {
|
||||
final ScrollController scrollController = ScrollController();
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
home: Scrollbar(
|
||||
isAlwaysShown: true,
|
||||
showTrackOnHover: true,
|
||||
controller: scrollController,
|
||||
child: SingleChildScrollView(
|
||||
controller: scrollController,
|
||||
child: const SizedBox(width: 4000.0, height: 4000.0)
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
// Idle scrollbar behavior
|
||||
expect(
|
||||
find.byType(Scrollbar),
|
||||
paints..rrect(
|
||||
rrect: RRect.fromRectAndRadius(
|
||||
const Rect.fromLTRB(790.0, 0.0, 798.0, 90.0),
|
||||
const Radius.circular(8.0),
|
||||
),
|
||||
color: const Color(0x1a000000),
|
||||
),
|
||||
);
|
||||
|
||||
// Drag scrollbar behavior
|
||||
const double scrollAmount = 10.0;
|
||||
final TestGesture dragScrollbarGesture = await tester.startGesture(const Offset(797.0, 45.0));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(
|
||||
find.byType(Scrollbar),
|
||||
paints..rrect(
|
||||
rrect: RRect.fromRectAndRadius(
|
||||
const Rect.fromLTRB(790.0, 0.0, 798.0, 90.0),
|
||||
const Radius.circular(8.0),
|
||||
),
|
||||
// Drag color
|
||||
color: const Color(0x99000000),
|
||||
),
|
||||
);
|
||||
|
||||
await dragScrollbarGesture.moveBy(const Offset(0.0, scrollAmount));
|
||||
await tester.pumpAndSettle();
|
||||
await dragScrollbarGesture.up();
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Hover scrollbar behavior
|
||||
final TestGesture gesture = await tester.createGesture(kind: ui.PointerDeviceKind.mouse);
|
||||
await gesture.addPointer();
|
||||
addTearDown(gesture.removePointer);
|
||||
await gesture.moveTo(const Offset(794.0, 5.0));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(
|
||||
find.byType(Scrollbar),
|
||||
paints
|
||||
..rect(
|
||||
rect: const Rect.fromLTRB(784.0, 0.0, 800.0, 600.0),
|
||||
color: const Color(0x08000000),
|
||||
)
|
||||
..line(
|
||||
p1: const Offset(784.0, 0.0),
|
||||
p2: const Offset(784.0, 600.0),
|
||||
strokeWidth: 1.0,
|
||||
color: const Color(0x1a000000),
|
||||
)
|
||||
..rrect(
|
||||
rrect: RRect.fromRectAndRadius(
|
||||
// Scrollbar thumb is larger
|
||||
const Rect.fromLTRB(786.0, 10.0, 798.0, 100.0),
|
||||
const Radius.circular(8.0),
|
||||
),
|
||||
// Hover color
|
||||
color: const Color(0x80000000),
|
||||
),
|
||||
);
|
||||
}, variant: const TargetPlatformVariant(<TargetPlatform>{
|
||||
TargetPlatform.linux,
|
||||
TargetPlatform.macOS,
|
||||
TargetPlatform.windows,
|
||||
TargetPlatform.fuchsia,
|
||||
}),
|
||||
);
|
||||
|
||||
testWidgets('Scrollbar uses values from ScrollbarTheme', (WidgetTester tester) async {
|
||||
final ScrollbarThemeData scrollbarTheme = _scrollbarTheme();
|
||||
final ScrollController scrollController = ScrollController();
|
||||
await tester.pumpWidget(MaterialApp(
|
||||
theme: ThemeData(scrollbarTheme: scrollbarTheme),
|
||||
home: Scrollbar(
|
||||
isAlwaysShown: true,
|
||||
controller: scrollController,
|
||||
child: SingleChildScrollView(
|
||||
controller: scrollController,
|
||||
child: const SizedBox(width: 4000.0, height: 4000.0)
|
||||
),
|
||||
),
|
||||
));
|
||||
await tester.pumpAndSettle();
|
||||
// Idle scrollbar behavior
|
||||
expect(
|
||||
find.byType(Scrollbar),
|
||||
paints..rrect(
|
||||
rrect: RRect.fromRectAndRadius(
|
||||
const Rect.fromLTRB(785.0, 10.0, 795.0, 97.0),
|
||||
const Radius.circular(6.0),
|
||||
),
|
||||
color: const Color(0xff4caf50),
|
||||
),
|
||||
);
|
||||
|
||||
// Drag scrollbar behavior
|
||||
const double scrollAmount = 10.0;
|
||||
final TestGesture dragScrollbarGesture = await tester.startGesture(const Offset(797.0, 45.0));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(
|
||||
find.byType(Scrollbar),
|
||||
paints..rrect(
|
||||
rrect: RRect.fromRectAndRadius(
|
||||
const Rect.fromLTRB(785.0, 10.0, 795.0, 97.0),
|
||||
const Radius.circular(6.0),
|
||||
),
|
||||
// Drag color
|
||||
color: const Color(0xfff44336),
|
||||
),
|
||||
);
|
||||
|
||||
await dragScrollbarGesture.moveBy(const Offset(0.0, scrollAmount));
|
||||
await tester.pumpAndSettle();
|
||||
await dragScrollbarGesture.up();
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Hover scrollbar behavior
|
||||
final TestGesture gesture = await tester.createGesture(kind: ui.PointerDeviceKind.mouse);
|
||||
await gesture.addPointer();
|
||||
addTearDown(gesture.removePointer);
|
||||
await gesture.moveTo(const Offset(794.0, 5.0));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(
|
||||
find.byType(Scrollbar),
|
||||
paints
|
||||
..rect(
|
||||
rect: const Rect.fromLTRB(770.0, 0.0, 800.0, 580.0),
|
||||
color: const Color(0xff000000),
|
||||
)
|
||||
..line(
|
||||
p1: const Offset(770.0, 0.0),
|
||||
p2: const Offset(770.0, 580.0),
|
||||
strokeWidth: 1.0,
|
||||
color: const Color(0xffffeb3b),
|
||||
)
|
||||
..rrect(
|
||||
rrect: RRect.fromRectAndRadius(
|
||||
// Scrollbar thumb is larger
|
||||
const Rect.fromLTRB(775.0, 20.0, 795.0, 107.0),
|
||||
const Radius.circular(6.0),
|
||||
),
|
||||
// Hover color
|
||||
color: const Color(0xff2196f3),
|
||||
),
|
||||
);
|
||||
}, variant: const TargetPlatformVariant(<TargetPlatform>{
|
||||
TargetPlatform.linux,
|
||||
TargetPlatform.macOS,
|
||||
TargetPlatform.windows,
|
||||
TargetPlatform.fuchsia,
|
||||
}),
|
||||
);
|
||||
|
||||
testWidgets('Scrollbar widget properties take priority over theme', (WidgetTester tester) async {
|
||||
const double thickness = 4.0;
|
||||
const double hoverThickness = 4.0;
|
||||
const bool showTrackOnHover = true;
|
||||
const Radius radius = Radius.circular(3.0);
|
||||
final ScrollController scrollController = ScrollController();
|
||||
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
theme: ThemeData.from(colorScheme: const ColorScheme.light()),
|
||||
home: Scrollbar(
|
||||
thickness: thickness,
|
||||
hoverThickness: hoverThickness,
|
||||
isAlwaysShown: true,
|
||||
showTrackOnHover: showTrackOnHover,
|
||||
radius: radius,
|
||||
controller: scrollController,
|
||||
child: SingleChildScrollView(
|
||||
controller: scrollController,
|
||||
child: const SizedBox(width: 4000.0, height: 4000.0)
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.pumpAndSettle();
|
||||
// Idle scrollbar behavior
|
||||
expect(
|
||||
find.byType(Scrollbar),
|
||||
paints..rrect(
|
||||
rrect: RRect.fromRectAndRadius(
|
||||
const Rect.fromLTRB(794.0, 0.0, 798.0, 90.0),
|
||||
const Radius.circular(3.0),
|
||||
),
|
||||
color: const Color(0x1a000000),
|
||||
),
|
||||
);
|
||||
|
||||
// Drag scrollbar behavior
|
||||
const double scrollAmount = 10.0;
|
||||
final TestGesture dragScrollbarGesture = await tester.startGesture(const Offset(797.0, 45.0));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(
|
||||
find.byType(Scrollbar),
|
||||
paints..rrect(
|
||||
rrect: RRect.fromRectAndRadius(
|
||||
const Rect.fromLTRB(794.0, 0.0, 798.0, 90.0),
|
||||
const Radius.circular(3.0),
|
||||
),
|
||||
// Drag color
|
||||
color: const Color(0x99000000),
|
||||
),
|
||||
);
|
||||
|
||||
await dragScrollbarGesture.moveBy(const Offset(0.0, scrollAmount));
|
||||
await tester.pumpAndSettle();
|
||||
await dragScrollbarGesture.up();
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Hover scrollbar behavior
|
||||
final TestGesture gesture = await tester.createGesture(kind: ui.PointerDeviceKind.mouse);
|
||||
await gesture.addPointer();
|
||||
addTearDown(gesture.removePointer);
|
||||
await gesture.moveTo(const Offset(794.0, 5.0));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(
|
||||
find.byType(Scrollbar),
|
||||
paints
|
||||
..rect(
|
||||
rect: const Rect.fromLTRB(792.0, 0.0, 800.0, 600.0),
|
||||
color: const Color(0x08000000),
|
||||
)
|
||||
..line(
|
||||
p1: const Offset(792.0, 0.0),
|
||||
p2: const Offset(792.0, 600.0),
|
||||
strokeWidth: 1.0,
|
||||
color: const Color(0x1a000000),
|
||||
)
|
||||
..rrect(
|
||||
rrect: RRect.fromRectAndRadius(
|
||||
// Scrollbar thumb is larger
|
||||
const Rect.fromLTRB(794.0, 10.0, 798.0, 100.0),
|
||||
const Radius.circular(3.0),
|
||||
),
|
||||
// Hover color
|
||||
color: const Color(0x80000000),
|
||||
),
|
||||
);
|
||||
}, variant: const TargetPlatformVariant(<TargetPlatform>{
|
||||
TargetPlatform.linux,
|
||||
TargetPlatform.macOS,
|
||||
TargetPlatform.windows,
|
||||
TargetPlatform.fuchsia,
|
||||
}),
|
||||
);
|
||||
|
||||
testWidgets('ThemeData colorScheme is used when no ScrollbarTheme is set', (WidgetTester tester) async {
|
||||
Widget buildFrame(ThemeData appTheme) {
|
||||
final ScrollController scrollController = ScrollController();
|
||||
return MaterialApp(
|
||||
theme: appTheme,
|
||||
home: Scrollbar(
|
||||
isAlwaysShown: true,
|
||||
showTrackOnHover: true,
|
||||
controller: scrollController,
|
||||
child: SingleChildScrollView(
|
||||
controller: scrollController,
|
||||
child: const SizedBox(width: 4000.0, height: 4000.0)
|
||||
),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// Scrollbar defaults for light themes:
|
||||
// - coloring based on ColorScheme.onSurface
|
||||
await tester.pumpWidget(buildFrame(ThemeData.from(colorScheme: const ColorScheme.light())));
|
||||
await tester.pumpAndSettle();
|
||||
// Idle scrollbar behavior
|
||||
expect(
|
||||
find.byType(Scrollbar),
|
||||
paints..rrect(
|
||||
rrect: RRect.fromRectAndRadius(
|
||||
const Rect.fromLTRB(790.0, 0.0, 798.0, 90.0),
|
||||
const Radius.circular(8.0),
|
||||
),
|
||||
color: const Color(0x1a000000),
|
||||
),
|
||||
);
|
||||
|
||||
// Drag scrollbar behavior
|
||||
const double scrollAmount = 10.0;
|
||||
TestGesture dragScrollbarGesture = await tester.startGesture(const Offset(797.0, 45.0));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(
|
||||
find.byType(Scrollbar),
|
||||
paints..rrect(
|
||||
rrect: RRect.fromRectAndRadius(
|
||||
const Rect.fromLTRB(790.0, 0.0, 798.0, 90.0),
|
||||
const Radius.circular(8.0),
|
||||
),
|
||||
// Drag color
|
||||
color: const Color(0x99000000),
|
||||
),
|
||||
);
|
||||
|
||||
await dragScrollbarGesture.moveBy(const Offset(0.0, scrollAmount));
|
||||
await tester.pumpAndSettle();
|
||||
await dragScrollbarGesture.up();
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Hover scrollbar behavior
|
||||
final TestGesture hoverGesture = await tester.createGesture(kind: ui.PointerDeviceKind.mouse);
|
||||
await hoverGesture.addPointer();
|
||||
addTearDown(hoverGesture.removePointer);
|
||||
await hoverGesture.moveTo(const Offset(794.0, 5.0));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(
|
||||
find.byType(Scrollbar),
|
||||
paints
|
||||
..rect(
|
||||
rect: const Rect.fromLTRB(784.0, 0.0, 800.0, 600.0),
|
||||
color: const Color(0x08000000),
|
||||
)
|
||||
..line(
|
||||
p1: const Offset(784.0, 0.0),
|
||||
p2: const Offset(784.0, 600.0),
|
||||
strokeWidth: 1.0,
|
||||
color: const Color(0x1a000000),
|
||||
)
|
||||
..rrect(
|
||||
rrect: RRect.fromRectAndRadius(
|
||||
// Scrollbar thumb is larger
|
||||
const Rect.fromLTRB(786.0, 10.0, 798.0, 100.0),
|
||||
const Radius.circular(8.0),
|
||||
),
|
||||
// Hover color
|
||||
color: const Color(0x80000000),
|
||||
),
|
||||
);
|
||||
|
||||
await hoverGesture.moveTo(const Offset(0.0, 0.0));
|
||||
|
||||
// Scrollbar defaults for dark themes:
|
||||
// - coloring slightly different based on ColorScheme.onSurface
|
||||
await tester.pumpWidget(buildFrame(ThemeData.from(colorScheme: const ColorScheme.dark())));
|
||||
await tester.pumpAndSettle(); // Theme change animation
|
||||
|
||||
// Idle scrollbar behavior
|
||||
expect(
|
||||
find.byType(Scrollbar),
|
||||
paints..rrect(
|
||||
rrect: RRect.fromRectAndRadius(
|
||||
const Rect.fromLTRB(790.0, 10.0, 798.0, 100.0),
|
||||
const Radius.circular(8.0),
|
||||
),
|
||||
color: const Color(0x4dffffff),
|
||||
),
|
||||
);
|
||||
|
||||
// Drag scrollbar behavior
|
||||
dragScrollbarGesture = await tester.startGesture(const Offset(797.0, 45.0));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(
|
||||
find.byType(Scrollbar),
|
||||
paints..rrect(
|
||||
rrect: RRect.fromRectAndRadius(
|
||||
const Rect.fromLTRB(790.0, 10.0, 798.0, 100.0),
|
||||
const Radius.circular(8.0),
|
||||
),
|
||||
// Drag color
|
||||
color: const Color(0xbfffffff),
|
||||
),
|
||||
);
|
||||
|
||||
await dragScrollbarGesture.moveBy(const Offset(0.0, scrollAmount));
|
||||
await tester.pumpAndSettle();
|
||||
await dragScrollbarGesture.up();
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Hover scrollbar behavior
|
||||
await hoverGesture.moveTo(const Offset(794.0, 5.0));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(
|
||||
find.byType(Scrollbar),
|
||||
paints
|
||||
..rect(
|
||||
rect: const Rect.fromLTRB(784.0, 0.0, 800.0, 600.0),
|
||||
color: const Color(0x0dffffff),
|
||||
)
|
||||
..line(
|
||||
p1: const Offset(784.0, 0.0),
|
||||
p2: const Offset(784.0, 600.0),
|
||||
strokeWidth: 1.0,
|
||||
color: const Color(0x40ffffff),
|
||||
)
|
||||
..rrect(
|
||||
rrect: RRect.fromRectAndRadius(
|
||||
// Scrollbar thumb is larger
|
||||
const Rect.fromLTRB(786.0, 20.0, 798.0, 110.0),
|
||||
const Radius.circular(8.0),
|
||||
),
|
||||
// Hover color
|
||||
color: const Color(0xa6ffffff),
|
||||
),
|
||||
);
|
||||
}, variant: const TargetPlatformVariant(<TargetPlatform>{
|
||||
TargetPlatform.linux,
|
||||
TargetPlatform.macOS,
|
||||
TargetPlatform.windows,
|
||||
TargetPlatform.fuchsia,
|
||||
}),
|
||||
);
|
||||
|
||||
testWidgets('Default ScrollbarTheme debugFillProperties', (WidgetTester tester) async {
|
||||
final DiagnosticPropertiesBuilder builder = DiagnosticPropertiesBuilder();
|
||||
const ScrollbarThemeData().debugFillProperties(builder);
|
||||
|
||||
final List<String> description = builder.properties
|
||||
.where((DiagnosticsNode node) => !node.isFiltered(DiagnosticLevel.info))
|
||||
.map((DiagnosticsNode node) => node.toString())
|
||||
.toList();
|
||||
|
||||
expect(description, <String>[]);
|
||||
});
|
||||
|
||||
testWidgets('ScrollbarTheme implements debugFillProperties', (WidgetTester tester) async {
|
||||
final DiagnosticPropertiesBuilder builder = DiagnosticPropertiesBuilder();
|
||||
ScrollbarThemeData(
|
||||
thickness: MaterialStateProperty.resolveWith(_getThickness),
|
||||
showTrackOnHover: true,
|
||||
isAlwaysShown: true,
|
||||
radius: const Radius.circular(3.0),
|
||||
thumbColor: MaterialStateProperty.resolveWith(_getThumbColor),
|
||||
trackColor: MaterialStateProperty.resolveWith(_getTrackColor),
|
||||
trackBorderColor: MaterialStateProperty.resolveWith(_getTrackBorderColor),
|
||||
crossAxisMargin: 3.0,
|
||||
mainAxisMargin: 6.0,
|
||||
minThumbLength: 120.0,
|
||||
).debugFillProperties(builder);
|
||||
|
||||
final List<String> description = builder.properties
|
||||
.where((DiagnosticsNode node) => !node.isFiltered(DiagnosticLevel.info))
|
||||
.map((DiagnosticsNode node) => node.toString())
|
||||
.toList();
|
||||
|
||||
expect(description, <String>[
|
||||
'thickness: Instance of \'_MaterialStatePropertyWith<double?>\'',
|
||||
'showTrackOnHover: true',
|
||||
'isAlwaysShown: true',
|
||||
'radius: Radius.circular(3.0)',
|
||||
'thumbColor: Instance of \'_MaterialStatePropertyWith<Color?>\'',
|
||||
'trackColor: Instance of \'_MaterialStatePropertyWith<Color?>\'',
|
||||
'trackBorderColor: Instance of \'_MaterialStatePropertyWith<Color?>\'',
|
||||
'crossAxisMargin: 3.0',
|
||||
'mainAxisMargin: 6.0',
|
||||
'minThumbLength: 120.0'
|
||||
]);
|
||||
|
||||
// On the web, Dart doubles and ints are backed by the same kind of object because
|
||||
// JavaScript does not support integers. So, the Dart double "4.0" is identical
|
||||
// to "4", which results in the web evaluating to the value "4" regardless of which
|
||||
// one is used. This results in a difference for doubles in debugFillProperties between
|
||||
// the web and the rest of Flutter's target platforms.
|
||||
}, skip: kIsWeb);
|
||||
}
|
||||
|
||||
ScrollbarThemeData _scrollbarTheme({
|
||||
MaterialStateProperty<double?>? thickness,
|
||||
bool showTrackOnHover = true,
|
||||
bool isAlwaysShown = true,
|
||||
Radius radius = const Radius.circular(6.0),
|
||||
MaterialStateProperty<Color?>? thumbColor,
|
||||
MaterialStateProperty<Color?>? trackColor,
|
||||
MaterialStateProperty<Color?>? trackBorderColor,
|
||||
double crossAxisMargin = 5.0,
|
||||
double mainAxisMargin = 10.0,
|
||||
double minThumbLength = 50.0,
|
||||
}) {
|
||||
return ScrollbarThemeData(
|
||||
thickness: thickness ?? MaterialStateProperty.resolveWith(_getThickness),
|
||||
showTrackOnHover: showTrackOnHover,
|
||||
isAlwaysShown: isAlwaysShown,
|
||||
radius: radius,
|
||||
thumbColor: thumbColor ?? MaterialStateProperty.resolveWith(_getThumbColor),
|
||||
trackColor: trackColor ?? MaterialStateProperty.resolveWith(_getTrackColor),
|
||||
trackBorderColor: trackBorderColor ?? MaterialStateProperty.resolveWith(_getTrackBorderColor),
|
||||
crossAxisMargin: crossAxisMargin,
|
||||
mainAxisMargin: mainAxisMargin,
|
||||
minThumbLength: minThumbLength,
|
||||
);
|
||||
}
|
||||
|
||||
double? _getThickness(Set<MaterialState> states) {
|
||||
if (states.contains(MaterialState.hovered))
|
||||
return 20.0;
|
||||
return 10.0;
|
||||
}
|
||||
|
||||
Color? _getThumbColor(Set<MaterialState> states) {
|
||||
if (states.contains(MaterialState.dragged))
|
||||
return Colors.red;
|
||||
if (states.contains(MaterialState.hovered))
|
||||
return Colors.blue;
|
||||
return Colors.green;
|
||||
}
|
||||
|
||||
Color? _getTrackColor(Set<MaterialState> states) {
|
||||
if (states.contains(MaterialState.hovered))
|
||||
return Colors.black;
|
||||
return null;
|
||||
}
|
||||
|
||||
Color? _getTrackBorderColor(Set<MaterialState> states) {
|
||||
if (states.contains(MaterialState.hovered))
|
||||
return Colors.yellow;
|
||||
return null;
|
||||
}
|
|
@ -742,6 +742,7 @@ void main() {
|
|||
applyElevationOverlayColor: false,
|
||||
pageTransitionsTheme: pageTransitionTheme,
|
||||
appBarTheme: const AppBarTheme(color: Colors.black),
|
||||
scrollbarTheme: const ScrollbarThemeData(radius: Radius.circular(10.0)),
|
||||
bottomAppBarTheme: const BottomAppBarTheme(color: Colors.black),
|
||||
colorScheme: const ColorScheme.light(),
|
||||
dialogTheme: const DialogTheme(backgroundColor: Colors.black),
|
||||
|
|
|
@ -292,6 +292,7 @@ void main() {
|
|||
applyElevationOverlayColor: false,
|
||||
pageTransitionsTheme: pageTransitionTheme,
|
||||
appBarTheme: const AppBarTheme(color: Colors.black),
|
||||
scrollbarTheme: const ScrollbarThemeData(radius: Radius.circular(10.0)),
|
||||
bottomAppBarTheme: const BottomAppBarTheme(color: Colors.black),
|
||||
colorScheme: const ColorScheme.light(),
|
||||
dialogTheme: const DialogTheme(backgroundColor: Colors.black),
|
||||
|
@ -384,6 +385,7 @@ void main() {
|
|||
applyElevationOverlayColor: true,
|
||||
pageTransitionsTheme: const PageTransitionsTheme(),
|
||||
appBarTheme: const AppBarTheme(color: Colors.white),
|
||||
scrollbarTheme: const ScrollbarThemeData(radius: Radius.circular(10.0)),
|
||||
bottomAppBarTheme: const BottomAppBarTheme(color: Colors.white),
|
||||
colorScheme: const ColorScheme.light(),
|
||||
dialogTheme: const DialogTheme(backgroundColor: Colors.white),
|
||||
|
|
Loading…
Reference in a new issue