How to change CupertinoSwitch size in flutter? - flutter

I want to change the size of the CupertinoSwitch in flutter. I have tried putting the switch in Container but changing the size of the container does not affect the switch.

You can copy paste run full code below
You can use Transform.scale and set scale, 1 means normal size, 0.8 means smaller size
code snippet
Transform.scale(
scale: 0.8,
child: CupertinoSwitch(
value: _switchValue,
onChanged: (bool value) {
setState(() {
_switchValue = value;
});
},
),
)
working demo
full code
import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: CupertinoSwitchDemo(),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
void _incrementCounter() {
setState(() {
_counter++;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'You have pushed the button this many times:',
),
Text(
'$_counter',
style: Theme.of(context).textTheme.display1,
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: Icon(Icons.add),
),
);
}
}
class CupertinoSwitchDemo extends StatefulWidget {
static const String routeName = '/cupertino/switch';
#override
_CupertinoSwitchDemoState createState() => _CupertinoSwitchDemoState();
}
class _CupertinoSwitchDemoState extends State<CupertinoSwitchDemo> {
bool _switchValue = false;
#override
Widget build(BuildContext context) {
return CupertinoPageScaffold(
navigationBar: CupertinoNavigationBar(
middle: const Text('Switch'),
// We're specifying a back label here because the previous page is a
// Material page. CupertinoPageRoutes could auto-populate these back
// labels.
previousPageTitle: 'Cupertino',
//trailing: CupertinoDemoDocumentationButton(CupertinoSwitchDemo.routeName),
),
child: DefaultTextStyle(
style: CupertinoTheme.of(context).textTheme.textStyle,
child: SafeArea(
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: <Widget>[
Semantics(
container: true,
child: Column(
children: <Widget>[
Transform.scale(
scale: 0.8,
child: CupertinoSwitch(
value: _switchValue,
onChanged: (bool value) {
setState(() {
_switchValue = value;
});
},
),
),
Text(
"Enabled - ${_switchValue ? "On" : "Off"}"
),
],
),
),
Semantics(
container: true,
child: Column(
children: const <Widget>[
CupertinoSwitch(
value: true,
onChanged: null,
),
Text(
'Disabled - On'
),
],
),
),
Semantics(
container: true,
child: Column(
children: const <Widget>[
CupertinoSwitch(
value: false,
onChanged: null,
),
Text(
'Disabled - Off'
),
],
),
),
],
),
),
),
),
);
}
}

The accepted answer solves almost everything you need. But keep in mind that, if you make your widget smaller with Transform.scale you still have the same invisible space that the widget had before scaling it. That means: even if you scale your Switch, it still occupies the original size. A workaround for that is just to wrap it with a container and give it a desired width and height.
Note: After scaling your Switch, in order to not apply the transformation when performing hit tests, set transformHitTests to false. That way you can more easily control the area where you can tap or click.
Container(
color: Colors.red,
height: 30, //set desired REAL HEIGHT
width: 35, //set desired REAL WIDTH
child: Transform.scale(
transformHitTests: false,
scale: .5,
child: CupertinoSwitch(
value: switchValue,
onChanged: (value) {
setState(() {
switchValue = value;
});
},
activeColor: Colors.green,
),
),
),

using Transform.scale is really a good way of doing it but can cause you some trouble designing or arranging widgets on the screen.
So instead, you can wrap your CupertinoSwitch in FittedBox which is inside another Container, giving you more control over your widget.
You can copy-paste the below code,
you only need to set height and width and make your FittedBox to BoxFit.contain.
Container(
height: 200.0,
width: 200.0,
child: FittedBox(
fit: BoxFit.contain,
child: CupertinoSwitch(
value: _switchValue,
onChanged: (value) {
setState(() {
_switchValue = value;
});
},
),
),
),

Related

how to dynamically build and display widgets based off a string value?

