mirror of
https://github.com/flutter/flutter.git
synced 2025-06-03 00:51:18 +00:00

Here's another PR with a couple of typos fixed. As you can see there was a typo in _fileReferenceI**n**dentifiers_, in class _ParsedProjectInfo._ Maybe we should do some check on that since I'm not sure if that property is used somewhere outside Flutter?
495 lines
18 KiB
Dart
495 lines
18 KiB
Dart
// 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 'package:flutter/material.dart';
|
|
|
|
/// Flutter code sample for [TextButton].
|
|
|
|
void main() {
|
|
runApp(const TextButtonExampleApp());
|
|
}
|
|
|
|
class TextButtonExampleApp extends StatefulWidget {
|
|
const TextButtonExampleApp({ super.key });
|
|
|
|
@override
|
|
State<TextButtonExampleApp> createState() => _TextButtonExampleAppState();
|
|
}
|
|
|
|
class _TextButtonExampleAppState extends State<TextButtonExampleApp> {
|
|
bool darkMode = false;
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return MaterialApp(
|
|
themeMode: darkMode ? ThemeMode.dark : ThemeMode.light,
|
|
theme: ThemeData(brightness: Brightness.light),
|
|
darkTheme: ThemeData(brightness: Brightness.dark),
|
|
home: Scaffold(
|
|
body: Padding(
|
|
padding: const EdgeInsets.all(16),
|
|
child: TextButtonExample(
|
|
darkMode: darkMode,
|
|
updateDarkMode: (bool value) {
|
|
setState(() { darkMode = value; });
|
|
},
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
class TextButtonExample extends StatefulWidget {
|
|
const TextButtonExample({ super.key, required this.darkMode, required this.updateDarkMode });
|
|
|
|
final bool darkMode;
|
|
final ValueChanged<bool> updateDarkMode;
|
|
|
|
@override
|
|
State<TextButtonExample> createState() => _TextButtonExampleState();
|
|
}
|
|
|
|
class _TextButtonExampleState extends State<TextButtonExample> {
|
|
TextDirection textDirection = TextDirection.ltr;
|
|
ThemeMode themeMode = ThemeMode.light;
|
|
late final ScrollController scrollController;
|
|
Future<void>? currentAction;
|
|
|
|
static const Widget verticalSpacer = SizedBox(height: 16);
|
|
static const Widget horizontalSpacer = SizedBox(width: 32);
|
|
|
|
static const ImageProvider grassImage = NetworkImage(
|
|
'https://flutter.github.io/assets-for-api-docs/assets/material/text_button_grass.jpeg',
|
|
);
|
|
static const ImageProvider defaultImage = NetworkImage(
|
|
'https://flutter.github.io/assets-for-api-docs/assets/material/text_button_nhu_default.png',
|
|
);
|
|
static const ImageProvider hoveredImage = NetworkImage(
|
|
'https://flutter.github.io/assets-for-api-docs/assets/material/text_button_nhu_hovered.png',
|
|
);
|
|
static const ImageProvider pressedImage = NetworkImage(
|
|
'https://flutter.github.io/assets-for-api-docs/assets/material/text_button_nhu_pressed.png',
|
|
);
|
|
static const ImageProvider runningImage = NetworkImage(
|
|
'https://flutter.github.io/assets-for-api-docs/assets/material/text_button_nhu_end.png',
|
|
);
|
|
|
|
@override
|
|
void initState() {
|
|
scrollController = ScrollController();
|
|
super.initState();
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
scrollController.dispose();
|
|
super.dispose();
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final ThemeData theme = Theme.of(context);
|
|
final ColorScheme colorScheme = theme.colorScheme;
|
|
|
|
// Adapt colors that are not part of the color scheme to
|
|
// the current dark/light mode. Used to define TextButton #7's
|
|
// gradients.
|
|
final (Color color1, Color color2, Color color3) = switch (colorScheme.brightness) {
|
|
Brightness.light => (Colors.blue.withOpacity(1.0), Colors.orange.withOpacity(1.0), Colors.yellow.withOpacity(1.0)),
|
|
Brightness.dark => (Colors.purple.withOpacity(1.0), Colors.cyan.withOpacity(1.0), Colors.yellow.withOpacity(1.0)),
|
|
};
|
|
|
|
// This gradient's appearance reflects the button's state.
|
|
// Always return a gradient decoration so that AnimatedContainer
|
|
// can interpolate in between. Used by TextButton #7.
|
|
Decoration? statesToDecoration(Set<MaterialState> states) {
|
|
if (states.contains(MaterialState.pressed)) {
|
|
return BoxDecoration(
|
|
gradient: LinearGradient(colors: <Color>[color2, color2]), // solid fill
|
|
);
|
|
}
|
|
return BoxDecoration(
|
|
gradient: LinearGradient(
|
|
colors: switch (states.contains(MaterialState.hovered)) {
|
|
true => <Color>[color1, color2],
|
|
false => <Color>[color2, color1],
|
|
},
|
|
),
|
|
);
|
|
}
|
|
|
|
// To make this method a little easier to read, the buttons that
|
|
// appear in the two columns to the right of the demo switches
|
|
// Card are broken out below.
|
|
|
|
final List<Widget> columnOneButtons = <Widget>[
|
|
TextButton(
|
|
onPressed: () {},
|
|
child: const Text('Enabled'),
|
|
),
|
|
verticalSpacer,
|
|
|
|
const TextButton(
|
|
onPressed: null,
|
|
child: Text('Disabled'),
|
|
),
|
|
verticalSpacer,
|
|
|
|
TextButton.icon(
|
|
onPressed: () {},
|
|
icon: const Icon(Icons.access_alarm),
|
|
label: const Text('TextButton.icon #1'),
|
|
),
|
|
verticalSpacer,
|
|
|
|
// Override the foreground and background colors.
|
|
//
|
|
// In this example, and most of the ones that follow, we're using
|
|
// the TextButton.styleFrom() convenience method to create a ButtonStyle.
|
|
// The styleFrom method is a little easier because it creates
|
|
// ButtonStyle MaterialStateProperty parameters for you.
|
|
// In this case, Specifying foregroundColor overrides the text,
|
|
// icon and overlay (splash and highlight) colors a little differently
|
|
// depending on the button's state. BackgroundColor is just the background
|
|
// color for all states.
|
|
TextButton.icon(
|
|
style: TextButton.styleFrom(
|
|
foregroundColor: colorScheme.onError,
|
|
backgroundColor: colorScheme.error,
|
|
),
|
|
onPressed: () { },
|
|
icon: const Icon(Icons.access_alarm),
|
|
label: const Text('TextButton.icon #2'),
|
|
),
|
|
verticalSpacer,
|
|
|
|
// Override the button's shape and its border.
|
|
//
|
|
// In this case we've specified a shape that has border - the
|
|
// RoundedRectangleBorder's side parameter. If the styleFrom
|
|
// side parameter was also specified, or if the TextButtonTheme
|
|
// defined above included a side parameter, then that would
|
|
// override the RoundedRectangleBorder's side.
|
|
TextButton(
|
|
style: TextButton.styleFrom(
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: const BorderRadius.all(Radius.circular(8)),
|
|
side: BorderSide(
|
|
color: colorScheme.primary,
|
|
width: 5,
|
|
),
|
|
),
|
|
),
|
|
onPressed: () { },
|
|
child: const Text('TextButton #3'),
|
|
),
|
|
verticalSpacer,
|
|
|
|
// Override overlay: the ink splash and highlight colors.
|
|
//
|
|
// The styleFrom method turns the specified overlayColor
|
|
// into a value MaterialStyleProperty<Color> ButtonStyle.overlay
|
|
// value that uses opacities depending on the button's state.
|
|
// If the overlayColor was Colors.transparent, no splash
|
|
// or highlights would be shown.
|
|
TextButton(
|
|
style: TextButton.styleFrom(
|
|
overlayColor: Colors.yellow,
|
|
),
|
|
onPressed: () { },
|
|
child: const Text('TextButton #4'),
|
|
),
|
|
];
|
|
|
|
final List<Widget> columnTwoButtons = <Widget>[
|
|
// Override the foregroundBuilder: apply a ShaderMask.
|
|
//
|
|
// Apply a ShaderMask to the button's child. This kind of thing
|
|
// can be applied to one button easily enough by just wrapping the
|
|
// button's child directly. However to affect all buttons in this
|
|
// way you can specify a similar foregroundBuilder in a TextButton
|
|
// theme or the MaterialApp theme's ThemeData.textButtonTheme.
|
|
TextButton(
|
|
style: TextButton.styleFrom(
|
|
foregroundBuilder: (BuildContext context, Set<MaterialState> states, Widget? child) {
|
|
return ShaderMask(
|
|
shaderCallback: (Rect bounds) {
|
|
return LinearGradient(
|
|
begin: Alignment.bottomCenter,
|
|
end: Alignment.topCenter,
|
|
colors: <Color>[
|
|
colorScheme.primary,
|
|
colorScheme.onPrimary,
|
|
],
|
|
).createShader(bounds);
|
|
},
|
|
blendMode: BlendMode.srcATop,
|
|
child: child,
|
|
);
|
|
},
|
|
),
|
|
onPressed: () { },
|
|
child: const Text('TextButton #5'),
|
|
),
|
|
verticalSpacer,
|
|
|
|
// Override the foregroundBuilder: add an underline.
|
|
//
|
|
// Add a border around button's child. In this case the
|
|
// border only appears when the button is hovered or pressed
|
|
// (if it's pressed it's always hovered too). Not that this
|
|
// border is different than the one specified with the styleFrom
|
|
// side parameter (or the ButtonStyle.side property). The foregroundBuilder
|
|
// is applied to a widget that contains the child and has already
|
|
// included the button's padding. It is unaffected by the button's shape.
|
|
// The styleFrom side parameter controls the button's outermost border and it
|
|
// outlines the button's shape.
|
|
TextButton(
|
|
style: TextButton.styleFrom(
|
|
foregroundBuilder: (BuildContext context, Set<MaterialState> states, Widget? child) {
|
|
return DecoratedBox(
|
|
decoration: BoxDecoration(
|
|
border: states.contains(MaterialState.hovered)
|
|
? Border(bottom: BorderSide(color: colorScheme.primary))
|
|
: const Border(), // essentially "no border"
|
|
),
|
|
child: child,
|
|
);
|
|
},
|
|
),
|
|
onPressed: () { },
|
|
child: const Text('TextButton #6'),
|
|
),
|
|
verticalSpacer,
|
|
|
|
// Override the backgroundBuilder to add a state specific gradient background
|
|
// and add an outline that only appears when the button is hovered or pressed.
|
|
//
|
|
// The gradient background decoration is computed by the statesToDecoration()
|
|
// method. The gradient flips horizontally when the button is hovered (watch
|
|
// closely). Because we want the outline to only appear when the button is hovered
|
|
// we can't use the styleFrom() side parameter, because that creates the same
|
|
// outline for all states. The ButtonStyle.copyWith() method is used to add
|
|
// a MaterialState<BorderSide?> property that does the right thing.
|
|
//
|
|
// The gradient background is translucent - all of the colors have opacity 0.5 -
|
|
// so the overlay's splash and highlight colors are visible even though they're
|
|
// drawn on the Material widget that's effectively behind the background. The
|
|
// border is also translucent, so if you look carefully, you'll see that the
|
|
// background - which is part of the button's Material but is drawn on top of the
|
|
// the background gradient - shows through the border.
|
|
TextButton(
|
|
onPressed: () {},
|
|
style: TextButton.styleFrom(
|
|
overlayColor: color2,
|
|
backgroundBuilder: (BuildContext context, Set<MaterialState> states, Widget? child) {
|
|
return AnimatedContainer(
|
|
duration: const Duration(milliseconds: 500),
|
|
decoration: statesToDecoration(states),
|
|
child: child,
|
|
);
|
|
},
|
|
).copyWith(
|
|
side: MaterialStateProperty.resolveWith<BorderSide?>((Set<MaterialState> states) {
|
|
if (states.contains(MaterialState.hovered)) {
|
|
return BorderSide(width: 3, color: color3);
|
|
}
|
|
return null; // defer to the default
|
|
}),
|
|
),
|
|
child: const Text('TextButton #7'),
|
|
),
|
|
verticalSpacer,
|
|
|
|
// Override the backgroundBuilder to add a grass image background.
|
|
//
|
|
// The image is clipped to the button's shape. We've included an Ink widget
|
|
// because the background image is opaque and would otherwise obscure the splash
|
|
// and highlight overlays that are painted on the button's Material widget
|
|
// by default. They're drawn on the Ink widget instead. The foreground color
|
|
// was overridden as well because white shows up a little better on the mottled
|
|
// green background.
|
|
TextButton(
|
|
onPressed: () {},
|
|
style: TextButton.styleFrom(
|
|
foregroundColor: Colors.white,
|
|
backgroundBuilder: (BuildContext context, Set<MaterialState> states, Widget? child) {
|
|
return Ink(
|
|
decoration: const BoxDecoration(
|
|
image: DecorationImage(
|
|
image: grassImage,
|
|
fit: BoxFit.cover,
|
|
),
|
|
),
|
|
child: child,
|
|
);
|
|
},
|
|
),
|
|
child: const Text('TextButton #8'),
|
|
),
|
|
verticalSpacer,
|
|
|
|
// Override the foregroundBuilder to specify images for the button's pressed
|
|
// hovered and default states. We switch to an additional image while the
|
|
// button's callback is "running".
|
|
//
|
|
// This is an example of completely changing the default appearance of a button
|
|
// by specifying images for each state and by turning off the overlays by
|
|
// overlayColor: Colors.transparent. AnimatedContainer takes care of the
|
|
// fade in and out segues between images.
|
|
//
|
|
// This foregroundBuilder function ignores its child parameter. Unfortunately
|
|
// TextButton's child parameter is required, so we still have
|
|
// to provide one.
|
|
TextButton(
|
|
onPressed: () async {
|
|
// This is slightly complicated so that if the user presses the button
|
|
// while the current Future.delayed action is running, the currentAction
|
|
// flag is only reset to null after the _new_ action completes.
|
|
late final Future<void> thisAction;
|
|
thisAction = Future<void>.delayed(const Duration(seconds: 1), () {
|
|
if (currentAction == thisAction) {
|
|
setState(() { currentAction = null; });
|
|
}
|
|
});
|
|
setState(() { currentAction = thisAction; });
|
|
},
|
|
style: TextButton.styleFrom(
|
|
overlayColor: Colors.transparent,
|
|
foregroundBuilder: (BuildContext context, Set<WidgetState> states, Widget? child) {
|
|
late final ImageProvider image;
|
|
if (currentAction != null) {
|
|
image = runningImage;
|
|
} else if (states.contains(WidgetState.pressed)) {
|
|
image = pressedImage;
|
|
} else if (states.contains(WidgetState.hovered)) {
|
|
image = hoveredImage;
|
|
} else {
|
|
image = defaultImage;
|
|
}
|
|
return AnimatedContainer(
|
|
width: 64,
|
|
height: 64,
|
|
duration: const Duration(milliseconds: 300),
|
|
curve: Curves.fastOutSlowIn,
|
|
decoration: BoxDecoration(
|
|
image: DecorationImage(
|
|
image: image,
|
|
fit: BoxFit.contain,
|
|
),
|
|
),
|
|
);
|
|
},
|
|
),
|
|
child: const Text('This child is not used'),
|
|
),
|
|
];
|
|
|
|
return Row(
|
|
children: <Widget> [
|
|
// The dark/light and LTR/RTL switches. We use the updateDarkMode function
|
|
// provided by the parent TextButtonExampleApp to rebuild the MaterialApp
|
|
// in the appropriate dark/light ThemeMdoe. The directionality of the rest
|
|
// of the UI is controlled by the Directionality widget below, and the
|
|
// textDirection local state variable.
|
|
TextButtonExampleSwitches(
|
|
darkMode: widget.darkMode,
|
|
updateDarkMode: widget.updateDarkMode,
|
|
textDirection: textDirection,
|
|
updateRTL: (bool value) {
|
|
setState(() {
|
|
textDirection = value ? TextDirection.rtl : TextDirection.ltr;
|
|
});
|
|
},
|
|
),
|
|
horizontalSpacer,
|
|
|
|
Expanded(
|
|
child: Scrollbar(
|
|
controller: scrollController,
|
|
thumbVisibility: true,
|
|
child: SingleChildScrollView(
|
|
scrollDirection: Axis.horizontal,
|
|
controller: scrollController,
|
|
child: Row(
|
|
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: <Widget>[
|
|
Directionality(
|
|
textDirection: textDirection,
|
|
child: Column(
|
|
children: columnOneButtons,
|
|
),
|
|
),
|
|
horizontalSpacer,
|
|
|
|
Directionality(
|
|
textDirection: textDirection,
|
|
child: Column(
|
|
children: columnTwoButtons
|
|
),
|
|
),
|
|
horizontalSpacer,
|
|
],
|
|
),
|
|
),
|
|
),
|
|
),
|
|
],
|
|
);
|
|
}
|
|
}
|
|
|
|
class TextButtonExampleSwitches extends StatelessWidget {
|
|
const TextButtonExampleSwitches({
|
|
super.key,
|
|
required this.darkMode,
|
|
required this.updateDarkMode,
|
|
required this.textDirection,
|
|
required this.updateRTL
|
|
});
|
|
|
|
final bool darkMode;
|
|
final ValueChanged<bool> updateDarkMode;
|
|
final TextDirection textDirection;
|
|
final ValueChanged<bool> updateRTL;
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Card(
|
|
child: Padding(
|
|
padding: const EdgeInsets.all(16),
|
|
child: IntrinsicWidth(
|
|
child: Column(
|
|
children: <Widget>[
|
|
Row(
|
|
children: <Widget>[
|
|
const Expanded(child: Text('Dark Mode')),
|
|
const SizedBox(width: 4),
|
|
Switch(
|
|
value: darkMode,
|
|
onChanged: updateDarkMode,
|
|
),
|
|
],
|
|
),
|
|
const SizedBox(height: 16),
|
|
Row(
|
|
children: <Widget>[
|
|
const Expanded(child: Text('RTL Text')),
|
|
const SizedBox(width: 4),
|
|
Switch(
|
|
value: textDirection == TextDirection.rtl,
|
|
onChanged: updateRTL,
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|