iOS開發(fā)-UIScrollView實(shí)踐深入學(xué)習(xí)

UIScrollView (包括它的子類 UITableView和UICollectionView)是iOS開發(fā)中最有擴(kuò)展性的UI控件虽另。UIScrollView 是 UIKit 中為數(shù)不多能響應(yīng)滑動(dòng)手勢的 view捂刺,相比自己用UIPanGestureRecognizer 實(shí)現(xiàn)一些基于滑動(dòng)手勢的效果族展,用 UIScrollView 的優(yōu)勢在于 bounce 和 decelerate 等特性可以讓 App 的用戶體驗(yàn)與 iOS 系統(tǒng)的用戶體驗(yàn)保持一致贵涵。本文通過一些實(shí)例講解 UIScrollView 的特性和實(shí)際使用中的經(jīng)驗(yàn)宾茂。

UIScrollView 和 Auto Layout

UIScrollView 在 Auto Layout 是一個(gè)很特殊的 view拴还,對(duì)于 UIScrollView 的 subview 來說自沧,它的 leading/trailing/top/bottom space 是相對(duì)于 UIScrollView 的 contentSize 而不是 bounds 來確定的树瞭,所以當(dāng)你嘗試用 UIScrollView 和它 subview 的 leading/trailing/top/bottom 來互相決定大小的時(shí)候拇厢,就會(huì)出現(xiàn)「Has ambiguous scrollable content width/height」的 warning。正確的姿勢是用 UIScrollView 外部的 view 或 UIScrollView 本身的 width/height 確定 subview 的尺寸晒喷,進(jìn)而確定 contentSize孝偎。因?yàn)?UIScrollView 本身的 leading/trailing/top/bottom 變得不好用,所以我習(xí)慣的做法是在 UIScrollView 和它原來的 subviews 之間增加一個(gè) content view凉敲,這樣做的好處有:

不會(huì)在 storyboard 里留下 error/warning
為 subview 提供 leading/trailing/top/bottom衣盾,方便 subview 的布局
通過調(diào)整 content view 的 size(可以是 constraint 的 IBOutlet)來調(diào)整 contentSize
不需要 hard code 與屏幕尺寸相關(guān)的代碼
更好地支持 rotation

UIScrollViewDelegate

UIScrollViewDelegate 是 UIScrollView 的 delegate protocol,UIScrollView 有意思的功能都是通過它的 delegate 方法實(shí)現(xiàn)的爷抓。了解這些方法被觸發(fā)的條件及調(diào)用的順序?qū)τ谑褂?UIScrollView 是很有必要的势决,本文主要講拖動(dòng)相關(guān)的效果果复,所以 zoom 相關(guān)的方法跳過不提走搁,拖動(dòng)相關(guān)的 delegate 方法按調(diào)用順序分別是:

  • (void)scrollViewDidScroll:(UIScrollView *)scrollView

