How to implement a horizontal scroll which is controlled by bottom minus bar in flutter - flutter

I have to implement a horizontal scroll list in flutter.I could do that and have included the code below(The code is still to be modified but the base of the code is good enough to put in the pictures and other such details)
But the problem is the minus bar below the horizontal scroll.I don't know what feature in flutter allows to do that.I search many things but other than radio boxes,check boxes, switches,etc I am not able to find any details of it.Please have a look at the screenshot of the app ,I have indicated the minus bar control in red.Home screen,the minus bar indicated in red
The code I have written:
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.black ,
body: Container(
margin: EdgeInsets.symmetric(vertical: 20.0),
height: 500,
child: ListView(
// This next line does the trick.
scrollDirection: Axis.horizontal,
shrinkWrap: true,
children: <Widget>[
Container(
width:400 ,
color: Colors.red,
),
Container(
width: 400.0,
color: Colors.blue,
),
Container(
width: 400.0,
color: Colors.green,
),
],
),
)
);
}
}

What you want to look for is not ListView but PageView here is a small code sample to try in DartPad and see how you could make your layout.
Basically I am using a PageController to change the current page by taping on certain widgets.
Code
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: MyWidget()),
);
}
}
class MyWidget extends StatefulWidget {
#override
_MyWidgetState createState() => _MyWidgetState();
}
class _MyWidgetState extends State<MyWidget> {
final _items = [Colors.red, Colors.blue, Colors.yellow];
final _pageController = PageController();
int _currentPageNotifier = 0;
final double _indicatorWidth = 30;
Widget _buildPageView() {
return PageView.builder(
controller: _pageController,
itemCount: _items.length,
itemBuilder: (context, index) => Center(
child: FlutterLogo(
colors: _items[index],
size: 50,
),
),
onPageChanged: (int index) =>
setState(() => _currentPageNotifier = index),
);
}
Widget _buildIndicator() {
List<Widget> itemWidgets = [];
for (int index = 0; index < _items.length; index++) {
itemWidgets.add(GestureDetector(
onTap: () => _pageController.animateToPage(
index,
duration: Duration(milliseconds: 300),
curve: Curves.ease,
),
child: Container(
decoration: BoxDecoration(
color: _currentPageNotifier == index
? Colors.green
: Colors.grey,
borderRadius: BorderRadius.circular(9),
),
margin: EdgeInsets.only(right: 10),
width: _indicatorWidth,
height: 8,
),
));
}
return Positioned(
bottom: MediaQuery.of(context).size.height / 2 - 50,
left: MediaQuery.of(context).size.width / 2 -
_items.length * _indicatorWidth +
_items.length * 10,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: itemWidgets,
),
);
}
#override
Widget build(BuildContext context) {
return Stack(
children: <Widget>[
_buildPageView(),
_buildIndicator(),
],
);
}
}

Related

How do I schedule widget deletion as a future event?

