Created
August 26, 2020 14:30
-
-
Save ykmnkmi/e002179eeb6f8b1b5f4bc00cb46c982d to your computer and use it in GitHub Desktop.
Flutter web tab switch and unfocus example
This file contains 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
import 'dart:ui' as ui show window; | |
import 'package:flutter/rendering.dart'; | |
import 'package:flutter/services.dart'; | |
import 'package:flutter/gestures.dart'; | |
import 'package:flutter/widgets.dart'; | |
void main() { | |
runApp(App()); | |
} | |
class App extends StatefulWidget { | |
const App({Key key}) : super(key: key); | |
@override | |
AppState createState() { | |
return AppState(); | |
} | |
} | |
class AppState extends State<App> { | |
@override | |
Widget build(BuildContext context) { | |
Widget result = DefaultTextStyle( | |
child: LoginPage(), | |
style: TextStyle( | |
color: Color(0xFF001030), | |
fontFamily: 'Segou UI', | |
fontSize: 16.0, | |
fontWeight: FontWeight.normal, | |
), | |
); | |
assert(() { | |
result = CheckedModeBanner(child: result); | |
return true; | |
}()); | |
return MediaQuery( | |
child: DecoratedBox( | |
child: Shortcuts( | |
child: Actions( | |
actions: WidgetsApp.defaultActions, | |
child: FocusTraversalGroup( | |
child: FocusHandler( | |
child: Localizations( | |
child: Directionality( | |
child: result, | |
textDirection: TextDirection.ltr, | |
), | |
delegates: <LocalizationsDelegate<WidgetsLocalizations>>[ | |
DefaultWidgetsLocalizations.delegate, | |
], | |
locale: Locale('en', 'kz'), | |
), | |
), | |
), | |
), | |
shortcuts: WidgetsApp.defaultShortcuts, | |
), | |
decoration: BoxDecoration( | |
color: Color(0xFFF2F5F9), | |
), | |
), | |
data: MediaQueryData.fromWindow(ui.window), | |
); | |
} | |
} | |
class FocusHandler extends InheritedWidget { | |
FocusHandler({Key key, @required Widget child}) | |
: nodes = <FocusNode>{}, | |
super(key: key, child: FocusHandlerContainer(child: child)); | |
final Set<FocusNode> nodes; | |
void add(FocusNode node) { | |
nodes.add(node); | |
} | |
void remove(FocusNode node) { | |
nodes.add(node); | |
} | |
void unfocus([FocusNode except]) { | |
for (final node in nodes) { | |
if (node.hasFocus && node != except) { | |
node.unfocus(); | |
} | |
} | |
} | |
@override | |
bool updateShouldNotify(FocusHandler oldWidget) { | |
return true; | |
} | |
static FocusHandler of(BuildContext context) { | |
return context.dependOnInheritedWidgetOfExactType<FocusHandler>(); | |
} | |
} | |
class FocusHandlerContainer extends StatefulWidget { | |
FocusHandlerContainer({Key key, @required this.child}) : super(key: key); | |
final Widget child; | |
@override | |
FocusHandlerContainerState createState() { | |
return FocusHandlerContainerState(); | |
} | |
} | |
class FocusHandlerContainerState extends State<FocusHandlerContainer> { | |
@override | |
Widget build(BuildContext context) { | |
return GestureDetector( | |
behavior: HitTestBehavior.opaque, | |
child: widget.child, | |
onTap: () { | |
print('unfocus'); | |
FocusHandler.of(context).unfocus(); | |
}, | |
); | |
} | |
} | |
class LoginPage extends StatefulWidget { | |
const LoginPage({Key key}) : super(key: key); | |
@override | |
LoginPageState createState() { | |
return LoginPageState(); | |
} | |
} | |
class LoginPageState extends State<LoginPage> { | |
TextEditingController loginController; | |
TextEditingController passwordController; | |
@override | |
void initState() { | |
super.initState(); | |
loginController = TextEditingController(); | |
passwordController = TextEditingController(); | |
} | |
@override | |
void dispose() { | |
passwordController.dispose(); | |
loginController.dispose(); | |
super.dispose(); | |
} | |
@override | |
Widget build(BuildContext context) { | |
return Title( | |
child: Align( | |
child: ConstrainedBox( | |
child: Column( | |
children: <Widget>[ | |
Padding( | |
child: Text('Sign In', style: TextStyle(fontSize: 16.0 * 1.75, fontWeight: FontWeight.bold)), | |
padding: EdgeInsets.only(bottom: 16.0 * 0.5), | |
), | |
Padding( | |
child: Input( | |
controller: loginController, | |
placeholder: Text('Login', style: TextStyle(color: Color(0xEE001030))), | |
), | |
padding: EdgeInsets.only(bottom: 16.0 * 0.5), | |
), | |
Padding( | |
child: Input( | |
controller: passwordController, | |
placeholder: Text('Password', style: TextStyle(color: Color(0xEE001030))), | |
obscureText: true, | |
), | |
padding: EdgeInsets.only(bottom: 16.0 * 0.5), | |
), | |
Row( | |
children: <Widget>[ | |
Button( | |
child: Text('Sign In', style: TextStyle(color: Color(0xFFFFFFFF))), | |
onTap: () { | |
setState(() { | |
print('sign in'); | |
}); | |
}, | |
), | |
], | |
) | |
], | |
mainAxisAlignment: MainAxisAlignment.center, | |
), | |
constraints: BoxConstraints(maxHeight: 16.0 * 20.0, maxWidth: 16.0 * 20.0), | |
), | |
), | |
color: Color(0xFFFFFFFF), | |
title: 'Sign In', | |
); | |
} | |
} | |
class Input extends StatefulWidget { | |
const Input({ | |
Key key, | |
@required this.controller, | |
this.focusNode, | |
this.placeholder, | |
this.obscureText = false, | |
}) : super(key: key); | |
final TextEditingController controller; | |
final FocusNode focusNode; | |
final Widget placeholder; | |
final bool obscureText; | |
@override | |
InputState createState() { | |
return InputState(); | |
} | |
} | |
class InputState extends State<Input> { | |
FocusNode focusNode; | |
void handleChange() { | |
setState(() {}); | |
} | |
@override | |
void initState() { | |
super.initState(); | |
focusNode = widget.focusNode ?? FocusNode(); | |
focusNode.addListener(handleChange); | |
if (widget.placeholder != null) { | |
widget.controller.addListener(handleChange); | |
} | |
} | |
@override | |
void didChangeDependencies() { | |
super.didChangeDependencies(); | |
if (widget.focusNode == null) { | |
FocusHandler.of(context).add(focusNode); | |
} | |
} | |
@override | |
void didUpdateWidget(Input oldWidget) { | |
super.didUpdateWidget(oldWidget); | |
if (widget.focusNode == null && oldWidget.focusNode != null) { | |
focusNode.removeListener(handleChange); | |
focusNode = FocusNode(); | |
FocusHandler.of(context).add(focusNode); | |
focusNode.addListener(handleChange); | |
} else if (widget.focusNode != null && oldWidget.focusNode == null) { | |
focusNode.removeListener(handleChange); | |
FocusHandler.of(context).remove(focusNode); | |
focusNode.dispose(); | |
focusNode = widget.focusNode; | |
focusNode.addListener(handleChange); | |
} | |
if (widget.placeholder == null && oldWidget.placeholder != null) { | |
widget.controller.removeListener(handleChange); | |
} else if (widget.placeholder != null && oldWidget.placeholder == null) { | |
widget.controller.addListener(handleChange); | |
} | |
} | |
@override | |
void dispose() { | |
if (widget.placeholder != null) { | |
widget.controller.removeListener(handleChange); | |
} | |
focusNode.removeListener(handleChange); | |
if (widget.focusNode == null) { | |
FocusHandler.of(context).remove(focusNode); | |
focusNode.dispose(); | |
} | |
super.dispose(); | |
} | |
@override | |
Widget build(BuildContext context) { | |
Widget result = EditableText( | |
backgroundCursorColor: const Color(0xFF001030), | |
controller: widget.controller, | |
cursorColor: const Color(0xFF001030), | |
focusNode: focusNode, | |
obscureText: widget.obscureText, | |
style: DefaultTextStyle.of(context).style, | |
); | |
if (widget.placeholder != null) { | |
result = Stack( | |
children: <Widget>[ | |
Opacity( | |
child: widget.placeholder, | |
opacity: widget.controller.text.isNotEmpty || focusNode.hasFocus ? 0.0 : 1.0, | |
), | |
result, | |
], | |
); | |
} | |
return DecoratedBox( | |
child: Padding( | |
child: result, | |
padding: EdgeInsets.all(16.0 * 1.0), | |
), | |
decoration: BoxDecoration( | |
borderRadius: BorderRadius.all(Radius.circular(16.0 * 0.5)), | |
color: Color(0xFFFFFFFF), | |
), | |
); | |
} | |
} | |
class Button extends StatefulWidget { | |
const Button({Key key, @required this.child, this.focusNode, this.onTap}) : super(key: key); | |
final Widget child; | |
final FocusNode focusNode; | |
final VoidCallback onTap; | |
@override | |
ButtonState createState() { | |
return ButtonState(); | |
} | |
} | |
class ButtonState extends State<Button> { | |
FocusNode focusNode; | |
bool mouseIn; | |
VoidCallback markNeedsBuild; | |
@override | |
void initState() { | |
super.initState(); | |
markNeedsBuild = (context as StatefulElement).markNeedsBuild; | |
focusNode = widget.focusNode ?? FocusNode(); | |
focusNode.addListener(markNeedsBuild); | |
mouseIn = false; | |
} | |
@override | |
void didChangeDependencies() { | |
super.didChangeDependencies(); | |
if (widget.focusNode == null) { | |
FocusHandler.of(context).add(focusNode); | |
} | |
} | |
@override | |
void didUpdateWidget(Button oldWidget) { | |
super.didUpdateWidget(oldWidget); | |
if (widget.focusNode == null && oldWidget.focusNode != null) { | |
focusNode.removeListener(markNeedsBuild); | |
focusNode = FocusNode(); | |
FocusHandler.of(context).add(focusNode); | |
focusNode.addListener(markNeedsBuild); | |
} else if (widget.focusNode != null && oldWidget.focusNode == null) { | |
focusNode.removeListener(markNeedsBuild); | |
FocusHandler.of(context).remove(focusNode); | |
focusNode.dispose(); | |
focusNode = widget.focusNode; | |
focusNode.addListener(markNeedsBuild); | |
} | |
} | |
@override | |
void dispose() { | |
focusNode.removeListener(markNeedsBuild); | |
if (widget.focusNode == null) { | |
FocusHandler.of(context).remove(focusNode); | |
focusNode.dispose(); | |
} | |
super.dispose(); | |
} | |
@override | |
Widget build(BuildContext context) { | |
Color color = Color(0xFF3699FF); | |
Color focusColor = Color(0xFF1477DD); | |
return Focus( | |
child: GestureDetector( | |
child: MouseRegion( | |
child: DecoratedBox( | |
child: Padding( | |
child: widget.child, | |
padding: EdgeInsets.symmetric( | |
horizontal: 16.0 * 1.0, | |
vertical: 16.0 * 0.65, | |
), | |
), | |
decoration: BoxDecoration( | |
borderRadius: BorderRadius.all(Radius.circular(16.0 * 0.42)), | |
color: widget.onTap == null ? color.withOpacity(0.6) : mouseIn || focusNode.hasFocus ? focusColor : color, | |
), | |
), | |
cursor: widget.onTap == null ? SystemMouseCursors.basic : SystemMouseCursors.click, | |
onEnter: (PointerEnterEvent event) { | |
mouseIn = true; | |
markNeedsBuild(); | |
}, | |
onExit: (PointerExitEvent event) { | |
mouseIn = false; | |
markNeedsBuild(); | |
}, | |
), | |
onTap: () { | |
if (widget.onTap != null) { | |
widget.onTap(); | |
FocusHandler.of(context).unfocus(focusNode); | |
} | |
}, | |
), | |
focusNode: focusNode, | |
onFocusChange: (bool hasFocus) { | |
markNeedsBuild(); | |
}, | |
onKey: (FocusNode focusNode, RawKeyEvent event) { | |
if (event.isKeyPressed(LogicalKeyboardKey.enter) || event.isKeyPressed(LogicalKeyboardKey.space)) { | |
if (widget.onTap != null) { | |
widget.onTap(); | |
} | |
return true; | |
} | |
return false; | |
}, | |
); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment