思路分析
一般的思路就是先把本地的emoji图片加载到内存,然后把emoji图片按照特定排列方式显示到自定义的view中,并且能响应点击事件(即点击到自定义view上时能知道点击的是哪个emoji),同时自定义view能够进行翻页来支持大量的emoji。
加载emoji图片
笔者是采用Bundle的方式去管理项目中要用到的一百多个emoji表情,所以是要从Bundele中加载完所有的emoji图片(gif格式)。这里我是采用了YYImageDecoder去解码gif图片并且把它的第一帧图片放到表情图片数组中。为什么要使用YYImageDecoder去解码图片而不是直接用imageNamed:方法去加载请参考这篇文章: iOS 处理图片的一些小 Tip
NSMutableArray *emotionArry = [NSMutableArray new];
NSString *path = [[[NSBundle mainBundle] resourcePath] stringByAppendingString:@"/myEmotion.bundle/"];
for (int i = 1; i < 135; i++) {
NSString *imgPath = [path stringByAppendingString:[NSString stringWithFormat:@"%d.gif",i]];
NSData *data = [NSData dataWithContentsOfFile:imgPath];
YYImageDecoder *decoder = [YYImageDecoder decoderWithData:data scale:0];
UIImage *image = [decoder frameAtIndex:0 decodeForDisplay:YES].image;
[emotionArry addObject:image];
}
emoji图片显示到自定义view上
上一步已经把emoji加载了,这时候要考虑怎么把emoji图片显示到自定义view上。因为emoji键盘中的每一页表情明显是流水布局,不少人第一反应就是用UICollectionView去实现,其中每一页就是一个Cell,每个Cell里面就是放若干个背景是emoji的UIButton,这样只需要两个Cell就能完成复用了,同时点击emoji表情Button时候也能知道点击的那个Button。
其实用UICollectionView完成表情翻页的功能是正确的,但是错误的是Cell里面不应该用大量的UIButton去完成emoji的布局和点击功能,因为这样会消耗大量的资源去创建UIButton和处理其布局信息(如果用AutoLayout的话),翻页的时候会感受到明显的卡顿。
正确的方法应该把每一页的emoji画成一张图片,这样翻页的时候其实就是显示不同的图片。
CGSize containerSize = CGSizeMake(ScreenWidth - 20, 92); //表情键盘大小
CGFloat horizonSpacing = (ScreenWidth - 20 - 9 * 24)/8; //表情水平间隔
CGFloat width = 24;
CGFloat height = 24;
NSMutableArray *containerArray = [NSMutableArray new];
for (int i = 0; i < 5; i ++) {
UIGraphicsBeginImageContext(CGSizeMake(ScreenWidth - 20, 92));//左右边距10 中间边距10
CGContextRef context = UIGraphicsGetCurrentContext();
[[UIColor whiteColor] setFill];
CGContextFillRect(context, CGRectMake(0, 0, containerSize.width, containerSize.height));
for (int j = 0; j < 27; j++) {
if (i == 4 && j == 26) {
continue;
}
UIImage *emotion = emotionArry[i * 27 + j];
CGRect emotionRect = CGRectMake((j % 9) * (width + horizonSpacing), floor(j/9) * (height + 10), width, height); // 计算出图片frame
[emotion drawInRect:emotionRect]; //把emoji绘制到画布上
}
UIImage *emotionContainerView = UIGraphicsGetImageFromCurrentImageContext(); //取出每一页的图片
[containerArray addObject:emotionContainerView];
UIGraphicsEndImageContext();
}
self.emotionsContainerViewArray = containerArray;
emoji的点击事件
emoji已经画成一张张的图片,但是但用户点击的时候如何知道他点击了哪个emoji?
因为UICollectionCell中只是简单的添加了一个UIImageView去显示图片,我们可以对UICollectionCell中添加tap手势,当触发手势时候,根据当前的Cell index和点击手势的x和y信息就可以算出用户点击的是哪个emoji。
- (void)touchEmotionsView:(UITapGestureRecognizer *)gesture {
CGPoint point = [gesture locationInView:gesture.view];
CGFloat horizonSpacing = (ScreenWidth - 20 - 9 * 24)/8;
CGFloat width = 24;
CGFloat height = 24;
CGFloat verticalSpacing = 10;
CGFloat x = point.x;
CGFloat y = point.y;
int indexX = floor(x/(horizonSpacing + width));
int indexY = floor(y/(verticalSpacing + height));
int index = (int)_indexPath.row * 27 + indexY * 9 + indexX;
if (index > 133) {
return;
} else {
NSString *emotion = [NSString stringWithFormat:@"[%d.gif]",index];
if ([self.parent.delegate respondsToSelector:@selector(selectedEmotion:)]) {
[self.parent.delegate selectedEmotion:emotion];
}
}
}
缓存绘制好的emoji图片
其实功能已经实现的差不多了,一般来说这个自定义emoji键盘都是实现成单例的,需要用的时候把这个emoji键盘view赋值给UITextView/UITextField的inputview就能把它显示到键盘的位置中去。
但是没必要每次重新启动程序时需要用到这个单例都要在初始化单例时候去重新画emoji图片,我们可以把画好的emoji图片缓存在本地,后面每次初始化时候就可以直接加载缓存的图片而不用重新绘制。
(void)initEmotionsContainer {
dispatch_async(dispatch_get_global_queue(QOS_CLASS_DEFAULT, 0), ^{
NSFileManager *fileManager = [[NSFileManager alloc] init];
NSString *pathDocuments = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
NSString *folderPath = [pathDocuments stringByAppendingPathComponent:@"emotion"];
// 判断文件夹是否存在,如果不存在,则创建
if (![[NSFileManager defaultManager] fileExistsAtPath:folderPath]) {
JZLog(@"创建文件夹成功 %@", folderPath);
[fileManager createDirectoryAtPath:folderPath withIntermediateDirectories:YES attributes:nil error:nil];
/****
绘制emoji图片
***/
UIImage *emotionContainerView = UIGraphicsGetImageFromCurrentImageContext();
[containerArray addObject:emotionContainerView];
NSData *data = [emotionContainerView yy_imageDataRepresentation];
NSString *dataPath = [folderPath stringByAppendingPathComponent:[NSString stringWithFormat:@"emotion%d", i]];
[data writeToFile:dataPath atomically:YES];
}
self.emotionsContainerViewArray = containerArray;
dispatch_async(dispatch_get_main_queue(), ^{
[self.collectionView reloadData];
});
} else {
JZLog(@"从磁盘加载表情");
NSMutableArray *containerArray = [NSMutableArray new];
for (int i = 0; i < 5; i++) {
NSString *dataPath = [folderPath stringByAppendingPathComponent:[NSString stringWithFormat:@"emotion%d", i]];
NSData *data = [NSData dataWithContentsOfFile:dataPath];
UIImage *emotionContainer = [UIImage imageWithData:data];
[containerArray addObject:emotionContainer];
}
self.emotionsContainerViewArray = containerArray;
dispatch_async(dispatch_get_main_queue(), ^{
[self.collectionView reloadData];
});
}
});
}
结尾
自定义Emoji键盘的就完成的差不多,其主要思路同时通过绘制控件而不是添加控件去减少对象的创建,并且加载缓存数据实现一次绘制多次使用。