I am looking for a way to do widget deletion in the future.
It's easiest to describe the problem through an example (and a MWE).
The user is presented with several AnimatedPositioneds containers, representing a card game.
The PositionedContainer part means that each card can be used for Gin Rummy, Bridge, or, in fact, any abstract numbers card game.
When the user clicks one card, the card slides up (using the Animated part of AnimatedContainer)
and then we'd like the card to be removed from the stack of widgets, i.e. to "disappear" (and not just hide through opacity)
import 'package:flutter/material.dart';
import 'dart:math';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text('Cards'),
),
body: Center(
child: Container(
alignment: Alignment.center,
child: CardGameWidget(),
decoration: BoxDecoration(
border: Border.all(
color: Colors.blueAccent,
),
),
),
),
),
);
}
}
class CardGameWidget extends StatefulWidget {
#override
CardGameWidgetState createState() => CardGameWidgetState();
}
class CardGameWidgetState extends State<CardGameWidget> {
List<Card> cards = [];
CardGameWidgetState() {
for (var i = 0; i < 5; ++i) {
this.cards.add(Card(
offset: Offset(i * 100.0, 200),
number: Random().nextInt(1 << 16))
);
}
}
Function onTap(int index) => (newOffset) {
setState(() {
cards[index].offset += Offset(0,-100);
});
};
#override
Widget build(BuildContext context) {
List<CardWidget> cardWidgets = [];
for (int i = 0; i < this.cards.length; ++i) {
cardWidgets.add(CardWidget(
onTap: onTap(i),
offset: this.cards[i].offset,
number: this.cards[i].number,
));
}
return Stack(children: cardWidgets);
}
}
class Card {
Card({this.offset, this.number});
Offset offset;
int number;
}
class CardWidget extends StatelessWidget {
CardWidget({
Key key,
this.onTap,
this.offset,
this.number,
});
final Function onTap;
final Offset offset;
final int number;
_handleTap(details) {
onTap(details.globalPosition);
}
#override
Widget build(BuildContext context) {
return AnimatedPositioned(
left: this.offset.dx,
top: this.offset.dy,
width: 100,
height: 100,
duration: Duration(seconds: 1),
child: GestureDetector(
onTapUp: _handleTap,
child: Container(
color: Colors.cyan,
padding: EdgeInsets.all(10),
margin: EdgeInsets.all(10),
child: FittedBox(
clipBehavior: Clip.antiAlias,
alignment: Alignment.centerLeft,
fit: BoxFit.contain,
child: Text(this.number.toString()),
))),
);
}
}
How do I schedule widget deletion as a future event, after the completion of an animation?
You can look into AnimiatedList:
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(primarySwatch: Colors.blue, brightness: Brightness.dark),
home: SimpleAnimatedList(),
);
}
}
class SimpleAnimatedList extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
body: SliceAnimatedList(),
);
}
}
class SliceAnimatedList extends StatefulWidget {
#override
_SliceAnimatedListState createState() => _SliceAnimatedListState();
}
class _SliceAnimatedListState extends State<SliceAnimatedList> {
final GlobalKey<AnimatedListState> listKey = GlobalKey<AnimatedListState>();
List<int> _items = [];
int counter = 0;
Widget slideIt(BuildContext context, int index, animation) {
int item = _items[index];
TextStyle textStyle = Theme.of(context).textTheme.headline4;
return SlideTransition(
position: Tween<Offset>(
begin: const Offset(-1, 0),
end: Offset(0, 0),
).animate(animation),
child: SizedBox(
height: 128.0,
child: Card(
color: Colors.primaries[item % Colors.primaries.length],
child: Center(
child: Text('Item $item', style: textStyle),
),
),
),
);
}
#override
Widget build(BuildContext context) {
return Column(
mainAxisSize: MainAxisSize.max,
children: <Widget>[
Expanded(
child: Container(
height: double.infinity,
child: AnimatedList(
key: listKey,
initialItemCount: _items.length,
itemBuilder: (context, index, animation) {
return slideIt(context, index, animation);
},
),
),
),
Container(
decoration: BoxDecoration(color: Colors.greenAccent),
child: Row(
mainAxisSize: MainAxisSize.max,
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
FlatButton(
onPressed: () {
setState(() {
listKey.currentState.insertItem(0,
duration: const Duration(milliseconds: 500));
_items = []
..add(counter++)
..addAll(_items);
});
},
child: Text(
"Add item to first",
style: TextStyle(color: Colors.black, fontSize: 20),
),
),
FlatButton(
onPressed: () {
if (_items.length <= 1) return;
listKey.currentState.removeItem(
0, (_, animation) => slideIt(context, 0, animation),
duration: const Duration(milliseconds: 500));
setState(() {
_items.removeAt(0);
});
},
child: Text(
"Remove first item",
style: TextStyle(color: Colors.black, fontSize: 20),
),
)
],
),
),
],
);
}
}

Stateful widget doesn't change state

