模仿iOS联络表单AppBar [英] Mimic iOS contact form AppBar

查看:86
本文介绍了模仿iOS联络表单AppBar的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试模仿iOS联系人表单应用栏。

I'm trying to mimic iOS contact form app bar.

扩展

崩溃

到目前为止是我到达的地方

Here is where I get so far

主屏幕

class CompanyScreen extends StatefulWidget {
  @override
  _CompanyScreenState createState() => _CompanyScreenState();
}

class _CompanyScreenState extends State<CompanyScreen> {
  @override
  Widget build(BuildContext context) {

    return Scaffold(
      body: CustomScrollView(
        slivers: <Widget>[
          SliverPersistentHeader(
            pinned: true,
            floating: true,
            delegate: SafeAreaPersistentHeaderDelegate(
                expandedHeight: 200,
                flexibleSpace:
                    SafeArea(child: Image.asset('assets/images/user.png'))),
          ),
          SliverList(
            delegate: SliverChildListDelegate([
              TextField(),
            ]),
          )
        ],
      ),
    );
  }
}

SliverHeader

SliverHeader

class SafeAreaPersistentHeaderDelegate extends SliverPersistentHeaderDelegate {
  final Widget title;

  final Widget flexibleSpace;

  final double expandedHeight;

  SafeAreaPersistentHeaderDelegate(
      {this.title, this.flexibleSpace, this.expandedHeight});

  @override
  Widget build(
      BuildContext context, double shrinkOffset, bool overlapsContent) {
    final Widget appBar = FlexibleSpaceBar.createSettings(
      minExtent: minExtent,
      maxExtent: maxExtent,
      currentExtent: max(minExtent, maxExtent - shrinkOffset),
      toolbarOpacity: 1,
      child: AppBar(
          actions: <Widget>[
            Container(
              height: 60,
              child: FlatButton(
                child: Text('Done'),
              ),
            )
          ],
          backgroundColor: Colors.blue,
          automaticallyImplyLeading: false,
          title: title,
          flexibleSpace: (title == null && flexibleSpace != null)
              ? Semantics(child: flexibleSpace, header: true)
              : flexibleSpace,
          centerTitle: true,
          toolbarOpacity: 1,
          bottomOpacity: 1.0),
    );
    return appBar;
  }

  @override
  double get maxExtent => expandedHeight;

  @override
  double get minExtent => 80;

  @override
  bool shouldRebuild(SafeAreaPersistentHeaderDelegate old) {
    if (old.flexibleSpace != flexibleSpace) {
      return true;
    }
    return false;
  }
}

更新:一切正常,但是我有一个问题图片下方的文字(添加照片),并在折叠时使该文字消失。使用这种解决方案,如果我将图像包装到一列中,则图像会扩大溢出范围并且无法缩放。

UPDATE: It all works but I have a problem add the text under the image (Add Photo) and make that text disappear when collapsed. With this solution, if I wrap the image into a column then image expands overflow and doesn't scale.

要求:


  1. AppBar和flex区域必须位于安全区域

  1. AppBar and the flex area must be in safe area

带有图像的窗口小部件的底部必须具有可以动态更改的文本(添加图像或更改图像),并且必须可单击

Widget with image must have text at the bottom which can be changed dynamically (Add image or Change image) and it must be clickable

当柔韧性区域以某种过渡折叠时,图像区域下的文本必须消失

The text under the image area must disappear when flex area is collapsed with some transition

在应用栏中添加标题,并带有操作按钮

Ability to add title in app bar lined up with action buttons

在应用栏中提供标题时,伸缩区域应在标题下方缩放,如果没有伸缩区域则应缩放进入上图所示的标题区域

When title in app bar is provided then flex area should scale bellow the title, if not flex area should scale into the title area as on the above image

任何对此表示赞赏的帮助

Any help with this greatly appreciated

推荐答案

我尝试了一下。.我不是条子专家,所以此解决方案可能并不完美。我已将您的代码作为起点。该列似乎取消了所有缩放,因此我手动进行了缩放。

I gave it a try.. I'm not an expert on slivers so this solution might not be perfect. I have taken your code as starting point. The column seems to deactivate all scaling so I scaled all manually.

这是您的应用栏

更新我对其进行了一些调整更像iOS应用栏,我还添加了额外的功能

UPDATE I have tweaked it a little so it feels more like iOS app bar plus I've added extra feature

import 'dart:math';

import 'package:flutter/material.dart';

double _defaultTextHeight = 14;
double _defaultTextPadding = 5;
double _defaultAppBarHeight = 60;
double _defaultMinAppBarHeight = 40;
double _unknownTextValue = 1;

