網易云信-新增自定義消息(iOS版)

前言

公司業務需要,PC端,移動端都用到了第三方 網易云信 IM 來實現在線客服咨詢。
在這當中難免遇到一些需求是網易云信沒有提供,需要自行編碼進行擴展的。寫此篇文章的目的正是因業務需要,需要在網易云信的基礎上進行消息類型的擴展。

此篇文章里的代碼是基于 網易云信 NIM_iOS_Demo_v4.5.0 版 進行修改的

如下圖所示的消息類型

帶圖片和文字,并且可點擊的消息類型,(注意收到的消息和發送的消息文本顏色不一樣)

標題是iOS版,可想而知,肯定還有其他如 Android版,Web版等,不可能此類型的消息(我稱它為圖文消息)只支持iOS,而在Android或Web端無法顯示問題。以下附上其他版本擴展的鏈接


正文

  1. 下載demo后,雙擊 NIMDemo/NIM.xcworkspace 打開項目,然后運行,確保下載下來的demo能正確運行起來。

  2. 運行沒有問題后,修改以下幾個文件配置,將demo修改為自己所用。

    • 修改 Classes/Util/NTESDemoConfig.m 中的_appKey,填入自己的appKey
- (instancetype)init
{
    if (self = [super init])
    {
        _appKey = @"填入自己的appKey";
        _apiURL = @"https://app.netease.im/api";
        _apnsCername = @"ENTERPRISE";
        _pkCername = @"DEMO_PUSH_KIT";
        
        _redPacketConfig = [[NTESRedPacketConfig alloc] init];        
    }
    return self;
}
  • 修改
- (NSString *)tokenByPassword
{
    //demo直接使用username作為account,md5(password)作為token
    //接入應用開發需要根據自己的實際情況來獲取 account和token
    //return [[NIMSDK sharedSDK] isUsingDemoAppKey] ? [self MD5String] : self;
    return [self MD5String];
}

修改上述代碼后,重新運行,即可使用自己的賬號密碼登錄了。

  1. 添加測試發送圖文鏈接的按鈕,點擊即發送圖文鏈接消息

編輯NTESCellLayoutConfig.m文件,在init函數中 _types 增加一條

- (instancetype)init
{
    if (self = [super init])
    {
        _types =  @[
                   @"NTESJanKenPonAttachment",
                   @"NTESSnapchatAttachment",
                   @"NTESChartletAttachment",
                   @"NTESWhiteboardAttachment",
                   @"NTESRedPacketAttachment",
                   @"NTESRedPacketTipAttachment",
                   // 添加圖文鏈接消息
                   @"NTESLinkAttachment"
                   ];
        _sessionCustomconfig = [[NTESSessionCustomContentConfig alloc] init];
        _chatroomTextConfig  = [[NTESChatroomTextContentConfig alloc] init];
        _chatroomRobotConfig = [[NTESChatroomRobotContentConfig alloc] init];
    }
    return self;
}

編輯 NTESCustomAttachmentDecoder.m文件,checkAttachment函數中添加如下代碼

//頭部導入
#import "NTESLinkAttachment.h"
//...

- (id<NIMCustomAttachment>)decodeAttachment:(NSString *)content
{
    id<NIMCustomAttachment> attachment = nil;

    NSData *data = [content dataUsingEncoding:NSUTF8StringEncoding];
    if (data) {
        NSDictionary *dict = [NSJSONSerialization JSONObjectWithData:data
                                                             options:0
                                                               error:nil];
        if ([dict isKindOfClass:[NSDictionary class]])
        {
            NSInteger type     = [dict jsonInteger:CMType];
            NSDictionary *data = [dict jsonDict:CMData];
            switch (type) {
                //...
                // 添加圖文鏈接 case
                case CustomMessageTypeLink:
                {
                    attachment = [[NTESLinkAttachment alloc] init];
                    ((NTESLinkAttachment *)attachment).title        = [data jsonString:CMLinkPacketTitle];
                    ((NTESLinkAttachment *)attachment).linkUrl      = [data jsonString:CMLinkPacketLinkUrl];
                    ((NTESLinkAttachment *)attachment).imageUrl     = [data jsonString:CMLinkPacketImageUrl];
                    ((NTESLinkAttachment *)attachment).describe     = [data jsonString:CMLinkPacketDescribe];
                }
                    break;
                default:
                    break;
            }
            attachment = [self checkAttachment:attachment] ? attachment : nil;
        }
    }
    return attachment;
}

- (BOOL)checkAttachment:(id<NIMCustomAttachment>)attachment
{

    // ... 省略前面的 if  else if 塊
    
    // 添加如下代碼
    else if ([attachment isKindOfClass:[NTESLinkAttachment class]])
    {
        check = YES;
    }
    return check;
}

編輯NTESSessionConfig.m文件,在mediaItems函數中添加如下代碼

//...
// 添加圖文鏈接測試按鈕,此處的 onTapMediaItemLinkPacket 在
 NTESSessionViewController.m 中添加 
NIMMediaItem *linkPacket  = [NIMMediaItem item:@"onTapMediaItemLinkPacket:"
                                   normalImage:[UIImage imageNamed:@"icon_redpacket_normal"]
                                 selectedImage:[UIImage imageNamed:@"icon_redpacket_pressed"]
                                         title:@"圖文鏈接"];
//...
if (isMe)
{
    items = @[janKenPon,fileTrans,tip];
}
else if(_session.sessionType == NIMSessionTypeTeam)
{
    // 在群組消息里添加
    items = @[janKenPon,teamMeeting,fileTrans,tip,redPacket,linkPacket];
}
else
{
    // 添加圖文鏈接測試按鈕
    items = @[janKenPon,audioChat,videoChat,fileTrans,snapChat,whiteBoard,tip,redPacket,linkPacket];
}

Classes/Sections/Session/Object/Attach 目錄下創建 NTESLinkAttachment文件,繼承 NSObject 類,實現 NIMCustomAttachment,NTESCustomAttachmentInfo 協議

創建Cocoa Touch Class文件 NTESLinkAttachment,命名規則盡量遵循云信命名規則

創建完成后,添加響應的屬性值 標題title,跳轉的鏈接linkUrl,圖片imageUrl,描述describe

NTESLinkAttachment.h 文件內容如下

#import <Foundation/Foundation.h>
#import "NTESCustomAttachmentDefines.h"

@interface NTESLinkAttachment : NSObject<NIMCustomAttachment,NTESCustomAttachmentInfo>

// 標題
@property (nonatomic, copy) NSString *title;

// 點擊跳轉的鏈接地址
@property (nonatomic, copy) NSString *linkUrl;

// 圖片
@property (nonatomic, copy) NSString *imageUrl;

// 描述
@property (nonatomic, copy) NSString *describe;

@end

NTESLinkAttachment.m文件內容如下

復制之后,會有報錯如 NTESSessionLinkContentView.h 找不到,和 CMLinkPacket***未定義等相關錯誤,先別急,后面會講到,如果看不順眼可以先注釋掉,回頭再過來放開注釋也行。(ps:本人非iOS開發,所以代碼部分不做詳細講解)

#import "NTESLinkAttachment.h"
#import "NTESSessionLinkContentView.h"

@implementation NTESLinkAttachment

- (NSString *)encodeAttachment
{
    NSDictionary *dict = @{
                           CMType : @(CustomMessageTypeRedPacket),
                           CMData : @{
                              CMLinkPacketTitle    : self.title,
                              CMLinkPacketLinkUrl  : self.linkUrl,
                              CMLinkPacketImageUrl : self.imageUrl,
                              CMLinkPacketDescribe : self.describe
                           }
                       };
    NSData *data = [NSJSONSerialization dataWithJSONObject:dict
                                                   options:0
                                                     error:nil];
    NSString *content = nil;
    if (data) {
        content = [[NSString alloc] initWithData:data
                                        encoding:NSUTF8StringEncoding];
    }
    return content;
}


- (NSString *)cellContent:(NIMMessage *)message{
    return @"NTESSessionLinkContentView";
}

- (CGSize)contentSize:(NIMMessage *)message cellWidth:(CGFloat)width{
    CGFloat w = 240.0f;
    CGFloat h = 40.0f;
    CGFloat padding = 3.0f * 3;
    if (self.imageUrl != nil) {
        h += 140.f;
    }
    if (self.describe != nil) {
        UIFont *font = [UIFont systemFontOfSize:12.0];
        CGFloat height = [NTESSessionLinkContentView getHeightByWidth:w - padding title:self.describe font:font];
        h += height + padding;
    }
    
    return CGSizeMake(w, h);
}

- (UIEdgeInsets)contentViewInsets:(NIMMessage *)message
{
    CGFloat bubblePaddingForImage    = 3.f;
    CGFloat bubbleArrowWidthForImage = 5.f;
    if (message.isOutgoingMsg) {
        return  UIEdgeInsetsMake(bubblePaddingForImage,bubblePaddingForImage,bubblePaddingForImage,bubblePaddingForImage + bubbleArrowWidthForImage);
    }else{
        return  UIEdgeInsetsMake(bubblePaddingForImage,bubblePaddingForImage + bubbleArrowWidthForImage, bubblePaddingForImage,bubblePaddingForImage);
    }
}

- (BOOL)canBeRevoked
{
    return YES;
}

- (BOOL)canBeForwarded
{
    return YES;
}

@end

現在再來補充上面缺失的部分。
NTESCustomAttachmentDefines.h 文件中定義如下四個字段。打開這個文件可以看到這個里面還定義了一些其他消息需要用到的字段,所以遵循人家的游戲規則,也在此處定義。

//...省略

typedef NS_ENUM(NSInteger,NTESCustomMessageType){
    CustomMessageTypeJanKenPon  = 1, //剪子石頭布
    CustomMessageTypeSnapchat   = 2, //閱后即焚
    CustomMessageTypeChartlet   = 3, //貼圖表情
    CustomMessageTypeWhiteboard = 4, //白板會話
    // (由于我其他平臺圖文消息type是5,剛好我們業務不需要發紅包功能,這里我只好把5變成我的圖文消息,把紅包類型的消息去除)
    CustomMessageTypeRedPacket  = 5, //紅包消息
    CustomMessageTypeRedPacketTip = 6, //紅包提示消息
};

//...省略


//紅包
#define CMRedPacketTitle   @"title"        //紅包標題
#define CMRedPacketContent @"content"      //紅包內容
#define CMRedPacketId      @"redPacketId"  //紅包ID
//紅包詳情
#define CMRedPacketSendId     @"sendPacketId"
#define CMRedPacketOpenId     @"openPacketId"
#define CMRedPacketDone       @"isGetDone"
// 添加此處四個字段用于圖文鏈接消息使用
#define CMLinkPacketTitle       @"title"        //標題
#define CMLinkPacketLinkUrl     @"link_url"      //跳轉鏈接
#define CMLinkPacketImageUrl    @"image_url"     //圖片鏈接
#define CMLinkPacketDescribe    @"describe"     //描述
//...省略

Classes/Sections/Session/View/SessionCell/SessionContentView目錄下創建Cocoach Touch Class文件 NIMSessionMessageContentView,此文件主要用來做圖文鏈接消息的顯示。

NIMSessionMessageContentView.h文件內容如下

#import "NIMSessionMessageContentView.h"

static NSString *const NIMDemoEventNameLinkingPacket = @"NIMDemoEventNameLinkingPacket";

@interface NTESSessionLinkContentView : NIMSessionMessageContentView

// 根據寬度,字體和文本內容獲取高度
+ (CGFloat)getHeightByWidth:(CGFloat)width title:(NSString *)title font:(UIFont *)font;

@end

NIMSessionMessageContentView.m文件內容如下

#import "NTESSessionLinkContentView.h"
#import "UIView+NTES.h"
#import "NTESLinkAttachment.h"
#import "NTESSessionUtil.h"
#import "UIImageView+WebCache.h"

CGFloat titleHeight = 40.f; // title高度
CGFloat imageHeight = 120.f;// 圖片高度

@interface NTESSessionLinkContentView()

// 圖文鏈接消息附件
@property (nonatomic,strong) NTESLinkAttachment *attachment;

@property (nonatomic,strong) UILabel *titleLabel;

@property (nonatomic,strong) UIImageView *imageView;

@property (nonatomic,strong) UILabel *describeLabel;

@end

@implementation NTESSessionLinkContentView

- (instancetype)initSessionMessageContentView{
    self = [super initSessionMessageContentView];
    if (self) {
        self.opaque = YES;
        
        _titleLabel = [[UILabel alloc] initWithFrame:CGRectZero];
        _imageView  = [[UIImageView alloc] initWithFrame:CGRectZero];
        _describeLabel = [[UILabel alloc] initWithFrame:CGRectZero];
    }
    return self;
}

- (void)refresh:(NIMMessageModel *)data
{
    [super refresh:data];
    NIMCustomObject *customObject = (NIMCustomObject*)data.message.messageObject;
    id attach = customObject.attachment;
    
    if ([attach isKindOfClass:[NTESLinkAttachment class]]) {
        self.attachment = (NTESLinkAttachment *)attach;
        
        self.titleLabel.text = self.attachment.title;
        [self addSubview:_titleLabel];
        
        if (self.attachment.imageUrl != nil) {
            NSURL *url = [NSURL URLWithString:self.attachment.imageUrl];
            // 默認圖片 default_image,記得在 Images.xcassets 中添加
            [self.imageView sd_setImageWithURL:url placeholderImage:[UIImage imageNamed:@"default_image"]];
            [self.imageView sizeToFit];
            [self addSubview:_imageView];
        }
        if (self.attachment.describe != nil) {
            self.describeLabel.text = self.attachment.describe;
            [self addSubview:_describeLabel];
        }
    }
}

- (void)layoutSubviews{
    [super layoutSubviews];
    BOOL outgoing = self.model.message.isOutgoingMsg;
    
    UIEdgeInsets contentInsets = self.model.contentViewInsets;
    CGSize contentSize = [self.model contentSize:self.superview.width];
    CGFloat padding = 15;
    
    self.titleLabel.frame = CGRectMake(padding, contentInsets.left, contentSize.width - padding, titleHeight);
    self.titleLabel.font = [UIFont systemFontOfSize:14.0];
    self.titleLabel.numberOfLines = 1;
    
    // 詳情描述距離
    CGFloat describeY = titleHeight;
    
    if (self.attachment != nil && self.attachment.imageUrl != nil) {
        self.imageView.frame = CGRectMake(
                                          contentInsets.left + contentInsets.right,
                                          titleHeight + contentInsets.top + 5,
                                          contentSize.width - (contentInsets.left + contentInsets.right), imageHeight);
        self.imageView.contentMode = UIViewContentModeScaleAspectFit;
        [self setBorderWithImageView:self.imageView top:TRUE left:FALSE bottom:TRUE right:FALSE borderColor:[UIColor lightGrayColor] borderWidth:0.3f];
        describeY += imageHeight + contentInsets.top * 3 + 5 ;
    }
    
    if (self.attachment != nil && self.attachment.describe != nil) {
        UIFont *font = [UIFont systemFontOfSize:12.0];
        self.describeLabel.font = font;
        self.describeLabel.numberOfLines = 3;
        CGFloat height = [NTESSessionLinkContentView getHeightByWidth:self.describeLabel.frame.size.width title:self.attachment.describe font:font];
        self.describeLabel.frame = CGRectMake(padding, describeY, contentSize.width - padding, height + padding);
    }
    
    // 發出去的消息
    if (outgoing)
    {
        self.titleLabel.textColor = [UIColor whiteColor];
        self.describeLabel.textColor = [UIColor whiteColor];
    }
    else
    {
        self.titleLabel.textColor = [UIColor blackColor];
        self.describeLabel.textColor = [UIColor grayColor];
    }
}

// 根據寬動態獲取高度
+ (CGFloat)getHeightByWidth:(CGFloat)width title:(NSString *)title font:(UIFont *)font
{
    UILabel *label = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, width, 0)];
    label.text = title;
    label.font = font;
    label.numberOfLines = 0;
    [label sizeToFit];
    CGFloat height = label.frame.size.height;
    return height;
}

// 設置元素邊框
-(void)setBorderWithImageView:(UIImageView *) imageView top:(BOOL)top left:(BOOL)left bottom:(BOOL)bottom right:(BOOL)right borderColor:(UIColor *)color borderWidth:(CGFloat)width
{
    // 垂直內邊距
    CGFloat verticalPadding = 5.0f;
    if (top)
    {
        CALayer *layer = [CALayer layer];
        layer.frame = CGRectMake(0, -verticalPadding, imageView.frame.size.width, width);
        layer.backgroundColor = color.CGColor;
        [imageView.layer addSublayer:layer];
    }
    if (left)
    {
        CALayer *layer = [CALayer layer];
        layer.frame = CGRectMake(0, 0, width, imageView.frame.size.height);
        layer.backgroundColor = color.CGColor;
        [imageView.layer addSublayer:layer];
    }
    if (bottom)
    {
        CALayer *layer = [CALayer layer];
        layer.frame = CGRectMake(0, imageView.frame.size.height - width + verticalPadding, imageView.frame.size.width, width);
        layer.backgroundColor = color.CGColor;
        [imageView.layer addSublayer:layer];
    }
    if (right)
    {
        CALayer *layer = [CALayer layer];
        layer.frame = CGRectMake(imageView.frame.size.width - width, 0, width, imageView.frame.size.height);
        layer.backgroundColor = color.CGColor;
        [imageView.layer addSublayer:layer];
    }
}

