首页 > 解决方案 > 水平列表视图动画

问题描述

我正在尝试创建一个类似列表视图的列表视图,我得到的东西还不错,但它不像运球时那么流畅。

我猜这是因为我调用 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);
          }
        }
      },
    );

如果您知道如何改进设置状态以使动画更流畅的方式,那将对我有很大帮助。

标签: flutterflutter-layoutflutter-animation

解决方案


首先,在通话setState后使用await是不好的。如果小部件由于用户导航到不同的页面而不再存在,则会引发异常。相反,这是在 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.'),
          )
        ],
      ),
    );
  }
}

推荐阅读