📝 Overview:
I wanted to add this mood-tracking UI to my app, but FlutterFlow doesn't have a built-in widget that can do this. I tried using a slider inside a rotated widget and turned it to 270 degrees, but that didn't work.
So, I built my custom widget and added it to my app.
✅ What This Mood Tracker Does
This UI shows how you're feeling using vertical sliders and emoji faces. Each slider stands for a mood—Happy, Calm, Anxious, Sad, and Angry. You can move the sliders up and down to show how strong your feelings are.
The slider will also change the emoji and color based on how you're feeling. It's simple and fun to use and works well in apps for:
Relaxation 🧘
Mental health 🧠
Mood journals 📔
Your mood will be saved in the App State so the app remembers how you felt, even when you open it later. You can also change or adjust the design to match your app.
🛠️How to Set It Up
Here are the easy steps:
Make 5 App State variables
Go to App State
Add variables like
HappyEmotion
,CalmEmotion
,AnxiousEmotion
,SadEmotion
andAngryEmotion
Set the type to
Double
and make them Persistent (so they’re saved)
Create a Custom Widget
Name it MoodTrackerScreen
Paste the code inside the widget
Here is the code:
// Automatic FlutterFlow imports
import '/backend/backend.dart';
import '/backend/schema/structs/index.dart';
import '/flutter_flow/flutter_flow_theme.dart';
import '/flutter_flow/flutter_flow_util.dart';
import '/custom_code/widgets/index.dart'; // Imports other custom widgets
import '/flutter_flow/custom_functions.dart'; // Imports custom functions
import 'package:flutter/material.dart';
// Begin custom widget code
// DO NOT REMOVE OR MODIFY THE CODE ABOVE!
import 'index.dart'; // Imports other custom widgets
import 'index.dart'; // Imports other custom widgets
class MoodTrackerScreen extends StatefulWidget {
const MoodTrackerScreen({Key? key, this.width, this.height})
: super(key: key);
final double? width;
final double? height;
@override
State<MoodTrackerScreen> createState() => _MoodTrackerScreenState();
}
class _MoodTrackerScreenState extends State<MoodTrackerScreen> {
final Map<String, double> moodLevels = {
'Happy': FFAppState().HappyEmotion,
'Calm': FFAppState().CalmEmotion,
'Anxious': FFAppState().AnxiousEmotion,
'Sad': FFAppState().SadEmotion,
'Angry': FFAppState().AngryEmotion,
};
final Map<String, Color> moodColors = {
'Happy': Colors.teal,
'Calm': Colors.amber,
'Anxious': Colors.purple,
'Sad': Colors.deepPurple,
'Angry': Colors.redAccent,
};
final Map<String, IconData> moodIcons = {
'Happy': Icons.sentiment_satisfied,
'Calm': Icons.sentiment_neutral,
'Anxious': Icons.sentiment_dissatisfied,
'Sad': Icons.sentiment_very_dissatisfied,
'Angry': Icons.sentiment_very_dissatisfied_rounded,
};
@override
Widget build(BuildContext context) {
return Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: moodLevels.keys.map((mood) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
VerticalMoodSlider(
value: moodLevels[mood]!,
onChanged: (newValue) {
setState(() {
moodLevels[mood] = newValue;
});
switch (mood) {
case 'Happy':
FFAppState().HappyEmotion = newValue;
break;
case 'Calm':
FFAppState().CalmEmotion = newValue;
break;
case 'Anxious':
FFAppState().AnxiousEmotion = newValue;
break;
case 'Sad':
FFAppState().SadEmotion = newValue;
break;
case 'Angry':
FFAppState().AngryEmotion = newValue;
break;
}
},
color: moodColors[mood]!,
),
const SizedBox(height: 16),
CircleAvatar(
radius: 22,
backgroundColor: moodColors[mood],
child: Icon(moodIcons[mood], color: Colors.white),
),
const SizedBox(height: 8),
Text(
mood,
style: const TextStyle(color: Colors.white),
),
],
);
}).toList(),
);
}
}
class VerticalMoodSlider extends StatelessWidget {
final double value;
final ValueChanged<double> onChanged;
final Color color;
const VerticalMoodSlider({
Key? key,
required this.value,
required this.onChanged,
required this.color,
}) : super(key: key);
@override
Widget build(BuildContext context) {
final double sliderHeight = 150;
final double thumbSize = 20;
final double trackWidth = 15;
return GestureDetector(
behavior: HitTestBehavior.opaque,
onVerticalDragUpdate: (details) {
final box = context.findRenderObject() as RenderBox;
final localOffset = box.globalToLocal(details.globalPosition);
double newValue = 1.0 - (localOffset.dy / box.size.height);
newValue = newValue.clamp(0.0, 1.0);
onChanged(newValue);
},
onTapDown: (details) {
final box = context.findRenderObject() as RenderBox;
final localOffset = box.globalToLocal(details.globalPosition);
double newValue = 1.0 - (localOffset.dy / box.size.height);
newValue = newValue.clamp(0.0, 1.0);
onChanged(newValue);
},
child: Container(
width: 40,
height: sliderHeight,
padding: const EdgeInsets.symmetric(horizontal: 12.5),
child: Stack(
children: [
// Background track (static)
Container(
width: trackWidth,
height: sliderHeight,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(10),
color: Color.fromARGB(112, 157, 151, 151),
),
),
// Animated active portion
AnimatedPositioned(
duration: const Duration(milliseconds: 200),
curve: Curves.easeOut,
bottom: 0,
height: (sliderHeight - thumbSize / 2) * value + thumbSize / 2,
width: trackWidth,
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.vertical(
bottom: Radius.circular(10),
top: Radius.circular(value == 1.0 ? 10 : 0),
),
color: color.withOpacity(0.5),
),
),
),
// Animated thumb
AnimatedAlign(
duration: const Duration(milliseconds: 200),
curve: Curves.easeOut,
alignment: Alignment(0, 1.0 - 2 * value),
child: Container(
width: trackWidth,
height: thumbSize,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: color,
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.2),
blurRadius: 4,
offset: Offset(0, 2),
),
],
),
),
),
],
),
),
);
}
}
If you found this helpful, please like, share, and comment 💬✨. If you need any help, feel free to DM me anytime! 📩😊
#Slider #MoodTracker #CustomCode #CustomUI