I'm building an App I have to build UI like below but I don't have any idea that how to create UI like this. Kindly guide me through this.
var listImages = [
"https://source.unsplash.com/random/200x200?sig=1",
"https://source.unsplash.com/random/200x200?sig=2",
"https://source.unsplash.com/random/200x200?sig=3",
"https://source.unsplash.com/random/200x200?sig=4",
"https://source.unsplash.com/random/200x200?sig=5"
];
Padding(
padding: const EdgeInsets.all(8.0),
child: Stack(
children: [
for (int i = 0; i < (listImages.length>=4?4:listImages.length); i++)
Transform.translate(
offset: Offset(i == 0 ? 0.0 : i * 44, 0),
child: Container(
height: 70,
width: 70,
alignment: Alignment.center,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(25),
color: Colors.black87,
),
clipBehavior: Clip.hardEdge,
child: (i+1)>=4?Text("+ ${listImages.length-3}",style:const TextStyle(color: Colors.green),):Image.network(listImages[i]),
),
)
],
),
)
Below are the methods used to prepare the layout
// this method return the layout as per your expectations, here images are the // list of items you want to use and max count are the list of max item you want // to show except the count tile.
Widget getItems(List<String> images, int maxCount) =>
Stack(
children: List.generate(images.length <= maxCount ? images.length : maxCount+1, (index) {
if(index == maxCount){
return Positioned(
left: index * 60,
child: Container(
padding: const EdgeInsets.all(2), // Border width
decoration: BoxDecoration(
border: Border.all(
color: Colors.grey,
width: 2,
),
color: Colors.black,
borderRadius: BorderRadius.circular(40)),
child: SizedBox.fromSize(
size: const Size.fromRadius(48), // Image radius
child: Center(
child: Text(
style: const TextStyle(
color: Color(0xff58D56D),
fontSize: 30
),
"+${images.length-maxCount}"
),
),
),
),
);
}
else {
return Positioned(
left: index * 60, child: getItemWidget(images[index]));
}
}),
);
// pass the image url you want to show.
Widget getItemWidget(String imageUrl) => Stack(
children: [
Container(
padding: const EdgeInsets.all(2), // Border width
decoration: BoxDecoration(
color: Colors.black,
borderRadius: BorderRadius.circular(40)),
child: ClipRRect(
borderRadius: BorderRadius.circular(40),
child: SizedBox.fromSize(
size: Size.fromRadius(48), // Image radius
child: Image.network(
imageUrl,
fit: BoxFit.fill,
),
),
),
),
Positioned(
bottom: 0,
left: 0,
child: Container(
padding: const EdgeInsets.all(2), // Border width
decoration: const BoxDecoration(
color: Colors.black,
shape: BoxShape.circle,
),
child: ClipRRect(
borderRadius: BorderRadius.circular(100),
child: SizedBox.fromSize(
size: Size.fromRadius(20), // Image radius
child: Image.network(
"https://play-lh.googleusercontent.com/STIZ_iftiehDCSynHXQaLqiL-F4kbZwasXOB2nae5pXTOpNKz8XSd7_VCF1Zgc3Z8Q",
fit: BoxFit.contain,
),
),
),
),
)
],
);
Below code is used to show the items
getItems(["https://cdn.pixabay.com/photo/2013/07/13/10/07/man-156584__340.png",
"https://static.vecteezy.com/system/resources/thumbnails/001/993/889/small/beautiful-latin-woman-avatar-character-icon-free-vector.jpg",
"https://static.toiimg.com/thumb/resizemode-4,msid-76729536,width-1200,height-900/76729536.jpg",
"https://www.nj.com/resizer/zovGSasCaR41h_yUGYHXbVTQW2A=/1280x0/smart/cloudfront-us-east-1.images.arcpublishing.com/advancelocal/SJGKVE5UNVESVCW7BBOHKQCZVE.jpg",
"https://i.kinja-img.com/gawker-media/image/upload/t_original/ijsi5fzb1nbkbhxa2gc1.png"], 3),
)
Output:
The point is using Stack and Positioned to positiond widgets.
(index to padding, maxPerson to set maximum widget to show)
https://dartpad.dev/ example
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
final colors = const [
Colors.yellow,
Colors.green,
Colors.blue,
Colors.orange,
Colors.cyan,
Colors.brown,
];
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: PersonStacker(colors: colors),
),
);
}
}
class PersonStacker extends StatelessWidget {
final List<Color> colors;
final int maxPerson;
const PersonStacker({required this.colors, this.maxPerson = 3});
#override
Widget build(BuildContext context) {
return Stack(
children: [
...colors
.getRange(0, maxPerson)
.toList()
.asMap()
.map((index, color) => MapEntry(index, Person(index, color: color)))
.values
.toList(),
...colors.length > maxPerson
? [Person(maxPerson, plus: colors.length - maxPerson)]
: []
],
);
}
}
class Person extends StatelessWidget {
final int index;
final Color color;
final Color colorBorder;
final double size = 100;
final double offset = 30;
final int? plus;
const Person(this.index,
{this.plus, this.color = Colors.black, this.colorBorder = Colors.black});
Widget renderChild() => plus != null
? Center(
child: Text(
"+$plus",
style: const TextStyle(color: Colors.white, fontSize: 20),
),
)
: Container();
#override
Widget build(BuildContext context) {
return Positioned(
left: index * offset,
child: Container(
width: size,
height: size,
decoration: BoxDecoration(
color: color,
border: Border.all(width: 2, color: colorBorder),
borderRadius: BorderRadius.circular(size),
),
child: renderChild(),
),
);
}
}
Result
Related
I want to create a progress bar in flutter which is like this
I get the data of week from backend but I have to create it this way.
and the icon on weeks or survey should be touchable.
thanks in advance.
Still a lot of things to change, but i thing it should be enough for the beginning
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Example',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const HomePage(),
);
}
}
class HomePage extends StatelessWidget {
const HomePage({super.key});
#override
Widget build(BuildContext context) {
List<String> points = [
'Survey',
'Week 1',
'Week 2',
'Week 3',
'Week 4',
];
var lineWidth =
MediaQuery.of(context).size.width - 16.0; // screen width - 2 * padding
var space = lineWidth / points.length; //space between dots
var currentSteps = 3; // <---- change this one
return Scaffold(
body: Padding(
padding: const EdgeInsets.all(8.0),
child: Center(
child: SizedBox(
height: 60.0,
child: Stack(
children: [
//grey line
Positioned(
top: 15,
left: 0,
right: 0,
child: Container(
height: 2.0,
width: double.infinity,
color: Colors.grey,
),
),
//red line
Positioned(
top: 15,
left: 0,
child: Container(
height: 2.0,
width: space * (currentSteps - 1) + space / 2,
color: Colors.orange,
),
),
//circles
Row(
children: points
.asMap()
.map((i, point) => MapEntry(
i,
SizedBox(
width: space,
child: Column(
children: [
Stack(
children: [
Container(
height: 30.0,
width: 30.0,
decoration: BoxDecoration(
shape: BoxShape.circle,
border: Border.all(
width: 1.5,
color: i == currentSteps - 1
? Colors.orange
: Colors.transparent,
),
),
child: Center(
child: Container(
height: 20.0,
width: 20.0,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: i < currentSteps
? Colors.orange
: Colors.grey,
),
),
),
),
if (i < currentSteps - 1)
const SizedBox(
height: 30.0,
width: 30.0,
child: Center(
child: Icon(
Icons.check,
size: 16.0,
color: Colors.white,
),
),
),
],
),
const SizedBox(height: 4.0),
Text(
point,
textAlign: TextAlign.left,
style: TextStyle(
color: i < currentSteps
? Colors.orange
: Colors.grey,
),
),
],
),
),
))
.values
.toList(),
),
],
),
),
),
),
);
}
}
You can try basic Stepper widget with type: StepperType.horizontal. As for the UI you like im_stepper package. If it fails to satisfy you can create custom widget with Stack and Row.
Try basic Stepper widget of flutter. if its not working properly then you need to create custom widget.
Following are the screenshots of the Ui of Buttons which I want to make using Flutter.
There are the packages Pie chart and Syncfusion's package, but these are different for this approach.
Help me to create these.
I've found my solution and now adding on the request of #dijkstra
I've used PieChart widget to implement it.
First of all, create a model class
class PieButtonModel {
PieButtonModel({this.title, required this.onPressed});
final VoidCallback onPressed;
final String? title;
}
Now, create a stateless class for Ui. I've used two PieChart widgets in stack to match the required output.
class PieButtonScreen extends StatelessWidget {
PieButtonScreen({Key? key, required this.buttonsData, this.startsFrom}) : super(key: key);
final List<PieButtonModel> buttonsData;
final double? startsFrom;
int touchedIndex = -1;
#override
Widget build(BuildContext context) {
return Center(
child: StatefulBuilder(builder: (context, stateful) {
return Stack(
clipBehavior: Clip.none,
children: [
Container(
decoration: const BoxDecoration(
color: kGreyColor,
shape: BoxShape.circle,
gradient: LinearGradient(
begin: Alignment.bottomCenter,
end: Alignment.topCenter,
colors: [
Color(0xff1143A7),
Color(0xff00B6FE),
],
),
),
width: double.infinity,
height: double.infinity,
),
Container(
margin: const EdgeInsets.all(10),
decoration: const BoxDecoration(
color: kWhiteColor,
shape: BoxShape.circle,
),
width: double.infinity,
height: double.infinity,
),
PieChart(
PieChartData(
pieTouchData: PieTouchData(touchCallback: (FlTouchEvent event, pieTouchResponse) {
stateful(() {
if (!event.isInterestedForInteractions ||
pieTouchResponse == null ||
pieTouchResponse.touchedSection == null) {
touchedIndex = -1;
return;
}
touchedIndex = pieTouchResponse.touchedSection!.touchedSectionIndex;
pieButtonPressed(touchedIndex, buttonsData);
});
}),
borderData: FlBorderData(
show: true,
border: const Border(
top: BorderSide(color: Colors.red),
)),
sectionsSpace: 5,
centerSpaceRadius: 60,
centerSpaceColor: const Color(0xFFe7e8e9),
startDegreeOffset: startsFrom ?? 0,
sections: showingSections()),
),
Positioned.fill(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
InkWell(
onTap: () {
Get.find<ValveViewController>().removeView();
},
child: Neumorphic(
style: const NeumorphicStyle(
boxShape: NeumorphicBoxShape.circle(),
color: kWhiteColor,
border: NeumorphicBorder(
color: Color(0xFF919192),
width: 2,
)),
child: Container(
height: 90,
width: 90,
decoration: BoxDecoration(
// shape: BoxShape.circle,
borderRadius: BorderRadius.circular(100),
),
child: const CircleAvatar(
backgroundImage: AssetImage('assets/images/icon.jpg'),
),
),
),
),
],
),
)
],
);
}),
);
}
}
And then, create a list of sections
List<PieChartSectionData> showingSections() {
return List.generate(buttonsData.length, (i) {
PieButtonModel pbm = buttonsData[i];
final isTouched = i == touchedIndex;
final fontSize = isTouched ? 14.0 : 11.0;
final radius = isTouched ? 100.0 : 90.0;
var val = buttonsData.length / 100;
List<String>? title = pbm.title?.replaceAll('/ ', '').split(" ");
return PieChartSectionData(
color: kGreyColor,
value: val,
title: title?.isEmpty == true
? ""
: title?.length == 1
? title![0]
: title!.length == 2
? "${title[0]}\n${title[1]}"
: title.length == 3
? "${title[0]}\n${title[1]}\n${title[2]}"
: "${title[0]}\n${title[1]}\n${title[2]}\n${title[3]}",
radius: radius,
titleStyle: TextStyle(
overflow: TextOverflow.ellipsis,
fontSize: fontSize,
color: kBlackColor,
),
);
});
}
Here is the output.
import 'package:flutter/material.dart';
import 'package:flutter_swiper/flutter_swiper.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
Widget build(BuildContext context) {
var width = MediaQuery.of(context).size.width;
var height = MediaQuery.of(context).size.height;
Color backgroundColor = Color(0xffF5F5F5);
Color buttonColor = Color(0xffF4FCFF);
Color iconColor = Colors.blueGrey.shade200;
Color fontColor = Colors.blueGrey.shade800;
int _currentIndex = 0;
List<Widget> _cardList = [
Container(
decoration: BoxDecoration(
color: Colors.red,
borderRadius: BorderRadius.circular(8),
),
child: Text(_currentIndex.toString()),
),
Container(
decoration: BoxDecoration(
color: Colors.blue,
borderRadius: BorderRadius.circular(8)
),
child: Text(_currentIndex.toString()),
),
Container(
decoration: BoxDecoration(
color: Colors.green,
borderRadius: BorderRadius.circular(8)
),
child: Text(_currentIndex.toString()),
),
];
void setIndex(int newIndex){
print("$_currentIndex $newIndex");
_currentIndex = newIndex;
this.setState((){
_currentIndex = newIndex;
});
}
void initState(){
this.setState(() {
_currentIndex = 0;
});
}
Map<int, double> _firstOpacityMap = {
0: 0,
1: 0.6,
2: 0.6
};
Map<int, double> _lastOpacityMap = {
0: 0.6,
1: 0.6,
2: 0
};
return Scaffold(
body: Container(
color: backgroundColor,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
height: height * 0.08,
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Container(
width: width * 0.04,
),
Container(
width: 50,
height: 50,
child: FloatingActionButton(
backgroundColor: buttonColor,
foregroundColor: iconColor,
elevation: 0,
child: Icon(Icons.menu_rounded),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
)
),
),
Container(
width: width * 0.45,
),
Container(
width: 50,
height: 50,
child: FloatingActionButton(
backgroundColor: buttonColor,
foregroundColor: iconColor,
elevation: 0,
child: Icon(Icons.home_outlined),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
)
),
),
Container(
width: width * 0.04,
),
],
),
Container(
height: height * 0.02,
),
Container(
margin: EdgeInsets.only(
left: width * 0.11
),
child: Column(
children: [
Text(
'Your Cards',
style: TextStyle(
fontFamily: 'Rubik Light',
fontWeight: FontWeight.w300,
fontSize: 30,
color: fontColor,
),
)
],
),
),
Container(
height: height * 0.02,
),
Container(
margin: EdgeInsets.only(
left: 0,
),
width: width,
height: 200,
child: Swiper(
itemCount: 3,
layout: SwiperLayout.CUSTOM,
customLayoutOption: CustomLayoutOption(
startIndex: -1,
stateCount: 3
).addTranslate([
new Offset(-330.0, 0),
new Offset(0.0, 0.0),
new Offset(330.0, 0)
// Error is below here
]).addOpacity([
_firstOpacityMap[_currentIndex],
1,
_lastOpacityMap[_currentIndex],
]),
itemWidth: 310,
itemHeight: 180,
curve: Curves.easeOut,
scrollDirection: Axis.horizontal,
onIndexChanged: (int newIndex) => setIndex(newIndex),
itemBuilder: (BuildContext context, int index){
return _cardList[index];
}
),
)
],
)
)
);
}
}
I am using flutter_swiper to show the cards in a custom SwiperLayout. Since I don't want them to be fully scrollable, I am trying to set the opacity based on the current index. I am getting the next Index in the onIndexChanged method from flutter_swiper, I wan't to set the variable currentIndex to the current index of the cards. But somehow the index isn't getting changed in the setState?
move int _currentIndex = 0; out of the build function.
I get this error:
════════ Exception caught by rendering library ═════════════════════════════════
SliverGeometry has a paintOffset that exceeds the remainingPaintExtent from the constraints.
The relevant error-causing widget was
SliverAppBarLayer
It happens every time I scroll it up. The error is not visible on the screen, only in the console which also prevents me to do hot restart/hot reload. What does it mean? Why does it happen? How to fix it, please?
screenshot
my SliverWidget: sliver_app_bar_layer.dart
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
class SliverAppBarLayer extends SingleChildRenderObjectWidget {
SliverAppBarLayer({Widget child, Key key}) : super(child: child, key: key);
#override
RenderObject createRenderObject(BuildContext context) {
return RenderSliverAppBarLayer();
}
}
class RenderSliverAppBarLayer extends RenderSliverToBoxAdapter {
RenderSliverAppBarLayer({
RenderBox child,
}) : super(child: child);
#override
void performResize() {}
#override
void performLayout() {
if (child == null) {
geometry = SliverGeometry.zero;
return;
}
final SliverConstraints constraints = this.constraints;
child.layout(constraints.asBoxConstraints(/* crossAxisExtent: double.infinity */), parentUsesSize: true);
double childExtent;
switch (constraints.axis) {
case Axis.horizontal:
childExtent = child.size.width;
break;
case Axis.vertical:
childExtent = child.size.height;
break;
}
assert(childExtent != null);
final double paintedChildSize = calculatePaintOffset(constraints, from: 0.0, to: childExtent);
final double cacheExtent = calculateCacheOffset(constraints, from: 0.0, to: childExtent);
assert(paintedChildSize.isFinite);
assert(paintedChildSize >= 0.0);
geometry = SliverGeometry(
scrollExtent: 0,
paintExtent: childExtent,
paintOrigin: constraints.scrollOffset,
cacheExtent: cacheExtent,
maxPaintExtent: childExtent,
hitTestExtent: paintedChildSize,
);
setChildParentData(child, constraints, geometry);
}
}
main.dart
import 'package:flutter/material.dart';
import 'theme/style_constants.dart';
import 'widgets/sliver_app_bar_layer.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData(
primarySwatch: Colors.teal,
canvasColor: Colors.transparent,
),
home: DemoScreen(),
);
}
}
class DemoScreen extends StatelessWidget {
static String id = '/demo';
int numberOfColumns(dynamic context) => ((MediaQuery.of(context).size.width - (2 * kBigBoxPadding)) / kMaxCrossAxisExtent).floor();
// Widget _buildGrid() => GridView.extent(
// maxCrossAxisExtent: kMaxCrossAxisExtent,
// padding: const EdgeInsets.all(4),
// mainAxisSpacing: 4,
// crossAxisSpacing: 4,
// children: _buildGridTileList(500));
List<Container> _buildGridTileList(dynamic context, int count) => List.generate(
count,
(i) => Container(
//NOTE: workaround according to: https://github.com/flutter/flutter/issues/25009
decoration: BoxDecoration(
color: colorBackground, //the color of the main container
border: Border.all(
//apply border to only that side where the line is appearing i.e. top | bottom | right | left.
width: 4, //depends on the width of the unintended line
color: colorBackground,
),
),
child: Container(
decoration: BoxDecoration(
color: colorBackground,
),
child: Center(
child: Text(
'$i / ${numberOfColumns(context)}',
style: TextStyle(color: Colors.grey),
//textAlign: TextAlign.center,
),
),
//margin: EdgeInsets.all(0),
),
));
// List<Widget> tabbarViewItems() {
// List<Widget> items = [];
// for (int i = 0; i < 25; i++) {
// Widget listView = _buildGrid();
// items.add(listView);
// }
// return items;
// }
List<Widget> listViewItems() {
List<Widget> items = [];
for (int i = 0; i < 500; i++) {
Widget widgetItem = Text(
'item $i',
textAlign: TextAlign.center,
style: TextStyle(color: Colors.white),
);
items.add(widgetItem);
}
return items;
}
#override
Widget build(BuildContext context) {
return DefaultTabController(
length: 25,
child: Scaffold(
backgroundColor: colorBackground,
//floatingActionButton: MyTabBar(),
floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat,
body: Stack(
children: [
Container(
width: double.infinity,
color: Colors.amber,
child: Image.network(
'https://images.unsplash.com/photo-1517248135467-4c7edcad34c4?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=2700&q=80',
fit: BoxFit.cover,
height: MediaQuery.of(context).size.height * kCoverHeightProportion,
),
//color: Colors.green,
),
Align(
alignment: Alignment.bottomCenter,
child: Container(
height: 40,
width: 300,
color: Colors.red,
),
),
Align(
alignment: Alignment.bottomCenter,
child: Container(
margin: EdgeInsets.only(left: kBigBoxPadding, right: kBigBoxPadding, top: kBigBoxPadding, bottom: kBottomBigBoxPadding),
//width: MediaQuery.of(context).size.width * 0.9,
//margin: EdgeInsets.symmetric(horizontal: kBigBoxPadding),
decoration: BoxDecoration(
//color: Colors.pink,
borderRadius: BorderRadius.all(Radius.circular(30)),
),
child: ClipRRect(
borderRadius: BorderRadius.all(Radius.circular(30)),
child: CustomScrollView(
//physics: FixedExtentScrollPhysics(),
anchor: kCoverHeightProportion *
kCoverHeightProportion *
MediaQuery.of(context).size.height /
(kCoverHeightProportion * (MediaQuery.of(context).size.height - kBigBoxPadding - kBottomBigBoxPadding)),
slivers: [
SliverAppBarLayer(
child: Container(
width: double.infinity,
height: 100,
decoration: BoxDecoration(
color: colorBackground, borderRadius: BorderRadius.only(topLeft: Radius.circular(30), topRight: Radius.circular(30))),
child: Center(
child: Text(
'La casa de don Juan',
style: TextStyle(color: colorPrimary1, fontSize: 32, fontWeight: FontWeight.w800),
),
),
),
),
SliverGrid.extent(
maxCrossAxisExtent: kMaxCrossAxisExtent,
childAspectRatio: 1,
mainAxisSpacing: 0,
crossAxisSpacing: 0,
children: _buildGridTileList(context, 250),
),
],
),
),
),
),
],
),
),
);
}
}
style_constants.dart
import 'package:flutter/material.dart';
const Color colorShade1 = Color(0xFFEFF0F2);
const Color colorShade2 = Color(0xFF777777);
const Color colorShade3 = Color(0xFF424242);
const Color colorShade4 = Color(0xFF4B4935);
const Color colorShade5 = Color(0xFF3D2916);
const Color colorShade6 = Color(0xFF1D1C0A);
const Color colorBackground = Color(0xFF101A24);
const Color colorPrimary1 = Color(0xFFCC9757);
const double kTabIconHeight = 28;
const double kCoverHeightProportion = 0.35;
const double kBigBoxPadding = 8;
const double kBottomBigBoxPadding = 60;
const double kMaxCrossAxisExtent = 150;
Just use prepared SliverPersistentHeaderDelegate it works from the box...
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData(
primarySwatch: Colors.teal,
canvasColor: Colors.transparent,
),
home: DemoScreen(),
);
}
}
class DemoScreen extends StatelessWidget {
static String id = '/demo';
int numberOfColumns(dynamic context) =>
((MediaQuery.of(context).size.width - (2 * kBigBoxPadding)) /
kMaxCrossAxisExtent)
.floor();
// Widget _buildGrid() => GridView.extent(
// maxCrossAxisExtent: kMaxCrossAxisExtent,
// padding: const EdgeInsets.all(4),
// mainAxisSpacing: 4,
// crossAxisSpacing: 4,
// children: _buildGridTileList(500));
List<Container> _buildGridTileList(dynamic context, int count) =>
List.generate(
count,
(i) => Container(
//NOTE: workaround according to: https://github.com/flutter/flutter/issues/25009
decoration: BoxDecoration(
color: colorBackground, //the color of the main container
border: Border.all(
//apply border to only that side where the line is appearing i.e. top | bottom | right | left.
width: 4, //depends on the width of the unintended line
color: colorBackground,
),
),
child: Container(
decoration: BoxDecoration(
color: colorBackground,
),
child: Center(
child: Text(
'$i / ${numberOfColumns(context)}',
style: TextStyle(color: Colors.grey),
//textAlign: TextAlign.center,
),
),
//margin: EdgeInsets.all(0),
),
));
// List<Widget> tabbarViewItems() {
// List<Widget> items = [];
// for (int i = 0; i < 25; i++) {
// Widget listView = _buildGrid();
// items.add(listView);
// }
// return items;
// }
List<Widget> listViewItems() {
List<Widget> items = [];
for (int i = 0; i < 500; i++) {
Widget widgetItem = Text(
'item $i',
textAlign: TextAlign.center,
style: TextStyle(color: Colors.white),
);
items.add(widgetItem);
}
return items;
}
#override
Widget build(BuildContext context) {
var topPadding = kCoverHeightProportion *
kCoverHeightProportion *
MediaQuery.of(context).size.height /
(kCoverHeightProportion *
(MediaQuery.of(context).size.height -
kBigBoxPadding -
kBottomBigBoxPadding));
return DefaultTabController(
length: 25,
child: Scaffold(
backgroundColor: colorBackground,
//floatingActionButton: MyTabBar(),
floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat,
body: Stack(
children: [
Container(
width: double.infinity,
color: Colors.amber,
child: Image.network(
'https://images.unsplash.com/photo-1517248135467-4c7edcad34c4?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=2700&q=80',
fit: BoxFit.cover,
height:
MediaQuery.of(context).size.height * kCoverHeightProportion,
),
//color: Colors.green,
),
Align(
alignment: Alignment.bottomCenter,
child: Container(
height: 40,
width: 300,
color: Colors.red,
),
),
Align(
alignment: Alignment.bottomCenter,
child: Container(
margin: EdgeInsets.only(
left: kBigBoxPadding,
right: kBigBoxPadding,
top: kBigBoxPadding,
bottom: kBottomBigBoxPadding),
//width: MediaQuery.of(context).size.width * 0.9,
//margin: EdgeInsets.symmetric(horizontal: kBigBoxPadding),
decoration: BoxDecoration(
//color: Colors.pink,
borderRadius: BorderRadius.all(Radius.circular(30)),
),
child: ClipRRect(
borderRadius: BorderRadius.all(Radius.circular(30)),
child: CustomScrollView(
//physics: FixedExtentScrollPhysics(),
anchor: topPadding,
slivers: [
SliverPadding(
padding: EdgeInsets.only(
top: topPadding,
),
sliver: SliverPersistentHeader(
pinned: true,
floating: false,
delegate: _SliverPersistentHeaderDelegate(
Container(
width: double.infinity,
height: 100,
decoration: BoxDecoration(
color: colorBackground,
borderRadius: BorderRadius.only(
topLeft: Radius.circular(30),
topRight: Radius.circular(30))),
child: Center(
child: Text(
'La casa de don Juan',
style: TextStyle(
color: colorPrimary1,
fontSize: 32,
fontWeight: FontWeight.w800),
),
),
),
),
),
),
SliverGrid.extent(
maxCrossAxisExtent: kMaxCrossAxisExtent,
childAspectRatio: 1,
mainAxisSpacing: 0,
crossAxisSpacing: 0,
children: _buildGridTileList(context, 250),
),
],
),
),
),
),
],
),
),
);
}
}
const Color colorShade1 = Color(0xFFEFF0F2);
const Color colorShade2 = Color(0xFF777777);
const Color colorShade3 = Color(0xFF424242);
const Color colorShade4 = Color(0xFF4B4935);
const Color colorShade5 = Color(0xFF3D2916);
const Color colorShade6 = Color(0xFF1D1C0A);
const Color colorBackground = Color(0xFF101A24);
const Color colorPrimary1 = Color(0xFFCC9757);
const double kTabIconHeight = 28;
const double kCoverHeightProportion = 0.35;
const double kBigBoxPadding = 8;
const double kBottomBigBoxPadding = 60;
const double kMaxCrossAxisExtent = 150;
class _SliverPersistentHeaderDelegate extends SliverPersistentHeaderDelegate {
_SliverPersistentHeaderDelegate(this.child);
final Widget child;
#override
Widget build(
BuildContext context, double shrinkOffset, bool overlapsContent) {
return child;
}
#override
double get maxExtent => 100;
#override
double get minExtent => 100;
#override
bool shouldRebuild(SliverPersistentHeaderDelegate oldDelegate) => false;
}
The image shows the UI i want to achieve.
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'dart:math' as Math;
const double _radiansPerDegree = Math.pi / 180;
final double _startAngle = -90.0 * _radiansPerDegree;
typedef double ItemAngleCalculator(int index);
class HomePage extends StatefulWidget {
#override
State createState() {
return new _HomePageState();
}
}
class _HomePageState extends State<HomePage> with TickerProviderStateMixin {
final List<Widget> items = [
new Container(
decoration: new BoxDecoration(shape: BoxShape.circle,
),
child: new MaterialButton(
onPressed: () {},
child: new Image.asset(
'images/recycling.png',
width: 60.0,
height: 60.0,
fit: BoxFit.cover,
),
),
),
new FlatButton(
onPressed: () {},
child: new Image.asset(
'images/gas-station.png',
width: 60.0,
height: 60.0,
fit: BoxFit.cover,
),
),
new FlatButton(
onPressed: () {},
child: new Image.asset(
'images/light-bulb.png',
width: 60.0,
height: 60.0,
fit: BoxFit.cover,
),
),
new FlatButton(
onPressed: () {},
child: new Image.asset(
'images/cflamp.png',
width: 60.0,
height: 60.0,
fit: BoxFit.cover,
),
),
new FlatButton(
onPressed: () {},
child: new Image.asset(
'images/plug.png',
width: 60.0,
height: 60.0,
fit: BoxFit.cover,
),
)
];
#override
Widget build(BuildContext context) {
return new Scaffold(
body: _buildBody(),
);
}
Widget _buildBody() {
return Container(
decoration: new BoxDecoration(
color: Colors.teal),
width: MediaQuery.of(context).size.width,
child: Padding(
padding: const EdgeInsets.all(25.0),
child: new Column(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
new Padding(
padding: const EdgeInsets.only(top: 20.0,left: 20.0,right: 20.0),
child: new Container(
decoration: new BoxDecoration(
gradient: new LinearGradient(colors: [
new Color(0xFFA19D9A),
Colors.white,
new Color(0xFFA19D9A),
]),
borderRadius:
new BorderRadius.all(new Radius.circular(25.0))),
child: new Container(
margin: const EdgeInsets.all(5.0),
decoration: new BoxDecoration(
color: Colors.teal,
borderRadius:
new BorderRadius.all(new Radius.circular(22.0))),
child: new Padding(
padding: const EdgeInsets.all(5.0),
child: new Text(
"Get recommendations by selecting any icon",
style: new TextStyle(
fontSize: 22.0,
color: Colors.white,
fontFamily: 'CaviarDreams',
fontWeight: FontWeight.w600),
textAlign: TextAlign.center,
),
),
),
),
),
_buildStackView(),
],
),
),
);
}
Widget _buildStackView() {
final List<Widget> beverages = <Widget>[];
double width = MediaQuery.of(context).size.width;
double height = MediaQuery.of(context).size.height;
double outerRadius = Math.min(width * 3 / 4, height * 3 / 4);
double innerWhiteRadius = outerRadius * 3 / 4;
for (int i = 0; i < items.length; i++) {
beverages.add(_buildIcons(i));
}
return Flexible(
child: Container(
padding: EdgeInsets.all(10.0),
child: new Stack(
alignment: AlignmentDirectional.center,
children: <Widget>[
_drawCircle(outerRadius, Color.fromRGBO(255, 255, 255, 0.3)),
_drawCircle(outerRadius - 25, Color.fromRGBO(255, 255, 255, 0.2)),
new CustomMultiChildLayout(
delegate: new _CircularLayoutDelegate(
itemCount: items.length,
radius: outerRadius / 2,
),
children: beverages,
),
GestureDetector(
onTap: () {
Navigator.of(context).pushNamed("/ask/capture");
},
child: Image.asset(
"images/earth-globe.png",
width: innerWhiteRadius,
height: innerWhiteRadius,
fit: BoxFit.cover,
)
),
],
),
),
);
}
// Draw a circle with given radius and color.
Widget _drawCircle(double radius, Color color) {
return new Container(
decoration: new BoxDecoration(shape: BoxShape.circle, color: color),
width: radius,
height: radius,
);
}
Widget _buildIcons(int index) {
final Widget item = items[index];
return new LayoutId(
id: 'BUTTON$index',
child: item,
);
}
}
double _calculateItemAngle(int index) {
double _itemSpacing = 360.0 / 5.0;
return _startAngle + index * _itemSpacing * _radiansPerDegree;
}
class _CircularLayoutDelegate extends MultiChildLayoutDelegate {
static const String actionButton = 'BUTTON';
final int itemCount;
final double radius;
_CircularLayoutDelegate({
#required this.itemCount,
#required this.radius,
});
Offset center;
#override
void performLayout(Size size) {
center = new Offset(size.width / 2, size.height / 2);
for (int i = 0; i < itemCount; i++) {
final String actionButtonId = '$actionButton$i';
if (hasChild(actionButtonId)) {
final Size buttonSize =
layoutChild(actionButtonId, new BoxConstraints.loose(size));
final double itemAngle = _calculateItemAngle(i);
positionChild(
actionButtonId,
new Offset(
(center.dx - buttonSize.width / 2) + (radius) * Math.cos(itemAngle),
(center.dy - buttonSize.height / 2) +
(radius) * Math.sin(itemAngle),
),
);
}
}
}
#override
bool shouldRelayout(_CircularLayoutDelegate oldDelegate) =>
itemCount != oldDelegate.itemCount ||
radius != oldDelegate.radius ;
}
I'm trying to achieve this UI in the image above using an implementation i found out but to no avail. Below is the image of what i want to achieve and the current code i have. If there is a best and efficient way to achieve this, i will be ver glad if i am pointed in that direction.
I didn't change the code much, but I think I got the look of the UI you wanted. Check it out ;)
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'dart:math' as Math;
const double _radiansPerDegree = Math.pi / 180;
final double _startAngle = -90.0 * _radiansPerDegree;
typedef double ItemAngleCalculator(int index);
class HomePage extends StatefulWidget {
#override
State createState() {
return new _HomePageState();
}
}
class _HomePageState extends State<HomePage> with TickerProviderStateMixin {
final List<Widget> items = [
new Container(
decoration: new BoxDecoration(
shape: BoxShape.circle,
color: Colors.white,
),
padding: EdgeInsets.all(10),
child: new MaterialButton(
onPressed: () {},
child: new Image.asset(
'assets/images/ball1.png',
width: 60.0,
height: 60.0,
fit: BoxFit.cover,
),
),
),
Container(
decoration: new BoxDecoration(
shape: BoxShape.circle,
color: Colors.white,
),
padding: EdgeInsets.all(10),
child: new FlatButton(
onPressed: () {},
child: new Image.asset(
'assets/images/ball1.png',
width: 60.0,
height: 60.0,
fit: BoxFit.cover,
),
),
),
Container(
decoration: new BoxDecoration(
shape: BoxShape.circle,
color: Colors.white,
),
padding: EdgeInsets.all(10),
child: new FlatButton(
onPressed: () {},
child: new Image.asset(
'assets/images/ball1.png',
width: 60.0,
height: 60.0,
fit: BoxFit.cover,
),
),
),
Container(
decoration: new BoxDecoration(
shape: BoxShape.circle,
color: Colors.white,
),
padding: EdgeInsets.all(10),
child: new FlatButton(
onPressed: () {},
child: new Image.asset(
'assets/images/ball1.png',
width: 60.0,
height: 60.0,
fit: BoxFit.cover,
),
),
),
Container(
decoration: new BoxDecoration(
shape: BoxShape.circle,
color: Colors.white,
),
padding: EdgeInsets.all(10),
child: new FlatButton(
onPressed: () {},
child: new Image.asset(
'assets/images/ball1.png',
width: 60.0,
height: 60.0,
fit: BoxFit.cover,
),
),
)
];
#override
Widget build(BuildContext context) {
return _buildBody();
}
Widget _buildBody() {
return Container(
decoration: new BoxDecoration(color: Colors.white),
width: MediaQuery.of(context).size.width,
child: Padding(
padding: const EdgeInsets.all(25.0),
child: new Column(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
new Padding(
padding:
const EdgeInsets.only(top: 20.0, left: 20.0, right: 20.0),
child: new Container(
decoration: new BoxDecoration(
gradient: new LinearGradient(colors: [
new Color(0xFFA19D9A),
Colors.white,
new Color(0xFFA19D9A),
]),
borderRadius:
new BorderRadius.all(new Radius.circular(25.0))),
child: new Container(
margin: const EdgeInsets.all(5.0),
decoration: new BoxDecoration(
color: Colors.teal,
borderRadius:
new BorderRadius.all(new Radius.circular(22.0))),
child: new Padding(
padding: const EdgeInsets.all(5.0),
child: new Text(
"Get recommendations by selecting any icon",
style: new TextStyle(
fontSize: 22.0,
color: Colors.white,
fontFamily: 'CaviarDreams',
fontWeight: FontWeight.w600),
textAlign: TextAlign.center,
),
),
),
),
),
_buildStackView(),
],
),
),
);
}
Widget _buildStackView() {
final List<Widget> beverages = <Widget>[];
double width = MediaQuery.of(context).size.width;
double height = MediaQuery.of(context).size.height;
double outerRadius = Math.min(width * 3 / 4, height * 3 / 4);
double innerWhiteRadius = outerRadius * 3 / 4;
for (int i = 0; i < items.length; i++) {
beverages.add(_buildIcons(i));
}
return Flexible(
child: Container(
padding: EdgeInsets.all(10.0),
child: new Stack(
alignment: AlignmentDirectional.center,
children: <Widget>[
_drawCircle(outerRadius + 5, Colors.teal[100]),
_drawCircle(outerRadius, Colors.white),
_drawCircle(outerRadius / 2, Colors.teal[100]),
_drawCircle((outerRadius - 40) / 2, Colors.teal),
new CustomMultiChildLayout(
delegate: new _CircularLayoutDelegate(
itemCount: items.length,
radius: outerRadius / 2,
),
children: beverages,
),
GestureDetector(
onTap: () {
Navigator.of(context).pushNamed("/ask/capture");
},
child: Icon(
Icons.check,
size: 100,
color: Colors.white,
)),
],
),
),
);
}
// Draw a circle with given radius and color.
Widget _drawCircle(double outerRadius, Color color) {
return new Container(
decoration: new BoxDecoration(shape: BoxShape.circle, color: color),
width: outerRadius,
height: outerRadius,
);
}
Widget _buildIcons(int index) {
final Widget item = items[index];
return new LayoutId(
id: 'BUTTON$index',
child: item,
);
}
}
double _calculateItemAngle(int index) {
double _itemSpacing = 360.0 / 5.0;
return _startAngle + index * _itemSpacing * _radiansPerDegree;
}
class _CircularLayoutDelegate extends MultiChildLayoutDelegate {
static const String actionButton = 'BUTTON';
final int itemCount;
final double radius;
_CircularLayoutDelegate({
#required this.itemCount,
#required this.radius,
});
Offset center;
#override
void performLayout(Size size) {
center = new Offset(size.width / 2, size.height / 2);
for (int i = 0; i < itemCount; i++) {
final String actionButtonId = '$actionButton$i';
if (hasChild(actionButtonId)) {
final Size buttonSize =
layoutChild(actionButtonId, new BoxConstraints.loose(size));
final double itemAngle = _calculateItemAngle(i);
positionChild(
actionButtonId,
new Offset(
(center.dx - buttonSize.width / 2) + (radius) * Math.cos(itemAngle),
(center.dy - buttonSize.height / 2) +
(radius) * Math.sin(itemAngle),
),
);
}
}
}
#override
bool shouldRelayout(_CircularLayoutDelegate oldDelegate) =>
itemCount != oldDelegate.itemCount || radius != oldDelegate.radius;
}
And this is the final output. P.S. I didn't have that image, so I used my own.