Core Animation 之 CALayer

這篇文章是我在看《iOS Core Animation: Advanced Techniques 》這本書的時候做的一些筆記,這本書很深入的將了Core Animation的原理性的東西般哼,是一本講解Core Animation原理非常深入的書燥爷,豆瓣評分有9.5 。如果把整本書全部讀完谅辣,理解修赞,我相信iOS 中的動畫就是件很輕松的事情了,可惜讀的是英文的桑阶,很多東西還是沒有搞懂柏副,摘抄了一些我自己覺得有用的點(diǎn),翻譯了一下蚣录,可能代碼比較少割择,大部分是原理性的東西,希望能對大家理解CALayer有一定的幫助萎河。

在網(wǎng)上也找到了這本書完整的中文翻譯荔泳,如果感興趣蕉饼,可以去看看。
https://www.gitbook.com/book/zsisme/ios-/details

1.The Layer Tree(圖層樹)

Core Animation這個名字很容易讓人產(chǎn)生誤解玛歌,你可能會覺得他主要的目的是動畫昧港,但是其實(shí)動畫只是這個框架的一方面。

**CALayer: **

CALayer在概念上跟UIView非常像支子,也是一個矩形的對象创肥,可以形成一個層級樹。CALayer中可以包含內(nèi)容(例如圖片值朋,文字叹侄,或者背景顏色等),也可以管理其中的子圖層(sublayer)昨登,有可以用來動畫和變換的屬性和方法圈膏。

每個UIView都內(nèi)部都包含一個layer對象(backing layer),UIView的渲染,布局和動畫篙骡,都是通過這個layer對象來管理的稽坤。但是UIView最顯著的特性之一:用戶交互,不是通過CALayer來管理的糯俗,因?yàn)镃ALayer不支持響應(yīng)鏈尿褪,不能響應(yīng)事件(但是CALayer提供了方法來判斷點(diǎn)擊事件是否在某個layer的bounds之內(nèi))

我是這樣理解的,CALayer就是一個不能進(jìn)行用戶交互的UIView得湘,UIView中大部分顯示都是通過CALayer來完成的杖玲,而UIView自己負(fù)責(zé)用戶交互的這一部分。

1.為什么iOS要設(shè)計(jì)這樣的UIView與CALayer的平行分層淘正?而不是只用一層UIView來處理所有的顯示和用戶交互摆马?

為了分離職責(zé),減少重復(fù)代碼鸿吆。事件處理和用戶交互在iOS和Mac OS上差別很大(最基本的用戶交互差別是iOS是基于觸摸操作囤采,Mac OS基于鼠標(biāo)和鍵盤的操作,這也是為什么iOS中是UIKit和UIView而Mac OS中是AppKit和NSView惩淳,它們雖然功能像似蕉毯,但是實(shí)現(xiàn)方法不同),但是相反的是繪圖思犁,布局和動畫等代虾,在iOS和Mac OS中概念基本相同。通過將其中的邏輯抽離出來到獨(dú)立的Core Animation框架中激蹲,Apple可以在iOS平臺和Mac OS平臺中進(jìn)行代碼共享棉磨。

2.CALayer可以做什么?

常見的:

  • 繪制陰影,圓角学辱,著色邊界
  • 3D變換和定位
  • 非矩形邊界
  • 多步乘瓤,非線性動畫

3.使用CALayer

CALayer的使用非常簡單环形,下面這個方法是最基礎(chǔ)的使用,可以看到跟UIView的使用非常像馅扣。

    func addLayer() {
    let layer = CALayer()
    layer.frame = CGRectMake(100, 100, 100, 100)
    layer.backgroundColor = UIColor.redColor().CGColor
    view.layer.addSublayer(layer)
 }

4.什么情況下你會選擇使用CALayer,而不是UIView着降?

  • 當(dāng)你想要寫一份可以同時在iOS和Mac上運(yùn)行的代碼
  • 你可能需要使用多種CALayer的子類(后面會提到)
  • 當(dāng)你的程序?qū)π阅芤蠓浅8叩臅r候差油,可能管理UIView額外的對象都對性能有影響的時候

2.The Backing Image(寄宿圖)

這個我也不知道咋翻譯。任洞。蓄喇。看網(wǎng)上有翻譯成寄宿圖的交掏,就借過來用一下了妆偏。其實(shí)感覺就是layer的一個填充圖片內(nèi)容的容器。

CALayer有一個屬性叫做:contents盅弛,在OC中是id類型钱骂,swift中是AnyObject?類型,這代表它可以是任意類型的對象挪鹏。但是你會發(fā)現(xiàn)如果給contents賦值CGImage類型以外的對象時见秽,得到的都是空白的結(jié)果。這是Mac OS中遺留下來的問題讨盒。之所以設(shè)計(jì)為id類型是為了在Mac OS中可以賦值CGImage或者NSImage,并且都可以得到想要的結(jié)果解取,但是在iOS中,如果賦值UIImage得到的也是一個空白的結(jié)果返顺。

//下面這段代碼可以不通過UIImageView而是通過CALayer禀苦,在屏幕上顯示一張圖片
let layer = CALayer()
layer.frame = CGRectMake(100, 100, 100, 100)
layer.contents = UIImage(named: "layer.jpg")?.CGImage
view.layer.addSublayer(layer)

其實(shí)對于UIView的很多屬性的操作,其實(shí)只是操作UIView內(nèi)部layer對應(yīng)的屬性

  • contentsGravity : 相當(dāng)于UIView中的contentMode屬性遂鹊。但是它是String類型的振乏,而不是枚舉(CALayer中對應(yīng)的大部分UIView中的枚舉屬性都是以KCA開頭的String)。contentsGravity也是用來判斷l(xiāng)ayer的內(nèi)容如何對齊秉扑。
  • masksToBounds : 對應(yīng)的是UIView中的clipsToBounds屬性昆码。

還有很多其他的屬性,就不一一列舉了,大家可以看一下CALayer和UIView對應(yīng)的屬性邻储,很多名稱差不多的赋咽,可能就是相對應(yīng)的。

自定義繪圖

