淺談UIView的刷新與繪制

topPic

概述:

UIView是我們在做iOS開發(fā)時(shí)每天都會接觸到的類,幾乎所有跟頁面顯示相關(guān)的控件也都繼承自它。但是關(guān)于UIView的布局、顯示、以及繪制原理等方面筆者一直一知半解,只有真正了解了它的原理才能更好的服務(wù)我們的開發(fā)。并且在市場對iOS開發(fā)者要求越來越高的大環(huán)境下,對App頁面流暢度的優(yōu)化也是對高級及以上開發(fā)者必問的面試題,這就需要我們要對UIView有更深的認(rèn)知。

一.UIView 與 CALayer

UIView:一個(gè)視圖(UIView)就是在屏幕上顯示的一個(gè)矩形塊(比如圖片,文字或者視頻),它能夠攔截類似于鼠標(biāo)點(diǎn)擊或者觸摸手勢等用戶輸入。視圖在層級關(guān)系中可以互相嵌套,一個(gè)視圖可以管理它的所有子視圖的位置,在iOS當(dāng)中,所有的視圖都從一個(gè)叫做UIView的基類派生而來,UIView可以處理觸摸事件,可以支持基于Core Graphics繪圖,可以做仿射變換(例如旋轉(zhuǎn)或者縮放),或者簡單的類似于滑動或者漸變的動畫。

CALayer:CALayer類在概念上和UIView類似,同樣也是一些被層級關(guān)系樹管理的矩形塊,同樣也可以包含一些內(nèi)容(像圖片,文本或者背景色),管理子圖層的位置。它們有一些方法和屬性用來做動畫和變換。和UIView最大的不同是CALayer不處理用戶的交互。

CALayer并不清楚具體的響應(yīng)鏈(iOS通過視圖層級關(guān)系用來傳送觸摸事件的機(jī)制),于是它并不能夠響應(yīng)事件,即使它提供了一些方法來判斷一個(gè)觸點(diǎn)是否在圖層的范圍之內(nèi)。

1. UIView 與 CALayer的關(guān)系

每一個(gè)UIView都有一個(gè)CALayer實(shí)例的圖層屬性,也就是所謂的backing layer,視圖的職責(zé)就是創(chuàng)建并管理這個(gè)圖層,以確保當(dāng)子視圖在層級關(guān)系中添加或者被移除的時(shí)候,他們關(guān)聯(lián)的圖層也同樣對應(yīng)在層級關(guān)系樹當(dāng)中有相同的操作.

兩者的關(guān)系:實(shí)際上這些背后關(guān)聯(lián)的圖層(Layer)才是真正用來在屏幕上顯示和做動畫,UIView僅僅是對它的一個(gè)封裝,提供了一些iOS類似于處理觸摸的具體功能,以及Core Animation底層方法的高級接口。

這里引申出面試常問的一個(gè)問題:為什么iOS要基于UIView和CALayer提供兩個(gè)平行的層級關(guān)系呢?為什么不用一個(gè)簡單的層級來處理所有事情呢?

原因在于要做職責(zé)分離(單一職責(zé)原則),這樣也能避免很多重復(fù)代碼。在iOS和Mac OS兩個(gè)平臺上,事件和用戶交互有很多地方的不同,基于多點(diǎn)觸控的用戶界面和基于鼠標(biāo)鍵盤有著本質(zhì)的區(qū)別,這就是為什么iOS有UIKitUIView,但是Mac OS有AppKitNSView的原因。他們功能上很相似,但是在實(shí)現(xiàn)上有著顯著的區(qū)別。把這種功能的邏輯分開并封裝成獨(dú)立的Core Animation框架,蘋果就能夠在iOS和Mac OS之間共享代碼,使得對蘋果自己的OS開發(fā)團(tuán)隊(duì)和第三方開發(fā)者去開發(fā)兩個(gè)平臺的應(yīng)用更加便捷。

2. CALayer的一些常用屬性

contents屬性

CALayer的contents屬性可以讓我們?yōu)閘ayer圖層設(shè)置一張圖片,我們看下它的定義

/* An object providing the contents of the layer, typically a CGImageRef,
 * but may be something else. (For example, NSImage objects are
 * supported on Mac OS X 10.6 and later.) Default value is nil.
 * Animatable. */

@property(nullable, strong) id contents;

