Mat*_*ira 1 performance mobile android dart flutter
我想知道为什么 Flutter 代码在某些设备中低于 60 fps(在这种情况下,Redmi Go 是分析模式,在 UI 图形中显示红色条)。有什么办法可以优化它吗?我昨天开始学习 Flutter,所以我想要一些技巧来帮助我了解一般的有状态小部件和小部件渲染层次结构。
提前致谢。
文件main.dart
import 'package:project/ui/home.dart';
import 'package:flutter/material.dart';
void main() => runApp(new MaterialApp(
home: BillSplitter(),
));
Run Code Online (Sandbox Code Playgroud)
文件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;
}
}
Run Code Online (Sandbox Code Playgroud)
您正在使用单个大型构建函数,您需要将其拆分为不同的小部件,因为当数据状态发生变化并且您调用 时setState(),所有后代小部件都将重新构建,因此避免使用非常庞大的嵌套构建函数,这将非常昂贵。
应用程序的性能也受调试的影响,生成一个发布应用程序将更有帮助的决定。
运行此命令来构建您的应用程序
flutter build apk。
您还可以检查Build and release an Android app或Build and release an iOS app
您可以使用 Flutter 分析工具来确定应用程序中的性能并确定性能问题,您可以在以下有关 Flutter 性能分析的链接中找到更多有用的详细信息。
还可以查看此链接以获取有关性能最佳实践的更多信息和详细信息。
更新
根据性能最佳实践
避免使用大型 build() 函数过大的单个 Widget。根据封装以及它们的变化方式将它们拆分为不同的小部件:当 setState() 对一个状态调用时,所有后代小部件都将重建。因此,将 setState() 调用本地化到 UI 实际需要更改的子树部分。如果更改包含在树的一小部分,请避免在树的高处调用 setState()。
并根据文档
调用 setState 通知框架此对象的内部状态已更改,可能会影响此子树中的用户界面,这会导致框架为此 State 对象安排构建。
因此,最好将代码划分为小部件以避免在长构建函数中使用大型嵌套部件,我建议阅读State management,它对于管理应用程序周围的数据非常有用,并提高了代码的可读性和可维护性,其中最终会带来更好的表现。
这里我提供了一个关于使用本问题提供的代码进行小部件解耦的示例,您需要阅读以下代码中的注释以了解我是如何划分应用小部件的,并且我使用了 flutter 性能和每秒帧数没有下降在调试模式下低于 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),
)),
),
),
],
),
],
);
}
}
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
3414 次 |
| 最近记录: |