Related
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
anyone has an idea on how's the best way to position widgets in orbit AROUND another (circular) widget in flutter like this?
Thank you very much for any help you can give :D
You can Use Stack Widget . For your Idea here is the Example:
import 'package:flutter/material.dart';
void main() {
runApp(new MaterialApp(home: new ExampleWidget()));
}
class ExampleWidget extends StatelessWidget {
#override
Widget build(BuildContext context) {
Widget bigCircle = new Container(
width: 300.0,
height: 300.0,
decoration: new BoxDecoration(
color: Colors.orange,
shape: BoxShape.circle,
),
);
return new Material(
color: Colors.black,
child: new Center(
child: new Stack(
children: <Widget>[
bigCircle,
new Positioned(
child: new CircleButton(onTap: () => print("Cool"), iconData: Icons.favorite_border),
top: 10.0,
left: 130.0,
),
new Positioned(
child: new CircleButton(onTap: () => print("Cool"), iconData: Icons.timer),
top: 120.0,
left: 10.0,
),
new Positioned(
child: new CircleButton(onTap: () => print("Cool"), iconData: Icons.place),
top: 120.0,
right: 10.0,
),
new Positioned(
child: new CircleButton(onTap: () => print("Cool"), iconData: Icons.local_pizza),
top: 240.0,
left: 130.0,
),
new Positioned(
child: new CircleButton(onTap: () => print("Cool"), iconData: Icons.satellite),
top: 120.0,
left: 130.0,
),
],
),
),
);
}
}
class CircleButton extends StatelessWidget {
final GestureTapCallback onTap;
final IconData iconData;
const CircleButton({Key key, this.onTap, this.iconData}) : super(key: key);
#override
Widget build(BuildContext context) {
double size = 50.0;
return new InkResponse(
onTap: onTap,
child: new Container(
width: size,
height: size,
decoration: new BoxDecoration(
color: Colors.white,
shape: BoxShape.circle,
),
child: new Icon(
iconData,
color: Colors.black,
),
),
);
}
}
/// from https://gist.github.com/pskink/50554a116698f03a862a356c38b75eb3#file-rotary_dial-dart-L225
Iterable<Rect> getBounds(Rect rect, int length) sync* {
final s = Size.square(rect.shortestSide / 6.5);
final radius = (rect.shortestSide - s.shortestSide) * 0.40;
for (var i = 0; i < length; i++) {
/// distance +
final angle = i * pi / 6 + pi * .01;
final center = rect.center + Offset(cos(angle), sin(angle)) * radius;
yield Rect.fromCenter(center: center, width: s.width, height: s.height);
}
}
class Avatar3xBtnMultiChildLayoutDelegate extends MultiChildLayoutDelegate {
final double iconSize;
Avatar3xBtnMultiChildLayoutDelegate(this.iconSize);
#override
void performLayout(Size size) {
int id = 1;
getBounds(Offset.zero & size, 3).forEach(
(rect) {
layoutChild(id, BoxConstraints.tight(rect.size));
positionChild(id, rect.centerRight);
id++;
},
);
}
#override
bool shouldRelayout(
covariant Avatar3xBtnMultiChildLayoutDelegate oldDelegate) {
return true;
}
}
And using it
class CAPG extends StatefulWidget {
const CAPG({Key? key}) : super(key: key);
#override
State<CAPG> createState() => _CAPGState();
}
class _CAPGState extends State<CAPG> {
double value = 300;
final iconSize = 24.0;
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Column(
children: [
Slider(
value: value,
max: 500,
onChanged: (v) {
setState(() {
value = v;
});
}),
Container(
width: value,
height: value,
clipBehavior: Clip.none,
decoration: BoxDecoration(
color: Colors.cyanAccent.withOpacity(.2),
shape: BoxShape.circle,
border: Border.all(),
),
child: CustomMultiChildLayout(
delegate: Avatar3xBtnMultiChildLayoutDelegate(iconSize),
children: [
LayoutId(
id: 1,
child: Material(
color: Colors.purple,
shape: CircleBorder(),
child: Icon(Icons.one_k, size: iconSize),
)),
LayoutId(
id: 2,
child: Material(
color: Colors.purple,
shape: CircleBorder(),
child: Icon(Icons.two_k, size: iconSize),
)),
LayoutId(
id: 3,
child: Material(
color: Colors.purple,
shape: CircleBorder(),
child: Icon(Icons.three_k_outlined, size: iconSize),
),
),
],
),
)
],
),
),
);
}
}
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.
I need a lil help to draw dotted line between 2 points without using google map's poly lines.
I tried with canvas, it does but not exactly start under and above location.
Right now you can see 2 points later it'll be more than 2 points.
It really appreciated if anyone help me to achieve.
I think drawing is more efficient than creating more containers like the one above. If you don't want to use the library, you can use my following method, simply adding a few lines:
Create class DashedLineVerticalPainter:
class DashedLineVerticalPainter extends CustomPainter {
#override
void paint(Canvas canvas, Size size) {
double dashHeight = 5, dashSpace = 3, startY = 0;
final paint = Paint()
..color = Colors.grey[300]
..strokeWidth = size.width;
while (startY < size.height) {
canvas.drawLine(Offset(0, startY), Offset(0, startY + dashHeight), paint);
startY += dashHeight + dashSpace;
}
}
#override
bool shouldRepaint(CustomPainter oldDelegate) => false;
}
And use it:
CustomPaint(
size: Size(1, double.infinity),
painter: DashedLineVerticalPainter()
)
Result:
I have made almost same looking widget by using https://pub.dev/packages/flutter_dash, you can also customise this widget according to your style.
Here is the code,Hope it helps.
Column(children: <Widget>[
Container(
margin: EdgeInsets.only(top: 16),
height: 25,
width: 25,
decoration: BoxDecoration(
shape: BoxShape.circle,
border:
Border.all(width: 1.5, color: Colors.greenAccent)),
),
Dash(
direction: Axis.vertical,
length: 130,
dashLength: 15,
dashColor: grey),
Container(
height: 25,
width: 25,
decoration: BoxDecoration(
shape: BoxShape.rectangle,
border: Border.all(width: 2, color: red)),
child: Container(
height: 20,
),
),
],
),
class _MyWidgetState extends State<MyWidget> {
List<Model> list = [];
#override
void initState() {
super.initState();
list.add(Model("Hyderabad", Colors.red));
list.add(Model("Visakhapatnam", Colors.green));
list.add(Model("Vijayawada", Colors.blue));
}
void addNew() {
setState(() {
list.add(Model("Karnool", Colors.black));
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: Colors.black,
title:
Text('Custom Stepper', style: TextStyle(color: Colors.white)),
actions: [
IconButton(
icon: Icon(Icons.add_circle, color: Colors.white),
onPressed: addNew)
]),
body: Container(
padding: EdgeInsets.all(15),
color: Colors.white,
child: ListView.builder(
itemCount: list.length,
itemBuilder: (con, ind) {
return ind != 0
? Column(mainAxisSize: MainAxisSize.min, children: [
Row(children: [
Column(
children: List.generate(
3,
(ii) => Padding(
padding: EdgeInsets.only(
left: 10, right: 10, top: 5, bottom: 5),
child: Container(
height: 3,
width: 2,
color: Colors.grey,
)),
),
),
Expanded(
child: Container(
color: Colors.grey.withAlpha(60),
height: 0.5,
padding: EdgeInsets.only(
left: 10,
right: 20,
),
))
]),
Row(children: [
Icon(Icons.location_on, color: list[ind].color),
Text(list[ind].address,
style: TextStyle(color: list[ind].color))
])
])
: Row(children: [
Icon(Icons.location_on, color: list[ind].color),
Text(list[ind].address,
style: TextStyle(color: list[ind].color))
]);
})));
}
}
class Model {
String address;
double lat;
double long;
Color color;
//Other fields if needed....
Model(this.address, this.color);
//initialise other fields so on....
}
return Container(
child: Row(
children: <Widget>[
Column(children: <Widget>[
Icon(Icons.radio_button_checked,color: Colors.orange,)
Dash(
direction: Axis.vertical,
length: 20,
dashLength: 5,
thickness:3.0,
dashColor: Colors.grey[400]),
Icon(Icons.location_on,color: Colors.blue,)
],
),
Column(children: <Widget>[
Text("Some text"),
Divider(),
Text("Some Text")
],
),
],
)
);