MJRefresh源碼閱讀1——結(jié)構(gòu)梳理

前言

MJRefresh幾乎是我們最常見的開源控件了芹血。很有必要研究研究,但我們?yōu)楣?jié)約時間,只研究這個控件的header部分啰扛,且只研究只有菊花和文字的普通樣式的。footerheader類似嗡贺,菊花型的刷新樣式和Gif圖片型的樣式更是同理隐解。


UIScrollView的分類:

tableView添加刷新header

[_tableView addLegendHeaderWithRefreshingTarget:self refreshingAction:@selector(pullRefresh)];

結(jié)束刷新:

[_tableView.header endRefreshing];

MJRefresh控件是如此友好,開發(fā)者使用只需要短短一兩行代碼诫睬。它是直接以UITableView的實(shí)例tableView對象來操作的煞茫,且結(jié)束刷新時,我們看到它是以tableViewheader屬性調(diào)用endRefreshing方法的岩臣。我們知道UITableView是沒有header屬性的溜嗜,它這個應(yīng)該是通過UITableView分類的方式來動態(tài)添加地屬性。這么做具有低侵入性的優(yōu)點(diǎn)架谎,不然也可以通過派生UITableView的方式來給其添加header屬性炸宵,但這樣做開發(fā)者在有刷新的地方就不能用UITableView,而一定要用其派生類了谷扣,這是不好的土全。自定義控件,讓使用者用起來越簡便越好会涎。

MJRefresh確實(shí)是以分類的方式添加刷新headerfooter的裹匙,而且是UIScrollView的分類,因?yàn)樵摽丶?code>UITableView和UICollectionView都支持刷新末秃。

我們來看UIScollView分類UIScollView+MJRefresh的源碼:

整體來看概页,在分類的頭文件(UIScollView+MJRefresh.h)中總共有這么些東西:

屏幕快照 2017-01-03 下午5.31.33.png

可以看到所有的屬性和方法分為headerfooter兩部分,而且無論是添加header抑或footer的方法都提供了好幾個接口练慕。就以header來說添加刷新頭就有addLegendHeader(傳統(tǒng)樣式刷新頭)和addGifHeader(Gif樣式刷新頭)兩類惰匙。而且單就以其中一類樣式來看,也分別提供了兩組回調(diào)方式不同的接口铃将,即blockSEL兩種方式项鬼。而且,在同一回調(diào)方式的一組里也有兩個方法:一個接口有dateKey參數(shù)劲阎,一個沒有這個參數(shù)绘盟。dateKey參數(shù)代表“時間的key”,默認(rèn)的刷新控件有顯示這個頁面上次刷新是什么時候,MJRefresh內(nèi)部維護(hù)了一個每個頁面最后一次刷新時間的字典龄毡,該字典就以傳入的dateKeykey(更正:每個頁面上次刷新的時間在MJRefresh中沒有以字典來維護(hù)吠卷,而是將每個界面上次刷新時間直接存入NSUserDefaults,正是以dateKey作為key來區(qū)分頁面的)沦零。

我們忽略其他代碼撤嫩,只看添加一個以SEL回調(diào)方式的legendHeader的代碼。那么分類的頭文件就是這樣的:

@interface UIScrollView (MJRefresh)
#pragma mark - 訪問下拉刷新控件
/** 下拉刷新控件 */
@property (strong, nonatomic, readonly) MJRefreshHeader *header;
/** 傳統(tǒng)的下拉刷新控件 */
@property (nonatomic, readonly) MJRefreshLegendHeader *legendHeader;

...

/**
 * 添加一個傳統(tǒng)的下拉刷新控件
 *
 * @param target 進(jìn)入刷新狀態(tài)就會自動調(diào)用target對象的action方法
 * @param action 進(jìn)入刷新狀態(tài)就會自動調(diào)用target對象的action方法
 */
- (MJRefreshLegendHeader *)addLegendHeaderWithRefreshingTarget:(id)target refreshingAction:(SEL)action;
/**
 * 添加一個傳統(tǒng)的下拉刷新控件
 *
 * @param target    進(jìn)入刷新狀態(tài)就會自動調(diào)用target對象的action方法
 * @param action    進(jìn)入刷新狀態(tài)就會自動調(diào)用target對象的action方法
 * @param dateKey   用來記錄刷新時間的key
 */
- (MJRefreshLegendHeader *)addLegendHeaderWithRefreshingTarget:(id)target refreshingAction:(SEL)action dateKey:(NSString *)dateKey;

/**
 * 移除下拉刷新控件
 */
- (void)removeHeader;

@end

同樣的我們也忽略.m文件中其他代碼:

@implementation UIScrollView (MJRefresh)
#pragma mark - 下拉刷新
- (MJRefreshLegendHeader *)addLegendHeaderWithRefreshingTarget:(id)target refreshingAction:(SEL)action
{
    return [self addLegendHeaderWithRefreshingTarget:target refreshingAction:action dateKey:nil];
}

- (MJRefreshLegendHeader *)addLegendHeaderWithRefreshingTarget:(id)target refreshingAction:(SEL)action dateKey:(NSString *)dateKey
{
    MJRefreshLegendHeader *header = [self addLegendHeader];
    header.refreshingTarget = target;
    header.refreshingAction = action;
    header.dateKey = dateKey;
    return header;
}

- (MJRefreshLegendHeader *)addLegendHeader
{
    MJRefreshLegendHeader *header = [[MJRefreshLegendHeader alloc] init];
    self.header = header; // 將header賦值給了scrollView的屬性header
    
    return header;
}

- (void)removeHeader
{
    self.header = nil;
}

#pragma mark - Property Methods
- (MJRefreshHeader *)header
{
    return objc_getAssociatedObject(self, &MJRefreshHeaderKey);
}

- (MJRefreshLegendHeader *)legendHeader
{
    if ([self.header isKindOfClass:[MJRefreshLegendHeader class]]) {
        return (MJRefreshLegendHeader *)self.header;
    }
    
    return nil;
}

static char MJRefreshHeaderKey;
- (void)setHeader:(MJRefreshHeader *)header
{
    if (header != self.header) {
        [self.header removeFromSuperview];
        
        [self willChangeValueForKey:@"header"];
        objc_setAssociatedObject(self, &MJRefreshHeaderKey,
                                 header,
                                 OBJC_ASSOCIATION_ASSIGN);
        [self didChangeValueForKey:@"header"];
        
        [self addSubview:header];
    }
}


#pragma mark - swizzle
+ (void)load
{
    Method method1 = class_getInstanceMethod([self class], NSSelectorFromString(@"dealloc"));
    Method method2 = class_getInstanceMethod([self class], @selector(deallocSwizzle));
    method_exchangeImplementations(method1, method2);
}

- (void)deallocSwizzle
{
    [self removeFooter];
    [self removeHeader];
    
    [self deallocSwizzle];
}

@end

可以看到在addLegendHeaderWithRefreshingTarget: refreshingAction: dateKey:方法里創(chuàng)建了MJRefreshLegendHeader類型的header蠢终,并將該方法的target,action,dateKey分別賦給header里與之對應(yīng)的屬性序攘。
而且在創(chuàng)建header實(shí)例后將其賦給分類的屬性header。我們知道分類中的屬性是不會自動生成setter/getter方法的寻拂,要通過運(yùn)行時(runtime)來實(shí)現(xiàn)程奠。這個我們可以在上面的代碼中看到,setHeader:方法里除了通過runtime來動態(tài)生成屬性祭钉,還手動為其添加了觀察(KVC和KVO的使用及原理)瞄沙,最后將header視圖添加在了scrollView上。

在分類的最后慌核,還以Method Swizzling的方式動態(tài)交換了deallocdeallocSwizzle倆方法的IMP(方法實(shí)現(xiàn))距境,使得在執(zhí)行dealloc方法時實(shí)際上跑的是deallocSwizzle方法的實(shí)現(xiàn),而在deallocSwizzle方法里移除了headerfooter垮卓。

總結(jié)一下垫桂,這個分類主要功能是提供了一個添加header的很便捷的接口。在接口方法里創(chuàng)建了header實(shí)例粟按,將其賦為分類的屬性诬滩,并添加在scrollView上(addSubView:)。


header的派生類——MJRefreshLegendHeader:

