CoreAnimation之變換

CoreAnimation之CALayer基礎

1. CGAffineTransform

  • CGAffineTransform基礎

UIView有一個CGAffineTransform類型的屬性,叫transform,用于在二維空間做旋轉,縮放和平移。CALayer也有一個與之對應的屬性叫affineTransform。CGAffineTransform所涉及的矩陣知識,這里有一篇文章說得很清楚: IOS矩陣之后的數學知識。CGPoint與CGAffineTransform的關系如圖:

Paste_Image.png

用CGPoint的每一列和CGAffineTransform矩陣的每一行對應元素相乘再求和,就形成了一個新的CGPoint類型的結果。要解釋一下圖中顯示的灰色元素,為了能讓矩陣做乘法,左邊矩陣的列數一定要和右邊矩陣的行數個數相同,所以要給矩陣填充一些標志值,使得既可以讓矩陣做乘法,又不改變運算結果,并且沒必要存儲這些添加的值,因為它們的值不會發生變化,但是要用來做運算。具體運算過程是這樣的:
Paste_Image.png

當對圖層應用變換矩陣,圖層矩形內的每一個點都被相應地做變換,從而形成一個新的四邊形的形狀。CGAffineTransform中的“仿射”的意思是無論變換矩陣用什么值,圖層中平行的兩條線在變換之后任然保持平行,CGAffineTransform可以做出任意符合上述標注的變換。下圖顯示了一些仿射的和非仿射的變換:
Paste_Image.png

  • CGAffineTransform應用

CGAffineTransform的前綴CG即Core Graphics,Core Graphics提供了一系列函數,對完全沒有數學基礎的開發者也能夠簡單地做一些變換。如下幾個函數都創建了一個CGAffineTransform實例:

/* Return a transform which translates by `(tx, ty)':
     t' = [ 1 0 0 1 tx ty ] */
CG_EXTERN CGAffineTransform CGAffineTransformMakeTranslation(CGFloat tx,
  CGFloat ty) CG_AVAILABLE_STARTING(__MAC_10_0, __IPHONE_2_0);
/* Return a transform which scales by `(sx, sy)':
     t' = [ sx 0 0 sy 0 0 ] */
CG_EXTERN CGAffineTransform CGAffineTransformMakeScale(CGFloat sx, CGFloat sy)
  CG_AVAILABLE_STARTING(__MAC_10_0, __IPHONE_2_0);
/* Return a transform which rotates by `angle' radians:
     t' = [ cos(angle) sin(angle) -sin(angle) cos(angle) 0 0 ] */
CG_EXTERN CGAffineTransform CGAffineTransformMakeRotation(CGFloat angle)
  CG_AVAILABLE_STARTING(__MAC_10_0, __IPHONE_2_0);

舉個栗子,點擊屏幕,將子視圖順時針旋轉45度:

#import "ViewController.h"
@interface ViewController ()
@end
@implementation ViewController{
    UIView *layerView;
}
-(void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    layerView = [[UIView alloc] initWithFrame:CGRectMake(0.0f, 0.0f, 100.0f, 100.0f)];
    layerView.backgroundColor = [UIColor purpleColor];
    layerView.center = self.view.center;
    [self.view addSubview:layerView];
}
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    [UIView animateWithDuration:1.25f animations:^{
        CGAffineTransform transform = CGAffineTransformMakeRotation(45.0f/180.0f*M_PI);
        layerView.layer.affineTransform = transform;
    }];
}
@end

運行效果:


running.gif

π是弧度單位,其對應的角度為180度,Core Animation使用弧度最為參數
弧度轉角度:

#define RADIANS_TO_DEGREES(x) ((x)/M_PI*180.0)

角度轉弧度:

#define DEGREES_TO_RADIANS(x) ((x)/180.0*M_PI)
  • 復合變換

CGAffineTransformConcat方法可以混合兩個已經存在的變換矩陣,在兩個變換的基礎上創建一個新的變換,舉個栗子,修改上面的代碼:

#import "ViewController.h"
@interface ViewController ()
@end
@implementation ViewController{
    UIView *layerView;
}
-(void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    layerView = [[UIView alloc] initWithFrame:CGRectMake(0.0f, 0.0f, 100.0f, 100.0f)];
    layerView.backgroundColor = [UIColor purpleColor];
    layerView.center = self.view.center;
    [self.view addSubview:layerView];
}
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    [UIView animateWithDuration:1.25f animations:^{
        //CGAffineTransform transform = CGAffineTransformMakeRotation(45.0f/180.0f*M_PI);
        //layerView.layer.affineTransform = transform;
        
        CGAffineTransform rotation = CGAffineTransformMakeRotation(45.0f/180.0f*M_PI);
        CGAffineTransform scale = CGAffineTransformMakeScale(1.5f, 1.5f);
        
        CGAffineTransform transform = CGAffineTransformConcat(rotation, scale);
        layerView.layer.affineTransform = transform;
    }];
}
@end

運行效果:


running.gif

CGAffineTransformConcat方法只能接受兩個參數,要做兩種以上的復合變換的話,需要用到下面這些方法:

/* Translate `t' by `(tx, ty)' and return the result:
     t' = [ 1 0 0 1 tx ty ] * t */
CG_EXTERN CGAffineTransform CGAffineTransformTranslate(CGAffineTransform t,
  CGFloat tx, CGFloat ty) CG_AVAILABLE_STARTING(__MAC_10_0, __IPHONE_2_0);
/* Scale `t' by `(sx, sy)' and return the result:
     t' = [ sx 0 0 sy 0 0 ] * t */
CG_EXTERN CGAffineTransform CGAffineTransformScale(CGAffineTransform t,
  CGFloat sx, CGFloat sy) CG_AVAILABLE_STARTING(__MAC_10_0, __IPHONE_2_0);
/* Rotate `t' by `angle' radians and return the result:
     t' =  [ cos(angle) sin(angle) -sin(angle) cos(angle) 0 0 ] * t */
CG_EXTERN CGAffineTransform CGAffineTransformRotate(CGAffineTransform t,
  CGFloat angle) CG_AVAILABLE_STARTING(__MAC_10_0, __IPHONE_2_0);

舉個栗子,我們來用這些函數組合一個更加復雜的變換,先縮小50%,再順時針旋轉30度,最后向右移動200個像,繼續修改上面的代碼:

#import "ViewController.h"
@interface ViewController ()
@end
@implementation ViewController{
    UIView *layerView;
}
-(void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    layerView = [[UIView alloc] initWithFrame:CGRectMake(0.0f, 0.0f, 100.0f, 100.0f)];
    layerView.backgroundColor = [UIColor purpleColor];
    layerView.center = self.view.center;
    [self.view addSubview:layerView];
}
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    [UIView animateWithDuration:1.25f animations:^{
        //CGAffineTransform transform = CGAffineTransformMakeRotation(45.0f/180.0f*M_PI);
        //layerView.layer.affineTransform = transform;
        
        //CGAffineTransform rotation = CGAffineTransformMakeRotation(45.0f/180.0f*M_PI);
        //CGAffineTransform scale = CGAffineTransformMakeScale(1.5f, 1.5f);
        
        //CGAffineTransform transform = CGAffineTransformConcat(rotation, scale);
        //layerView.layer.affineTransform = transform;
        
        CGAffineTransform transform = CGAffineTransformIdentity;
        transform = CGAffineTransformScale(transform, 0.5f, 0.5f);
        transform = CGAffineTransformRotate(transform, 30.0f/180.0f*M_PI);
        transform = CGAffineTransformTranslate(transform, 200.0f, 0.0f);
        layerView.layer.affineTransform = transform;
    }];
}
@end

運行效果:


running.gif

有些需要注意的地方:圖片向右邊發生了平移,但并沒有指定距離那么遠(200像素),另外它還有點向下發生了平移。原因在于當你按順序做了變換,上一個變換的結果將會影響之后的變換,所以200像素的向右平移同樣也被旋轉了30度,縮小了50%,所以它實際上是斜向移動了100像素。這意味著變換的順序會影響最終的結果,也就是說旋轉之后的平移和平移之后的旋轉結果可能不同。


