快捷搜索:

实现移动端可共用动效算法开发

实现移动端可共用动效算法开发

本文主要介绍如何在Android和iOS设备上,用同一套C语言代码实现一组动画效果的设计和开发过程。

其中包括平台层和可共用C层的设计和实现,C层处理需要高运算效率的图像处理,粒子系统等算法。

项目背景:

1、为了让App展示更酷炫,UX团队在设计中增加了大量的动画效果。

2、面对同一个效果,往往不同开发保持着不同的开发思路。最后实现的效果往往不一致。为了保持效果的一致性,需要花费大量的时间跟动效设计师沟通、调优,甚至重新开发。

解决办法:

基于可以跨Android和iOS 的C/C++开发语言,1人开发和交流,提炼和实现Android、iOS的核心效果算法。C层提供微调接口给平台层,例如气泡大小等等。

以下我们以项目中遇到的水波和气泡效果为例,讨论怎样获得可共用的C/C++代码。效果图如下:

实现移动端可共用动效算法开发

这两种效果都具有随机性和人机交互,例如水波的波峰、波谷、振幅都是随机的;气泡是有生命的,从创建开始就有了粒子属性,还可以在用户点击的位置动态产生气泡。 避免让用户看起来像播放gif动画。

那我们接着需要思考除了用C/C++语言开发外,哪些动效算法适合做成跨平台的呢?

这里我们对Android和iOS设备做了一层抽象。做过图像处理的开发都比较了解这点:屏幕上显示的图像,都是由像素点组成的。像素点不同颜色和位置勾画出了整个图像。

而且在设备里面一般都是通过一块内存来存放像素点信息的,映射到代码里面就是一个二维数组。那刚好联想到Android和iOS中对图片的处理都会从原始的jpeg或者png文件解析到一块内存中,显示的时候通过像素合成到显存中。

具体步骤如下:(以气泡效果为例)

1、创建自定义View

这个可以做成模版,后面的需要自定义的动画,都可以一样的复用。

Android:

public class BubbleView extends View {

……

@Override

public void onDraw(Canvas canvas) {

long startTime = System.currentTimeMillis(), endTime;

//刷新内存

DRLib.showBubbles(baseBitmap, baseBitmap.getWidth(),baseBitmap.getHeight());

//更新到画布

canvas.drawBitmap(baseBitmap, srcRect, srcRect, null);

endTime = (System.currentTimeMillis() - startTime);

Log.e("TAG", "Bubble endTime =" + endTime);

handler.postDelayed(runnable, (long) (Math.max(5.0d, 50.0d - endTime)));

}

//点击交互

@Override

public boolean onTouchEvent(MotionEvent event) {

……

switch (action) {

case MotionEvent.ACTION_DOWN:

DRLib.addBubble(pos_x, pos_y);

break;

}

return true;

}

}

iOS :

class BundlesView: UIView {

……

func setupSubviews() {

BubbleOC.initBubbles(UIImage(named:"bubble"))

timer = Timer(timeInterval: 0.05, target: self, selector: #selector(self.showBubbles), userInfo: nil, repeats: true)

RunLoop.main.add(timer!, forMode:RunLoopMode.commonModes)

}

func showBubbles() {

let uiImage = BubbleOC.showBubbles((NSInteger)(self.frame.width), height:(NSInteger)(self.frame.height));

let bgColor = UIColor.init(patternImage: uiImage!);

//iOS 需要从UIImage再解析出来颜色数据,设置到背景。这里相当于做了Color Data转UIImage,再UIImage变Color Data的过程。有时间再研究有没有简化方法。

self.backgroundColor = bgColor;

}

}

以上代码大家注意一下刷新频率,我们这里把他设置成每秒钟20帧。一般系统自刷新都是50帧以上。我们需要取舍效果的功耗和流畅度。

除此之外,我们需要加算好恒定的刷新周期,在Android中有50.0d - endTime,就是减去了中间代码的运算时间。在效果算法调优的过程中,如果想得到效果运算时间,需要取多次采样的平均值。

2、创建内存

Android:

@Override

protected void onSizeChanged(int w, int h, int oldw, int oldh) {

super.onSizeChanged(w, h, oldw, oldh);

baseBitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);

//baseBitmap图像合成Buffer

Bitmap bubbleBitmap = BitmapFactory.decodeResource(this.getResources(), R.mipmap.particle_bubble).copy(Bitmap.Config.ARGB_8888, true);

DRLib.initBubbles(bubbleBitmap);

//bubbleBitmap 原始图像数据Buffer

}

iOS :

@implementation BubbleOC

+ (UIImage *) initBubbles:(UIImage *)uiImage {

CGImageRef imageRef;

imageRef = uiImage.CGImage;

int width = (int)CGImageGetWidth(imageRef);

int height = (int)CGImageGetHeight(imageRef);

bubbleWidth = width;

bubbleHeight = height;

size_t bitsPerComponent;

bitsPerComponent = CGImageGetBitsPerComponent(imageRef);

size_t bitsPerPixel;

bitsPerPixel = CGImageGetBitsPerPixel(imageRef);

size_t bytesPerRow;

bytesPerRow = CGImageGetBytesPerRow(imageRef);

CGColorSpaceRef colorSpace;

colorSpace = CGImageGetColorSpace(imageRef);

CGBitmapInfo bitmapInfo;

bitmapInfo = CGImageGetBitmapInfo(imageRef);

bool shouldInterpolate;

shouldInterpolate = CGImageGetShouldInterpolate(imageRef);

CGColorRenderingIntent intent;

intent = CGImageGetRenderingIntent(imageRef);

CGDataProviderRef dataProvider;

dataProvider = CGImageGetDataProvider(imageRef);

CFDataRef data;

UInt8* buffer;

data = CGDataProviderCopyData(dataProvider);

buffer = (UInt8*)CFDataGetBytePtr(data);

initBubble(buffer, width, height);

CFDataRef effectedData;

effectedData = CFDataCreate(NULL, buffer, CFDataGetLength(data));

CGDataProviderRef effectedDataProvider;

effectedDataProvider = CGDataProviderCreateWithCFData(effectedData);

CGImageRef effectedCgImage;

UIImage* effectedImage;

effectedCgImage = CGImageCreate(

width, height,

bitsPerComponent, bitsPerPixel, bytesPerRow,

colorSpace, bitmapInfo, effectedDataProvider,

NULL, shouldInterpolate, intent);

effectedImage = [[UIImage alloc] initWithCGImage:effectedCgImage];

CGImageRelease(effectedCgImage);

CFRelease(effectedDataProvider);

CFRelease(effectedData);

CFRelease(data);

return effectedImage;

}

+ (UIImage*) showBubbles:(NSInteger)width height:(NSInteger)height {

UInt8* buffer;

int size = (int)(width * height << 2);

buffer = (UInt8 *)malloc(size * sizeof(UInt8));//创建像素合成内存

bubbleCycle(buffer, (int)width, (int)height);

CFDataRef effectedData;

effectedData = CFDataCreate(NULL, buffer, size);

CGDataProviderRef effectedDataProvider;

effectedDataProvider = CGDataProviderCreateWithCFData(effectedData);

CGImageRef effectedCgImage;

UIImage* effectedImage;

effectedCgImage = CGImageCreate(

width, height,

8, 32, width * 4,

CGColorSpaceCreateDeviceRGB(), kCGBitmapByteOrder32Big | kCGImageAlphaFirst, effectedDataProvider,

NULL, true, kCGRenderingIntentDefault);

effectedImage = [[UIImage alloc] initWithCGImage:effectedCgImage];

CGImageRelease(effectedCgImage);

CFRelease(effectedDataProvider);

CFRelease(effectedData);

free(buffer);

buffer = nil;

return effectedImage;

}

@end

3、C语言公用算法

由于示例效果都是全View更新,我们把对C层的处理请求,抽象出3个方法。1、初始化资源 2、更新效果 3、平台交互 4、释放资源。

我们采用粒子系统,来统一管理每个气泡的生命周期。可以先看一下简化的例子系统。

typedef struct BitmapStruct {

unsigned char *data;

int width;

int height;

} Bitmap;

typedef struct ParticleStruct{

int visible;

int life;

int size;

int cx;

int cy;

int cz;

int speedX;

int speedY;

int speedIncX;

int speedIncY;

int alpha;

int alphaInc;

int angle;

int radius;

int color;

int width;

int height;

Bitmap bitmap; //原始数据的像素的Buffer

} Particle;

extern Particle *addPaticel(int *data, int width, int height, int color) ;

//核心气泡处理算法

Particle particles[BUBBLE_MAX]; //粒子数组

//初始化环境:由于气泡效果每一个粒子的数据是同一个,我们为了节省内存,只创建一个原数据的Bitmap

void initBubble(unsigned char *data, int width, int height) {

long bitsSize = width * height * 4;

particleBitmap.data = (unsigned char *) malloc(bitsSize);

particleBitmap.width = width;

particleBitmap.height = height;

memcpy(particleBitmap.data, data, bitsSize);

}

//更新效果:每个粒子投射到画布上的像素合成处理

void drawBubble(Particle particle, unsigned char *layerBuffer, int layerWidth, int layerHeight) {

for (int y = 0; y < particleBitmap.height; y++) {

if (particle.cy + y < 0) {

break;

}

unsigned int *destLine = (unsigned int *) layerBuffer + (particle.cy + y) * layerWidth + particle.cx;

for (int x = 0; x < particleBitmap.width; x++) {

if (x + particle.cx == layerWidth - 1) {

break;

}

unsigned int srcColor = *((unsigned int *) particleBitmap.data + y * particleBitmap.width + x);

unsigned int alpha = srcColor >> 24;

unsigned int destAlpha;

switch (alpha) {//根据原始数据alpha,跟背景色混合处理

case 0:

break;

case 255:

*(destLine + x) = srcColor;

break;

default:

destAlpha = ((particle.alpha * alpha) >> 8);

*(destLine + x) = (destAlpha << 24) | (srcColor & 0x00FFFFFF);

break;

}

}

}

}

您可能还会对下面的文章感兴趣: