I have a Row that is made out of either one or two elements. Depending on the latter, I want to animate the width of the first element to either be fully expanded, or to accommodate for the second element.
I am using AnimatedContainers within the row to achieve this, but there is no animation. I thought this probably has to do with Flutter not recognizing it as the same after re-rendering based on its parent's state, so I tried using a Unique Key. This however didn't work either.
class TabsBar extends StatelessWidget {
TabsBar({this.currentIndex, this.onIndexChanged, Key key}) : super(key: key);
final int currentIndex;
final Function(int) onIndexChanged;
#override
Widget build(BuildContext context) {
final tabsBloc = BlocProvider.of<TabsBarBloc>(context);
return BlocBuilder<TabsBarBloc, TabsBarBlocState>(
builder: (context, tabsBarState) {
return SafeArea(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: standardSpacing),
child: Row(
children: [
Expanded(
child: AnimatedContainer(
key: key,
height: 60,
duration: Duration(milliseconds: 200),
//....//
),
//HERE: IF INDEX IS == 2, I WANT THE CONTAINER ABOVE TO ANIMATE INTO ITS NEW SIZE WHICH IS LESS WIDE TO ACCOMMODATE FOR THE CONTAINER BELOW
currentIndex == 2
? GestureDetector(
onTap: () {},
behavior: HitTestBehavior.translucent,
child: AnimatedContainer(
duration: Duration(milliseconds: 200),
padding: const EdgeInsets.only(left: standardSpacing),
height: 60,
child: Image.asset(
'${imagesPath}add_piece.png',
),
),
)
: HorizontalSpacing(0),
],
),
),
);
});
I also tried using a Layout Builder to define the width explicitly and using an Animated Container as a parent to the Row. Neither worked.
Can anybody point me towards what I might be missing?
I don't fully understand what value of the AnimatedContainer are you trying to change but it could be easier to just animate the second container of the row and change it's width to 0 so it dissapears from the screen
class Home extends StatefulWidget {
#override
State<Home> createState() => HomeState();
}
class HomeState extends State<Home> {
int currentIndex = 2; //or you could use a boolean type
#override
Widget build(BuildContext context) {
return Column(mainAxisSize: MainAxisSize.min, children: [
MyWidget(
currentIndex: currentIndex,
),
FlatButton(
onPressed: () => setState(() {
currentIndex = currentIndex == 1 ? 2 : 1;
}),
child: Text('Change'))
]);
}
}
class MyWidget extends StatelessWidget {
final int currentIndex;
static const double standardSpacing = 8.0;
MyWidget({this.currentIndex, Key key}) : super(key: key);
#override
Widget build(BuildContext context) {
return SafeArea(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: standardSpacing),
child: Row(
children: [
Expanded(
child: Container( //Normal Container
height: 60,
color: Colors.blue)
),
GestureDetector(
onTap: () {},
behavior: HitTestBehavior.translucent,
child: AnimatedContainer(
duration: Duration(milliseconds: 200),
padding: const EdgeInsets.only(left: standardSpacing),
height: 60,
width: currentIndex == 2 ? 36 : 0, //when currentIndex is not 2, it's size its zero
child: const Icon(Icons.save),
),
)
],
),
),
);
}
}
Related
i have simple row contains 2 item like following
class Test2 extends StatefulWidget {
const Test2({Key? key}) : super(key: key);
#override
State<Test2> createState() => Test2State();
}
class Test2State extends State<Test2> {
bool selected = false ;
#override
Widget build(BuildContext context) {
return Scaffold(
bottomNavigationBar: FloatingActionButton(
child: Icon(Icons.done),
onPressed: () {
setState(() {
selected = !selected;
});
},
),
backgroundColor: Colors.blue,
appBar: AppBar(),
body:
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
AnimatedContainer(
duration: Duration(milliseconds: 500),
color: Colors.yellowAccent,
width: !selected? 50:MediaQuery.of(context).size.width,
height:50
),
Container(
width:50,
child:Icon(Icons.star)
)
],
)
);
}
}
now when i change bool value to true it animated to left side but it Caused inspect widget to some pixels on the right .. even though if i use Flexible to my second item it solve problem . but also my icon disappear
i need animated to max lef side with keep my icon visible
how can i solve this
On AnimatedContainer you are setting width MediaQuery.of(context).size.width on click, therefore there will be no available space for second container which width is 50.
AnimatedContainer(
duration: Duration(milliseconds: 500),
color: Colors.yellowAccent,
width: !selected ? 50 : MediaQuery.of(context).size.width - 50, // -50 space for second container
height: 50),
Container(
width: 50,
child: Icon(Icons.star),
)
Imagine Facebook mobile app, where you tap on the notification about someone like your comment. The app will open the appropriate screen, scroll you down to the comment, and after you arrive there, the comment row will flash yellow for a while, rapidly turn transparent, and then it's done.
I just want to make the same flashing animation to a ListView/Column element to let users know that something is happening there as a result of their action. But from what I gathered, to create just a simple animation like that needs a complex elaborate contraption with Animation widgets.
There's a widget that does a much appreciated fade animation called FadeInImage. I just need to provide destination URL, placeholder image asset, and the widget will handle the rest. I'm wondering if there's such alternative where I can just provide a key to a widget, and then call from anywhere: rowKey.currentState.flash(color: Colors.yellow). Or perhaps a way to let me tell the ListView or Column to flash certain row like listViewKey.currentState.items[5].flash(color: Colors.yellow).
There is no a Widget like you are looking for, but you can create a custom widget if you know the Flutter basics. You will be able to build from simple animations to the most advanced ones.
I made a simple example, a list of elements where you can select any element from the list (index).
When you open the screen, you will see the scroll animation, after that, the blink animation will start.
class FlashingHome extends StatelessWidget {
const FlashingHome({Key? key}) : super(key: key);
void _goToWidget(BuildContext context, int index) {
Navigator.of(context).push(
MaterialPageRoute(
builder: (_) => FlashingList(index: index),
),
);
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
children: [
MaterialButton(
color: Colors.greenAccent,
child: const Text('Go to element 5'),
onPressed: () => _goToWidget(context, 5),
),
MaterialButton(
color: Colors.greenAccent,
child: const Text('Go to element 10'),
onPressed: () => _goToWidget(context, 10),
),
],
),
),
);
}
}
class FlashingList extends StatefulWidget {
const FlashingList({required this.index, Key? key}) : super(key: key);
final int index;
#override
State<FlashingList> createState() => _FlashingListState();
}
class _FlashingListState extends State<FlashingList>
with SingleTickerProviderStateMixin {
final _scrollController = ScrollController();
late final AnimationController _animationController;
final _itemSize = 150.0;
Timer? _timer;
Future<void> _startScrolling() async {
await _scrollController.animateTo(
_itemSize * widget.index,
duration: const Duration(seconds: 1),
curve: Curves.easeOut,
);
// after the scroll animation finishes, start the blinking
_animationController.repeat(reverse: true);
// the duration of the blinking
_timer = Timer(const Duration(seconds: 3), () {
setState(() {
_animationController.stop();
_timer?.cancel();
});
});
}
#override
void initState() {
_animationController = AnimationController(
vsync: this,
duration: const Duration(milliseconds: 500),
);
WidgetsBinding.instance!.addPostFrameCallback((_) => _startScrolling());
super.initState();
}
#override
void dispose() {
_timer?.cancel();
_animationController.dispose();
_scrollController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Flashing List'),
),
body: ListView.builder(
controller: _scrollController,
itemCount: 15,
itemExtent: 150,
itemBuilder: (context, index) {
final item = Padding(
padding: const EdgeInsets.all(20.0),
child: Text('My Item :$index'),
);
return Padding(
padding: const EdgeInsets.all(4.0),
child: FittedBox(
child: index == widget.index && _animationController.isDismissed
? FadeTransition(
opacity: _animationController,
child: Container(
color: Colors.yellow,
child: item,
),
)
: Container(
color: Colors.grey[200],
child: item,
),
),
);
},
),
);
}
}
Result:
Now that you know how to create an automatic scrolling list, animated item, you can customize this with a more complex animation and extract into a custom widget that you can reuse in your projects.
Reference: https://docs.flutter.dev/development/ui/animations
Try shimmer
While the data is being fetched, you can create a simple animation which will give the feel that something's loading. Here's a simple example.
I am using FAB onPress to reflect the changes.
bool isApiCallProcess = false;
#override
Widget build(BuildContext context) {
return SafeArea(
child: Scaffold(
floatingActionButton: FloatingActionButton(
onPressed: () {
setState(() {
isApiCallProcess = !isApiCallProcess;
});
},
),
backgroundColor: Colors.white,
body: (isApiCallProcess == false)
? CircleAvatar(
backgroundColor:
Colors.black12,
radius: 40,
backgroundImage: AssetImage(
'images/dosa.jpg',
),
):
Shimmer.fromColors(
baseColor: Colors.grey[300]!,
highlightColor: Colors.grey[100]!,
child: Wrap(
children: [
Column(
children: [
const CircleAvatar(
radius: 40,
backgroundImage: AssetImage(
'',
),
),
const SizedBox(
height: 10,
),
Container(
decoration: ShapeDecoration(
color: Colors.grey[400]!,
shape: const
RoundedRectangleBorder(),
),
height: 12,
width: 60,
),
],
),
],
),
),
),
);
Here's the screenshots :
expected behavior
i tried this code but it give me completely difference result from left side and strange animated
double val = 0;
#override
Widget build(BuildContext context) {
return Stack(
children: [
Container(
height: 400,
color: Colors.red,
),
TweenAnimationBuilder(
duration: const Duration(milliseconds: 150),
tween: Tween<double>(begin: 0 , end: val) ,
builder: (BuildContext context, double? value, Widget? child) {
return (
Transform(
alignment: Alignment.center,
transform: Matrix4.identity()
..setEntry(3, 2, 0.001)
..setEntry(0, 3, 200 * value!)
..rotateY((pi/6)*value),
child: DefaultTabController(
length: 5,
child: Scaffold(
body: Center(
child: Container(
color: Colors.yellowAccent,
child: IconButton(
onPressed: () {
setState(() {
setState(() {
val == 0 ? val = 1 : val = 0 ;
});
});
},
icon: Text('tab me'),
),
),
)
)
)
)
);
}
)
],
);
}
also i need only the red Container the one who animated from down to up , but i don't know why the main screen is also animate .. i need it never animated ..
any suggestion most welcome guys .. thanks
Instead of custom animation, you can use AnimatedContainer().
Create a boolean like selected which will tell the animated container when to close and when to open the container. And using setState you can toggle the animation.
Align your AnimatedContainer() with Align() and give alignment: Alignment.bottomCenter. And give height:0 is not selected and when selected give height the half of screen using MediaQuery.of(context)
class MyStatefulWidget extends StatefulWidget {
const MyStatefulWidget({Key? key}) : super(key: key);
#override
State<MyStatefulWidget> createState() => _MyStatefulWidgetState();
}
class _MyStatefulWidgetState extends State<MyStatefulWidget> {
bool selected = false;
#override
Widget build(BuildContext context) {
return Column(children: [
ElevatedButton(
onPressed: () {
setState(() {
selected = !selected;
});
},
child: Text("Tap Me!!"),
),
Spacer(),
GestureDetector(
onTap: () {
setState(() {
selected = !selected;
});
},
child: Align(
alignment: Alignment.bottomCenter,
child: AnimatedContainer(
width: double.infinity,
height: selected ? MediaQuery.of(context).size.height / 2 : 0,
color: selected ? Colors.red : Colors.blue,
alignment:
selected ? Alignment.center : AlignmentDirectional.topCenter,
duration: const Duration(seconds: 2),
curve: Curves.fastOutSlowIn,
child: const FlutterLogo(size: 75),
),
),
)
]);
}
}
You can try the same code in dartpad here
I want to create a website using flutter web but I'm unable to navigate to sections in the same page. Here's an example of what I want to achieve using flutter.
P.S. Navigator is not working:
I created an example with PageView
class MyHomePage extends StatelessWidget {
var list = ["Home","Services", "Work", "About"];
var colors = [Colors.orange, Colors.blue, Colors.red, Colors.green];
PageController controller = PageController();
#override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: Column(
mainAxisSize: MainAxisSize.max,
children: <Widget>[
Row(
children: <Widget>[
Container(
width: 50,
height: 50,
margin: EdgeInsets.all(8),
decoration: BoxDecoration(
color: Colors.blue,
borderRadius: BorderRadius.circular(10)
),
),
Spacer(),
Row(
children: List.generate(3, (index){
return GestureDetector(
onTap: (){
_scrollToIndex(index);
},
child: Container(
margin: EdgeInsets.all(8),
child: Text(
list[index+1]
),
),
);
}),
)
],
),
Expanded(
child : PageView(
scrollDirection: Axis.vertical,
pageSnapping: false,
controller: controller,
children: List.generate(list.length, (index){
return Container(
width: MediaQuery.of(context).size.width,
height: double.maxFinite,
color: colors[index],
child: Center(
child: Text(
list[index],
style: TextStyle(
color: Colors.white,
fontSize: 50
),
),
),
);
})
),
),
],
)
),
);
}
void _scrollToIndex(int index) {
controller.animateToPage(index + 1, duration: Duration(seconds: 2), curve: Curves.fastLinearToSlowEaseIn);
}
}
The output:
ScrollController is the thing you are looking for.
Add a new one to your ScrolView and you can set where you want it to scroll to.
Josteve mentioned a way of doing it. But I'd like to show the other way which provides more features as one would expect in the gif example you have put.
You can see the demo here: https://mohith7548.github.io/portfolio/
My project has 3 sections called About, Blog & Projects. It also has another top section called Home. So the order of screens is Home, About, Blog & Projects. Each section takes full-screen height & width. So the starting offset for these pages are [0 * screenHeight, 1 * screenHeight, 2 * screenHeight, 3 * screenHeight] respectively. screenHeight can be accessed by MediaQuery.of(context).size.height inside build method.
class Portfolio extends StatefulWidget {
#override
_PortfolioState createState() => _PortfolioState();
}
class _PortfolioState extends State<Portfolio> {
ScrollController _scrollController;
String _curNavItem;
static double offsetHome = 0;
static double offsetAbout = SizeConfig.screenHeight;
static double offsetBlog = 2 * SizeConfig.screenHeight;
static double offsetProjects = 3 * SizeConfig.screenHeight;
#override
void initState() {
super.initState();
_scrollController = ScrollController();
}
#override
void dispose() {
super.dispose();
_scrollController.dispose();
}
void scrollTo(String title) {
double offset = 0;
switch (title) {
case Constants.HOME:
offset = offsetHome;
break;
case Constants.ABOUT:
offset = offsetAbout;
break;
case Constants.BLOG:
offset = offsetBlog;
break;
case Constants.PROJECTS:
offset = offsetProjects;
break;
}
setState(() {
_curNavItem = title;
});
// animate to the pag
_scrollController.animateTo(
offset,
duration: Duration(milliseconds: 500),
curve: Curves.easeInOutQuart,
);
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: CustomScrollView(
physics: PageScrollPhysics(), // use NeverScrollableScrollPhysics() to block user scrolling
controller: _scrollController,
slivers: <Widget>[
// This is just SliverAppBar wrapped in InterheritedWidget called NavState
// You can use just SliverAppBar
NavState(
curNavItem: _curNavItem,
scrollTo: scrollTo,
child: AppBanner(key: _appBannerKey), // SliverAppBar in another file
),
SliverList(
delegate: SliverChildListDelegate([
About(),
Blog(),
Projects(),
]),
)
],
),
);
}
}
You can do this in different ways:
TabBarView https://stackoverflow.com/a/60624536/10976088
PageView https://stackoverflow.com/a/60778791/10976088
NavigationRail https://api.flutter.dev/flutter/material/NavigationRail-class.html
My method: Using a state management way to keep name or index of content pages and change visible page. I do it with the Riverpod package here:
Suppose you want to have a fixed SidebarView and HeaderView in all pages and also you have a ContentPage that will be changed.
So you can have a RootPage including these 3 sections and change ContentPage by the riverpod, so that only ContentPage will be changed.
class RootPage extends StatelessWidget {
const RootPage({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
drawer: SidebarView(),
body: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (Responsive.isDesktop(context))
const Expanded(
flex: 1,
child: SidebarView(),
),
Expanded(
flex: 5,
child: SafeArea(
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
children: [
HeaderView(),
Expanded(
child: Padding(
padding: const EdgeInsets.all(16),
child: Consumer(
builder: (context, ref, _) {
var watch = ref.watch(pageVisibleStateProvider);
return contentPageSelection(watch.state);
},
),
),
),
],
),
),
),
],
),
);
}
}
simply change content page:
Widget contentPageSelection(String pageName){
switch(pageName){
case "page1":
return Page1();
case "page2":
return Page2();
case "page3":
return Page3();
default:
return DefaultPage();
}
}
where:
final pageVisibleStateProvider = StateProvider<String>((_) => "defaultPage");
and:
class SidebarView extends StatelessWidget {
const SidebarView({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Container(
child: Text("sidebar content"),
);
}
}
class HeaderView extends StatelessWidget {
const HeaderView({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Container(
child: Text("HeaderView content"),
);
}
}
Now you can change content page. for example you want to show Page2:
ElevatedButton(
onPressed: (){
ref.read(pageVisibleStateProvider.notifier).state = "page2";
},
child: Text("go to page 2"),
)
where page2 and other content pages only includes content not sidebar or header:
class Page2 extends StatelessWidget {
const Page2({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Text("page2 content");
}
}
I want to create slider animation with Images and also want to allow user to use swipe gesture to move back and forth. Another requirement is Page indicator. For this purpose, I used
page_indicator: ^0.1.3
Currently I am able to slide between images using swipe gesture with page indicator and now I want to animate slides repeatedly with x amount of duration.
My code is below.
final PageController controller = new PageController();
#override
Widget build(BuildContext context) {
List<Widget> list = new List<Widget>();
list.add(new SliderBox(image: 'assets/shirt.png'));
list.add(new SliderBox(image: 'assets/laptops.png'));
list.add(new SliderBox(image: 'assets/bags.png'));
PageIndicatorContainer container = new PageIndicatorContainer(
pageView: new PageView(
children: list,
controller: controller,
),
length: 3,
padding: EdgeInsets.fromLTRB(10, 40, 10, 10),
indicatorSpace: 10,
indicatorColor: Colors.grey[350],
indicatorSelectorColor: Colors.grey,
);
return Stack(children: <Widget>[
Container(color: Colors.grey[100], height: double.infinity),
Container(
color: Colors.white,
child: container,
margin: EdgeInsets.only(bottom: 50)),Text('$moveToPage')
]);
class SliderBox extends StatelessWidget {
final image;
const SliderBox({Key key, this.image}) : super(key: key);
#override
Widget build(BuildContext context) {
// TODO: implement build
return Container(
padding: EdgeInsets.all(10),
child: Image.asset(
image,
height: 300,
fit: BoxFit.fill,
));
}
}
I've modified your widget a little in order to provide you a complete example. You can do this in multiple ways, with AnimationController by itself or even combined with a custom Animation or, you can go the fastest way for what it seems that you want to achieve: using a recursive method that waits a x duration (time stand on a single page) and then animates with some new duration to the new page. For that you can for example:
Make your List available within the state itself in order to retrieve its length.
Create the recursive method that will handle the animation itself.
Make sure that you call it after the first frame is rendered on the screen to prevent the PageController of being accessed before having a PageView rendered on the screen, which you probably don't want. For that you take advantage of the WidgetsBinding.instance.addPostFrameCallback.
class Carousel extends StatefulWidget {
_CarouselState createState() => _CarouselState();
}
class _CarouselState extends State<Carousel> with SingleTickerProviderStateMixin {
final PageController _controller = PageController();
List<Widget> _list = [
SliderBox(
child: FlutterLogo(
colors: Colors.red,
)),
SliderBox(
child: FlutterLogo(
colors: Colors.green,
)),
SliderBox(
child: FlutterLogo(
colors: Colors.blue,
))
];
#override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) => _animateSlider());
}
void _animateSlider() {
Future.delayed(Duration(seconds: 2)).then((_) {
int nextPage = _controller.page.round() + 1;
if (nextPage == _list.length) {
nextPage = 0;
}
_controller
.animateToPage(nextPage, duration: Duration(seconds: 1), curve: Curves.linear)
.then((_) => _animateSlider());
});
}
#override
Widget build(BuildContext context) {
PageIndicatorContainer container = new PageIndicatorContainer(
pageView: new PageView(
children: _list,
controller: _controller,
),
length: _list.length,
padding: EdgeInsets.fromLTRB(10, 40, 10, 10),
indicatorSpace: 10,
indicatorColor: Colors.grey[350],
indicatorSelectorColor: Colors.grey,
);
return Stack(
children: <Widget>[
Container(color: Colors.grey[100], height: double.infinity),
Container(color: Colors.white, child: container, margin: EdgeInsets.only(bottom: 50)),
],
);
}
}
class SliderBox extends StatelessWidget {
final Widget child;
const SliderBox({Key key, this.child}) : super(key: key);
#override
Widget build(BuildContext context) {
return Container(padding: EdgeInsets.all(10), child: child);
}
}