自定义 Layer 属性的动画

2024-08-22 18:48
文章标签 自定义 属性 动画 layer

本文主要是介绍自定义 Layer 属性的动画,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

转自@nixzhuGitHub主页(译者:@nixzhu),原文《Animating Custom Layer Properties

 

默认情况下,CALayer 及其子类的绝大部分标准属性都可以执行动画,无论是添加一个 CAAnimation 到 Layer(显式动画),亦或是为属性指定一个动作然后修改它(隐式动画)。

但有时候我们希望能同时为好几个属性添加动画,使它们看起来像是一个动画一样;或者,我们需要执行的动画不能通过使用标准 Layer 属性动画来实现。
在本文中,我们将讨论如何子类化 CALayer 并添加我们自己的属性,以便比较容易地创建那些如果以其他方式实现起来会很麻烦的动画效果。
一般说来,我们希望添加到 CALayer 的子类上的可动画属性有三种类型:
1. 能间接动画 Layer (或其子类)的一个或多个标准属性的属性。
2. 能触发 Layer 的支持图像(backing image)(即 contents 属性)重绘的属性。
3. 不涉及 Layer 重绘或对任何已有属性执行动画的属性。
间接属性动画
能间接修改其它标准 Layer 属性的自定义属性是这些选项中最简单的。它们真的只是自定义 setter 方法以将它们的输入转换为适用于创建动画的一个或多个不同的值。
如果被我们设置的属性已经预设好标准动画,那我们完全不需要编写任何实际的动画代码,因为我们修改这些属性后,它们就会继承任何被配置在当前 CATransaction 上的动画设置,并且自动执行动画。
换句话说,即使 CALayer 不知道如何对我们自定义的属性进行动画,它依然能对因自定义属性被改变而引起的其它可见副作用进行动画,而这恰好就是我们所关心的全部内容。
为了演示这种方法,让我们来创建一个简单的模拟时钟,之后我们可以使用被声明为 NSDate 类型 time 属性来设置它的时间。我会将从创建一个静态的时钟面盘开始。这个时钟包含三个 CAShapeLayer 实例——一个用于时钟面盘的圆形 Layer 和两个用于时针和分针的长方形 Sublayer。
 
  1. @interface ClockFace: CAShapeLayer 
  2.   
  3. @property (nonatomic, strong) NSDate *time; 
  4.   
  5. @end 
  6.   
  7. @interface ClockFace () 
  8.   
  9. // 私有属性,译者注:这里申明的是 CALayer ,下面分配的却是 CAShapeLayer ,按照文字,应该都是 CAShapeLayer 才对 
  10. @property (nonatomic, strong) CALayer *hourHand; 
  11. @property (nonatomic, strong) CALayer *minuteHand; 
  12.   
  13. @end 
  14.   
  15. @implementation ClockFace 
  16.   
  17. - (id)init 
  18.     if ((self = [super init])) 
  19.     { 
  20.         self.bounds = CGRectMake(0, 0, 200, 200); 
  21.         self.path = [UIBezierPath bezierPathWithOvalInRect:self.bounds].CGPath; 
  22.         self.fillColor = [UIColor whiteColor].CGColor; 
  23.         self.strokeColor = [UIColor blackColor].CGColor; 
  24.         self.lineWidth = 4; 
  25.   
  26.         self.hourHand = [CAShapeLayer layer]; 
  27.         self.hourHand.path = [UIBezierPath bezierPathWithRect:CGRectMake(-2, -70, 4, 70)].CGPath; 
  28.         self.hourHand.fillColor = [UIColor blackColor].CGColor; 
  29.         self.hourHand.position = CGPointMake(self.bounds.size.width / 2, self.bounds.size.height / 2); 
  30.         [self addSublayer:self.hourHand]; 
  31.   
  32.         self.minuteHand = [CAShapeLayer layer]; 
  33.         self.minuteHand.path = [UIBezierPath bezierPathWithRect:CGRectMake(-1, -90, 2, 90)].CGPath; 
  34.         self.minuteHand.fillColor = [UIColor blackColor].CGColor; 
  35.         self.minuteHand.position = CGPointMake(self.bounds.size.width / 2, self.bounds.size.height / 2); 
  36.         [self addSublayer:self.minuteHand]; 
  37.     } 
  38.     return self; 
  39.   
  40. @end 
同时我们要设置一个基本的 View Controller ,它包含一个 UIDatePicker ,这样我们就能测试我们的 Layer (日期选择器在 Storyboard 里设置)了:
 
  1. @interface ViewController () 
  2.   
  3. @property (nonatomic, strong) IBOutlet UIDatePicker *datePicker; 
  4. @property (nonatomic, strong) ClockFace *clockFace; 
  5.   
  6. @end 
  7.   
  8.   
  9. @implementation ViewController 
  10.   
  11. - (void)viewDidLoad 
  12.     [super viewDidLoad]; 
  13.   
  14.     // 添加时钟面板 Layer 
  15.     self.clockFace = [[ClockFace alloc] init]; 
  16.     self.clockFace.position = CGPointMake(self.view.bounds.size.width / 2, 150); 
  17.     [self.view.layer addSublayer:self.clockFace]; 
  18.   
  19.     // 设置默认时间 
  20.     self.clockFace.time = [NSDate date]; 
  21.   
  22. - (IBAction)setTime 
  23.     self.clockFace.time = self.datePicker.date; 
  24.   
  25. @end 
现在我们只需要实现 time 属性的 setter 方法。这个方法使用 NSCalendar 将时间变为小时和分钟,之后我们将它们转换为角坐标。然后我们就可以使用这些角度去生成两个 CGAffineTransform 以旋转时针和分针。
 
  1. - (void)setTime:(NSDate *)time 
  2.     _time = time; 
  3.   
  4.     NSCalendar *calendar = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar]; 
  5.     NSDateComponents *components = [calendar components:NSHourCalendarUnit | NSMinuteCalendarUnit fromDate:time]; 
  6.     self.hourHand.affineTransform = CGAffineTransformMakeRotation(components.hour / 12.0 * 2.0 * M_PI); 
  7.     self.minuteHand.affineTransform = CGAffineTransformMakeRotation(components.minute / 60.0 * 2.0 * M_PI); 
结果看起来像这样: 
 
你可以 从 GitHub 上 下载这个项目看看。
如你所见,我们实在没有做什么太费脑筋的事情;我们并没有创建一个新的可动画属性,而只是在单个方法里设置了几个标准可动画 Layer 属性而已。因此,如果我们想创建的动画并不能映射到任何已有的 Layer 属性上时,该怎么办呢?
对 Layer 的内容执行动画
假设不使用几个分离的 Layer 来实现我们的时钟面板,那我们可以改用 Core Graphics 来绘制时钟。(这通常会降低性能,但我们可以假想我们所要实现的效果需要许多复杂的绘图操作,而它们很难用常规的 Layer 属性和 transform 来复制。)我们要怎么做呢?
很类似 NSManagedObject , CALayer 具有为任何被声明的属性生成动态 setter 和 getter 的能力。在当前的实现中,我们是让编译器去合成 time 属性的 ivar 和 getter 方法,而我们自己实现了 setter 方法。但让我们来改变一下:丢弃我们的 setter 并将属性标记为 @dynamic 。同时我们也丢弃分离的时针和分针 Layer ,因为我们将自己去绘制它们。
 
  1. @interface ClockFace () 
  2.   
  3. @end 
  4.   
  5.   
  6. @implementation ClockFace 
  7.   
  8. @dynamic time; 
  9.   
  10. - (id)init 
  11.     if ((self = [super init])) 
  12.     { 
  13.         self.bounds = CGRectMake(0, 0, 200, 200); 
  14.     } 
  15.     return self; 
  16.   
  17. @end 
译者注:没使用过 @dynamic 这样的高级货,心中还有点小激动呢!
在我们开始做事之前,需要先做一个小调整:因为不幸的是,CALayer 不知道如何对 NSDate 属性进行插值(interpolate)(例如,它不能自动生成 NSDate 实例之间的中间值,虽然它可以处理数字类型和其它例如 CGColor 和 CGAffineTransform 这样的类型)。我们可以保留我们的自定义 setter 方法并用它设置另一个动态属性(它表示等价的 NSTimeInterval,这是一个数字值,可以被插值),但为了保持例子的简单性,我们会用一个浮点值替换 NSDate 属性来表征时钟的小时,为了更新用户界面,我们使用一个简单的 UITextField 来设置浮点值,不再使用日期选择器:
 
  1. @interface ViewController () <UITextFieldDelegate> 
  2.   
  3. @property (nonatomic, strong) IBOutlet UITextField *textField; 
  4. @property (nonatomic, strong) ClockFace *clockFace; 
  5.   
  6. @end 
  7.   
  8.   
  9. @implementation ViewController 
  10.   
  11. - (void)viewDidLoad 
  12.     [super viewDidLoad]; 
  13.   
  14.     // 添加时钟面板 Layer 
  15.     self.clockFace = [[ClockFace alloc] init]; 
  16.     self.clockFace.position = CGPointMake(self.view.bounds.size.width / 2, 150); 
  17.     [self.view.layer addSublayer:self.clockFace]; 
  18.   
  19. - (BOOL)textFieldShouldReturn:(UITextField *)textField 
  20.     [textField resignFirstResponder]; 
  21.     return YES; 
  22.   
  23. - (void)textFieldDidEndEditing:(UITextField *)textField 
  24.     self.clockFace.time = [textField.text floatValue]; 
  25.   
  26. @end 
现在,既然我们已经移除了自定义的 setter 方法,那我们要如何才能知晓 time 属性的改变呢?我们需要一个无论何时 time 属性改变时都能自动通知 CALayer 的方式,这样它才好重绘它的内容。我们通过覆写 +needsDisplayForKey: 方法即可做到这一点,如下:
 
  1. + (BOOL)needsDisplayForKey:(NSString *)key 
  2.     if ([@"time" isEqualToString:key]) 
  3.     { 
  4.         return YES; 
  5.     } 
  6.     return [super needsDisplayForKey:key]; 
这就告诉了 Layer ,无论何时 time 属性被修改,它都需要调用 -display 方法。现在我们就覆写 -display 方法,添加一个 NSLog 语句打印出 time 的值:
 
  1. - (void)display 
  2.     NSLog(@"time: %f", self.time); 
如果我们设置 time 属性为 1.5 ,我们就会看到 -display 被调用,打印出新值:
 
  1. 2014-04-28 22:37:04.253 ClockFace[49145:60b] time: 1.500000 
但这还不是我们真正想要的;我们希望 time 属性能在旧值和新值之间有多个帧长的平滑的过渡动画。为了实现这一点,我们需要为 time 属性指定一个动画(或“动作(action)”),而通过覆写 -actionForKey: 方法就能做到:
 
  1. - (id<CAAction>)actionForKey:(NSString *)key 
  2.     if ([key isEqualToString:@"time"]) 
  3.     { 
  4.         CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:key]; 
  5.         animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear]; 
  6.         animation.fromValue = @(self.time); 
  7.         return animation; 
  8.     } 
  9.     return [super actionForKey:key]; 
现在,如果我们再次设置 time 属性,我们就会看到 -display 被多次调用。调用的次数大约为每秒 60 次,至于动画的长度,默认为 0.25 秒,大约是 15 帧:
 
  1. 2014-04-28 22:37:04.253 ClockFace[49145:60b] time: 1.500000 
  2. 2014-04-28 22:37:04.255 ClockFace[49145:60b] time: 1.500000 
  3. 2014-04-28 22:37:04.351 ClockFace[49145:60b] time: 1.500000 
  4. 2014-04-28 22:37:04.370 ClockFace[49145:60b] time: 1.500000 
  5. 2014-04-28 22:37:04.388 ClockFace[49145:60b] time: 1.500000 
  6. 2014-04-28 22:37:04.407 ClockFace[49145:60b] time: 1.500000 
  7. 2014-04-28 22:37:04.425 ClockFace[49145:60b] time: 1.500000 
  8. 2014-04-28 22:37:04.443 ClockFace[49145:60b] time: 1.500000 
  9. 2014-04-28 22:37:04.461 ClockFace[49145:60b] time: 1.500000 
  10. 2014-04-28 22:37:04.479 ClockFace[49145:60b] time: 1.500000 
  11. 2014-04-28 22:37:04.497 ClockFace[49145:60b] time: 1.500000 
  12. 2014-04-28 22:37:04.515 ClockFace[49145:60b] time: 1.500000 
  13. 2014-04-28 22:37:04.755 ClockFace[49145:60b] time: 1.500000 
由于某些原因,当我们在每个中间点打印 time 值时,我们一直看到的是最终值。为何不能得到插值呢?因为我们查看的是错误的 time 属性。
当你设置某个 CALayer 的某个属性,你实际设置的是 model Layer 的值——这里的 model Layer 表示正在进行的动画结束时, Layer 所达到的最终状态。如果你取 model Layer 的值,它就总是给你它被设置到的最终值。
但连接到 model Layer 的是所谓的 presentation Layer ——它是 model Layer 的一个拷贝,但它的值所表示的是 当前的,中间动画状态。如果我们修改 -display 方法去打印 Layer 的 presentationLayer 的 time 属性,那我们就会看到我们所期望的插值。(同时我们也使用 presentationLayer 的 time 属性来获取动画的开始值,替代 self.time ):
 
  1. - (id<CAAction>)actionForKey:(NSString *)key 
  2.     if ([key isEqualToString:@"time"]) 
  3.     { 
  4.         CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:key]; 
  5.         animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear]; 
  6.         animation.fromValue = @([[self presentationLayer] time]); 
  7.         return animation; 
  8.     } 
  9.     return [super actionForKey:key]; 
  10.   
  11. - (void)display 
  12.     NSLog(@"time: %f", [[self presentationLayer] time]); 
下面是打印出的值:
 
  1. 2014-04-28 22:43:31.200 ClockFace[49176:60b] time: 0.000000 
  2. 2014-04-28 22:43:31.203 ClockFace[49176:60b] time: 0.002894 
  3. 2014-04-28 22:43:31.263 ClockFace[49176:60b] time: 0.363371 
  4. 2014-04-28 22:43:31.300 ClockFace[49176:60b] time: 0.586421 
  5. 2014-04-28 22:43:31.318 ClockFace[49176:60b] time: 0.695179 
  6. 2014-04-28 22:43:31.336 ClockFace[49176:60b] time: 0.803713 
  7. 2014-04-28 22:43:31.354 ClockFace[49176:60b] time: 0.912598 
  8. 2014-04-28 22:43:31.372 ClockFace[49176:60b] time: 1.021573 
  9. 2014-04-28 22:43:31.391 ClockFace[49176:60b] time: 1.134173 
  10. 2014-04-28 22:43:31.409 ClockFace[49176:60b] time: 1.242892 
  11. 2014-04-28 22:43:31.427 ClockFace[49176:60b] time: 1.352016 
  12. 2014-04-28 22:43:31.446 ClockFace[49176:60b] time: 1.460729 
  13. 2014-04-28 22:43:31.464 ClockFace[49176:60b] time: 1.500000 
  14. 2014-04-28 22:43:31.636 ClockFace[49176:60b] time: 1.500000 
所以现在我们所要做就是画出时钟。我们将使用普通的 Core Graphics 函数以绘制到一个 Graphics Context 上来做到这一点,然后将产生出图像设置为我们 Layer 的 contents。下面是更新后的 -display 方法:
 
  1. - (void)display 
  2.     // 获取时间插值 
  3.     float time = [self.presentationLayer time]; 
  4.   
  5.     // 创建绘制上下文 
  6.     UIGraphicsBeginImageContextWithOptions(self.bounds.size, NO, 0); 
  7.     CGContextRef ctx = UIGraphicsGetCurrentContext(); 
  8.   
  9.     // 绘制时钟面板 
  10.     CGContextSetLineWidth(ctx, 4); 
  11.     CGContextStrokeEllipseInRect(ctx, CGRectInset(self.bounds, 2, 2)); 
  12.   
  13.     // 绘制时针 
  14.     CGFloat angle = time / 12.0 * 2.0 * M_PI; 
  15.     CGPoint center = CGPointMake(self.bounds.size.width / 2, self.bounds.size.height / 2); 
  16.     CGContextSetLineWidth(ctx, 4); 
  17.     CGContextMoveToPoint(ctx, center.x, center.y); 
  18.     CGContextAddLineToPoint(ctx, center.x + sin(angle) * 80, center.y - cos(angle) * 80); 
  19.     CGContextStrokePath(ctx); 
  20.   
  21.     // 绘制分针 
  22.     angle = (time - floor(time)) * 2.0 * M_PI; 
  23.     CGContextSetLineWidth(ctx, 2); 
  24.     CGContextMoveToPoint(ctx, center.x, center.y); 
  25.     CGContextAddLineToPoint(ctx, center.x + sin(angle) * 90, center.y - cos(angle) * 90); 
  26.     CGContextStrokePath(ctx); 
  27.   
  28.     //set backing image 设置 contents  
  29.     self.contents = (id)UIGraphicsGetImageFromCurrentImageContext().CGImage; 
  30.     UIGraphicsEndImageContext(); 
结果看起来如下: 
 
如你所见,不同于第一个时钟动画,随着时针的变化,分针实际上对每一个小时都会转上满满一圈(就像一个真正的时钟那样),而不仅仅只是通过最短的路径移动到它的最终位置;因为我们正在动画的是 time 值本身而不仅仅是时针或分针的位置,所以上下文信息被保留了。
通过这样的方式绘制一个时钟并不是很理想,因为 Core Graphics 函数没有硬件加速,可能会引起动画帧数的下降。另一种能每秒重绘 contents 图像 60 次的方式是用一个数组存储一些预先绘制好的图像,然后基于合适的插值简单的选择对应的图像即可。实现代码大概如下:
 
  1. const NSInteger hoursOnAClockFace = 12; 
  2.   
  3. - (void)display 
  4.     // 获取时间插值  
  5.     float time = [self.presentationLayer time] / hoursOnAClockFace; 
  6.   
  7.     // 从之前定义好的图像数组里获取图像帧 
  8.     NSInteger numberOfFrames = [self.frames count]; 
  9.     NSInteger index = round(time * numberOfFrames) % numberOfFrames; 
  10.     UIImage *frame = self.frames[index]; 
  11.     self.contents = (id)frame.CGImage; 
通过避免在每一帧里都用昂贵的软件绘制,我们能改善动画的性能,但代价是我们需要在内存里存储所有预先绘制的动画帧图像,对于一个复杂的动画来说,这可能造成惊人的内存浪费。
但这提出了一个有趣的可能性。如果我们完全不在 -display 里更新 contents 图像会发生什么?我们做一些其它的事情怎样?
非可视属性的动画
在 -display 里更新其它 Layer 属性是不必要的,因为我们可以很简单地直接对任何这样的属性做动画,如同我们在第一个时钟面板例子里所做的那样。但如果我们设置一些其它的东西,比如某些完全不和 Layer 相关的东西,会怎样呢?
下面的代码使用一个 CALayer 结合 AVAudioPlayer 来创建一个可动画的音量控制器。通过把音量调到动态 Layer 属性上,我们可以使用 Core Animation 的属性插值来平滑的在两个不同的音量之间渐变,以同样的方式我们可以动画 Layer 上的任何 cosmetic 属性:
 
  1. @interface AudioLayer : CALayer 
  2.   
  3. - (id)initWithAudioFileURL:(NSURL *)URL; 
  4.   
  5. @property (nonatomic, assign) float volume; 
  6.   
  7. - (void)play; 
  8. - (void)stop; 
  9. - (BOOL)isPlaying; 
  10.   
  11. @end 
  12.   
  13.   
  14. @interface AudioLayer () 
  15.   
  16. @property (nonatomic, strong) AVAudioPlayer *player; 
  17.   
  18. @end 
  19.   
  20.   
  21. @implementation AudioLayer 
  22.   
  23. @dynamic volume; 
  24.   
  25. - (id)initWithAudioFileURL:(NSURL *)URL 
  26.     if ((self = [self init])) 
  27.     { 
  28.         self.volume = 1.0; 
  29.         self.player = [[AVAudioPlayer alloc] initWithContentsOfURL:URL error:NULL]; 
  30.     } 
  31.     return self; 
  32.   
  33. - (void)play 
  34.     [self.player play]; 
  35.   
  36. - (void)stop 
  37.     [self.player stop]; 
  38.   
  39. - (BOOL)isPlaying 
  40.     return self.player.playing; 
  41.   
  42. + (BOOL)needsDisplayForKey:(NSString *)key 
  43.     if ([@"volume" isEqualToString:key]) 
  44.     { 
  45.         return YES; 
  46.     } 
  47.     return [super needsDisplayForKey:key]; 
  48.   
  49. - (id<CAAction>)actionForKey:(NSString *)key 
  50.     if ([key isEqualToString:@"volume"]) 
  51.     { 
  52.         CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:key]; 
  53.         animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear]; 
  54.         animation.fromValue = @([[self presentationLayer] volume]); 
  55.         return animation; 
  56.     } 
  57.     return [super actionForKey:key]; 
  58.   
  59. - (void)display 
  60.     // 设置音量值为合适的音量插值 
  61.     self.player.volume = [self.presentationLayer volume]; 
  62.   
  63. @end 
我们可以通过使用一个简单的有着播放、停止、音量增大以及音量减小按钮的 View Controller 来做测试:
 
  1. @interface ViewController () 
  2.   
  3. @property (nonatomic, strong) AudioLayer *audioLayer; 
  4.   
  5. @end 
  6.   
  7.   
  8. @implementation ViewController 
  9.   
  10. - (void)viewDidLoad 
  11.     [super viewDidLoad]; 
  12.   
  13.     NSURL *musicURL = [NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:@"music" ofType:@"caf"]]; 
  14.     self.audioLayer = [[AudioLayer alloc] initWithAudioFileURL:musicURL]; 
  15.     [self.view.layer addSublayer:self.audioLayer]; 
  16.   
  17. - (IBAction)playPauseMusic:(UIButton *)sender 
  18.     if ([self.audioLayer isPlaying]) 
  19.     { 
  20.         [self.audioLayer stop]; 
  21.         [sender setTitle:@"Play Music" forState:UIControlStateNormal]; 
  22.     } 
  23.     else 
  24.     { 
  25.         [self.audioLayer play]; 
  26.         [sender setTitle:@"Pause Music" forState:UIControlStateNormal]; 
  27.     } 
  28.   
  29. - (IBAction)fadeIn 
  30.     self.audioLayer.volume = 1; 
  31.   
  32. - (IBAction)fadeOut 
  33.     self.audioLayer.volume = 0; 
  34.   
  35. @end 
注意:尽管我们的 Layer 没有可见的外观,但它依然需要被添加到屏幕上的视图层级里,以便动画能正常工作。
结论
CALayer 的动态属性提供了一个简单的机制来实现任何形式的动画——不仅仅只是内建的那些——而通过覆写 -display 方法,我们可以使用这些属性去控制任何我们想控制的东西,甚至是音量值这样的东西。
通过使用这些属性,我们不仅仅避免了重复造轮子,同时还确保了我们的自定义动画能与标准动画的时机和控制函数协同工作,以此就能非常容易地与其它动画属性同步。

这篇关于自定义 Layer 属性的动画的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1097098

相关文章

【前端学习】AntV G6-08 深入图形与图形分组、自定义节点、节点动画(下)

【课程链接】 AntV G6:深入图形与图形分组、自定义节点、节点动画(下)_哔哩哔哩_bilibili 本章十吾老师讲解了一个复杂的自定义节点中,应该怎样去计算和绘制图形,如何给一个图形制作不间断的动画,以及在鼠标事件之后产生动画。(有点难,需要好好理解) <!DOCTYPE html><html><head><meta charset="UTF-8"><title>06

滚雪球学Java(87):Java事务处理:JDBC的ACID属性与实战技巧!真有两下子!

咦咦咦,各位小可爱,我是你们的好伙伴——bug菌,今天又来给大家普及Java SE啦,别躲起来啊,听我讲干货还不快点赞,赞多了我就有动力讲得更嗨啦!所以呀,养成先点赞后阅读的好习惯,别被干货淹没了哦~ 🏆本文收录于「滚雪球学Java」专栏,专业攻坚指数级提升,助你一臂之力,带你早日登顶🚀,欢迎大家关注&&收藏!持续更新中,up!up!up!! 环境说明:Windows 10

Flutter 进阶:绘制加载动画

绘制加载动画:由小圆组成的大圆 1. 定义 LoadingScreen 类2. 实现 _LoadingScreenState 类3. 定义 LoadingPainter 类4. 总结 实现加载动画 我们需要定义两个类:LoadingScreen 和 LoadingPainter。LoadingScreen 负责控制动画的状态,而 LoadingPainter 则负责绘制动画。

自定义类型:结构体(续)

目录 一. 结构体的内存对齐 1.1 为什么存在内存对齐? 1.2 修改默认对齐数 二. 结构体传参 三. 结构体实现位段 一. 结构体的内存对齐 在前面的文章里我们已经讲过一部分的内存对齐的知识,并举出了两个例子,我们再举出两个例子继续说明: struct S3{double a;int b;char c;};int mian(){printf("%zd\n",s

Spring 源码解读:自定义实现Bean定义的注册与解析

引言 在Spring框架中,Bean的注册与解析是整个依赖注入流程的核心步骤。通过Bean定义,Spring容器知道如何创建、配置和管理每个Bean实例。本篇文章将通过实现一个简化版的Bean定义注册与解析机制,帮助你理解Spring框架背后的设计逻辑。我们还将对比Spring中的BeanDefinition和BeanDefinitionRegistry,以全面掌握Bean注册和解析的核心原理。

用Unity2D制作一个人物,实现移动、跳起、人物静止和动起来时的动画:中(人物移动、跳起、静止动作)

上回我们学到创建一个地形和一个人物,今天我们实现一下人物实现移动和跳起,依次点击,我们准备创建一个C#文件 创建好我们点击进去,就会跳转到我们的Vision Studio,然后输入这些代码 using UnityEngine;public class Move : MonoBehaviour // 定义一个名为Move的类,继承自MonoBehaviour{private Rigidbo

Oracle type (自定义类型的使用)

oracle - type   type定义: oracle中自定义数据类型 oracle中有基本的数据类型,如number,varchar2,date,numeric,float....但有时候我们需要特殊的格式, 如将name定义为(firstname,lastname)的形式,我们想把这个作为一个表的一列看待,这时候就要我们自己定义一个数据类型 格式 :create or repla

HTML5自定义属性对象Dataset

原文转自HTML5自定义属性对象Dataset简介 一、html5 自定义属性介绍 之前翻译的“你必须知道的28个HTML5特征、窍门和技术”一文中对于HTML5中自定义合法属性data-已经做过些介绍,就是在HTML5中我们可以使用data-前缀设置我们需要的自定义属性,来进行一些数据的存放,例如我们要在一个文字按钮上存放相对应的id: <a href="javascript:" d

一步一步将PlantUML类图导出为自定义格式的XMI文件

一步一步将PlantUML类图导出为自定义格式的XMI文件 说明: 首次发表日期:2024-09-08PlantUML官网: https://plantuml.com/zh/PlantUML命令行文档: https://plantuml.com/zh/command-line#6a26f548831e6a8cPlantUML XMI文档: https://plantuml.com/zh/xmi

Python中的属性装饰器:解锁更优雅的编程之道

引言 在Python的世界里,装饰器是一个强大的工具,它允许我们以一种非侵入性的方式修改函数或方法的行为。而当我们谈论“属性装饰器”时,则是在探讨如何使用装饰器来增强类中属性的功能。这不仅让我们的代码更加简洁、易读,同时也提供了强大的功能扩展能力。本文将带你深入了解属性装饰器的核心概念,并通过一系列实例展示其在不同场景下的应用,从基础到进阶,再到实际项目的实战经验分享,帮助你解锁Python编程