仿网易侧滑Demo-简单易懂好上手
自我介绍下:大肠腰子汤(一个很骚气的id)。我的第一篇帖子。呵呵呵呵呵呵呵呵呵呵呵呵。
本人是个菜鸟,大家轻喷。否则等我厉害了。。。呵呵,别让我找到你的帖子。
此贴也就本菜鸟以一个菜鸟的思路,写出的一个菜鸟的解决方案,适合比我更菜的菜鸟,或者跟我一样的臭屌丝。
最近,大大们看到网易新闻,就心动了,也让我们做一个。。。。
做就做吧。。。希望做完了要给我涨工资。我好买皮肤。。。。
好了,闲话少说。抱着对知识的渴望,对党对人民的热爱,下面我们进入正题。
//////////////////////////////////////////////////////////割了大大的小小//////////////////////////////////////////////////////////////////
- shift + command + n,选择single View application,让我们一起嗨嗨皮皮的把新的项目创建起来!
- 啥都不用说,进去故事板,直接拽一个tabbarcontroller 进来
就是这样,让我们做一个狂拽酷炫霸的码农。 - 下面说下解决思路。本屌丝以为,有两种解决方案:
1,以tabbarcontroller作为rootviewcontroller,然后再tabbarvcontroller.view上加入侧滑手势。当往右滑动时,瞬间截图。然后加载menuView和该截图,覆盖掉原来
的tabbarcontroller,随着手势移动,移动该截图,显示出menView。当选中menuView中的某个选项后,再通过类似的方法,运用截图,恢复。
2,以tabbarcontroller作为rootviewcontroller,然后再tabbarvcontroller.view上加入手势,判断侧滑。当往右滑动时,就把tabbarcontroller.view往右移动,往左滑动则相反。
经过深思熟虑,我选择了方案2。
-----小明:"这是为什么呢??"
-----大肠腰子汤:“因为你是沙比。”
呵呵。言归正传,理由如下:
1,想象,如果通过截图来操作,会非常麻烦。若你在滑动开始时,进行截图,则截图会消耗部分时间(据我估计大概有0.3s以上,我是iphone4做的 测试),如果用户滑动的快些,那这0.3s中并 不会出现滑动效果,并且在0.3s结束后,图片会突然跳到我手指滑动到的位置。试想下如果我在0.3s 内从坐标(0,0)滑动到(160,240),那会是多么差的用户体验。当然你可以说本屌丝 的手机烂,但你不能否认iphone4还是存在着很大一批 用户的。
2,好吧,我们不在滑动的时候截图,而是预先截图,将图片保存起来。呵呵呵,我也天真的这么想。然后呢。。。然后我发现,我要不停的不停的截图。一旦界面发生变化,我就要截图。。。
3,这是最牛x的理由。请我们打开网易新闻客户端,然后我们快速的从下往上滑动手指,让tableview绚丽地转起来。紧接着,我们滑动navgationbar,让menuView显示出来。然后你就看到 &*^的,tableview还在转,让我们一起为此而呵呵。
好了,说了三条理由否认了解决方案1,那么我们开始实现解决方案2。如果亲,你有别的解决方案,请您本着对知识的渴望,对党对人民的热爱,大方地通知本屌丝以下。
- 思路出来了,下面就是码农的活了。
首先我们定制tabbarcontroller:创建类SideMenuTabBarController ,继承:UITabBarController,实现UIGestureRecognizerDelegate协议。
#import <UIKit/UIKit.h> @interface SideMenuTabBarController : UITabBarController <UIGestureRecognizerDelegate> @end
千万不要忘了,进入storyboard,把tabbarcontroller的类改成咱们自定义的类。
“是时候展现真正的技术了”。。。
既然用了这么炸天的侧滑,我们当然不能把tabbar展示出来,不然还有什么意义。所以我们先隐藏tabbar:- (id) initWithCoder:(NSCoder *)aDecoder { self = [super initWithCoder:aDecoder]; if(self) { self.tabBar.hidden = YES; } return self; }
爽歪歪。command+run ,我看到了我雪白的模拟器。
- 下面在此进入sotryboard。还记得网易新闻能往左往右两侧滑动进入不同的menuView么。那我们也用两个view,同时创建leftviewcontroller和rightviewcontroller分别去管理他们。
注意了,请分别给两个view设置storyboardID。这个的作用一会就能知道。 - 让我们在SideMenuTabBarController里面添加两个变量:
@property (nonatomic, strong) LeftViewController *leftVC;
@property (nonatomic, strong) RightViewController *rightVC;
这两个view其实就是侧滑后两侧的menuview。
随后,在SideMenuTabBarController的viewDidLoad方法中,初始化这两个变量。
- (void)viewDidLoad { [super viewDidLoad]; //初始化两侧的view UIStoryboard *storyBoard=[UIStoryboard storyboardWithName:@"Main" bundle:nil]; _leftVC = [storyBoard instantiateViewControllerWithIdentifier:@"LEFT_SIDE_VC"]; _rightVC = [storyBoard instantiateViewControllerWithIdentifier:@"RIGHT_SIDE_VC"]; _leftVC.view.tag = SIDE_VIEW_TAG; _rightVC.view.tag = SIDE_VIEW_TAG;
//给tabbbarController添加手势
UIPanGestureRecognizer *panGesture = [[UIPanGestureRecognizer alloc] init];
[panGesture addTarget:self action:@selector(handleGesture:)];
panGesture.delegate = self;
[self.view addGestureRecognizer:panGesture];
[self setSelectedIndex:0];
}额,忘了说了,在这个方法里,我们同时把手势加上去吧。
-------------细心的小明:“我看到了,handleGesture方法”。
-------------大肠腰子汤:“戴比”。。。。。。。 - 由于该类要多次获取window对象,我毫不犹豫的写了个方法:
//获取window对象 - (UIWindow *) getWindow { return [[[UIApplication sharedApplication] delegate] window]; }
好了,铺垫的差不多了。下面来点干货了。
//手势回掉方法 - (void) handleGesture:(UIPanGestureRecognizer *)panGusture { UIWindow *window = [self getWindow]; CGPoint translationInView = [panGusture translationInView:window]; switch (panGusture.state) { case UIGestureRecognizerStateBegan://手势开始 { originalX = self.view.frame.origin.x; } break; case UIGestureRecognizerStateChanged://手势进行中 { /*
*由于手指的移动最多为320像素
*因此以320作为分母
*由于translationInView.x获得手势在x轴的位移量
*因此加上originalX 可获得此时手指相对window的x坐标
*因而分子为translationInView.x + originalX
*系数为320 - 50 (50为移动结束后 剩余显示的值)
*/ float percentage = (translationInView.x + originalX)/320; CGRect frame = self.view.frame; frame.origin.x = percentage * (320 - 50); if(frame.origin.x > 0) [self setSideViewController:Left]; else [self setSideViewController:Right]; [self.view setFrame:frame]; } break; case UIGestureRecognizerStateEnded://手势结束 { float xPosition = 0; float x = self.view.frame.origin.x; /* *0,< | -270/0 *0,> | 270/0 *<,> | 0/-270 *>,< | 0/270 */ if(originalX == 0)//初始阶段 { //SLIDE_DISTANCE作为分界线 if(x < originalX - SLIDE_DISTANCE) xPosition = - 270; else if (x > SLIDE_DISTANCE) xPosition = 270; else xPosition = 0; } else if(originalX < - 250) { if(x < originalX - SLIDE_DISTANCE) xPosition = originalX; else xPosition = 0; } else if (originalX > 250) { if(x < originalX - SLIDE_DISTANCE) xPosition = 0; else xPosition = originalX; } CGRect frame = self.view.frame; frame.origin.x = xPosition; //动画 [UIView animateWithDuration:0.15 animations:^{ [self.view setFrame:frame]; }]; } break; default: break; } }好长的一段代码!!!让我们以欣赏的角度,来分析下这段代码的逻辑。--(轻喷please)
首先我们获取手势在window中的的坐标,保存在translationInView中。
然后我们伴随siwtch语句,一步步的解析:
1)UIGestureRecognizerStateBegan
手势开始时,很简单我们把self.view.origin.x保存在一个类变量中:originalX。该变量在interface中声明,此处不再赘述。
2)UIGestureRecognizerStateChanged
手势移动时:注释是这样写的:
/*
*由于手指的移动最多为320像素
*因此以320作为分母
*由于translationInView.x获得手势在x轴的位移量
*因此加上originalX 可获得此时手指相对window的x坐标
*因而分子为translationInView.x + originalX
*系数为320 - 50 (50为移动结束后 剩余显示的值)
*/
很简单。我最多允许view移动270个像素,保留50像素宽度在屏幕上。因此要把手指的移动位置和屏幕宽度相除得到一个比例,用该比例获得tabbarcontroller.view的移动位置。 比例的算法为:(translationInView.x + originalX)/ 320。对于originalX,只可能为三个值:0,270,-270。
3)UIGestureRecognizerStateEnded
手势结束时:
先定义两个变量 x和xPosition。x描述此刻view相对于window的x坐标,xPosition用于记录view的目的x坐标。
同样有一段注释:/* *0,< | -270/0 *0,> | 270/0 *<,> | 0/-270 *>,< | 0/270 */
解释下该段注释:
以第一行为例:
(0,< | -270/0):如果手势开始时候,tabbarcontrolle.view的x轴坐标为0,并且手势的移动方向为左,
那么xPosition的值只可能为-270或者0。当xposition=-270表示往左移动成功,显示出右侧的menuView,
当xposition=0表示往左移动失败,恢复tabbarcontroller.view的x坐标。
以第三行注释为例:
(<,> | 0/-270):如果手势开始时候,tabbarcontrolle.view的x轴坐标为-270,即tabbarcontroller.view往左偏移,只显示右侧部分的内容,
并且手势的移动方向为又,
那么xPosition的值只可能为270或者0。当xposition=0表示往左移动成功,显示出右侧的menuView,
当xposition=-270表示往左移动失败,恢复tabbarcontroller.view的x坐标。
以此类推,能得出7种情况,就是switch语句的逻辑内容。
而我判断每种情况下,手势是否有效的标志是:该手势位移量是否超过 SLIDE_DISTANCE 。例如:x < originalX - SLIDE_DISTANCE
#define SLIDE_DISTANCE 100
- ------------------------小明:“[self setSideViewController:Left];这是个什么方法”
------------------------大肠腰子汤:“” - 好,下面我们来说说这个方法:
因为往左或者往右滑动的时候,显示出的menuview会不同,所以我根据滑动的方向,来设置menuview:
typedef enum { Left, Right }Side_Direction; //设置侧面vc - (void) setSideViewController:(Side_Direction) direction{ //删除原本存在的last_view UIWindow *window = [self getWindow]; UIView *side_view = [window viewWithTag:SIDE_VIEW_TAG]; if(side_view) { if(direction == Left && side_view == _leftVC.view) return; if(direction == Right && side_view == _rightVC.view) return; [side_view removeFromSuperview]; } //添加新的view if(direction == Left) [window insertSubview:_leftVC.view atIndex:0]; else [window insertSubview:_rightVC.view atIndex:0]; }
- 最后,为了能让menuView通知tabbarcontroller,设置selectedIndex,我们给SideMenuTabBarController新增方法:
//显示选中的view
- (void) showControllerAtIndex:(int) index;
//显示选中的view - (void) showControllerAtIndex:(int) index { if(index >= self.viewControllers.count) return; [UIView animateWithDuration:0.2 animations:^{ //消失动画 CGRect disappearFrame = self.view.frame; if(disappearFrame.origin.x < 0) disappearFrame.origin.x = - 350; else disappearFrame.origin.x = 350; self.view.frame = disappearFrame; } completion:^(BOOL finished) { //重新显示 [UIView animateWithDuration:0.3 animations:^{ self.selectedIndex = index; CGRect frame = self.view.frame; frame.origin.x = 0; self.view.frame = frame; }]; }]; }
很简单的推出推进的动画。大功告成。
终于搞定了。测试以下,爽歪歪。
------------------小明:“是不是少了点什么?”。 - 的确少了很多。
比如:我们的手势何时该开启,何时不该开?。-------------大肠腰子汤:“这个视情况而定。比如使用navigationcontroller的时候应该防止和ios7自带的侧滑手势冲突等”。
又比如:网易新闻侧滑后的的view会缩小,这个怎么实现?--------------大肠腰子汤:“据我观测,此功能目前只在ios7上有效。有可能是用了autolayout后,直接按比例缩小abbacontroller.view的size来实现”。
一句话总结:能力有限,暂时只能做成这样 && 轻喷 && 好人一生平安。
最后附上demo:http://download.csdn.net/detail/justalloc/6441779