c语言移动端开发,实现移动端可共用动效算法开发

原标题:实现移动端可共用动效算法开发

c81ae755dba50d40c24745220e5bcd29.png

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

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

项目背景:

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

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

解决办法:

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

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

07e3bf571cb14c349e5be362d0e1a83a.png

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

}

}

}

}

在本例的中我们的气泡图片是有透明度的,所以需要跟背景颜色做颜色混合。我们需要理解alpha的取值,0是全透明,我们就不需要在背景色上做修改。255是不透明直接用气泡的像素点颜色取代背景色。

除此之外的取值,都是要做像素点颜色合成,可以简单理解alpha值就是要往目标像素点,加本像素点多少颜色值。PNG图片就是带alpha值的图片,我们在创建显示png图片内存时一般都用GRBA8888,每个像素点占用32位。但是JPG图片我们可以只使用RGB888,每个像素点可以节省一个字节,甚至可以损失点精度设置成RGB565。

void bubbleCycle(unsigned char *layerBuffer, int layerWidth, int layerHeight) {

int i, count;

float scale, half_height;

….

for (i = 0; i < BUBBLE_MAX; i++) {

if (count == 0)

break;

if (particles[i].visible == 1) {

particles[i].life--;

particles[i].cx += particles[i].speedX;

particles[i].cy += particles[i].speedY;

particles[i].speedX += particles[i].speedIncX;

particles[i].speedY += particles[i].speedIncY;

//根据粒子属性,实现一个活泼的小粒子

drawBubble(particles[i], layerBuffer, layerWidth, layerHeight);

//粒子回收

if (particles[i].life == 0 || particles[i].cy < 0

|| particles[i].cx < -particleBitmap.width || particles[i].cx > layerWidth + particleBitmap.width) {

particles[i].visible = 0;

i--;

bubbleCount--;

}

count--;

}

}

//产生新粒子

count = random() % BUBBLE_MAX;

for (i = 0; i < count; i++) {

……

particles[i].visible = 1;

bubbleCount++;

}

}

//跟用户交互式时的动态添加粒子处理

void addBubble(int x, int y) {

……

particles[i].alpha = 155 + random() % 100;

particles[i].visible = 1;

}

//释放资源

void freeBubble() {

if (particleBitmap.data != NULL) {

free(particleBitmap.data);

particleBitmap.data = NULL;

}

}

通过上面的步骤我们实现了Android和iOS两端一致的动效。核心代码都是通过C层共用代码提供。

示例的代码,可以从以下链接下载:

https://github.com/153493932/Animation

以上都是个人思考和实践。上面的设计主要针对有较强交互和随机的动效,而且需要团队自己从零开发的时候使用。

效果固定的动效可以利用Lottie SDK,由动效工程师直接倒出Json文件播放。大家如果有问题可以随时交流。

责任编辑:

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值