skip to Main Content

I want to add something cool to my age input screen in Flutter, and the main way I want to do that is by having a image grow when the age slider is increased, and shrink when the age slider is decreased. Its a pretty simple animation that I have no idea how to implement, and it would be greatly appreciated if someone answered this question. Here is a screenshot of my current age screen (there is a big space for the growing image): (check below image), and the code for the screenshot. Thanks!

EDIT: A BIG bonus that would be super helpful for me is to make the animation change from a kid to an adult to an elder as the user increases the slider. So the image starts off as a kid, gets bigger and the image changes to an adult, and then shrinks a little bit and changes to an elderly man/woman. If this is pulled off I will be super greatful. Thanks again!

import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:workout_app/Screens/Components/Sign_Up_Screens/screen1.dart';
import 'package:workout_app/Screens/Components/Sign_Up_Screens/screen3.dart';

class screen2 extends StatefulWidget {
  @override
  State<StatefulWidget> createState() => Main();
}

class Main extends State<screen2> {
  void returnScreen(context) {
    Navigator.of(context).pushReplacement(
      MaterialPageRoute(
        fullscreenDialog: true,
        builder: (context) => screen1(),
      ),
    );
  }

    
  @override
  void initState() {
    loadData();
    super.initState();
  }

  @override
  void dispose() {
    super.dispose();
  }
  

  @override
  int age = 10;
  bool nextValid = false;

  void toast(String text) {
    final scaffold = ScaffoldMessenger.of(context);
    scaffold.showSnackBar(
      SnackBar(
        width: MediaQuery.of(context).size.width * .5,
        behavior: SnackBarBehavior.floating,
        content: Text(text),
        duration: const Duration(seconds: 1),
        elevation: 10,
      ),
    );
  }

