OGeek|极客世界-中国程序员成长平台

标题: ios - 如何实现执行 contentStretch 反转的 UIView? [打印本页]

作者: 菜鸟教程小白    时间: 2022-12-13 11:55
标题: ios - 如何实现执行 contentStretch 反转的 UIView?

UIView contentStretch属性定义了一个区域,在该区域内内容被拉伸(stretch)以填充 View 。这很好,但我发现自己需要完全相反的东西:我想要一个 View ,我可以定义一个不拉伸(stretch)但外边缘拉伸(stretch)直到填满 View 的矩形。

  +-------------------------------------------+
  |                                           |
  |         +----------+                      |
  |         |          |                      |
  |         |          |                      |
  |         |  Content |      Stretch to fill |
  |         |          |                      |
  |         |          |                      |
  |         +----------+                      |
  |                                           |
  +-------------------------------------------+

所以在上面的 View 中,外部矩形是我提出的 View 。内部矩形是不可拉伸(stretch)的内容。理想情况下,我希望能够将不可拉伸(stretch)内容的中心点定位在外部矩形内的任何位置,并且仍然让外部位填充到边缘。

示例使用场景:带有透明中心“孔”的黑色覆盖层,跟随鼠标/触摸,用于使用手电筒等探索场景。

我想这样做的一种方法是绘制内容 UIView,然后绘制其他四个 View ,大小适当,以覆盖外部区域,但我希望有一个单 View 解决方案来实现更流畅的动画效果。我猜我需要一个 UIView 并使用 Core Animation 来弄乱它的图层?



Best Answer-推荐答案


所以我的第一次尝试(在我对这个问题的另一个回答中),在 drawRect: 中做所有事情,在我的 iPad 2 上有点滞后。我决定通过创建一个单独的 CALayer 每个切片,让 Core Animation 担心缩放切片。

在这个版本中,工作是在我的自定义 UIView 子类的 layoutSubviews 方法中完成的。只要 View 的大小发生变化(包括 View 首次出现时),系统就会调用 layoutSubviews。我们还可以使用 setNeedsLayout 消息要求系统调用 layoutSubviews。系统会自动合并布局请求,就像合并绘图请求一样。

我需要三个私有(private)实例变量:

@implementation OutstretchView {
    CALayer *_slices[3][3];
    BOOL _imageDidChange : 1;
    BOOL _hasSlices : 1;
}

我的 layoutSubviews 方法从处理没有图像或没有 fixedRect 的简单情况开始:

- (void)layoutSubviews {
    [super layoutSubviews];

    if (!self.image) {
        [self hideSlices];
        self.layer.contents = nil;
        return;
    }

    if (CGRectIsNull(self.fixedRect)) {
        [self hideSlices];
        self.layer.contents = (__bridge id)self.image.CGImage;
        return;
    }

如果我还没有创建九个切片层,它会懒惰地创建它们:

    if (!_hasSlices)
        [self makeSlices];

如果图像发生变化,它会重新计算切片层的图像。 _imageDidChange 标志在 setImage: 中设置。

    if (_imageDidChange) {
        [self setSliceImages];
        _imageDidChange = NO;
    }

最后,它设置九个切片层中每一层的框架。

    [self setSliceFrames];
}

当然,所有实际工作都发生在辅助方法中,但它们非常简单。隐藏切片(当没有图像或没有 fixedRect 时)很简单:

- (void)hideSlices {
    if (!_hasSlices)
        return;
    for (int y = 0; y < 3; ++y) {
        for (int x = 0; x < 3; ++x) {
            _slices[y][x].hidden = YES;
        }
    }
}

创建切片层也很简单:

- (void)makeSlices {
    if (_hasSlices)
        return;
    CALayer *myLayer = self.layer;
    for (int y = 0; y < 3; ++y) {
        for (int x = 0; x < 3; ++x) {
            _slices[y][x] = [CALayer layer];
            [myLayer addSublayer:_slices[y][x]];
        }
    }
    _hasSlices = YES;
}

要制作切片图像并设置切片图层帧,我需要其他答案中的 rect 辅助函数:

static CGRect rect(CGFloat *xs, CGFloat *ys) {
    return CGRectMake(xs[0], ys[0], xs[1] - xs[0], ys[1] - ys[0]);
}

创建切片图像需要一些工作,因为我必须计算切片之间边界的坐标。我使用 CGImageCreateWithImageInRect 从用户提供的图像创建切片图像。

- (void)setSliceImages {
    UIImage *image = self.image;
    CGImageRef cgImage = image.CGImage;
    CGFloat scale = image.scale;
    CGRect fixedRect = self.fixedRect;
    fixedRect.origin.x *= scale;
    fixedRect.origin.y *= scale;
    fixedRect.size.width *= scale;
    fixedRect.size.height *= scale;
    CGFloat xs[4] = { 0, fixedRect.origin.x, CGRectGetMaxX(fixedRect), CGImageGetWidth(cgImage) };
    CGFloat ys[4] = { 0, fixedRect.origin.y, CGRectGetMaxY(fixedRect), CGImageGetHeight(cgImage) };

    for (int y = 0; y < 3; ++y) {
        for (int x = 0; x < 3; ++x) {
            CGImageRef imageSlice = CGImageCreateWithImageInRect(cgImage, rect(xs + x, ys + y));
            _slices[y][x].contents = (__bridge id)imageSlice;
            CGImageRelease(imageSlice);
        }
    }
}

设置切片层帧是类似的,虽然我不必在这里处理图像比例。 (也许我应该使用 UIScreen 比例尺?嗯。这令人困惑。我没有在 Retina 设备上尝试过。)

- (void)setSliceFrames {
    CGRect bounds = self.bounds;
    CGRect fixedRect = self.fixedRect;
    CGPoint fixedCenter = self.fixedCenter;
    fixedRect = CGRectOffset(fixedRect, fixedCenter.x - fixedRect.size.width / 2, fixedCenter.y - fixedRect.size.height / 2);
    CGFloat xs[4] = { bounds.origin.x, fixedRect.origin.x, CGRectGetMaxX(fixedRect), CGRectGetMaxX(bounds) };
    CGFloat ys[4] = { bounds.origin.y, fixedRect.origin.y, CGRectGetMaxY(fixedRect), CGRectGetMaxY(bounds) };

    for (int y = 0; y < 3; ++y) {
        for (int x = 0; x < 3; ++x) {
            _slices[y][x].frame = rect(xs + x, ys + y);
        }
    }
}

这个版本在我的 iPad 2 上似乎更流畅。它可能在旧设备上也能很好地工作。

我的测试项目的这个版本是on github too .

关于ios - 如何实现执行 contentStretch 反转的 UIView?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/9493337/






欢迎光临 OGeek|极客世界-中国程序员成长平台 (http://sqlite.in/) Powered by Discuz! X3.4