- (void)onTouchUpInside:(id)sender
{
    if ([self.delegate respondsToSelector:@selector(onCatchEvent:)]) {
        NIMKitEvent *event = [[NIMKitEvent alloc] init];
        event.eventName = NIMDemoEventNameLinkingPacket;
        event.messageModel = self.model;
        event.data = self;
        [self.delegate onCatchEvent:event];
    }
}

@end

接下來我們添加圖文按鈕的點擊事件處理。

下面代碼添加此處按鈕點擊處理事件

打開文件 NTESSessionViewController.m, 編輯函數 onTapCell
在 if else if 代碼塊后面添加如下代碼

// 頭部需導入
#import "NTESLinkAttachment.h"
#import "NTESSessionLinkContentView.h"
#import "NTESWebViewController.h"

// ...

// 添加圖文鏈接消息點擊事件
else if ([eventName isEqualToString:NIMDemoEventNameLinkingPacket]) {
   NIMCustomObject *object = event.messageModel.message.messageObject;
   NTESLinkAttachment *attachment = (NTESLinkAttachment *)object.attachment;
   [self onOpenWebView:attachment];
   handled = YES;
}
// ....

// 添加上面調用的 onOpenWebView 函數 
- (void)onOpenWebView:(NTESLinkAttachment *)attachment {
    // NTESWebViewController 是點擊顯示的圖文消息后要跳轉的頁面,在構造函數添加跳轉時傳入 linkUrl
    NTESWebViewController *vc = [[NTESWebViewController alloc] initWithUrl:attachment.linkUrl];
    // 設置title
    if (attachment && attachment.title != nil) {
        vc.title = attachment.title;
    }
    [self.navigationController pushViewController:vc animated:YES];
}

//...
#pragma mark - 圖文鏈接
- (void)onTapMediaItemLinkPacket:(NIMMediaItem *)item
{
    // 此處模擬測試數據
    NTESLinkAttachment *attachment = [[NTESLinkAttachment alloc] init];
    [attachment setTitle:@"暖冬季歡樂送"];
    [attachment setLinkUrl:@"http://www.lxweimin.com/u/bd57ade96e8a"];
    [attachment setImageUrl:@"https://www.baidu.com/img/bd_logo1.png"];
    [attachment setDescribe:@"家具滿1000元減100元再返100元現金券!點擊查看詳情!"];
    NIMMessage *message = [NTESSessionMsgConverter msgWithLink:attachment];
    [self sendMessage:message];
}
//...

在目錄 Classes/Sections/Session/ViewController 添加上面使用到的 NTESWebViewController,用來顯示點擊后的網頁
NTESWebViewController.h內容如下

#import <UIKit/UIKit.h>

@interface NTESWebViewController : UIViewController<UIWebViewDelegate>
{
    UIWebView *webView;
}

- (instancetype)initWithUrl:(NSString *)url;

@end

NTESWebViewController.m內容如下

#import "NTESWebViewController.h"

@interface NTESWebViewController ()<UINavigationControllerDelegate>

@property (nonatomic, strong) UIActivityIndicatorView *activityIndicator;
@property (nonatomic, strong) NSString *url;

@end

@implementation NTESWebViewController

- (instancetype)initWithUrl:(NSString *)url
{
    self = [super init];
    if (self)
    {
        _url = url;
    }
    return self;
}

- (void)viewDidLoad {
    [super viewDidLoad];
    
    // app 尺寸,去掉狀態欄
    CGRect mainScreen = [UIScreen mainScreen].applicationFrame;
    // 1.創建webview,并設置大小
    webView = [[UIWebView alloc] initWithFrame:CGRectMake(mainScreen.origin.x, mainScreen.origin.y, mainScreen.size.width, mainScreen.size.height)];
    // 2.創建請求
    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:self.url]];
    // 3.加載網頁
    [webView loadRequest:request];
    // 4.將webview添加到界面
    [self.view addSubview:webView];
    [webView setDelegate:self];
}

- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}

- (void)webViewDidStartLoad:(UIWebView *)webView {
    // 創建UIActivityIndicatorView背底半透明View
    CGRect mainScreen = [UIScreen mainScreen].applicationFrame;
    UIView *view = [[UIView alloc] initWithFrame:CGRectMake(mainScreen.origin.x, mainScreen.origin.y, mainScreen.size.width, mainScreen.size.height)];
    [view setTag:108];
    [view setBackgroundColor:[UIColor whiteColor]];
    [self.view addSubview:view];
    
    self.activityIndicator = [[UIActivityIndicatorView alloc] initWithFrame:CGRectMake(0.0f, 0.0f, 40.0f, 40.0f)];
    [self.activityIndicator setCenter:view.center];
    [self.activityIndicator setActivityIndicatorViewStyle:UIActivityIndicatorViewStyleGray];
    [view addSubview:self.activityIndicator];
    
    [self.activityIndicator startAnimating];
}

- (void)webViewDidFinishLoad:(UIWebView *)webView {
    [self.activityIndicator stopAnimating];
    UIView *view = (UIView *)[self.view viewWithTag:108];
    [view removeFromSuperview];
}

- (void)webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error {
    [self.activityIndicator stopAnimating];
    UIView *view = (UIView *)[self.view viewWithTag:108];
    [view removeFromSuperview];
}

@end
  1. 添加顯示自定義的圖文消息
    上面第3個步驟其實已經做了大部分自定義的圖文鏈接消息的顯示工作了,此處添加圖文鏈接消息的轉換代碼,
    編輯NTESSessionMsgConverter.h 頭文件
// ...
@class NTESLinkAttachment

@interface NTESSessionMsgConverter : NSObject
// ...
// 添加鏈接消息
+ (NIMMessage *)msgWithLink:(NTESLinkAttachment *)attachment;
@end

在實現文件NTESSessionMsgConverter.m 添加以下代碼

//...
#import "NTESLinkAttachment.h"

@implementation NTESSessionMsgConverter
//...
+ (NIMMessage *)msgWithLink:(NTESLinkAttachment *)attachment
{
    NIMMessage *message               = [[NIMMessage alloc] init];
    NIMCustomObject *customObject     = [[NIMCustomObject alloc] init];
    customObject.attachment           = attachment;
    message.messageObject             = customObject;
    message.apnsContent = @"發來了鏈接信息";
    return message;
}

@end

5.修改消息列表中,顯示的縮略文字

添加顯示[圖文鏈接]字樣,如果不添加,默認顯示的是[未知消息]

編輯 NTESSessionListViewController.m, 在contentForRecentSession中添加一條邏輯判斷

// ...
#import "NTESLinkAttachment.h"

// ...
- (NSAttributedString *)contentForRecentSession:(NIMRecentSession *)recent{
    //...
    else if ([object.attachment isKindOfClass:[NTESLinkAttachment class]]) {
        text = @"[圖文鏈接]";
     } else {
        text = @"[未知消息]";
     }
    //...
}
// ...

尾篇

到此,云信iOS端的擴展自定義消息已經完成。當然,這只是iOS的顯示正常了,其他如web,Android,pc等客戶端收到此類的消息,顯示有問題,也是需要擴展調整的。此篇文章其他端的文章我會陸續更新,如果有需要的同學可以關注下。

以下附上其他版本擴展的鏈接

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 173,057評論 25 708
  • 前言 公司業務需要,PC端,移動端都用到了第三方 網易云信 IM 來實現在線客服咨詢。在這當中難免遇到一些需求是網...
    醉生夢死閱讀 6,574評論 1 4
  • 前言 公司業務需要,PC端,移動端都用到了第三方 網易云信 IM 來實現在線客服咨詢。在這當中難免遇到一些需求是網...
    醉生夢死閱讀 3,992評論 20 1
  • 世界上沒有兩塊相同是石頭,沒有兩片相同的樹葉,同時,也沒有相同的兩個人。即使長相相同的雙胞胎往往性格也會截然不同,...
    望舒的雨巷暢想閱讀 371評論 0 4
  • 我寧愿把自己偽裝的讓別人覺得 我是整天就會哈哈哈哈 笑點低 很無腦的傻逼 我也不想讓別人看到我...
    南風入弦閱讀 326評論 0 1