how to get the argument value of a draggable in flutter dart? - flutter

I have some draggable pictures and 3 different drag targets. With each picture I have passed a position argument which stores whether the picture should be in first dragtarget or second or third. How can i access that position whenever a picture is droped on the target what is the position. is that the correct target. Thankyou.
// ignore_for_file: prefer_const_constructors, file_names, non_constant_identifier_names
import 'dart:async';
import 'package:dyslexia/pages/level2/round3/Q2spell.dart';
import 'package:dyslexia/utilities/nextButton.dart';
import 'package:flutter/material.dart';
import 'package:flutter_tts/flutter_tts.dart';
import '../../../utilities/QuestionWidget.dart';
class Q1MTP extends StatefulWidget {
const Q1MTP({Key? key}) : super(key: key);
#override
State<Q1MTP> createState() => _Q1MTPState();
}
class _Q1MTPState extends State<Q1MTP> {
String question = "Match the Picture with correct sentence?";
var changeButton = false;
var score = 0;
final FlutterTts flutterTts = FlutterTts();
// ignore: recursive_getters
speak(word) async {
await flutterTts.setLanguage("en-US");
await flutterTts.setPitch(1);
await flutterTts.setVolume(10.0);
await flutterTts.speak(word);
}
var letter = "A";
bool isPlayingMsg = false;
bool draged1 = false;
bool draged2 = false;
bool draged3 = false;
bool color = false;
bool correct = false;
var sentence1 = "The cat is on the mat";
var sentence2 = "The car is driving on the road";
var sentence3 = "The cat is running after the mouse";
double timer = 20.0;
bool canceltimer = false;
String showtimer = "30";
int pos1 = 1;
int pos2 = 2;
int pos3 = 3;
void starttimer() async {
const onesec = Duration(seconds: 1);
Timer.periodic(onesec, (Timer t) {
if (mounted) {
setState(() {
if (timer < 1) {
t.cancel();
//Navigator.of(context).pushReplacement(
// MaterialPageRoute(builder: (context) => Q11sound()));
} else if (canceltimer == true) {
t.cancel();
} else {
timer = timer - 1;
}
showtimer = timer.toString();
});
}
});
}
#override
void initState() {
starttimer();
super.initState();
}
// double line = ((timer.toDouble() - 1.0) / (20.0 - 1.0));
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
automaticallyImplyLeading: true,
backgroundColor: Colors.cyan,
title: Text("AD&DY"),
),
body: Padding(
padding: const EdgeInsets.all(14.0),
child: SingleChildScrollView(
child: Column(children: [
/*
LinearPercentIndicator(
width: MediaQuery.of(context).size.width - 60,
animation: true,
lineHeight: 20.0,
animationDuration: 2000,
percent: ((timer.toDouble() - 1.0) / (20.0 - 1.0)),
center: Text(timer.toString()),
trailing: Icon(Icons.timer),
linearStrokeCap: LinearStrokeCap.roundAll,
progressColor: Colors.greenAccent,
),
*/
SizedBox(height: MediaQuery.of(context).size.height * 0.02),
QuestionWidget(question: question),
SizedBox(height: MediaQuery.of(context).size.height * 0.05),
Divider(
thickness: 1.0,
),
SizedBox(height: MediaQuery.of(context).size.height * 0.05),
Column(
children: [
Container(
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topRight,
end: Alignment.bottomLeft,
colors: const [
Colors.cyan,
Colors.white,
],
)),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
draggablePic("assets/images/$sentence3.png", pos3),
draggablePic("assets/images/$sentence1.jpg", pos1),
draggablePic("assets/images/$sentence2.jpg", pos2),
],
),
),
SizedBox(height: MediaQuery.of(context).size.height * 0.05),
Wrap(
spacing: 10,
runSpacing: 15,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
option(sentence1),
DragTarget<Image>(
builder: (
BuildContext context,
List<dynamic> accepted,
List<dynamic> rejected,
) {
return Material(
borderRadius: BorderRadius.circular(80),
child: Container(
height: 50,
width: 50,
color: Color.fromARGB(255, 255, 255, 255),
child: Container(
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topRight,
end: Alignment.bottomLeft,
colors: const [
Colors.white,
Colors.cyan,
],
)),
)),
);
},
onAccept: (dragW) {
setState(() {
draged1 = true;
});
},
),
]),
Divider(
thickness: 3,
indent: 50,
endIndent: 50,
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
option(sentence2),
DragTarget<Image>(
builder: (
BuildContext context,
List<dynamic> accepted,
List<dynamic> rejected,
) {
return Material(
borderRadius: BorderRadius.circular(80),
child: Container(
height: 50,
width: 50,
color: Color.fromARGB(255, 255, 255, 255),
child: Container(
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topRight,
end: Alignment.bottomLeft,
colors: const [
Colors.white,
Colors.cyan,
],
)),
)),
);
},
onAccept: (dragW) {
setState(() {});
},
),
]),
Divider(
thickness: 3,
indent: 50,
endIndent: 50,
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
option(sentence3),
DragTarget<Image>(
builder: (
BuildContext context,
List<dynamic> accepted,
List<dynamic> rejected,
) {
return Material(
borderRadius: BorderRadius.circular(80),
child: Container(
height: 50,
width: 50,
color: Color.fromARGB(255, 255, 255, 255),
child: Container(
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topRight,
end: Alignment.bottomLeft,
colors: const [
Colors.white,
Colors.cyan,
],
)),
)),
);
},
onAccept: (dragW) {
setState(() {});
},
),
]),
Divider(
thickness: 3,
indent: 50,
endIndent: 50,
),
],
),
],
),
SizedBox(height: MediaQuery.of(context).size.height * 0.1),
nextButton(changeButton: changeButton, Location: Q2spell()),
]),
),
),
);
}
Widget option(sentence) {
return Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Material(
elevation: 3,
child: Container(
color: Color.fromARGB(255, 153, 209, 216),
height: MediaQuery.of(context).size.height * 0.07,
width: MediaQuery.of(context).size.width * 0.6,
child: Center(child: Text(sentence)),
),
)
],
);
}
Widget draggablePic(pic, position) {
return Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
Draggable<Image>(
// Data is the value this Draggable stores.
//data: position,
feedback: Image(
image: AssetImage(pic),
height: 90,
width: 90,
),
childWhenDragging: Material(
borderRadius: BorderRadius.circular(10),
child: Container(
height: 50.0,
width: 70.0,
color: Color.fromARGB(255, 255, 255, 255),
),
),
// onDragEnd: changecolor(),
child: Image(
image: AssetImage(pic),
height: 80,
width: 80,
))
],
);
}
}

