Peiris
Peiris

Reputation: 135

Flutter Horizontal Stepper - When the steps changes, next step UI scrolls down if the content is exceeds than the screen

I'm using flutter horizontal stepper and there are 4 steps, in the 3rd step there is a form, which the content exceeds the screen height. Since stepper is handling scrolling, I didn't need to use a scroll there. The issue is when goes to 3rd screen, the UI content scrolls down. It should appear from the bottom. Any idea what I can do about it?

Upvotes: 1

Views: 2864

Answers (3)

khan
khan

Reputation: 1150

You need to make a copy of the stepper then add the scroll controller to its method.

class TmpStepper extends StatefulWidget {
   const TmpStepper({
   super.key,
   required this.steps,
   this.physics,
   this.type = TmpStepperType.vertical,
   this.scrollController,
   this.currentStep = 0,
   this.onStepTapped,
   this.onStepContinue,
   this.onStepCancel,
   this.controlsBuilder,
   this.elevation,
   this.margin,
 }) : assert(steps != null),
    assert(type != null),
    assert(currentStep != null),
    assert(0 <= currentStep && currentStep < steps.length);

// Add your scrollcontroller
final ScrollController? scrollController;

Then under the method _buildHorizontal() add that scrollController defined above widget.scrollController as shown bellow.

return Column(
  children: <Widget>[
    Material(
      elevation: widget.elevation ?? 2,
      child: SingleChildScrollView(
        scrollDirection: Axis.horizontal,
        child: Container(
          margin: const EdgeInsets.symmetric(horizontal: 24.0),
          child: Row(
            children: children,
          ),
        ),
      ),
    ),
    Expanded(
      child: ListView(
        physics: widget.physics,
        padding: const EdgeInsets.all(24.0),
        controller: widget.scrollController,
        children: <Widget>[
          AnimatedSize(
            curve: Curves.fastOutSlowIn,
            duration: kThemeAnimationDuration,
            child: Column(crossAxisAlignment: CrossAxisAlignment.stretch, children: stepPanels),
          ),
          _buildVerticalControls(widget.currentStep),
        ],
      ),
    ),
  ],
);

initialize the stepperScrollController on your stateful widget

class MyStepperScreen extends StatefulWidget {
   const MyStepperScreen({Key? key}) : super(key: key);
   @override
   State<MyStepperScreen> createState() => _MyStepperScreenState();
}

class _MyStepperScreenState extends State<MyStepperScreen> {
  late ScrollController _stepperScrollController;
  
  @override
  void initState() {
    super.initState();
    _stepperScrollController = ScrollController(keepScrollOffset:false)..addListener(() =>  _controlScroll());
  }

  // You can do whatever you want with the scrollController listener
  void controlScroll() {
   print(stepperScrollController.offset);
  }

  @override
  Widget build(BuildContext context) {
   return TmpStepper(
            elevation: 0,
            margin: EdgeInsets.zero,
            type: TmpStepperType.horizontal,
            physics: ClampingScrollPhysics(),
            currentStep: 0,
            scrollController: _stepperScrollController,
            onStepTapped: (step) {
              _stepperScrollController.jumpTo(0.0); // You should write it in a proper way as this is just for example
            },
            steps: []
         );
   }
}

Upvotes: 0

Raouf Rahiche
Raouf Rahiche

Reputation: 31356

Using LayoutBuilder and some investigation about the default Stepper elements sizes you can achieve the wanted result.

enter image description here

  1. Wrap your Stepper with a LayoutBuilder
  2. Set the physics to NeverScrollableScrollPhysics() (You can do that when the user is in the page where you have the form only)
  3. Wrap your form with a constrained widget (SizedBox will work)
  4. Set the height of the SizedBox to the available hight (from LayoutBuilder builder ) minus the total of the sizes of the Step default (or custom widgets)

if you are using the default Stepper widget controls the total of height is:

height: constraints.maxHeight - (72 + 48 + 24 + 24)

enter image description here

Code:

/// Flutter code sample for Stepper

import 'package:flutter/material.dart';

void main() => runApp(const MyApp());

/// This is the main application widget.
class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  static const String _title = 'Flutter Code Sample';

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: _title,
      home: Scaffold(
        appBar: AppBar(title: const Text(_title)),
        body: const Center(
          child: MyStatefulWidget(),
        ),
      ),
    );
  }
}

/// This is the stateful widget that the main application instantiates.
class MyStatefulWidget extends StatefulWidget {
  const MyStatefulWidget({Key? key}) : super(key: key);

  @override
  _MyStatefulWidgetState createState() => _MyStatefulWidgetState();
}

/// This is the private State class that goes with MyStatefulWidget.
class _MyStatefulWidgetState extends State<MyStatefulWidget> {
  int _index = 0;

  @override
  Widget build(BuildContext context) {
    return LayoutBuilder(
      builder: (BuildContext context, BoxConstraints constraints) {
        print(constraints);
        return Stepper(
          currentStep: _index,
          type: StepperType.horizontal,
          onStepCancel: () {
            if (_index > 0) {
              setState(() {
                _index -= 1;
              });
            }
          },
          onStepContinue: () {
            if (_index <= 0) {
              setState(() {
                _index += 1;
              });
            }
          },
          onStepTapped: (int index) {
            setState(() {
              _index = index;
            });
          },
          physics: NeverScrollableScrollPhysics(),
          steps: <Step>[
            Step(
              title: const Text('Step 1 title'),
              content: Container(alignment: Alignment.centerLeft, child: const Text('Content for Step 1')),
            ),
            Step(
              title: Text('Step 2 title'),
              content: Container(
                height: constraints.maxHeight - (72 + 48 + 24 + 24),
                child: SingleChildScrollView(
                  reverse: true,
                  child: Column(
                    children: [
                      for (int i = 0; i < 20; i++)
                        Padding(
                          padding: const EdgeInsets.all(8.0),
                          child: TextFormField(
                            decoration: InputDecoration(hintText: 'Hint $i'),
                          ),
                        ),
                    ],
                  ),
                ),
              ),
            ),
          ],
        );
      },
    );
  }
}

Upvotes: 0

Prabhanshu Tiwari
Prabhanshu Tiwari

Reputation: 302

You should use singlechildecrollview and scroll controller and set srollposition accordingly through scroll controller

Upvotes: 0

Related Questions