I've created an app with a large amount of buttons that update a string value.
there is a container in the middle that needs to display different widgets for each button chosen. and when a new button is pressed, the middle container will need to pop the old displaying widgets and build a new one in it's place.
I thought about layering the middle container with all the widgets to display and use the visibility widget connected to the string value using if statements to show/hide them but I don't think this is the best way to do it.
I've put together sample code below
class TestPage extends StatefulWidget {
const TestPage({Key? key}) : super(key: key);
#override
State<TestPage> createState() => _TestPageState();
}
class _TestPageState extends State<TestPage> {
String displayText = 'Choose option';
#override
Widget build(BuildContext context) {
return Scaffold(
body: Row(
children: [
Expanded(
child: Column(
children: [
SizedBox(
height: 50,
),
Button(
text: 'option A',
onTap: () {
displayText = 'Option A';
},
),
//what would I put in the onTap property to get the app to build the OneOfTheWidgetsToBuild widget in the Display Container below based off the updated string value?
SizedBox(
height: 5,
),
Button(text: 'option B', onTap: null),
],
),
),
Expanded(
child: Column(
children: [
SizedBox(
height: 50,
),
Center(
child: Container(
//DISPLAY CONTAINER
height: 40,
width: 100,
color: Colors.yellow,
child: Center(
child: Text(
'$displayText',
style: TextStyle(color: Colors.black),
),
),
),
)
],
),
),
Expanded(
child: Column(
children: [
SizedBox(
height: 50,
),
Button(text: 'option C', onTap: null),
SizedBox(
height: 5,
),
Button(text: 'option D', onTap: null),
],
),
),
],
),
);
}
}
and here is the widget to display in the middle box:
class OneOfTheWidgetsToBuild extends StatelessWidget {
const OneOfTheWidgetsToBuild({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Container(
height: 40,
width: 100,
color: Colors.green,
);
}
}
thanks so much for your help
If I understand you correctly: You want to show a bunch of different widgets based on the string's value.
One way to go about it, instead of using a bunch of if/else statements is to use a switch case and a function to correctly render the correct widgets.
For example:
Create a widget:
var _displayWidget = Container();
then, create a function to update that widget
void updateWidget(String option) {
switch (option) {
case 'Option A':
_displayWidget = widgetOne();
break;
case 'Option B':
_displayWidget = widgetTwo();
break;
default:
_displayWidget = Container();
break;
}
}
and then, in your Build method:
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Test'),
),
body: Column(
children: [
DropdownButton<String>(
items: const [
DropdownMenuItem(
child: Text('Option A'),
value: 'Option A',
),
DropdownMenuItem(
child: Text('Option B'),
value: 'Option B',
),
],
onChanged: (value) {
setState(() {
updateWidget(value!);
});
},
),
_displayWidget,
],
),
);
}

widget into List<Widget> does not update into build method even if i call setState

i have the following simple full code
import 'dart:developer';
import 'package:flutter/material.dart';
class Test extends StatefulWidget {
const Test({Key? key}) : super(key: key);
#override
State<Test> createState() => _TestState();
}
class _TestState extends State<Test> {
List myListWidget = [];
late bool isColorWhie = false;
#override
Widget build(BuildContext context) {
return GestureDetector(
onTap: (){
setState(() {
myListWidget.add(
Container(
width: 50,
height: 50,
color: isColorWhie?Colors.white:Colors.red,
)
);
});
},
child: Scaffold(
backgroundColor: Colors.green,
body: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
...myListWidget,
TextButton(
onPressed: (){
setState(() {
isColorWhie = !isColorWhie; // here never update
log('done');
});
},
child: const Text('tab to Change color',style: TextStyle(color: Colors.white),)
)
],
),
),
),
);
}
}
i tap on any point on screen to add Container into myListWidget thn call setState(() {}); to update ui.
everything fine now but when i change the isColorWhie to true it should change the color to white but it never update !
i am totally confused why it does not update ? And how could i handle with this ?
For base color change, I am using a separate button, also switching the list value.
One thing variable does update the UI, you need to handle state inside the item(state-management property) or reinitialize the variable to get update state.
class Test extends StatefulWidget {
const Test({Key? key}) : super(key: key);
#override
State<Test> createState() => _TestState();
}
class _TestState extends State<Test> {
List<bool> myListWidgetState = [];
bool isColorWhie = false;
#override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () {
setState(
() {
myListWidgetState.add(isColorWhie);
},
);
},
child: Scaffold(
backgroundColor: Colors.green,
body: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
...myListWidgetState.map(
(e) {
return Container(
width: 50,
height: 50,
color: e ? Colors.white : Colors.red,
);
},
),
TextButton(
onPressed: () {
myListWidgetState = myListWidgetState.map((e) => !e).toList();
setState(() {});
print(isColorWhie);
},
child: const Text(
'tab to Change color',
style: TextStyle(color: Colors.white),
),
),
TextButton(
onPressed: () {
setState(() {
isColorWhie = !isColorWhie;
});
print(isColorWhie);
},
child: const Text(
'tab to Change base color',
style: TextStyle(color: Colors.white),
),
),
],
),
),
),
);
}
}
Since you create a container as an object in GestureDetector and save it to your list, it will not change. It is now permanently saved (of course as long as you do not delete the element) as an entry in your list.
Your logic works exactly as you programmed it. For example, if you were to recompile the app and press the TextButton and then anywhere on your screen, a white container would also appear.
If you want to dynamically change the color of all containers at once, then you can do the following:
class _TestState extends State<Test> {
int containerCounter = 0;
late bool isColorWhie = false;
#override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () {
setState(() {
containerCounter++;
});
},
child: Scaffold(
backgroundColor: Colors.green,
body: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Container(
width: 50,
child: ListView.builder(
shrinkWrap: true,
itemCount: containerCounter,
itemBuilder: ((context, index) {
return Container(
height: 50,
color: isColorWhie ? Colors.white : Colors.red,
);
}),
),
),
TextButton(
onPressed: () {
setState(() {
isColorWhie = !isColorWhie; // here never update
});
},
child: const Text(
'tab to Change color',
style: TextStyle(color: Colors.white),
))
],
),
),
),
);
}
}

