系列文章:
- 老司機(jī)帶你走進(jìn)Core Animation 之CAAnimation
- 老司機(jī)帶你走進(jìn)Core Animation 之CADisplayLink
- 老司機(jī)帶你走進(jìn)Core Animation 之幾種動(dòng)畫的簡(jiǎn)單應(yīng)用
- 老司機(jī)帶你走進(jìn)Core Animation 之CAShapeLayer和CATextLayer
- 老司機(jī)帶你走進(jìn)Core Animation 之圖層的透視嬉橙、漸變及復(fù)制
- 老司機(jī)帶你走進(jìn)Core Animation 之粒子發(fā)射、TileLayer與異步繪制
這回呢,當(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是一個(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的么=诡蜓。=)
所以呢熬甫,老司機(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)控制圖層變換后的景深效果
的,也就是透視效果
。
上面的圖片分別展示了具有透視效果的旋轉(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)不好懂有额,看下面的圖吧。
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è)樣子的。
嘖嘖嘖寡痰,沒(méi)想到今天的內(nèi)容就這么講完了抗楔。
額,內(nèi)容比較少拦坠,的確是今天講的這幾個(gè)比較簡(jiǎn)單连躏。我知道只是這樣你們是不會(huì)放過(guò)我的。那我就放一個(gè)這幾個(gè)屬性聯(lián)合起來(lái)的一個(gè)小應(yīng)用吧贞滨。
忽略倒影的層次感吧入热,截圖問(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è)喜歡吧~