除了contents可以設(shè)置背景圖片以外吨娜,我們可以直接使用Core Graphics來直接繪制圖形來填充到backing layer中脓匿,-drawRect: 方法可以在UIView的子類中重寫來實(shí)現(xiàn)自定義繪圖。只要UIView發(fā)現(xiàn)drawRect方法實(shí)現(xiàn)了宦赠,就會自動生成backing layer陪毡,所以如果你不需要這個背景圖片的話米母,最好不要留一個空白的drawRect方法,因?yàn)檫@樣會浪費(fèi)內(nèi)存毡琉。
-drawRect: 方法在view第一次顯示在屏幕上時自動調(diào)用铁瞒,在這個方法中用Core Graphics來繪制圖形到背景圖片中,圖形會一直緩存在內(nèi)存中直到view需要刷新(一般都是通過手動調(diào)用 -setNeedsDisplay方法來實(shí)現(xiàn)桅滋,但是view有一些屬性的改變也會引起重繪慧耍,比如bounds

CALayer有一個可選的delegate屬性,遵循CALayerDelegate協(xié)議(非正式協(xié)議)丐谋,當(dāng)CALayer需要內(nèi)容信息的時候芍碧,會從delegate中取獲取。
當(dāng)layer需要重繪的時候号俐,CALayer會調(diào)用delegate的

- (void)displayLayer:(CALayerCALayer *)layer;

方法來獲取背景圖片泌豆。

如果delegate沒有實(shí)現(xiàn)上面的方法,CALayer會自動創(chuàng)建一個空的背景圖片和繪制的圖形上下文(context)吏饿,作為調(diào)用delegate的

- (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx;

方法的參數(shù)踪危。

現(xiàn)在你應(yīng)該理解和知道如何使用CALayerDelegate了,但是除非你是在創(chuàng)建獨(dú)立的layers猪落,否則基本上不用手動去實(shí)現(xiàn)CALayerDelegate協(xié)議陨倡。這是因?yàn)閁IView在創(chuàng)建backing layer的時候會自動設(shè)置自己為layer的delegate并且提供-displayLayer:方法的實(shí)現(xiàn)。在使用UIView的backing layer的時候许布,你不需要去實(shí)現(xiàn)-displayLayer:方法或者-drawLayer:inContext:方法來繪制內(nèi)容到layer的backing image兴革,只需要實(shí)現(xiàn)-drawRect:方法,UIView會自動處理蜜唾,包括在需要重繪的時候會自動調(diào)用 -display方法杂曲。

3.Layout Geometry(圖層幾何)

Layout

  • UIView 三個主要的layout屬性: frame, bounds, center
  • CALayer 的三個layout屬性: frame , bounds, position

UIView的frame,bounds和center其實(shí)只是內(nèi)部layer相對應(yīng)屬性的get和set方法袁余,當(dāng)你改變一個view的frame的時候擎勘,你其實(shí)改變的是內(nèi)部layer的frame。

錨點(diǎn)(anchorPoint) :
一個layer的錨點(diǎn)代表的是它的哪個點(diǎn)在position(相對于super layer)的位置,所以錨點(diǎn)與position總是重合的颖榜,默認(rèn)的錨點(diǎn)是在layer的中心點(diǎn)上棚饵。錨點(diǎn)是在單位坐標(biāo)中描述的,左上角的位置是{0,0}掩完,右下角的位置是{1,1}噪漾,默認(rèn)位置是{0.5,0.5}。

坐標(biāo)系統(tǒng)

layer跟view一樣且蓬,也是相對于自己的父圖層來放置的欣硼,layer的position是以superlayer的bounds為參照的。如果superlayer移動的話恶阴,所有的sublayers都要跟著移動诈胜,這樣的好處是我們可以通過移動最底部的superlayer可以把所有的sublayer作為一個單元一起移動豹障。但是,有時候我們需要知道的是一個layer相對于另外一個layer的位置焦匈,而不是相對于它的superlayer的位置血公。CALayer提供了一些方法來在layer之間的坐標(biāo)系進(jìn)行轉(zhuǎn)換:

- (CGPoint)convertPoint:(CGPoint)point fromLayer:(CALayer *)layer; 
- (CGPoint)convertPoint:(CGPoint)point toLayer:(CALayer *)layer; 
- (CGRect)convertRect:(CGRect)rect fromLayer:(CALayer *)layer;
- (CGRect)convertRect:(CGRect)rect toLayer:(CALayer *)layer;

Hit Testing

一般情況下我們會使用帶有backing layer的UIView而不是直接使用CALayer來創(chuàng)建視圖,很重要的一個原因是缓熟,layer處理觸摸事件或者手勢非常復(fù)雜累魔。CALayer不了解響應(yīng)鏈,所有不能直接相應(yīng)觸摸或者手勢荚虚,但是它提供了兩個方法來讓我們自己實(shí)現(xiàn)觸摸操作薛夜,兩個方法的參數(shù)都是一個CGPoint籍茧,代表當(dāng)前觸摸的點(diǎn)

  • -containsPoint: 判斷點(diǎn)是否在layer的frame之內(nèi)
  • -hitTest: 返回layer变屁,或者包含觸摸點(diǎn)的最深層的sublayer,當(dāng)點(diǎn)不在layer上時舆乔,返回nil

layer masking
layer的mask屬性也是一個CALayer對象,設(shè)置layer的mask屬性與直接添加一個sublayer類似,但是顯示的時候并不是一樣的艾杏。作為mask的layer定義的是parent layer的可視部分,而不是直接繪制在parent layer中絮吵。所以开皿,mask的color等屬性設(shè)置沒有任何影響,只有它的輪廓才是最重要的部分漓帚。mask的作用像是一個切割的工具母债,只有parent layer中mask包含的那一部分會被切出來并且保留。

5.Transforms(變換)

下面幾個單詞我總是混淆尝抖,經(jīng)常會用到毡们。

  • transform :變換
  • translate : 平移
  • transition : 過渡
  • transcation : 事務(wù)處理(動畫中)

1.仿射變換

CGAffineTransform(3*3的矩陣) 代表的是 二維平面 的 旋轉(zhuǎn)(rotation),縮放(scale)昧辽,平移(translation)
仿射變換(Affine) 代表的是平面之內(nèi)變化之前平行的線衙熔,變化之后依然平行

創(chuàng)建CGAffineTransform

CGAffineTransformMakeRotation(CGFloat angle) 
CGAffineTransformMakeScale(CGFloat sx, CGFloat sy) 
CGAffineTransformMakeTranslation(CGFloat tx, CGFloat ty)

仿射變換結(jié)合

//通過已經(jīng)存在的變換生成新的變換
CGAffineTransformRotate(CGAffineTransform t, CGFloat angle)
CGAffineTransformScale(CGAffineTransform t, CGFloat sx, CGFloat sy) 
CGAffineTransformTranslate(CGAffineTransform t, CGFloat tx, CGFloat ty)

單位矩陣

CGAffineTransformIdentity

兩個已經(jīng)存在的變換矩陣結(jié)合

CGAffineTransformConcat(CGAffineTransform t1, CGAffineTransform t2)

2.3D變換

CATransform3D是一個4*4的矩陣,可以在三維空間內(nèi)對一個對象進(jìn)行選擇搅荞,縮放红氯,評、平移等變換咕痛。
CATransform3D和CGAffineTransform提供的方法很像

CATransform3DMakeRotation(CGFloat angle, CGFloat x, CGFloat y, CGFloat z) 
CATransform3DMakeScale(CGFloat sx, CGFloat sy, CGFloat sz)
CATransform3DMakeTranslation(Gloat tx, CGFloat ty, CGFloat tz)

translation和scale相比CGAffineTransform多了一個代表z軸的z參數(shù)痢甘,rotation方法接收x,y茉贡,z和一個angle參數(shù)(一起形成了一個代表旋轉(zhuǎn)軸的矢量)

6.Specialized Layers(特殊的layer)

  • CAShapeLayer

CAShapeLayer繪制的是矢量圖形产阱,而不是位圖。通過CGPath定義一個合適的性狀块仆,指定顏色构蹬,線寬等屬性來渲染出一個layer王暗。
與直接在CALayer的contents中通過Core Graphics來繪制圖形相比,CAShapeLayer具有以下優(yōu)勢:
速度快庄敛,內(nèi)存效率高(不用像CALayer一樣創(chuàng)建一個backing image)俗壹,不會被layer的bounds切邊,沒有像素

單獨(dú)設(shè)置layer每個角的圓角

//define path parameters
CGRect rect = CGRectMake(50, 50, 100, 100); 
CGSize radii = CGSizeMake(20, 20);                    
UIRectCorner corners = UIRectCornerTopRight | UIRectCornerBottomRight | UIRectCornerBottomLeft;
//create path
UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:rect byRoundingCorners:corners cornerRadii:radii];

使用CAShapeLayer的一個列子

func setShapeLayer() {
    //生成路徑
    let path = UIBezierPath()
    path.moveToPoint(CGPoint(x: 100,y: 100))
    path.addLineToPoint(CGPoint(x: 130,y: 200))
    path.addLineToPoint(CGPoint(x: 50,y: 130))
    path.addLineToPoint(CGPoint(x: 150,y: 130))
    path.addLineToPoint(CGPoint(x: 70,y: 200))
    path.closePath()
    //生成shape layer并且設(shè)置渲染屬性
    let shapeLayer = CAShapeLayer()
    shapeLayer.strokeColor = UIColor.redColor().CGColor
    shapeLayer.fillColor = UIColor.clearColor().CGColor
    shapeLayer.lineWidth = 5
    shapeLayer.path = path.CGPath
    //添加到view中
    view.layer.addSublayer(shapeLayer)
    }
  • CAGradientLayer

CAGradientLayer可以用來形成兩種或者多種顏色之間的平滑漸變藻烤。

  1. 基礎(chǔ)漸變

     func addGradientLayer() {
         let gradientLayer = CAGradientLayer()
         gradientLayer.frame = CGRectMake(100, 100, 100, 100)
         gradientLayer.colors = [UIColor.redColor().CGColor,
                             UIColor.blueColor().CGColor]
         gradientLayer.startPoint = CGPointMake(0, 0)
         gradientLayer.endPoint = CGPointMake(1, 1)
         view.layer.addSublayer(gradientLayer)
    

    }

