自定义NSButton实现hover、highlighted效果

NSButton并没有UIButton可以设置state的接口,虽然系统提供了很多button的样式,但是自定义程度不够高,比如hover或highlighted的效果。没关系,既然没有,那就自定义好了。

其实,自定义NSButton非常简单,基本思路就是要在响应不同的鼠标事件时让button呈现不同的状态(通过字体或背景颜色区分,也可以是边框颜色或宽度)。

先上效果:
ButtonEffect.gif

左边的button是通过各种颜色的变化来表示hover、highlighted和selected状态的,右边的button是通过图片切换来表示,原理相同。

为了响应不同鼠标事件,可以定义四种状态,分别对应正常、鼠标移入、鼠标按下、鼠标释放。

typedef NS_ENUM(NSUInteger, SWSTAnswerButtonState) {
    SWSTAnswerButtonNormalState      = 0,
    SWSTAnswerButtonHoverState       = 1,
    SWSTAnswerButtonHighlightState   = 2,
    SWSTAnswerButtonSelectedState    = 3
};

NSButton继承了NSResponder的键鼠方法,我们要实现这四种状态需要重写mouseEntered:、mouseExited:、mouseDown:以及mouseUp:四个方法。

- (void)mouseEntered:(NSEvent *)theEvent {
    
    self.hover = YES;
    if (!self.selected) {
        self.buttonState = SWSTAnswerButtonHoverState;
    }
}

- (void)mouseExited:(NSEvent *)theEvent {
    
    self.hover = NO;
    if (!self.selected) {
        [self setButtonState:SWSTAnswerButtonNormalState];
    }
}

- (void)mouseDown:(NSEvent *)event {
    
    self.mouseUp = NO;
    if (self.enabled && !self.selected) {
        self.buttonState = SWSTAnswerButtonHighlightState;
    }
}

- (void)mouseUp:(NSEvent *)event {
    
    self.mouseUp = YES;
    if (self.enabled) {
        if (self.canSelected && self.hover) {
            self.selected = !self.selected;
            self.buttonState = self.selected ? SWSTAnswerButtonSelectedState : SWSTAnswerButtonNormalState;
        } else {
            if (!self.selected) {
                self.buttonState = SWSTAnswerButtonNormalState;
            }
        }
    }
}

通过mouseEntered:和mouseExited:来标记hover状态,而mouseDown:来标记highlighted状态,最后mouseUp:标记selected状态。

在buttonState改变的时候更新UI:

- (void)updateButtonApperaceWithState:(SWSTAnswerButtonState)state {
    
    CGFloat cornerRadius = 0.f;
    CGFloat borderWidth = 0.f;
    NSColor *borderColor = nil;
    NSColor *themeColor = nil;
    NSColor *backgroundColor = nil;
    switch (state) {
        case SWSTAnswerButtonNormalState: {
            cornerRadius = self.cornerNormalRadius;
            borderWidth = self.borderNormalWidth;
            borderColor = self.borderNormalColor;
            themeColor = self.normalColor;
            backgroundColor = self.backgroundNormalColor;
            if (self.normalImage != nil) {
                self.defaultImage = self.normalImage;
            }
            break;
        }
        case SWSTAnswerButtonHoverState: {
            cornerRadius = self.cornerHoverRadius;
            borderWidth = self.borderHoverWidth;
            borderColor = self.borderHoverColor;
            themeColor = self.hoverColor;
            backgroundColor = self.backgroundHoverColor;
            if (self.hoverImage != nil) {
                self.defaultImage = self.hoverImage;
            }
        }
            break;
        case SWSTAnswerButtonHighlightState: {
            cornerRadius = self.cornerHighlightRadius;
            borderWidth = self.borderHighlightWidth;
            borderColor = self.borderHighlightColor;
            themeColor = self.highlightColor;
            backgroundColor = self.backgroundHighlightColor;
            if (self.highlightImage != nil) {
                self.defaultImage = self.highlightImage;
            }
        }
            break;
        case SWSTAnswerButtonSelectedState: {
            cornerRadius = self.cornerSelectedRadius;
            borderWidth = self.borderSelectedWidth;
            borderColor = self.borderSelectedColor;
            themeColor = self.selectedColor;
            backgroundColor = self.backgroundSelectedColor;
            if (self.selectedImage != nil) {
                self.defaultImage = self.selectedImage;
            }
        }
            break;
    }
    if (self.defaultImage != nil) {
        self.image = self.defaultImage;
    }
    [self setFontColor:themeColor];
    
    if (self.hasBorder) {
        self.layer.cornerRadius = cornerRadius;
        self.layer.borderWidth = borderWidth;
        self.layer.borderColor = borderColor.CGColor;
    } else {
        self.layer.cornerRadius = 0.f;
        self.layer.borderWidth = 0.f;
        self.layer.borderColor = [NSColor clearColor].CGColor;
    }
    self.layer.backgroundColor = backgroundColor.CGColor;
}

