之前在 Code4App 上看到的一个粒子动画实现,下面先上图看下动画效果,接着分析下里面的核心实现方便大家实现其它更酷炫的动画效果。
源码地址:Code4App Demo
废话少说,先上图看下效果。
先看下把图片变成一个个像素点的代码实现:
- (NSArray*)getRGBAsFromImage:(UIImage*)image {
//1. get the image into your data buffer.
CGImageRef imageRef = [image CGImage];
NSUInteger imageW = CGImageGetWidth(imageRef);
NSUInteger imageH = CGImageGetHeight(imageRef);
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
NSUInteger bytesPerPixel = 4; //一个像素4字节
NSUInteger bytesPerRow = bytesPerPixel * imageW;
unsigned char *rawData = (unsigned char*)calloc(imageH*imageW*bytesPerPixel, sizeof(unsigned char)); //元数据
NSUInteger bitsPerComponent = 8;
CGContextRef context = CGBitmapContextCreate(rawData, imageW, imageH, bitsPerComponent, bytesPerRow, colorSpace, kCGImageAlphaPremultipliedLast|kCGBitmapByteOrder32Big);
CGColorSpaceRelease(colorSpace);
CGContextDrawImage(context, CGRectMake(0, 0, imageW, imageH), imageRef);
CGContextRelease(context);
//2. Now your rawData contains the image data in the RGBA8888 pixel format.
CGFloat addY = (_maxParticleCount == 0) ? 1 : (imageH/_maxParticleCount);
CGFloat addX = (_maxParticleCount == 0) ? 1 : (imageW/_maxParticleCount);
NSMutableArray *result = [NSMutableArray new];
for (int y = 0; y < imageH; y+=addY) {
for (int x = 0; x < imageW; x+=addX) {
NSUInteger byteIndex = bytesPerRow*y + bytesPerPixel*x;
//rawData一维数组存储方式RGBA(第一个像素)RGBA(第二个像素)...
CGFloat red = ((CGFloat) rawData[byteIndex] ) / 255.0f;
CGFloat green = ((CGFloat) rawData[byteIndex + 1] ) / 255.0f;
CGFloat blue = ((CGFloat) rawData[byteIndex + 2] ) / 255.0f;
CGFloat alpha = ((CGFloat) rawData[byteIndex + 3] ) / 255.0f;
if (alpha == 0 ||
(_ignoredWhite && (red+green+blue == 3)) ||
(_ignoredBlack && (red+green+blue == 0))) {
//要忽略的粒子
continue;
}
AZParticle *particle = [AZParticle new];
particle.color = [UIColor colorWithRed:red green:green blue:blue alpha:alpha];
particle.point = CGPointMake(x, y);
if (_customColor) {
particle.customColor = _customColor;
}
if (_randomPointRange > 0) {
particle.randomPointRange = _randomPointRange;
}
[result addObject:particle];
}
}
free(rawData);
return result;
}
然后通过 CADisplayLink 累加一个变量再触发自己重绘,其中重绘实现如下:
-(void)drawInContext:(CGContextRef)ctx {
int count = 0;
for (AZParticle *particle in _particleArray) {
if (particle.delayTime > _animTime) {
continue;
}
CGFloat curTime = _animTime - particle.delayTime;
if (curTime >= _animDuration + particle.delayDuration) { //到达了目的地的粒子原地等待下没到达的粒子
curTime = _animDuration + particle.delayDuration;
count ++;
}
CGFloat curX = [self easeInOutQuad:curTime begin:_beginPoint.x end:particle.point.x + self.bounds.size.width/2-CGImageGetWidth(_image.CGImage)/2 duration:_animDuration + particle.delayDuration];
CGFloat curY = [self easeInOutQuad:curTime begin:_beginPoint.y end:particle.point.y + self.bounds.size.height/2 - CGImageGetHeight(_image.CGImage)/2 duration:_animDuration + particle.delayDuration];
CGContextAddEllipseInRect(ctx, CGRectMake(curX , curY , 1, 1));
const CGFloat* components = CGColorGetComponents(particle.color.CGColor);
CGContextSetRGBFillColor(ctx, components[0], components[1], components[2], components[3]);
CGContextFillPath(ctx);
}
if (count == _particleArray.count) {
[self reset];
if (_azDelegate && [_azDelegate respondsToSelector:@selector(onAnimEnd)]) {
[_azDelegate onAnimEnd];
}
}
}
iPhone6 上试了下性能,还是比较差的,动画中 CPU 基本在 90% 以上,但是大家可以根据里面的核心实现,做一下性能优化,定制下自己想要的动画效果。
可以把刷新频率稍微降低一点
可以把图片切割成4个像素块、16个像素块、或者一些很小的块状图片
可以自定义动画路径,做成一些有想象力的曲线路径
可以封装成 UIView 的 category 方法,更加方便使用
……
例如可以稍微改造一下实现下面的效果: