苹果的应用讲究用户体验

有的时候仔细想想

的确,很多细节决定了用户体验

比如说惯性拖动

可以说之前没有任何一家厂商能把触摸惯性拖动做的像苹果的UI那么流畅

Cocos2D中实现能够惯性拖动的选择界面

完成的效果:

制作一个简单的图层,通过传入许多的节点,图层自动将节点排版,并能够通过物理拖拽来选择其中的某一个节点,并通知节点的代理来处理

首先新建一个cocos2d项目,我用的版本是2.0,命名为SimplePhysicsDragSelectorTest

新建一个objective-c class,我这里命名为SZSimplePhysicsDragSelector

在SimplePhysicsDragSelector.h文件里添加以下代码:

#import "cocos2d.h"@class SZSimplePhysicsDragSelector;
@protocol SZSimplePhysicsDragSelectorDelegate <NSObject>
@optional
// call when the selected icon changes
-(void)onSelectedIconChanged:(SZSimplePhysicsDragSelector*)selector;
@end@interface SZSimplePhysicsDragSelector : CCLayer
{CCNode *s_content;//所有节点图标的父节点NSMutableArray *s_icons;//节点图标清单CCNode *s_selectedIcon;//选定的节点
    BOOL isDragging;//是否在拖拽状态CGPoint lastTouchPoint;//上一个触摸点float lastx;//上一个图层内容x坐标float xvel;//内容在x轴上的速度int maxX;//内容可以自然移动到的最大极限x坐标int minX;//内容可以自然移动到的最小极限x坐标float acceleration;//加速度float f;//合外力id<SZSimplePhysicsDragSelectorDelegate> s_delegate;//代理
}@property (nonatomic, readonly) NSMutableArray *Icons;
@property (nonatomic, readonly) CCNode *SelectedIcon;
@property (nonatomic, assign) id<SZSimplePhysicsDragSelectorDelegate> Delegate;- (id)initWithIcons:(NSArray*)icons;@end

这里声明了SZSimplePhysicsDragSelector需要使用到的变量和方法,同时声明了SZSimplePhysicsDragSelector代理的方法

变量的作用如注释里描述的,后面将会详细说到

解释下代理方法:

-(void)onSelectedIconChanged:(SZSimplePhysicsDragSelector*)selector;

在图层选择的节点发生改变时将会发送此消息给代理,如果改变为没有选择节点也会发送此消息

初始化

在SZSimplePhysicsDragSelector.m文件中添加以下代码:

@implementation SZSimplePhysicsDragSelector@synthesize Delegate = s_delegate;
@synthesize Icons = s_icons;
@synthesize SelectedIcon = s_selectedIcon;- (id)initWithIcons:(NSArray *)icons
{self = [super init];if (self) {s_icons = [[NSMutableArray alloc] initWithArray:icons];s_content = nil;s_selectedIcon = nil;isDragging = NO;lastTouchPoint = CGPointZero;lastx = 0.0f;xvel = 0.0f;minX = 0;maxX = 0;acceleration = 0.0f;f = 0.0f;self.isTouchEnabled = true;// 启用接收触摸事件
        s_delegate = nil;}return self;
}- (void)dealloc
{[s_icons release];[super dealloc];
}#pragma mark Override methods-(void) onEnter
{[super onEnter];s_content = [[CCSprite alloc]init];[self addChild:s_content];[self scheduleUpdate];//开启计时器
}-(void) onExit
{[self unscheduleUpdate];//关闭计时器
    [self removeChild:s_content cleanup:YES];[s_content release];s_content = nil;s_selectedIcon = nil;[super onExit];
}@end

以上代码实现了初始化&内存释放以及onEnter和onExist方法

在选择器被添加到某一个节点中时,将会自动创建一个内容节点s_content,用来存放所有的节点,并一起移动

布局节点

在onEnter方法中布局视图,并实现layout方法-(void) onEnter

-(void) onEnter
{[super onEnter];s_content = [[CCSprite alloc]init];[self addChild:s_content];[self layout];[self scheduleUpdate];
}-(void) layout
{int i = 1;for (CCNode *icon in s_icons) {CGPoint position = ccp((i-1) * 180, 0);float distance = fabsf(icon.position.x)/100;icon.position = position;if (![s_content.children containsObject:icon]) {[s_content addChild:icon];}i++;}s_selectedIcon = [s_icons lastObject];if ([s_delegate respondsToSelector:@selector(onSelectedIconChanged:)]) {[s_delegate onSelectedIconChanged:self];}minX = - (i-1) * 180 - 100;maxX = 100;
}