2. CATransform3D

  • CATransform3D基礎

affineTransform屬性只能用來做2D變換,CALayer還有一個用來做3D變換的屬性叫transform,其類型是CATransform3D類型。和CGAffineTransform類似,CATransform3D也是一個矩陣,但是和2x3的矩陣不同,CATransform3D是一個可以在3維空間內做變換的4x4的矩陣:


Paste_Image.png

我們對X軸和Y軸比較熟悉了,分別以右和下為正方向,Z軸和這兩個軸分別垂直,指向視角外為正方向:


Paste_Image.png

由圖所見,繞Z軸的旋轉等同于之前二維空間的仿射旋轉,但是繞X軸和Y軸的旋轉就突破了屏幕的二維空間,并且在用戶視角看來發生了傾斜。
  • CATransform3D應用

CATransform3D類型的矩陣,和Core Graphics的函數類似,但是3D的平移和旋轉多處了一個z參數,并且旋轉函數除了angle之外多出了x,y,z三個參數,分別決定了每個坐標軸方向上的旋轉:

/* Returns a transform that translates by '(tx, ty, tz)':
 * t' =  [1 0 0 0; 0 1 0 0; 0 0 1 0; tx ty tz 1]. */
CA_EXTERN CATransform3D CATransform3DMakeTranslation (CGFloat tx,
    CGFloat ty, CGFloat tz)
    CA_AVAILABLE_STARTING (10.5, 2.0, 9.0, 2.0);
/* Returns a transform that scales by `(sx, sy, sz)':
 * t' = [sx 0 0 0; 0 sy 0 0; 0 0 sz 0; 0 0 0 1]. */
CA_EXTERN CATransform3D CATransform3DMakeScale (CGFloat sx, CGFloat sy,
    CGFloat sz)
    CA_AVAILABLE_STARTING (10.5, 2.0, 9.0, 2.0);
/* Returns a transform that rotates by 'angle' radians about the vector
 * '(x, y, z)'. If the vector has length zero the identity transform is
 * returned. */
CA_EXTERN CATransform3D CATransform3DMakeRotation (CGFloat angle, CGFloat x,
    CGFloat y, CGFloat z)
    CA_AVAILABLE_STARTING (10.5, 2.0, 9.0, 2.0);

舉個栗子,點擊屏幕將子視圖旋轉-45度:

#import "ViewController.h"
@interface ViewController ()
@end
@implementation ViewController{
    UIView *layerView;
}
-(void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    
    layerView = [[UIView alloc] initWithFrame:CGRectMake(0.0f, 0.0f, 150.0f, 150.0f)];
    layerView.center = self.view.center;
    layerView.layer.backgroundColor = [UIColor blueColor].CGColor;
    [self.view addSubview:layerView];
}
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    [UIView animateWithDuration:1.25f animations:^{
        CATransform3D transform = CATransform3DIdentity;
        transform = CATransform3DRotate(transform, -(45.0f/180.0f*M_PI), 0.0f, 1.0f, 0.0f);
        layerView.layer.transform = transform;
    }];
}
@end

運行效果:


running.gif

看起來好像只是寬度被壓扁了而已,這是因為我們在用一個斜向的視角看它,而不是透視。

  • 透視效果

CATransform3D的透視效果通過一個矩陣中一個很簡單的元素來控制:m34。m34用于按比例縮放X和Y的值來計算到底要離視角多遠,其默認值是0,我們可以通過設置m34為-1.0 / d來應用透視效果,d代表了想象中視角相機和屏幕之間的距離,以像素為單位,那應該如何計算這個距離呢?實際上并不需要如何計算,大概估算一個就好了,一般是500-1000。修改上面的代碼:

#import "ViewController.h"
@interface ViewController ()
@end
@implementation ViewController{
    UIView *layerView;
}
-(void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    
    layerView = [[UIView alloc] initWithFrame:CGRectMake(0.0f, 0.0f, 150.0f, 150.0f)];
    layerView.center = self.view.center;
    layerView.layer.backgroundColor = [UIColor blueColor].CGColor;
    [self.view addSubview:layerView];
}
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    [UIView animateWithDuration:1.25f animations:^{
        CATransform3D transform = CATransform3DIdentity;
        transform.m34 = -1.0f / 500.0f;
        transform = CATransform3DRotate(transform, -(45.0f/180.0f*M_PI), 0.0f, 1.0f, 0.0f);
        layerView.layer.transform = transform;
    }];
}
@end

運行效果:


running.gif
  • 滅點

當在透視角度繪圖的時候,遠離相機視角的物體將會變小變遠,當遠離到一個極限距離,它們可能就縮成了一個點,于是所有的物體最后都匯聚消失在同一個點,這個點叫滅點。

Core Animation定義了滅點位于變換圖層的anchorPoint,這就是說,當圖層發生變換時,這個點永遠位于圖層變換之前anchorPoint的位置。當改變一個圖層的position,你也改變了它的滅點,做3D變換的時候要時刻記住這一點,當你視圖通過調整m34來讓它更加有3D效果,應該首先把它放置于屏幕中央,然后通過平移來把它移動到指定位置(而不是直接改變它的position),這樣所有的3D圖層都共享一個滅點。

  • sublayerTransform

如果有多個子視圖或者子圖層,每個都做3D變換,那就需要分別設置相同的m34值,并且確保在變換之前都在屏幕中央共享同一個position,這樣操作起來很麻煩,CALayer有一個叫做sublayerTransform的屬性可以解決這個問題。

sublayerTransform也是CATransform3D類型,但和對一個圖層的變換不同,它影響到所有的子圖層,這意味著你可以一次性對包含這些圖層的容器做變換。

使用sublayerTransform,滅點被設置在容器圖層的中點,從而不需要再對子圖層分別設置了。這意味著你可以隨意使用position和frame來放置子圖層,而不需要把它們放置在屏幕中點,然后為了保證統一的滅點用變換來做平移。

  • doubleSided

CALayer有一個叫做doubleSided的屬性來控制圖層的背面是否要被繪制,這是一個BOOL類型,默認為YES,如果設置為NO,那么當圖層正面從相機視角消失的時候,它將不會被繪制。舉個栗子:

#import "ViewController.h"
@interface ViewController ()
@end
@implementation ViewController{
    UIView *topView;
    UIView *bottomView;
}
-(void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    
    UIImage *image = [UIImage imageNamed:@"3"];
    
    topView = [[UIView alloc] initWithFrame:CGRectMake(85.0f, 100.0f, 150.0f, 150.0f)];
    bottomView = [[UIView alloc] initWithFrame:CGRectMake(85.0f, 300.0f, 150.0f, 150.0f)];
    
    bottomView.layer.doubleSided = NO;
    
    [self configView:topView image:image];
    [self configView:bottomView image:image];
}
-(void)configView:(UIView *)aView image:(UIImage *)aImage{
    aView.layer.backgroundColor = [UIColor greenColor].CGColor;
    aView.layer.contents = (__bridge id _Nullable)(aImage.CGImage);
    aView.layer.contentsGravity = kCAGravityResizeAspect;
    aView.layer.contentsScale = [UIScreen mainScreen].scale;
    [self.view addSubview:aView];
}
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    [UIView animateWithDuration:5.0f animations:^{
        CATransform3D transform = CATransform3DIdentity;
        transform = CATransform3DRotate(transform, M_PI, 0.0f, 1.0f, 0.0f);
        topView.layer.transform = transform;
        bottomView.layer.transform = transform;
    }];
}
@end

運行效果:


running.gif

3. 固體對象

現在我們懂得了在3D空間的一些圖層布局的基礎,接下來試著創建一個固態的3D對象(實際上是一個技術上所謂的空洞對象,但它以固態呈現),我們用三個獨立的視圖來構建一個殘缺的正方體

Interface Build布局如圖:

Paste_Image.png

代碼:

#import "TestViewController.h"
@interface TestViewController ()
@property (nonatomic, weak) IBOutlet UIView *containerView;
@property (nonatomic, strong) IBOutletCollection(UIView) NSArray *faces;
@end

@implementation TestViewController

- (void)addFace:(NSInteger)index withTransform:(CATransform3D)transform{
    //get the face view and add it to the container
    UIView *face = self.faces[index];
    [self.containerView addSubview:face];
    //center the face view within the container
    CGSize containerSize = self.containerView.bounds.size;
    face.center = CGPointMake(containerSize.width / 2.0, containerSize.height / 2.0);
    // apply the transform
    face.layer.transform = transform;
}

- (void)viewDidLoad{
    [super viewDidLoad];
    //set up the container sublayer transform
    CATransform3D perspective = CATransform3DIdentity;
    perspective.m34 = -1.0 / 500.0;
    self.containerView.layer.sublayerTransform = perspective;
    //add cube face 1
    CATransform3D transform = CATransform3DMakeTranslation(0, 0, 100);
    [self addFace:0 withTransform:transform];
    //add cube face 2
    transform = CATransform3DMakeTranslation(100, 0, 0);
    transform = CATransform3DRotate(transform, M_PI_2, 0, 1, 0);
    [self addFace:1 withTransform:transform];
    //add cube face 3
    transform = CATransform3DMakeTranslation(0, -100, 0);
    transform = CATransform3DRotate(transform, M_PI_2, 1, 0, 0);
    [self addFace:2 withTransform:transform];
}
@end

運行效果:

Paste_Image.png

從這個角度看正方體并不是很明顯,看起來只是一個方塊,為了更好地欣賞它,我們將更換一個不同的視角

但是旋轉這個正方體將會顯得很笨重,因為我們要單獨對每個面做旋轉,這時候我們之前說的sublayerTransform就派上了用場

添加兩行代碼去旋轉containerView圖層的perspective變換矩陣:

    perspective = CATransform3DRotate(perspective, -M_PI_4, 1, 0, 0);
    perspective = CATransform3DRotate(perspective, -M_PI_4, 0, 1, 0);

把相機(或者相對相機的整個場景)繞Y軸旋轉45度,并且繞X軸旋轉45度,現在從另一個角度去觀察正方體,就能看出它的真實面貌:

Paste_Image.png

效果出來了,但是我還想旋轉一下這個正方體,看看其他的面,怎么辦呢

so easy!繞y軸旋轉咯

說干就干,修改代碼:

#import "TestViewController.h"
@interface TestViewController ()
@property (nonatomic, weak) IBOutlet UIView *containerView;
@property (nonatomic, strong) IBOutletCollection(UIView) NSArray *faces;
@end

@implementation TestViewController{
    CATransform3D rotateTransform;
}

- (void)addFace:(NSInteger)index withTransform:(CATransform3D)transform{
    //get the face view and add it to the container
    UIView *face = self.faces[index];
    [self.containerView addSubview:face];
    //center the face view within the container
    CGSize containerSize = self.containerView.bounds.size;
    face.center = CGPointMake(containerSize.width / 2.0, containerSize.height / 2.0);
    // apply the transform
    face.layer.transform = transform;
}

- (void)viewDidLoad{
    [super viewDidLoad];
    //set up the container sublayer transform
    
    rotateTransform = CATransform3DIdentity;
    rotateTransform.m34 = -1.0f / 500.0f;
    
    CATransform3D perspective = CATransform3DIdentity;
    perspective.m34 = -1.0 / 500.0;
    perspective = CATransform3DRotate(perspective, -M_PI_4, 1, 0, 0);
    perspective = CATransform3DRotate(perspective, -M_PI_4, 0, 1, 0);
    self.containerView.layer.sublayerTransform = perspective;

    //add cube face 1
    CATransform3D transform = CATransform3DMakeTranslation(0, 0, 100);
    [self addFace:0 withTransform:transform];
    //add cube face 2
    transform = CATransform3DMakeTranslation(100, 0, 0);
    transform = CATransform3DRotate(transform, M_PI_2, 0, 1, 0);
    [self addFace:1 withTransform:transform];
    //add cube face 3
    transform = CATransform3DMakeTranslation(0, -100, 0);
    transform = CATransform3DRotate(transform, M_PI_2, 1, 0, 0);
    [self addFace:2 withTransform:transform];
}

