Show tickmark depending on drop down value in Dart - flutter

Need to tickmark this widget depending on drop down value pending
confirmed, dispatched, recieved.
if passed pending it display pending with tick and if its confirmed on
dropdown it shows confimed with two ticks and dispatched with three ticks
and so on. Tried creating drop down which selects the all four values dont understand how
to implement tickmarks based on the text value and show that widget that I made.
Please help. Thanks.
class OrderListScreen extends StatefulWidget {
const OrderListScreen({Key? key}) : super(key: key);
#override
State<OrderListScreen> createState() => _OrderListScreenState();
}
class _OrderListScreenState extends State<OrderListScreen> {
#override
Widget build(BuildContext context) {
return Material(
child: Container(
child: Column(
children: <Widget>[
Text(
" Please select the order status from the dropdown Below:",
style: TextStyle(backgroundColor: Colors.orange)),
Container(
child: Material(
child: DropdownButton<String>(
items: <String>[
'Pending',
'Confirmed',
'Dispatched',
'Recieved'
].map((String value) {
return DropdownMenuItem<String>(
value: value,
child: Text(value),
);
}).toList(),
onChanged: (_) {},
),
)),
],
),
),
);
}
}
OrderStatus Widget (that has all dropdown values):
OrderStatusBar(title: widget.order.orderStatus, status: true),
class OrderStatusBar extends StatefulWidget {
const OrderStatusBar({Key? key, required this.title, required this.status})
: super(key: key);
final String title;
final bool status;
#override
State<OrderStatusBar> createState() => _OrderStatusBarState();
}
class _OrderStatusBarState extends State<OrderStatusBar> {
#override
Widget build(BuildContext context) {
return Directionality(
textDirection: TextDirection.rtl,
child: Row(
children: [
widget.status ? dottedCircleWithCheckMark() : dottedCircle(),
const SizedBox(width: 30),
Text(
widget.title.tr,
style: TextStyle(
fontSize: 20,
fontWeight: widget.status ? FontWeight.bold : null,
),
),
],
),
);
}
}
const size = 25.0;
const strokeWidth = 1.0;
const checkedColor = Color.fromRGBO(232, 113, 65, 1);
Widget dottedLine() {
return Directionality(
textDirection: TextDirection.rtl,
child: Align(
alignment: Alignment.topRight,
child: Container(
margin: const EdgeInsets.fromLTRB(0, 0, size / 2, 0),
child: const Padding(
padding: EdgeInsets.only(left: 27 / 2),
child: SizedBox(
height: size,
child: DottedLine(
dashColor: Colors.black,
direction: Axis.vertical,
lineLength: size,
lineThickness: strokeWidth,
dashLength: 5,
dashGapLength: 5,
),
),
),
),
),
);
}
dottedCircle() {
return DottedBorder(
borderType: BorderType.Circle,
dashPattern: const [5, 5],
child: Container(
height: size,
width: size,
decoration: const BoxDecoration(shape: BoxShape.circle),
));
}
dottedCircleWithCheckMark() {
return Container(
height: size + strokeWidth * 2,
width: size + strokeWidth * 2,
decoration: const BoxDecoration(
shape: BoxShape.circle,
color: checkedColor,
),
child: const Icon(
Icons.check,
color: Colors.white,
size: size / 4 * 3,
),
);
}

Create a callback on OrderListScreen to get selected item.
class OrderListScreen extends StatefulWidget {
final Function(String? selectedValue) callback;
const OrderListScreen({Key? key, required this.callback}) : super(key: key);
#override
State<OrderListScreen> createState() => _OrderListScreenState();
}
And get value from from onCHanged
onChanged: (v) {
widget.callback(v);
setState(() {
selectedValue = v;
});
},
Now on parent widget.
class _AppXState extends State<AppX> {
final items = <String>['Pending', 'Confirmed', 'Dispatched', 'Recieved'];
int selectedItemIndex = 0;
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: [
OrderListScreen(
callback: (selectedValue) {
if (selectedValue != null && items.contains(selectedValue)) {
selectedItemIndex = items.indexOf(selectedValue);
setState(() {});
}
},
),
for (int i = 0; i < items.length; i++)
OrderStatusBar(title: items[i], status: i <= selectedItemIndex),
],
),
);
}
}
Test snippet
class AppX extends StatefulWidget {
AppX({Key? key}) : super(key: key);
#override
State<AppX> createState() => _AppXState();
}
class _AppXState extends State<AppX> {
final items = <String>['Pending', 'Confirmed', 'Dispatched', 'Recieved'];
int selectedItemIndex = 0;
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: [
OrderListScreen(
callback: (selectedValue) {
if (selectedValue != null && items.contains(selectedValue)) {
selectedItemIndex = items.indexOf(selectedValue);
setState(() {});
}
},
),
for (int i = 0; i < items.length; i++)
OrderStatusBar(title: items[i], status: i <= selectedItemIndex),
],
),
);
}
}
class OrderListScreen extends StatefulWidget {
final Function(String? selectedValue) callback;
const OrderListScreen({Key? key, required this.callback}) : super(key: key);
#override
State<OrderListScreen> createState() => _OrderListScreenState();
}
class _OrderListScreenState extends State<OrderListScreen> {
String? selectedValue;
#override
Widget build(BuildContext context) {
return Material(
child: Container(
child: Column(
children: <Widget>[
Text(" Please select the order status from the dropdown Below:",
style: TextStyle(backgroundColor: Colors.orange)),
Container(
child: Material(
child: DropdownButton<String>(
value: selectedValue,
items: <String>[
'Pending',
'Confirmed',
'Dispatched',
'Recieved'
].map((String value) {
return DropdownMenuItem<String>(
value: value,
child: Row(
children: [
Text(value),
],
),
);
}).toList(),
onChanged: (v) {
widget.callback(v);
setState(() {
selectedValue = v;
});
},
),
)),
],
),
),
);
}
}

