iOS中UIScrollerView的用法及基于AotoLayout的控件悬停
UIScrollView为了显示多于一个屏幕的内容或者超过你能放在内存中的内容。Scroll View为你处理缩小放大手势,UIScrollView实现了这些手势,并且替你处理对于它们的探测和回应。其中需要注意的子类是UITableView以及UITextView(用来显示大量的文字)。还有一个UIWebView,尽管那不是UIScrollView的直接子类,它适用UIScrollView去显示网页内容contentsize是内容的宽和高,contentsize.width是内容的宽度,contentsize.heght是高度,contentsize是UIScrollView的一个属性,它是一个CGSize,是由核心图形所定义的架构,那定义了你可以滚轴内容的宽度和高度,你也可以添加可以上下滚动的额外区域。第一种方法是你可以通过添加内容的大小来完成。另外一个比较动态的选择是UIScrollView的另一个属性contentInset,contentInset增加你在contentsize中指定的内容能够滚动的上下左右区域数量contentInset.top以及contentInset.buttom分别表示上面和下面的距离。
在滚轴视图中,有一个叫做ContentOffset的属性跟踪UIScrollView的具体位置,你能够自己获取和设置它,ContentOffset是你当前可视内容在滚轴视图边界的左上角那个点。如图:
可以看出,ContentOffset内容中的那个点不是从contentInset的左上角开始的,而是内容的左上角,此时的ContentOffset是正值,但有时也是负值,如下图所示:
使用一个ScrollView
创建一个UIScrollView
CGRectframe = CGRectMake( 0, 0, 200, 200);
scrollView= [[UIScrollView alloc] initWithFrame: frame];
添加子视图(框架可以超过scroll view的边界)
frame= CGRectMake( 0, 0, 500, 500);
myImageView= [[UIImageView alloc] initWithFrame: frame];
[scrollViewaddSubview:myImageView];
设置内容尺寸
scrollView.contentSize= CGSize(500,500);
扩展Scroll View 的行为
- 应用程序通常需要知道有关的滚图的事件
- scrolloffset改变的时候
- 拖动开始和结束
- 减速的开始和结束
通过子类化扩展Scroll View 的行为
- 创建一个子类
- 重写一些功能并改变行为
- 关于这种方式的争议
应用程序的逻辑和行为变成了视图本身的一部分,就像,你可能有一些定制的滚轴逻辑,,在那你只在意一个视图控制,但你想在不同地方重复使用你的滚轴视图,如果你必须为每个都子类化,你最后会有很多不同的滚轴视图子类以及在视图中的特定应用逻辑。
编写很多子类是很沉闷的事情,你最后会有很多无法重复使用的单独视图,而MVC的视图部分的一个重点是视图是可以在不同的控制器和不同的模式之中重复使用的,如果我们把所有逻辑都放在视图中,它减少了可复用性。
你的代码变得很牢固地配对在一起,它实际上变成了超类的一部分,你无法从UIScrollView中析取它,之后用其它东西代替,如果它在你控制器中且为控制器的一部分,在之后更容易改变它工作的方式和重新安排你应用程序的一些部分。
通过委派来扩展Scroll View 的行为(常用的)
委派是一个单独的对象,协议,定义了委派会实现的一系列功能的Objective-C协议,它创建了一系列很清晰的撤销点,在那里你能定制行为和外观。它在这些对象之间保持了松散的配对,视图本身与视图控制器或任何其它的控制器对象,委派不是滚轴视图的直接子类,它比起牢固配对的子类更加的松散。
理论篇
在滚动的过程中,实际上就是contentOffset的值在不断变化,当手指触摸后,UIScrollView会暂时拦截触摸事件,使用一个计时器。假如在计时器到点后没有发生手指移动事件,那么UIScrollView发送 tracking events 到被点击的 subview 上面。如果在计时器到点前发生了移动事件,那么UIScrollView取消 tracking 然后自己发生滚动。
可以重载子类
- (BOOL)touchesShouldBegin:(NSSet *)touches withEvent:(UIEvent *)event inContentView:(UIView *)view;
决定自己是否接受 touch 事件
- (BOOL)touchesShouldCancelInContentView:(UIView *)view;
开始发送 tracking messages 给 subview 的时候调用这个方法,决定是否发送 tracking messages 消息到 subview。
返回NO -> 发送,表示不取消
返回YES -> 不发送,表示会取消
@property(nonatomic,readonly,getter=isTracking) BOOL tracking;
当 touch 后还没有拖动的时候值是YES,否则NO
@property(nonatomic,readonly,getter=isZoomBouncing) BOOL zoomBouncing;
当内容放大到最大或者最小的时候值是YES,否则NO
@property(nonatomic,readonly,getter=isZooming) BOOL zooming;
当正在缩放的时候值是YES,否则NO
@property(nonatomic,readonly,getter=isDecelerating) BOOL decelerating;
当滚动后,手指放开但是还在继续滚动中。这个时候是YES,其他时候是NO
@property(nonatomic) CGFloat decelerationRate;
设置手指放开后的减速率
@property(nonatomic) CGFloat maximumZoomScale;
表示放大的最大倍数
@property(nonatomic) CGFloat minimumZoomScale;
表示缩小的最小倍数
@property(nonatomic,getter=isPagingEnabled) BOOL pagingEnabled;
当值为YES的时候,就会产生翻页那种效果
@property(nonatomic,getter=isScrollEnabled) BOOL scrollEnabled;
决定是否可以滚动
@property(nonatomic) BOOL delaysContentTouches;
当值为YES的时候,用户一旦触碰,然后再一定时间内没有移动,UIScrollView会发送 tracking events,然后用户移动手指足够长度触发滚动事件,这个时候,UIScrollView发送了-(void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event到 subview,然后UIScrollView开始滚动。假如值为NO,UIScrollView发送 tracking events 后,就算用户移动手指,UIScrollView也不会滚动。
@property(nonatomic) BOOL showsHorizontalScrollIndicator;
滚动时是否显示水平滚动条
@property(nonatomic) BOOL showsVerticalScrollIndicator;
滚动时是否显示垂直滚动条
@property(nonatomic) BOOL bounces;
默认是YES,就是滚动超过边界会有反弹回来的效果,如果设置为NO,那么滚动到边界就会立刻停止
@property(nonatomic) BOOL bouncesZoom;
这个效果反映在缩放上面,如果缩放超过最大缩放,就会有反弹效果,加入设置为NO,则达到最大或者最小的时候立刻停止
@property(nonatomic,getter=isDirectionalLockEnabled) BOOL directionalLockEnabled;
默认是NO,可以在垂直和水平方向同时运动。当值为YES的时候,加入一开始是垂直或者水平运动,那么接下来会锁定另外一个方向的滚动。加入一开始是对角方向滚动,则不会禁止某个方向
@property(nonatomic) UIScrollViewIndicatorStyle indicatorStyle;
滚动条的样式,基本只是设置颜色
@property(nonatomic) UIEdgeInsets scrollIndicatorInsets;
设置滚动条的位置
方法篇
直接上代码看
#pragma mark UIScrollViewDelegate
//只要滚动了就会触发
- (void)scrollViewDidScroll:(UIScrollView *)scrollView;
{
// NSLog(@" scrollViewDidScroll");
NSLog(@"ContentOffset x is %f,yis %f",scrollView.contentOffset.x,scrollView.contentOffset.y);
}
//开始拖拽视图
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView;
{
NSLog(@"scrollViewWillBeginDragging");
}
//完成拖拽
- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate;
{
NSLog(@"scrollViewDidEndDragging");
}
//将开始降速时
- (void)scrollViewWillBeginDecelerating:(UIScrollView *)scrollView;
{
NSLog(@"scrollViewWillBeginDecelerating");
}
//减速停止了时执行,手触摸时执行
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView;
{
NSLog(@"scrollViewDidEndDecelerating");
}
//滚动动画停止时执行,代码改变时出发,也就是setContentOffset改变时
- (void)scrollViewDidEndScrollingAnimation:(UIScrollView *)scrollView;
{
NSLog(@"scrollViewDidEndScrollingAnimation");
}
//设置放大缩小的视图,要是uiscrollview的subview
- (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView;
{
NSLog(@"viewForZoomingInScrollView");
return viewA;
}
//完成放大缩小时调用
- (void)scrollViewDidEndZooming:(UIScrollView *)scrollView withView:(UIView *)view atScale:(float)scale;
{
viewA.frame=CGRectMake(50,0,100,400);
NSLog(@"scale between minimum and maximum. called after any 'bounce' animations");
}// scale between minimum and maximum. called after any 'bounce' animations
//如果你不是完全滚动到滚轴视图的顶部,你可以轻点状态栏,那个可视的滚轴视图会一直滚动到顶部,那是默认行为,你可以通过该方法返回NO来关闭它
- (BOOL)scrollViewShouldScrollToTop:(UIScrollView *)scrollView;
{
NSLog(@"scrollViewShouldScrollToTop");
returnYES;
}
- (void)scrollViewDidScrollToTop:(UIScrollView *)scrollView;
{
NSLog(@"scrollViewDidScrollToTop");
}
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
// Return YES for supported orientations
return (interfaceOrientation == UIInterfaceOrientationPortrait);
}
@end
基于AotoLayout的控件悬停
在很多的电商类的APP中我们经常会用到一种滑动悬停的效果,所以这次尝试着利用AutoLayout来实现这种效果。话不多说先上图
效果:
效果上过了,还是理顺下思路上吧。从效果图上不难发现我们的程序是基于UIScrollerView的。界面上的展示同样都是UIScrollView的子控件。当然了难点也就是基于UIScrollerView的内部子控件的布局以及AutoLayout约束的修改。
我们看一下我们的需要的一些基本的控件布局:
好了,现在开始开始搭建新的项目了,首先在Storyboard上搭建最基本的布局,并将控件基本布局到相应的位置,以备我们更方便的添加约束。
到了这里就开始对我们的控件添加基本的约束了从上开始吧。上面的控件我们能确定是UIImageView的heigth以及top、left以及rihgt。至于bottom到了后面会着重设置的。
然后继续来操作悬停的View。这个View因为处在两个view的中间所以我们要暂时确定height以及left和right的约束就足够了了
终于到最后一个UIImageView了,同样的能确定的是height、right、left以及bottom这四项。
到了这里针对单个的控件的布局基本完成了,细心的你可能会发现以上的操作都没有进行关联以及没有对width进行约束。那么下面我们就要开始进行三者的关联以及width上的设置。
因为悬停的view处与两个imageView的中间,所以我们只需要对它进行处理就好了
其实这两个约束完全是可以在设置悬停view的时候的添加,之所以在这里添加是我们在后面的控制器会用到这两个约束,为了好辨认我们就给他们定义上别名:
然后我们按住command选择我们的三个控件然后按住control拖向scrollView的父控件view设置等width。
好了,到了这里我们的约束已经不会再报错了,然后在逻辑上也是基本是通顺的了,但是对于我们悬停的功能来说就少了关键的一个环节那么就是一旦悬停view悬停了,那么它与上下两个imageView的约束要怎么处理呢?所以我们还需要一个辅助的约束用来悬停之后的关联处理
添加之后我们给这个约束起个别名hidden悬停,不过细心的你一定会发现添加之后会sb会提示出有冲突的约束,那么我们只需要对我们刚刚添加的约束进行一下操作就OK了
好了,到了这里我们在AutoLayout上要做的事情就这些了,下面我们就可以安心的写代码了。
@interface ViewController ()/**中间的View*/@property (weak, nonatomic) IBOutlet UIView *centreView;/**上面的ImageView*/@property (weak, nonatomic) IBOutlet UIImageView *topImageView;/**centreView的top约束*/@property (weak, nonatomic) IBOutlet NSLayoutConstraint *topConstraint;/**centreView的bottom约束*/@property (weak, nonatomic) IBOutlet NSLayoutConstraint *bottomConstraint;/**topImageView暂禁用的约束*/@property (weak, nonatomic) IBOutlet NSLayoutConstraint *hiddenConstraint;
@end
然后在scrollView的滑动代理方法中实现
- (void)scrollViewDidScroll:(UIScrollView *)scrollView{
//获得top图片的高度
CGFloat imageH = self.topImageView.frame.size.height;
//获取偏移量
CGFloat offsetY = scrollView.contentOffset.y;
//centrView的frame
CGRect centreFrame = self.centreView.frame;
if (offsetY>=imageH) {
//将centreView的向上和向下的约束禁用
self.bottomConstraint.active = NO;
self.topConstraint.active = NO;
//将topImageView与bottomImageView的约束使用关联
self.hiddenConstraint.active = YES;
//悬停在位置
centreFrame.origin.y = 0;
self.centreView.frame = centreFrame;
//添加在scrollView的父控件
[self.view addSubview:self.centreView];
}else{
//添加在topIamgeView的下面
centreFrame.origin.y = imageH;
self.centreView.frame = centreFrame;
[scrollView addSubview:self.centreView];
//记住一定要先添加到scrollView上之后在修改约束的内容,不然添加的约束会不成,因为系统无法建立他们之间的联系。
self.hiddenConstraint.active = NO;
self.topConstraint.active = YES;
self.bottomConstraint.active = YES;
}
//等比例的伸缩
CGFloat scale= 1-(offsetY/60);
scale = (scale>=1)?scale :1;
self.topImageView.transform = CGAffineTransformMakeScale(scale, scale);
}
最后补充:
在子控件的尺寸不能通过UIScrollView来计算,可以通过以下方式计算
- 设置固定值(width==100,height==300)
- 相对于UIScrollView以外的其他控件来计算尺寸
在UIScrollView里面布局子控件,sb里面默认是是需要子控件的尺寸以及子控件与UIScrollView之间的间距来计算出scroller的conentSize的。所以这就是上面的子控件要与父控件来等宽的原因了。
UIScrollView的frame应该通过子控件以外的其他控件来计算。