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

相关推荐
alexhilton7 小时前
面向开发者的系统设计:像建筑师一样思考
android·kotlin·android jetpack
BG11 小时前
Flutter 简仿Excel表格组件介绍
flutter
zhangmeng15 小时前
FlutterBoost在iOS26真机运行崩溃问题
flutter·app·swift
恋猫de小郭15 小时前
对于普通程序员来说 AI 是什么?AI 究竟用的是什么?
前端·flutter·ai编程
卡尔特斯15 小时前
Flutter A GlobalKey was used multipletimes inside one widget'schild list.The ...
flutter
CYRUS_STUDIO16 小时前
用 Frida 控制 Android 线程:kill 命令、挂起与恢复全解析
android·linux·逆向
CYRUS_STUDIO16 小时前
Frida 实战:Android JNI 数组 (jobjectArray) 操作全流程解析
android·逆向
w_y_fan19 小时前
Flutter 滚动组件总结
前端·flutter
用户0919 小时前
Gradle Cache Entries 深度探索
android·java·kotlin
醉过才知酒浓19 小时前
Flutter Getx 的页面传参
flutter