Related

How do I populate a variable by getting a value from a stateful widget?

I have a variable that is supposed to be populated when an if statement is true.
The if statement is supposed to be true after a value is updated from a stateful widget when pressed.
The stateful widget is _BudgetCategoryCard and it has a bool that is set true when pressed. After being pressed, the value changes and the color of the card turns green as shown in this line: color: widget.hasBeenPressed ? Colors.green : Colors.white
However, after the value of hasBeenPressed has been set true, this if statement should be true but it isn't
if (budgetCategoryCards[index].getHasBeenPressed()) {
setState(() {
selectedBudget = budgetCategoryCards[index].getBudgetName();
});
}
I'm not sure if there is a better way/practice for retrieving values from a stateful widget or if parts of this code should be re-written for improvement but if anyone could recommend changes that would also be tremendously appreciated.
I tried simplifying the code and apologies for the bad code.
Does anyone know why this variable selectedBudget is not getting populated?
main
import 'package:flutter/material.dart';
import 'package:stackoverflow/home_page.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
List<String> budgets = ["gas", "food", "clothes"];
return MaterialApp(
home: CreateExpenseCardScreen(budgetsList: budgets),
);
}
}
home page
import 'package:flutter/material.dart';
class CreateExpenseCardScreen extends StatefulWidget {
const CreateExpenseCardScreen({
Key? key,
required this.budgetsList,
}) : super(key: key);
final List<String> budgetsList;
#override
State<CreateExpenseCardScreen> createState() =>
_CreateExpenseCardScreenState();
}
class _CreateExpenseCardScreenState extends State<CreateExpenseCardScreen> {
String selectedBudget = "";
#override
Widget build(BuildContext context) {
List<_BudgetCategoryCard> budgetCategoryCards = List.generate(
widget.budgetsList.length,
(index) {
return _BudgetCategoryCard(
budgetName: widget.budgetsList[index],
hasBeenPressed: false,
);
},
);
return Scaffold(
body: SingleChildScrollView(
child: Column(
children: [
SizedBox(height: 100),
...List.generate(
budgetCategoryCards.length,
(index) {
// why is if statement never true even after pressing card?
//
// as a result, selectedBudget doesnt get assigned a value
if (budgetCategoryCards[index].getHasBeenPressed()) {
setState(() {
selectedBudget = budgetCategoryCards[index].getBudgetName();
});
}
return budgetCategoryCards[index];
},
),
Padding(
padding: const EdgeInsets.all(16.0),
child: GestureDetector(
onTap: () {
if (selectedBudget.isEmpty) {
// send error
} else {
Navigator.pop(
context,
[
selectedBudget,
],
);
}
},
child: Container(
height: 50,
color: Colors.green,
child: const Center(
child: Padding(
padding: EdgeInsets.all(8.0),
child: Text(
"Create",
style: TextStyle(
fontSize: 18,
color: Colors.white,
),
),
),
),
),
),
),
],
),
),
);
}
}
class _BudgetCategoryCard extends StatefulWidget {
_BudgetCategoryCard({
Key? key,
required this.budgetName,
required this.hasBeenPressed,
}) : super(key: key);
final String budgetName;
bool hasBeenPressed;
bool getHasBeenPressed() {
return hasBeenPressed;
}
String getBudgetName() {
return budgetName;
}
#override
State<_BudgetCategoryCard> createState() => _BudgetCategoryCardState();
}
class _BudgetCategoryCardState extends State<_BudgetCategoryCard> {
#override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 8.0, horizontal: 16.0),
child: GestureDetector(
onTap: () {
setState(() {
widget.hasBeenPressed = true;
});
},
child: Container(
height: 50,
color: widget.hasBeenPressed ? Colors.green : Colors.white,
child: Center(
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Text(
widget.budgetName,
style: TextStyle(
color: widget.hasBeenPressed
? Colors.white
: Colors.black.withOpacity(0.5),
),
),
),
),
),
),
);
}
}

