UPDATE (2021/05/11):
Flutter now natively has Hover Events implemented Widgets.
There is a MouseCursor for Widgets like RaisedButton and properties like hoverColor or hoverElevation.
https://api.flutter.dev/flutter/services/MouseCursor-class.html
You can also use an InkWell anywhere else as stated in the accepted answer.
Original Question:
How can the cursor appearance be changed within Flutter?
I know that with the Listener() Widget we can listen for Mouse-Events,
but I haven't found any information regarding hovering events for flutter web.
Has someone found a soulution yet?
I had difficulties finding documentation on the now built-in support. Here is what helped me: https://github.com/flutter/flutter/issues/58260
And this did the trick for me, without changing index.html etc.
MouseRegion(
cursor: SystemMouseCursors.click,
child: GestureDetector(
child: Icon(
Icons.add_comment,
size: 20,
),
onTap: () {},
),
),
Also see the official documentation: https://api.flutter.dev/flutter/rendering/MouseCursor-class.html
Widget build(BuildContext context) {
return Center(
child: MouseRegion(
cursor: SystemMouseCursors.text,
child: Container(
width: 200,
height: 100,
decoration: BoxDecoration(
color: Colors.blue,
border: Border.all(color: Colors.yellow),
),
),
),
);
}
And here https://api.flutter.dev/flutter/material/MaterialStateMouseCursor-class.html yet another wonderful example from the official docs that "...defines a mouse cursor that resolves to SystemMouseCursors.forbidden when its widget is disabled."
Starting with dev channel build version 1.19.0–3.0.pre there is built-in support for the pointer cursor. The same method as bellow is used with the difference that is applied to the Flutter app container element flt-glass-pane. Using the bellow method will just duplicate the behavior.
In order to override the pointer cursor, you can use the bellow method but applied on the flt-glass-pane element.
A workaround for this is the following:
You have to set an id (for example app-container on the entire body of the app's index.html template).
This is how your index.html will look like:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>My awesome app</title>
</head>
<body id="app-container">
<script src="main.dart.js" type="application/javascript"></script>
</body>
</html>
Next, you have to create a wrapper dart class. I called it hand_cursor.dart:
import 'package:flutter_web/gestures.dart';
import 'package:flutter_web/widgets.dart';
import 'package:universal_html/html.dart' as html;
// see https://pub.dev/packages/universal_html
class HandCursor extends MouseRegion {
// get a reference to the body element that we previously altered
static final appContainer = html.window.document.getElementById('app-container');
HandCursor({Widget child}) : super(
onHover: (PointerHoverEvent evt) {
appContainer.style.cursor='pointer';
// you can use any of these:
// 'help', 'wait', 'move', 'crosshair', 'text' or 'pointer'
// more options/details here: http://www.javascripter.net/faq/stylesc.htm
},
onExit: (PointerExitEvent evt) {
// set cursor's style 'default' to return it to the original state
appContainer.style.cursor='default';
},
child: child
);
}
After that, wherever you want to have the hand cursor shown, you have to wrap your element in this HandCursor wrapper. See the class awesome_button.dart bellow:
import 'package:awesome_app/widgets/containers/hand_cursor.dart';
import 'package:flutter_web/material.dart';
import 'package:flutter_web/widgets.dart';
class AwesomeButton extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Stack(
children: <Widget>[
HandCursor(
child: IconButton(
onPressed: () {
// do some magic
},
icon: Icon(Icons.star)
),
)
],
);
}
}
A short explanation can be found here.
A more versatile update, that works on the new web projects created with the master channel of Flutter, can be found here.
I hope it helps.
You can use an InkWell that has an onHover event
InkWell(
onTap: () {},
onHover: (value) {
setState(() {
isHovered = value;
});
},
child: Container(
width: 50,
height: 72,
color: Colors.black
)
);
Make sure to have something onTap, even an empty function, else it is considered to be disabled, and the hover won't work
The previous method is deprecated. Here is the updated code
import 'package:flutter/gestures.dart';
import 'package:flutter/widgets.dart';
import 'package:universal_html/prefer_sdk/html.dart' as html;
class HandCursor extends MouseRegion {
static final appContainer = html.window.document.getElementById('app-container');
HandCursor({Widget child})
: super(
onHover: (PointerHoverEvent evt) {
appContainer.style.cursor = 'pointer';
},
onExit: (PointerExitEvent evt) {
appContainer.style.cursor = 'default';
},
child: child,
);
}
And in your pubspec.yaml file, add universal_html as a package as a dependency. The version may change.
dependencies:
flutter:
sdk: flutter
universal_html: ^1.1.4
You still want to have an id of app-container attached to the body of your html. Here is my html file.
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Your App Title</title>
</head>
<body id="app-container">
<script src="main.dart.js" type="application/javascript"></script>
</body>
</html>
You want to put the code for the HandCursor widget in its own file. You can call it hand_cursor.dart. And to use it on the widget you want the hand to show up on, import it into the file you're working on and wrap the widget you want in the HandCursor widget.
From Flutter beta version 1.19.0-4.1.pre, add id to body and set cursor of that doesn't work. Because flt-glass-pane is replacing the cursor.
So the solution is that set cursor directly to flt-glass-pane.
Below is the update that is working.
class HandCursor extends MouseRegion {
static final appContainer = html.window.document.querySelectorAll('flt-glass-pane')[0];
HandCursor({Widget child}) : super(
onHover: (PointerHoverEvent evt) {
appContainer.style.cursor='pointer';
},
onExit: (PointerExitEvent evt) {
appContainer.style.cursor='default';
},
child: child
);
}
final appContainer
= html.document.getElementsByTagName('body')[0] as html.Element;
GestureDetector(
child: MouseRegion(
child: Text(
'https://github.com/yumi0629',
style: textStyle,
),
onHover: (_) => appContainer.style.cursor = 'pointer',
onExit: (_) => appContainer.style.cursor = 'default',
),
onTap: () {
print('open');
js.context.callMethod(
'open', ['https://github.com/yumi0629']);
},
)
The most easy way what i know
InkWell(
onTap: (){},
mouseCursor: MaterialStateMouseCursor.clickable,
...
I believe that mouse events won't work on the web, Listener Widget was demoed on Google I/O 2019 and worked with mouse, but that was as a ChromeOS app and not a web app.
According to Flutter web on GitHub:
At this time, desktop UI interactions are not fully complete, so a UI built with flutter_web may feel like a mobile app, even when running on a desktop browser.
Adapted answer by Constantin Stan
For those who want to have the click effect similar to InkWell widget and with border radius option:
Add to your pubspec.yaml file
dependencies:
universal_html: ^1.1.4
Then add to the index.html file the following the tag <body id="app-container"> as below:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Your App Title</title>
</head>
<body id="app-container">
<script src="main.dart.js" type="application/javascript"></script>
</body>
</html>
Finally create the following widget and use encapsulated all the necessary widgets:
import 'package:flutter/foundation.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:universal_html/prefer_sdk/html.dart' as html;
class InkWellMouseRegion extends InkWell {
InkWellMouseRegion({
Key key,
#required Widget child,
#required GestureTapCallback onTap,
double borderRadius = 0,
}) : super(
key: key,
child: !kIsWeb ? child : HoverAware(child: child),
onTap: onTap,
borderRadius: BorderRadius.circular(borderRadius),
);
}
class HoverAware extends MouseRegion {
// get a reference to the body element that we previously altered
static final appContainer = html.window.document.getElementById('app-container');
HoverAware({Widget child}) : super(
onHover: (PointerHoverEvent evt) {
appContainer.style.cursor='pointer';
// you can use any of these:
// 'help', 'wait', 'move', 'crosshair', 'text' or 'pointer'
// more options/details here: http://www.javascripter.net/faq/stylesc.htm
},
onExit: (PointerExitEvent evt) {
// set cursor's style 'default' to return it to the original state
appContainer.style.cursor='default';
},
child: child
);
}
Related
My app has several textfields and I want to have a tooltip so that users know can see the definition of each field.
I came across this answer, but it wasn't helpful: Flutter Tooltip on One Tap. Therefore I decided to try and fix it myself.
Here is how to do it:
First add GestureDetector as child for Tooltip,
TooltipTriggerMode.manual for triggerMode.
add onTapDown, onTapUp, and onTapCancel as follows
Widget build(BuildContext context) {
final tooltipkey = GlobalKey<TooltipState>();
return Tooltip(
key: tooltipkey,
message: message,
triggerMode: TooltipTriggerMode.manual, // make it manual
child: GestureDetector(
behavior: HitTestBehavior.opaque,
onTapDown: (_) => _onTapDown(tooltipkey), // add this
onTapUp: (_) => _onTapUpAndCancel(tooltipkey), // add this
onTapCancel: () => _onTapUpAndCancel(tooltipkey), // add this
child: Icon(EvaIcons.questionMarkCircleOutline),
),
);
}
and the helper functions shown inside the code above:
void _onTapDown(GlobalKey<TooltipState> tooltipkey) {
tooltipkey.currentState?.ensureTooltipVisible();
}
void _onTapUpAndCancel(GlobalKey<TooltipState> tooltipkey) {
tooltipkey.currentState?.deactivate();
}
Hooray, it works. Now you can hold down the icon to display the tooltip immediately instead of holding it down for a while (the default configuration of tooltip).
In my web enabled flutter app, To add media query I have added css class in body tag in index.html file.
<head>
....
<style>
.mobile{
max-width:auto;
}
#media (min-width: 600px) {
.mobile{
max-width:400px;
}
}
</style>
</head>
<body class="mobile">
<script src="main.dart.js" type="application/javascript"></script>
</body>
But flutter here is creating flt-glass-pane tag inside the body tag and it is rendering UI overflowing the body tag.
In my second approach, I have to add MediaQuery at screen level, but I don't want to add media query in each scaffold/screen.
So is there any alternative so that I could apply media query in MateriaApp or in index.html file only once.
Use "builder" in Material App
MaterialApp(
...
...
builder: (context, child) {
double screenWidth = MediaQuery.of(context).size.width;
return Center(
child: Container(
width: screenWidth > 600 ? 400 : screenWidth,
child: child,
),
);
},
);
i'm still new in using flutter driver in testing, but as far as i know there are few identifiers that we can use to locate / identify elements, like By Text, By Type, etc
But the problem is, the app that i want to test doesn't have the identifier that i can use to locate them (please correct me if i'm wrong).. the widget code of the app looks like this
Widget _buildNextButton() {
return Align(
alignment: Alignment.bottomRight,
child: Container(
child: IconButton(
icon: Icon(Icons.arrow_forward),
onPressed: () => _controller.nextPage(),
),
),
);
}
where that widget is on a class that extends StatefulWidget.
How can i locate that icon in my test script and click it? can i use something like this? And what type of finder should i use? (byValueKey? bySemanticLabel? byType? or what?)
static final arrowKey = find.byValueKey(LoginKey.nextButton);
TestDriverUtil.tap(driver, arrowKey);
We have text and value checks here in Flutter Driver but if you don't have that you can always go the the hierarchy of app.
what I mean by hierarchy is so button has fix or specific parent right?
Let's take your example here, We have Align > Container > IconButton > Icon widget hierarchy which will not be true for others like there might be IconButton but not with the Container parent.
or StreamBuilder or anything that we can think of.
Widget _buildNextButton() {
return Align(
alignment: Alignment.bottomRight,
child: Container(
child: IconButton(
icon: Icon(Icons.arrow_forward),
onPressed: () => print("clicked button"),
),
),
);
}
This hierarchy should be atleast ideal for top bottom or bottom top approach.
Now what I mean by Top to bottom approach is Align must have IconButton and for bottom to up approach we are saying IconButton must have Align widget as parent.
Here i have taken top down approach so what I'm checking from below code is finding IconButton who is decendent of Align Widget.
also i added firstMatchOnly true as I was checking what happens if same hierarchy appears for both so
test('IconButton find and tap test', () async {
var findIconButton = find.descendant(of: find.byType("Align"), matching: find.byType("IconButton"), firstMatchOnly: true);
await driver.waitFor(findIconButton);
await driver.tap(findIconButton);
await Future.delayed(Duration(seconds: 3));
});
to check for multiple IconButtons with same Align as parent, we need to have some difference like parent should be having Text view or other widget.
find.descendant(of: find.ancestor(
of: find.byValue("somevalue"),
matching: find.byType("CustomWidgetClass")), matching: find.byType("IconButton"), firstMatchOnly: true)
usually I go something like above where I have split the code in seperate file and check for that widget.
But ultimately find something unique about that widget and you can work on that.
**In Lib directory dart class for connecting that widget**
class Testing extends StatelessWidget {
Testing();
// This widget is the root of your application.
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: YourClass(), // Next button containing class that need to test
);
}
}
**In Test directory**
testWidgets('Next widget field test', (WidgetTester tester) async {
// Build our app and trigger a frame.
await tester.pumpWidget(Testing());
// find Widget
var buttonFind = find.byIcon(Icons.arrow_forward);
expect(buttonFind, findsOneWidget);
IconButton iconButton = tester.firstWidget(buttonFind);
expect(iconButton.color, Colors.blue);
});
I created a fixed display ad 320px *60px. How do I insert in flutter web where I want?
This is the code I obtained from adsense:
<script async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"></script>
<!-- MyAd -->
<ins class="adsbygoogle"
style="display:inline-block;width:320px;height:60px"
data-ad-client="xxxxxxxxx"
data-ad-slot="xxxxxxxx"></ins>
<script>
(adsbygoogle = window.adsbygoogle || []).push({});
</script>
Or is it possible to make flutter not occupy the bottom 60 pixels of the screen and insert the adsense ad there by manipulating flt-glass-pane somehow?
I'm looking at a way to insert adsense ads to a mobile website built in flutter-web
I was able to insert ads into the listview in flutter-web as below:
Created an html file adview.html
<script async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"></script>
<ins class="adsbygoogle"
style="display:inline-block;width:320px;height:80px"
data-ad-client="ca-pub-xxxxxx"
data-ad-slot="xxxxxxx"></ins>
<script>
(adsbygoogle = window.adsbygoogle || []).push({});
</script>
And then, an IFrameElement to make use of the html:
// ignore: avoid_web_libraries_in_flutter
import 'dart:html';
import 'dart:ui' as ui;
import 'package:flutter/material.dart';
Widget adsenseAdsView() {
// ignore: undefined_prefixed_name
ui.platformViewRegistry.registerViewFactory(
'adViewType',
(int viewID) => IFrameElement()
..width = '320'
..height = '100'
..src = 'adview.html'
..style.border = 'none');
return SizedBox(
height: 100.0,
width: 320.0,
child: HtmlElementView(
viewType: 'adViewType',
),
);
}
Then we can add the widget from the function adsenseAdsView() wherever we want. I added it in ListView.
There is a new package that can help with this. Hopefully makes the process a little simpler.
https://pub.dev/packages/admanager_web
Example to implement:
AdBlock(
size: AdBlockSize.largeRectangle,
adUnitId: "/6355419/Travel/Europe",)
You can add Adsense ad unit In Flutter Web with the help of below widget. It's work perfectly fine.
create widget with any name you like. I'm name BannerAdUnit
only replace div tag ad unit id and script add Url as you can see in the below widget inside html body tag.
import 'dart:html' as html;
import 'dart:ui' as ui;
import 'package:flutter/material.dart';
String viewID = "your-view-id";
class BannerAdUnit extends StatelessWidget {
const BannerAdUnit({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
ui.platformViewRegistry.registerViewFactory(
viewID,
(int id) => html.IFrameElement()
..style.width = '100%'
..style.height = '100%'
..srcdoc = '''
<!DOCTYPE html>
<html> <head> </head> <body>
<div data-frill-widget="your ad unit id will come here" style="width: 340px; height: 460px;"></div>
<script async src="url"></script> </body>
</html> '''
..style.border = 'none');
return SizedBox(
height: 460,
width: 340,
child: HtmlElementView(
viewType: viewID,
),
);
}
}
The story is not end here. When you add the above code You will see one beautiful error something like that.
The name 'platformViewRegistry' is being referenced through the prefix
'ui', but it isn't defined in any of the libraries imported using that
prefix. (Documentation)
How to fix this error? Don't worry I'll show you how to fix this error.
Create new file on the root name it analysis_options.yaml
and copy and paste the below code and the error is gone.
analyzer:
errors:
undefined_prefixed_name: ignore
Note: On the debugging mode flutter Web app not show you any ads.
if you want to see ads deploy your Flutter Web App. you will see ads like this.
If you guys don't want to write these spaghetti code try this package flutter_ad_manager_web. I created this helpful package for display Adsense ads on Flutter Web.
Hello i do have a problem using Webviews in my Webapp
case use :
Dashboard, which is loading multiple Webviews where each Webview with each local storage is merged with the main local storage.
first problem :
i had to use the easywebview package to solve my loading webviews issues of a webview on chrome, but i would like to use webview package.
second problem :
Whene i used my chrome app with an easywebview loading inside of it i had two localstorage running independantly i wish that i can merge all my localstorages in the dashboard one.
code sample
import 'package:easy_web_view/easy_web_view.dart';
class LoginFrameWidget extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Container(
height: double.infinity,
width: double.infinity,
child: EasyWebView(
src: "http://localhost:5111/",
isHtml: false,
isMarkdown: false,
convertToWidgets: false,
onLoaded: () {},
));
}
}
Image of the localstorages:
localstorages
Solution needed:
make one main LocalStorage
using webviews instead of easywebview (if possible)
after some research i ended up with a different solutions:
//setup iframe
_iframeElement.height = '500';
_iframeElement.width = '500';
//listen to iframe (window.post.message)
window.onMessage.listen((event) {
print(event.data);
//some localstorage logic
});
_iframeElement.src = 'path';
_iframeElement.style.border = 'none';
// ignore: undefined_prefixed_name
ui.platformViewRegistry.registerViewFactory(
'iframeElement',
(int viewId) => _iframeElement,
);
_iframeWidget = HtmlElementView(
key: UniqueKey(),
viewType: 'iframeElement',
);
and than i just passed the collected data on queries
if there is a better solution i would listen to that.