本文正在参与「金石方案」
Compose推出之初,就曾引发广泛的评论,其间一个比较遍及的声响便是——“这跟Flutter也长得太像了吧?!”
这儿说的长得像,实际更多指的是UI编码的风格类似,而关于这种风格有一个专门的术语,叫做声明式UI。
关于那些已经习惯了指令式UI的Android或iOS开发人员来说,刚开端的确很难了解什么是声明式UI。就像当初刚踏入编程领域的咱们,同样也很难了解面向进程编程和面向对象编程的差异相同。
为了帮助这部分原生开发人员完结从指令式UI到声明式UI的思想改动,本文将结合示例代码编写、动画演示以及日子例子类比等形式,详细介绍声明式UI的概念、长处及其运用。
按例,先奉上思想导图一张,方便复习:
指令式UI的特色
既然指令式UI与声明式UI是相对的,那就让咱们先来回忆一下,在一个常规的视图更新流程中,假如选用的是指令式UI,会是怎样的一个操作办法。
以Android为例,首要咱们都知道,Android所选用的界面布局,是基于View与ViewGroup对象、以树状结构来进行构建的视图层级。
当咱们需求对某个节点的视图进行更新时,通常需求执行以下两个操作进程:
- 运用findViewById()等办法遍历树节点以找到对应的视图。
- 经过调用视图对象公开的setter办法更新视图的UI状况
咱们以一个最简单的计数器运用为例:
这个运用仅有的逻辑便是“当用户点击”+”号按钮时数字加1”。在传统的Android完结办法下,代码应该是这姿态的:
class CounterActivity : AppCompatActivity() {
var count: Int = 0
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_counter)
val countTv = findViewById<TextView>(R.id.count_tv)
countTv.text = count.toString()
val plusBtn = findViewById<Button>(R.id.plus_btn)
plusBtn.setOnClickListener {
count += 1
countTv.text = count.toString()
}
}
}
这段代码看起来没有任何难度,也没有明显的问题。可是,假设咱们鄙人一个版别中增加了更多的需求:
- 当用户点击”+”号按钮,数字加1的一起鄙人方容器中增加一个方块。
- 当用户点击”-“号按钮,数字减1的一起鄙人方容器中移除一个方块。
- 当数字为0时,下方容器的背景色变为透明。
现在,咱们的代码变成了这样:
class CounterActivity : AppCompatActivity() {
var count: Int = 0
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_counter)
// 数字
val countTv = findViewById<TextView>(R.id.count_tv)
countTv.text = count.toString()
// 方块容器
val blockContainer = findViewById<LinearLayout>(R.id.block_container)
// "+"号按钮
val plusBtn = findViewById<Button>(R.id.plus_btn)
plusBtn.setOnClickListener {
count += 1
countTv.text = count.toString()
// 方块
val block = View(this).apply {
setBackgroundColor(Color.WHITE)
layoutParams = LinearLayout.LayoutParams(40.dp, 40.dp).apply {
bottomMargin = 20.dp
}
}
blockContainer.addView(block)
when {
count > 0 -> {
blockContainer.setBackgroundColor(Color.parseColor("#FF6200EE"))
}
count == 0 -> {
blockContainer.setBackgroundColor(Color.TRANSPARENT)
}
}
}
// "-"号按钮
val minusBtn = findViewById<Button>(R.id.minus_btn)
minusBtn.setOnClickListener {
if(count <= 0) return@setOnClickListener
count -= 1
countTv.text = count.toString()
blockContainer.removeViewAt(0)
when {
count > 0 -> {
blockContainer.setBackgroundColor(Color.parseColor("#FF6200EE"))
}
count == 0 -> {
blockContainer.setBackgroundColor(Color.TRANSPARENT)
}
}
}
}
}
已经开端看得有点难受了吧?这正是指令式UI的特色,侧重于描绘怎么做,咱们需求像下达指令相同,手动处理每一项UI的更新,假如UI的杂乱度足够高的话,就会引发一系列问题,比如:
- 可保护性差:需求编写很多的代码逻辑来处理UI变化,这会使代码变得臃肿、杂乱、难以保护。
- 可复用性差:UI的规划与更新逻辑耦合在一起,导致只能在当时程序运用,难以复用。
- 健壮性差:UI元素之间的关联度高,每个纤细的改动都或许一系列不知道的连锁反应。
声明式UI的特色
而同样的功用,假如选用的是声明式UI,则代码应该是这姿态的:
class _CounterPageState extends State<CounterPage> {
int _count = 0;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Column(
children: [
// 数字
Text(
_count.toString(),
style: const TextStyle(fontSize: 48),
),
Row(
mainAxisSize: MainAxisSize.min,
children: [
// +"号按钮
ElevatedButton(
onPressed: () {
setState(() {
_count++;
});
},
child: const Text("+")),
// "-"号按钮
ElevatedButton(
onPressed: () {
setState(() {
if (_count == 0) return;
_count--;
});
},
child: const Text("-"))
],
),
Expanded(
// 方块容器
child: Container(
width: 60,
padding: const EdgeInsets.all(10),
color: _count > 0 ? const Color(0xFF6200EE) : Colors.transparent,
child: ListView.separated(
itemCount: _count,
itemBuilder: (BuildContext context, int index) {
// 方块
return Container(width: 40, height: 40, color: Colors.white);
},
separatorBuilder: (BuildContext context, int index) {
return const Divider(color: Colors.transparent, height: 10);
},
),
))
],
),
);
}
}
在这样的代码中,咱们简直看不到任何操作UI更新的代码,而这正是声明式UI的特色,它侧重于描绘做什么,而不是怎么做,开发者只需求重视UI应该怎么出现,而不需求关心UI的详细完结进程。
开发者要做的,就只是供给不同UI与不同状况之间的映射联系,而无需编写怎么在不同UI之间进行切换的代码。
所谓状况,指的是构建用户界面时所需求的数据,例如一个文本框要显示的内容,一个进展条要显示的进展等。Flutter结构答应咱们仅描绘当时状况,而转化的作业则由结构完结,当咱们改动状况时,用户界面将主动重新构建。
下面咱们将依照通常情况下,用声明式UI完结一个Flutter运用所需求阅历的几个进程,来详细解析前面计数器运用的代码:
- 分析运用或许存在的各种状况
根据咱们前面关于“状况”的界说,咱们能够很容易地得出,在本例中,数字(_count值)本身即为计数器运用的状况,其间还包括数字为0时的一个特殊状况。
- 供给每个不同状况所对应要展示的UI
build办法是将状况转化为UI的办法,它能够在任何需求的时分被结构调用。咱们经过重写该办法来声明UI的结构:
关于顶部的文本,只需声明每次都运用最新回来的状况(数字)即可:
Text(
_count.toString(),
...
),
关于方块容器,只需声明当_count的值为0时,容器的背景色彩为透明色,否则为特定色彩:
Container(
color: _count > 0 ? const Color(0xFF6200EE) : Colors.transparent,
...
)
关于方块,只需声明回来的方块个数由_count的值决定:
ListView.separated(
itemCount: _count,
itemBuilder: (BuildContext context, int index) {
// 方块
return Container(width: 40, height: 40, color: Colors.white);
},
...
),
- 根据用户交互或数据查询结果更改状况
当由于用户的点击数字发生变化,而咱们需求改写页面时,就能够调用setState办法。setState办法将会驱动build办法生成新的UI:
// "+"号按钮
ElevatedButton(
onPressed: () {
setState(() {
_count++;
});
},
child: const Text("+")),
// "-"号按钮
ElevatedButton(
onPressed: () {
setState(() {
if (_count == 0) return;
_count--;
});
},
child: const Text("-"))
],
能够结合动画演示来回忆这整个进程:
最终,用一个公式来总结一下UI、状况与build办法三者的联系,那便是:
以指令式和声明式别离点一杯奶茶
现在,你能了解指令式UI与声明式UI的差异了吗?假如仍是有些抽象,咱们能够用一个点奶茶的例子来做个比方:
当咱们用指令式UI的思想办法去点一杯奶茶,相当于咱们需求告知制造者,冲一杯奶茶必须依照煮水、冲茶、加牛奶、加糖这几个进程,一步步来完结,也即咱们需求明晰每一个进程,然后使得咱们的主意详细而可操作。
而当咱们用声明式UI的思想办法去点一杯奶茶,则相当于咱们只需求告知制造者,我需求一杯“温度适中、口感浓郁、有一点点甜味”的奶茶,而不用关心详细的制造进程和操作细节。
声明式编程的长处
综合以上内容,咱们能够得出声明式UI有以下几个长处:
-
简化开发:开发者只需求保护状况->UI的映射联系,而不需求重视详细的完结细节,很多的UI完结逻辑被转移到了结构中。
-
可保护性强:经过函数式编程的办法构建和组合UI组件,使代码愈加简练、明晰、易懂,便于保护。
-
可复用性强:将UI的规划和完结分离开来,使得同样的UI组件能够在不同的运用程序中运用,提高了代码的可复用性。
总结与展望
总而言之,声明式UI是一种愈加高层次、愈加抽象的编程办法,其最大的长处在于能极大地简化现有的开发模式,因此在现代运用程序中得到广泛的运用,跟着更多结构的选用与更多开发者的加入,声明式UI必将继续发展壮大,成为今后构建用户界面的首选办法。