iOS_47_ImageView的3D透视变形

正常的图片如下:



绕X轴,经过60度的旋转之后,如图所示:

- (void)transform3d_1
{
    // 沿X轴,3d变换60度
    CATransform3D rotate = CATransform3DMakeRotation(M_PI/3, 1, 0, 0);
    _colorImgView.layer.transform = rotate;
}




下面使用3D变换,相机位置不同时的效果分别如下 :

- (void)transform3d_2
{    // 下面3张图片,分别演示了相机距离图片锚点为40,80,160的情况
    CATransform3D rotate = CATransform3DMakeRotation(M_PI/3, 1, 0, 0);//角度代表倒下去的程度,即与竖直面的夹角,0度代表不倒下
    _colorImgView.layer.transform = CATransform3DPerspect(rotate, CGPointMake(0, 0), 80);// 40,80,160
}



/*
 center指的是相机的位置,
 相机的位置是相对于要进行变换的CALayer的来说的,
 原点是CALayer的anchorPoint在整个CALayer的位置,
 例如CALayer的大小是(100, 200), anchorPoint值为(0.5, 0.5),
 此时anchorPoint在整个CALayer中的位置就是(50, 100),正中心的位置。
 传入透视变换的相机位置为(0, 0),那么相机所在的位置相对于CALayer就是(50, 100)。
 
 如果希望相机在左上角,则需要传入(-50, -100)。
 
 disZ表示的是相机离z=0平面(也可以理解为屏幕)的距离。
 */
CATransform3D CATransform3DMakePerspective(CGPoint center, float disZ)
{
    CATransform3D transToCenter = CATransform3DMakeTranslation(-center.x, -center.y, 0);
    CATransform3D transBack = CATransform3DMakeTranslation(center.x, center.y, 0);
    CATransform3D scale = CATransform3DIdentity;
    scale.m34 = -1.0f/disZ;
    return CATransform3DConcat(CATransform3DConcat(transToCenter, scale), transBack);
}
CATransform3D CATransform3DPerspect(CATransform3D t, CGPoint center, float disZ)
{
    return CATransform3DConcat(t, CATransform3DMakePerspective(center, disZ));
}


相机距离为40时


相机距离为80时


相机距离为160时




由上面可以知道,相机 距离 ImageView的锚点越近,透视效果越强; 距离越远,透视效果越弱;

如下图所示:








如果要绕底边进行透视,那么需将图层的锚点移动到底边的中点上,如下图所示

- (void)transform3d_3
{    // 有bug
    // 如果要绕固定ImgeView的边(如底边)旋转,只需要调整 layer 的 anchorPoint 到对应的边上就行了
    CALayer *layer = [_colorImgView layer];
    CGPoint oldAnchorPoint = layer.anchorPoint;
    
    // sg__old anchor point:0.500000,0.500000
    NSLog(@"sg__old anchor point:%f,%f",oldAnchorPoint.x,oldAnchorPoint.y);
    
    // 向下,向右 为正;此时锚点在底边中点
    [layer setAnchorPoint:CGPointMake(0.5, 1.0)];
    
    // 根据新旧锚点,移动图层的坐标位置
    // sg__layer pos old__187.000000,333.000000
    NSLog(@"sg__layer pos old__%f,%f",layer.position.x,layer.position.y);
    CGFloat posX = layer.position.x + layer.bounds.size.width * (layer.anchorPoint.x - oldAnchorPoint.x);
    CGFloat posY = layer.position.y + layer.bounds.size.height * (layer.anchorPoint.y - oldAnchorPoint.y);
    // sg__layer pos new__187.000000,453.000000
    NSLog(@"sg__layer pos new__%f,%f",posX,posY);
    
    [layer setPosition:CGPointMake(posX,posY)];
    
    CATransform3D rotate = CATransform3DMakeRotation(M_PI/3, 1, 0, 0);
    _colorImgView.layer.transform = CATransform3DPerspect(rotate, CGPointMake(0, 0), 160);
}



上面的有一个bug,现加以解释说明并修正

为什么只是绕底边旋转了60度,会看起来这么扁,按照勾股定理应该

高只有长的1/2呀,而现在是长480,高只有100,理论上变形后的高度应该有240的


如下图所示这是因为移动了imgView的anchorPoint,从0.5,0.5向下移动到了0.5,1.0,

也就是相机(观察点)现在在 eye1处了。

以 yz 面上的点为例,在 eye2 处,红色矩形在 xy 面上的投影为比较靠下的 A 点,

这也就是为什么旋转之后,矩形的高度比较小的原因了(不足宽度的1/2)

ok,现在只需要把 eye1 往上移动到视图中心即可。即往上移动240/2=120



修正后的代码及效果图如下:

- (void)transform3d_4
{
    // 如果要绕固定ImgeView的边(如底边)旋转,只需要调整 layer 的 anchorPoint 到对应的边上就行了
    CALayer *layer = [_colorImgView layer];
    CGPoint oldAnchorPoint = layer.anchorPoint;
    
    // sg__old anchor point:0.500000,0.500000
    NSLog(@"sg__old anchor point:%f,%f",oldAnchorPoint.x,oldAnchorPoint.y);
    
    // 向下,向右 为正;此时锚点在底边中点
    [layer setAnchorPoint:CGPointMake(0.5, 1.0)];
    
    // 根据新旧锚点,移动图层的坐标位置
    // sg__layer pos old__187.000000,333.000000
    NSLog(@"sg__layer pos old__%f,%f",layer.position.x,layer.position.y);
    CGFloat posX = layer.position.x + layer.bounds.size.width * (layer.anchorPoint.x - oldAnchorPoint.x);
    CGFloat posY = layer.position.y + layer.bounds.size.height * (layer.anchorPoint.y - oldAnchorPoint.y);
    // sg__layer pos new__187.000000,453.000000
    NSLog(@"sg__layer pos new__%f,%f",posX,posY);
    
    [layer setPosition:CGPointMake(posX,posY)];
    
    CATransform3D rotate = CATransform3DMakeRotation(M_PI/3, 1, 0, 0);
    
    // 由于上面整个图层向底边移动了,所以此时,这个相机位置的Y值,应该上移一段距离,到中心位置;240为图片的高
    _colorImgView.layer.transform = CATransform3DPerspect(rotate, CGPointMake(0, -240/2.0), 160);
}









那么问题来了,如果要绕左边,类似于开门关门进行透视呢?即相机 沿Y轴方向3D变换,那么只要修改一下参数即可

照猫画虎做一遍

#pragma mark - 绕Y轴变换
- (void)transform3d_Y_1
{
    // 沿Y轴,3d变换60度
    CATransform3D rotate = CATransform3DMakeRotation(M_PI/3, 0, 1, 0);
    _colorImgView.layer.transform = rotate;
}


- (void)transform3d_Y_2
{
    // 下面3张图片,分别演示了相机距离图片锚点为40,80,160的情况
    CATransform3D rotate = CATransform3DMakeRotation(M_PI/3, 0, 1, 0);//角度代表倒下去的程度,即与竖直面的夹角,0度代表不倒下
    _colorImgView.layer.transform = CATransform3DPerspect(rotate, CGPointMake(0, 0), 160);// 40,80,160
}
相机距离40时:



相机距离80时:


相机距离160时:




// 有bug
- (void)transform3d_Y_3
{
    // 如果要绕固定ImgeView的边(如左边)旋转,只需要调整 layer 的 anchorPoint 到对应的边上就行了
    CALayer *layer = [_colorImgView layer];
    CGPoint oldAnchorPoint = layer.anchorPoint;
    
    // sg__old anchor point: 0.5  ,  0.5
    NSLog(@"sg__old anchor point:%f,%f",oldAnchorPoint.x,oldAnchorPoint.y);
    
    // 向下,向右 为正;此时锚点在左边中点
    [layer setAnchorPoint:CGPointMake(0, 0.5)];
    
    // 根据新旧锚点,移动图层的坐标位置
    // sg__layer pos old__187.000000,333.000000
    NSLog(@"sg__layer pos old__%f,%f",layer.position.x,layer.position.y);
    CGFloat posX = layer.position.x + layer.bounds.size.width * (layer.anchorPoint.x - oldAnchorPoint.x);
    CGFloat posY = layer.position.y + layer.bounds.size.height * (layer.anchorPoint.y - oldAnchorPoint.y);
    // sg__layer pos new__67.000000,333.000000
    NSLog(@"sg__layer pos new__%f,%f",posX,posY);
    
    [layer setPosition:CGPointMake(posX,posY)];
    
    CATransform3D rotate = CATransform3DMakeRotation(M_PI/3, 0, 1, 0);
    _colorImgView.layer.transform = CATransform3DPerspect(rotate, CGPointMake(0, 0), 160);// 这儿有bug
}