解释下layout方法

将180pt作为每两个节点之间的间距,同时第一个节点在s_content中的位置应该是(0,0)所以计算得出位置的公式(间距和初始位置可以根据需要更改)

position = ccp((i-1) * 180, 0)

之后添加节点到s_content,并且设置最后一个为初始选定的节点,最后通知代理选定节点发生更改

关于极限位置(minX,maxX)是这样设定的,前面说到180作为间距,(0,0)为初始节点位置,所以最后一个节点的x坐标为(i-1) * 180(i为节点个数),当需要选择右边的节点时实际上是将s_content的位置向左移动,所以选择到最后一个节点时s_content的位置应该是-(i-1) * 180,同理第一个选择到第一个节点时s_content的位置应该是(0,0),此外我希望极限位置能够比头尾节点位置的范围稍大,所以最终我设定

minX = - (i-1) * 180 - 100;

maxX = 100;

触摸记录

布局完成,接下来我们需要实现触摸事件消息来记录数据供模拟物理使用

在SZSimplePhysicsDragSelector.m文件中添加以下代码:

- (void)ccTouchesBegan:(NSSet *)touches withEvent:(UIEvent *)event;
{s_selectedIcon = nil;if ([s_delegate respondsToSelector:@selector(onSelectedIconChanged:)]) {[s_delegate onSelectedIconChanged:self];}UITouch *touch = [touches anyObject];CGPoint position = [self convertTouchToNodeSpace:touch];lastTouchPoint = position;isDragging = true;
}- (void)ccTouchesMoved:(NSSet *)touches withEvent:(UIEvent *)event;
{UITouch *touch = [touches anyObject];CGPoint position = [self convertTouchToNodeSpace:touch];CGPoint translate = ccpSub(position, lastTouchPoint);translate.y = 0;s_content.position = ccpAdd(s_content.position, translate);lastTouchPoint = position;
}- (void)ccTouchesEnded:(NSSet *)touches withEvent:(UIEvent *)event;
{isDragging = false;
}- (void)ccTouchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event;
{isDragging = false;
}

这里分开说下4个触摸事件

- (void)ccTouchesBegan:(NSSet *)touches withEvent:(UIEvent *)event;
在方法中我们清空了选择的节点并通知代理选择的节点改变,标记自身状态为拖拽中

- (void)ccTouchesMoved:(NSSet *)touches withEvent:(UIEvent *)event;
在方法中根据此刻触摸点与上一次触摸点的位置差,来移动s_content的位置,从而使内容跟随触摸移动,最后在记录下此刻的位置为上一次触摸位置,供下一次计算使用

- (void)ccTouchesEnded:(NSSet *)touches withEvent:(UIEvent *)event;

标记自身状态为未拖拽

- (void)ccTouchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event;

标记自身状态为未拖拽

这样我们已经能够辨别自身是否在拖拽状态以及正确拖拽内容

模拟物理计算

首先说明一下思路:

我们在udpate方法中我们需要检测图层的状态:

若图层在被拖拽状态,则不需要模拟物理,只需要计算出用户触摸拖拽内容在x轴上的速度

若图层在未拖拽状态,则根据已经记录下的x轴移动速度,和通过受力计算出的加速度,改变x轴移动速度,最后在根据计算出的移动速度来计算实际位移

在SZSimplePhysicsDragSelector.m文件中添加以下代码:

