Flutter系列之UI篇:flutter与android UI构建的区别—flutter widget VS android view

9 篇文章 0 订阅
9 篇文章 1 订阅

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的实心圆:

  1. 继承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;
  }
}
  1. 创建一个CustomPaint,将CirclePainter传递给它
void main() => runApp(CustomPaint(painter: CirclePainter()));

手势监测

在Android中,两种方式进行手势监测:

  1. 某些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中,手势监测也有两种方式:

  1. 该Widget本身支持触碰事件的监听,我们只需设置监听代码即可。例如,为RaisedButton设置点击事件
RaisedButton(
      onPressed: ()=>print("callback when the button was click"),
      child: Text("Button")
      )

上面,我们为onPressed参数传递了一个回调函数,当RaisedButton被点击时将会回调该函数。

  1. 该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 data")));

返回到上一个界面

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的异步编程,我们留到异步编程相关篇章,本篇不再讲述。

关注公众号,随时接收优质技术文章

在这里插入图片描述

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Vincent(朱志强)

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值