美文网首页
iOS开发之QQ侧滑导航栏的实现

iOS开发之QQ侧滑导航栏的实现

作者: 祥子_HelloWorld | 来源:发表于2019-08-02 09:42 被阅读0次

对于手机QQ的主界面的UI已经变动过好多次,这次讲的是被很多人模仿的效果。

一、效果展示:
二、实现思路

如上图所示,主要思路如下:

  1. SliderNavigation拥有三个子视图:leftView,rightView,mainView。左右滑动时就通过这三个视图之间层次关系的切换来实现。

  2. 其实只有上述三个视图完全够了,但是又另外加上了三个属性:leftVC,rightVC,mainVC。这样做的目的是简化操作,同时mainVC还有记录已展示过的视图的任务,这样所有视图都可以通过左右滑动唤出导航栏来了。这样每个子视图上展示的是对应控制器的视图,即[leftView addSubview:leftVC.view];,其他类似。

  3. 当向左滑动时,调整视图层级关系,因为向左滑动是展示右视图,所以将leftView调整到最底层,同时让mainView随手指移动,这样mainView之下的rightView就展示出来了。

  4. 有了上述三点,接下来就可以通过给各个环节添加动画来实现好看的效果了。

三、接口定义

.h文件中定义好外界可以自定义的一些属性。
首先是三个控制器

//左右控制器与主控制器  
@property (strong, nonatomic) UIViewController *leftController;  
@property (strong, nonatomic) UIViewController *rightController;  
@property (strong, nonatomic) UIViewController *mainController;  

其次是左右视图的一些相关设定,有判断点、便宜量、动画时间、能否被拉出等

//左右视图被拉出以后主视图的X方向的offset(正值)  
@property (assign, nonatomic) CGFloat leftOffsetX;  
@property (assign, nonatomic) CGFloat rightOffsetX;  
  
//左右视图被拉的过程中的判断点的X值(正值)  
@property (assign, nonatomic) CGFloat leftJudgeX;  
@property (assign, nonatomic) CGFloat rightJudegX;  
  
//左右视图拉出所用的时间  
@property (assign, nonatomic) NSTimeInterval leftOpenDuration;  
@property (assign, nonatomic) NSTimeInterval rightOpenDuration;  
  
//左右视图收回时所用的时间  
@property (assign, nonatomic) NSTimeInterval leftCloseDuration;  
@property (assign, nonatomic) NSTimeInterval rightCloseDuration;  
  
//左右视图被拉出以后主视图放缩的比例(0到1)  
@property (assign, nonatomic) CGFloat rightScale;  
@property (assign, nonatomic) CGFloat leftScale;  
  
//左右视图能否被拉出  
@property (assign, nonatomic) BOOL canShowRight;  
@property (assign, nonatomic) BOOL canShowLeft;  

刚才也说过,mainVC要记下已经展示过的主视图,可以将这些加入到字典中,这样做的作用是下次可以方便的展示出来。另外,让每一个想展示的视图对应的控制器赋值给mainVC可以实现在所有界面中都能通过左右拉来叫出导航栏的功能。什么意思呢?最根部的依旧是我们封装的sliderNavigation类,其上图的层次依旧存在,只是改变了mainVC的值,这样给用户的体验就是,虽然主界面变了,但依然可以拉出左右导航栏来。

为此我们设置一个字典来保存已经展示过的控制器

//用以记录被当做主控制器展示主视图过的控制器  
@property (strong, nonatomic) NSMutableDictionary *controllersDict;  

接下来是几个public方法声明,将这种Manager性质的类作为单例,暴露出其展示左右视图的功能供按钮控制,然后是可以让其展示自定义类作为主界面。

//单例  
+ (id)sharedInstance;  
  
//展示左右视图  
- (void)showLeftView;  
- (void)showRightView;  
  
//展示自定义类的主视图,参数:自定义类名  
- (void)showContentViewWithModel:(NSString *)className;  
四、具体实现

首先定义一些常量

//制造反弹的动态效果,当通过按钮叫出导航栏时有效  
static const CGFloat kOpenSpringDamping = 0.65f;  
static const CGFloat kOpenSpringVelocity = 0.10f;  
  
//定义常量表示拉动方向  
typedef NS_ENUM(NSUInteger, sliderMoveDirection) {  
    SliderMoveDirectionLeft = 0,  
    SliderMoveDirectionRight,  
};  

然后重点这里讲一下关键代码或方法,其余的讲一下思路

我们可以在初始化方法中将接口中声明的变量赋默认值,当用户没有为这些值赋值时便可以用这些默认值

首先我们初始化三个子视图为屏幕大小并根据添加到sliderNavigation的子视图中,注意添加顺序:我们希望让主视图在最上方,所以前两个随意,主视图必须最后添加。

- (void)_initSubviews  
{  
    _rightView = [[UIView alloc] initWithFrame:self.view.bounds];  
    [self.view insertSubview:_rightView atIndex:0];  
      
    _leftView = [[UIView alloc] initWithFrame:self.view.bounds];  
    [self.view insertSubview:_leftView atIndex:1];  
      
    //主视图要最后添加(即添加到最上面显示)  
    _mainView = [[UIView alloc] initWithFrame:self.view.bounds];  
    [self.view insertSubview:_mainView aboveSubview:_leftView];  
}  

然后我们初始化左右控制器,将左右控制器视图分别添加到左右视图中去。
在实现上述public方法“展示自定义类的主视图”时,传入参数为类名,将其作为键来从字典中取控制器,如果没有则以此类名新建一个控制器并加入到字典中。如果当前主视图上已经有视图,则将其移除。接着将自定义类的视图添加到mainView上,并相应赋值。

当然,不要忘了关闭左右导航栏(因为展示的类有可能是通过左右导航栏点出来的)

- (void)showContentViewWithModel:(NSString *)className  
{  
    [self _closeSliderNavigation];  
      
    UIViewController *controller = [self.controllersDict objectForKey:className];  
    if (controller == nil) {  
        Class c = NSClassFromString(className);  
        controller = [[c alloc] init];  
        [self.controllersDict setObject:controller forKey:className];  
    }  
      
    //如果当前已经有视图被显示,则将其取消  
    if (_mainView.subviews.count > 0) {  
        [[_mainView.subviews firstObject] removeFromSuperview];  
    }  
      
    controller.view.frame = _mainView.frame;  
    [_mainView addSubview:controller.view];  
      
    self.mainController = controller;  
}  

接着是动画,这里用到的动画主要就是改变视图的大小和位置,用transform即可。获得transform的方法单独抽出来,使用concat将大小变换矩阵和位置变换矩阵连接。接着在动画块中改变主视图的transform即可,当然了,也可以设置上阴影效果等。需要注意的是要根据滑动方向将相应视图调整到最底层。

CGAffineTransform concat = [self _transformWithMoveDirection:SliderMoveDirectionLeft];  
  
[self.view sendSubviewToBack:_leftView];<span style="white-space:pre">                </span>   //将另一个视图调到最下面  
[self _configureViewShadowWithDirection:SliderMoveDirectionLeft];      //设置阴影  
  
[UIView animateWithDuration:self.rightOpenDuration  
                      delay:0  
     usingSpringWithDamping:kOpenSpringDamping<span style="white-space:pre">          </span>   //弹性效果  
      initialSpringVelocity:kOpenSpringVelocity  
                    options:UIViewAnimationOptionCurveLinear  
                 animations:^{  
                     _mainView.transform = concat;  
                 }  
                 completion:^(BOOL finished) {  
                     _showingLeft = NO;  
                     _showingRight = YES;  
                     self.mainController.view.userInteractionEnabled = NO;  
                     _tapGesture.enabled = YES;  
                 }];  

另一方向的雷同

最主要的还是滑动手势操作,也是比较麻烦的地方。不过其实思路比较清晰:获取偏移量,在滑动时计算出对应的变换矩阵并设置,在滑动结束时根据位置与判断点的关系做出相应的动画调整。

例如,滑动过程中向右拉时:

