分析MJRefresh框架,并模擬下拉刷新

圖一

分析:MJRefresh框架主要使用了分層的設(shè)計(jì)思想,基類和很多子類,每個(gè)類做什么都分工明確,所以也就使得可擴(kuò)展性很強(qiáng)。
基類所負(fù)責(zé)的事情主要是:

  • 開始刷新、結(jié)束刷新、并用KVO來進(jìn)行監(jiān)聽
  • prepare:來添加子view、修改屬性
  • placeSubViews:修改子view的frame
  • setState:通過設(shè)置刷新狀態(tài)來調(diào)用刷新方法

由于這些方法子類也會更改,所以將方法公開,讓子類能夠繼承

注意1

在給父類寫分類的時(shí)候由于,分類可以寫屬性的方法,但是無法寫實(shí)例變量,所以用運(yùn)行時(shí)的方法給scrollView增加一個(gè)實(shí)例變量,如下:

#import <UIKit/UIKit.h>
#import "EOCRefreshBaseHeader.h"

@interface UIScrollView (EOCRefresh)
@property(nonatomic, strong)EOCRefreshBaseHeader *eocHeader;
@end
#import "UIScrollView+EOCRefresh.h"
#import <objc/runtime.h>

@implementation UIScrollView (EOCRefresh)

const static char EOCHeaderKey = 'H';

- (void)setEocHeader:(EOCRefreshBaseHeader *)eocHeader {
    
    if (eocHeader != self.eocHeader) {
        //刪除舊的,添加新的header
        [self.eocHeader removeFromSuperview];
        [self insertSubview:eocHeader atIndex:0];
        //分類可以寫屬性的方法,但是無法寫實(shí)例變量,所以用運(yùn)行時(shí)的方法給scrollView增加一個(gè)實(shí)例變量
        objc_setAssociatedObject(self, &EOCHeaderKey, eocHeader, OBJC_ASSOCIATION_ASSIGN);
    }
}

- (EOCRefreshBaseHeader *)eocHeader {
    return objc_getAssociatedObject(self, &EOCHeaderKey);
}
@end

只有寫了實(shí)例變量,我們才能

self.table.eocHeader = [EOCRefreshNormalHeader headerWithRefreshingBlock:^{
        NSLog(@"header refreshing");
    }];

這樣的進(jìn)行使用。

上面在將header加到tableView上面時(shí),一共做了三件事情

  • 創(chuàng)建header
  • 加到superView
  • 監(jiān)聽offset的改變,做出操作(kvo達(dá)成)
注意2

