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

圖一

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

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

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

注意1

在給父類寫分類的時候由于沉馆,分類可以寫屬性的方法,但是無法寫實例變量德崭,所以用運行時的方法給scrollView增加一個實例變量斥黑,如下:

#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];
        //分類可以寫屬性的方法眉厨,但是無法寫實例變量锌奴,所以用運行時的方法給scrollView增加一個實例變量
        objc_setAssociatedObject(self, &EOCHeaderKey, eocHeader, OBJC_ASSOCIATION_ASSIGN);
    }
}

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

只有寫了實例變量,我們才能

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

這樣的進行使用缺猛。

上面在將header加到tableView上面時缨叫,一共做了三件事情

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

我們一般把控件的初始化方法寫在initWithFrame方法中荔燎,因為調(diào)用init方法的時候耻姥,它會來調(diào)用initWithFrame。

- (instancetype)initWithFrame:(CGRect)frame {
    //initWithFrame 你調(diào)用init方法的時候有咨,它會來調(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添加的時候婉商,要移除原來的監(jiān)聽似忧。當(dāng)多個tableView創(chuàng)建多個header來監(jiān)聽。
  2. 這個時候還沒有設(shè)置控件的frame丈秩,還可以在這里面設(shè)置它的一些固定的屬性:寬和X值盯捌,而高度因頭部控件和尾部控件的高度不一樣,所以先不進行設(shè)置蘑秽。
-(void)willMoveToSuperview:(UIView *)newSuperview {
    //當(dāng)self被添加到superView的時候饺著,調(diào)用
    if (newSuperview && [newSuperview isKindOfClass:[UIScrollView class]]) {
        
        //非空,而且是UIScrollView
        //同一個header被不同的table來添加的時候
        [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)有子控件需要初始化時幼衰,要將父控件寫為[[self alloc] init],這樣才會哪個子類調(diào)用就初始化成哪個子類缀雳。

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

往下拉的時候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è)置寬度识椰,因為這個時候scrollView還為nil,因為alloc要比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挤牛, 你滑動的時候莹痢,contentInset是改變的,因為你滑動到了臨界點 進行刷新的時候墓赴,有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值會漸變的坦辟,實際效果是在refresh狀態(tài)下不漸變
        //往下拉的時候,yOffset是為負(fù)的章办,下拉的距離大于臨界值就是刷新锉走,InsetTop為臨界值,不然就是下拉的距離
        CGFloat finalInsetTop = (-yOffset > boundaryOffset)?boundaryOffset:-yOffset;
        self.scrollView.eoc_insetT = finalInsetTop;
    }

    if (self.scrollView.dragging) {  //正在滑動
        self.alpha = pullingPercent;   // 往下拉的時候藕届,顏色漸變
        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)的時候休偶,header回去的時候也是漸變
        }
    }
}

- (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;
            //因為彈簧效果梁厉,當(dāng)往下拉,松手后往上彈得時候踏兜,會使有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

如果一個控件經(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加進來:創(chuàng)建header舍悯、加到superView、監(jiān)聽offset的改變睡雇,做出操作(kvo達(dá)成)

-(instancetype)initWithFrame:(CGRect)frame {
    //initWithFrame 你調(diào)用init方法的時候萌衬,它會來調(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的時候,調(diào)用
    if (newSuperview && [newSuperview isKindOfClass:[UIScrollView class]]) {
        
        //非空它抱,而且是UIScrollView
        //同一個header被不同的table來添加的時候
        [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)前的時間存起來
    [[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)的時候,如果要實現(xiàn)順時針旋轉(zhuǎn)下來观蓄,逆時針旋轉(zhuǎn)回去混移,需要將其中一個設(shè)置為CGAffineTransformMakeRotation(0.00001-M_PI),用0.00001減去M_PI侮穿,因為旋轉(zhuǎn)的時候遵循下面的三點

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

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

- (void)setState:(EOCRefreshState)state {
    
    [super setState:state];
    //1袭祟、刷新的時候验残,箭頭要隱藏,loadview顯示巾乳; 2您没、pulling的時候,箭頭的方向改變
    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.reibang.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)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市辑舷,隨后出現(xiàn)的幾起案子喻犁,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,888評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件肢础,死亡現(xiàn)場離奇詭異还栓,居然都是意外死亡,警方通過查閱死者的電腦和手機传轰,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,677評論 3 399
  • 文/潘曉璐 我一進店門剩盒,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人慨蛙,你說我怎么就攤上這事辽聊。” “怎么了期贫?”我有些...
    開封第一講書人閱讀 168,386評論 0 360
  • 文/不壞的土叔 我叫張陵跟匆,是天一觀的道長。 經(jīng)常有香客問我通砍,道長玛臂,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,726評論 1 297
  • 正文 為了忘掉前任封孙,我火速辦了婚禮迹冤,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘虎忌。我一直安慰自己泡徙,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 68,729評論 6 397
  • 文/花漫 我一把揭開白布膜蠢。 她就那樣靜靜地躺著堪藐,像睡著了一般。 火紅的嫁衣襯著肌膚如雪狡蝶。 梳的紋絲不亂的頭發(fā)上庶橱,一...
    開封第一講書人閱讀 52,337評論 1 310
  • 那天贮勃,我揣著相機與錄音贪惹,去河邊找鬼。 笑死寂嘉,一個胖子當(dāng)著我的面吹牛奏瞬,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播泉孩,決...
    沈念sama閱讀 40,902評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼硼端,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了寓搬?” 一聲冷哼從身側(cè)響起珍昨,我...
    開封第一講書人閱讀 39,807評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后镣典,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體兔毙,經(jīng)...
    沈念sama閱讀 46,349評論 1 318
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,439評論 3 340
  • 正文 我和宋清朗相戀三年兄春,在試婚紗的時候發(fā)現(xiàn)自己被綠了澎剥。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,567評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡赶舆,死狀恐怖哑姚,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情芜茵,我是刑警寧澤叙量,帶...
    沈念sama閱讀 36,242評論 5 350
  • 正文 年R本政府宣布,位于F島的核電站九串,受9級特大地震影響宛乃,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜蒸辆,卻給世界環(huán)境...
    茶點故事閱讀 41,933評論 3 334
  • 文/蒙蒙 一征炼、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧躬贡,春花似錦谆奥、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,420評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至檐蚜,卻和暖如春魄懂,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背闯第。 一陣腳步聲響...
    開封第一講書人閱讀 33,531評論 1 272
  • 我被黑心中介騙來泰國打工市栗, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人咳短。 一個月前我還...
    沈念sama閱讀 48,995評論 3 377
  • 正文 我出身青樓填帽,卻偏偏與公主長得像,于是被迫代替她去往敵國和親咙好。 傳聞我的和親對象是個殘疾皇子篡腌,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,585評論 2 359

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