简体   繁体   English

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

[英]How to create sticky shop button animation in Flutter?

How can I create this sticky buy button animation of Adidas app in Flutter. 如何在Flutter中创建Adidas应用程序的粘性购买按钮动画。 I have tried to use a scroll controller to listen for the position of user and then use an animated container but it is of no use since I have to define my scroll controller in my initstate while the height of my containers are relative to my device's height. 我试图使用滚动控制器来监听用户的位置,然后使用动画容器,但它没有用,因为我必须在我的initstate中定义我的滚动控制器,而我的容器的高度是相对于我的设备的高度。 here is the link of video for the animation: https://drive.google.com/file/d/1TzIUBr6abRQI87xAVu4NOPG67aftzceK/view?usp=sharing 这是动画视频的链接: https//drive.google.com/file/d/1TzIUBr6abRQI87xAVu4NOPG67aftzceK/view?usp=sharing

this is what the widget tree looks like: 这就是小部件树的样子:

 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,])

Output : 输出

在此输入图像描述

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),
      ),
    );
  }
}

Another answer (variable height boxes) 另一个答案(可变高度框)

在此输入图像描述

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,
        ),
      ),
    );
  }
}

I would show a floatingActionButton when the embedded button is not visible. 当嵌入式按钮不可见时,我会显示一个floatingActionButton。 Here's a solution based on this thread : 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),
    );
  }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM