如何实现动态图表曲线?可以动态切换曲线,使用UICollectionView
by
admin on 2014-03-25 12:23:27 in
,
需要实现的效果:
1、头部的月份部分要能够根据滑动的位置显示。即使一个月中的一天在显示区域,月份也要显示出来。如果整屏都是同一个月的,那么月份居中显示。准确的说,月份的Label要显示在所在区域的居中位置。
2、切换数据源(dataSource)的时候要动态调整曲线。
github地址:
https://github.com/ganlv/GLLineChartView
详细说明:
1、自定义UICollectionView
从iOS6开始,苹果引入了UICollectionView。功能非常强大,可以用户自定义布局。自定义UICollectionView 只需要自己实现一个UICollectionViewLayout即可。具体的可以看苹果的官方文档:
https://developer.apple.com/library/ios/documentation/WindowsViews/Conceptual/CollectionViewPGforIOS/Introduction/Introduction.html
大概是需要实现几个方法:
[objc] view plaincopy
- (void)prepareLayout ; //初始化,这个会最先被执行
- (CGSize)collectionViewContentSize;//确定contentSize,CollectionView本质上还是一个UIScrollView
- (NSArray*)layoutAttributesForElementsInRect:(CGRect)rect;//最关键的需要实现的方法
//cell用的layout属性
- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath;
//头部区域的Layout
- (UICollectionViewLayoutAttributes *)preLayoutAttributesForSupplementaryViewOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath;
这里就不说明所有方法的实现了,说说核心方法的实现:
prepareLayout的实现是为了保证能够通过位置信息快速的定位到indexPath,这样能够快速的获得一个区域里面的indexPaths。
[objc] view plaincopy
-(void)prepareLayout
{
[super prepareLayout];
_indexDictionary = [NSMutableDictionary dictionary];
NSInteger index = 0;
for (NSInteger section = 0;section < [self.collectionView numberOfSections] ; section ++) {
for (NSInteger item = 0 ; item < [self.collectionView numberOfItemsInSection:section]; item++) {
NSIndexPath *indexPath = [NSIndexPath indexPathForItem:item inSection:section];
[_indexDictionary setObject:indexPath forKey:[NSNumber numberWithInteger:index]];
index++;
}
}
}
-(NSArray*)layoutAttributesForElementsInRect:(CGRect)rect;
这个是最核心需要实现的方法,获得指定区域内的UICollectionViewLayoutAttribute。这里需要注意的是,由于我们的Header是动态的。每次滚动的时候都需要调整LayoutAttribute。这里需要先指定:
[objc] view plaincopy
- (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds
{
return YES;
}
下面是layoutAttributesForElementsInRect:这里其实分3个部分:
① 确定需要处理的indexPath
② 确定Header的Attribute
③ 确定Cell的Attribute
[objc] view plaincopy
-(NSArray*)layoutAttributesForElementsInRect:(CGRect)rect
{
NSMutableArray *attrs = [NSMutableArray array];
//start offset
CGFloat startXOffset = rect.origin.x;
NSInteger startIndex = startXOffset/[self cellWidth] -1;
if(startIndex < 0){
startIndex = 0;
}
//end offset
CGFloat endXOffset = rect.origin.x + rect.size.width;
NSInteger endIndex= endXOffset/[self cellWidth];
if(endXOffset < [self.collectionView contentSize].width){
endIndex = endIndex + 1;
}
//for update index
for (NSInteger index = startIndex; index <= endIndex ; ++index) {
NSIndexPath *indexPath = [_indexDictionary objectForKey:[NSNumber numberWithInteger:index]];
if(indexPath == nil){
continue;
}
if(indexPath.item == 0 || index == startIndex){
UICollectionViewLayoutAttributes *sectionAttr = [self layoutAttributesForSupplementaryViewOfKind:GLCollectionViewLayoutTop atIndexPath:indexPath];
[attrs addObject:sectionAttr];
}
UICollectionViewLayoutAttributes *itemAttr = [self layoutAttributesForItemAtIndexPath:indexPath];
[attrs addObject:itemAttr];
}
return attrs;
}
2、让曲线能够动态的变化
先说明原理,整个iOS的animation是由一个animation service来完成的。可以用Core Animation来控制位置的移动。但是在我们这个曲线中,无法用Core Animation来实现。Core Animation只能控制简单的位置、旋转以及缩放等。
但是在我们这个Case里面,曲线是由3个点控制,且在变换的过程中,上下变换的方向和大小是不一样的,所以就不能用简单的CoreAnimation来实现了。这里的解决方案可以是CADisplayLink,CADisplayLink的效果是每一帧执行一次。需要指定动画的时间,根据动画的时间来确定每一帧的运动距离,达到动画结束时间的时候就可以调用[CADisaplayLink invalidate];来完成动画。代码框架如下:
[objc] view plaincopy
-(void)setTargetChartLine:(GLChartLine)achartLine targetOldChartLine:(GLChartLine)aoldChartLine animate:(BOOL)animate
{
if(animate && (chartLine.startPoint != 0 || chartLine.endPoint != 0 || chartLine.mainPoint !=0) ){
_targetChartLine = achartLine;
_startChartLine = chartLine;
_startDate = [NSDate date];
_oldTargetChartLine = aoldChartLine;
_oldStartChartLine =oldChartLine;
CADisplayLink *_display =[CADisplayLink displayLinkWithTarget:self selector:@selector(animateChartLine:)];
[_display addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
}else{
chartLine = achartLine;
oldChartLine = aoldChartLine;
[self setNeedsDisplay];
}
}
-(void)animateChartLine:(CADisplayLink *)sender
{
NSTimeInterval timeDuration = 0.3;//300毫秒
NSTimeInterval duration = [[NSDate date] timeIntervalSinceDate:_startDate];
if(duration >= timeDuration){
chartLine = _targetChartLine;
oldChartLine = _oldTargetChartLine;
[self setNeedsDisplay];
[sender invalidate];
return;
}
CGFloat nowStart = _startChartLine.startPoint + (duration/timeDuration)*(_targetChartLine.startPoint - _startChartLine.startPoint);
CGFloat nowMain = _startChartLine.mainPoint + (duration/timeDuration)*(_targetChartLine.mainPoint - _startChartLine.mainPoint);
CGFloat endMain = _startChartLine.endPoint + (duration/timeDuration)*(_targetChartLine.endPoint - _startChartLine.endPoint);
chartLine = GLChartLineMake(nowStart, nowMain, endMain);
CGFloat oldNowStart = _oldStartChartLine.startPoint + (duration/timeDuration)*(_oldTargetChartLine.startPoint - _oldStartChartLine.startPoint);
CGFloat oldNowMain = _oldStartChartLine.mainPoint + (duration/timeDuration)*(_oldTargetChartLine.mainPoint - _oldStartChartLine.mainPoint);
CGFloat oldEndMain = _oldStartChartLine.endPoint + (duration/timeDuration)*(_oldTargetChartLine.endPoint - _oldStartChartLine.endPoint);
oldChartLine = GLChartLineMake(oldNowStart, oldNowMain, oldEndMain);
[self setNeedsDisplay];
}
每次确定好了Cell的3个点之后,就调用一次needDisplay。然后系统会执行drawInContext方法,在drawInContext里面处理具体的绘图逻辑,只是需要使用CoreGraph的画图API即可,具体的可以去看github上的代码。
github地址:
https://github.com/ganlv/GLLineChartView
转载自:http://www.ganlvji.com/dynamic_chart/
评论