Is it possible to override the opacity value inside a child widget?
I have a list of items and based on an inactive status I'm making them partially transparent.
ListView.builder(
itemBuilder:(c,i) {
if(status) return MyCard(active:status);
else return Opacity(opacity: 0.5, child: MyCard(active: status);
},
itemCount: 5,
);
But now, all the widgets regardless of active or inactive need to show a download button with full visibility.
class MyCard extends StatelessWidget{
///
Widget build(c){
return Column(
children:[
WidgetA(),
WidgetB(),
// this should be always fully visible.
// Can we override the parent's opacity property somehow?
DownloadButton(),
]
);
}
}
Is this behavior possible using Opacity? Or do I need to visit each of the child items separately?
Wrap WidgetA and WidgetB inside a Column whose parent is Opacity.
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: ListView(
children: <Widget>[
MyCard(false),
MyCard(true),
MyCard(false),
],
),
),
);
}
}
class MyCard extends StatelessWidget {
MyCard(this.status);
final bool status;
#override
Widget build(BuildContext context) {
return Column(
children: <Widget>[
Opacity(
opacity: status ? 1 : 0.3,
child: Column(
children: const <Widget>[
Text('Widget A'),
Text('Widget B'),
],
),
),
TextButton(
onPressed: () {},
child: const Text('DL Button'),
),
],
);
}
}
Is this behavior possible using Opacity?
No
Or do I need to visit each of the child items separately?
Yes
You can split your MyCard widget not contain DownloadButton and then Opacity only for MyCard
Related
There are lots of widgets like Visibility or AnimatedOpacity, but I want a widget to appear and grow to its full size with a smooth animation, moving the other Column's children around it apart.
AnimatedContainer would be cool, but I don't want to set the child's constraints (e.g. height) from the parent (then I would have to test and set the correct size on every UI change).
Use Hero Animation check this link to learn:
https://docs.flutter.dev/development/ui/animations/hero-animations
Example :
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
initialRoute: '/first',
routes: {
'/first': (context) => FirstScreen(),
'/second': (context) => SecondScreen(),
},
);
}
}
class FirstScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('First Screen'),
),
body: Padding(
padding: EdgeInsets.all(15),
child: Column(
children: [
Hero(
tag: "HeroOne",
child: Icon(
Icons.image,
size: 50.0,
),
),
ElevatedButton(
child: Text('Go to second screen'),
onPressed: () {
Navigator.push(context, CustomPageRoute(SecondScreen()));
},
),
],
),
),
);
}
}
class SecondScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Second Screen"),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Hero(
tag: "HeroOne",
child: Icon(
Icons.image,
size: 150.0,
),
),
ElevatedButton(
child: Text('Back to first screen!'),
onPressed: () {
Navigator.pop(context);
},
),
],
)
),
);
}
}
class CustomPageRoute<T> extends PageRoute<T> {
final Widget child;
CustomPageRoute(this.child);
#override
Color get barrierColor => Colors.black;
#override
String get barrierLabel => '';
#override
bool get maintainState => true;
#override
Duration get transitionDuration => Duration(seconds: 2);
#override
Widget buildPage({1}
BuildContext context,
Animation<double> animation,
Animation<double> secondaryAnimation
) {
return FadeTransition(
opacity: animation,
child: child,
);
}
}
For More Check :
https://www.woolha.com/tutorials/flutter-creating-hero-transition-examples
While researching more for my own question, I found the AnimatedSize widget which does exactly what I need:
To dynamically show and hide a widget with a size animation, just wrap it in AnimatedSize() and give it a duration.
https://api.flutter.dev/flutter/widgets/AnimatedSize-class.html
I'm writing a very simple coupon app, however I have a problem with navigating to different pages. I have two buttons, each of them is used to navigate to a different page, but Flutter is returning a black screen instead.
Here's the error: "There are multiple heroes that share the same tag within a subtree.".
Here's the video: https://streamable.com/id4nf2
Here's the code:
Hamburger
import 'package:flutter/cupertino.dart';
import 'package:makdolan_flutter/shared/home_screen_items.dart';
class Hamburger extends StatelessWidget {
#override
Widget build(BuildContext context) {
return CupertinoPageScaffold(
navigationBar: CupertinoNavigationBar(
middle: Text('Hamburger'),
),
child: SafeArea(
child: HomeScreenItems(
imagePath: 'lib/assets/images/coupon_hamburger.png',
onClassicCouponPressed: () {
Navigator.pushNamed(context, '/classic-coupon');
},
onMailCouponPressed: () {},
),
),
);
}
}
Home Screen Items
import 'package:flutter/cupertino.dart';
double _sizedBoxHeight = 16.0;
double _paddingHorizontal = 8.0;
class HomeScreenItems extends StatelessWidget {
final String imagePath;
final VoidCallback onClassicCouponPressed;
final VoidCallback onMailCouponPressed;
HomeScreenItems({#required this.imagePath, this.onClassicCouponPressed, this.onMailCouponPressed});
#override
Widget build(BuildContext context) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Image.asset(imagePath),
SizedBox(height: _sizedBoxHeight),
Padding(
padding: EdgeInsets.symmetric(horizontal: _paddingHorizontal),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
CupertinoButton(
child: Text('KLASYCZNY KUPON'),
color: CupertinoColors.activeBlue,
onPressed: onClassicCouponPressed,
),
SizedBox(height: _sizedBoxHeight),
CupertinoButton(
child: Text('KUPON Z MAILA'),
color: CupertinoColors.activeOrange,
onPressed: onMailCouponPressed,
)
],
),
)
],
);
}
}
Classic Coupon
import 'package:flutter/cupertino.dart';
class ClassicCoupon extends StatelessWidget {
#override
Widget build(BuildContext context) {
return CupertinoPageScaffold(
child: Text('Test'),
);
}
}
I don't know the code you use to change the screen with the BottomNavigationBar but if you're preserving them with a CupertinoTabBar or PageView or anything and each page has its own CupertinoNavigationBar then by default they use a heroTag for animate from one route to another when using Nagivation.pop/push/etc
Try give them a hero tag to each one
class Hamburger extends StatelessWidget {
#override
Widget build(BuildContext context) {
return CupertinoPageScaffold(
navigationBar: CupertinoNavigationBar(
middle: Text('Hamburger'),
heroTag: 'HamburgerTag' //give them a different heroTag to each one
),
child: SafeArea(
child: HomeScreenItems(
imagePath: 'lib/assets/images/coupon_hamburger.png',
onClassicCouponPressed: () {
Navigator.pushNamed(context, '/classic-coupon');
},
onMailCouponPressed: () {},
),
),
);
}
}
class ClassicCoupon extends StatelessWidget {
#override
Widget build(BuildContext context) {
return CupertinoPageScaffold(
navigationBar: CupertinoNavigationBar(heroTag: 'HamburgerTag') //Give the same name so it makes the transition animation
child: Text('Test'),
);
}
}
Do the same to the other 2 pages (Jak and Lody).
class Jak extends StatelessWidget {
#override
Widget build(BuildContext context) {
return CupertinoPageScaffold(
navigationBar: CupertinoNavigationBar(
middle: Text('Jak'),
heroTag: 'JakTag' //give them a different heroTag to each one
),
child: ...
);
}
}
class Lody extends StatelessWidget {
#override
Widget build(BuildContext context) {
return CupertinoPageScaffold(
navigationBar: CupertinoNavigationBar(
middle: Text('Lody'),
heroTag: 'LodyTag' //give them a different heroTag to each one
),
child: ...
);
}
}
When the Navigator try to move to a new route it checks all the heroTags from the previous and the next route and start doing the animations (it cheks the one with the same name to start animating), if you don't define the tags it will use a _defaultHeroTag, and if there are 2 tags with the same value the error There are multiple heroes that share the same tag within a subtree. happens
You need to use Scaffold () for any page where you don't want black screen while navigation.
Its should be like return Scaffold (body: Column())
EDITED
Hero widget tag needs to be unique.
void main() {
runApp(
CupertinoApp(
routes: {
"Screen1": (context) => Screen1(),
"Screen2": (context) => Screen2(),
},
home: Screen1(),
),
);
}
class Screen1 extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: [
Hero(
tag: "Screen2",
child: GestureDetector(
onTap: () {
Navigator.pushNamed(context, "Screen2");
},
child: Image.asset("assets/images/banane.jpg")),
),
RaisedButton(
color: Colors.blue,
child: Text("go to screen2"),
onPressed: () => Navigator.of(context).pushNamed("Screen2"),
),
],
),
);
}
}
class Screen2 extends StatelessWidget {
#override
Widget build(BuildContext context) {
return CupertinoPageScaffold(
navigationBar: CupertinoNavigationBar(),
child: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Hero(
tag: "Screen2", child: Image.asset("assets/images/banane.jpg")),
Center(
child: RaisedButton(
color: Colors.red,
child: Text("go to screen1"),
onPressed: () => Navigator.of(context).pop(),
),
),
],
),
),
);
}
}
result click me
good luck buddy
I want to place an Iconbutton in the top right corner of my Scaffold that programmatically opens a drawer. It should be the top right corner for every displaytype.
Using an appbar would ruin the look of the page since I just need a small icon that shows that a drawer is available. How do I do this the best way?
My Scaffold is a default one.
How can I achieve this the best way?
You can achieve this using a Stack .
Wrap the body of your Scaffold with a Stack widget and use the Positioned widget as the first child of the stack.
GlobalKey<ScaffoldState> _scafKey = GlobalKey();
#override
Widget build(BuildContext context) {
return SafeArea(
child: Scaffold(
key: _scafKey,
drawer: YourDrawerWidget(),
body: Stack(
children: <Widget>[
Positioned(
top: 0,
right: 0,
child: IconButton(
icon: Icon(Icons.short_text),
onPressed: (){
_scafKey.currentState.openDrawer();
})),
Container(),
],
),
),
);
}
Replace the container with your widget(the original body of the scaffold).
And also the icon of the IconButton to your Icon.
Here the MyHomePage class is the AppBar you need for your Scaffold. All you need to do is change the icon.
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: MyHomePage(),
drawer: Container(
width: 100,
color: Colors.red,
),
),
);
}
}
class MyHomePage extends StatelessWidget implements PreferredSizeWidget {
#override
Widget build(BuildContext context) {
return SafeArea(
child: Container(
color: Colors.transparent,
child: Row(
mainAxisAlignment: MainAxisAlignment.end,
children: <Widget>[
AppBarAction(),
],
),
),
);
}
#override
Size get preferredSize => Size.fromHeight(60);
}
class AppBarAction extends StatelessWidget {
#override
Widget build(BuildContext context) {
return IconButton(
icon: Icon(Icons.print),
onPressed: () {
Scaffold.of(context).openDrawer();
},
);
}
}
I need to copy a value of a Text Widget and copy this to another.
I tried to this using keys, but I don't know how to acess the Text Widget in this case.
Is it possible to do in Flutter, using the onPressed property?
import 'package:flutter/material.dart';
class TextWidget extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
"Value to be copied",
key: Key('text1')
),
RaisedButton(
onPressed: (){
// code here
},
child: Text("Copy value"),
),
SizedBox(height: 40),
Text(
"",
key: Key('text2')
)
],
),
),
);
}
}
Answering your question directly: you can access text inside Text widget using its data property.
Text widget = Text('text value');
String text = widget.data;
print(text); // text value
Next, you can't access widgets by their key properties. At least you shouldn't, because they were designed for different purpose: here's a video and an article about keys in Flutter.
What you can do here is turn your TextWidget from StatelessWidget into StatefulWidget and render contents of your second Text based on the state. Good introduction into what the state is and why you should use it can be found on official Flutter website: Start thinking declaratively.
Then you can save your first Text widget in a variable and then access its contents directly using data property update, then update state of the whole widget.
Example 1 on DartPad
More canonical and in general preferrable approach is to render contents of both buttons based on the state and get desired text from state variable and not from the widget itself, as proposed by Sebastian and MSARKrish.
Example 2 on DartPad
Note that you can't change data attribute of a Text widget imperatively, like you would do in JavaScript DOM API with innerText:
_textWidget.data = "New text"; // Doesn't work
because its data is final. In Flutter you have to think declaratively, and it worth it.
Try this
import 'package:flutter/material.dart';
final Color darkBlue = Color.fromARGB(255, 18, 32, 47);
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData.dark().copyWith(scaffoldBackgroundColor: darkBlue),
debugShowCheckedModeBanner: false,
home: Scaffold(
body: Center(
child: MyWidget(),
),
),
);
}
}
class MyWidget extends StatefulWidget {
#override
_MyWidgetState createState() => _MyWidgetState();
}
class _MyWidgetState extends State<MyWidget> {
String _text = "Value to be copied";
bool _buttonToggle;
#override
void initState() {
super.initState();
_buttonToggle = false;
}
#override
Widget build(BuildContext context) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(_text),
SizedBox(height: 40),
RaisedButton(
onPressed: _toggle,
child: Text("Copy value"),
),
Switch(
value: _buttonToggle,
onChanged: (_) => _toggle(),
),
SizedBox(height: 40),
Text(_buttonToggle ? _text : '')
],
);
}
void _toggle() {
setState(() => _buttonToggle = !_buttonToggle);
}
}
class TextWidget extends StatefulWidget {
#override
_TextWidgetState createState() => _TextWidgetState();
}
class _TextWidgetState extends State<TextWidget> {
String text1Value = "text to be copied";
String text2Value = "";
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
text1Value,
),
RaisedButton(
onPressed: () {
setState(() {
text2Value = text1Value;
});
},
child: Text("Copy value"),
),
SizedBox(height: 40),
Text(
text2Value,
)
],
),
),
);
}
}
So I have this ChangeNotifierProvider high in my widget tree as I am seeing many children widgets to listen to its value.
What I am currently doing is that I pass down the Provider.of(context) object from the parent widget into it's children via constructors whenever I am to reuse some values/functions on my children widgets. For example, everytime I create a Provider.of(context) object for my children widgets, it seems that it does not carry over the updated values I have on the Parent Provider but rather this one has my default null/0/'empty' ones like it has only been created. This lead me to pass down the initial Provider.of(context) object to each children that will use the updated values and functions of the ChangeNotifier.
This setup is working for me, however, when my Widget Tree has started being complex, I am constantly passing down values through each widget and to some that do not even use it at all just for its children to listen to the main provider.
I think what I may be doing now is anti-pattern of the Provider Architecture, I am hoping you guys can help me on a more optimized and efficient way of doing this.
Thank you very much!
P.S. There are some things in the documentation that I am not yet quite grasping properly.
Edits Below to include sample code and visualization:
provider_type.dart
class ProviderType extends ChangeNotifier{
String valueA = '';
String valueB = '';
}
home.dart
import ..provider_type.dart
...
Widget build(BuildContext context){
return ChangeNotifierProvider<ProviderType>(
create: (context) => ProviderType(),
child: ScreenColumn();
);
}
...
screen_column.dart
import ..screen_a.dart
import ..screen_b.dart
class ScreenColumn extends StatelessWidget{
Widget build(BuildContext context){
var providerType = Provider.of<ProviderType>(context);
return Column(
children: <Widget>[
ScreenA(providerType: providerType),
ScreenB(providerType: providerType),
],
);
}
}
screen_a.dart
class ScreenA extends StatelessWidget{
final ProviderType providerType;
ScreenA({this.providerType});
Widget build(BuildContext context){
return Text(
'${providerType.valueA}'
);
}
}
screen_b.dart
import ..screen_c.dart
class ScreenB extends StatelessWidget{
final ProviderType providerType;
ScreenB({this.providerType});
Widget build(BuildContext context){
return ScreenC(providerType: providerType);
}
}
screen_c.dart
class ScreenC extends StatelessWidget{
final ProviderType providerType;
ScreenB({this.providerType});
Widget build(BuildContext context){
return Column(
children: <Widget>[
Text(
'${providerType.valueA}'
)
Text(
'${providerType.valueB}'
)
Text(
'${providerType.valueC}'
)
]
);
}
}
Visualization
So what I am currently doing is to pass down the object providerType from ScreenColumn to Screens A, B, and C just so each of them have the same "Source of Values". Cause when I try to make different Provider.of objects and use them, they do not share the same updated values when I do some computation.
Is there something I can do to make this more efficient or is there a better way that I need to do?
To those who may be wondering or are searching for answers to the same question, look at my sample code below that shows how you can reuse/share your Provider Values and Functions at any point in your widget tree as long as they are under your Parent Provider.
And yes, you can actually just create Provider.of Objects anywhere in
your tree without passing down the initial Provider.of object that you
have created.
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
class ProviderType extends ChangeNotifier {
String value = DateTime.now().toString();
changeValue() {
value = DateTime.now().toString();
notifyListeners();
}
}
void main() => runApp(AppIndex());
class AppIndex extends StatelessWidget {
const AppIndex({
Key key,
}) : super(key: key);
#override
Widget build(BuildContext context) {
return ChangeNotifierProvider<ProviderType>(
create: (context) => ProviderType(),
child: MaterialApp(
home: Home(),
),
);
}
}
class Home extends StatelessWidget {
#override
Widget build(BuildContext context) {
var providerType = Provider.of<ProviderType>(context);
return Scaffold(
appBar: AppBar(
title: Text('Sample App'),
),
body: ScreenColumn(),
floatingActionButton: FloatingActionButton.extended(
onPressed: () => providerType.changeValue(),
label: Text('ChangeValue'),
),
);
}
}
class ScreenColumn extends StatelessWidget {
#override
Widget build(BuildContext context) {
return SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
ScreenA(),
ScreenB(),
ScreenC(),
ScreenC(),
],
));
}
}
class ScreenA extends StatelessWidget {
#override
Widget build(BuildContext context) {
var providerType = Provider.of<ProviderType>(context);
return Card(
color: Colors.red,
elevation: 8.0,
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Text(providerType.value),
),
);
}
}
class ScreenB extends StatelessWidget {
#override
Widget build(BuildContext context) {
var providerType = Provider.of<ProviderType>(context);
return Card(
color: Colors.blue,
elevation: 8.0,
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
Text(providerType.value),
ScreenC(),
ScreenC(),
],
),
),
);
}
}
class ScreenC extends StatelessWidget {
#override
Widget build(BuildContext context) {
// var providerType = Provider.of<ProviderType>(context);
return Card(
color: Colors.green,
elevation: 8.0,
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
Text('This is Screen B with no Provider.of Object'),
ScreenD(),
ScreenD(),
ScreenD(),
],
),
),
);
}
}
class ScreenD extends StatelessWidget {
#override
Widget build(BuildContext context) {
var providerType = Provider.of<ProviderType>(context);
return Card(
color: Colors.yellow,
elevation: 8.0,
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
Text(
'This is Screen D. A Provider.of object was created here without inheriting the Parent\'s Provider.of object.'),
Text(providerType.value),
],
),
),
);
}
}