iOS Lottie的原理解析

Lottie是Airbnb開(kāi)源的一套動(dòng)畫(huà)框架晴叨,它可以幫助把開(kāi)發(fā)人員從動(dòng)畫(huà)的制作上解放出來(lái)。設(shè)計(jì)師可以直接通過(guò)AE設(shè)計(jì)并導(dǎo)出動(dòng)畫(huà),客戶(hù)端無(wú)需做處理就可以直接使用昔头。這確實(shí)是一個(gè)偉大的創(chuàng)新伊群,強(qiáng)烈推薦大家使用考杉。關(guān)于Lottie的安裝及使用這里就不啰嗦了策精,屬于Baidu+翻墻+Google可以解決的問(wèn)題。(https://github.com/airbnb/lottie-ios)

正好最近一段時(shí)間得閑崇棠,所以就看了看Lottie-iOS的源碼咽袜,在這里記錄一點(diǎn),有不正確的地方也希望大家可以不吝指教枕稀。

1. 動(dòng)畫(huà)

關(guān)于iOS動(dòng)畫(huà)的一些基本知識(shí)可以參見(jiàn) https://www.gitbook.com/book/zsisme/ios-/details 很棒的一本書(shū)询刹,內(nèi)容豐富講解細(xì)致,推薦大家讀讀萎坷。

2. 代碼組織

lottie-ios.png

整套代碼同時(shí)支持Mac和iOS為了方便我們只分析iOS的部分凹联。

#if TARGET_OS_IPHONE || TARGET_IPHONE_SIMULATOR
#import <UIKit/UIKit.h>
@compatibility_alias LOTView UIView;
#else
#import <AppKit/AppKit.h>
@compatibility_alias LOTView NSView;
#endif

代碼功能組織如下

AnimatableLayers: 動(dòng)畫(huà)View和Layer的相關(guān)定義和實(shí)現(xiàn)
AnimatableProperties:  屬性動(dòng)畫(huà)的相關(guān)定義和實(shí)現(xiàn)
AnimationCache: 動(dòng)畫(huà)數(shù)據(jù)的LRU Cache
Extensions: 相關(guān)類(lèi)的擴(kuò)展
MacCompatability: 為支持Mac的一些移植文件
Models: 基礎(chǔ)數(shù)據(jù)結(jié)構(gòu)的定義
Private/PublicHeaders: Interface的定義

3. 代碼詳解

從PublicHeaders我們可以看到Lottie暴露出來(lái)的給我們的是兩個(gè)類(lèi):

LOTAnimationView(動(dòng)畫(huà)的View)
LOTAnimationTransitionController(Controller了之間的專(zhuān)場(chǎng)動(dòng)畫(huà))

本文著重分析LOTAnimationView

LOTAnimationView結(jié)構(gòu)

大體上來(lái)講LOTAnimationView的結(jié)構(gòu)如下,負(fù)責(zé)動(dòng)畫(huà)顯示的LOTCompositionLayer哆档,記錄動(dòng)畫(huà)狀態(tài)的LOTAnimationState以及負(fù)責(zé)動(dòng)畫(huà)生命周期的CADisplayLink蔽挠。


Lottie-iOS.png

LOTCompositionLayer通過(guò)動(dòng)畫(huà)數(shù)據(jù)LOTComposition來(lái)初始化。Lottie的動(dòng)畫(huà)數(shù)據(jù)為json文件/數(shù)據(jù)瓜浸,針對(duì)json文件在性能上也實(shí)現(xiàn)了針對(duì)LOTComposition數(shù)據(jù)的LRU Cache也就是LOTAnimationCache澳淑,這點(diǎn)我們可以從頭文件定義和內(nèi)部實(shí)現(xiàn)上可以看到。

+ (instancetype)animationNamed:(NSString *)animationName inBundle:(NSBundle *)bundle {
  NSArray *components = [animationName componentsSeparatedByString:@"."];
  animationName = components.firstObject;
  
  LOTComposition *comp = [[LOTAnimationCache sharedCache] animationForKey:animationName];
  if (comp) {
    return [[LOTAnimationView alloc] initWithModel:comp];
  }
  
  NSError *error;
  NSString *filePath = [bundle pathForResource:animationName ofType:@"json"];
  NSData *jsonData = [[NSData alloc] initWithContentsOfFile:filePath];
  NSDictionary  *JSONObject = jsonData ? [NSJSONSerialization JSONObjectWithData:jsonData
                                                                         options:0 error:&error] : nil;
  if (JSONObject && !error) {
    LOTComposition *laScene = [[LOTComposition alloc] initWithJSON:JSONObject];
    [[LOTAnimationCache sharedCache] addAnimation:laScene forKey:animationName];
    return [[LOTAnimationView alloc] initWithModel:laScene];
  }
  
  NSException* resourceNotFoundException = [NSException exceptionWithName:@"ResourceNotFoundException"
                                                                   reason:[error localizedDescription]
                                                                 userInfo:nil];
  @throw resourceNotFoundException;
}
#import <Foundation/Foundation.h>

@class LOTComposition;

@interface LOTAnimationCache : NSObject

+ (instancetype)sharedCache;

- (void)addAnimation:(LOTComposition *)animation forKey:(NSString *)key;
- (LOTComposition *)animationForKey:(NSString *)key;

@end

LOTAnimationCache的實(shí)現(xiàn)

LOTAnimationCache是一個(gè)LRU的Cache插佛,如果不了解LRU Cache可以去https://en.wikipedia.org/wiki/Cache_replacement_policies 科普下

@implementation LOTAnimationCache {
  NSMutableDictionary *animationsCache_; // cache數(shù)據(jù)
  NSMutableArray *lruOrderArray_; // 通過(guò)key數(shù)組來(lái)保證lru順序杠巡,最近使用的在數(shù)組最尾部,該淘汰的在數(shù)組最頂部朗涩,
}

- (void)addAnimation:(LOTComposition *)animation forKey:(NSString *)key {
  if (lruOrderArray_.count >= kLOTCacheSize) {
    NSString *oldKey = lruOrderArray_[0];
    [animationsCache_ removeObjectForKey:oldKey];
    [lruOrderArray_ removeObject:oldKey];
  }
  [lruOrderArray_ removeObject:key];
  [lruOrderArray_ addObject:key];
  [animationsCache_ setObject:animation forKey:key];
}

- (LOTComposition *)animationForKey:(NSString *)key {
  LOTComposition *animation = [animationsCache_ objectForKey:key];
  [lruOrderArray_ removeObject:key];
  [lruOrderArray_ addObject:key];
  return animation;
}

LOTComposition (構(gòu)圖數(shù)據(jù))

@property (nonatomic, readonly) CGRect compBounds;
@property (nonatomic, readonly) NSNumber *startFrame;
@property (nonatomic, readonly) NSNumber *endFrame;
@property (nonatomic, readonly) NSNumber *framerate;
@property (nonatomic, readonly) NSTimeInterval timeDuration;
@property (nonatomic, readonly) LOTLayerGroup *layerGroup;
@property (nonatomic, readonly) LOTAssetGroup *assetGroup;

看到上面的屬性值是不是一頭霧水忽孽,但是也可以稍微做一些推測(cè):動(dòng)畫(huà)包含了整個(gè)顯示大小,時(shí)長(zhǎng)谢床,開(kāi)始和結(jié)束幀兄一,圖層資源和圖片資源,如果希望進(jìn)一步理解則需要從底層數(shù)據(jù)結(jié)構(gòu)往上進(jìn)行分析识腿。

所以我們看看AnimatableProperties出革,Models,是不是第一眼看上去覺(jué)得和一些我們已知的概念相似渡讼?animatable properties骂束,是不是聯(lián)想到了CALayer的屬性動(dòng)畫(huà)?我們知道CALayer的屬性值修改時(shí)會(huì)有隱式動(dòng)畫(huà)成箫,我們可以溫習(xí)一下 https://zsisme.gitbooks.io/ios-/content/chapter7/transactions.html

當(dāng)你改變一個(gè)屬性展箱,Core Animation是如何判斷動(dòng)畫(huà)類(lèi)型和持續(xù)時(shí)間的呢?實(shí)際上動(dòng)畫(huà)執(zhí)行的時(shí)間取決于當(dāng)前事務(wù)的設(shè)置蹬昌,動(dòng)畫(huà)類(lèi)型取決于圖層行為混驰。

事務(wù)實(shí)際上是Core Animation用來(lái)包含一系列屬性動(dòng)畫(huà)集合的機(jī)制,任何用指定事務(wù)去改變可以做動(dòng)畫(huà)的圖層屬性都不會(huì)立刻發(fā)生變化,而是當(dāng)事務(wù)一旦提交的時(shí)候開(kāi)始用一個(gè)動(dòng)畫(huà)過(guò)渡到新值栖榨。

事務(wù)是通過(guò)CATransaction類(lèi)來(lái)做管理昆汹,這個(gè)類(lèi)的設(shè)計(jì)有些奇怪,不像你從它的命名預(yù)期的那樣去管理一個(gè)簡(jiǎn)單的事務(wù)婴栽,而是管理了一疊你不能訪(fǎng)問(wèn)的事務(wù)满粗。CATransaction沒(méi)有屬性或者實(shí)例方法,并且也不能用+alloc和-init方法創(chuàng)建它愚争。但是可以用+begin和+commit分別來(lái)入椨辰裕或者出棧。

任何可以做動(dòng)畫(huà)的圖層屬性都會(huì)被添加到棧頂?shù)氖聞?wù)轰枝,你可以通過(guò)+setAnimationDuration:方法設(shè)置當(dāng)前事務(wù)的動(dòng)畫(huà)時(shí)間劫扒,或者通過(guò)+animationDuration方法來(lái)獲取值(默認(rèn)0.25秒)。