In this code, when I change page (I'm using PageView as is it in code below) flutter doesn't trigger rebuild, so condition if(_page == 1) will take effect after I press "hot reload". Any tips for solution? I calling this class in main.dart (HomePage) which is Stateless widget. Could it be the problem?
Thanks for any help!
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
int _page = 0;
class Guide extends StatefulWidget {
#override
State<StatefulWidget> createState() {
return new GuideState();
}
}
class GuideState extends State<Guide> {
#override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: Center(
child: Column(children: [
if (_page == 1)
Padding(
padding: EdgeInsets.fromLTRB(0, 10, 0, 0),
child: Image(
image: AssetImage('graphics/Logo.png'),
height: MediaQuery.of(context).size.height * 0.1)),
SizedBox(height: 500, child: Page()),
]))));
}
}
class Page extends StatefulWidget {
PageState createState() => PageState();
}
class PageState extends State<Page> {
final controller = PageController(
initialPage: 0,
);
#override
Widget build(BuildContext context) {
return Scaffold(
//appBar: AppBar(title: Text('PageView Widget in Flutter')),
body: Center(
child: Container(
width: MediaQuery.of(context).size.width * 0.95,
height: MediaQuery.of(context).size.height * 0.6,
child: PageView(
controller: controller,
onPageChanged: (page) {
setState(() {
if (page == 1) {
_page = 1;
}
});
},
pageSnapping: true,
scrollDirection: Axis.horizontal,
children: <Widget>[
Container(
//color: Colors.pink,
//child: Center(
child: Text(
'1. Tento text bude nahrán z databáze.',
style: TextStyle(fontSize: 25, color: Colors.black),
)),
Container(
//color: Colors.green,
child: Text(
'2. Tento text bude nahrán z databáze',
style: TextStyle(fontSize: 25, color: Colors.black),
)),
Container(
// color: Colors.lightBlue,
child: Text(
'3. Tento text bude nahrán z databáze',
style: TextStyle(fontSize: 25, color: Colors.black),
)),
],
),
)));
}
}
The variable _page is set as global, it has to be part of as state inorder to trigger changes, but in your case you want to change a widget base on action in another child widget, this can be done in several ways depending on your choice the easies in you case is to have a function as a parameter for your child widget Page :
class Page extends StatefulWidget {
final Function(int) onChange;
const Page({Key key, this.onChange}) : super(key: key);
PageState createState() => PageState();
}
and then call it when the page change
onPageChanged: (page) {
widget.onChange(page);
},
so with this you can handle the change in you parent widget and trigger state change
class GuideState extends State<Guide> {
int _page = 0;
#override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: Center(
child: Column(
children:[
if (_page == 1)
Padding(
padding: EdgeInsets.fromLTRB(0, 10, 0, 0),
child: Image(
image: AssetImage('graphics/Logo.png'),
height: MediaQuery.of(context).size.height * 0.1,
),
),
SizedBox(
height: 500,
child: Page(
onChange: (page) {
setState(() => _page = page);
},
),
),
],
),
),
),
);
}
}
int _page = 0; is not part of the state of your Guide widget. Place it here:
class GuideState extends State<Guide> {
int _page = 0;
...

Flutter expandable tiles inside grid view

I'm trying to achieve this functionality on flutter but honestly have no idea how to do it. I've been trying to figure it out for weeks, I tried flutter_staggered_grid_view, which was kind of the closest to this, but that didn't help either. Does anyone have any idea on how to achieve this effect?
You can use the Wrap widget as grid, and use some custom widget with AnimatedContainer to expand and retract the bloc.
//number of childs used in the example
static const itemCount = 8;
//list of each bloc expandable state, that is changed to trigger the animation of the AnimatedContainer
List<bool> expandableState = List.generate(itemCount, (index) => false);
Widget bloc (double width, int index) {
bool isExpanded = expandableState[index];
return GestureDetector(
onTap: () {
setState(() {
//changing the current expandableState
expandableState[index] = !isExpanded;
});
},
child: AnimatedContainer(
duration: Duration(milliseconds: 200),
margin: const EdgeInsets.all(20.0),
width: !isExpanded ? width * 0.4 : width * 0.8,
height: !isExpanded ? width * 0.4 : width * 0.8,
color: Colors.red,
),
);
}
#override
Widget build(BuildContext context) {
double width = MediaQuery.of(context).size.width;
return Scaffold(
body: Align(
child: SingleChildScrollView(
child: Wrap(
children: List.generate(itemCount, (index) {
return bloc(width, index);
}),
),
),
),
);
}
You could have a list of the items that should be expanded and lay them out in the grid view accordingly (using flutter_staggered_grid_view for example).
I edited the example written in the library docs to achieve the following result:
Basically,
create a StatefulWidget and add a list to it (_expandedIndices). The purpose of the list is to keep track of the indices of the items that are expanded.
add a GestureDetector on the grid cells to detect taps and add/remove indices from the list (add the index to the list if it is not already there, otherwise remove it).
Don't forget to put the code that updates the list inside a setState.
in the staggeredTileBuilder configure crossAxisCellCount and mainAxisCellCount based on whether the item in index should be expanded or not.
class StaggeredGridViewWithExpandableCells extends StatefulWidget {
#override
_StaggeredGridViewWithExpandableCellsState createState() =>
_StaggeredGridViewWithExpandableCellsState();
}
class _StaggeredGridViewWithExpandableCellsState
extends State<StaggeredGridViewWithExpandableCells> {
final _expandedIndices = Set<int>();
#override
Widget build(BuildContext context) {
return StaggeredGridView.countBuilder(
crossAxisCount: 4,
itemCount: 16,
itemBuilder: (BuildContext context, int index) => GestureDetector(
onTap: () => setState(() => _expandedIndices.contains(index) ? _expandedIndices.remove(index) : _expandedIndices.add(index)),
child: new Container(
color: Colors.green,
child: new Center(
child: new CircleAvatar(
backgroundColor: Colors.white,
child: new Text('$index'),
),
)),
),
staggeredTileBuilder: (int index) =>
new StaggeredTile.count(_expandedIndices.contains(index) ? 4 : 2, 1),
mainAxisSpacing: 4.0,
crossAxisSpacing: 4.0,
);
}
}
You can use Wrap widget to achieve the result. Please see the code below.
import 'package:flutter/material.dart';
final Color darkBlue = const 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(
appBar: AppBar(title: const Text("Demo")),
body: Center(
child: MyWidget(),
),
),
);
}
}
class MyWidget extends StatefulWidget {
#override
_MyWidgetState createState() => _MyWidgetState();
}
class _MyWidgetState extends State<MyWidget> {
final int _cells = 8;
final double _containerSizeSmall = 75;
final double _containerSizeLarge = 170;
final double _padding = 10;
int _clicked = 0;
#override
Widget build(BuildContext context) {
final Size size = MediaQuery.of(context).size;
return SingleChildScrollView(
child: Container(
height: size.height,
width: 240,
child: Wrap(
children: List.generate(
_cells,
(col) => Padding(
padding: EdgeInsets.all(_padding),
child: GestureDetector(
onTap: () {
setState(() {
_clicked != col + 1 ? _clicked = col + 1 : _clicked = 0;
});
},
child: Container(
height: _clicked == col + 1
? _containerSizeLarge
: _containerSizeSmall,
width: _clicked == col + 1
? _containerSizeLarge
: _containerSizeSmall,
decoration: const BoxDecoration(
color: Colors.blue,
borderRadius: const BorderRadius.all(
const Radius.circular(5),
),
),
child: Center(child: Text('${col + 1}')),
),
),
),
),
),
),
);
}
}

Flutter how to remove padding on top of scrollbar listview

First of all, sorry for my bad english. So my question is why there are some padding on top of the scrollbar and how to remove it? I think there's a problem with the overlay widget in my code but I can't find the problem and how to solve it. Please help and thank you. PS: I don't want to use the default dropdown widget because I don't really like the design so I made a custom dropdown button with some help from other code.
So here's my code :
import 'package:flutter/material.dart';
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
bool dropdownOpen = false;
OverlayEntry dropdown;
dynamic actionKey = LabeledGlobalKey("actionKey");
ScrollController sc = ScrollController();
OverlayEntry createDropdown() {
RenderBox renderBox = actionKey.currentContext.findRenderObject();
Offset offset = renderBox.localToGlobal(Offset.zero);
return OverlayEntry(
builder: (context) {
return Positioned(
left: offset.dx,
width: renderBox.size.width,
top: offset.dy + renderBox.size.height,
height: 230,
child: Material(
elevation: 20,
child: Scrollbar(
isAlwaysShown: true,
controller: sc,
child: ListView.builder(
controller: sc,
padding: EdgeInsets.all(0),
itemCount: 5,
itemBuilder: (BuildContext context, int index){
return Container(
height: 50,
alignment: Alignment.center,
child: Text((index+1).toString())
);
}
)
)
)
);
}
);
}
#override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData(primaryColor: Colors.red),
home: Scaffold(
body: Center(
child: GestureDetector(
key: actionKey,
child: Container(
height: 50,
width: 100,
color: Colors.yellow,
alignment: Alignment.center,
child: Text("Click")
),
onTap: (){
setState((){
if(dropdownOpen){
dropdown.remove();
dropdownOpen = false;
} else {
dropdown = createDropdown();
Overlay.of(context).insert(dropdown);
dropdownOpen = true;
}
});
}
)
)
),
);
}
}
and here's what it looks like :
image

How to give some space (margin/padding) between pages in PageView?

I am using PageView.builder to create pages.
PageView.builder(
itemCount: _pagesList.length,
itemBuilder: (BuildContext context, int index) {
return Container(
color: _pagesList[index],
);
}
)
What I currently have:
What I want:
i.e. I want to provide some Padding between pages (when they are being scrolled)
Reason: I will display Images in these pages, and since the Images will cover the full width of each page, it doesn't look nice when we scroll pages, since they are knitted together, like this:
How can I solve this?
PageController imagesController =
PageController(initialPage: 0, viewportFraction: 1.1);
PageView(
itemBuilder: (BuildContext context, int index) {
return Padding(
padding: EdgeInsets.only(left: 10, right: 10),
child: Container(
color: _pagesList[index],
),
);
}
),
If you want to add padding and still have your pages as wide as the screen:
I needed this exact same thing, also for displaying images. I wanted to add padding but at the same time have each image take up the entire screen width. I figured I could use Fahad Javed's technique and tweaking it a little bit by calculating the viewPortFraction based on the screen width and padding.
#override
Widget build(BuildContext context) {
double screenWidth = MediaQuery.of(context).size.width; // screen width
double screenPad = 16.0; // screen padding for swiping between pages
int _currentPosition = 0;
return PageView.builder(
scrollDirection: Axis.horizontal,
itemCount: data.length,
controller: PageController(
initialPage: _currentPosition,
viewportFraction:
1 + (screenPad * 2 / screenWidth)), // calculate viewPortFraction
onPageChanged: (int value) {
_currentPosition = value;
},
itemBuilder: (BuildContext context, int position) {
return Padding(
padding: EdgeInsets.only(left: screenPad, right: screenPad),
child: Text('YOUR PAGE CONTENT'),
);
},
);
}
This answer from on the question asked by Amon Kataria Github
final pageController = PageController(viewportFraction: 1.1);
PageView.builder(
controller: pageController,
itemCount: _pagesList.length,
itemBuilder: (BuildContext context, int index) {
return FractionallySizedBox(
widthFactor: 1 / pageController.viewportFraction,
child: Container(
color: _pagesList[index],
),
);
},
);
Thanks #mono0926
Best effort:
import 'package:flutter/material.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: 'Flutter Demo',
home: Scaffold(
body: MyPageView()
)
);
}
}
class MyPageView extends StatefulWidget {
MyPageView({Key key}) : super(key: key);
_MyPageViewState createState() => _MyPageViewState();
}
class _MyPageViewState extends State<MyPageView> {
#override
Widget build(BuildContext context) {
return PageView(
children: <Widget>[
Container(
color: Colors.black,
child: Card(
color: Colors.red,
)
),
Container(
color: Colors.black,
child: Card(
color: Colors.blue,
),
),
Container(
color: Colors.black,
child: Card(
color: Colors.green,
),
),
],
);
}
}
You just need to add some padding around each page and the width of the page view must be at least the 'card width + the padding from both sides'. This worked for me:
class MyWidget extends StatelessWidget {
final _CARD_WIDTH = 220.0;
final PageController _controller = PageController(initialPage: 0);
#override
Widget build(BuildContext context) {
return Container(
height: _CARD_WIDTH,
width: _CARD_WIDTH + 32,
child: PageView(
scrollDirection: Axis.horizontal,
controller: _controller,
children: <Widget>[
_buildImageCard("1"),
_buildImageCard("2"),
_buildImageCard("3"),
],
),
);
}
Widget _buildImageCard(String text) {
return Padding(
padding: const EdgeInsets.only(left: 16.0, right: 16),
child: Container(
decoration: BoxDecoration(
color: Colors.blue,
borderRadius: BorderRadius.circular(15),
),
width: _CARD_WIDTH,
height: _CARD_WIDTH,
child: Center(
child: Text(text),
),
),
);
}
}