Here is a material design of Expanded panel that looks like:
I'd like to make a similar one with Flutter, not sure if I've to start with something like the below code or know, and how to complete it!
new ExpansionPanelList(
children: <ExpansionPanel>[
new ExpansionPanel(
headerBuilder: (BuildContext context, bool isExpanded) {
isExpanded = true;
return new ListTile(
// leading: item.iconpic,
title: new Text(
"First",
textAlign: TextAlign.left,
style: new TextStyle(
fontSize: 20.0,
fontWeight: FontWeight.w400,
),
));
},
body: new Text("school"),
isExpanded: true,
),
new ExpansionPanel(
headerBuilder: (BuildContext context, bool isExpanded) {
isExpanded = true;
return new ListTile(
// leading: item.iconpic,
title: new Text(
"Second",
textAlign: TextAlign.left,
style: new TextStyle(
fontSize: 20.0,
fontWeight: FontWeight.w400,
),
));
},
isExpanded: false,
body: new Text("hospital"),
),
new ExpansionPanel(
headerBuilder: (BuildContext context, bool isExpanded) {
isExpanded = true;
return new ListTile(
// leading: item.iconpic,
title: new Text(
"Third",
textAlign: TextAlign.left,
style: new TextStyle(
fontSize: 20.0,
fontWeight: FontWeight.w400,
),
));
},
body: new Text("va facility"),
isExpanded: true)
]),
UPDATE
I just need to start and have the empty panels
In case if you particularly need to mimic the images you referenced from the material design. You would want to build your own custom expansion panel.
I have a simple example using AnimatedContainer to show you how to create the expanded and collapsed effects, and it is up to you to populate both the header and the body sections with what you want.
class AnimateExpanded extends StatefulWidget {
#override
_AnimateExpandedState createState() => new _AnimateExpandedState();
}
class _AnimateExpandedState extends State<AnimateExpanded> {
double _bodyHeight = 0.0;
#override
Widget build(BuildContext context) {
return new Scaffold(
backgroundColor: Colors.grey[500],
body: new SingleChildScrollView(
child: new Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
new Card(
child: new Container(
height: 50.0,
child: new Row(
mainAxisAlignment: MainAxisAlignment.end,
children: <Widget>[
new IconButton(
icon: new Icon(Icons.keyboard_arrow_down),
onPressed: () {
setState(() {
this._bodyHeight = 300.0;
});
},
)
],
),
),
),
new Card(
child: new AnimatedContainer(
child: new Row(
mainAxisAlignment: MainAxisAlignment.end,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
new IconButton(
icon: new Icon(Icons.keyboard_arrow_up),
onPressed: () {
setState(() {
this._bodyHeight = 0.0;
});
},
),
],
),
curve: Curves.easeInOut,
duration: const Duration(milliseconds: 500),
height: _bodyHeight,
// color: Colors.red,
),
),
],
),
),
);
}
}
Here's a working example (including main etc so you can just paste into a file and run)
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
class ListItem {
final WidgetBuilder bodyBuilder;
final String title;
final String subtitle;
bool isExpandedInitially;
ListItem({
#required this.bodyBuilder,
#required this.title,
this.subtitle = "",
this.isExpandedInitially = false,
}) : assert(title != null),
assert(bodyBuilder != null);
ExpansionPanelHeaderBuilder get headerBuilder =>
(context, isExpanded) => new Row(children: [
new SizedBox(width: 100.0, child: new Text(title)),
new Text(subtitle)
]);
}
class ExpansionList extends StatefulWidget {
/// The items that the expansion list should display; this can change
/// over the course of the object but probably shouldn't as it won't
/// transition nicely or anything like that.
final List<ListItem> items;
ExpansionList(this.items) {
// quick check to make sure there's no duplicate titles.
assert(new Set.from(items.map((li) => li.title)).length == items.length);
}
#override
State<StatefulWidget> createState() => new ExpansionListState();
}
class ExpansionListState extends State<ExpansionList> {
Map<String, bool> expandedByTitle = new Map();
#override
Widget build(BuildContext context) {
return new ExpansionPanelList(
children: widget.items
.map(
(item) => new ExpansionPanel(
headerBuilder: item.headerBuilder,
body: new Builder(builder: item.bodyBuilder),
isExpanded:
expandedByTitle[item.title] ?? item.isExpandedInitially),
)
.toList(growable: false),
expansionCallback: (int index, bool isExpanded) {
setState(() {
expandedByTitle[widget.items[index].title] = !isExpanded;
});
},
);
}
}
void main() => runApp(
new MaterialApp(
home: new SingleChildScrollView(
child: new SafeArea(
child: new Material(
child: new ExpansionList(
[
new ListItem(
title: "Title 1",
subtitle: "Subtitle 1",
bodyBuilder: (context) => new Text("Body 1")),
new ListItem(
title: "Title 2",
subtitle: "Subtitle 2",
bodyBuilder: (context) => new Text("Body 1"),
isExpandedInitially: true)
],
),
),
),
),
),
);
If I had to guess, you're missing the parts where you pass in expanded into each expansion header, and the part where you keep track of whether each expansion header is expanded or not.
I've done it a particular way here that assumes each title is unique; you could do something similar but rely on different properties. Or you could build everything in the initState method of your ExpansionListState equivalent.
This is a full working example of pretty much the exact UI you have in the picture in your post. You can simply download the flutter gallery from the play store to see the result. They did it a different way than I did (building everything in the initState method), and it's more complicated than what I did, but would be worth understanding as well.
Hope that helps =)
You can use ExpansionTile inside ListView like this
ListView(
shrinkWrap: true,
children: <Widget>[
ExpansionTile(
backgroundColor: Colors.amber,
leading: Icon(Icons.event),
title: Text('Test1'),
children: <Widget>[
ListTile(title: Text('Title of the item')),
ListTile(
title: Text('Title of the item2'),
)
],
),
ExpansionTile(
title: Text('Test2'),
children: <Widget>[
ListTile(title: Text('Title of the item')),
ListTile(
title: Text('Title of the item2'),
)
],
)
],
)
Related
I started learning Flutter trying to make my first app. I don't have a developper's background, so I'm trying to learn everything by doin' it.
My app is receiving some user's data from a json file (name, surname, country, level, ...) and show the whole list of user's name and by tapping on a name a second page opens where you get all the details.
What I'd like to do now is to add a "settings page", where the user can filter, using two dropboxes, the country and/or the level.
If none of the dropboxes are selected the first page should show the whole list of persons by every country and from every level (as it does now), otherwise the list should be filtered to show only the persons from the country selected and only for the level selected.
I just need a hint about what to look for and study in order to realize it. Is my actual approach for the app ok?
Thanks alot for any kind of help.
Diego
main.dart
import 'package:flutter/material.dart';
import 'dart:async';
import 'package:http/http.dart' as http;
import 'dart:convert';
//import pages
import './contactdetails.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'USDDN EU Judges',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: new MyHomePage(title: 'USDDN EU Judges'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
Future<List<User>> _getUser() async {
var data = await http.get(
"https://www.disconnecteddog.com/home/json/usddneujudgesdatabase.json");
var jsonData = json.decode(data.body);
List<User> users = [];
for (var u in jsonData) {
User user = User(
u["Index"],
u["Name"],
u["Country"],
u["Level"],
u["Inthesportsince"],
u["Active"],
u["English"],
u["Email"],
u["Picture"]);
users.add(user);
}
print(users.length);
return users;
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
actions: <Widget>[
//
IconButton(icon: new Icon(Icons.filter_list, color: Colors.white,), onPressed: null)
],
),
body: Container(
child: FutureBuilder(
future: _getUser(),
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (snapshot.data == null) {
return Container(
child: Center(child: Text("Loading judges database...")));
} else {
return ListView.builder(
itemCount: snapshot.data.length,
itemBuilder: (BuildContext context, int index) {
return ListTile(
leading: CircleAvatar(
backgroundImage:
NetworkImage(snapshot.data[index].picture),
),
title: Text(snapshot.data[index].name),
subtitle: Row(
children: <Widget>[
Text("Level: "),
Text(snapshot.data[index].level),
],
),
onTap: () {
Navigator.push(
context,
new MaterialPageRoute(
builder: (context) =>
DetailPage(snapshot.data[index])));
},
);
},
);
}
},
),
),
);
}
}
class User {
final int index;
final String name;
final String country;
final String level;
final String inthesportsince;
final String active;
final String english;
final String email;
final String picture;
User(this.index, this.name, this.country, this.level, this.inthesportsince,
this.active, this.english, this.email, this.picture);
}
Contactdetails.dart
import 'package:flutter/material.dart';
import 'package:usddn_judges/main.dart';
class DetailPage extends StatelessWidget {
final User user;
DetailPage(this.user);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(user.name),
),
body: Container(
//height: 120.0,
child: Padding(
padding: const EdgeInsets.fromLTRB(0.0, 10.0, 0.0, 5.0),
child: Card(
margin: EdgeInsets.all(10.0),
elevation: 2.0,
child: new Column(
children: <Widget>[
new ListTile(
leading: new Icon(
Icons.account_box,
color: Colors.blue,
size: 26.0,
),
title: new Text(
user.name,
style: new TextStyle(fontWeight: FontWeight.w400),
),
),
new Divider(color: Colors.blue),
new ListTile(
leading: new Icon(
Icons.map,
color: Colors.blue,
size: 26.0,
),
title: Row(
children: <Widget>[
new Text("Country: "),
new Text(
user.country,
style: new TextStyle(fontWeight: FontWeight.w400),
),
],
),
),
new Divider(color: Colors.blue),
new ListTile(
leading: new Icon(
Icons.multiline_chart,
color: Colors.blue,
size: 26.0,
),
title: Row(
children: <Widget>[
new Text("Level: "),
new Text(
user.level,
style: new TextStyle(fontWeight: FontWeight.w400),
),
],
),
),
new Divider(color: Colors.blue),
new ListTile(
leading: new Icon(
Icons.language,
color: Colors.blue,
size: 26.0,
),
title: Row(
children: <Widget>[
new Text("English: "),
new Text(
user.english,
style: new TextStyle(fontWeight: FontWeight.w400),
),
],
),
),
new Divider(color: Colors.blue),
new ListTile(
leading: new Icon(
Icons.flash_on,
color: Colors.blue,
size: 26.0,
),
title: Row(
children: <Widget>[
new Text("Active: "),
new Text(
user.active,
style: new TextStyle(fontWeight: FontWeight.w400),
),
],
),
),
new Divider(color: Colors.blue),
new ListTile(
leading: new Icon(
Icons.event,
color: Colors.blue,
size: 26.0,
),
title: Row(
children: <Widget>[
new Text("In the sport since: "),
new Text(
user.inthesportsince,
style: new TextStyle(fontWeight: FontWeight.w400),
),
],
),
),
],
),
),
),
),
);
}
}
Main Contact List
Details Page
I think you should look into List.where().
https://api.dartlang.org/stable/2.1.0/dart-core/Iterable/where.html
By this you can filter your users based on the values within the filter.
users.where((user) => user.country == selectedCountry);
This is just an example, null handling and a smarter where clause is probably necessary.
I hope this will help you getting started.
Create a new Screen for filters, lets name it as FilterScreen. Then, you can use any state management framework (provider, BloC etc.) to store the filters that user entered in the FilterScreen. After returning the search screen, if there is any filter entered requery the list.
I’m just trying to use ExpansionTile in Flutter, from example I modified to become like this:
I want to hide the arrow and use Switch to expand the tile, is it possible? Or do I need custom widget which render children programmatically? Basically, I just need to show/hide the children
Here’s my code:
import 'package:flutter/material.dart';
void main() {
runApp(ExpansionTileSample());
}
class ExpansionTileSample extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: const Text('ExpansionTile'),
),
body: ListView.builder(
itemBuilder: (BuildContext context, int index) =>
EntryItem(data[index]),
itemCount: data.length,
),
),
);
}
}
// One entry in the multilevel list displayed by this app.
class Entry {
Entry(this.title,[this.question='',this.children = const <Entry>[]]);
final String title;
final String question;
final List<Entry> children;
}
// The entire multilevel list displayed by this app.
final List<Entry> data = <Entry>[
Entry(
'Chapter A',
'',
<Entry>[
Entry(
'Section A0',
'',
<Entry>[
Entry('Item A0.1'),
Entry('Item A0.2'),
Entry('Item A0.3'),
],
),
Entry('Section A1','text'),
Entry('Section A2'),
],
),
Entry(
'Chapter B',
'',
<Entry>[
Entry('Section B0'),
Entry('Section B1'),
],
),
Entry(
'Chapter C',
'',
<Entry>[
Entry('Section C0'),
Entry('Section C1')
],
),
];
// Displays one Entry. If the entry has children then it's displayed
// with an ExpansionTile.
class EntryItem extends StatelessWidget {
const EntryItem(this.entry);
final Entry entry;
Widget _buildTiles(Entry root) {
if (root.children.isEmpty) return Container(
child:Padding(
padding: const EdgeInsets.symmetric(
vertical: 8.0,
horizontal: 32.0,
),
child:Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children:[
Text(root.title),
Divider(height: 10.0,),
root.question=='text'?Container(
width: 100.0,
child:TextField(
decoration: const InputDecoration(helperText: "question")
),
):Divider()
]
)
)
);
return ExpansionTile(
//key: PageStorageKey<Entry>(root),
title: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children:[
Text(root.title),
Switch(
value:false,
onChanged: (_){},
)
]
),
children: root.children.map(_buildTiles).toList(),
);
}
#override
Widget build(BuildContext context) {
return _buildTiles(entry);
}
}
#diegoveloper 's answer is almost ok,one small issue not covereed is: it doesn't propogate click on Switch further to ExpansionTile, so if you click outside switch it's expanding, while clicking on Switch does nothing. Wrap it with IgnorePointer, and on expansion events set the value for switch. It's a bit backwards logic, but works good.
...
return ExpansionTile(
onExpansionChanged: _onExpansionChanged,
// IgnorePointeer propogates touch down to tile
trailing: IgnorePointer(
child: Switch(
value: isExpanded,
onChanged: (_) {},
),
),
title: Row(mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [
Text(root.title),
]),
children: root.children.map((entry) => EntryItem(entry)).toList(),
);
...
I think this will help you
initiallyExpanded : true
itemBuilder: (context, index) {
return Column(
children: <Widget>[
Divider(
height: 17.0,
color: Colors.white,
),
ExpansionTile(
key: Key(index.toString()), //attention
initiallyExpanded : true,
leading: Icon(Icons.person, size: 50.0, color: Colors.black,),
title: Text('Faruk AYDIN ${index}',style: TextStyle(color: Color(0xFF09216B), fontSize: 17.0, fontWeight: FontWeight.bold)),
subtitle: Text('Software Engineer', style: TextStyle(color: Colors.black, fontSize: 13.0, fontWeight: FontWeight.bold),),
children: <Widget>[
Padding(padding: EdgeInsets.all(25.0),
child : Text('DETAİL ${index} \n' + 'It is a long established fact that a reader will be distracted by the readable content of a page when looking at its layout. The point of using Lorem Ipsum is that it has a more-or-less normal distribution of letters, as opposed to using "Content here, content here", making it look like readable English.',)
)
],
onExpansionChanged: ((newState){
if(newState)
setState(() {
Duration(seconds: 20000);
selected = index;
});
else setState(() {
selected = -1;
});
})
),
]
);
Yes, it's possible, I modified your code a little :
class EntryItem extends StatefulWidget {
const EntryItem(this.entry);
final Entry entry;
#override
EntryItemState createState() {
return new EntryItemState();
}
}
class EntryItemState extends State<EntryItem> {
var isExpanded = false;
_onExpansionChanged(bool val) {
setState(() {
isExpanded = val;
});
}
Widget _buildTiles(Entry root) {
if (root.children.isEmpty)
return Container(
child: Padding(
padding: const EdgeInsets.symmetric(
vertical: 8.0,
horizontal: 32.0,
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(root.title),
Divider(
height: 10.0,
),
root.question == 'text'
? Container(
width: 100.0,
child: TextField(
decoration: const InputDecoration(
helperText: "question")),
)
: Divider()
])));
return ExpansionTile(
onExpansionChanged: _onExpansionChanged,
trailing: Switch(
value: isExpanded,
onChanged: (_) {},
),
//key: PageStorageKey<Entry>(root),
title: Row(mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [
Text(root.title),
]),
children: root.children.map((entry) => EntryItem(entry)).toList(),
);
}
#override
Widget build(BuildContext context) {
return _buildTiles(widget.entry);
}
}
Basically I changed from Stateless to Stateful because you need to handle the state of your Switch widget.
There is a trailing property from ExpansionTile where I put the Switch to remove the arrow widget by default.
Listen the onExpansionChanged: _onExpansionChanged,, to change the status of the Switch.
And finally build the children as new widgets:
children: root.children.map((entry) => EntryItem(entry)).toList(),
initiallyExpanded = true , this answer is correct but if we have a TextFiled inside the ExpansionTile's children , then the keyboard automatically hides(bug).
So my solution is wrap the children with Visibility widget and control visibilty.
initially declare bool _expansionVisibility = false;
ExpansionTile(
onExpansionChanged: (changed) {
setState(() {
print("changed $changed");
if (changed) {
_expansionVisibility = true;
} else {
_expansionVisibility = false;
}
});
},
title: Text(
"Change Password",
),
children: <Widget>[
Visibility(
visible: _expansionVisibility,
child: Container(),
),
],
),
Short answer: Set initiallyExpanded true or false, accordingly with the help of onExpansionChanged. But remember initiallyExpanded applies only for initial state, so the key of the widget should be changed to apply changes. Now to change key a workaround is:
ExpansionTile(
key: PageStorageKey("${DateTime.now().millisecondsSinceEpoch}"),
initiallyExpanded: ....
onExpansionChanged: ....
.
.
.
)
I'm new to Flutter,
I want to destruct cards created initially and construct them again as per data provided in API call.
Basically when I tap on button in UI, it should call APIs and based on data from API call, if it is different from the data I already have, I want to destruct cards and construct them again.
How I can achieve this?
The cards will auto update their content when you make the call again, it is like refreshing your data.
I have made a simple example with a single card that shows data from this JSON Where I am calling the API first time in initState and then repeating the call each time I press on the FAB.
I am adding the index variable just to show you the updates (updating my single card with the next item in the list)
Also it is worth noting that I am handling the null or empty values poorly for the sake of time.
Also forget about the UI overflow ¯_(ツ)_/¯
class CardListExample extends StatefulWidget {
#override
_CardListExampleState createState() => new _CardListExampleState();
}
class _CardListExampleState extends State<CardListExample> {
Map cardList = {};
int index = 0;
#override
void initState() {
_getRequests();
super.initState();
}
_getRequests() async {
String url = "https://jsonplaceholder.typicode.com/users";
var httpClinet = createHttpClient();
var response = await httpClinet.get(
url,
);
var data = JSON.decode(response.body);
//print (data);
setState(() {
this.cardList = data[index];
this.index++;
});
print(cardList);
print(cardList["name"]);
}
#override
Widget build(BuildContext context) {
return new Scaffold(
floatingActionButton:
new FloatingActionButton(onPressed: () => _getRequests()),
appBar: new AppBar(
title: new Text("Card List Example"),
),
body: this.cardList != {}
? new ListView(children: <Widget>[
new Card(
child: new Column(
children: <Widget>[
new Row(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
new Text(
cardList["name"] ?? '',
style: Theme.of(context).textTheme.display1,
),
new Text(
this.cardList['email'] ?? '',
maxLines: 50,
),
],
),
new Text(cardList["website"] ?? '')
],
),
),
])
: new Center(child: new CircularProgressIndicator()),
);
}
}
Yes, Answer from Aziza works.
Though I used the code as below :
void main() =>
runApp(new MaterialApp(
onGenerateRoute: (RouteSettings settings) {
switch (settings.name) {
case '/about':
return new FromRightToLeft(
builder: (_) => new _aboutPage.About(),
settings: settings,
);
}
},
home : new HomePage(),
theme: new ThemeData(
fontFamily: 'Poppins',
primarySwatch: Colors.blue,
),
));
class HomePage extends StatefulWidget{
#override
HomePageState createState() => new HomePageState();
}
class HomePageState extends State<HomePage>{
List data;
Future<String> getData() async{
var response = await http.get(
Uri.encodeFull(<SOMEURL>),
headers: {
"Accept" : "application/json"
}
);
this.setState((){
data = JSON.decode(response.body);
});
return "Success";
}
#override
void initState() {
// TODO: implement initState
super.initState();
this.getData();
}
#override
Widget build(BuildContext context){
return new Scaffold(
appBar : new AppBar(
title : new Text("ABC API"),
actions: <Widget>[
new IconButton( // action button
icon: new Icon(Icons.cached),
onPressed: () => getData(),
)],
),
drawer: new Drawer(
child: new ListView(
children: <Widget> [
new Container(
height: 120.0,
child: new DrawerHeader(
padding: new EdgeInsets.all(0.0),
decoration: new BoxDecoration(
color: new Color(0xFFECEFF1),
),
child: new Center(
child: new FlutterLogo(
colors: Colors.blueGrey,
size: 54.0,
),
),
),
),
new ListTile(
leading: new Icon(Icons.chat),
title: new Text('Support'),
onTap: () {
Navigator.pop(context);
Navigator.of(context).pushNamed('/support');
}
),
new ListTile(
leading: new Icon(Icons.info),
title: new Text('About'),
onTap: () {
Navigator.pop(context);
Navigator.of(context).pushNamed('/about');
}
),
new Divider(),
new ListTile(
leading: new Icon(Icons.exit_to_app),
title: new Text('Sign Out'),
onTap: () {
Navigator.pop(context);
}
),
],
)
),
body: this.data != null ?
new ListView.builder(
itemCount: data.length,
itemBuilder: (BuildContext context, int index){
return new Container(
padding: new EdgeInsets.fromLTRB(8.0,5.0,8.0,0.0),
child: new Card(
child: new Padding(
padding: new EdgeInsets.fromLTRB(10.0,12.0,8.0,0.0),
child: new Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
new ListTile(
enabled: data[index]['active'] == '1' ? true : false,
title: new Text(data[index]['header'],
style:Theme.of(context).textTheme.headline,
),
subtitle: new Text("\n" + data[index]['description']),
),
new ButtonTheme.bar(
child: new ButtonBar(
children: <Widget>[
new FlatButton(
child: new Text(data[index]['action1']),
onPressed: data[index]['active'] == '1' ? _launchURL :null,
),
],
),
),
],
),
),
),
);
},
)
:new Center(child: new CircularProgressIndicator()),
);
}
}
_launchURL() async {
const url = 'http://archive.org';
if (await canLaunch(url)) {
await launch(url);
} else {
throw 'Could not launch $url';
}
}
class FromRightToLeft<T> extends MaterialPageRoute<T> {
FromRightToLeft({ WidgetBuilder builder, RouteSettings settings })
: super(builder: builder, settings: settings);
#override
Widget buildTransitions(
BuildContext context,
Animation<double> animation,
Animation<double> secondaryAnimation,
Widget child) {
if (settings.isInitialRoute)
return child;
return new SlideTransition(
child: new Container(
decoration: new BoxDecoration(
boxShadow: [
new BoxShadow(
color: Colors.black26,
blurRadius: 25.0,
)
]
),
child: child,
),
position: new Tween(
begin: const Offset(1.0, 0.0),
end: const Offset(0.0, 0.0),
)
.animate(
new CurvedAnimation(
parent: animation,
curve: Curves.fastOutSlowIn,
)
),
);
}
#override Duration get transitionDuration => const Duration(milliseconds: 400);
}
The above code includes Navigation drawer, page navigation animation and also answer to the above question.
class _DaftarMuridState extends State<DaftarMurid> {
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text(widget.title),
),
body: new Column(
children: <Widget>[
new Flexible(
child: new FirebaseAnimatedList(//new
query: db.reference().child("Murid"),
sort: (a, b) => a.key.compareTo(b.key),
padding: new EdgeInsets.all(8.0),
itemBuilder: (_, DataSnapshot dataSnapshot, Animation<double> animations,x){
return new DaftarMuridView(
snapshot: dataSnapshot,
animation: animations,
);//new
}
),
),
],
),
);
}
}
class DaftarMuridViewState extends State<DaftarMuridView>{
DaftarMuridViewState({this.snapshot, this.animation});
final DataSnapshot snapshot;
final Animation animation;
#override
Widget build(BuildContext context) {
var size = MediaQuery.of(context).size;
String fotoUrl = snapshot.value['Foto'];
String ig = snapshot.value['Instagram'];
hash.putIfAbsent(snapshot.value['Nama'], () => false);
bool expanded = hash[snapshot.value['Nama']];
var expansionPanel = new ExpansionPanelList(
expansionCallback: (int index, bool isExpanded) {
setState(() {
hash.remove(snapshot.value['Nama']);
hash.putIfAbsent(snapshot.value['Nama'], () => !isExpanded);
expanded = !expanded;
});
},
children: [new ExpansionPanel(headerBuilder: (BuildContext context, bool isExpanded) {
return new ListTile(
leading: const Icon(Icons.school),
title: new Text(
snapshot.value['Nama'],
textAlign: TextAlign.left,
style: new TextStyle(
fontSize: 20.0,
fontWeight: FontWeight.w400,
),
));
},
body: new ListView(
physics: const BouncingScrollPhysics(),
shrinkWrap: true,
padding: const EdgeInsets.all(8.0),
children: <Widget>[
new CachedNetworkImage(
imageUrl: fotoUrl == null?"https://drive.google.com/uc?export=download&id=1tkqO59S9jiWpkzHQNJRKLuCGYIn5kK_v":fotoUrl,
placeholder: new CircularProgressIndicator(),
errorWidget: new CachedNetworkImage(imageUrl: "https://drive.google.com/uc?export=download&id=1tkqO59S9jiWpkzHQNJRKLuCGYIn5kK_v"),
fadeOutDuration: new Duration(seconds: 1),
fadeInDuration: new Duration(seconds: 1),
height: size.height / 2.0,
width: size.width / 2.0,
alignment: Alignment.center,
),
new ListTile(
leading: const Icon(Icons.today),
title: const Text('Tanggal Lahir'),
subtitle: new Text(snapshot.value['Tanggal Lahir']),
),
new Row(
children: <Widget>[
ig != null ?
new FlatButton(
onPressed: () => _instagram(ig),
child: new CachedNetworkImage(imageUrl: "http://diylogodesigns.com/blog/wp-content/uploads/2016/05/Instagram-logo-png-icon.png", width: size.width / 4.0, height: size.height / 4.0, ),
)
: new Container(),
],
),
],
),
isExpanded: expanded)],
);
return new SizeTransition(
sizeFactor: new CurvedAnimation(
parent: animation, curve: Curves.easeOut),
axisAlignment: 0.0,
child: expansionPanel,
);
}
}
is my code not efficient? the process is Get Data from Firebase -> Store it to list view
it's a bit lag when open the activity, maybe because getting the data. But is there a solution for make it doesn't lag?
I cut some code that isn't important.
Use Futures to perform time consuming operations, so it will not freeze the UI.
https://www.dartlang.org/tutorials/language/futures
I have been trying to add listeners when using DefaultTabController. However, every time I add a TabController in order to get the current index in either TabBar and TabBarView, I lose sync between them.
This is my code below:
#override
Widget build(BuildContext context) {
return new DefaultTabController(
length: subPages.length,
child: new Scaffold(
appBar: appBar('homepage'),
body: new Center(
child: new NestedScrollView(
headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) {
return <Widget>[
new SliverAppBar(
backgroundColor: Colors.white,
title: new TabBar(
labelColor: Colors.black,
indicatorColor: Colors.black,
labelStyle: new TextStyle(fontWeight: FontWeight.bold),
tabs: subPages.map((String str) => new Tab(text: str)).toList(),
),
),
];
},
body: new TabBarView(
children: subPages.map((String str) {
return new ListView(
padding: const EdgeInsets.symmetric(vertical: 8.0, horizontal: 16.0),
children: subPages.map((String str) {
return new Padding(
padding: const EdgeInsets.symmetric(vertical: 8.0),
child: new Text(str),
);
}).toList(),
);
}).toList(),
),
),
),
floatingActionButton: new FloatingActionButton(
backgroundColor: Colors.black,
onPressed: null,
tooltip: 'Increment',
child: new Icon(Icons.add),
),
),
);
}
I use this:
new DefaultTabController(
child: Builder(
builder: (context) {
final tabController = DefaultTabController.of(context)!;
tabController.addListener(() {
print("New tab index: ${tabController.index}");
});
return Scaffold(
...
);
}
),
);
Define a tabController and a listener (once changing tab, it will be triggered twice)
class _ScreenState extends State<Screen> with SingleTickerProviderStateMixin {
late TabController tabController;
#override
void initState() {
super.initState();
this.tabController = TabController(length: 3, vsync: this);
this.tabController.addListener(() {
if (this.tabController.indexIsChanging) {
print(this.tabController.index);
print(this.tabController.previousIndex);
}
});
}
}
Pass it to the TabBar and TabBarView, like:
TabBar(
controller: this.tabController,
tabs: [
Tab(text: "0"),
Tab(text: "1"),
Tab(text: "2"),
],
)
TabBarView(controller: this.tabController, children: [...])