既然上面在分類中創(chuàng)建了MJRefreshLegendHeader類型的實(shí)例灭将,它是一個header的派生子類疼鸟,表示傳統(tǒng)樣式的header∶硎铮可以看到在它的頭文件中沒有暴露給外部任何東西空镜,只能看到它是繼承于MJRefreshHeader類的。

#import "MJRefreshHeader.h"

@interface MJRefreshLegendHeader : MJRefreshHeader

@end

再看它的.m文件中是怎么實(shí)現(xiàn)的:

#import "MJRefreshLegendHeader.h"
#import "MJRefreshConst.h"
#import "UIView+MJExtension.h"

@interface MJRefreshLegendHeader()
@property (nonatomic, weak) UIImageView *arrowImage;
@property (nonatomic, weak) UIActivityIndicatorView *activityView;
@end

@implementation MJRefreshLegendHeader
#pragma mark - 懶加載
- (UIImageView *)arrowImage
{
    if (!_arrowImage) {
        UIImageView *arrowImage = [[UIImageView alloc] initWithImage:[UIImage imageNamed:MJRefreshSrcName(@"arrow.png")]];
        [self addSubview:_arrowImage = arrowImage];
    }
    return _arrowImage;
}

- (UIActivityIndicatorView *)activityView
{
    if (!_activityView) {
        UIActivityIndicatorView *activityView = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
        activityView.bounds = self.arrowImage.bounds;
        [self addSubview:_activityView = activityView];
    }
    return _activityView;
}

#pragma mark - 初始化
- (void)layoutSubviews
{
    [super layoutSubviews];
    
    // 箭頭
    CGFloat arrowX = (self.stateHidden && self.updatedTimeHidden) ? self.mj_w * 0.5 : (self.mj_w * 0.5 - 100);
    self.arrowImage.center = CGPointMake(arrowX, self.mj_h * 0.5);
    
    // 指示器
    self.activityView.center = self.arrowImage.center;
}

#pragma mark - 公共方法
#pragma mark 設(shè)置狀態(tài)
- (void)setState:(MJRefreshHeaderState)state
{
    if (self.state == state) return;
    
    // 舊狀態(tài)
    MJRefreshHeaderState oldState = self.state;
    
    switch (state) {
        case MJRefreshHeaderStateIdle: {
            if (oldState == MJRefreshHeaderStateRefreshing) {
                self.arrowImage.transform = CGAffineTransformIdentity;
                
                [UIView animateWithDuration:MJRefreshSlowAnimationDuration animations:^{
                    self.activityView.alpha = 0.0;
                } completion:^(BOOL finished) {
                    self.arrowImage.alpha = 1.0;
                    self.activityView.alpha = 1.0;
                    [self.activityView stopAnimating];
                }];
            } else {
                [UIView animateWithDuration:MJRefreshFastAnimationDuration animations:^{
                    self.arrowImage.transform = CGAffineTransformIdentity;
                }];
            }
            break;
        }
            
        case MJRefreshHeaderStatePulling: {
            [UIView animateWithDuration:MJRefreshFastAnimationDuration animations:^{
                self.arrowImage.transform = CGAffineTransformMakeRotation(0.000001 - M_PI);
            }];
            break;
        }
            
        case MJRefreshHeaderStateRefreshing: {
            [self.activityView startAnimating];
            self.arrowImage.alpha = 0.0;
            break;
        }
            
        default:
            break;
    }
    
    // super里面有回調(diào)捌朴,應(yīng)該在最后面調(diào)用
    [super setState:state];
}

@end

看了實(shí)現(xiàn)文件我們可以明白legendHeader是什么樣的了:它在父類原有header的基礎(chǔ)上新添了一個箭頭圖標(biāo)和一個菊花視圖吴攒。在下拉的過程中的不同狀態(tài)下,箭頭和菊花會做出相應(yīng)的動畫顯示男旗。這也就是整個MJRefreshLegendHeader實(shí)現(xiàn)文件所做的事舶斧。

可以看到欣鳖,作者重寫了arrowImageactivityView倆視圖的getter方法來創(chuàng)建其實(shí)例察皇,賦給屬性,并addSubView:MJRefreshLegendHeader的實(shí)例上。
而且作者是在layoutSubviews這個方法中來設(shè)置調(diào)整倆視圖的坐標(biāo)位置的什荣。

然后就是最核心的setState:方法了矾缓,它是一個重寫的父類的setter方法,state是其父類MJRefreshHeader中的屬性稻爬。這個方法的功能是當(dāng)header在不同狀態(tài)時嗜闻,刷新頭顯示相應(yīng)的動畫。

說起狀態(tài)桅锄,在其父類中定義了一個表示“狀態(tài)”的枚舉琉雳。

// 下拉刷新控件的狀態(tài)
typedef enum {
    /** 普通閑置狀態(tài) */
    MJRefreshHeaderStateIdle = 1,
    /** 松開就可以進(jìn)行刷新的狀態(tài) */
    MJRefreshHeaderStatePulling,
    /** 正在刷新中的狀態(tài) */
    MJRefreshHeaderStateRefreshing,
    /** 即將刷新的狀態(tài) */
    MJRefreshHeaderStateWillRefresh
} MJRefreshHeaderState;

MJRefreshHeaderStateIdle,即默認(rèn)/閑置狀態(tài)友瘤,圖示:

屏幕快照 2017-01-03 22.36.31.png

MJRefreshHeaderStatePulling翠肘,即已經(jīng)拉過臨界值,松開手便會觸發(fā)刷新辫秧,圖示:
屏幕快照 2017-01-03 22.36.57.png

MJRefreshHeaderStateRefreshing束倍,即已松開手,正在刷新盟戏,圖示:
屏幕快照 2017-01-03 23.02.32.png

我們梳理一下下拉刷新過程的邏輯:
我們剛開始往下還沒超過臨界值時绪妹,如圖一所示:箭頭是向下的,文字為“下拉可以刷新”柿究。對應(yīng)的代碼為第一個case中的else部分邮旷;
當(dāng)我們繼續(xù)往下拉超過臨界值時,如圖二所示:箭頭旋轉(zhuǎn)為向上蝇摸,文字變?yōu)椤八砷_可以刷新”廊移。對應(yīng)的代碼為第二個case,即MJRefreshHeaderStatePulling部分探入;
當(dāng)我們松開手后狡孔,如圖三所示:箭頭不見了,菊花出現(xiàn)并轉(zhuǎn)動蜂嗽,文字變?yōu)椤罢谒⑿聰?shù)據(jù)中...”苗膝,并且請留意時間也更新了。對應(yīng)的代碼為第一個caseif部分植旧。

整個過程包括了箭頭和菊花的顯示變化辱揭,文字的變化,以及時間的變化病附。但在該類的代碼中只寫了箭頭和菊花的顯示變化问窃,文字和時間的變化是在其父類的setState:方法中完成的,在該方法的結(jié)尾它調(diào)用了父類的setState:方法完沪,這我們都看到了域庇。
該類是header的派生子類嵌戈,它只處理了新派生出的功能,即箭頭和菊花听皿。而父類處理了文字和時間熟呛,那我們也大概能猜測出其父類MJRefreshHeaderheader是個什么模樣:它沒有菊花和箭頭,但已有文字和時間尉姨,且能根據(jù)不同狀態(tài)來變化文字和時間的顯示庵朝。


header的基類——MJRefreshComponent:

從上面我們已知道派生類MJRefreshLegendHeader主要是在其父類的基礎(chǔ)上添加了箭頭和菊花的處理,其父類MJRefreshHeader已經(jīng)是個成型的刷新頭了又厉。麻雀雖小九府,五臟俱全。它能處理狀態(tài)變化的邏輯,雖然沒有圖標(biāo)動畫顯示,但有文本跟隨狀態(tài)而變化出皇。但MJRefreshHeader還不是header的盡頭,它仍然繼承于一個叫MJRefreshComponent的類勾怒,意為“刷新組件”,它定義和實(shí)現(xiàn)了刷新的最基本行為声旺。我們來看看它的代碼:

MJRefreshComponent.h文件:

@interface MJRefreshComponent : UIView
{
    UIEdgeInsets _scrollViewOriginalInset;
    __weak UIScrollView *_scrollView;

}

