shape property

ShapeBorder? shape
final

The shape of the app bar's Material as well as its shadow.

If this property is null, then the ambient AppBarThemeData.shape is used. Both properties default to null. If both properties are null then the shape of the app bar's Material is just a simple rectangle.

A shadow is only displayed if the elevation is greater than zero.

This sample demonstrates how to implement a custom app bar shape for the shape property.

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

import 'package:material_ui/material_ui.dart';

/// Flutter code sample for [AppBar.shape].

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

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

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

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

  @override
  Widget build(BuildContext context) {
    final ColorScheme colorScheme = Theme.of(context).colorScheme;

    return Scaffold(
      appBar: AppBar(
        shape: const CustomAppBarShape(),
        backgroundColor: colorScheme.primaryContainer,
        title: const Text('AppBar Sample'),
        bottom: PreferredSize(
          preferredSize: const Size.fromHeight(64.0),
          child: Padding(
            padding: const .symmetric(horizontal: 16.0),
            child: TextField(
              decoration: InputDecoration(
                border: OutlineInputBorder(
                  borderSide: BorderSide(color: colorScheme.primary),
                ),
                enabledBorder: OutlineInputBorder(
                  borderSide: BorderSide(color: colorScheme.onPrimaryContainer),
                ),
                filled: true,
                hintText: 'Enter a search term',
                fillColor: colorScheme.surface,
                prefixIcon: Icon(
                  Icons.search_rounded,
                  color: colorScheme.primary,
                ),
                suffixIcon: Icon(
                  Icons.tune_rounded,
                  color: colorScheme.primary,
                ),
              ),
            ),
          ),
        ),
      ),
      body: ListView.builder(
        padding: const .only(top: 45.0),
        itemCount: 20,
        itemBuilder: (BuildContext context, int index) {
          return ListTile(title: Text('Item $index'));
        },
      ),
    );
  }
}

class CustomAppBarShape extends OutlinedBorder {
  // Implementing the constructor allows the CustomAppBarShape to be
  // properly compared when calling the `identical` method.
  const CustomAppBarShape({super.side});

  Path _getPath(Rect rect) {
    final Path path = Path();
    final Size size = Size(rect.width, rect.height * 1.5);

    final double p0 = size.height * 0.75;
    path.lineTo(0.0, p0);

    final Offset controlPoint = Offset(size.width * 0.4, size.height);
    final Offset endPoint = Offset(size.width, size.height / 2);
    path.quadraticBezierTo(
      controlPoint.dx,
      controlPoint.dy,
      endPoint.dx,
      endPoint.dy,
    );

    path.lineTo(size.width, 0.0);
    path.close();

    return path;
  }

  @override
  Path getOuterPath(Rect rect, {TextDirection? textDirection}) {
    return _getPath(rect.inflate(side.width));
  }

  @override
  Path getInnerPath(Rect rect, {TextDirection? textDirection}) {
    return _getPath(rect);
  }

  @override
  void paint(Canvas canvas, Rect rect, {TextDirection? textDirection}) {
    if (rect.isEmpty) {
      return;
    }
    canvas.drawPath(
      getOuterPath(rect, textDirection: textDirection),
      side.toPaint(),
    );
  }

  @override
  ShapeBorder scale(double t) {
    return CustomAppBarShape(side: side.scale(t));
  }

  @override
  OutlinedBorder copyWith({BorderSide? side}) {
    return CustomAppBarShape(side: side ?? this.side);
  }

  // The lerpFrom method is necessary for the CustomAppBarShape to be
  // properly animated when changing the AppBar shape and when
  // the AppBar is rebuilt.
  @override
  ShapeBorder? lerpFrom(ShapeBorder? a, double t) {
    if (a is CustomAppBarShape) {
      return CustomAppBarShape(side: BorderSide.lerp(a.side, side, t));
    }
    return super.lerpFrom(a, t);
  }

  // The lerpTo method is necessary for the CustomAppBarShape to be
  // properly animated when changing the AppBar shape and when
  // the AppBar is rebuilt.
  @override
  ShapeBorder? lerpTo(ShapeBorder? b, double t) {
    if (b is CustomAppBarShape) {
      return CustomAppBarShape(side: BorderSide.lerp(b.side, side, t));
    }
    return super.lerpTo(b, t);
  }
}

See also:

  • elevation, which defines the size of the shadow below the app bar.
  • shadowColor, which is the color of the shadow below the app bar.

Implementation

// TODO(framework): Replace the following block with a @dartpad directive
// when it's supported. https://github.com/dart-lang/dartdoc/issues/4123
/// {@macro material_ui.dartpad_guide}
///
/// {@example /example/lib/app_bar/app_bar.4.dart#body}
///
/// </callout-box>
///
/// See also:
///
///  * [elevation], which defines the size of the shadow below the app bar.
///  * [shadowColor], which is the color of the shadow below the app bar.
final ShapeBorder? shape;