老司機(jī)帶你走進(jìn)Core Animation 之圖層的透視蝗蛙、漸變及復(fù)制

老司機(jī)帶你走進(jìn)Core Animation 之圖層的透視、漸變及復(fù)制

系列文章:


這回呢,當(dāng)然還是順著頭文件里面的幾個(gè)類,老司機(jī)一個(gè)一個(gè)捋吧汹来。

老司機(jī)的想法就是要把CoreAnimation頭文件中的類大概都說(shuō)一遍,畢竟一開始把系列名定成了《老司機(jī)帶你走進(jìn)CoreAnimation》(深切的覺(jué)得自己給自己坑了改艇。收班。。)谒兄。


我給自己挖的坑

所以呢摔桦,在今天的博客里你將會(huì)看到以下截個(gè)內(nèi)容

  • CATransform3D
  • CATransformLayer
  • CAGradientLayer
  • CAReplicatorLayer
  • DWMirrorView

廢話不多說(shuō),直接進(jìn)入主題承疲。


CATransform3D

先介紹一下CATransform3D吧邻耕。

CATransform3D

正如上圖所示瘦穆,我們可以清晰的看到,CATransform3D是一個(gè)結(jié)構(gòu)體赊豌。而且蘋果很友好的調(diào)整了一下書寫格式扛或,正如你看到的,它像是一個(gè)4 X 4的矩陣碘饼。

事實(shí)上他的原理就是一個(gè)4 X 4矩陣熙兔。

其實(shí)他還有一個(gè)弟弟,CGAffineTransform艾恼。這是一個(gè)3 X 3的矩陣住涉。
他們的作用都一樣,進(jìn)行坐標(biāo)變換钠绍。
不同點(diǎn)在于舆声,CATransform3D作用與3維坐標(biāo)系的坐標(biāo)變換,CGAffineTransform作用于2維坐標(biāo)系的坐標(biāo)變換柳爽。

所以CGAffineTransform用于對(duì)UIView進(jìn)行變換媳握,而CATransform3D用于對(duì)CALayer進(jìn)行變換。

雖然老司機(jī)從小到大都是數(shù)學(xué)課代表磷脯,不過(guò)我要很鄭重的告訴你蛾找,數(shù)學(xué)是一門靠悟性的學(xué)問(wèn),不是我講給你聽赵誓,你就能消化的打毛,所以關(guān)于矩陣計(jì)算什么的,請(qǐng)各位同學(xué)自己消化理解(咳咳俩功,我會(huì)告訴你我高數(shù)幻枉、線代、概率沒(méi)有一科過(guò)70的么=诡蜓。=)


一臉無(wú)辜

所以呢熬甫,老司機(jī)直接來(lái)介紹CATransform3D的相關(guān)api吧。(CGAffineTransform的api與CATransform3D相似万牺,可類比使用)罗珍。

  • CATransform3DIdentity

生成一個(gè)無(wú)任何變換的默認(rèn)矩陣,可用于使變換后的Layer恢復(fù)初始狀態(tài)


  • CATransform3DMakeTranslation
  • CATransform3DMakeScale
  • CATransform3DMakeRotation

分別是平移脚粟、縮放覆旱、旋轉(zhuǎn),這三個(gè)api最大的相同點(diǎn)就在于函數(shù)名中都有Make核无。意思就是生成指定變換的矩陣扣唱。與之相對(duì)的就是下面的api??

  • CATransform3DTranslate
  • CATransform3DScale
  • CATransform3DRotate

與之前三個(gè)api的不同點(diǎn)在于,這三個(gè)api都多了一個(gè)參數(shù),同樣是一個(gè)CATransform3D結(jié)構(gòu)體噪沙。我想你一定猜到了炼彪,就是對(duì)給定的矩陣在其現(xiàn)有基礎(chǔ)上進(jìn)行指定的變換。

值得注意的是正歼,以上兩個(gè)旋轉(zhuǎn)api中x/y/z三個(gè)參數(shù)均為指定旋轉(zhuǎn)軸辐马,可選值0和1,0代表此軸不做旋轉(zhuǎn)局义,1代表作旋轉(zhuǎn)喜爷。例如想對(duì)x、y軸做45度旋轉(zhuǎn)萄唇,則angle = M____PI____4,x = 1,y = 1,z = 0檩帐。另外,此處旋轉(zhuǎn)角度為弧度制哦另萤,不是角度制湃密。


  • CATransform3DConcat

返回兩個(gè)矩陣的乘積。


  • CATransform3DInvert

反轉(zhuǎn)矩陣


  • CATransform3DMakeAffineTransform
  • CATransform3DGetAffineTransform

CGAffineTransform與CATransform3D相互轉(zhuǎn)化的api


  • CATransform3DIsIdentity
  • CATransform3DEqualToTransform
  • CATransform3DIsAffine

這三個(gè)api見名知意了四敞,不多說(shuō)泛源。

哦,重要的一點(diǎn)你一定要知道目养,所有的矩陣變換都是相對(duì)于圖層的錨點(diǎn)進(jìn)行的俩由。還記得錨點(diǎn)的概念么毒嫡?不記得可以去這個(gè)系列的第一篇文章補(bǔ)課哦癌蚁。

其實(shí)呢,關(guān)于CATransform3D你只要會(huì)使用以上api對(duì)圖層做3維坐標(biāo)轉(zhuǎn)換就夠了兜畸。不過(guò)上述這些變換默認(rèn)情況下都是不具備透視效果的努释,因?yàn)槟闼吹降亩际菆D層在x軸y軸上的投影,那想要透視效果怎么辦呢咬摇??jī)蓚€(gè)辦法伐蒂,CATranformLayer,以及M34肛鹏。

M34

老司機(jī)說(shuō)過(guò)逸邦,CATransform3D不過(guò)是一個(gè)4 X 4的矩陣。那么其實(shí)這16個(gè)數(shù)字中在扰,每一個(gè)數(shù)字都有自己可以控制的轉(zhuǎn)換缕减,這是純數(shù)學(xué)知識(shí)啊,自己領(lǐng)悟=芒珠。=不過(guò)老司機(jī)可以單獨(dú)說(shuō)說(shuō)M34這個(gè)數(shù)桥狡。這個(gè)數(shù)是用來(lái)控制圖層變換后的景深效果的,也就是透視效果

M34

上面的圖片分別展示了具有透視效果的旋轉(zhuǎn)及動(dòng)畫裹芝。

代碼上的體現(xiàn)就是

    CALayer * staticLayerA = [CALayer layer];
    staticLayerA.bounds = CGRectMake(0, 0, 100, 100);
    staticLayerA.position = CGPointMake(self.view.center.x - 75, self.view.center.y - 100);
    staticLayerA.backgroundColor = [UIColor redColor].CGColor;
    [self.view.layer addSublayer:staticLayerA];
    
    CATransform3D transA = CATransform3DIdentity;
    transA.m34 = - 1.0 / 500;
    transA = CATransform3DRotate(transA, M_PI / 3, 1, 0, 0);
    staticLayerA.transform = transA;

使用上很簡(jiǎn)單部逮,代碼里M34 = - 1.0 / 500 的意思就是圖層距離屏幕向里500的單位。如果向外則是M34 = 1.0 / 500嫂易。這個(gè)距離至一般掌握至500~1000這個(gè)范圍會(huì)取得不錯(cuò)的效果兄朋。

這里需要注意的是M34的賦值一定要寫在矩陣變換前面,具體為什么說(shuō)實(shí)話老司機(jī)也不知道怜械。


CATransformLayer

老司機(jī)上面提到過(guò)蜈漓,CALayer做矩陣變換你能看到的只是他在XY軸上的投影,這時(shí)你若想看到透視效果宫盔,就需要使用到M34或CATransformLayer融虽。其實(shí)他兩個(gè)又有一些區(qū)別,CATransformLayer是讓你看到的不只是其在XY軸上的投影灼芭。

說(shuō)起來(lái)不好懂有额,看下面的圖吧。

TransformLayer

CATransformLayer可以讓其的子視圖各自現(xiàn)實(shí)自身的真實(shí)形狀彼绷,而不是其在父視圖的投影巍佑。

你可能還不懂,其實(shí)你看的正方體是六個(gè)CALayer經(jīng)過(guò)矩陣變換拼成的實(shí)實(shí)在在的正方體寄悯。

    //create cube layer
    CATransformLayer *cube = [CATransformLayer layer];
    
    //add cube face 1
    CATransform3D ct = CATransform3DMakeTranslation(0, 0, 50);
    [cube addSublayer:[self faceWithTransform:ct]];
    
    //add cube face 2
    ct = CATransform3DMakeTranslation(50, 0, 0);
    ct = CATransform3DRotate(ct, M_PI_2, 0, 1, 0);
    [cube addSublayer:[self faceWithTransform:ct]];
    
    //add cube face 3
    ct = CATransform3DMakeTranslation(0, -50, 0);
    ct = CATransform3DRotate(ct, M_PI_2, 1, 0, 0);
    [cube addSublayer:[self faceWithTransform:ct]];
    
    //add cube face 4
    ct = CATransform3DMakeTranslation(0, 50, 0);
    ct = CATransform3DRotate(ct, -M_PI_2, 1, 0, 0);
    [cube addSublayer:[self faceWithTransform:ct]];
    
    //add cube face 5
    ct = CATransform3DMakeTranslation(-50, 0, 0);
    ct = CATransform3DRotate(ct, -M_PI_2, 0, 1, 0);
    [cube addSublayer:[self faceWithTransform:ct]];
    
    //add cube face 6
    ct = CATransform3DMakeTranslation(0, 0, -50);
    ct = CATransform3DRotate(ct, M_PI, 0, 1, 0);
    [cube addSublayer:[self faceWithTransform:ct]];
    
    //center the cube layer within the container
    CGSize containerSize = self.containerView.bounds.size;
    cube.position = CGPointMake(containerSize.width / 2.0, containerSize.height / 2.0);
- (CALayer *)faceWithTransform:(CATransform3D)transform
{
    //create cube face layer
    CALayer *face = [CALayer layer];
    face.bounds = CGRectMake(0, 0, 100, 100);
    //apply a random color
    CGFloat red = (rand() / (double)INT_MAX);
    CGFloat green = (rand() / (double)INT_MAX);
    CGFloat blue = (rand() / (double)INT_MAX);
    face.backgroundColor = [UIColor colorWithRed:red green:green blue:blue alpha:1.0].CGColor;
    face.transform = transform;
    return face;
}

使用起來(lái)就是這么簡(jiǎn)單萤衰,把各個(gè)變換后的layer加入到CATransformLayer中就可以了。

本身CATransformLayer不具有任何其他屬性猜旬,其實(shí)他更像是一個(gè)容器脆栋。它本身至渲染其子圖層,自身沒(méi)有任何layer的屬性洒擦。

最重要的一點(diǎn)是椿争,當(dāng)圖層加入到CATransformLayer中以后,hitTest和convertPoint兩個(gè)方法就失效了熟嫩,請(qǐng)注意這點(diǎn)秦踪。


CAGradientLayer

CAGradientLayer本身的屬性也比較少,而且完全是針對(duì)于過(guò)渡顏色來(lái)的掸茅。

  • colors

圖層顯示的所有顏色的數(shù)組


  • locations

每個(gè)顏色對(duì)應(yīng)的位置椅邓。注意,這個(gè)位置指的是顏色的位置昧狮,而不是過(guò)渡線的位置景馁。


  • startPoint
  • endPoint

是顏色過(guò)渡的方向,會(huì)沿著起點(diǎn)到終點(diǎn)的向量進(jìn)行過(guò)渡陵且。


  • type

過(guò)渡模式裁僧,當(dāng)前蘋果給我們暴露的只有一種模式个束,kCAGradientLayerAxial。

需要說(shuō)明的是聊疲,CAGradientLayer只能做矩形的漸變圖層茬底。

你要怎么做?

所以說(shuō)這個(gè)效果要如何實(shí)現(xiàn)呢获洲?其實(shí)啊阱表,這只是一個(gè)錯(cuò)覺(jué),看這個(gè)贡珊。

矩形漸變層

所以說(shuō)看到這你就知道了吧最爬,兩個(gè)拼一起的CAGradientLayer,然后用一個(gè)shapeLayer做了一個(gè)mask就成了環(huán)形的過(guò)渡層了门岔。這一招老司機(jī)早就做了過(guò)爱致,還記得么,歌詞顯示那一章寒随。