Flutter scrollable layout with dynamic child

I want to create a generic Layout which accepts a child Widget as a parameter, that lays out the content as follows:
I have an AppBar at the Top, a Title (headline), and below that the Content (could be anything). At the bottom, I have a Column with a few buttons. If the content is too big for the screen, all those widgets, except the AppBar, are scrollable. If the content fits the screen, the title and content should be aligned at the top, and the buttons at the bottom.
To showcase what I mean, I created a drawing:
It is easy to create to scrollable content functionality. But I struggle with laying out the content so that the buttons are aligned at the bottom, if the content does NOT need to be scrollable.
It is important to say that I don't know the height of the content widget or the buttons. They are dynamic and can change their height. Also, the title is optional and can have two different sizes.
What I tried is the following:
import 'package:flutter/material.dart';
class BaseScreen extends StatelessWidget {
final String? title;
final bool bigHeader;
final Widget child;
final Widget bottomButtons;
const BaseScreen({
Key? key,
required this.child,
required this.bottomButtons,
this.bigHeader = true,
this.title,
}) : super(key: key);
#override
Widget build(BuildContext context) {
final AppBar appBar = AppBar(
title: Text("AppBar"),
);
double minChildHeight = MediaQuery.of(context).size.height -
MediaQuery.of(context).viewInsets.bottom -
MediaQuery.of(context).viewInsets.top -
MediaQuery.of(context).viewPadding.bottom -
MediaQuery.of(context).viewPadding.top -
appBar.preferredSize.height;
if (title != null) {
minChildHeight -= 20;
if (bigHeader) {
minChildHeight -= bigHeaderStyle.fontSize!;
} else {
minChildHeight -= smallHeaderStyle.fontSize!;
}
}
final Widget content = Column(
mainAxisSize: MainAxisSize.min,
children: [
if (title != null)
Text(
title!,
style: bigHeader ? bigHeaderStyle : smallHeaderStyle,
textAlign: TextAlign.center,
),
if (title != null)
const SizedBox(
height: 20,
),
ConstrainedBox(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
child,
bottomButtons,
],
),
constraints: BoxConstraints(
minHeight: minChildHeight,
),
),
],
);
return Scaffold(
appBar: appBar,
body: SingleChildScrollView(
child: content,
),
);
}
TextStyle get bigHeaderStyle {
return TextStyle(fontSize: 20);
}
TextStyle get smallHeaderStyle {
return TextStyle(fontSize: 16);
}
}
The scrolling effects work perfectly, but the Buttons are not aligned at the bottom. Instead, they are aligned directly below the content. Does anyone know how I can fix this?
DartPad you can check here
customscrollview tutorial
Scaffold(
// bottomNavigationBar: ,
appBar: AppBar(
title: Text(" App Bar title ${widgets.length}"),
),
//============
body: CustomScrollView(
slivers: [
SliverFillRemaining(
hasScrollBody: false,
child: Column(
// controller: _mycontroller,
children: [
title,
...contents,
// ---------------------This give Expansion and button get down --------
Expanded(
child: Container(),
),
// ---------------------This give Expansion and button get down --------
Buttons
],
),
)
],
))
We can Achieve with the help of CustomScrollView widget and Expanded widget.here Expanded widget just expand between the widget
Sample Code
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
void main() {
runApp(
MaterialApp(debugShowCheckedModeBanner: false, home: MyApp()),
);
}
class MyApp extends StatefulWidget {
#override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
var widgets = [];
var _mycontroller = ScrollController();
#override
Widget build(BuildContext context) {
var title = Center(
child: Text(
"Scrollable title ${widgets.length}",
style: TextStyle(fontSize: 30),
));
var contents = [
...widgets,
];
var Buttons = Row(
children: [
Expanded(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Container(
height: 100,
child: ElevatedButton(
onPressed: () {
setState(() {
widgets.add(Container(
height: 100,
child: ListTile(
title: Text(widgets.length.toString()),
subtitle: Text("Contents BTN1"),
),
));
});
// _mycontroller.jumpTo(widgets.length * 100);
},
child: Text("BTN1"),
),
),
)),
Expanded(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Container(
height: 100,
child: ElevatedButton(
onPressed: () {
setState(() {
if (widgets.length > 0) {
widgets.removeLast();
}
});
// _mycontroller.jumpTo(widgets.length * 100);
},
child: Text("BTN2"),
),
),
))
],
);
return MaterialApp(
debugShowCheckedModeBanner: false,
home: SafeArea(
child: Scaffold(
// bottomNavigationBar: ,
appBar: AppBar(
title: Text(" App Bar title ${widgets.length}"),
),
body: CustomScrollView(
slivers: [
SliverFillRemaining(
hasScrollBody: false,
child: Column(
// controller: _mycontroller,
children: [
title,
...contents,
Expanded(
child: Container(),
),
Buttons
],
),
)
],
)),
),
);
}
}
Try this:
import 'package:flutter/material.dart';
void main() => runApp(const MyApp());
class MyApp extends StatelessWidget {
const MyApp();
#override
Widget build(BuildContext context) {
return MaterialApp(
home: BaseScreen(
bottomButtons: [
ElevatedButton(onPressed: () {}, child: const Text('Button 1')),
ElevatedButton(onPressed: () {}, child: const Text('Button 2')),
],
content: Container(
color: Colors.lightGreen,
height: 200,
),
title: 'Title',
),
);
}
}
class BaseScreen extends StatelessWidget {
final bool bigHeader;
final List<Widget> bottomButtons;
final String? title;
final Widget content;
const BaseScreen({
this.bigHeader = true,
required this.bottomButtons,
required this.content,
this.title,
});
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('AppBar'),
),
body: CustomScrollView(
slivers: [
SliverFillRemaining(
hasScrollBody: false,
child: Column(
children: [
if (title != null)
Padding(
padding: const EdgeInsets.symmetric(vertical: 12),
child: Text(
title!,
style: bigHeader ? _bigHeaderStyle : _smallHeaderStyle,
textAlign: TextAlign.center,
),
),
content,
const Spacer(),
...bottomButtons,
],
),
),
],
),
);
}
TextStyle get _bigHeaderStyle => const TextStyle(fontSize: 20);
TextStyle get _smallHeaderStyle => const TextStyle(fontSize: 16);
}
Screenshots:
without_scrolling
scrolled_up
scrolled_down

