Files
keyboard/CustomKeyboard/View/KBKeyButton.m
CodeST fd7b3a7f75 优化键盘弹出宽度
优化键盘按钮视觉效果
2025-11-21 19:40:57 +08:00

223 lines
8.7 KiB
Objective-C
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//
// KBKeyButton.m
// CustomKeyboard
//
#import "KBKeyButton.h"
#import "KBKey.h"
#import "KBSkinManager.h"
@interface KBKeyButton ()
// 内部缓存:便于从按钮查找到所属的 KBKeyboardView
@property (nonatomic, weak, readonly) UIView *kb_keyboardContainer;
@property (nonatomic, strong) UIImageView *normalImageView; /// 没有皮肤的时候展示
@property (nonatomic, strong) UIColor *baseBackgroundColor; /// 无按下状态下,由皮肤/主题决定的底色(由 normalImageView 展示)
@end
@implementation KBKeyButton
- (instancetype)initWithFrame:(CGRect)frame {
if (self = [super initWithFrame:frame]) {
[self addSubview:self.normalImageView];
self.normalImageView.translatesAutoresizingMaskIntoConstraints = NO;
[NSLayoutConstraint activateConstraints:@[
[self.normalImageView.topAnchor constraintEqualToAnchor:self.topAnchor],
[self.normalImageView.bottomAnchor constraintEqualToAnchor:self.bottomAnchor],
[self.normalImageView.leadingAnchor constraintEqualToAnchor:self.leadingAnchor constant:2],
[self.normalImageView.trailingAnchor constraintEqualToAnchor:self.trailingAnchor constant:-2],
]];
[self applyDefaultStyle];
}
return self;
}
- (void)applyDefaultStyle {
self.titleLabel.font = [UIFont systemFontOfSize:18 weight:UIFontWeightSemibold];
KBSkinTheme *t = [KBSkinManager shared].current;
[self setTitleColor:t.keyTextColor forState:UIControlStateNormal];
[self setTitleColor:t.keyTextColor forState:UIControlStateHighlighted];
// 颜色由 normalImageView 控制,按钮本身保持透明,避免叠加背景影响视觉
self.backgroundColor = [UIColor clearColor];
self.layer.cornerRadius = 6.0; // 圆角
self.layer.masksToBounds = NO;
self.layer.shadowColor = [UIColor colorWithWhite:0 alpha:0.1].CGColor; // 阴影效果
self.layer.shadowOpacity = 1.0;
self.layer.shadowOffset = CGSizeMake(0, 1);
self.layer.shadowRadius = 1.5;
// 初始状态下根据主题设置底色(给没有皮肤图的按键使用)
[self refreshStateAppearance];
// 懒创建图标视图,用于后续皮肤按键小图标展示
if (!self.iconView) {
UIImageView *iv = [[UIImageView alloc] initWithFrame:CGRectZero];
// 作为按键的整块皮肤背景,铺满整个按钮区域
iv.contentMode = UIViewContentModeScaleToFill;
iv.clipsToBounds = YES;
iv.translatesAutoresizingMaskIntoConstraints = NO;
[self addSubview:iv];
// 让皮肤图片撑满整个按钮
[NSLayoutConstraint activateConstraints:@[
[iv.topAnchor constraintEqualToAnchor:self.topAnchor],
[iv.bottomAnchor constraintEqualToAnchor:self.bottomAnchor],
[iv.leadingAnchor constraintEqualToAnchor:self.leadingAnchor constant:2],
[iv.trailingAnchor constraintEqualToAnchor:self.trailingAnchor constant:-2],
]];
self.iconView = iv;
// 文字保持居中;若需要显示文字,则覆盖在皮肤图片之上
self.titleEdgeInsets = UIEdgeInsetsZero;
[self bringSubviewToFront:self.titleLabel];
}
}
- (void)setKey:(KBKey *)key {
_key = key;
}
- (void)setHighlighted:(BOOL)highlighted {
[super setHighlighted:highlighted];
// 按下时整体做一个等比缩放动画,不改背景色和透明度。
// 这样无论是纯文字键还是整块皮肤图,都有统一的“按下”视觉反馈。
CGFloat scale = highlighted ? 0.9 : 1.0; // 可根据手感微调 0.9~0.95
// 没有皮肤图片时normalImageView 做灰度按下效果
BOOL hasIcon = (self.iconView.image != nil);
UIColor *normalBgColor = self.baseBackgroundColor ?: [UIColor whiteColor];
UIColor *highlightBgColor = [self kb_darkerColorForColor:normalBgColor];
[UIView animateWithDuration:0.08
delay:0
options:UIViewAnimationOptionBeginFromCurrentState | UIViewAnimationOptionCurveEaseOut
animations:^{
if (!hasIcon && !self.normalImageView.hidden) {
self.normalImageView.backgroundColor = highlighted ? highlightBgColor : normalBgColor;
}
self.transform = CGAffineTransformMakeScale(scale, scale);
}
completion:nil];
// 将“按下/抬起”事件转发给键盘视图,用于显示/隐藏顶部预览气泡。
UIView *container = self.kb_keyboardContainer;
if ([container respondsToSelector:@selector(showPreviewForButton:)] &&
[container respondsToSelector:@selector(hidePreview)]) {
if (highlighted) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
[container performSelector:@selector(showPreviewForButton:) withObject:self];
#pragma clang diagnostic pop
} else {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
[container performSelector:@selector(hidePreview)];
#pragma clang diagnostic pop
}
}
}
- (void)setSelected:(BOOL)selected {
[super setSelected:selected];
[self refreshStateAppearance];
}
- (void)refreshStateAppearance {
// 选中态用于 Shift/CapsLock 等特殊按键的高亮显示
KBSkinTheme *t = [KBSkinManager shared].current;
UIColor *base = nil;
if (self.isSelected) {
base = t.keyHighlightBackground ?: t.keyBackground;
} else {
base = t.keyBackground;
}
if (!base) {
base = [UIColor whiteColor];
}
self.baseBackgroundColor = base;
// 按键背景统一由 normalImageView 控制,按钮本身透明
self.backgroundColor = [UIColor clearColor];
// 有皮肤图时仅展示 icon不再显示普通背景色
if (self.iconView.image != nil || self.normalImageView.hidden) {
return;
}
self.normalImageView.backgroundColor = base;
}
- (void)applyThemeForCurrentKey {
// 根据皮肤映射加载图标(若有),支持大小写变体:
// - identifier: 逻辑按键标识(如 letter_q
// - caseVariant: 0/1/2 => 无变体/小写/大写
NSString *identifier = self.key.identifier;
NSInteger variant = (NSInteger)self.key.caseVariant;
UIImage *iconImg = [[KBSkinManager shared] iconImageForKeyIdentifier:identifier caseVariant:variant];
// 设置整块按键背景图(若有)
self.iconView.image = iconImg;
self.iconView.hidden = (iconImg == nil);
BOOL hasIcon = (iconImg != nil);
self.normalImageView.hidden = hasIcon;
if (hasIcon) {
// 有图标:仅显示图片,完全隐藏文字
[self setTitle:@"" forState:UIControlStateNormal];
[self setTitle:@"" forState:UIControlStateHighlighted];
[self setTitle:@"" forState:UIControlStateSelected];
self.titleLabel.hidden = YES;
self.normalImageView.backgroundColor = [UIColor clearColor];
} else {
// 无图标:按键标题正常显示(使用 key.title并根据 hidden_keys 决定要不要隐藏
[self setTitle:self.key.title forState:UIControlStateNormal];
BOOL hideTextBySkin = [[KBSkinManager shared] shouldHideKeyTextForIdentifier:identifier];
self.titleLabel.hidden = hideTextBySkin;
}
}
- (UIImageView *)normalImageView{
if (!_normalImageView) {
_normalImageView = [[UIImageView alloc] init];
// 初始给一个默认色,后续会由 refreshStateAppearance / 皮肤统一覆盖
_normalImageView.backgroundColor = [UIColor whiteColor];
_normalImageView.layer.cornerRadius = 6;
_normalImageView.layer.masksToBounds = true;
}
return _normalImageView;
}
/// 在当前底色的基础上略微变暗,作为按下时的“灰度”效果
- (UIColor *)kb_darkerColorForColor:(UIColor *)color {
if (!color) return [UIColor colorWithWhite:0.9 alpha:1.0];
CGFloat h = 0, s = 0, b = 0, a = 0;
if ([color getHue:&h saturation:&s brightness:&b alpha:&a]) {
return [UIColor colorWithHue:h saturation:s brightness:MAX(b * 0.9, 0.0) alpha:a];
}
CGFloat white = 0;
if ([color getWhite:&white alpha:&a]) {
return [UIColor colorWithWhite:MAX(white * 0.9, 0.0) alpha:a];
}
return color;
}
@end
@implementation KBKeyButton (KBKeyboardContainer)
- (UIView *)kb_keyboardContainer {
UIView *v = self.superview;
while (v) {
// KBKeyboardView 是当前容器类型,这里用类名字符串避免直接引用头文件死循环
if ([NSStringFromClass(v.class) isEqualToString:@"KBKeyboardView"]) {
return v;
}
v = v.superview;
}
return nil;
}
@end