Related

I wants to show images from server to the flutter app.I have implemented below code please guide me why its not working

In below code at the end of the code I have created one widget but when I am passing doc to the widget its breaking the code by opening box.dart and errors_patch.dart files I couldn't get where I am making mistake.In list view builder i have checked for length of file.
First I have to show icon and when tapped on Icon it should open the dialogue box with image inside it
class PanDetails extends StatefulWidget {
const PanDetails({Key? key}) : super(key: key);
#override
State<PanDetails> createState() => _PanDetailsState();
}
class _PanDetailsState extends State<PanDetails> {
TextEditingController panController = TextEditingController();
List<File?>? panDetailsImages;
List panfiles = \[\];
#override
void initState() {
init();
super.initState();
}
Future init() async {
dynamic companyId = getIt<SharedPreferences>().getString('companyId');
//final docs = DioClient().KycDetails(companyId);
dynamic responseData = await getIt<DioClient>().KycDetails(companyId);
final details = responseData\['data'\];
Pan pandetails = Pan.fromJson(details\['pan'\]);
setState(() {
List<String> panfiles = pandetails.files;
this.panfiles = panfiles;
});
print('panfiles...))))))))))))${panfiles\[0\].toString()}');
}
#override
Widget build(BuildContext context) {
return LayoutBuilder(builder: (context, constraints) {
double maxHeight = constraints.maxHeight;
double maxWidth = constraints.maxWidth;
double h1p = maxHeight * 0.01;
double h10p = maxHeight * 0.1;
double w10p = maxWidth * 0.1;
double w1p = maxWidth * 0.01;
return SafeArea(
child: Scaffold(
backgroundColor: Colours.black,
appBar: AppBar(
elevation: 0,
automaticallyImplyLeading: false,
toolbarHeight: h10p * .9,
flexibleSpace: AppbarWidget()),
body: Container(
width: maxWidth,
decoration: const BoxDecoration(
color: Colours.white,
borderRadius: BorderRadius.only(
topLeft: Radius.circular(10),
topRight: Radius.circular(10),
)),
child: ListView(children: \[
InkWell(
onTap: () {
Navigator.pop(context);
},
child: Padding(
padding: EdgeInsets.symmetric(
horizontal: w1p * 3, vertical: h1p * 3),
child: Row(
children: \[
SvgPicture.asset("assets/images/arrowLeft.svg"),
SizedBox(
width: w10p * .3,
),
const Text(
"PAN Details",
style: TextStyles.leadingText,
),
\],
),
),
),
Padding(
padding: EdgeInsets.only(
left: w1p * 6, right: w1p * 6, top: h1p * 3),
child: Container(
decoration: BoxDecoration(
boxShadow: \[
BoxShadow(
color: Color(0x26000000),
offset: Offset(0, 1),
blurRadius: 1,
spreadRadius: 0)
\],
color: Colours.paleGrey,
),
child: TextFormField(
controller: panController,
decoration: InputDecoration(
contentPadding: EdgeInsets.symmetric(
horizontal: w1p * 6, vertical: h1p * .5),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(10),
),
fillColor: Colours.paleGrey,
hintText: "PAN Number",
hintStyle: TextStyles.textStyle120,
)),
),
),
SizedBox(
height: h1p * 3,
),
Row(
mainAxisAlignment: MainAxisAlignment.start,
children: \[
DocumentUploading(
maxWidth: maxWidth,
maxHeight: maxHeight,
onFileSelection: (files) {
panDetailsImages = files;
setState(() {});
},
),
SizedBox(
width: 40,
height: 50,
child: ListView.builder(
scrollDirection: Axis.horizontal,
itemCount: panfiles.length,
itemBuilder: (context, index) {
final doc = panfiles\[index\];
// print(doc);
return GestureDetector(
child: imageDialog(doc.toString()));
},
),
//_checkController();
)
\],
),
InkWell(
onTap: () async {
Map<String, dynamic> panDetails =
await getIt<KycManager>().storePanCardDetails(
panController.text,
filePath:
panDetailsImages?.first?.path ?? "");
Fluttertoast.showToast(msg: panDetails\['msg'\]);
// ScaffoldMessenger.of(context).showSnackBar(SnackBar(
// behavior: SnackBarBehavior.floating,
// content: Text(
// panDetails\['msg'\],
// style: const TextStyle(color: Colors.green),
// )));
if (panDetails\['error'\] == false) {
Navigator.pop(context);
}
},
child: Submitbutton(
maxWidth: maxWidth,
maxHeight: maxHeight,
isKyc: true,
content: "Save & Continue",
))
\]))));
});
}
}
Widget imageDialog(path) {
return Dialog(
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: \[
Container(
width: 220,
height: 200,
child: Image.network(
'$path',
fit: BoxFit.cover,
),
),
\],
),[![enter image description here][1]][1]
);
}

Consider canceling any active work during "dispose" or using the "mounted" getter to determine if the State is still active

I have built an app that sends the live location(latitude and longitude) of a user every 5 seconds upon clicking on a button. However, when I switch to another screen using the Bottom Navigation Bar, I get a very weird error that I don't understand. Also, when I go back to the screen from where I started(the one that sends location), I get the same error and it keeps popping up on the terminal every 5 seconds instead of the location. The sending location part is basically an API where I post the latitude and longitude. Here is the error:
E/flutter (12094): [ERROR:flutter/lib/ui/ui_dart_state.cc(198)] Unhandled Exception: This widget has been unmounted, so the State no longer has a context (and should be considered defunct).
E/flutter (12094): Consider canceling any active work during "dispose" or using the "mounted" getter to determine if the State is still active.
Following are the codes for the location sending and navigation bar widgets:
Widget that sends the location. Also, clicking on the button toggles the value of the status variable between active and inactive. This changes the color of the button and also puts an end to the api call that sends the location:
class HomePageState extends State<HomePage> {
bool isLoading = true;
bool isPressed = false;
String? status;
Future<void> getStatus() async { //This method stores the status in localStorage
SharedPreferences localStorage = await SharedPreferences.getInstance();
status = localStorage.getString('loginStatus');
}
#override
void initState() {
// TODO: implement initState
// statusExec();
Provider.of<ChangeLocationProvider>(context, listen: false)
.getLocationDetails()
.then((_) {
setState(() {
isLoading = false;
});
});
getStatus();
status == 'active' ? isPressed = true : isPressed = false; //This here determines the status from the localStorage when we come back from another screen
super.initState();
}
apiCall(String execStatus) async { //This is the method that sends the location
print('Is Pressed: $isPressed');
print('execStatus: $execStatus');
Random number = Random();
Timer.periodic(const Duration(seconds: 5), (Timer t) {
if (isPressed == true) {
double latitude = Provider.of<LocationProvider>(context, listen: false)
.coorDinates['lat']; //The error points at this line
// double latitude = number.nextDouble();
double longitude = Provider.of<LocationProvider>(context, listen: false)
.coorDinates['lng'];
// double longitude = number.nextDouble();
print('Latitude LAt: $latitude');
print('Longitude Long: $longitude');
print('Status Inside: $status');
Provider.of<ChangeLocationProvider>(context, listen: false)
.postLocation(latitude, longitude, execStatus);
} else {
double latitude = Provider.of<LocationProvider>(context, listen: false)
.coorDinates['lat'];
// double latitude = number.nextDouble();
double longitude = Provider.of<LocationProvider>(context, listen: false)
.coorDinates['lng'];
Provider.of<ChangeLocationProvider>(context, listen: false)
.postLocation(latitude, longitude, execStatus);
t.cancel();
}
});
}
#override
Widget build(BuildContext context) {
final height = (MediaQuery.of(context).size.height);
final width = MediaQuery.of(context).size.width;
final tabLayout = width > 600;
final largeLayout = width > 350 && width < 600;
final provider =
Provider.of<ChangeLocationProvider>(context).locationDetails;
print('Address ${Provider.of<LocationProvider>(context).address}');
// TODO: implement build
return Scaffold(
body: Container(
width: double.infinity,
height: double.infinity,
decoration: const BoxDecoration(
image: DecorationImage(
image: AssetImage(
'assets/images/WhatsApp Image 2022-04-12 at 13.21.05.png'),
fit: BoxFit.cover)),
child: Stack(
children: [
Container(
width: double.infinity,
height: double.infinity,
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Padding(
padding: EdgeInsets.only(top: height * 0.05),
child: Text(
'Get Set Go',
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: !tabLayout && !largeLayout ? 25 : 35),
),
),
],
),
),
Positioned(
left: width * 0.25,
top: !tabLayout && !largeLayout ? height * 0.42 : height * 0.4,
right: width * 0.25,
child: InkWell(
onTap: () async {
SharedPreferences localStorage =
await SharedPreferences.getInstance();
setState(() {
isPressed = !isPressed;
status = isPressed == true ? 'active' : 'inactive';
localStorage.setString('loginStatus', status!);
print(isPressed);
print(status);
});
if (status == 'active') {
apiCall(status!);
} else {
return;
}
},
child: Container(
width: width * 0.05,
height: height * 0.15,
decoration: const BoxDecoration(
color: Colors.white,
shape: BoxShape.circle,
boxShadow: [
BoxShadow(
color: Colors.grey,
blurRadius: 10,
offset: Offset(1, 2))
]),
child: Padding(
padding: EdgeInsets.all(10.0),
child: Container(
width: width * 0.02,
height: height * 0.15,
decoration: BoxDecoration(
color: status == 'active'
? const Color.fromARGB(255, 36, 192, 41)
: Colors.red,
shape: BoxShape.circle,
),
child: Center(
child: Text(
status == 'active' ? 'Active' : 'Inactive',
style: const TextStyle(
color: Colors.white,
fontWeight: FontWeight.bold,
),
),
),
),
),
),
),
),
Positioned(
.............
],
),
));
}
}
Bottom Navigation Bar widget:
class CustomBottomNavigation extends StatefulWidget {
final bool isPressed;
CustomBottomNavigationState createState() => CustomBottomNavigationState();
CustomBottomNavigation(this.isPressed);
}
class CustomBottomNavigationState extends State<CustomBottomNavigation> {
int index = 0;
bool isLoading = true;
String? status;
bool? isButtonPressed;
#override
void initState() {
// TODO: implement initState
Provider.of<ProfileProvider>(context, listen: false).getProfile().then((_) {
setState(() {
isLoading = false;
});
});
// print('IS PRESSSSSSSED: ${widget.isPressed}');
super.initState();
}
final screens = [
HomePage(), //This is the initial page and also where the API gets called
Dashboard(),
// Notifications(),
Profile(),
PresentOrders(),
DeliveredOrders()
];
#override
Widget build(BuildContext context) {
final height = MediaQuery.of(context).size.height;
final width = MediaQuery.of(context).size.width;
final profileProvider = Provider.of<ProfileProvider>(context).profile;
// TODO: implement build
return Scaffold(
body: isLoading
? const Center(
child: CircularProgressIndicator(
color: Colors.green,
),
)
: screens[index],
extendBody: true,
bottomNavigationBar: isLoading
? const Text('....')
: Container(
height: height * 0.06,
width: double.infinity,
margin: EdgeInsets.only(bottom: height * 0.02),
child: Stack(
children: [
Center(
child: Container(
width: width * 0.75,
height: height * 0.05,
decoration: const BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.only(
bottomLeft: Radius.circular(20),
bottomRight: Radius.circular(20)),
boxShadow: [
BoxShadow(
color: Colors.grey,
blurRadius: 5,
offset: Offset(0, 2))
]),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Container(
height: double.infinity,
width: width * 0.35,
padding: EdgeInsets.only(left: width * 0.02),
child: Row(
children: [
InkWell(
onTap: () {
setState(() {
index = 3;
});
},
child: Image.asset(
'assets/images/Icon awesome-shopping-cart.png'),
),
SizedBox(width: width * 0.1),
InkWell(
onTap: () {
setState(() {
index = 1;
});
},
child: Image.asset(
'assets/images/Icon ionic-ios-settings.png'),
)
],
),
),
Container(
height: double.infinity,
width: width * 0.35,
// padding: EdgeInsets.only(right: width * 0.02),
// color: Colors.blue,
child: Row(
children: [
Padding(
padding:
EdgeInsets.only(left: width * 0.06),
child: InkWell(
onTap: () {
setState(() {
index = 4;
});
},
child: Icon(Icons.delivery_dining_rounded,
size: 35),
),
),
SizedBox(width: width * 0.1),
InkWell(
onTap: () {
setState(() {
index = 2;
});
},
child: profileProvider['data']
['profile_pic'] ==
null
? const CircleAvatar(
radius: 15,
backgroundColor: Colors.amber,
)
: CircleAvatar(
radius: 15,
backgroundImage: NetworkImage(
'http://34.100.212.22${profileProvider['data']['profile_pic']}'),
// child: ClipRRect(
// borderRadius: BorderRadius.circular(20),
// child: Image.network(
// 'http://34.100.212.22${profileProvider['data']['profile_pic']}',
// fit: BoxFit.contain,
// )),
))
],
),
)
],
),
),
),
],
),
),
floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked,
floatingActionButton: FloatingActionButton(
onPressed: () {
// Navigator.of(context).pushNamed('/home-screen');
setState(() {
index = 0;
});
},
child: Align(
alignment: Alignment.topCenter,
child: Container(
// height: height * 0.075,
// width: width * 0.8,
decoration: BoxDecoration(
color: Colors.white,
shape: BoxShape.circle,
boxShadow: const [
BoxShadow(
color: Colors.grey, blurRadius: 5, offset: Offset(0, 2))
],
border: Border.all(
color: Colors.green, width: 2, style: BorderStyle.solid)),
child: Center(
// child: Image.asset('assets/images/Icon ionic-ios-home.png')
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Container(
width: double.infinity,
height: double.infinity,
decoration: const BoxDecoration(
shape: BoxShape.circle, color: Colors.red),
),
),
),
),
),
));
}
}
Create a variable of type timer
late Timer _timer;
Assign the timer that you defined to this variable
_timer = Timer.periodic(Duration(seconds:5),(){
//Implementation of apis you already added
}
);
Now in the dispose method cancel this timer
#override
void dispose(){
_timet.cancel();//cancel the timer here
}

