I have just a stack that contains a guillotine menu and a list. But below page (list and fab) isn't clickable! First code is Main page, second code is books (below page) and third code is guillotine menu.
How can I make clickable below page?
main page
import 'package:firebase_example/Ui/Books.dart';
import 'package:firebase_example/Ui/GuillotineMenu.dart';
import 'package:flutter/material.dart';
class Home extends StatefulWidget {
#override
_HomeState createState() => _HomeState();
}
class _HomeState extends State<Home> {
final GlobalKey<ScaffoldState> _drawerKey = new GlobalKey();
#override
Widget build(BuildContext context) {
return new MaterialApp(
debugShowCheckedModeBanner: false,
home: SafeArea(
top: false,
bottom: false,
child: new Container(
child: new Stack(
alignment: Alignment.topLeft,
children: <Widget>[
Books(),
GuillotineMenu(),
],
),
),
),
);
What you're looking for is IgnorePointer. By using it you should be able to exclude your widget from hit testing (and so allow things under it to be touched instead).
You're going to have to be careful about how you implement the button that makes the guillotine menu open/close though. I'd advise making it a part of the item lower in the stack, and then setting IgnorePointer's ignoring = true when the guillotine is closed.
Note that there might be a 'better' way of implementing the guillotine menu using PageRoutes. That way you'd just be pushing/popping a new route on top of the existing navigator rather than having to maintain your own stack.
Here's the code:
class GuillotinePageRoute<T> extends PageRoute<T> {
GuillotinePageRoute({
#required this.builder,
RouteSettings settings: const RouteSettings(),
this.maintainState: true,
bool fullscreenDialog: false,
}) : assert(builder != null),
super(settings: settings, fullscreenDialog: fullscreenDialog);
final WidgetBuilder builder;
#override
final bool maintainState;
#override
Duration get transitionDuration => const Duration(milliseconds: 500);
#override
Color get barrierColor => null;
#override
Widget buildPage(BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation) {
final Widget result = builder(context);
assert(() {
if (result == null) {
throw new FlutterError('The builder for route "${settings.name}" returned null.\n'
'Route builders must never be null.');
}
return true;
}());
return result;
}
#override
Widget buildTransitions(
BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation, Widget child) {
MediaQueryData queryData = MediaQuery.of(context);
var topInset = queryData.padding.top;
Offset origin = Offset((kToolbarHeight / 2.0), topInset + (kToolbarHeight / 2.0));
Curve curve = animation.status == AnimationStatus.forward ? Curves.bounceOut : Curves.bounceIn;
var rotateTween = new Tween(begin: -pi / 2.0, end: 0.0);
Cubic opacityCurve = Cubic(0.0, 1.0, 0.0, 1.0);
return new AnimatedBuilder(
animation: animation,
child: child,
builder: (context, child) {
return Opacity(
opacity: opacityCurve.transform(animation.value),
child: Transform(
transform: Matrix4.identity()..rotateZ(rotateTween.lerp(curve.transform(animation.value))),
origin: origin,
child: child,
),
);
},
);
}
#override
String get barrierLabel => null;
}
And using it in an example:
import 'dart:math';
import 'package:flutter/material.dart';
void main() => runApp(new MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Flutter Demo',
theme: new ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(),
);
}
}
class MenuPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Column(children: [
AppBar(
leading: new IconButton(icon: Icon(Icons.menu), onPressed: () => Navigator.pop(context)),
elevation: 0.0,
),
Expanded(
child: Container(
child: Center(
child: Text(
"Menu page!",
style: TextStyle(color: Colors.white, decoration: TextDecoration.none),
),
),
color: Colors.blue,
),
),
]);
}
}
class MyHomePage extends StatelessWidget {
void pushGuillotine(BuildContext context, WidgetBuilder builder) {
Navigator.push(context, new GuillotinePageRoute(builder: builder));
}
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text("This is a title"),
leading: new RotatedBox(
quarterTurns: -1,
child: IconButton(icon: Icon(Icons.menu), onPressed: () => pushGuillotine(context, (context) => MenuPage())),
),
),
body: Container(
color: Colors.blueGrey,
child: Center(
child: new Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
new Text(
'This is the home page.',
),
new Text(
'Hello world!',
style: Theme.of(context).textTheme.display1,
),
],
),
),
),
);
}
}
class GuillotinePageRoute<T> extends PageRoute<T> {
GuillotinePageRoute({
#required this.builder,
RouteSettings settings: const RouteSettings(),
this.maintainState: true,
bool fullscreenDialog: false,
}) : assert(builder != null),
super(settings: settings, fullscreenDialog: fullscreenDialog);
final WidgetBuilder builder;
#override
final bool maintainState;
#override
Duration get transitionDuration => const Duration(milliseconds: 500);
#override
Color get barrierColor => null;
#override
Widget buildPage(BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation) {
final Widget result = builder(context);
assert(() {
if (result == null) {
throw new FlutterError('The builder for route "${settings.name}" returned null.\n'
'Route builders must never be null.');
}
return true;
}());
return result;
}
#override
Widget buildTransitions(
BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation, Widget child) {
MediaQueryData queryData = MediaQuery.of(context);
var topInset = queryData.padding.top;
Offset origin = Offset((kToolbarHeight / 2.0), topInset + (kToolbarHeight / 2.0));
Curve curve = animation.status == AnimationStatus.forward ? Curves.bounceOut : Curves.bounceIn;
var rotateTween = new Tween(begin: -pi / 2.0, end: 0.0);
Cubic opacityCurve = Cubic(0.0, 1.0, 0.0, 1.0);
return new AnimatedBuilder(
animation: animation,
child: child,
builder: (context, child) {
return Opacity(
opacity: opacityCurve.transform(animation.value),
child: Transform(
transform: Matrix4.identity()..rotateZ(rotateTween.lerp(curve.transform(animation.value))),
origin: origin,
child: child,
),
);
},
);
}
#override
String get barrierLabel => null;
}
It's a stack use the gesture detector's onTapDown which is not available in a floating action button
If you have a widget with no interaction and want to make it clickable use the GestureDetector widget as parent, you can see the example in the link.
--- edit
This answer helps you when you need to make a widget without interaction have some, not exactly what this question is talk about, honest mistake but i could help others..
Related
Im building a list from Firestore collection stream using ListViewBuilder, each item is a Text widget in the list, Im trying to change color of the text onTap the Text widget,
Item 1
Item 2
Item 3
when touching/onTap Item1, text color of Item1 should be changed
I implemented using GestureDetector with setState but on onTap>setState execution, the listview of the stream is rebuilt, giving a second of blank screen/flicker and loosing the actual state since its refreshed
var _dynamicTextColor = Colors.green;
return ListView.builder(
reverse: true,
itemCount: itemStream.length,
itemBuilder: (context, itemIndex) => Container(
child:GestureDetector(
onTap: (){
setState(() {
_dynamicTextColor = Colors.white;
});
},
child: Text(itemStream[itemIndex]['title'], style: TextStyle(color:_dynamicTextColor),),
),
),
);
You can try the following. This will use the selected property to decide which container should be blue.
class _TestState extends State<Test> {
String selected = "first";
#override
Widget build(BuildContext context) {
return Column(
children: <Widget>[
GestureDetector(
onTap: () {
setState(() {
selected = 'first';
});
},
child: Container(
height: 200,
width: 200,
color: selected == 'first' ? Colors.blue : Colors.transparent,
child: Text("First"),
),
),
GestureDetector(
onTap: () {
setState(() {
selected = 'second';
});
},
child: Container(
height: 200,
width: 200,
color: selected == 'second' ? Colors.blue : Colors.transparent,
child: Text("Second"),
),
),
],
);
}
}
I recommend you use a state management, for example Provider.
I prepared an example, I hope you understand it and solve your problem.
First, add the provider package to pubspec.yaml
provider: ^4.3.3
Then, create a class where you will manage the states, as you would with setState. The code would be like this:
class ColorProvider extends ChangeNotifier {
ColorProvider();
bool isPressed = true;
Color color = Colors.black;
changeColor() {
if (isPressed == true) {
color = Colors.green;
isPressed = false;
} else {
color = Colors.black;
isPressed = true;
}
notifyListeners();
return isPressed;
}
}
Once this is done, you must Wrap the App material in a ChangeNotifierProvider widget:
import 'package:flutter/material.dart';
import 'package:flutter_application_1/home_page.dart';
import 'package:provider/provider.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return ChangeNotifierProvider(
create: (_) => ColorProvider(),
child: MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: HomePage(),
),
);
}
}
And finally, call the Provider in the widget:
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
class HomePage extends StatelessWidget {
#override
Widget build(BuildContext context) {
final providerWatch = context.watch<ColorProvider>();
final providerRead = context.read<ColorProvider>();
String text = 'This is an example text';
return Scaffold(
body: Container(
child: Center(
child: GestureDetector(
behavior: HitTestBehavior.translucent,
onTap: () => providerRead.changeColor(),
child: Text(
text,
style: TextStyle(color: providerWatch.color),
)),
),
));
}
}
and this would be the result:
I'm trying to make an application like Instagram. But I can't fully view the zoomed picture because of other pictures.
Codes:
List colors = [
Colors.green,
Colors.blue,
Colors.red,
];
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Demo"),
),
body: ListView.builder(
itemCount: 3,
itemBuilder: (_, int index) {
TransformationController controller = TransformationController();
return Container(
child: InteractiveViewer(
transformationController: controller,
onInteractionEnd: (_) {
setState(() {
controller.toScene(Offset.zero);
});
},
child: ColorFiltered(
colorFilter: ColorFilter.mode(colors[index], BlendMode.color),
child: FlutterLogo(),
),
),
height: 200,
width: 200,
);
},
),
);
}
Note: I can't use widgets like stack because of listview.builder
You could either use Visibility widget or Opacity widget to change the visibility of images not being currently panned. Please see the code below I'm using AnimatedOpacity just so that the disappearance of the images does not look too sudden, but you may use Opacity widget as well.
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(title: const Text("Flutter Demo")),
body: MyStatefulWidget(),
),
);
}
}
class MyStatefulWidget extends StatefulWidget {
#override
_MyStatefulWidgetState createState() => _MyStatefulWidgetState();
}
class _MyStatefulWidgetState extends State<MyStatefulWidget> {
static List colors = [
Colors.green,
Colors.blue,
Colors.red,
];
final List<double> _opacity = List.generate(colors.length, (index) => 1);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("Demo"),
),
body: ListView.builder(
itemCount: 3,
itemBuilder: (_, int index) {
final TransformationController controller =
TransformationController();
return AnimatedOpacity(
opacity: _opacity[index],
duration: const Duration(milliseconds: 100),
child: Container(
child: InteractiveViewer(
transformationController: controller,
onInteractionStart: (details) {
for (int i = 0; i < _opacity.length; i++) {
_opacity[i] = i == index ? 1 : 0;
}
setState(() {});
},
onInteractionEnd: (_) {
setState(() {
_opacity.fillRange(0, _opacity.length, 1);
controller.toScene(Offset.zero);
});
},
child: ColorFiltered(
colorFilter: ColorFilter.mode(colors[index], BlendMode.color),
child: const FlutterLogo(),
),
),
height: 200,
width: 200,
),
);
},
),
);
}
}
I am trying to use Flutter's 'EditableText' class.
I have 3 questions regarding it.
'enableInteractiveSelection' is set to be 'true' but I do not see 'cut, copy, paste'.
'onSelectionChanged' seems not working. I don't see anything in the console even when I change selection.
can someone explain me how to use 'selectionControls'?
Below is my 'EditableText' code
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Editable Text'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
TextEditingController _mainContentController;
TextSelectionControls _mainContentSelectionController;
FocusNode _textFieldFocusNode;
#override
void initState() {
_textFieldFocusNode = FocusNode();
_mainContentController = TextEditingController();
super.initState();
}
#override
void dispose() {
_mainContentController.dispose();
_textFieldFocusNode.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Column(
children: <Widget>[
Text('Editable Text Below'),
Container(
padding: EdgeInsets.all(10),
child: EditableText(
focusNode: _textFieldFocusNode,
controller: _mainContentController,
selectionColor: Colors.blue,
style: TextStyle(color: Colors.black, fontSize: 17),
cursorColor: Colors.blue,
textInputAction: TextInputAction.newline,
maxLines: null,
onChanged: (text) {
print(text);
},
enableInteractiveSelection: true,
onSelectionChanged:
(TextSelection textSelection, SelectionChangedCause cause) {
print('working?');
print(textSelection.start);
print(cause);
},
selectionControls: _mainContentSelectionController,
),
)
],
),
);
}
}
You can just use the TextField which has a custom implementation for interactive controllers for Android and iOS.
This is an implementation for Android :
class _MaterialTextSelectionControls extends TextSelectionControls {
#override
Size handleSize = const Size(_kHandleSize, _kHandleSize);
/// Builder for material-style copy/paste text selection toolbar.
#override
Widget buildToolbar(BuildContext context, Rect globalEditableRegion, Offset position, TextSelectionDelegate delegate) {
assert(debugCheckHasMediaQuery(context));
assert(debugCheckHasMaterialLocalizations(context));
return ConstrainedBox(
constraints: BoxConstraints.tight(globalEditableRegion.size),
child: CustomSingleChildLayout(
delegate: _TextSelectionToolbarLayout(
MediaQuery.of(context).size,
globalEditableRegion,
position,
),
child: _TextSelectionToolbar(
handleCut: canCut(delegate) ? () => handleCut(delegate) : null,
handleCopy: canCopy(delegate) ? () => handleCopy(delegate) : null,
handlePaste: canPaste(delegate) ? () => handlePaste(delegate) : null,
handleSelectAll: canSelectAll(delegate) ? () => handleSelectAll(delegate) : null,
),
)
);
}
/// Builder for material-style text selection handles.
#override
Widget buildHandle(BuildContext context, TextSelectionHandleType type, double textHeight) {
final Widget handle = Padding(
padding: const EdgeInsets.only(right: 26.0, bottom: 26.0),
child: SizedBox(
width: _kHandleSize,
height: _kHandleSize,
child: CustomPaint(
painter: _TextSelectionHandlePainter(
color: Theme.of(context).textSelectionHandleColor
),
),
),
);
// [handle] is a circle, with a rectangle in the top left quadrant of that
// circle (an onion pointing to 10:30). We rotate [handle] to point
// straight up or up-right depending on the handle type.
switch (type) {
case TextSelectionHandleType.left: // points up-right
return Transform(
transform: Matrix4.rotationZ(math.pi / 2.0),
child: handle
);
case TextSelectionHandleType.right: // points up-left
return handle;
case TextSelectionHandleType.collapsed: // points up
return Transform(
transform: Matrix4.rotationZ(math.pi / 4.0),
child: handle
);
}
assert(type != null);
return null;
}
}
You can check the source code from text_selection.dart, the same for cupertino.
In your sample you are using a null variable _mainContentSelectionController for that reason you don't see anything when long press your input.
Try creating a new class from TextSelectionControls and copy the logic from _MaterialTextSelectionControls.
Sample:
selectionControls: MySelectionControls(),
...
class MySelectionControls extends TextSelectionControls {
#override
Widget buildHandle(BuildContext context, TextSelectionHandleType type, double textLineHeight) {
// TODO: implement buildHandle
return null;
}
#override
Widget buildToolbar(BuildContext context, Rect globalEditableRegion, Offset position, TextSelectionDelegate delegate) {
// TODO: implement buildToolbar
return null;
}
#override
// TODO: implement handleSize
Size get handleSize => null;
}
I have been trying to create backdrop menu with some navigation buttons each with his own stateless widget page . I want to pass the child widget page based on index position of the clicked menu.
backlayer.dart
class BackLayer extends StatefulWidget {
AnimationController controller;
Widget child;
BackLayer({this.controller});
_BackLayerState createState() => _BackLayerState();
}
class _BackLayerState extends State<BackLayer> {
var _currentPageIndex;
List<String> navlist = ['Home', 'About', 'Contact Us'];
bool get isPanelVisible {
final AnimationStatus status = widget.controller.status;
return status == AnimationStatus.completed ||
status == AnimationStatus.forward;
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: ListView.builder(
itemCount: navlist.length,
itemBuilder: (BuildContext context, int index) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
ListTile(
onTap: () {
setState(() {
_currentPageIndex = navlist[index];
});
// PanelClass(frontLayer: About(),);
print(navlist[index].toString());
widget.controller
.fling(velocity: isPanelVisible ? -1.0 : 1.0);
},
title: Text(
navlist[index],
textAlign: TextAlign.center,
style: TextStyle(
color: Colors.black87, fontWeight: FontWeight.bold),
),
)
],
);
I want to pass the reference of the clicked menu inside backdrop.dart ,where i am gonna pass that reference to panelClass body as the child .
Backdrop.dart
class BackDrop extends StatefulWidget {
_BackDropState createState() => _BackDropState();
}
class _BackDropState extends State<BackDrop> with TickerProviderStateMixin {
AnimationController controller;
#override
void initState() {
super.initState();
controller = AnimationController(
duration: Duration(seconds: 3), vsync: this, value: 1.0);
}
#override
void dispose() {
super.dispose();
controller.dispose();
}
bool get isPanelVisible {
final AnimationStatus status = controller.status;
return status == AnimationStatus.completed ||
status == AnimationStatus.forward;
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
elevation: 0.0,
backgroundColor: Theme.of(context).accentColor,
title: Text('Home'),
leading: IconButton(
onPressed: () {
controller.fling(velocity: isPanelVisible ? -1.0 : 1.0);
},
icon: AnimatedIcon(
progress: controller.view,
icon: AnimatedIcons.close_menu,
),
),
),
body: PanelClass(controller: controller,frontLayer: 'Need to get reference here',),
);
}
}
I'm new in flutter development and I have a problem:
I create new AnimatedCrossFade and add two different ListView to first and second child, but always one child ListView scrollable. How can I do scrollable ListView in first and second child?
(Same situation with simple GridView.)
This looks like a bug in AnimatedCrossFade. I filed an issue.
You can work around it by not using AnimatedCrossFade and building your own animation using FadeTransition. Source code is below. Click the play button, it will start like this
and end like this:
You can fade back and forth by clicking the button again. Both lists are scrollable and it remembers your scroll position.
import 'package:flutter/widgets.dart';
import 'package:flutter/material.dart';
void main() {
runApp(new MyApp());
}
class MyApp extends StatefulWidget {
createState() => new MyAppState();
}
class MyAppState extends State<MyApp> with TickerProviderStateMixin {
AnimationController _controller;
Key _key1 = new GlobalKey();
Key _key2 = new GlobalKey();
#override
void initState() {
_controller = new AnimationController(
duration: const Duration(milliseconds: 700),
vsync: this,
);
}
Widget _buildList(Key key, Color color, double opacity) {
return new Opacity(
opacity: opacity,
child: new Container(
// Keep the hidden ListView around to maintain its scroll position
// but set its height to 0.0 so it can't interfere with touches
height: opacity == 0.0 ? 0.0 : null,
decoration: new BoxDecoration(color: color),
child: new ListView(
key: new GlobalObjectKey(color),
children: new List.generate(
100,
(index) => new Text('Item $index'),
),
),
),
);
}
#override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Flutter Demo',
home: new Scaffold(
floatingActionButton: new FloatingActionButton(
child: new Icon(Icons.play_arrow),
onPressed: () {
if (_controller.status == AnimationStatus.dismissed ||
_controller.status == AnimationStatus.reverse) {
_controller.forward();
} else {
_controller.reverse();
}
},
),
body: new Center(
child: new Container(
width: 100.0,
height: 100.0,
child: new AnimatedBuilder(
animation: _controller,
builder: (BuildContext context, Widget child) {
return new Stack(
children: [
_buildList(_key1, Colors.red[200], _controller.value),
_buildList(_key2, Colors.blue[200], 1.0 - _controller.value),
],
);
}
),
),
),
),
);
}
}
}