我們一般把控件的初始化方法寫在initWithFrame方法中,因?yàn)檎{(diào)用init方法的時(shí)候,它會來調(diào)用initWithFrame。

- (instancetype)initWithFrame:(CGRect)frame {
    //initWithFrame 你調(diào)用init方法的時(shí)候,它會來調(diào)用initWithFrame
    if (self = [super initWithFrame:frame]) {
        [self prepare];
        self.state = EOCRefreshStateIdle;// 初始化默認(rèn)狀態(tài),即閑置狀態(tài)header是在屏幕外面的
    }
    return self;
}

比較簡單的控件,我們可以使用autoresizingMask來適配,譬如這里self.autoresizingMask = UIViewAutoresizingFlexibleWidth;//autoSizing,self的寬度它會跟著superView一起改變

注意3
  1. 監(jiān)聽要放在基類的willMoveToSuperview方法中,并且當(dāng)header被不同的tableView添加的時(shí)候,要移除原來的監(jiān)聽。當(dāng)多個(gè)tableView創(chuàng)建多個(gè)header來監(jiān)聽。
  2. 這個(gè)時(shí)候還沒有設(shè)置控件的frame,還可以在這里面設(shè)置它的一些固定的屬性:寬和X值,而高度因頭部控件和尾部控件的高度不一樣,所以先不進(jìn)行設(shè)置。
-(void)willMoveToSuperview:(UIView *)newSuperview {
    //當(dāng)self被添加到superView的時(shí)候,調(diào)用
    if (newSuperview && [newSuperview isKindOfClass:[UIScrollView class]]) {
        
        //非空,而且是UIScrollView
        //同一個(gè)header被不同的table來添加的時(shí)候
        [self.superview removeObserver:self forKeyPath:@"contentOffset"];
        
        self.scrollView = (UIScrollView *)newSuperview;
        self.originalScrollInsets = self.scrollView.contentInset;
        //控件還沒有設(shè)置frame
        self.eoc_x = 0.f;
        self.eoc_w = self.scrollView.eoc_w;
        //footer和header都繼承,這兩者的高度是不一樣
        [newSuperview addObserver:self forKeyPath:@"contentOffset" options:NSKeyValueObservingOptionNew context:nil];
    }
}
注意4

當(dāng)有子控件需要初始化時(shí),要將父控件寫為[[self alloc] init],這樣才會哪個(gè)子類調(diào)用就初始化成哪個(gè)子類。

+ (instancetype)headerWithRefreshingBlock:(eocRefreshingBlock)block {
    EOCRefreshBaseHeader *header = [[self alloc] init];
    header.refreshingBlock = block;
    return header;
}
注意5

往下拉的時(shí)候yOffset是為負(fù)數(shù)。并且header它能夠懸停:是通過contentInset來改變的,改變它的可見范圍。

#import "EOCRefreshBaseHeader.h"

@implementation EOCRefreshBaseHeader

+ (instancetype)headerWithRefreshingBlock:(eocRefreshingBlock)block {
    EOCRefreshBaseHeader *header = [[self alloc] init];
    header.refreshingBlock = block;
    return header;
}
- (void)prepare {
    [super prepare];
    //設(shè)置高度
    self.eoc_h = 50.f;
    // 不能再這里設(shè)置寬度,因?yàn)檫@個(gè)時(shí)候scrollView還為nil,因?yàn)閍lloc要比willMoveToSuperview先調(diào)用
//    self.eoc_w = self.scrollView.eoc_w;

}

- (void)placeSubviews {
    
    [super placeSubviews];
    //設(shè)置y坐標(biāo),跟superView緊密相關(guān)  placeSubviews寫在layoutSubviews中的,所以要在這里面設(shè)置Y值
    self.eoc_y = -self.eoc_h-self.originalScrollInsets.top;
    
}

- (void)scrollOffsetDidChange:(NSDictionary *)change {
    [super scrollOffsetDidChange:change];
    // 我們的header它能夠懸停:contentInset來改變的
    //1、正在refreshing, 你滑動的時(shí)候,contentInset是改變的,因?yàn)槟慊瑒拥搅伺R界點(diǎn) 進(jìn)行刷新的時(shí)候,有EOCRefreshStateIdle和EOCRefreshStateRefreshing兩種狀態(tài),兩種狀態(tài)的contentInset不同的
    CGFloat yOffset = self.scrollView.eoc_offsetY;
    CGFloat boundaryOffset = self.originalScrollInsets.top+self.eoc_h;  // 臨界值
    CGFloat pullingPercent = -yOffset/boundaryOffset;
    
    if (self.state == EOCRefreshStateRefreshing) {
         self.alpha = 1.f;   //如果不寫它的話,刷新的情況下,alpha值會漸變的,實(shí)際效果是在refresh狀態(tài)下不漸變
        //往下拉的時(shí)候,yOffset是為負(fù)的,下拉的距離大于臨界值就是刷新,InsetTop為臨界值,不然就是下拉的距離
        CGFloat finalInsetTop = (-yOffset > boundaryOffset)?boundaryOffset:-yOffset;
        self.scrollView.eoc_insetT = finalInsetTop;
    }

    if (self.scrollView.dragging) {  //正在滑動
        self.alpha = pullingPercent;   // 往下拉的時(shí)候,顏色漸變
        if (self.state == EOCRefreshStateIdle && -yOffset > boundaryOffset) {
            //處于閑置狀態(tài)、而且大于邊界值
            self.state = EOCRefreshStatePulling;   //設(shè)置為正在拉的狀態(tài)
        } else if (self.state == EOCRefreshStatePulling && -yOffset < boundaryOffset) {
            self.state = EOCRefreshStateIdle;   // 設(shè)置為閑置狀態(tài)
        }
    } else {  //松手了
        if (self.state == EOCRefreshStatePulling) {
            self.state = EOCRefreshStateRefreshing;  // 設(shè)置為刷新狀態(tài)
        } else if (pullingPercent < 1) {
            self.alpha = pullingPercent;  // 小于1,即還沒有到刷新狀態(tài)的時(shí)候,header回去的時(shí)候也是漸變
        }
    }
}

- (void)setState:(EOCRefreshState)state {
    [super setState:state];
    if (state == EOCRefreshStateRefreshing) {
        [UIView animateWithDuration:0.4f animations:^{
            self.scrollView.eoc_insetT = self.originalScrollInsets.top+self.eoc_h;
            //因?yàn)閺椈尚Ч?dāng)往下拉,松手后往上彈得時(shí)候,會使有yOffset小于臨界值,從而調(diào)用上面的  CGFloat finalInsetTop = (-yOffset > boundaryOffset)?boundaryOffset:-yOffset; self.scrollView.eoc_insetT = finalInsetTop;  這句代碼使header被隱藏了一部分,所以要加上下面這句代碼
            self.scrollView.eoc_offsetY = -(self.originalScrollInsets.top+self.eoc_h);
        }];
        //刷新block
        [self beginRefresh];
    } else if (state == EOCRefreshStateIdle) {
       [UIView animateWithDuration:0.4f animations:^{
        self.scrollView.contentInset = self.originalScrollInsets;
       }];
    }
    
}
@end
注意6

訪問bundle中的image資源方法

   _arrowImageView =
 [[UIImageView alloc] initWithImage:[UIImage imageWithContentsOfFile:[[NSBundle bundleWithPath:[[NSBundle bundleForClass:[EOCRefreshNormalHeader class]] pathForResource:@"MJRefresh" ofType:@"bundle"]] pathForResource:@"arrow@2x" ofType:@"png"]]];

從MJRefresh.bundle中獲取圖片到EOCRefreshNormalHeader類中

注意7

如果一個(gè)控件經(jīng)常用到,而且屬性都一樣,可以寫成它的分類,并且放在基類里面, 如下面的label:


#import <UIKit/UIKit.h>

@interface EOCRefreshBaseView : UIView

typedef NS_ENUM(NSInteger, EOCRefreshState) {
    EOCRefreshStateIdle = 1,        //閑置狀態(tài)
    EOCRefreshStatePulling,         //釋放就刷新的狀態(tài)
    EOCRefreshStateRefreshing,      //正在刷新狀態(tài)
};

typedef void (^eocRefreshingBlock)();

//刷新數(shù)據(jù)的block
@property(nonatomic, strong)eocRefreshingBlock refreshingBlock;
@property(nonatomic, strong)UIScrollView *scrollView;
@property(nonatomic, assign)UIEdgeInsets originalScrollInsets;
@property(nonatomic, assign)EOCRefreshState state;

- (void)prepare;  // 來添加子view、修改屬性
- (void)placeSubviews;  // 修改子view的frame
- (void)endRefresh;  //結(jié)束刷新
- (void)beginRefresh;  //開始刷新
- (void)scrollOffsetDidChange:(NSDictionary *)change;

@end

@interface UILabel (EOCLabel)
+ (instancetype)eocLabel;
- (CGFloat)textWidth;

@end
#import "EOCRefreshBaseView.h"
@interface EOCRefreshBaseView ()
@end
@implementation EOCRefreshBaseView
//head加進(jìn)來:創(chuàng)建header、加到superView、監(jiān)聽offset的改變,做出操作(kvo達(dá)成)

-(instancetype)initWithFrame:(CGRect)frame {
    //initWithFrame 你調(diào)用init方法的時(shí)候,它會來調(diào)用initWithFrame
    if (self = [super initWithFrame:frame]) {
        [self prepare];
        self.state = EOCRefreshStateIdle;  // 初始化默認(rèn)狀態(tài),即閑置狀態(tài)header是在屏幕外面的
    }
    return self;
}

-(void)prepare {
    self.backgroundColor = [UIColor redColor];
    self.autoresizingMask = UIViewAutoresizingFlexibleWidth;//autoSizing,self的寬度它會跟著superView一起改變
}

-(void)layoutSubviews {
    
    [super layoutSubviews];
    //修改子view的frame
    [self placeSubviews];
}

-(void)placeSubviews {}

-(void)willMoveToSuperview:(UIView *)newSuperview {
    //當(dāng)self被添加到superView的時(shí)候,調(diào)用
    if (newSuperview && [newSuperview isKindOfClass:[UIScrollView class]]) {
        
        //非空,而且是UIScrollView
        //同一個(gè)header被不同的table來添加的時(shí)候
        [self.superview removeObserver:self forKeyPath:@"contentOffset"];
        
        self.scrollView = (UIScrollView *)newSuperview;
        self.originalScrollInsets = self.scrollView.contentInset;
        //控件還沒有設(shè)置frame
        self.eoc_x = 0.f;
        self.eoc_w = self.scrollView.eoc_w;
        //footer和header都繼承,這兩者的高度是不一樣
        [newSuperview addObserver:self forKeyPath:@"contentOffset" options:NSKeyValueObservingOptionNew context:nil];
    }
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
    if ([keyPath isEqualToString:@"contentOffset"]) {
        [self scrollOffsetDidChange:change];
    }
}

-(void)scrollOffsetDidChange:(NSDictionary *)change {}

-(void)setState:(EOCRefreshState)state {
    _state = state;
    
}

-(void)endRefresh {
    //把當(dāng)前的時(shí)間存起來
    [[NSUserDefaults standardUserDefaults]setObject:[NSDate date] forKey:@"lastUpdateDate"];
    self.state = EOCRefreshStateIdle;
    
}

-(void)beginRefresh {
    
    if (_refreshingBlock) {
        _refreshingBlock();
    }
}
@end

@implementation UILabel (EOCLabel)

+ (instancetype)eocLabel {
    UILabel *label = [[UILabel alloc] init];
    label.font = [UIFont systemFontOfSize:14.f];
    label.backgroundColor = [UIColor clearColor];
    label.textColor = [UIColor blueColor];
    label.autoresizingMask = UIViewAutoresizingFlexibleWidth;
    label.textAlignment = NSTextAlignmentCenter;
    return label;
}

- (CGFloat)textWidth {
    
    NSString *text = self.text;
    UIFont *font = self.font;
    CGSize textSize = [text sizeWithAttributes:@{NSFontAttributeName:font}];
    return textSize.width;
    
}
@end
注意8

箭頭旋轉(zhuǎn)的時(shí)候,如果要實(shí)現(xiàn)順時(shí)針旋轉(zhuǎn)下來,逆時(shí)針旋轉(zhuǎn)回去,需要將其中一個(gè)設(shè)置為CGAffineTransformMakeRotation(0.00001-M_PI),用0.00001減去M_PI,因?yàn)樾D(zhuǎn)的時(shí)候遵循下面的三點(diǎn)

  1. 基本按照最短路徑來,如果順時(shí)針、逆時(shí)針角度一樣的,按順時(shí)針來
  2. CGAffineTransformMakeRotation會根據(jù)最初始的值來計(jì)算角度
  3. 角度為正,順時(shí)針;角度為負(fù),逆時(shí)針