這個(gè)方法在任何方式觸發(fā) contentOffset 變化的時(shí)候都會(huì)被調(diào)用(包括用戶拖動(dòng),減速過程曲稼,直接通過代碼設(shè)置等),可以用于監(jiān)控 contentOffset 的變化清女,并根據(jù)當(dāng)前的 contentOffset 對(duì)其他 view 做出隨動(dòng)調(diào)整。

  • (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView

用戶開始拖動(dòng) scroll view 的時(shí)候被調(diào)用。

  • (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset

該方法從 iOS 5 引入父泳,在 didEndDragging 前被調(diào)用,當(dāng) willEndDragging 方法中 velocity 為 CGPointZero(結(jié)束拖動(dòng)時(shí)兩個(gè)方向都沒有速度)時(shí)前翎,didEndDragging 中的 decelerate 為 NO脾歇,即沒有減速過程藕各,willBeginDecelerating 和 didEndDecelerating 也就不會(huì)被調(diào)用。反之激况,當(dāng) velocity 不為 CGPointZero 時(shí)誉碴,scroll view 會(huì)以 velocity 為初速度代咸,減速直到 targetContentOffset呐芥。值得注意的是思瘟,這里的 targetContentOffset 是個(gè)指針滨攻,沒錯(cuò)光绕,你可以改變減速運(yùn)動(dòng)的目的地诞帐,這在一些效果的實(shí)現(xiàn)時(shí)十分有用停蕉,實(shí)例章節(jié)中會(huì)具體提到它的用法慧起,并和其他實(shí)現(xiàn)方式作比較驯绎。

  • (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate

在用戶結(jié)束拖動(dòng)后被調(diào)用剩失,decelerate 為 YES 時(shí)拴孤,結(jié)束拖動(dòng)后會(huì)有減速過程演熟。注兄纺,在 didEndDragging 之后估脆,如果有減速過程疙赠,scroll view 的 dragging 并不會(huì)立即置為 NO圃阳,而是要等到減速結(jié)束之后捍岳,所以這個(gè) dragging 屬性的實(shí)際語義更接近 scrolling祟同。

  • (void)scrollViewWillBeginDecelerating:(UIScrollView *)scrollView

減速動(dòng)畫開始前被調(diào)用。

  • (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView

減速動(dòng)畫結(jié)束時(shí)被調(diào)用砖顷,這里有一種特殊情況:當(dāng)一次減速動(dòng)畫尚未結(jié)束的時(shí)候再次 drag scroll view滤蝠,didEndDecelerating 不會(huì)被調(diào)用物咳,并且這時(shí) scroll view 的 dragging 和 decelerating 屬性都是 YES览闰。新的 dragging 如果有加速度压鉴,那么 willBeginDecelerating 會(huì)再一次被調(diào)用击蹲,然后才是 didEndDecelerating歌豺;如果沒有加速度,雖然 willBeginDecelerating 不會(huì)被調(diào)用轮听,但前一次留下的 didEndDecelerating 會(huì)被調(diào)用血巍,所以連續(xù)快速滾動(dòng)一個(gè) scroll view 時(shí)述寡,delegate 方法被調(diào)用的順序(不含 didScroll)可能是這樣的:

scrollViewWillBeginDragging:
scrollViewWillEndDragging: withVelocity: targetContentOffset:
scrollViewDidEndDragging: willDecelerate:
scrollViewWillBeginDecelerating:
scrollViewWillBeginDragging:
scrollViewWillEndDragging: withVelocity: targetContentOffset:
scrollViewDidEndDragging: willDecelerate:
scrollViewWillBeginDecelerating:
...
scrollViewWillBeginDragging:
scrollViewWillEndDragging: withVelocity: targetContentOffset:
scrollViewDidEndDragging: willDecelerate:
scrollViewWillBeginDecelerating:
scrollViewDidEndDecelerating:

雖然很少有因?yàn)檫@個(gè)導(dǎo)致的 bug,但是你需要知道這種很常見的用戶操作會(huì)導(dǎo)致的中間狀態(tài)螟炫。例如你嘗試在 UITableViewDataSource 的 tableView:cellForRowAtIndexPath: 方法中基于 tableView 的 dragging 和 decelerating 屬性判斷是在用戶拖拽還是減速過程中的話可能會(huì)誤判。

實(shí)例

下面通過一些實(shí)例然评,更詳細(xì)地演示和描述以上各 delegate 方法的用途碗淌。

  1. Table View 中圖片加載邏輯的優(yōu)化
    雖然這種優(yōu)化方式在現(xiàn)在的機(jī)能和網(wǎng)絡(luò)環(huán)境下可能看似不那么必要亿眠,但在我最初看到這個(gè)方法是的 09 年(印象中是 Tweetie 作者在 08 年寫的 Blog,可能有誤),遙想 iPhone 3G/3GS 的機(jī)能,這個(gè)方法為多圖的 table view 的性能帶來很大的提升,也成了我的秘密武器山卦。而現(xiàn)在账蓉,在移動(dòng)網(wǎng)絡(luò)環(huán)境下,你依然值得這么做來為用戶節(jié)省流量箱玷。

先說一下原文的思路:

當(dāng)用戶手動(dòng) drag table view 的時(shí)候,會(huì)加載 cell 中的圖片舶得;
在用戶快速滑動(dòng)的減速過程中,不加載過程中 cell 中的圖片(但文字信息還是會(huì)被加載珠插,只是減少減速過程中的網(wǎng)絡(luò)開銷和圖片加載的開銷)捻撑;
在減速結(jié)束后,加載所有可見 cell 的圖片(如果需要的話)江解;

問題 1:

前面提到鳖枕,剛開始拖動(dòng)的時(shí)候宾符,dragging 為 YES,decelerating 為 NO哄褒;decelerate 過程中,dragging 和 decelerating 都為 YES罚舱;decelerate 未結(jié)束時(shí)開始下一次拖動(dòng),dragging 和 decelerating 依然都為 YES包个。所以無法簡單通過 table view 的 dragging 和 decelerating 判斷是在用戶拖動(dòng)還是減速過程。

解決這個(gè)問題很簡單糯而,添加一個(gè)變量如 userDragging熄驼,在 willBeginDragging 中設(shè)為 YES,didEndDragging 中設(shè)為 NO祭芦。那么 tableView: cellForRowAtIndexPath: 方法中胃夏,是否 load 圖片的邏輯就是:

if (!self.userDragging && tableView.decelerating) {  
    cell.imageView.image = nil;
} else {
    // code for loading image from network or disk
}

問題 2:

這么做的話侮叮,decelerate 結(jié)束后囊榜,屏幕上的 cell 都是不帶圖片的砂沛,解決這個(gè)問題也不難,你需要一個(gè)形如 loadImageForVisibleCells 的方法静浴,加載可見 cell 的圖片:

- (void)loadImageForVisibleCells
{
    NSArray *cells = [self.tableView visibleCells];
    for (GLImageCell *cell in cells) {
        NSIndexPath *indexPath = [self.tableView indexPathForCell:cell];
        [self setupCell:cell withIndexPath:indexPath];
    }
}

問題 3:

這個(gè)問題可能不容易被發(fā)現(xiàn),在減速過程中如果用戶開始新的拖動(dòng)得问,當(dāng)前屏幕的 cell 并不會(huì)被加載(前文提到的調(diào)用順序問題導(dǎo)致),而且問題 1 的方案并不能解決問題 3漓骚,因?yàn)檫@些 cell 已經(jīng)在屏上,不會(huì)再次經(jīng)過 cellForRowAtIndexPath 方法叉信。雖然不容易發(fā)現(xiàn)硅急,但解決很簡單,只需要在 scrollViewWillBeginDragging: 方法里也調(diào)用一次 loadImageForVisibleCells 即可荚板。

再優(yōu)化

上述方法在那個(gè)年代的確提升了 table view 的 performance,但是你會(huì)發(fā)現(xiàn)在減速過程最后最慢的那零點(diǎn)幾秒時(shí)間免绿,其實(shí)還是會(huì)讓人等得有些心急,尤其如果你的 App 只有圖片沒有文字辽故。在 iOS 5 引入了 scrollViewWillEndDragging: withVelocity: targetContentOffset: 方法后,配合 SDWebImage彤枢,我嘗試再優(yōu)化了一下這個(gè)方法以提升用戶體驗(yàn):

如果內(nèi)存中有圖片的緩存,減速過程中也會(huì)加載該圖片
如果圖片屬于 targetContentOffset 能看到的 cell业栅,正常加載,這樣一來帮孔,快速滾動(dòng)的最后一屏出來的的過程中,用戶就能看到目標(biāo)區(qū)域的圖片逐漸加載
你可以嘗試用類似 fade in 或者 flip 的效果緩解生硬的突然出現(xiàn)(尤其是像本例這樣只有圖片的 App)
核心代碼:

- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView
{
    self.targetRect = nil;
    [self loadImageForVisibleCells];
}

- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset
{
    CGRect targetRect = CGRectMake(targetContentOffset->x, targetContentOffset->y, scrollView.frame.size.width, scrollView.frame.size.height);
    self.targetRect = [NSValue valueWithCGRect:targetRect];
}

- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
{
    self.targetRect = nil;
    [self loadImageForVisibleCells];
}

是否需要加載圖片的邏輯:

BOOL shouldLoadImage = YES;  
if (self.targetRect && !CGRectIntersectsRect([self.targetRect CGRectValue], cellFrame)) {  
    SDImageCache *cache = [manager imageCache];
    NSString *key = [manager cacheKeyForURL:targetURL];
    if (![cache imageFromMemoryCacheForKey:key]) {
        shouldLoadImage = NO;
    }
}
if (shouldLoadImage) {  
    // load image
}

更值得高興的是,通過判斷是否 nil,targetRect 同時(shí)起到了原來 userDragging 的作用幢妄。

2. 分頁的幾種實(shí)現(xiàn)方式

利用 UIScrollView 有多種方法實(shí)現(xiàn)分頁,但是各自的效果和用途不盡相同忍法,其中方法 2 和方法 3 的區(qū)別也正是一些同類 App 在模仿 Glow 的首頁 Bubble 翻轉(zhuǎn)效果時(shí)跟 Glow 體驗(yàn)上的的差距所在(但愿他們不會(huì)看到本文并且調(diào)整他們的實(shí)現(xiàn)方式)羹蚣。本例通過三種方法實(shí)現(xiàn)相似的一個(gè)場景咽弦,你可以通過安裝到手機(jī)上來感受三種實(shí)現(xiàn)方式的不同用戶體驗(yàn)。為了區(qū)分每個(gè)例子的重點(diǎn)闹蒜,本例沒有重用機(jī)制,重用相關(guān)內(nèi)容見例 3砌烁。

2.1 pagingEnabled

這是系統(tǒng)提供的分頁方式疏唾,最簡單,但是有一些局限性:

只能以 frame size 為單位翻頁顿天,減速動(dòng)畫阻尼大,減速過程不超過一頁
需要一些 hacking 實(shí)現(xiàn) bleeding 和 padding(即頁與頁之間有 padding鸟缕,在當(dāng)前頁可以看到前后頁的部分內(nèi)容)
Sample 中 Pagination 有簡單實(shí)現(xiàn) bleeding 和 padding 效果的代碼,主要的思路是:

讓 scroll view 的寬度為 page 寬度 + padding番甩,并且設(shè)置 clipsToBounds 為 NO
這樣雖然能看到前后頁的內(nèi)容,但是無法響應(yīng) touch,所以需要另一個(gè)覆蓋期望的可觸摸區(qū)域的 view 來實(shí)現(xiàn)類似 touch bridging 的功能
適用場景:上述局限性同時(shí)也是這種實(shí)現(xiàn)方式的優(yōu)點(diǎn)牺汤,比如一般 App 的引導(dǎo)頁(教程),Calendar 里的月視圖追迟,都可以用這種方法實(shí)現(xiàn)。

2.2 Snap

這種方法就是在 didEndDragging 且無減速動(dòng)畫厢绝,或在減速動(dòng)畫完成時(shí),snap 到一個(gè)整數(shù)頁。核心算法是通過當(dāng)前 contentOffset 計(jì)算最近的整數(shù)頁及其對(duì)應(yīng)的 contentOffset娄周,通過動(dòng)畫 snap 到該頁。這個(gè)方法實(shí)現(xiàn)的效果都有個(gè)通病掷酗,就是最后的 snap 會(huì)在 decelerate 結(jié)束以后才發(fā)生且轨,總感覺很突兀。

2.3 修改 targetContentOffset

通過修改 scrollViewWillEndDragging: withVelocity: targetContentOffset: 方法中的 targetContentOffset 直接修改目標(biāo) offset 為整數(shù)頁位置至朗。其中核心代碼:

- (CGPoint)nearestTargetOffsetForOffset:(CGPoint)offset
{
    CGFloat pageSize = BUBBLE_DIAMETER + BUBBLE_PADDING;
    NSInteger page = roundf(offset.x / pageSize);
    CGFloat targetX = pageSize * page;
    return CGPointMake(targetX, offset.y);
}

- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset
{
    CGPoint targetOffset = [self nearestTargetOffsetForOffset:*targetContentOffset];
    targetContentOffset->x = targetOffset.x;
    targetContentOffset->y = targetOffset.y;
}

適用場景:方法 2 和 方法 3 的原理近似唆香,效果也相近,適用場景也基本相同倘待,但方法 3 的體驗(yàn)會(huì)好很多祸挪,snap 到整數(shù)頁的過程很自然,或者說用戶完全感知不到 snap 過程的存在。這兩種方法的減速過程流暢公黑,適用于一屏有多頁,但需要按整數(shù)頁滑動(dòng)的場景朝蜘;也適用于如圖表中自動(dòng) snap 到整數(shù)天的場景步做;還適用于每頁大小不同的情況下 snap 到整數(shù)頁的場景(不做舉例煮剧,自行發(fā)揮,其實(shí)只需要修改計(jì)算目標(biāo) offset 的方法)。

3. 重用

大部分的 iOS 開發(fā)應(yīng)該都清楚 UITableView 的 cell 重用機(jī)制驱还,這種重用機(jī)制減少了內(nèi)存開銷也提高了 performance闷沥,UIScrollView 作為 UITableView 的父類戳粒,在很多場景中也很適合應(yīng)用重用機(jī)制(其實(shí)不只是 UIScrollView奄妨,任何場景中會(huì)反復(fù)出現(xiàn)的元素都應(yīng)該適當(dāng)?shù)匾胫赜脵C(jī)制)树枫。

你可以參照 UITableView 的 cell 重用機(jī)制奔誓,總結(jié)重用機(jī)制如下:

  • 維護(hù)一個(gè)重用隊(duì)列
  • 當(dāng)元素離開可見范圍時(shí)体谒,removeFromSuperview 并加入重用隊(duì)列(enqueue)
  • 當(dāng)需要加入新的元素時(shí),先嘗試從重用隊(duì)列獲取可重用元素(dequeue)并且從重用隊(duì)列移除
  • 如果隊(duì)列為空,新建元素
  • 這些一般都在 scrollViewDidScroll: 方法中完成

實(shí)際使用中,需要注意的點(diǎn)是:

  • 當(dāng)重用對(duì)象為 view controller 時(shí)樟蠕,記得 addChildeViewController
  • 當(dāng) view 或 view controller 被重用但其對(duì)應(yīng) model 發(fā)生變化的時(shí)候吓懈,需要及時(shí)清理重用前留下的內(nèi)容
  • 數(shù)據(jù)可以適當(dāng)做緩存,在重用的時(shí)候嘗試從緩存中讀取數(shù)據(jù)甚至之前的狀態(tài)(如 table view 的 contentOffset),以得到更好的用戶體驗(yàn)
  • 當(dāng) on screen 的元素?cái)?shù)量可確定的時(shí)候温兼,有時(shí)候可以提前 init 這些元素吝羞,不會(huì)在 scroll 過程中遇到因?yàn)?init 開銷帶來的卡頓(尤其是以 view controller 為重用對(duì)象的時(shí)候)

