拖动层并播放动画

本文介绍了一种使用手势拖动层并在手势结束后播放动画的方法。通过监听pan手势,使图层沿圆形路径移动,并在手势结束时根据当前速度播放动画,使图层继续沿圆周运动。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

拖动层并播放动画

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

imageimage

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

  • 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。需要用其他方案提到。但是这个示例积累了很多知识。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值