在Cocos2d中实现能够惯性拖动的选择界面
蘋果的應(yīng)用講究用戶體驗(yàn)
有的時(shí)候仔細(xì)想想
的確,很多細(xì)節(jié)決定了用戶體驗(yàn)
比如說慣性拖動(dòng)
可以說之前沒有任何一家廠商能把觸摸慣性拖動(dòng)做的像蘋果的UI那么流暢
?
Cocos2D中實(shí)現(xiàn)能夠慣性拖動(dòng)的選擇界面
完成的效果:
制作一個(gè)簡單的圖層,通過傳入許多的節(jié)點(diǎn),圖層自動(dòng)將節(jié)點(diǎn)排版,并能夠通過物理拖拽來選擇其中的某一個(gè)節(jié)點(diǎn),并通知節(jié)點(diǎn)的代理來處理
?
首先新建一個(gè)cocos2d項(xiàng)目,我用的版本是2.0,命名為SimplePhysicsDragSelectorTest
新建一個(gè)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;//所有節(jié)點(diǎn)圖標(biāo)的父節(jié)點(diǎn)NSMutableArray *s_icons;//節(jié)點(diǎn)圖標(biāo)清單CCNode *s_selectedIcon;//選定的節(jié)點(diǎn) BOOL isDragging;//是否在拖拽狀態(tài)CGPoint lastTouchPoint;//上一個(gè)觸摸點(diǎn)float lastx;//上一個(gè)圖層內(nèi)容x坐標(biāo)float xvel;//內(nèi)容在x軸上的速度int maxX;//內(nèi)容可以自然移動(dòng)到的最大極限x坐標(biāo)int minX;//內(nèi)容可以自然移動(dòng)到的最小極限x坐標(biāo)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需要使用到的變量和方法,同時(shí)聲明了SZSimplePhysicsDragSelector代理的方法
變量的作用如注釋里描述的,后面將會(huì)詳細(xì)說到
解釋下代理方法:
-(void)onSelectedIconChanged:(SZSimplePhysicsDragSelector*)selector;
?在圖層選擇的節(jié)點(diǎn)發(fā)生改變時(shí)將會(huì)發(fā)送此消息給代理,如果改變?yōu)闆]有選擇節(jié)點(diǎn)也會(huì)發(fā)送此消息
?
初始化
在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];//開啟計(jì)時(shí)器 }-(void) onExit {[self unscheduleUpdate];//關(guān)閉計(jì)時(shí)器 [self removeChild:s_content cleanup:YES];[s_content release];s_content = nil;s_selectedIcon = nil;[super onExit]; }@end?
以上代碼實(shí)現(xiàn)了初始化&內(nèi)存釋放以及onEnter和onExist方法
在選擇器被添加到某一個(gè)節(jié)點(diǎn)中時(shí),將會(huì)自動(dòng)創(chuàng)建一個(gè)內(nèi)容節(jié)點(diǎn)s_content,用來存放所有的節(jié)點(diǎn),并一起移動(dòng)
?
布局節(jié)點(diǎn)
在onEnter方法中布局視圖,并實(shí)現(xiàn)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作為每兩個(gè)節(jié)點(diǎn)之間的間距,同時(shí)第一個(gè)節(jié)點(diǎn)在s_content中的位置應(yīng)該是(0,0)所以計(jì)算得出位置的公式(間距和初始位置可以根據(jù)需要更改)
position = ccp((i-1) * 180, 0)
之后添加節(jié)點(diǎn)到s_content,并且設(shè)置最后一個(gè)為初始選定的節(jié)點(diǎn),最后通知代理選定節(jié)點(diǎn)發(fā)生更改
關(guān)于極限位置(minX,maxX)是這樣設(shè)定的,前面說到180作為間距,(0,0)為初始節(jié)點(diǎn)位置,所以最后一個(gè)節(jié)點(diǎn)的x坐標(biāo)為(i-1) *?180(i為節(jié)點(diǎn)個(gè)數(shù)),當(dāng)需要選擇右邊的節(jié)點(diǎn)時(shí)實(shí)際上是將s_content的位置向左移動(dòng),所以選擇到最后一個(gè)節(jié)點(diǎn)時(shí)s_content的位置應(yīng)該是-(i-1) *?180,同理第一個(gè)選擇到第一個(gè)節(jié)點(diǎn)時(shí)s_content的位置應(yīng)該是(0,0),此外我希望極限位置能夠比頭尾節(jié)點(diǎn)位置的范圍稍大,所以最終我設(shè)定
minX = - (i-1) * 180 - 100;
maxX = 100;
?
觸摸記錄?
布局完成,接下來我們需要實(shí)現(xiàn)觸摸事件消息來記錄數(shù)據(jù)供模擬物理使用
在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個(gè)觸摸事件
- (void)ccTouchesBegan:(NSSet *)touches withEvent:(UIEvent *)event;
在方法中我們清空了選擇的節(jié)點(diǎn)并通知代理選擇的節(jié)點(diǎn)改變,標(biāo)記自身狀態(tài)為拖拽中
- (void)ccTouchesMoved:(NSSet *)touches withEvent:(UIEvent *)event;
在方法中根據(jù)此刻觸摸點(diǎn)與上一次觸摸點(diǎn)的位置差,來移動(dòng)s_content的位置,從而使內(nèi)容跟隨觸摸移動(dòng),最后在記錄下此刻的位置為上一次觸摸位置,供下一次計(jì)算使用
- (void)ccTouchesEnded:(NSSet *)touches withEvent:(UIEvent *)event;
標(biāo)記自身狀態(tài)為未拖拽
- (void)ccTouchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event;
標(biāo)記自身狀態(tài)為未拖拽
這樣我們已經(jīng)能夠辨別自身是否在拖拽狀態(tài)以及正確拖拽內(nèi)容
?
模擬物理計(jì)算
首先說明一下思路:
我們?cè)趗dpate方法中我們需要檢測(cè)圖層的狀態(tài):
若圖層在被拖拽狀態(tài),則不需要模擬物理,只需要計(jì)算出用戶觸摸拖拽內(nèi)容在x軸上的速度
若圖層在未拖拽狀態(tài),則根據(jù)已經(jīng)記錄下的x軸移動(dòng)速度,和通過受力計(jì)算出的加速度,改變x軸移動(dòng)速度,最后在根據(jù)計(jì)算出的移動(dòng)速度來計(jì)算實(shí)際位移
?
在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方法中,我們已經(jīng)啟用了計(jì)時(shí)器,所以u(píng)dpate方法將會(huì)在每個(gè)最小時(shí)間間隔被調(diào)用?
其他就如同剛才整理的那樣,沒什么問題,主要使這個(gè)受力問題,這個(gè)受力是我經(jīng)過了好多數(shù)值的嘗試后,得出的比較能符合要求的效果
內(nèi)容受到的力分為
F1阻力:方向與內(nèi)容移動(dòng)速度方向相反,大小與移動(dòng)速度快慢呈正比
F1 = - xvel * 0.1;
F2超出邊界的額外受力:方向與超出邊界的方向相反,大小與超出邊界的距離呈正比
F2 = (minX - pos.x);或者F2 = (maxX - pos.x);
F3將選定節(jié)點(diǎn)吸至屏幕中央的吸力:方向從選定節(jié)點(diǎn)指向屏幕中央,大小與選定節(jié)點(diǎn)到屏幕中央的距離呈正比:
F3 = - distance;
此外有個(gè)細(xì)節(jié),如果我們不斷的施加吸力,會(huì)出現(xiàn)一種情況:很難將選定的節(jié)點(diǎn)拖拽出去,因?yàn)槲μ罅?#xff0c;所以在代碼中添加了一個(gè)條件
fabsf(xvel) < 100,當(dāng)移動(dòng)速度小于100時(shí),才產(chǎn)生吸力,這樣你會(huì)發(fā)現(xiàn)拖拽順暢多了,并且也能夠在選定了節(jié)點(diǎn)后短時(shí)間內(nèi)變?yōu)殪o止
?
還有什么?
最后在添加一個(gè)隨著移動(dòng)而變化節(jié)點(diǎn)大小的效果,讓拖拽看起來更加舒服
在原有代碼內(nèi)添加以下內(nèi)容:
-(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);} }
?
測(cè)試
好了,代碼完成了,接下來測(cè)試一下效果
把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];
運(yùn)行ios模擬器,你應(yīng)該看到以下效果:
?
還算滿意,希望大家能夠用到各位的游戲中
?
測(cè)試代碼
測(cè)試代碼可以在以下鏈接下載
SimplePhysicsDragSelectorTest.zip
?
轉(zhuǎn)載于:https://www.cnblogs.com/sawyerzhu/archive/2012/08/13/2636275.html
總結(jié)
以上是生活随笔為你收集整理的在Cocos2d中实现能够惯性拖动的选择界面的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: arm开发tq2440上的c++裸奔程序
- 下一篇: IIS相关问题及解决方案