-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    [NSTimer scheduledTimerWithTimeInterval:0.01f target:self selector:@selector(timerAction) userInfo:nil repeats:YES];
}

-(void)timerAction{
    static CGFloat angle = 1.0f;
    rotateTransform = CATransform3DRotate(rotateTransform, angle/180.0f*M_PI, 0.0f, 1.0f, 0.0f);
    self.containerView.layer.transform = rotateTransform;
}

@end

跑起來:

running.gif

納尼,這是什么鬼??

這是由于盡管Core Animation圖層存在于3D空間之內,但它們并不都存在同一個3D空間。每個圖層的3D場景其實是扁平化的,當你從正面觀察一個圖層,看到的實際上由子圖層創建的想象出來的3D場景,但當你傾斜這個圖層,你會發現實際上這個3D場景僅僅是被繪制在圖層的表面。

總之一句話,圖層是扁平的,直接把superLayer繞y軸旋轉的方法行不通

要想實現旋轉正方體的效果,就得將所有的子圖層全部挨個兒做變換

到最后還得用sublayerTransform

繼續修改代碼:

#import "TestViewController.h"
@interface TestViewController ()
@property (nonatomic, weak) IBOutlet UIView *containerView;
@property (nonatomic, strong) IBOutletCollection(UIView) NSArray *faces;
@end

@implementation TestViewController{
//    CATransform3D rotateTransform;
}

- (void)addFace:(NSInteger)index withTransform:(CATransform3D)transform{
    //get the face view and add it to the container
    UIView *face = self.faces[index];
    [self.containerView addSubview:face];
    //center the face view within the container
    CGSize containerSize = self.containerView.bounds.size;
    face.center = CGPointMake(containerSize.width / 2.0, containerSize.height / 2.0);
    // apply the transform
    face.layer.transform = transform;
}

- (void)viewDidLoad{
    [super viewDidLoad];
    //set up the container sublayer transform
    
//    rotateTransform = CATransform3DIdentity;
//    rotateTransform.m34 = -1.0f / 500.0f;
    
    CATransform3D perspective = CATransform3DIdentity;
    perspective.m34 = -1.0 / 500.0;
    perspective = CATransform3DRotate(perspective, -M_PI_4, 1, 0, 0);
    perspective = CATransform3DRotate(perspective, -M_PI_4, 0, 1, 0);
    self.containerView.layer.sublayerTransform = perspective;
    
    //add cube face 1
    CATransform3D transform = CATransform3DMakeTranslation(0, 0, 100);
    [self addFace:0 withTransform:transform];
    //add cube face 2
    transform = CATransform3DMakeTranslation(100, 0, 0);
    transform = CATransform3DRotate(transform, M_PI_2, 0, 1, 0);
    [self addFace:1 withTransform:transform];
    //add cube face 3
    transform = CATransform3DMakeTranslation(0, -100, 0);
    transform = CATransform3DRotate(transform, M_PI_2, 1, 0, 0);
    [self addFace:2 withTransform:transform];
}

-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    [NSTimer scheduledTimerWithTimeInterval:0.01f target:self selector:@selector(timerAction) userInfo:nil repeats:YES];
}

-(void)timerAction{
    static CGFloat angle = 1.0f;
    CATransform3D transform3d = self.containerView.layer.sublayerTransform;
    transform3d = CATransform3DRotate(transform3d, angle/180.0f*M_PI, 0.0f, 1.0f, 0.0f);
    self.containerView.layer.sublayerTransform = transform3d;
    
//    rotateTransform = CATransform3DRotate(rotateTransform, angle/180.0f*M_PI, 0.0f, 1.0f, 0.0f);
//    self.containerView.layer.transform = rotateTransform;
}

@end

最終運行效果:

running.gif
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容