博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
iOS_仿QQ表情键盘
阅读量:5928 次
发布时间:2019-06-19

本文共 19037 字,大约阅读时间需要 63 分钟。

当UITextFiled和UITextView这种文本输入类控件成为第一响应者时,弹出的键盘由他们的一个UIView类的inputView属性来控制,当inputView为nil时会弹出系统的键盘,想要弹出自定义的键盘,将我们自定义的UIView对象给inputView属性赋值即可。表情键盘重点在于排列各个表情和删除键,以及表情键盘上的各种回调设置;

下面为键盘预览图,兼容了竖屏各版本适配,横屏没有兼顾。横屏适配参见这篇博客

图1为6的常用表情,图2为6的全部表情,图3为5的全部表情,表情个数统一为7列3排,根据屏幕不同修改间距以及键盘高度;

    

 

下面为项目结构图:采用MVC模式,View层提供表情键盘以及自定义的UITextView。Model层提供表情数据。我为了简单就直接把聊天工具栏通过storyboard拖到了VC上,这里应该再封装一个toolView的;

1、首先来弄好数据层FaceManager,具有一个单例方法、声明了三个数组属性来存放不同的表情;表情图片由Face文件夹来提供;

AllFaces通过我一个名为“emoticons”的plist文件来获取,里面存放的是一个个表情字典,对应着Face中的图片名和图片文字描述;

RecentlyFaces是最近使用过的图片,从本地获取;BigFaces是用来扩展其他大型以及动态效果表情的,没有实现

1 @interface FacesManager : NSObject 2  3 @property (nonatomic, strong, readonly)NSArray * RecentlyFaces; 4 @property (nonatomic, strong, readonly)NSArray * AllFaces; 5 @property (nonatomic, strong, readonly)NSArray * BigFaces; 6  7 + (instancetype)share; 8 - (void)fetchRecentlyFaces; 9 @end10 11 #import "FacesManager.h"12 13 @implementation FacesManager14 15 +(instancetype)share16 {17     static FacesManager * m = nil;18     static dispatch_once_t onceToken;19     dispatch_once(&onceToken, ^{20         m = [[FacesManager alloc] init];21     });22     return m ;23 }24 25 - (instancetype)init26 {27     self = [super init];28     if (self) {29         [self fetchAllFaces];30         [self fetchBigFaces];31     }32     return self;33 }34 35 - (void)fetchAllFaces36 {37     NSString * path = [[NSBundle mainBundle] pathForResource:@"emoticons" ofType:@"plist"];38     NSArray * arrFace = [NSArray arrayWithContentsOfFile:path];39     _AllFaces = arrFace;40 }41 42 - (void)fetchRecentlyFaces43 {44     NSUserDefaults * defauls = [NSUserDefaults standardUserDefaults];45     NSArray * arrFace = [defauls objectForKey:@"RecentlyFaces"];46     _RecentlyFaces = arrFace;47 }48 49 - (void)fetchBigFaces50 {51     52 }53 54 @end
View Code
 

 2、数据层弄好后,实现关键的FaceKeyBoardView;首先在这个view上我们需要向外发送:点击每个表情、发送键、删除键的事件,所以需要提供三个向外的回调接口:点击表情的回调、点击删除的回调、点击发送的回调;然后再分析视图结构,首先view上部贴了一个ScrollView用来滑动显示每一页表情、一个稍微靠下的的pageController用于显示当前页数、以及底部的toolBar;

FaceKeyBoardView.h

define了几个需要用到的参数。设置了三个回调接口;

1 #import 
2 #define GrayColor [UIColor colorWithRed:231 / 255.0 green:231 / 255.0 blue:231 / 255.0 alpha:1] 3 #define ScreenWidth [UIScreen mainScreen].bounds.size.width 4 #define ScreenHeight [UIScreen mainScreen].bounds.size.height 5 #define ToolBarHeight 40 6 7 typedef void (^FaceKeyBoardBlock)(NSString * faceName,NSInteger faceTag); 8 typedef void (^FaceKeyBoardSendBlock)(void); 9 typedef void (^FaceKeyBoardDeleteBlock)(void);10 11 @interface FaceKeyBoardView : UIView12 13 - (void)setFaceKeyBoardBlock:(FaceKeyBoardBlock)block;14 - (void)setFaceKeyBoardSendBlock:(FaceKeyBoardSendBlock)block;15 - (void)setFaceKeyBoardDeleteBlock:(FaceKeyBoardDeleteBlock)block;16 @end
View Code

 

FaceKeyBoardView.m:

用到的属性:

1 @interface FaceKeyBoardView ()
2 3 { 4 CGFloat _FKBViewH; 5 } 6 7 @property (nonatomic, strong)NSArray * arrFace; 8 @property (nonatomic, strong)UIScrollView * scFace; 9 @property (nonatomic, strong)FaceKeyBoardBlock block;10 @property (nonatomic, strong)FaceKeyBoardSendBlock sendBlock;11 @property (nonatomic, strong)FaceKeyBoardDeleteBlock deleteBlock;12 @property (nonatomic, strong)UIToolbar * toolBar;13 @property (nonatomic, strong)UIPageControl * pageC;14 15 @property (nonatomic, strong)FacesManager * FManager;16 17 @end
View Code

 

a、首先来重写view的init方法,在这里面设定好view的frame以及设定view的子控件。由于屏幕尺寸不同,所以表情竖直方向间距不同,就会影响到表情键盘的高度,所以frame是动态计算出来的;然后再loadview方法中初始化facemanager用来提供数据,以及获得全部表情和设置toolBar;

1 - (instancetype)init 2 { 3     self = [super init]; 4     if (self) { 5         [self setViewFrame]; 6         [self loadKeyBoardView]; 7     } 8     return self; 9 }10 11 - (void)setViewFrame12 {13     CGFloat marginY = (ScreenWidth - 7 * 30) / (7 + 1);14     CGFloat scViewH = 3 * (30 + marginY) + marginY*2 + 10;15     _FKBViewH = scViewH + ToolBarHeight;16     self.frame = CGRectMake(0, ScreenHeight - _FKBViewH, ScreenWidth, _FKBViewH);17 }18 19 - (void)loadKeyBoardView20 {21     //初始化manager22     self.FManager = [FacesManager share];23     //获取数据24     [self fetchAllFaces];25     //设置toolBar26     [self setToolBar];27 }
View Code

 

b、设置了三个方法来获取不同的表情数据用于显示不同的表情键盘;第一次时默认执行fetchAllFaces;
1 - (void)fetchRecentlyFaces 2 { 3     //更新manager 4     [self.FManager fetchRecentlyFaces]; 5     self.arrFace = self.FManager.RecentlyFaces; 6     [self setFaceFrame]; 7 } 8  9 - (void)fetchAllFaces10 {11     self.arrFace = self.FManager.AllFaces;12     //设置表情scrollView13     [self setFaceFrame];14 }15 16 - (void)fetchBigFaces17 {18     self.arrFace = nil;19     [self setFaceFrame];20 }
View Code
 

c、在setFaceFrame方法中设置scrollView以及pageController;我通过表情数量来循环设置每个表情按钮的位置,固定了7列三行,根据屏幕设置不同的间距,在每一页的右下角设置删除按钮。根据页数来设置scrollView的内容宽度以及pageController的页数;

1 - (void)setFaceFrame 2 { 3     //列数 4     NSInteger colFaces = 7; 5     //行数 6     NSInteger rowFaces = 3; 7     //设置face按钮frame 8     CGFloat FaceW = 30; 9     CGFloat FaceH = 30;10     CGFloat marginX = (ScreenWidth - colFaces * FaceW) / (colFaces + 1);11     CGFloat marginY = marginX;12     NSLog(@"%lf",marginX);13     14     //表情数量15     NSInteger FaceCount = self.arrFace.count;16     //每页表情数和scrollView页数;17     NSInteger PageFaceCount = colFaces * rowFaces ;18     NSInteger SCPages = FaceCount / PageFaceCount + 1;19 20     CGFloat scViewH = rowFaces * (FaceH + marginY) + marginY*2 + 10;21     //初始化scrollView22     self.scFace = [[UIScrollView alloc] initWithFrame:CGRectMake(0, 0, ScreenWidth, scViewH)];23     self.scFace.contentSize = CGSizeMake(ScreenWidth * SCPages, scViewH);24     self.scFace.pagingEnabled = YES;25     self.scFace.bounces = NO;26     self.scFace.delegate = self;27     [self addSubview:self.scFace];28     //初始化贴在sc上的view29     UIView * BtnView = [[UIView alloc] init];30     BtnView.frame = CGRectMake(0, 0, ScreenWidth * SCPages, scViewH);31     [BtnView setBackgroundColor:GrayColor];32     [self.scFace addSubview:BtnView];33     34     for (NSInteger i = 0; i < FaceCount + SCPages; i ++)35     {36         //当前页数37         NSInteger currentPage = i / PageFaceCount;38         //当前行39         NSInteger rowIndex = i / colFaces - (currentPage * rowFaces);40         //当前列41         NSInteger colIndex = i % colFaces;42         43         //viewW * currentPage换页44         CGFloat btnX = marginX + colIndex * (FaceW + marginX) + ScreenWidth * currentPage;45         CGFloat btnY = rowIndex * (marginY + FaceH) + marginY;46         if ((i - (currentPage + 1) * (PageFaceCount - 1) == currentPage || i == FaceCount + SCPages - 1) && self.arrFace)47         {48             //创建删除按钮49             CGFloat btnDelteX = (currentPage + 1) * ScreenWidth - (marginX + FaceW);50             CGFloat btnDelteY = 2 * (FaceH + marginY) +marginY;51             52             UIButton * btnDelte = [UIButton buttonWithType:UIButtonTypeSystem];53             btnDelte.frame = CGRectMake(btnDelteX, btnDelteY, FaceW, FaceH);54             [btnDelte setBackgroundImage:[UIImage imageNamed:@"icon_delete-2"] forState:UIControlStateNormal];55             [btnDelte setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];56             btnDelte.titleLabel.font = [UIFont boldSystemFontOfSize:15];57             58             [btnDelte addTarget:self action:@selector(tapDeleteBtn) forControlEvents:UIControlEventTouchUpInside];59             60             [BtnView addSubview:btnDelte];61         }62         else63         {64         //创建face按钮65         UIButton * btn = [[UIButton alloc] init];66         btn.frame = CGRectMake(btnX , btnY, FaceW, FaceH);67         //tga68         btn.tag = i - currentPage;69         //按钮回调;70         [btn addTarget:self action:@selector(tapFaceBtnWithButton:) forControlEvents:UIControlEventTouchUpInside];71         NSString * strIMG = self.arrFace[i - currentPage][@"png"];72         [btn setImage:[UIImage imageNamed:strIMG] forState:UIControlStateNormal];73         [BtnView addSubview:btn];74         }75     }76     77     //创建pageController78     CGFloat pageH = 10;79     CGFloat pageW = ScreenWidth;80     CGFloat pageX = 0;81     CGFloat pageY = scViewH - pageH - marginY;82     self.pageC = [[UIPageControl alloc] initWithFrame:CGRectMake(pageX, pageY, pageW, pageH)];83     self.pageC.numberOfPages = SCPages;84     self.pageC.currentPage = 0;85     self.pageC.pageIndicatorTintColor = [UIColor lightGrayColor];86     self.pageC.currentPageIndicatorTintColor = [UIColor grayColor];87     [self addSubview:self.pageC];88 }
View Code

 

b、还需要把c中的各种点击事件和scrollView的代理事件实现

点击表情的事件中我执行了2个操作:1、将点击的表情存到本地常用表情数组中,逻辑为,如果数组中已有这个表情,就将此表情移到最前面,没有就将表情插入到数组第一位。这里我想着数据量不是很大就使用了Preference来存放本地;2、将表情的文字描述和表情按钮的tga通过block传出去;

当scrollView翻动时,让pageController的当前页数跟着变化;点击删除发送回调;

1 //点击表情 2 - (void)tapFaceBtnWithButton:(UIButton *)button 3 { 4     //将表情存储为常用表情 5     NSUserDefaults * defaults = [NSUserDefaults standardUserDefaults]; 6     NSMutableArray * arrFaces = (NSMutableArray *)[defaults objectForKey:@"RecentlyFaces"]; 7      8     if (!arrFaces) 9     {10         arrFaces = [NSMutableArray array];11         NSDictionary * dicFace = @{
@"png":self.arrFace[button.tag][@"png"],@"faceTag":@(button.tag),@"chs":self.arrFace[button.tag][@"chs"]};12 [arrFaces addObject:dicFace];13 [defaults setObject:arrFaces forKey:@"RecentlyFaces"];14 [defaults synchronize];15 }16 //NSLog(@"%p",arrFaces);17 else18 {19 //需要新建一个可变数组,不然修改数组会报错。20 NSMutableArray * arrM = [NSMutableArray arrayWithArray:arrFaces];21 BOOL isHaveSameFace = NO;22 for (NSDictionary * dic in arrFaces)23 {24 //NSLog(@"%ld--%ld",button.tag,[dic[@"faceTag"] integerValue]);25 NSString * strFace = self.arrFace[button.tag][@"chs"];26 NSString * strFaceDic = dic[@"chs"];27 if ([strFace isEqualToString:strFaceDic])28 {29 [arrM removeObject:dic];30 NSLog(@"%@",dic);31 //后添加的排在前面;32 [arrM insertObject:dic atIndex:0];33 isHaveSameFace = YES;34 }35 }36 if (!isHaveSameFace)37 {38 NSDictionary * dicFace = @{
@"png":self.arrFace[button.tag][@"png"],@"faceTag":@(button.tag),@"chs":self.arrFace[button.tag][@"chs"]};39 [arrM insertObject:dicFace atIndex:0];40 }41 [defaults setObject:arrM forKey:@"RecentlyFaces"];42 [defaults synchronize];43 }44 //block传值45 self.block(self.arrFace[button.tag][@"chs"],button.tag);46 }47 48 //点击删除49 - (void)tapDeleteBtn50 {51 self.deleteBlock();52 }53 54 -(void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset55 {56 self.pageC.currentPage = targetContentOffset->x / ScreenWidth;57 }
View Code

d、设置toolBar、然后将各个按钮的点击事件实现:

1 - (void)setToolBar 2 { 3     self.toolBar = [[UIToolbar alloc] initWithFrame:CGRectMake(0, self.scFace.frame.size.height, ScreenWidth, ToolBarHeight)]; 4     self.toolBar.backgroundColor = GrayColor; 5  6     [self addSubview:self.toolBar]; 7     UIBarButtonItem * spaceItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFlexibleSpace target:nil action:nil]; 8     UIBarButtonItem * recentlyFaceItem = [[UIBarButtonItem alloc] initWithTitle:@"最近表情" style:UIBarButtonItemStylePlain target:self action:@selector(tapRecentlyFaceBtn)]; 9     UIBarButtonItem * normalFaceItem = [[UIBarButtonItem alloc] initWithTitle:@"普通" style:UIBarButtonItemStylePlain target:self action:@selector(tapNormalFaceBtn)];10     UIBarButtonItem * bigFaceItem = [[UIBarButtonItem alloc] initWithTitle:@"大表情" style:UIBarButtonItemStylePlain target:self action:@selector(tapBigFaceBtn)];11     UIBarButtonItem * sendItem = [[UIBarButtonItem alloc] initWithTitle:@"发送" style:UIBarButtonItemStylePlain target:self action:@selector(tapSendBtn)];12 13     [self.toolBar setItems:[NSArray arrayWithObjects:recentlyFaceItem,spaceItem,normalFaceItem,spaceItem,bigFaceItem,spaceItem,sendItem, nil]];14 }15 16 17 //点击ToolBar上的按钮回调18 - (void)tapRecentlyFaceBtn19 {20     [self fetchRecentlyFaces];21 }22 - (void)tapSendBtn23 {24     self.sendBlock();25 }26 - (void)tapBigFaceBtn{27     [self fetchBigFaces];28 }29 - (void)tapNormalFaceBtn30 {31     [self fetchAllFaces];32 }
View Code

 