widget communication using void callback flutter

I have a two widget classes , one is the main. I have a class widget have a slider and I want to change a value in the main widget which its contain a row have this slider widget as a child and that text.
here is what I'm trying to do
class MyHomePage extends StatelessWidget {
final double distance ;
final VoidCallback distanceChanged;
const MyHomePage({#required this.distanceChanged,this.distance});
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Progress Widget'),
centerTitle: true,
),
body:
Column(
children: [
DistanceSlider(),
Container(height: 120,
),
Text('$distance',
),
],
),
);
}
}
and here is the slider widget class
class DistanceSlider extends StatefulWidget {
#override
_DistanceSliderState createState() => _DistanceSliderState();
}
class _DistanceSliderState extends State<DistanceSlider> {
double _currentSliderValue = 10;
#override
Widget build(BuildContext context) {
return
Container(
height: 150,
child:
Card(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(15),
),
child: Padding(
padding: EdgeInsets.all(5),
child: Column(
children: <Widget> [
Expanded(child:
Padding(padding: EdgeInsets.all(20),
child: Row(
children: [Text('Max Distance',
style: TextStyle(
fontSize: 18,
color: Colors.black26,
),
),
Spacer(),
Text(_currentSliderValue.toInt().toString()+'Kms',
style: TextStyle(
fontSize: 18,
),
),
],
),
),
),
SliderTheme(
data: SliderTheme.of(context).copyWith(
inactiveTrackColor: Colors.black12,
trackHeight: 3.0,
),
child: Slider(
value: _currentSliderValue,
min: 0,
max: 100,
divisions: 5,
// _currentSliderValue.round().toString(),
onChanged: (double value) {
setState(() {
_currentSliderValue = value;
}
);
MyHomePage(distance: value,);
},
),
),
],
),
),
),
);
}
}
how can I change the value of distance in the first class based on the slider value ?
I think what you're looking for is a state management solution. Get started here.
I tend to use value holders (if you mix them with ChangeNotifier you can easily react to changes) that I pass to child widgets.
In your case something like this:
class ValueHolder with ChangeNotifier {
double _value;
double get value => _value;
void set(double newValue) {
_value = newValue;
notifyListeners();
}
}
And then instead of declaring _currentSliderValue in the child widget, pass the ValueHolder to the child and use this instead. In the parent widget, attach a listener to the ValueHolder which calls setState on the parent (or update only the Text widget using a StreamBuilder).

