rodydavis / floating_search_bar

Floating Search Bar like Gmail for Flutter
https://rodydavis.github.io/floating_search_bar/
MIT License
32 stars 27 forks source link

Android update margin top: : Matrix4.translationValues(0.0, 8.0, 0.0) #7

Open studiozocaro opened 4 years ago

studiozocaro commented 4 years ago

import 'dart:math' as math;

import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; import 'package:flutter/foundation.dart' show TargetPlatform;

class SliverFloatingBar extends StatefulWidget { /// Creates a material design app bar that can be placed in a [CustomScrollView]. /// /// The arguments [forceElevated], [primary], [floating], [pinned], [snap] /// and [automaticallyImplyLeading] must not be null. const SliverFloatingBar({ Key key, this.leading, this.automaticallyImplyLeading = true, this.title, this.trailing, this.elevation = 5.0, this.backgroundColor, this.floating = false, this.pinned = false, this.snap = false, }) : assert(automaticallyImplyLeading != null), assert(floating != null), assert(pinned != null), assert(snap != null), assert(floating || !snap, 'The "snap" argument only makes sense for floating app bars.'), super(key: key);

/// A widget to display before the [title]. /// /// If this is null and [automaticallyImplyLeading] is set to true, the [AppBar] will /// imply an appropriate widget. For example, if the [AppBar] is in a [Scaffold] /// that also has a [Drawer], the [Scaffold] will fill this widget with an /// [IconButton] that opens the drawer. If there's no [Drawer] and the parent /// [Navigator] can go back, the [AppBar] will use a [BackButton] that calls /// [Navigator.maybePop]. final Widget leading;

/// Controls whether we should try to imply the leading widget if null. /// /// If true and [leading] is null, automatically try to deduce what the leading /// widget should be. If false and [leading] is null, leading space is given to [title]. /// If leading widget is not null, this parameter has no effect. final bool automaticallyImplyLeading;

/// The primary widget displayed in the appbar. /// /// Typically a [Text] widget containing a description of the current contents /// of the app. final Widget title;

final Widget trailing;

/// The z-coordinate at which to place this app bar when it is above other /// content. This controls the size of the shadow below the app bar. /// /// Defaults to 4, the appropriate elevation for app bars. /// /// If [forceElevated] is false, the elevation is ignored when the app bar has /// no content underneath it. For example, if the app bar is [pinned] but no /// content is scrolled under it, or if it scrolls with the content, then no /// shadow is drawn, regardless of the value of [elevation]. final double elevation;

/// The color to use for the app bar's material. Typically this should be set /// along with [brightness], [iconTheme], [textTheme]. /// /// Defaults to [ThemeData.primaryColor]. final Color backgroundColor;

/// Whether the app bar should become visible as soon as the user scrolls /// towards the app bar. /// /// Otherwise, the user will need to scroll near the top of the scroll view to /// reveal the app bar. /// /// If [snap] is true then a scroll that exposes the app bar will trigger an /// animation that slides the entire app bar into view. Similarly if a scroll /// dismisses the app bar, the animation will slide it completely out of view. /// /// ## Animated Examples /// /// The following animations show how the app bar changes its scrolling /// behavior based on the value of this property. /// /// App bar with [floating] set to false: /// {@animation 476 400 https://flutter.github.io/assets-for-api-docs/assets/material/app_bar.mp4} /// App bar with [floating] set to true: /// {@animation 476 400 https://flutter.github.io/assets-for-api-docs/assets/material/app_bar_floating.mp4} /// /// See also: /// /// * [SliverAppBar] for more animated examples of how this property changes the /// behavior of the app bar in combination with [pinned] and [snap]. final bool floating;

/// Whether the app bar should remain visible at the start of the scroll view. /// /// The app bar can still expand and contract as the user scrolls, but it will /// remain visible rather than being scrolled out of view. /// /// ## Animated Examples /// /// The following animations show how the app bar changes its scrolling /// behavior based on the value of this property. /// /// App bar with [pinned] set to false: /// {@animation 476 400 https://flutter.github.io/assets-for-api-docs/assets/material/app_bar.mp4} /// App bar with [pinned] set to true: /// {@animation 476 400 https://flutter.github.io/assets-for-api-docs/assets/material/app_bar_pinned.mp4} /// /// See also: /// /// * [SliverAppBar] for more animated examples of how this property changes the /// behavior of the app bar in combination with [floating]. final bool pinned;

/// If [snap] and [floating] are true then the floating app bar will "snap" /// into view. /// /// If [snap] is true then a scroll that exposes the floating app bar will /// trigger an animation that slides the entire app bar into view. Similarly if /// a scroll dismisses the app bar, the animation will slide the app bar /// completely out of view. /// /// Snapping only applies when the app bar is floating, not when the appbar /// appears at the top of its scroll view. /// /// ## Animated Examples /// /// The following animations show how the app bar changes its scrolling /// behavior based on the value of this property. /// /// App bar with [snap] set to false: /// {@animation 476 400 https://flutter.github.io/assets-for-api-docs/assets/material/app_bar_floating.mp4} /// App bar with [snap] set to true: /// {@animation 476 400 https://flutter.github.io/assets-for-api-docs/assets/material/app_bar_floating_snap.mp4} /// /// See also: /// /// * [SliverAppBar] for more animated examples of how this property changes the /// behavior of the app bar in combination with [pinned] and [floating]. final bool snap;

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

class _SliverFloatingBarState extends State with TickerProviderStateMixin { FloatingHeaderSnapConfiguration _snapConfiguration;

void _updateSnapConfiguration() { if (widget.snap && widget.floating) { _snapConfiguration = FloatingHeaderSnapConfiguration( vsync: this, curve: Curves.easeOut, duration: const Duration(milliseconds: 200), ); } else { _snapConfiguration = null; } }

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

_updateSnapConfiguration();

}

@override void didUpdateWidget(SliverFloatingBar oldWidget) { super.didUpdateWidget(oldWidget); if (widget.snap != oldWidget.snap || widget.floating != oldWidget.floating) _updateSnapConfiguration(); }

@override Widget build(BuildContext context) { final double topPadding = MediaQuery.of(context).padding.top; final double collapsedHeight = (widget.pinned && widget.floating) ? topPadding : null;

return MediaQuery.removePadding(
  context: context,
  removeBottom: true,
  child: SliverPersistentHeader(
    floating: widget.floating,
    pinned: widget.pinned,
    delegate: _SliverAppBarDelegate(
      leading: widget.leading,
      automaticallyImplyLeading: widget.automaticallyImplyLeading,
      title: widget.title,
      trailing: widget.trailing,
      elevation: widget.elevation,
      backgroundColor: widget.backgroundColor,
      floating: widget.floating,
      pinned: widget.pinned,
      snapConfiguration: _snapConfiguration,
      collapsedHeight: collapsedHeight,
      topPadding: topPadding,
    ),
  ),
);

} }

