水平列表视图动画

时间:2018-10-27 13:58:39

标签: flutter flutter-layout flutter-animation

我正在尝试创建一个像list view这样的列表视图,我得到了一些不错的东西,但它并不像运球那样流畅。

我猜这是因为我多次调用setState并在动画过程中更新树,但我不知道如何使其更平滑,也许只调用一次setState。 我正在使用继承的小部件通过树传递状态,这是编辑状态的定义

void onChangeIndex (int newIndex) {
    fadeText(_index, newIndex);
    changePosition(newIndex);
  }

  void fadeText(int oldIndex,int newIndex) async {
    setState(() {
      _visible[oldIndex] = !_visible[oldIndex];
        });
    await Future.delayed(Duration(milliseconds: 300));
    setState(() {
      _visible[newIndex] = !_visible[newIndex];
    debugPrint(_visible[oldIndex].toString());
    });     
  }

  void changePosition(newIndex) async {
    await Future.delayed(Duration(milliseconds: 50));    
    setState(() {
      _index = newIndex;
      _scrollController.animateTo((_index)*320.0, duration: Duration(milliseconds: 800), curve: Curves.fastOutSlowIn);
    });
  }

当我拖动列表视图时会调用此部分,其作用是滚动列表视图并为文本设置动画

这是我的列表视图中包含的卡,如果结构混乱,对不起,我感到很陌生。

GestureDetector(
      child: Padding(
        padding: EdgeInsets.all(6.0),
        child: Card(
          elevation: 0.0,
          color: Colors.transparent,
          child: Container(
              width: 295.0,
              child: Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  mainAxisAlignment: MainAxisAlignment.spaceBetween,
                  children: <Widget>[
                    Row(
                      children:<Widget>[ 
                        Expanded(
                          child: 
                          Card(
                            shape: RoundedRectangleBorder(
                              borderRadius: BorderRadius.all(Radius.circular(15.0)),
                            ),
                            elevation: 5.0,
                            child: ClipRRect(
                              borderRadius: BorderRadius.circular(10.0),
                              child: Image.asset('images/paris.jpg', fit: BoxFit.contain),
                            ),  
                          )
                        ),

                      ]
                    ),
                    Row(
                      children: <Widget>[
                        Expanded(
                          child: AnimatedOpacity(
                            opacity: StateManager.of(context).visible[widget.index] ? 1.0 : 0.0,
                            duration: Duration(milliseconds: 300),
                            child: Container(
                              height: 24.0,
                              child: Padding(
                                padding: const EdgeInsets.only(
                                  left: 8.0
                                ),
                                child: Text(
                                  "Paris, France", 
                                  style: TextStyle(
                                    color: Color.fromRGBO(0,0,0,0.9),
                                    fontSize: 24.0,
                                    fontWeight: FontWeight.bold,
                                    ),
                                  ),
                              )                             
                            )
                          )
                        ), 
                      ],
                    ),
                    Row(
                      children: <Widget>[
                        Expanded(
                          child: AnimatedOpacity(
                            opacity: StateManager.of(context).visible[widget.index] ? 1.0 : 0.0,
                            duration: Duration(milliseconds: 300),
                            child:  Container(
                              height: 30.0,
                              child: Padding(
                                padding: const EdgeInsets.only(
                                  left: 8.0
                                ),
                                child: Text(
                                  "Visiter ou bien aller au travail à vélo facilement grâce aux nombreux parkings à vélo présent dans cette ville.", 
                                  style: TextStyle(
                                    color: Color.fromRGBO(0,0,0,0.7),
                                    fontSize: 12.0,
                                    fontWeight: FontWeight.bold,
                                    ),
                                  ),
                              )  
                            )
                          )
                        ), 
                      ],
                    ),
                  ])),
          // shape: RoundedRectangleBorder(
          //   borderRadius: BorderRadius.all(Radius.circular(15.0)),
          // ),
        ),
      ),
      onHorizontalDragEnd: (details) { 
        var cardIndex = StateManager.of(context).index;                
        if(details.velocity.pixelsPerSecond.dx > 0) {
          if(cardIndex>0) {
            StateManager.of(context).onChangeIndex(cardIndex-1);
          }
        }else if(details.velocity.pixelsPerSecond.dx < 0){
          if(cardIndex<2) { 
            StateManager.of(context).onChangeIndex(cardIndex+1);
          }
        }
      },
    );

如果您有任何想法可以改善我设置状态以使动画更流畅的方式,那将对我有很大帮助。

2 个答案:

答案 0 :(得分:1)

首先,在setState调用之后使用await是不好的。如果由于用户导航到另一个页面而不再存在该小部件,则将引发异常。而是here is how you can create staggered (delayed) animations in Flutter


这是由PageController驱动的解决方案。文本淡入淡出动画的控制器可以在以下项目的状态中找到:

import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Playground',
      home: DestinationsPage(),
    );
  }
}

class DestinationsPage extends StatefulWidget {
  @override
  _DestinationsPageState createState() => _DestinationsPageState();
}

class _DestinationsPageState extends State<DestinationsPage> {
  PageController _pageController;
  int _selectedPage = 0;

  @override
  void initState() {
    super.initState();
    _pageController = PageController(
      initialPage: 0,
      viewportFraction: 0.8,
    )..addListener(_updateSelectedPage);
  }

  void _updateSelectedPage() {
    final closestPage = _pageController.page.round();
    final isClosestPageSelected = (_pageController.page - closestPage).abs() < 0.2;
    final selectedPage = isClosestPageSelected ? closestPage : null;
    if (_selectedPage != selectedPage) {
      setState(() {
        _selectedPage = selectedPage;
      });
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Destinations')),
      body: PageView.builder(
        controller: _pageController,
        itemBuilder: (context, index) {
          return _DestinationItem(
            page: index,
            selected: (index == _selectedPage),
          );
        },
      ),
    );
  }
}

class _DestinationItem extends StatefulWidget {
  // some content, in this case just
  final int page;

  // indicates that the page is selected and that the text should be visible
  final bool selected;

  const _DestinationItem({Key key, this.page, this.selected}) : super(key: key);

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

class _DestinationItemState extends State<_DestinationItem> with SingleTickerProviderStateMixin<_DestinationItem> {
  AnimationController _textTransitionController;

  @override
  void initState() {
    super.initState();
    _textTransitionController = AnimationController(
      vsync: this,
      duration: Duration(milliseconds: 400),
      value: widget.selected ? 1.0 : 0.0,
    );
  }

  @override
  void didUpdateWidget(_DestinationItem oldWidget) {
    super.didUpdateWidget(oldWidget);
    if (widget.selected != oldWidget.selected) {
      if (widget.selected) {
        _textTransitionController.forward();
      } else {
        _textTransitionController.reverse();
      }
    }
  }

  @override
  void dispose() {
    // important call, otherwise throws error if the PageView destroys
    // the widget while the fade transition is still running
    _textTransitionController.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: EdgeInsets.symmetric(horizontal: 8.0),
      child: Column(
        children: <Widget>[
          Container(
            height: 200.0,
            color: Colors.orange,
            margin: EdgeInsets.symmetric(horizontal: 8.0),
            child: Center(
              child: Text('Image ${widget.page}'),
            ),
          ),
          SizedBox(height: 16.0),
          FadeTransition(
            opacity: _textTransitionController,
            child: Text(
                'Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua.'),
          )
        ],
      ),
    );
  }
}

答案 1 :(得分:0)

这是与我的其他答案类似的解决方案,但是在这种情况下,文本不是PageView的一部分。使用AnimatedSwitcher淡入和淡出文本:

import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Playground',
      home: DestinationsPage(),
    );
  }
}

class DestinationsPage extends StatefulWidget {
  @override
  _DestinationsPageState createState() => _DestinationsPageState();
}

class _DestinationsPageState extends State<DestinationsPage> {
  PageController _pageController;
  int _selectedPage = 0;

  @override
  void initState() {
    super.initState();
    _pageController = PageController(
      initialPage: 0,
      viewportFraction: 0.8,
    )..addListener(_updateSelectedPage);
  }

  void _updateSelectedPage() {
    final closestPage = _pageController.page.round();
    final isClosestPageSelected = (_pageController.page - closestPage).abs() < 0.2;
    final selectedPage = isClosestPageSelected ? closestPage : null;
    if (_selectedPage != selectedPage) {
      setState(() {
        _selectedPage = selectedPage;
      });
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(title: Text('Destinations')),
        body: Column(
          children: <Widget>[
            SizedBox(
              height: 200.0,
              child: PageView.builder(
                controller: _pageController,
                itemBuilder: (context, index) {
                  return Container(
                    color: Colors.orange,
                    margin: EdgeInsets.symmetric(horizontal: 8.0),
                    child: Center(
                      child: Text('Image ${index}'),
                    ),
                  );
                },
              ),
            ),
            SizedBox(height: 16.0),
            Expanded(
              child: AnimatedSwitcher(
                duration: Duration(milliseconds: 500),
                switchInCurve: Interval(0.5, 1.0, curve: Curves.ease),
                switchOutCurve: Interval(0.5, 1.0, curve: Curves.ease),
                child: _buildSelectedPageText(),
              ),
            )
          ],
        )
    );
  }

  Widget _buildSelectedPageText() {
    if(_selectedPage != null) {
      return Text(
        'Text for page ${_selectedPage}!',
        key: ValueKey(_selectedPage), // setting key is important, see switcher docs
      );
    } else {
      return null;
    }
  }
}
相关问题