解决上述bug,即由于图层整个左移了,所以相机要向右移动一段距离,对齐到视图的中心

- (void)transform3d_Y_4
{// 如果要绕固定ImgeView的边(如底边)旋转,只需要调整 layer 的 anchorPoint 到对应的边上就行了
    CALayer *layer = [_colorImgView layer];
    CGPoint oldAnchorPoint = layer.anchorPoint;
    
    // sg__old anchor point:0.500000,0.500000
    NSLog(@"sg__old anchor point:%f,%f",oldAnchorPoint.x,oldAnchorPoint.y);
    
    // 向下,向右 为正;此时锚点在左边中点
    [layer setAnchorPoint:CGPointMake(0, 0.5)];
    
    // 根据新旧锚点,移动图层的坐标位置
    // sg__layer pos old__187.000000,333.000000
    NSLog(@"sg__layer pos old__%f,%f",layer.position.x,layer.position.y);
    CGFloat posX = layer.position.x + layer.bounds.size.width * (layer.anchorPoint.x - oldAnchorPoint.x);
    CGFloat posY = layer.position.y + layer.bounds.size.height * (layer.anchorPoint.y - oldAnchorPoint.y);
    // sg__layer pos new__67.000000,333.000000
    NSLog(@"sg__layer pos new__%f,%f",posX,posY);
    
    [layer setPosition:CGPointMake(posX,posY)];
    
    CATransform3D rotate = CATransform3DMakeRotation(M_PI/3, 0, 1, 0);
    
    // 由于上面整个图层向左边移动了,所以此时,这个相机位置的X值,应该右移一段距离,到中心位置;240为图片的宽
    _colorImgView.layer.transform = CATransform3DPerspect(rotate, CGPointMake(240/2.0, 0), 160);
}










  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
当一个View超出屏幕时,你可以使用ScrollView或者HorizontalScrollView来解决这个问题。 如果你想拖动一个ImageView,并且希望它不会超出屏幕,你可以使用以下代码: ```java imageView.setOnTouchListener(new OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: { // 手指按下时记录位置 lastX = event.getRawX(); lastY = event.getRawY(); break; } case MotionEvent.ACTION_MOVE: { // 移动的距离 float dx = event.getRawX() - lastX; float dy = event.getRawY() - lastY; // 获取imageView的参数 RelativeLayout.LayoutParams lp = (RelativeLayout.LayoutParams) v.getLayoutParams(); // 移动imageView lp.leftMargin += dx; lp.topMargin += dy; // 确定imageView不会超出屏幕 int screenWidth = getResources().getDisplayMetrics().widthPixels; int screenHeight = getResources().getDisplayMetrics().heightPixels; if (lp.leftMargin < 0) { lp.leftMargin = 0; } else if (lp.leftMargin + v.getWidth() > screenWidth) { lp.leftMargin = screenWidth - v.getWidth(); } if (lp.topMargin < 0) { lp.topMargin = 0; } else if (lp.topMargin + v.getHeight() > screenHeight) { lp.topMargin = screenHeight - v.getHeight(); } // 重新设置imageView的参数 v.setLayoutParams(lp); // 更新位置记录 lastX = event.getRawX(); lastY = event.getRawY(); break; } case MotionEvent.ACTION_UP: { // 手指抬起时不需要做任何事情 break; } } return true; } }); ``` 这个代码片段会允许你拖动ImageView,但是它不会被拖动超出屏幕。如果ImageView被拖动到了屏幕边缘,它会停在那里并且无法继续拖动。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值