本控件效果如下图所示:
本控件设计的几个原则 :
1.扩展性好,代码不冗余。
2.逻辑清晰。
3.回调接口清晰。
4.移植性好。
首先来讨论下本控件的扩展性:
效果图给出的是3行7列。如果这个行列因子可控,起码可以应付策划哪一天突发奇想想重新设计这个表情键盘的行列,如果写死在代码中,就不太方便了。
因此这个因子可以抛出来。
抛出的代码如下:
@property(nonatomic,assign) NSInteger rowCount; //行数@property(nonatomic,assign) NSInteger colCount; //列数@property(nonatomic,assign) NSInteger pageCount; //页数
对应的逻辑为:
把表情键盘中的元素看作是一个硬盘的带分布索引的二维数组即可,元素地列表中位置计算代码如下:
-(void) buildElements:(NSMutableArray*) elements{if([elements count] == 0) return;NSInteger rowMargin = (self.frame.size.width - self.colCount * 30) / (self.colCount + 1); //行间距NSInteger colMargin = (self.frame.size.height - self.rowCount * 30) / (self.rowCount + 1); //列间距//计算每一个表情的位置for(int k = 0; k < self.pageCount; k ++){for(int i = 0; i < self.rowCount; i ++){for(int j = 0; j < self.colCount; j ++){UIImageView* emojiView = [[UIImageView alloc] initWithFrame:CGRectMake(rowMargin + (k * self.frame.size.width) + j * (30 + rowMargin),colMargin + i * (30 + colMargin), 30, 30)];NSInteger idx = self.rowCount * self.colCount * k + i * self.colCount + j;if(idx >= [elements count]){idx = [elements count] - 1;break;}emojiView.contentMode = UIViewContentModeScaleAspectFit;emojiView.image = [UIImage imageNamed:elements[idx]];emojiView.tag = idx;emojiView.userInteractionEnabled = true;[emojiView addGestureRecognizer:[[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleGesture:)]];[self addSubview:emojiView];}}}}
为了均分滚动列表的size,上面的代码中将间距给计算了,注意计算方式。
3.为了能够滚动,可以扩展下UIScrollView,然后监听ScrollView的contentSize 来更新表情键盘的页数。这里使用了kvo来进行监听,页数的计算式如下:
//-------------------kvo 实现观察主题 ----------------
//对于滑动翻页使用kvo监听机制
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context{CGPoint offset = [change[NSKeyValueChangeNewKey] CGPointValue];CGPoint oldOffset = [change[NSKeyValueChangeOldKey] CGPointValue];CGFloat oldOffsetX = oldOffset.x;CGFloat deltaOfOffsetX = offset.x - oldOffsetX;NSInteger nCurIndex = deltaOfOffsetX / SCREEN_WIDTH;[self updatePointWithIdx:nCurIndex];}-(void)dealloc{[self.emojiPanel removeObserver:self forKeyPath:@"contentOffset" context:nil];
}
//-------------------kvo 实现观察主题 end----------------
4.有些策划比较细致,注意用户体验。给每一页的最后一个表情设定为“删除”
其实这个计算在加载表情资源的时候做一个小处理就行,处理的计算逻辑为:
if(保存的表情的列表当前长度+1 % (行*列)==0 || 保存的表情的列表当前长度 -1) {
这个列表就保存上面那个删除表情。
}
计算代码如下:
NSMutableArray* emojiList = [NSMutableArray array];NSInteger total = 54;for(int i = 0;i < total; i ++){if( i < 10){[emojiList addObject:[NSString stringWithFormat:@"f00%d",i]];}if(i >= 10 && i < 100){if( ([emojiList count] + 1) % 21 == 0 || i == total - 1){[emojiList addObject:@"f_del.png"];}else{[emojiList addObject:[NSString stringWithFormat:@"f0%d",i]];}}}int pageCount = ceilf([emojiList count] / 21.0);self.emojiPanel = [[ScrollView alloc] initWithFrame:CGRectMake(0, 50, SCREEN_WIDTH, 210)];self.emojiPanel.backgroundColor = [UIColor greenColor];self.emojiPanel.rowCount = 3;self.emojiPanel.colCount = 7;self.emojiPanel.pageCount = pageCount;self.emojiPanel.pagingEnabled = true;self.emojiPanel.contentSize = CGSizeMake(self.emojiPanel.frame.size.width * pageCount, 210);[self.emojiPanel buildElements:emojiList];//使用kvo监听ScrollView滑动监听事件[self.emojiPanel addObserver:self forKeyPath:@"contentOffset" options:NSKeyValueObservingOptionNew context:nil];self.emojiPanel.emojiClick = ^(NSInteger idx){NSLog(@"271------------:%ld",idx);};[self.view addSubview:self.emojiPanel];
6.回调的接口较简单,具体见代码,在此不作分析。
下面奉送全部代码:
.h文件:
#import <UIKit/UIKit.h>//为表情添加一个回调
typedef void (^EmojiOnClickListener) (NSInteger);/**滑动面板,主要实现,数据分布显示*/
@interface ScrollView : UIScrollView@property(nonatomic,assign) NSInteger rowCount; //行数@property(nonatomic,assign) NSInteger colCount; //列数@property(nonatomic,assign) NSInteger pageCount; //页数@property(nonatomic,strong) NSMutableArray* dataList;-(void) buildElements:(NSMutableArray*) elements;/**普通样式的block 表情点击回调*/
@property (nonatomic, copy) void (^onClickBlock)(NSInteger idx);//对于typedef定义的block其实是一个匿名函数,此处声明一个全局变量保持这个函数引用
@property(nonatomic,copy) EmojiOnClickListener emojiClick;@end
。m文件:
#import "ScrollView.h"@implementation ScrollView-(instancetype) initWithFrame:(CGRect)frame{if(self = [super initWithFrame:frame]){}return self;}-(void) buildElements:(NSMutableArray*) elements{if([elements count] == 0) return;NSInteger rowMargin = (self.frame.size.width - self.colCount * 30) / (self.colCount + 1); //行间距NSInteger colMargin = (self.frame.size.height - self.rowCount * 30) / (self.rowCount + 1); //列间距//计算每一个表情的位置for(int k = 0; k < self.pageCount; k ++){for(int i = 0; i < self.rowCount; i ++){for(int j = 0; j < self.colCount; j ++){UIImageView* emojiView = [[UIImageView alloc] initWithFrame:CGRectMake(rowMargin + (k * self.frame.size.width) + j * (30 + rowMargin),colMargin + i * (30 + colMargin), 30, 30)];NSInteger idx = self.rowCount * self.colCount * k + i * self.colCount + j;if(idx >= [elements count]){idx = [elements count] - 1;break;}emojiView.contentMode = UIViewContentModeScaleAspectFit;emojiView.image = [UIImage imageNamed:elements[idx]];emojiView.tag = idx;emojiView.userInteractionEnabled = true;[emojiView addGestureRecognizer:[[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleGesture:)]];[self addSubview:emojiView];}}}}-(void) handleGesture:(UITapGestureRecognizer*) gestureRecognizer{UIView* viewClicked=[gestureRecognizer view];NSLog(@"66---------------:%ld",(long)viewClicked.tag);if(self.emojiClick){self.emojiClick(viewClicked.tag);}}@end
在vc中的测试代码如下:
//测试表情键盘
-(void) testEmojiPanel{NSMutableArray* emojiList = [NSMutableArray array];NSInteger total = 54;for(int i = 0;i < total; i ++){if( i < 10){[emojiList addObject:[NSString stringWithFormat:@"f00%d",i]];}if(i >= 10 && i < 100){if( ([emojiList count] + 1) % 21 == 0 || i == total - 1){[emojiList addObject:@"f_del.png"];}else{[emojiList addObject:[NSString stringWithFormat:@"f0%d",i]];}}}int pageCount = ceilf([emojiList count] / 21.0);self.emojiPanel = [[ScrollView alloc] initWithFrame:CGRectMake(0, 50, SCREEN_WIDTH, 210)];self.emojiPanel.backgroundColor = [UIColor greenColor];self.emojiPanel.rowCount = 3;self.emojiPanel.colCount = 7;self.emojiPanel.pageCount = pageCount;self.emojiPanel.pagingEnabled = true;self.emojiPanel.contentSize = CGSizeMake(self.emojiPanel.frame.size.width * pageCount, 210);[self.emojiPanel buildElements:emojiList];//使用kvo监听ScrollView滑动监听事件[self.emojiPanel addObserver:self forKeyPath:@"contentOffset" options:NSKeyValueObservingOptionNew context:nil];self.emojiPanel.emojiClick = ^(NSInteger idx){NSLog(@"271------------:%ld",idx);};[self.view addSubview:self.emojiPanel];//构建索引点[self buildPagePoint];}//private --method 构建索引点
-(void) buildPagePoint{if(self.emojiPanel.pageCount == 0) return;NSInteger pointTotalWidth = 100; //假设索引点占用宽度NSInteger pointWidth = 10;NSInteger startOffsetX = (self.emojiPanel.frame.size.width - pointTotalWidth) / 2; //起始偏移量NSInteger offsetX = (pointTotalWidth - self.emojiPanel.pageCount * pointWidth) / (self.emojiPanel.pageCount + 1); //偏移量for(int i = 0; i < self.emojiPanel.pageCount; i ++){UIImageView* pointIndexView = [[UIImageView alloc] initWithFrame:CGRectMake(startOffsetX + offsetX + i *(pointWidth + offsetX),270,pointWidth,pointWidth)];if(i == 0){pointIndexView.backgroundColor = [UIColor redColor];}else{pointIndexView.backgroundColor = [UIColor grayColor];}pointIndexView.tag = 100 + i;pointIndexView.layer.masksToBounds = true;pointIndexView.layer.cornerRadius = pointWidth / 2;[self.view addSubview:pointIndexView];}}//这个地方俺是故意这样写滴,,^_^
-(void)updatePointWithIdx:(NSInteger) idx{for(int i = 0 ; i < self.emojiPanel.pageCount; i ++){if( i == idx ){[self.view viewWithTag:(100 + i)].backgroundColor = [UIColor redColor];}else{[self.view viewWithTag:(100 + i)].backgroundColor = [UIColor grayColor];}}}//-------------------kvo 实现观察主题 ----------------
//对于滑动翻页使用kvo监听机制
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context{CGPoint offset = [change[NSKeyValueChangeNewKey] CGPointValue];CGPoint oldOffset = [change[NSKeyValueChangeOldKey] CGPointValue];CGFloat oldOffsetX = oldOffset.x;CGFloat deltaOfOffsetX = offset.x - oldOffsetX;NSInteger nCurIndex = deltaOfOffsetX / SCREEN_WIDTH;[self updatePointWithIdx:nCurIndex];}-(void)dealloc{[self.emojiPanel removeObserver:self forKeyPath:@"contentOffset" context:nil];
}
//-------------------kvo 实现观察主题 end----------------