项目中模拟器不能运行,所以GIF图不是LiceCap录屏的,用的是Mac中的QuickTime软件录的手机的屏幕,不知道为有点模糊。先看下项目做做来的效果。

请自动忽略滑动部分其他界面的UI ,因为其他的还没完成。
- 界面注意思路
1 ScrollView来滑动,做分页,每次滑动的分页距离为屏幕的1/3
2 根据滑动距离放大图片和文字。
3 因为图片的放大缩小,滑动时需要实时绘制线条保持线条和图片的间隙为0.
ScrollView的创建
-
ScrollView分页的做法是利用UIView的clipsToBounds属性,clipsToBounds设置为NO,就意外着不裁剪超出ScrollView部分的视图。
打开视图层级,可以看到ScrollView就为图片下方的frame。
屏幕快照 2018-05-30 上午12.26.28.png
还有就是要知道ScrollView的分页滑动距离大小ScrollView的frame的宽度。结合两点可以创建ScrollView每次分页的距离为屏幕的1/3。
ScrollView 视图的滑动范围改变。
-
创建出了ScrollView大小如上图,那么我滑动下图所示的范围,因为不在ScrollView的区域范围内,ScrollView没法响应视图外的事件。
屏幕快照 2018-05-30 上午12.26.28.png
-
在事件传递和响应者链中介绍了一个系统方法
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
用来查找最合适的View。该方法遍历查找最合适的view是会调用
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent*)event
来查看是否触摸点是否在控件上。所以我们可以通过重写上面两种方法中其中一种来实现视图外点击事件的响应。
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
UIView *view = [super hitTest:point withEvent:event];
if (view == nil) {
// 大小为0,0开始 contentSize.width,为宽度的矩形。
CGRect rect = CGRectMake(0, 0, self.contentSize.width, self.height);
// 只要在矩形范围内,最合适的view返回self。
if (CGRectContainsPoint(rect, point)) {
view = self;
}
}
return view;
}
根据图片放大图片和文字
-
在最初看到这个需求时,第一反应就是看看缩放动画能不能满足需求
缩放.jpeg
我们可以看到最左边放大后,缩放是在原始控件的锚点为基点向外缩放,默认的锚点是(0.5,0.5),图片下方其实是两个label,放大导致
问题1 : 文字由小放大,导致文字模糊。
问题2 : 文字本来是纯白色,放大后文字不是纯白色
问题3 : 两个label在放大前,间距正常,放大后,两个label越来越小,如果放大倍数够大,两个label还会发生重叠。
-
在放大文字会模糊的情况发生时,我就放弃了,采用了方案2.采用layout变换 ,下面是方案2.
屏幕快照 2018-05-31 上午11.16.03.png
所以,我们在滑动时根据滑动的距离来获取当前滑动位置放大缩小的倍数,label重新计算font,然后计算该font存放的size。
看下关键代码
// kScreenWidth * 3是滑动分页的距离,_currentPage是当前页,
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
_currentPage = (NSInteger)(scrollView.contentOffset.x / kScreenWidth * 3);
// 现在最右边滑动
if (scrollView.contentOffset.x > kScreenWidth / 3 *(_rankMedalModel.levelArray.count - 1) || scrollView.contentOffset.x < 0 || _currentPage == _rankMedalModel.levelArray.count - 1) {
return;
}
// 右滑 (不能超出边界)
if (scrollView.contentOffset.x > _oriOffsetX) {
// 放大缩小的比例
CGFloat rate = (scrollView.contentOffset.x - _currentPage * (kScreenWidth / 3)) / kScreenWidth * 3;
[self animationWithRate:rate index1:_currentPage index2:_currentPage+1];
}
// 左滑 (不能超出边界)
else{
// 放大缩小的比例
CGFloat rate = ((_currentPage + 1) * (kScreenWidth / 3) - scrollView.contentOffset.x ) / kScreenWidth * 3;
[self animationWithRate:rate index1:_currentPage+1 index2:_currentPage];
}
_oriOffsetX = scrollView.contentOffset.x;
}
根据缩放的倍率,计算font,重新计算size,进行update。
/**
图片文字动画
@param rate <#rate description#>
@param index1 <#index1 description#>
@param index2 <#index2 description#>
*/
- (void)animationWithRate:(CGFloat)rate index1:(NSInteger)index1 index2:(NSInteger)index2 {
// 改变后的图片宽高 ,图片放大缩小
CGFloat subtractWidth = 14 * rate > 14 ? 14 : 14 * rate;
CGFloat subtractHeight = 16 * rate > 16 ? 16 : 16 * rate;
[_imageArray[index1] mas_updateConstraints:^(MASConstraintMaker *make) {
make.size.mas_equalTo(CGSizeMake(41 - subtractWidth,46 - subtractHeight));
}];
[_imageArray[index2] mas_updateConstraints:^(MASConstraintMaker *make) {
make.size.mas_equalTo(CGSizeMake(27 + subtractWidth, 30 + subtractHeight));
}];
// 经验文字缩小
_expLabelArray[index1].font = [UIFont systemFontOfSize:(14 - 4 *rate)];
CGSize expSize1 = [_expLabelArray[index1] sizeThatFits:CGSizeMake(kScreenWidth - 20, 15)];
[_expLabelArray[index1] mas_updateConstraints:^(MASConstraintMaker *make) {
make.size.mas_equalTo(CGSizeMake(kScreenWidth / 3 - 20, expSize1.height));
}];
// 经验文字放大
_expLabelArray[index2].font = [UIFont systemFontOfSize:(10 + 4 * rate)];
CGSize expSize2 = [_expLabelArray[index2] sizeThatFits:CGSizeMake(kScreenWidth - 20, 15)];
[_expLabelArray[index2] mas_updateConstraints:^(MASConstraintMaker *make) {
make.size.mas_equalTo(CGSizeMake(kScreenWidth / 3 - 20, expSize2.height));
}];
// 奖励缩小
_rewardLabelArray[index1].font = [UIFont systemFontOfSize:(14 - 4 *rate)];
CGSize rewardSize1 = [_rewardLabelArray[index1] sizeThatFits:CGSizeMake(kScreenWidth / 3, 30)];
[_rewardLabelArray[index1] mas_updateConstraints:^(MASConstraintMaker *make) {
make.size.mas_equalTo(CGSizeMake(kScreenWidth - 20, rewardSize1.height));
}];
// 奖励放大
_rewardLabelArray[index2].font = [UIFont systemFontOfSize:(10 + 4 * rate)];
CGSize rewardSize2 = [_rewardLabelArray[index2] sizeThatFits:CGSizeMake(kScreenWidth / 3, 30)];
[_rewardLabelArray[index2] mas_updateConstraints:^(MASConstraintMaker *make) {
make.size.mas_equalTo(CGSizeMake(kScreenWidth - 20, rewardSize2.height));
}];
[self layoutIfNeeded];
// 实时绘制线条
[self addLine];
}
线条的绘制
- 为什么要实时绘制
UI给的这个等级图片不是纯白色的,而是有透明度的,如果我从ScrollView的开始绘制到contentSize的最大位置,
会出现线条出现在图片底部的bug。
所以我只能从第一个图片后端,绘制到第一个图片前端,再从第二个图片末端,绘制到第三个图片前端。依次类推。
- 绘制的方法
1 绘制线条我们一般会想到在drawRect里调用Core Graphics方法进行绘制。或者在drawRect 使用 UIBezierPath进行绘制。
but。。
如图

