Flutter: Как я могу улучшить производительность этого приложения? - PullRequest
0 голосов
/ 12 февраля 2020

Я хочу знать, почему этот код Flutter ниже 60 кадров в секунду на некоторых устройствах (Redmi Go, в этом случае, режим профилирования, показывающий красные полосы на графике пользовательского интерфейса). Есть ли способ, которым я мог бы оптимизировать это? Вчера я начал изучать Flutter, поэтому хочу получить несколько советов, которые помогут мне понять иерархию виджетов с сохранением состояния и иерархию рендеринга виджетов в целом.

Заранее спасибо.

Файл main.dart

import 'package:project/ui/home.dart';
import 'package:flutter/material.dart';

void main() => runApp(new MaterialApp(
      home: BillSplitter(),
    ));

Файл home.dart

import 'package:flutter/material.dart';

class BillSplitter extends StatefulWidget {
  @override
  _BillSplitterState createState() => _BillSplitterState();
}

class _BillSplitterState extends State<BillSplitter> {
  int _tipPercentage = 0;
  int _personCounter = 1;
  double _billAmount = 0.0;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(
          "APP Teste 1.0",
          style: TextStyle(fontWeight: FontWeight.bold),
        ),
        centerTitle: true,
        backgroundColor: Colors.purple.withOpacity(0.5),
      ),
      body: Container(
        margin: EdgeInsets.only(top: MediaQuery.of(context).size.height * 0.1),
        alignment: Alignment.center,
        color: Colors.white,
        child: ListView(
          scrollDirection: Axis.vertical,
          padding: EdgeInsets.all(20.5),
          children: <Widget>[
            Container(
              width: 150,
              height: 150,
              decoration: BoxDecoration(
                  color: Colors.purple.withOpacity(0.1),
                  borderRadius: BorderRadius.circular(12.0)),
              child: Center(
                child: Column(
                  mainAxisAlignment: MainAxisAlignment.center,
                  children: <Widget>[
                    Text(
                      "Total por pessoa",
                      style: TextStyle(
                          color: Colors.purple,
                          fontWeight: FontWeight.normal,
                          fontSize: 17.0),
                    ),
                    Padding(
                      padding: const EdgeInsets.all(10.0),
                      child: Text(
                        "R\$ ${_calculateTotalPerPerson(_billAmount, _personCounter, _tipPercentage)}",
                        style: TextStyle(
                            color: Colors.purple,
                            fontWeight: FontWeight.bold,
                            fontSize: 35.0),
                      ),
                    )
                  ],
                ),
              ),
            ),
            Container(
              margin: EdgeInsets.only(top: 20.0),
              padding: EdgeInsets.all(12.0),
              decoration: BoxDecoration(
                  color: Colors.transparent,
                  border: Border.all(
                      color: Colors.blueGrey.shade100,
                      style: BorderStyle.solid),
                  borderRadius: BorderRadius.circular(12.0)),
              child: Column(
                children: <Widget>[
                  TextField(
                    keyboardType:
                        TextInputType.numberWithOptions(decimal: true),
                    style: TextStyle(color: Colors.purple),
                    decoration: InputDecoration(
                        prefixText: "Total da Conta: R\$ ",
                        prefixIcon: Icon(Icons.attach_money)),
                    onChanged: (String value) {
                      try {
                        _billAmount = double.parse(value);
                      } catch (e) {
                        _billAmount = 0.0;
                      }
                    },
                  ),
                  Row(
                    mainAxisAlignment: MainAxisAlignment.spaceBetween,
                    children: <Widget>[
                      Text(
                        "Dividir",
                        style: TextStyle(color: Colors.grey.shade700),
                      ),
                      Row(
                        children: <Widget>[
                          InkWell(
                            onTap: () {
                              setState(() {
                                if (_personCounter > 1) {
                                  _personCounter--;
                                }
                              });
                            },
                            child: Container(
                              width: 40.0,
                              height: 40.0,
                              margin: EdgeInsets.all(10.0),
                              decoration: BoxDecoration(
                                  borderRadius: BorderRadius.circular(7.0),
                                  color: Colors.purpleAccent.withOpacity(0.1)),
                              child: Center(
                                  child: Text(
                                "-",
                                style: TextStyle(
                                    color: Colors.purple,
                                    fontWeight: FontWeight.bold,
                                    fontSize: 17.0),
                              )),
                            ),
                          ),
                          Text(
                            "$_personCounter",
                            style: TextStyle(
                                color: Colors.purple,
                                fontWeight: FontWeight.bold,
                                fontSize: 17.0),
                          ),
                          InkWell(
                            onTap: () {
                              setState(() {
                                _personCounter++;
                              });
                            },
                            child: Container(
                              width: 40.0,
                              height: 40.0,
                              margin: EdgeInsets.all(10.0),
                              decoration: BoxDecoration(
                                  borderRadius: BorderRadius.circular(7.0),
                                  color: Colors.purpleAccent.withOpacity(0.1)),
                              child: Center(
                                  child: Text(
                                "+",
                                style: TextStyle(
                                    color: Colors.purple,
                                    fontWeight: FontWeight.bold,
                                    fontSize: 17.0),
                              )),
                            ),
                          ),
                        ],
                      ),
                    ],
                  ),
                  Row(
                    mainAxisAlignment: MainAxisAlignment.spaceBetween,
                    children: <Widget>[
                      Text(
                        "Gorjeta",
                        style: TextStyle(color: Colors.grey.shade700),
                      ),
                      Padding(
                        padding: const EdgeInsets.all(18.0),
                        child: Text(
                          "R\$ ${(_calculateTotalTip(_billAmount, _personCounter, _tipPercentage)).toStringAsFixed(2)}",
                          style: TextStyle(
                              color: Colors.purple,
                              fontWeight: FontWeight.bold,
                              fontSize: 17.0),
                        ),
                      )
                    ],
                  ),
                  Column(
                    children: <Widget>[
                      Text(
                        "$_tipPercentage%",
                        style: TextStyle(
                            color: Colors.purple,
                            fontSize: 17.0,
                            fontWeight: FontWeight.bold),
                      ),
                      Slider(
                          activeColor: Colors.purple,
                          inactiveColor: Colors.grey,
                          min: 0,
                          max: 100,
                          divisions: 10,
                          value: _tipPercentage.toDouble(),
                          onChanged: (double value) {
                            setState(() {
                              _tipPercentage = value.round();
                            });
                          })
                    ],
                  )
                ],
              ),
            )
          ],
        ),
      ),
    );
  }

  _calculateTotalPerPerson(double billAmount, int splitBy, int tipPercentage) {
    double totalPerPerson =
        (billAmount + _calculateTotalTip(billAmount, splitBy, tipPercentage)) /
            splitBy;

    return totalPerPerson.toStringAsFixed(2);
  }

  _calculateTotalTip(double billAmount, int splitBy, int tipPercentage) {
    double totalTip = 0.0;

    if (billAmount < 0 || billAmount.toString().isEmpty || billAmount == null) {
    } else {
      totalTip = (billAmount * tipPercentage) / 100;
    }

    return totalTip;
  }
}

1 Ответ

0 голосов
/ 12 февраля 2020

Вы используете одну большую функцию сборки, вам нужно разделить ее на разные виджеты, потому что, когда состояние данных изменяется и вы вызываете setState(), все потомки-потомки будут перестраиваться, поэтому избегайте использования очень большой вложенной функции сборки, которая будет очень дорогостоящим.

Кроме того, отладка влияет на производительность приложения, поэтому будет полезно принять решение о создании приложения выпуска.

выполните эту команду, чтобы построить приложение flutter build apk.

Вы также можете проверить Построить и выпустить Android приложение или Построить и выпустить iOS приложение

Вы можете использовать профилирование Flutter Инструменты для определения производительности и определения проблем производительности в вашем приложении, вы можете найти более полезную информацию в следующей ссылке о профилировании производительности Flutter.

Профилирование производительности Flutter

также перейдите по этой ссылке для получения дополнительной информации и сведений о лучших методах повышения производительности.

Performan Лучшие практики CE

Обновление

Согласно Лучшие практики производительности

Избегайте слишком больших одиночных виджетов с большой функцией build (). Разделите их на разные виджеты, основываясь на инкапсуляции, а также на том, как они меняются: когда setState () вызывается для State, все дочерние виджеты будут перестраиваться. Поэтому локализуйте вызов setState () для той части поддерева, пользовательский интерфейс которой нужно изменить. Избегайте вызова setState () высоко в дереве, если изменение содержится в небольшой части дерева.

и в соответствии с документами

Вызов setState уведомляет платформу о том, что внутреннее состояние этого объекта изменилось таким образом, что это может повлиять на пользовательский интерфейс в этом поддереве, что заставляет платформу запланировать построение для этого объекта состояния.

Так что гораздо лучше разделить ваш код на маленькие виджеты, чтобы избежать больших вложенных виджетов в функции длинной сборки, и я рекомендую прочитать о Управление состоянием , это очень полезно для управления данными в вашем приложении и улучшает читаемость и возможность сопровождения вашего кода, что в конечном итоге приведет к повышению производительности.

Здесь я приведу пример развязки виджетов с помощью кода, приведенного в этом вопросе, вам необходимо прочитать комментарии в следующем коде, чтобы понять, как я разделил виджеты приложения, и я использовал флаттер производительность и количество кадров в секунду не упали ниже 30 в режиме отладки, в то время как предоставленный вами код упал ниже 15 кадров в секунду.

Вы можете сравнить оба кода и увидеть разницу в производительности, и не Не стесняйтесь спрашивать меня о коде для уточнения.

Файл home.dart

import 'package:flutter/material.dart';

/*
  I created this class for a better data management around the application
  but this design is not recommended
 */
class Bill {
  static int _tipPercentage = 0;
  static int _personCounter = 1;
  static double _billAmount = 0.0;

  static _calculateTotalPerPerson(
      double billAmount, int splitBy, int tipPercentage) {
    double totalPerPerson =
        (billAmount + _calculateTotalTip(billAmount, splitBy, tipPercentage)) /
            splitBy;

    return totalPerPerson.toStringAsFixed(2);
  }

  static _calculateTotalTip(double billAmount, int splitBy, int tipPercentage) {
    double totalTip = 0.0;

    if (billAmount < 0 || billAmount.toString().isEmpty || billAmount == null) {
    } else {
      totalTip = (billAmount * tipPercentage) / 100;
    }

    return totalTip;
  }
}

// I converted the main widget to a stateless widget
// and for a better data management I highly recommend searching about
// state management
class BillSplitter extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(
          "APP Teste 1.0",
          style: TextStyle(fontWeight: FontWeight.bold),
        ),
        centerTitle: true,
        backgroundColor: Colors.purple.withOpacity(0.5),
      ),
      body: Container(
        margin: EdgeInsets.only(top: MediaQuery.of(context).size.height * 0.1),
        alignment: Alignment.center,
        color: Colors.white,
        /*
        Inside this ListView a lot of children widgets which can be converted
        to smaller widgets in different classes based on the state of the widget
        either it's stateless or stateful
         */
        child: ListView(
          scrollDirection: Axis.vertical,
          padding: EdgeInsets.all(20.5),
          children: <Widget>[
            CurrentBillContainer(), //Check this widget class down below
            BillCalculator(),
          ],
        ),
      ),
    );
  }
}

/*
  This container is for the upper pink box which holds the bill value
  and viewed in a different widget with a different build function
  which will enhance the build() function time
 */
class CurrentBillContainer extends StatefulWidget {
  @override
  _CurrentBillContainerState createState() => _CurrentBillContainerState();
}

class _CurrentBillContainerState extends State<CurrentBillContainer> {
  @override
  Widget build(BuildContext context) {
    return Container(
      width: 150,
      height: 150,
      decoration: BoxDecoration(
          color: Colors.purple.withOpacity(0.1),
          borderRadius: BorderRadius.circular(12.0)),
      child: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text(
              "Total por pessoa",
              style: TextStyle(
                  color: Colors.purple,
                  fontWeight: FontWeight.normal,
                  fontSize: 17.0),
            ),
            Padding(
              padding: const EdgeInsets.all(10.0),
              child: Text(
                "R\$ ${Bill._calculateTotalPerPerson(Bill._billAmount, Bill._personCounter, Bill._tipPercentage)}",
                style: TextStyle(
                    color: Colors.purple,
                    fontWeight: FontWeight.bold,
                    fontSize: 35.0),
              ),
            )
          ],
        ),
      ),
    );
  }
}

class BillCalculator extends StatefulWidget {
  @override
  _BillCalculatorState createState() => _BillCalculatorState();
}

class _BillCalculatorState extends State<BillCalculator> {
  @override
  Widget build(BuildContext context) {
    return Container(
      margin: EdgeInsets.only(top: 20.0),
      padding: EdgeInsets.all(12.0),
      decoration: BoxDecoration(
          color: Colors.transparent,
          border: Border.all(
              color: Colors.blueGrey.shade100, style: BorderStyle.solid),
          borderRadius: BorderRadius.circular(12.0)),
      child: Column(
        children: <Widget>[
          TotalBillTextField(),
          DividirRow(),
          Row(
            mainAxisAlignment: MainAxisAlignment.spaceBetween,
            children: <Widget>[
              Text(
                "Gorjeta",
                style: TextStyle(color: Colors.grey.shade700),
              ),
              Padding(
                padding: const EdgeInsets.all(18.0),
                child: Text(
                  "R\$ ${(Bill._calculateTotalTip(Bill._billAmount, Bill._personCounter, Bill._tipPercentage)).toStringAsFixed(2)}",
                  style: TextStyle(
                      color: Colors.purple,
                      fontWeight: FontWeight.bold,
                      fontSize: 17.0),
                ),
              )
            ],
          ),
          Column(
            children: <Widget>[
              Text(
                "${Bill._tipPercentage}%",
                style: TextStyle(
                    color: Colors.purple,
                    fontSize: 17.0,
                    fontWeight: FontWeight.bold),
              ),
              Slider(
                  activeColor: Colors.purple,
                  inactiveColor: Colors.grey,
                  min: 0,
                  max: 100,
                  divisions: 10,
                  value: Bill._tipPercentage.toDouble(),
                  onChanged: (double value) {
                    setState(() {
                      Bill._tipPercentage = value.round();
                    });
                  })
            ],
          )
        ],
      ),
    );
  }
}

/*
  Take this TextField as an example you can create a stateless widget for it
  and place it inside the column of BillCalculator class
  and you can apply the same concept all over the application,
  and divide widgets to small classes and inside sub folders to reduce the size
  of the build function and optimize the performance and make it easier to
  maintain and add a new features in your application
 */
class TotalBillTextField extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return TextField(
      keyboardType: TextInputType.numberWithOptions(decimal: true),
      style: TextStyle(color: Colors.purple),
      decoration: InputDecoration(
          prefixText: "Total da Conta: R\$ ",
          prefixIcon: Icon(Icons.attach_money)),
      onChanged: (String value) {
        try {
          Bill._billAmount = double.parse(value);
        } catch (e) {
          Bill._billAmount = 0.0;
        }
      },
    );
  }
}

/*
  This row has to be a Stateful widget because you are using
  setState() function, you can apply the same method to all of the widgets
 */
class DividirRow extends StatefulWidget {
  @override
  _DividirRowState createState() => _DividirRowState();
}

class _DividirRowState extends State<DividirRow> {
  @override
  Widget build(BuildContext context) {
    return Row(
      mainAxisAlignment: MainAxisAlignment.spaceBetween,
      children: <Widget>[
        Text(
          "Dividir",
          style: TextStyle(color: Colors.grey.shade700),
        ),
        Row(
          children: <Widget>[
            InkWell(
              onTap: () {
                setState(() {
                  if (Bill._personCounter > 1) {
                    Bill._personCounter--;
                  }
                });
              },
              child: Container(
                width: 40.0,
                height: 40.0,
                margin: EdgeInsets.all(10.0),
                decoration: BoxDecoration(
                    borderRadius: BorderRadius.circular(7.0),
                    color: Colors.purpleAccent.withOpacity(0.1)),
                child: Center(
                    child: Text(
                      "-",
                      style: TextStyle(
                          color: Colors.purple,
                          fontWeight: FontWeight.bold,
                          fontSize: 17.0),
                    )),
              ),
            ),
            Text(
              "${Bill._personCounter}",
              style: TextStyle(
                  color: Colors.purple,
                  fontWeight: FontWeight.bold,
                  fontSize: 17.0),
            ),
            InkWell(
              onTap: () {
                setState(() {
                  Bill._personCounter++;
                });
              },
              child: Container(
                width: 40.0,
                height: 40.0,
                margin: EdgeInsets.all(10.0),
                decoration: BoxDecoration(
                    borderRadius: BorderRadius.circular(7.0),
                    color: Colors.purpleAccent.withOpacity(0.1)),
                child: Center(
                    child: Text(
                      "+",
                      style: TextStyle(
                          color: Colors.purple,
                          fontWeight: FontWeight.bold,
                          fontSize: 17.0),
                    )),
              ),
            ),
          ],
        ),
      ],
    );
  }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...