Last active
September 8, 2020 06:16
-
-
Save kdmatrosov/2c7a8197fe5fbea5df88a33b14993c2a to your computer and use it in GitHub Desktop.
Revisions
-
kdmatrosov revised this gist
May 20, 2020 . 1 changed file with 1 addition and 1 deletion.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -316,7 +316,7 @@ class _MyHomePageState extends State<MyHomePage> { ## Используйте `const` Рекомендуется использовать ключевое слово `const` для значений, которые возможно инициализировать во время компиляции, а также при вызове конструктора виджета (если он поддерживает `const`, конечно), что позволяет работать с одним и тем же каноническим экземпляром, тем самым избегая повторных вычислений *(прим. подробнее про [работу const](https://habr.com/ru/post/501804/))*. Давайте ещё раз используем наш пример с `setState`, но в этот раз мы добавим счетчик, который будет увеличивать значение каждый раз на 1, когда мы нажимаем кнопку. ```java -
kdmatrosov revised this gist
May 20, 2020 . 1 changed file with 1 addition and 1 deletion.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -142,7 +142,7 @@ class FooterWidget extends StatelessWidget { Это обычная ошибка, которую многие совершают, когда начинают использовать Flutter и впервые сталкиваются с необходимостью обновить `StatefulWidget` с помощью `setState`. Следующий пример - это представление, содержащее квадрат в центре и `FloatingActionButton` кнопку, при каждом нажатии на которую вызывается изменение цвета. О, а еще на странице также есть виджет с фоновым изображением. Кроме того, мы добавим вызов `print` оператора внутри `build` метода каждого виджета, чтобы посмотреть, как он работает. ```java class _MyHomePageState extends State<MyHomePage> { Color _currentColor = Colors.grey; -
kdmatrosov revised this gist
May 20, 2020 . 1 changed file with 15 additions and 0 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -781,3 +781,18 @@ class _MyHomePageState extends State<MyHomePage> { } ``` Оба эти варианта намного эффективнее, чем простое использование виджета `Opacity`. ## Заключение Как я уже упоминал ранее, Flutter достаточно мощен, чтобы запускать наши приложения без проблем. Однако, всегда полезно следовать хорошим практикам и максимально оптимизировать наше приложение. Если хотите ещё больше узнать по данной теме, рекомендую видео-выступление Filip Hráček на Flutter Europe, где он рассказывает об оптимизации и [профилировании Flutter приложений](https://www.youtube.com/watch?v=vVg9It7cOfY). **Дополнительные материалы:** * https://flutter.dev/docs/perf/rendering/best-practices * https://api.flutter.dev/flutter/widgets/StatefulWidget-class.html#performance-considerations * https://api.flutter.dev/flutter/widgets/Opacity-class.html#transparent-image **Об авторе:** Diego Velásquez – это Flutter и Dart [GDE](https://en.wikipedia.org/wiki/Google_Developer_Expert) из Лимы, Перу. Он также входит в комитет по Flutter. Diego увлечен мобильными приложениями и работает в качестве архитектора мобильного программного обеспечения. Вы можете подписаться на него в [Twitter](https://twitter.com/diegoveloper). -
kdmatrosov revised this gist
May 20, 2020 . 1 changed file with 2 additions and 7 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -687,15 +687,10 @@ class _MyHomePageState extends State<MyHomePage> } ```  Мы видим, что анимация работает, однако, виджет `Opacity` (и, возможно, его поддерево) перестраивается каждый кадр, что не очень здорово. Но у нас для оптимизации есть следующие варианты: 1. Использовать `FadeTransition` ```java -
kdmatrosov revised this gist
May 20, 2020 . 1 changed file with 159 additions and 1 deletion.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -627,4 +627,162 @@ flutter: building `CounterWidget` ); } ``` Внешне поведение никак не изменилось, но если заглянем в консоль, то увидим там только одну запись, соответствующую увеличению счетчика, других обновлений нет. Таким образом мы оптимизировали нашу анимацию. ## Не используйте виджет `Opacity`, особенно, в анимациях Давайте еще немного про анимации поговорим. Если нам что-то надо скрыть или показать, то мы обычно применяем виджет `Opacity`. Давайте переделаем предыдущий пример, но вместо `Transform`, мы будем использовать `Opacity`, чтобы наш виджет исчезал и появлялся снова и снова. ```java class _MyHomePageState extends State<MyHomePage> with SingleTickerProviderStateMixin { AnimationController _controller; int counter = 0; void _onPressed() { setState(() { counter++; }); _controller.forward(from: 0.0); } @override void initState() { _controller = AnimationController( vsync: this, duration: const Duration(milliseconds: 600)); _controller.addStatusListener((status) { if (status == AnimationStatus.completed) { _controller.reverse(); } }); super.initState(); } @override void dispose() { _controller.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return Scaffold( floatingActionButton: FloatingActionButton( onPressed: _onPressed, splashColor: Colors.red, child: Icon(Icons.slow_motion_video), ), body: AnimatedBuilder( animation: _controller, child: CounterWidget( counter: counter, ), builder: (_, child) => Opacity( opacity: (1 - _controller.value), child: child, ), ), ); } } ```  As we see the animation works, however, animating Opacity widget directly causes the widget (and possibly its subtree) to rebuild each frame, which is not very efficient. So to improve and optimize this we have 2 options: 1 - Using FadeTransition Однако, как мы видим, анимация работает, анимация виджета непрозрачности непосредственно заставляет виджет (и, возможно, его поддерево) перестраивать каждый кадр, что не очень эффективно. Так что для оптимизации у нас есть следующие варианты: 1. Использовать `FadeTransition` ```java class _MyHomePageState extends State<MyHomePage> with SingleTickerProviderStateMixin { AnimationController _controller; int counter = 0; void _onPressed() { setState(() { counter++; }); _controller.forward(from: 0.0); } @override void initState() { _controller = AnimationController( vsync: this, duration: const Duration(milliseconds: 600)); _controller.addStatusListener((status) { if (status == AnimationStatus.completed) { _controller.reverse(); } }); super.initState(); } @override void dispose() { _controller.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return Scaffold( floatingActionButton: FloatingActionButton( onPressed: _onPressed, splashColor: Colors.red, child: Icon(Icons.slow_motion_video), ), body: FadeTransition( opacity: Tween(begin: 1.0, end: 0.0).animate(_controller), child: CounterWidget( counter: counter, ), ), ); } } ``` 2. Использовать `AnimatedOpacity` ```java const duration = const Duration(milliseconds: 600); class _MyHomePageState extends State<MyHomePage> { int counter = 0; double opacity = 1.0; void _onPressed() async { counter++; setState(() { opacity = 0.0; }); await Future.delayed(duration); setState(() { opacity = 1.0; }); } @override Widget build(BuildContext context) { return Scaffold( floatingActionButton: FloatingActionButton( onPressed: _onPressed, splashColor: Colors.red, child: Icon(Icons.slow_motion_video), ), body: AnimatedOpacity( opacity: opacity, duration: duration, child: CounterWidget( counter: counter, ), ), ); } } ``` Оба эти варианта намного эффективнее, чем простое использование виджета `Opacity`. -
kdmatrosov revised this gist
May 19, 2020 . 1 changed file with 2 additions and 2 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -519,7 +519,7 @@ class MyHomePage extends StatelessWidget { Часто мы хотим добавить анимацию к нашим виджетам. В таких случаях обычно мы просто добавляем слушателя *(addListener)* для нашего `AnimationController` и вызываем `setState`. Но, как мы видели в самом начале, так работает не лучшим образом. Вместо этого мы будем использовать виджет `AnimatedBuilder` для обновления только того виджета, который мы хотим анимировать. Давайте создадим экран, который содержит виджет в центре со значением счетчика и кнопку, при нажатии на которую виджет вращается на 360 градусов. После этого добавим вывод в консоль в виджет счетчика `CounterWidget`, и нажмём на кнопку, чтобы посмотреть, что произойдет. ```java class _MyHomePageState extends State<MyHomePage> with SingleTickerProviderStateMixin { @@ -599,7 +599,7 @@ flutter: building `CounterWidget` ... flutter: building `CounterWidget` ``` Мы видим, что в начале вращения число в нашем виджете увеличивается на 1, но в консоли слишком много логов, хотя должен был появиться только один, после того, как мы задали значение счетчика с помощью `setState`. Всё просто! Надо использовать у `AnimatedBuilder` свойство `child`, которое позволяет нам кэшировать виджеты для повтороного использования их в анимации. Мы делаем это потому, что этот виджет не будет меняться, единственное, что он будет делать, это вращаться, но для этого у нас есть `Transform` виджет. ```java -
kdmatrosov revised this gist
May 19, 2020 . 1 changed file with 2 additions and 5 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -599,10 +599,7 @@ flutter: building `CounterWidget` ... flutter: building `CounterWidget` ``` Мы видим, что в начале вращения число в нашем виджете увеличивается на 1, но в консоли слишком много логов, хотя должен был появиться только один, после того, как мы задаем значение счетчика с помощью `setState`. Всё просто! Надо использовать у `AnimatedBuilder` свойство `child`, которое позволяет нам кэшировать виджеты для повтороного использования их в анимации. Мы делаем это потому, что этот виджет не будет меняться, единственное, что он будет делать, это вращаться, но для этого у нас есть `Transform` виджет. ```java @@ -630,4 +627,4 @@ Simple, we use the child attribute provided by the AnimatedBuilder, which allows ); } ``` Внешне поведение никак не изменилось, но если заглянем в консоль, то увидим там только одну запись, соответствующую увеличению счетчика, других обновлений нет. Таким образом мы оптимизировали нашу анимацию. -
kdmatrosov revised this gist
May 19, 2020 . 1 changed file with 2 additions and 1 deletion.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -629,4 +629,5 @@ Simple, we use the child attribute provided by the AnimatedBuilder, which allows ), ); } ``` Внешне поведкние никак не изменилось, но если заглянем в консоль, то увидим там только одну запись, соответствующую увеличению счетчика, других обновлений нет. Таким образом мы оптимизировали нашу анимацию. -
kdmatrosov revised this gist
May 19, 2020 . 1 changed file with 30 additions and 1 deletion.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -600,4 +600,33 @@ flutter: building `CounterWidget` flutter: building `CounterWidget` ``` We see that while rotating it is rebuilding our widget. However, there are many print statements, how can we avoid that? (It's ok to rebuild just one time because the setState we use to refresh the counter). Мы видим, что в начале вращения число в нашем виджете увеличивается на 1, но в консоли слишком много логов. Как мы можем этого избежать? (Это нормально перестроить виджет один раз, так как значение счетчика мы задаем с помощью `setState`). Simple, we use the child attribute provided by the AnimatedBuilder, which allows us to cache widgets to reuse them in our animation. We do this because that widget is not going to change, the only thing it will do is rotate, but for that we have the Transform widget. Всё просто! Надо использовать у `AnimatedBuilder` свойство `child`, которое позволяет нам кэшировать виджеты для повтороного использования их в анимации. Мы делаем это потому, что этот виджет не будет меняться, единственное, что он будет делать, это вращаться, но для этого у нас есть `Transform` виджет. ```java @override Widget build(BuildContext context) { return Scaffold( floatingActionButton: FloatingActionButton( onPressed: _onPressed, splashColor: Colors.red, child: Icon(Icons.slow_motion_video), ), body: AnimatedBuilder( animation: _controller, child: CounterWidget( counter: counter, ), builder: (_, child) => Transform( alignment: Alignment.center, transform: Matrix4.identity() ..setEntry(3, 2, 0.001) ..rotateY(360 * _controller.value * (pi / 180.0)), child: child, ), ), ); } ``` -
kdmatrosov revised this gist
May 19, 2020 . 1 changed file with 14 additions and 1 deletion.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -519,7 +519,7 @@ class MyHomePage extends StatelessWidget { Часто мы хотим добавить анимацию к нашим виджетам. В таких случаях обычно мы просто добавляем слушателя *(addListener)* для нашего `AnimationController` и вызываем `setState`. Но, как мы видели в самом начале, так работает не лучшим образом. Вместо этого мы будем использовать виджет `AnimatedBuilder` для обновления только того виджета, который мы хотим анимировать. Давайте создадим экран, который содержит виджет в центре со значением счетчика и кнопку, при нажатии на которую виджет вращается на 360 градусов. После этого добавим вывод в консоль в виджет счетчика `CounterWidget`, чтобы посмотреть, что произойдет, и понажимаем на кнопку. ```java class _MyHomePageState extends State<MyHomePage> with SingleTickerProviderStateMixin { @@ -588,3 +588,16 @@ class CounterWidget extends StatelessWidget { } ```  Получили в консоли ряд сообщений. ``` flutter: building `CounterWidget` flutter: building `CounterWidget` flutter: building `CounterWidget` flutter: building `CounterWidget` flutter: building `CounterWidget` ... flutter: building `CounterWidget` ``` We see that while rotating it is rebuilding our widget. However, there are many print statements, how can we avoid that? (It's ok to rebuild just one time because the setState we use to refresh the counter). Мы видим, что в начале вращения число в нашем виджете увеличивается на 1, но в консоли слишком много логов. Как мы можем этого избежать? (Это нормально перестроить виджет один раз, так как значение счетчика мы задаем с помощью `setState`). -
kdmatrosov revised this gist
May 19, 2020 . 1 changed file with 1 addition and 0 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -587,3 +587,4 @@ class CounterWidget extends StatelessWidget { } } ```  -
kdmatrosov revised this gist
May 19, 2020 . 1 changed file with 69 additions and 0 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -518,3 +518,72 @@ class MyHomePage extends StatelessWidget { ## Избегайте ненужных перестроений виджетов внутри `AnimatedBuilder` Часто мы хотим добавить анимацию к нашим виджетам. В таких случаях обычно мы просто добавляем слушателя *(addListener)* для нашего `AnimationController` и вызываем `setState`. Но, как мы видели в самом начале, так работает не лучшим образом. Вместо этого мы будем использовать виджет `AnimatedBuilder` для обновления только того виджета, который мы хотим анимировать. Давайте создадим экран, который содержит виджет в центре со значением счетчика и кнопку, при нажатии на которую виджет вращается на 360 градусов. ```java class _MyHomePageState extends State<MyHomePage> with SingleTickerProviderStateMixin { AnimationController _controller; int counter = 0; void _onPressed() { setState(() { counter++; }); _controller.forward(from: 0.0); } @override void initState() { _controller = AnimationController( vsync: this, duration: const Duration(milliseconds: 600)); super.initState(); } @override void dispose() { _controller.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return Scaffold( floatingActionButton: FloatingActionButton( onPressed: _onPressed, splashColor: Colors.red, child: Icon(Icons.slow_motion_video), ), body: AnimatedBuilder( animation: _controller, builder: (_, child) => Transform( alignment: Alignment.center, transform: Matrix4.identity() ..setEntry(3, 2, 0.001) ..rotateY(360 * _controller.value * (pi / 180.0)), child: CounterWidget( counter: counter, ), ), ), ); } } class CounterWidget extends StatelessWidget { final int counter; const CounterWidget({Key key, this.counter}) : super(key: key); @override Widget build(BuildContext context) { print('building `CounterWidget`'); return Center( child: Text( counter.toString(), style: Theme.of(context).textTheme.display4.apply(fontWeightDelta: 3), ), ); } } ``` -
kdmatrosov revised this gist
May 19, 2020 . 1 changed file with 1 addition and 1 deletion.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -517,4 +517,4 @@ class MyHomePage extends StatelessWidget { ## Избегайте ненужных перестроений виджетов внутри `AnimatedBuilder` Часто мы хотим добавить анимацию к нашим виджетам. В таких случаях обычно мы просто добавляем слушателя *(addListener)* для нашего `AnimationController` и вызываем `setState`. Но, как мы видели в самом начале, так работает не лучшим образом. Вместо этого мы будем использовать виджет `AnimatedBuilder` для обновления только того виджета, который мы хотим анимировать. -
kdmatrosov revised this gist
May 19, 2020 . 1 changed file with 2 additions and 0 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -516,3 +516,5 @@ class MyHomePage extends StatelessWidget { С этим небольшим изменением мы мгновенно переходим в самый низ без каких-либо задержек. ## Избегайте ненужных перестроений виджетов внутри `AnimatedBuilder` Часто мы хотим добавить анимацию к нашим виджетам. В таких случаях обычно мы просто добавляем слушателя к нашему `AnimationController` и вызываем `setState`. Но, как мы видели в самом начале, это не очень хорошая практика. Вместо этого мы будем использовать виджет `AnimatedBuilder` для обновления только того виджета, который мы хотим анимировать. -
kdmatrosov revised this gist
May 19, 2020 . 1 changed file with 2 additions and 0 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -514,3 +514,5 @@ class MyHomePage extends StatelessWidget {  С этим небольшим изменением мы мгновенно переходим в самый низ без каких-либо задержек. ## Избегайте ненужных перестроений виджетов внутри `AnimatedBuilder` -
kdmatrosov revised this gist
May 19, 2020 . 1 changed file with 39 additions and 0 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -474,4 +474,43 @@ class MyHomePage extends StatelessWidget { Как можно увидеть на анимации выше, переход происходит очень долго (~10 секунд). Так получается из-за того, что дочерние элементы сами определяют свой размер. Это даже блокирует UI! Чтобы избежать этого, мы должны использовать свойство `itemExtent`, благодаря которому при прокрутке не совершается лишней работы по расчету позиции скролла, так как размеры элементов заранее известны. ```java class MyHomePage extends StatelessWidget { final widgets = List.generate( 10000, (index) => Container( color: Colors.primaries[index % Colors.primaries.length], child: ListTile( title: Text('Index: $index'), ), ), ); final _scrollController = ScrollController(); void _onPressed() async { _scrollController.jumpTo( _scrollController.position.maxScrollExtent, ); } @override Widget build(BuildContext context) { return Scaffold( floatingActionButton: FloatingActionButton( onPressed: _onPressed, splashColor: Colors.red, child: Icon(Icons.slow_motion_video), ), body: ListView( controller: _scrollController, children: widgets, itemExtent: 200, ), ); } } ```  С этим небольшим изменением мы мгновенно переходим в самый низ без каких-либо задержек. -
kdmatrosov revised this gist
May 19, 2020 . 1 changed file with 43 additions and 1 deletion.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -429,7 +429,49 @@ class BackgroundWidget extends StatelessWidget { ## Используйте `itemExtent` для `ListView` при больших списках Иногда, когда у нас есть очень длинный список, и мы хотим быстро переместиться по нему, например, в самый конец, очень важно использовать `itemExtent`. Давайте рассмотрим простой пример. У нас есть список из 10 тысяч элементов. При нажатии на кнопку мы перейдем к последнему элементу. В этом примере мы не будем использовать `itemExtent` и позволим элементам списка самим определить свой размер. ```java class MyHomePage extends StatelessWidget { final widgets = List.generate( 10000, (index) => Container( height: 200.0, color: Colors.primaries[index % Colors.primaries.length], child: ListTile( title: Text('Index: $index'), ), ), ); final _scrollController = ScrollController(); void _onPressed() async { _scrollController.jumpTo( _scrollController.position.maxScrollExtent, ); } @override Widget build(BuildContext context) { return Scaffold( floatingActionButton: FloatingActionButton( onPressed: _onPressed, splashColor: Colors.red, child: Icon(Icons.slow_motion_video), ), body: ListView( controller: _scrollController, children: widgets, ), ); } } ```  Как можно увидеть на анимации выше, переход происходит очень долго (~10 секунд). Так получается из-за того, что дочерние элементы сами определяют свой размер. Это даже блокирует UI! Чтобы избежать этого, мы должны использовать свойство `itemExtent`, благодаря которому при прокрутке не совершается лишней работы по расчету позиции скролла, так как размеры элементов заранее известны. -
kdmatrosov revised this gist
May 19, 2020 . 1 changed file with 8 additions and 1 deletion.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -425,4 +425,11 @@ class BackgroundWidget extends StatelessWidget { } } ``` Теперь после клика по кнопке мы видим вывод только для основного виджета (конечно, если использовать те подходы, что я описал выше, избавиться можно и от этого вывода) и избегаем перестроения виджета, вызванного с `const`. ## Используйте `itemExtent` для `ListView` при больших списках Иногда, когда у нас есть очень длинный список, и мы хотим быстро переместиться по нему, например, в самый конец, то очень важно использовать `itemExtent`. Давайте рассмотрим простой пример. У нас есть список из 10 тысяч элементов. При нажатии на кнопку мы перейдем к последнему элементу. В этом примере мы не будем использовать `itemExtent` и позволим элементам списка самим определить свой размер. -
kdmatrosov revised this gist
May 19, 2020 . 1 changed file with 1 addition and 1 deletion.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -425,4 +425,4 @@ class BackgroundWidget extends StatelessWidget { } } ``` Теперь после клика по кнопке мы видим вывод только для основного виджета (конечно, если использовать те подходы, что я описал выше, избавиться можно и от этого вывода) и избегаем перестроения виджета, вызванного с `const`. -
kdmatrosov revised this gist
May 19, 2020 . 1 changed file with 54 additions and 2 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -369,8 +369,60 @@ class BackgroundWidget extends StatelessWidget { ```  У нас снова 2 вывода в консоли, один из которых относится к основнову виджету, а другой – к `BackgroundWidget`. Каждый раз, когда мы нажимаем кнопку, мы видим, что дочерний виджет также перестраивается, хотя его содержимое никак не меняется. ``` flutter: building `MyHomePage` flutter: building `BackgroundWidget` ``` А сейчас добавим `const` при работе с виджетом `BackgroundWidget`: ```java class _MyHomePageState extends State<MyHomePage> { int _counter = 0; void _onPressed() { setState(() { _counter++; }); } @override Widget build(BuildContext context) { print('building `MyHomePage`'); return Scaffold( floatingActionButton: FloatingActionButton( onPressed: _onPressed, child: Icon(Icons.colorize), ), body: Stack( children: [ Positioned.fill( child: const BackgroundWidget(), ), Center( child: Text( _counter.toString(), style: Theme.of(context).textTheme.display4.apply( color: Colors.white, fontWeightDelta: 2, ), )), ], ), ); } } class BackgroundWidget extends StatelessWidget { const BackgroundWidget(); @override Widget build(BuildContext context) { print('building `BackgroundWidget`'); return Image.network( 'https://cdn.pixabay.com/photo/2017/08/30/01/05/milky-way-2695569_960_720.jpg', fit: BoxFit.cover, ); } } ``` Теперь после клика по кнопке мы видим вывод только для основного виджета (конечно, если использовать те подходы, что я описал выше, избавиться можно и от этого вывода) и мы избегаем перестроения виджета, вызванного с `const`. -
kdmatrosov revised this gist
May 18, 2020 . 1 changed file with 7 additions and 0 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -366,4 +366,11 @@ class BackgroundWidget extends StatelessWidget { ); } } ```  У нас снова 2 вывода в консоли, один из которых относится к основнову виджету, а другой – к `BackgroundWidget`. Каждый раз, когда мы нажимаем кнопку, мы видим, что дочерний виджет также перестраивается, хотя содержимое никак не меняется. ``` flutter: building `MyHomePage` flutter: building `BackgroundWidget` ``` -
kdmatrosov revised this gist
May 18, 2020 . 1 changed file with 50 additions and 0 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -317,3 +317,53 @@ class _MyHomePageState extends State<MyHomePage> { ## Используйте `const` Рекомендуется использовать ключевое слово `const` для значений, которые возможно инициализировать во время компиляции, а также при вызове конструктора виджета (если он поддерживает `const`, конечно), что позволяет работать с одним и тем же каноническим экземпляром, тем самым избегая повторных вычислений *(прим. подробнее про [работу const(https://habr.com/ru/post/501804/))*. Давайте ещё раз используем наш пример с `setState`, но в этот раз мы добавим счетчик, который будет увеличивать значение каждый раз на 1, когда мы нажимаем кнопку. ```java class _MyHomePageState extends State<MyHomePage> { int _counter = 0; void _onPressed() { setState(() { _counter++; }); } @override Widget build(BuildContext context) { print('building `MyHomePage`'); return Scaffold( floatingActionButton: FloatingActionButton( onPressed: _onPressed, child: Icon(Icons.colorize), ), body: Stack( children: [ Positioned.fill( child: BackgroundWidget(), ), Center( child: Text( _counter.toString(), style: Theme.of(context).textTheme.display4.apply( color: Colors.white, fontWeightDelta: 2, ), )), ], ), ); } } class BackgroundWidget extends StatelessWidget { @override Widget build(BuildContext context) { print('building `BackgroundWidget`'); return Image.network( 'https://cdn.pixabay.com/photo/2017/08/30/01/05/milky-way-2695569_960_720.jpg', fit: BoxFit.cover, ); } } ``` -
kdmatrosov revised this gist
May 18, 2020 . 1 changed file with 4 additions and 0 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -313,3 +313,7 @@ class _MyHomePageState extends State<MyHomePage> { } ``` Самое прекрасное, что и в этом случае у нас нет ненужных обновлений. ## Используйте `const` Рекомендуется использовать ключевое слово `const` для значений, которые возможно инициализировать во время компиляции, а также при вызове конструктора виджета (если он поддерживает `const`, конечно), что позволяет работать с одним и тем же каноническим экземпляром, тем самым избегая повторных вычислений *(прим. подробнее про [работу const(https://habr.com/ru/post/501804/))*. -
kdmatrosov revised this gist
May 18, 2020 . No changes.There are no files selected for viewing
-
kdmatrosov revised this gist
May 18, 2020 . 1 changed file with 1 addition and 1 deletion.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -253,7 +253,7 @@ class _MyHomePageState extends State<MyHomePage> { ``` Кликнем по кнопке и... ничего не увидим в консоли. Это работает, как и ожидалось, мы просто обновляем только тот виджет, который нам нужен *(прим. подробнее про [ValueListenableBuilder и ValueNotifier](https://www.youtube.com/watch?v=s-ZG-jS5QHQ))*. Но есть еще один интересный виджет, который мы также можем использовать в этом случае, если мы хотим дополнительно разделить бизнес-логику и представление (но, возможно, добавив немного логики в него) и обрабатывать больше данных в вне виджета *(в уведомителе)*. Снова тот же пример, но уже с `ChangeNotifier`. ```java -
kdmatrosov revised this gist
May 18, 2020 . 1 changed file with 109 additions and 1 deletion.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -202,6 +202,114 @@ flutter: building `BackgroundWidget` ``` Каждый раз, когда мы нажимаем кнопку, мы обновляем весь экран: `Scaffold`, `BackgroundWidget` и, наконец, то, что и хотели обновить, – квадрат-`Container`. Как мы уже выяснили выше, перестраивать виджеты без необходимости – нехорошая практика. Обновляем только то, что нам нужно. Многие знают, что это можно сделать с помощью различных пакетов управления состоянием: [flutter_bloc](https://pub.dev/packages/flutter_bloc), [mobx](https://pub.dev/packages/mobx), [provider](https://pub.dev/packages/provider) и т. д. Но мало кто знает, что также это можно сделать с помощью классов, которые Flutter уже предлагает из коробки, без каких-либо сторонных библиотек. Давайте, рассмотрим тот же пример, но обновленный с помощью `ValueNotifier`. ```java class _MyHomePageState extends State<MyHomePage> { final _colorNotifier = ValueNotifier<Color>(Colors.grey); Random _random = new Random(); void _onPressed() { int randomNumber = _random.nextInt(30); _colorNotifier.value = Colors.primaries[randomNumber % Colors.primaries.length]; } @override void dispose() { _colorNotifier.dispose(); super.dispose(); } @override Widget build(BuildContext context) { print('building `MyHomePage`'); return Scaffold( floatingActionButton: FloatingActionButton( onPressed: _onPressed, child: Icon(Icons.colorize), ), body: Stack( children: [ Positioned.fill( child: BackgroundWidget(), ), Center( child: ValueListenableBuilder( valueListenable: _colorNotifier, builder: (_, value, __) => Container( height: 150, width: 150, color: value, ), ), ), ], ), ); } } ``` Кликнем по кнопке и... ничего не увидим в консоли. Это работает, как и ожидалось, мы просто обновляем только тот виджет, который нам нужен *(прим. подробнее про [ValueListenableBuilder и ValueNotifier](https://www.youtube.com/watch?v=s-ZG-jS5QHQ))*. Но есть еще один интересный виджет, который мы также можем использовать в случае, если мы хотим разделить бизнес-логику и представление (но, возможно, добавив немного логики в него) и обрабатывать больше данных в уведомителе. Снова тот же пример, но уже с `ChangeNotifier`. ```java //------ ChangeNotifier class ----// class MyColorNotifier extends ChangeNotifier { Color myColor = Colors.grey; Random _random = new Random(); void changeColor() { int randomNumber = _random.nextInt(30); myColor = Colors.primaries[randomNumber % Colors.primaries.length]; notifyListeners(); } } //------ State class ----// class _MyHomePageState extends State<MyHomePage> { final _colorNotifier = MyColorNotifier(); void _onPressed() { _colorNotifier.changeColor(); } @override void dispose() { _colorNotifier.dispose(); super.dispose(); } @override Widget build(BuildContext context) { print('building `MyHomePage`'); return Scaffold( floatingActionButton: FloatingActionButton( onPressed: _onPressed, child: Icon(Icons.colorize), ), body: Stack( children: [ Positioned.fill( child: BackgroundWidget(), ), Center( child: AnimatedBuilder( animation: _colorNotifier, builder: (_, __) => Container( height: 150, width: 150, color: _colorNotifier.myColor, ), ), ), ], ), ); } } ``` Самое прекрасное, что и в этом случае у нас нет ненужных обновлений. -
kdmatrosov revised this gist
May 18, 2020 . 1 changed file with 5 additions and 0 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -200,3 +200,8 @@ class BackgroundWidget extends StatelessWidget { flutter: building `MyHomePage` flutter: building `BackgroundWidget` ``` Каждый раз, когда мы нажимаем кнопку, мы обновляем весь экран: `Scaffold`, `BackgroundWidget` и, наконец, то, что и хотели обновить, – квадрат-`Container`. Как мы уже выяснили выше, перестраивать виджеты без необходимости – нехорошая практика. Обновляем только то, что нам нужно. Многие знают, что это можно сделать с помощью различных пакетов управления состоянием, такого как [flutter_bloc](https://pub.dev/packages/flutter_bloc), [mobx](https://pub.dev/packages/mobx), [provider](https://pub.dev/packages/provider) и т. д. Но мало кто знает, что также это можно сделать с помощью классов, которые Flutter уже предлагает из коробки, без каких-либо сторонных библиотек. Давайте, рассмотрим тот же пример, но обновленный с помощью `ValueNotifier`. -
kdmatrosov revised this gist
May 18, 2020 . 1 changed file with 64 additions and 1 deletion.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -136,4 +136,67 @@ class FooterWidget extends StatelessWidget { ``` У `Stateful`/`Stateless` виджетов есть специальный механизм "кэширования", учитывающий ключ, тип виджета и его атрибуты, который позволяет не перестраивать виджет без необходимости. Кроме того, это помогает нам инкапсулировать и рефакторировать наши виджеты. ([Разделяй и властвуй](https://en.wikipedia.org/wiki/Divide-and-conquer_algorithm)) И было бы неплохо добавить `const` в наши виджеты. Позже мы увидим, почему это важно. ## Избегайте обновления всех виджетов Это обычная ошибка, которую многие совершают, когда начинают использовать Flutter и впервые сталкиваются с необходимостью обновить `StatefulWidget` с помощью `setState`. Следующий пример - это представление, содержащее квадрат в центре и `FloatingActionButton` кнопку, при каждом нажатии на которую вызывается изменение цвета. О, а еще на странице также есть виджет с фоновым изображением. Кроме того, мы добавим несколько `print` операторов внутри `build` метода каждого виджета, чтобы посмотреть, как он работает. ```java class _MyHomePageState extends State<MyHomePage> { Color _currentColor = Colors.grey; Random _random = new Random(); void _onPressed() { int randomNumber = _random.nextInt(30); setState(() { _currentColor = Colors.primaries[randomNumber % Colors.primaries.length]; }); } @override Widget build(BuildContext context) { print('building `MyHomePage`'); return Scaffold( floatingActionButton: FloatingActionButton( onPressed: _onPressed, child: Icon(Icons.colorize), ), body: Stack( children: [ Positioned.fill( child: BackgroundWidget(), ), Center( child: Container( height: 150, width: 150, color: _currentColor, ), ), ], ), ); } } class BackgroundWidget extends StatelessWidget { @override Widget build(BuildContext context) { print('building `BackgroundWidget`'); return Image.network( 'https://cdn.pixabay.com/photo/2017/08/30/01/05/milky-way-2695569_960_720.jpg', fit: BoxFit.cover, ); } } ```  После клика по кнопке в консоли мы увидим два вывода ``` flutter: building `MyHomePage` flutter: building `BackgroundWidget` ``` -
kdmatrosov revised this gist
May 18, 2020 . 1 changed file with 1 addition and 1 deletion.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -134,6 +134,6 @@ class FooterWidget extends StatelessWidget { } } ``` У `Stateful`/`Stateless` виджетов есть специальный механизм "кэширования", учитывающий ключ, тип виджета и его атрибуты, который позволяет не перестраивать виджет без необходимости. Кроме того, это помогает нам инкапсулировать и рефакторировать наши виджеты. ([Разделяй и властвуй](https://en.wikipedia.org/wiki/Divide-and-conquer_algorithm)) И было бы неплохо добавить `const` в наши виджеты. Позже мы увидим, почему это важно. -
kdmatrosov revised this gist
May 18, 2020 . 1 changed file with 4 additions and 1 deletion.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -133,4 +133,7 @@ class FooterWidget extends StatelessWidget { ); } } ``` `Stateful`/`Stateless` виджеты имеют специальный механизм "кэширования", учитывающий ключ, тип виджета и его атрибуты, который позволяет не перестраивать виджет без необходимости. Кроме того, это помогает нам инкапсулировать и рефакторировать наши виджеты. ([Разделяй и властвуй](https://en.wikipedia.org/wiki/Divide-and-conquer_algorithm)) И было бы неплохо добавить `const` в наши виджеты. Позже мы увидим, почему это важно.
NewerOlder