在很多APP里都能看到屏幕上方有一条用于显示分页控制器的View,一般分页控制器栏的总体宽度都会超过屏幕宽度,为了让用户减少手动滑动分页控制器栏的操作,所以分页控制器的半自动滑动就必不可少了。(代码采用双语方式)
36氪客户端.png 腾讯新闻客户端.png该功能点主要有五个:
1.选中标题和上一个选中标题互换字体颜色。
2.底部线条永远跟随选中标题,并且页面滑动时线条要向对应的分栏标题方向滑动。
3.点击标题按钮时,判断点击标题的临近标题按钮是否在屏幕内,不在则滑动显示临近标题。
4.滑动页面切换标题栏时,判断滑到的标题栏的临近标题按钮是否在屏幕内,不在则滑动显示临近标题。
5.不同长度的标题如何动态显示按钮和控制底部线条滑动。
实际效果如下:
功能点一,很容易用一个按钮属性记录上一个选中按钮就可以搞定了,然后每次点击后就重新设置上次选中按钮,设置后再将当前选中按钮赋值给选中按钮属性记录,周而复始即可。
功能点二,因为页面取屏幕宽度,底部线条要移动的范围是下一个按钮的宽度,所以需要换算移动比例,此处命名scrollGap为页面滑动距离,scrollGap<0,页面向左滑动。页面滑动时监听方法
- (void)scrollViewDidScroll:(UIScrollView *)scrollView;
func scrollViewDidScroll(_ scrollView: UIScrollView)
通过计算获取到页面滑动距离,设置底部线条的位置。
if (scrollGap < 0) { // 页面向左滑动 底部线向右滑动
CGFloat scale = -scrollGap*2/Screen_Width;
CGFloat gap = ([self.buttonWidths[index] floatValue]+[self.buttonWidths[index+1] floatValue]-2*self.bottomLineWidth)/2 + self.bottomLineWidth;
if (scale <= 1) {
bottomLine.frame = CGRectMake(bottomLine.frame.origin.x, bottomLine.frame.origin.y, self.bottomLineWidth+scale*gap, 2);
}
else {
bottomLine.frame = CGRectMake(self.bottomLastX+(scale-1)*gap, bottomLine.frame.origin.y, self.bottomLineWidth+gap-(scale-1)*gap, 2);
}
}
else { // 页面向右滑动 底部线向左滑动
CGFloat scale = scrollGap*2/Screen_Width;
CGFloat gap = ([self.buttonWidths[index] floatValue]+[self.buttonWidths[index-1] floatValue]-2*self.bottomLineWidth)/2 + self.bottomLineWidth;
if (scale <= 1) {
bottomLine.frame = CGRectMake(self.bottomLastX-scale*gap, bottomLine.frame.origin.y, self.bottomLineWidth+scale*gap, 2);
}
else {
bottomLine.frame = CGRectMake(self.bottomLastX-gap, bottomLine.frame.origin.y, self.bottomLineWidth+gap-(scale-1)*gap, 2);
}
}
if scrollGap < 0 { // 页面向左滑动 底部线向右滑动
let scale : CGFloat = -scrollGap*2.0/Screen_Width
let gap : CGFloat = ((buttonWidths[index] as! CGFloat)+(buttonWidths[index+1] as! CGFloat)-2*self.bottomLineWidth)/2 + bottomLineWidth;
if scale <= 1 {
bottomLine.frame = CGRect.init(x: bottomLine.frame.origin.x, y: bottomLine.frame.origin.y, width: bottomLineWidth+scale*gap, height: 2.0)
}
else {
bottomLine.frame = CGRect.init(x: bottomLastX+(scale-1)*gap, y: bottomLine.frame.origin.y, width: bottomLineWidth+gap-(scale-1)*gap, height: 2.0)
}
}
else { // 页面向右滑动 底部线向左滑动
let scale : CGFloat = scrollGap*2.0/Screen_Width;
let gap : CGFloat = ((buttonWidths[index] as! CGFloat)+(buttonWidths[index-1] as! CGFloat)-2*bottomLineWidth)/2 + bottomLineWidth;
if (scale <= 1) {
bottomLine.frame = CGRect.init(x: bottomLastX-scale*gap, y: bottomLine.frame.origin.y, width: bottomLineWidth+scale*gap, height: 2.0)
}
else {
bottomLine.frame = CGRect.init(x: bottomLastX-gap, y: bottomLine.frame.origin.y, width: bottomLineWidth+gap-(scale-1)*gap, height: 2.0)
}
}
功能点三,判断相邻的标题按钮是否在屏幕内,这本身需要两个判断,一个是判断当前标题按钮距离屏幕左侧近还是右侧近,如果是距离左侧近,那只需要判断当前按钮左侧的标题按钮是否在,反之判断右侧的。
我们通过当前点击按钮的center.x和其所在scrollView容器的contentOffset.x比较判断它的位置。当距离左侧近时,又有两种情况,当前按钮完全在屏幕内,当前按钮未完全在屏幕内。但是我们不用去判断而是统一为如果当前按钮不是最左侧按钮,那么它的做边距距离屏幕左边距是否有一个标题按钮的距离,如果有那么它临近的按钮自然就在屏幕上,否则就不完全在屏幕上。
接下来判断临近的按钮是否在屏幕内。
如图,
CGFloat leftGap = CGRectGetMinX(button.frame) - contentOffsetX;
let leftGap : CGFloat = button.frame.minX - contentOffsetX
leftGap是点击按钮左边距离当前scrollView在屏幕中最左边的距离,有正负值,那么要判断点击按钮临近左侧按钮是否在屏幕内,首先判断leftGap < self.buttonWidth的条件,为true那么临近的按钮未完全显示,否则完全显示不用做滑动处理。我们做的滑动处理都是在条件为true的基础上。右侧同理。
CGFloat contentOffsetX = backScrollView.contentOffset.x;
CGFloat gap = button.center.x - contentOffsetX;
if (gap < Screen_Width/2) { // 按钮距离屏幕左边近
// 按钮左边距距离屏幕左边距的距离
CGFloat leftGap = CGRectGetMinX(button.frame) - contentOffsetX;
if (index == 0) { // 按钮为最左侧按钮 只把自己显示全即可
[self reSetTitleScrollViewOffsetWithX:contentOffsetX+leftGap];
} else {
CGFloat leftButtonWidth = [self.buttonWidths[index-1] floatValue];
if ( leftGap < leftButtonWidth){ // 按钮距离左边距不足临近按钮距离 此时需要移动
NSLog(@"向右滑动显示左侧按钮");
[self reSetTitleScrollViewOffsetWithX:contentOffsetX+leftGap-leftButtonWidth];
}
}
} else if (gap > Screen_Width/2){
// 按钮右边距距离屏幕右边距的距离
CGFloat rightGap = contentOffsetX+Screen_Width-CGRectGetMaxX(button.frame);
if (index == self.titles.count-1) { // 当前是最右侧按钮
[self reSetTitleScrollViewOffsetWithX:contentOffsetX-rightGap];
} else {
CGFloat rightButtonWidth = [self.buttonWidths[index+1] floatValue];
if (rightGap < rightButtonWidth) { // 按钮距离右边距不足一个按钮距离 此时需要移动
[self reSetTitleScrollViewOffsetWithX:contentOffsetX+rightButtonWidth-rightGap];
}
}
}
- (void)reSetTitleScrollViewOffsetWithX:(CGFloat)pointX{
[UIView animateWithDuration:0.3 animations:^{
backScrollView.contentOffset = CGPointMake(pointX, 0);
}];
}
let contentOffsetX : CGFloat = backScrollView.contentOffset.x
let gap : CGFloat = button.center.x - contentOffsetX
if gap < Screen_Width/2 { // 按钮距离屏幕左边近
// 按钮左边距距离屏幕左边距的距离
let leftGap : CGFloat = button.frame.minX - contentOffsetX
if index == 0 { // 按钮为最左侧按钮 只把自己显示全即可
self.reSetTitleScrollViewOffsetWithX(pointX: contentOffsetX+leftGap)
}
else {
let leftButtonWidth : CGFloat = buttonWidths[index-1] as! CGFloat
if leftGap < leftButtonWidth{ // 按钮距离左边距不足临近按钮距离 此时需要移动
print("向右滑动显示左侧按钮")
self.reSetTitleScrollViewOffsetWithX(pointX: contentOffsetX+leftGap-leftButtonWidth)
}
}
}
else if gap > Screen_Width/2 {
// 按钮右边距距离屏幕右边距的距离
let rightGap : CGFloat = contentOffsetX+Screen_Width-button.frame.maxX
if index == titles.count-1 { // 当前是最右侧按钮
self.reSetTitleScrollViewOffsetWithX(pointX: contentOffsetX-rightGap)
}
else {
let rightButtonWidth : CGFloat = buttonWidths[index+1] as! CGFloat
if rightGap < rightButtonWidth { // 按钮距离右边距不足一个按钮距离 此时需要移动
self.reSetTitleScrollViewOffsetWithX(pointX: contentOffsetX+rightButtonWidth-rightGap)
}
}
}
func reSetTitleScrollViewOffsetWithX(pointX : CGFloat) {
UIView.animate(withDuration: 0.3) {
backScrollView.contentOffset = CGPoint.init(x: pointX, y: 0)
}
}
因为这种分栏标题一屏显示的标题一般都大于3个,所以如果按钮居中,那么两侧的按钮一定都在屏幕内,所以不用做处理。
功能点四,通过页面滑动完成的监听方法,
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView;
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView)
获取当前页面滑动到的位置index,有了index就可以做类似功能三的判断了。
详细可下载demo查看:
OC版demo下载地址
Swift版demo下载地址 GitHub给个Star噢!
喜欢就点个赞呗!
欢迎大家提出更好的改进意见和建议,一起进步!
MARK
OC版做了优化,可配置isBackGroud熟悉开关选择不同显示样式,显示如下:
segmentDemo.gif
网友评论