- (void)viewDidLoad {
    [super viewDidLoad];

    UIBezierPath * path = [UIBezierPath bezierPathWithArcCenter:CGPointMake(50, 50) radius:45 startAngle:- 7.0 / 6 * M_PI endAngle:M_PI / 6 clockwise:YES];
    
    [self.view.layer addSublayer:[self createShapeLayerWithPath:path]];
    
    CAGradientLayer * leftL = [self createGradientLayerWithColors:@[(id)[UIColor redColor].CGColor,(id)[UIColor yellowColor].CGColor]];
    leftL.position = CGPointMake(25, 40);
    
    CAGradientLayer * rightL = [self createGradientLayerWithColors:@[(id)[UIColor greenColor].CGColor,(id)[UIColor yellowColor].CGColor]];
    rightL.position = CGPointMake(75, 40);
    
    CALayer * layer = [CALayer layer];
    layer.bounds = CGRectMake(0, 0, 100, 80);
    layer.position = self.view.center;
    [layer addSublayer:leftL];
    [layer addSublayer:rightL];
    [self.view.layer addSublayer:layer];
    
    CAShapeLayer * mask = [self createShapeLayerWithPath:path];
    mask.position = CGPointMake(50, 40);
    layer.mask = mask;
    mask.strokeEnd = 0;
    self.mask = mask;
}

-(CAShapeLayer *)createShapeLayerWithPath:(UIBezierPath *)path
{
    CAShapeLayer * layer = [CAShapeLayer layer];
    layer.path = path.CGPath;
    layer.bounds = CGRectMake(0, 0, 100, 75);
    layer.position = self.view.center;
    layer.fillColor = [UIColor clearColor].CGColor;
    layer.strokeColor = [UIColor colorWithRed:33 / 255.0 green:192 / 255.0 blue:250 / 255.0 alpha:1].CGColor;
    layer.lineCap = @"round";
    layer.lineWidth = 10;
    return layer;
}

-(CAGradientLayer *)createGradientLayerWithColors:(NSArray *)colors
{
    CAGradientLayer * gradientLayer = [CAGradientLayer layer];
    gradientLayer.colors = colors;
    gradientLayer.locations = @[@0,@0.8];
    gradientLayer.startPoint = CGPointMake(0, 1);
    gradientLayer.endPoint = CGPointMake(0, 0);
    gradientLayer.bounds = CGRectMake(0, 0, 50, 80);
    return gradientLayer;
}

CAReplicatorLayer

CAReplicatorLayer官方的解釋是一個(gè)高效處理復(fù)制圖層的中間層糠悯。他能復(fù)制圖層的所有屬性包括動(dòng)畫妻往。

使用起來(lái)很簡(jiǎn)單互艾,從他的屬性一一看:

  • instanceCount

實(shí)例數(shù),復(fù)制后的實(shí)例數(shù)讯泣。

  • preservesDepth

這是一個(gè)bool值纫普,默認(rèn)為No,如果設(shè)為Yes好渠,將會(huì)具有3維透視效果昨稼。

  • instanceDelay
  • instanceTransform
  • instanceColor
  • instanceRedOffset
  • instanceGreenOffset
  • instanceBlueOffset
  • instanceAlphaOffset

這一排屬性都是每一個(gè)實(shí)例與上一個(gè)實(shí)例對(duì)應(yīng)屬性的偏移量。

還是上代碼吧晦墙,直觀點(diǎn)悦昵。

