IOS控件系列--优雅的表情键盘设计(扩展性好)

本控件效果如下图所示:


本控件设计的几个原则 :

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];


5.下面的索引点就非常简单了,直接监听滚动列表的滑动事件即可达到。


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----------------





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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值