e、还要实现三个设置回调接口的方法、此时faceKeyBoardView中的方法都实现了;

1 //点击表情接口 2 - (void)setFaceKeyBoardBlock:(FaceKeyBoardBlock)block 3 { 4     self.block = block; 5 } 6 //发送接口 7 -(void)setFaceKeyBoardSendBlock:(FaceKeyBoardSendBlock)block 8 { 9     self.sendBlock = block;10 }11 //删除接口12 -(void)setFaceKeyBoardDeleteBlock:(FaceKeyBoardDeleteBlock)block13 {14     self.deleteBlock = block;15 }
View Code

 

3、自定义一个UITextView、也可以使用UITextFiledView

.h:具有一个发送回调以及切换键盘状态的方法:

1 #import "XQGTextView.h"2 #import "FaceKeyBoardView.h"3 4 @interface XQGTextView ()5 6 @property (nonatomic, strong)FaceKeyBoardView * viewFaceKB;7 @property (nonatomic, strong)SendBlock block;8 @end
View Code

 

.m:实现方法以及初始化表情键盘,在faceKeyBoard发送回调和删除回调中实现需要做的事;

1 @implementation XQGTextView 2  3 - (instancetype)initWithFrame:(CGRect)frame 4 { 5     self = [super initWithFrame:frame]; 6     if (self) { 7         [self loadFaceKeyBoardView]; 8     } 9     return self;10 }11 12 - (void)awakeFromNib13 {14     [self loadFaceKeyBoardView];15 }16 17 - (void)loadFaceKeyBoardView18 {19     self.viewFaceKB = [[FaceKeyBoardView alloc] init];20     21     __weak __block XQGTextView * copy_self = self;22     23     [self.viewFaceKB setFaceKeyBoardBlock:^(NSString *faceName, NSInteger faceTag) {24         copy_self.text = [copy_self.text stringByAppendingString:faceName];25     }];26     27     [self.viewFaceKB setFaceKeyBoardSendBlock:^{28         copy_self.block();29         //清空textview30         copy_self.text = nil;31     }];32     [self.viewFaceKB setFaceKeyBoardDeleteBlock:^{33         NSMutableString * string = [[NSMutableString alloc] initWithString:copy_self.text];34         [string deleteCharactersInRange:NSMakeRange(copy_self.text.length - 1, 1)];35         copy_self.text = string;36     }];37 }38 39 -(void)changeKeyBoard40 {41     if (self.inputView != nil)42     {43         self.inputView = nil;44         [self reloadInputViews];45     }46     else47     {48         self.inputView = self.viewFaceKB;49         [self reloadInputViews];50     }51 }52 53 - (void)setFaceKeyBoard54 {55     self.inputView = self.viewFaceKB;56 }57 58 - (void)setSendBlock:(SendBlock)block59 {60     self.block = block;61 }62 63 @end
View Code

 

4、在VC中进行操作:

a、在storyBoard中构建聊天工具栏:将textView绑定为我自定义的textView;

b、在发送回调中进行发送消息,利用正则表达式解析出表情发送图文混排的消息;以及聊天工具栏跟随键盘高度变化而变化的设置等;点击表情按钮弹出表情键盘,键盘切换;代码中都有详细描述;

.m:

1 #import "MainViewController.h"  2 #import "XQGTextView.h"  3   4 @interface MainViewController ()
5 6 @property (weak, nonatomic) IBOutlet XQGTextView *viewText; 7 @property (weak, nonatomic) IBOutlet UIView *viewChatToolBar; 8 @property (weak, nonatomic) IBOutlet UILabel *lalText; 9 10 @end 11 12 @implementation MainViewController 13 14 - (void)viewDidLoad { 15 [super viewDidLoad]; 16 //清空常用表情 17 // NSUserDefaults * defaults = [NSUserDefaults standardUserDefaults]; 18 // NSMutableArray * arrFaces = [defaults objectForKey:@"RecentlyFaces"]; 19 // arrFaces = nil; 20 // [defaults setObject:arrFaces forKey:@"RecentlyFaces"]; 21 22 self.viewText.delegate = self; 23 //发送回调 24 [self.viewText setSendBlock:^{ 25 [self sendPictureAndText]; 26 }]; 27 //监听键盘弹出的通知 28 NSNotificationCenter *center = [NSNotificationCenter defaultCenter]; 29 [center addObserver:self selector:@selector(KeyBoardWillChangeFrame:) name:UIKeyboardWillChangeFrameNotification object:nil]; 30 } 31 32 //发送图文 33 - (void)sendPictureAndText 34 { 35 //正则表达式取出表情 36 NSString * str = self.viewText.text; 37 NSMutableAttributedString * strAtt = [[NSMutableAttributedString alloc] initWithString:str]; 38 //创建匹配正则表达式类型描述模板 39 NSString * pattern = @"\\[[a-zA-Z0-9\\u4e00-\\u9fa5]+\\]"; 40 //依据正则表达式创建匹配对象 41 NSError * error = nil; 42 //CaseInsensitive 43 NSRegularExpression * regular = [NSRegularExpression regularExpressionWithPattern:pattern options:NSRegularExpressionCaseInsensitive error:&error]; 44 if (regular == nil) 45 { 46 NSLog(@"正则创建失败"); 47 NSLog(@"%@",error.localizedDescription); 48 return; 49 } 50 //把搜索出来的结果存到数组中 51 NSArray * result = [regular matchesInString:strAtt.string options:NSMatchingReportCompletion range:NSMakeRange(0, strAtt.string.length)]; 52 53 NSString * path = [[NSBundle mainBundle] pathForResource:@"emoticons.plist" ofType:nil]; 54 NSArray * arrPlist = [NSArray arrayWithContentsOfFile:path]; 55 56 for (NSInteger i = result.count - 1; i >= 0; i--) 57 { 58 NSTextCheckingResult * r = result[i]; 59 //NSLog(@"%@",NSStringFromRange(r.range)); 60 NSString * imageStr = [strAtt.string substringWithRange:r.range]; 61 //NSLog(@"%@",imageStr); 62 63 for (NSDictionary * dic in arrPlist) 64 { 65 if ([dic[@"chs"] isEqualToString:imageStr]) 66 { 67 NSTextAttachment * textAtt = [[NSTextAttachment alloc] init]; 68 textAtt.image = [UIImage imageNamed:dic[@"png"]]; 69 NSAttributedString * strImage = [NSAttributedString attributedStringWithAttachment:textAtt]; 70 [strAtt replaceCharactersInRange:r.range withAttributedString:strImage]; 71 } 72 } 73 } 74 self.lalText.attributedText = strAtt; 75 } 76 77 //监听键盘弹出的方法 78 -(void)KeyBoardWillChangeFrame: (NSNotification *)noteInfo 79 { 80 //获取键盘的Y值 81 CGRect keySize = [noteInfo.userInfo[UIKeyboardFrameEndUserInfoKey] CGRectValue]; 82 CGFloat keyY = keySize.origin.y; 83 //让view跟随键盘移动 84 CGFloat viewY = keyY - self.view.bounds.size.height; 85 //让view变化和键盘变化一致 86 self.view.transform = CGAffineTransformMakeTranslation(0, viewY); 87 } 88 89 -(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event 90 { 91 UITouch * touch = [touches anyObject]; 92 if ([touch.view isEqual:self.view]) { 93 [self.view endEditing:YES]; 94 } 95 } 96 //监控编辑结束状态 97 -(void)textViewDidEndEditing:(UITextView *)textView 98 { 99 self.viewText.inputView = nil;100 }101 102 103 - (IBAction)tapVoice:(UIButton *)sender {104 NSLog(@"切换语音");105 }106 107 - (IBAction)tapFace:(UIButton *)sender108 {109 //如果还没弹出键盘就直接弹出表情键盘;弹出了就改变键盘样式110 if (self.viewText.isFirstResponder)111 {112 [self.viewText changeKeyBoard];113 }114 else115 {116 [self.viewText setFaceKeyBoard];117 [self.viewText becomeFirstResponder];118 }119 }120 121 - (IBAction)tapMoreFunction:(UIButton *)sender {122 NSLog(@"更多功能");123 }124 125 126 @end
View Code

 

这样,自定义表情键盘就实现了,聊天工具栏还需要进行进一步的封装;

 

转载于:https://www.cnblogs.com/xiaoqiuge/p/4858057.html

你可能感兴趣的文章
EA - indicator
查看>>
牵引力技术部告诉你,学java哪里好?
查看>>
阿里云智能对话分析服务使用教程
查看>>
做了「负载均衡」就可以随便加机器了吗?这三招来帮你!
查看>>
leetCode 8 String to Integer
查看>>
让我们荡起双桨,Android 小船波浪动画
查看>>
Angular 6 服务端渲染之 udao 终章
查看>>
数字货币跨品种对冲的探讨及DEMO
查看>>
Java集合系列之LinkedHashMap
查看>>
前端面试中容易让你忽略的知识点(一)
查看>>
android 多aar上传私人maven服务器
查看>>
iOS隐藏某个页面导航栏的方法
查看>>
Vue源码中为什么要const _toStr = Object.prototype.toString?
查看>>
数组扁平化
查看>>
《ios编程第四版》随感,随想
查看>>
微信小程序全局状态管理,并提供Vuex的开发体验
查看>>
分布式相关部分笔记整理
查看>>
OceanBase 负载均衡的魅力
查看>>
werkzeug源码分析——从官网的示例代码开始
查看>>
Android Studio Plugin 插件开发教程(一) —— 开发你的第一个插件
查看>>