#pragma mark - 文字處理
/** 文字顏色 */
@property (strong, nonatomic) UIColor *textColor;
/** 字體大小 */
@property (strong, nonatomic) UIFont *font;

#pragma mark - 刷新處理
/** 正在刷新的回調(diào) */
@property (copy, nonatomic) void (^refreshingBlock)();
/** 設(shè)置回調(diào)對象和回調(diào)方法 */
- (void)setRefreshingTarget:(id)target refreshingAction:(SEL)action;
@property (weak, nonatomic) id refreshingTarget;
@property (assign, nonatomic) SEL refreshingAction;
/** 進(jìn)入刷新狀態(tài) */
- (void)beginRefreshing;
/** 結(jié)束刷新狀態(tài) */
- (void)endRefreshing;
/** 是否正在刷新 */
- (BOOL)isRefreshing;
@end

MJRefreshComponent.m文件:

@interface MJRefreshComponent()
/** 記錄scrollView剛開始的inset */
@property (assign, nonatomic) UIEdgeInsets scrollViewOriginalInset;
/** 父控件 */
@property (weak, nonatomic) UIScrollView *scrollView;
@end

@implementation MJRefreshComponent
#pragma mark - 初始化
- (instancetype)initWithFrame:(CGRect)frame
{
    if (self = [super initWithFrame:frame]) {
        // 基本屬性
        self.autoresizingMask = UIViewAutoresizingFlexibleWidth;
        self.backgroundColor = [UIColor clearColor];
        
        // 默認(rèn)文字顏色和字體大小
        self.textColor = MJRefreshLabelTextColor;
        self.font = MJRefreshLabelFont;
    }
    return self;
}

- (void)willMoveToSuperview:(UIView *)newSuperview
{
    [super willMoveToSuperview:newSuperview];
    
    // 舊的父控件
    [self.superview removeObserver:self forKeyPath:MJRefreshContentOffset context:nil];
    
    if (newSuperview) { // 新的父控件
        [newSuperview addObserver:self forKeyPath:MJRefreshContentOffset options:NSKeyValueObservingOptionNew context:nil];
        
        // 設(shè)置寬度
        self.mj_w = newSuperview.mj_w;
        // 設(shè)置位置
        self.mj_x = 0;
        
        // 記錄UIScrollView
        self.scrollView = (UIScrollView *)newSuperview;
        // 設(shè)置永遠(yuǎn)支持垂直彈簧效果
        self.scrollView.alwaysBounceVertical = YES;
        // 記錄UIScrollView最開始的contentInset
        self.scrollViewOriginalInset = self.scrollView.contentInset;
    }
}

#pragma mark - 公共方法
- (void)setRefreshingTarget:(id)target refreshingAction:(SEL)action
{
    self.refreshingTarget = target;
    self.refreshingAction = action;
}

- (void)beginRefreshing
{
    
}

- (void)endRefreshing
{
    
}

- (BOOL)isRefreshing {
    return NO;
}
@end

可以看到MJRefreshComponent類作為header的基類笔链,它只是實(shí)現(xiàn)了一個header最基本最宏觀的東西,具體邏輯是沒有的腮猖。比如只有一些基本屬性的設(shè)置鉴扫,一些基本行為的方法,而且方法并未實(shí)現(xiàn)澈缺,等著讓子類去重寫實(shí)現(xiàn)坪创。而且實(shí)現(xiàn)了一個最根本最核心的行為,就是當(dāng)header被添加到scrollView上時姐赡,監(jiān)聽scrollViewcontentOffset屬性莱预,這是這個控件的最核心行為,一切狀態(tài)變化项滑,以及狀態(tài)變化后引起的UI變化都由此“監(jiān)聽”而生依沮。


結(jié)尾