How to create a roller like animation? Similar to slot machine

I want to create a custom animation, basically something like a slot machine, but I want the user to be able to move up/down each block with letters by his finger, not spin automatically.
I tried looking into this library: https://pub.dev/packages/roll_slot_machine/example but doesn't seem to fit my needs. Can I get any hints how to approach this?
What I am trying to achieve:
Using CupertinoPicker can archive similar like this. You can use Stack for background selection, Container's decoration to improve view.
Run on dartPad
class CustomRollStateMachine extends StatefulWidget {
const CustomRollStateMachine({Key? key}) : super(key: key);
#override
_CustomRollStateMachineState createState() => _CustomRollStateMachineState();
}
class _CustomRollStateMachineState extends State<CustomRollStateMachine> {
int aCode = 'A'.codeUnitAt(0);
int zCode = 'Z'.codeUnitAt(0);
late List<String> alphabets = List<String>.generate(
zCode - aCode + 1,
(index) => String.fromCharCode(aCode + index),
);
List<String?> result = List.filled(4, null);
#override
Widget build(BuildContext context) {
return Column(
children: [
SizedBox(
height: 124,
child: Row(
children: [
Expanded(
child: RollerSlot(
callback: (p0) {
setState(() {
result[0] = p0;
});
},
data: alphabets,
),
),
Expanded(
child: RollerSlot(
callback: (p0) {
setState(() {
result[1] = p0;
});
},
data: alphabets,
),
),
Expanded(
child: RollerSlot(
callback: (p0) {
setState(() {
result[2] = p0;
});
},
data: alphabets,
),
),
Expanded(
child: RollerSlot(
callback: (p0) {
setState(() {
result[3] = p0;
});
},
data: alphabets,
),
),
],
),
),
Text("Result: ${result.join()}")
],
);
}
}
class RollerSlot extends StatelessWidget {
const RollerSlot({
Key? key,
required this.data,
required this.callback,
}) : super(key: key);
final List<String> data;
final Function(String) callback;
#override
Widget build(BuildContext context) {
return Container(
margin: const EdgeInsets.all(8.0),
decoration: BoxDecoration(
border: Border.all(
color: Colors.black,
),
),
child: CupertinoPicker(
itemExtent: 24.0,
onSelectedItemChanged: (value) => callback(data[value]),
children: data
.map(
(e) => Text(e),
)
.toList()),
);
}
}

Change color back after setState

I have column of InkWells. When a button is pressed, it changes color to red from white. The problem is, when another button is pressed, the previous button doesn't change back to white.
How can I solve this?
class NavigationButton extends StatefulWidget {
const NavigationButton({
Key key,
this.title,
}) : super(key: key);
final String title;
#override
_NavigationButtonState createState() => _NavigationButtonState();
}
class _NavigationButtonState extends State<NavigationButton> {
Color _color = Colors.white;
#override
Widget build(BuildContext context) {
return Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
InkWell(
highlightColor: Colors.transparent,
splashColor: Colors.transparent,
child: Container(
child: Text(
widget.title,
style: TextStyle(color: Colors.white, fontSize: 25)
)
),
onTap: () {
setState(() {
_color = Colors.red;
});
}
),
SizedBox(width: 10),
Container(
width: 10,
height: 10,
decoration: BoxDecoration(color: _color, shape: BoxShape.circle)
)
]
);
}
}
The menu wuth navigation buttons:
child: Container(
height: 205,
width: 210,
padding: EdgeInsets.only(right: 10),
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
for (int index = 0; index < _sectionsName.length; index++)
NavigationButton(title: _sectionsName[index], index: index)
]
)
)
Thanks in advance...
can be solved by taking a boolean variable isSeleted;
For the current situation you will have to take bool in the widget where you have defined the NavigationButton List and wrap navigationButton with gesturedetector.
import 'package:flutter/material.dart';
class NavigationButton extends StatefulWidget {
final buttonColor;
final textColor;
final String title;
const NavigationButton({
Key key,
this.title,
this.buttonColor,
this.textColor,
}) : super(key: key);
#override
_NavigationButtonState createState() => _NavigationButtonState();
}
class _NavigationButtonState extends State<NavigationButton> {
#override
Widget build(BuildContext context) {
return Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
SizedBox(width: 10),
Container(
decoration: BoxDecoration(
color: widget.buttonColor,
),
child: Container(
child: Text(
widget.title,
style: TextStyle(
color: widget.textColor,
fontSize: 25,
),
),
),
)
],
);
}
}
class MainScreen extends StatefulWidget {
const MainScreen({Key key}) : super(key: key);
#override
_MainScreenState createState() => _MainScreenState();
}
class _MainScreenState extends State<MainScreen> {
// List isSelected = [];
Map _sectionsName = {"one ": false, "two": false};
#override
void initState() {
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
height: 205,
width: 210,
padding: EdgeInsets.only(right: 10),
child: ListView(
children: _sectionsName.keys
.map<Widget>((item) => GestureDetector(
onTap: () {
_sectionsName.forEach((key, value) {
if (key == item) {
setState(() {
_sectionsName[key] = true;
});
}else{
_sectionsName[key] = false;
}
});
},
child: NavigationButton(
title: item,
buttonColor: _sectionsName[item] == true
? Colors.red
: Colors.white,
textColor: _sectionsName[item] == true
? Colors.white
: Colors.black,
),
))
.toList(),
),
),
);
}
}