Flutter - Checkbox animation doesn't show

The value is effectively changing when clicking but the animation doesn't show :
Here's my code :
var editGender = Padding(
padding: const EdgeInsets.only(top: 12.0),
child: Column(
children: <Widget>[
CheckboxListTile(
value: _male,
onChanged: _maleChanged,
title: Text("Male"),
activeColor: Theme.of(context).primaryColor,
),
CheckboxListTile(
value: _female,
onChanged: _femaleChanged,
title: Text("Female"),
activeColor: Theme.of(context).primaryColor,
)
],
),
);
When tapping the edit button :
FlatButton(
onPressed: (){
buildShowRoundedModalBottomSheet(context, title, editGender, option);
},
child: Text('Edit'),
it shows the bottom sheet :
Future buildShowRoundedModalBottomSheet(BuildContext context, String title, Widget content,[String date]) {
return showRoundedModalBottomSheet(
context: context,
radius: 20.0,
builder: (context){
return Padding(
padding: const EdgeInsets.only(top: 20.0, bottom: 20.0, left: 20.0, right: 20.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Text(
title,
style: TextStyle(
fontFamily: 'SamsungSans',
fontSize: 20.0,
),
),
content,
...
I am passing the same context to the widget :/
setState would change the value but it wouldn't rebuild your bottom sheet as it is being called on a onPressed of a FlatButton. You are certainly not invoking that onPressed again but you wouldn't want to do it either.
As I mentioned in the comments a StatefulBuilder would do the job.
A working example
import 'package:flutter/material.dart';
import 'package:rounded_modal/rounded_modal.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
bool value = false;
void _incrementCounter() {
showRoundedModalBottomSheet(
context: context,
builder: (context) {
return StatefulBuilder(builder: (context, setState) {
return Container(
height: 200.0,
child: Checkbox(value: value, onChanged: (val) {
setState(() {
value = val;
});
}),
);
});
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'You have pushed the button this many times:',
),
Text(
'$_counter',
style: Theme.of(context).textTheme.display1,
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: Icon(Icons.add),
),
);
}
}
As commented by #10101010, you'll have to use a Stateful widget. And In _femaleChanged and _maleChanged, you'll have to use setState(). Example :
void _femaleChanged(bool value) => setState(() => _female = value);