Last active
May 5, 2021 17:38
-
-
Save umaqs/499509f07996ce13dcf8375215832b9c to your computer and use it in GitHub Desktop.
Centered TextField test
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Copyright (c) 2019, the Dart project authors. Please see the AUTHORS file | |
// for details. 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'; | |
import 'package:flutter/services.dart'; | |
void main() => runApp(MyApp()); | |
class MyApp extends StatelessWidget { | |
@override | |
Widget build(BuildContext context) { | |
return MaterialApp( | |
title: 'Flutter Demo', | |
debugShowCheckedModeBanner: false, | |
theme: ThemeData( | |
primarySwatch: Colors.blue, | |
), | |
home: MyHomePage(title: 'Flutter Demo Home Page'), | |
); | |
} | |
} | |
class MyHomePage extends StatefulWidget { | |
MyHomePage({Key key, this.title}) : super(key: key); | |
final String title; | |
@override | |
_MyHomePageState createState() => _MyHomePageState(); | |
} | |
class _MyHomePageState extends State<MyHomePage> { | |
String _mediumCenteredHelperText; | |
String _mediumCenteredErrorText; | |
@override | |
Widget build(BuildContext context) { | |
return GestureDetector( | |
onTap: () => FocusScope.of(context).unfocus(), | |
child: Scaffold( | |
appBar: AppBar( | |
title: Text(widget.title), | |
), | |
body: Column( | |
mainAxisAlignment: MainAxisAlignment.center, | |
crossAxisAlignment: CrossAxisAlignment.stretch, | |
children: [ | |
Padding( | |
padding: context.allPadding16, | |
child: Text( | |
'Type \'error\' to display an error message\nType \'help\' to display the helper text', | |
), | |
), | |
context.vBox24, | |
Padding( | |
padding: context.paddingEdges16, | |
child: GhCenteredTextField( | |
autocorrect: false, | |
labelText: 'Label', | |
hintText: 'Your First Name', | |
errorText: _mediumCenteredErrorText, | |
helperText: _mediumCenteredHelperText, | |
onChanged: (text) { | |
setState(() { | |
_mediumCenteredErrorText = | |
text.toLowerCase().contains('error') | |
? 'Error text' | |
: null; | |
_mediumCenteredHelperText = | |
text.toLowerCase().contains('help') | |
? 'Helper text' | |
: null; | |
}); | |
}, | |
textCapitalization: TextCapitalization.none, | |
), | |
), | |
], | |
), | |
), | |
); | |
} | |
} | |
class GhCenteredTextField extends StatefulWidget { | |
const GhCenteredTextField({ | |
Key key, | |
this.labelText, | |
this.hintText, | |
this.helperText, | |
this.errorText, | |
this.initialValue, | |
this.controller, | |
this.onChanged, | |
this.onEditingComplete, | |
this.inputActionType, | |
this.keyboardType, | |
this.inputFormatters, | |
this.textCapitalization = TextCapitalization.sentences, | |
this.autocorrect = true, | |
this.autofocus = false, | |
this.isLarge = false, | |
this.obscureText = false, | |
this.showClearButton = false, | |
}) : assert(autocorrect != null), | |
assert(autofocus != null), | |
assert(isLarge != null), | |
assert(obscureText != null), | |
assert(showClearButton != null), | |
super(key: key); | |
final String labelText; | |
final String hintText; | |
final String helperText; | |
final String errorText; | |
final String initialValue; | |
final TextEditingController controller; | |
final ValueChanged<String> onChanged; | |
final VoidCallback onEditingComplete; | |
final TextInputAction inputActionType; | |
final TextInputType keyboardType; | |
final List<TextInputFormatter> inputFormatters; | |
final TextCapitalization textCapitalization; | |
final bool autocorrect; | |
final bool autofocus; | |
final bool isLarge; | |
final bool obscureText; | |
final bool showClearButton; | |
@override | |
_GhCenteredTextFieldState createState() => _GhCenteredTextFieldState(); | |
} | |
class _GhCenteredTextFieldState extends State<GhCenteredTextField> { | |
TextEditingController _controller; | |
bool _hasFocus; | |
@override | |
void initState() { | |
super.initState(); | |
_controller = widget.controller ?? TextEditingController(); | |
if (widget.initialValue != null && widget.initialValue.isNotEmpty) { | |
_controller.text = widget.initialValue; | |
} | |
_controller.addListener(_onTyped); | |
_hasFocus = widget.autofocus; | |
} | |
void _onTyped() { | |
setState(() {}); | |
} | |
void _onFocusChanged(bool hasFocus) { | |
setState(() { | |
_hasFocus = hasFocus; | |
}); | |
} | |
@override | |
void dispose() { | |
_controller.removeListener(_onTyped); | |
if (widget.controller == null) { | |
_controller.dispose(); | |
} | |
super.dispose(); | |
} | |
bool get _displayClearIcon { | |
return (_hasFocus || widget.showClearButton) && _controller.text.isNotEmpty; | |
} | |
bool get _hasLabel => widget.labelText != null; | |
bool get _hasError => widget.errorText != null; | |
bool get _hasHelperText => widget.helperText != null; | |
// Widget get _suffixIcon { | |
// return AnimatedOpacity( | |
// opacity: _displayClearIcon ? 1 : 0, | |
// duration: kToggleableAnimDuration, | |
// child: IconButton( | |
// onPressed: () { | |
// _controller.clear(); | |
// widget.onChanged?.call(''); | |
// }, | |
// icon: Icon( | |
// Icons.clear, | |
// color: Colors.grey, | |
// ), | |
// ), | |
// ); | |
// } | |
@override | |
Widget build(BuildContext context) { | |
final theme = Theme.of(context); | |
final textStyle = | |
widget.isLarge ? theme.textTheme.headline2 : theme.textTheme.headline3; | |
final hintStyle = textStyle.copyWith(color: Colors.grey); | |
final labelStyle = theme.textTheme.headline4.copyWith( | |
color: _hasError ? Colors.orangeAccent : null, | |
); | |
final footerStyle = theme.textTheme.headline4.copyWith(height: 2); | |
return Theme( | |
data: context.centeredTextFieldThemeData(hasError: _hasError), | |
child: Column( | |
children: [ | |
if (_hasLabel) | |
Text( | |
widget.labelText, | |
style: labelStyle, | |
textAlign: TextAlign.center, | |
), | |
context.vBox32, | |
FocusScope( | |
onFocusChange: _onFocusChanged, | |
child: IntrinsicWidth( | |
child: TextField( | |
textCapitalization: widget.textCapitalization, | |
autofocus: widget.autofocus, | |
controller: _controller, | |
obscureText: widget.obscureText, | |
style: textStyle, | |
textAlign: _controller.text.isEmpty | |
? TextAlign.left | |
: TextAlign.center, | |
onChanged: widget.onChanged, | |
onEditingComplete: widget.onEditingComplete, | |
textInputAction: widget.inputActionType, | |
inputFormatters: widget.inputFormatters, | |
keyboardType: widget.keyboardType, | |
decoration: InputDecoration( | |
labelText: widget.hintText, | |
hintText: widget.hintText, | |
labelStyle: hintStyle, | |
hintStyle: hintStyle, | |
alignLabelWithHint: true, | |
floatingLabelBehavior: FloatingLabelBehavior.never, | |
contentPadding: context.textFieldContentPadding, | |
border: InputBorder.none, | |
enabledBorder: InputBorder.none, | |
focusedBorder: InputBorder.none, | |
errorBorder: InputBorder.none, | |
focusedErrorBorder: InputBorder.none), | |
), | |
), | |
), | |
Divider( | |
color: _hasError ? Colors.orangeAccent : Colors.blue, | |
height: 4, | |
thickness: 2, | |
), | |
context.vBox16, | |
if (_hasError) | |
Text( | |
widget.errorText, | |
style: footerStyle.copyWith(color: Colors.orangeAccent), | |
textAlign: TextAlign.center, | |
maxLines: 4, | |
) | |
else if (_hasHelperText) | |
Text( | |
widget.helperText, | |
style: footerStyle, | |
textAlign: TextAlign.center, | |
maxLines: 4, | |
), | |
], | |
), | |
); | |
} | |
} | |
extension GhThemeBuildContextExtension on BuildContext { | |
ThemeData centeredTextFieldThemeData({bool hasError}) { | |
final data = Theme.of(this); | |
return data.copyWith( | |
primaryColor: data.primaryColor, | |
hintColor: Colors.grey, | |
textSelectionTheme: data.textSelectionTheme.copyWith( | |
cursorColor: data.primaryColor, | |
), | |
// inputDecorationTheme: InputDecorationTheme( | |
// contentPadding: EdgeInsets.zero, | |
// border: InputBorder.none, | |
// enabledBorder: InputBorder.none, | |
// focusedBorder: InputBorder.none, | |
// errorBorder: InputBorder.none, | |
// focusedErrorBorder: InputBorder.none, | |
// ), | |
); | |
} | |
/// skip any of the [min] and [max] params to avoid clamping | |
/// | |
/// context.textScaleFactorClamped() will result in MediaQuery.of(context).textScaleFactor | |
double textScaleFactorClamped({double min, double max}) { | |
final textScaleFactor = MediaQuery.of(this).textScaleFactor; | |
if (min == null && max == null) { | |
return textScaleFactor; | |
} | |
if (max == null) { | |
return textScaleFactor.clamp(min, min + textScaleFactor) as double; | |
} | |
if (min == null) { | |
return textScaleFactor.clamp(0, max) as double; | |
} | |
return textScaleFactor.clamp(min, max) as double; | |
} | |
double get size40 => 40; | |
double get loadingIndicatorSize => 40; | |
double get badgeSize32 => 32; | |
double get iconSize16 => 16; | |
double get iconSize24 => 24; | |
double get iconSize32 => 32; | |
double get iconSizeBioLogin => 48; | |
double get iconSize56 => 56; | |
double get iconSize64 => 64; | |
double get iconSize112 => 112; | |
SizedBox get vBoxBetweenButtons => const SizedBox(height: 16); | |
SizedBox get vBoxBottom => const SizedBox(height: 24); | |
SizedBox get vBox10PctScreenHeight { | |
final height = MediaQuery.of(this).size.height; | |
return SizedBox(height: height * 0.10); | |
} | |
SizedBox get vBoxBottomDashboardTabWithFloatingActions => | |
const SizedBox(height: 80); | |
SizedBox get vBox4 => const SizedBox(height: 4); | |
SizedBox get vBox8 => const SizedBox(height: 8); | |
SizedBox get vBox12 => const SizedBox(height: 12); | |
SizedBox get vBox16 => const SizedBox(height: 16); | |
SizedBox get vBox24 => const SizedBox(height: 24); | |
SizedBox get vBox32 => const SizedBox(height: 32); | |
SizedBox get vBox40 => const SizedBox(height: 40); | |
SizedBox get vBox56 => const SizedBox(height: 56); | |
SizedBox get hBoxBetweenButtons => const SizedBox(width: 16); | |
SizedBox get hBox4 => const SizedBox(width: 4); | |
SizedBox get hBox8 => const SizedBox(width: 8); | |
SizedBox get hBox12 => const SizedBox(width: 12); | |
SizedBox get hBox16 => const SizedBox(width: 16); | |
SizedBox get hBox24 => const SizedBox(width: 24); | |
SizedBox get hBox32 => const SizedBox(width: 32); | |
EdgeInsetsGeometry get allPadding4 => const EdgeInsets.all(4); | |
EdgeInsetsGeometry get allPadding8 => const EdgeInsets.all(8); | |
EdgeInsetsGeometry get allPadding16 => const EdgeInsets.all(16); | |
EdgeInsetsGeometry get allPadding24 => const EdgeInsets.all(24); | |
EdgeInsetsGeometry get textFieldContentPadding => | |
const EdgeInsets.only(bottom: 8); | |
EdgeInsetsGeometry get tabPaddingEdges => | |
const EdgeInsets.symmetric(horizontal: 4); | |
EdgeInsetsGeometry get paddingEdges8 => | |
const EdgeInsets.symmetric(horizontal: 8); | |
EdgeInsetsGeometry get paddingLeft16 => const EdgeInsets.only(left: 16); | |
EdgeInsetsGeometry get paddingVertical16 => | |
const EdgeInsets.symmetric(vertical: 16); | |
EdgeInsetsGeometry get paddingEdges16 => | |
const EdgeInsets.symmetric(horizontal: 16); | |
EdgeInsetsGeometry get paddingEdges24 => | |
const EdgeInsets.symmetric(horizontal: 24); | |
double get edge16 => 16; | |
} | |
const kToggleableAnimDuration = Duration(milliseconds: 150); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment