I'm trying to make timeline UI like below:
But i end up doing following:
I want to increase the height of vertical Separator when no of lines of my Description text increases. How should i do that ?
Here is the link for the code
I like Osama's answer too but here's my quick custom implementation. It uses a CustomPainter to draw the lines.
import 'package:flutter/material.dart';
class Timeline extends StatelessWidget {
const Timeline({
Key? key,
required this.children,
this.indicators,
this.isLeftAligned = true,
this.itemGap = 12.0,
this.gutterSpacing = 4.0,
this.padding = const EdgeInsets.all(8),
this.controller,
this.lineColor = Colors.grey,
this.physics,
this.shrinkWrap = true,
this.primary = false,
this.reverse = false,
this.indicatorSize = 30.0,
this.lineGap = 4.0,
this.indicatorColor = Colors.blue,
this.indicatorStyle = PaintingStyle.fill,
this.strokeCap = StrokeCap.butt,
this.strokeWidth = 2.0,
this.style = PaintingStyle.stroke,
}) : itemCount = children.length,
assert(itemGap >= 0),
assert(lineGap >= 0),
assert(indicators == null || children.length == indicators.length),
super(key: key);
final List<Widget> children;
final double itemGap;
final double gutterSpacing;
final List<Widget>? indicators;
final bool isLeftAligned;
final EdgeInsets padding;
final ScrollController? controller;
final int itemCount;
final ScrollPhysics? physics;
final bool shrinkWrap;
final bool primary;
final bool reverse;
final Color lineColor;
final double lineGap;
final double indicatorSize;
final Color indicatorColor;
final PaintingStyle indicatorStyle;
final StrokeCap strokeCap;
final double strokeWidth;
final PaintingStyle style;
#override
Widget build(BuildContext context) {
return ListView.separated(
padding: padding,
separatorBuilder: (_, __) => SizedBox(height: itemGap),
physics: physics,
shrinkWrap: shrinkWrap,
itemCount: itemCount,
controller: controller,
reverse: reverse,
primary: primary,
itemBuilder: (context, index) {
final child = children[index];
final _indicators = indicators;
Widget? indicator;
if (_indicators != null) {
indicator = _indicators[index];
}
final isFirst = index == 0;
final isLast = index == itemCount - 1;
final timelineTile = <Widget>[
CustomPaint(
foregroundPainter: _TimelinePainter(
hideDefaultIndicator: indicator != null,
lineColor: lineColor,
indicatorColor: indicatorColor,
indicatorSize: indicatorSize,
indicatorStyle: indicatorStyle,
isFirst: isFirst,
isLast: isLast,
lineGap: lineGap,
strokeCap: strokeCap,
strokeWidth: strokeWidth,
style: style,
itemGap: itemGap,
),
child: SizedBox(
height: double.infinity,
width: indicatorSize,
child: indicator,
),
),
SizedBox(width: gutterSpacing),
Expanded(child: child),
];
return IntrinsicHeight(
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
children:
isLeftAligned ? timelineTile : timelineTile.reversed.toList(),
),
);
},
);
}
}
class _TimelinePainter extends CustomPainter {
_TimelinePainter({
required this.hideDefaultIndicator,
required this.indicatorColor,
required this.indicatorStyle,
required this.indicatorSize,
required this.lineGap,
required this.strokeCap,
required this.strokeWidth,
required this.style,
required this.lineColor,
required this.isFirst,
required this.isLast,
required this.itemGap,
}) : linePaint = Paint()
..color = lineColor
..strokeCap = strokeCap
..strokeWidth = strokeWidth
..style = style,
circlePaint = Paint()
..color = indicatorColor
..style = indicatorStyle;
final bool hideDefaultIndicator;
final Color indicatorColor;
final PaintingStyle indicatorStyle;
final double indicatorSize;
final double lineGap;
final StrokeCap strokeCap;
final double strokeWidth;
final PaintingStyle style;
final Color lineColor;
final Paint linePaint;
final Paint circlePaint;
final bool isFirst;
final bool isLast;
final double itemGap;
#override
void paint(Canvas canvas, Size size) {
final indicatorRadius = indicatorSize / 2;
final halfItemGap = itemGap / 2;
final indicatorMargin = indicatorRadius + lineGap;
final top = size.topLeft(Offset(indicatorRadius, 0.0 - halfItemGap));
final centerTop = size.centerLeft(
Offset(indicatorRadius, -indicatorMargin),
);
final bottom = size.bottomLeft(Offset(indicatorRadius, 0.0 + halfItemGap));
final centerBottom = size.centerLeft(
Offset(indicatorRadius, indicatorMargin),
);
if (!isFirst) canvas.drawLine(top, centerTop, linePaint);
if (!isLast) canvas.drawLine(centerBottom, bottom, linePaint);
if (!hideDefaultIndicator) {
final Offset offsetCenter = size.centerLeft(Offset(indicatorRadius, 0));
canvas.drawCircle(offsetCenter, indicatorRadius, circlePaint);
}
}
#override
bool shouldRepaint(CustomPainter oldDelegate) {
return false;
}
}
You'd call it something like:
Timeline(
children: <Widget>[
Container(height: 100, color: color),
Container(height: 50, color: color),
Container(height: 200, color: color),
Container(height: 100, color: color),
],
indicators: <Widget>[
Icon(Icons.access_alarm),
Icon(Icons.backup),
Icon(Icons.accessibility_new),
Icon(Icons.access_alarm),
],
),
new ListView.builder(
itemBuilder: (BuildContext context, int index) {
return new Stack(
children: <Widget>[
new Padding(
padding: const EdgeInsets.only(left: 50.0),
child: new Card(
margin: new EdgeInsets.all(20.0),
child: new Container(
width: double.infinity,
height: 200.0,
color: Colors.green,
),
),
),
new Positioned(
top: 0.0,
bottom: 0.0,
left: 35.0,
child: new Container(
height: double.infinity,
width: 1.0,
color: Colors.blue,
),
),
new Positioned(
top: 100.0,
left: 15.0,
child: new Container(
height: 40.0,
width: 40.0,
decoration: new BoxDecoration(
shape: BoxShape.circle,
color: Colors.white,
),
child: new Container(
margin: new EdgeInsets.all(5.0),
height: 30.0,
width: 30.0,
decoration: new BoxDecoration(
shape: BoxShape.circle,
color: Colors.red),
),
),
)
],
);
},
itemCount: 5,
)
the output will be like this image
For those who are scrolling here to find an easy way to implement timelines, now you can do this easily with timeline_tile.
Check out this specific delivery layout:
Or this weather timeline:
Also, the beautiful_timelines repository contains some examples built with this package.
Web demo
class MyTimeLine extends StatefulWidget {
#override
_TimeLineState createState() => _TimeLineState();
}
class _TimeLineState extends State<MyTimeLine> {
#override
Widget build(BuildContext context) {
return new Padding(
padding: new EdgeInsets.symmetric(horizontal: 10.0),
child: new Column(
children: <Widget>[
IntrinsicHeight(
child: new Row(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
Wrap(
direction: Axis.vertical,
children: <Widget>[
new Container(
width: 30.0,
child: new Center(
child: new Stack(
children: <Widget>[
new Padding(
padding: new EdgeInsets.only(left: 12.0),
child: new Container(
margin:
new EdgeInsets.symmetric(vertical: 4.0),
height: double.infinity,
width: 1.0,
color: Colors.deepOrange),
),
new Container(
padding: new EdgeInsets.only(),
child: new Icon(Icons.star, color: Colors.white),
decoration: new BoxDecoration(
color: new Color(0xff00c6ff),
shape: BoxShape.circle),
)
],
),
),
),
],
),
new Expanded(
child: new Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
new Padding(
padding: new EdgeInsets.only(left: 20.0, top: 5.0),
child: new Text(
'Header Text',
style: new TextStyle(
fontWeight: FontWeight.w500,
color: Colors.deepOrange,
fontSize: 16.0),
),
),
new Padding(
padding: new EdgeInsets.only(left: 20.0, top: 5.0),
child: new Text(
'Lorem ipsum description here description here Lorem ipsum description here description here Lorem ipsum description here Lorem ipsum description here description here Lorem ipsum description here description here Lorem ipsum description here description here Lorem ipsum description here description here Lorem ipsum description here description here Lorem ipsum description here description here Lorem ipsum description here description here Lorem ipsum description here description here Lorem ipsum description here description here Lorem ipsum description here description here '),
)
],
),
)
],
),
)
],
),
);
}
}
Check out this pub.
https://pub.dev/packages/timeline_widget
It helps create beautiful timeline ui with everything customised. Also available in left right and center aligned options. This is an example of how to use it!
TimelineView(
align: TimelineAlign.rightAlign,
lineWidth: 4,
lineColor: Colors.deepOrange,
imageBorderColor: Colors.deepOrange,
image: [
Container(
padding: EdgeInsets.all(15),
child: Image.asset("assets/pre-breakfast-image.png")),
Container(
padding: EdgeInsets.all(15),
child: Image.asset("assets/breakfast-image.png")),
Container(
padding: EdgeInsets.all(15),
child: Image.asset("assets/pre-lunch-image.png")),
Container(
padding: EdgeInsets.all(15),
child: Image.asset("assets/lunch-image.png")),
Container(
padding: EdgeInsets.all(15),
child: Image.asset("assets/evening-snack-image.png")),
Container(
padding: EdgeInsets.all(20),
child: Image.asset("assets/dinner-image.png")),
],
height: 150,
width: MediaQuery.of(context).size.width,
imageHeight: 50,
children: [
Container(
margin: EdgeInsets.fromLTRB(20, 0, 0, 0),
child: _widgetWeather(20, 71, 31)),
Container(
margin: EdgeInsets.fromLTRB(20, 0, 0, 0),
child: _widgetWeather(15, 75, 55)),
Container(
margin: EdgeInsets.fromLTRB(20, 0, 0, 0),
child: _widgetWeather(25, 73, 30)),
Container(
margin: EdgeInsets.fromLTRB(20, 0, 0, 0),
child: _widgetWeather(22, 65, 35)),
Container(
margin: EdgeInsets.fromLTRB(20, 0, 0, 0),
child: _widgetWeather(21, 55, 32)),
Container(
margin: EdgeInsets.fromLTRB(20, 0, 0, 0),
child: _widgetWeather(20, 65, 35)),
],
),
you can use flutter_timeline, both body and indicator, lines are customizable.
https://github.com/softmarshmallow/flutter-timeline
this is the latest, supported package out there for flutter.
Related
I have a flutter app that is complex enough and i have been developing it for a while.
the problem is after finishing up with the design code , i started implementing API's to receive actual data and populate the app with it. the app is now crashing quite a lot ( although a little less in release mode).
I am thinking it might be because the android device runs out of memory because of all the complex widgets I display in the app.
I have build all of my widgets Stateful I don't know if that could be a related factor (I am a beginner with flutter programming).
Please I have spent a lot of time in developing this app and it's my first enterprise level flutter app.
I haven't had experience with flutter before and I knew this kind of problems would arise and have no solution I would have gone with Java and Swift to develop the app.
Any help is appreciated guys. Thanks.
the app only displays about a 30-40 images, I don't know where all the data coming from.
Here is the code :
import 'package:flutter/material.dart';
import 'package:bestiee/componenets/constants.dart';
import 'package:bestiee/componenets/cards/single-item-hot-screen-card.dart';
import 'package:bestiee/componenets/buttons/small-round-more-button.dart';
import 'package:bestiee/componenets/cards/single-place-hot-screen-card.dart';
import 'package:bestiee/componenets/cards/single-person-hot-screen-card.dart';
import 'package:bestiee/componenets/cards/single-place-large-hot-screen-card.dart';
import 'package:bestiee/screens/hotScreenSingleScreens/more-hot-items-screen.dart';
import 'package:bestiee/screens/hotScreenSingleScreens/more-hot-places-screen.dart';
import 'package:bestiee/screens/hotScreenSingleScreens/more-hot-people-screen.dart';
import 'package:bestiee/translations.dart';
import 'package:bestiee/models/models.dart';
import 'package:bestiee/networking.dart';
import 'package:bestiee/constants.dart';
import 'dart:convert';
import 'package:http/http.dart';
class HotScreen extends StatefulWidget {
static List<Item> items = [];
static List<Place> places = [];
static List<Person> people = [];
static bool isFirstRun = true;
#override
_HotScreenState createState() => _HotScreenState();
}
class _HotScreenState extends State<HotScreen> {
String getTranslation(String text) {
try {
String translation = Translations.of(context).text(text);
return translation;
} catch (ex) {
print(ex);
return '';
}
}
getAllPlaces() async {
HotScreen.places.removeRange(0, HotScreen.places.length);
Response response = await getRequest(devBaseURL + plainPlaceAPI);
var allPlacesMap = jsonDecode(response.body);
print(allPlacesMap.length);
for (int i = 0; i < allPlacesMap.length; i++) {
var json = allPlacesMap[i];
Place place = Place.fromJson(json);
setState(() {
HotScreen.places.add(place);
});
}
}
getAllItems() async {
HotScreen.items.removeRange(0, HotScreen.items.length);
Response response = await getRequest(devBaseURL + plainItemAPI);
var allItemsMap = jsonDecode(response.body);
for (int i = 0; i < allItemsMap.length; i++) {
var json = allItemsMap[i];
Item item = Item.fromJson(json);
setState(() {
HotScreen.items.add(item);
});
}
}
getAllPeople() async {
HotScreen.people.removeRange(0, HotScreen.people.length);
Response response = await getRequest(devBaseURL + plainPersonAPI);
var allPeopleMap = jsonDecode(response.body);
for (int i = 0; i < allPeopleMap.length; i++) {
var json = allPeopleMap[i];
Person person = Person.fromJson(json);
setState(() {
HotScreen.people.add(person);
});
}
}
#override
void initState() {
super.initState();
if(HotScreen.isFirstRun == true){
getAllItems();
getAllPlaces();
getAllPeople();
HotScreen.isFirstRun = false;
}
}
#override
Widget build(BuildContext context) {
double screenWidth = MediaQuery.of(context).size.width;
return Scaffold(
body: Container(
decoration: kPageMainBackgroundColorBoxDecoration,
child: SafeArea(
child: Padding(
padding: const EdgeInsets.all(8.0),
//main screen scrollable widgets
child: ListView(
shrinkWrap: true,
children: <Widget>[
Text(
getTranslation('Bestiee'),
style: kBazarGalleryTitleStyle,
),
SizedBox(
height: 20,
),
Padding(
padding: const EdgeInsets.only(bottom: 10),
child: Text('Featured Items', style: kFeatureTitleTextStyle),
),
Container(
height: 700 / 3.5,
child: ListView.builder(
scrollDirection: Axis.horizontal,
shrinkWrap: true,
itemCount: HotScreen.items.length,
itemBuilder: (contet, int index) {
return SingleItemCard(
item: HotScreen.items[index],
isPersonItem: false,
moduleName: HotScreen.items[index].moduleName,
);
}),
),
SizedBox(
height: 10,
),
//more button
Align(
alignment: Alignment.topLeft,
child: Container(
width: 80,
height: 30,
child: SmallRoundMoreButton(onPressed: () {
Navigator.pushNamed(context, MoreHotItemsScreen.id);
}),
),
),
SizedBox(
height: 20,
),
//second hand section
Padding(
padding: const EdgeInsets.only(bottom: 10),
child: Text('Second Hand', style: kFeatureTitleTextStyle),
),
// Container(
// height: 700 / 3.5,
// child: ListView.builder(
// scrollDirection: Axis.horizontal,
// shrinkWrap: true,
// itemCount: 9,
// itemBuilder: (contet, int index) {
// return SingleItemCard();
// }),
// ),
SizedBox(
height: 10,
),
Align(
alignment: Alignment.topLeft,
child: Container(
width: 80,
height: 30,
child: SmallRoundMoreButton(
onPressed: () {
Navigator.pushNamed(context, MoreHotItemsScreen.id);
},
),
),
),
SizedBox(
height: 30,
),
//places section
Text(
'Featured Plces',
style: kFeatureTitleTextStyle,
),
Container(
height: 140,
child: ListView.builder(
scrollDirection: Axis.horizontal,
shrinkWrap: true,
itemCount: HotScreen.places.length,
itemBuilder: (context, int index) {
return Container(
width: 190,
height: 100,
child: SinglePlaceCard(
place: HotScreen.places[index],
));
},
),
),
//
Align(
alignment: Alignment.topLeft,
child: Container(
width: 80,
height: 30,
child: SmallRoundMoreButton(
onPressed: () {
Navigator.pushNamed(context, MoreHotPlacesScreen.id);
},
),
),
),
SizedBox(
height: 30,
),
//people section
Padding(
padding: const EdgeInsets.only(bottom: 10.0),
child: Text(
'People',
style: kFeatureTitleTextStyle,
),
),
Container(
height: 120,
child: ListView.builder(
scrollDirection: Axis.horizontal,
shrinkWrap: true,
itemCount: HotScreen.people.length,
itemBuilder: (context, int index) {
return Align(
alignment: Alignment.topLeft,
child: Padding(
padding: const EdgeInsets.only(right: 8.0),
child: SinglePersonHotScreenCard(
person: HotScreen.people[index],
),
),
);
},
),
),
Align(
alignment: Alignment.topLeft,
child: Container(
width: 80,
height: 30,
child: SmallRoundMoreButton(
onPressed: () {
Navigator.pushNamed(context, MoreHotPeopleScreen.id);
},
),
),
),
SizedBox(
height: 60,
),
//single shops section
Container(
height: 100.0 * 3,
child: Column(
children: List.generate(5, (index) {
return Align(
alignment: Alignment.topLeft,
child: Padding(
padding: const EdgeInsets.only(right: 8.0),
child: SinglePlaceLargeHotCard(place: HotScreen.places[index],)
),
);
}),
)
),
],
),
),
),
),
);
}
}
code for SingleItemCard :
class SingleItemCard extends StatelessWidget {
SingleItemCard({
this.cardHeight = 300,
this.cardWidth = 200,
this.color = kAppOrangeColor,
this.isExtraInfoShowen = true,
this.item,
this.isPersonItem,
this.moduleName});
final double cardHeight;
final double cardWidth;
final Color color;
final bool isExtraInfoShowen;
final Item item;
final bool isPersonItem;
final String moduleName;
#override
Widget build(BuildContext context) {
String imageURL ;
//remove the null part in production
if(item.moduleName == 'item' || item.moduleName == null ){
imageURL = imageBaseURLPlaces + item.imageVariants[0].imageURL;
}else if (item.moduleName == 'person' ){
imageURL = imageBaseURLPeople + item.imageVariants[0].imageURL;
print(imageURL);
}
int ratingUserCount = item.ratingUserCount;
return FittedBox(
child: Padding(
padding: const EdgeInsets.only(right: 7),
child: GestureDetector(
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => SingleItemScreen(
item: item,
),
),
);
},
child: Stack(
alignment: Alignment.bottomRight,
children: <Widget>[
//upper item description
Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Visibility(
visible: isExtraInfoShowen,
child: CircleRater(
isRatingDisabled: true,
rating: item.rating,
ratingUserCount: item.ratingUserCount != null ? ratingUserCount : 0,
),
),
SizedBox(
width: 150,
child: Text(
// 'Item Name Here no. 1',
item.name,
overflow: TextOverflow.clip,
style: TextStyle(
fontSize: 15,
color: Colors.black,
fontWeight: FontWeight.w500),
softWrap: false,
),
),
//item card
Container(
height: cardHeight,
width: cardWidth,
child: Container(
width: 100,
decoration: BoxDecoration(
color: color,
borderRadius: BorderRadius.all(Radius.circular(10)),
),
child: ClipRRect(
borderRadius: BorderRadius.all(Radius.circular(10)),
// child: Image.asset(
// 'images/item-image1.jpeg',
// width: 180,
// fit: BoxFit.fill,
// ),
child: CachedNetworkImage(imageUrl: imageURL, fit: BoxFit.fill,)
),
),
),
],
),
//lower price widget
Visibility(
visible: isExtraInfoShowen,
child: Align(
alignment: Alignment.bottomLeft,
child: Container(
width: 100,
height: 20,
decoration: BoxDecoration(
borderRadius: BorderRadius.only(
bottomRight: Radius.circular(10),
topLeft: Radius.circular(5)),
color: color),
child: Padding(
padding: const EdgeInsets.symmetric(
vertical: 3, horizontal: 4),
child: Align(
alignment: Alignment.bottomRight,
child: Text(
// '1000000 IQD',
item.priceIQD.toString() + ' IQD',
style: TextStyle(
fontSize: 12,
color: Colors.white,
),
textAlign: TextAlign.start,
),
),
),
),
),
)
],
),
),
),
);
}
}
SinglePlaceCard :
class SinglePlaceCard extends StatelessWidget {
SinglePlaceCard({this.place});
Place place;
#override
Widget build(BuildContext context) {
double screenWidth = MediaQuery.of(context).size.width;
double widgetWidth = screenWidth / 2.3;
int ratingUserCount = place.ratingUserCount;
String imageURL = place.coverImages != null
? imageBaseURLPlaces +
place.coverImages[0].imageURL
: '';
return GestureDetector(
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => SinglePlaceScreen(place: place, isScreenCalledFromOwnerSelfRegistrationScreen: false,),
),
);
},
child: FittedBox(
child: Padding(
padding: const EdgeInsets.only(right: 10),
child: Container(
child: Stack(
alignment: Alignment.bottomRight,
children: <Widget>[
Column(
//upper place name and rating
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
CircleRater(
isRatingDisabled: true,
rating: place.rating,
ratingUserCount: place.ratingUserCount != null ? ratingUserCount : 0,
),
SizedBox(
width: widgetWidth,
child: Text(
place.name,
overflow: TextOverflow.clip,
style: TextStyle(
fontSize: 12,
color: Colors.black,
fontWeight: FontWeight.w500),
softWrap: false,
),
),
//place card image
Container(
height: 80,
width: 170,
child: Container(
width: 150,
decoration: BoxDecoration(
color: kAppMainDarkGreenColor,
borderRadius: BorderRadius.all(Radius.circular(10)),
),
child: ClipRRect(
borderRadius: BorderRadius.all(Radius.circular(10)),
child: place.coverImages == null
? Image.asset(
'images/blank-placeholder-images/blank-image-placeholder.png',
width: screenWidth / 4,
fit: BoxFit.fill,
)
: CachedNetworkImage(
imageUrl: imageURL,
fit: BoxFit.fill,
),
),
),
),
],
),
//lower tag
Align(
alignment: Alignment.bottomLeft,
child: Container(
width: 100,
height: 20,
decoration: BoxDecoration(
borderRadius: BorderRadius.only(
topLeft: Radius.circular(3),
bottomRight: Radius.circular(8)),
color: Colors.black54),
child: Padding(
padding: const EdgeInsets.symmetric(
vertical: 3, horizontal: 4),
child: Align(
alignment: Alignment.bottomRight,
child: FittedBox(
child: Row(
mainAxisAlignment: MainAxisAlignment.end,
children: <Widget>[
EyeIconWithText(
size: 13,
),
Padding(
padding: const EdgeInsets.only(right: 5),
child: Container(
width: 10,
height: 10,
decoration: BoxDecoration(
borderRadius: BorderRadius.all(
Radius.circular(8),
),
color: place.isOnline != null &&
place.isOnline == true
? Colors.green
: Colors.red),
),
),
Text(
place.isOpen != null
? place.isOpen ? 'Open Now' : 'Colosed Now'
: 'Closed Now',
style: TextStyle(
fontSize: 12,
color: Colors.white,
),
textAlign: TextAlign.start,
),
],
),
),
),
),
),
)
],
),
),
),
),
);
}
}
SinglePersonHotScreenCard :
class SinglePersonHotScreenCard extends StatelessWidget {
SinglePersonHotScreenCard(
{ this.isExtraInfoShowen = false, this.person});
final bool isExtraInfoShowen;
final Person person;
#override
Widget build(BuildContext context) {
double screenWidth = MediaQuery.of(context).size.width;
final ratingPersonCount = person.ratingUserCount;
return Padding(
padding: const EdgeInsets.only(bottom: 10),
child: GestureDetector(
onTap: () {
Navigator.pushNamed(context, SinglePersonScreen.id);
},
child: FittedBox(
child: Column(
children: <Widget>[
//upper label and rating
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
CircleRater(
isRatingDisabled: true,
rating: person.rating,
ratingUserCount: person.ratingUserCount != null ? ratingPersonCount : 0,
),
Text(
person.name,
style: TextStyle(color: Colors.black),
),
],
),
Stack(
alignment: Alignment.center,
children: <Widget>[
Container(
decoration: BoxDecoration(
color: Colors.black54,
borderRadius: BorderRadius.all(Radius.circular(1000)),
),
width: screenWidth / 3.6,
height: screenWidth / 3.6,
),
Visibility(
visible: person != null,
child: CircleAvatar(
radius: 50,
backgroundImage: NetworkImage(imageBaseURLPeople + person.profileImageURL,)
),
),
Visibility(
visible: person == null,
child: ClipOval(
child: SizedBox(
width: screenWidth / 4,
child:
Image.asset('images/blank-placeholder-images/person-placeholder.jpg'),
),
),
),
],
),
Text(
person.jobTitle,
style: TextStyle(color: Colors.black),
),
Padding(
padding: const EdgeInsets.only(top: 2.0),
child: Container(
width: 15,
height: 15,
decoration: kOnlineOfflineIndicatorBoxDecoration,
),
),
Padding(
padding: const EdgeInsets.all(4.0),
child: Visibility(
visible: this.isExtraInfoShowen,
child: ThumbsUpWithLabel(),
),
)
],
),
),
),
);
}
}
I have ran the devtool, the app uses too much m memory, I am not sure why.
I changed the code there is a lot less crashes. I use this code
getAllPlaces() async {
List<Place> places = widget.places;
places.removeRange(0, places.length);
List<Place> _places = [];
Response response = await getRequest(devBaseURL + plainPlaceAPI);
var allPlacesMap = jsonDecode(response.body);
for (int i = 0; i < allPlacesMap.length; i++) {
var json = allPlacesMap[i];
Place place = Place.fromJson(json);
_places.add(place);
}
setState(() {
places.addAll(_places );
print('all added');
});
}
The app crash seems to be caused by Out Of Memory issue. Checking on the snippets you've provided, memory issues were caused by nested ListViews rendering Widgets that displays images. While the widgets in ListViews are only rendered when near the viewport, multiple ListViews still adds up to the total memory consumed. I suggest trimming down the images displayed and nest ListViews sparingly to prevent these issues.
I don't have much experience in flutter yet, and I curious of how can I achieved custom AppBar that can be animated.
I just want to apply a simple animation to the AppBar which will only change the height of the AppBar. As I understand that the AppBar must be a PreferredSizeWidget and I want to animate it to change the height, there are couple articles that I read but mostly it uses SilverAppBar.
Thanks.
class CustomAppBarRounded extends StatelessWidget implements PreferredSizeWidget{
final String _appBarTitle;
CustomAppBarRounded(this._appBarTitle);
#override
Widget build(BuildContext context) {
return new Container(
child: new LayoutBuilder(builder: (context, constraint) {
final width = constraint.maxWidth * 8;
return new ClipRect(
child: Stack(
children: <Widget>[
new OverflowBox(
maxHeight: double.infinity,
maxWidth: double.infinity,
child: new SizedBox(
width: width,
height: width,
child: new Padding(
padding: new EdgeInsets.only(
bottom: width / 2 - preferredSize.height / 3
),
child: new DecoratedBox(
decoration: new BoxDecoration(
color: Colors.indigo,
shape: BoxShape.circle,
boxShadow: [
new BoxShadow(color: Colors.black54, blurRadius: 10.0)
],
),
),
),
),
),
new Center(
child: new Text("${this._appBarTitle}",
style: TextStyle(
fontSize: 24,
fontWeight: FontWeight.bold,
color: Colors.white,
shadows: [
Shadow(color: Colors.black54, blurRadius: 10.0)
]
),
)
),
],
)
);
}),
);
}
#override
Size get preferredSize => const Size.fromHeight(100.0);
}
I've figured out how to achieve what I wanted. So I returned the PreferredSizeWidget
class CustomRoundedAppBar extends StatelessWidget{
double height = 100;
final String title;
CustomRoundedAppBar(
this.height,
this.title);
#override
Widget build(BuildContext context) {
return PreferredSize(
preferredSize: Size(this.height, this.height),
child: AnimatedContainer(
duration: Duration(seconds: 1),
height: this.height,
child: new LayoutBuilder(builder: (context, constraint){
final width =constraint.maxWidth * 8;
return new ClipRect(
child: Stack(
children: <Widget>[
new OverflowBox(
maxHeight: double.infinity,
maxWidth: double.infinity,
child: new SizedBox(
width: width,
height: width,
child: new Padding(
padding: new EdgeInsets.only(
bottom: width / 2 - this.height / 3
),
child: new DecoratedBox(
decoration: new BoxDecoration(
color: Colors.indigo,
shape: BoxShape.circle,
boxShadow: [
new BoxShadow(color: Colors.black54, blurRadius: 10.0)
],
),
),
),
),
),
new Center(
child: new Text("${this.title}",
style: TextStyle(
fontSize: 24,
fontWeight: FontWeight.bold,
color: Colors.white,
shadows: [
Shadow(color: Colors.black54, blurRadius: 10.0)
]
),
)
),
],
)
);
})
),
);
}
}
And on the Scaffold I have an action when button is pressed it will change the height, which must be on the setState()
onPressed: (){
setState(() {
this.height = 200;
this. _appBarTitle = "TEST";
});
},
I want to create a popup menu when clicking on a button from the appbar .. i want something like this to appear:
is there a way to do this in flutter? a package or something?
I tried, but I've faced some problems with showing subwidget exactly this way. So, here two solutions:
class TestScreen extends StatefulWidget {
#override
State<StatefulWidget> createState() => _TestScreenState();
}
class _TestScreenState extends State<TestScreen> with SingleTickerProviderStateMixin {
AnimationController animationController;
bool _menuShown = false;
#override
void initState() {
animationController = AnimationController(vsync: this, duration: Duration(milliseconds: 500));
super.initState();
}
#override
Widget build(BuildContext context) {
Animation opacityAnimation = Tween(begin: 0.0, end: 1.0).animate(animationController);
if (_menuShown)
animationController.forward();
else
animationController.reverse();
return Scaffold(
appBar: AppBar(
actions: <Widget>[IconButton(icon: Icon(Icons.menu), onPressed: (){
setState(() {
_menuShown = !_menuShown;
});
})],
),
body: Stack(
overflow: Overflow.visible,
children: <Widget>[
Positioned(
child: FadeTransition(
opacity: opacityAnimation,
child: _ShapedWidget(),
),
right: 4.0,
top: 16.0,
),
],
),
);
}
}
class _ShapedWidget extends StatelessWidget {
_ShapedWidget();
final double padding = 4.0;
#override
Widget build(BuildContext context) {
return Center(
child: Material(
clipBehavior: Clip.antiAlias,
shape:
_ShapedWidgetBorder(borderRadius: BorderRadius.all(Radius.circular(padding)), padding: padding),
elevation: 4.0,
child: Container(
padding: EdgeInsets.all(padding).copyWith(bottom: padding * 2),
child: SizedBox(width: 150.0, height: 250.0, child: Center(child: Text('ShapedWidget'),),),
)),
);
}
}
class _ShapedWidgetBorder extends RoundedRectangleBorder {
_ShapedWidgetBorder({
#required this.padding,
side = BorderSide.none,
borderRadius = BorderRadius.zero,
}) : super(side: side, borderRadius: borderRadius);
final double padding;
#override
Path getOuterPath(Rect rect, {TextDirection textDirection}) {
return Path()
..moveTo(rect.width - 8.0 , rect.top)
..lineTo(rect.width - 20.0, rect.top - 16.0)
..lineTo(rect.width - 32.0, rect.top)
..addRRect(borderRadius
.resolve(textDirection)
.toRRect(Rect.fromLTWH(rect.left, rect.top, rect.width, rect.height - padding)));
}
}
In this case subwidget is below appbar
class TestScreen extends StatefulWidget {
#override
State<StatefulWidget> createState() => _TestScreenState();
}
class _TestScreenState extends State<TestScreen> with SingleTickerProviderStateMixin {
AnimationController animationController;
bool _menuShown = false;
#override
void initState() {
animationController = AnimationController(vsync: this, duration: Duration(milliseconds: 500));
super.initState();
}
#override
Widget build(BuildContext context) {
Animation opacityAnimation = Tween(begin: 0.0, end: 1.0).animate(animationController);
if (_menuShown)
animationController.forward();
else
animationController.reverse();
return Scaffold(
appBar: AppBar(
elevation: 0.0,
actions: <Widget>[Stack(
overflow: Overflow.visible,
children: <Widget>[IconButton(icon: Icon(Icons.menu), onPressed: (){
setState(() {
_menuShown = !_menuShown;
});
}),
Positioned(
child: FadeTransition(
opacity: opacityAnimation,
child: _ShapedWidget(onlyTop: true,),
),
right: 4.0,
top: 48.0,
),
],)],
),
body: Stack(
overflow: Overflow.visible,
children: <Widget>[
Positioned(
child: FadeTransition(
opacity: opacityAnimation,
child: _ShapedWidget(),
),
right: 4.0,
top: -4.0,
),
],
),
);
}
}
class _ShapedWidget extends StatelessWidget {
_ShapedWidget({this.onlyTop = false});
final double padding = 4.0;
final bool onlyTop;
#override
Widget build(BuildContext context) {
return Center(
child: Material(
clipBehavior: Clip.antiAlias,
shape:
_ShapedWidgetBorder(borderRadius: BorderRadius.all(Radius.circular(padding)), padding: padding),
elevation: 4.0,
child: Container(
padding: EdgeInsets.all(padding).copyWith(bottom: padding * 2),
child: onlyTop ? SizedBox(width: 150.0, height: 20.0,) : SizedBox(width: 150.0, height: 250.0, child: Center(child: Text('ShapedWidget'),),),
)),
);
}
}
class _ShapedWidgetBorder extends RoundedRectangleBorder {
_ShapedWidgetBorder({
#required this.padding,
side = BorderSide.none,
borderRadius = BorderRadius.zero,
}) : super(side: side, borderRadius: borderRadius);
final double padding;
#override
Path getOuterPath(Rect rect, {TextDirection textDirection}) {
return Path()
..moveTo(rect.width - 8.0 , rect.top)
..lineTo(rect.width - 20.0, rect.top - 16.0)
..lineTo(rect.width - 32.0, rect.top)
..addRRect(borderRadius
.resolve(textDirection)
.toRRect(Rect.fromLTWH(rect.left, rect.top, rect.width, rect.height - padding)));
}
}
In this case top of subwidget is on appbar, but appbar has to have 0.0 elevation
Actually, both of these solutions are not complete in my opinion, but it can help you to find what you need
It might be too late for an answer. But this can be simply achieved by using OverlayEntry widget. We create a widget of that shape and pass it to OverlayEntry widget and then use Overlay.of(context).insert(overlayEntry) to show the overlay and overlayEntry.remove method to remove it.
Here is a medium link to create a Custom DropDown Menu
Hope this helps!
There is a package called flutter_portal which works like Overlay/OverlayEntry but in a declarative way. You can use it for implementing custom tooltips, context menus, or dialogs.
CustomPopupMenu(
pressType: PressType.singleClick,
controller: menu,
arrowColor: AppColor.white,
menuBuilder: () => ClipRect(
clipBehavior: Clip.hardEdge,
child: Container(
height: MediaQuery.of(context).size.height *
ComponentSize.container1height,
width: MediaQuery.of(context).size.width *
ComponentSize.conatiner1width,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(
ComponentSize.borderradius),
color: AppColor.white,
),
child: ListView.builder(
itemCount: Details.length,
itemBuilder: (context, index) {
return Column(
children: [
InkWell(
onTap: () {
do somthing
},
child: Column(
children: [
Container(
padding: EdgeInsets.only(
left:
ComponentSize.paddingleft),
alignment: Alignment.centerLeft,
child: Text(
Details[index],
style: const TextStyle(
color: Colors.black,
fontFamily: 'Taml_001'),
textAlign: TextAlign.start,
),
),
Container(
alignment: Alignment.centerLeft,
padding: EdgeInsets.only(
left:
ComponentSize.paddingleft),
child: Text(Details[index],
style: TextStyle(
color: AppColor.black
.withOpacity(
ComponentSize
.opacity1),
fontSize: ComponentSize
.containerfontsize)),
)
],
),
),
const Divider(),
],
);
},
),
)),
child: Container(
color: AppColor.white,
padding: EdgeInsets.only(
top: ComponentSize.paddingbottom,
bottom: ComponentSize.paddingtop,
left: ComponentSize.padding1left),
width: ComponentSize.container2width,
height: ComponentSize.container2height,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
SizedBox(
child: Column(
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(
width: ComponentSize.textcontainerwidth,
height: ComponentSize.textcontainerheight,
child: SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: Text(
Tamil,
style: const TextStyle(
color: Colors.black,
fontFamily: 'Taml_001'),
),
),
),
SizedBox(
width: ComponentSize.textcontainerwidth,
height: ComponentSize.textcontainerheight,
child: SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: Text(
English,
style: const TextStyle(
color: Colors.black),
),
),
)
],
),
),
SizedBox(
child: Icon(
Icons.expand_more,
size: ComponentSize.iconarrowsize,
color: Colors.black,
),
)
],
),
),
),
Map item;
List data;
Future getdata() async{
http.Response response= await http.get(Uri.encodeFull("https://talaikis.com/api/quotes/"));
item=json.decode(response.body);
setState(() {
data=item["quotes"];
});
debugPrint(data.toString());
}
I want to display this request in stateful widget which is like this
#override
Widget build(BuildContext context) {
return new Scaffold(
drawer: drawerLeft(),
appBar: AppBar(
title: Text(
"IN TIME",
style: TextStyle(color: Colors.black, fontWeight: FontWeight.w700),
),
backgroundColor: clr,
elevation: 0.0,
leading: MaterialButton(
child: Icon(
Icons.view_headline,
color: Colors.black,
),
onPressed: () {
scaffoldKey.currentState.openDrawer();
},
)),
key: scaffoldKey,
body: AnimatedContainer(
padding: EdgeInsets.only(top: 50.0),
duration: Duration(milliseconds: 1000),
curve: Curves.ease,
color: clr,
child: PageView.builder(
itemCount: 7, //7days
onPageChanged: (int page) {
this.setState(() {
Random rnd;
rnd = new Random();
int r = 0 + rnd.nextInt(_colors.length - 0);
clr = _colors[r];
});
},
controller: pageViewController,
itemBuilder: (BuildContext context, int index) {
return Padding(
padding: const EdgeInsets.only(left: 10.0),
child: Stack(
children: <Widget>[
Container(
decoration: new BoxDecoration(
borderRadius: BorderRadius.circular(20.0),
color: Colors.white,
),
height:
MediaQuery.of(scaffoldKey.currentContext).size.height -
150.0,
width:
MediaQuery.of(scaffoldKey.currentContext).size.width -
20.0,
child: Stack(
children: <Widget>[
Positioned(
width: MediaQuery.of(scaffoldKey.currentContext)
.size
.width -
100.0,
left: index != currentPage
? getMappedValue(20.0, 100.0, 160.0, 20.0, pos)
: getMappedValue(20.0, 100.0, 20.0, -120.0, pos),
top: 20.0,
child: Opacity(
opacity: index != currentPage
? getMappedValue(20.0, 100.0, 0.0, 01.0, pos)
: getMappedValue(20.0, 100.0, 01.0, 00.0, pos),
child: Column(
children: <Widget>[
Row(
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
children: <Widget>[
Text(
_days[index],
maxLines: 1,
softWrap: true,
style: TextStyle(
color: Colors.deepOrange,
fontSize: 22.0,
fontWeight: FontWeight.w600),
),
],
),
Padding(
padding: const EdgeInsets.only(top: 15.0),
child: Text(
'Quote for the day',
softWrap: true,
style: TextStyle(
fontSize: 30.0,
fontWeight: FontWeight.w300),
),
),
],
),
),
),
],
),
),
],
),
);
},
),
),
);
}
}
*Please help me out *
If I understand, you want to display the quotes under some padding that are being retrieved to your List which I'll assume to be an inferenced List<String>. If so, you could just use a ListView within your widget tree to display every fetched item in that list, like so:
(...)
data != null
? Padding(
padding: const EdgeInsets.only(top: 15.0),
child: ListView.builder(
itemCount: data.length,
itemBuilder: (BuildContext context, int index) {
return ListTile(
title: Text(data[index]),
);
},
),
)
: Container(),
(...)
I want to create a widget that will build describe in this photo
I already created the meter bar but I still don't know how to add numbers from start to end of bar and a arrow in bottom where place what points you have and with same color above of it
child: Row(children: <Widget>[
Column(children: <Widget>[
Padding(
padding: EdgeInsets.all(5.0),
child: Container(
decoration: new BoxDecoration(
border: new Border(right: BorderSide(color: Colors.black))
),
child: Column(
children: <Widget>[
Text('Points'),
Text('38'),
],
),
),
),
],),
// green bar
Column(children: <Widget>[
Padding(
padding: EdgeInsets.only(right:10.0),
child: Container(
width:ratewidth,
decoration: new BoxDecoration(
border: new Border(bottom: BorderSide(color: Colors.green, width: 5.0))
),
),
)
],),
//yellow bar
Column(children: <Widget>[
Padding(
padding: EdgeInsets.only(right:10.0),
child: Container(
width:ratewidth,
decoration: new BoxDecoration(
border: new Border(bottom: BorderSide(color: Colors.yellow, width: 5.0))
),
),
),
],),
...
],)
With a combination of Row, Column and Align it can be done in a few lines.
The hardest part is actually the triangle. Usually, you'll want to use CustomPainter for the triangle, but I was lazy here. So I combined translation, rotation, and a clip.
import 'dart:math';
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: MyHome(),
);
}
}
class MyHome extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Column(
children: <Widget>[
Padding(
padding: const EdgeInsets.all(8.0),
child: ScoreMeter(
score: 1,
),
)
],
),
);
}
}
class ScoreMeter extends StatelessWidget {
final int score;
ScoreMeter(
{
this.score,
Key key})
: super(key: key);
#override
Widget build(BuildContext context) {
return SizedBox(
height: 100.0,
child: Row(
children: <Widget>[
Expanded(
child: ScoreMeterItem(
score: score, color: Colors.green, minRange: 0, maxRange: 50),
),
Expanded(
child: ScoreMeterItem(
score: score,
color: Colors.yellow,
minRange: 51,
maxRange: 100),
),
Expanded(
child: ScoreMeterItem(
score: score,
color: Colors.orange,
minRange: 101,
maxRange: 150),
),
Expanded(
child: ScoreMeterItem(
score: score, color: Colors.red, minRange: 151, maxRange: 200),
),
Expanded(
child: ScoreMeterItem(
score: score,
color: Colors.purple,
minRange: 201,
maxRange: 250),
),
Expanded(
child: ScoreMeterItem(
score: score,
color: Colors.brown,
minRange: 251,
maxRange: 300),
),
],
),
);
}
}
class ScoreMeterItem extends StatelessWidget {
/// Hello World
final int score;
final Color color;
final int minRange;
final int maxRange;
ScoreMeterItem(
{this.score,
this.color = Colors.grey,
#required this.minRange,
#required this.maxRange,
Key key})
: assert(minRange != null),
assert(maxRange != null),
super(key: key);
#override
Widget build(BuildContext context) {
final theme = Theme.of(context);
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 4.0),
child: Column(
children: <Widget>[
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Text(minRange.toString(), style: theme.textTheme.caption),
Text(maxRange.toString(), style: theme.textTheme.caption),
],
),
ScoreMeterBar(color: color),
score >= minRange && score <= maxRange
? SizedBox(
height: 10.0,
child: Align(
alignment: Alignment(
(score - minRange) / (maxRange - minRange) * 2 - 1,
0.0),
child: Arrow(color: color),
),
)
: SizedBox()
],
),
);
}
}
class Arrow extends StatelessWidget {
final Color color;
Arrow({this.color});
#override
Widget build(BuildContext context) {
return SizedBox(
height: 5.0,
width: 10.0,
child: ClipRect(
child: OverflowBox(
maxWidth: 10.0,
maxHeight: 10.0,
child: Align(
alignment: Alignment.topCenter,
child: Transform.translate(
offset: Offset(.0, 5.0),
child: Transform.rotate(
angle: pi / 4,
child: Container(
width: 10.0,
height: 10.0,
color: color,
),
),
),
),
),
),
);
}
}
class ScoreMeterBar extends StatelessWidget {
final Color color;
ScoreMeterBar({this.color = Colors.grey, Key key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Container(
height: 8.0,
decoration: BoxDecoration(
borderRadius: BorderRadius.all(
Radius.circular(4.0),
),
color: color,
),
);
}
}