anchor property

double anchor
final

The relative position of the zero scroll offset.

For example, if anchor is 0.5 and the AxisDirection determined by scrollDirection and reverse is AxisDirection.down or AxisDirection.up, then the zero scroll offset is vertically centered within the viewport. If the anchor is 1.0, and the axis direction is AxisDirection.right, then the zero scroll offset is on the left edge of the viewport.

Most scroll views by default are ordered GrowthDirection.forward. Changing the default values of ScrollView.anchor, ScrollView.center, or both, can configure a scroll view for GrowthDirection.reverse.

This sample shows a CustomScrollView, with Radio buttons in the AppBar.bottom that change the AxisDirection to illustrate different configurations. The CustomScrollView.anchor and CustomScrollView.center properties are also set to have the 0 scroll offset positioned in the middle of the viewport, with GrowthDirection.forward and GrowthDirection.reverse illustrated on either side. The sliver that shares the CustomScrollView.center key is positioned at the CustomScrollView.anchor.

To see it in action, copy and run this code snippet on DartPad.

import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';

/// Flutter code sample for [GrowthDirection]s.

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

class ExampleApp extends StatelessWidget {
  const ExampleApp({super.key});

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(home: MyWidget());
  }
}

class MyWidget extends StatefulWidget {
  const MyWidget({super.key});

  @override
  State<MyWidget> createState() => _MyWidgetState();
}

class _MyWidgetState extends State<MyWidget> {
  final List<String> _alphabet = <String>[
    'A',
    'B',
    'C',
    'D',
    'E',
    'F',
    'G',
    'H',
    'I',
    'J',
    'K',
    'L',
    'M',
    'N',
    'O',
    'P',
    'Q',
    'R',
    'S',
    'T',
    'U',
    'V',
    'W',
    'X',
    'Y',
    'Z',
  ];
  final Widget _spacer = const SizedBox.square(dimension: 10);
  final UniqueKey _center = UniqueKey();
  AxisDirection _axisDirection = .down;

  Widget _getArrows(AxisDirection axisDirection) {
    final Widget arrow = switch (axisDirection) {
      AxisDirection.up => const Icon(Icons.arrow_upward_rounded),
      AxisDirection.down => const Icon(Icons.arrow_downward_rounded),
      AxisDirection.left => const Icon(Icons.arrow_back_rounded),
      AxisDirection.right => const Icon(Icons.arrow_forward_rounded),
    };
    return Flex(
      direction: flipAxis(axisDirectionToAxis(axisDirection)),
      mainAxisAlignment: .center,
      children: <Widget>[arrow, arrow],
    );
  }

  void _onAxisDirectionChanged(AxisDirection? axisDirection) {
    if (axisDirection != null && axisDirection != _axisDirection) {
      setState(() {
        // Respond to change in axis direction.
        _axisDirection = axisDirection;
      });
    }
  }

  Widget _getLeading(SliverConstraints constraints, bool isForward) {
    return Container(
      color: isForward ? Colors.orange[300] : Colors.green[400],
      padding: const .all(8.0),
      child: Column(
        mainAxisAlignment: .spaceBetween,
        children: <Widget>[
          Text(constraints.axis.toString()),
          _spacer,
          Text(constraints.axisDirection.toString()),
          _spacer,
          Text(constraints.growthDirection.toString()),
          _spacer,
          _getArrows(
            isForward
                ? _axisDirection
                // This method is available to conveniently flip an AxisDirection
                // into its opposite direction.
                : flipAxisDirection(_axisDirection),
          ),
        ],
      ),
    );
  }

  Widget _getRadioRow() {
    return DefaultTextStyle(
      style: const TextStyle(fontWeight: .bold, color: Colors.white),
      child: RadioTheme(
        data: RadioThemeData(
          fillColor: WidgetStateProperty.all<Color>(Colors.white),
        ),
        child: Padding(
          padding: const .all(8.0),
          child: RadioGroup<AxisDirection>(
            groupValue: _axisDirection,
            onChanged: _onAxisDirectionChanged,
            child: Row(
              mainAxisAlignment: .spaceAround,
              children: <Widget>[
                Radio<AxisDirection>(value: AxisDirection.up),
                const Text('up'),
                _spacer,
                Radio<AxisDirection>(value: AxisDirection.down),
                const Text('down'),
                _spacer,
                Radio<AxisDirection>(value: AxisDirection.left),
                const Text('left'),
                _spacer,
                Radio<AxisDirection>(value: AxisDirection.right),
                const Text('right'),
                _spacer,
              ],
            ),
          ),
        ),
      ),
    );
  }

  Widget _getList({required bool isForward}) {
    // The SliverLayoutBuilder is not necessary, and is here to allow us to see
    // the SliverConstraints & directional information that is provided to the
    // SliverList when laying out.
    return SliverLayoutBuilder(
      builder: (BuildContext context, SliverConstraints constraints) {
        return SliverList.builder(
          itemCount: 27,
          itemBuilder: (BuildContext context, int index) {
            final Widget child;
            if (index == 0) {
              child = _getLeading(constraints, isForward);
            } else {
              child = Container(
                color: isForward
                    ? (index.isEven ? Colors.amber[100] : Colors.amberAccent)
                    : (index.isEven ? Colors.green[100] : Colors.lightGreen),
                padding: const .all(8.0),
                child: Center(child: Text(_alphabet[index - 1])),
              );
            }
            return Padding(padding: const .all(8.0), child: child);
          },
        );
      },
    );
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('GrowthDirections'),
        bottom: PreferredSize(
          preferredSize: const Size.fromHeight(50),
          child: Padding(padding: const .all(8.0), child: _getRadioRow()),
        ),
      ),
      body: CustomScrollView(
        // This method is available to conveniently determine if an scroll
        // view is reversed by its AxisDirection.
        reverse: axisDirectionIsReversed(_axisDirection),
        // This method is available to conveniently convert an AxisDirection
        // into its Axis.
        scrollDirection: axisDirectionToAxis(_axisDirection),
        // Places the leading edge of the center sliver in the middle of the
        // viewport. Changing this value between 0.0 (the default) and 1.0
        // changes the position of the inflection point between GrowthDirections
        // in the viewport when the slivers are laid out.
        anchor: 0.5,
        center: _center,
        slivers: <Widget>[
          _getList(isForward: false),
          SliverToBoxAdapter(
            // This sliver will be located at the anchor. The scroll position
            // will progress in either direction from this point.
            key: _center,
            child: const Padding(
              padding: .all(8.0),
              child: Center(
                child: Text('0', style: TextStyle(fontWeight: .bold)),
              ),
            ),
          ),
          _getList(isForward: true),
        ],
      ),
    );
  }
}

Implementation

// TODO(framework): Replace the following block with a @dartpad directive
// when it's supported. https://github.com/dart-lang/dartdoc/issues/4123
/// <small>
///
/// To see it in action, copy and run this code snippet on [DartPad](https://dartpad.dev/).
///
/// </small>
///
/// {@example /examples/api/lib/rendering/growth_direction/growth_direction.0.dart#body}
///
/// </callout-box>
/// {@endtemplate}
final double anchor;