class AppBarSliverHeader extends SliverPersistentHeaderDelegate {
  final String title;
  final double expandedHeight;
  final double safeAreaPadding;
  final Widget flexibleImage;
  final double flexibleSize;
  final String flexibleTitle;
  final double flexiblePadding;
  final bool flexToTop;
  final Function onTap;
  final Widget rightButton;
  final Widget leftButton;

  AppBarSliverHeader(
      {this.title,
      this.onTap,
      this.flexibleImage,
      @required this.expandedHeight,
      @required this.safeAreaPadding,
      this.flexibleTitle = '',
      this.flexToTop = false,
      this.leftButton,
      this.rightButton,
      this.flexibleSize = 30,
      this.flexiblePadding = 4});

  double _textPadding(double shrinkOffset) {
    return _defaultTextPadding * _scaleFactor(shrinkOffset);
  }

  double _widgetPadding(double shrinkOffset) {
    double offset;
    if (title == null) {
      offset = _defaultMinAppBarHeight * _scaleFactor(shrinkOffset);
    } else {
      if (flexToTop) {
        offset = _defaultAppBarHeight * _scaleFactor(shrinkOffset);
      } else {
        offset = (_defaultAppBarHeight - _defaultMinAppBarHeight) *
                _scaleFactor(shrinkOffset) +
            _defaultMinAppBarHeight;
      }
    }
    return offset;
  }

  double _topOffset(double shrinkOffset) {
    double offset;
    if (title == null) {
      offset = safeAreaPadding +
          (_defaultMinAppBarHeight * _scaleFactor(shrinkOffset));
    } else {
      if (flexToTop) {
        offset = safeAreaPadding +
            (_defaultAppBarHeight * _scaleFactor(shrinkOffset));
      } else {
        offset = safeAreaPadding +
            ((_defaultAppBarHeight - _defaultMinAppBarHeight) *
                _scaleFactor(shrinkOffset)) +
            _defaultMinAppBarHeight;
      }
    }

    return offset;
  }

  double _calculateWidgetHeight(double shrinkOffset) {
    double actualTextHeight = _scaleFactor(shrinkOffset) * _defaultTextHeight +
        _textPadding(shrinkOffset) +
        _unknownTextValue;

    final padding = title == null
        ? (2 * flexiblePadding)
        : flexToTop ? (2 * flexiblePadding) : flexiblePadding;

    final trueMinExtent = minExtent - _topOffset(shrinkOffset);

    final trueMaxExtent = maxExtent - _topOffset(shrinkOffset);

    double minWidgetSize =
        trueMinExtent - padding;

    double widgetHeight =
        ((trueMaxExtent - actualTextHeight) - shrinkOffset) - padding;

    return widgetHeight >= minWidgetSize ? widgetHeight : minWidgetSize;
  }

  double _scaleFactor(double shrinkOffset) {
    final ratio = (maxExtent - minExtent) / 100;
    double percentageHeight = shrinkOffset / ratio;
    double limitedPercentageHeight =
        percentageHeight >= 100 ? 100 : percentageHeight;
    return 1 - (limitedPercentageHeight / 100);
  }

  Widget _builtContent(BuildContext context, double shrinkOffset) {
    _topOffset(shrinkOffset);
    return SafeArea(
      bottom: false,
      child: Semantics(
        child: Padding(
          padding: title == null
              ? EdgeInsets.symmetric(vertical: flexiblePadding)
              : flexToTop
                  ? EdgeInsets.symmetric(vertical: flexiblePadding)
                  : EdgeInsets.only(bottom: flexiblePadding),
          child: GestureDetector(
            onTap: onTap,
            child: Column(
              mainAxisSize: MainAxisSize.min,
              children: <Widget>[
                LimitedBox(
                    maxWidth: _calculateWidgetHeight(shrinkOffset),
                    maxHeight: _calculateWidgetHeight(shrinkOffset),
                    child: Container(
                      decoration: BoxDecoration(
                          borderRadius: BorderRadius.all(Radius.circular(
                              _calculateWidgetHeight(shrinkOffset))),
                          color: Colors.white),
                      child: ClipRRect(
                        borderRadius: BorderRadius.circular(
                            _calculateWidgetHeight(shrinkOffset)),
                        child: flexibleImage,
                      ),
                    )),
                Padding(
                  padding: EdgeInsets.only(top: _textPadding(shrinkOffset)),
                  child: Text(
                    flexibleTitle,
                    textScaleFactor: _scaleFactor(shrinkOffset),
                    style: TextStyle(
                        fontSize: _defaultTextHeight,
                        color: Colors.white
                            .withOpacity(_scaleFactor(shrinkOffset)), height: 1),
                  ),
                )
              ],
            ),
          ),
        ),
        button: true,
      ),
    );
  }

