左右侧栏已经是当前APP最流行的布局,很多客户端软件都使用了左右侧栏,例如网易新闻,人人网,Weico等等。
这篇博客以当前网易新闻客户端的模式为例仿写了一个左右侧栏架构实现。
先看一下Demo的实现效果
实现主要思路以及细节:
视图控制器有三个视图按不同层次排列,最上层的是主要显示视图_mainContentView,下面的为左右侧栏视图;
点击左侧栏不同按钮压入不同的主视图控制器;
在显示侧栏时点击视图空白区域闭合,利用tap手势;
拖动主页面根据不同的方向和位置进行移动和缩放, 利用pan手势;
向右拖显示左侧栏,向左拖显示右侧栏;
首先,点击左侧栏时,左侧栏将点击的数据模型传给分栏控制器,让其更改主视图内容
模型:
@interface ClassModel : NSObject
@property (strong, nonatomic) NSString *title;
@property (strong, nonatomic) NSString *className;
@property (strong, nonatomic) NSString *contentText;
@property (strong, nonatomic) NSString *imageName;
+ (id)classModelWithTitle:(NSString *)title className:(NSString *)className contentText:(NSString *)text andImageName:(NSString *)imageName;
@end
一个工厂方法,模型存入不同选择下的不同视图控制器的具体内容。
以新闻页为例:
ClassModel *newscm = [ClassModel classModelWithTitle:@"新闻" className:@"NewsViewController" contentText:@"新闻视图内容" andImageName:@"sidebar_nav_news"];
来看主视图切换不同界面的方法:
- (void)showContentControllerWithModel:(ClassModel *)model
{
[self closeSideBar];
UIViewController *controller = _controllersDict[model.className];
if (!controller)
{
Class c = NSClassFromString(model.className);
HRViewController *vc = [[c alloc] init];
controller = [[UINavigationController alloc] initWithRootViewController:vc];
vc.contentText = model.contentText;
[_controllersDict setObject:controller forKey:model.className];
}
if (_mainContentView.subviews.count > 0)
{
UIView *view = [_mainContentView.subviews firstObject];
[view removeFromSuperview];
}
controller.view.frame = _mainContentView.frame;
[_mainContentView addSubview:controller.view];
}
中间省略了配置导航栏的内容。
这个方法中又一个[_mainContentView.subviews firstObject]方法,这个方法是IOS7才有的,按以前习惯写[0]也好。
实现视图移动动画主要是通过仿射变换来实现的,也有的例子通过操作frame,看习惯了。
一个根据方向返回仿射变换值的私有方法
- (CGAffineTransform)transformWithDirection:(RMoveDirection)direction
{
CGFloat translateX = 0;
switch (direction) {
case RMoveDirectionLeft:
translateX = -RContentOffset;
break;
case RMoveDirectionRight:
translateX = RContentOffset;
break;
default:
break;
}
MyLog(@"%.2f",translateX);
CGAffineTransform transT = CGAffineTransformMakeTranslation(translateX, 0);
CGAffineTransform scaleT = CGAffineTransformMakeScale(1.0, RContentScale);
CGAffineTransform conT = CGAffineTransformConcat(transT, scaleT);
return conT;
}
一个根据方向配置页面的阴影效果的私有方法
- (void)configureViewShadowWithDirection:(RMoveDirection)direction
{
CGFloat shadowW;
switch (direction)
{
case RMoveDirectionLeft:
shadowW = 2.0f;
break;
case RMoveDirectionRight:
shadowW = -2.0f;
break;
default:
break;
}
_mainContentView.layer.shadowOffset = CGSizeMake(shadowW, 1.0);
_mainContentView.layer.shadowColor = [UIColor blackColor].CGColor;
_mainContentView.layer.shadowOpacity = 0.8f;
}
点击导航栏左侧按钮的展开侧栏方法(右侧类似)
- (void)leftItemClick
{
CGAffineTransform conT = [self transformWithDirection:RMoveDirectionRight];
[self.view sendSubviewToBack:_rightSideView];
[self configureViewShadowWithDirection:RMoveDirectionRight];
[UIView animateWithDuration:ROpenDuration
animations:^{
_mainContentView.transform = conT;
}
completion:^(BOOL finished) {
_tapGestureRec.enabled = YES;
}];
}
关闭侧栏返回主页面的方法
- (void)closeSideBar
{
CGAffineTransform oriT = CGAffineTransformIdentity;
[UIView animateWithDuration:RCloseDuration
animations:^{
_mainContentView.transform = oriT;
}
completion:^(BOOL finished) {
_tapGestureRec.enabled = NO;
}];
}
最后是最重要的,手势拖动的实现方法
- (void)moveViewWithGesture:(UIPanGestureRecognizer *)panGes
{
static CGFloat currentTranslateX;
if (panGes.state == UIGestureRecognizerStateBegan)
{
currentTranslateX = _mainContentView.transform.tx;
}
if (panGes.state == UIGestureRecognizerStateChanged)
{
CGFloat transX = [panGes translationInView:_mainContentView].x;
transX = transX + currentTranslateX;
CGFloat sca;
if (transX > 0)
{
[self.view sendSubviewToBack:_rightSideView];
[self configureViewShadowWithDirection:RMoveDirectionRight];
if (_mainContentView.frame.origin.x < RContentOffset)
{
sca = 1 - (_mainContentView.frame.origin.x/RContentOffset) * (1-RContentScale);
}
else
{
sca = RContentScale;
}
}
else //transX < 0
{
[self.view sendSubviewToBack:_leftSideView];
[self configureViewShadowWithDirection:RMoveDirectionLeft];
if (_mainContentView.frame.origin.x > -RContentOffset)
{
sca = 1 - (-_mainContentView.frame.origin.x/RContentOffset) * (1-RContentScale);
}
else
{
sca = RContentScale;
}
}
CGAffineTransform transS = CGAffineTransformMakeScale(1.0, sca);
CGAffineTransform transT = CGAffineTransformMakeTranslation(transX, 0);
CGAffineTransform conT = CGAffineTransformConcat(transT, transS);
_mainContentView.transform = conT;
}
else if (panGes.state == UIGestureRecognizerStateEnded)
{
CGFloat panX = [panGes translationInView:_mainContentView].x;
CGFloat finalX = currentTranslateX + panX;
if (finalX > RJudgeOffset)
{
CGAffineTransform conT = [self transformWithDirection:RMoveDirectionRight];
[UIView beginAnimations:nil context:nil];
_mainContentView.transform = conT;
[UIView commitAnimations];
_tapGestureRec.enabled = YES;
return;
}
if (finalX < -RJudgeOffset)
{
CGAffineTransform conT = [self transformWithDirection:RMoveDirectionLeft];
[UIView beginAnimations:nil context:nil];
_mainContentView.transform = conT;
[UIView commitAnimations];
_tapGestureRec.enabled = YES;
return;
}
else
{
CGAffineTransform oriT = CGAffineTransformIdentity;
[UIView beginAnimations:nil context:nil];
_mainContentView.transform = oriT;
[UIView commitAnimations];
_tapGestureRec.enabled = NO;
}
}
}
根据不同的状态设置不同状态下的仿射变换值,来赋给_mainContentView,在到达临界状态后,该视图的缩放不再进行。
当拖动手势结束后,根据拖动的位置,来确定视图如何进行移动。
这个例子主要的代码都贴在了上面,这里的素材是随便取的。现在的网易客户端的侧栏背景为深色视图,左侧栏栏目跟这个demo差不多。
Demo源码:点击打开链接
以上就是本篇博客全部内容,欢迎指正和交流。转载注明出处~~