这样就可以简单的实现你想要的几乎任何NSButton支持的样式,只要扩展对应的属性就好了。

不过,在实现样式的时候,发现了一个严重的问题。在重写mouseDown:和mouseUp:方法后,NSButton的action不执行了。这里调用super方法也不管用,看来系统会在这种情况下忽略action方法。

既然系统不执行,我在mouseUp:方法里手动执行一下:

- (void)mouseUp:(NSEvent *)event {
    
    self.mouseUp = YES;
    if (self.enabled) {
        if (self.canSelected && self.hover) {
            self.selected = !self.selected;
            self.buttonState = self.selected ? SWSTAnswerButtonSelectedState : SWSTAnswerButtonNormalState;
        } else {
            if (!self.selected) {
                self.buttonState = SWSTAnswerButtonNormalState;
            }
        }
        if (self.hover && self.enabled) {
            NSString *selString = NSStringFromSelector(self.action);
            if ([selString hasSuffix:@":"]) {
                [self.target performSelector:self.action withObject:self afterDelay:0.f];
            } else {
                [self.target performSelector:self.action withObject:nil afterDelay:0.f];
            }
        }
    }
}

修改后的mouseUp:方法在hover(确保鼠标在button的frame内)及enabled的状态下会通过performSelector:withObject:afterDelay:方法让button的target执行button的action。看起来有点奇怪,但确实可以这么干。

NSButton的初始化可以在代码里给所有属性赋值,但我相信你会觉得这些代码看着很恶心。其实完全不用放在代码里,这些初始化可以全丢给storyboard。

在storyboard里选中NSButton,选择右边第三个标签(Identity),看到有一行是User Defined Runtime Attributes。这里可以在运行时初始化一些自定义属性,支持基本类型外,居然还支持NSPoint、NSSize、NSRect、NScolor,甚至是NSImage(传图片名就好了)。

自定义NSButton实现hover、highlighted效果_第1张图片
UserDefined.png

最后,在updateTrackingAreas方法中,添加了自己的NSTrackingArea,以保证窗口不在最前时移动鼠标到button也可以实现hover效果。

- (void)updateTrackingAreas {
    
    [super updateTrackingAreas];
    if (self.trackingArea != nil) {
        [self removeTrackingArea:self.trackingArea];
        self.trackingArea = nil;
    }
    NSTrackingAreaOptions options = NSTrackingInVisibleRect|NSTrackingMouseEnteredAndExited|NSTrackingActiveAlways;
    self.trackingArea = [[NSTrackingArea alloc] initWithRect:CGRectZero options:options owner:self userInfo:nil];
    [self addTrackingArea:self.trackingArea];
}

完整代码见gitlab:https://gitlab.com/Maulyn/CustomButtonDemo

欢迎随时交流~

你可能感兴趣的:(自定义NSButton实现hover、highlighted效果)