Beginner Help Required - BottomNavBar - android

I am just learning and can't wrap my head around something.
I am building a simple app but the app requires the first thing shown is a splash screen of some sorts.
Upon tapping the only button on the SplashScreen, ideally it would load the rest of the app however I also want my app inside to work around a bottomNavBar.
I have done the Bottom Navigation Bar on my own and it works so I can cycle between my pages, but my main.dart is pointing towards my Splash_Screen. Where as in the Nav model I am using, main points to the Nav.dart file.
How do I get my app to launch in this sequence: Splash_Screen --> when buttom tapped --> go inside where Bottom Navigation Bar will be leading to it's respective 3 pages.
Thanks in advance.
EDIT: CODE
main.dart
import 'package:flutter/material.dart';
import 'package:offroad/screens/splash_screen.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: SplashScreen(),
);
}
}
SplashScreen.dart
import 'package:flutter/material.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:offroad/screens/home_screen.dart';
class SplashScreen extends StatefulWidget {
#override
_SplashScreenState createState() => _SplashScreenState();
}
class _SplashScreenState extends State<SplashScreen> {
Color mainColor = Color(0xFFF1330A);
#override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
decoration: BoxDecoration(
image: DecorationImage(
image: AssetImage('images/splash.jpg'),
fit: BoxFit.cover,
),
),
child: Container(
color: Colors.black54,
child: Stack(
children: <Widget>[
Container(),
Positioned(
bottom: 90,
child: Container(
width: MediaQuery.of(context).size.width,
child: GestureDetector(
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (_) => Home(),
),
);
},
child: Container(
margin: EdgeInsets.symmetric(horizontal: 80),
height: 80,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(20),
color: mainColor,
),
child: Center(
child: Text(
'Entrar',
style: TextStyle(
fontSize: 25,
color: Colors.white,
fontWeight: FontWeight.bold,
),
),
),
),
),
),
),
Positioned(
bottom: 230,
child: Container(
width: MediaQuery.of(context).size.width,
child: Text(
'Tu mundo 4x4\n empieza aqui!',
style: GoogleFonts.amiri(
height: 1.2,
textStyle: TextStyle(
color: Colors.white,
fontWeight: FontWeight.bold,
fontSize: 40,
),
),
textAlign: TextAlign.center,
),
),
),
],
),
),
),
);
}
}
Once my splash page loads and the button is tapped I want it to now go inside and load this, which is what I had working seperately but with the main.dart pointing to the "nav.dart"

Not sure what you are trying to achieve exactly but you should use the routes or onGenerateRoute in MaterialApp to define your routes.
In that case you can get rid of the home and set the initial route to Splash Screen and the default as nav.
You can then navigate to your tab component using a named route defined in your routes or onGenerateRoute.

Related

ListView inside of stack causing issues in flutter

I will be sure to accept the correct answer to this question
Hello,
I am trying to create a layout similar to this one
My plan was to create a column with two expanded widgets, one with a flex of five and the other with a flex of five so that they both take half of the available space, inside the first expanded, I added a background Image, and a Stack widget. Inside the Stack widget, I put 2 text widgets and a listview, I wrapped the listview in a container and set it a height to avoid an unbounded constraint. Then I wrapped the listview inside a positioned widget and tried to nudge it below. I can position the widget later but all I care about is for the listview to be horizontal and overflow the parent container slightly, but nothing shows up when I run the given code.
Here is all the code under main.dart
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
class HomePage extends StatefulWidget {
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
String user = FirebaseAuth.instance.currentUser.email == null
? FirebaseAuth.instance.currentUser.phoneNumber
: FirebaseAuth.instance.currentUser.email;
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: Scaffold(
body: Column(
children: [
Expanded(
flex: 4,
child: Container(
width: double.infinity,
decoration: BoxDecoration(
color: Colors.red,
borderRadius: BorderRadius.only(
bottomLeft: Radius.circular(35),
bottomRight: Radius.circular(35),
),
),
child: Stack(
alignment: AlignmentDirectional.center,
fit: StackFit.expand,
children: [
Text(
'1268',
style: TextStyle(
color: Colors.white,
fontWeight: FontWeight.w900,
fontSize: 60,
fontFamily: 'Rubik',
),
),
Text(
'Points Available',
style: TextStyle(
color: Colors.white,
fontWeight: FontWeight.w900,
fontSize: 15,
fontFamily: 'Rubik',
),
),
Positioned(
//TODO: Mess with this to make the listview overflow its parent container
top: 100,
child: Container(
height: 44,
color: Colors.white,
child: ListView(
children: [],
),
),
)
],
),
),
),
Expanded(
flex: 6,
child: Container(
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.only(
topLeft: Radius.circular(35),
topRight: Radius.circular(35),
),
),
),
)
],
),
),
);
}
}
STEPS TO REPRODUCE
font_awesome_flutter: ^9.1.0
Under the pubspec.yaml and import the package in the main.dart
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
This is all you need to reproduce the error, you should see that nothing shows up, I am not sure why and I would love to finally be able to reproduce this layout
The problem is not with the ListView, but with Positioned.
Add left: 0 and right: 0 values to it.
Positioned (
left: 0,
right: 0,
top: 100,
child: ...
)
In this case, do not forget to explicitly specify the width of the child widget. For example: width: double.infinity,
Another option:
you can only add left: 0 to Positioned and not add right: 0, but then Positioned itself or its child widget must have a fixed width. For example: width: 200

Flutter: Yellow Lines Under Text

I understand that I do not have a Scaffolding around my text but I'm brand new to flutter and can't seem to work out how to add scaffolding to my text. My code is below:
import 'package:flutter/material.dart';
import 'package:google_fonts/google_fonts.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Test Bench',
home: new Container(
decoration: BoxDecoration(color: Colors.pinkAccent),
child: Padding(
padding: const EdgeInsets.all(30.0),
child: Text(
"Hello, World",
style: TextStyle(
fontSize: 60.0,
fontWeight: FontWeight.bold,
fontFamily: 'Oswald',
color: Colors.black),
),
),
),
);
}
}
You can check by following code. Also refer
import 'package:flutter/material.dart';
import 'package:google_fonts/google_fonts.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Test Bench',
home: Scaffold(
body: new Container(
decoration: BoxDecoration(color: Colors.pinkAccent),
child: Padding(
padding: const EdgeInsets.all(30.0),
child: Text(
"Hello, World",
style: TextStyle(
fontSize: 60.0,
fontWeight: FontWeight.bold,
fontFamily: 'Oswald',
color: Colors.black),
),
),
),
),
);
}
}
I think you need a explanation for this:
Always uses Scaffold (generally better) or any other component that provides material theme like a simple Material widget
remove yellow line text
We can remove yellow lines by adding the Parent widget as "Material" or use "Scaffold"
return MaterialApp(
home: Scaffold(
body: Container(
child: SingleChildScrollView(child: Text('Test')),
),
),
);

How to make a transparent container stand out from the background?

I'm trying to make my containers transparent and still want them to easily standout from the background color, just like below image. (this is PSD image)
Desired Layout
I tried wrapping Container inside a Material widget like this:
class customBar extends StatelessWidget {
const customBar({
Key key,
}) : super(key: key);
#override
Widget build(BuildContext context) {
return Material(
color: Colors.transparent,
elevation: 2,
borderRadius: BorderRadius.circular(10),
child: Container(
width: double.infinity,
height: 65,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(10),
//color: Color(0xff0a4873),
),
child: Text(
'',
textAlign: TextAlign.left,
),
),
);
}
}
but it gives it too much elevation
also tried using a solid color but that doesn't give me what is required either,
Expanded(
flex: 4,
child: Padding(
padding: const EdgeInsets.all(10.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
customBar(),
Container(
width: double.infinity,
height: 60,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(10),
color: Color(0xff0a4873),
),
child: Text(
'',
textAlign: TextAlign.left,
),
),
],
),
),
),
here is the output:
Output
Material is not necessary. If you want to adjust color transparency either use Colors.YOUR_COLOR.withOpacity(0.0 .. to .. 1.0) or Color.fromRGBO(RED, BLUE, GREEN, 0.0 .. to .. 1.0)
Examples:
Colors.blue.withOpacity(0.3);
// OR
Color.fromRGBO(50, 45, 220, 0.3);
Edit
You can simplify your layout like this.
HomePage
class HomePage extends StatefulWidget {
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
#override
Widget build(BuildContext context) {
return Scaffold(
body: CustomContainer(
child: Column(
children: [
// other widgets and your containers here
],
),
),
);
}
}
Your CustomContainer Widget
class CustomContainer extends StatelessWidget {
final Widget child;
CustomContainer({this.child});
#override
Widget build(BuildContext context) {
return Container(
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomLeft,
colors: [
Color(0xFF1187be),
Color(0xff061465),
],
),
),
child: child,
);
}
}