本篇我們首先研究了提供添加header的接口的分類;然后研究了一個有箭頭和菊花顯示樣式的枪狂,header的派生類危喉;最后研究了其父類的父類MJRefreshComponent,而跳過了MJRefreshHeader類州疾。之所以跳過該類辜限,是因?yàn)樵擃愂?code>MJRefresh的核心類,它實(shí)現(xiàn)了一個成型的严蓖,可以運(yùn)轉(zhuǎn)的header薄嫡,但本篇篇幅已經(jīng)夠長氧急,因此會另起一篇。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末岂座,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子杭措,更是在濱河造成了極大的恐慌费什,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,123評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件手素,死亡現(xiàn)場離奇詭異鸳址,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)泉懦,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,031評論 2 384
  • 文/潘曉璐 我一進(jìn)店門稿黍,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人崩哩,你說我怎么就攤上這事巡球。” “怎么了邓嘹?”我有些...
    開封第一講書人閱讀 156,723評論 0 345
  • 文/不壞的土叔 我叫張陵酣栈,是天一觀的道長。 經(jīng)常有香客問我汹押,道長矿筝,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,357評論 1 283
  • 正文 為了忘掉前任棚贾,我火速辦了婚禮窖维,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘妙痹。我一直安慰自己铸史,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,412評論 5 384
  • 文/花漫 我一把揭開白布怯伊。 她就那樣靜靜地躺著沛贪,像睡著了一般。 火紅的嫁衣襯著肌膚如雪震贵。 梳的紋絲不亂的頭發(fā)上利赋,一...
    開封第一講書人閱讀 49,760評論 1 289
  • 那天,我揣著相機(jī)與錄音猩系,去河邊找鬼媚送。 笑死,一個胖子當(dāng)著我的面吹牛寇甸,可吹牛的內(nèi)容都是我干的塘偎。 我是一名探鬼主播疗涉,決...
    沈念sama閱讀 38,904評論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼吟秩!你這毒婦竟也來了咱扣?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,672評論 0 266
  • 序言:老撾萬榮一對情侶失蹤涵防,失蹤者是張志新(化名)和其女友劉穎闹伪,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體壮池,經(jīng)...
    沈念sama閱讀 44,118評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡偏瓤,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,456評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了椰憋。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片厅克。...
    茶點(diǎn)故事閱讀 38,599評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖橙依,靈堂內(nèi)的尸體忽然破棺而出证舟,到底是詐尸還是另有隱情,我是刑警寧澤窗骑,帶...
    沈念sama閱讀 34,264評論 4 328
  • 正文 年R本政府宣布褪储,位于F島的核電站,受9級特大地震影響慧域,放射性物質(zhì)發(fā)生泄漏鲤竹。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,857評論 3 312
  • 文/蒙蒙 一昔榴、第九天 我趴在偏房一處隱蔽的房頂上張望辛藻。 院中可真熱鬧,春花似錦互订、人聲如沸吱肌。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,731評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽氮墨。三九已至,卻和暖如春吐葵,著一層夾襖步出監(jiān)牢的瞬間规揪,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,956評論 1 264
  • 我被黑心中介騙來泰國打工温峭, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留猛铅,地道東北人。 一個月前我還...
    沈念sama閱讀 46,286評論 2 360
  • 正文 我出身青樓凤藏,卻偏偏與公主長得像奸忽,于是被迫代替她去往敵國和親堕伪。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,465評論 2 348

推薦閱讀更多精彩內(nèi)容

  • MJRefresh 下拉刷新第三方庫栗菜,是一個功能強(qiáng)大簡單實(shí)用的下拉刷新控件欠雌。 整個框架邏輯清晰,類之間的解耦做的很...
    吳佩在天涯閱讀 989評論 0 16
  • MJRefresh 是著名開發(fā)者及培訓(xùn)講師李明杰老師的作品疙筹,到現(xiàn)在在github已經(jīng)有10000多顆star富俄,真真...
    藍(lán)色小石頭閱讀 4,365評論 6 33
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn)腌歉,斷路器蛙酪,智...
    卡卡羅2017閱讀 134,628評論 18 139
  • 昨天出差齐苛,很晚返程翘盖,據(jù)說兒子一直問媽媽怎么還沒回來。 今早睡不醒凹蜂,他又很早來鉆我們被窩馍驯,又抱著他安撫了蠻久,然后起...
    方方_208e閱讀 311評論 0 0
  • 今天去爬山玛痊,無意間摸到一個小小的洞汰瘫,突然想起來了,王家衛(wèi)導(dǎo)的一部電影——《花樣年華》擂煞。 梁朝偉(周慕云)和張曼玉(...
    蘇木南思密達(dá)閱讀 309評論 0 1