  void submitData() async {
    if(nextValid == false) {
      toast('Please input your age');
      return;
    }
    SharedPreferences prefs = await SharedPreferences.getInstance();
    await prefs.setInt('age', age);
    Navigator.of(context).pushReplacement(
      MaterialPageRoute(
        fullscreenDialog: true,
        builder: (context) => SingleSelectListViewWithLogo(items : ['Tone Up - You want visible muscles with as little mass as possible and a low body fat percentage', 'Bulk Up - You want large, defined muscles, with a low percentage of body fat', 'Get Jacked - You want to lift an insane amount of weight and don't care about body fat or muscle definition']),
      ),
    );
  }

  void loadData() async {
    SharedPreferences prefs = await SharedPreferences.getInstance();
    try {
      int? x = prefs.getInt('age');
      print(x);
      if(x != null) {
        setState(() => {age = x, nextValid = true});
      }
    } catch (Exception){
      //continue;
    }
  }

  Widget build(BuildContext context) {
    Size size = MediaQuery.of(context).size;
    return Material(
      child: Scaffold(
      body: 
        Container (
          decoration: const BoxDecoration(color: Color.fromARGB(255, 46, 46, 46)),
          height: size.height,
          width: double.infinity,
          child: Stack(
            children: <Widget> [
              Positioned (
                top: size.height * .34,
                height: size.height * .6,
                width: size.width * .9,
                left: size.width * .05,
                child: Container (
                  decoration: BoxDecoration(
                    borderRadius: BorderRadius.circular(15),
                    color: Color.fromARGB(255, 73, 73, 73),
                  ),
                ),
              ),
              Positioned(
                top: size.height * .06,
                left: size.width * .03,
                child: InkWell(
                  onTap: () {
                    returnScreen(context);
                  },
                  child: Image.asset(
                    alignment: Alignment.topLeft,
                    "assets/images/whitebackarrow.png",
                    width: size.width * .07,
                  ),
                ),
              ), 
              Positioned(
                top: size.height * .09,
                left: size.width * .35,
                child: const Text(
                  style: TextStyle(fontSize: 50, color: Color.fromARGB(255, 255, 255, 255), fontWeight: FontWeight.bold),
                  'Next,'
                )
              ),
              Positioned(
                top: size.height * .19,
                left: size.width * .06,
                child: const Text(
                  style: TextStyle(fontSize: 25, color: Color.fromARGB(255, 255, 255, 255)),
                  'Lets customize your workout!'
                )
              ),
              Positioned(
                top: size.height * .38,
                left: size.width * .13,
                child: const Text(
                  style: TextStyle(fontSize: 25, color: Color.fromARGB(255, 0, 0, 0), fontWeight: FontWeight.bold),
                  'What's your current age?'
                )
              ),
              Positioned (
                top: size.height * .7,
                left: size.width * .1,
                child: SliderTheme(
                  data: const SliderThemeData(
                    trackHeight: 30,
                    inactiveTrackColor: Color.fromARGB(255, 160, 160, 160),
                    activeTrackColor: Color.fromARGB(255, 40, 39, 39),
                    thumbColor: Colors.black,
                    disabledActiveTrackColor: Colors.black,
                    disabledInactiveTrackColor: Colors.black12,
                    thumbShape: RoundSliderThumbShape(enabledThumbRadius: 25.0),
                  ),
                  child: Container (
                    width: size.width * .8,
                    child: Slider(
                      label: "Select Age",
                      value: age.toDouble(),
                      onChanged: (value) {
                        setState(() {
                          age = value.toInt();
                          nextValid = true;
                        });
                      },
                      min: 10,
                      max: 99,
                    )
                  )
                )
              ),
              Positioned (
                top: size.height * .605,
                left: size.width * .25,
                child: Text(
                  age.toString(),
                  style: const TextStyle(
                    fontSize: 50.0,
                    fontWeight: FontWeight.bold
                  ),
                ),
              ),
              Positioned (
                top: size.height * .64,
                left: size.width * .42,
                child: const Text (
                  'years old',
                  style: TextStyle(
                    fontSize: 25.0,
                  ),
                )
              ),
              Positioned(
                top: size.height * .825,
                left: size.width * .1,
                child: SizedBox(
                  width: size.width * .8,
                  height: size.height * .08,
                  child: ElevatedButton(
                    style: ButtonStyle(                  
                      backgroundColor: MaterialStateProperty.all<Color>(!nextValid ? Color.fromRGBO(69, 75, 85, 1) : Color.fromARGB(255, 23, 100, 22)),
                    ),
                    child: const Text('Continue',
                      style: TextStyle(fontSize: 20),
                    ),
                    onPressed: () async {
                      submitData();
                    },
                  ),
                ),
              ),
            ]
          )
        )
      )
    );
  }
}

Age Screen. This is the code for the screen:

2

Answers


  1. According to your code, you can just have an Image.asset with the height property. Use the age state as the height of the image. You can do simple maths here where you can multiply the age with some number to fit the image to your liking.

    Image.asset(
      "assets/images/bqubique.png",
      height: age.toDouble(),
    ),
    

    To animate the image I’ll check it off with an AnimatedContainer but in this case you’d need to rework the above solution to this:

    AnimatedContainer(
      duration: const Duration(seconds: 1),
      //Choose which curve you want to have here, I randomly selected bounceIn (refer to Curves class documentation)
      curve: Curves.bounceIn,
      height: age.toDouble(),
      child: Image.asset(
        "assets/images/bqubique.png",
      ),
    )
    

    GIF Of the final result

    Edit: Just to add a bit more details:

    AnimatedContainer lets you animate your Containers with the given property (height, width, decoration, color, etc.). When you insert a state variable (in this case the age variable) as the input to one of these parameters, the AnimatedContainer will automatically animate its property based on the input changes. It requires Duration in order to know how long the animation will take to run. You can additionally add fancier animations than just linear ones by including the curve property.

    To learn more about animations please refer to Introduction to animations.

    To learn more about AnimatedContainer please refer to AnimatedContainer class

    To learn more about Curves please refer to Curves class

    Login or Signup to reply.
  2. Little late to answer. I did following to achieve the same.

    import 'package:flutter/material.dart';
    
    void main() {
      runApp(MaterialApp(
        theme: ThemeData(useMaterial3: true),
        home: const MyPage(),
      ));
    }
    
    class MyPage extends StatelessWidget {
      const MyPage({super.key});
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(
            title: const Text('Slider Example'),
          ),
          body: const Center(
            child: Card(
              child: CustomSlider(),
            ),
          ),
          bottomSheet: SafeArea(
            child: BottomSheet(
                onClosing: () {},
                builder: (ctx) {
                  return const Padding(
                    padding: EdgeInsets.symmetric(horizontal: 40, vertical: 20),
                    child: Text(
                        'ℹ️ The gender choices and corresponding emojis shown here are for demonstration purpose only and does not reflect my view in any manner.'),
                  );
                }),
          ),
        );
      }
    }
    
    class CustomSlider extends StatefulWidget {
      const CustomSlider({
        super.key,
      });
    
      @override
      State<CustomSlider> createState() => _CustomSliderState();
    }
    
    class _CustomSliderState extends State<CustomSlider> {
      var _age = 21;
      final _data = {
        const IntRange(0, 6): {'m': 'πŸ‘ΆπŸ»', 'f': 'πŸ‘ΆπŸ»'},
        const IntRange(7, 18): {'m': 'πŸ‘¦', 'f': 'πŸ‘§'},
        const IntRange(19, 60): {'m': 'πŸ§”β€β™‚οΈ', 'f': 'πŸ‘©'},
        const IntRange(61, 100): {'m': 'πŸ‘΄', 'f': 'πŸ‘΅'},
      };
      var _gender = 'f';
    
      @override
      Widget build(BuildContext context) {
        final range = _data.keys.firstWhere((range) => _age.within(range));
        final text = _data[range]![_gender]!;
        final size = 72 + (_age - range.start) / (range.end - range.start) * 50;
    
        return Column(
          mainAxisSize: MainAxisSize.min,
          children: [
            Text(
              'What's your current age?',
              style: Theme.of(context).textTheme.headlineMedium,
            ),
            const SizedBox(
              height: 20,
            ),
            Center(
              child: SizedBox.square(
                dimension: 300,
                child: Center(
                  child: AnimatedSwitcher(
                    duration: const Duration(milliseconds: 700),
                    child: Text(
                      key: ValueKey(text),
                      text,
                      textAlign: TextAlign.center,
                      style: TextStyle(fontSize: size),
                    ),
                  ),
                ),
              ),
            ),
            Slider(
              value: _age.toDouble(),
              onChanged: _onAgeChanged,
              min: 0,
              max: 100,
              divisions: 101,
            ),
            const SizedBox(
              height: 20,
            ),
            Text(
              'Your current age $_age',
              style: const TextStyle(fontSize: 24),
            ),
            const SizedBox(
              height: 20,
            ),
            Row(
              mainAxisSize: MainAxisSize.min,
              children: [
                Radio<String>(
                  value: 'm',
                  groupValue: _gender,
                  onChanged: _onGenderChanged,
                ),
                const Text('Male'),
                const SizedBox(
                  width: 20,
                ),
                Radio<String>(
                  value: 'f',
                  groupValue: _gender,
                  onChanged: _onGenderChanged,
                ),
                const Text('Female'),
              ],
            )
          ],
        );
      }
    
      void _onAgeChanged(double value) {
        _age = value.toInt();
        setState(() {});
      }
    
      void _onGenderChanged(newValue) {
        _gender = newValue!;
        setState(() {});
      }
    }
    
    @immutable
    class IntRange {
      final int start;
      final int end;
    
      const IntRange(
        this.start,
        this.end,
      );
    
      @override
      String toString() => 'IntRange(start: $start, end: $end)';
    
      @override
      bool operator ==(Object other) {
        if (identical(this, other)) return true;
    
        return other is IntRange && other.start == start && other.end == end;
      }
    
      @override
      int get hashCode => start.hashCode ^ end.hashCode;
    }
    
    extension Range on int {
      bool within(IntRange range) {
        return this >= range.start && this <= range.end;
      }
    }
    
    

    Demo: https://youtube.com/shorts/CyymV-GQvj4?feature=share

    PS: I know there is already an accepted answer. I thought why not share it here as the solution is ready.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search