//     Do any additional setup after loading the view.
        CALayer * layer = [CALayer layer];
        layer.bounds = CGRectMake(0, 0, 30, 30);
        layer.position = CGPointMake(self.view.center.x - 50, self.view.center.y - 50);
        layer.backgroundColor = [UIColor redColor].CGColor;
        layer.cornerRadius = 15;
        [self.view.layer addSublayer:layer];
    
        CABasicAnimation * animation1 = [CABasicAnimation animationWithKeyPath:@"opacity"];
        animation1.fromValue = @(0);
        animation1.toValue = @(1);
        animation1.duration = 1.5;
    //    animation1.autoreverses = YES;
    
        CABasicAnimation * animation2 = [CABasicAnimation animationWithKeyPath:@"transform.scale"];
        animation2.toValue = @(1.5);
        animation2.fromValue = @(0.5);
        animation2.duration = 1.5;
    //    animation2.autoreverses = YES;
    
        CAAnimationGroup * ani = [CAAnimationGroup animation];
        ani.animations = @[animation1,animation2];
        ani.duration = 1.5;
        ani.repeatCount = MAXFLOAT;
        ani.autoreverses = YES;
    
        [layer addAnimation:ani forKey:nil];
    
        CAReplicatorLayer * rec = [CAReplicatorLayer layer];
        [rec addSublayer:layer];
        rec.instanceCount = 3;
        rec.instanceDelay = 0.5;
        rec.instanceTransform = CATransform3DMakeTranslation(50, 0, 0);
        [self.view.layer addSublayer:rec];
    
        CAReplicatorLayer * rec2 = [CAReplicatorLayer layer];
        [rec2 addSublayer:rec];
        rec2.instanceCount = 3;
        rec2.instanceDelay = 0.5;
        rec2.instanceTransform = CATransform3DMakeTranslation(0, 50, 0);
        [self.view.layer addSublayer:rec2];

正如你所看到的,CAReplicatorLayer支持嵌套使用晌畅。它的效果是如下這個(gè)樣子的。

ReplicatorLayer

嘖嘖嘖寡痰,沒(méi)想到今天的內(nèi)容就這么講完了抗楔。

額,內(nèi)容比較少拦坠,的確是今天講的這幾個(gè)比較簡(jiǎn)單连躏。我知道只是這樣你們是不會(huì)放過(guò)我的。那我就放一個(gè)這幾個(gè)屬性聯(lián)合起來(lái)的一個(gè)小應(yīng)用吧贞滨。

Mirror.gif

忽略倒影的層次感吧入热,截圖問(wèn)題拍棕,正常是一個(gè)梯度漸變下去的。

其實(shí)拿到一個(gè)需求勺良,我們先分析一下想要實(shí)現(xiàn)他的步驟绰播,這個(gè)過(guò)程對(duì)于開發(fā)其實(shí)是很重要的。

首先來(lái)說(shuō)尚困,我們看到的倒影蠢箩,我們應(yīng)該可以考慮CAReplicator做一個(gè)復(fù)制圖層,配合instranceTransform屬性做出倒影效果

然后來(lái)說(shuō)事甜,我們看到了倒影漸變效果谬泌,我們應(yīng)該想到的是使用CAGradientLayer去實(shí)現(xiàn)過(guò)渡效果。

最后一些細(xì)節(jié)的參數(shù)我們可以根據(jù)需求去進(jìn)行相關(guān)設(shè)置逻谦。

分析過(guò)后其實(shí)思路還是挺清晰的掌实,一步步實(shí)現(xiàn)就好。
實(shí)現(xiàn)起來(lái)很簡(jiǎn)單邦马,代碼量也不多潮峦,我就直接放代碼就好了。

#pragma mark ---tool method---

-(void)handleMirrorDistant:(CGFloat)distant
{
    CAReplicatorLayer * layer = (CAReplicatorLayer *)self.layer;
    CATransform3D transform = CATransform3DIdentity;
    transform = CATransform3DTranslate(transform, 0, distant + self.bounds.size.height, 0);
    transform = CATransform3DScale(transform, 1, -1, 0);
    layer.instanceTransform = transform;
}

-(NSArray *)getMaskLayerLocations
{
    CGFloat height = self.bounds.size.height * 2 + self.mirrorDistant;
    CGFloat mirrowScale = self.bounds.size.height * (1 + self.mirrorScale) + self.mirrorDistant;
    return @[@0,@((self.bounds.size.height + self.mirrorDistant) / height),@(mirrowScale / height)];
}

-(CGFloat)safeValueBetween0And1:(CGFloat)value
{
    if (value > 1) {
        value = 1;
    } else if (value < 0) {
        value = 0;
    }
    return value;
}

