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),
),
),
],
),
)
],
),
),
);
}
}
Related
How do I get a draggable container like this, which when reaches a certain point gets vanished and a function could be called at that time. Also, the visibility of text is also decreasing and arrow icon is rotating according to the length. How can I achieve this functionality?
I have written a code, and I can add visibility widget for same. But, how to constraint it and what is the best way of doing it? Please help me out -
import 'package:flutter/material.dart';
class SwipeButton extends StatefulWidget {
const SwipeButton({Key? key}) : super(key: key);
#override
_SwipeButtonState createState() => _SwipeButtonState();
}
class _SwipeButtonState extends State<SwipeButton> {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Container(
color: Colors.orange,
width: MediaQuery.of(context).size.width * 0.9,
height: 50,
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 8.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Draggable<int>(
data: 10,
feedback: Container(
height: 20.0,
width: 20.0,
color: Colors.white,
child: const Center(
child: Icon(
Icons.arrow_forward,
size: 12,
),
),
),
axis: Axis.horizontal,
childWhenDragging: Container(
height: 20.0,
width: 20.0,
color: Colors.orange,
child: const Center(
child: Text(''),
),
),
child: Container(
height: 20.0,
width: 20.0,
color: Colors.white,
child: const Center(
child: Icon(
Icons.arrow_forward,
size: 12,
),
),
),
),
DragTarget<int>(
builder: (
BuildContext context,
List<dynamic> accepted,
List<dynamic> rejected,
) {
return Container(
height: 20.0,
width: 20.0,
color: Colors.orange,
child: const Center(
child: Text(''),
),
);
},
onAccept: (int data) {
setState(() {
print(data);
});
},
),
],
),
),
),
),
);
}
}
For the arrow, we can use Transform.rotate.
Transform.rotate(
angle: deg2rad(-(180 + 20) * (posX / maxWidth)), // 20 comes from icon size
child: const Icon(
Icons.arrow_forward,
size: 12,
),
),
I'm not sure why onDragUpdate is not updating the UI, you can get current position of drag from that, I store it on posX.
I am using GestureDetector for test case, apply the same logic you will get the result.
Run on dartPad
class SwipeButton extends StatefulWidget {
const SwipeButton({Key? key}) : super(key: key);
#override
_SwipeButtonState createState() => _SwipeButtonState();
}
class _SwipeButtonState extends State<SwipeButton> {
double deg2rad(double deg) => deg * pi / 180;
double posX = 0.0;
#override
Widget build(BuildContext context) {
final maxWidth = MediaQuery.of(context).size.width * 0.9;
return Scaffold(
body: Center(
child: Container(
color: Colors.orange,
width: maxWidth * 0.9,
height: 50,
child: Stack(
alignment: Alignment.center,
children: [
AnimatedPositioned(
duration: const Duration(milliseconds: 100),
left: posX,
key: const ValueKey("item 1"),
child: Container(
height: 20.0,
width: 20.0,
color: Colors.white,
alignment: Alignment.center,
child: Transform.rotate(
angle: deg2rad(-(180 + 20) * (posX / maxWidth)),
child: const Icon(
Icons.arrow_forward,
size: 12,
),
),
),
),
GestureDetector(
onPanEnd: (details) {
setState(() {
posX = 0;
});
},
onPanUpdate: (details) {
setState(() {
posX = details.localPosition.dx;
});
if (posX / maxWidth > .95) {
debugPrint("on success drag");
}
},
)
],
),
),
),
);
}
}
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.
I have been trying to make a new app being a beginner. So, adding shadows to things is completely new to me.
So, Following is my code:
Container(
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
ClipOval(
child: Material(
color: Colors.white, // button color
child: InkWell(
// splashColor: Colors.red, // inkwell color
child: SizedBox(
width: 46, height: 46, child: Icon(Icons.menu,color: Colors.red,),),
onTap: () {},
),
),
),
],
),
),
Following is the mock:
Adding shadow to ClipOval:
Center(
child: Container(
decoration: BoxDecoration(
shape: BoxShape.circle,
boxShadow: [
BoxShadow(
color: Colors.green,
blurRadius: 50.0,
spreadRadius: 10.0,
)
],
),
child: ClipOval(
child: Image.network(
'https://i.picsum.photos/id/384/536/354.jpg?hmac=MCKw0mm4RrI3IrF4QicN8divENQ0QthnQp9PVjCGblo',
width: 100,
height: 100,
fit: BoxFit.cover,
),
),
),
),
Output:
You can create your own CustomClipper
class CustomClipperOval extends CustomClipper<Rect> {
#override
Rect getClip(Size size) {
return Rect.fromCircle(
center: new Offset(size.width / 2, size.width / 2),
radius: size.width / 2 + 3);
}
#override
bool shouldReclip(CustomClipper<Rect> oldClipper) {
return false;
}
}
class ClipOvalShadow extends StatelessWidget {
final Shadow shadow;
final CustomClipper<Rect> clipper;
final Widget child;
ClipOvalShadow({
#required this.shadow,
#required this.clipper,
#required this.child,
});
#override
Widget build(BuildContext context) {
return CustomPaint(
painter: _ClipOvalShadowPainter(
clipper: this.clipper,
shadow: this.shadow,
),
child: ClipRect(child: child, clipper: this.clipper),
);
}
}
class _ClipOvalShadowPainter extends CustomPainter {
final Shadow shadow;
final CustomClipper<Rect> clipper;
_ClipOvalShadowPainter({#required this.shadow, #required this.clipper});
#override
void paint(Canvas canvas, Size size) {
var paint = shadow.toPaint();
var clipRect = clipper.getClip(size).shift(Offset(0, 0));
canvas.drawOval(clipRect, paint);
}
#override
bool shouldRepaint(CustomPainter oldDelegate) {
return true;
}
}
And then to use it
ClipOvalShadow(
shadow: Shadow(
color: Colors.amber,
offset: Offset(1.0, 1.0),
blurRadius: 2,
),
clipper: CustomClipperOval(),
child: ClipOval(
child: Material(
color: Colors.white, // button color
child: InkWell(
// splashColor: Colors.red, // inkwell color
child: Container(
width: 46,
height: 46,
child: Icon(
Icons.menu,
color: Colors.black,
),
),
onTap: () {},
),
),
),
),
The Result will be