Hi I want my Flutter App to have a fullscreen StartUp Animation.
I use Lottie to display the animation, however I can't get the framing and fit right.
I want the Animation to fit the whole screen. In case the screen is too big to have the animation fullscreen, I want to "zoom in" and crop the Edges off (Default BoxFit.cover behaviour).
This is my current Code:
Widget build(BuildContext context) {
return BaseWidget<StartUpViewModel>(
model: StartUpViewModel(),
builder: (context, model, child) {
return Scaffold(
body: SizedBox(
height: MediaQuery.of(context).size.height,
width: MediaQuery.of(context).size.width,
child: Lottie.asset(
"assets/lottie/dummy.json",
fit: BoxFit.cover,
controller: animationController,
onLoaded: (comp) {
animationController
..duration = comp.duration
..forward();
},
),
));
},
);
}
}
However, if I use this code, the animation is not centered. It scales up the animation but alignes the left side of the animation with the screen, and crops away the right side by doing so.
After some trial and error I found out! I'll leave this here because it is very unintuitive and someone might need this in the future:
Widget build(BuildContext context) {
return BaseWidget<StartUpViewModel>(
model: StartUpViewModel(),
builder: (context, model, child) {
return Scaffold(
body: SizedBox.expand(
child: FittedBox(
fit: BoxFit.cover,
child: Lottie.asset(
"assets/lottie/dummy.json",
fit: BoxFit.fill,
controller: animationController,
onLoaded: (comp) {
animationController.addStatusListener((status) {
if (status == AnimationStatus.completed) {
print("AnimationComnpleze");
setState(() {});
}
});
animationController
..duration = comp.duration
..forward();
},
),
),
),
);
},
);
}
}
Related
I am using Hero in my app for smooth transitions but I faced an issue: I would like the Hero-Widget rotate 90 degress on transition. I found out I can use flightShuttleBuilder for this and tried it like this:
flightShuttleBuilder: (
BuildContext flightContext,
Animation<double> animation,
HeroFlightDirection flightDirection,
BuildContext fromHeroContext,
BuildContext toHeroContext,
) {
final Widget toHero = toHeroContext.widget;
return RotationTransition(
turns: animation,
child: toHero,
);
},
As you can see I used the RotationTransition and it kind of works but not exactly how I want it. It rotates the widget during the flight by 360 instead of 90.
The widget should stay rotated on the 2nd screen. I tried that with RotatedBox. This is the full widget on the 2nd screen:
child: Hero(
tag: 'america',
flightShuttleBuilder: (
BuildContext flightContext,
Animation<double> animation,
HeroFlightDirection flightDirection,
BuildContext fromHeroContext,
BuildContext toHeroContext,
) {
final Widget toHero = toHeroContext.widget;
return RotationTransition(
turns: animation.drive(Tween(begin: 0, end: 0.25)),
child: toHero,
);
},
child: RotatedBox(
quarterTurns: 1,
child: SizedBox(
height: 100,
width: 100,
child: SvgPicture.asset(
'assets/icons/america.svg',
),
),
),
),
Is there a way to get this done and how would I do that? Couldn't find anything on this. Let me know if you need any more info!
With the help of #Michael Horn I got it working. As he suggested I changed the turns value. That fixed the rotation. However I wanted the widget to stay rotated on the 2nd screen. I did that with RotatedBox. But when simply giving it the value 1, Hero will take that Rotated Widget for flightShuttleBuilder and the sets it back when building is finished. I fixed this by changing the quarterTurns after all the widgets are build:
int rotation = 0;
#override
void initState() {
super.initState();
WidgetsBinding.instance!.addPostFrameCallback((timeStamp) {
setState(() {
rotation = 1;
});
});
}
and my widget:
child: Hero(
tag: 'america',
flightShuttleBuilder: (
BuildContext flightContext,
Animation<double> animation,
HeroFlightDirection flightDirection,
BuildContext fromHeroContext,
BuildContext toHeroContext,
) {
final Widget toHero = toHeroContext.widget;
return RotationTransition(
turns: animation.drive(Tween(begin: 0, end: 0.25)), // <- for the during-flight 90 degree rotation
child: toHero,
);
},
child: RotatedBox(
quarterTurns: rotation, // <- important to not change that dynamically
child: SizedBox(
height: 100,
width: 100,
child: SvgPicture.asset(
'assets/icons/america.svg',
),
),
),
),
I would like my app have a model bottom sheet. The bottom sheet is shown only when user click a button. The bottom sheet first take up around 0.5 or less of the screen which is enough to show popular choices from a listview. User can pick their choice right from here but they also can drag up to view all the choices. The bottom sheet can only be either half or full screen. Once it go full screen, I expect it behave like a scaffold (user can scroll the list view but can not drag down to a bottom sheet anymore). How can I do it in flutter?
When user drag the bottom sheet up, it turn into a scaffold like the screen on the right.
[UPDATED]
Another slide panels, we_slide:
import 'package:we_slide/we_slide.dart';
final _colorScheme = Theme.of(context).colorScheme;
final double _panelMinSize = 70.0;
final double _panelMaxSize = MediaQuery.of(context).size.height / 2;
return Scaffold(
backgroundColor: Colors.black,
body: WeSlide(
panelMinSize: _panelMinSize,
panelMaxSize: _panelMaxSize,
body: Container(
color: _colorScheme.background,
child: Center(child: Text("This is the body 💪")),
),
panel: Container(
color: _colorScheme.primary,
child: Center(child: Text("This is the panel 😊")),
),
panelHeader: Container(
height: _panelMinSize,
color: _colorScheme.secondary,
child: Center(child: Text("Slide to Up ☝️")),
),
),
);
OR
bottom_sheet:
showFlexibleBottomSheet(
minHeight: 0,
initHeight: 0.5,
maxHeight: 1,
context: context,
builder: _buildBottomSheet,
anchors: [0, 0.5, 1],
isSafeArea: true,
);
Widget _buildBottomSheet(
BuildContext context,
ScrollController scrollController,
double bottomSheetOffset,
) {
return Material(
child: Container(
child: ListView(
controller: scrollController,
...
),
),
);
}
[OUTDATED]
Try this package sliding_sheet:
return SheetListenerBuilder(
// buildWhen can be used to only rebuild the widget when needed.
buildWhen: (oldState, newState) => oldState.isAtTop != newState.isAtTop,
builder: (context, state) {
return AnimatedContainer(
elevation: !state.isAtTop ? elevation : 0.0,
duration: const Duration(milliseconds: 400),
child: child,
);
},
);
My FutureBuilder rebuilds unnecessarily when navigate between screens. Each time I have to get the download URL from the Firebase Storage and this results in extremely flickering. How can I prevent that the FutureBuilder rebuilds everytime I navigate between screens?
I already tried the following solutions:
https://medium.com/saugo360/flutter-my-futurebuilder-keeps-firing-6e774830bc2
Flutter Switching to Tab Reloads Widgets and runs FutureBuilder
But without success. With both the FutureBuilder get always rebuild.
This is my code (Attempt 1):
class _GuestbookCardImageState extends State<GuestbookCardImage> {
final AsyncMemoizer _memoizer = AsyncMemoizer();
_getFeaturedImages() {
return this._memoizer.runOnce(() async {
return await MyStorage().getDownloadUrl(widget.guestbook.imagePath);
});
}
#override
Widget build(BuildContext context) {
return Card(
semanticContainer: true,
clipBehavior: Clip.antiAliasWithSaveLayer,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10.0),
),
elevation: 2,
margin: EdgeInsets.all(5),
child: FutureBuilder(
future: _getFeaturedImages(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return GuestbookPlaceholder();
} else {
if (snapshot.data != null) {
return Hero(
tag: "to_single_guestbook_" + widget.guestbook.entryCode,
child: Container(
width: Get.width / 1.7,
height: Get.height / 3.5,
child: Image.network(
snapshot.data,
fit: BoxFit.cover,
),
),
);
} else {
return Hero(
tag: "to_single_guestbook_" + widget.guestbook.entryCode,
child: GuestbookPlaceholder());
}
}
}),
);
}
}
Attempt 2
class _GuestbookCardImageState extends State<GuestbookCardImage> {
Future<String> _future;
#override
void initState() {
_future = MyStorage().getDownloadUrl(widget.guestbook.imagePath);
super.initState();
}
#override
Widget build(BuildContext context) {
return Card(
semanticContainer: true,
clipBehavior: Clip.antiAliasWithSaveLayer,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10.0),
),
elevation: 2,
margin: EdgeInsets.all(5),
child: FutureBuilder(
future: _future,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return GuestbookPlaceholder();
} else {
if (snapshot.data != null) {
return Hero(
tag: "to_single_guestbook_" + widget.guestbook.entryCode,
child: Container(
width: Get.width / 1.7,
height: Get.height / 3.5,
child: CachedNetworkImage(
imageUrl: snapshot.data, fit: BoxFit.cover),
),
);
} else {
return Hero(
tag: "to_single_guestbook_" + widget.guestbook.entryCode,
child: GuestbookPlaceholder());
}
}
}),
);
}
}
What I expect
I expect that after changing the screen and return to the initial screen my images still appearing without the need of reload. I am using Hero animation for the images. This loading behavior destroys my animation because while loading the image urls it shows a placeholder.
A small video how this looks: (And this happens also if I push to the detail screen and pop back to the initial screen)
What could I do to solve this issue?
I think you need to go higher in the widget tree and preserve the state of your page. you can try this solution. The solution is to use PageView widget to display your screen through the navigation bar, and the PageView allowing you to save the page state easily.
What helped me was setting the memCacheWidth property to be screen width x devicePixelRatio, so the image is cached in the highest resolution that will ever be necessary.
Note: if you want to do zooming, consider doing width x 10 (or some other number which works for your case).
CachedNetworkImage(
imageUrl: url,
memCacheWidth: mediaQuery.size.width.ceil() * mediaQuery.devicePixelRatio.ceil(),
// ... other properties ...
),
I have a Stack with multiple MatrixGesture Containers with images that can be drag around, pinch zoom and rotate. I want to save the state of each container in the position and shape that it is, because after a little change using setState, everything go back to the original position.
All the information of the images is in a List with a specific object type.
Here is a snippet of the code to display the images:
class NewOutfitState extends State<NewOutfit> {
List<DisplayGarment> garmentsList;
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text("New"),
body: new Container(
child:MyList(myList: garmentsList, canvasSize: realCanvas,);
)
}
}
class MyListState extends State<MyList>{
List<DisplayGarment> myList;
double realCanvas;
bool flag=false;
int touch=0;
#override
void initState() {
super.initState();
myList=widget.myList;
realCanvas = widget.canvasSize;
}
#override
Widget build(BuildContext context) {
return Stack(
children: getList(),
);
}
List<Widget> getList(){
List<Widget> listWidget=[];
for(int i=0;i<myList.length;i++) {
final ValueNotifier<Matrix4> notifier1= ValueNotifier(Matrix4.identity());
DisplayGarment _garments = myList[i];
listWidget.add(
MatrixGestureDetector(
key: Key(i.toString()),
onMatrixUpdate: (m, tm, sm, rm) {
notifier1.value = m;
},
child: AnimatedBuilder(
animation: notifier1,
builder: (ctx, child) {
return Transform(
transform: notifier1.value,
child: Stack(
children: <Widget>[
Container(
padding: EdgeInsets.all(4),
alignment: Alignment(0, -0.5),
child:
Container(
padding: EdgeInsets.all(0),
height: imgHeigh,
width: imgWidth,
child:
DottedBorder(
color: Colors.transparent,
strokeWidth: 0,
child: Center(
child: CachedNetworkImage(
imageUrl:
'https://fashiers.com/garments_img/'+imgUrl,
height: imgHeigh,
width: imgWidth,
)),
);
,)
)
],),);},),
)
);
}
return listWidget;
}
}
Also is it possible to set a border around the image when that is tap on and remove border on other that was probably tap before?
I appreciate any help with this.
For the first part, you should not put notifier1 initialization inside the build function. That's why every time you use setStatus, your position reset. This part should be initialize inside initState() and that means you should keep all garments location value in this class. (e.g. List<ValueNotifier>)
Second part, indexing the garments may be the instinctive solution. Display/Hide border by:
child: _selectedIndex == i ?
// Widget with border
: //Widget without border
and set _selectedIndex inside onMatrixUpdate()
I am trying to load images from network and show them in a GridView. I am using a StatefulWidget and loading the images inside the build method. But according to my understanding its not good to make a network call inside the build method. How can I download images from the network inside my BLoC file and later pass the list of downloaded images to the widget? Below is my current implementation.
class MovieList extends StatefulWidget {
#override
State<StatefulWidget> createState() {
return MovieListState();
}
}
class MovieListState extends State<MovieList> {
#override
void initState() {
super.initState();
bloc.fetchAllMovies();
}
#override
void dispose() {
bloc.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Popular Movies'),
),
body: StreamBuilder(
stream: bloc.allMovies,
builder: (context, AsyncSnapshot<ItemModel> snapshot) {
if (snapshot.hasData) {
return buildList(snapshot);
} else if (snapshot.hasError) {
return Text(snapshot.error.toString());
}
return Center(child: CircularProgressIndicator());
},
),
);
}
Widget buildList(AsyncSnapshot<ItemModel> snapshot) {
return GridView.builder(
itemCount: snapshot.data.results.length,
gridDelegate:
new SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 2),
itemBuilder: (BuildContext context, int index) {
return GridTile(
child: InkResponse(
enableFeedback: true,
child: Image.network(
'https://image.tmdb.org/t/p/w185${snapshot.data
.results[index].poster_path}',
fit: BoxFit.cover,
),
onTap: () => openDetailPage(snapshot.data, index),
),
);
});
}
openDetailPage(ItemModel data, int index) {
Navigator.push(
context,
MaterialPageRoute(builder: (context) {
return MovieDetailBlocProvider(
child: MovieDetail(
title: data.results[index].title,
posterUrl: data.results[index].backdrop_path,
description: data.results[index].overview,
releaseDate: data.results[index].release_date,
voteAverage: data.results[index].vote_average.toString(),
movieId: data.results[index].id,
),
);
}),
);
}
}
You can use loadingBuilder which is inbuilt feature from flutter for Image.Network
Image.network(
widget.networkUrl,
fit: BoxFit.fill,
loadingBuilder: (BuildContext context, Widget child,
ImageChunkEvent? loadingProgress) {
if (loadingProgress == null) return child;
return Center(
child: CircularProgressIndicator(
value: loadingProgress.expectedTotalBytes != null
? loadingProgress.cumulativeBytesLoaded /
loadingProgress.expectedTotalBytes!
: null,
),
);
},
),
I would recommend you to use
https://pub.dartlang.org/packages/cached_network_image
It's really works good for my cases.
Simple code example from their r
CachedNetworkImage(
imageUrl: "http://via.placeholder.com/350x150",
placeholder: (context, url) => new CircularProgressIndicator(),
errorWidget: (context, url, error) => new Icon(Icons.error),
),
or
Image(image: CachedNetworkImageProvider(url))
You should add to the pubspec file
cached_network_image: <actual version here>
into the dependencies section
If you want a circular shape for your image. You can use Circle Avatar in such a way that it will act as loader and displayer both....
Parent circle avatar will be having loader and If we put transparent color to child circle avatar it will show loading until it is loaded...
Plus point with this way is you can simply give border also by setting background color of parent circle avatar and increasing it's radius slightly.
CircleAvatar(
backgroundColor: Colors.red,
radius: 65,
backgroundImage: AssetImage('assets/bottombar/loading.gif'),
child: CircleAvatar(
radius: 65,
backgroundColor: Colors.transparent,
backgroundImage: NetworkImage(_url),
),
),
You can also use FadeInImage
https://flutter.dev/docs/cookbook/images/fading-in-images
FadeInImage.assetNetwork(
placeholder: 'assets/loading.gif',
image: 'https://picsum.photos/250?image=9',
),
It is a good practice to handle errors related to, for example, lack of Internet, when trying to load an image from the Internet. ErrorBuilder is really good if you use Image.network()
Image.network(
'https://example.does.not.exist/image.jpg',
errorBuilder: (BuildContext context, Object exception, StackTrace stackTrace) {
// Appropriate logging or analytics, e.g.
// myAnalytics.recordError(
// 'An error occurred loading "https://example.does.not.exist/image.jpg"',
// exception,
// stackTrace,
// );
return Text('😢');
},
),
Best way to Handle Network Images
The best way to load image from the network in flutter is to use flutter's built-in network function and customize it according to your requirements, I do not recommend/prefer to use any package like CachedNetworkImage because it sometimes works on mobile phone and give unexpected error on the web.
You can manage the Network image like this:
Image.network(
netWorkImageURL,
fit: BoxFit.fill,
// When image is loading from the server it takes some time
// So we will show progress indicator while loading
loadingBuilder: (BuildContext context, Widget child,
ImageChunkEvent? loadingProgress) {
if (loadingProgress == null) return child;
return Center(
child: CircularProgressIndicator(
value: loadingProgress.expectedTotalBytes != null
? loadingProgress.cumulativeBytesLoaded /
loadingProgress.expectedTotalBytes!
: null,
),
);
},
// When dealing with networks it completes with two states,
// complete with a value or completed with an error,
// So handling errors is very important otherwise it will crash the app screen.
// I showed dummy images from assets when there is an error, you can show some texts or anything you want.
errorBuilder: (context, exception, stackTrace) {
return Image.asset(
AppAssets.dummyPostImg,
fit: BoxFit.cover,
height: (widget.hideBelowImage == null ||
widget.hideBelowImage == false)
? 170.h
: 130.h,
width: double.infinity,
);
},
),