I am trying to change the color of the element the user clicked on using a GestureDetector:
new GestureDetector(
onTap: (){
// Change the color of the container beneath
},
child: new Container(
width: 80.0,
height: 80.0,
margin: new EdgeInsets.all(10.0),
color: Colors.orange,
),
),
The problem is that I can't use setState inside of onTap. Otherwise I would have created a color variable. Any suggestions?
You can use setState() inside of onTap. In fact, that's exactly the right thing to do in this situation. If you are having trouble calling setState(), make sure your widget is stateful (see the interactivity tutorial).
You might also want to check out FlatButton or InkWell as more material-y ways to capture touches. If you really want a GestureDetector, read up on HitTestBehavior to make sure you're configuring it correctly.
Here's an example that changes to a random color every time it's clicked.
import 'dart:math';
import 'package:flutter/material.dart';
void main() {
runApp(new MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Flutter Demo',
home: new MyHome(),
);
}
}
class MyHome extends StatefulWidget {
#override
State createState() => new _MyHomeState();
}
class _MyHomeState extends State<MyHome> {
final Random _random = new Random();
Color _color = Colors.orange;
#override
Widget build(BuildContext context) {
return new Scaffold(
body: new Center(
child: new GestureDetector(
onTap: () {
// Change the color of the container beneath
setState(() {
_color = new Color.fromRGBO(
_random.nextInt(256),
_random.nextInt(256),
_random.nextInt(256),
1.0
);
});
},
child: new Container(
width: 80.0,
height: 80.0,
margin: new EdgeInsets.all(10.0),
color: _color,
),
),
),
);
}
}
Related
Im new to flutter. I would like to ask a question about my code. I have take a look on youtube and some google tutorial on this inkwell and on tap function to open new class activity on flutter.But the result is, when the image is tapped it open different image screen but they share same class file.
How can I have a separate page for different image click. For example,
I have five image in my flutter carousel slider.
Image 1 will open sliderpage 1. Image 2 will open sliderpage 2 and so on.Means they are on separate page instead of different image open same page but only show different images. Im trying this tutorial but they do have same page but different images displayed after on tap event is called. url https://www.youtube.com/watch?v=l9XOUoJsdy4
class _HomePageState extends State<HomePage> {
#override
Widget build(BuildContext context) {
Widget image_carousel = new Container(
height: 345.0,
child: new Carousel(
boxFit: BoxFit.fill,
images: [
AssetImage('assets/s7.jpg'),
AssetImage('assets/s3.jpg'),
AssetImage('assets/s5.jpg'),
AssetImage('assets/s2.jpg'),
AssetImage('assets/s4.jpg'),
],
autoplay: true,
animationCurve: Curves.fastOutSlowIn,
animationDuration: Duration(milliseconds: 500),
dotColor: Colors.red[50],
dotSize: 4.0,
indicatorBgPadding: 2.0,
),
);
return Scaffold(
body: new Column(
children: <Widget>[
image_carousel,
//grid view
Container(
height:163.0,
child: Products(),
)
],
),
);
}
}
On this code, this code just display carousel image without any event on click is done , I was expecting to have different page routing by on tap event is happen when image assets is clicked and navigate to different pages.
First of all, you need to install carousel_slider, then create two screens:
The first one will contain carousel_slider when you click on the image it will navigate to the second screen and passing image URL of the image you clicked on, To have on tap event you need to wrap you Image widget with GestureDetector
import 'package:flutter/material.dart';
import 'package:carousel_slider/carousel_slider.dart';
import './image_screen.dart';
void main() => runApp(MaterialApp(home: Demo()));
class Demo extends StatefulWidget {
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<Demo> {
#override
Widget build(BuildContext context) {
Widget image_carousel = new Container(
height: 345.0,
child: CarouselSlider(
height: 400.0,
items: [
'http://pic3.16pic.com/00/55/42/16pic_5542988_b.jpg',
'http://photo.16pic.com/00/38/88/16pic_3888084_b.jpg',
'http://pic3.16pic.com/00/55/42/16pic_5542988_b.jpg',
'http://photo.16pic.com/00/38/88/16pic_3888084_b.jpg'
].map((i) {
return Builder(
builder: (BuildContext context) {
return Container(
width: MediaQuery.of(context).size.width,
margin: EdgeInsets.symmetric(horizontal: 5.0),
decoration: BoxDecoration(color: Colors.amber),
child: GestureDetector(
child: Image.network(i, fit: BoxFit.fill),
onTap: () {
Navigator.push<Widget>(
context,
MaterialPageRoute(
builder: (context) => ImageScreen(i),
),
);
}));
},
);
}).toList(),
));
return Scaffold(
body: new Column(
children: <Widget>[
image_carousel,
],
),
);
}
}
The second screen will contain only the image you clicked on:
import 'package:flutter/material.dart';
class ImageScreen extends StatefulWidget {
final String url;
ImageScreen(this.url);
#override
_MyImageScreen createState() => _MyImageScreen(url);
}
class _MyImageScreen extends State<ImageScreen> {
final String url;
_MyImageScreen(this.url);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('ImageScreen'),
),
body: Image.network(url, width: double.infinity));
}
}
I need to build a bottomNavigationBar with a vertical overflow TabBar item,
so I tried to use OverflowBox,it looks like useful.
but there is another problew,the overflowed part cannot respond to the button.
so what should I do make the GestureDetector effective?
or you have other ways to build a bottomNavigationBar like this?
thank you very much!
this is screen capture
this is main.dart:
import 'package:flutter/material.dart';
void main() => runApp(new MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Flutter Demo',
theme: new ThemeData(
primarySwatch: Colors.blue,
),
home: new MyHomePage(title: 'OverflowBox touch test'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => new _MyHomePageState();
}
List<Color> _colors = [
Colors.blue,
Colors.green,
Colors.yellow,
];
class _MyHomePageState extends State<MyHomePage> with SingleTickerProviderStateMixin{
String _tip = "";
TabController controller;
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text(widget.title),
),
body: new TabBarView(
controller: controller,
children: <Widget>[
new Center(child: new Text("page 1")),
new Center(child: new Text("page 2")),
new Center(child: new Text("page 3")),
],
),
bottomNavigationBar: new Container(
height: 72.0,
alignment: Alignment.bottomCenter,
color: const Color.fromRGBO(45, 45, 45, 1.0),
child: new TabBar(
controller: controller,
indicatorWeight: 0.01,
tabs: <Widget>[
_getBarItem(0),
_getBarItem(1),
_getBarItem(2),
],
),
),
);
}
#override
void initState() {
super.initState();
controller = new TabController(length: 3, vsync: this);
}
#override
void dispose() {
controller.dispose();
super.dispose();
}
Widget _getBarItem(int idx){
Widget ret = new Container(
width: 80.0,
height: idx==1?120.0:50.0,
color: _colors[idx],
);
if(idx==1){
ret = new OverflowBox(
maxHeight: double.infinity,
alignment: Alignment.bottomCenter,
child: new Container(
color: Colors.black,
child: new GestureDetector(
onTapDown: (x){
controller.animateTo(idx);
},
child: ret,
),
),
);
}
return ret;
}
}
Try to make your GestureDetector wrap your OverflowBox
Use this behavior on the Gesture Detector and then wrap your overflow with it.
GestureDetector(
behavior: HitTestBehavior.translucent,
onTap(){..
)
You should use a Stack with a Positioned widget instead of an OverflowBox.
PS: Using functions that return widget should be avoided.
I can tell you the reason. OverflowBox widget layout is sizedByParent, so the size is 72. When hit test will ingore the pointer out the size. so the child widget overflowed part cannot respond to the button.
I have a Hero that has a sub-element I want to change size as part of the hero animation (in this case, I want its aspect ratio to change).
Here's my (stripped down) build method:
Widget build(BuildContext build) {
final aspect = expanded ? 1.3 : 1.0;
return new Hero(
child: Column(
children: <Widget>[
new AspectRatio(
aspect: aspect,
child: // Stuff that creates a header
),
new Container(
child: // General descriptions
)
],
);
}
Right now, the aspect ratio jumps between the two values. My question is, can I animate aspect along with the hero animation, and have it slowly shift from 1.3 to 1.0 and back?
For a complete example (which also shows a weird overflow issue during a hero animation) see this Gist:
https://gist.github.com/fuzzybinary/74196bccecc6dd070b35474c4c9223d7
Since I'm in my house right now I'm not able to try out your code right now. But I have a working hero animation example with me. Try this out.
// main.dart class
import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart' show timeDilation;
import 'package:flutter_test7/photo_hero.dart';
import 'package:flutter_test7/second_page.dart';
class HeroAnimation extends StatelessWidget {
Widget build(BuildContext context) {
timeDilation = 2.5; // 1.0 means normal animation speed.
return new Scaffold(
appBar: new AppBar(
title: const Text('Basic Hero Animation'),
),
body: new Center(
child: new PhotoHero(
photo: 'images/flippers-alpha.png',
width: 300.0,
onTap: () {
Navigator.of(context).pushNamed('/second_page');
},
),
),
);
}
}
void main() {
runApp(
new MaterialApp(
home: new HeroAnimation(),
routes: <String, WidgetBuilder>{
'/second_page': (context) => new SecondPage()
},
),
);
}
// photo_hero.dart class
import 'package:flutter/material.dart';
class PhotoHero extends StatelessWidget {
const PhotoHero({Key key, this.photo, this.onTap, this.width})
: super(key: key);
final String photo;
final VoidCallback onTap;
final double width;
Widget build(BuildContext context) {
return new SizedBox(
width: width,
child: new Hero(
tag: photo,
child: new Material(
color: Colors.transparent,
child: new InkWell(
onTap: onTap,
child: new Image.asset(
photo,
fit: BoxFit.contain,
),
),
),
),
);
}
}
// second_page.dart class
import 'package:flutter/material.dart';
import 'package:flutter_test7/photo_hero.dart';
class SecondPage extends StatefulWidget {
#override
_SecondPageState createState() => new _SecondPageState();
}
class _SecondPageState extends State<SecondPage> {
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: const Text('Flippers Page'),
),
body: new Container(
color: Colors.lightBlueAccent,
padding: const EdgeInsets.all(16.0),
alignment: Alignment.topLeft,
child: new PhotoHero(
photo: 'images/flippers-alpha.png',
width: 100.0,
onTap: () {
Navigator.of(context).pop();
},
),
),
);
}
}
Hope this helps.
I'm trying to create a widget that has a button and whenever that button is pressed, a list opens up underneath it filling in all of the space under the button. I implemented it with a simple Column, something like this:
class _MyCoolWidgetState extends State<MyCoolWidget> {
...
#override
Widget build(BuildContext context) {
return new Column(
children: <Widget>[
new MyButton(...),
isPressed ? new Expanded(
child: new SizedBox(
width: MediaQuery.of(context).size.width,
child: new MyList()
)
) : new Container()
]
)
}
}
This works totally fine in a lot of cases, but not all.
The problem I'm having with creating this widget is that if a MyCoolWidget is placed inside a Row for example with other widgets, lets say other MyCoolWidgets, the list is constrained by the width that the Row implies on it.
I tried fixing this with an OverflowBox, but with no luck unfortunately.
This widget is different from tabs in the sense that they can be placed anywhere in the widget tree and when the button is pressed, the list will fill up all the space under the button even if this means neglecting constraints.
The following image is a representation of what I'm trying to achieve in which "BUTTON1" and "BUTTON2" or both MyCoolWidgets in a Row:
Edit: Snippet of the actual code
class _MyCoolWidgetState extends State<MyCoolWidget> {
bool isTapped = false;
#override
Widget build(BuildContext context) {
return new Column(
children: <Widget>[
new SizedBox(
height: 20.0,
width: 55.0,
child: new Material(
color: Colors.red,
child: new InkWell(
onTap: () => setState(() => isTapped = !isTapped),
child: new Text("Surprise"),
),
),
),
bottomList()
],
);
}
Widget comboList() {
if (isTapped) {
return new Expanded(
child: new OverflowBox(
child: new Container(
color: Colors.orange,
width: MediaQuery.of(context).size.width,
child: new ListView( // Random list
children: <Widget>[
new Text("ok"),
new Text("ok"),
new Text("ok"),
new Text("ok"),
new Text("ok"),
new Text("ok"),
new Text("ok"),
new Text("ok"),
new Text("ok"),
new Text("ok"),
new Text("ok"),
new Text("ok"),
new Text("ok"),
],
)
)
),
);
} else {
return new Container();
}
}
}
I'm using it as follows:
class Home extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new Row(
children: <Widget>[
new Expanded(child: new MyCoolWidget()),
new Expanded(child: new MyCoolWidget()),
]
)
}
}
Here is a screenshot of what the code is actually doing:
From the comments, it was clarified that what the OP wants is this:
Making a popup that covers everything and goes from wherever the button is on the screen to the bottom of the screen, while also filling it horizontally, regardless of where the button is on the screen. It would also toggle open/closed when the button is pressed.
There are a few options for how this could be done; the most basic would be to use a Dialog & showDialog, except that it has some issues around SafeArea that make that difficult. Also, the OP is asking for the button to toggle rather than pressing anywhere not the dialog (which is what dialog does - either that or blocks touches behind the dialog).
This is a working example of how to do something like this. Full disclaimer - I'm not stating that this is a good thing to do, or even a good way to do it... but it is a way to do it.
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
void main() => runApp(new MyApp());
// We're extending PopupRoute as it (and ModalRoute) do a lot of things
// that we don't want to have to re-create. Unfortunately ModalRoute also
// adds a modal barrier which we don't want, so we have to do a slightly messy
// workaround for that. And this has a few properties we don't really care about.
class NoBarrierPopupRoute<T> extends PopupRoute<T> {
NoBarrierPopupRoute({#required this.builder});
final WidgetBuilder builder;
#override
Color barrierColor;
#override
bool barrierDismissible = true;
#override
String barrierLabel;
#override
Widget buildPage(BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation) {
return new Builder(builder: builder);
}
#override
Duration get transitionDuration => const Duration(milliseconds: 100);
#override
Iterable<OverlayEntry> createOverlayEntries() sync* {
// modalRoute creates two overlays - the modal barrier, then the
// actual one we want that displays our page. We simply don't
// return the modal barrier.
// Note that if you want a tap anywhere that isn't the dialog (list)
// to close it, then you could delete this override.
yield super.createOverlayEntries().last;
}
#override
Widget buildTransitions(
BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation, Widget child) {
// if you don't want a transition, remove this and set transitionDuration to 0.
return new FadeTransition(opacity: new CurvedAnimation(parent: animation, curve: Curves.easeOut), child: child);
}
}
class PopupButton extends StatefulWidget {
final String text;
final WidgetBuilder popupBuilder;
PopupButton({#required this.text, #required this.popupBuilder});
#override
State<StatefulWidget> createState() => PopupButtonState();
}
class PopupButtonState extends State<PopupButton> {
bool _active = false;
#override
Widget build(BuildContext context) {
return new FlatButton(
onPressed: () {
if (_active) {
Navigator.of(context).pop();
} else {
RenderBox renderbox = context.findRenderObject();
Offset globalCoord = renderbox.localToGlobal(new Offset(0.0, context.size.height));
setState(() => _active = true);
Navigator
.of(context, rootNavigator: true)
.push(
new NoBarrierPopupRoute(
builder: (context) => new Padding(
padding: new EdgeInsets.only(top: globalCoord.dy),
child: new Builder(builder: widget.popupBuilder),
),
),
)
.then((val) => setState(() => _active = false));
}
},
child: new Text(widget.text),
);
}
}
class MyApp extends StatefulWidget {
#override
State<StatefulWidget> createState() => MyAppState();
}
class MyAppState extends State<MyApp> {
#override
Widget build(BuildContext context) {
return new MaterialApp(
home: new SafeArea(
child: new Container(
color: Colors.white,
child: new Column(children: [
new PopupButton(
text: "one",
popupBuilder: (context) => new Container(
color: Colors.blue,
),
),
new PopupButton(
text: "two",
popupBuilder: (context) => new Container(color: Colors.red),
)
]),
),
),
);
}
}
For even more outlandish suggestions, you can take the finding the location part of this and look at this answer which describes how to create a child that isn't constrained by it's parent's position.
However you end up doing this, it's probably best that the list not to be a direct child of the button as a lot of things in flutter depend on a child's sizing and making it be able to expand to the full screen size could quite easily cause problems.
I have a button that has an image as as a child. I want it so that while the button is being pressed, the image changes to a different one, and when the user stops pressing the button, it goes back to its original image.
Basically I want it to act like a raised button but with custom images for the raised and pressed states.
Here's the relevant code:
class LoginButton extends StatefulWidget {
#override
_LoginButtonState createState() => new _LoginButtonState();
}
class _LoginButtonState extends State<LoginButton> {
void _onClicked() {
setState(() {
//I don't know what I should put here to cause the image to redraw
//only on button press
});
}
#override
Widget build(BuildContext context) {
var assetImage = new AssetImage("assets/loginscreen/btn.png");
var image = new Image(image: assetImage, height: 50.0, width: 330.0);
return new Container(
height: image.height,
width: image.width,
child: new FlatButton(
onPressed: _onClicked,
child: new ConstrainedBox(
constraints: new BoxConstraints.expand(),
child: image,
),
),
);
}
}
You can define your default image as a property of the class _LoginButtonStatelike this
class _LoginButtonState extends State<LoginButton> {
String _myImage = "assets/loginscreen/btn.png"; ... }
Now your _onClick method should contain the change of the state, simply you can change the string of _myImage to the new image using an if condition
void _onClicked() {
setState(() {
if (_myImage == "assets/loginscreen/btn.png"){
_myImage = "<my new asset>"; //change myImage to the other one
}
else {
_myImage = "assets/loginscreen/btn.png"; //change myImage back to the original one
}
});
}
and within your widget build:
var assetImage = new AssetImage(_myImage);
===========================================================================
Update
I have managed to do a similar idea to what you are trying to achieve using GesutureDetector , here I tested it with colors, but it should not be so different from changing a link of an image, although I presume repainting an image would be slower than changing colors.
Here is the full code I used:
import 'package:flutter/material.dart';
void main() {
runApp(new MaterialApp(
home: new MyApp (),
));
}
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => new _MyAppState();
}
class _MyAppState extends State<MyApp> {
var _myColor = Colors.blue;
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text ("Tap me!"),
centerTitle: true,
),
body: new GestureDetector(
onTapDown:(TapDownDetails details) { setState(() {
_myColor = Colors.orange;
});
},
onTapUp: (TapUpDetails details) {
setState(() {
_myColor = Colors.blue;
});
},
child: new Container (
color: _myColor,
),
),
);
}
}
For me i wanted a button with no extra padding and would be darken when i press it!
Here is the code that work for me:
Container(
height: image.height,
width: image.width,
decoration: BoxDecoration(
image: DecorationImage(
image: AssetImage('/images/image.jpeg'),
),
),
child: new FlatButton(
padding: EdgeInsets.all(0.0),
onPressed: () {},
child: null),
),
Credits to https://www.youtube.com/watch?v=Ze-BZBv85Ck! This video help me for this! :)