博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
拖动层并播放动画
阅读量:6237 次
发布时间:2019-06-22

本文共 5115 字,大约阅读时间需要 17 分钟。

在下面的示例中,用手势拖动Layer转动,当手势结束时,会播放动画继续让Layer沿着圆的轨道转动一会儿。

 

这里包括两个动作,以及针对这两个动作的处理。即:

  • pan手势,即拖动,这时不播放动画,要确保Layer的运动是按照圆的轨迹来移动,而不是拖动到哪里到哪里
  • pan手势的结束,其实应该用swipe手势,这里是简单的监控到pan手势结束,然后按照当前速度,取一个最小值,当超过该值的时候,播放动画继续转动一段时间

这是自定义视图的初始化代码部分:

- (id)initWithFrame:(CGRect)frame { 

    
    self = [super initWithFrame:frame]; 
    if (self) { 
        [self initLayers]; 
        
        UIPanGestureRecognizer *panGesture = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(doPanAction:)]; 
        [panGesture setMaximumNumberOfTouches:2]; 
        [self addGestureRecognizer:panGesture]; 
        [panGesture release]; 
    } 
    return self; 
}

- (void) initLayers{ 

    startLayer=[CALayer layer]; 
    UIImage *image=[UIImage imageNamed:@"4.png"]; 
    startLayer.bounds=CGRectMake(0, 0, image.size.width, image.size.height); 
    startLayer.contents=(id)[image CGImage]; 
    startLayer.position=CGPointMake(768/2-RADIAS, 1024/2); 
    
    [self.layer addSublayer:startLayer]; 
    
    [image release]; 
}

这里增加了一个Layer,就是上面看到的小星星。另外,监听pan手势。

当监听到pan手势时,调用:

- (void)doPanAction:(UIPanGestureRecognizer *)gestureRecognizer{ 

    
    CGPoint locationInView = [gestureRecognizer locationInView:self]; 
    NSLog(@"pan … x: %f, y: %f",locationInView.x,locationInView.y); 
    
    CALayer *layer=[self.layer hitTest:locationInView]; 
    
    if (layer==startLayer) { 
        CGPoint velocityInView=[gestureRecognizer velocityInView:self];
        NSLog(@"catch it! velocity.x: %f, velocity.y: %f",velocityInView.x,velocityInView.y); 
        
        struct PanLocationData panData; 
        panData.panLocation=locationInView; 
        panData.currentVelocity=velocityInView; 
        panData.currentLocation=startLayer.position; 
        
        [CATransaction begin]; 
        [CATransaction setValue:[NSNumber numberWithBool:YES] 
                         forKey:kCATransactionDisableActions]; 
        
        startLayer.position=[self getNextPanLocation:panData]; 
        [CATransaction commit];

这里没有写出该方法的最后几行,主要是那些是用于播放pan后的动画的,暂且忽略。

这里最重要的是拖动的下一个坐标点不是当前pan的坐标点,而是圆的轨迹点。需要另外一个方法来计算:

- (CGPoint) getNextPanLocation:(struct PanLocationData) data{ 

    //防止坐标越界 
    if (data.panLocation.x<MIN_X) { 
        data.panLocation.x=MIN_X; 
    } 
    if (data.panLocation.x>MAX_X) { 
        data.panLocation.x=MAX_X; 
    } 
    if (data.panLocation.y<MIN_Y) { 
        data.panLocation.y=MIN_Y; 
    } 
    if (data.panLocation.y>MAX_Y) { 
        data.panLocation.y=MAX_Y; 
    } 
    
    //设置根据x坐标和y坐标的定位点变量 
    CGPoint xLocation=data.panLocation,yLocation=data.panLocation; 
    
    //根据x坐标获得y坐标 
    if (xLocation.y<CENTER_LOCATION_Y) { 
        xLocation.y=-[self getLocationY:xLocation.x]+CENTER_LOCATION_Y; 
    }else{ 
        xLocation.y=[self getLocationY:xLocation.x]+CENTER_LOCATION_Y; 
    } 
    
    if (yLocation.x<CENTER_LOCATION_Y) { 
        yLocation.x=-[self getLocationX:yLocation.y]+CENTER_LOCATION_X; 
    }else{ 
        yLocation.x=[self getLocationX:yLocation.y]+CENTER_LOCATION_X; 
    } 
    
    CGPoint returnPoint=xLocation; 
    
    //在接近x极值时切换到根据y坐标定位x坐标 
    if (xLocation.x<MIN_X+0.1 || xLocation.x>MAX_X-0.1) { 
        returnPoint= yLocation; 
    } 
    
    //防止出现跳动回退的情况,即手势向前拖动,图形向后跳动 
    if (data.currentVelocity.x*(returnPoint.x-data.currentLocation.x)<0) { 
        returnPoint=data.currentLocation; 
    } 
    
    return returnPoint; 
}

代码写到这里,发现了个问题,用手势拖拽一个小的layer做弧形移动,问题很大,比如在接近左侧或者右侧边缘时,上下拖动Layer的时候很困难。因为这时x的增量不起作用了,而计算坐标是以x点为基础的。因此在边缘情况下做了个处理,用y坐标来计算x坐标。

计算坐标的方法:

- (float)getLocationY:(float) x{ 

    return RADIAS*sqrt(1-pow((x-CENTER_LOCATION_X)/RADIAS, 2));
}

- (float)getLocationX:(float) y{ 

    return RADIAS*sqrt(1-pow((y-CENTER_LOCATION_Y)/RADIAS, 2));
}

这里的公式原型是:sin2(a)+cos2(a)=1。其实都可以从勾股定理推出。sin是对边比斜边,cos是邻边比斜边。

在做这个代码的时候,把初中高中的一些三角函数方面的知识复习了一下。呵呵。

在pan手势结束,用如下代码做了判断:

if (gestureRecognizer.state==UIGestureRecognizerStateEnded) { 

    [self doPostPanAction:panData]; 
}

处理pan结束的方法,主要是播放动画:

- (void)doPostPanAction:(struct PanLocationData) data{ 

    //判断x速度和y速度在圆外切方向是否形成贡献,贡献值是否大于最小值 
    if (pow(data.currentVelocity.x,2)+pow(data.currentVelocity.y, 2)>pow(MIN_VELOCITY,2)) { 
        BOOL clockwise=FALSE;//顺时针标志 
        //如下情况顺时针 
        if ((data.currentLocation.y-CENTER_LOCATION_Y>0 && data.currentVelocity.x<0) 
            || (data.currentLocation.y-CENTER_LOCATION_Y<0 && data.currentVelocity.x>0)) { 
            clockwise=TRUE; 
        } 
        
        CAKeyframeAnimation *anim=[CAKeyframeAnimation animationWithKeyPath:@"position"]; 
        
        NSMutableArray *values=[NSMutableArray array]; 
        
        //计算当前值的角度 
        float currentArc=atan((startLayer.position.y-CENTER_LOCATION_Y)/(startLayer.position.x-CENTER_LOCATION_X))*360/(2*M_PI); 
        
        if (startLayer.position.x-CENTER_LOCATION_X<0) { 
            if(currentArc<0){ 
                currentArc=180-abs(currentArc); 
            }else { 
                currentArc=-(180-abs(currentArc)); 
            } 
        } 
        
        NSLog(@">>>>current arc:%f",currentArc); 
        
        CGPoint currentPoint; 
        
        for (int i=0; i<LOOP_COUNT; i++) { 
            if (clockwise) { 
                currentPoint=CGPointMake(RADIAS*cos((currentArc+i)*2*M_PI/360)+CENTER_LOCATION_X,
                                         RADIAS*sin((currentArc+i)*2*M_PI/360)+CENTER_LOCATION_Y); 
            }else { 
                currentPoint=CGPointMake(RADIAS*cos((currentArc-i)*2*M_PI/360)+CENTER_LOCATION_X, 
                                         RADIAS*sin((currentArc-i)*2*M_PI/360)+CENTER_LOCATION_Y); 
            } 
            [values addObject:[NSValue valueWithCGPoint:currentPoint]];
        } 
        startLayer.position=currentPoint; 
        
        anim.values=values; 
        [anim setDuration:2.0]; 
        
        [startLayer addAnimation:anim forKey:@"demoAnimation"]; 
    } 
}

这里需要的一些数学知识是,判断用户操作的是顺时针还是逆时针,我没有找到很好的办法,是通过坐标系象限以及手势的x、y方向速度来判断的。

另外,就是动画如何播放。我的思路是,按照角度来,从当前角度开始,每次转动1度。这里需要把弧度转换为角度。我的做法是,已知x、y,因此知道对边和邻边,这样可以得到tan,即正切。然后用arctan取得角度。

这个角度还不能直接用,要判断是在第几象限,有可能需要获取它的补角。

其他的技术点,就是使用关键帧动画。可参照以前的示例。

这个示例不会用于正式生产环境的,因为用户体验很不好。因为Layer太小,手势在操作过程中很容易离开layer。需要用其他方案提到。但是这个示例积累了很多知识。

转载地址:http://uvzia.baihongyu.com/

你可能感兴趣的文章
java数组复制的几种常见用法
查看>>
去哪网实习总结:JavaWeb中文传參乱码问题的解决(JavaWeb)
查看>>
Xeon Phi之MIC编程知识点
查看>>
jigloo安装和介绍
查看>>
php monolog 的写日志到unix domain socket 测试终于成功
查看>>
【转】JSP中的9大隐藏对象
查看>>
荆慕瑶
查看>>
bilibili携手WeTest,保障视频类应用优质适配体验
查看>>
无线应用安全剖析
查看>>
GNU/Linux安全基线与加固-0.1
查看>>
当产品/后端/QA/你自己说了这些话,就要警惕了!
查看>>
聊聊directory traversal attack
查看>>
OC消息转发机制
查看>>
理解函数防抖Debounce
查看>>
10分钟了解react引入的hooks
查看>>
用一个简易的 web chat 说说 Python、Golang、Nodejs 的异步
查看>>
Nginx代理访问提示ERR_CONTENT_LENGTH_MISMATCH
查看>>
【iOS开发】在Xcode中做一个 a包合成脚本
查看>>
注册、登录和 token 的安全之道
查看>>
离线批量数据通道Tunnel的最佳实践及常见问题
查看>>