-(void) update:(ccTime)dt;
{[self updateMove:dt];
}- (void) updateMove:(ccTime)dt
{if ( !isDragging ){// *** CHANGE BEHAVIOR HERE *** //
        float F1 = 0.0f;float F2 = 0.0f;float F3 = 0.0f;CGPoint pos = s_content.position;//F1// frictionF1 = - xvel * 0.1;//F2// prevent icons out of rangeif ( pos.x < minX ){F2 = (minX - pos.x);}else if ( pos.x > maxX ){F2 = (maxX - pos.x);}//F3// suck planetif (fabsf(xvel) < 100 && !s_selectedIcon) {CCNode *nearestIcon = nil;for (CCNode *icon in s_icons) {if (nearestIcon) {CGPoint pt1 = [icon.parent convertToWorldSpace:icon.position];float distance1 = fabsf(pt1.x - [CCDirector sharedDirector].winSize.width/2);CGPoint pt2 = [nearestIcon.parent convertToWorldSpace:nearestIcon.position];float distance2 = fabsf(pt2.x - [CCDirector sharedDirector].winSize.width/2);if (distance1 < distance2) {nearestIcon = icon;}}else {nearestIcon = icon;}}if (nearestIcon) {s_selectedIcon = nearestIcon;if ([s_delegate respondsToSelector:@selector(onSelectedIconChanged:)]) {[s_delegate onSelectedIconChanged:self];}}}if (s_selectedIcon) {CGPoint pt = [s_selectedIcon.parent convertToWorldSpace:s_selectedIcon.position];;float distance = pt.x - [CCDirector sharedDirector].winSize.width/2;F3 = - distance;}//CALCULATEf = F1 + F2 + F3;acceleration = f/1;xvel += acceleration;pos.x += xvel*dt;s_content.position = pos;}else{xvel = ( s_content.position.x - lastx ) / dt;lastx = s_content.position.x;}
}

在onEnter方法中,我们已经启用了计时器,所以udpate方法将会在每个最小时间间隔被调用

其他就如同刚才整理的那样,没什么问题,主要使这个受力问题,这个受力是我经过了好多数值的尝试后,得出的比较能符合要求的效果

内容受到的力分为

F1阻力:方向与内容移动速度方向相反,大小与移动速度快慢呈正比

F1 = - xvel * 0.1;

F2超出边界的额外受力:方向与超出边界的方向相反,大小与超出边界的距离呈正比

F2 = (minX - pos.x);或者F2 = (maxX - pos.x);

F3将选定节点吸至屏幕中央的吸力:方向从选定节点指向屏幕中央,大小与选定节点到屏幕中央的距离呈正比:

F3 = - distance;

此外有个细节,如果我们不断的施加吸力,会出现一种情况:很难将选定的节点拖拽出去,因为吸力太大了,所以在代码中添加了一个条件

fabsf(xvel) < 100,当移动速度小于100时,才产生吸力,这样你会发现拖拽顺畅多了,并且也能够在选定了节点后短时间内变为静止

还有什么?

最后在添加一个随着移动而变化节点大小的效果,让拖拽看起来更加舒服

在原有代码内添加以下内容:

-(void) layout
{int i = 1;for (CCNode *icon in s_icons) {CGPoint position = ccp((i-1) * 180, 0);float distance = fabsf(icon.position.x)/100;float scale = 1/(1+distance);icon.position = position;icon.scale = scale;//初始化缩放比例if (![s_content.children containsObject:icon]) {[s_content addChild:icon];}i++;}s_selectedIcon = [s_icons lastObject];if ([s_delegate respondsToSelector:@selector(onSelectedIconChanged:)]) {[s_delegate onSelectedIconChanged:self];}minX = - (i-1) * 180 - 100;maxX = 100;
}-(void) update:(ccTime)dt;
{[self updateMove:dt];
    [self updateScale:dt];//更新缩放比例}-(void) updateScale:(ccTime)dt;
{for (CCNode *icon in s_icons) {CGPoint pt = [self convertToNodeSpace:[icon.parent convertToWorldSpace:icon.position]];float distance = fabsf(pt.x)/100;icon.scale = 1/(1+distance);}
}

测试

好了,代码完成了,接下来测试一下效果

把HelloWorldLayer的初始化方法替换为以下代码:

        // create and initialize a LabelCCLabelTTF *label = [CCLabelTTF labelWithString:@"Sawyer's Test" fontName:@"Marker Felt" fontSize:64];// ask director for the window sizeCGSize size = [[CCDirector sharedDirector] winSize];// position the label on the center of the screenlabel.position =  ccp( size.width /2 , size.height/2 );// add the label as a child to this Layer
        [self addChild: label];// add the test selector to the layerNSMutableArray *icons = [NSMutableArray array];int i = 10;while (i) {[icons addObject:[CCSprite spriteWithFile:@"Icon@2x.png"]];i--;}SZSimplePhysicsDragSelector *selector = [[[SZSimplePhysicsDragSelector alloc] initWithIcons:icons] autorelease];selector.position = self.anchorPointInPoints;        selector.Delegate = self;[self addChild:selector];

运行ios模拟器,你应该看到以下效果:

还算满意,希望大家能够用到各位的游戏中

测试代码

测试代码可以在以下链接下载

SimplePhysicsDragSelectorTest.zip

转载于:https://www.cnblogs.com/sawyerzhu/archive/2012/08/13/2636275.html

在Cocos2d中实现能够惯性拖动的选择界面相关推荐

  1. cocos2d中CCAnimation的使用(cocos2d 1.0以上版本)

    原文地址:cocos2d中CCAnimation的使用(cocos2d 1.0以上版本)作者:七贤林子 在cocos2d  0.9及以下版本中,CCAnimation中可以使用animationWit ...

  2. cocos2d 中判断CGPoint或者CGSize是否相等

    cocos2d 中判断CGPoint是否相等 调用CGPointEqualToPoint(point1, point2) 判断CGSize是否相等 调用CGSizeEqualToSize(size1, ...

  3. Cocos2d中使用颜色混合:加算,减算

    Cocos2d中使用颜色混合:加算,减算 转自http://blog.sina.com.cn/s/blog_7a2ffd5c0100xtid.html CCSprite有一个ccBlendFunc类型 ...

  4. 疯狂ios之cocos2d中的声音

    13.13 cocos2d中的声音 任何一个游戏中都不能缺少音乐和音效,苹果公司在iOS系统中提供了两个框架用于播放音乐,分别是AVAudioPlayer和OpenAL.使用AVAudioPlayer ...

  5. android 浮动文字提示,怎么在Android中实现一个自由拖动并显示文字的悬浮框

    怎么在Android中实现一个自由拖动并显示文字的悬浮框 发布时间:2021-01-27 15:34:05 来源:亿速云 阅读:107 作者:Leah 今天就跟大家聊聊有关怎么在Android中实现一 ...

  6. 疯狂ios之cocos2d中的文本

    在游戏当中经常需要添加标签和文本对此cocos2d提供了强大的文本渲染功能.cocos2d支持所有内置的iOS字体以及一些TrueType字体. 在cocos2d中文本渲染功能通常由两个类实现CCLa ...

  7. Cocos2D中图片加-hd后缀的说明

    你可能注意到实际上游戏中的sprite都有2张图片,它都对应该精灵,并包含在资源包中(resource pack): player.png(27x40 pixels)和player-hd.png(do ...

  8. cocos2d中,设置层的可视区域

    http://www.cocoachina.com/bbs/read.php?tid=97164 cocos2d中,设置层的可视区域在真机上不管用 -(void) visit{     glEnabl ...

  9. cocos2d中的Color3B、Color4B、Color4F的使用

    首先给出cocos2d标准库中的三个类的构造函数和定义,他们也是可以互相转换的    cocos2d中表示颜色有三种对象: Color3B 用三个 0-255 的整数描述颜色. Color4B 用四个 ...

最新文章

  1. HTML5 基础知识(二)
  2. Unity3D 游戏引擎之IOS高级界面发送消息与Unity3D消息的接收(九)
  3. a标签点击后变色_中国科学家研发的不退色的变色环保图料登上《科学》子刊...
  4. 10+知识图谱开放下载,让你的学习效率提升5倍! | “右脑”开发套餐
  5. Springboot整合mybatis框架(含实例Demo)
  6. 机器学习基础(二十三)—— 概念、定义
  7. 6个常见校园网认证客户端故障原因及解决方法
  8. 更改stata外部命令存放位置
  9. elcom协议_物联网@电力系统通信协议
  10. 如何注册一个免费的iTunes帐号(Apple ID)
  11. wpsmac和pc版的区别_WPS Mac版本与Microsoft有什么区别?
  12. 卡西欧计算机怎么进制转换,卡西欧计算机怎么把十进制转换二进制
  13. java 里面 todo 作用
  14. postgresql中DROP OWNED BY user_name对普通用户和只读用户的区别
  15. 可视化均衡插件-Acon Digital Equalize 2 v2.1.1 WiN-MAC
  16. android svg 线条动画教程,简单的SVG线条动画
  17. Android P (9.0)刘海屏(DisplayCutout)适配方法
  18. matlab提取数据的一部分,matlab处理excel数据【怎么用MATLAB从excel中提取部分数据】...
  19. PS制作各种证件照及换背景色
  20. 等待面试结果焦虑_在技​​术面试中进行实时编码时,您如何应对焦虑?

热门文章

  1. 硬件知识:电脑组装机必备的知识梳理
  2. Git 实用技巧记录,看这篇你就明白了!
  3. 数据迁移,不停机上线的正确姿势
  4. sql语句提高数据库查询效率
  5. 火炬之光2找不到服务器,火炬之光2无法运行解决办法详细介绍
  6. php 空函数,PHP 中函数 isset(), empty(), is_null() 的区别
  7. docker php 安装swoole,swoole(1)使用docker安装swoole环境
  8. 华为手机下拉菜单没了_用了三年才知道华为录屏这么强大!再不会用,手机钱打水漂了...
  9. 实现linux cp 命令和修改配置文件
  10. java流读取xml_使用FileInputStream(用于Java)读取XML文件?