效果:

顏色漸變
  1. 分段漸變

默認(rèn)的color在漸變中都是均勻分布的绷雏,我們可以使用locations屬性來控制每個color的分布范圍。
locations是一個范圍的數(shù)組怖亭,每個值都是在{0-1}的范圍中涎显,代表對應(yīng)colors數(shù)組中顏色在漸變中的范圍(所以,如果提供了locations這個屬性兴猩,即必須保證locations的數(shù)量與colors的一致)期吓。

func addMultipartGradientLayer() {
    let gradientLayer = CAGradientLayer()
    gradientLayer.frame = CGRectMake(100, 300, 100, 100)
    gradientLayer.colors = [UIColor.redColor().CGColor,
                            UIColor.blueColor().CGColor,
                            UIColor.greenColor().CGColor]
    gradientLayer.startPoint = CGPointMake(0, 0.5)
    gradientLayer.endPoint = CGPointMake(1, 0.5)
    gradientLayer.locations = [0.2,0.5,0.8]
    view.layer.addSublayer(gradientLayer)
}

效果:


分段漸變

locations屬性可以用來動畫,利用CAShapeLayer作為CAGradientLayer的mask倾芝,可以做出文字顏色漸變效果的動畫讨勤,大家可以自己去試試。

  • CAEmitterLayer

CAEmitterLayer是一個可以用來創(chuàng)建實(shí)時粒子動畫(比如煙霧晨另,火焰潭千,雨)效果的高性能的粒子引擎。
CAEmitterLayer作為CAEmitterCell的一個容器借尿,我們需要創(chuàng)建一個或者多個CAEmitterCell對象作為粒子的模板刨晴,CAEmitterLayer負(fù)責(zé)基于這些模板創(chuàng)建一個粒子流。
CAEmitterCell跟CALayer很像路翻,也有一個contents的屬性狈癞,還有其它很多的屬性,大部分都是用來控制粒子外觀的帚桩。

func addEmitterLayer() {
    let emitter = CAEmitterLayer()
    emitter.frame = CGRectMake(100, 100, 100, 100)
    emitter.renderMode = kCAEmitterLayerPoint
    emitter.emitterPosition = view.center
    let cell = CAEmitterCell()
    cell.contents = UIImage(named: "emiter")?.CGImage
    cell.birthRate = 150
    cell.lifetime = 5.0
    cell.alphaSpeed = -0.4
    cell.velocityRange = 50
    cell.emissionRange = CGFloat(M_PI*2.0)
    cell.color = UIColor(red: 1, green: 0.5, blue: 0.1, alpha: 1.0).CGColor
    emitter.emitterCells = [cell]
    view.layer.addSublayer(emitter)
}

效果:

粒子效果

