flutter构建UI与android究竟有何不同?
内容概览UI界面的编写方式在Android中,通常使用xml编写UI,然后通过LayoutInflater解析为一个树状结构的UI tree,同时也支持代码方式编写UI。Flutter只能通过代码的方式构造UI tree,代表一个界面。UI界面的组成元素在Android中,UI界面的组成元素是View,一切界面元素都继承View类,由View衍生而来。而Flutter UI界面的组...
内容概览
UI界面的编写方式
在Android中,通常使用xml编写UI,然后通过LayoutInflater解析为一个树状结构的UI tree,同时也支持代码方式编写UI。Flutter只能通过代码的方式构造UI tree,代表一个界面。
UI界面的组成元素
在Android中,UI界面的组成元素是View,一切界面元素都继承View类,由View衍生而来。而Flutter UI界面的组成元素是Widget,任何界面元素均继承Widget。
UI界面元素的可变性
Android中View是可变的,当用户交互或数据更新时,可直接调用View的invalidate方法重绘,达到更新UI的目的。
Flutter中Widget本身是不可变的(immutable)。那么Flutter如何做到更新界面呢?Flutter将Widget分为两种:
-
stateless widget 无状态的Widget,基类为StatelessWidget,整个生命周期外观不发生变化,例如,显示app图标的元素不会在运行期间发生改变。stateless widget的build函数负责构建该Widget,可以是单个Widget或复杂的Widget tree。
-
abstract class StatelessWidget extends Widget { -
... -
-
@protected -
Widget build(BuildContext context); -
-
... -
}
-
stateful widget 拥有状态的Widget,基类为StatefulWidget,自身关联一个State对象,保存着该Widget的状态信息,如当前是否选中等。状态信息也可以保存在Widget中,然后State通过Widget获取状态信息。stateful widget的createState函数负责创建自身关联的State对象。
-
abstract class StatefulWidget extends Widget { -
... -
-
@protected -
State createState(); -
-
... -
}
stateful widget将自身的构建委托给State对象,State对象的build函数负责构建该Widget,当用户交互或数据发生变化时,Widget状态发生改变,调用State的setState方法通知它,而后State根据当前的状态信息,重新构建Widget tree。
-
abstract class State<T extends StatefulWidget> extends Diagnosticable { -
... -
-
@protected -
Widget build(BuildContext context); -
-
... -
}
假设现在我们自定义一个选择框SampleCheckBox,SampleCheckBox根据用户选择而展现“选中”和“取消”两种外观。
第一步, SampleCheckBox继承StatefulWidget,并在createState函数中创建自己的State对象SampleCheckBoxState。
-
class SampleCheckBox extends StatefulWidget{ -
@override -
SampleCheckBoxState createState() { -
return SampleCheckBoxState(); -
} -
}
第二步, 实现SampleCheckBoxState,SampleCheckBoxState根据当前的状态值_isChecked决定如何构造SampleCheckBox。为了降低复杂性、便于理解大意,我们假设CheckedBox和UncheckedBox是已存在的两个StatelessWidget,他们的外观分别展示“选中”和“取消”。CheckBoxContainer是支持点击事件的Widget。
-
class SampleCheckBoxState extends State<SampleCheckBox> { -
bool _isChecked = false; -
-
@override -
Widget build(BuildContext context) { -
return CheckBoxContainer( -
child: _isChecked ? CheckedBox() : UncheckedBox(), -
//设置点击事件的回调代码 -
onTap: () { -
setState(() { -
_isChecked = !_isChecked; -
}); -
}); -
} -
}
在上面的例子中,当我们触发点击事件时,会调用SampleCheckBoxState的setState函数通知它,setState函数接受一个回调函数为参数,该回调函数为我们提供了修改状态数据的时机,之后SampleCheckBoxState根据_isChecked的最新值重新构建SampleCheckBox。
通过上面的介绍,你应该对Flutter Widget的构建有所了解,但有可能对此依然“头懵”,无法透彻地明白Flutter Widget与Android View的区别。我给大家做一个类比:
我们把Android View和Flutter Widget都比作画板,假设现在我们要用画板来展示个人肖像,轮流展示四个人的肖像:朱志强(笔者)、刘备、关羽、张飞。
对于Android View来说,只需准备一张画板,先展示朱志强的肖像,轮到刘备时,直接擦除当前内容,绘制刘备的肖像,以此类推。
对于Flutter Widget来说,由于Widget是不可变的,所以我们要准备四张画板,分别画有四人的肖像,并定义一个超级画板,超级画板并不直接绘制肖像,仅仅是其余四张画板的组织者。当需要展示这四个人中某个人的肖像时,拿出对应的画板来展示。超级画板就相当于Stateful Widget。
自定义Widget
在Android中,我们通过继承已存在的某个View来实现自定义View,例如,当我想定制一个特殊的文本显示View时,我可以继承TextView。
Flutter与之不同,自定义Widget其实是对其他已存在的Widget的组装。自定义Widget需直接继承StatelessWidget或StatefulWidget,然后重写build函数,实现该Widget的构造。例如,当我想定制一个特殊的文本显示Widget时,不能继承Text,而是要继承StatelessWidget或StatefulWidget,在build函数中返回一个特殊的Text,如文本字体变粗。
-
class CustomText extends StatelessWidget{ -
-
final String content; -
CustomText(this.content); -
-
@override -
Widget build(BuildContext context) { -
return Text( -
content, -
style: TextStyle(fontWeight: FontWeight.bold), -
textDirection: TextDirection.ltr -
); -
} -
}
动态添加child widget
在Android中,ViewGroup绘制出来后,可随时通过addView/removeView来添加/删除child view。在Flutter中,由于Widget是不可变的,如果想动态添加一个child widget如Text,父widget只能准备两套widget tree,一套添加了Text,一套没有添加,然后根据状态返回不同的widget tree。
-
class SampleWidget extends StatefulWidget{ -
@override -
SampleWidgetState createState()=>SampleWidgetState(); -
-
} -
-
class SampleWidgetState extends State<SampleWidget>{ -
bool _needAddTextChild = false; -
@override -
Widget build(BuildContext context) { -
return _needAddTextChild ? Container(child: Text("添加文本")) : Container(); -
} -
-
}
canvas 绘图
在Android 中,你可以继承View并重写它的onDraw方法,通过canvas自定义绘图。在Flutter中,自定义绘图要使用CustomPaint这个Widget,并为其提供一个CustomPainter,CustomPainter负责实现绘制逻辑。下面这个例子,在CustomPaint的正中间绘制一个蓝色、半径为10的实心圆:
-
继承CustomPainter,重写paint方法,实现绘制圆的逻辑
-
class CirclePainter extends CustomPainter { -
var paintStyle = Paint()..color = Colors.blue; -
-
@override -
void paint(Canvas canvas, Size size) { -
//绘制一个实心圆 -
canvas.drawCircle(Offset(size.width / 2, size.height / 2), 10, paintStyle); -
} -
-
@override -
bool shouldRepaint(CustomPainter oldDelegate) { -
//该方法用于判断是否重绘,由于这里不是动态绘制,所以直接返回false -
return false; -
} -
}
-
创建一个CustomPaint,将CirclePainter传递给它
-
void main() => runApp(CustomPaint(painter: CirclePainter()));
手势监测
在Android中,两种方式进行手势监测:
-
某些View本身支持设置监听器,处理手势操作,例如,CheckBox可设置用户“选中/取消”的监听器
-
checkBox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { -
@Override -
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { -
... -
} -
});
2.本身不支持特定事件的监听器或想进行复杂的手势处理时,重写View的onTouchEvent方法
-
public class MyView extends View { -
... -
-
@Override -
public boolean onTouchEvent(MotionEvent event) { -
... -
} -
-
... -
}
在Flutter中,手势监测也有两种方式:
-
该Widget本身支持触碰事件的监听,我们只需设置监听代码即可。例如,为RaisedButton设置点击事件
-
RaisedButton( -
onPressed: ()=>print("callback when the button was click"), -
child: Text("Button") -
);
上面,我们为onPressed参数传递了一个回调函数,当RaisedButton被点击时将会回调该函数。
-
该Widget本身不支持触碰事件的监听或无法满足复杂的手势处理,那么需要用GestureDetector包裹该Widget。GestureDetector是一个用来手势监测的特殊Widget。被GestureDetector包裹后,该Widget的手势监测交给GestureDetector处理。
-
GestureDetector( -
child: Text("可响应点击的文本",textDirection: TextDirection.ltr), -
onTap: ()=>print("文本被点击了"), -
);
GestureDetector包含丰富的触碰监听,这里不再详细列出。
界面管理及界面跳转
在Android中,Activity代表一个界面,每个Activity都有对应的View tree。界面之间的跳转就是Activity之间的跳转。Activity还可以将UI“分块管理”——分成若干Fragment。
Flutter中,一个界面就是一个widget,该widget可以添加child widget,构成一个复杂的widget tree。界面之间的跳转就是widget之间的跳转。
跳转到新界面
Android中,通过startActivity/startActivityForResult跳转到新的actiivty界面。
Flutter中,通过Navigator的push函数进入新界面。Navigator把不同的界面称之为“路由”(route), push函数接受一个“路由”参数。我们可以使用MaterialPageRoute,并传递WidgetBuilder函数给它,告诉它如何构建要跳转到的界面(也就是一个widget)。
-
Navigator.of(context) -
.push(MaterialPageRoute(builder: (context) => NextPageWidget()));
传递数据到新界面
Android中,我们通过Intent传递数据至新启动的activity。
Flutter中,向新界面传递数据,直接在表示新界面的widget的构造函数中定义参数来接受该数据。
-
Navigator.of(context) -
.push(MaterialPageRoute(builder: (context) => NextPageWidget("new >)));
返回到上一个界面
Android中,当我们想通过代码的方式返回上一个activity时,可以调用当前activity的finish方法。
Flutter中,通过Navigator的pop函数返回至上一个界面。
-
Navigator.of(context).pop();
将数据回传到上一个界面
Android中,当前activity调用setResult方法向上一个界面回传数据。并在上一个activity的onActivityResult方法里接收数据。
Flutter中,将数据传递给pop函数进行回传。
-
Navigator.of(context).pop("returned data");
启动新界面时,设置接收回传的数据。
-
var returnedData = await Navigator.of(context) -
.push(MaterialPageRoute(builder: (context) => NextPageWidget()));
await关键字涉及到Flutter的异步编程,我们留到异步编程相关篇章,本篇不再讲述。
品略图书馆 http://www.pinlue.com/
更多推荐



所有评论(0)