In Flutter, Slider, I want to show label instead of Values. The idea is for Search If I want to specify it at City level ( the lowest radius of search)
And next at County
Next at State level
Next at Country
and so on , last being at World Level ( the highest).
Currently Slider only displays numerical values.
Please use below code snippet
class DemoContent extends StatefulWidget {
const DemoContent({Key? key}) : super(key: key);
#override
State<DemoContent> createState() => _DemoContentState();
}
class _DemoContentState extends State<DemoContent> {
double _searchLevel = 0.0;
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Slider(
value: _searchLevel,
onChanged: (value) {
setState(() {
_searchLevel = value;
});
},
max: 4.0,
min: 0.0,
divisions: 4,
label: _getLabel(),
),
),
);
}
String _getLabel(){
String label = '';
if(_searchLevel == 0.0){
label = 'City';
}else if(_searchLevel == 1.0){
label = 'County';
}else if(_searchLevel == 2.0){
label = 'State';
} else if(_searchLevel == 3.0){
label = 'Country';
}else if(_searchLevel == 4.0){
label = 'World';
}
return label;
}
}
Related
I have built a custom slider and have been using GestureDetector with onHorizontalDragUpdate to report drag changes, update the UI and value.
However, when a user lifts their finger, there can sometimes be a small, unintentional hop/drag, enough to adjust the value on the slider and reduce accuracy. How can I stop this occuring?
I have considered adding a small delay to prevent updates if the drag hasn't moved for a tiny period and assessing the primaryDelta, but unsure if this would be fit for purpose or of there is a more routine common practive to prevent this.
--
Example of existing drag logic I am using. The initial drag data is from onHorizontalDragUpdate in _buildThumb. When the slider is rebuilt, the track size and thumb position is calculated in the LayoutBuilder and then the value is calculated based on the thumb position.
double valueForPosition({required double min, required double max}) {
double posIncrements = ((max) / (_divisions));
double posIncrement = (_thumbPosX / (posIncrements));
double incrementVal =
(increment) * (posIncrement + widget.minimumValue).round() +
(widget.minimumValue - widget.minimumValue.truncate());
return incrementVal.clamp(widget.minimumValue, widget.maximumValue);
}
double thumbPositionForValue({required double min, required double max}) {
return (max / (widget.maximumValue - widget.minimumValue - 1)) *
(value - widget.minimumValue - 1);
}
double trackWidthForValue({
required double min,
required double max,
required double thumbPosition,
}) {
return (thumbPosition + (_thumbTouchZoneWidth / 2))
.clamp(min, max)
.toDouble();
}
bool isDragging = false;
bool isSnapping = false;
Widget _buildSlider() {
return SizedBox(
height: _contentHeight,
child: LayoutBuilder(
builder: (context, constraints) {
double minThumbPosX = -(_thumbTouchZoneWidth - _thumbWidth) / 2;
double maxThumbPosX =
constraints.maxWidth - (_thumbTouchZoneWidth / 2);
if (isDragging) {
_thumbPosX = _thumbPosX.clamp(minThumbPosX, maxThumbPosX);
value = valueForPosition(min: minThumbPosX, max: maxThumbPosX);
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
widget.onChanged(value);
});
} else {
_thumbPosX = thumbPositionForValue(
min: minThumbPosX,
max: maxThumbPosX,
);
}
double minTrackWidth = 0;
double maxTrackWidth = constraints.maxWidth;
double trackWidth = 0;
if (isDragging) {
trackWidth = (_thumbPosX + (_thumbTouchZoneWidth / 2))
.clamp(_thumbWidth, constraints.maxWidth);
} else {
trackWidth = trackWidthForValue(
min: minTrackWidth,
max: maxTrackWidth,
thumbPosition: _thumbPosX,
);
}
return Stack(
alignment: Alignment.centerLeft,
clipBehavior: Clip.none,
children: [
_buildLabels(),
_buildInactiveTrack(),
Positioned(
width: trackWidth,
child: _buildActiveTrack(),
),
Positioned(
left: _thumbPosX,
child: _buildThumb(),
),
],
);
},
),
);
}
Widget _buildThumb() {
return GestureDetector(
behavior: HitTestBehavior.opaque,
dragStartBehavior: DragStartBehavior.down,
onHorizontalDragUpdate: (details) {
setState(() {
_thumbPosX += details.delta.dx;
isDragging = true;
});
},
child: // Thumb UI
);
}
Updated: I make a little adjustment by adding a delay state and lastChangedTime.
If the user stops dragging for a short period (3 sec), the slider will be locked until the next new value is updated + a short delay (1.5 sec)
I follow your train of thought and make a simple example from Slider widget.
Is the result act like your expected? (You can adjust the Duration to any number)
DartPad: https://dartpad.dev/?id=95f2bd6d004604b3c37f27dd2852cb31
class MyStatefulWidget extends StatefulWidget {
const MyStatefulWidget({super.key});
#override
State<MyStatefulWidget> createState() => _MyStatefulWidgetState();
}
class _MyStatefulWidgetState extends State<MyStatefulWidget> {
double _currentSliderValue = 20;
DateTime lastChangedTime = DateTime.now();
bool isDalying = false;
#override
Widget build(BuildContext context) {
return Column(
children: [
Text(_currentSliderValue.toString()),
const SizedBox(height: 30),
Slider(
value: _currentSliderValue,
max: 100,
label: _currentSliderValue.round().toString(),
onChanged: (double value) async {
if (isDalying) {
await Future.delayed(
Duration(milliseconds: 1500),
() => isDalying = false,
);
} else {
if (DateTime.now().difference(lastChangedTime) >
Duration(seconds: 3)) {
isDalying = true;
} else {
setState(() {
_currentSliderValue = value;
});
}
}
lastChangedTime = DateTime.now();
},
),
],
);
}
}
I want to hide completely the password field, I set obscure text true but it shows the characters as I type them how hide completely the characters?
If you want completely hide it, you must use controller and implement like this:
class TextFieldPassWord extends StatefulWidget {
#override
_TextFieldPassWordState createState() => _TextFieldPassWordState();
}
class _TextFieldPassWordState extends State<TextFieldPassWord> {
String _valueShow = "";
String _value = "";
#override
Widget build(BuildContext context) {
return Scaffold(
body: TextField(
controller: TextEditingController.fromValue(
TextEditingValue(
text: _valueShow,
selection: TextSelection.collapsed(offset: _valueShow.length),
),
),
onChanged: (String str) {
String value = "";
if (str.length > _value.length) {
value += str.substring(_value.length, str.length);
}
if (str.length < _value.length) {
value = _value.substring(1, str.length);
}
String valueToShow = "*" * str.length;
setState(() {
_valueShow = valueToShow;
_value = value;
});
},
),
);
}
}
I have a one String which contains words: achievement admission advertise pencil. I have a list with pairs of numbers:
class Pair<T1, T2> {
final T1 a;
final T2 b;
Pair(this.a, this.b);
}
String letters = "achievement admission advertise pencil";
List<Pair> words = [Pair(3, 5), Pair(6, 8), Pair(9, 11), Pair(12, 14), Pair(15, 17)];
I want to change color part of String using index from which sign to which sign. For example after 2 seconds letters from 3 to 5 should have color green. After next 2 seconds only letters from 6 to 8 should be green, after next 2 seconds only letters from 9 to 11 should be green, rest letters should return to black. There is any way to do that?
Yes, you can create a custom widget for this. Here is a working example:
(Use with SyllableText(text: letters, parts: words))
class SyllableText extends StatefulWidget {
const SyllableText({
required this.text,
required this.parts,
Key? key,
}) : super(key: key);
final String text;
final List<Pair> parts;
#override
SyllableTextState createState() => SyllableTextState();
}
class SyllableTextState extends State<SyllableText> {
int currentPartIndex = 0;
#override
void initState() {
super.initState();
Future.doWhile(() async {
await Future.delayed(Duration(seconds: 2));
if (mounted && currentPartIndex < widget.parts.length) {
setState(() => currentPartIndex++);
return true;
} else {
return false;
}
});
}
#override
Widget build(BuildContext context) {
if (currentPartIndex < widget.parts.length) {
final part = widget.parts[currentPartIndex];
final startText = widget.text.substring(0, part.a);
final coloredText = widget.text.substring(part.a, part.b + 1);
final endText = widget.text.substring(part.b + 1);
return Text.rich(
TextSpan(
children: [
TextSpan(text: startText),
TextSpan(text: coloredText, style: TextStyle(color: Colors.green)),
TextSpan(text: endText),
],
),
);
} else {
return Text(widget.text);
}
}
}
I have a simple usecase which is some how super tricky for a beginner in flutter.
I need these values returned for the scenario explained below
There are 2 containers in a row (green and orange)
OnTapDown on green container it should return ‘Green’ (this is straight forward and done)
Without lifting the finger off the screen, I drag my finger over the Orange container and I need that to return ‘Orange’
How do I solve this?
One solution could be to wrap your layout with GestureDetector and "guess" the position of your elements to then know where the drag ends.
EDIT: Adding a real check on the target position to make it more robust thanks to #GoodSp33d comment:
class DragView extends StatefulWidget {
const DragView({Key? key}) : super(key: key);
#override
_DragViewState createState() => _DragViewState();
}
GlobalKey orangeContainerKey = GlobalKey();
GlobalKey greenContainerKey = GlobalKey();
class _DragViewState extends State<DragView> {
Rect? getGlobalPaintBounds(GlobalKey element) {
final renderObject = element.currentContext!.findRenderObject();
var translation = renderObject?.getTransformTo(null).getTranslation();
if (translation != null && renderObject?.paintBounds != null) {
return renderObject?.paintBounds
.shift(Offset(translation.x, translation.y));
} else {
return null;
}
}
bool isInRect(double x, double y, Rect? rect) {
if (rect != null)
return x >= rect.left &&
x <= rect.right &&
y <= rect.bottom &&
y >= rect.top;
return false;
}
#override
Widget build(BuildContext context) {
double _cursorX = 0;
double _cursorY = 0;
return GestureDetector(
onHorizontalDragUpdate: (details) {
_cursorX = details.globalPosition.dx;
_cursorY = details.globalPosition.dy;
},
onHorizontalDragEnd: (details) {
if (isInRect(
_cursorX, _cursorY, getGlobalPaintBounds(orangeContainerKey)))
print("Orange");
if (isInRect(
_cursorX, _cursorY, getGlobalPaintBounds(greenContainerKey)))
print("Green");
},
child: Row(
children: [
Expanded(
child: Container(key: greenContainerKey, color: Colors.green),
),
Expanded(
child: Container(key: orangeContainerKey, color: Colors.orange),
),
],
),
);
}
}
Second edit moving the detection to the onDragUpdate and checks to make it happens only on rect changes:
GlobalKey? currentObject;
onHorizontalDragUpdate: (details) {
_cursorX = details.globalPosition.dx;
_cursorY = details.globalPosition.dy;
if (isInRect(
_cursorX, _cursorY, getGlobalPaintBounds(orangeContainerKey))) {
if (currentObject == null || currentObject != orangeContainerKey) {
print("Orange");
currentObject = orangeContainerKey;
}
}
if (isInRect(_cursorX, _cursorY,
getGlobalPaintBounds(greenContainerKey))) if (currentObject ==
null ||
currentObject != greenContainerKey) {
print("Green");
currentObject = greenContainerKey;
}
},
I have an array of image location strings.
I have an int called displayImage in my class, and when I initialize an instance of that class, I set the value of displayImage as 0. So I see the image corresponding to the first image in the array.
Then I call a function, which, after a timeout, changes the value of the int from 0 to 1, but I don't see the corresponding second image. The image doesn't update. How to get the image to change when the index displayImage of the array changes?
var images = ["amusement1.jpeg", "amusement2.jpg", "amusement3.png", "amusement4.jpeg",
"amusement5.jpeg", "amusement6.jpeg"];
class RandomWords extends StatefulWidget {
#override
_RandomWordsState createState() => _RandomWordsState(0);
}
class _RandomWordsState extends State<RandomWords> {
final _suggestions = <WordPair>[];
final _biggerFont = TextStyle(fontSize: 10.0);
int displayImage;
_RandomWordsState(firstImage) {
this.displayImage = firstImage;
}
Widget _buildRow(WordPair pair) {
return ListTile(
title: Text(
pair.asPascalCase,
style: _biggerFont,
)
);
}
final timeout = Duration(seconds: 3);
void handleTimeout() {
print(this.displayImage);
this.displayImage++;
print(this.displayImage);
}
switchImages () {
print("switching image");
const timeout = Duration(seconds: 3);
const ms = Duration(milliseconds: 1);
Timer startTimeout(milliseconds) {
var duration = milliseconds == null ? timeout : ms * milliseconds;
return Timer(duration, handleTimeout);
}
startTimeout(1000);
}
Widget _buildSuggestions() {
return ListView.builder(
padding: EdgeInsets.all(16.0),
itemBuilder: (context, i) {
if (i.isOdd) return Divider();
final index = i ~/ 2;
if (index >= _suggestions.length) {
_suggestions.addAll(generateWordPairs().take(10));
}
return _buildRow(_suggestions[index]);
});
}
#override
Widget build(BuildContext context) {
switchImages();
String imageUrl = "assets/amusement/" + images[displayImage];
final wordPair = WordPair.random();
return Image(image: AssetImage(imageUrl));
}
}
Try updating this:
void handleTimeout() {
print(this.displayImage);
this.displayImage++;
print(this.displayImage);
}
To this:
void handleTimeout() {
print(this.displayImage);
setState((){
this.displayImage++;
});
print(this.displayImage);
}
To make the Widget rebuild, you should trigger the change using the setState method.