Анимация просмотра горизонтального списка

Я пытаюсь создать представление списка, подобное этому представлению списка, я получаю что-то не плохое, но это не так гладко, как на дрибле.

Я предполагаю, что это потому, что я вызываю 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 ответа

Прежде всего, используя 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.'),
          )
        ],
      ),
    );
  }
}

Вот решение, которое похоже на мой другой ответ, но в этом случае текст не является частью 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;
    }
  }
}
Другие вопросы по тегам