如何写对 Flutter centerSlice

前言

作为一个 iOS 开发者来实现 Flutter 点 9 的拉伸效果开始可能有些不知所措,总是不能调试出想要的效果,所以觉得有必要了解下点 9 图的实现原理。

先看 API

iOS 的 API 是设定一个 UIEdgeInsetsUIEdgeInsets 的参数是 left``top``right``bottom,也就是距离图片边缘的距离。

objc 复制代码
- (UIImage *)resizableImageWithCapInsets:(UIEdgeInsets)capInsets API_AVAILABLE(ios(5.0));

而 Flutter 的 API 则是设置一个中心的矩形区域

dart 复制代码
/// The center slice for a nine-patch image.
///
/// The region of the image inside the center slice will be stretched both
/// horizontally and vertically to fit the image into its destination. The
/// region of the image above and below the center slice will be stretched
/// only horizontally and the region of the image to the left and right of
/// the center slice will be stretched only vertically.
final Rect? centerSlice;

开始有些迷惑了,这个和 iOS 中的是一个意思吗?

再看源码

ImageDecorationImage 都有 centerSlice 属性,最终都会执行 paintImage 方法

dart 复制代码
void paintImage({
  ...
  Rect? centerSlice,
  ...
})

如果 centerSlice 不为 null ,就会执行 drawImageNine 方法,其中 center 参数即 centerSlice, dst则是最终图片拉伸后的大小。

dart 复制代码
/// Draws the given [Image] into the canvas using the given [Paint].
///
/// The image is drawn in nine portions described by splitting the image by
/// drawing two horizontal lines and two vertical lines, where the `center`
/// argument describes the rectangle formed by the four points where these
/// four lines intersect each other. (This forms a 3-by-3 grid of regions,
/// the center region being described by the `center` argument.)
///
/// The four regions in the corners are drawn, without scaling, in the four
/// corners of the destination rectangle described by `dst`. The remaining
/// five regions are drawn by stretching them to fit such that they exactly
/// cover the destination rectangle while maintaining their relative
/// positions.
void drawImageNine(Image image, Rect center, Rect dst, Paint paint);

从注释中我们可以了解到,绘制点 9 图会根据根据 center 将图片分成 3 x 3 的 9 个区域 (这应该就是点 9 图命名的由来),边角的四个区域不会拉伸,其他五个区域则会拉伸来铺满剩余 dst

这样来看其实 centerSlice 和 iOS 中的 capInsets 本质上是一样的,只是参数的定义不同。

实践一下

原图 30 x 30, scale 为 3.0

centerSlice 设置为 Rect.fromLTWH(30, 30, 30, 30),拉伸后的效果如下图所示

可得出以下结论:

  • 1、3、7、9 四个边角不会拉伸
  • 2、8 会在水平方向拉伸
  • 4、6 会在垂直方向拉伸
  • 5 会在水平和垂直方向拉伸

centerSlice.fromLTRB

iOS 开发者可能会下意识的的将 Rect.fromLTRB 理解成 UIEdgeInsets 的含义,但这里的 R B 和 UIEdgeInsets 不是同一个概念,这里的 R B 指的是矩形右边和底部相对于坐标轴初始点 (0, 0) 的位置。

dart 复制代码
/// Construct a rectangle from its left, top, right, and bottom edges.
const Rect.fromLTRB(this.left, this.top, this.right, this.bottom);

以上图的气泡为例,iOS 是 UIEdgeInsetsMake(10, 20, 10, 4),flutter 是 Rect.fromLTRB(10, 20, 110, 26),如果设置为 Rect.fromLTRB(10, 20, 10, 4), 就会遇到以下错误

centerSlice was used with a BoxFit that does not guarantee that the image is fully visible.

paintImage 方法的源码可以看到是一下 assert

dart 复制代码
if (centerSlice != null) {
  // We don't have the ability to draw a subset of the image at the same time
  // as we apply a nine-patch stretch.
  assert(sourceSize == inputSize,
      'centerSlice was used with a BoxFit that does not guarantee that the image is fully visible.');
}

其中 inputSize 初始值为图片的像素值,经过计算后变成了 centerSlice 的 size {0, -16}

dart 复制代码
Offset? sliceBorder;
sliceBorder = inputSize / scale - centerSlice.size as Offset;
inputSize = inputSize - sliceBorder * scale as Size;
/// 等价于以下计算
inputSize = inputSize - (inputSize / scale - centerSlice.size) * scale as Size;
inputSize = inputSize - inputSize + centerSlice.size * scale as Size;
inputSize = centerSlice.size * scale as Size;

sourceSize 来源于 fittedSizes.source,此时是 {0, 0}

dart 复制代码
final FittedSizes fittedSizes = applyBoxFit(fit, inputSize / scale, outputSize);

FittedSizes applyBoxFit(BoxFit fit, Size inputSize, Size outputSize) {
  if (inputSize.height <= 0.0 || inputSize.width <= 0.0 || outputSize.height <= 0.0 || outputSize.width <= 0.0) {
    return const FittedSizes(Size.zero, Size.zero);
  }
  ...
}
相关推荐
醉过才知酒浓1 小时前
Flutter Getx 的页面传参
flutter
火柴就是我1 天前
flutter 之真手势冲突处理
android·flutter
Speed1231 天前
`mockito` 的核心“打桩”规则
flutter·dart
法的空间1 天前
Flutter JsonToDart 支持 JsonSchema
android·flutter·ios
恋猫de小郭1 天前
Android 将强制应用使用主题图标,你怎么看?
android·前端·flutter
玲珑Felone1 天前
从flutter源码看其渲染机制
android·flutter
ALLIN2 天前
Flutter 三种方式实现页面切换后保持原页面状态
flutter
Dabei2 天前
Flutter 国际化
flutter
Dabei2 天前
Flutter MQTT 通信文档
flutter
Dabei2 天前
Flutter 中实现 TCP 通信
flutter