我的ScrollView的宽度是屏幕的1/3,导致了绘制的内容区域的大小就为屏幕的1/3。无法绘制其他2/3距离。
- 所以我只能用CAShapeLayer + UIBezierPath来进行绘制。因为每次绘制的时候都要for循环移除屏幕上的CAShapeLayer。感觉不是很好,但是也没办法。
- (void)addLine {
// 移除layer
NSArray *layerArray = [NSArray arrayWithArray:self.layer.sublayers];
for (CALayer *layer in layerArray) {
if ([layer isKindOfClass:[CAShapeLayer class]]) {
[layer removeFromSuperlayer];
}
}
// 计算尾部白线百分比
CGFloat percent = 0.f;
if (_rankMedalModel.currentLevel < _rankMedalModel.levelArray.count) {
// 白色末端的经验总值
CGFloat allExp = _rankMedalModel.levelArray[_rankMedalModel.currentLevel].exp - _rankMedalModel.levelArray[_rankMedalModel.currentLevel - 1].exp;
// 当前剩余的经验
CGFloat leftExp = [_rankMedalModel.expValue integerValue] - _rankMedalModel.levelArray[_rankMedalModel.currentLevel - 1].exp;
percent = leftExp / allExp;
}
// 绘制底部灰线
for (int i = 0; i < _imageArray.count ; i++) {
if (i < _imageArray.count - 1) {
CAShapeLayer *grayLayer = [CAShapeLayer layer];
grayLayer.strokeColor = [[UIColor whiteColor] colorWithAlphaComponent:0.2].CGColor;
grayLayer.lineWidth = 3;
UIBezierPath *bezierPath = [UIBezierPath bezierPath];
[bezierPath moveToPoint:CGPointMake(_imageArray[i].right, _imageArray[i].centerY)];
[bezierPath addLineToPoint:CGPointMake(_imageArray[i+1].left, _imageArray[i+1].centerY)];
grayLayer.path = bezierPath.CGPath;
[self.layer addSublayer:grayLayer];
}
}
for (int i = 0; i < _rankMedalModel.currentLevel; i++) {
// 绘制完整白线
if (i < _rankMedalModel.currentLevel - 1) {
CAShapeLayer *whiteLayer = [CAShapeLayer layer];
whiteLayer.strokeColor = [UIColor whiteColor].CGColor;
whiteLayer.lineWidth = 3;
UIBezierPath *bezierPath = [UIBezierPath bezierPath];
[bezierPath moveToPoint:CGPointMake(_imageArray[i].right, _imageArray[i].centerY)];
[bezierPath addLineToPoint:CGPointMake(_imageArray[i+1].left, _imageArray[i+1].centerY)];
whiteLayer.path = bezierPath.CGPath;
[self.layer addSublayer:whiteLayer];
}
// 当前等级不是最后一级,绘制当前等级白线
if (i == _rankMedalModel.currentLevel - 1 && _rankMedalModel.currentLevel != _rankMedalModel.levelArray.count) {
CAShapeLayer *whiteLayer = [CAShapeLayer layer];
whiteLayer.strokeColor = [UIColor whiteColor].CGColor;
whiteLayer.lineWidth = 3;
whiteLayer.lineCap = kCALineCapRound;
UIBezierPath *bezierPath = [UIBezierPath bezierPath];
[bezierPath moveToPoint:CGPointMake(_imageArray[i].right, _imageArray[i].centerY)];
[bezierPath addLineToPoint:CGPointMake(_imageArray[i].right + (_imageArray[i].left - _imageArray[i-1].right) * percent , _imageArray[i+1].centerY)];
whiteLayer.path = bezierPath.CGPath;
[self.layer addSublayer:whiteLayer];
}
}
}
网友评论