How can I add another part to this page and make it so when I press on one question it opens up a different set of questions? Complex UI

Marcinus created this:
https://user-images.githubusercontent.com/16286046/72802272-afed4e00-3c4b-11ea-80f7-e98717babbb2.gif
I want to add another page to the sets of questions and make it so that when I pick a certain question, it opens up a certain question. Im quite new to flutter so any help would be appreciated
import 'package:flutter/material.dart';
void main() {
runApp(MaterialApp(
theme: ThemeData(primarySwatch: Colors.blue, brightness: Brightness.dark),
home: FlightsStepper(),
));
}
class FlightsStepper extends StatefulWidget {
#override
_FlightsStepperState createState() => _FlightsStepperState();
}
class _FlightsStepperState extends State<FlightsStepper> {
int pageNumber = 1;
#override
Widget build(BuildContext context) {
Widget page = pageNumber == 1
? Page(
key: Key('page1'),
onOptionSelected: () => setState(() => pageNumber = 2),
question:
'Do you typically fly for business, personal reasons, or some other reason?',
answers: <String>['Business', 'Personal', 'Others'],
number: 1,
)
: Page(
key: Key('page2'),
onOptionSelected: () => setState(() => pageNumber = 1),
question: 'How many hours is your average flight?',
answers: <String>[
'Less than two hours',
'More than two but less than five hours',
'Others'
],
number: 2,
);
return Scaffold(
body: Container(
width: double.infinity,
height: double.infinity,
decoration: backgroundDecoration,
child: SafeArea(
child: Stack(
children: <Widget>[
ArrowIcons(),
Plane(),
Line(),
Positioned.fill(
left: 32.0 + 8,
child: AnimatedSwitcher(
child: page,
duration: Duration(milliseconds: 250),
),
),
],
),
),
),
);
}
}
class Line extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Positioned(
left: 32.0 + 32 + 8,
top: 40,
bottom: 0,
width: 1,
child: Container(color: Colors.white.withOpacity(0.5)),
);
}
}
class Page extends StatefulWidget {
final int number;
final String question;
final List<String> answers;
final VoidCallback onOptionSelected;
const Page(
{Key key,
#required this.onOptionSelected,
#required this.number,
#required this.question,
#required this.answers})
: super(key: key);
#override
_PageState createState() => _PageState();
}
class _PageState extends State<Page> with SingleTickerProviderStateMixin {
List<GlobalKey<_ItemFaderState>> keys;
int selectedOptionKeyIndex;
AnimationController _animationController;
#override
void initState() {
super.initState();
keys = List.generate(
2 + widget.answers.length,
(_) => GlobalKey<_ItemFaderState>(),
);
_animationController = AnimationController(
vsync: this,
duration: Duration(milliseconds: 500),
);
onInit();
}
Future<void> animateDot(Offset startOffset) async {
OverlayEntry entry = OverlayEntry(
builder: (context) {
double minTop = MediaQuery.of(context).padding.top + 52;
return AnimatedBuilder(
animation: _animationController,
builder: (context, child) {
return Positioned(
left: 26.0 + 32 + 8,
top: minTop +
(startOffset.dy - minTop) * (1 - _animationController.value),
child: child,
);
},
child: Dot(),
);
},
);
Overlay.of(context).insert(entry);
await _animationController.forward(from: 0);
entry.remove();
}
#override
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
SizedBox(height: 32),
ItemFader(key: keys[0], child: StepNumber(number: widget.number)),
ItemFader(
key: keys[1],
child: StepQuestion(question: widget.question),
),
Spacer(),
...widget.answers.map((String answer) {
int answerIndex = widget.answers.indexOf(answer);
int keyIndex = answerIndex + 2;
return ItemFader(
key: keys[keyIndex],
child: OptionItem(
name: answer,
onTap: (offset) => onTap(keyIndex, offset),
showDot: selectedOptionKeyIndex != keyIndex,
),
);
}),
SizedBox(height: 64),
],
);
}
void onTap(int keyIndex, Offset offset) async {
for (GlobalKey<_ItemFaderState> key in keys) {
await Future.delayed(Duration(milliseconds: 40));
key.currentState.hide();
if (keys.indexOf(key) == keyIndex) {
setState(() => selectedOptionKeyIndex = keyIndex);
animateDot(offset).then((_) => widget.onOptionSelected());
}
}
}
void onInit() async {
for (GlobalKey<_ItemFaderState> key in keys) {
await Future.delayed(Duration(milliseconds: 40));
key.currentState.show();
}
}
}
class StepNumber extends StatelessWidget {
final int number;
const StepNumber({Key key, #required this.number}) : super(key: key);
#override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.only(left: 64, right: 16),
child: Text(
'0$number',
style: TextStyle(
fontSize: 64,
fontWeight: FontWeight.bold,
color: Colors.white.withOpacity(0.5),
),
),
);
}
}
class StepQuestion extends StatelessWidget {
final String question;
const StepQuestion({Key key, #required this.question}) : super(key: key);
#override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.only(left: 64, right: 16),
child: Text(
question,
style: TextStyle(fontSize: 24),
),
);
}
}
class OptionItem extends StatefulWidget {
final String name;
final void Function(Offset dotOffset) onTap;
final bool showDot;
const OptionItem(
{Key key, #required this.name, #required this.onTap, this.showDot = true})
: super(key: key);
#override
_OptionItemState createState() => _OptionItemState();
}
class _OptionItemState extends State<OptionItem> {
#override
Widget build(BuildContext context) {
return InkWell(
onTap: () {
RenderBox object = context.findRenderObject();
Offset globalPosition = object.localToGlobal(Offset.zero);
widget.onTap(globalPosition);
},
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 16),
child: Row(
children: <Widget>[
SizedBox(width: 26),
Dot(visible: widget.showDot),
SizedBox(width: 26),
Expanded(
child: Text(
widget.name,
style: TextStyle(fontWeight: FontWeight.bold, fontSize: 26),
),
)
],
),
),
);
}
}
class ItemFader extends StatefulWidget {
final Widget child;
const ItemFader({Key key, #required this.child}) : super(key: key);
#override
_ItemFaderState createState() => _ItemFaderState();
}
class _ItemFaderState extends State<ItemFader>
with SingleTickerProviderStateMixin {
//1 means its below, -1 means its above
int position = 1;
AnimationController _animationController;
Animation _animation;
void show() {
setState(() => position = 1);
_animationController.forward();
}
void hide() {
setState(() => position = -1);
_animationController.reverse();
}
#override
void initState() {
super.initState();
_animationController = AnimationController(
vsync: this,
duration: Duration(milliseconds: 600),
);
_animation = CurvedAnimation(
parent: _animationController,
curve: Curves.easeInOut,
);
}
#override
void dispose() {
_animationController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: _animation,
builder: (context, child) {
return Transform.translate(
offset: Offset(0, 64 * position * (1 - _animation.value)),
child: Opacity(
opacity: _animation.value,
child: child,
),
);
},
child: widget.child,
);
}
}
class Dot extends StatelessWidget {
final bool visible;
const Dot({Key key, this.visible = true}) : super(key: key);
#override
Widget build(BuildContext context) {
return Container(
width: 12,
height: 12,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: visible ? Colors.white : Colors.transparent,
),
);
}
}
class ArrowIcons extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Positioned(
left: 8,
bottom: 0,
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
IconButton(
icon: Icon(Icons.arrow_upward),
onPressed: () {},
),
Container(
decoration: BoxDecoration(
shape: BoxShape.circle,
color: Colors.white,
),
child: IconButton(
color: Color.fromRGBO(120, 58, 183, 1),
icon: Icon(Icons.arrow_downward),
onPressed: () {},
),
),
],
),
);
}
}
class Plane extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Positioned(
left: 32.0 + 8,
top: 32,
child: RotatedBox(
quarterTurns: 2,
child: Icon(
Icons.airplanemode_active,
size: 64,
),
),
);
}
}
const backgroundDecoration = BoxDecoration(
gradient: LinearGradient(
colors: [
Color.fromRGBO(76, 61, 243, 1),
Color.fromRGBO(120, 58, 183, 1),
],
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
),
);
Navigator push() and pop() could be what you are looking for if you want to open a new page, pass the particular questions data as arguments
Flutter Official Doc

Flutter : Custom Radio Button

How can I create a custom radio button group like this in flutter
Here is the full code
class CustomRadio extends StatefulWidget {
#override
createState() {
return new CustomRadioState();
}
}
class CustomRadioState extends State<CustomRadio> {
List<RadioModel> sampleData = new List<RadioModel>();
#override
void initState() {
// TODO: implement initState
super.initState();
sampleData.add(new RadioModel(false, 'A', 'April 18'));
sampleData.add(new RadioModel(false, 'B', 'April 17'));
sampleData.add(new RadioModel(false, 'C', 'April 16'));
sampleData.add(new RadioModel(false, 'D', 'April 15'));
}
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text("ListItem"),
),
body: new ListView.builder(
itemCount: sampleData.length,
itemBuilder: (BuildContext context, int index) {
return new InkWell(
//highlightColor: Colors.red,
splashColor: Colors.blueAccent,
onTap: () {
setState(() {
sampleData.forEach((element) => element.isSelected = false);
sampleData[index].isSelected = true;
});
},
child: new RadioItem(sampleData[index]),
);
},
),
);
}
}
class RadioItem extends StatelessWidget {
final RadioModel _item;
RadioItem(this._item);
#override
Widget build(BuildContext context) {
return new Container(
margin: new EdgeInsets.all(15.0),
child: new Row(
mainAxisSize: MainAxisSize.max,
children: <Widget>[
new Container(
height: 50.0,
width: 50.0,
child: new Center(
child: new Text(_item.buttonText,
style: new TextStyle(
color:
_item.isSelected ? Colors.white : Colors.black,
//fontWeight: FontWeight.bold,
fontSize: 18.0)),
),
decoration: new BoxDecoration(
color: _item.isSelected
? Colors.blueAccent
: Colors.transparent,
border: new Border.all(
width: 1.0,
color: _item.isSelected
? Colors.blueAccent
: Colors.grey),
borderRadius: const BorderRadius.all(const Radius.circular(2.0)),
),
),
new Container(
margin: new EdgeInsets.only(left: 10.0),
child: new Text(_item.text),
)
],
),
);
}
}
class RadioModel {
bool isSelected;
final String buttonText;
final String text;
RadioModel(this.isSelected, this.buttonText, this.text);
}
To use :
void main() {
runApp(new MaterialApp(
home: new CustomRadio(),
));
}
Screenshot :
Screenshot (Null safe)
Full code:
Create this custom class.
class MyRadioListTile<T> extends StatelessWidget {
final T value;
final T groupValue;
final String leading;
final Widget? title;
final ValueChanged<T?> onChanged;
const MyRadioListTile({
required this.value,
required this.groupValue,
required this.onChanged,
required this.leading,
this.title,
});
#override
Widget build(BuildContext context) {
final title = this.title;
return InkWell(
onTap: () => onChanged(value),
child: Container(
height: 56,
padding: EdgeInsets.symmetric(horizontal: 16),
child: Row(
children: [
_customRadioButton,
SizedBox(width: 12),
if (title != null) title,
],
),
),
);
}
Widget get _customRadioButton {
final isSelected = value == groupValue;
return Container(
padding: EdgeInsets.symmetric(horizontal: 12, vertical: 8),
decoration: BoxDecoration(
color: isSelected ? Colors.blue : null,
borderRadius: BorderRadius.circular(4),
border: Border.all(
color: isSelected ? Colors.blue : Colors.grey[300]!,
width: 2,
),
),
child: Text(
leading,
style: TextStyle(
color: isSelected ? Colors.white : Colors.grey[600]!,
fontWeight: FontWeight.bold,
fontSize: 18,
),
),
);
}
}
Use it in your widget like a regular RadioListTile.
class _MyPageState extends State<MyPage> {
int _value = 1;
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: [
MyRadioListTile<int>(
value: 1,
groupValue: _value,
leading: 'A',
title: Text('One'),
onChanged: (value) => setState(() => _value = value!),
),
MyRadioListTile<int>(
value: 2,
groupValue: _value,
leading: 'B',
title: Text('Two'),
onChanged: (value) => setState(() => _value = value!),
),
MyRadioListTile<int>(
value: 3,
groupValue: _value,
leading: 'C',
title: Text('Three'),
onChanged: (value) => setState(() => _value = value!),
),
],
),
);
}
}
I achieved that with the following logic.
reply if you need a detailed explanation
import 'package:flutter/material.dart';
class Parent extends StatefulWidget {
Parent({
Key key,
}) : super(key: key);
#override
_ParentState createState() => _ParentState();
}
class _ParentState extends State<Parent> {
int _selectedItem = 0;
selectItem(index) {
setState(() {
_selectedItem = index;
print(selectItem.toString());
});
}
#override
Widget build(BuildContext context) {
//...YOUR WIDGET TREE HERE
return ListView.builder(
shrinkWrap: true,
itemCount: 5,
itemBuilder: (context, index) {
return CustomItem(
selectItem, // callback function, setstate for parent
index: index,
isSelected: _selectedItem == index,
title: index.toString(),
);
},
);
}
}
class CustomItem extends StatefulWidget {
final String title;
final int index;
final bool isSelected;
Function(int) selectItem;
CustomItem(
this.selectItem, {
Key key,
this.title,
this.index,
this.isSelected,
}) : super(key: key);
_CustomItemState createState() => _CustomItemState();
}
class _CustomItemState extends State<CustomItem> {
#override
Widget build(BuildContext context) {
return Row(
children: <Widget>[
Text("${widget.isSelected ? "true" : "false"}"),
RaisedButton(
onPressed: () {
widget.selectItem(widget.index);
},
child: Text("${widget.title}"),
)
],
);
}
}
You can create it with ListView and List Item with one local variable to store the selected item. And you can render the selected the ListItem based on the variable.
P.S. Let me know if you need code snippet.
[EDIT]
As you have requested, Here is code snipper which will show you how you can maintain the state of each ListView item.
Now you can play with it and make it the way you want. If you want only one selected item you can write the logic that way.
void main() {
runApp(new MaterialApp(
home: new ListItemDemo(),
));
}
class ListItemDemo extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text("ListItem"),
),
body: new ListView.builder(
itemCount: 10,
itemBuilder: (BuildContext context, int index) {
return new MyListItem(
title: "Hello ${index + 1}",
);
}),
);
}
}
class MyListItem extends StatefulWidget {
final String title;
MyListItem({this.title});
#override
_MyListItemState createState() => new _MyListItemState();
}
class _MyListItemState extends State<MyListItem> {
bool isSelected;
#override
void initState() {
super.initState();
isSelected = false;
}
#override
Widget build(BuildContext context) {
return new Row(
children: <Widget>[
new Text("${widget.title} ${isSelected ? "true" : "false"}"),
new RaisedButton(
onPressed: () {
if (isSelected) {
setState(() {
isSelected = false;
});
} else {
setState(() {
isSelected = true;
});
}
},
child: new Text("Select"),
)
],
);
}
}
https://i.stack.imgur.com/Hq0O2.png
Here is Custom Radio Group Button Widget. You can easily customize all property as per your requirement. Use:
GroupRadioButton(
label: [Text("A"), Text("B"), Text("C"), Text("D")],
padding: EdgeInsets.symmetric(vertical: 10),
spaceBetween: 5,
radioRadius: 10,
color: Const.mainColor,
onChanged: (listIndex) {
print(listIndex);
},
),
This is GroupRadioButton widget
import 'package:flutter/material.dart';
class GroupRadioButton extends StatefulWidget {
GroupRadioButton({
#required this.label,
#required this.padding,
#required this.onChanged,
this.color = Colors.blue,
this.radioRadius = 14.0,
this.spaceBetween = 5.0,
this.mainAxisAlignment = MainAxisAlignment.start,
this.crossAxisAlignment = CrossAxisAlignment.start,
});
final Color color;
final List<Widget> label;
final EdgeInsets padding;
final Function(int) onChanged;
final double radioRadius;
final double spaceBetween;
final MainAxisAlignment mainAxisAlignment;
final CrossAxisAlignment crossAxisAlignment;
#override
_GroupRadioButtonState createState() => _GroupRadioButtonState();
}
class _GroupRadioButtonState extends State<GroupRadioButton> {
int selectedIndex = 0;
#override
Widget build(BuildContext context) {
return ListView.builder(
shrinkWrap: true,
itemCount: widget.label != null ? widget.label.length : 0,
itemBuilder: (context, index) {
return LabeledRadio(
selectedIndex: selectedIndex,
color: widget.color,
onChanged: (value) {
setState(() {
selectedIndex = value;
widget.onChanged(value);
// print(value);
});
},
index: index,
label: widget.label[index],
crossAxisAlignment: widget.crossAxisAlignment,
mainAxisAlignment: widget.mainAxisAlignment,
radioRadius: widget.radioRadius,
spaceBetween: widget.spaceBetween,
padding: widget.padding,
);
});
}
}
class LabeledRadio extends StatelessWidget {
LabeledRadio({
#required this.label,
#required this.index,
#required this.color,
//#required this.groupValue,
//#required this.value,
#required this.onChanged,
#required this.radioRadius,
#required this.padding,
#required this.spaceBetween,
#required this.mainAxisAlignment,
#required this.crossAxisAlignment,
this.selectedIndex,
});
final Color color;
final int selectedIndex;
final Widget label;
final index;
final EdgeInsets padding;
//final bool groupValue;
//final bool value;
final Function(int) onChanged;
final double radioRadius;
final double spaceBetween;
final MainAxisAlignment mainAxisAlignment;
final CrossAxisAlignment crossAxisAlignment;
#override
Widget build(BuildContext context) {
return InkWell(
onTap: () {
onChanged(index);
},
child: Padding(
padding: padding,
child: Row(
mainAxisAlignment: mainAxisAlignment,
crossAxisAlignment: crossAxisAlignment,
children: <Widget>[
Container(
decoration: BoxDecoration(
//color: Const.mainColor,
shape: BoxShape.circle,
border: Border.all(color: color, width: 2),
),
padding: EdgeInsets.all(2),
child: selectedIndex == index
? Container(
height: radioRadius,
width: radioRadius,
decoration: BoxDecoration(
color: color,
shape: BoxShape.circle,
),
)
: Container(
height: radioRadius,
width: radioRadius,
),
),
SizedBox(
width: spaceBetween,
),
label,
],
),
),
);
}
}
My RadioButton is like the 'Radio' widget:
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
class RadioButton<T> extends StatefulWidget {
RadioButton({
Key key,
#required this.value,
#required this.caption,
#required this.groupValue,
#required this.onChanged,
}) : assert(value != null),
assert(caption != null),
assert(groupValue != null),
assert(onChanged != null),
super(key: key);
final T value;
final T groupValue;
final String caption;
final Function onChanged;
#override
State<StatefulWidget> createState() => _RadioButtonState();
}
class _RadioButtonState extends State<RadioButton> {
#override
Widget build(BuildContext context) {
final bool selected = widget.value == widget.groupValue;
return GestureDetector(
onTap: () {
widget.onChanged(widget.value);
},
child: Container(
width: double.maxFinite,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(8),
color: selected ? Colors.red : Colors.white),
child: Padding(
padding: const EdgeInsets.all(16),
child: Text(
widget.caption,
textAlign: TextAlign.center,
style: Theme.of(context)
.textTheme
.button
.copyWith(color: selected ? Colors.white : Colors.red),
),
),
),
);
}
}
import 'package:flutter/material.dart';
class CustomRadio extends StatefulWidget {
#override
createState() {
return new CustomRadioState();
}
}
class CustomRadioState extends State<CustomRadio> {
List<RadioModel> sampleData = new List<RadioModel>();
#override
void initState() {
// TODO: implement initState
super.initState();
sampleData.add(new RadioModel(true, 'A',0xffe6194B));
sampleData.add(new RadioModel(false, 'B',0xfff58231));
sampleData.add(new RadioModel(false, 'C',0xffffe119));
sampleData.add(new RadioModel(false, 'D',0xffbfef45));
}
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text("ListItem"),
),
body: new ListView.builder(
itemCount: sampleData.length,
itemBuilder: (BuildContext context, int index) {
return new InkWell(
splashColor: Colors.blueAccent,
onTap: () {
setState(() {
sampleData.forEach((element) => element.isSelected = false);
sampleData[index].isSelected = true;
});
},
child: new RadioItem(sampleData[index]),
);
},
),
);
}
}
class RadioItem extends StatelessWidget {
final RadioModel _item;
RadioItem(this._item);
#override
Widget build(BuildContext context) {
return new Container(
margin: new EdgeInsets.all(15.0),
child: new Row(
mainAxisSize: MainAxisSize.max,
children: <Widget>[
new Container(
height: 25.0,
width: 25.0,
alignment: Alignment.center,
child:Container(
height: 15.0,
width: 15.0,
decoration: new BoxDecoration(
color:Color(_item.colorCode),
borderRadius: const BorderRadius.all(const Radius.circular(15)),
)
),
decoration: new BoxDecoration(
color: Colors.transparent,
border: new Border.all(
width: 3.0,
color: _item.isSelected
? Color(_item.colorCode)
: Colors.transparent),
borderRadius: const BorderRadius.all(const Radius.circular(25)),
),
),
new Container(
margin: new EdgeInsets.only(left: 10.0)
)
],
),
);
}
}
class RadioModel {
bool isSelected;
final String buttonText;
final int colorCode;
RadioModel(this.isSelected, this.buttonText,this.colorCode);
}
void main() {
runApp(new MaterialApp(
home: new CustomRadio(),
));
}
Click here to check out put-> Here