-(void)valueInit
{
    self.mirrorDistant = 0;
    self.mirrorScale = 0.5;
    self.mirrored = YES;
    self.dynamic = YES;
    self.mirrorAlpha = 0.5;
}

#pragma mark ---override---

-(instancetype)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];
    if (self) {
        [self valueInit];
    }
    return self;
}

-(void)awakeFromNib
{
    [super awakeFromNib];
    [self valueInit];
}

+(Class)layerClass
{
    return [CAReplicatorLayer class];
}

-(void)drawRect:(CGRect)rect
{
    [super drawRect:rect];
    CAReplicatorLayer * layer = (CAReplicatorLayer *)self.layer;
    if (self.mirrored) {
        if (self.dynamic) {
            [self.mirrorImageView removeFromSuperview];
            self.mirrorImageView = nil;
            layer.instanceCount = 2;
            if (CATransform3DEqualToTransform(layer.instanceTransform, CATransform3DIdentity)) {
                [self handleMirrorDistant:self.mirrorDistant];
            }
        }
        else
        {
            layer.instanceCount = 1;
            CGSize size = CGSizeMake(self.bounds.size.width, self.bounds.size.height * self.mirrorScale);
            if (size.height > 0.0f && size.width > 0.0f)
            {
                UIGraphicsBeginImageContextWithOptions(size, NO, 0.0f);
                CGContextRef context = UIGraphicsGetCurrentContext();
                CGContextScaleCTM(context, 1.0f, -1.0f);
                CGContextTranslateCTM(context, 0.0f, -self.bounds.size.height);
                [self.layer renderInContext:context];
                self.mirrorImageView.image = UIGraphicsGetImageFromCurrentImageContext();
                UIGraphicsEndImageContext();
            }
            self.mirrorImageView.alpha = self.mirrorAlpha;
            self.mirrorImageView.frame = CGRectMake(0, self.bounds.size.height + self.mirrorDistant, size.width, size.height);
        }
        self.layer.mask = self.maskLayer;
    }
    else
    {
        layer.instanceCount = 1;
        [self.mirrorImageView removeFromSuperview];
        self.mirrorImageView = nil;
        self.layer.mask = nil;
    }
    
}

#pragma mark ---setter/getter---

-(void)setMirrored:(BOOL)mirrored
{
    _mirrored = mirrored;
    [self setNeedsDisplay];
}

-(void)setDynamic:(BOOL)dynamic
{
    _dynamic = dynamic;
    [self setNeedsDisplay];
}

-(void)setMirrorAlpha:(CGFloat)mirrorAlpha
{
    _mirrorAlpha = [self safeValueBetween0And1:mirrorAlpha];
    if (self.mirrored) {
        if (self.dynamic) {
            CAReplicatorLayer * layer = (CAReplicatorLayer *)self.layer;
            layer.instanceAlphaOffset = self.mirrorAlpha - 1;
        }
        else
        {
            [self setNeedsDisplay];
        }
    }
    
}

-(void)setMirrorScale:(CGFloat)mirrorScale
{
    _mirrorScale = [self safeValueBetween0And1:mirrorScale];
    if (self.mirrored) {
        self.maskLayer.locations = [self getMaskLayerLocations];
        if (!self.dynamic) {
            [self setNeedsDisplay];
        }
    }
}

-(void)setMirrorDistant:(CGFloat)mirrorDistant
{
    _mirrorDistant = mirrorDistant;
    if (self.mirrored) {
        self.maskLayer = nil;
        [self handleMirrorDistant:mirrorDistant];
        [self setNeedsDisplay];
    }
}

-(CAGradientLayer *)maskLayer
{
    if (!_maskLayer) {
        _maskLayer = [CAGradientLayer layer];
        _maskLayer.frame = CGRectMake(0, 0, self.bounds.size.width, self.bounds.size.height * 2 + self.mirrorDistant);
        _maskLayer.startPoint = CGPointMake(0, 0);
        _maskLayer.endPoint = CGPointMake(0, 1);
        _maskLayer.locations = [self getMaskLayerLocations];
        _maskLayer.colors = @[(id)[UIColor blackColor].CGColor,(id)[UIColor blackColor].CGColor,(id)[UIColor clearColor].CGColor];
    }
    return _maskLayer;
}

