Flutter-->自定义Widget(类比Android自定义View)

Android自定义View

简单复习一下Android中自定义View的流程:

创建一个类CustomView继承自View然后实现如下方法:

  • onDraw方法: 进行绘制操作, 确定自身的内容
  • onMeasure方法: 进行测量操作, 确定自身的大小, 位置由parent决定
  • onTouchEvent方法: 进行手势处理
kotlin 复制代码
class CustomView(context: Context, attrs: AttributeSet?) : View(context, attrs) {

    override fun onTouchEvent(event: MotionEvent?): Boolean {
        //进行手势处理
        return super.onTouchEvent(event)
    }

    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
        //进行测量操作, 确定自身的大小, 位置由parent决定
    }

    override fun onDraw(canvas: Canvas) {
        //进行绘制操作, 确定自身的内容
    }

}

自定义属性通过xml配置:

xml 复制代码
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="CustomView">
        <attr name="custom_attr" format="boolean" />
    </declare-styleable>
</resources>

这样一个自定义View就实现了...

Flutter自定义Widget

那么在Flutter中, 怎么自定义Widget呢? 准确来说, 应该是自定义RenderObject.

这里请忽略CustomPaint小部件...

dart 复制代码
class CustomRenderObject extends RenderBox {

  @override
  bool hitTestSelf(Offset position) {
    return true;
  }
  
  @override
  void handleEvent(PointerEvent event, BoxHitTestEntry entry) {
    //进行手势处理, 这里需要注意的是,
    //如果想要`handleEvent`方法被回调, 那么`hitTestSelf`必须返回true
  }

  @override
  void performLayout() {
    //进行测量操作, 确定自身的大小, 位置由parent决定
  }

  @override
  void paint(PaintingContext context, Offset offset) {
    //进行绘制操作, 确定自身的内容
  }
}
dart 复制代码
class CustomWidget extends LeafRenderObjectWidget {
  const CustomWidget({super.key});

  @override
  RenderObject createRenderObject(BuildContext context) {
    return CustomRenderObject();
  }
}

这里的CustomWidget相对于Android中自定义View的配置属性.

注意

如果想要handleEvent回调, 那么hitTestSelf方法必须返回true, 否则不会触发.

总结

Android Flutter
绘制入口 View.onDraw RenderObject.paint
测量入口 View.onMeasure RenderObject.performLayout
手势入口 View.onTouchEvent RenderObject.handleEvent
属性配置 xml Widget
绘制相关 Canvas PaintingContext.canvas
绘制图像 Canvas.drawBitmap Canvas.drawImage
绘制图形 Canvas.drawPath Canvas.drawPath
绘制文本 Canvas.drawText TextPainter.paint
触发重新绘制 View.invalidate RenderObject.markNeedsPaint
触发重新布局 View.requestLayout RenderObject.markNeedsLayout

完整示例代码:

dart 复制代码
class CustomWidget extends LeafRenderObjectWidget {
  const CustomWidget({super.key});

  @override
  RenderObject createRenderObject(BuildContext context) {
    return CustomRenderObject();
  }
}

/// [RenderSliver]
class CustomRenderObject extends RenderBox {
  Offset localPosition = Offset.zero;

  @override
  bool hitTestSelf(Offset position) {
    return true;
  }

  @override
  void handleEvent(PointerEvent event, BoxHitTestEntry entry) {
    //进行手势处理, 这里需要注意的是,
    //如果想要`handleEvent`方法被回调, 那么`hitTestSelf`必须返回true
    localPosition = event.localPosition;
    markNeedsPaint();
    //markNeedsLayout();
  }

  @override
  void performLayout() {
    //进行测量操作, 确定自身的大小, 位置由parent决定
    size = constraints.constrain(const Size(100, 100));
  }

  @override
  void paint(PaintingContext context, Offset offset) {
    //进行绘制操作, 确定自身的内容
    //context.canvas.drawImage(image, offset, paint);
    //context.canvas.drawPath(path, paint);
    //TextPainter()..layout()..paint(canvas, offset);

    context.canvas.drawRect(
      paintBounds.shift(offset),
      Paint()
        ..style = PaintingStyle.stroke
        ..color = Colors.purpleAccent,
    );

    context.canvas.drawCircle(
      localPosition + offset,
      10,
      Paint()
        ..style = PaintingStyle.fill
        ..color = Colors.purpleAccent,
    );

    TextPainter(
        text: TextSpan(
          text: localPosition.toString(),
          style: const TextStyle(
            color: Colors.purpleAccent,
            fontSize: 8,
          ),
        ),
        textDirection: TextDirection.ltr)
      ..layout()
      ..paint(context.canvas, offset);
  }
}

这里介绍一下LeafRenderObjectWidget SingleChildRenderObjectWidgetMultiChildRenderObjectWidget的区别:

类名 说明
LeafRenderObjectWidget 不接受任何子Widget
SingleChildRenderObjectWidget 接受一个子Widget
MultiChildRenderObjectWidget 接受一组子Widget

附加

RenderObject有2个关键的子类RenderBoxRenderSliver

RenderBox对应的是BoxConstraints约束, 也叫盒子约束;
RenderSliver对应的是SliverConstraints约束, 也叫条子约束;

BoxConstraints

dart 复制代码
const BoxConstraints({
  this.minWidth = 0.0,
  this.maxWidth = double.infinity,
  this.minHeight = 0.0,
  this.maxHeight = double.infinity,
});

盒子约束, 就是简单的约束大小使用. 这个在Flutter非常常见, 也是用得最多的一种约束.只要宽高即可.

SliverConstraints

dart 复制代码
const SliverConstraints({
  required this.axisDirection,
  required this.growthDirection,
  required this.userScrollDirection,
  required this.scrollOffset,
  required this.precedingScrollExtent,
  required this.overlap,
  required this.remainingPaintExtent,
  required this.crossAxisExtent,
  required this.crossAxisDirection,
  required this.viewportMainAxisExtent,
  required this.remainingCacheExtent,
  required this.cacheOrigin,
});

条子约束就比较复杂, 通常在ListView GridView可滚动的小部件中用得最多, 这是Flutter中用来协调滚动事件非常重要的约束, 这你就不展开了...

源码地址

还有一篇类比Android自定义ViewGroup的文章即将发布...


群内有各(pian)种(ni)各(jin)样(qun)的大佬,等你来撩.

联系作者

点此QQ对话 该死的空格 点此快速加群

相关推荐
试行40 分钟前
Android实现自定义下拉列表绑定数据
android·java
Dingdangr6 小时前
Android中的Intent的作用
android
技术无疆6 小时前
快速开发与维护:探索 AndroidAnnotations
android·java·android studio·android-studio·androidx·代码注入
GEEKVIP6 小时前
Android 恢复挑战和解决方案:如何从 Android 设备恢复删除的文件
android·笔记·安全·macos·智能手机·电脑·笔记本电脑
AiFlutter11 小时前
Flutter之Package教程
flutter
Jouzzy12 小时前
【Android安全】Ubuntu 16.04安装GDB和GEF
android·ubuntu·gdb
极客先躯13 小时前
java和kotlin 可以同时运行吗
android·java·开发语言·kotlin·同时运行
Mingyueyixi15 小时前
Flutter Spacer引发的The ParentDataWidget Expanded(flex: 1) 惨案
前端·flutter
Good_tea_h15 小时前
Android中的单例模式
android·单例模式
计算机源码社20 小时前
分享一个基于微信小程序的居家养老服务小程序 养老服务预约安卓app uniapp(源码、调试、LW、开题、PPT)
android·微信小程序·uni-app·毕业设计项目·毕业设计源码·计算机课程设计·计算机毕业设计开题