292 lines
11 KiB
Objective-C
292 lines
11 KiB
Objective-C
//
|
||
// KBKeyboardLegacyBuilder.m
|
||
// CustomKeyboard
|
||
//
|
||
|
||
#import "KBKeyboardLegacyBuilder.h"
|
||
#import "KBKey.h"
|
||
#import "KBKeyButton.h"
|
||
#import "KBBackspaceLongPressHandler.h"
|
||
#import "KBConfig.h"
|
||
#import <Masonry/Masonry.h>
|
||
|
||
static const CGFloat kKBSpecialKeySquareMultiplier = 1.2;
|
||
static const CGFloat kKBReturnWidthMultiplier = 2.4;
|
||
static const CGFloat kKBSpaceWidthMultiplier = 3.0;
|
||
static inline CGFloat KBLegacyRowHorizontalInset(void) {
|
||
return KBFit(6.0f);
|
||
}
|
||
|
||
@implementation KBKeyboardLegacyBuilder
|
||
|
||
- (void)buildRow:(UIView *)row
|
||
withKeys:(NSArray<KBKey *> *)keys
|
||
edgeSpacerMultiplier:(CGFloat)edgeSpacerMultiplier
|
||
shiftOn:(BOOL)shiftOn
|
||
backspaceHandler:(KBBackspaceLongPressHandler *)backspaceHandler
|
||
target:(id)target
|
||
action:(SEL)action {
|
||
if (!row || keys.count == 0) { return; }
|
||
|
||
// 第 4 行(底部控制行)使用单独的布局规则:
|
||
// 123/ABC、Emoji、Send 给定尺寸,Space 自动吃掉剩余宽度。
|
||
BOOL isBottomControlRow = [self kb_isBottomControlRowWithKeys:keys];
|
||
|
||
CGFloat spacing = 0; // 键与键之间的间距
|
||
UIView *previous = nil;
|
||
UIView *leftSpacer = nil;
|
||
UIView *rightSpacer = nil;
|
||
|
||
if (edgeSpacerMultiplier > 0.0) {
|
||
leftSpacer = [UIView new];
|
||
rightSpacer = [UIView new];
|
||
leftSpacer.backgroundColor = [UIColor clearColor];
|
||
rightSpacer.backgroundColor = [UIColor clearColor];
|
||
[row addSubview:leftSpacer];
|
||
[row addSubview:rightSpacer];
|
||
[leftSpacer mas_makeConstraints:^(MASConstraintMaker *make) {
|
||
make.left.equalTo(row.mas_left).offset(KBLegacyRowHorizontalInset());
|
||
make.centerY.equalTo(row);
|
||
make.height.mas_equalTo(1);
|
||
}];
|
||
[rightSpacer mas_makeConstraints:^(MASConstraintMaker *make) {
|
||
make.right.equalTo(row.mas_right).offset(-KBLegacyRowHorizontalInset());
|
||
make.centerY.equalTo(row);
|
||
make.height.mas_equalTo(1);
|
||
}];
|
||
}
|
||
|
||
for (NSInteger i = 0; i < keys.count; i++) {
|
||
KBKey *key = keys[i];
|
||
KBKeyButton *btn = [[KBKeyButton alloc] init];
|
||
btn.key = key;
|
||
[btn setTitle:key.title forState:UIControlStateNormal];
|
||
// 在设置完标题后,按当前皮肤应用图标与文字显隐
|
||
[btn applyThemeForCurrentKey];
|
||
if (target && action) {
|
||
[btn addTarget:target action:action forControlEvents:UIControlEventTouchDown];
|
||
}
|
||
[row addSubview:btn];
|
||
|
||
if (key.type == KBKeyTypeBackspace) {
|
||
[backspaceHandler bindDeleteButton:btn showClearLabel:YES];
|
||
}
|
||
|
||
// Shift 按钮选中态随大小写状态变化
|
||
if (key.type == KBKeyTypeShift) {
|
||
btn.selected = shiftOn;
|
||
}
|
||
|
||
[btn mas_makeConstraints:^(MASConstraintMaker *make) {
|
||
make.top.bottom.equalTo(row);
|
||
if (previous) {
|
||
make.left.equalTo(previous.mas_right).offset(spacing);
|
||
} else {
|
||
if (leftSpacer) {
|
||
make.left.equalTo(leftSpacer.mas_right).offset(spacing);
|
||
} else {
|
||
make.left.equalTo(row.mas_left).offset(KBLegacyRowHorizontalInset());
|
||
}
|
||
}
|
||
}];
|
||
|
||
// 字符键:等宽
|
||
if (key.type == KBKeyTypeCharacter) {
|
||
if (previous && [previous isKindOfClass:[KBKeyButton class]]) {
|
||
KBKeyButton *prevBtn = (KBKeyButton *)previous;
|
||
if (prevBtn.key.type == KBKeyTypeCharacter) {
|
||
[btn mas_makeConstraints:^(MASConstraintMaker *make) {
|
||
make.width.equalTo(previous);
|
||
}];
|
||
}
|
||
}
|
||
} else {
|
||
// special keys: 宽度在第二遍统一设置
|
||
}
|
||
|
||
previous = btn;
|
||
}
|
||
|
||
// 右侧使用内边距或右占位
|
||
if (previous) {
|
||
[previous mas_makeConstraints:^(MASConstraintMaker *make) {
|
||
if (rightSpacer) {
|
||
make.right.equalTo(rightSpacer.mas_left).offset(-spacing);
|
||
} else {
|
||
make.right.equalTo(row.mas_right).offset(-KBLegacyRowHorizontalInset());
|
||
}
|
||
}];
|
||
}
|
||
|
||
// 底部控制行:在第一轮已完成左右约束的前提下,仅给 123/ABC、Emoji、Send 指定宽度,
|
||
// Space 不加宽度约束,让其自动填充剩余空间。
|
||
if (isBottomControlRow) {
|
||
[self kb_applyBottomControlRowWidthInRow:row];
|
||
return;
|
||
}
|
||
|
||
// 第二遍:以首个字符键为基准,统一设置特殊键宽度倍数
|
||
KBKeyButton *firstChar = nil;
|
||
BOOL hasCharacterInRow = NO;
|
||
for (UIView *v in row.subviews) {
|
||
if (![v isKindOfClass:[KBKeyButton class]]) continue;
|
||
KBKeyButton *b = (KBKeyButton *)v;
|
||
if (b.key.type == KBKeyTypeCharacter) {
|
||
firstChar = b;
|
||
hasCharacterInRow = YES;
|
||
break;
|
||
}
|
||
}
|
||
// 若该行没有字符键(例如底部控制行之外的特殊行),则使用行内第一个按钮作为基准宽度
|
||
if (!firstChar) {
|
||
for (UIView *v in row.subviews) {
|
||
if ([v isKindOfClass:[KBKeyButton class]]) {
|
||
firstChar = (KBKeyButton *)v;
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
if (firstChar) {
|
||
// 如果该行本身没有字符键(如底部控制行),且基准按钮是 123/ABC/#+= 等,
|
||
// 也将其约束为 1:1,避免 123/ABC 不是正方形。
|
||
if (!hasCharacterInRow &&
|
||
(firstChar.key.type == KBKeyTypeModeChange ||
|
||
firstChar.key.type == KBKeyTypeSymbolsToggle ||
|
||
firstChar.key.type == KBKeyTypeCustom)) {
|
||
[firstChar mas_makeConstraints:^(MASConstraintMaker *make) {
|
||
make.width.equalTo(firstChar.mas_height).multipliedBy(kKBSpecialKeySquareMultiplier);
|
||
}];
|
||
}
|
||
|
||
for (UIView *v in row.subviews) {
|
||
if (![v isKindOfClass:[KBKeyButton class]]) continue;
|
||
KBKeyButton *b = (KBKeyButton *)v;
|
||
|
||
// 避免对基准按钮自身添加 self == self * k 的无效约束
|
||
if (b == firstChar) continue;
|
||
if (b.key.type == KBKeyTypeCharacter) continue;
|
||
|
||
BOOL isBottomModeKey = (b.key.type == KBKeyTypeModeChange) ||
|
||
(b.key.type == KBKeyTypeSymbolsToggle) ||
|
||
(b.key.type == KBKeyTypeCustom);
|
||
// 一类键强制近似正方形(宽 ~ 高)
|
||
if (b.key.type == KBKeyTypeShift ||
|
||
b.key.type == KBKeyTypeBackspace ||
|
||
isBottomModeKey) {
|
||
[b mas_makeConstraints:^(MASConstraintMaker *make) {
|
||
make.width.equalTo(b.mas_height).multipliedBy(kKBSpecialKeySquareMultiplier);
|
||
}];
|
||
continue;
|
||
}
|
||
|
||
CGFloat multiplier = 1.5;
|
||
|
||
// Space:宽度更大
|
||
if (b.key.type == KBKeyTypeSpace) {
|
||
multiplier = kKBSpaceWidthMultiplier;
|
||
}
|
||
// Send 按钮:宽度为基准键的 2.4 倍
|
||
else if (b.key.type == KBKeyTypeReturn) {
|
||
multiplier = kKBReturnWidthMultiplier;
|
||
}
|
||
// 其它特殊键(如 Globe)保持适度放大
|
||
else if (b.key.type == KBKeyTypeGlobe) {
|
||
multiplier = 1.5;
|
||
}
|
||
|
||
[b mas_makeConstraints:^(MASConstraintMaker *make) {
|
||
make.width.equalTo(firstChar).multipliedBy(multiplier);
|
||
}];
|
||
}
|
||
|
||
// 如果有左右占位,则把占位宽度设置为字符键宽度的一定倍数,以实现整体居中;
|
||
// 同时强约束左右占位宽度相等,避免在某些系统上由于布局冲突导致只压缩一侧,
|
||
// 出现“左侧有空隙,右侧无空隙”的情况。
|
||
if (leftSpacer && rightSpacer) {
|
||
// 1) 左右占位宽度必须相等
|
||
[leftSpacer mas_makeConstraints:^(MASConstraintMaker *make) {
|
||
make.width.equalTo(rightSpacer);
|
||
}];
|
||
// 2) 同时都接近字符键宽度的 edgeSpacerMultiplier 倍数
|
||
[rightSpacer mas_makeConstraints:^(MASConstraintMaker *make) {
|
||
make.width.equalTo(firstChar).multipliedBy(edgeSpacerMultiplier);
|
||
}];
|
||
}
|
||
}
|
||
}
|
||
|
||
#pragma mark - Row Helpers (Bottom Control Row)
|
||
|
||
// 判断是否为底部控制行:包含 Space + Return,且有 ModeChange/SymbolsToggle,
|
||
// 并且不再含字符键。
|
||
- (BOOL)kb_isBottomControlRowWithKeys:(NSArray<KBKey *> *)keys {
|
||
BOOL hasSpace = NO;
|
||
BOOL hasReturn = NO;
|
||
BOOL hasModeOrSymbols = NO;
|
||
BOOL hasCharacters = NO;
|
||
|
||
for (KBKey *k in keys) {
|
||
if (k.type == KBKeyTypeSpace) {
|
||
hasSpace = YES;
|
||
} else if (k.type == KBKeyTypeReturn) {
|
||
hasReturn = YES;
|
||
} else if (k.type == KBKeyTypeModeChange || k.type == KBKeyTypeSymbolsToggle) {
|
||
hasModeOrSymbols = YES;
|
||
} else if (k.type == KBKeyTypeCharacter) {
|
||
hasCharacters = YES;
|
||
}
|
||
}
|
||
return (hasSpace && hasReturn && hasModeOrSymbols && !hasCharacters);
|
||
}
|
||
|
||
- (void)kb_applyBottomControlRowWidthInRow:(UIView *)row {
|
||
if (!row) { return; }
|
||
|
||
KBKeyButton *modeBtn = nil;
|
||
KBKeyButton *spaceBtn = nil;
|
||
KBKeyButton *retBtn = nil;
|
||
NSMutableArray<KBKeyButton *> *customButtons = [NSMutableArray array];
|
||
|
||
for (UIView *v in row.subviews) {
|
||
if (![v isKindOfClass:[KBKeyButton class]]) continue;
|
||
KBKeyButton *b = (KBKeyButton *)v;
|
||
switch (b.key.type) {
|
||
case KBKeyTypeModeChange:
|
||
case KBKeyTypeSymbolsToggle:
|
||
modeBtn = b;
|
||
break;
|
||
case KBKeyTypeCustom:
|
||
[customButtons addObject:b];
|
||
break;
|
||
case KBKeyTypeSpace:
|
||
spaceBtn = b;
|
||
break;
|
||
case KBKeyTypeReturn:
|
||
retBtn = b;
|
||
break;
|
||
default:
|
||
break;
|
||
}
|
||
}
|
||
|
||
if (!modeBtn || customButtons.count == 0 || !spaceBtn || !retBtn) {
|
||
return;
|
||
}
|
||
|
||
[modeBtn mas_makeConstraints:^(MASConstraintMaker *make) {
|
||
make.width.equalTo(row.mas_height).multipliedBy(kKBSpecialKeySquareMultiplier);
|
||
}];
|
||
for (KBKeyButton *custom in customButtons) {
|
||
[custom mas_makeConstraints:^(MASConstraintMaker *make) {
|
||
make.width.equalTo(row.mas_height).multipliedBy(kKBSpecialKeySquareMultiplier);
|
||
}];
|
||
}
|
||
[retBtn mas_makeConstraints:^(MASConstraintMaker *make) {
|
||
make.width.equalTo(modeBtn.mas_width).multipliedBy(2.0);
|
||
}];
|
||
// Space 不设置宽度;通过此前已建立的左右约束自动占满剩余宽度。
|
||
(void)spaceBtn;
|
||
}
|
||
|
||
@end
|