  @override
  Widget build(
      BuildContext context, double shrinkOffset, bool overlapsContent) {
    final Widget appBar = FlexibleSpaceBar.createSettings(
      minExtent: minExtent,
      maxExtent: maxExtent,
      currentExtent: max(minExtent, maxExtent - shrinkOffset),
      toolbarOpacity: 1,
      child: AppBar(
          actions: <Widget>[rightButton == null ? Container() : rightButton],
          leading: leftButton == null ? Container() : leftButton,
          backgroundColor: Colors.blue,
          automaticallyImplyLeading: false,
          title: title != null
              ? Text(
                  title,
                  style: TextStyle(
                      color: flexToTop
                          ? Colors.white.withOpacity(_scaleFactor(shrinkOffset))
                          : Colors.white),
                )
              : null,
          flexibleSpace: Padding(
            padding: EdgeInsets.only(top: _widgetPadding(shrinkOffset)),
            child: _builtContent(context, shrinkOffset),
          ),
          centerTitle: true,
          toolbarOpacity: 1,
          bottomOpacity: 1.0),
    );
    return appBar;
  }

  @override
  double get maxExtent => expandedHeight + safeAreaPadding;

  @override
  double get minExtent => title == null
      ? _defaultAppBarHeight + safeAreaPadding
      : flexToTop
          ? _defaultAppBarHeight + safeAreaPadding
          : _defaultAppBarHeight + safeAreaPadding + flexibleSize;

  @override
  bool shouldRebuild(AppBarSliverHeader old) {
    if (old.flexibleImage != flexibleImage) {
      return true;
    }
    return false;
  }
}

这是用法

Scaffold(
      body: CustomScrollView(
        slivers: <Widget>[
          SliverPersistentHeader(
            pinned: true,
            floating: true,
            delegate: AppBarSliverHeader(
                expandedHeight: 250,
                safeAreaPadding: MediaQuery.of(context).padding.top,
                title: 'New Contact',
                flexibleImage: Image.asset('assets/images/avatar.png'),
                flexibleTitle: 'Add Image',
                flexiblePadding: 6,
                flexibleSize: 50,
                flexToTop: true,
                onTap: () {
                  print('hello');
                },
                leftButton: IconButton(
                  icon: Text('Cancel'),
                  iconSize: 60,
                  padding: EdgeInsets.zero,
                  onPressed: () {},
                ),
                rightButton: IconButton(
                  icon: Text('Done'),
                  iconSize: 60,
                  padding: EdgeInsets.zero,
                  onPressed: () {},
                )),
          ),
          SliverList(
            delegate: SliverChildListDelegate([
              TextField(),
            ]),
          )
        ],
      ),
    );

有些事情也让我感到惊讶。首先是文字大小。似乎文字大小不是实际的文字大小,因此我在此处添加了 _unknownTextValue 进行补偿。另外,即使将文字大小设置为0,文字小部件的大小仍为1px,因此我在注释代码中对此进行了补偿。另一件事是我想对图像使用 CircularAvatar ,但是显然, CircularAvatar 小部件在更改大小时内置了动画

There are some things which took me by surprise as well. First is text size. It seems like text size is not an actual text size so I've added _unknownTextValue there for compensation. Also even if text size is set to 0 then the Text widget has still 1px size so I've compensated that in commented code. Another thing is I wanted to use CircularAvatar for the image but apparently the CircularAvatar widget has built in animation when changing the size which interfere with app bar animation so I've built custom avatar.

更新:为了使实际的文本高度与字体大小相同,我在TextStyle中添加了height属性1。似乎可行,但是有时文本字段上偶尔会出现高达1px的溢出,因此我将_unknownTextValue保持在1px

UPDATE: To make actual text height same as font size, I have added height property 1 to TextStyle. It seems to work however there is still occasional overflow on the textfield of up to 1px so I've kept _unknownTextValue at 1px

正如我所说的,我不是Sliver专家,所以可能会有一个更好的解决方案,所以我建议您等待其他答案

As I said I'm not sliver expert so there might be a better solutions out there so I would suggest you to wait for other answers

注意:我只在2个iOS设备上进行过测试,因此您应该进一步测试以使用它

NOTE: I only tested it on 2 iOS devices so you should test further to use it

带有标题

没有标题

已激活标题和flexToTop

With Title and flexToTop activated

这篇关于模仿iOS联络表单AppBar的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