如何在OS X状态栏中添加动画图标?

18

我想把一个图标放到Mac OS状态栏中,作为我的Cocoa应用程序的一部分。目前的做法是:

NSStatusBar *bar = [NSStatusBar systemStatusBar];

sbItem = [bar statusItemWithLength:NSVariableStatusItemLength];
[sbItem retain];

[sbItem setImage:[NSImage imageNamed:@"Taski_bar_icon.png"]];
[sbItem setHighlightMode:YES];
[sbItem setAction:@selector(stopStart)];

但如果我想要这个图标有动画效果(3-4帧),该如何实现呢?


2
好的,我想要给人留下应用正在处理数据的印象 - 这种情况很少发生,但了解这一点可能会有好处。或者我不应该这样做? - kender
8
没问题,这是一个有效的用法。时间机器甚至也这样做了。 - Rob Keniger
4
我认为Dropbox处理这个问题非常好。它很微妙,但它会告诉你事情正在更新。 - Chris Gregg
Time Machine曾经可以做到这一点。在macOS 10.15中不再发生。 - the Tin Man
3个回答

28
你需要反复调用 -setImage: 方法来更新 NSStatusItem 中的图像,每次传递一个不同的图像。最简单的方法是使用 NSTimer 和一个实例变量来存储动画的当前帧。
类似如下代码:
/*

assume these instance variables are defined:

NSInteger currentFrame;
NSTimer* animTimer;

*/

- (void)startAnimating
{
    currentFrame = 0;
    animTimer = [NSTimer scheduledTimerWithTimeInterval:1.0/30.0 target:self selector:@selector(updateImage:) userInfo:nil repeats:YES];
}

- (void)stopAnimating
{
    [animTimer invalidate];
}

- (void)updateImage:(NSTimer*)timer
{
    //get the image for the current frame
    NSImage* image = [NSImage imageNamed:[NSString stringWithFormat:@"image%d",currentFrame]];
    [statusBarItem setImage:image];
    currentFrame++;
    if (currentFrame % 4 == 0) {
        currentFrame = 0;
    }
}

是否也可以将NSView或NSImageView放置在状态栏项目上?如何实现? - zs2020
你必须通过[animTimer fire]告诉animTimer触发,还是它会自动触发? - Marshall Moutenot
1
+scheduledTimerWithTimeInterval:... 方法会为您将计时器添加到运行循环中(这就是 scheduled 的作用),因此您不需要自己启动计时器。 - Rob Keniger
1
  • (void)updateImage:(NSTimer*)timer 添加 currentFrame++;
- Alexander
谢谢,我已经添加了计数器递增功能。 - Rob Keniger

5

我重新编写了Rob的解决方案,以便可以重复使用:

我有9个框架,所有图像名称的最后一位是帧数,这样每次可以重置图像以动画化图标。

//IntervalAnimator.h

    #import <Foundation/Foundation.h>

@protocol IntervalAnimatorDelegate <NSObject>

- (void)onUpdate;

@end


@interface IntervalAnimator : NSObject
{
    NSInteger numberOfFrames;
    NSInteger currentFrame;
    __unsafe_unretained id <IntervalAnimatorDelegate> delegate;
}

@property(assign) id <IntervalAnimatorDelegate> delegate;
@property (nonatomic) NSInteger numberOfFrames;
@property (nonatomic) NSInteger currentFrame;

- (void)startAnimating;
- (void)stopAnimating;
@end

#import "IntervalAnimator.h"

@interface IntervalAnimator()
{
    NSTimer* animTimer;
}

@end

@implementation IntervalAnimator
@synthesize numberOfFrames;
@synthesize delegate;
@synthesize currentFrame;

- (void)startAnimating
{
    currentFrame = -1;
    animTimer = [NSTimer scheduledTimerWithTimeInterval:0.50 target:delegate selector:@selector(onUpdate) userInfo:nil repeats:YES];
}

- (void)stopAnimating
{
    [animTimer invalidate];
}

@end

如何使用:

  1. Conform to protocol in your StatusMenu class

    //create IntervalAnimator object
    
    animator = [[IntervalAnimator alloc] init];
    
    [animator setDelegate:self];
    
    [animator setNumberOfFrames:9];
    
    [animator startAnimating];
    
  2. Implement protocol method

    -(void)onUpdate    {
    
        [animator setCurrentFrame:([animator currentFrame] + 1)%[animator numberOfFrames]];
    
        NSImage* image = [NSImage imageNamed:[NSString stringWithFormat:@"icon%ld", (long)[animator currentFrame]]];
    
        [statusItem setImage:image];
    
    }
    

4

我最近在一个简单的项目中也做了类似的事情,所以我发布了我个人用 Swift 编写的版本:

class StatusBarIconAnimationUtils: NSObject {
    private var currentFrame = 0
    private var animTimer : Timer
    private var statusBarItem: NSStatusItem!
    private var imageNamePattern: String!
    private var imageCount : Int!

    init(statusBarItem: NSStatusItem!, imageNamePattern: String, imageCount: Int) {
        self.animTimer = Timer.init()
        self.statusBarItem = statusBarItem
        self.imageNamePattern = imageNamePattern
        self.imageCount = imageCount
        super.init()
    }

    func startAnimating() {
        stopAnimating()
        currentFrame = 0
        animTimer = Timer.scheduledTimer(timeInterval: 5.0 / 30.0, target: self, selector: #selector(self.updateImage(_:)), userInfo: nil, repeats: true)
    }

    func stopAnimating() {
        animTimer.invalidate()
        setImage(frameCount: 0)
    }

    @objc private func updateImage(_ timer: Timer?) {
        setImage(frameCount: currentFrame)
        currentFrame += 1
        if currentFrame % imageCount == 0 {
            currentFrame = 0
        }
    }

    private func setImage(frameCount: Int) {
        let imagePath = "\(imageNamePattern!)\(frameCount)"
        print("Switching image to: \(imagePath)")
        let image = NSImage(named: NSImage.Name(imagePath))
        image?.isTemplate = true // best for dark mode
        DispatchQueue.main.async {
            self.statusBarItem.button?.image = image
        }
    }
}

使用方法:

private let statusItem = 
    NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength)

// Assuming we have a set of images with names: statusAnimatedIcon0, ..., statusAnimatedIcon6
private lazy var iconAnimation = 
    StatusBarIconAnimationUtils.init(statusBarItem: statusItem, imageNamePattern: "statusAnimatedIcon", 
    imageCount: 7)

private func startAnimation() {
    iconAnimation.startAnimating()
}

private func stopAnimating() {
    iconAnimation.stopAnimating()
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接