class _FloatingAppBar extends StatefulWidget { const _FloatingAppBar({Key key, this.child}) : super(key: key);

final Widget child;

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

// A wrapper for the widget created by _SliverAppBarDelegate that starts and /// stops the floating appbar's snap-into-view or snap-out-of-view animation. class _FloatingAppBarState extends State<_FloatingAppBar> { ScrollPosition _position;

@override void didChangeDependencies() { super.didChangeDependencies(); if (_position != null) _position.isScrollingNotifier.removeListener(_isScrollingListener); _position = Scrollable.of(context)?.position; if (_position != null) _position.isScrollingNotifier.addListener(_isScrollingListener); }

@override void dispose() { if (_position != null) _position.isScrollingNotifier.removeListener(_isScrollingListener); super.dispose(); }

RenderSliverFloatingPersistentHeader _headerRenderer() { return context.ancestorRenderObjectOfType( const TypeMatcher()); }

void _isScrollingListener() { if (_position == null) return;

// When a scroll stops, then maybe snap the appbar into view.
// Similarly, when a scroll starts, then maybe stop the snap animation.
final RenderSliverFloatingPersistentHeader header = _headerRenderer();
if (_position.isScrollingNotifier.value)
  header?.maybeStopSnapAnimation(_position.userScrollDirection);
else
  header?.maybeStartSnapAnimation(_position.userScrollDirection);

}

@override Widget build(BuildContext context) => widget.child; }