所以當(dāng)用0.00001減去M_PI的時(shí)候,那么來的路徑變短了,所以就原路返回了

- (void)setState:(EOCRefreshState)state {
    
    [super setState:state];
    //1、刷新的時(shí)候,箭頭要隱藏,loadview顯示; 2、pulling的時(shí)候,箭頭的方向改變
    if (state == EOCRefreshStateIdle) {
        [UIView animateWithDuration:0.4f animations:^{
            self.loadView.alpha = 1.f;
            _arrowImageView.transform = CGAffineTransformIdentity;
        } completion:^(BOOL finished) {
//            self.loadView.hidden = YES;
            self.loadView.alpha = 0.f;
            [self.loadView stopAnimating];
            _arrowImageView.alpha = 1.f;
            
        }];
    } else if (state == EOCRefreshStatePulling) {
        
        [UIView animateWithDuration:0.4f animations:^{
            _arrowImageView.transform = CGAffineTransformMakeRotation(0.0001-M_PI);
            
        }];
        
    } else if (state == EOCRefreshStateRefreshing) {
        _arrowImageView.alpha = 0.f;
//        _loadView.hidden = NO;
        _loadView.alpha = 0.f;
        [_loadView startAnimating];
        
    }
    
}

附注:
在iOS11過后,我們需要的contentInset的上下左右都要減去安全邊距。
如下:

#import <UIKit/UIKit.h>

@interface UIScrollView (EOCExtention)

@property (readonly, nonatomic) UIEdgeInsets eoc_inset;

@property (assign, nonatomic) CGFloat eoc_insetT;
@property (assign, nonatomic) CGFloat eoc_insetB;
@property (assign, nonatomic) CGFloat eoc_insetL;
@property (assign, nonatomic) CGFloat eoc_insetR;

@property (assign, nonatomic) CGFloat eoc_offsetX;
@property (assign, nonatomic) CGFloat eoc_offsetY;

@property (assign, nonatomic) CGFloat eoc_contentW;
@property (assign, nonatomic) CGFloat eoc_contentH;

@end
#import "UIScrollView+EOCExtention.h"

@implementation UIScrollView (EOCExtention)

- (UIEdgeInsets)eoc_inset {
    
    UIEdgeInsets insets = self.contentInset;
    
    if (@available(iOS 11, *)) {
        // http://www.lxweimin.com/p/4f60d45097f0
        insets = self.adjustedContentInset;
        
    }
    return insets;
    
}

- (void)setEoc_insetT:(CGFloat)eoc_insetT
{
    //右邊是不是等于adjustContentInsets:safeAreaInset+contentInset
    
    UIEdgeInsets inset = self.contentInset;
    inset.top = eoc_insetT;
    
    if (@available(iOS 11, *)) {
        
        inset.top -= self.safeAreaInsets.top;
        
    }
    
    self.contentInset = inset;
    
}

