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

相关推荐
ujainu17 小时前
Flutter + OpenHarmony 游戏开发进阶:用户输入响应——GestureDetector 实现点击发射
flutter·游戏·openharmony
Doro再努力17 小时前
【Linux操作系统10】Makefile深度解析:从依赖推导到有效编译
android·linux·运维·服务器·编辑器·vim
Daniel李华17 小时前
echarts使用案例
android·javascript·echarts
hudawei99617 小时前
TweenAnimationBuilder和AnimatedBuilder两种动画的比较
flutter·ui·动画·tweenanimation·animatedbuilder
ujainu17 小时前
Flutter + OpenHarmony 实现无限跑酷游戏开发实战—— 对象池化、性能优化与流畅控制
flutter·游戏·性能优化·openharmony·endless runner
做人不要太理性18 小时前
CANN Runtime 运行时组件深度解析:任务调度机制、存储管理策略与维测体系构建逻辑
android·运维·魔珐星云
我命由我1234518 小时前
Android 广播 - 静态注册与动态注册对广播接收器实例创建的影响
android·java·开发语言·java-ee·android studio·android-studio·android runtime
ZH154558913118 小时前
Flutter for OpenHarmony Python学习助手实战:自动化脚本开发的实现
python·学习·flutter
朗迹 - 张伟19 小时前
Tauri2 导出 Android 详细教程
android
lpruoyu19 小时前
【Android第一行代码学习笔记】Android架构_四大组件_权限_持久化_通知_异步_服务
android·笔记·学习