Cocoa学习

我的Cocoa/Cocoa Touch学习笔记

山寨一下QQ for Mac样式的头像选择菜单

腾讯一直被大家吐槽山寨别人,这次我决定山寨一下腾讯,对象就是QQ for Mac的头像选择菜单。下面是原版的头像选择菜单:

这个菜单,一眼望过去,基本上就是一个有自定义View的菜单项加上一个普通菜单项。菜单附着在一个“头像”上。“头像”本身也是一个自定义按钮,这里不多深究;我们实现的时候,可以把菜单作为一个快捷菜单附着在一个按钮上。

菜单的自定义View,由一个Label(NSTextField),8个头像(行为类似NSButton)组成。另外,在鼠标悬停在头像上的时候还有一个Focus Ring。

问题分析到这里,我们就可以开始实现了。首先,我们先来创建两个菜单项:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#define IMAGES_COUNT 8

- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
    // 一些变量
    NSInteger sideMargin = 25., headMargin=10., \
    seperateMargin = 2., imageWidth = 44., \
    imagesPerLine = 4., menuTitleHeight = 20.,\
    menuTitleLeftMargin = 20., menuTitleWidth = 180.;

    // 创建菜单
    self.myMenuItem = [[NSMenuItem alloc] initWithTitle:@"My Menu" action:nil keyEquivalent:@""];
    self.myMenuItem.target = self;
    NSMenuItem *otherMenu = [[NSMenuItem alloc] initWithTitle:@"自定义头像..." action:@selector(doSomething:) keyEquivalent:@""];

    // 创建菜单项的自定义视图
    self.menuView = [[NSView alloc] initWithFrame:NSMakeRect(0, 0, \
                                                                sideMargin*2+seperateMargin*(imagesPerLine - 1)+imageWidth*imagesPerLine, \
                                                                imageWidth*(IMAGES_COUNT/imagesPerLine)+seperateMargin*(IMAGES_COUNT/imagesPerLine - 1)+headMargin*2+menuTitleHeight)];

}

接着,在-applicationDidFinishLaunching:方法中继续加入Lebel:

1
2
3
4
5
6
7
8
9
10
11
...

NSTextField *menuViewTitle = [[NSTextField alloc] initWithFrame:NSMakeRect(menuTitleLeftMargin, headMargin+(seperateMargin + imageWidth)*(IMAGES_COUNT/imagesPerLine), menuTitleWidth, menuTitleHeight)];
menuViewTitle.stringValue = @"系统头像";
menuViewTitle.textColor = [NSColor grayColor];
menuViewTitle.backgroundColor = [NSColor controlColor];
menuViewTitle.font = [NSFont systemFontOfSize:15];
[menuViewTitle setEditable:NO];
[menuViewTitle setBordered:NO];

[self.menuView addSubview:menuViewTitle];

然后是8个头像按钮,这里我尽量地模仿QQ的大小尺寸,使用了一些变量来指定尺寸,以方便修改。对于尺寸的大小,如果你有疑问,可以简单的在纸上画一下就清楚了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
// 头像按钮
for (int i = 0; i < IMAGES_COUNT; i++) {
    NSRect buttonRect = NSMakeRect(sideMargin + (imageWidth + seperateMargin) * (i % imagesPerLine), \
                                    headMargin + ((IMAGES_COUNT / imagesPerLine) - ((i / imagesPerLine) + 1)) * (imageWidth + seperateMargin), \
                                    imageWidth, imageWidth);
    NSButton *button = [[NSButton alloc] initWithFrame:buttonRect];
    [button setButtonType:NSSwitchButton];
    [button setImagePosition:NSImageOnly];
    button.image = [[NSImage imageNamed:[[NSString alloc] initWithFormat:@"avatar_small_%d", i + 1]] roundCornersImageCornerRadius:4];
    // 用于实现FocusRing(并不是很好的方法)
    button.alternateImage = [[NSImage imageNamed:[[NSString alloc] initWithFormat:@"avatar_small_%d_h", i + 1]] roundCornersImageCornerRadius:5];
    [button.cell setHighlightsBy:NSNoCellMask];
    button.tag = i;
    button.target = self;
    button.action = @selector(buttonClicked:);
    [button setBordered:NO];

    // 增加鼠标跟踪区域
    int options = NSTrackingMouseEnteredAndExited | NSTrackingActiveAlways | NSTrackingAssumeInside;
    NSTrackingArea *trackingArea;
    NSDictionary *userInfo = [[NSDictionary alloc] initWithObjectsAndKeys:button,@"button", nil];
    trackingArea = [[NSTrackingArea alloc] initWithRect:buttonRect
                                      options:options
                                        owner:self
                                     userInfo:userInfo];
    [self.menuView addTrackingArea:trackingArea];

    [self.menuView addSubview:button];
}

self.myMenuItem.view = self.menuView;

// 然后把菜单项加入菜单
[self.attachedMenu addItem:self.myMenuItem];
[self.attachedMenu addItem:otherMenu];

关于如何将菜单附着到按钮上,我过去曾写过文章讲述了,这里就不赘述了。另外,头像的图片有圆角显示,关于如何实现,我过去也曾写过介绍文章

问题差不多解决了,最后用两个鼠标事件回调函数来处理鼠标悬停时的Focus Ring效果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
    // Insert code here to initialize your application
    [[self window] setAcceptsMouseMovedEvents:YES]; // 注册接受鼠标事件

    ... (其他代码)
}

- (void)mouseEntered:(NSEvent *)theEvent {
    NSButton *currentButton = [(NSDictionary *)[theEvent userData] objectForKey:@"button"];
    [currentButton setState:NSOnState];
}

- (void)mouseExited:(NSEvent *)theEvent {
    NSButton *currentButton = [(NSDictionary *)[theEvent userData] objectForKey:@"button"];
    [currentButton setState:NSOffState];
}

山寨的效果效果如下:

关于Focus Ring的实现我要说明一下,我这里用的方法比较粗糙,QQ应该是用Quartz绘制的Focus ring,而我是用了一张图片来“伪造”了一个Focus ring。我这里选择使用NSSwitchButton类型的按钮是为了利用alternativeImage来实现Focus ring效果。之所以这么做,是因为我一时间没有想到比较好的绘制Focus ring的方法 – 基础不过关啊 – 所以,如果谁知道怎么做,劳烦您能不吝赐教。

示例代码我已经推送到Github上了,有兴趣的可以签出来看看,并提出宝贵意见。另外,我在示例代码中使用了QQ for Mac里面的头像图片,希望腾讯勿怪。

(全文完)

Comments