Core Animation在每個(gè)run loop周期中自動(dòng)開(kāi)始一次新的事務(wù)(run loop是iOS負(fù)責(zé)收集用戶(hù)輸入狸膏,處理定時(shí)器或者網(wǎng)絡(luò)事件并且重新繪制屏幕的東西),即使你不顯式的用[CATransaction begin]開(kāi)始一次事務(wù)添怔,任何在一次run loop循環(huán)中屬性的改變都會(huì)被集中起來(lái)湾戳,然后做一次0.25秒的動(dòng)畫(huà)。

在這里广料,這些屬性值也是有異曲同工之妙的砾脑,我們可以看看定義

@interface LOTAnimatableColorValue ()

@property (nonatomic, readonly) NSArray *colorKeyframes;
@property (nonatomic, readonly) NSArray<NSNumber *> *keyTimes;
@property (nonatomic, readonly) NSArray<CAMediaTimingFunction *> *timingFunctions;
@property (nonatomic, readonly) NSTimeInterval delay;
@property (nonatomic, readonly) NSTimeInterval duration;

@property (nonatomic, readonly) NSNumber *startFrame;
@property (nonatomic, readonly) NSNumber *durationFrames;
@property (nonatomic, readonly) NSNumber *frameRate;

@end

所有的Animatable Properties都包含如上相似的結(jié)構(gòu),timingFunctions.count + 1 = colorKeyframes.count
包含了屬性的值數(shù)組,以及開(kāi)始結(jié)束幀艾杏,持續(xù)時(shí)長(zhǎng)韧衣,幀與幀之間的時(shí)間函數(shù)等。而這些就是構(gòu)成整個(gè)動(dòng)畫(huà)的基礎(chǔ)购桑,也可以簡(jiǎn)單的理解為一組更加靈活的動(dòng)畫(huà)屬性畅铭。值得說(shuō)一點(diǎn)的是,Lottie的動(dòng)畫(huà)核心基礎(chǔ)是CAKeyframeAnimation 關(guān)鍵幀動(dòng)畫(huà)勃蜘,是貫穿整個(gè)動(dòng)畫(huà)最底層的原理硕噩。

@protocol LOTAnimatableValue <NSObject>

- (CAKeyframeAnimation *)animationForKeyPath:(NSString *)keypath;
- (BOOL)hasAnimation;

@end

- (nullable CAKeyframeAnimation *)animationForKeyPath:(nonnull NSString *)keypath {
  if (self.hasAnimation == NO) {
    return nil;
  }
  CAKeyframeAnimation *keyframeAnimation = [CAKeyframeAnimation animationWithKeyPath:keypath];
  keyframeAnimation.keyTimes = self.keyTimes;
  keyframeAnimation.values = self.boundsKeyframes;
  keyframeAnimation.timingFunctions = self.timingFunctions;
  keyframeAnimation.duration = self.duration;
  keyframeAnimation.beginTime = self.delay;
  keyframeAnimation.fillMode = kCAFillModeForwards;
  return keyframeAnimation;
}

引用一段關(guān)鍵幀動(dòng)畫(huà)的描述

關(guān)鍵幀動(dòng)畫(huà),是CAPropertyAnimation的子類(lèi)缭贡,與CABasicAnimation的區(qū)別是:
CABasicAnimation只能從一個(gè)數(shù)值(fromValue)變到另一個(gè)數(shù)值(toValue)炉擅,而CAKeyframeAnimation會(huì)使用一個(gè)NSArray保存這些數(shù)值

屬性說(shuō)明:
values:上述的NSArray對(duì)象。里面的元素稱(chēng)為“關(guān)鍵幀”(keyframe)阳惹。動(dòng)畫(huà)對(duì)象會(huì)在指定的時(shí)間(duration)內(nèi)谍失,依次顯示values數(shù)組中的每一個(gè)關(guān)鍵幀

path:代表路徑可以設(shè)置一個(gè)CGPathRef、CGMutablePathRef莹汤,讓圖層按照路徑軌跡移動(dòng)快鱼。path只對(duì)CALayer的anchorPoint和position起作用。如果設(shè)置了path,那么values將被忽略

keyTimes:可以為對(duì)應(yīng)的關(guān)鍵幀指定對(duì)應(yīng)的時(shí)間點(diǎn)攒巍,其取值范圍為0到1.0嗽仪,keyTimes中的每一個(gè)時(shí)間值都對(duì)應(yīng)values中的每一幀。如果沒(méi)有設(shè)置keyTimes柒莉,各個(gè)關(guān)鍵幀的時(shí)間是平分的

那么有了這么些屬性值闻坚,再往上我們可以猜測(cè)應(yīng)該到了構(gòu)建layer的時(shí)候了。
在Models里我們可以看到有如下的定義

#import "LOTComposition.h"
#import "LOTLayer.h"
#import "LOTMask.h"
#import "LOTShapeCircle.h"
#import "LOTShapeFill.h"
#import "LOTShapeGroup.h"
#import "LOTShapePath.h"
#import "LOTShapeRectangle.h"
#import "LOTShapeStroke.h"
#import "LOTShapeTransform.h"
#import "LOTShapeTrimPath.h"
#import "LOTLayerGroup.h"
#import "LOTAsset.h"

這些類(lèi)定義了Lottie的基礎(chǔ)數(shù)據(jù)結(jié)構(gòu)兢孝,注意的是LOTLayer等并不是圖層窿凤,而是圖層的數(shù)據(jù),有興趣的同學(xué)可以對(duì)比著CALayer的屬性看一看跨蟹,結(jié)構(gòu)上有很多相通的地方雳殊。

再往上就是通過(guò)數(shù)據(jù)構(gòu)成的AnimatableLayers
值得一提的是,在構(gòu)建Layer上涉及到很多數(shù)學(xué)知識(shí)窗轩,有興趣的可以研究下兩個(gè)擴(kuò)展文件

CGGeometry+LOTAdditions
CGRect LOT_RectIntegral(CGRect rect);

// Centering

// Returns a rectangle of the given size, centered at a point
CGRect LOT_RectCenteredAtPoint(CGPoint center, CGSize size, BOOL integral);

// Returns the center point of a CGRect
CGPoint LOT_RectGetCenterPoint(CGRect rect);

// Insetting

// Inset the rectangle on a single edge
CGRect LOT_RectInsetLeft(CGRect rect, CGFloat inset);
CGRect LOT_RectInsetRight(CGRect rect, CGFloat inset);
CGRect LOT_RectInsetTop(CGRect rect, CGFloat inset);
CGRect LOT_RectInsetBottom(CGRect rect, CGFloat inset);

// Inset the rectangle on two edges
CGRect LOT_RectInsetHorizontal(CGRect rect, CGFloat leftInset, CGFloat rightInset);
CGRect LOT_RectInsetVertical(CGRect rect, CGFloat topInset, CGFloat bottomInset);

// Inset the rectangle on all edges
CGRect LOT_RectInsetAll(CGRect rect, CGFloat leftInset, CGFloat rightInset, CGFloat topInset, CGFloat bottomInset);

// Framing

// Returns a rectangle of size framed in the center of the given rectangle
CGRect LOT_RectFramedCenteredInRect(CGRect rect, CGSize size, BOOL integral);

// Returns a rectangle of size framed in the given rectangle and inset
CGRect LOT_RectFramedLeftInRect(CGRect rect, CGSize size, CGFloat inset, BOOL integral);
CGRect LOT_RectFramedRightInRect(CGRect rect, CGSize size, CGFloat inset, BOOL integral);
CGRect LOT_RectFramedTopInRect(CGRect rect, CGSize size, CGFloat inset, BOOL integral);
CGRect LOT_RectFramedBottomInRect(CGRect rect, CGSize size, CGFloat inset, BOOL integral);

CGRect LOT_RectFramedTopLeftInRect(CGRect rect, CGSize size, CGFloat insetWidth, CGFloat insetHeight, BOOL integral);
CGRect LOT_RectFramedTopRightInRect(CGRect rect, CGSize size, CGFloat insetWidth, CGFloat insetHeight, BOOL integral);
CGRect LOT_RectFramedBottomLeftInRect(CGRect rect, CGSize size, CGFloat insetWidth, CGFloat insetHeight, BOOL integral);
CGRect LOT_RectFramedBottomRightInRect(CGRect rect, CGSize size, CGFloat insetWidth, CGFloat insetHeight, BOOL integral);

// Divides a rect into sections and returns the section at specified index

CGRect LOT_RectDividedSection(CGRect rect, NSInteger sections, NSInteger index, CGRectEdge fromEdge);

// Returns a rectangle of size attached to the given rectangle
CGRect LOT_RectAttachedLeftToRect(CGRect rect, CGSize size, CGFloat margin, BOOL integral);
CGRect LOT_RectAttachedRightToRect(CGRect rect, CGSize size, CGFloat margin, BOOL integral);
CGRect LOT_RectAttachedTopToRect(CGRect rect, CGSize size, CGFloat margin, BOOL integral);
CGRect LOT_RectAttachedBottomToRect(CGRect rect, CGSize size, CGFloat margin, BOOL integral);

CGRect LOT_RectAttachedBottomLeftToRect(CGRect rect, CGSize size, CGFloat marginWidth, CGFloat marginHeight, BOOL integral);
CGRect LOT_RectAttachedBottomRightToRect(CGRect rect, CGSize size, CGFloat marginWidth, CGFloat marginHeight, BOOL integral);
CGRect LOT_RectAttachedTopRightToRect(CGRect rect, CGSize size, CGFloat marginWidth, CGFloat marginHeight, BOOL integral);
CGRect LOT_RectAttachedTopLeftToRect(CGRect rect, CGSize size, CGFloat marginWidth, CGFloat marginHeight, BOOL integral);

// Combining
// Adds all values of the 2nd rect to the first rect
CGRect LOT_RectAddRect(CGRect rect, CGRect other);
CGRect LOT_RectAddPoint(CGRect rect, CGPoint point);
CGRect LOT_RectAddSize(CGRect rect, CGSize size);
CGRect LOT_RectBounded(CGRect rect);