-(UIImageView *)mirrorImageView
{
    if (!_mirrorImageView) {
        _mirrorImageView = [[UIImageView alloc] initWithFrame:self.bounds];
        _mirrorImageView.contentMode = UIViewContentModeScaleToFill;
        _mirrorImageView.userInteractionEnabled = NO;
        [self addSubview:_mirrorImageView];
    }
    return _mirrorImageView;
}

因?yàn)楸旧碇蛔鳛橐粋€(gè)容器存在勇婴,不需要對(duì)外界留一些接口忱嘹,所以總共才200行代碼,不過(guò)實(shí)現(xiàn)的效果還是可以的耕渴。


今天的內(nèi)容告一段落了=拘悦。=老司機(jī)更的速度呢的確是有點(diǎn)慢,忙是一方面橱脸,是另一方面础米。

不過(guò)老司機(jī)會(huì)把剩下的幾個(gè)類在下一期說(shuō)完的=。=

demo老司機(jī)放在了網(wǎng)盤里添诉,你可以來(lái)這里找屁桑。

至于鏡像控件,老司機(jī)封裝好了單獨(dú)放在了一個(gè)倉(cāng)庫(kù)栏赴,你可以來(lái)這里找蘑斧。

最后,如果你喜歡老司機(jī)的文章须眷,點(diǎn)個(gè)關(guān)注點(diǎn)個(gè)喜歡吧~

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末竖瘾,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子花颗,更是在濱河造成了極大的恐慌捕传,老刑警劉巖,帶你破解...
    沈念sama閱讀 212,599評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件扩劝,死亡現(xiàn)場(chǎng)離奇詭異庸论,居然都是意外死亡职辅,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,629評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門聂示,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)域携,“玉大人,你說(shuō)我怎么就攤上這事催什『鳎” “怎么了?”我有些...
    開封第一講書人閱讀 158,084評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵蒲凶,是天一觀的道長(zhǎng)气筋。 經(jīng)常有香客問(wèn)我,道長(zhǎng)旋圆,這世上最難降的妖魔是什么宠默? 我笑而不...
    開封第一講書人閱讀 56,708評(píng)論 1 284
  • 正文 為了忘掉前任,我火速辦了婚禮灵巧,結(jié)果婚禮上搀矫,老公的妹妹穿的比我還像新娘。我一直安慰自己刻肄,他們只是感情好瓤球,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,813評(píng)論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著敏弃,像睡著了一般卦羡。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上麦到,一...
    開封第一講書人閱讀 50,021評(píng)論 1 291
  • 那天绿饵,我揣著相機(jī)與錄音,去河邊找鬼瓶颠。 笑死拟赊,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的粹淋。 我是一名探鬼主播吸祟,決...
    沈念sama閱讀 39,120評(píng)論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼廓啊!你這毒婦竟也來(lái)了欢搜?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,866評(píng)論 0 268
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤谴轮,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后吹埠,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體第步,經(jīng)...
    沈念sama閱讀 44,308評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡疮装,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,633評(píng)論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了粘都。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片廓推。...
    茶點(diǎn)故事閱讀 38,768評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖翩隧,靈堂內(nèi)的尸體忽然破棺而出樊展,到底是詐尸還是另有隱情,我是刑警寧澤堆生,帶...
    沈念sama閱讀 34,461評(píng)論 4 333
  • 正文 年R本政府宣布专缠,位于F島的核電站,受9級(jí)特大地震影響淑仆,放射性物質(zhì)發(fā)生泄漏涝婉。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,094評(píng)論 3 317
  • 文/蒙蒙 一蔗怠、第九天 我趴在偏房一處隱蔽的房頂上張望墩弯。 院中可真熱鬧,春花似錦寞射、人聲如沸渔工。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,850評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)引矩。三九已至,卻和暖如春策治,著一層夾襖步出監(jiān)牢的瞬間脓魏,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,082評(píng)論 1 267
  • 我被黑心中介騙來(lái)泰國(guó)打工通惫, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留茂翔,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,571評(píng)論 2 362
  • 正文 我出身青樓履腋,卻偏偏與公主長(zhǎng)得像珊燎,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子遵湖,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,666評(píng)論 2 350

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