仿高德路线规划滑动效果
因为项目有个界面要模仿高德地图路径规划滑动
效果,因此写了demo
,并简单说下分析过程。
高德地图效果演示:
demo效果演示:
Demo地址:https://github.com/fangjinfeng/MySampleCode/tree/master/FJFBlogProjectDemo
一. 分析
-
首先,我们可以看出这个滚动的视图应该是
UIScrollView
或者UIScrollView的子类(比如:UITableView)
; -
其次,从高德地图里的视图一开始的滑动,可以看出这个滑动是平稳的滑动,没有加速和减速,因此这里不可能是
UIScrollView
的滚动效果,因为UIScrollView
的滚动效果是由一个加减速的过程,因此一开始滑动,应该是通过滑动手势UIPanGestureRecognizer
,来移动UIScrollView
的y值
来移动 -
接着滑动到指定位置之后,
UIScrollView
的y
值固定不动,然后UIScrollView
的内容进行滚动。这里就涉及到滑动手势UIPanGestureRecognizer
的滑动,还有UIScrollView
内部的滚动的处理。高德地图的演示效果里面,一开始滑动视图向上移动,移动到指定的点之后,立马就变成视图的滚动,这里可以分析,UIScrollView
既支持手势的滑动
又支持视图的滚动
,只是通过条件来判断限制两者的执行逻辑。 -
同时我们可以看到,如果一开始
向上拉动
视图力度大一点,视图会直接滚动到指定位置,如果力度小,就恢复
到原来位置,因此这里需要依据手势滑动的加速度
来进行判断处理。 -
而当你滑动到
中间位置
的时候,也需要依据最后滑动的位置
来判断应该动画滚动到上方还是下方。 -
最后滑动的时候上方的视图和滑动视图本身有背景颜色的
渐变效果
,这里需要依据滑动距离
来判断。
二.代码分析:
- 首先由于滚动视图(
demo
里面是UITableView
)需要支持手势滑动和内部滚动,因此需要写一个类FJBaseTableView
继承自UITableView
,然后在FJBaseTableView
的实现里面重写如下方法:
// 当有 多个手势 都可以 响应
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer {
return YES;
}
来支持响应多个手势。
- 然后给
滚动视图tableView
添加滑动手势,当tableView
从底部滑动到顶部指定位置时,应该限制tableView
内部的视图滚动。
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
if (self.tableView.frame.origin.y > _scrollViewStartPositionY) {
[scrollView setContentOffset:CGPointMake(0, 0)];
}
}
这里的_scrollViewStartPositionY
是顶部指定位置。
- 接着看下
手势滑动
的处理逻辑:
#pragma mark - 手势处理
- (void)handlePanGesture:(UIPanGestureRecognizer *)sender {
if (sender.state == UIGestureRecognizerStateBegan) {
_beganPoint = [sender locationInView:sender.view.superview];
_curPoint = sender.view.center;
_topTipContainerViewCurrentY = _topContainerView.frame.origin.y;
_previousOffsetY = self.tableView.contentOffset.y;
} else if(sender.state == UIGestureRecognizerStateChanged) {
CGPoint point = [sender locationInView:sender.view.superview];
CGFloat offsetY = _previousOffsetY - self.tableView.contentOffset.y;
NSInteger y_offset = point.y - _beganPoint.y - offsetY;
if (sender.view.frame.origin.y >= _scrollViewStartPositionY || (self.tableView.contentOffset.y == 0 && self.tableView.contentSize.height > self.tableView.frame.size.height)) {
sender.view.center = CGPointMake(_curPoint.x, _curPoint.y + y_offset);
[self updateViewControlsWithSlideOffset:y_offset];
}
if (sender.view.frame.origin.y > _scrollViewLimitMaxY) {
sender.view.y = _scrollViewLimitMaxY;
[self updateViewControlsWithSlideUp:NO];
}
else if(sender.view.frame.origin.y < _scrollViewStartPositionY) {
sender.view.y = _scrollViewStartPositionY;
[self updateViewControlsWithSlideUp:YES];
}
} else if(sender.state == UIGestureRecognizerStateEnded) {
if (sender.view.frame.origin.y <= _scrollViewStartPositionY || sender.view.frame.origin.y > _scrollViewLimitMaxY) {
if (sender.view.frame.origin.y <= _scrollViewStartPositionY) {
[self updateViewControlsWithSlideUp:YES];
}
if (sender.view.frame.origin.y > _scrollViewLimitMaxY) {
[self updateViewControlsWithSlideUp:NO];
}
return;
}
// 滑动速度处理
CGPoint velocity = [sender velocityInView:self.view];
CGFloat speed = 350;
if (velocity.y < - speed) {
// 快速向上
[self tableViewMoveToTop];
return;
} else if (velocity.y > speed) {
// 快速向下
[self tableViewMoveToBottom];
return;
}
// 滑动临界值
CGFloat criticalValue = _scrollViewLimitMaxY/2.0;
if (sender.view.frame.origin.y <= criticalValue) {
[self tableViewMoveToTop];
} else {
[self tableViewMoveToBottom];
}
}
}
这里几个点需要注意:
-
_beganPoint、_curPoint
两个参数是用来计算手势滑动
距离然后调整scrollView
的滑动距离
。而_previousOffsetY
是用来记录滑动之前tableView
的内部视图的偏移距离,因为当tableView
滑动到顶部指定位置后,tableView
开始滚动,这时候tableView
向下滑动是先移动了tableView
内部的滚动距离,然后才是滑动距离
,因此需要将这部分值先记录,然后去除掉,才是tableView
向下真正需要滑动的距离。
CGFloat offsetY = _previousOffsetY - self.tableView.contentOffset.y;
NSInteger y_offset = point.y - _beganPoint.y - offsetY;
2.滑动过程中,顶部视图的移动和渐变处理,这里先依据滑动的距离算出tableView滑动距离
与tableView最大滑动距离
的比值
,然后再算出顶部视图
需要移动的距离
和背景的透明度
。
- (void)updateViewControlsWhenSliding {
if (self.tableView.frame.origin.y > _scrollViewStartPositionY && self.tableView.frame.origin.y < _scrollViewLimitMaxY) {
CGFloat offsetLimitDistance = _scrollViewLimitMaxY - _scrollViewStartPositionY;
CGFloat offsetDistance = self.tableView.frame.origin.y - _scrollViewStartPositionY;
if (offsetDistance > 0 && offsetDistance < offsetLimitDistance) {
CGFloat topViewHeight = [FJFTopContainerView viewHeight];
CGFloat topViewHeightOffset = offsetDistance * (topViewHeight / offsetLimitDistance);
CGFloat viewAlpha = offsetDistance / offsetLimitDistance;
_topContainerView.y = topViewHeightOffset - topViewHeight;
_topContainerView.alpha = viewAlpha;
}
}
}
3.滑动速度处理,依据velocityInView
函数获取速度值
,然后依据当前速度值大小
和正负
和设定的速度值
比较来判断是否需要向上或向下移动
。
// 滑动速度处理
CGPoint velocity = [sender velocityInView:self.view];
CGFloat speed = 350;
if (velocity.y < - speed) {
// 快速向上
[self tableViewMoveToTop];
return;
} else if (velocity.y > speed) {
// 快速向下
[self tableViewMoveToBottom];
return;
}
4.滑动临界值处理,判断最后滑动位置与底部指定位置一半,两个值的大小来判断滑动的方向。
// 滑动临界值
CGFloat criticalValue = _scrollViewLimitMaxY/2.0;
if (sender.view.frame.origin.y <= criticalValue) {
[self tableViewMoveToTop];
} else {
[self tableViewMoveToBottom];
}
三.总结
这里最主要就是介绍了分析的思路,来找出可靠的实现方法,具体逻辑,详见demo