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对话 该死的空格 点此快速加群

相关推荐
weiggle12 小时前
第七篇:状态提升与单向数据流——架构设计的核心
android
xingpanvip12 小时前
星盘接口开发文档:本命盘接口指南
android·开发语言·css·php·lua
goldenrolan12 小时前
A公司物料替代测试系统 v1.7:从需求到 exe/apk 的 AI 辅助全链路实践
android·自动化测试·软件测试·python·ai
AC赳赳老秦14 小时前
用 OpenClaw 搭建服务器故障应急响应系统,自动处理 80% 常见运维故障
android·运维·服务器·python·rxjava·deepseek·openclaw
风华圆舞15 小时前
Flutter + 鸿蒙 Intents Kit:页面直达能力的完整接入方案
flutter·ui·华为·harmonyos
韩曙亮15 小时前
【Flutter】Flutter 组件 ④ ( 组件渲染 的 三棵树理论 | Widget 树 → Element 树 → RenderObject 树 )
flutter·element·widget·renderobject
骇客之技术15 小时前
AutoLua:在安卓上写 Lua 脚本
android·junit·lua
kiros_wang16 小时前
Android 常见面试题
android
货拉拉技术16 小时前
Hook植入日志协助定位问题方案
android
FlightYe16 小时前
Android投屏MirrorCast全链路
android