例 2 中的場景很適合以 view 為重用單位,本例新增一個(gè)以 view controller 為重用對(duì)象的例子,該例子同時(shí)演示了聯(lián)動(dòng)效果判族,具體見下個(gè)例子。

4. 聯(lián)動(dòng)/視差滾動(dòng)

上一個(gè)例子里 main scroll view 和 title view 里的 scroll view 就是一個(gè)聯(lián)動(dòng)的例子,所謂聯(lián)動(dòng)合冀,就是當(dāng) A 滾動(dòng)的時(shí)候,在 scrollViewDidScroll: 里根據(jù) A 的 contentOffset 動(dòng)態(tài)計(jì)算 B 的 contentOffset 并設(shè)給 B朝抖。同樣對(duì)于非 scroll view 的 C砌滞,也可以動(dòng)態(tài)計(jì)算 C 的 frame 或是 transform(Glow 的氣泡為例)實(shí)現(xiàn)視差滾動(dòng)或者其他高級(jí)動(dòng)畫,這在現(xiàn)在許多應(yīng)用的引導(dǎo)頁面里會(huì)被用到华畏。

UIScrollView代碼

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末仑乌,一起剝皮案震驚了整個(gè)濱河市决帖,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,311評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件洒宝,死亡現(xiàn)場離奇詭異,居然都是意外死亡靠瞎,警方通過查閱死者的電腦和手機(jī)制恍,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門爱榕,熙熙樓的掌柜王于貴愁眉苦臉地迎上來八匠,“玉大人坑夯,你說我怎么就攤上這事淑履。” “怎么了蹋偏?”我有些...
    開封第一講書人閱讀 152,671評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵黎棠,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么扛门? 我笑而不...
    開封第一講書人閱讀 55,252評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮火焰,結(jié)果婚禮上绒怨,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好音同,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,253評(píng)論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著必指,像睡著了一般霜第。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,031評(píng)論 1 285
  • 那天晴玖,我揣著相機(jī)與錄音,去河邊找鬼。 笑死锐帜,一個(gè)胖子當(dāng)著我的面吹牛简软,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播肛跌,決...
    沈念sama閱讀 38,340評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼据过,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,973評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎壕鹉,沒想到半個(gè)月后晾浴,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,466評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,937評(píng)論 2 323
  • 正文 我和宋清朗相戀三年最岗,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了右蹦。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片何陆。...
    茶點(diǎn)故事閱讀 38,039評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡钠怯,死狀恐怖贤姆,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情薄疚,我是刑警寧澤碧信,帶...
    沈念sama閱讀 33,701評(píng)論 4 323
  • 正文 年R本政府宣布,位于F島的核電站输涕,受9級(jí)特大地震影響音婶,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜莱坎,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,254評(píng)論 3 307
  • 文/蒙蒙 一衣式、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦碴卧、人聲如沸弱卡。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,259評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽婶博。三九已至,卻和暖如春荧飞,著一層夾襖步出監(jiān)牢的瞬間凡人,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評(píng)論 1 262
  • 我被黑心中介騙來泰國打工叹阔, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留挠轴,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,497評(píng)論 2 354
  • 正文 我出身青樓耳幢,卻偏偏與公主長得像岸晦,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子睛藻,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,786評(píng)論 2 345

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