最近制作自制表情键盘的时候,突然了解到还有一个叫做UICollectionView (集合视图)的类,就研究了一下,确实在做表情键盘上要比用 UIScrollView(滚动视图) 要简单的多,用法与 UITableView(表格视图) 相似,但楼主觉得稍微麻烦那么一点点,就是因为它必须要自顶一个cell,这个cell的类型是UICollectionViewCell,用到的仅仅是初级的用法,高级用法也请查看其他资料。

这一次按照自己的理解,用了一下自认为的MVC架构模式,如理解不妥,特请指正,话不多说,首先看一我的工程目录吧

接着看一下运行的结果吧,因为加了约束,所以横屏竖屏都是可以的

其实一般情况下,打印的表情发送到服务器的形式就是文本中的形式,以[  ]作为标识符,如果想在文本框中打出表情也是可以的,可以去看一下之前的一篇博客 iOS学习-------文字表情(NSAttributeString 属性字符串 以及 NSRegularExpression 正则表达类)

因为表情是通过本地的plist文件来读取的,所以首先看一下plist文件

整体是一个数组,每个数组元素都是一个字典,那么看到字典,首先想到的就是模型了

Model(模型)

首先创建一个表情类,即Emoticon类,存取数据的,以后的操作就是对表情(Emoticon)类的操作,而不是对字典的操作,首先是头文件
//
//  Emoticon.h
//  表情键盘
//
//  Created by YueWen on 15/10/3.
//  Copyright (c) 2015年 YueWen. All rights reserved.
//#import <UIKit/UIKit.h>@interface Emoticon : NSObject/***  繁体的汉字*/
@property(nonatomic,strong)NSString * cht;/***  动态图名*/
@property(nonatomic,strong)NSString * gif;/***  种类*/
@property(nonatomic,strong)NSNumber * type;/***  简体的汉字*/
@property(nonatomic,strong)NSString * chs;/***  静态图名*/
@property(nonatomic,strong)NSString * png;/***  自定义初始化方法**  @param dict 含参字典**  @return 返回创建好的Emoticon对象*/
-(instancetype)initWithDictionary:(NSDictionary *)dict;/***  便利构造器**  @param dict 含参字典**  @return 返回创建好的Emoticon对象*/
+(instancetype)emoticonWithDictionary:(NSDictionary *)dict;@end

模型类很简单,只需要在实现.m文件中,实现 便利初始化方法 以及 便利构造器 即可,如果对便利初始化方法以及便利构造器不熟悉,也欢迎去之前的博客Objective-C学习- 便利初始化函数和便利构造器去复习一下。实现方法如下

首先实现便利初始化方法
- (instancetype)initWithDictionary:(NSDictionary *)dict
{self = [super init];if (self){//通过MVC进行赋值[self setValuesForKeysWithDictionary:dict];}return self;
}
目前只见过3种 '+' 方法,便利构造器,单例以及纯过程封装,下面便是便利构造器
+(instancetype)emoticonWithDictionary:(NSDictionary *)dict
{__autoreleasing Emoticon * emoticon = [[Emoticon alloc]initWithDictionary:dict];return emoticon;
}

因为不是很过复杂,所以没有写Manager(管理者),一般情况下,Manager是一个单例。

View   (视图)

自定义的UICollectionViewCell

因为在UICollectionView(集合视图)中,没有默认的Cell,这点不像是UITableViewCell,因为没有用storyBoard拖东西,所以用的纯代码进行的布局,首先创建了一个叫做BaseCollectionViewCell (Cell 基类) ,为了以后好拓展,虽然看起来在做这个的时候会麻烦点。
创建的基类(BaseCollectionViewCell)如下
#import <UIKit/UIKit.h>
#import "Emoticon.h"typedef void(^emoticonButtonClickBlock)(UIImage * buttonImage,NSString * imageName);@interface BaseCollectionViewCell : UICollectionViewCell/***  根据表情模型赋值**  @param emoticon 表情模型*/
-(void)setInformationWithEmoticon:(Emoticon *)emoticon;/***  设置block回调**  @param emoticonButtonBlock 回调的代码块*/
-(void)emotionButtonClickBlockHandle:(emoticonButtonClickBlock)emoticonButtonBlock;@end

实现方法木有,因为实现几乎没有意义

接着就是自定义的表情cell了,思路就是在cell上放置一个 button ,让 button 的 frame 大小等于 cell 的bounds,当点击的时候,通过回调返回点击按钮的表情以及文字标识符。但是返回的属性UIImage 和 imageName 是不能被修改的,所以属性上用的 readOnly,头文件如下
#import <UIKit/UIKit.h>typedef void(^emoticonButtonClickBlock)(UIImage * buttonImage,NSString * imageName);@interface EmoticonCell : UICollectionViewCell/***  button上的表情*/
@property(nonatomic,strong,readonly)UIImage * buttonImage;/***  button上表情的名字*/
@property(nonatomic,strong,readonly)NSString * imageName;@end

因为继承了BaseCollectionViewCell,所以在基类中没有实现的方法需要在EmoticonCell类中进行实现,首先实现基类中的两个方法

/***  设置block回调**  @param emoticonButtonBlock 回调的代码块*/
-(void)emotionButtonClickBlockHandle:(emoticonButtonClickBlock)emoticonButtonBlock
{self.b = emoticonButtonBlock;
}/***  根据表情模型赋值**  @param emoticon 表情模型*/
-(void)setInformationWithEmoticon:(Emoticon *)emoticon
{UIImage * image = [UIImage imageNamed:emoticon.png];[self setButtonImage:image WithImageName:emoticon.chs];}
在setInfomationWithEmoticon:方法中用到了一个自定义的设置的方法,是之前写的,又不想删除,所以就起到了一个简化代码的作用,很简单的实现,如下
/***  设置button上按钮的图片和图片名字**  @param buttonImage 按钮上的图片*  @param imageName   按钮上图片的名字*/
-(void)setButtonImage:(UIImage *)buttonImage WithImageName:(NSString *)imageName
{_buttonImage = buttonImage;_imageName = imageName;//设置button属性[self.emoticonButton setImage:_buttonImage forState:UIControlStateNormal];
}

因为Cell的基类说到底就是UIView,所以必须要重写两个创建方法,如下

//用纯代码创建的时候走的创建方法
-(instancetype)initWithFrame:(CGRect)frame
{if (self = [super initWithFrame:frame]){[self cellInit];}return self;
}//用xib或者storyboard创建的时候走的创建方法
-(void)awakeFromNib
{[self cellInit];
}

因为cellInit是自定义的创建方式,自然每个创建方法都要用,所以楼主打包成一个方法,偷个懒0.0

-(void)cellInit
{//初始化按钮self.emoticonButton = [UIButton buttonWithType:UIButtonTypeCustom];//设置大小self.emoticonButton.frame = self.bounds;//注册回调方法[self.emoticonButton addTarget:self action:@selector(emoticonButtonClick) forControlEvents:UIControlEventTouchUpInside];//添加button[self addSubview:self.emoticonButton];}

当button被点击的时候,之前的回调就有用了

/***  按钮被点击的时候,对父控件进行回调*/
-(void)emoticonButtonClick
{//如果自己的回调被赋值后,才进行回调if (self.b){self.b(_buttonImage,_imageName);}
}

这样自定义的Cell类就完成了。

EmoticonsView(展示表情的视图)

EmoticonsView是在这里面最复杂的一个视图了,与其说它复杂不是因为逻辑复杂,也不是因为代码多,而是这里面用到了一个以前没有接触过的UICollectionView(集合视图)类,之前的一切也都是为了它再做铺垫,但是好处就是他的用法与UITableView(表格视图很像),这也提供了一些参考,话不多说,希望代码的写法能给大家一些启发,自然这里的用法都是最基础的用法,并不能代表这个组件已经完全驾驭。
首先因为他不是最后的boss(ViewController),所以也是需要汇报的,自然是少不了回调的,因为用Block回调比较多,所以依旧选择用Block回调,下面是声明文件.h中的声明:
#import <UIKit/UIKit.h>typedef void(^emoticonsViewWithButtonPressedBlock)(UIImage * image, NSString * imageName);@interface EmoticonsView : UIView/***  设置block回调**  @param buttonPressedBlock 返回代码块*/
-(void)emoticonsViewWithButtonPressedBlockHandle:(emoticonsViewWithButtonPressedBlock)buttonPressedBlock;@end

接着就是在延展中,加入三个属性对象,分别用来显示表情(collectionView),存储表情的信息(emoticons),以及回调的方法Block(b),但是有一点不同,要用UICollectionView,要遵守三个协议,不是UITableView的两个协议,因为多了一个类似布局的协议,如下

@interface EmoticonsView ()<UICollectionViewDataSource,UICollectionViewDelegate,UICollectionViewDelegateFlowLayout>//视图集合
@property(nonatomic,strong)UICollectionView * collectionView;//存储表情的数组
@property(nonatomic,strong)NSArray * emoticons;//回调代码块
@property(nonatomic,strong)emoticonsViewWithButtonPressedBlock b;@end

因为是view,所以重写的那两个方法不再赘余,只说明自写的init方法

-(void)viewInit
{//配置collectionView[self loadCollectionView];//注册cell[self registerClassWithCell];//加载数组self.emoticons = [self loadEmoticons];//贴到视图上[self addSubview:self.collectionView];//开始适配[self layoutCollectionView];
}

先从最简单的开始,加载数组,思路是先从plist文件中加载数据,然后通过遍历出的字典,通过字典转模型,再返回处理好的数组,如下

#pragma mark - 表情数组的加载
/***  加载表情数据**  @return 返回存储表情的数组*/
-(NSArray *)loadEmoticons
{//路径NSString * path = [[NSBundle mainBundle]pathForResource:@"emoticons" ofType:@"plist"];//加载数据NSArray * emoticons = [NSArray arrayWithContentsOfFile:path];//整合的数组NSMutableArray * mutableEmoticons = [NSMutableArray array];//可变数组进行数据整合for (NSDictionary * infoDict in emoticons){//字典转模型Emoticon * emoticon = [Emoticon emoticonWithDictionary:infoDict];//可变数组添加[mutableEmoticons addObject:emoticon];}return [NSArray arrayWithArray:mutableEmoticons];
}

因为没有storyboard直接用UICollectionViewController,所以要结合自定义cell,只能通过在init中进行注册,为什么要单独出一个方法呢,不就是一句话么,如果cell足够多的话,只需要改动这个方法即可,不需要改动init方法。

/***  注册各种cell*/
-(void)registerClassWithCell
{//注册cell,让cell的重用标识符是@“Emoticon”[self.collectionView registerClass:[EmoticonCell class] forCellWithReuseIdentifier:@"Emoticon"];
}

倒数第二麻烦的就应该是布局了,虽然代码看起来很爽,但是确实是说,用stroyboard来布局是非常高效的,但楼主用的是代码。需要注意的就是一点,必须先添加到父视图上才可以进行布局,不然会出很多麻烦

-(void)layoutCollectionView
{//水平适配NSArray * horizontal = [NSLayoutConstraint constraintsWithVisualFormat:@"H:|[_collectionView]|" options:0 metrics:nil views:NSDictionaryOfVariableBindings(_collectionView)];[self addConstraints:horizontal];//垂直适配NSArray * verital = [NSLayoutConstraint constraintsWithVisualFormat:@"V:|[_collectionView]|" options:0 metrics:nil views:NSDictionaryOfVariableBindings(_collectionView)];[self addConstraints:verital];
}

最麻烦的就是数UICollectionView了,因为它有着各种协议以及各种配置问题,因为有注释,所以很明确,看一下配置方法

/***  配置集合视图(CollectionView)*/
-(void)loadCollectionView
{//集合视图的布局对象,必须有!如果不想设置其他的属性,就只有这句init即可UICollectionViewFlowLayout * layout = [[UICollectionViewFlowLayout alloc]init];//水平滚动,也就说布局是竖直优先layout.scrollDirection = UICollectionViewScrollDirectionHorizontal;//初始化集合视图(UICollenctionView)self.collectionView = [[UICollectionView alloc]initWithFrame:CGRectZero collectionViewLayout:layout];//手动适配屏幕self.collectionView.translatesAutoresizingMaskIntoConstraints = NO;//分页显示,否则太滑,继承于ScrollView[self.collectionView setPagingEnabled:YES];//默认是黑色的self.collectionView.backgroundColor = [UIColor groupTableViewBackgroundColor];//不显示水平滚动栏self.collectionView.showsHorizontalScrollIndicator = NO;//设置代理和数据源self.collectionView.delegate = self;self.collectionView.dataSource = self;
}

首先实现集合视图的数据源方法,首先看两个,很好理解,和UITableView简直就是双胞胎,只不过tableView用row,而CollectionView用item

#pragma mark - UICollectionView DateSource
//返回几个组,也是分组的,默认也是1,这个和UITableView一致
- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView
{return 1;
}//返回组中的数据的个数
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
{return self.emoticons.count;
}

接着就是实现自定义cell的方法,与UItableView稍有不同,但也是很好理解的

- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{//获取数据Emoticon * emoticon = self.emoticons[indexPath.row];//创建cellBaseCollectionViewCell * cell = [collectionView dequeueReusableCellWithReuseIdentifier:NSStringFromClass([emoticon class]) forIndexPath:indexPath];//避免强引用循环__weak __block EmoticonsView * copy_self = self;//对cell设置进行的回调[cell emotionButtonClickBlockHandle:^(UIImage *buttonImage, NSString *imageName) {//如果上报代码块存在if (copy_self.b){copy_self.b(buttonImage,imageName);}}];//设置数据[cell setInformationWithEmoticon:emoticon];return  cell;
}
因为UICollectionViewDelegate的方法没有用到的,所以没有写相关方法,直接用的UICollectionViewDelegateFlowLayout方法,更多方法也可以按住option点击进入开发文档进行查看,方法如下
#pragma mark - UICollectionView DelegateFlowLayout//每个item的大小(可以根据indexPath定制)
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath
{return CGSizeMake(30, 30);
}//每组距离边界的大小,逆时针,上,左,下,右
- (UIEdgeInsets)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout insetForSectionAtIndex:(NSInteger)section
{return UIEdgeInsetsMake(20, 20, 20, 20);
}

显示的view完毕

MyTextField  (文本框)

MyTextField 其实是继承于UITextView,但是楼主当时打的太快,所以起错了名字,改的时候又太麻烦,所以就不改了,这个类的作用就是能够调用我们之前写好的表情键盘的类,它需要知道目前键盘的类型是啥,也就是一个标志位。为了显得高大上一点,用的枚举
typedef enum : NSUInteger {KeyBoardTypeSystem,KeyBoardTypeFace,
} KeyBoardType;

声明文件中只需要声明一个可查看键盘状态的属性,一个切换键盘的方法和具体改变键盘的方法即可

@interface MyTextField : UITextView/***  当前键盘的状态*/
@property(nonatomic,assign,readonly)KeyBoardType currentKeyBoardType;/***  切换键盘的方法*/
-(void)switchKeyBoard;/***  改变键盘**  @param keyBoard 改变键盘的样式*/
-(void)changeKeyBoard:(KeyBoardType)keyBoard;@end

在延展中只需要一个表情视图(EmoticonsView)即可

@interface MyTextField ()@property(nonatomic,strong)EmoticonsView * emoticonView;@end

自定义的init方法

-(void)fieldInit
{//初始化键盘viewself.emoticonView = [[EmoticonsView alloc]initWithFrame:CGRectMake(0, 0, [UIScreen mainScreen].bounds.size.width, 200)];//初始化当前状态_currentKeyBoardType = KeyBoardTypeSystem;//避免强引用循环__block __weak MyTextField * copy_self = self;//设置回调[self.emoticonView emoticonsViewWithButtonPressedBlockHandle:^(UIImage *image, NSString *imageName) {//为text设置内容copy_self.text = [copy_self.text stringByAppendingString:imageName];}];}

实现切换键盘的两个方法即可

切换键盘的方法如下
-(void)switchKeyBoard
{switch (self.currentKeyBoardType) {//如果是表情键盘,就换成系统键盘case KeyBoardTypeFace:[self changeKeyBoard:KeyBoardTypeSystem];break;//如果是系统键盘,就换成表情键盘case KeyBoardTypeSystem:[self changeKeyBoard:KeyBoardTypeFace];break;default:break;}
}

改变键盘的方法如下

-(void)changeKeyBoard:(KeyBoardType)keyBoardType
{//判断是什么键盘switch (keyBoardType) {//如果是系统键盘case KeyBoardTypeSystem:self.inputView = nil;//nil表示系统默认自带的键盘break;//如果是表情键盘case KeyBoardTypeFace:self.inputView = self.emoticonView;break;default:break;}_currentKeyBoardType = keyBoardType;//如果是第一响应就要刷新一下if (self.isFirstResponder){[self reloadInputViews];}
}

Controller(控制器)

controller算是最后的小老板了,因为之前的铺垫,所以他的逻辑就变得很简单了
首先在延展中声明一个切换键盘的 按钮(UIButton) 以及 自定义的文本域(MyTextField) 即可
//文本域
@property(nonatomic,strong)MyTextField * textField;//切换状态的按钮
@property(nonatomic,strong)UIButton * stateButton;

viewDidLoad上,楼主依旧用的是方法进行

#pragma mark - 自带的加载方法
- (void)viewDidLoad {[super viewDidLoad];//设置背景色[self.view setBackgroundColor:[UIColor whiteColor]];//加载按钮[self loadStateButton];//适配切换按钮[self layoutStateButton];//加载textField[self loadTextView];//适配textField[self layoutTextView];//注意位置不可随意,必须在button适配好之后才可以,因为它是以button为基准的}

首先加载button,用storyboard是很简单,因为用的是代码,所以看起来比较复杂,不做过多的解释,相信都能看得懂

/***  加载键盘状态转换按钮*/
-(void)loadStateButton
{//初始化button对象self.stateButton = [UIButton buttonWithType:UIButtonTypeSystem];//设置button的title[self.stateButton setTitle:@"切换输入法" forState:UIControlStateNormal];//解除自动适配self.stateButton.translatesAutoresizingMaskIntoConstraints = NO;//添加目标动作回调[self.stateButton addTarget:self action:@selector(stateButtonClick) forControlEvents:UIControlEventTouchUpInside];//添加组件[self.view addSubview:self.stateButton];
}

接着是button的适配

/***  对切换按钮进行适配*/
-(void)layoutStateButton
{//水平适配NSArray * horizontal = [NSLayoutConstraint constraintsWithVisualFormat:@"H:|-[_stateButton]-|" options:0 metrics:nil views:NSDictionaryOfVariableBindings(_stateButton)];[self.view addConstraints:horizontal];//垂直适配NSArray * verital = [NSLayoutConstraint constraintsWithVisualFormat:@"V:|-20-[_stateButton]" options:0 metrics:nil views:NSDictionaryOfVariableBindings(_stateButton)];[self.view addConstraints:verital];
}

按钮配置完毕后,就需要适配文本域了,加载方法如下

/***  加载textField*/
-(void)loadTextView
{//创建文本对象self.textField = [[MyTextField alloc]initWithFrame:CGRectZero];//设置背景色self.textField.backgroundColor = [UIColor groupTableViewBackgroundColor];//解除自动适配self.textField.translatesAutoresizingMaskIntoConstraints = NO;//添加视图[self.view addSubview:self.textField];}

为文本域添加约束

/***  适配文本框*/
-(void)layoutTextView
{//水平适配NSArray * horizontal = [NSLayoutConstraint constraintsWithVisualFormat:@"H:|-10-[_textField]-10-|" options:0 metrics:nil views:NSDictionaryOfVariableBindings(_textField)];[self.view addConstraints:horizontal];//垂直布局NSArray * verital = [NSLayoutConstraint constraintsWithVisualFormat:@"V:[_stateButton]-8-[_textField(150)]" options:0 metrics:nil views:NSDictionaryOfVariableBindings(_textField,_stateButton)];[self.view addConstraints:verital];
}

切换按钮的回调方法,因为之前的铺垫,依旧很简单

/***  切换按钮点击*/
-(void)stateButtonClick
{[self.textField switchKeyBoard];
}

基本的功能到此结束。

但是这样完善有一个不舒服的地方,就是输入完毕东西之后,键盘不能自动消失,但是重新加一个按钮又显得很low,所以楼主用的是一个轻击手势,怎么用呢,在延展中加入一个属性
//轻击手势
@property(nonatomic,strong)UITapGestureRecognizer * tapGestureRecognizer;

在viewDidLoad中加入这个方法

 //配置手势[self loadTapGestureRecognizer];

配制方法

/***  加载轻击手势*/
-(void)loadTapGestureRecognizer
{//初始化轻击手势self.tapGestureRecognizer = [[UITapGestureRecognizer alloc]initWithTarget:self action:@selector(tapAction)];//必须双击self.tapGestureRecognizer.numberOfTapsRequired = 2;//添加手势[self.view addGestureRecognizer:self.tapGestureRecognizer];}

轻击后的回调方法

/***  轻击手势的目标动作回调*/
-(void)tapAction
{//如果是第一响应if ([self.textField isFirstResponder]){//撤销键盘,解除第一响应[self.view endEditing:YES];}
}

这样,双击后键盘就自动撤掉了。更多手势识别可以去看看之前的博客iOS学习-------手势识别,3Q

iOS开发-------自定义简单的表情键盘(UICollectionView 集合视图)相关推荐

  1. iOS开发自定义键盘回车键Return Key

    在iOS开发中,用户在进行文本输入的时候,往往会用到虚拟键盘上的回车键,也就是Return Key.回车键有时候可以是"完成"(表示输入结束),可以是"下一项" ...

  2. iOS开发:简单的Toast提示框实现

    今天小年,再分享一篇2018年度最后一篇博客,博主是以iOS开发出身,那就最后一篇博文就分享一下关于iOS的内容吧.iOS开发过程中,有些时候操作App的时候,需要给用户对应的响应提示操作,使用系统自 ...

  3. iOS 【快速集成QQ表情键盘】

    Demo演示: 如果是UITextView,导入#import "UITextView+YZEmotion.h" 如果是UITextField,导入#import "UI ...

  4. ios开发 自定义btn_iOS一步步实现一个高度自定义UIButton控件

    需求背景 日常开发中UIButton的图片与标题默认的布局是固定的,是在水平方向左右排列.但是我们会经常需要更改image和title的位置来实现需求,这是个很常见的需求就不多说了.所以下面就来谈谈如 ...

  5. iOS开发-自定义UIAlterView(iOS 7)

    App中不可能少了弹框,弹框是交互的必要形式,使用起来也非常简单,不过最近需要自定义一个弹框,虽然iOS本身的弹框已经能满足大部分的需求,但是不可避免还是需要做一些自定义的工作.iOS7之前是可以自定 ...

  6. iOS开发之简单画板实现

    2019独角兽企业重金招聘Python工程师标准>>> 这几天在学习Quartz2D,学习了一个简单画板的实现,现在把实现过程记录一下. 主要用到的点就是画线,截屏,绘制图片,选择图 ...

  7. iOS开发之解决系统数字键盘无文字时delete键无法监听的技巧

    最近在做用户登录获取验证码时添加图形验证码功能,就是只有正确输入图形验证码才能收到后台发送的短信验证码.效果如下: 看起来虽然是个小功能,但是实际操作起来,会发现苹果给我们留下的坑,当然更多的是自己给 ...

  8. iOS开发-自定义相机(仿微信)拍照、视频录制

    网上有很多自定义相机的例子,这里只是我临时写的一个小demo,仅供参考: 用到了下面几个库: #import <AVFoundation/AVFoundation.h> #import & ...

  9. ios开发 自定义btn_iOS——自定义button的几种方法

    自定义button主要分为两大类. 1.在UIButton的基础上,进行继承,更改button中imageView和label的位置和大小. 图文相对位置变化 2.使用UIView实现button构成 ...

最新文章

  1. R语言在图上标出点坐标_从基变换的角度理解旋转矩阵R
  2. java中日期类型与字符串相互转换
  3. Linux系统下GitLab服务的的搭建
  4. ssh服务端口转发详解
  5. CentOS7下LVS+Keepalived实现高性能高可用负载均衡
  6. 007-配置IP和DNS
  7. 无法加载站点/服务的所有 ISAPI 筛选器,因此启动中止——Service Unavailable解决方法合集...
  8. Xilinx IP核 之DDS
  9. php 图像生成缩略图
  10. Python利用模糊查询两个excel文件数据 导出新表格
  11. ftp访问命令 linux,linux访问ftp服务器命令
  12. 答题器真的什么问题都能答出来吗?
  13. 计算年龄:sql计算
  14. 【T+】畅捷通T+选项设置界面没有“数据精度”调整项
  15. 小米/红米手机如何通过USB数据线把手机网络共享给电脑
  16. php单例是什么意思,什么是PHP单例模式?
  17. Ubuntu16.04+CUDA8.0+GTX960M安装
  18. 微信链接防拦截跳转系统 微信域名防屏蔽技术
  19. 公众号点击图片变成另一张_公众号互动内容:小众潮流or下一个风口?
  20. 颠覆认知!保研的方法竟然多达13种?

热门文章

  1. 为什么SSH服务器拒绝了密码?
  2. Cocos 荣耀讲师线下聚(mian)会(ji)!
  3. 暗黑2服务器修改,暗黑2服务器频道LOGO制作教程
  4. winrar 去广告 使用 hxd 或 任意二进制修改器
  5. 奔富葡萄酒全新沉浸式体验馆亮相上海;爱莉安娜-格兰德推出全新个人香水R.E.M. | 美通企业日报...
  6. c语言精灵宝可梦对战游戏,精灵宝可梦:把游戏中的对战高玩,放到动画的世界里会是什么位置...
  7. 驾驭大数据 - 电子书下载(高清版PDF格式+EPUB格式)
  8. html color 在线,HTML颜色一览(color)
  9. oracle 19c使用dgmgrl来执行switchover和failover切换
  10. 面向对象 抽象类与接口类