- (CGFloat)eoc_insetT
{
    return self.eoc_inset.top;
}

- (void)setEoc_insetB:(CGFloat)eoc_insetB
{
    UIEdgeInsets inset = self.contentInset;
    inset.bottom = eoc_insetB;
    
    if (@available(iOS 11, *)) {
        
        inset.bottom -= self.safeAreaInsets.bottom;
        
    }
    
    self.contentInset = inset;
}

- (CGFloat)eoc_insetB
{
    return self.eoc_inset.bottom;
}

- (void)setEoc_insetL:(CGFloat)eoc_insetL
{
    UIEdgeInsets inset = self.contentInset;
    inset.left = eoc_insetL;
    
    if (@available(iOS 11, *)) {
        
        inset.left -= self.safeAreaInsets.left;
        
    }
    
    self.contentInset = inset;
}

- (CGFloat)eoc_insetL
{
    return self.eoc_inset.left;
}

- (void)setEoc_insetR:(CGFloat)eoc_insetR
{
    UIEdgeInsets inset = self.contentInset;
    inset.right = eoc_insetR;
    
    if (@available(iOS 11, *)) {
        
        inset.right -= self.safeAreaInsets.right;
        
    }
    
    self.contentInset = inset;
}

- (CGFloat)eoc_insetR
{
    return self.eoc_inset.right;
}

- (void)setEoc_offsetX:(CGFloat)eoc_offsetX
{
    CGPoint offset = self.contentOffset;
    offset.x = eoc_offsetX;
    self.contentOffset = offset;
}

- (CGFloat)eoc_offsetX
{
    return self.contentOffset.x;
}

- (void)setEoc_offsetY:(CGFloat)eoc_offsetY
{
    CGPoint offset = self.contentOffset;
    offset.y = eoc_offsetY;
    self.contentOffset = offset;
}

- (CGFloat)eoc_offsetY
{
    return self.contentOffset.y;
}

- (void)setEoc_contentW:(CGFloat)eoc_contentW
{
    CGSize size = self.contentSize;
    size.width = eoc_contentW;
    self.contentSize = size;
}

- (CGFloat)eoc_contentW
{
    return self.contentSize.width;
}

- (void)setEoc_contentH:(CGFloat)eoc_contentH
{
    CGSize size = self.contentSize;
    size.height = eoc_contentH;
    self.contentSize = size;
}

- (CGFloat)eoc_contentH
{
    return self.contentSize.height;
}

@end
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,527評論 6 544
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,687評論 3 429
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,640評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,957評論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 72,682評論 6 413
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 56,011評論 1 329
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,009評論 3 449
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 43,183評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,714評論 1 336
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 41,435評論 3 359
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 43,665評論 1 374
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,148評論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,838評論 3 350
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,251評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,588評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 52,379評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 48,627評論 2 380

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