這個(gè)屬性的類型被定義為id,意味著它可以是任何類型的對象。在這種情況下,你可以給contents屬性賦任何值,你的app都能夠編譯通過。但是,如果你給contents賦的不是CGImage,那么你得到的圖層將是空白的。事實(shí)上,你真正要賦值的類型應(yīng)該是CGImageRef,它是一個(gè)指向CGImage結(jié)構(gòu)的指針,UIImage有一個(gè)CGImage屬性,它返回一個(gè)CGImageRef,但是要使用它還需要進(jìn)行強(qiáng)轉(zhuǎn):

layer.contents = (__bridge id _Nullable)(image.CGImage);
contentGravity屬性
/* A string defining how the contents of the layer is mapped into its
 * bounds rect. Options are `center', `top', `bottom', `left',
 * `right', `topLeft', `topRight', `bottomLeft', `bottomRight',
 * `resize', `resizeAspect', `resizeAspectFill'. The default value is
 * `resize'. Note that "bottom" always means "Minimum Y" and "top"
 * always means "Maximum Y". */

@property(copy) CALayerContentsGravity contentsGravity;

如果我們?yōu)閳D層layer設(shè)置contents為一張圖片,那么可以使用這個(gè)屬性來讓圖片自適應(yīng)layer的大小,它類似于UIView的contentMode屬性,但是它是一個(gè)NSString類型,而不是像對應(yīng)的UIKit部分,那里面的值是枚舉。contentsGravity可選的常量值有以下一些:

kCAGravityCenter
kCAGravityTop
kCAGravityBottom
kCAGravityLeft
kCAGravityRight
kCAGravityTopLeft
kCAGravityTopRight
kCAGravityBottomLeft
kCAGravityBottomRight
kCAGravityResize
kCAGravityResizeAspect
kCAGravityResizeAspectFill

例如,如果要讓圖片等比例拉伸去自適應(yīng)layer的大小可以直接這樣設(shè)置

layer.contentsGravity = kCAGravityResizeAspect;
contentsScale屬性
/* Defines the scale factor applied to the contents of the layer. If
 * the physical size of the contents is '(w, h)' then the logical size
 * (i.e. for contentsGravity calculations) is defined as '(w /
 * contentsScale, h / contentsScale)'. Applies to both images provided
 * explicitly and content provided via -drawInContext: (i.e. if
 * contentsScale is two -drawInContext: will draw into a buffer twice
 * as large as the layer bounds). Defaults to one. Animatable. */

@property CGFloat contentsScale

contentsScale屬性定義了contents設(shè)置圖片的像素尺寸和視圖大小的比例,默認(rèn)情況下它是一個(gè)值為1.0的浮點(diǎn)數(shù)。這個(gè)屬性其實(shí)屬于支持Retina屏幕機(jī)制的一部分,它的值等于當(dāng)前設(shè)備的物理尺寸與邏輯尺寸的比值。如果contentsScale設(shè)置為1.0,將會以每個(gè)點(diǎn)1個(gè)像素繪制圖片,如果設(shè)置為2.0,則會以每個(gè)點(diǎn)2個(gè)像素繪制圖片。當(dāng)用代碼的方式來處理contents設(shè)置圖片的時(shí)候,一定要手動的設(shè)置圖層的contentsScale屬性,否則圖片在Retina設(shè)備上就顯示得不正確啦。代碼如下:

layer.contentsScale = [UIScreen mainScreen].scale;
maskToBounds屬性

maskToBounds屬性的功能類似于UIView的clipsToBounds屬性,如果設(shè)置為YES,則會將超出layer范圍的圖片進(jìn)行裁剪.

contentsRect屬性

contentsRect屬性在我們的日常開發(fā)中用的不多,它的主要作用是可以讓我們顯示contents所設(shè)置圖片的一個(gè)子區(qū)域。它是單位坐標(biāo)取值在0到1之間。默認(rèn)值是{0, 0, 1, 1},這意味著整個(gè)圖片默認(rèn)都是可見的,如果我們指定一個(gè)小一點(diǎn)的矩形,比如{0,0,0.5,0.5},那么layer顯示的只有圖片的左上角,也就是1/4的區(qū)域。

實(shí)際上給layer的contents賦CGImage的值不是唯一的設(shè)置其寄宿圖的方法。我們也可以直接用Core Graphics直接繪制。通過繼承UIView并實(shí)現(xiàn)-drawRect:方法來自定義繪制,如果單獨(dú)使用CALayer那么可以實(shí)現(xiàn)其代理(CALayerDelegate)方法- (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx;在這里面進(jìn)行自主繪制。實(shí)際的方法繪制流程我們在下面進(jìn)行探討。

二.View的布局與顯示

1.圖像顯示原理

在開始介紹圖像的布局與顯示之前,我們有必要先了解下圖像的顯示原理,也就是我們創(chuàng)建一個(gè)顯示控件是怎么通過CPU與GPU的運(yùn)算顯示在屏幕上的。這個(gè)過程大體分為劉六個(gè)階段:


繪制
  • 布局 :首先一個(gè)視圖由CPU進(jìn)行Frame布局,準(zhǔn)備視圖(view)和圖層(layer)的層級關(guān)系,以及設(shè)置圖層屬性(位置,背景色,邊框)等等。
  • 顯示:view的顯示圖層(layer),它的寄宿圖片被繪制的階段。所謂的寄宿圖,就是上面我們提到過的layer所顯示的內(nèi)容。它有兩種設(shè)置形式:一種是直接設(shè)置layer.contents,賦值一個(gè)CGImageRef;第二種是重寫UIView的drawRect:CALayerDelegatedrawLayer:inContext:方法,實(shí)現(xiàn)自定義繪制。注意:如果實(shí)現(xiàn)了這兩個(gè)方法,會額外的消耗CPU的性能。
  • 準(zhǔn)備:這是Core Animation準(zhǔn)備發(fā)送數(shù)據(jù)到渲染服務(wù)的階段。這個(gè)階段主要對視圖所用的圖片進(jìn)行解碼以及圖片的格式轉(zhuǎn)換。PNG或者JPEG壓縮之后的圖片文件會比同質(zhì)量的位圖小得多。但是在圖片繪制到屏幕上之前,必須把它擴(kuò)展成完整的未解壓的尺寸(通常等同于圖片寬 x 長 x 4個(gè)字節(jié))。為了節(jié)省內(nèi)存,iOS通常直到真正繪制的時(shí)候才去解碼圖片。
  • 提交:CPU會將處理視圖和圖層的層級關(guān)系打包,通過IPC(內(nèi)部處理通信)通道提交給渲染服務(wù),渲染服務(wù)由OpenGL ES和GPU組成。
  • 生成幀緩存:渲染服務(wù)首先將圖層數(shù)據(jù)交給OpenGL ES進(jìn)行紋理生成和著色,生成前后幀緩存。再根據(jù)顯示硬件的刷新頻率,一般以設(shè)備的VSync信號和CADisplayLink為標(biāo)準(zhǔn),進(jìn)行前后幀緩存的切換。
  • 渲染 :將最終要顯示在畫面上的后幀緩存交給GPU,進(jìn)行采集圖片和形狀,運(yùn)行變換,應(yīng)用紋理和混合,最終顯示在屏幕上。

注意:當(dāng)圖層被成功打包,發(fā)送到渲染服務(wù)器之后,CPU仍然要做如下工作:為了顯示屏幕上的圖層,Core Animation必須對渲染樹種的每個(gè)可見圖層通過OpenGL循環(huán)轉(zhuǎn)換成紋理三角板。由于GPU并不知曉Core Animation圖層的任何結(jié)構(gòu),所以必須要由CPU做這些事情。

前四個(gè)階段都在軟件層面處理(通過CPU),第五階段也有CPU參與,只有最后一個(gè)完全由GPU執(zhí)行。而且,你真正能控制只有前兩個(gè)階段:布局和顯示,Core Animation框架在內(nèi)部處理剩下的事務(wù),你也控制不了它。所以接下來我們來重點(diǎn)分析布局與顯示階段。

2.布局

布局:布局就是一個(gè)視圖在屏幕上的位置與大小。UIView有三個(gè)比較重要的布局屬性:frameboundscenter.UIView提供了用來通知系統(tǒng)某個(gè)view布局發(fā)生變化的方法,也提供了在view布局重新計(jì)算后調(diào)用的可重寫的方法。

layoutSubviews()方法

layoutSubviews():當(dāng)一個(gè)視圖“認(rèn)為”應(yīng)該重新布局自己的子控件時(shí),它便會自動調(diào)用自己的layoutSubviews方法,在該方法中“刷新”子控件的布局.這個(gè)方法并沒有系統(tǒng)實(shí)現(xiàn),需要我們重新這個(gè)方法,在里面實(shí)現(xiàn)子控件的重新布局。這個(gè)方法很開銷很大,因?yàn)樗鼤诿總€(gè)子視圖上起作用并且調(diào)用它們相應(yīng)的layoutSubviews方法.系統(tǒng)會根據(jù)當(dāng)前run loop的不同狀態(tài)來觸發(fā)layoutSubviews調(diào)用的機(jī)制,并不需要我們手動調(diào)用。以下是他的觸發(fā)時(shí)機(jī):

  • 直接修改 view 的大小時(shí)會觸發(fā)
  • 調(diào)用addSubview會觸發(fā)子視圖的layoutSubviews
  • 用戶在 UIScrollView 上滾動(layoutSubviews 會在UIScrollView和它的父view上被調(diào)用)
  • 用戶旋轉(zhuǎn)設(shè)備
  • 更新視圖的 constraints
    這些方式都會告知系統(tǒng)view的位置需要被重新計(jì)算,繼而會調(diào)用layoutSubviews.當(dāng)然也可以直接觸發(fā)layoutSubviews的方法。
setNeedsLayout()方法

setNeedsLayout()方法的調(diào)用可以觸發(fā)layoutSubviews,調(diào)用這個(gè)方法代表向系統(tǒng)表示視圖的布局需要重新計(jì)算。不過調(diào)用這個(gè)方法只是為當(dāng)前的視圖打了一個(gè)臟標(biāo)記,告知系統(tǒng)需要在下一次run loop中重新布局這個(gè)視圖。也就是調(diào)用setNeedsLayout()后會有一段時(shí)間間隔,然后觸發(fā)layoutSubviews.當(dāng)然這個(gè)間隔不會對用戶造成影響,因?yàn)橛肋h(yuǎn)不會長到對界面造成卡頓。

layoutIfNeeded()方法

layoutIfNeeded()方法的作用是告知系統(tǒng),當(dāng)前打了臟標(biāo)記的視圖需要立即更新,不要等到下一次run loop到來時(shí)在更新,此時(shí)該方法會立即觸發(fā)layoutSubviews方法。當(dāng)然但如果你調(diào)用了layoutIfNeeded之后,并且沒有任何操作向系統(tǒng)表明需要刷新視圖,那么就不會調(diào)用layoutsubview.這個(gè)方法在你需要依賴新布局,無法等到下一次 run loop的時(shí)候會比setNeedsLayout有用。

3.顯示

和布局的方法類似,顯示也有觸發(fā)更新的方法,它們由系統(tǒng)在檢測到更新時(shí)被自動調(diào)用,或者我們可以手動調(diào)用直接刷新。

drawRect:方法

在上面我們提到過,如果要設(shè)置視圖的寄宿圖,除了直接設(shè)置view.layer.contents屬性,還可以自主進(jìn)行繪制。繪制的方法就是實(shí)現(xiàn)view的drawRect:方法。這個(gè)方法類似于布局的layoutSubviews方法,它會對當(dāng)前View的顯示進(jìn)行刷新,不同的是它不會觸發(fā)后續(xù)對視圖的子視圖方法的調(diào)用。跟layoutSubviews一樣,我們不能直接手動調(diào)用drawRect:方法,應(yīng)該調(diào)用間接的觸發(fā)方法,讓系統(tǒng)在 run loop 中的不同結(jié)點(diǎn)自動調(diào)用。具體的繪制流程我們在本文第三節(jié)進(jìn)行介紹。

setNeedsDisplay()方法

這個(gè)方法類似于布局中的setNeedsLayout。它會給有內(nèi)容更新的視圖設(shè)置一個(gè)內(nèi)部的標(biāo)記,但在視圖重繪之前就會返回。然后在下一個(gè)run loop中,系統(tǒng)會遍歷所有已標(biāo)標(biāo)記的視圖,并調(diào)用它們的drawRect:方法。大部分時(shí)候,在視圖中更新任何 UI 組件都會把相應(yīng)的視圖標(biāo)記為“dirty”,通過設(shè)置視圖“內(nèi)部更新標(biāo)記”,在下一次run loop中就會重繪,而不需要顯式的調(diào)用setNeedsDisplay.

三.UIView的系統(tǒng)繪制與異步繪制流程

UIView的繪制流程

接下來我們看下UIView的繪制流程

繪制

  • UIView調(diào)用setNeedsDisplay,這個(gè)方法我們已經(jīng)介紹過了,它并不會立即開始繪制。
  • UIView 調(diào)用setNeedsDisplay,實(shí)際會調(diào)用其layer屬性的同名方法,此時(shí)相當(dāng)于給layer打上繪制標(biāo)記。
  • 在當(dāng)前run loop 將要結(jié)束的時(shí)候,才會調(diào)用CALayer的display方法進(jìn)入到真正的繪制當(dāng)中
  • 在CALayer的display方法中,會判斷layer的代理方法displayLayer:是否被實(shí)現(xiàn),如果代理沒有實(shí)現(xiàn)這個(gè)方法,則進(jìn)入系統(tǒng)繪制流程,否則進(jìn)入異步繪制入口。

系統(tǒng)繪制

xitong
  • 在系統(tǒng)繪制開始時(shí),在CALayer內(nèi)部會創(chuàng)建一個(gè)繪制上下文,這個(gè)上下文可以理解為CGContextRef,我們在drawRect:方法中獲取到的currentRef就是它。

  • 然后layer會判斷是否有delegate,沒有delegate就調(diào)用CALayerdrawInContext方法,如果有代理,并且你實(shí)現(xiàn)了CALayerDelegate協(xié)議中的-drawLayer:inContext:方法或者UIView中的-drawRect:方法(其實(shí)就是前者的包裝方法),那么系統(tǒng)就會調(diào)用你實(shí)現(xiàn)的這兩個(gè)方法中的一個(gè)。

    關(guān)于這里的代理我的理解是:如果你直接使用的UIView,那么layer的代理就是當(dāng)前view,你直接實(shí)現(xiàn)-drawRect:,然后在這個(gè)方法里面進(jìn)行自主繪制; 如果你用的是單獨(dú)創(chuàng)建的CALayer,那么你需要設(shè)置layer.delegate = self; 當(dāng)然這里的self就是持有l(wèi)ayer的視圖或是控制器了,這時(shí)你需要實(shí)現(xiàn)-drawLayer:inContext:方法,然后在這個(gè)方法里面進(jìn)行繪制。

  • 最后CALayer把位圖傳給GPU去渲染,也就是將生成的 bitmap 位圖賦值給 layer.content 屬性。

注意:使用CPU進(jìn)行繪圖的代價(jià)昂貴,除非絕對必要,否則你應(yīng)該避免重繪你的視圖。提高繪制性能的秘訣就在于盡量避免去繪制。

異步繪制

什么是異步繪制?

通過上面的介紹我們熟悉了系統(tǒng)繪制流程,系統(tǒng)繪制就是在主線程中進(jìn)行上下文的創(chuàng)建,控件的自主繪制等,這就導(dǎo)致了主線程頻繁的處理UI繪制的工作,如果要繪制的元素過多,過于頻繁,就會造成卡頓。而異步繪制就是把復(fù)雜的繪制過程放到后臺線程中執(zhí)行,從而減輕主線程負(fù)擔(dān),來提升UI流暢度。

異步繪制流程
pic

上面很明顯的展示了異步繪制過程:

  • 從上圖看,異步繪制的入口在layer的代理方法displayLayer:,如果要進(jìn)行異步繪制,我們必須在自定義view中實(shí)現(xiàn)這個(gè)方法
  • displayLayer:方法中我們開辟子線程
  • 在子線程中我們創(chuàng)建繪制上下文,并借助Core Graphics 相關(guān)API完成自主繪制
  • 完成繪制后生成Image圖片
  • 最后回到主線程,把Image圖片賦值給layer的contents屬性。

當(dāng)然我們在日常開發(fā)中還要考慮線程的管理與繪制時(shí)機(jī)等問題,使用第三方庫YYAsyncLayer可以讓我們把注意力放在具體的繪制上,具體的使用流程可以點(diǎn)這里去查看.

四.總結(jié)

我們知道,當(dāng)我們實(shí)現(xiàn)了CALayerDelegate協(xié)議中的-drawLayer:inContext:方法或者UIView中的-drawRect:方法,圖層就創(chuàng)建了一個(gè)繪制上下文,這個(gè)上下文需要的大小的內(nèi)存可從這個(gè)算式得出:圖層寬X圖層高X4字節(jié),寬高的單位均為像素。對于一個(gè)在Retina iPad上的全屏圖層來說,這個(gè)內(nèi)存量就是 2048X15264字節(jié),相當(dāng)于12MB內(nèi)存,圖層每次重繪的時(shí)候都需要重新抹掉內(nèi)存然后重新分配。可見使用Core Graphics利用CPU進(jìn)行繪制代價(jià)是很高的,那么如何進(jìn)行高效的繪圖呢?iOS-Core-Animation-Advanced-Techniques給出了答案,我們在日常開發(fā)中完全可以使用Core AnimationCAShapeLayer代替Core Graphics進(jìn)行圖形的繪制,具體的方法這里就不介紹了,感興趣的可以自行去查看。

參考引用:
iOS-Core-Animation-Advanced-Techniques
YYAsyncLayer
https://juejin.cn/post/6844903567610871816

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

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