CAEmitterCell的屬性一般分為以下幾類 :

  • 粒子的某個屬性的開始值 : 比如color指定的將被用來與contents里面的image生成混合顏色
  • 某個會在粒子之間產(chǎn)生變化的值的范圍 : 比如emissionRange代表的是粒子發(fā)射的范圍
  • 某些會隨著時間改變的值 : 比如alphaSpeed,表示的是alpha每秒鐘的增量亿驾,設(shè)置成負(fù)數(shù)的時候,可以產(chǎn)生談出的效果

總結(jié):
特殊的CALayer還有很多種账嚎,包括CALayer的特性也還有很多莫瞬,因?yàn)槲乙策€沒讀完這本書,所以后面再加入吧郭蕉,不過大家可以去我上面提到的中文翻譯完整地址去看看疼邀,我這里面只是提到了書中很小的一部分,是我自己覺得對我來說挺有用的一些方面召锈,總之我覺得 《iOS Core Animation: Advanced Techniques 》這本書非常好旁振,希望有興趣學(xué)習(xí)動畫的同學(xué)可以去看看,強(qiáng)力推薦??。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末拐袜,一起剝皮案震驚了整個濱河市吉嚣,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌蹬铺,老刑警劉巖尝哆,帶你破解...
    沈念sama閱讀 212,884評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異甜攀,居然都是意外死亡秋泄,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,755評論 3 385
  • 文/潘曉璐 我一進(jìn)店門规阀,熙熙樓的掌柜王于貴愁眉苦臉地迎上來恒序,“玉大人,你說我怎么就攤上這事谁撼∑缧玻” “怎么了?”我有些...
    開封第一講書人閱讀 158,369評論 0 348
  • 文/不壞的土叔 我叫張陵彤敛,是天一觀的道長与帆。 經(jīng)常有香客問我了赌,道長墨榄,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,799評論 1 285
  • 正文 為了忘掉前任勿她,我火速辦了婚禮袄秩,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘逢并。我一直安慰自己之剧,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,910評論 6 386
  • 文/花漫 我一把揭開白布砍聊。 她就那樣靜靜地躺著背稼,像睡著了一般。 火紅的嫁衣襯著肌膚如雪玻蝌。 梳的紋絲不亂的頭發(fā)上蟹肘,一...
    開封第一講書人閱讀 50,096評論 1 291
  • 那天,我揣著相機(jī)與錄音俯树,去河邊找鬼帘腹。 笑死,一個胖子當(dāng)著我的面吹牛许饿,可吹牛的內(nèi)容都是我干的阳欲。 我是一名探鬼主播,決...
    沈念sama閱讀 39,159評論 3 411
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼球化!你這毒婦竟也來了秽晚?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,917評論 0 268
  • 序言:老撾萬榮一對情侶失蹤筒愚,失蹤者是張志新(化名)和其女友劉穎爆惧,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體锨能,經(jīng)...
    沈念sama閱讀 44,360評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡扯再,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,673評論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了址遇。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片熄阻。...
    茶點(diǎn)故事閱讀 38,814評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖倔约,靈堂內(nèi)的尸體忽然破棺而出秃殉,到底是詐尸還是另有隱情,我是刑警寧澤浸剩,帶...
    沈念sama閱讀 34,509評論 4 334
  • 正文 年R本政府宣布钾军,位于F島的核電站,受9級特大地震影響绢要,放射性物質(zhì)發(fā)生泄漏吏恭。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,156評論 3 317
  • 文/蒙蒙 一重罪、第九天 我趴在偏房一處隱蔽的房頂上張望樱哼。 院中可真熱鬧,春花似錦剿配、人聲如沸搅幅。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,882評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽茄唐。三九已至,卻和暖如春蝇更,著一層夾襖步出監(jiān)牢的瞬間沪编,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,123評論 1 267
  • 我被黑心中介騙來泰國打工簿寂, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留漾抬,地道東北人。 一個月前我還...
    沈念sama閱讀 46,641評論 2 362
  • 正文 我出身青樓常遂,卻偏偏與公主長得像纳令,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,728評論 2 351

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