class _SliverAppBarDelegate extends SliverPersistentHeaderDelegate { _SliverAppBarDelegate({ @required this.leading, @required this.automaticallyImplyLeading, @required this.title, @required this.trailing, @required this.elevation, @required this.backgroundColor, @required this.floating, @required this.pinned, @required this.snapConfiguration, @required this.collapsedHeight, @required this.topPadding, });

final Widget trailing; final bool automaticallyImplyLeading; final Color backgroundColor; final double elevation; final bool floating; final Widget leading; final bool pinned; final Widget title; final double collapsedHeight; final double topPadding;

@override double get minExtent => collapsedHeight ?? (topPadding + kToolbarHeight);

@override final FloatingHeaderSnapConfiguration snapConfiguration;

@override double get maxExtent => math.max(topPadding + (kToolbarHeight), minExtent);

@override bool shouldRebuild(covariant _SliverAppBarDelegate oldDelegate) { return leading != oldDelegate.leading || automaticallyImplyLeading != oldDelegate.automaticallyImplyLeading || title != oldDelegate.title || trailing != oldDelegate.trailing || elevation != oldDelegate.elevation || topPadding != oldDelegate.topPadding || collapsedHeight != oldDelegate.collapsedHeight || backgroundColor != oldDelegate.backgroundColor || pinned != oldDelegate.pinned || floating != oldDelegate.floating || snapConfiguration != oldDelegate.snapConfiguration; }

@override String toString() { return ''; }

@override Widget build( BuildContext context, double shrinkOffset, bool overlapsContent) { final double visibleMainHeight = maxExtent - shrinkOffset; var platform = Theme.of(context).platform; // // Truth table for toolbarOpacity: // // pinned | floating | bottom != null || opacity // // ---------------------------------------------- // // 0 | 0 | 0 || fade // // 0 | 0 | 1 || fade // // 0 | 1 | 0 || fade // // 0 | 1 | 1 || fade // // 1 | 0 | 0 || 1.0 // // 1 | 0 | 1 || 1.0 // // 1 | 1 | 0 || 1.0 // // 1 | 1 | 1 || fade final double toolbarOpacity = !pinned || (floating) ? ((visibleMainHeight) / kToolbarHeight).clamp(0.0, 1.0) : 1.0;

final Widget appBar = FlexibleSpaceBar.createSettings(
  minExtent: minExtent,
  maxExtent: maxExtent,
  currentExtent: math.max(minExtent, maxExtent - shrinkOffset),
  toolbarOpacity: toolbarOpacity,
  child: Container(
    //  margin: const EdgeInsets.only(top: 10.0),
    padding: EdgeInsets.symmetric(horizontal: 12.0),
    child: SafeArea(
      child: Material(
        color: backgroundColor,
        borderRadius: BorderRadius.circular(8.0),
        elevation: elevation,
        child: ListTile(
          leading: leading ??
              (Scaffold.of(context).hasDrawer && automaticallyImplyLeading
                  ? IconButton(
                      icon: Icon(Icons.menu),
                      onPressed: () {
                        Scaffold.of(context).openDrawer();
                      },
                    )
                  : null),
          title: title,
          trailing: trailing ??
              (Scaffold.of(context).hasEndDrawer &&
                      automaticallyImplyLeading
                  ? IconButton(
                      icon: Icon(Icons.menu),
                      onPressed: () {
                        Scaffold.of(context).openEndDrawer();
                      },
                    )
                  : null),
        ),
      ),
    ),
    transform: platform == TargetPlatform.iOS
        ? Matrix4.translationValues(0.0, 0.0, 0.0)
        : Matrix4.translationValues(0.0, 8.0, 0.0),
  ),
);
return Container(child: floating ? _FloatingAppBar(child: appBar) : appBar);

} }