UIView contentStretch属性定义了一个区域,在该区域内拉伸内容以填充视图。这很好,但我发现自己需要完全相反的东西:我想要一个视图,我可以定义一个不伸展的矩形,但外边缘拉伸直到它们填满视图。
+-------------------------------------------+
| |
| +----------+ |
| | | |
| | | |
| | Content | Stretch to fill |
| | | |
| | | |
| +----------+ |
| |
+-------------------------------------------+
所以在上面的视图中,外部rect是我提出的视图。内部矩形是不可拉伸的内容。理想情况下,我希望能够将不可伸缩内容的中心点定位在外部矩形内的任何位置,并且仍然将外部位填充到边缘。
示例使用场景:带有透明中心“孔”的黑色覆盖图,跟随鼠标/触摸,用于使用手电筒等探测场景。
我认为这样做的一种方法是绘制内容UIView,然后绘制其他四个视图,大小适当,覆盖外部区域,但我希望有一个单视图解决方案,以实现更流畅的动画。我猜我需要有一个UIView并使用Core Animation来搞定它的图层?
答案 0 :(得分:2)
所以我的第一次尝试(在我对这个问题的另一个答案中),在drawRect:
中执行所有操作,在iPad 2上有点滞后。我决定通过创建单独的CALayer
来重做它。每个切片让Core Animation担心缩放切片。
在此版本中,工作是在我的自定义layoutSubviews
子类的UIView
方法中完成的。只要视图的大小发生变化(包括视图首次出现时),系统就会调用layoutSubviews
。我们还可以要求系统使用layoutSubviews
消息来呼叫setNeedsLayout
。系统自动合并布局请求,就像它合并绘制请求一样。
我需要三个私有实例变量:
@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。
答案 1 :(得分:1)
据推测,你不需要其他四个观点,你需要八个观点。内部“固定”矩形将视图划分为九个切片:
+----+-----+---------+
| | | |
+----+-----+---------|
| |fixed| |
| | | |
+----+-----+---------+
| | | |
| | | |
| | | |
+----+-----+---------+
请注意,九个切片中的每个切片可能需要不同的水平和垂直缩放组合。最终看起来像这样:
无论如何,我认为仅仅通过创建覆盖UIView
的{{1}}子类来做到这一点并不难。
我命名了我的子类drawRect:
并给它了三个属性:
OutstretchView
@interface OutstretchView : UIView
@property (nonatomic) UIImage *image;
@property (nonatomic) CGRect fixedRect; // in image coordinates
@property (nonatomic) CGPoint fixedCenter; // in view coordinates
@end
属性定义了永远不应拉伸的图像部分。 fixedRect
属性定义了视图中应该绘制部分图像的位置。
要实际绘制图像的九个切片,定义一个在图像坐标中采用fixedCenter
并在视图坐标中采用CGRect
并绘制图像的指定部分的方法会很有帮助在视图的指定部分。我使用Quartz 2D的剪辑和CTM功能来完成“繁重的工作”:
CGRect
我还将使用一个小辅助方法,从两个X坐标的数组和两个Y坐标的数组创建一个- (void)drawRect:(CGRect)imageRect ofImage:(UIImage *)image inRect:(CGRect)viewRect {
CGContextRef gc = UIGraphicsGetCurrentContext();
CGContextSaveGState(gc); {
CGContextClipToRect(gc, viewRect);
CGContextTranslateCTM(gc, viewRect.origin.x, viewRect.origin.y);
CGContextScaleCTM(gc, viewRect.size.width / imageRect.size.width, viewRect.size.height / imageRect.size.height);
CGContextTranslateCTM(gc, -imageRect.origin.x, -imageRect.origin.y);
[image drawAtPoint:CGPointZero];
} CGContextRestoreGState(gc);
}
:
CGRect
有了这些助手,我可以实施static CGRect rect(CGFloat *xs, CGFloat *ys) {
return CGRectMake(xs[0], ys[0], xs[1] - xs[0], ys[1] - ys[0]);
}
。首先,我处理了无法绘制图像或没有定义drawRect:
的简单情况:
fixedRect
然后我计算视图坐标中的矩形,其中将绘制图像的固定部分:
- (void)drawRect:(CGRect)dirtyRect {
UIImage *image = self.image;
if (!image)
return;
CGRect imageBounds = (CGRect){ CGPointZero, image.size };
CGRect viewBounds = self.bounds;
CGRect imageFixedRect = self.fixedRect;
if (CGRectIsNull(imageFixedRect)) {
[image drawInRect:viewBounds];
return;
}
接下来,我创建一个数组,其中包含四个(是,四个)X坐标,用于定义视图坐标空间中切片的边缘,以及一个用于Y坐标的类似数组,以及另外两个用于图像坐标空间中坐标的数组:
CGPoint imageFixedCenter = self.fixedCenter;
CGRect viewFixedRect = CGRectOffset(imageFixedRect, imageFixedCenter.x - imageFixedRect.size.width / 2, imageFixedCenter.y - imageFixedRect.size.height / 2);
最后是简单的部分,绘制切片:
CGFloat viewSlicesX[4] = { viewBounds.origin.x, viewFixedRect.origin.x, CGRectGetMaxX(viewFixedRect), CGRectGetMaxX(viewBounds) };
CGFloat viewSlicesY[4] = { viewBounds.origin.y, viewFixedRect.origin.y, CGRectGetMaxY(viewFixedRect), CGRectGetMaxY(viewBounds) };
CGFloat imageSlicesX[4] = { imageBounds.origin.x, imageFixedRect.origin.x, CGRectGetMaxX(imageFixedRect), CGRectGetMaxX(imageBounds) };
CGFloat imageSlicesY[4] = { imageBounds.origin.y, imageFixedRect.origin.y, CGRectGetMaxY(imageFixedRect), CGRectGetMaxY(imageBounds) };
我的iPad 2有点迟钝。
我的测试项目的这个版本是on github。