CGPoint LOT_PointAddedToPoint(CGPoint point1, CGPoint point2);

CGRect LOT_RectSetHeight(CGRect rect, CGFloat height);

CGFloat LOT_PointDistanceFromPoint(CGPoint point1, CGPoint point2);
CGFloat LOT_DegreesToRadians(CGFloat degrees);

GLKMatrix4 LOT_GLKMatrix4FromCATransform(CATransform3D xform);

CATransform3D LOT_CATransform3DFromGLKMatrix4(GLKMatrix4 xform);

CATransform3D LOT_CATransform3DSlerpToTransform(CATransform3D fromXorm, CATransform3D toXform, CGFloat amount );

CGFloat LOT_RemapValue(CGFloat value, CGFloat low1, CGFloat high1, CGFloat low2, CGFloat high2 );
CGPoint LOT_PointByLerpingPoints(CGPoint point1, CGPoint point2, CGFloat value);
UIColor+Expanded
- (NSString *)LOT_colorSpaceString;

- (NSArray *)LOT_arrayFromRGBAComponents;

- (BOOL)LOT_red:(CGFloat *)r green:(CGFloat *)g blue:(CGFloat *)b alpha:(CGFloat *)a;

- (UIColor *)LOT_colorByLuminanceMapping;

- (UIColor *)LOT_colorByMultiplyingByRed:(CGFloat)red green:(CGFloat)green blue:(CGFloat)blue alpha:(CGFloat)alpha;
- (UIColor *)       LOT_colorByAddingRed:(CGFloat)red green:(CGFloat)green blue:(CGFloat)blue alpha:(CGFloat)alpha;
- (UIColor *) LOT_colorByLighteningToRed:(CGFloat)red green:(CGFloat)green blue:(CGFloat)blue alpha:(CGFloat)alpha;
- (UIColor *)  LOT_colorByDarkeningToRed:(CGFloat)red green:(CGFloat)green blue:(CGFloat)blue alpha:(CGFloat)alpha;

- (UIColor *)LOT_colorByMultiplyingBy:(CGFloat)f;
- (UIColor *)       LOT_colorByAdding:(CGFloat)f;
- (UIColor *) LOT_colorByLighteningTo:(CGFloat)f;
- (UIColor *)  LOT_colorByDarkeningTo:(CGFloat)f;

- (UIColor *)LOT_colorByMultiplyingByColor:(UIColor *)color;
- (UIColor *)       LOT_colorByAddingColor:(UIColor *)color;
- (UIColor *) LOT_colorByLighteningToColor:(UIColor *)color;
- (UIColor *)  LOT_colorByDarkeningToColor:(UIColor *)color;

- (NSString *)LOT_stringFromColor;
- (NSString *)LOT_hexStringValue;

+ (UIColor *)LOT_randomColor;
+ (UIColor *)LOT_colorWithString:(NSString *)stringToConvert;
+ (UIColor *)LOT_colorWithRGBHex:(UInt32)hex;
+ (UIColor *)LOT_colorWithHexString:(NSString *)stringToConvert;

+ (UIColor *)LOT_colorWithName:(NSString *)cssColorName;

+ (UIColor *)LOT_colorByLerpingFromColor:(UIColor *)fromColor toColor:(UIColor *)toColor amount:(CGFloat)amount;

搞清楚里面各個(gè)函數(shù)的作用夯秃,起碼會(huì)多學(xué)些數(shù)學(xué)知識(shí)。

先寫(xiě)到這吧痢艺,等有空了再增加點(diǎn)仓洼。

BTW Lottie確實(shí)是一個(gè)好工具

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市堤舒,隨后出現(xiàn)的幾起案子色建,更是在濱河造成了極大的恐慌,老刑警劉巖舌缤,帶你破解...
    沈念sama閱讀 216,402評(píng)論 6 499
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件箕戳,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡国撵,警方通過(guò)查閱死者的電腦和手機(jī)陵吸,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,377評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)卸留,“玉大人走越,你說(shuō)我怎么就攤上這事〕苌” “怎么了旨指?”我有些...
    開(kāi)封第一講書(shū)人閱讀 162,483評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀(guān)的道長(zhǎng)喳整。 經(jīng)常有香客問(wèn)我谆构,道長(zhǎng),這世上最難降的妖魔是什么框都? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,165評(píng)論 1 292
  • 正文 為了忘掉前任搬素,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘熬尺。我一直安慰自己摸屠,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,176評(píng)論 6 388
  • 文/花漫 我一把揭開(kāi)白布粱哼。 她就那樣靜靜地躺著季二,像睡著了一般。 火紅的嫁衣襯著肌膚如雪揭措。 梳的紋絲不亂的頭發(fā)上胯舷,一...
    開(kāi)封第一講書(shū)人閱讀 51,146評(píng)論 1 297
  • 那天,我揣著相機(jī)與錄音绊含,去河邊找鬼桑嘶。 笑死,一個(gè)胖子當(dāng)著我的面吹牛躬充,可吹牛的內(nèi)容都是我干的逃顶。 我是一名探鬼主播,決...
    沈念sama閱讀 40,032評(píng)論 3 417
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼充甚,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼口蝠!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起津坑,我...
    開(kāi)封第一講書(shū)人閱讀 38,896評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎傲霸,沒(méi)想到半個(gè)月后疆瑰,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,311評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡昙啄,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,536評(píng)論 2 332
  • 正文 我和宋清朗相戀三年穆役,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片梳凛。...
    茶點(diǎn)故事閱讀 39,696評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡耿币,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出韧拒,到底是詐尸還是另有隱情淹接,我是刑警寧澤,帶...
    沈念sama閱讀 35,413評(píng)論 5 343
  • 正文 年R本政府宣布叛溢,位于F島的核電站塑悼,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏楷掉。R本人自食惡果不足惜厢蒜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,008評(píng)論 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧斑鸦,春花似錦愕贡、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,659評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至攒庵,卻和暖如春嘴纺,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背浓冒。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,815評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工栽渴, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人稳懒。 一個(gè)月前我還...
    沈念sama閱讀 47,698評(píng)論 2 368
  • 正文 我出身青樓闲擦,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親场梆。 傳聞我的和親對(duì)象是個(gè)殘疾皇子墅冷,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,592評(píng)論 2 353

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

  • 在iOS中隨處都可以看到絢麗的動(dòng)畫(huà)效果,實(shí)現(xiàn)這些動(dòng)畫(huà)的過(guò)程并不復(fù)雜或油,今天將帶大家一窺ios動(dòng)畫(huà)全貌寞忿。在這里你可以看...
    每天刷兩次牙閱讀 8,485評(píng)論 6 30
  • 書(shū)寫(xiě)的很好,翻譯的也棒顶岸!感謝譯者腔彰,感謝感謝! iOS-Core-Animation-Advanced-Techni...
    錢(qián)噓噓閱讀 2,296評(píng)論 0 6
  • 在iOS中隨處都可以看到絢麗的動(dòng)畫(huà)效果辖佣,實(shí)現(xiàn)這些動(dòng)畫(huà)的過(guò)程并不復(fù)雜霹抛,今天將帶大家一窺iOS動(dòng)畫(huà)全貌。在這里你可以看...
    F麥子閱讀 5,110評(píng)論 5 13
  • 前言 本文只要描述了iOS中的Core Animation(核心動(dòng)畫(huà):隱式動(dòng)畫(huà)卷谈、顯示動(dòng)畫(huà))杯拐、貝塞爾曲線(xiàn)、UIVie...
    GitHubPorter閱讀 3,621評(píng)論 7 11
  • 【手寫(xiě)愛(ài)情繪本5.0】歲月勿勿的飛逝世蔗,轉(zhuǎn)眼間又到了圣誕節(jié)端逼,不由得心里最深處有些觸動(dòng),也不知道狂歡的人群是否己經(jīng)酣然...
    主播亞?wèn)|閱讀 646評(píng)論 3 3