How to make opaque tutorial screen in flutter?

I want to make tutorial screen that show to user at beginning. it's like below :
my specific question, how to make some certain elements will show normally and other are opaque ?
also the arrow and text, how to make them point perfectly based on mobile device screen size (mobile responsiveness) ?
As RoyalGriffin mentioned, you can use highlighter_coachmark library, and I am also aware of the error you are getting, the error is there because you are using RangeSlider class which is imported from 2 different packages. Can you try this example in your app and check if it is working?
Add highlighter_coachmark to your pubspec.yaml file
dependencies:
flutter:
sdk: flutter
highlighter_coachmark: ^0.0.3
Run flutter packages get
Example:
import 'package:highlighter_coachmark/highlighter_coachmark.dart';
void main() => runApp(MaterialApp(home: HomePage()));
class HomePage extends StatefulWidget {
#override
State<HomePage> createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
GlobalKey _fabKey = GlobalObjectKey("fab"); // used by FAB
GlobalKey _buttonKey = GlobalObjectKey("button"); // used by RaisedButton
#override
Widget build(BuildContext context) {
return Scaffold(
floatingActionButton: FloatingActionButton(
key: _fabKey, // setting key
onPressed: null,
child: Icon(Icons.add),
),
body: Center(
child: RaisedButton(
key: _buttonKey, // setting key
onPressed: showFAB,
child: Text("RaisedButton"),
),
),
);
}
// we trigger this method on RaisedButton click
void showFAB() {
CoachMark coachMarkFAB = CoachMark();
RenderBox target = _fabKey.currentContext.findRenderObject();
// you can change the shape of the mark
Rect markRect = target.localToGlobal(Offset.zero) & target.size;
markRect = Rect.fromCircle(center: markRect.center, radius: markRect.longestSide * 0.6);
coachMarkFAB.show(
targetContext: _fabKey.currentContext,
markRect: markRect,
children: [
Center(
child: Text(
"This is called\nFloatingActionButton",
style: const TextStyle(
fontSize: 24.0,
fontStyle: FontStyle.italic,
color: Colors.white,
),
),
)
],
duration: null, // we don't want to dismiss this mark automatically so we are passing null
// when this mark is closed, after 1s we show mark on RaisedButton
onClose: () => Timer(Duration(seconds: 1), () => showButton()),
);
}
// this is triggered once first mark is dismissed
void showButton() {
CoachMark coachMarkTile = CoachMark();
RenderBox target = _buttonKey.currentContext.findRenderObject();
Rect markRect = target.localToGlobal(Offset.zero) & target.size;
markRect = markRect.inflate(5.0);
coachMarkTile.show(
targetContext: _fabKey.currentContext,
markRect: markRect,
markShape: BoxShape.rectangle,
children: [
Positioned(
top: markRect.bottom + 15.0,
right: 5.0,
child: Text(
"And this is a RaisedButton",
style: const TextStyle(
fontSize: 24.0,
fontStyle: FontStyle.italic,
color: Colors.white,
),
),
)
],
duration: Duration(seconds: 5), // this effect will only last for 5s
);
}
}
Output:
You can use this library to help you achieve what you need. It allows you to mark views which you want to highlight and how you want to highlight them.
Wrap your current top widget with a Stack widget, having the first child of the Stack your current widget.
Below this widget add a Container with black color, wrapped with Opacity like so:
return Stack(
children: <Widget>[
Scaffold( //first child of the stack - the current widget you have
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: <Widget>[
Text("Foo"),
Text("Bar"),
],
),
)),
Opacity( //seconds child - Opaque layer
opacity: 0.7,
child: Container(
decoration: BoxDecoration(color: Colors.black),
),
)
],
);
you then need to create image assets of the descriptions and arrows, in 1x, 2x, 3x resolutions, and place them in your assets folder in the appropriate structure as described here: https://flutter.dev/docs/development/ui/assets-and-images#declaring-resolution-aware-image-assets
you can then use Image.asset(...) widget to load your images (they will be loaded in the correct resolution), and place these widgets on a different container that will also be a child of the stack, and will be placed below the black container in the children list (the Opacity widget on the example above).
It should be mentioned that instead of an opaque approach the Material-oriented feature_discovery package uses animation and integrates into the app object hierarchy itself and therefore requires less custom highlight programming. The turnkey solution also supports multi-step highlights.
Screenshot (Using null-safety):
Since highlighter_coachmark doesn't support null-safety as of this writing, use tutorial_coach_mark which supports null-safety.
Full Code:
class HomePage extends StatefulWidget {
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
late final List<TargetFocus> targets;
final GlobalKey _key1 = GlobalKey();
final GlobalKey _key2 = GlobalKey();
final GlobalKey _key3 = GlobalKey();
#override
void initState() {
super.initState();
targets = [
TargetFocus(
identify: 'Target 1',
keyTarget: _key1,
contents: [
TargetContent(
align: ContentAlign.bottom,
child: _buildColumn(title: 'First Button', subtitle: 'Hey!!! I am the first button.'),
),
],
),
TargetFocus(
identify: 'Target 2',
keyTarget: _key2,
contents: [
TargetContent(
align: ContentAlign.top,
child: _buildColumn(title: 'Second Button', subtitle: 'I am the second.'),
),
],
),
TargetFocus(
identify: 'Target 3',
keyTarget: _key3,
contents: [
TargetContent(
align: ContentAlign.left,
child: _buildColumn(title: 'Third Button', subtitle: '... and I am third.'),
)
],
),
];
}
Column _buildColumn({required String title, required String subtitle}) {
return Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(
title,
style: TextStyle(fontWeight: FontWeight.bold, fontSize: 20),
),
Padding(
padding: const EdgeInsets.only(top: 10.0),
child: Text(subtitle),
)
],
);
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Padding(
padding: const EdgeInsets.all(20),
child: Stack(
children: [
Align(
alignment: Alignment.topLeft,
child: ElevatedButton(
key: _key1,
onPressed: () {},
child: Text('Button 1'),
),
),
Align(
alignment: Alignment.center,
child: ElevatedButton(
key: _key2,
onPressed: () {
TutorialCoachMark(
context,
targets: targets,
colorShadow: Colors.cyanAccent,
).show();
},
child: Text('Button 2'),
),
),
Align(
alignment: Alignment.bottomRight,
child: ElevatedButton(
key: _key3,
onPressed: () {},
child: Text('Button 3'),
),
),
],
),
),
);
}
}
Thanks to #josxha for the suggestion.
If you don't want to rely on external libraries, you can just do it yourself. It's actually not that hard.
Using a stack widget you can put the semi-transparent overlay on top of everything. Now, how do you "cut holes" into that overlay that emphasize underlying UI elements?
Here is an article that covers the exact topic: https://www.flutterclutter.dev/flutter/tutorials/how-to-cut-a-hole-in-an-overlay/2020/510/
I will summarize the possibilities you have:
Use a ClipPath
By using a CustomClipper, given a widget, you can define what's being drawn and what's not. You can then just draw a rectangle or an oval around the relevant underlying UI element:
class InvertedClipper extends CustomClipper<Path> {
#override
Path getClip(Size size) {
return Path.combine(
PathOperation.difference,
Path()..addRect(
Rect.fromLTWH(0, 0, size.width, size.height)
),
Path()
..addOval(Rect.fromCircle(center: Offset(size.width -44, size.height - 44), radius: 40))
..close(),
);
}
#override
bool shouldReclip(CustomClipper<Path> oldClipper) => true;
}
Insert it like this in your app:
ClipPath(
clipper: InvertedClipper(),
child: Container(
color: Colors.black54,
),
);
Use a CustomPainter
Instead of cutting a hole in an overlay, you can directly draw a shape that is as big as the screen and has the hole already cut out:
class HolePainter extends CustomPainter {
#override
void paint(Canvas canvas, Size size) {
final paint = Paint()
..color = Colors.black54;
canvas.drawPath(
Path.combine(
PathOperation.difference,
Path()..addRect(
Rect.fromLTWH(0, 0, size.width, size.height)
),
Path()
..addOval(Rect.fromCircle(center: Offset(size.width -44, size.height - 44), radius: 40))
..close(),
),
paint
);
}
#override
bool shouldRepaint(CustomPainter oldDelegate) {
return false;
}
}
Insert it like this:
CustomPaint(
size: MediaQuery.of(context).size,
painter: HolePainter()
);
Use ColorFiltered
This solution works without paint. It cuts holes where children in the widget trees are inserted by using a specific blendMode:
ColorFiltered(
colorFilter: ColorFilter.mode(
Colors.black54,
BlendMode.srcOut
),
child: Stack(
children: [
Container(
decoration: BoxDecoration(
color: Colors.transparent,
),
child: Align(
alignment: Alignment.bottomRight,
child: Container(
margin: const EdgeInsets.only(right: 4, bottom: 4),
height: 80,
width: 80,
decoration: BoxDecoration(
// Color does not matter but must not be transparent
color: Colors.black,
borderRadius: BorderRadius.circular(40),
),
),
),
),
],
),
);