Flutter setState not updating var

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.

Flutter - I am looking for a way to create a circle with icons positioned evenly on it

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.

Why calling setState in a callback inside child widget, which have another setState, breaks program?

Here is a custom Switch widget I implemented using Animation.
enum SwitchType {
LockToggle, EnableToggle
}
class DiamondSwitch extends StatefulWidget {
final double width, height;
final SwitchType switchType;
final double switchThumbSize;
final VoidCallback onTapCallback;
DiamondSwitch({
key, this.width, this.height,
this.switchType, this.switchThumbSize,
#required this.onTapCallback
}) : super(key: key);
#override
_DiamondSwitchState createState() => _DiamondSwitchState(
width: width, height: height,
switchType: switchType, switchThumbSize: switchThumbSize,
onTapCallback: onTapCallback
);
}
class _DiamondSwitchState extends State<DiamondSwitch> {
final double width, height;
final int _toggleAnimationDuration = 1000;
bool _isOn = false;
final List<Color>
_darkGradientShades = <Color>[
Colors.black, Color.fromRGBO(10, 10, 10, 1.0)
],
_lightGradientShades = <Color>[
Colors.white, Color.fromRGBO(150, 150, 150, 1.0)
];
final SwitchType switchType;
final double switchThumbSize;
List<Icon> _switchIcons = new List<Icon>();
final double _switchIconSize = 35.0;
final VoidCallback onTapCallback;
_DiamondSwitchState({
this.width = 100.0, this.height = 40.0,
this.switchThumbSize = 40.0, #required this.switchType,
#required this.onTapCallback
});
#override
void initState() {
_switchIcons.addAll(
(switchType == SwitchType.LockToggle)?
<Icon>[
Icon(
Icons.lock,
color: Colors.black,
size: _switchIconSize,
),
Icon(
Icons.lock_open,
color: Colors.white,
size: _switchIconSize,
)
]
:
<Icon>[
Icon(
Icons.done,
color: Colors.black,
size: _switchIconSize,
),
Icon(
Icons.close,
color: Colors.white,
size: _switchIconSize,
)
]
);
super.initState();
}
#override
Widget build(BuildContext context) {
return AnimatedContainer(
duration: Duration(milliseconds: _toggleAnimationDuration),
width: width, height: height,
decoration: ShapeDecoration(
shape: BeveledRectangleBorder(
borderRadius: BorderRadius.circular(28.0),
side: (_isOn)?
BorderSide(
color: Color.fromRGBO(45, 45, 45, 1.0),
width: 0.5,
)
:
BorderSide.none,
),
gradient: LinearGradient(
colors: <Color>[
...((_isOn)? _darkGradientShades : _lightGradientShades)
],
begin: Alignment(1.0, -0.8), end: Alignment(-0.7, 1.0),
stops: <double>[0.4, 1.0]
),
),
alignment: Alignment(0.0, 0.0),
child: Stack(
alignment: Alignment(0.0, 0.0),
children: <Widget>[
AnimatedPositioned(
duration: Duration(milliseconds: _toggleAnimationDuration),
curve: Curves.easeIn,
left: (_isOn)? 0.0 : ((width * 70) / 100),
right: (_isOn)? ((width * 70) / 100) : 0.0,
child: GestureDetector(
onTap: () {
onTapCallback();
setState(() {
_isOn = !_isOn;
});
},
child: AnimatedSwitcher(
duration: Duration(milliseconds: _toggleAnimationDuration),
transitionBuilder: (Widget child, Animation<double> animation) {
return FadeTransition(
opacity: animation,
child: child,
);
},
child: Transform.rotate(
alignment: Alignment.center,
angle: (math.pi / 4),
child: Container(
width: switchThumbSize, height: switchThumbSize,
alignment: Alignment.center,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(3.0),
color: (_isOn)? Colors.white : Colors.black,
border: (_isOn)?
null
:
Border.all(
color: Color.fromRGBO(87, 87, 87, 1.0),
width: 1.0,
),
),
child: Transform.rotate(
alignment: Alignment.center,
angle: -(math.pi / 4),
child: (_isOn)?
_switchIcons[0]
:
_switchIcons[1],
),
),
),
),
),
)
],
),
);
}
}
I added a onTapCallback because I need to set another flag in the parent widget to trigger a Image change. Here is related code that belongs to parent widget;
DiamondSwitch(
switchType: SwitchType.LockToggle,
width: 186.0,
height: 60.0,
switchThumbSize: 41.0,
onTapCallback: () {
this.setState(() {
this._isLockOn = !this._isLockOn;
});
},
key: UniqueKey(),
),
When I run this code, animation doesn't work. It detects tap and executes onTap callback, and all code in onTap Works, (I tested with print methods), but as I said, animation isn't happening.
I want to learn why does this happen, is this about how Flutter work? If yes, can you explain?
TY for taking time ^.^!
EDIT
I want to know why does getting a method with setState breaks the animation I'm sharing the current parent widget with the approach of the answerer #pulyaevskiy implemented.
class _SettingsState extends State<Settings> {
bool
_isLockOn = false,
_isPassiconOn = false;
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.black,
body: SafeArea(
child: Column(
children: <Widget>[
/*Lock Toggle Graphic*/
Align(
alignment: Alignment(-0.1, 0.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.end,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
/*Lock Toggle Text*/
RotatedBox(
quarterTurns: 3,
child: Column(
children: <Widget>[
Text(
(_isLockOn)? "locked" : "unlocked",
style: TextStyle(
color: Colors.white,
fontFamily: "Philosopher",
fontSize: 25.0,
shadows: <Shadow>[
Shadow(
color: Color.fromRGBO(184, 184, 184, 0.68),
offset: Offset(2.0, 2.0),
blurRadius: 4.0,
),
],
),
),
Padding(
padding: const EdgeInsets.only(top: 5.5),
child: Container(
color: Color.fromRGBO(204, 204, 204, 1.0),
width: 30.0, height: 1.0,
),
),
],
),
),
/*Lock Toggle Image*/
Image.asset(
"assets/images/settings_screen/crystal_"
"${(_isLockOn)? "white_light_up" : "black_outline"}.png",
scale: 5.0,
alignment: Alignment.center,
),
],
),
),
/*Lock Toggle Switch*/
Padding(
padding: const EdgeInsets.only(top: 12.5),
child: DiamondSwitch(
switchType: SwitchType.LockToggle,
width: 186.0,
height: 60.0,
switchThumbSize: 41.0,
flagToControl: this._isLockOn,
onTapCallback: () {
this.setState(() {
this._isLockOn = !this._isLockOn;
});
},
key: UniqueKey(),
),
),
/*Separator*/
WhiteDiamondSeparator(paddingAmount: 36.5),
/*Passicon Toggle Graphic*/
Align(
alignment: Alignment(-0.32, 0.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.end,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
/*Lock Toggle Text*/
RotatedBox(
quarterTurns: 3,
child: Column(
children: <Widget>[
Text(
"passicon",
style: TextStyle(
color: Colors.white,
fontFamily: "Philosopher",
fontSize: 25.0,
shadows: <Shadow>[
Shadow(
color: Color.fromRGBO(184, 184, 184, 0.68),
offset: Offset(2.0, 2.0),
blurRadius: 4.0,
),
],
),
),
Padding(
padding: const EdgeInsets.only(top: 5.5),
child: Container(
color: Color.fromRGBO(204, 204, 204, 1.0),
width: 30.0, height: 1.0,
),
),
],
),
),
/*Passicon Toggle Image*/
Padding(
padding: const EdgeInsets.only(left: 40),
child: Image.asset(
"assets/images/settings_screen/emote_"
"${(_isPassiconOn)? "winking" : "nervous"}.png",
scale: 3.25,
alignment: Alignment.center,
),
),
],
),
),
/*Passicon Toggle Switch*/
Padding(
padding: const EdgeInsets.only(top: 42.5),
child: DiamondSwitch(
switchType: SwitchType.PassiconToggle,
width: 186.0,
height: 60.0,
switchThumbSize: 41.0,
flagToControl: this._isPassiconOn,
onTapCallback: () {
this.setState(() {
this._isPassiconOn = !this._isPassiconOn;
});
},
key: UniqueKey(),
),
),
/*Separator*/
WhiteDiamondSeparator(paddingAmount: 36.5),
],
)
),
);
}
}
And I added flagToControl in DiamondSwitch and using it in _DiamondSwitchState as bool get _isOn => widget.flagToControl;.
In the old approach, if I don't execute and other thing than
setState() { _isOn = !_isOn; }
animation happens as it should. What am I missing?
In your onTapCallback you are changing state of your parent widget, which works indeed. However it does not affect state of the DiamondSwitch widget itself
(I see that in GestureDetector you also setState of the switch widget, but there are a few issues with this approach).
To fix this you can pass the value of this._isLockOn from your parent widget's state to the DiamondSwitch child. This means you need another property on your switch widget. E.g.
class DiamondSwitch extends StatefulWidget {
final bool isOn;
// ... remaining fields go here
DiamondSwitch({this.isOn, ...});
}
Then change _DiamondSwitchState as well. Could simply proxy _isOn to the widget's value:
class _DiamondSwitchState extends State<DiamondSwitch> {
bool get _isOn => widget.isOn;
}
This is much better than keeping state of isOn in two places as you have now (in parent widget AND in the switch itself). With this change your isLockOn state is only kept on the parent widget and you just pass it down to the switch child to use.
This means that for GestureDetector's onTap property you'll simply pass the onTapCallback of the parent widget as well, no need to wrap it with another function.
The last part: in your parent widget's build method:
DiamondSwitch(
isOn: this._isLockOn, // <-- new line here to pass the value down
switchType: SwitchType.LockToggle,
width: 186.0,
height: 60.0,
switchThumbSize: 41.0,
onTapCallback: () {
this.setState(() {
this._isLockOn = !this._isLockOn;
});
},
key: UniqueKey(),
),
Another benefit of doing it this way is that now you can initialize your switch with a different default value if needed (right now it's hardcoded to always start off as false). So if you load value of isLockOn from a database and it's set to true you can immediately pass this value to the switch child and represent your state correctly.