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

相关推荐
從南走到北37 分钟前
JAVA国际版二手车交易二手车市场系统源码支持Android+IOS+H5+APP
android·java·ios
江上清风山间明月43 分钟前
Android 系统中进程和线程的区别
android·python·线程·进程
2501_940094022 小时前
mig烧录卡资源 Mig-Switch游戏合集 烧录卡 1.75T
android·游戏·安卓·switch
渡我白衣2 小时前
深入理解 OverlayFS:用分层的方式重新组织 Linux 文件系统
android·java·linux·运维·服务器·开发语言·人工智能
2501_915106322 小时前
iOS性能调优的系统化实践,从架构分层到多工具协同的全流程优化指南(开发者深度版)
android·ios·小程序·架构·uni-app·iphone·webview
stevenzqzq3 小时前
android recyclerview缓存_缓存问题解决办法
android·java·缓存
下位子3 小时前
『OpenGL学习滤镜相机』- Day10: 相机预览与 OpenGL 结合
android·opengl
那就逆流而上吧4 小时前
Android AIDL 的详细讲解和实践指南
android
初遇你时动了情5 小时前
flutter vscode 终端无法使用fvm 版本切换、项目运行
ide·vscode·flutter
TDengine (老段)5 小时前
TDengine 字符串函数 POSITION 用户手册
android·java·大数据·数据库·物联网·时序数据库·tdengine