How to expand a card on tap in flutter?

I would like to achieve the material design card behavior on tap. When I tap it, it should expand fullscreen and reveal additional content/new page. How do I achieve it?
https://material.io/design/components/cards.html#behavior
I tried with Navigator.of(context).push() to reveal new page and play with Hero animations to move the card background to new Scaffold, however it seems it is not the way to go since new page is not revealing from the card itself, or I cannot make it to. I am trying to achieve the same behavior as in the material.io that I presented above. Would you please guide me somehow?
Thank you
A while ago I tried replicating that exact page/transition and while I didn't get it to look perfectly like it, I did get fairly close. Keep in mind that this was put together quickly and doesn't really follow best practices or anything.
The important part is the Hero widgets, and especially the tags that go along with them - if they don't match, it won't do it.
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
backgroundColor: Colors.deepPurple,
),
body: ListView.builder(
itemBuilder: (context, index) {
return TileItem(num: index);
},
),
),
);
}
}
class TileItem extends StatelessWidget {
final int num;
const TileItem({Key key, this.num}) : super(key: key);
#override
Widget build(BuildContext context) {
return Hero(
tag: "card$num",
child: Card(
shape: RoundedRectangleBorder(
borderRadius: const BorderRadius.all(
Radius.circular(8.0),
),
),
clipBehavior: Clip.antiAliasWithSaveLayer,
child: Stack(
children: <Widget>[
Column(
children: <Widget>[
AspectRatio(
aspectRatio: 485.0 / 384.0,
child: Image.network("https://picsum.photos/485/384?image=$num"),
),
Material(
child: ListTile(
title: Text("Item $num"),
subtitle: Text("This is item #$num"),
),
)
],
),
Positioned(
left: 0.0,
top: 0.0,
bottom: 0.0,
right: 0.0,
child: Material(
type: MaterialType.transparency,
child: InkWell(
onTap: () async {
await Future.delayed(Duration(milliseconds: 200));
Navigator.push(
context,
MaterialPageRoute(
builder: (context) {
return new PageItem(num: num);
},
fullscreenDialog: true,
),
);
},
),
),
),
],
),
),
);
}
}
class PageItem extends StatelessWidget {
final int num;
const PageItem({Key key, this.num}) : super(key: key);
#override
Widget build(BuildContext context) {
AppBar appBar = new AppBar(
primary: false,
leading: IconTheme(data: IconThemeData(color: Colors.white), child: CloseButton()),
flexibleSpace: Container(
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [
Colors.black.withOpacity(0.4),
Colors.black.withOpacity(0.1),
],
),
),
),
backgroundColor: Colors.transparent,
);
final MediaQueryData mediaQuery = MediaQuery.of(context);
return Stack(children: <Widget>[
Hero(
tag: "card$num",
child: Material(
child: Column(
children: <Widget>[
AspectRatio(
aspectRatio: 485.0 / 384.0,
child: Image.network("https://picsum.photos/485/384?image=$num"),
),
Material(
child: ListTile(
title: Text("Item $num"),
subtitle: Text("This is item #$num"),
),
),
Expanded(
child: Center(child: Text("Some more content goes here!")),
)
],
),
),
),
Column(
children: <Widget>[
Container(
height: mediaQuery.padding.top,
),
ConstrainedBox(
constraints: BoxConstraints(maxHeight: appBar.preferredSize.height),
child: appBar,
)
],
),
]);
}
}
EDIT: in response to a comment, I'm going to write an explanation of how Hero works (or at least how I think it works =D).
Basically, when a transition between pages is started, the underlying mechanism that performs the transition (part of the Navigator more or less) looks for any 'hero' widgets in the current page and the new page. If a hero is found, its size and position is calculated for each of the pages.
As the transition between the pages is performed, the hero from the new page is moved to an overlay in the same place as the old hero, and then its size and position is animated towards its final size and position in the new page. (Note that you can change if you want with a bit of work - see this blog for more information about that).
This is what the OP was trying to achieve:
When you tap on a Card, its background color expands and becomes a background color of a Scaffold with an Appbar.
The easiest way to do this is to simply put the scaffold itself in the hero. Anything else will obscure the AppBar during the transition, as while it's doing the hero transition it is in an overlay. See the code below. Note that I've added in a class to make the transition happen slower so you can see what's going on, so to see it at normal speed change the part where it pushes a SlowMaterialPageRoute back to a MaterialPageRoute.
That looks something like this:
import 'dart:math';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
backgroundColor: Colors.deepPurple,
),
body: ListView.builder(
itemBuilder: (context, index) {
return TileItem(num: index);
},
),
),
);
}
}
Color colorFromNum(int num) {
var random = Random(num);
var r = random.nextInt(256);
var g = random.nextInt(256);
var b = random.nextInt(256);
return Color.fromARGB(255, r, g, b);
}
class TileItem extends StatelessWidget {
final int num;
const TileItem({Key key, this.num}) : super(key: key);
#override
Widget build(BuildContext context) {
return Hero(
tag: "card$num",
child: Card(
color: colorFromNum(num),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(
Radius.circular(8.0),
),
),
clipBehavior: Clip.antiAliasWithSaveLayer,
child: Stack(
children: <Widget>[
Column(
children: <Widget>[
AspectRatio(
aspectRatio: 485.0 / 384.0,
child: Image.network("https://picsum.photos/485/384?image=$num"),
),
Material(
type: MaterialType.transparency,
child: ListTile(
title: Text("Item $num"),
subtitle: Text("This is item #$num"),
),
)
],
),
Positioned(
left: 0.0,
top: 0.0,
bottom: 0.0,
right: 0.0,
child: Material(
type: MaterialType.transparency,
child: InkWell(
onTap: () async {
await Future.delayed(Duration(milliseconds: 200));
Navigator.push(
context,
SlowMaterialPageRoute(
builder: (context) {
return new PageItem(num: num);
},
fullscreenDialog: true,
),
);
},
),
),
),
],
),
),
);
}
}
class PageItem extends StatelessWidget {
final int num;
const PageItem({Key key, this.num}) : super(key: key);
#override
Widget build(BuildContext context) {
return Hero(
tag: "card$num",
child: Scaffold(
backgroundColor: colorFromNum(num),
appBar: AppBar(
backgroundColor: Colors.white.withOpacity(0.2),
),
),
);
}
}
class SlowMaterialPageRoute<T> extends MaterialPageRoute<T> {
SlowMaterialPageRoute({
WidgetBuilder builder,
RouteSettings settings,
bool maintainState = true,
bool fullscreenDialog = false,
}) : super(builder: builder, settings: settings, fullscreenDialog: fullscreenDialog);
#override
Duration get transitionDuration => const Duration(seconds: 3);
}
However, there are situations in which it might not be optimal to have the entire scaffold doing the transition - maybe it has a lot of data, or is designed to fit in a specific amount of space. In that case, an option to make a version of whatever you want to do the hero transition that is essentially a 'fake' - i.e. have a stack with two layers, one which is the hero and has a background colour, scaffold, and whatever else you want to show up during the transition, and another layer on top which completely obscures the bottom layer (i.e. has a background with 100% opacity) that also has an app bar and whatever else you want.
There are probably better ways of doing it than that - for example, you could specify the hero separately using the method mentioned in the blog I linked to.
I achieved this by using the Flutter Hero Animation Widget. In order to do that you will need:
A source page where you start from and that contains the card you want to expand to full screen. Let's call it 'Home'
A destination page that will represent how your card will look like once expanded. Let's call it 'Details'.
(Optional) A data model to store data
Now let's take a look at this example below (You can find the full project code here):
First, let's make an Item class (i will put it in models/item.dart) to store our data. Each item will have its own id, title, subtitle, details and image url :
import 'package:flutter/material.dart';
class Item {
String title, subTitle, details, img;
int id;
Item({this.id, this.title, this.subTitle, this.details, this.img});
}
Now, let's initialize our material app in the main.dart file :
import 'package:flutter/material.dart';
import 'package:expanding_card_animation/home.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: Home(),
);
}
}
Next, we will make our home page. It'll be a simple stateless widget, and will contain a list of Items that will be displayed in a ListView of Cards. A gesture detector is used to expand the card when tapping it. The expansion is just a navigation to the details page, but with the Hero animation, it looks like it just expanded the Card.
import 'package:flutter/material.dart';
import 'package:expanding_card_animation/details.dart';
import 'package:expanding_card_animation/models/item.dart';
class Home extends StatelessWidget {
List<Item> listItems = [
Item(
id: 1,
title: 'Title 1',
subTitle: 'SubTitle 1',
details: 'Details 1',
img:
'https://d1fmx1rbmqrxrr.cloudfront.net/cnet/i/edit/2019/04/eso1644bsmall.jpg'),
Item(
id: 2,
title: 'Title 2',
subTitle: 'SubTitle 2',
details: 'Details 2',
img:
'https://cdn.pixabay.com/photo/2015/04/23/22/00/tree-736885__340.jpg'),
Item(
id: 3,
title: 'Title 3',
subTitle: 'SubTitle 3',
details: 'Details 3',
img: 'https://miro.medium.com/max/1200/1*mk1-6aYaf_Bes1E3Imhc0A.jpeg'),
];
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Home screen'),
),
body: Container(
margin: EdgeInsets.fromLTRB(40, 10, 40, 0),
child: ListView.builder(
itemCount: listItems.length,
itemBuilder: (BuildContext c, int index) {
return GestureDetector(
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => Details(listItems[index])),
);
},
child: Card(
elevation: 7,
shape: RoundedRectangleBorder(
side: BorderSide(color: Colors.grey[400], width: 1.0),
borderRadius: BorderRadius.circular(10.0),
),
margin: EdgeInsets.fromLTRB(0, 0, 0, 20),
child: Column(
children: [
//Wrap the image widget inside a Hero widget
Hero(
//The tag must be unique for each element, so we used an id attribute
//in the item object for that
tag: '${listItems[index].id}',
child: Image.network(
"${listItems[index].img}",
scale: 1.0,
repeat: ImageRepeat.noRepeat,
fit: BoxFit.fill,
height: 250,
),
),
Divider(
height: 10,
),
Text(
listItems[index].title,
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
),
),
SizedBox(
height: 20,
),
],
),
),
);
}),
),
);
}
}
Finally, let's make the details page. It's also a simple stateless widget that will take the item's info as an input, and display them on full screen. Note that we wrapped the image widget inside another Hero widget, and make sure that you use the same tags used in the source page(here, we used the id in the passed item for that) :
import 'package:flutter/material.dart';
import 'package:expanding_card_animation/models/item.dart';
class Details extends StatelessWidget {
final Item item;
Details(this.item);
#override
Widget build(BuildContext context) {
return SafeArea(
child: Scaffold(
appBar: AppBar(
backgroundColor: Colors.transparent,
elevation: 0,
),
extendBodyBehindAppBar: true,
body: Container(
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Hero(
//Make sure you have the same id associated to each element in the
//source page's list
tag: '${item.id}',
child: Image.network(
"${item.img}",
scale: 1.0,
repeat: ImageRepeat.noRepeat,
fit: BoxFit.fitWidth,
height: MediaQuery.of(context).size.height / 3,
),
),
SizedBox(
height: 30,
),
ListTile(
title: Text(
item.title,
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 20,
),
),
subtitle: Text(item.subTitle),
),
Divider(
height: 20,
thickness: 1,
),
Padding(
padding: EdgeInsets.only(left: 20),
child: Text(
item.details,
style: TextStyle(
fontSize: 25,
),
),
),
],
),
),
),
);
}
}
And that's it, now you can customize it as you wish. Hope i helped.

Categories

Resources