UIView contentStretch属性定义了一个区域,在该区域内内容被拉伸(stretch)以填充 View 。这很好,但我发现自己需要完全相反的东西:我想要一个 View ,我可以定义一个不拉伸(stretch)但外边缘拉伸(stretch)直到填满 View 的矩形。
+-------------------------------------------+
| |
| +----------+ |
| | | |
| | | |
| | Content | Stretch to fill |
| | | |
| | | |
| +----------+ |
| |
+-------------------------------------------+
所以在上面的 View 中,外部矩形是我提出的 View 。内部矩形是不可拉伸(stretch)的内容。理想情况下,我希望能够将不可拉伸(stretch)内容的中心点定位在外部矩形内的任何位置,并且仍然让外部位填充到边缘。
示例使用场景:带有透明中心“孔”的黑色覆盖层,跟随鼠标/触摸,用于使用手电筒等探索场景。
我想这样做的一种方法是绘制内容 UIView,然后绘制其他四个 View ,大小适当,以覆盖外部区域,但我希望有一个单 View 解决方案来实现更流畅的动画效果。我猜我需要一个 UIView 并使用 Core Animation 来弄乱它的图层?
Best Answer-推荐答案 strong>
所以我的第一次尝试(在我对这个问题的另一个回答中),在 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/
|