【问题标题】:How to create sticky shop button animation in Flutter?如何在 Flutter 中创建粘性商店按钮动画?
【发布时间】:2019-11-04 19:07:40
【问题描述】:

如何在 Flutter 中创建 Adidas 应用的粘性购买按钮动画。我尝试使用滚动控制器来监听用户的位置,然后使用动画容器,但这没有用,因为我必须在我的 initstate 中定义我的滚动控制器,而我的容器的高度是相对于我的设备的高度. 这是动画的视频链接: 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,])

【问题讨论】:

    标签: flutter dart


    【解决方案1】:

    输出

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

    【讨论】:

    • 很好的答案。但我正在使用一列,所以我不能使用索引。有什么想法吗?
    • 是的,我正在使用单子滚动视图。不,我没有理由不能切换到那个。但是我将如何使用您在示例中使用的索引?
    • 我已经添加了屏幕截图,因为我的代码非常冗长。
    • 当我的 FAB 到达某个小部件时,我想隐藏它
    • 小部件树就像 Scaffold(appbar,FAB,_body),_body= SingleChildSrollView child:Column[Container(child:Listview),Container(child:PageView(children:[GridView]),Container ,Container(这里应该是shop按钮的地方,代替FAB的那个),GridView,])
    【解决方案2】:

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

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

    【讨论】:

      【解决方案3】:

      当嵌入式按钮不可见时,我会显示一个 floatingActionButton。这是基于此线程的解决方案: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),
          );
        }
      }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2021-06-08
        • 1970-01-01
        • 2019-11-27
        • 2022-06-14
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多