如何在Flutter中创建粘性商店按钮动画?

时间:2019-06-22 00:48:03

标签: flutter dart

如何在Flutter中创建阿迪达斯应用程序的此粘性购买按钮动画。我试图使用滚动控制器来监听用户的位置,然后使用动画容器,但这没有用,因为我必须在初始状态下定义滚动控制器,而容器的高度是相对于设备的高度而言的。 这是动画的视频链接: https://drive.google.com/file/d/1TzIUBr6abRQI87xAVu4NOPG67aftzceK/view?usp=sharing

小部件树如下所示:

 Scaffold(appbar,FAB,_body),
_body= SingleChildSrollView child:Column[Container(child:Listview)
,Container(child:PageView(children:[GridView])
,Container
,Container(this is where the shop button should be, the one that replaces the FAB)
,GridView,])

3 个答案:

答案 0 :(得分:4)

输出

enter image description here

void main() => runApp(MaterialApp(home: Scaffold(body: HomePage(), appBar: AppBar())));

class HomePage extends StatefulWidget {
  @override
  _HomePageState createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> with WidgetsBindingObserver {
  ScrollController _controller = ScrollController();
  double _boxHeight = 200, _screenHeight;
  int _itemIndex = 5;
  bool _itemVisibility = true;

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

    double offsetEnd;
    WidgetsBinding.instance.addPostFrameCallback((_) {
      RenderBox box = context.findRenderObject();
      _screenHeight = box.globalToLocal(Offset(0, MediaQuery.of(context).size.height)).dy;
      offsetEnd = ((_itemIndex + 1) - (_screenHeight / _boxHeight)) * _boxHeight;
    });

    _controller.addListener(() {
      if (_controller.position.pixels >= offsetEnd) {
        if (_itemVisibility) setState(() => _itemVisibility = false);
      } else {
        if (!_itemVisibility) setState(() => _itemVisibility = true);
      }
    });
  }

  @override
  Widget build(BuildContext context) {
    return Stack(
      children: <Widget>[
        ListView.builder(
          controller: _controller,
          itemCount: 8,
          itemBuilder: (context, index) {
            return _buildBox(
              index: index,
              color: index == _itemIndex ? Colors.cyan : Colors.blue[((index + 1) * 100) % 900],
            );
          },
        ),
        Positioned(
          bottom: 0,
          right: 0,
          left: 0,
          child: Visibility(
            visible: _itemVisibility,
            child: _buildBox(index: _itemIndex, color: Colors.cyan),
          ),
        ),
      ],
    );
  }

  Widget _buildBox({int index, Color color}) {
    return Container(
      height: _boxHeight,
      color: color,
      alignment: Alignment.center,
      child: Text(
        "${index}",
        style: TextStyle(fontSize: 52, fontWeight: FontWeight.bold),
      ),
    );
  }
}

答案 1 :(得分:1)

另一个答案(可变高度框)

enter image description here

void main() => runApp(MaterialApp(home: Scaffold(body: HomePage(), appBar: AppBar())));

class HomePage extends StatefulWidget {
  @override
  _HomePageState createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> with WidgetsBindingObserver {
  ScrollController _controller = ScrollController();
  double _screenHeight, _hRatings = 350, _hSize = 120, _hWidth = 130, _hComfort = 140, _hQuality = 150, _hBuy = 130, _hQuestions = 400;
  bool _itemVisibility = true;

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

    double offsetEnd;
    WidgetsBinding.instance.addPostFrameCallback((_) {
      RenderBox box = context.findRenderObject();
      _screenHeight = box.globalToLocal(Offset(0, MediaQuery.of(context).size.height)).dy;
      offsetEnd = (_hRatings + _hSize + _hWidth + _hComfort + _hQuality + _hBuy) - _screenHeight;
    });

    _controller.addListener(() {
      if (_controller.position.pixels >= offsetEnd) {
        if (_itemVisibility) setState(() => _itemVisibility = false);
      } else {
        if (!_itemVisibility) setState(() => _itemVisibility = true);
      }
    });
  }

  @override
  Widget build(BuildContext context) {
    return Stack(
      children: <Widget>[
        ListView(
          controller: _controller,
          children: <Widget>[
            _buildBox(_hRatings, "Ratings box", Colors.blue[200]),
            _buildBox(_hSize, "Size box", Colors.blue[300]),
            _buildBox(_hWidth, "Width box", Colors.blue[400]),
            _buildBox(_hComfort, "Comfort box", Colors.blue[500]),
            _buildBox(_hQuality, "Quality box", Colors.blue[600]),
            _buildBox(_hBuy, "Buy box", Colors.orange[700]),
            _buildBox(_hQuestions, "Questions part", Colors.blue[800]),
          ],
        ),
        Positioned(
          bottom: 0,
          right: 0,
          left: 0,
          child: Visibility(
            visible: _itemVisibility,
            child: _buildBox(_hBuy, "Buy box", Colors.orange[700]),
          ),
        ),
      ],
    );
  }

  Widget _buildBox(double height, String text, Color color) {
    return Container(
      height: height,
      color: color,
      alignment: Alignment.center,
      child: Text(
        text,
        style: TextStyle(
          fontSize: 32,
          color: Colors.black,
          fontWeight: FontWeight.bold,
        ),
      ),
    );
  }
}

答案 2 :(得分:0)

当嵌入式按钮不可见时,我将显示一个floatActionButton。这是基于此线程的解决方案:How to know if a widget is visible within a viewport?

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

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

class MyApp extends StatefulWidget {
  @override
  State<StatefulWidget> createState() => new MyAppState();
}

class MyAppState extends State<MyApp> {
  GlobalKey<State> key = new GlobalKey();

  double fabOpacity = 1.0;

  @override
  Widget build(BuildContext context) {
return new MaterialApp(
  home: new Scaffold(
    appBar: new AppBar(
      title: new Text("Scrolling."),
    ),
    body: NotificationListener<ScrollNotification>(
      child: new ListView(
        itemExtent: 100.0,
        children: [
          ContainerWithBorder(),
          ContainerWithBorder(),
          ContainerWithBorder(),
          ContainerWithBorder(),
          ContainerWithBorder(),
          ContainerWithBorder(),
          ContainerWithBorder(),
          ContainerWithBorder(),
          new MyObservableWidget(key: key),
          ContainerWithBorder(),
          ContainerWithBorder(),
          ContainerWithBorder(),
          ContainerWithBorder(),
          ContainerWithBorder(),
          ContainerWithBorder(),
          ContainerWithBorder()
        ],
      ),
      onNotification: (ScrollNotification scroll) {
        var currentContext = key.currentContext;
        if (currentContext == null) return false;

        var renderObject = currentContext.findRenderObject();
        RenderAbstractViewport viewport = RenderAbstractViewport.of(renderObject);
        var offsetToRevealBottom = viewport.getOffsetToReveal(renderObject, 1.0);
        var offsetToRevealTop = viewport.getOffsetToReveal(renderObject, 0.0);

        if (offsetToRevealBottom.offset > scroll.metrics.pixels ||
            scroll.metrics.pixels > offsetToRevealTop.offset) {
          if (fabOpacity != 1.0) {
            setState(() {
              fabOpacity = 1.0;
            });
          }
        } else {
          if (fabOpacity == 1.0) {
            setState(() {
              fabOpacity = 0.0;
            });
          }
        }
        return false;
      },
    ),
    floatingActionButton: new Opacity(
      opacity: fabOpacity,
      child: Align(
        alignment: Alignment.bottomCenter,
        child: new FloatingActionButton.extended(
          label: Text('sticky buy button'),
          shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(4.0)),
          onPressed: () {
            print("YAY");
          },
        ),
      ),
    ),
  ),
);
  }
}

class MyObservableWidget extends StatefulWidget {
  const MyObservableWidget({Key key}) : super(key: key);

  @override
  State<StatefulWidget> createState() => new MyObservableWidgetState();
}

class MyObservableWidgetState extends State<MyObservableWidget> {
  @override
  Widget build(BuildContext context) {
    return new RaisedButton(
      onPressed: () {

      },
      color: Colors.lightGreenAccent,
      child: Text('This is my buy button', style: TextStyle(color: Colors.blue),),
    );
  }
}

class ContainerWithBorder extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return new Container(
      decoration: new BoxDecoration(border: new Border.all(), color: Colors.grey),
    );
  }
}