CGFloat translateX = [recognizer translationInView:_mainView].x;  
translateX += currentOffsetX;  
  
float scale = 0;  
//向右拉,展示的是左视图  
if (translateX > 0) {  
    if (self.canShowLeft == NO || self.leftController == nil) {  
        return;  
    }  
      
    //将右视图放到底部以将左视图显示出来  
    [self.view sendSubviewToBack:_rightView];  
    [self _configureViewShadowWithDirection:SliderMoveDirectionRight];  
      
    if (_mainView.frame.origin.x < self.leftOffsetX) {  
        scale = 1 - (_mainView.frame.origin.x / self.leftOffsetX) * (1 - self.leftScale);  
    } else {  
        scale = self.leftScale;  
    }  
      
} else if (translateX < 0) {……}  

比较头痛的十scale的计算。这里的要求是当view从最初到最末时scale的变化为1.0到self.leftScale,因此利用数学知识推出这个公式即可。上述代码省略了向左拉的代码。
而在拉动结束状态则与左拉右拉动画实现类似。

CGFloat translateX = [recognizer translationInView:_mainView].x;  
translateX += currentOffsetX;  
  
if (translateX > self.leftJudgeX) {  
    if (self.canShowLeft == NO || self.leftController == nil) {  
        return;  
    }  
      
    CGAffineTransform trans = [self _transformWithMoveDirection:SliderMoveDirectionRight];  
    [UIView beginAnimations:nil context:nil];  
    _mainView.transform = trans;  
    [UIView commitAnimations];  
      
    _showingLeft = YES;  
    _showingRight = NO;  
    self.mainController.view.userInteractionEnabled = NO;  
    _tapGesture.enabled = YES;  
      
} else if (translateX < -self.rightJudgeX) {……}  
五、源码

附上源码:ios侧边导航栏
参考代码:参考代码

来源:http://blog.csdn.net/u013604612/article/details/39434775

相关文章

  • iOS开发之QQ侧滑导航栏的实现

    对于手机QQ的主界面的UI已经变动过好多次,这次讲的是被很多人模仿的效果。 一、效果展示: 二、实现思路 如上图所...

  • iOS 解决自定义导航栏不能侧滑返回

    iOS中,导航栏默认是可以侧滑,但是自定义导航之后,侧滑返回失效。 解决办法:在自定义导航栏中增加侧滑手势判断,代...

  • iOS导航栏侧滑失效问题

    iOS导航栏侧滑失效问题 关于iOS的导航栏, 想必各个iOS开发者都是经常要面对的问题.也是必须熟练掌握的一个技...

  • iOS导航栏--侧滑失效问题

    iOS导航栏侧滑失效问题 关于iOS的导航栏, 想必各个iOS开发者都是经常要面对的问题.也是必须熟练掌握的一个技...

  • ios导航栏-侧滑

    参考:iOS导航栏使用总结 一、实现侧滑 直接上代码即可 @interface BaseViewControlle...

  • 导航控制器切换效果

    自定义侧滑效果,侧滑返回时界面整体滑动。 相关文章: 【iOS】让我们一次性解决导航栏的所有问题 如何实现类似网易...

  • 扩展FDFullScreenPopGesture支持限制侧滑区域

    前言 开发中FDFullscreenPopGesture对开发帮助甚大,很方便解决了导航栏的隐藏显示及全屏侧滑的功...

  • 20170321 NavigationBar  Layout

     iOS 自定义导航栏笔记  IOS开发之Autolayout——“Content Compression...

  • QQ音乐侧滑栏的实现

    对于现有的实现侧滑栏的方式,主要有两种,一种是将侧滑栏作为视图控制器,用视图控制器控制侧滑栏,这个网上有很多写好的...

  • Flutter 侧滑栏及城市选择UI的实现

    Flutter 侧滑栏及城市选择UI的实现 前言   目前移动市场上很多业务都需要开发Android/IOS两个端...

网友评论

      本文标题:iOS开发之QQ侧滑导航栏的实现

      本文链接:https://www.haomeiwen.com/subject/vqjcdctx.html